Skip to content

Commit 69754c4

Browse files
authored
Merge branch 'bug-extended-chart' into main
2 parents 2d91bb7 + dda1753 commit 69754c4

File tree

14 files changed

+324
-68
lines changed

14 files changed

+324
-68
lines changed
157 Bytes
Binary file not shown.

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

src/classes/slide.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -421,14 +421,31 @@ export class Slide implements ISlide {
421421
): Promise<AnalyzedElementType> {
422422
const isChart = sourceElement.getElementsByTagName('c:chart');
423423
if (isChart.length) {
424+
const target = await XmlHelper.getTargetByRelId(
425+
sourceArchive,
426+
slideNumber,
427+
sourceElement,
428+
'chart',
429+
);
430+
424431
return {
425432
type: ElementType.Chart,
426-
target: await XmlHelper.getTargetByRelId(
427-
sourceArchive,
428-
slideNumber,
429-
sourceElement,
430-
'chart',
431-
),
433+
target: target,
434+
} as AnalyzedElementType;
435+
}
436+
437+
const isChartEx = sourceElement.getElementsByTagName('cx:chart');
438+
if (isChartEx.length) {
439+
const target = await XmlHelper.getTargetByRelId(
440+
sourceArchive,
441+
slideNumber,
442+
sourceElement,
443+
'chartEx',
444+
);
445+
446+
return {
447+
type: ElementType.Chart,
448+
target: target,
432449
} as AnalyzedElementType;
433450
}
434451

@@ -681,6 +698,7 @@ export class Slide implements ISlide {
681698
*/
682699
async copyRelatedContent(): Promise<void> {
683700
const charts = await Chart.getAllOnSlide(this.sourceArchive, this.relsPath);
701+
684702
for (const chart of charts) {
685703
await new Chart({
686704
mode: 'append',

src/constants/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ export const TargetByRelIdMap = {
66
relAttribute: 'r:id',
77
prefix: '../charts/chart',
88
} as TargetByRelIdMapParam,
9+
chartEx: {
10+
relRootTag: 'cx:chart',
11+
relAttribute: 'r:id',
12+
prefix: '../charts/chartEx',
13+
} as TargetByRelIdMapParam,
914
image: {
1015
relRootTag: 'a:blip',
1116
relAttribute: 'r:embed',

src/dev.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,41 @@ const automizer = new Automizer({
1717
const run = async () => {
1818
const ppt = automizer
1919
.loadRoot(`RootTemplate.pptx`)
20-
.load(`SlideWithCharts.pptx`, 'charts')
21-
.load(`SlideWithImages.pptx`, 'images');
20+
.load(`EmptySlide.pptx`, 'EmptySlide')
21+
.load(`ChartWaterfall.pptx`, 'ChartWaterfall')
22+
.load(`ChartBarsStacked.pptx`, 'ChartBarsStacked');
2223

23-
ppt.addSlide('charts', 1);
24-
ppt.addSlide('charts', 2);
25-
ppt.addSlide('images', 1);
26-
ppt.addSlide('images', 2);
24+
const result = await pres
25+
.addSlide('EmptySlide', 1, (slide) => {
26+
// slide.addElement('ChartBarsStacked', 1, 'BarsStacked', [
27+
// modify.setChartData(<ChartData>{
28+
// series: [{ label: 'series 1' }],
29+
// categories: [
30+
// { label: 'cat 2-1', values: [50] },
31+
// { label: 'cat 2-2', values: [14] },
32+
// { label: 'cat 2-3', values: [15] },
33+
// { label: 'cat 2-4', values: [26] },
34+
// ],
35+
// }),
36+
// ]);
2737

28-
ppt.modify(ModifyPresentationHelper.sortSlides([3, 2, 1]));
29-
30-
const summary = await ppt.write('reorder.pptx');
38+
slide.addElement('ChartWaterfall', 1, 'Waterfall 1', [
39+
modify.setExtendedChartData(<ChartData>{
40+
series: [{ label: 'series 1' }],
41+
categories: [
42+
{ label: 'cat 2-1', values: [50] },
43+
{ label: 'cat 2-2', values: [14] },
44+
{ label: 'cat 2-3', values: [15] },
45+
{ label: 'cat 2-4', values: [26] },
46+
{ label: 'cat 2-4', values: [26] },
47+
{ label: 'cat 2-4', values: [26] },
48+
{ label: 'cat 2-4', values: [26] },
49+
{ label: 'cat 2-4', values: [26] },
50+
],
51+
}),
52+
]);
53+
})
54+
.write(`modify-existing-waterfall-chart.test.pptx`);
3155
};
3256

3357
run().catch((error) => {

src/enums/element-type.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,8 @@ export enum ElementType {
33
Image = 'Image',
44
Shape = 'Generic',
55
}
6+
7+
export enum ElementSubtype {
8+
chart = 'chart',
9+
chartEx = 'chartEx',
10+
}

src/helper/file-helper.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@ export class FileHelper {
2929
return archive.files[file].async(type || 'string');
3030
}
3131

32+
static fileExistsInArchive(archive: JSZip, file: string): boolean {
33+
if (archive === undefined || archive.files[file] === undefined) {
34+
return false;
35+
}
36+
}
37+
3238
static extractFileContent(file: Buffer): Promise<JSZip> {
3339
const zip = new JSZip();
34-
return zip.loadAsync((file as unknown) as InputType);
40+
return zip.loadAsync(file as unknown as InputType);
3541
}
3642

3743
static getFileExtension(filename: string): string {

src/helper/modify-chart-helper.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,33 @@ export default class ModifyChartHelper {
206206
// XmlHelper.dump(chart)
207207
};
208208

209+
/**
210+
* Set chart data to modify extended chart types.
211+
* See `__tests__/modify-existing-extended-chart.test.js`
212+
*/
213+
static setExtendedChartData =
214+
(data: ChartData) =>
215+
(
216+
element: XMLDocument | Element,
217+
chart?: Document,
218+
workbook?: Workbook,
219+
): void => {
220+
const slots = [] as ChartSlot[];
221+
data.series.forEach((series: ChartSeries, s: number) => {
222+
slots.push({
223+
index: s,
224+
series: series,
225+
targetCol: s + 1,
226+
type: 'extendedSeries',
227+
});
228+
});
229+
230+
new ModifyChart(chart, workbook, data, slots).modifyExtended();
231+
232+
XmlHelper.dump(chart);
233+
// XmlHelper.dump(workbook.table)
234+
};
235+
209236
static setAxisRange =
210237
(range: ChartAxisRange) =>
211238
(chart: XMLDocument): void => {

src/helper/modify-xml-helper.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,23 @@ export default class ModifyXmlHelper {
116116
static value =
117117
(value: number | string, index?: number) =>
118118
(element: Element): void => {
119-
element.getElementsByTagName('c:v')[0].firstChild.textContent =
120-
String(value);
119+
const valueElement = element.getElementsByTagName('c:v');
120+
if (!valueElement.length) {
121+
XmlHelper.dump(element);
122+
throw 'Unable to set value @index: ' + index;
123+
}
124+
125+
valueElement[0].firstChild.textContent = String(value);
121126
if (index !== undefined) {
122127
element.setAttribute('idx', String(index));
123128
}
124129
};
125130

131+
static textContent =
132+
(value: number | string) =>
133+
(element: Element): void => {
134+
element.firstChild.textContent = String(value);
135+
};
126136
static attribute =
127137
(attribute: string, value: string | number) =>
128138
(element: Element): void => {

src/helper/xml-helper.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
44
import { FileHelper } from './file-helper';
55
import {
66
DefaultAttribute,
7+
HelperElement,
78
OverrideAttribute,
89
RelationshipAttribute,
9-
HelperElement,
1010
ModifyXmlCallback,
1111
} from '../types/xml-types';
1212
import { TargetByRelIdMap } from '../constants/constants';
1313
import { XmlPrettyPrint } from './xml-pretty-print';
14-
import {
15-
GetRelationshipsCallback,
16-
SourceSlideIdentifier,
17-
Target,
18-
} from '../types/types';
19-
import { XmlTemplateHelper } from './xml-template-helper';
14+
import { GetRelationshipsCallback, Target } from '../types/types';
15+
import _ from 'lodash';
2016
import { vd } from './general-helper';
2117

2218
export class XmlHelper {
@@ -142,26 +138,50 @@ export class XmlHelper {
142138
return max;
143139
}
144140

141+
static pushRelTargets(element: Element, prefix: string, targets: Target[]) {
142+
const type = element.getAttribute('Type');
143+
const target = element.getAttribute('Target');
144+
const rId = element.getAttribute('Id');
145+
146+
const subtype = _.last(prefix.split('/'));
147+
const relType = _.last(type.split('/'));
148+
149+
const matchNumber = target.match(/(\d+)/);
150+
const stripNumber =
151+
matchNumber && matchNumber[1] ? Number(matchNumber[1]) : 0;
152+
153+
if (XmlHelper.targetMatchesRelationship(relType, subtype, target, prefix)) {
154+
targets.push({
155+
file: target,
156+
rId: rId,
157+
number: stripNumber,
158+
type: type,
159+
subtype: subtype,
160+
} as Target);
161+
}
162+
}
163+
164+
static targetMatchesRelationship(relType, subtype, target, prefix) {
165+
if (relType === 'package') return true;
166+
167+
return relType === subtype && target.indexOf(prefix) === 0;
168+
}
169+
145170
static async getTargetsFromRelationships(
146171
archive: JSZip,
147172
path: string,
148-
prefix: string,
173+
prefix: string | string[],
149174
suffix?: string | RegExp,
150175
): Promise<Target[]> {
176+
const prefixes = typeof prefix === 'string' ? [prefix] : prefix;
177+
151178
return XmlHelper.getRelationships(
152179
archive,
153180
path,
154-
(element: Element, rels: Target[]) => {
155-
const target = element.getAttribute('Target');
156-
if (target.indexOf(prefix) === 0) {
157-
rels.push({
158-
file: target,
159-
rId: element.getAttribute('Id'),
160-
number: Number(
161-
target.replace(prefix, '').replace(suffix || '.xml', ''),
162-
),
163-
} as Target);
164-
}
181+
(element: Element, targets: Target[]) => {
182+
prefixes.forEach((prefix) => {
183+
XmlHelper.pushRelTargets(element, prefix, targets);
184+
});
165185
},
166186
);
167187
}

0 commit comments

Comments
 (0)