easy-template-x 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/easy-template-x.cjs +176 -96
- package/dist/es/easy-template-x.mjs +173 -95
- package/dist/types/errors/index.d.ts +4 -2
- package/dist/types/errors/internalArgumentMissingError.d.ts +5 -0
- package/dist/types/errors/internalError.d.ts +3 -0
- package/dist/types/errors/missingCloseDelimiterError.d.ts +2 -1
- package/dist/types/errors/missingStartDelimiterError.d.ts +2 -1
- package/dist/types/errors/tagOptionsParseError.d.ts +2 -1
- package/dist/types/errors/templateDataError.d.ts +3 -0
- package/dist/types/errors/templateSyntaxError.d.ts +3 -0
- package/dist/types/errors/unclosedTagError.d.ts +2 -1
- package/dist/types/errors/unknownContentTypeError.d.ts +2 -1
- package/dist/types/errors/unopenedTagError.d.ts +2 -1
- package/dist/types/plugins/chart/chartData.d.ts +5 -3
- package/dist/types/plugins/chart/chartDataValidation.d.ts +2 -0
- package/dist/types/plugins/loop/strategy/loopTableColumnsStrategy.d.ts +3 -1
- package/package.json +1 -1
- package/src/compilation/tagParser.ts +2 -2
- package/src/errors/index.ts +4 -2
- package/src/errors/{missingArgumentError.ts → internalArgumentMissingError.ts} +2 -2
- package/src/errors/internalError.ts +6 -0
- package/src/errors/missingCloseDelimiterError.ts +3 -1
- package/src/errors/missingStartDelimiterError.ts +3 -1
- package/src/errors/tagOptionsParseError.ts +3 -1
- package/src/errors/{argumentError.ts → templateDataError.ts} +1 -1
- package/src/errors/templateSyntaxError.ts +6 -0
- package/src/errors/unclosedTagError.ts +3 -1
- package/src/errors/unknownContentTypeError.ts +3 -1
- package/src/errors/unopenedTagError.ts +3 -1
- package/src/plugins/chart/chartData.ts +28 -3
- package/src/plugins/chart/chartDataValidation.ts +103 -0
- package/src/plugins/chart/chartPlugin.ts +2 -2
- package/src/plugins/chart/updateChart.ts +9 -45
- package/src/plugins/image/imagePlugin.ts +2 -2
- package/src/plugins/loop/strategy/loopTableColumnsStrategy.ts +30 -18
- package/src/xml/xml.ts +24 -18
- package/src/zip/jsZipHelper.ts +2 -2
- package/dist/types/errors/argumentError.d.ts +0 -3
- package/dist/types/errors/missingArgumentError.d.ts +0 -5
|
@@ -5,9 +5,16 @@ var xmldom = require('@xmldom/xmldom');
|
|
|
5
5
|
var getProp = require('lodash.get');
|
|
6
6
|
var JSON5 = require('json5');
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class InternalError extends Error {
|
|
9
9
|
constructor(message) {
|
|
10
|
-
super(message);
|
|
10
|
+
super(`Internal error: ${message}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class InternalArgumentMissingError extends InternalError {
|
|
15
|
+
constructor(argName) {
|
|
16
|
+
super(`Argument '${argName}' is missing.`);
|
|
17
|
+
this.argName = argName;
|
|
11
18
|
}
|
|
12
19
|
}
|
|
13
20
|
|
|
@@ -25,28 +32,27 @@ class MaxXmlDepthError extends Error {
|
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
class
|
|
29
|
-
constructor(
|
|
30
|
-
super(
|
|
31
|
-
this.argName = argName;
|
|
35
|
+
class TemplateSyntaxError extends Error {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(message);
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
class MissingCloseDelimiterError extends
|
|
41
|
+
class MissingCloseDelimiterError extends TemplateSyntaxError {
|
|
36
42
|
constructor(openDelimiterText) {
|
|
37
43
|
super(`Close delimiter is missing from '${openDelimiterText}'.`);
|
|
38
44
|
this.openDelimiterText = openDelimiterText;
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
class MissingStartDelimiterError extends
|
|
48
|
+
class MissingStartDelimiterError extends TemplateSyntaxError {
|
|
43
49
|
constructor(closeDelimiterText) {
|
|
44
50
|
super(`Open delimiter is missing from '${closeDelimiterText}'.`);
|
|
45
51
|
this.closeDelimiterText = closeDelimiterText;
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
class TagOptionsParseError extends
|
|
55
|
+
class TagOptionsParseError extends TemplateSyntaxError {
|
|
50
56
|
constructor(tagRawText, parseError) {
|
|
51
57
|
super(`Failed to parse tag options of '${tagRawText}': ${parseError.message}.`);
|
|
52
58
|
this.tagRawText = tagRawText;
|
|
@@ -54,7 +60,13 @@ class TagOptionsParseError extends Error {
|
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
class
|
|
63
|
+
class TemplateDataError extends Error {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super(message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class UnclosedTagError extends TemplateSyntaxError {
|
|
58
70
|
constructor(tagName) {
|
|
59
71
|
super(`Tag '${tagName}' is never closed.`);
|
|
60
72
|
this.tagName = tagName;
|
|
@@ -67,7 +79,7 @@ class UnidentifiedFileTypeError extends Error {
|
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
class UnknownContentTypeError extends
|
|
82
|
+
class UnknownContentTypeError extends TemplateDataError {
|
|
71
83
|
constructor(contentType, tagRawText, path) {
|
|
72
84
|
super(`Content type '${contentType}' does not have a registered plugin to handle it.`);
|
|
73
85
|
this.contentType = contentType;
|
|
@@ -76,7 +88,7 @@ class UnknownContentTypeError extends Error {
|
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
|
|
79
|
-
class UnopenedTagError extends
|
|
91
|
+
class UnopenedTagError extends TemplateSyntaxError {
|
|
80
92
|
constructor(tagName) {
|
|
81
93
|
super(`Tag '${tagName}' is closed but was never opened.`);
|
|
82
94
|
this.tagName = tagName;
|
|
@@ -395,7 +407,7 @@ function normalizeDoubleQuotes(text) {
|
|
|
395
407
|
|
|
396
408
|
class JsZipHelper {
|
|
397
409
|
static toJsZipOutputType(binaryOrType) {
|
|
398
|
-
if (!binaryOrType) throw new
|
|
410
|
+
if (!binaryOrType) throw new InternalArgumentMissingError("binaryOrType");
|
|
399
411
|
let binaryType;
|
|
400
412
|
if (typeof binaryOrType === 'function') {
|
|
401
413
|
binaryType = binaryOrType;
|
|
@@ -493,13 +505,19 @@ class Parser {
|
|
|
493
505
|
* handles xml namespaces more forgivingly (required mainly by the
|
|
494
506
|
* RawXmlPlugin).
|
|
495
507
|
*/
|
|
496
|
-
static parser = new xmldom.DOMParser(
|
|
508
|
+
static parser = new xmldom.DOMParser({
|
|
509
|
+
errorHandler: {
|
|
510
|
+
// Ignore xmldom warnings. They are often incorrect since we are
|
|
511
|
+
// parsing OOXML, not HTML.
|
|
512
|
+
warning: () => {}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
497
515
|
parse(str) {
|
|
498
516
|
const doc = this.domParse(str);
|
|
499
517
|
return xml.create.fromDomNode(doc.documentElement);
|
|
500
518
|
}
|
|
501
519
|
domParse(str) {
|
|
502
|
-
if (str === null || str === undefined) throw new
|
|
520
|
+
if (str === null || str === undefined) throw new InternalArgumentMissingError("str");
|
|
503
521
|
return Parser.parser.parseFromString(str, "text/xml");
|
|
504
522
|
}
|
|
505
523
|
|
|
@@ -509,7 +527,7 @@ class Parser {
|
|
|
509
527
|
* https://stackoverflow.com/questions/7918868/how-to-escape-xml-entities-in-javascript
|
|
510
528
|
*/
|
|
511
529
|
encodeValue(str) {
|
|
512
|
-
if (str === null || str === undefined) throw new
|
|
530
|
+
if (str === null || str === undefined) throw new InternalArgumentMissingError("str");
|
|
513
531
|
if (typeof str !== 'string') throw new TypeError(`Expected a string, got '${str.constructor.name}'.`);
|
|
514
532
|
return str.replace(/[<>&'"]/g, c => {
|
|
515
533
|
switch (c) {
|
|
@@ -595,7 +613,7 @@ class Create {
|
|
|
595
613
|
};
|
|
596
614
|
}
|
|
597
615
|
cloneNode(node, deep) {
|
|
598
|
-
if (!node) throw new
|
|
616
|
+
if (!node) throw new InternalArgumentMissingError("node");
|
|
599
617
|
if (!deep) {
|
|
600
618
|
const clone = Object.assign({}, node);
|
|
601
619
|
clone.parentNode = null;
|
|
@@ -748,8 +766,8 @@ let Query$1 = class Query {
|
|
|
748
766
|
* Returns all siblings between 'firstNode' and 'lastNode' inclusive.
|
|
749
767
|
*/
|
|
750
768
|
siblingsInRange(firstNode, lastNode) {
|
|
751
|
-
if (!firstNode) throw new
|
|
752
|
-
if (!lastNode) throw new
|
|
769
|
+
if (!firstNode) throw new InternalArgumentMissingError("firstNode");
|
|
770
|
+
if (!lastNode) throw new InternalArgumentMissingError("lastNode");
|
|
753
771
|
const range = [];
|
|
754
772
|
let curNode = firstNode;
|
|
755
773
|
while (curNode && curNode !== lastNode) {
|
|
@@ -769,8 +787,8 @@ let Modify$1 = class Modify {
|
|
|
769
787
|
* already know the relevant index.
|
|
770
788
|
*/
|
|
771
789
|
insertBefore(newNode, referenceNode) {
|
|
772
|
-
if (!newNode) throw new
|
|
773
|
-
if (!referenceNode) throw new
|
|
790
|
+
if (!newNode) throw new InternalArgumentMissingError("newNode");
|
|
791
|
+
if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
|
|
774
792
|
if (!referenceNode.parentNode) throw new Error(`'${"referenceNode"}' has no parent`);
|
|
775
793
|
const childNodes = referenceNode.parentNode.childNodes;
|
|
776
794
|
const beforeNodeIndex = childNodes.indexOf(referenceNode);
|
|
@@ -784,17 +802,17 @@ let Modify$1 = class Modify {
|
|
|
784
802
|
* already know the relevant index.
|
|
785
803
|
*/
|
|
786
804
|
insertAfter(newNode, referenceNode) {
|
|
787
|
-
if (!newNode) throw new
|
|
788
|
-
if (!referenceNode) throw new
|
|
805
|
+
if (!newNode) throw new InternalArgumentMissingError("newNode");
|
|
806
|
+
if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
|
|
789
807
|
if (!referenceNode.parentNode) throw new Error(`'${"referenceNode"}' has no parent`);
|
|
790
808
|
const childNodes = referenceNode.parentNode.childNodes;
|
|
791
809
|
const referenceNodeIndex = childNodes.indexOf(referenceNode);
|
|
792
810
|
xml.modify.insertChild(referenceNode.parentNode, newNode, referenceNodeIndex + 1);
|
|
793
811
|
}
|
|
794
812
|
insertChild(parent, child, childIndex) {
|
|
795
|
-
if (!parent) throw new
|
|
813
|
+
if (!parent) throw new InternalArgumentMissingError("parent");
|
|
796
814
|
if (xml.query.isTextNode(parent)) throw new Error('Appending children to text nodes is forbidden');
|
|
797
|
-
if (!child) throw new
|
|
815
|
+
if (!child) throw new InternalArgumentMissingError("child");
|
|
798
816
|
if (!parent.childNodes) parent.childNodes = [];
|
|
799
817
|
|
|
800
818
|
// revert to append
|
|
@@ -817,9 +835,9 @@ let Modify$1 = class Modify {
|
|
|
817
835
|
parent.childNodes.splice(childIndex, 0, child);
|
|
818
836
|
}
|
|
819
837
|
appendChild(parent, child) {
|
|
820
|
-
if (!parent) throw new
|
|
838
|
+
if (!parent) throw new InternalArgumentMissingError("parent");
|
|
821
839
|
if (xml.query.isTextNode(parent)) throw new Error('Appending children to text nodes is forbidden');
|
|
822
|
-
if (!child) throw new
|
|
840
|
+
if (!child) throw new InternalArgumentMissingError("child");
|
|
823
841
|
if (!parent.childNodes) parent.childNodes = [];
|
|
824
842
|
|
|
825
843
|
// update references
|
|
@@ -840,7 +858,7 @@ let Modify$1 = class Modify {
|
|
|
840
858
|
* * **Note**: It is more efficient to call removeChild(parent, childIndex).
|
|
841
859
|
*/
|
|
842
860
|
remove(node) {
|
|
843
|
-
if (!node) throw new
|
|
861
|
+
if (!node) throw new InternalArgumentMissingError("node");
|
|
844
862
|
if (!node.parentNode) throw new Error('Node has no parent');
|
|
845
863
|
xml.modify.removeChild(node.parentNode, node);
|
|
846
864
|
}
|
|
@@ -856,8 +874,8 @@ let Modify$1 = class Modify {
|
|
|
856
874
|
*/
|
|
857
875
|
|
|
858
876
|
removeChild(parent, childOrIndex) {
|
|
859
|
-
if (!parent) throw new
|
|
860
|
-
if (childOrIndex === null || childOrIndex === undefined) throw new
|
|
877
|
+
if (!parent) throw new InternalArgumentMissingError("parent");
|
|
878
|
+
if (childOrIndex === null || childOrIndex === undefined) throw new InternalArgumentMissingError("childOrIndex");
|
|
861
879
|
if (!parent.childNodes || !parent.childNodes.length) throw new Error('Parent node has node children');
|
|
862
880
|
|
|
863
881
|
// get child index
|
|
@@ -2328,7 +2346,7 @@ const TagDisposition = Object.freeze({
|
|
|
2328
2346
|
|
|
2329
2347
|
class TagParser {
|
|
2330
2348
|
constructor(delimiters) {
|
|
2331
|
-
if (!delimiters) throw new
|
|
2349
|
+
if (!delimiters) throw new InternalArgumentMissingError("delimiters");
|
|
2332
2350
|
this.delimiters = delimiters;
|
|
2333
2351
|
const tagOptionsRegex = `${Regex.escape(delimiters.tagOptionsStart)}(?<tagOptions>.*?)${Regex.escape(delimiters.tagOptionsEnd)}`;
|
|
2334
2352
|
this.tagRegex = new RegExp(`^${Regex.escape(delimiters.tagStart)}(?<tagName>.*?)(${tagOptionsRegex})?${Regex.escape(delimiters.tagEnd)}`, 'm');
|
|
@@ -2644,7 +2662,7 @@ class ImagePlugin extends TemplatePlugin {
|
|
|
2644
2662
|
return '';
|
|
2645
2663
|
}
|
|
2646
2664
|
if (transparencyPercent < 0 || transparencyPercent > 100) {
|
|
2647
|
-
throw new
|
|
2665
|
+
throw new TemplateDataError(`Transparency percent must be between 0 and 100, but was ${transparencyPercent}.`);
|
|
2648
2666
|
}
|
|
2649
2667
|
const alpha = Math.round((100 - transparencyPercent) * 1000);
|
|
2650
2668
|
return `<a:alphaModFix amt="${alpha}" />`;
|
|
@@ -2865,9 +2883,9 @@ class LoopTableColumnsStrategy {
|
|
|
2865
2883
|
if (!openRow) return false;
|
|
2866
2884
|
const closeRow = officeMarkup.query.containingTableRowNode(closeCell);
|
|
2867
2885
|
if (!closeRow) return false;
|
|
2868
|
-
const openColumnIndex =
|
|
2886
|
+
const openColumnIndex = this.getColumnIndex(openRow, openCell);
|
|
2869
2887
|
if (openColumnIndex === -1) return false;
|
|
2870
|
-
const closeColumnIndex =
|
|
2888
|
+
const closeColumnIndex = this.getColumnIndex(closeRow, closeCell);
|
|
2871
2889
|
if (closeColumnIndex === -1) return false;
|
|
2872
2890
|
|
|
2873
2891
|
// If the tags are in different columns, assume it's a table rows loop (iterate rows, not columns).
|
|
@@ -2879,8 +2897,8 @@ class LoopTableColumnsStrategy {
|
|
|
2879
2897
|
const lastCell = officeMarkup.query.containingTableCellNode(closeTag.xmlTextNode);
|
|
2880
2898
|
const firstRow = officeMarkup.query.containingTableRowNode(firstCell);
|
|
2881
2899
|
const lastRow = officeMarkup.query.containingTableRowNode(lastCell);
|
|
2882
|
-
const firstColumnIndex =
|
|
2883
|
-
const lastColumnIndex =
|
|
2900
|
+
const firstColumnIndex = this.getColumnIndex(firstRow, firstCell);
|
|
2901
|
+
const lastColumnIndex = this.getColumnIndex(lastRow, lastCell);
|
|
2884
2902
|
const table = officeMarkup.query.containingTableNode(firstCell);
|
|
2885
2903
|
|
|
2886
2904
|
// Remove the loop tags
|
|
@@ -2899,16 +2917,16 @@ class LoopTableColumnsStrategy {
|
|
|
2899
2917
|
mergeBack(columnsWrapperGroups, firstCell, lastCell) {
|
|
2900
2918
|
const table = officeMarkup.query.containingTableNode(firstCell);
|
|
2901
2919
|
const firstRow = officeMarkup.query.containingTableRowNode(firstCell);
|
|
2902
|
-
const firstColumnIndex =
|
|
2920
|
+
const firstColumnIndex = this.getColumnIndex(firstRow, firstCell);
|
|
2903
2921
|
const lastRow = officeMarkup.query.containingTableRowNode(lastCell);
|
|
2904
|
-
const lastColumnIndex =
|
|
2905
|
-
let index = firstColumnIndex
|
|
2922
|
+
const lastColumnIndex = this.getColumnIndex(lastRow, lastCell);
|
|
2923
|
+
let index = firstColumnIndex;
|
|
2906
2924
|
for (const colWrapperGroup of columnsWrapperGroups) {
|
|
2907
2925
|
if (colWrapperGroup.length !== 1) {
|
|
2908
2926
|
throw new Error('Expected a single synthetic table as the columns wrapper.');
|
|
2909
2927
|
}
|
|
2910
2928
|
const colWrapper = colWrapperGroup[0];
|
|
2911
|
-
this.
|
|
2929
|
+
this.insertColumnAfterIndex(table, colWrapper, index);
|
|
2912
2930
|
index++;
|
|
2913
2931
|
}
|
|
2914
2932
|
|
|
@@ -2925,20 +2943,20 @@ class LoopTableColumnsStrategy {
|
|
|
2925
2943
|
// For each row in the original table
|
|
2926
2944
|
const rows = table.childNodes?.filter(node => node.nodeName === 'w:tr') || [];
|
|
2927
2945
|
for (const row of rows) {
|
|
2928
|
-
const
|
|
2946
|
+
const syntheticRow = xml.create.cloneNode(row, false);
|
|
2929
2947
|
const cells = row.childNodes?.filter(node => node.nodeName === 'w:tc') || [];
|
|
2930
2948
|
|
|
2931
2949
|
// Copy only the cells within our column range
|
|
2932
2950
|
for (let i = firstColumnIndex; i <= lastColumnIndex; i++) {
|
|
2933
2951
|
if (cells[i]) {
|
|
2934
|
-
xml.modify.appendChild(
|
|
2952
|
+
xml.modify.appendChild(syntheticRow, xml.create.cloneNode(cells[i], true));
|
|
2935
2953
|
}
|
|
2936
2954
|
}
|
|
2937
|
-
xml.modify.appendChild(syntheticTable,
|
|
2955
|
+
xml.modify.appendChild(syntheticTable, syntheticRow);
|
|
2938
2956
|
}
|
|
2939
2957
|
return syntheticTable;
|
|
2940
2958
|
}
|
|
2941
|
-
|
|
2959
|
+
insertColumnAfterIndex(table, column, index) {
|
|
2942
2960
|
// Get all rows from both tables
|
|
2943
2961
|
const sourceRows = column.childNodes?.filter(node => node.nodeName === 'w:tr') || [];
|
|
2944
2962
|
const targetRows = table.childNodes?.filter(node => node.nodeName === 'w:tr') || [];
|
|
@@ -2956,21 +2974,31 @@ class LoopTableColumnsStrategy {
|
|
|
2956
2974
|
if (!sourceCell) {
|
|
2957
2975
|
throw new Error(`Cell not found in synthetic source table row ${i}.`);
|
|
2958
2976
|
}
|
|
2959
|
-
|
|
2977
|
+
const targetCell = this.getColumnByIndex(targetRow, index);
|
|
2978
|
+
const newCell = xml.create.cloneNode(sourceCell, true);
|
|
2979
|
+
if (targetCell) {
|
|
2980
|
+
xml.modify.insertAfter(newCell, targetCell);
|
|
2981
|
+
} else {
|
|
2982
|
+
xml.modify.appendChild(targetRow, newCell);
|
|
2983
|
+
}
|
|
2960
2984
|
}
|
|
2961
2985
|
}
|
|
2962
2986
|
removeColumn(table, index) {
|
|
2963
2987
|
const rows = table.childNodes?.filter(node => node.nodeName === 'w:tr') || [];
|
|
2964
2988
|
for (const row of rows) {
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
}
|
|
2968
|
-
if (row.childNodes.length <= index) {
|
|
2989
|
+
const cell = this.getColumnByIndex(row, index);
|
|
2990
|
+
if (!cell) {
|
|
2969
2991
|
continue;
|
|
2970
2992
|
}
|
|
2971
|
-
xml.modify.remove(
|
|
2993
|
+
xml.modify.remove(cell);
|
|
2972
2994
|
}
|
|
2973
2995
|
}
|
|
2996
|
+
getColumnIndex(row, cell) {
|
|
2997
|
+
return row.childNodes?.filter(child => child.nodeName === 'w:tc')?.findIndex(child => child === cell);
|
|
2998
|
+
}
|
|
2999
|
+
getColumnByIndex(row, index) {
|
|
3000
|
+
return row.childNodes?.filter(child => child.nodeName === 'w:tc')?.[index];
|
|
3001
|
+
}
|
|
2974
3002
|
}
|
|
2975
3003
|
|
|
2976
3004
|
class LoopTableRowsStrategy {
|
|
@@ -3210,13 +3238,10 @@ const chartTypes = Object.freeze({
|
|
|
3210
3238
|
barChart: "c:barChart",
|
|
3211
3239
|
line3DChart: "c:line3DChart",
|
|
3212
3240
|
lineChart: "c:lineChart",
|
|
3213
|
-
stockChart: "c:stockChart",
|
|
3214
3241
|
doughnutChart: "c:doughnutChart",
|
|
3215
3242
|
ofPieChart: "c:ofPieChart",
|
|
3216
3243
|
pie3DChart: "c:pie3DChart",
|
|
3217
3244
|
pieChart: "c:pieChart",
|
|
3218
|
-
surface3DChart: "c:surface3DChart",
|
|
3219
|
-
surfaceChart: "c:surfaceChart",
|
|
3220
3245
|
scatterChart: "c:scatterChart",
|
|
3221
3246
|
bubbleChart: "c:bubbleChart"
|
|
3222
3247
|
});
|
|
@@ -3257,6 +3282,19 @@ const formatIds = Object.freeze({
|
|
|
3257
3282
|
// Functions
|
|
3258
3283
|
//
|
|
3259
3284
|
|
|
3285
|
+
function chartFriendlyName(chartType) {
|
|
3286
|
+
const name = chartType.replace("c:", "").replace("Chart", "");
|
|
3287
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
3288
|
+
}
|
|
3289
|
+
function isStandardChartType(chartType) {
|
|
3290
|
+
return chartType === chartTypes.area3DChart || chartType === chartTypes.areaChart || chartType === chartTypes.bar3DChart || chartType === chartTypes.barChart || chartType === chartTypes.line3DChart || chartType === chartTypes.lineChart || chartType === chartTypes.doughnutChart || chartType === chartTypes.ofPieChart || chartType === chartTypes.pie3DChart || chartType === chartTypes.pieChart;
|
|
3291
|
+
}
|
|
3292
|
+
function isScatterChartType(chartType) {
|
|
3293
|
+
return chartType === chartTypes.scatterChart;
|
|
3294
|
+
}
|
|
3295
|
+
function isBubbleChartType(chartType) {
|
|
3296
|
+
return chartType === chartTypes.bubbleChart;
|
|
3297
|
+
}
|
|
3260
3298
|
function isStandardChartData(chartData) {
|
|
3261
3299
|
return "categories" in chartData;
|
|
3262
3300
|
}
|
|
@@ -3311,6 +3349,80 @@ function bubbleSizeValues(xValues, series) {
|
|
|
3311
3349
|
return xValues.map(x => sizeValuesMap[x]);
|
|
3312
3350
|
}
|
|
3313
3351
|
|
|
3352
|
+
function validateChartData(chartType, chartData) {
|
|
3353
|
+
if (isStandardChartType(chartType)) {
|
|
3354
|
+
validateStandardChartData(chartData, chartType);
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
if (isScatterChartType(chartType)) {
|
|
3358
|
+
validateScatterChartData(chartData, chartType);
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
if (isBubbleChartType(chartType)) {
|
|
3362
|
+
validateScatterChartData(chartData, chartType);
|
|
3363
|
+
validateBubbleChartData(chartData, chartType);
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
throw new TemplateDataError("Invalid chart data: " + JSON.stringify(chartData));
|
|
3367
|
+
}
|
|
3368
|
+
function validateStandardChartData(chartData, chartType) {
|
|
3369
|
+
if (!chartData.categories) {
|
|
3370
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart must have categories.`);
|
|
3371
|
+
}
|
|
3372
|
+
if (!chartData.categories.names) {
|
|
3373
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart categories must have a "names" field.`);
|
|
3374
|
+
}
|
|
3375
|
+
for (const ser of chartData.series) {
|
|
3376
|
+
if (!ser.values) {
|
|
3377
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart series must have a "values" field.`);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// Check if the series values and category names have the same length (same number of x and y values)
|
|
3381
|
+
if (ser.values.length != chartData.categories.names.length) {
|
|
3382
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart series values and category names must have the same length.`);
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// Verify series values are numbers
|
|
3386
|
+
for (const val of ser.values) {
|
|
3387
|
+
if (val === null || val === undefined) {
|
|
3388
|
+
continue;
|
|
3389
|
+
}
|
|
3390
|
+
if (typeof val === "number") {
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart series values must be numbers.`);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
function validateScatterChartData(chartData, chartType) {
|
|
3398
|
+
if (!chartData.series) {
|
|
3399
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart must have series.`);
|
|
3400
|
+
}
|
|
3401
|
+
for (const ser of chartData.series) {
|
|
3402
|
+
if (!ser.values) {
|
|
3403
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart series must have a "values" field.`);
|
|
3404
|
+
}
|
|
3405
|
+
for (const val of ser.values) {
|
|
3406
|
+
// Verify series values are valid point objects
|
|
3407
|
+
if (typeof val === "object" && "x" in val && "y" in val) {
|
|
3408
|
+
continue;
|
|
3409
|
+
}
|
|
3410
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart series values must have x and y properties.`);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
function validateBubbleChartData(chartData, chartType) {
|
|
3415
|
+
for (const ser of chartData.series) {
|
|
3416
|
+
for (const val of ser.values) {
|
|
3417
|
+
// Verify series points have a "size" property (x and y are checked in validateScatterChartData)
|
|
3418
|
+
if (typeof val === "object" && "size" in val) {
|
|
3419
|
+
continue;
|
|
3420
|
+
}
|
|
3421
|
+
throw new TemplateDataError(`${chartFriendlyName(chartType)} chart series values must have a "size" property.`);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3314
3426
|
// Based on: https://github.com/OpenXmlDev/Open-Xml-PowerTools/blob/vNext/OpenXmlPowerTools/ChartUpdater.cs
|
|
3315
3427
|
|
|
3316
3428
|
const space = " ";
|
|
@@ -3325,9 +3437,6 @@ async function updateChart(chartPart, chartData) {
|
|
|
3325
3437
|
chartData.series[i].name = seriesName(ser.name, i);
|
|
3326
3438
|
}
|
|
3327
3439
|
|
|
3328
|
-
// Input validation
|
|
3329
|
-
validateChartData(chartData);
|
|
3330
|
-
|
|
3331
3440
|
// Get the chart node
|
|
3332
3441
|
const root = await chartPart.xmlRoot();
|
|
3333
3442
|
if (root.nodeName !== "c:chartSpace") {
|
|
@@ -3345,8 +3454,12 @@ async function updateChart(chartPart, chartData) {
|
|
|
3345
3454
|
if (!chartNode) {
|
|
3346
3455
|
const plotAreaChildren = plotAreaNode.childNodes?.map(child => `<${child.nodeName}>`);
|
|
3347
3456
|
const supportedChartTypes = Object.values(chartTypes).join(", ");
|
|
3348
|
-
throw new
|
|
3457
|
+
throw new TemplateSyntaxError(`Unsupported chart type. Plot area children: ${plotAreaChildren?.join(", ")}. Supported chart types: ${supportedChartTypes}`);
|
|
3349
3458
|
}
|
|
3459
|
+
const chartType = chartNode.nodeName;
|
|
3460
|
+
|
|
3461
|
+
// Input validation
|
|
3462
|
+
validateChartData(chartType, chartData);
|
|
3350
3463
|
|
|
3351
3464
|
// Read the first series
|
|
3352
3465
|
const firstSeries = readFirstSeries(chartNode, chartData);
|
|
@@ -3357,41 +3470,6 @@ async function updateChart(chartPart, chartData) {
|
|
|
3357
3470
|
// Update inline series
|
|
3358
3471
|
updateInlineSeries(chartNode, firstSeries, chartData);
|
|
3359
3472
|
}
|
|
3360
|
-
function validateChartData(chartData) {
|
|
3361
|
-
if (isStandardChartData(chartData)) {
|
|
3362
|
-
for (const ser of chartData.series) {
|
|
3363
|
-
// Check if the series values and category names have the same length (same number of x and y values)
|
|
3364
|
-
if (ser.values.length != chartData.categories.names.length) {
|
|
3365
|
-
throw new ArgumentError("Invalid chart data. Series values and category names must have the same length.");
|
|
3366
|
-
}
|
|
3367
|
-
|
|
3368
|
-
// Verify series values are numbers
|
|
3369
|
-
for (const val of ser.values) {
|
|
3370
|
-
if (val === null || val === undefined) {
|
|
3371
|
-
continue;
|
|
3372
|
-
}
|
|
3373
|
-
if (typeof val === "number") {
|
|
3374
|
-
continue;
|
|
3375
|
-
}
|
|
3376
|
-
throw new ArgumentError("Invalid chart data. Series values must be numbers.");
|
|
3377
|
-
}
|
|
3378
|
-
}
|
|
3379
|
-
return;
|
|
3380
|
-
}
|
|
3381
|
-
if (isScatterChartData(chartData)) {
|
|
3382
|
-
for (const ser of chartData.series) {
|
|
3383
|
-
for (const val of ser.values) {
|
|
3384
|
-
// Verify series values are valid point objects
|
|
3385
|
-
if (typeof val === "object" && "x" in val && "y" in val) {
|
|
3386
|
-
continue;
|
|
3387
|
-
}
|
|
3388
|
-
throw new ArgumentError("Invalid scatter chart data. Series values must contain x and y properties.");
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3391
|
-
return;
|
|
3392
|
-
}
|
|
3393
|
-
throw new ArgumentError("Invalid chart data: " + JSON.stringify(chartData));
|
|
3394
|
-
}
|
|
3395
3473
|
|
|
3396
3474
|
//
|
|
3397
3475
|
// Read the first series
|
|
@@ -4155,7 +4233,7 @@ class ChartPlugin extends TemplatePlugin {
|
|
|
4155
4233
|
async simpleTagReplacements(tag, data, context) {
|
|
4156
4234
|
const chartNode = xml.query.findParentByName(tag.xmlTextNode, "c:chart");
|
|
4157
4235
|
if (!chartNode) {
|
|
4158
|
-
throw new
|
|
4236
|
+
throw new TemplateSyntaxError("Chart tag not placed in chart title");
|
|
4159
4237
|
}
|
|
4160
4238
|
const content = data.getScopeData();
|
|
4161
4239
|
if (!content) {
|
|
@@ -4400,7 +4478,7 @@ class TemplateHandler {
|
|
|
4400
4478
|
/**
|
|
4401
4479
|
* Version number of the `easy-template-x` library.
|
|
4402
4480
|
*/
|
|
4403
|
-
version = "6.
|
|
4481
|
+
version = "6.1.0" ;
|
|
4404
4482
|
constructor(options) {
|
|
4405
4483
|
this.options = new TemplateHandlerOptions(options);
|
|
4406
4484
|
|
|
@@ -4538,7 +4616,6 @@ class TemplateHandler {
|
|
|
4538
4616
|
}
|
|
4539
4617
|
}
|
|
4540
4618
|
|
|
4541
|
-
exports.ArgumentError = ArgumentError;
|
|
4542
4619
|
exports.Base64 = Base64;
|
|
4543
4620
|
exports.Binary = Binary;
|
|
4544
4621
|
exports.COMMENT_NODE_NAME = COMMENT_NODE_NAME;
|
|
@@ -4546,6 +4623,8 @@ exports.DelimiterSearcher = DelimiterSearcher;
|
|
|
4546
4623
|
exports.Delimiters = Delimiters;
|
|
4547
4624
|
exports.Docx = Docx;
|
|
4548
4625
|
exports.ImagePlugin = ImagePlugin;
|
|
4626
|
+
exports.InternalArgumentMissingError = InternalArgumentMissingError;
|
|
4627
|
+
exports.InternalError = InternalError;
|
|
4549
4628
|
exports.LOOP_CONTENT_TYPE = LOOP_CONTENT_TYPE;
|
|
4550
4629
|
exports.LinkPlugin = LinkPlugin;
|
|
4551
4630
|
exports.LoopPlugin = LoopPlugin;
|
|
@@ -4553,7 +4632,6 @@ exports.MalformedFileError = MalformedFileError;
|
|
|
4553
4632
|
exports.MaxXmlDepthError = MaxXmlDepthError;
|
|
4554
4633
|
exports.MimeType = MimeType;
|
|
4555
4634
|
exports.MimeTypeHelper = MimeTypeHelper;
|
|
4556
|
-
exports.MissingArgumentError = MissingArgumentError;
|
|
4557
4635
|
exports.MissingCloseDelimiterError = MissingCloseDelimiterError;
|
|
4558
4636
|
exports.MissingStartDelimiterError = MissingStartDelimiterError;
|
|
4559
4637
|
exports.OfficeMarkup = OfficeMarkup;
|
|
@@ -4573,10 +4651,12 @@ exports.TagDisposition = TagDisposition;
|
|
|
4573
4651
|
exports.TagOptionsParseError = TagOptionsParseError;
|
|
4574
4652
|
exports.TagParser = TagParser;
|
|
4575
4653
|
exports.TemplateCompiler = TemplateCompiler;
|
|
4654
|
+
exports.TemplateDataError = TemplateDataError;
|
|
4576
4655
|
exports.TemplateExtension = TemplateExtension;
|
|
4577
4656
|
exports.TemplateHandler = TemplateHandler;
|
|
4578
4657
|
exports.TemplateHandlerOptions = TemplateHandlerOptions;
|
|
4579
4658
|
exports.TemplatePlugin = TemplatePlugin;
|
|
4659
|
+
exports.TemplateSyntaxError = TemplateSyntaxError;
|
|
4580
4660
|
exports.TextPlugin = TextPlugin;
|
|
4581
4661
|
exports.UnclosedTagError = UnclosedTagError;
|
|
4582
4662
|
exports.UnidentifiedFileTypeError = UnidentifiedFileTypeError;
|