Skip to content

Commit

Permalink
feat(master): remove master and related layouts; #85
Browse files Browse the repository at this point in the history
  • Loading branch information
singerla committed Dec 25, 2024
1 parent f4ec662 commit 7948705
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 43 deletions.
18 changes: 12 additions & 6 deletions __tests__/add-slide-master.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import Automizer from '../src/automizer';
import { ModifyTextHelper } from '../src';
import { ModifyTextHelper, XmlDocument, XmlHelper } from '../src';
import { XmlRelationshipHelper } from '../src/helper/xml-relationship-helper';
import { FileHelper } from '../src/helper/file-helper';
import { Target } from '../src/types/types';
import { vd } from '../src/helper/general-helper';

test('Append and modify slideMastes and use slideLayouts', async () => {
const automizer = new Automizer({
templateDir: `${__dirname}/pptx-templates`,
outputDir: `${__dirname}/pptx-output`,
verbosity: 2,
verbosity: 1,
});

const pres = await automizer
const pres = automizer
.loadRoot(`EmptyTemplate.pptx`)
.load(`SlideWithNotes.pptx`, 'notes')
.load('SlidesWithAdditionalMaster.pptx')
Expand Down Expand Up @@ -48,9 +52,11 @@ test('Append and modify slideMastes and use slideLayouts', async () => {
// You need to pass the index of the desired layout after all
// related layouts of all imported masters have been added to rootTemplate.
slide.useSlideLayout(26);
})
});

pres.removeMasters(1, 0);

.write(`add-slide-master.test.pptx`);
await pres.write(`add-slide-master.test.pptx`);

expect(pres.masters).toBe(3);
// expect(pres.masters).toBe(3);
});
32 changes: 26 additions & 6 deletions src/automizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,34 @@ import {
PresentationInfo,
SourceIdentifier,
StatusTracker,
Target,
} from './types/types';
import { IPresentationProps } from './interfaces/ipresentation-props';
import { PresTemplate } from './interfaces/pres-template';
import { RootPresTemplate } from './interfaces/root-pres-template';
import { Template } from './classes/template';
import { ModifyXmlCallback, TemplateInfo } from './types/xml-types';
import { GeneralHelper, log, Logger } from './helper/general-helper';
import {
ModifyXmlCallback,
TemplateInfo,
XmlDocument,
} from './types/xml-types';
import { GeneralHelper, log, Logger, vd } from './helper/general-helper';
import { Master } from './classes/master';
import path from 'path';
import * as fs from 'fs';
import { XmlHelper } from './helper/xml-helper';
import ModifyPresentationHelper from './helper/modify-presentation-helper';
import { contentTracker as Tracker, ContentTracker } from './helper/content-tracker';
import {
contentTracker as Tracker,
ContentTracker,
} from './helper/content-tracker';
import JSZip from 'jszip';
import { ISlide } from './interfaces/islide';
import { IMaster } from './interfaces/imaster';
import { ContentTypeExtension } from './enums/content-type-map';
import slugify from 'slugify';
import { XmlRelationshipHelper } from './helper/xml-relationship-helper';
import { FileHelper } from './helper/file-helper';

/**
* Automizer
Expand Down Expand Up @@ -389,6 +399,10 @@ export default class Automizer implements IPresentationProps {
return this;
}

public removeMasters(length: number, from: number) {
this.modify(ModifyPresentationHelper.removeSlideMaster(length, from, this));
}

/**
* Searches this.templates to find template by given name.
* @internal
Expand Down Expand Up @@ -463,10 +477,13 @@ export default class Automizer implements IPresentationProps {
}

async finalizePresentation() {
const currentMasterId =
await ModifyPresentationHelper.getFirstSlideMasterId(this);

await this.writeMasterSlides();
await this.writeSlides();
await this.writeMediaFiles();
await this.normalizePresentation();
await this.normalizePresentation(currentMasterId);
await this.applyModifyPresentationCallbacks();

// TODO: refactor content tracker, move this to root template
Expand Down Expand Up @@ -531,6 +548,7 @@ export default class Automizer implements IPresentationProps {
this.rootTemplate.archive,
`ppt/presentation.xml`,
this.modifyPresentation,
this,
);
}

Expand All @@ -541,9 +559,11 @@ export default class Automizer implements IPresentationProps {
* TODO: Use every imported image only once
* TODO: Check for lost relations
*/
async normalizePresentation(): Promise<void> {
async normalizePresentation(currentMasterId: number): Promise<void> {
this.modify(ModifyPresentationHelper.normalizeSlideIds);
this.modify(ModifyPresentationHelper.normalizeSlideMasterIds);
this.modify(
ModifyPresentationHelper.normalizeSlideMasterIds(currentMasterId),
);

if (this.params.cleanup) {
if (this.params.removeExistingSlides) {
Expand Down
2 changes: 1 addition & 1 deletion src/classes/master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class Master extends HasShapes implements IMaster {
await this.applyRelModifications();

const info = this.targetTemplate.automizer.params.showIntegrityInfo;
const assert = this.targetTemplate.automizer.params.showIntegrityInfo;
const assert = this.targetTemplate.automizer.params.assertRelatedContents;
await this.checkIntegrity(info, assert);

await this.cleanSlide(this.targetPath);
Expand Down
8 changes: 8 additions & 0 deletions src/helper/content-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export class ContentTracker {

relationTags = contentTrack();

deleted: TrackedFiles = {
'ppt/slideMasters': [],
};

constructor() {}

reset(): void {
Expand Down Expand Up @@ -290,6 +294,10 @@ export class ContentTracker {
const relations = this.relations[section];
return relations.filter((rel) => rel.attributes.Target === target);
}

deletedFile(section: string, file: any) {
this.deleted[section].push(file);
}
}

export const contentTracker = new ContentTracker();
163 changes: 134 additions & 29 deletions src/helper/modify-presentation-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { contentTracker as Tracker } from './content-tracker';
import { FileHelper } from './file-helper';
import IArchive from '../interfaces/iarchive';
import { XmlDocument, XmlElement } from '../types/xml-types';
import { vd } from './general-helper';
import { log, vd } from './general-helper';
import { XmlRelationshipHelper } from './xml-relationship-helper';
import { Target } from '../types/types';
import Automizer from '../automizer';

export default class ModifyPresentationHelper {
/**
Expand Down Expand Up @@ -50,37 +53,66 @@ export default class ModifyPresentationHelper {
* PowerPoint will complain on any p:sldLayoutId-id lower than its
* corresponding slideMaster-id. omg.
*/
static normalizeSlideMasterIds = async (
xml: XmlDocument,
i: number,
archive: IArchive,
) => {
const slides = ModifyPresentationHelper.getSlideMastersCollection(xml);
let currentId;
await XmlHelper.modifyCollectionAsync(
slides,
async (slide: XmlElement, i) => {
const masterId = i + 1;
if (i === 0) {
currentId = Number(slide.getAttribute('id'));
}

slide.setAttribute('id', String(currentId));
currentId++;

const slideMasterXml = await XmlHelper.getXmlFromArchive(
archive,
`ppt/slideMasters/slideMaster${masterId}.xml`,
);
static normalizeSlideMasterIds =
(currentId: number) =>
async (
presXml: XmlDocument,
i: number,
archive: IArchive,
pres: Automizer,
) => {
const slides =
ModifyPresentationHelper.getSlideMastersCollection(presXml);

const slideLayouts =
slideMasterXml.getElementsByTagName('p:sldLayoutId');
XmlHelper.modifyCollection(slideLayouts, (slideLayout: XmlElement) => {
slideLayout.setAttribute('id', String(currentId));
const deletedIds = pres.content.deleted['ppt/slideMasters'].map(
(deleted: any) => deleted.targetMasterId,
);

await XmlHelper.modifyCollectionAsync(
slides,
async (slide: XmlElement, i) => {
const masterId = i + 1;
if (deletedIds.includes(masterId)) {
return;
}

const slideMasterXml = await XmlHelper.getXmlFromArchive(
archive,
`ppt/slideMasters/slideMaster${masterId}.xml`,
);

slide.setAttribute('id', String(currentId));
currentId++;
});
},

const slideLayouts =
slideMasterXml.getElementsByTagName('p:sldLayoutId');

XmlHelper.modifyCollection(
slideLayouts,
(slideLayout: XmlElement) => {
slideLayout.setAttribute('id', String(currentId));
currentId++;
},
);
},
);

deletedIds.forEach((deletedId) => {
const existingMasters = presXml.getElementsByTagName('p:sldMasterId');
XmlHelper.sliceCollection(existingMasters, 1, deletedId - 1);
});

XmlHelper.dump(slides.item(0));
};

static getFirstSlideMasterId = async (pres: Automizer) => {
const presXml = await XmlHelper.getXmlFromArchive(
pres.rootTemplate.archive,
'ppt/presentation.xml',
);
const slides = ModifyPresentationHelper.getSlideMastersCollection(presXml);
const first = slides.item(0);
return Number(first.getAttribute('id'));
};

/**
Expand Down Expand Up @@ -154,4 +186,77 @@ export default class ModifyPresentationHelper {
);
});
}

static removeSlideMaster =
(length: number, from: number, pres: Automizer) =>
async (presXml: XmlDocument) => {
for (let i = 0; i < length; i += 1) {
const targetMasterId = from + i + 1;
const masterToRemove = `slideMaster${targetMasterId}.xml`;

const layouts = (await new XmlRelationshipHelper().initialize(
pres.rootTemplate.archive,
`${masterToRemove}.rels`,
`ppt/slideMasters/_rels`,
'../slideLayouts/slideLayout',
)) as Target[];

const layoutFiles = layouts.map(
(f) => f.file, // path.resolve(`ppt/presentation.xml/${f.file}`),
);

const removedLayouts = await FileHelper.removeFromDirectory(
pres.rootTemplate.archive,
'ppt/slideLayouts/',
(file) => {
return !layoutFiles.includes(file.relativePath);
},
);

const themes = (await new XmlRelationshipHelper().initialize(
pres.rootTemplate.archive,
`${masterToRemove}.rels`,
`ppt/slideMasters/_rels`,
'../theme/theme',
)) as Target[];

const themesFiles = themes.map(
(f) => f.file, // path.resolve(`ppt/presentation.xml/${f.file}`),
);

// const removedThemes = await FileHelper.removeFromDirectory(
// pres.rootTemplate.archive,
// 'ppt/theme/',
// (file) => {
// return !themesFiles.includes(file.relativePath);
// },
// );

const removedMasters = await FileHelper.removeFromDirectory(
pres.rootTemplate.archive,
'ppt/slideMasters/',
(file) => {
return file.relativePath === masterToRemove;
},
);

const removedMasterRels = await FileHelper.removeFromDirectory(
pres.rootTemplate.archive,
'ppt/slideMasters/_rels',
(file) => {
return file.relativePath === `${masterToRemove}.rels`;
},
);

log('removed Layouts:' + removedLayouts.length, 2);
// log('removed Themes:' + removedThemes.length, 2);
log('removed Masters:' + removedMasters.length, 2);
log('removed MasterRels:' + removedMasterRels.length, 2);

pres.content.deletedFile('ppt/slideMasters', {
masterToRemove,
targetMasterId: targetMasterId,
});
}
};
}
4 changes: 3 additions & 1 deletion src/helper/xml-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@ import {
ContentTypeExtension,
ContentTypeMap,
} from '../enums/content-type-map';
import Automizer from '../automizer';

export class XmlHelper {
static async modifyXmlInArchive(
archive: IArchive,
file: string,
callbacks: ModifyXmlCallback[],
parent?: Automizer,
): Promise<void> {
const fileProxy = await archive;
const xml = await XmlHelper.getXmlFromArchive(fileProxy, file);

let i = 0;
for (const callback of callbacks) {
await callback(xml, i++, fileProxy);
await callback(xml, i++, fileProxy, parent);
}

XmlHelper.writeXmlToArchive(await archive, file, xml);
Expand Down
2 changes: 2 additions & 0 deletions src/types/xml-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import IArchive from '../interfaces/iarchive';
import { TableData, TableInfo } from './table-types';
import Automizer from '../automizer';

export type DefaultAttribute = {
Extension: string;
Expand Down Expand Up @@ -94,4 +95,5 @@ export type ModifyXmlCallback = (
xml: XmlDocument | XmlElement,
index?: number,
archive?: IArchive,
automizer?: Automizer,
) => void;

0 comments on commit 7948705

Please sign in to comment.