Skip to content

Commit 78d7cc1

Browse files
committed
Merge remote-tracking branch 'origin/feature-use-pptxgenjs'
# Conflicts: # README.md # src/classes/slide.ts # src/classes/template.ts # src/dev.ts # yarn.lock
2 parents ee31549 + e45099e commit 78d7cc1

17 files changed

+487
-230
lines changed

README.md

Lines changed: 9 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
`pptx-automizer` is a Node.js-based PowerPoint (.pptx) generator that automates the manipulation of existing .pptx files. With `pptx-automizer`, you can import your library of .pptx templates, merge templates, and customize slide content. `pptx-automizer` will not write files from scratch, but edit and merge existing pptx files. You can style template slides within PowerPoint, and these templates will be seamlessly integrated into the output presentation. Most of the content can be modified by using callbacks with [xmldom](https://github.com/xmldom/xmldom).
44

5+
If you require to create elements from scratch, `pptx-automizer` wraps around [PptxGenJS](https://github.com/gitbrent/PptxGenJS). Use the powerful syntax of `PptxGenJS` to add dynamic content to your existing .pptx template files.
6+
57
`pptx-automizer` is particularly well-suited for users who aim to manage their own library of .pptx template files, making it an ideal choice for those who work with intricate, well-designed customized layouts. With this tool, any existing slide or even a single element can serve as a data-driven template for generating output .pptx files.
68

79
This project is accompanied by [automizer-data](https://github.com/singerla/automizer-data). You can use `automizer-data` to import, browse and transform .xlsx- or .sav-data into perfectly fitting graph or table data.
@@ -26,11 +28,9 @@ If you require commercial support for complex .pptx automation, you can explore
2628
- [As a Package](#as-a-package)
2729
- [Usage](#usage)
2830
- [Basic Example](#basic-example)
29-
- [Load files from buffer/URL](#load-files-from-bufferurl)
30-
- [How to Select Slides And Shapes](#how-to-select-slides-and-shapes)
31+
- [How to Select Slides Shapes](#how-to-select-slides-shapes)
3132
- [Select slide by number and shape by name](#select-slide-by-number-and-shape-by-name)
3233
- [Select slides by creationId](#select-slides-by-creationid)
33-
- [Get Shape Info](#get-shape-info)
3434
- [Find and Modify Shapes](#find-and-modify-shapes)
3535
- [Modify Text](#modify-text)
3636
- [Modify Images](#modify-images)
@@ -46,7 +46,6 @@ If you require commercial support for complex .pptx automation, you can explore
4646
- [Import and modify slide Masters](#import-and-modify-slide-masters)
4747
- [Track status of automation process](#track-status-of-automation-process)
4848
- [More examples](#more-examples)
49-
- [Create a new modifier](#create-a-new-modifier)
5049
- [Troubleshooting](#troubleshooting)
5150
- [Testing](#testing)
5251
- [Special Thanks](#special-thanks)
@@ -228,32 +227,7 @@ const finalJSZip = await pres.getJSZip();
228227
const base64 = await finalJSZip.generateAsync({ type: 'base64' });
229228
```
230229

231-
## Load files from buffer/URL
232-
233-
It is possible to load `.pptx` files from buffer:
234-
235-
```ts
236-
const rootTemplate = await fs.readFile(
237-
`${__dirname}/pptx-templates/RootTemplate.pptx`,
238-
);
239-
const slideWithShapes = await fs.readFile(
240-
`${__dirname}/pptx-templates/SlideWithShapes.pptx`,
241-
);
242-
243-
// Additionally, you can fetch a template from url as well:
244-
const url =
245-
'https://raw.githubusercontent.com/singerla/pptx-automizer/main/__tests__/pptx-templates/SlideWithShapes.pptx';
246-
const response = await fetch(url);
247-
const buffer = await response.arrayBuffer();
248-
const bytes = new Uint8Array(buffer);
249-
250-
const pres = automizer
251-
.loadRoot(rootTemplate)
252-
.load(slideWithShapes, 'shapes')
253-
.load(bytes, 'shapesFromUrl');
254-
```
255-
256-
## How to Select Slides And Shapes
230+
## How to Select Slides Shapes
257231

258232
`pptx-automizer` needs a selector to find the required shape on a template slide. While an imported .pptx file is identified by filename or custom label, there are different ways to address its slides and shapes.
259233

@@ -379,32 +353,9 @@ If you decide to use the `creationId` method, you are safe to add, remove and re
379353

380354
> Please note: PowerPoint is going to update a shape's `creationId` only in case the shape was copied & pasted on a slide with an already existing identical shape `creationId`. If you were copying a slide, each shape `creationId` will be copied, too. As a result, you have unique shape ids, but different slide `creationIds`. If you are now going to paste a shape an such a slide, a new creationId will be given to the pasted shape. As a result, slide ids are unique throughout a presentation, but shape ids are unique only on one slide.
381355
382-
## Get Shape Info
383-
384-
If you need to find out e.g. a shape's coordinates on a slide or if you quickly want to read the text body, you can run `slide.getElement('Cloud')`:
385-
386-
```ts
387-
pres
388-
.addSlide('shapes', 2, async (slide) => {
389-
// Read a shape and print its text fragments:
390-
const info = await slide.getElement('Cloud');
391-
console.log(info.getText());
392-
393-
// Or take a look at the shape's coordinates:
394-
console.log(info.position);
395-
396-
// Dump element xml:
397-
XmlHelper.dump(info.getXmlElement())
398-
})
399-
```
400-
401-
Find out how to cross-slide copy properties from one shape to another:
402-
- [Read shape info](https://github.com/singerla/pptx-automizer/blob/main/__tests__/read-shape-info.test.ts)
403-
404-
405356
## Find and Modify Shapes
406357

407-
There are basically two ways to access a target shape on a slide:
358+
There are basically to ways to access a target shape on a slide:
408359

409360
- `slide.modifyElement(...)` requires an existing shape on the current slide,
410361
- `slide.addElement(...)` adds a shape from another slide to the current slide.
@@ -559,39 +510,6 @@ Find out more about formatting cells:
559510
- [Modify and style table cells](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-existing-table.test.ts)
560511
- [Insert data into table with empty cells](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-existing-table-create-text.test.ts)
561512

562-
563-
If you need to add rows or columns in a table with merged cells, you can add tags to your template table to expand it:
564-
```ts
565-
slide.modifyElement(
566-
'NestedTable3',
567-
modify.setTable(tableData, {
568-
adjustHeight: false,
569-
adjustWidth: false,
570-
expand: [
571-
{
572-
// Find a cell containing '{{each:row}}' and
573-
// clone it 3 times row-wise
574-
mode: 'row',
575-
tag: '{{each:row}}',
576-
count: 3,
577-
},
578-
{
579-
// Find a cell containing '{{each:sub}}' and
580-
// clone it once column-wise. Merged cells will
581-
// be cloned as well.
582-
mode: 'column',
583-
tag: '{{each:sub}}',
584-
count: 1,
585-
},
586-
],
587-
}),
588-
);
589-
```
590-
591-
Please find some examples in the tests:
592-
- [Expand a table with merged cells](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-nested-table.test.ts)
593-
594-
595513
## Modify Charts
596514

597515
All data and styles of a chart can be modified. Please note that if your template contains more data than your data object, Automizer will remove these extra nodes. Conversely, if you provide more data, new nodes will be cloned from the first existing one in the template.
@@ -665,37 +583,15 @@ pres
665583
});
666584
```
667585

668-
## Add Bulleted List
669-
670-
You can add a bulleted list to a shape. It is also possible to pass a list of nested arrays of strings.
671-
672-
```ts
673-
const bulletPoints = ['first line', 'second line', 'third line'];
674-
const indentedBulletPoints = ['first line', [ 'indent 1-1', 'indent 1-2', [ 'indent 2-1', 'indent 2-2', ] ], 'second line', 'third line'];
675-
676-
pres.addSlide('general', 2, (slide) => {
677-
slide.modifyElement(
678-
'replaceText', //shape selector
679-
modify.setBulletList(bulletPoints),
680-
);
681-
})
682-
.addSlide('general', 2, (slide) => {
683-
slide.modifyElement(
684-
'replaceText', //shape selector
685-
modify.setBulletList(indentedBulletPoints),
686-
);
687-
});
688-
```
689-
690-
# Tips and Tricks
586+
# Tipps and Tricks
691587

692588
## Loop through the slides of a presentation
693589

694590
If you would like to modify elements in a single .pptx file, it is important to know that `pptx-automizer` is not able to directly "jump" to a shape to modify it.
695591

696592
This is how it works internally:
697593

698-
- Load a root template to append slides to it
594+
- Load a root template to append slides to
699595
- (Probably) load root template again to modify slides
700596
- Load other templates
701597
- Append a loaded slide to (probably truncated) root template
@@ -727,7 +623,7 @@ const run = async () => {
727623
// Defining a "name" as second params makes it a little easier
728624
.load(`SlideWithShapes.pptx`, 'myTemplate');
729625

730-
// Get useful information about loaded templates:
626+
// This is brandnew: get useful information about loaded templates:
731627
const myTemplates = await pres.getInfo();
732628
const mySlides = myTemplates.slidesByTemplate(`myTemplate`);
733629

@@ -888,11 +784,6 @@ To specify another slideLayout for an added output slide, you need to count slid
888784

889785
To add and modify shapes on a slide master, please take a look at [Add and modify shapes](https://github.com/singerla/pptx-automizer#add-and-modify-shapes).
890786

891-
If you require to modify slide master backgrounds, please refer to
892-
893-
- [Modify master background color](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-master-background-color.test.ts).
894-
- [Modify master background image](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-master-background-image.test.ts).
895-
896787
```ts
897788
// Import another slide master and all its slide layouts.
898789
// Index 1 means, you want to import the first of all masters:
@@ -967,56 +858,6 @@ const automizer = new Automizer({
967858
});
968859
```
969860

970-
## Create a new modifier
971-
972-
If the built-in modifiers of `pptx-automizer` are not sufficient to your task, you can access the target xml elements with [xmldom](https://github.com/xmldom/xmldom). A modifier is a wrapper for such an operation.
973-
974-
Let's first take a look at a (simplified) existing modifier: `ModifyTextHelper.content('This is my text')`.
975-
976-
```ts
977-
// "setTextContent" is a function that returns a function.
978-
// A "label" argument needs to be passed to "setTextContent".
979-
const setTextContent = function (label: number | string) {
980-
// On setup, we can handle the argument.
981-
const newTextContent = String(label);
982-
983-
// A new function is returned to apply the label at runtime.
984-
return function (shape: XmlElement) {
985-
// "shape" contains a modifiable xmldom object.
986-
// You can use a selector to find the required 'a:t' element:
987-
const textElement = shape.getElementsByTagName('a:t').item(0);
988-
989-
// You can now apply the "newTextContent".
990-
if (textElement?.firstChild) {
991-
// Refer to xmldom for available functions.
992-
textElement.firstChild.textContent = newTextContent;
993-
}
994-
// It is possible to output the xml to console at any time.
995-
// XmlHelper.dump(element);
996-
};
997-
};
998-
```
999-
1000-
This function will construct an anonymous callback function on setup, while the callback function itself will be executed on runtime, when it's up to the target element on a slide.
1001-
1002-
You can use the modifier e.g. on adding an element:
1003-
1004-
```ts
1005-
pres.addSlide('SlideWithShapes.pptx', 2, (slide) => {
1006-
// This will import the 'Drum' shape
1007-
slide.modifyElement('Cloud', [
1008-
// 1. Dump the original xml:
1009-
// Notice: don't call XmlHelper.dump, just pass it
1010-
XmlHelper.dump,
1011-
// 2. Apply modifier from the example above:
1012-
setTextContent('New text'),
1013-
XmlHelper.dump,
1014-
]);
1015-
});
1016-
```
1017-
1018-
We can wrap any xml modification by such a modifier. If you have a working example and you think it will be useful to others, you are very welcome to fork this repo and send a pull request or simply [post it](https://github.com/singerla/pptx-automizer/issues/new).
1019-
1020861
## More examples
1021862

1022863
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.:
@@ -1029,18 +870,14 @@ Take a look into [**tests**-directory](https://github.com/singerla/pptx-automize
1029870
- [Update chart legend](https://github.com/singerla/pptx-automizer/blob/main/__tests__/modify-chart-legend.test.ts)
1030871

1031872
## Troubleshooting
1032-
1033873
If you encounter problems when opening a `.pptx`-file modified by this library, you might worry about PowerPoint not giving any details about the error. It can be hard to find the cause, but there are some things you can check:
1034874

1035875
- **Broken relation**: There are still unsupported shape types and `pptx-automizer` wil not copy required relations of those. You can inflate `.pptx`-output and check `ppt/slides/_rels/slide[#].xml.rels`-files to find possible missing files.
1036876
- **Unsupported media**: You can also take a look at the `ppt/media`-directory of an inflated `.pptx`-file. If you discover any unusual file formats, remove or replace the files by one of the [known types](https://github.com/singerla/pptx-automizer/blob/main/src/enums/content-type-map.ts).
1037877
- **Broken animation**: Pay attention to modified/removed shapes which are part of an animation. In case of doubt, (temporarily) remove all animations from your template. (see [#78](https://github.com/singerla/pptx-automizer/issues/78))
1038878
- **Proprietary/Binary contents** (e.g. ThinkCell): Walk through all slides, slideMasters and slideLayouts and seek for hidden Objects. Hit `ALT+F10` to toggle the sidebar.
1039-
- **Chart styles not working**: If you try to change e.g. color or size of a chart data label, and it doesn't work as expected, try to remove all data labels and activate them again. If this does not help, try to give the first data label of a series a slightly different style (this creates a single data point).
1040-
- **Replace Text not working**: Disable spell checking for the whole tag; If this doesn't help, cut out your e.g. {CustomerName} tag from textbox to clipboard, paste it into a plaintext editor to remove all (visible and invisible) formatting. Copy & paste {CustomerName} back to the textbox. (see [#82](https://github.com/singerla/pptx-automizer/issues/82) and [#73](https://github.com/singerla/pptx-automizer/issues/73))
1041-
- **No related chart worksheet**: It might happen to PowerPoint to lose the worksheet relation for a chart. If a chart gets corrupted by this, you will see a normal chart on your slide, but get an error message if you try to open the datasheet. Please replace the corrupted chart by a working one. (see [#104](https://github.com/singerla/pptx-automizer/issues/104))
1042879

1043-
If none of these could help, please don't hesitate to [talk about it](https://github.com/singerla/pptx-automizer/issues/new).
880+
If none of these could help, please don't hesitate to [talk about it](https://github.com/singerla/pptx-automizer/issues/new).
1044881

1045882
## Testing
1046883

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Automizer from '../src/automizer';
2+
import { ChartData, modify } from '../src';
3+
4+
const dataChartAreaLine = [
5+
{
6+
name: 'Actual Sales',
7+
labels: ['Jan', 'Feb', 'Mar'],
8+
values: [1500, 4600, 5156],
9+
},
10+
{
11+
name: 'Projected Sales',
12+
labels: ['Jan', 'Feb', 'Mar'],
13+
values: [1000, 2600, 3456],
14+
},
15+
];
16+
17+
test('generate a chart with pptxgenjs and add it to a template slide', async () => {
18+
const automizer = new Automizer({
19+
templateDir: `${__dirname}/pptx-templates`,
20+
outputDir: `${__dirname}/pptx-output`,
21+
});
22+
23+
const pres = automizer
24+
.loadRoot(`RootTemplate.pptx`)
25+
.load(`EmptySlide.pptx`, 'empty')
26+
.load(`SlideWithCharts.pptx`, 'charts');
27+
28+
pres.addSlide('empty', 1, (slide) => {
29+
// Use pptxgenjs to add generated contents from scratch:
30+
slide.generate((pSlide, objectName, pptxGenJs) => {
31+
pSlide.addChart(pptxGenJs.ChartType.line, dataChartAreaLine, {
32+
x: 1,
33+
y: 1,
34+
w: 8,
35+
h: 4,
36+
objectName,
37+
});
38+
});
39+
});
40+
41+
// Mix the created chart with modified existing chart
42+
pres.addSlide('charts', 2, (slide) => {
43+
slide.modifyElement('ColumnChart', [
44+
modify.setChartData(<ChartData>{
45+
series: [{ label: 'series 1' }, { label: 'series 3' }],
46+
categories: [
47+
{ label: 'cat 2-1', values: [50, 50] },
48+
{ label: 'cat 2-2', values: [14, 50] },
49+
],
50+
}),
51+
]);
52+
});
53+
54+
const result = await pres.write(`generate-pptxgenjs-charts.test.pptx`);
55+
56+
expect(result.charts).toBe(4);
57+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Automizer from '../src/automizer';
2+
import { ChartData, modify } from '../src';
3+
4+
test('insert an image with pptxgenjs on a template slide', async () => {
5+
const automizer = new Automizer({
6+
templateDir: `${__dirname}/pptx-templates`,
7+
outputDir: `${__dirname}/pptx-output`,
8+
});
9+
10+
const pres = automizer
11+
.loadRoot(`RootTemplate.pptx`)
12+
.load(`EmptySlide.pptx`, 'empty');
13+
14+
pres.addSlide('empty', 1, (slide) => {
15+
// Use pptxgenjs to add image from file:
16+
slide.generate((pptxGenJSSlide, objectName) => {
17+
pptxGenJSSlide.addImage({
18+
path: `${__dirname}/images/test.png`,
19+
x: 1,
20+
y: 2,
21+
objectName,
22+
});
23+
});
24+
});
25+
26+
const result = await pres.write(`generate-pptxgenjs-image.test.pptx`);
27+
28+
expect(result.images).toBe(1);
29+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Automizer from '../src/automizer';
2+
import { ChartData, modify } from '../src';
3+
4+
test('insert a textbox with pptxgenjs on a template slide', async () => {
5+
const automizer = new Automizer({
6+
templateDir: `${__dirname}/pptx-templates`,
7+
outputDir: `${__dirname}/pptx-output`,
8+
});
9+
10+
const pres = automizer
11+
.loadRoot(`RootTemplate.pptx`)
12+
.load(`EmptySlide.pptx`, 'empty');
13+
14+
pres.addSlide('empty', 1, (slide) => {
15+
// Use pptxgenjs to add text from scratch:
16+
slide.generate((pptxGenJSSlide, objectName) => {
17+
pptxGenJSSlide.addText('Test', {
18+
x: 1,
19+
y: 1,
20+
color: '363636',
21+
objectName,
22+
});
23+
}, 'custom object name');
24+
});
25+
26+
const result = await pres.write(`generate-pptxgenjs-text.test.pptx`);
27+
28+
expect(result.slides).toBe(2);
29+
});

0 commit comments

Comments
 (0)