Skip to content

Commit 7cc74c5

Browse files
authored
Merge pull request #38 from singerla/feature-cache-1
sync main branch
2 parents a6da056 + 82e47cf commit 7cc74c5

File tree

19 files changed

+589
-130
lines changed

19 files changed

+589
-130
lines changed

README.md

Lines changed: 144 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,19 @@ This project comes along with [automizer-data](https://github.com/singerla/autom
99
This generator can only be used on the server-side and requires a [Node.js](https://nodejs.org/en/download/package-manager/) environment.
1010

1111
## Limitations
12+
### Shape types
1213
Please note that this project is *work in progress*. At the moment, you might encounter difficulties for special shape types that require further relations (e.g. links will not work properly). Although, most shape types are already supported, such as connection shapes, tables or charts. You are welcome to [report any issue](https://github.com/singerla/pptx-automizer/issues/new).
1314

15+
### Chart types
16+
Extended chart types, like waterfall or map charts, are not supported right now.
17+
18+
### PowerPoint Version
1419
All testing focuses on PowerPoint 2019 pptx file format.
1520

21+
### Slide Masters and -Layouts
22+
It is currently not supported to import slide masters or slide layouts into the root presentation. You can append any slide in any order, but you need to assure all pptx files to have the same set of master slides. Imported slide masters will be matched by number, e.g. if your imported slide uses master #3, your root presentation also needs to have at least three master slides.
23+
24+
1625
## Install
1726
There are basically two ways to use *pptx-automizer*.
1827

@@ -40,15 +49,9 @@ $ npm install pptx-automizer
4049
```
4150
in the root folder of your project. This will download and install the most recent version into your existing project.
4251

43-
## Example
44-
```js
45-
import Automizer, { modify } from "pptx-automizer"
46-
47-
// If you want to track the steps of creation process,
48-
// you can use a custom callback:
49-
const myStatusTracker = (status: StatusTracker) => {
50-
console.log(status.info + ' (' + status.share + '%)');
51-
};
52+
## General Example
53+
```ts
54+
import Automizer from "pptx-automizer"
5255

5356
// First, let's set some preferences!
5457
const automizer = new Automizer({
@@ -69,12 +72,14 @@ const automizer = new Automizer({
6972
// truncate root presentation and start with zero slides
7073
removeExistingSlides: true,
7174

72-
// use a callback function to track pptx generation process
73-
statusTracker: myStatusTracker,
75+
// use a callback function to track pptx generation process.
76+
// statusTracker: myStatusTracker,
7477
})
7578

7679
// Now we can start and load a pptx template.
77-
// Each addSlide will append to any existing slide in RootTemplate.pptx.
80+
// With removeExistingSlides set to 'false', each addSlide will append to
81+
// any existing slide in RootTemplate.pptx. Otherwise, we are going to start
82+
// with a truncated root template.
7883
let pres = automizer.loadRoot(`RootTemplate.pptx`)
7984
// We want to make some more files available and give them a handy label.
8085
.load(`SlideWithShapes.pptx`, 'shapes')
@@ -89,9 +94,33 @@ pres.addSlide('graph', 1)
8994
.addSlide('shapes', 1)
9095
.addSlide(`SlideWithImages.pptx`, 2)
9196

92-
// You can also select and import a single element from a template slide.
93-
// The desired shape will be identified by its name from slide-xml's
94-
// 'p:cNvPr'-element.
97+
// Finally, we want to write the output file.
98+
pres.write(`myPresentation.pptx`).then(summary => {
99+
console.log(summary)
100+
})
101+
```
102+
103+
104+
## Modify shapes with built-in functions
105+
It is possible to modify an existing element on a newly added slide.
106+
107+
```ts
108+
import { modify } from "pptx-automizer"
109+
110+
pres.addSlide('shapes', 2, (slide) => {
111+
slide.modifyElement('Drum', [
112+
// You can use some of the builtin modifiers to edit a shape's xml:
113+
modify.setPosition({x: 1000000, h:5000000, w:5000000}),
114+
// Log your target xml into the console:
115+
modify.dump
116+
])
117+
})
118+
```
119+
120+
## Add and modify shapes
121+
You can also select and import a single element from a template slide. The desired shape will be identified by its name from slide-xml's `p:cNvPr`-element.
122+
123+
```ts
95124
pres.addSlide('SlideWithImages.pptx', 1, (slide) => {
96125
// Pass the template name, the slide number, the element's name and
97126
// (optionally) a callback function to directly modify the child nodes
@@ -102,17 +131,14 @@ pres.addSlide('SlideWithImages.pptx', 1, (slide) => {
102131
.data = 'Custom content'
103132
})
104133
})
134+
```
105135

106-
// It is possible to modify an existing element on a newly added slide.
107-
pres.addSlide('shapes', 2, (slide) => {
108-
slide.modifyElement('Drum', [
109-
// You can use some of the builtin modifiers to edit a shape's xml:
110-
modify.setPosition({x: 1000000, h:5000000, w:5000000}),
111-
// Log your target xml into the console:
112-
modify.dump
113-
])
114-
})
115136

137+
## Modify charts
138+
139+
All data and styles can be modified. Please notice: If your template has more data than your data object, automizer will remove these nodes. The other way round, new nodes will be created from the existing ones in case you provide more data.
140+
141+
```ts
116142
// Modify an existing chart on an added slide.
117143
pres.addSlide('charts', 2, (slide) => {
118144
slide.modifyElement('ColumnChart', [
@@ -132,11 +158,15 @@ pres.addSlide('charts', 2, (slide) => {
132158
{ label: 'cat 2-4', values: [ 26, 50, 20 ] }
133159
]
134160
})
135-
// Please notice: If your template has more data than your data
136-
// object, automizer will remove these nodes.
137161
])
138162
})
139163

164+
```
165+
## Remove elements from a slide
166+
167+
You can as well remove elements from slides.
168+
169+
```ts
140170
// Remove existing charts, images or shapes from added slide.
141171
pres
142172
.addSlide('charts', 2, (slide) => {
@@ -147,13 +177,99 @@ pres
147177
slide.removeElement('Textfeld 5');
148178
slide.addElement('images', 2, 'imageJPG');
149179
})
180+
```
150181

151-
// Finally, we want to write the output file.
152-
pres.write(`myPresentation.pptx`).then(summary => {
182+
183+
## Sort output slides
184+
185+
There are three ways to arrange slides in an output presentation.
186+
187+
1. By default, all slides will be appended to the existing slides in your root template. The order of `addSlide`-calls will define slide sortation in output presentation.
188+
189+
2. You can alternatively remove all existing slides by setting the `removeExistingSlides` flag to true. The first slide added with `addSlide` will be first slide in the output presentation. If you want to insert slides from root template, you need to load it a second time.
190+
191+
```ts
192+
import Automizer from "pptx-automizer"
193+
194+
const automizer = new Automizer({
195+
templateDir: `my/pptx/templates`,
196+
outputDir: `my/pptx/output`,
197+
198+
// truncate root presentation and start with zero slides
199+
removeExistingSlides: true,
200+
})
201+
202+
203+
let pres = automizer.loadRoot(`RootTemplate.pptx`)
204+
// We load this twice to make it slide reordeing available
205+
.load(`RootTemplate.pptx`, 'root')
206+
.load(`SlideWithShapes.pptx`, 'shapes')
207+
.load(`SlideWithGraph.pptx`, 'graph')
208+
209+
pres.addSlide('root', 1) // First slide will be taken from root
210+
.addSlide('graph', 1)
211+
.addSlide('shapes', 1)
212+
.addSlide('root', 3) // Third slide from root we be appended
213+
.addSlide('root', 2) // Second and third slide will switch position
214+
})
215+
216+
pres.write(`mySortedPresentation.pptx`).then(summary => {
153217
console.log(summary)
154218
})
155219
```
156220

221+
222+
3. Use `sortSlides`-callback
223+
You can pass an array of numbers and create a callback and apply it to `presentation.xml`.
224+
This will also work without adding slides.
225+
226+
Slides will be appended to the existing slides by slide number (starting from 1). You may find irritating results in case you skip a slide number.
227+
228+
```ts
229+
import ModifyPresentationHelper from './helper/modify-presentation-helper';
230+
231+
//
232+
// You may truncate root template or you may not
233+
// ...
234+
235+
// It is possible to skip adding slides, try sorting an unmodified presentation
236+
pres.addSlide('charts', 1);
237+
.addSlide('charts', 2);
238+
.addSlide('images', 1);
239+
.addSlide('images', 2);
240+
241+
const order = [3, 2, 4, 1];
242+
pres.modify(ModifyPresentationHelper.sortSlides(order));
243+
```
244+
245+
246+
## Track status of automation process
247+
248+
When creating large presentations, you might want to have some information about the current status. Use a custom status tracker:
249+
250+
```ts
251+
import Automizer, { StatusTracker } from "pptx-automizer"
252+
253+
// If you want to track the steps of creation process,
254+
// you can use a custom callback:
255+
const myStatusTracker = (status: StatusTracker) => {
256+
console.log(status.info + ' (' + status.share + '%)');
257+
};
258+
259+
const automizer = new Automizer({
260+
// ...
261+
statusTracker: myStatusTracker,
262+
})
263+
```
264+
265+
## More examples
266+
Take a look into [__tests__-directory](https://github.com/singerla/pptx-automizer/blob/main/__tests__) to see a lot of examples for several use cases, e.g.:
267+
* [Style chart series or datapoints](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-existing-chart-styled.test.ts)
268+
* [Use tags inside text to replace contents](https://github.com/singerla/pptx-automizer/blob/main/__tests__/replace-tagged-text.test.ts)
269+
* [Modify vertical line charts](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-chart-vertical-lines.test.ts)
270+
* [Set table cell and border styles](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-existing-table.test.ts)
271+
272+
157273
### Testing
158274
You can run all unit tests using these commands:
159275
```
157 Bytes
Binary file not shown.

__tests__/sort-slides.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Automizer from '../src/automizer';
2+
import ModifyPresentationHelper from '../src/helper/modify-presentation-helper';
3+
4+
test('read root presentation and sort slides', async () => {
5+
const automizer = new Automizer({
6+
templateDir: `${__dirname}/pptx-templates`,
7+
outputDir: `${__dirname}/pptx-output`,
8+
});
9+
const pres = automizer
10+
.loadRoot(`RootTemplate.pptx`)
11+
.load(`SlideWithShapes.pptx`, 'shapes')
12+
.load(`SlideWithCharts.pptx`, 'charts')
13+
.load(`SlideWithImages.pptx`, 'images');
14+
15+
for (let i = 0; i <= 2; i++) {
16+
pres.addSlide('shapes', 1);
17+
}
18+
19+
pres.addSlide('charts', 1);
20+
pres.addSlide('charts', 2);
21+
pres.addSlide('images', 1);
22+
pres.addSlide('images', 2);
23+
24+
const order = [2, 1, 8, 7, 5, 6, 3, 4];
25+
pres.modify(ModifyPresentationHelper.sortSlides(order));
26+
27+
await pres.write(`sort-slides.test.pptx`);
28+
29+
expect(pres).toBeInstanceOf(Automizer);
30+
});

src/automizer.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Master } from './classes/master';
1616
import path from 'path';
1717
import * as fs from 'fs';
1818
import { XmlHelper } from './helper/xml-helper';
19+
import ModifyPresentationHelper from './helper/modify-presentation-helper';
1920

2021
/**
2122
* Automizer
@@ -161,7 +162,7 @@ export default class Automizer implements IPresentationProps {
161162
/**
162163
* Parses all loaded templates and collects creationIds for slides and
163164
* elements. This will make finding templates and elements independent
164-
* from slide number and element name.
165+
* of slide number and element name.
165166
* @returns Promise<TemplateInfo[]>
166167
*/
167168
public async setCreationIds(): Promise<TemplateInfo[]> {
@@ -272,6 +273,7 @@ export default class Automizer implements IPresentationProps {
272273
*/
273274
public async write(location: string): Promise<AutomizerSummary> {
274275
await this.writeSlides();
276+
await this.normalizePresentation();
275277
await this.applyModifyPresentationCallbacks();
276278

277279
const rootArchive = await this.rootTemplate.archive;
@@ -312,6 +314,18 @@ export default class Automizer implements IPresentationProps {
312314
);
313315
}
314316

317+
/**
318+
* Apply some callbacks to restore archive/xml structure
319+
* and prevent corrupted pptx files.
320+
*
321+
* TODO: Remove unused parts (slides, related items) from archive.
322+
* TODO: Use every imported image only once
323+
* TODO: Check for lost relations
324+
*/
325+
normalizePresentation(): void {
326+
this.modify(ModifyPresentationHelper.normalizeSlideIds);
327+
}
328+
315329
/**
316330
* Applies path prefix to given location string.
317331
* @param location path and/or filename
@@ -325,6 +339,10 @@ export default class Automizer implements IPresentationProps {
325339
return this.templateDir + location;
326340
} else if (fs.existsSync(this.templateFallbackDir + location)) {
327341
return this.templateFallbackDir + location;
342+
} else {
343+
vd('No file matches "' + location + '"');
344+
vd('@templateDir: ' + this.templateDir);
345+
vd('@templateFallbackDir: ' + this.templateFallbackDir);
328346
}
329347
break;
330348
case 'output':

src/classes/shape.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import JSZip from 'jszip';
22

33
import { XmlHelper } from '../helper/xml-helper';
4-
import { GeneralHelper } from '../helper/general-helper';
4+
import { GeneralHelper, vd } from '../helper/general-helper';
55
import {
66
ImportedElement,
77
ShapeModificationCallback,
@@ -10,6 +10,7 @@ import {
1010
import { RootPresTemplate } from '../interfaces/root-pres-template';
1111
import { HelperElement } from '../types/xml-types';
1212
import { ImageTypeMap } from '../enums/image-type-map';
13+
import { ElementSubtype } from '../enums/element-type';
1314

1415
export class Shape {
1516
mode: string;
@@ -42,6 +43,7 @@ export class Shape {
4243
callbacks: ShapeModificationCallback[];
4344
hasCreationId: boolean;
4445
contentTypeMap: typeof ImageTypeMap;
46+
subtype: ElementSubtype;
4547

4648
constructor(shape: ImportedElement) {
4749
this.mode = shape.mode;
@@ -55,9 +57,11 @@ export class Shape {
5557

5658
this.callbacks = GeneralHelper.arrayify(shape.callback);
5759
this.contentTypeMap = ImageTypeMap;
60+
5861
if (shape.target) {
5962
this.sourceNumber = shape.target.number;
6063
this.sourceRid = shape.target.rId;
64+
this.subtype = shape.target.subtype;
6165
}
6266
}
6367

0 commit comments

Comments
 (0)