From a6da0563f5e078a17f9626d1c7842601518d84d4 Mon Sep 17 00:00:00 2001 From: singerla Date: Thu, 3 Nov 2022 22:10:32 +0100 Subject: [PATCH 1/8] feature(cache): write temporary files to disk; use system zip/unzip --- src/classes/template.ts | 14 +++++++++----- src/helper/cache-helper.ts | 29 +++++++++++++++++++++++++++++ src/helper/file-helper.ts | 8 ++++++-- 3 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 src/helper/cache-helper.ts diff --git a/src/classes/template.ts b/src/classes/template.ts index 21ae4be..c8b10df 100644 --- a/src/classes/template.ts +++ b/src/classes/template.ts @@ -11,6 +11,7 @@ import { XmlTemplateHelper } from '../helper/xml-template-helper'; import { SlideInfo } from '../types/xml-types'; import { XmlHelper } from '../helper/xml-helper'; import { vd } from '../helper/general-helper'; +import CacheHelper from '../helper/cache-helper'; export class Template implements ITemplate { /** @@ -52,23 +53,26 @@ export class Template implements ITemplate { creationIds: SlideInfo[]; existingSlides: number; - constructor(location: string) { + constructor(location: string, cache?: CacheHelper) { this.location = location; const file = FileHelper.readFile(location); - this.archive = FileHelper.extractFileContent(file as unknown as Buffer); + this.archive = FileHelper.extractFileContent( + file as unknown as Buffer, + cache?.setLocation(location), + ); } static import( location: string, name?: string, + cache?: CacheHelper, ): PresTemplate | RootPresTemplate { let newTemplate: PresTemplate | RootPresTemplate; - if (name) { - newTemplate = new Template(location) as PresTemplate; + newTemplate = new Template(location, cache) as PresTemplate; newTemplate.name = name; } else { - newTemplate = new Template(location) as RootPresTemplate; + newTemplate = new Template(location, cache) as RootPresTemplate; newTemplate.slides = []; newTemplate.counter = [ new CountHelper('slides', newTemplate), diff --git a/src/helper/cache-helper.ts b/src/helper/cache-helper.ts new file mode 100644 index 0000000..e01a4a9 --- /dev/null +++ b/src/helper/cache-helper.ts @@ -0,0 +1,29 @@ +import path from 'path'; +import fs from 'fs'; +import JSZip from 'jszip'; +import { vd } from './general-helper'; +const extract = require('extract-zip'); + +export default class CacheHelper { + dir: string; + currentLocation: string; + currentSubDir: string; + + constructor(dir: string) { + this.dir = dir; + } + + setLocation(location: string) { + this.currentLocation = location; + const baseName = path.basename(this.currentLocation); + this.currentSubDir = this.dir + '/' + baseName; + return this; + } + + store() { + extract(this.currentLocation, { dir: this.currentSubDir }).catch((err) => { + throw err; + }); + return this; + } +} diff --git a/src/helper/file-helper.ts b/src/helper/file-helper.ts index b56543d..7dfeea3 100644 --- a/src/helper/file-helper.ts +++ b/src/helper/file-helper.ts @@ -4,6 +4,8 @@ import JSZip, { InputType, OutputType } from 'jszip'; import { AutomizerSummary } from '../types/types'; import { IPresentationProps } from '../interfaces/ipresentation-props'; +import CacheHelper from './cache-helper'; +import { vd } from './general-helper'; export class FileHelper { static readFile(location: string): Promise { @@ -17,6 +19,7 @@ export class FileHelper { archive: JSZip, file: string, type?: OutputType, + cache?: CacheHelper, ): Promise { if (archive === undefined) { throw new Error('No files found, expected: ' + file); @@ -29,9 +32,10 @@ export class FileHelper { return archive.files[file].async(type || 'string'); } - static extractFileContent(file: Buffer): Promise { + static extractFileContent(file: Buffer, cache?: CacheHelper): Promise { + cache?.store(); const zip = new JSZip(); - return zip.loadAsync((file as unknown) as InputType); + return zip.loadAsync(file as unknown as InputType); } static getFileExtension(filename: string): string { From 08a71ea50e14270c3fc3df20f2eab5d332ab50ad Mon Sep 17 00:00:00 2001 From: singerla Date: Tue, 10 Jan 2023 16:01:47 +0100 Subject: [PATCH 2/8] feature(zip): add ContentTracker to track all used files (WIP) --- src/automizer.ts | 4 + src/classes/slide.ts | 10 +++ src/classes/template.ts | 2 + src/dev.ts | 110 +++++++++----------------- src/helper/file-helper.ts | 5 ++ src/interfaces/ipresentation-props.ts | 2 + src/interfaces/root-pres-template.ts | 2 + src/shapes/chart.ts | 9 ++- src/shapes/image.ts | 1 + 9 files changed, 73 insertions(+), 72 deletions(-) diff --git a/src/automizer.ts b/src/automizer.ts index 49bb47c..46b767e 100644 --- a/src/automizer.ts +++ b/src/automizer.ts @@ -17,6 +17,7 @@ import path from 'path'; import * as fs from 'fs'; import { XmlHelper } from './helper/xml-helper'; import ModifyPresentationHelper from './helper/modify-presentation-helper'; +import { ContentTracker } from './helper/content-tracker'; /** * Automizer @@ -42,6 +43,7 @@ export default class Automizer implements IPresentationProps { params: AutomizerParams; status: StatusTracker; + content: ContentTracker; modifyPresentation: ModifyXmlCallback[]; /** @@ -62,6 +64,8 @@ export default class Automizer implements IPresentationProps { this.timer = Date.now(); this.setStatusTracker(params?.statusTracker); + this.content = new ContentTracker(); + if (params.rootTemplate) { const location = this.getLocation(params.rootTemplate, 'template'); this.rootTemplate = Template.import(location) as RootPresTemplate; diff --git a/src/classes/slide.ts b/src/classes/slide.ts index 6626638..15399f4 100644 --- a/src/classes/slide.ts +++ b/src/classes/slide.ts @@ -25,6 +25,7 @@ import { Image } from '../shapes/image'; import { Chart } from '../shapes/chart'; import { GenericShape } from '../shapes/generic'; import { vd } from '../helper/general-helper'; +import { ContentTracker } from '../helper/content-tracker'; export class Slide implements ISlide { /** @@ -98,6 +99,7 @@ export class Slide implements ISlide { */ targetRelsPath: string; status: StatusTracker; + content: ContentTracker; /** * List of unsupported tags in slide xml * @internal @@ -126,6 +128,7 @@ export class Slide implements ISlide { this.importElements = []; this.status = params.presentation.status; + this.content = params.presentation.content; } /** @@ -176,6 +179,9 @@ export class Slide implements ISlide { this.status.info = 'Appending slide ' + this.targetNumber; + this.targetTemplate.content.used(this.targetPath); + this.targetTemplate.content.used(this.targetRelsPath); + await this.copySlideFiles(); await this.copyRelatedContent(); await this.addSlideToPresentation(); @@ -519,6 +525,7 @@ export class Slide implements ISlide { `ppt/slides/slide${this.sourceNumber}.xml`, this.targetArchive, `ppt/slides/slide${this.targetNumber}.xml`, + this.targetTemplate.content, ); await FileHelper.zipCopy( @@ -526,6 +533,7 @@ export class Slide implements ISlide { `ppt/slides/_rels/slide${this.sourceNumber}.xml.rels`, this.targetArchive, `ppt/slides/_rels/slide${this.targetNumber}.xml.rels`, + this.targetTemplate.content, ); } @@ -562,6 +570,7 @@ export class Slide implements ISlide { `ppt/notesSlides/notesSlide${sourceNotesNumber}.xml`, this.targetArchive, `ppt/notesSlides/notesSlide${this.targetNumber}.xml`, + this.targetTemplate.content, ); await FileHelper.zipCopy( @@ -569,6 +578,7 @@ export class Slide implements ISlide { `ppt/notesSlides/_rels/notesSlide${sourceNotesNumber}.xml.rels`, this.targetArchive, `ppt/notesSlides/_rels/notesSlide${this.targetNumber}.xml.rels`, + this.targetTemplate.content, ); } diff --git a/src/classes/template.ts b/src/classes/template.ts index 21ae4be..ad10178 100644 --- a/src/classes/template.ts +++ b/src/classes/template.ts @@ -11,6 +11,7 @@ import { XmlTemplateHelper } from '../helper/xml-template-helper'; import { SlideInfo } from '../types/xml-types'; import { XmlHelper } from '../helper/xml-helper'; import { vd } from '../helper/general-helper'; +import { ContentTracker } from '../helper/content-tracker'; export class Template implements ITemplate { /** @@ -75,6 +76,7 @@ export class Template implements ITemplate { new CountHelper('charts', newTemplate), new CountHelper('images', newTemplate), ]; + newTemplate.content = new ContentTracker(); } return newTemplate; diff --git a/src/dev.ts b/src/dev.ts index 3844ed9..ea2e451 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -1,4 +1,5 @@ import Automizer, { ChartData, modify } from './index'; +import { vd } from './helper/general-helper'; const automizer = new Automizer({ templateDir: `${__dirname}/../__tests__/pptx-templates`, @@ -7,83 +8,50 @@ const automizer = new Automizer({ }); const run = async () => { - // const pres = automizer - // .loadRoot(`RootTemplate.pptx`) - // .load(`SlideWithCharts.pptx`, 'charts'); - // - // const result = await pres - // .addSlide('charts', 2, (slide) => { - // slide.modifyElement('ColumnChart', [ - // modify.setChartData({ - // series: [ - // { label: 'series 1' }, - // { label: 'series 2' }, - // { label: 'series 3' }, - // ], - // categories: [ - // { label: 'cat 2-1', values: [50, 50, 20] }, - // { label: 'cat 2-2', values: [14, 50, 20] }, - // { label: 'cat 2-3', values: [15, 50, 20] }, - // { label: 'cat 2-4', values: [26, 50, 20] }, - // ], - // }), - // ]); - // }) - // .write(`modify-existing-chart.test.pptx`); - const pres = automizer .loadRoot(`RootTemplate.pptx`) - .load(`EmptySlide.pptx`, 'EmptySlide') - .load(`ChartWaterfall.pptx`, 'ChartWaterfall') - .load(`ChartBarsStacked.pptx`, 'ChartBarsStacked'); + .load(`RootTemplate.pptx`, 'root') + .load(`ChartBarsStacked.pptx`, 'charts'); - const result = await pres - .addSlide('EmptySlide', 1, (slide) => { - // slide.addElement('ChartBarsStacked', 1, 'BarsStacked', [ - // modify.setChartData({ - // series: [{ label: 'series 1' }], - // categories: [ - // { label: 'cat 2-1', values: [50] }, - // { label: 'cat 2-2', values: [14] }, - // { label: 'cat 2-3', values: [15] }, - // { label: 'cat 2-4', values: [26] }, - // ], - // }), - // ]); + const data = { + series: [ + { label: 'series s1' }, + { label: 'series s2' }, + { label: 'series s3' }, + { label: 'series s4' }, + ], + categories: [ + { label: 'item test r1', values: [10, 16, 12, 15] }, + { label: 'item test r2', values: [12, 18, 15, 15] }, + { label: 'item test r3', values: [14, 12, 11, 15] }, + { label: 'item test r4', values: [8, 11, 9, 15] }, + { label: 'item test r5', values: [6, 15, 7, 15] }, + { label: 'item test r6', values: [16, 16, 9, 3] }, + ], + }; - // slide.modifyElement('Waterfall 1', [ - // modify.setExtendedChartData({ - // series: [{ label: 'series 1' }], - // categories: [ - // { label: 'cat 2-1', values: [100] }, - // { label: 'cat 2-2', values: [20] }, - // { label: 'cat 2-3', values: [50] }, - // { label: 'cat 2-4', values: [-40] }, - // { label: 'cat 2-5', values: [130] }, - // { label: 'cat 2-6', values: [-60] }, - // { label: 'cat 2-7', values: [70] }, - // { label: 'cat 2-8', values: [140] }, - // ], - // }), - // ]); + const dataSmaller = { + series: [{ label: 'series s1' }, { label: 'series s2' }], + categories: [ + { label: 'item test r1', values: [10, 16] }, + { label: 'item test r2', values: [12, 18] }, + ], + }; - slide.addElement('ChartWaterfall', 1, 'Waterfall 1', [ - modify.setExtendedChartData({ - series: [{ label: 'series 1' }], - categories: [ - { label: 'cat 2-1', values: [100] }, - { label: 'cat 2-2', values: [20] }, - { label: 'cat 2-3', values: [50] }, - { label: 'cat 2-4', values: [-40] }, - { label: 'cat 2-5', values: [130] }, - { label: 'cat 2-6', values: [-60] }, - { label: 'cat 2-7', values: [70] }, - { label: 'cat 2-8', values: [140] }, - ], - }), - ]); + const result = await pres + .addSlide('charts', 1, (slide) => { + slide.modifyElement('BarsStacked', [modify.setChartData(data)]); + slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); }) - .write(`modify-existing-waterfall-chart.test.pptx`); + .addSlide('root', 1, (slide) => { + slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); + }) + .addSlide('charts', 1, (slide) => { + slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]); + }) + .write(`create-presentation-content-tracker.test.pptx`); + + vd(pres.rootTemplate.content); }; run().catch((error) => { diff --git a/src/helper/file-helper.ts b/src/helper/file-helper.ts index d1579a4..4b7817e 100644 --- a/src/helper/file-helper.ts +++ b/src/helper/file-helper.ts @@ -4,6 +4,7 @@ import JSZip, { InputType, OutputType } from 'jszip'; import { AutomizerSummary } from '../types/types'; import { IPresentationProps } from '../interfaces/ipresentation-props'; +import { ContentTracker } from './content-tracker'; export class FileHelper { static readFile(location: string): Promise { @@ -58,12 +59,16 @@ export class FileHelper { sourceFile: string, targetArchive: JSZip, targetFile?: string, + contentTracker?: ContentTracker, ): Promise { if (sourceArchive.files[sourceFile] === undefined) { throw new Error(`Zipped file not found: ${sourceFile}`); } const content = sourceArchive.files[sourceFile].async('nodebuffer'); + if (contentTracker) { + contentTracker.used(targetFile); + } return targetArchive.file(targetFile || sourceFile, content); } diff --git a/src/interfaces/ipresentation-props.ts b/src/interfaces/ipresentation-props.ts index ab9be7c..d4cb3a1 100644 --- a/src/interfaces/ipresentation-props.ts +++ b/src/interfaces/ipresentation-props.ts @@ -1,6 +1,7 @@ import { AutomizerParams, StatusTracker } from '../types/types'; import { PresTemplate } from './pres-template'; import { RootPresTemplate } from './root-pres-template'; +import { ContentTracker } from '../helper/content-tracker'; export interface IPresentationProps { rootTemplate: RootPresTemplate; @@ -8,5 +9,6 @@ export interface IPresentationProps { params: AutomizerParams; timer: number; status?: StatusTracker; + content?: ContentTracker; getTemplate(name: string): PresTemplate; } diff --git a/src/interfaces/root-pres-template.ts b/src/interfaces/root-pres-template.ts index 5b09c3f..c8d6468 100644 --- a/src/interfaces/root-pres-template.ts +++ b/src/interfaces/root-pres-template.ts @@ -1,6 +1,7 @@ import { ISlide } from './islide'; import { ICounter } from './icounter'; import { ITemplate } from './itemplate'; +import { ContentTracker } from '../helper/content-tracker'; export interface RootPresTemplate extends ITemplate { slides: ISlide[]; @@ -13,4 +14,5 @@ export interface RootPresTemplate extends ITemplate { appendSlide(slide: ISlide): Promise; countExistingSlides(): Promise; truncate(): Promise; + content?: ContentTracker; } diff --git a/src/shapes/chart.ts b/src/shapes/chart.ts index a18a3eb..df9e0a3 100644 --- a/src/shapes/chart.ts +++ b/src/shapes/chart.ts @@ -237,6 +237,7 @@ export class Chart extends Shape implements IChart { `ppt/charts/${this.subtype}${this.sourceNumber}.xml`, this.targetArchive, `ppt/charts/${this.subtype}${this.targetNumber}.xml`, + this.targetTemplate.content, ); await FileHelper.zipCopy( @@ -244,6 +245,7 @@ export class Chart extends Shape implements IChart { `ppt/charts/_rels/${this.subtype}${this.sourceNumber}.xml.rels`, this.targetArchive, `ppt/charts/_rels/${this.subtype}${this.targetNumber}.xml.rels`, + this.targetTemplate.content, ); } @@ -256,6 +258,7 @@ export class Chart extends Shape implements IChart { `ppt/charts/${this.styleRelationFiles.relTypeChartStyle[0]}`, this.targetArchive, `ppt/charts/style${this.targetNumber}.xml`, + this.targetTemplate.content, ); } @@ -265,6 +268,7 @@ export class Chart extends Shape implements IChart { `ppt/charts/${this.styleRelationFiles.relTypeChartColorStyle[0]}`, this.targetArchive, `ppt/charts/colors${this.targetNumber}.xml`, + this.targetTemplate.content, ); } @@ -278,6 +282,7 @@ export class Chart extends Shape implements IChart { imageInfo.source, this.targetArchive, imageInfo.target, + this.targetTemplate.content, ); } } @@ -392,11 +397,13 @@ export class Chart extends Shape implements IChart { } async copyWorksheetFile(): Promise { + const targetFile = `ppt/embeddings/${this.worksheetFilePrefix}${this.targetWorksheet}${this.wbExtension}`; await FileHelper.zipCopy( this.sourceArchive, `ppt/embeddings/${this.worksheetFilePrefix}${this.sourceWorksheet}${this.wbExtension}`, this.targetArchive, - `ppt/embeddings/${this.worksheetFilePrefix}${this.targetWorksheet}${this.wbExtension}`, + targetFile, + this.targetTemplate.content, ); } diff --git a/src/shapes/image.ts b/src/shapes/image.ts index 2076ee9..d636c98 100644 --- a/src/shapes/image.ts +++ b/src/shapes/image.ts @@ -114,6 +114,7 @@ export class Image extends Shape implements IImage { `ppt/media/${this.sourceFile}`, this.targetArchive, `ppt/media/${this.targetFile}`, + this.targetTemplate.content, ); } From 40901227abe38586e4098007587f2d50c114303f Mon Sep 17 00:00:00 2001 From: singerla Date: Wed, 11 Jan 2023 14:48:30 +0100 Subject: [PATCH 3/8] chore(zip): set compression level; track files & rels --- src/automizer.ts | 14 ++++++- src/classes/slide.ts | 7 ---- src/dev.ts | 4 +- src/helper/content-tracker.ts | 75 +++++++++++++++++++++++++++++++++++ src/helper/file-helper.ts | 56 ++++++++++++++++---------- src/helper/xml-helper.ts | 11 +++-- src/shapes/chart.ts | 6 --- src/shapes/image.ts | 1 - src/types/types.ts | 4 ++ 9 files changed, 138 insertions(+), 40 deletions(-) create mode 100644 src/helper/content-tracker.ts diff --git a/src/automizer.ts b/src/automizer.ts index 46b767e..59293a2 100644 --- a/src/automizer.ts +++ b/src/automizer.ts @@ -18,6 +18,7 @@ import * as fs from 'fs'; import { XmlHelper } from './helper/xml-helper'; import ModifyPresentationHelper from './helper/modify-presentation-helper'; import { ContentTracker } from './helper/content-tracker'; +import JSZip from 'jszip'; /** * Automizer @@ -281,7 +282,18 @@ export default class Automizer implements IPresentationProps { await this.applyModifyPresentationCallbacks(); const rootArchive = await this.rootTemplate.archive; - const content = await rootArchive.generateAsync({ type: 'nodebuffer' }); + const options: JSZip.JSZipGeneratorOptions<'nodebuffer'> = { + type: 'nodebuffer', + }; + + if (this.params.compression > 0) { + options.compression = 'DEFLATE'; + options.compressionOptions = { + level: this.params.compression, + }; + } + + const content = await rootArchive.generateAsync(options); return FileHelper.writeOutputFile( this.getLocation(location, 'output'), diff --git a/src/classes/slide.ts b/src/classes/slide.ts index 15399f4..981214d 100644 --- a/src/classes/slide.ts +++ b/src/classes/slide.ts @@ -179,9 +179,6 @@ export class Slide implements ISlide { this.status.info = 'Appending slide ' + this.targetNumber; - this.targetTemplate.content.used(this.targetPath); - this.targetTemplate.content.used(this.targetRelsPath); - await this.copySlideFiles(); await this.copyRelatedContent(); await this.addSlideToPresentation(); @@ -525,7 +522,6 @@ export class Slide implements ISlide { `ppt/slides/slide${this.sourceNumber}.xml`, this.targetArchive, `ppt/slides/slide${this.targetNumber}.xml`, - this.targetTemplate.content, ); await FileHelper.zipCopy( @@ -533,7 +529,6 @@ export class Slide implements ISlide { `ppt/slides/_rels/slide${this.sourceNumber}.xml.rels`, this.targetArchive, `ppt/slides/_rels/slide${this.targetNumber}.xml.rels`, - this.targetTemplate.content, ); } @@ -570,7 +565,6 @@ export class Slide implements ISlide { `ppt/notesSlides/notesSlide${sourceNotesNumber}.xml`, this.targetArchive, `ppt/notesSlides/notesSlide${this.targetNumber}.xml`, - this.targetTemplate.content, ); await FileHelper.zipCopy( @@ -578,7 +572,6 @@ export class Slide implements ISlide { `ppt/notesSlides/_rels/notesSlide${sourceNotesNumber}.xml.rels`, this.targetArchive, `ppt/notesSlides/_rels/notesSlide${this.targetNumber}.xml.rels`, - this.targetTemplate.content, ); } diff --git a/src/dev.ts b/src/dev.ts index ea2e451..8f704fd 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -1,5 +1,6 @@ import Automizer, { ChartData, modify } from './index'; import { vd } from './helper/general-helper'; +import { contentTracker } from './helper/content-tracker'; const automizer = new Automizer({ templateDir: `${__dirname}/../__tests__/pptx-templates`, @@ -51,7 +52,8 @@ const run = async () => { }) .write(`create-presentation-content-tracker.test.pptx`); - vd(pres.rootTemplate.content); + contentTracker.dump(); + // vd(pres.rootTemplate.content); }; run().catch((error) => { diff --git a/src/helper/content-tracker.ts b/src/helper/content-tracker.ts new file mode 100644 index 0000000..015573f --- /dev/null +++ b/src/helper/content-tracker.ts @@ -0,0 +1,75 @@ +import { SourceSlideIdentifier } from '../types/types'; +import { Slide } from '../classes/slide'; +import { FileHelper } from './file-helper'; + +export type TrackedPresentation = { + slides: TrackedSlide[]; +}; + +export type TrackedSlide = { + targetPath: string; + targetRelsPath: string; +}; + +export type FileInfo = { + base: string; + extension: string; + dir: string; +}; + +export class ContentTracker { + files: Record = { + 'ppt/slides': [], + 'ppt/slides/_rels': [], + 'ppt/charts': [], + 'ppt/charts/_rels': [], + 'ppt/embeddings': [], + }; + + relations: Record< + string, + { + base: string; + attribute: string; + value: string; + }[] + > = { + // '.': [], + 'ppt/slides/_rels': [], + 'ppt/charts/_rels': [], + 'ppt/_rels': [], + ppt: [], + }; + + constructor() {} + + trackFile(file): void { + const info = FileHelper.getFileInfo(file); + if (this.files[info.dir]) { + this.files[info.dir].push(info.base); + } else { + console.log(`Could not track file ${file}`); + } + } + + trackRelation(file: string, attribute: string, value: string): void { + const info = FileHelper.getFileInfo(file); + + if (this.relations[info.dir]) { + this.relations[info.dir].push({ + base: info.base, + attribute, + value, + }); + } else { + // console.log(`Could not track relation ${info.dir}`); + } + } + + dump() { + console.log(this.files); + console.log(this.relations); + } +} + +export const contentTracker = new ContentTracker(); diff --git a/src/helper/file-helper.ts b/src/helper/file-helper.ts index 4b7817e..bbdd41b 100644 --- a/src/helper/file-helper.ts +++ b/src/helper/file-helper.ts @@ -4,7 +4,7 @@ import JSZip, { InputType, OutputType } from 'jszip'; import { AutomizerSummary } from '../types/types'; import { IPresentationProps } from '../interfaces/ipresentation-props'; -import { ContentTracker } from './content-tracker'; +import { contentTracker, FileInfo } from './content-tracker'; export class FileHelper { static readFile(location: string): Promise { @@ -19,22 +19,15 @@ export class FileHelper { file: string, type?: OutputType, ): Promise { - if (archive === undefined) { - throw new Error('No files found, expected: ' + file); - } + FileHelper.check(archive, file); - if (archive.files[file] === undefined) { - console.trace(); - throw new Error('Archived file not found: ' + file); - } return archive.files[file].async(type || 'string'); } - static fileExistsInArchive(archive: JSZip, file: string): boolean { - if (archive === undefined || archive.files[file] === undefined) { - return false; - } - return true; + static removeFromArchive(archive: JSZip, file: string): JSZip { + FileHelper.check(archive, file); + + return archive.remove(file); } static extractFileContent(file: Buffer): Promise { @@ -46,6 +39,32 @@ export class FileHelper { return path.extname(filename).replace('.', ''); } + static getFileInfo(filename: string): FileInfo { + return { + base: path.basename(filename), + dir: path.dirname(filename), + extension: path.extname(filename).replace('.', ''), + }; + } + + static check(archive: JSZip, file: string) { + FileHelper.isArchive(archive); + FileHelper.fileExistsInArchive(archive, file); + } + + static isArchive(archive) { + if (archive === undefined || !archive.files) { + throw new Error('Archive is invalid or empty.'); + } + } + + static fileExistsInArchive(archive: JSZip, file: string): boolean { + if (archive === undefined || archive.files[file] === undefined) { + return false; + } + return true; + } + /** * Copies a file from one archive to another. The new file can have a different name to the origin. * @param {JSZip} sourceArchive - Source archive @@ -59,16 +78,13 @@ export class FileHelper { sourceFile: string, targetArchive: JSZip, targetFile?: string, - contentTracker?: ContentTracker, + tmp?: any, ): Promise { - if (sourceArchive.files[sourceFile] === undefined) { - throw new Error(`Zipped file not found: ${sourceFile}`); - } + FileHelper.check(sourceArchive, sourceFile); const content = sourceArchive.files[sourceFile].async('nodebuffer'); - if (contentTracker) { - contentTracker.used(targetFile); - } + contentTracker.trackFile(targetFile); + return targetArchive.file(targetFile || sourceFile, content); } diff --git a/src/helper/xml-helper.ts b/src/helper/xml-helper.ts index b1755a7..b873c23 100644 --- a/src/helper/xml-helper.ts +++ b/src/helper/xml-helper.ts @@ -14,6 +14,7 @@ import { XmlPrettyPrint } from './xml-pretty-print'; import { GetRelationshipsCallback, Target } from '../types/types'; import _ from 'lodash'; import { vd } from './general-helper'; +import { contentTracker, ContentTracker } from './content-tracker'; export class XmlHelper { static async modifyXmlInArchive( @@ -75,10 +76,11 @@ export class XmlHelper { const newElement = xml.createElement(element.tag); for (const attribute in element.attributes) { const value = element.attributes[attribute]; - newElement.setAttribute( - attribute, - typeof value === 'function' ? value(xml) : value, - ); + const setValue = typeof value === 'function' ? value(xml) : value; + + newElement.setAttribute(attribute, setValue); + + contentTracker.trackRelation(element.file, attribute, setValue); } if (element.assert) { @@ -267,6 +269,7 @@ export class XmlHelper { element.getAttribute(attributeName) === attributeValue ) { element.setAttribute(attributeName, replaceValue); + contentTracker.trackRelation(path, attributeName, replaceValue); } } return XmlHelper.writeXmlToArchive(archive, path, xml); diff --git a/src/shapes/chart.ts b/src/shapes/chart.ts index df9e0a3..b0b2a52 100644 --- a/src/shapes/chart.ts +++ b/src/shapes/chart.ts @@ -237,7 +237,6 @@ export class Chart extends Shape implements IChart { `ppt/charts/${this.subtype}${this.sourceNumber}.xml`, this.targetArchive, `ppt/charts/${this.subtype}${this.targetNumber}.xml`, - this.targetTemplate.content, ); await FileHelper.zipCopy( @@ -245,7 +244,6 @@ export class Chart extends Shape implements IChart { `ppt/charts/_rels/${this.subtype}${this.sourceNumber}.xml.rels`, this.targetArchive, `ppt/charts/_rels/${this.subtype}${this.targetNumber}.xml.rels`, - this.targetTemplate.content, ); } @@ -258,7 +256,6 @@ export class Chart extends Shape implements IChart { `ppt/charts/${this.styleRelationFiles.relTypeChartStyle[0]}`, this.targetArchive, `ppt/charts/style${this.targetNumber}.xml`, - this.targetTemplate.content, ); } @@ -268,7 +265,6 @@ export class Chart extends Shape implements IChart { `ppt/charts/${this.styleRelationFiles.relTypeChartColorStyle[0]}`, this.targetArchive, `ppt/charts/colors${this.targetNumber}.xml`, - this.targetTemplate.content, ); } @@ -282,7 +278,6 @@ export class Chart extends Shape implements IChart { imageInfo.source, this.targetArchive, imageInfo.target, - this.targetTemplate.content, ); } } @@ -403,7 +398,6 @@ export class Chart extends Shape implements IChart { `ppt/embeddings/${this.worksheetFilePrefix}${this.sourceWorksheet}${this.wbExtension}`, this.targetArchive, targetFile, - this.targetTemplate.content, ); } diff --git a/src/shapes/image.ts b/src/shapes/image.ts index d636c98..2076ee9 100644 --- a/src/shapes/image.ts +++ b/src/shapes/image.ts @@ -114,7 +114,6 @@ export class Image extends Shape implements IImage { `ppt/media/${this.sourceFile}`, this.targetArchive, `ppt/media/${this.targetFile}`, - this.targetTemplate.content, ); } diff --git a/src/types/types.ts b/src/types/types.ts index 3f51807..e9ad2bc 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -33,6 +33,10 @@ export type AutomizerParams = { * Buffer unzipped pptx on disk */ cacheDir?: string; + /** + * Zip compression level 0-9 + */ + compression?: number; rootTemplate?: string; presTemplates?: string[]; useCreationIds?: boolean; From 841250e00784e1db6c879c827832d53fa2be8d86 Mon Sep 17 00:00:00 2001 From: singerla Date: Wed, 11 Jan 2023 17:12:02 +0100 Subject: [PATCH 4/8] chore(zip): remove contents that became unneeded due to "removeExistingSlides: true" --- src/automizer.ts | 14 +++++---- src/dev.ts | 4 +-- src/helper/content-tracker.ts | 7 +---- src/helper/file-helper.ts | 1 + src/helper/modify-presentation-helper.ts | 38 ++++++++++++++++++++++++ src/helper/xml-helper.ts | 37 +++++++++++++++++++---- src/shapes/chart.ts | 31 ++++++++++++++++--- src/types/xml-types.ts | 5 ++-- 8 files changed, 112 insertions(+), 25 deletions(-) diff --git a/src/automizer.ts b/src/automizer.ts index 59293a2..23b51b9 100644 --- a/src/automizer.ts +++ b/src/automizer.ts @@ -17,7 +17,7 @@ import path from 'path'; import * as fs from 'fs'; import { XmlHelper } from './helper/xml-helper'; import ModifyPresentationHelper from './helper/modify-presentation-helper'; -import { ContentTracker } from './helper/content-tracker'; +import { contentTracker, ContentTracker } from './helper/content-tracker'; import JSZip from 'jszip'; /** @@ -45,7 +45,7 @@ export default class Automizer implements IPresentationProps { status: StatusTracker; content: ContentTracker; - modifyPresentation: ModifyXmlCallback[]; + modifyPresentation: ModifyXmlCallback[] = []; /** * Creates an instance of `pptx-automizer`. @@ -53,7 +53,6 @@ export default class Automizer implements IPresentationProps { */ constructor(params: AutomizerParams) { this.templates = []; - this.modifyPresentation = []; this.params = params; this.templateDir = params?.templateDir ? params.templateDir + '/' : ''; @@ -334,12 +333,17 @@ export default class Automizer implements IPresentationProps { * Apply some callbacks to restore archive/xml structure * and prevent corrupted pptx files. * - * TODO: Remove unused parts (slides, related items) from archive. * TODO: Use every imported image only once * TODO: Check for lost relations */ - normalizePresentation(): void { + async normalizePresentation(): Promise { this.modify(ModifyPresentationHelper.normalizeSlideIds); + + if (this.params.removeExistingSlides) { + this.modify(ModifyPresentationHelper.removeUnusedFiles); + } + + this.modify(ModifyPresentationHelper.removeUnusedContentTypes); } /** diff --git a/src/dev.ts b/src/dev.ts index 8f704fd..87d56cd 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -6,11 +6,12 @@ const automizer = new Automizer({ templateDir: `${__dirname}/../__tests__/pptx-templates`, outputDir: `${__dirname}/../__tests__/pptx-output`, removeExistingSlides: true, + compression: 9, }); const run = async () => { const pres = automizer - .loadRoot(`RootTemplate.pptx`) + .loadRoot(`ChartBarsStacked.pptx`) .load(`RootTemplate.pptx`, 'root') .load(`ChartBarsStacked.pptx`, 'charts'); @@ -52,7 +53,6 @@ const run = async () => { }) .write(`create-presentation-content-tracker.test.pptx`); - contentTracker.dump(); // vd(pres.rootTemplate.content); }; diff --git a/src/helper/content-tracker.ts b/src/helper/content-tracker.ts index 015573f..6de5d28 100644 --- a/src/helper/content-tracker.ts +++ b/src/helper/content-tracker.ts @@ -47,28 +47,23 @@ export class ContentTracker { const info = FileHelper.getFileInfo(file); if (this.files[info.dir]) { this.files[info.dir].push(info.base); - } else { - console.log(`Could not track file ${file}`); } } trackRelation(file: string, attribute: string, value: string): void { const info = FileHelper.getFileInfo(file); - if (this.relations[info.dir]) { this.relations[info.dir].push({ base: info.base, attribute, value, }); - } else { - // console.log(`Could not track relation ${info.dir}`); } } dump() { console.log(this.files); - console.log(this.relations); + // console.log(this.relations); } } diff --git a/src/helper/file-helper.ts b/src/helper/file-helper.ts index bbdd41b..1bc6f2d 100644 --- a/src/helper/file-helper.ts +++ b/src/helper/file-helper.ts @@ -5,6 +5,7 @@ import JSZip, { InputType, OutputType } from 'jszip'; import { AutomizerSummary } from '../types/types'; import { IPresentationProps } from '../interfaces/ipresentation-props'; import { contentTracker, FileInfo } from './content-tracker'; +import { vd } from './general-helper'; export class FileHelper { static readFile(location: string): Promise { diff --git a/src/helper/modify-presentation-helper.ts b/src/helper/modify-presentation-helper.ts index c82b636..4e4baa1 100644 --- a/src/helper/modify-presentation-helper.ts +++ b/src/helper/modify-presentation-helper.ts @@ -1,5 +1,8 @@ import { XmlHelper } from './xml-helper'; import { vd } from './general-helper'; +import { contentTracker } from './content-tracker'; +import { FileHelper } from './file-helper'; +import JSZip from 'jszip'; export default class ModifyPresentationHelper { /** @@ -31,4 +34,39 @@ export default class ModifyPresentationHelper { slide.setAttribute('id', String(firstId + i)); }); }; + + static async removeUnusedFiles( + xml: XMLDocument, + i: number, + archive: JSZip, + ): Promise { + for (const dir in contentTracker.files) { + const requiredFiles = contentTracker.files[dir]; + + archive.folder(dir).forEach((relativePath) => { + if ( + !relativePath.includes('/') && + !requiredFiles.includes(relativePath) + ) { + FileHelper.removeFromArchive(archive, dir + '/' + relativePath); + } + }); + } + } + + static async removeUnusedContentTypes( + xml: XMLDocument, + i: number, + archive: JSZip, + ): Promise { + await XmlHelper.removeIf({ + archive, + file: `[Content_Types].xml`, + tag: 'Override', + clause: (xml: XMLDocument, element: Element) => { + const filename = element.getAttribute('PartName').substring(1); + return FileHelper.fileExistsInArchive(archive, filename) ? false : true; + }, + }); + } } diff --git a/src/helper/xml-helper.ts b/src/helper/xml-helper.ts index b873c23..4df59aa 100644 --- a/src/helper/xml-helper.ts +++ b/src/helper/xml-helper.ts @@ -22,10 +22,12 @@ export class XmlHelper { file: string, callbacks: ModifyXmlCallback[], ): Promise { - const xml = await XmlHelper.getXmlFromArchive(await archive, file); + const jsZip = await archive; + const xml = await XmlHelper.getXmlFromArchive(jsZip, file); + let i = 0; for (const callback of callbacks) { - callback(xml); + callback(xml, i++, jsZip); } return await XmlHelper.writeXmlToArchive(await archive, file, xml); @@ -95,6 +97,27 @@ export class XmlHelper { return newElement as unknown as HelperElement; } + static async removeIf(element: HelperElement): Promise { + const xml = await XmlHelper.getXmlFromArchive( + element.archive, + element.file, + ); + + const collection = xml.getElementsByTagName(element.tag); + const toRemove = []; + XmlHelper.modifyCollection(collection, (item: Element, index) => { + if (element.clause(xml, item)) { + toRemove.push(item); + } + }); + + toRemove.forEach((item) => { + XmlHelper.remove(item); + }); + + await XmlHelper.writeXmlToArchive(element.archive, element.file, xml); + } + static async getNextRelId(rootArchive: JSZip, file: string): Promise { const presentationRelsXml = await XmlHelper.getXmlFromArchive( rootArchive, @@ -438,17 +461,19 @@ export class XmlHelper { ): void { if (from !== undefined) { for (let i = from; i < length; i++) { - const toRemove = collection[i]; - toRemove.parentNode.removeChild(toRemove); + XmlHelper.remove(collection[i]); } } else { for (let i = collection.length; i > length; i--) { - const toRemove = collection[i - 1]; - toRemove.parentNode.removeChild(toRemove); + XmlHelper.remove(collection[i - 1]); } } } + static remove(toRemove: Element): void { + toRemove.parentNode.removeChild(toRemove); + } + static sortCollection( collection: HTMLCollectionOf, order: number[], diff --git a/src/shapes/chart.ts b/src/shapes/chart.ts index b0b2a52..a71db4b 100644 --- a/src/shapes/chart.ts +++ b/src/shapes/chart.ts @@ -8,6 +8,7 @@ import { HelperElement, RelationshipAttribute } from '../types/xml-types'; import { ImportedElement, Target, Workbook } from '../types/types'; import { IChart } from '../interfaces/ichart'; import { RootPresTemplate } from '../interfaces/root-pres-template'; +import { contentTracker } from '../helper/content-tracker'; export class Chart extends Shape implements IChart { sourceWorksheet: number | string; @@ -349,21 +350,38 @@ export class Chart extends Shape implements IChart { const type = element.getAttribute('Type'); switch (type) { case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package': - element.setAttribute( + this.updateTargetWorksheetRelation( + targetRelFile, + element, 'Target', `${this.wbEmbeddingsPath}${this.worksheetFilePrefix}${this.targetWorksheet}${this.wbExtension}`, ); break; case this.relTypeChartColorStyle: - element.setAttribute('Target', `colors${this.targetNumber}.xml`); + this.updateTargetWorksheetRelation( + targetRelFile, + element, + 'Target', + `colors${this.targetNumber}.xml`, + ); break; case this.relTypeChartStyle: - element.setAttribute('Target', `style${this.targetNumber}.xml`); + this.updateTargetWorksheetRelation( + targetRelFile, + element, + 'Target', + `style${this.targetNumber}.xml`, + ); break; case this.relTypeChartImage: const target = element.getAttribute('Target'); const imageInfo = this.getTargetChartImageUri(target); - element.setAttribute('Target', imageInfo.rel); + this.updateTargetWorksheetRelation( + targetRelFile, + element, + target, + imageInfo, + ); break; } }); @@ -375,6 +393,11 @@ export class Chart extends Shape implements IChart { ); } + updateTargetWorksheetRelation(targetRelFile, element, attribute, value) { + element.setAttribute(attribute, value); + contentTracker.trackRelation(targetRelFile, attribute, value); + } + getTargetChartImageUri(origin: string): { source: string; target: string; diff --git a/src/types/xml-types.ts b/src/types/xml-types.ts index 07ee9fe..8addc10 100644 --- a/src/types/xml-types.ts +++ b/src/types/xml-types.ts @@ -24,8 +24,8 @@ export type OverrideAttribute = { export type HelperElement = { archive: JSZip; assert?: (xml: XMLDocument) => void; - clause?: (xml: XMLDocument) => boolean; - parent: (xml: XMLDocument) => Element; + clause?: (xml: XMLDocument, element?: Element) => boolean; + parent?: (xml: XMLDocument) => Element; file: string; tag: string; attributes?: @@ -66,4 +66,5 @@ export type ElementInfo = { export type ModifyXmlCallback = ( xml: XMLDocument | Element, index?: number, + archive?: JSZip, ) => void; From e3403bc3f94ba3dd4bb937de27192f0365a55ce4 Mon Sep 17 00:00:00 2001 From: singerla Date: Thu, 12 Jan 2023 17:25:49 +0100 Subject: [PATCH 5/8] chore(zip): remove unneeded images --- __tests__/modify-chart-scatter-images.test.ts | 33 ++ __tests__/pptx-templates/ChartScatter.pptx | Bin 65579 -> 109556 bytes .../RootTemplateWithImages.pptx | Bin 0 -> 48235 bytes src/automizer.ts | 2 + src/dev.ts | 91 ++--- src/helper/content-tracker.ts | 320 ++++++++++++++++-- src/helper/file-helper.ts | 15 +- src/helper/modify-presentation-helper.ts | 58 +++- src/helper/xml-helper.ts | 41 ++- src/shapes/chart.ts | 6 +- src/types/types.ts | 44 ++- 11 files changed, 517 insertions(+), 93 deletions(-) create mode 100644 __tests__/modify-chart-scatter-images.test.ts create mode 100644 __tests__/pptx-templates/RootTemplateWithImages.pptx diff --git a/__tests__/modify-chart-scatter-images.test.ts b/__tests__/modify-chart-scatter-images.test.ts new file mode 100644 index 0000000..6e287ad --- /dev/null +++ b/__tests__/modify-chart-scatter-images.test.ts @@ -0,0 +1,33 @@ +import Automizer, { modify } from '../src/index'; +import { ChartData } from '../dist'; + +test('create presentation, add and modify a scatter chart with embedded point images.', async () => { + const automizer = new Automizer({ + templateDir: `${__dirname}/pptx-templates`, + outputDir: `${__dirname}/pptx-output`, + }); + + const pres = automizer + .loadRoot(`RootTemplate.pptx`) + .load(`ChartScatter.pptx`, 'charts'); + + const dataScatter = ({ + series: [{ label: 'series s1' }], + categories: [ + { label: 'r1', values: [{ x: 10, y: 20 }] }, + { label: 'r2', values: [{ x: 21, y: 11 }] }, + { label: 'r3', values: [{ x: 22, y: 28 }] }, + { label: 'r4', values: [{ x: 13, y: 13 }] }, + ], + }); + + const result = await pres + .addSlide('charts', 3, (slide) => { + slide.modifyElement('ScatterPointImages', [ + modify.setChartScatter(dataScatter), + ]); + }) + .write(`modify-chart-scatter-images.test.pptx`); + + // expect(result.charts).toBe(2); +}); diff --git a/__tests__/pptx-templates/ChartScatter.pptx b/__tests__/pptx-templates/ChartScatter.pptx index 6e8e4b9457dbe704ad7354278e5bc9d3e99e3fe1..e9344ae7d5f58639de05a863789b6264733df882 100644 GIT binary patch delta 53891 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 _hz+~rSv&=+;=7Udl`8a8Vb~&-Hr?bnmM8Mm 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 literal 0 HcmV?d00001 diff --git a/src/automizer.ts b/src/automizer.ts index 23b51b9..c9521c7 100644 --- a/src/automizer.ts +++ b/src/automizer.ts @@ -281,6 +281,7 @@ export default class Automizer implements IPresentationProps { await this.applyModifyPresentationCallbacks(); const rootArchive = await this.rootTemplate.archive; + const options: JSZip.JSZipGeneratorOptions<'nodebuffer'> = { type: 'nodebuffer', }; @@ -343,6 +344,7 @@ export default class Automizer implements IPresentationProps { this.modify(ModifyPresentationHelper.removeUnusedFiles); } + this.modify(ModifyPresentationHelper.removedUnusedImages); this.modify(ModifyPresentationHelper.removeUnusedContentTypes); } diff --git a/src/dev.ts b/src/dev.ts index 87d56cd..772d39c 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -1,6 +1,7 @@ import Automizer, { ChartData, modify } from './index'; import { vd } from './helper/general-helper'; import { contentTracker } from './helper/content-tracker'; +import ModifyPresentationHelper from './helper/modify-presentation-helper'; const automizer = new Automizer({ templateDir: `${__dirname}/../__tests__/pptx-templates`, @@ -11,47 +12,61 @@ const automizer = new Automizer({ const run = async () => { const pres = automizer - .loadRoot(`ChartBarsStacked.pptx`) - .load(`RootTemplate.pptx`, 'root') - .load(`ChartBarsStacked.pptx`, 'charts'); - - const data = { - series: [ - { label: 'series s1' }, - { label: 'series s2' }, - { label: 'series s3' }, - { label: 'series s4' }, - ], - categories: [ - { label: 'item test r1', values: [10, 16, 12, 15] }, - { label: 'item test r2', values: [12, 18, 15, 15] }, - { label: 'item test r3', values: [14, 12, 11, 15] }, - { label: 'item test r4', values: [8, 11, 9, 15] }, - { label: 'item test r5', values: [6, 15, 7, 15] }, - { label: 'item test r6', values: [16, 16, 9, 3] }, - ], - }; - - const dataSmaller = { - series: [{ label: 'series s1' }, { label: 'series s2' }], - categories: [ - { label: 'item test r1', values: [10, 16] }, - { label: 'item test r2', values: [12, 18] }, - ], - }; + .loadRoot(`inputdemo.pptx`) + .load(`inputdemo.pptx`, 'ContentSlides'); const result = await pres - .addSlide('charts', 1, (slide) => { - slide.modifyElement('BarsStacked', [modify.setChartData(data)]); - slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); - }) - .addSlide('root', 1, (slide) => { - slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); + .addSlide('ContentSlides', 2, (slide) => { + // Could modify here }) - .addSlide('charts', 1, (slide) => { - slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]); - }) - .write(`create-presentation-content-tracker.test.pptx`); + .write(`outputdemo.pptx`); + + // const pres = automizer + // .loadRoot(`RootTemplateWithImages.pptx`) + // .load(`RootTemplate.pptx`, 'root') + // .load(`SlideWithImages.pptx`, 'images') + // .load(`ChartBarsStacked.pptx`, 'charts'); + // + // const data = { + // series: [ + // { label: 'series s1' }, + // { label: 'series s2' }, + // { label: 'series s3' }, + // { label: 'series s4' }, + // ], + // categories: [ + // { label: 'item test r1', values: [10, 16, 12, 15] }, + // { label: 'item test r2', values: [12, 18, 15, 15] }, + // { label: 'item test r3', values: [14, 12, 11, 15] }, + // { label: 'item test r4', values: [8, 11, 9, 15] }, + // { label: 'item test r5', values: [6, 15, 7, 15] }, + // { label: 'item test r6', values: [16, 16, 9, 3] }, + // ], + // }; + // + // const dataSmaller = { + // series: [{ label: 'series s1' }, { label: 'series s2' }], + // categories: [ + // { label: 'item test r1', values: [10, 16] }, + // { label: 'item test r2', values: [12, 18] }, + // ], + // }; + // + // const result = await pres + // .addSlide('charts', 1, (slide) => { + // slide.modifyElement('BarsStacked', [modify.setChartData(data)]); + // slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); + // }) + // .addSlide('images', 1) + // .addSlide('root', 1, (slide) => { + // slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); + // }) + // .addSlide('charts', 1, (slide) => { + // slide.addElement('images', 2, 'imageJPG'); + // slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]); + // }) + // .modify(ModifyPresentationHelper.checkIntegrity) + // .write(`create-presentation-content-tracker.test.pptx`); // vd(pres.rootTemplate.content); }; diff --git a/src/helper/content-tracker.ts b/src/helper/content-tracker.ts index 6de5d28..da3e45b 100644 --- a/src/helper/content-tracker.ts +++ b/src/helper/content-tracker.ts @@ -1,24 +1,23 @@ -import { SourceSlideIdentifier } from '../types/types'; +import { + Target, + TrackedFiles, + TrackedRelation, + TrackedRelationInfo, + TrackedRelations, + TrackedRelationTag, +} from '../types/types'; import { Slide } from '../classes/slide'; import { FileHelper } from './file-helper'; - -export type TrackedPresentation = { - slides: TrackedSlide[]; -}; - -export type TrackedSlide = { - targetPath: string; - targetRelsPath: string; -}; - -export type FileInfo = { - base: string; - extension: string; - dir: string; -}; +import { XmlHelper } from './xml-helper'; +import { vd } from './general-helper'; +import JSZip from 'jszip'; +import { RelationshipAttribute } from '../types/xml-types'; export class ContentTracker { - files: Record = { + archive: JSZip; + files: TrackedFiles = { + 'ppt/slideMasters': [], + 'ppt/slideMasters/_rels': [], 'ppt/slides': [], 'ppt/slides/_rels': [], 'ppt/charts': [], @@ -26,41 +25,304 @@ export class ContentTracker { 'ppt/embeddings': [], }; - relations: Record< - string, - { - base: string; - attribute: string; - value: string; - }[] - > = { + relations: TrackedRelations = { // '.': [], 'ppt/slides/_rels': [], + 'ppt/slideMasters/_rels': [], 'ppt/charts/_rels': [], 'ppt/_rels': [], ppt: [], }; + relationTags: TrackedRelationTag[] = [ + { + source: 'ppt/presentation.xml', + relationsKey: 'ppt/_rels/presentation.xml.rels', + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster', + tag: 'p:sldMasterId', + role: 'slideMaster', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', + tag: 'p:sldId', + role: 'slide', + }, + ], + }, + { + source: 'ppt/slides', + relationsKey: 'ppt/slides/_rels', + isDir: true, + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + tag: 'c:chart', + role: 'chart', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + tag: 'a:blip', + role: 'image', + attribute: 'r:embed', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + tag: 'asvg:svgBlip', + role: 'image', + attribute: 'r:embed', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', + role: 'slideLayout', + tag: null, + }, + ], + }, + { + source: 'ppt/charts', + relationsKey: 'ppt/charts/_rels', + isDir: true, + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package', + tag: 'c:externalData', + role: 'externalData', + }, + ], + }, + { + source: 'ppt/slideMasters', + relationsKey: 'ppt/slideMasters/_rels', + isDir: true, + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', + tag: 'p:sldLayoutId', + role: 'slideLayout', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + tag: 'a:blip', + role: 'image', + attribute: 'r:embed', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + tag: 'asvg:svgBlip', + role: 'image', + attribute: 'r:embed', + }, + ], + }, + ]; + constructor() {} - trackFile(file): void { + trackFile(file: string): void { const info = FileHelper.getFileInfo(file); if (this.files[info.dir]) { this.files[info.dir].push(info.base); } } - trackRelation(file: string, attribute: string, value: string): void { + trackRelation(file: string, attributes: RelationshipAttribute): void { const info = FileHelper.getFileInfo(file); if (this.relations[info.dir]) { this.relations[info.dir].push({ base: info.base, - attribute, - value, + attributes, }); } } + async analyzeContents(archive: JSZip) { + this.setArchive(archive); + + await this.analyzeRelationships(); + await this.trackSlideMasters(); + } + + setArchive(archive: JSZip) { + this.archive = archive; + } + + /** + * This will be replaced by future slideMaster handling. + */ + async trackSlideMasters() { + const slideMasters = this.getRelationTag( + 'ppt/presentation.xml', + ).getRelationTargets('slideMaster'); + + const slideMasterInfo = await this.getRelatedContents(slideMasters); + + slideMasterInfo.forEach((slideMasterInfo) => { + this.trackFile('ppt/' + slideMasterInfo.file); + this.trackFile('ppt/_rels/' + slideMasterInfo.file + '.rels'); + }); + + const relationTagInfo = this.getRelationTag('ppt/slideMasters'); + await this.analyzeRelationship(relationTagInfo); + } + + async getRelatedContents( + trackedRelations: TrackedRelation[], + ): Promise { + const relatedContents = []; + for (const trackedRelation of trackedRelations) { + for (const target of trackedRelation.targets) { + const trackedRelationInfo = await target.getRelatedContent(); + relatedContents.push(trackedRelationInfo); + } + } + return relatedContents; + } + + getRelationTag(source: string): TrackedRelationTag { + return contentTracker.relationTags.find( + (relationTag) => relationTag.source === source, + ); + } + + async analyzeRelationships(): Promise { + for (const relationTagInfo of this.relationTags) { + await this.analyzeRelationship(relationTagInfo); + } + } + + async analyzeRelationship( + relationTagInfo: TrackedRelationTag, + ): Promise { + relationTagInfo.getRelationTargets = (role: string) => { + return relationTagInfo.tags.filter((tag) => tag.role === role); + }; + + for (const relationTag of relationTagInfo.tags) { + if (relationTagInfo.isDir === true) { + const files = this.files[relationTagInfo.source]; + for (const file of files) { + await this.pushRelationTagTargets( + relationTagInfo.source + '/' + file, + file, + relationTag, + relationTagInfo, + ); + } + } else { + const pathInfo = FileHelper.getFileInfo(relationTagInfo.source); + await this.pushRelationTagTargets( + relationTagInfo.source, + pathInfo.base, + relationTag, + relationTagInfo, + ); + } + } + } + + async pushRelationTagTargets( + file: string, + filename: string, + relationTag: TrackedRelation, + relationTagInfo, + ): Promise { + const attribute = relationTag.attribute || 'r:id'; + + const addTargets = await XmlHelper.getRelationshipItems( + this.archive, + file, + relationTag.tag, + (element, rels) => { + rels.push({ + file, + filename, + rId: element.getAttribute(attribute), + type: relationTag.type, + }); + }, + ); + + this.addCreatedRelationsFunctions( + addTargets, + contentTracker.relations[relationTagInfo.relationsKey], + relationTagInfo, + ); + + const existingTargets = relationTag.targets || []; + relationTag.targets = [...existingTargets, ...addTargets]; + } + + addCreatedRelationsFunctions( + addTargets: Target[], + createdRelations: TrackedRelationInfo[], + relationTagInfo: TrackedRelationTag, + ): void { + addTargets.forEach((addTarget) => { + addTarget.getCreatedContent = this.getCreatedContent( + createdRelations, + addTarget, + ); + addTarget.getRelatedContent = this.getRelatedContent( + relationTagInfo, + addTarget, + ); + }); + } + + getCreatedContent( + createdRelations: TrackedRelationInfo[], + addTarget: Target, + ) { + return () => { + return createdRelations.find((relation) => { + return ( + relation.base === addTarget.filename + '.rels' && + relation.attributes?.Id === addTarget.rId + ); + }); + }; + } + + getRelatedContent(relationTagInfo: TrackedRelationTag, addTarget: Target) { + return async () => { + if (addTarget.relatedContent) return addTarget.relatedContent; + + const relationsFile = + relationTagInfo.isDir === true + ? relationTagInfo.relationsKey + '/' + addTarget.filename + '.rels' + : relationTagInfo.relationsKey; + + const relationTarget = await XmlHelper.getRelationshipItems( + this.archive, + relationsFile, + 'Relationship', + (element, rels) => { + const rId = element.getAttribute('Id'); + + if (rId === addTarget.rId) { + const target = element.getAttribute('Target'); + const fileInfo = FileHelper.getFileInfo(target); + + rels.push({ + file: target, + filename: fileInfo.base, + rId: rId, + type: element.getAttribute('Type'), + }); + } + }, + ); + + addTarget.relatedContent = relationTarget.find( + (relationTarget) => relationTarget.rId === addTarget.rId, + ); + + return addTarget.relatedContent; + }; + } + dump() { console.log(this.files); // console.log(this.relations); diff --git a/src/helper/file-helper.ts b/src/helper/file-helper.ts index 1bc6f2d..edfac78 100644 --- a/src/helper/file-helper.ts +++ b/src/helper/file-helper.ts @@ -2,9 +2,9 @@ import fs from 'fs'; import path from 'path'; import JSZip, { InputType, OutputType } from 'jszip'; -import { AutomizerSummary } from '../types/types'; +import { AutomizerSummary, FileInfo } from '../types/types'; import { IPresentationProps } from '../interfaces/ipresentation-props'; -import { contentTracker, FileInfo } from './content-tracker'; +import { contentTracker } from './content-tracker'; import { vd } from './general-helper'; export class FileHelper { @@ -20,7 +20,11 @@ export class FileHelper { file: string, type?: OutputType, ): Promise { - FileHelper.check(archive, file); + const exists = FileHelper.check(archive, file); + + if (!exists) { + throw new Error('File is not in archive: ' + file); + } return archive.files[file].async(type || 'string'); } @@ -44,13 +48,14 @@ export class FileHelper { return { base: path.basename(filename), dir: path.dirname(filename), + isDir: filename[filename.length - 1] === '/', extension: path.extname(filename).replace('.', ''), }; } - static check(archive: JSZip, file: string) { + static check(archive: JSZip, file: string): boolean { FileHelper.isArchive(archive); - FileHelper.fileExistsInArchive(archive, file); + return FileHelper.fileExistsInArchive(archive, file); } static isArchive(archive) { diff --git a/src/helper/modify-presentation-helper.ts b/src/helper/modify-presentation-helper.ts index 4e4baa1..fadd85b 100644 --- a/src/helper/modify-presentation-helper.ts +++ b/src/helper/modify-presentation-helper.ts @@ -1,8 +1,8 @@ import { XmlHelper } from './xml-helper'; -import { vd } from './general-helper'; -import { contentTracker } from './content-tracker'; +import { contentTracker as Tracker } from './content-tracker'; import { FileHelper } from './file-helper'; import JSZip from 'jszip'; +import { vd } from './general-helper'; export default class ModifyPresentationHelper { /** @@ -35,25 +35,37 @@ export default class ModifyPresentationHelper { }); }; + /** + * contentTracker.files includes all files that have been + * copied into the root template by automizer. We remove all other files. + */ static async removeUnusedFiles( xml: XMLDocument, i: number, archive: JSZip, ): Promise { - for (const dir in contentTracker.files) { - const requiredFiles = contentTracker.files[dir]; - - archive.folder(dir).forEach((relativePath) => { + const skipDirs = ['ppt/slideMasters', 'ppt/slideMasters/_rels']; + for (const dir in Tracker.files) { + if (skipDirs.includes(dir)) { + continue; + } + const requiredFiles = Tracker.files[dir]; + archive.folder(dir).forEach((relativePath, file) => { if ( !relativePath.includes('/') && !requiredFiles.includes(relativePath) ) { - FileHelper.removeFromArchive(archive, dir + '/' + relativePath); + FileHelper.removeFromArchive(archive, file.name); } }); } } + /** + * PPT won't complain about unused items in [Content_Types].xml, + * but we remove them anyway in case the file mentioned in PartName- + * attribute does not exist. + */ static async removeUnusedContentTypes( xml: XMLDocument, i: number, @@ -69,4 +81,36 @@ export default class ModifyPresentationHelper { }, }); } + + static async removedUnusedImages( + xml: XMLDocument, + i: number, + archive: JSZip, + ): Promise { + await Tracker.analyzeContents(archive); + const extensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'emf']; + const keepFiles = []; + const addFiles = async (section: string) => { + const imagesInfo = + Tracker.getRelationTag(section).getRelationTargets('image'); + const images = await Tracker.getRelatedContents(imagesInfo); + images.forEach((image) => keepFiles.push(image.filename)); + }; + + await addFiles('ppt/slides'); + await addFiles('ppt/slideMasters'); + + archive.folder('ppt/media').forEach((relativePath, file) => { + if (!relativePath.includes('/')) { + const info = FileHelper.getFileInfo(file.name); + if ( + extensions.includes(info.extension.toLowerCase()) && + !keepFiles.includes(info.base) + ) { + archive.remove(file.name); + FileHelper.removeFromArchive(archive, file.name); + } + } + }); + } } diff --git a/src/helper/xml-helper.ts b/src/helper/xml-helper.ts index 4df59aa..f5a50ad 100644 --- a/src/helper/xml-helper.ts +++ b/src/helper/xml-helper.ts @@ -27,7 +27,7 @@ export class XmlHelper { let i = 0; for (const callback of callbacks) { - callback(xml, i++, jsZip); + await callback(xml, i++, jsZip); } return await XmlHelper.writeXmlToArchive(await archive, file, xml); @@ -81,10 +81,13 @@ export class XmlHelper { const setValue = typeof value === 'function' ? value(xml) : value; newElement.setAttribute(attribute, setValue); - - contentTracker.trackRelation(element.file, attribute, setValue); } + contentTracker.trackRelation( + element.file, + element.attributes as RelationshipAttribute, + ); + if (element.assert) { element.assert(xml); } @@ -97,14 +100,14 @@ export class XmlHelper { return newElement as unknown as HelperElement; } - static async removeIf(element: HelperElement): Promise { + static async removeIf(element: HelperElement): Promise { const xml = await XmlHelper.getXmlFromArchive( element.archive, element.file, ); const collection = xml.getElementsByTagName(element.tag); - const toRemove = []; + const toRemove: Element[] = []; XmlHelper.modifyCollection(collection, (item: Element, index) => { if (element.clause(xml, item)) { toRemove.push(item); @@ -116,6 +119,8 @@ export class XmlHelper { }); await XmlHelper.writeXmlToArchive(element.archive, element.file, xml); + + return toRemove; } static async getNextRelId(rootArchive: JSZip, file: string): Promise { @@ -244,13 +249,22 @@ export class XmlHelper { archive: JSZip, path: string, cb: GetRelationshipsCallback, + ): Promise { + return this.getRelationshipItems(archive, path, 'Relationship', cb); + } + + static async getRelationshipItems( + archive: JSZip, + path: string, + tag: string, + cb: GetRelationshipsCallback, ): Promise { const xml = await XmlHelper.getXmlFromArchive(archive, path); - const relationships = xml.getElementsByTagName('Relationship'); + const relationshipItems = xml.getElementsByTagName(tag); const rels = []; - Object.keys(relationships) - .map((key) => relationships[key] as Element) + Object.keys(relationshipItems) + .map((key) => relationshipItems[key] as Element) .filter((element) => element.getAttribute !== undefined) .forEach((element) => cb(element, rels)); @@ -292,7 +306,14 @@ export class XmlHelper { element.getAttribute(attributeName) === attributeValue ) { element.setAttribute(attributeName, replaceValue); - contentTracker.trackRelation(path, attributeName, replaceValue); + } + + if (element.getAttribute !== undefined) { + contentTracker.trackRelation(path, { + Id: element.getAttribute('Id'), + Target: element.getAttribute('Target'), + Type: element.getAttribute('Type'), + }); } } return XmlHelper.writeXmlToArchive(archive, path, xml); @@ -420,6 +441,8 @@ export class XmlHelper { targetRelFile: string, attributes: RelationshipAttribute, ): HelperElement { + contentTracker.trackRelation(targetRelFile, attributes); + return { archive, file: targetRelFile, diff --git a/src/shapes/chart.ts b/src/shapes/chart.ts index a71db4b..e18e4bb 100644 --- a/src/shapes/chart.ts +++ b/src/shapes/chart.ts @@ -384,6 +384,11 @@ export class Chart extends Shape implements IChart { ); break; } + contentTracker.trackRelation(targetRelFile, { + Id: element.getAttribute('Id'), + Target: element.getAttribute('Target'), + Type: element.getAttribute('Type'), + }); }); await XmlHelper.writeXmlToArchive( @@ -395,7 +400,6 @@ export class Chart extends Shape implements IChart { updateTargetWorksheetRelation(targetRelFile, element, attribute, value) { element.setAttribute(attribute, value); - contentTracker.trackRelation(targetRelFile, attribute, value); } getTargetChartImageUri(origin: string): { diff --git a/src/types/types.ts b/src/types/types.ts index e9ad2bc..883e3fe 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,5 +1,6 @@ import JSZip from 'jszip'; import { ElementSubtype, ElementType } from '../enums/element-type'; +import { RelationshipAttribute } from './xml-types'; export type SourceSlideIdentifier = number | string; export type SlideModificationCallback = (document: Document) => void; @@ -71,14 +72,49 @@ export type AutomizerSummary = { }; export type Target = { file: string; + type: string; + filename: string; number?: number; rId?: string; prefix?: string; subtype?: ElementSubtype; - type: string; - filename: string; - filenameExt: string; - filenameBase: string; + filenameExt?: string; + filenameBase?: string; + getCreatedContent?: () => TrackedRelationInfo; + getRelatedContent?: () => Promise; + relatedContent?: Target; +}; +export type FileInfo = { + base: string; + extension: string; + dir: string; + isDir: boolean; +}; +export type TrackedFiles = Record; +export type TrackedRelationInfo = { + base: string; + attributes?: RelationshipAttribute; +}; +export type TrackedRelations = Record; +export type TrackedRelation = { + tag: string; + type?: string; + attribute?: string; + role?: + | 'image' + | 'slideMaster' + | 'slide' + | 'chart' + | 'externalData' + | 'slideLayout'; + targets?: Target[]; +}; +export type TrackedRelationTag = { + source: string; + relationsKey: string; + isDir?: boolean; + tags: TrackedRelation[]; + getRelationTargets?: (role: string) => TrackedRelation[]; }; export type ImportElement = { presName: string; From 4ee8a598072fb713f8b36064e86cb78d0347ec3c Mon Sep 17 00:00:00 2001 From: singerla Date: Fri, 13 Jan 2023 13:13:18 +0100 Subject: [PATCH 6/8] chore(zip): add cleanup-param; protect slideLayout-images from cleanup deletion --- .../RootTemplateWithImages.pptx | Bin 48235 -> 66078 bytes src/automizer.ts | 11 +- src/constants/constants.ts | 96 +++++++++++- src/dev.ts | 83 ++++------- src/helper/content-tracker.ts | 140 +++++------------- src/helper/file-helper.ts | 17 ++- src/helper/modify-presentation-helper.ts | 47 +++--- src/types/types.ts | 6 +- 8 files changed, 215 insertions(+), 185 deletions(-) diff --git a/__tests__/pptx-templates/RootTemplateWithImages.pptx b/__tests__/pptx-templates/RootTemplateWithImages.pptx index fd42b0e97852d933e5d612a2c37a8d6816a8a40b..1d9e0bb6abd24e95d11451abb0b22011b8ac395c 100644 GIT binary patch delta 28556 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 delta 10882 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 G-v0sJ&?!a$ diff --git a/src/automizer.ts b/src/automizer.ts index c9521c7..4887fb9 100644 --- a/src/automizer.ts +++ b/src/automizer.ts @@ -340,12 +340,13 @@ export default class Automizer implements IPresentationProps { async normalizePresentation(): Promise { this.modify(ModifyPresentationHelper.normalizeSlideIds); - if (this.params.removeExistingSlides) { - this.modify(ModifyPresentationHelper.removeUnusedFiles); + if (this.params.cleanup) { + if (this.params.removeExistingSlides) { + this.modify(ModifyPresentationHelper.removeUnusedFiles); + } + this.modify(ModifyPresentationHelper.removedUnusedImages); + this.modify(ModifyPresentationHelper.removeUnusedContentTypes); } - - this.modify(ModifyPresentationHelper.removedUnusedImages); - this.modify(ModifyPresentationHelper.removeUnusedContentTypes); } /** diff --git a/src/constants/constants.ts b/src/constants/constants.ts index c4da173..a5b3e28 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -1,4 +1,8 @@ -import { TargetByRelIdMapParam } from '../types/types'; +import { + TargetByRelIdMapParam, + TrackedRelation, + TrackedRelationTag, +} from '../types/types'; export const TargetByRelIdMap = { chart: { @@ -22,3 +26,93 @@ export const TargetByRelIdMap = { prefix: '../media/image', } as TargetByRelIdMapParam, }; + +export const imagesTrack: () => TrackedRelation[] = () => [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + tag: 'a:blip', + role: 'image', + attribute: 'r:embed', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + tag: 'asvg:svgBlip', + role: 'image', + attribute: 'r:embed', + }, +]; + +export const contentTrack: TrackedRelationTag[] = [ + { + source: 'ppt/presentation.xml', + relationsKey: 'ppt/_rels/presentation.xml.rels', + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster', + tag: 'p:sldMasterId', + role: 'slideMaster', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', + tag: 'p:sldId', + role: 'slide', + }, + ], + }, + { + source: 'ppt/slides', + relationsKey: 'ppt/slides/_rels', + isDir: true, + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + tag: 'c:chart', + role: 'chart', + }, + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', + role: 'slideLayout', + tag: null, + }, + ...imagesTrack(), + ], + }, + { + source: 'ppt/charts', + relationsKey: 'ppt/charts/_rels', + isDir: true, + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package', + tag: 'c:externalData', + role: 'externalData', + }, + ], + }, + { + source: 'ppt/slideMasters', + relationsKey: 'ppt/slideMasters/_rels', + isDir: true, + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', + tag: 'p:sldLayoutId', + role: 'slideLayout', + }, + ...imagesTrack(), + ], + }, + { + source: 'ppt/slideLayouts', + relationsKey: 'ppt/slideLayouts/_rels', + isDir: true, + tags: [ + { + type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster', + role: 'slideMaster', + tag: null, + }, + ...imagesTrack(), + ], + }, +]; diff --git a/src/dev.ts b/src/dev.ts index 772d39c..5b656cc 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -7,66 +7,43 @@ const automizer = new Automizer({ templateDir: `${__dirname}/../__tests__/pptx-templates`, outputDir: `${__dirname}/../__tests__/pptx-output`, removeExistingSlides: true, - compression: 9, + cleanup: true, + compression: 5, }); const run = async () => { const pres = automizer - .loadRoot(`inputdemo.pptx`) - .load(`inputdemo.pptx`, 'ContentSlides'); + .loadRoot(`RootTemplateWithImages.pptx`) + .load(`RootTemplate.pptx`, 'root') + .load(`SlideWithImages.pptx`, 'images') + .load(`ChartBarsStacked.pptx`, 'charts'); + + const dataSmaller = { + series: [{ label: 'series s1' }, { label: 'series s2' }], + categories: [ + { label: 'item test r1', values: [10, 16] }, + { label: 'item test r2', values: [12, 18] }, + ], + }; const result = await pres - .addSlide('ContentSlides', 2, (slide) => { - // Could modify here + .addSlide('charts', 1, (slide) => { + slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]); + slide.addElement('charts', 1, 'BarsStacked', [ + modify.setChartData(dataSmaller), + ]); }) - .write(`outputdemo.pptx`); - - // const pres = automizer - // .loadRoot(`RootTemplateWithImages.pptx`) - // .load(`RootTemplate.pptx`, 'root') - // .load(`SlideWithImages.pptx`, 'images') - // .load(`ChartBarsStacked.pptx`, 'charts'); - // - // const data = { - // series: [ - // { label: 'series s1' }, - // { label: 'series s2' }, - // { label: 'series s3' }, - // { label: 'series s4' }, - // ], - // categories: [ - // { label: 'item test r1', values: [10, 16, 12, 15] }, - // { label: 'item test r2', values: [12, 18, 15, 15] }, - // { label: 'item test r3', values: [14, 12, 11, 15] }, - // { label: 'item test r4', values: [8, 11, 9, 15] }, - // { label: 'item test r5', values: [6, 15, 7, 15] }, - // { label: 'item test r6', values: [16, 16, 9, 3] }, - // ], - // }; - // - // const dataSmaller = { - // series: [{ label: 'series s1' }, { label: 'series s2' }], - // categories: [ - // { label: 'item test r1', values: [10, 16] }, - // { label: 'item test r2', values: [12, 18] }, - // ], - // }; - // - // const result = await pres - // .addSlide('charts', 1, (slide) => { - // slide.modifyElement('BarsStacked', [modify.setChartData(data)]); - // slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); - // }) - // .addSlide('images', 1) - // .addSlide('root', 1, (slide) => { - // slide.addElement('charts', 1, 'BarsStacked', [modify.setChartData(data)]); - // }) - // .addSlide('charts', 1, (slide) => { - // slide.addElement('images', 2, 'imageJPG'); - // slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]); - // }) - // .modify(ModifyPresentationHelper.checkIntegrity) - // .write(`create-presentation-content-tracker.test.pptx`); + .addSlide('images', 1) + .addSlide('root', 1, (slide) => { + slide.addElement('charts', 1, 'BarsStacked', [ + modify.setChartData(dataSmaller), + ]); + }) + .addSlide('charts', 1, (slide) => { + slide.addElement('images', 2, 'imageJPG'); + slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]); + }) + .write(`create-presentation-content-tracker.test.pptx`); // vd(pres.rootTemplate.content); }; diff --git a/src/helper/content-tracker.ts b/src/helper/content-tracker.ts index da3e45b..9fb8328 100644 --- a/src/helper/content-tracker.ts +++ b/src/helper/content-tracker.ts @@ -12,16 +12,15 @@ import { XmlHelper } from './xml-helper'; import { vd } from './general-helper'; import JSZip from 'jszip'; import { RelationshipAttribute } from '../types/xml-types'; +import { contentTrack } from '../constants/constants'; export class ContentTracker { archive: JSZip; files: TrackedFiles = { 'ppt/slideMasters': [], - 'ppt/slideMasters/_rels': [], + 'ppt/slideLayouts': [], 'ppt/slides': [], - 'ppt/slides/_rels': [], 'ppt/charts': [], - 'ppt/charts/_rels': [], 'ppt/embeddings': [], }; @@ -29,94 +28,13 @@ export class ContentTracker { // '.': [], 'ppt/slides/_rels': [], 'ppt/slideMasters/_rels': [], + 'ppt/slideLayouts/_rels': [], 'ppt/charts/_rels': [], 'ppt/_rels': [], ppt: [], }; - relationTags: TrackedRelationTag[] = [ - { - source: 'ppt/presentation.xml', - relationsKey: 'ppt/_rels/presentation.xml.rels', - tags: [ - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster', - tag: 'p:sldMasterId', - role: 'slideMaster', - }, - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', - tag: 'p:sldId', - role: 'slide', - }, - ], - }, - { - source: 'ppt/slides', - relationsKey: 'ppt/slides/_rels', - isDir: true, - tags: [ - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', - tag: 'c:chart', - role: 'chart', - }, - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', - tag: 'a:blip', - role: 'image', - attribute: 'r:embed', - }, - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', - tag: 'asvg:svgBlip', - role: 'image', - attribute: 'r:embed', - }, - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', - role: 'slideLayout', - tag: null, - }, - ], - }, - { - source: 'ppt/charts', - relationsKey: 'ppt/charts/_rels', - isDir: true, - tags: [ - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package', - tag: 'c:externalData', - role: 'externalData', - }, - ], - }, - { - source: 'ppt/slideMasters', - relationsKey: 'ppt/slideMasters/_rels', - isDir: true, - tags: [ - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', - tag: 'p:sldLayoutId', - role: 'slideLayout', - }, - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', - tag: 'a:blip', - role: 'image', - attribute: 'r:embed', - }, - { - type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', - tag: 'asvg:svgBlip', - role: 'image', - attribute: 'r:embed', - }, - ], - }, - ]; + relationTags = contentTrack; constructor() {} @@ -142,6 +60,7 @@ export class ContentTracker { await this.analyzeRelationships(); await this.trackSlideMasters(); + await this.trackSlideLayouts(); } setArchive(archive: JSZip) { @@ -154,16 +73,28 @@ export class ContentTracker { async trackSlideMasters() { const slideMasters = this.getRelationTag( 'ppt/presentation.xml', - ).getRelationTargets('slideMaster'); + ).getTrackedRelations('slideMaster'); + + await this.addAndAnalyze(slideMasters, 'ppt/slideMasters'); + } - const slideMasterInfo = await this.getRelatedContents(slideMasters); + async trackSlideLayouts() { + const usedSlideLayouts = + this.getRelationTag('ppt/slideMasters').getTrackedRelations( + 'slideLayout', + ); - slideMasterInfo.forEach((slideMasterInfo) => { - this.trackFile('ppt/' + slideMasterInfo.file); - this.trackFile('ppt/_rels/' + slideMasterInfo.file + '.rels'); + await this.addAndAnalyze(usedSlideLayouts, 'ppt/slideLayouts'); + } + + async addAndAnalyze(trackedRelations: TrackedRelation[], section: string) { + const targets = await this.getRelatedContents(trackedRelations); + + targets.forEach((target) => { + this.trackFile(section + '/' + target.filename); }); - const relationTagInfo = this.getRelationTag('ppt/slideMasters'); + const relationTagInfo = this.getRelationTag(section); await this.analyzeRelationship(relationTagInfo); } @@ -195,13 +126,19 @@ export class ContentTracker { async analyzeRelationship( relationTagInfo: TrackedRelationTag, ): Promise { - relationTagInfo.getRelationTargets = (role: string) => { + relationTagInfo.getTrackedRelations = (role: string) => { return relationTagInfo.tags.filter((tag) => tag.role === role); }; for (const relationTag of relationTagInfo.tags) { + relationTag.targets = relationTag.targets || []; + if (relationTagInfo.isDir === true) { - const files = this.files[relationTagInfo.source]; + const files = this.files[relationTagInfo.source] || []; + if (!files.length) { + // vd('no files'); + // vd(relationTagInfo.source); + } for (const file of files) { await this.pushRelationTagTargets( relationTagInfo.source + '/' + file, @@ -250,8 +187,7 @@ export class ContentTracker { relationTagInfo, ); - const existingTargets = relationTag.targets || []; - relationTag.targets = [...existingTargets, ...addTargets]; + relationTag.targets = [...relationTag.targets, ...addTargets]; } addCreatedRelationsFunctions( @@ -323,9 +259,15 @@ export class ContentTracker { }; } - dump() { - console.log(this.files); - // console.log(this.relations); + async collect( + section: string, + role: string, + collection: string[], + ): Promise { + const trackedRelations = + this.getRelationTag(section).getTrackedRelations(role); + const images = await this.getRelatedContents(trackedRelations); + images.forEach((image) => collection.push(image.filename)); } } diff --git a/src/helper/file-helper.ts b/src/helper/file-helper.ts index edfac78..1c95ef3 100644 --- a/src/helper/file-helper.ts +++ b/src/helper/file-helper.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import JSZip, { InputType, OutputType } from 'jszip'; +import JSZip, { InputType, JSZipObject, OutputType } from 'jszip'; import { AutomizerSummary, FileInfo } from '../types/types'; import { IPresentationProps } from '../interfaces/ipresentation-props'; @@ -29,6 +29,21 @@ export class FileHelper { return archive.files[file].async(type || 'string'); } + static removeFromDirectory( + archive: JSZip, + dir: string, + cb: (file: JSZipObject, relativePath: string) => boolean, + ): string[] { + const removed = []; + archive.folder(dir).forEach((relativePath, file) => { + if (!relativePath.includes('/') && cb(file, relativePath)) { + FileHelper.removeFromArchive(archive, file.name); + removed.push(file.name); + } + }); + return removed; + } + static removeFromArchive(archive: JSZip, file: string): JSZip { FileHelper.check(archive, file); diff --git a/src/helper/modify-presentation-helper.ts b/src/helper/modify-presentation-helper.ts index fadd85b..cfe0b46 100644 --- a/src/helper/modify-presentation-helper.ts +++ b/src/helper/modify-presentation-helper.ts @@ -44,19 +44,20 @@ export default class ModifyPresentationHelper { i: number, archive: JSZip, ): Promise { - const skipDirs = ['ppt/slideMasters', 'ppt/slideMasters/_rels']; + // Need to skip some dirs until masters and layouts are handled properly + const skipDirs = [ + 'ppt/slideMasters', + 'ppt/slideMasters/_rels', + 'ppt/slideLayouts', + 'ppt/slideLayouts/_rels', + ]; for (const dir in Tracker.files) { if (skipDirs.includes(dir)) { continue; } const requiredFiles = Tracker.files[dir]; - archive.folder(dir).forEach((relativePath, file) => { - if ( - !relativePath.includes('/') && - !requiredFiles.includes(relativePath) - ) { - FileHelper.removeFromArchive(archive, file.name); - } + FileHelper.removeFromDirectory(archive, dir, (file, relativePath) => { + return !requiredFiles.includes(relativePath); }); } } @@ -88,29 +89,25 @@ export default class ModifyPresentationHelper { archive: JSZip, ): Promise { await Tracker.analyzeContents(archive); + const extensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'emf']; const keepFiles = []; - const addFiles = async (section: string) => { - const imagesInfo = - Tracker.getRelationTag(section).getRelationTargets('image'); - const images = await Tracker.getRelatedContents(imagesInfo); - images.forEach((image) => keepFiles.push(image.filename)); - }; - await addFiles('ppt/slides'); - await addFiles('ppt/slideMasters'); + await Tracker.collect('ppt/slides', 'image', keepFiles); + await Tracker.collect('ppt/slideMasters', 'image', keepFiles); + await Tracker.collect('ppt/slideLayouts', 'image', keepFiles); - archive.folder('ppt/media').forEach((relativePath, file) => { - if (!relativePath.includes('/')) { + const removed = FileHelper.removeFromDirectory( + archive, + 'ppt/media', + (file) => { const info = FileHelper.getFileInfo(file.name); - if ( + return ( extensions.includes(info.extension.toLowerCase()) && !keepFiles.includes(info.base) - ) { - archive.remove(file.name); - FileHelper.removeFromArchive(archive, file.name); - } - } - }); + ); + }, + ); + vd(removed); } } diff --git a/src/types/types.ts b/src/types/types.ts index 883e3fe..9da740d 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -46,6 +46,10 @@ export type AutomizerParams = { * before automation starts. */ removeExistingSlides?: boolean; + /** + * Eventually remove all unnecessary files from archive. + */ + cleanup?: boolean; /** * statusTracker will be triggered on each appended slide. * You can e.g. attach a custom callback to a progress bar. @@ -114,7 +118,7 @@ export type TrackedRelationTag = { relationsKey: string; isDir?: boolean; tags: TrackedRelation[]; - getRelationTargets?: (role: string) => TrackedRelation[]; + getTrackedRelations?: (role: string) => TrackedRelation[]; }; export type ImportElement = { presName: string; From 0398c5777eeed09e9297a8bbde13edb24897010e Mon Sep 17 00:00:00 2001 From: singerla Date: Fri, 13 Jan 2023 17:18:23 +0100 Subject: [PATCH 7/8] chore(zip): finalize cleanup --- src/classes/template.ts | 5 +---- src/helper/file-helper.ts | 6 +----- src/helper/modify-presentation-helper.ts | 20 +++++++------------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/classes/template.ts b/src/classes/template.ts index 20b7aba..6dc241a 100644 --- a/src/classes/template.ts +++ b/src/classes/template.ts @@ -57,10 +57,7 @@ export class Template implements ITemplate { constructor(location: string, cache?: CacheHelper) { this.location = location; const file = FileHelper.readFile(location); - this.archive = FileHelper.extractFileContent( - file as unknown as Buffer, - cache?.setLocation(location), - ); + this.archive = FileHelper.extractFileContent(file as unknown as Buffer); } static import( diff --git a/src/helper/file-helper.ts b/src/helper/file-helper.ts index 3033483..a79b4e0 100644 --- a/src/helper/file-helper.ts +++ b/src/helper/file-helper.ts @@ -5,8 +5,6 @@ import JSZip, { InputType, JSZipObject, OutputType } from 'jszip'; import { AutomizerSummary, FileInfo } from '../types/types'; import { IPresentationProps } from '../interfaces/ipresentation-props'; import { contentTracker } from './content-tracker'; -import CacheHelper from './cache-helper'; -import { vd } from './general-helper'; export class FileHelper { static readFile(location: string): Promise { @@ -20,7 +18,6 @@ export class FileHelper { archive: JSZip, file: string, type?: OutputType, - cache?: CacheHelper, ): Promise { const exists = FileHelper.check(archive, file); @@ -52,8 +49,7 @@ export class FileHelper { return archive.remove(file); } - static extractFileContent(file: Buffer, cache?: CacheHelper): Promise { - cache?.store(); + static extractFileContent(file: Buffer): Promise { const zip = new JSZip(); return zip.loadAsync(file as unknown as InputType); } diff --git a/src/helper/modify-presentation-helper.ts b/src/helper/modify-presentation-helper.ts index cfe0b46..240a00d 100644 --- a/src/helper/modify-presentation-helper.ts +++ b/src/helper/modify-presentation-helper.ts @@ -2,7 +2,6 @@ import { XmlHelper } from './xml-helper'; import { contentTracker as Tracker } from './content-tracker'; import { FileHelper } from './file-helper'; import JSZip from 'jszip'; -import { vd } from './general-helper'; export default class ModifyPresentationHelper { /** @@ -97,17 +96,12 @@ export default class ModifyPresentationHelper { await Tracker.collect('ppt/slideMasters', 'image', keepFiles); await Tracker.collect('ppt/slideLayouts', 'image', keepFiles); - const removed = FileHelper.removeFromDirectory( - archive, - 'ppt/media', - (file) => { - const info = FileHelper.getFileInfo(file.name); - return ( - extensions.includes(info.extension.toLowerCase()) && - !keepFiles.includes(info.base) - ); - }, - ); - vd(removed); + FileHelper.removeFromDirectory(archive, 'ppt/media', (file) => { + const info = FileHelper.getFileInfo(file.name); + return ( + extensions.includes(info.extension.toLowerCase()) && + !keepFiles.includes(info.base) + ); + }); } } From 3fd1b71474892b339728692613bec5bcc3c721af Mon Sep 17 00:00:00 2001 From: singerla Date: Fri, 13 Jan 2023 17:24:09 +0100 Subject: [PATCH 8/8] chore(docs): add cleanup / compression params to readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 6f65e47..ab8989b 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,14 @@ const automizer = new Automizer({ // truncate root presentation and start with zero slides removeExistingSlides: true, + // activate `cleanup` to eventually remove unused files: + cleanup: false, + + // Set a value from 0-9 to specify the zip-compression level. + // The lower the number, the faster your output file will be ready. + // Higher compression levels produce smaller files. + compression: 0, + // use a callback function to track pptx generation process. // statusTracker: myStatusTracker, })