Skip to content

Commit 3cc77bf

Browse files
committed
feature(templates): finds slides and elements by creation id
1 parent 167502a commit 3cc77bf

File tree

11 files changed

+257
-13
lines changed

11 files changed

+257
-13
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Automizer, { modify } from '../src/index';
2+
3+
test('create presentation, add and modify an existing table by creation id.', async () => {
4+
const automizer = new Automizer({
5+
templateDir: `${__dirname}/pptx-templates`,
6+
outputDir: `${__dirname}/pptx-output`,
7+
});
8+
9+
const data1 = {
10+
body: [
11+
{ label: 'item test r1', values: ['test1', 10, 16, 12, 11] },
12+
{ label: 'item test r2', values: ['test2', 12, 18, 15, 12] },
13+
{ label: 'item test r3', values: ['test3', 14, 12, 11, 14] },
14+
],
15+
};
16+
17+
const pres = automizer
18+
.loadRoot(`RootTemplate.pptx`)
19+
.load(`SlideWithTables.pptx`, 'tables');
20+
21+
await pres.setCreationIds()
22+
23+
const result = await pres
24+
.addSlide('tables', 1950777067, (slide) => {
25+
slide.modifyElement(
26+
'{EFC74B4C-D832-409B-9CF4-73C1EFF132D8}',
27+
[modify.setTableData(data1)]);
28+
})
29+
.write(`modify-existing-table.test.pptx`);
30+
31+
// expect(result.tables).toBe(2); // tbd
32+
});

src/automizer.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IPresentationProps } from './interfaces/ipresentation-props';
55
import { PresTemplate } from './interfaces/pres-template';
66
import { RootPresTemplate } from './interfaces/root-pres-template';
77
import { Template } from './classes/template';
8+
import { TemplateInfo } from './types/xml-types';
89

910
/**
1011
* Automizer
@@ -83,6 +84,24 @@ export default class Automizer implements IPresentationProps {
8384
return this;
8485
}
8586

87+
/**
88+
* Parses all loaded templates and collects creationIds for slides and
89+
* elements. This will make finding templates and elements independent
90+
* from slide number and element name.
91+
* @returns Promise<TemplateInfo[]>
92+
*/
93+
public async setCreationIds(): Promise<TemplateInfo[]> {
94+
const templateCreationId = []
95+
for(const template of this.templates) {
96+
const slideInfo = await template.setCreationIds()
97+
templateCreationId.push({
98+
name: template.name,
99+
slides: slideInfo
100+
})
101+
}
102+
return templateCreationId
103+
}
104+
86105
/**
87106
* Determines whether template is root or default template.
88107
* @param template
@@ -112,6 +131,12 @@ export default class Automizer implements IPresentationProps {
112131

113132
const template = this.getTemplate(name);
114133

134+
if(template.creationIds !== undefined) {
135+
slideNumber = template.creationIds
136+
.find(slideInfo => slideInfo.id === slideNumber)
137+
.number
138+
}
139+
115140
const newSlide = new Slide({
116141
presentation: this,
117142
template,

src/classes/shape.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class Shape {
3838
targetElement: XMLDocument;
3939

4040
callbacks: ShapeModificationCallback[];
41+
hasCreationId: boolean;
4142

4243
constructor(shape: ImportedElement) {
4344
this.mode = shape.mode;
@@ -47,6 +48,7 @@ export class Shape {
4748
this.sourceSlideNumber = shape.sourceSlideNumber;
4849
this.sourceSlideFile = `ppt/slides/slide${this.sourceSlideNumber}.xml`;
4950
this.sourceElement = shape.sourceElement;
51+
this.hasCreationId = shape.hasCreationId;
5052

5153
this.callbacks = GeneralHelper.arrayify(shape.callback);
5254

@@ -93,7 +95,12 @@ export class Shape {
9395
this.targetArchive,
9496
this.targetSlideFile,
9597
);
96-
const sourceElementOnTargetSlide = await XmlHelper.findByName(
98+
99+
const findMethod = (this.hasCreationId)
100+
? 'findByCreationId'
101+
: 'findByName'
102+
103+
const sourceElementOnTargetSlide = await XmlHelper[findMethod](
97104
targetSlideXml,
98105
this.name,
99106
);

src/classes/slide.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,12 @@ export class Slide implements ISlide {
285285
const template = this.root.getTemplate(importElement.presName);
286286
const sourcePath = `ppt/slides/slide${importElement.slideNumber}.xml`;
287287
const sourceArchive = await template.archive;
288-
const sourceElement = await XmlHelper.findByElementName(
288+
const hasCreationId = (template.creationIds !== undefined)
289+
const method = (hasCreationId)
290+
? 'findByElementCreationId'
291+
: 'findByElementName';
292+
293+
const sourceElement = await XmlHelper[method](
289294
sourceArchive,
290295
sourcePath,
291296
importElement.selector,
@@ -306,6 +311,7 @@ export class Slide implements ISlide {
306311
return {
307312
mode: importElement.mode,
308313
name: importElement.selector,
314+
hasCreationId: hasCreationId,
309315
sourceArchive,
310316
sourceSlideNumber: importElement.slideNumber,
311317
sourceElement,

src/classes/template.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { ISlide } from '../interfaces/islide';
77
import { PresTemplate } from '../interfaces/pres-template';
88
import { RootPresTemplate } from '../interfaces/root-pres-template';
99
import { ITemplate } from '../interfaces/itemplate';
10+
import { XmlTemplateHelper } from '../helper/xml-template-helper';
11+
import { SlideInfo } from '../types/xml-types';
1012

1113
export class Template implements ITemplate {
1214
/**
@@ -45,6 +47,8 @@ export class Template implements ITemplate {
4547
*/
4648
counter: ICounter[];
4749

50+
creationIds: SlideInfo[];
51+
4852
constructor(location: string) {
4953
this.location = location;
5054
const file = FileHelper.readFile(location);
@@ -73,6 +77,15 @@ export class Template implements ITemplate {
7377
return newTemplate;
7478
}
7579

80+
async setCreationIds(): Promise<SlideInfo[]> {
81+
const archive = await this.archive
82+
83+
const xmlTemplateHelper = new XmlTemplateHelper(archive)
84+
this.creationIds = await xmlTemplateHelper.getCreationIds()
85+
86+
return this.creationIds
87+
}
88+
7689
async appendSlide(slide: ISlide): Promise<void> {
7790
if (this.counter[0].get() === undefined) {
7891
await this.initializeCounter();

src/dev.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@ const automizer = new Automizer({
77

88
const pres = automizer
99
.loadRoot(`EmptyTemplate.pptx`)
10-
.load(`SlideWithTables.pptx`, 'table');
10+
.load(`SlideWithTables.pptx`, 'table')
11+
.load('SlideWithImages.pptx')
12+
.load(`RootTemplateWithCharts.pptx`);
13+
14+
15+
//
16+
// pres.getCreationIds()
17+
// .then(response => {
18+
// console.dir(response, {depth: 5})
19+
// })
1120

1221
const data1 = {
1322
body: [
@@ -29,15 +38,26 @@ const data2 = [
2938
[14, 12, 11],
3039
];
3140

32-
pres
33-
.addSlide('table', 1, (slide) => {
34-
slide.modifyElement('TableWithHeader', [modify.setTableData(data1)]);
35-
})
36-
.write(`modify-table.test.pptx`)
41+
const run = async() => {
42+
const info = await pres.setCreationIds()
43+
// console.dir(info, {depth: 5})
44+
45+
await pres
46+
.addSlide('table', 1950777067, (slide) => {
47+
slide.modifyElement(
48+
'{EFC74B4C-D832-409B-9CF4-73C1EFF132D8}',
49+
[modify.setTableData(data1)]);
50+
})
51+
.write(`modify-table.test.pptx`)
52+
.then((result) => {
53+
console.info(result);
54+
})
3755

38-
.then((result) => {
39-
console.info(result);
40-
})
56+
return pres
57+
}
58+
59+
run()
4160
.catch((error) => {
4261
console.error(error);
4362
});
63+

src/helper/xml-helper.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { TargetByRelIdMap } from '../constants/constants';
1212
import { XmlPrettyPrint } from './xml-pretty-print';
1313
import { GetRelationshipsCallback, Target } from '../types/types';
14+
import { XmlTemplateHelper } from './xml-template-helper';
1415

1516
export class XmlHelper {
1617
static async getXmlFromArchive(
@@ -71,7 +72,7 @@ export class XmlHelper {
7172
const parent = element.parent(xml);
7273
parent.appendChild(newElement);
7374

74-
XmlHelper.writeXmlToArchive(element.archive, element.file, xml);
75+
await XmlHelper.writeXmlToArchive(element.archive, element.file, xml);
7576

7677
return (newElement as unknown) as HelperElement;
7778
}
@@ -239,6 +240,16 @@ export class XmlHelper {
239240
return target;
240241
}
241242

243+
static async findByElementCreationId(
244+
archive: JSZip,
245+
path: string,
246+
creationId: string,
247+
): Promise<XMLDocument> {
248+
const slideXml = await XmlHelper.getXmlFromArchive(archive, path);
249+
250+
return XmlHelper.findByCreationId(slideXml, creationId);
251+
}
252+
242253
static async findByElementName(
243254
archive: JSZip,
244255
path: string,
@@ -254,7 +265,26 @@ export class XmlHelper {
254265

255266
for (const i in names) {
256267
if (names[i].getAttribute && names[i].getAttribute('name') === name) {
257-
return names[i].parentNode.parentNode as XMLDocument;
268+
return names[i]
269+
.parentNode
270+
.parentNode as XMLDocument;
271+
}
272+
}
273+
274+
return null;
275+
}
276+
277+
static findByCreationId(doc: Document, creationId: string): XMLDocument {
278+
const creationIds = doc.getElementsByTagName('a16:creationId')
279+
280+
for (const i in creationIds) {
281+
if (creationIds[i].getAttribute && creationIds[i].getAttribute('id') === creationId) {
282+
return creationIds[i]
283+
.parentNode
284+
.parentNode
285+
.parentNode
286+
.parentNode
287+
.parentNode as XMLDocument;
258288
}
259289
}
260290

src/helper/xml-template-helper.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import JSZip from "jszip";
2+
import {ElementInfo, SlideInfo} from "../types/xml-types";
3+
import {XmlHelper} from "./xml-helper";
4+
5+
export class XmlTemplateHelper {
6+
archive: JSZip;
7+
relType: string;
8+
path: string;
9+
10+
constructor(archive: JSZip) {
11+
this.relType = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide'
12+
this.archive = archive
13+
this.path = 'ppt/_rels/presentation.xml.rels'
14+
}
15+
16+
async getCreationIds(): Promise<SlideInfo[]> {
17+
const archive = await this.archive
18+
const relationships = await XmlHelper.getTargetsByRelationshipType(
19+
archive,
20+
this.path,
21+
this.relType
22+
)
23+
24+
const creationIds = []
25+
for(const slideRel of relationships) {
26+
const slideXml = await XmlHelper.getXmlFromArchive(
27+
archive,
28+
'ppt/' + slideRel.file
29+
)
30+
31+
const creationIdSlide = slideXml
32+
.getElementsByTagName('p14:creationId')
33+
.item(0)
34+
.getAttribute('val')
35+
36+
const elementIds = this.elementCreationIds(slideXml)
37+
38+
creationIds.push({
39+
id: Number(creationIdSlide),
40+
number: this.parseSlideRelFile(slideRel.file),
41+
elements: elementIds
42+
})
43+
}
44+
45+
return creationIds
46+
}
47+
48+
parseSlideRelFile(slideRelFile: string): number {
49+
return Number(slideRelFile
50+
.replace('slides/slide', '')
51+
.replace('.xml', '')
52+
)
53+
}
54+
55+
elementCreationIds(slideXml): ElementInfo[] {
56+
const slideElements = slideXml
57+
.getElementsByTagName('p:cNvPr')
58+
59+
const elementIds = <ElementInfo[]> []
60+
for(const item in slideElements) {
61+
const slideElement = slideElements[item]
62+
if(slideElement.getAttribute !== undefined) {
63+
const creationIdElement = slideElement
64+
.getElementsByTagName('a16:creationId')
65+
if(creationIdElement.item(0)) {
66+
elementIds.push(
67+
this.getElementInfo(slideElement)
68+
)
69+
}
70+
}
71+
}
72+
return elementIds
73+
}
74+
75+
getElementInfo(slideElement): ElementInfo {
76+
const elementName = slideElement.getAttribute('name')
77+
const type = slideElement.parentNode.parentNode.tagName
78+
const creationId = slideElement
79+
.getElementsByTagName('a16:creationId')
80+
.item(0)
81+
.getAttribute('id')
82+
83+
return {
84+
name: elementName,
85+
type: type.replace('p:', ''),
86+
id: creationId
87+
}
88+
}
89+
}

src/interfaces/pres-template.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { ITemplate } from './itemplate';
2+
import { SlideInfo} from "../types/xml-types";
23

34
export interface PresTemplate extends ITemplate {
45
name: string;
6+
setCreationIds(): Promise<SlideInfo[]>
7+
creationIds?: SlideInfo[]
58
}

src/types/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export type ImportElement = {
4848
export type ImportedElement = {
4949
mode: string;
5050
name?: string;
51+
hasCreationId?: boolean;
5152
sourceArchive: JSZip;
5253
sourceSlideNumber: number;
5354
callback?: ImportElement['callback'];

0 commit comments

Comments
 (0)