easy-template-x 6.2.2 → 7.0.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/README.md +36 -7
- package/dist/cjs/easy-template-x.cjs +920 -430
- package/dist/es/easy-template-x.mjs +920 -431
- package/dist/types/src/compilation/delimiters/attributesDelimiterSearcher.d.ts +15 -0
- package/dist/types/src/compilation/delimiters/delimiterMark.d.ts +19 -0
- package/dist/types/src/compilation/delimiters/delimiterSearcher.d.ts +9 -0
- package/dist/types/src/compilation/delimiters/delimiterSearcher.tests.d.ts +1 -0
- package/dist/types/src/compilation/delimiters/index.d.ts +2 -0
- package/dist/types/src/compilation/delimiters/textNodesDelimiterSearcher.d.ts +18 -0
- package/dist/types/{compilation → src/compilation}/index.d.ts +1 -2
- package/dist/types/src/compilation/scopeData.tests.d.ts +1 -0
- package/dist/types/src/compilation/tag.d.ts +29 -0
- package/dist/types/src/compilation/tagParser.d.ts +16 -0
- package/dist/types/src/compilation/tagParser.tests.d.ts +1 -0
- package/dist/types/src/compilation/tagUtils.d.ts +2 -0
- package/dist/types/{compilation → src/compilation}/templateCompiler.d.ts +1 -1
- package/dist/types/src/compilation/templateCompiler.tests.d.ts +1 -0
- package/dist/types/src/errors/malformedFileError.d.ts +3 -0
- package/dist/types/src/office/contentTypesFile.tests.d.ts +1 -0
- package/dist/types/src/office/mediaFiles.tests.d.ts +1 -0
- package/dist/types/{office → src/office}/officeMarkup.d.ts +8 -6
- package/dist/types/src/office/officeMarkup.tests.d.ts +1 -0
- package/dist/types/{office → src/office}/omlNode.d.ts +22 -0
- package/dist/types/src/office/rel.tests.d.ts +1 -0
- package/dist/types/{office → src/office}/relationship.d.ts +2 -2
- package/dist/types/src/plugins/chart/chartDataValidation.tests.d.ts +1 -0
- package/dist/types/{plugins → src/plugins}/chart/chartPlugin.d.ts +3 -1
- package/dist/types/src/plugins/image/createImage.d.ts +3 -0
- package/dist/types/{plugins → src/plugins}/image/imageContent.d.ts +2 -2
- package/dist/types/{plugins → src/plugins}/image/imagePlugin.d.ts +3 -6
- package/dist/types/src/plugins/image/imageUtils.d.ts +3 -0
- package/dist/types/src/plugins/image/updateImage.d.ts +3 -0
- package/dist/types/{plugins → src/plugins}/link/linkPlugin.d.ts +3 -1
- package/dist/types/{plugins → src/plugins}/loop/loopPlugin.d.ts +3 -1
- package/dist/types/{plugins → src/plugins}/loop/strategy/iLoopStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopListStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopParagraphStrategy.d.ts +3 -3
- package/dist/types/src/plugins/loop/strategy/loopParagraphStrategy.tests.d.ts +1 -0
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopTableColumnsStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopTableRowsStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/rawXml/rawXmlPlugin.d.ts +2 -1
- package/dist/types/{plugins → src/plugins}/text/textPlugin.d.ts +4 -1
- package/dist/types/src/templateHandler.tests.d.ts +1 -0
- package/dist/types/{xml → src/xml}/xml.d.ts +5 -4
- package/dist/types/src/xml/xml.tests.d.ts +1 -0
- package/dist/types/{xml → src/xml}/xmlNode.d.ts +1 -1
- package/dist/types/test/fixtures/fixtureUtils.d.ts +1 -0
- package/dist/types/test/testUtils.d.ts +8 -0
- package/package.json +17 -19
- package/src/compilation/delimiters/attributesDelimiterSearcher.ts +109 -0
- package/src/compilation/delimiters/delimiterMark.ts +32 -0
- package/src/compilation/delimiters/delimiterSearcher.tests.ts +571 -0
- package/src/compilation/delimiters/delimiterSearcher.ts +41 -0
- package/src/compilation/delimiters/index.ts +2 -0
- package/src/compilation/delimiters/textNodesDelimiterSearcher.ts +185 -0
- package/src/compilation/index.ts +1 -2
- package/src/compilation/scopeData.tests.ts +40 -0
- package/src/compilation/tag.ts +22 -3
- package/src/compilation/tagParser.tests.ts +845 -0
- package/src/compilation/tagParser.ts +170 -66
- package/src/compilation/tagUtils.ts +9 -0
- package/src/compilation/templateCompiler.tests.ts +91 -0
- package/src/compilation/templateCompiler.ts +1 -1
- package/src/delimiters.ts +1 -1
- package/src/errors/malformedFileError.ts +3 -7
- package/src/office/contentTypesFile.tests.ts +88 -0
- package/src/office/docx.ts +2 -2
- package/src/office/mediaFiles.tests.ts +56 -0
- package/src/office/officeMarkup.tests.ts +114 -0
- package/src/office/officeMarkup.ts +48 -19
- package/src/office/omlNode.ts +75 -2
- package/src/office/openXmlPart.ts +1 -1
- package/src/office/rel.tests.ts +23 -0
- package/src/office/relationship.ts +22 -6
- package/src/office/relsFile.ts +21 -22
- package/src/office/xlsx.ts +2 -2
- package/src/plugins/chart/chartDataValidation.tests.ts +109 -0
- package/src/plugins/chart/chartPlugin.ts +11 -5
- package/src/plugins/chart/updateChart.ts +4 -4
- package/src/plugins/image/createImage.ts +115 -0
- package/src/plugins/image/imageContent.ts +16 -4
- package/src/plugins/image/imagePlugin.ts +28 -138
- package/src/plugins/image/imageUtils.ts +22 -0
- package/src/plugins/image/updateImage.ts +113 -0
- package/src/plugins/link/linkPlugin.ts +10 -3
- package/src/plugins/loop/loopPlugin.ts +11 -1
- package/src/plugins/loop/strategy/iLoopStrategy.ts +3 -3
- package/src/plugins/loop/strategy/loopListStrategy.ts +3 -3
- package/src/plugins/loop/strategy/loopParagraphStrategy.tests.ts +64 -0
- package/src/plugins/loop/strategy/loopParagraphStrategy.ts +5 -5
- package/src/plugins/loop/strategy/loopTableColumnsStrategy.ts +3 -3
- package/src/plugins/loop/strategy/loopTableRowsStrategy.ts +3 -3
- package/src/plugins/rawXml/rawXmlPlugin.ts +8 -2
- package/src/plugins/text/textPlugin.ts +37 -5
- package/src/templateHandler.tests.ts +84 -0
- package/src/templateHandler.ts +12 -15
- package/src/utils/path.ts +8 -1
- package/src/xml/xml.tests.ts +260 -0
- package/src/xml/xml.ts +55 -24
- package/src/xml/xmlNode.ts +1 -1
- package/src/zip/jsZipHelper.ts +1 -1
- package/src/zip/zip.ts +3 -0
- package/dist/types/compilation/delimiterMark.d.ts +0 -8
- package/dist/types/compilation/delimiterSearcher.d.ts +0 -12
- package/dist/types/compilation/tag.d.ts +0 -15
- package/dist/types/compilation/tagParser.d.ts +0 -11
- package/dist/types/errors/malformedFileError.d.ts +0 -4
- package/src/compilation/delimiterMark.ts +0 -16
- package/src/compilation/delimiterSearcher.ts +0 -183
- /package/dist/types/{compilation → src/compilation}/scopeData.d.ts +0 -0
- /package/dist/types/{compilation → src/compilation}/templateContext.d.ts +0 -0
- /package/dist/types/{delimiters.d.ts → src/delimiters.d.ts} +0 -0
- /package/dist/types/{errors → src/errors}/index.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/internalArgumentMissingError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/internalError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/maxXmlDepthError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/missingCloseDelimiterError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/missingStartDelimiterError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/tagOptionsParseError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/templateDataError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/templateSyntaxError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unclosedTagError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unidentifiedFileTypeError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unknownContentTypeError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unopenedTagError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unsupportedFileTypeError.d.ts +0 -0
- /package/dist/types/{extensions → src/extensions}/extensionOptions.d.ts +0 -0
- /package/dist/types/{extensions → src/extensions}/index.d.ts +0 -0
- /package/dist/types/{extensions → src/extensions}/templateExtension.d.ts +0 -0
- /package/dist/types/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/types/{mimeType.d.ts → src/mimeType.d.ts} +0 -0
- /package/dist/types/{office → src/office}/contentTypesFile.d.ts +0 -0
- /package/dist/types/{office → src/office}/docx.d.ts +0 -0
- /package/dist/types/{office → src/office}/index.d.ts +0 -0
- /package/dist/types/{office → src/office}/mediaFiles.d.ts +0 -0
- /package/dist/types/{office → src/office}/openXmlPart.d.ts +0 -0
- /package/dist/types/{office → src/office}/relsFile.d.ts +0 -0
- /package/dist/types/{office → src/office}/xlsx.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartColors.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartData.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartDataValidation.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/updateChart.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/defaultPlugins.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/image/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/link/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/link/linkContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/loop/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/loop/loopTagOptions.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/loop/strategy/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/pluginContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/rawXml/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/rawXml/rawXmlContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/templatePlugin.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/text/index.d.ts +0 -0
- /package/dist/types/{templateData.d.ts → src/templateData.d.ts} +0 -0
- /package/dist/types/{templateHandler.d.ts → src/templateHandler.d.ts} +0 -0
- /package/dist/types/{templateHandlerOptions.d.ts → src/templateHandlerOptions.d.ts} +0 -0
- /package/dist/types/{types.d.ts → src/types.d.ts} +0 -0
- /package/dist/types/{utils → src/utils}/array.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/base64.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/binary.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/index.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/number.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/path.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/regex.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/sha1.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/txt.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/types.d.ts +0 -0
- /package/dist/types/{xml → src/xml}/index.d.ts +0 -0
- /package/dist/types/{xml → src/xml}/xmlDepthTracker.d.ts +0 -0
- /package/dist/types/{xml → src/xml}/xmlTreeIterator.d.ts +0 -0
- /package/dist/types/{zip → src/zip}/index.d.ts +0 -0
- /package/dist/types/{zip → src/zip}/jsZipHelper.d.ts +0 -0
- /package/dist/types/{zip → src/zip}/zip.d.ts +0 -0
- /package/dist/types/{zip → src/zip}/zipObject.d.ts +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var JSZip = require('jszip');
|
|
4
3
|
var xmldom = require('@xmldom/xmldom');
|
|
4
|
+
var JSZip = require('jszip');
|
|
5
5
|
var getProp = require('lodash.get');
|
|
6
6
|
var JSON5 = require('json5');
|
|
7
7
|
|
|
@@ -19,9 +19,8 @@ class InternalArgumentMissingError extends InternalError {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
class MalformedFileError extends Error {
|
|
22
|
-
constructor(
|
|
23
|
-
super(
|
|
24
|
-
this.expectedFileType = expectedFileType;
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -202,8 +201,16 @@ function isNumber(value) {
|
|
|
202
201
|
class Path {
|
|
203
202
|
static getFilename(path) {
|
|
204
203
|
const lastSlashIndex = path.lastIndexOf('/');
|
|
205
|
-
return path.
|
|
204
|
+
return path.substring(lastSlashIndex + 1);
|
|
206
205
|
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get the directory of a path.
|
|
209
|
+
* Exclude the last slash.
|
|
210
|
+
*
|
|
211
|
+
* Example:
|
|
212
|
+
* /folder/subfolder/file.txt -> /folder/subfolder
|
|
213
|
+
*/
|
|
207
214
|
static getDirectory(path) {
|
|
208
215
|
const lastSlashIndex = path.lastIndexOf('/');
|
|
209
216
|
return path.substring(0, lastSlashIndex);
|
|
@@ -409,84 +416,6 @@ function countOccurrences(text, substring) {
|
|
|
409
416
|
return (text.match(new RegExp(substring, 'g')) || []).length;
|
|
410
417
|
}
|
|
411
418
|
|
|
412
|
-
class JsZipHelper {
|
|
413
|
-
static toJsZipOutputType(binaryOrType) {
|
|
414
|
-
if (!binaryOrType) throw new InternalArgumentMissingError("binaryOrType");
|
|
415
|
-
let binaryType;
|
|
416
|
-
if (typeof binaryOrType === 'function') {
|
|
417
|
-
binaryType = binaryOrType;
|
|
418
|
-
} else {
|
|
419
|
-
binaryType = binaryOrType.constructor;
|
|
420
|
-
}
|
|
421
|
-
if (Binary.isBlobConstructor(binaryType)) return 'blob';
|
|
422
|
-
if (Binary.isArrayBufferConstructor(binaryType)) return 'arraybuffer';
|
|
423
|
-
if (Binary.isBufferConstructor(binaryType)) return 'nodebuffer';
|
|
424
|
-
throw new Error(`Binary type '${binaryType.name}' is not supported.`);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
class ZipObject {
|
|
429
|
-
get name() {
|
|
430
|
-
return this.zipObject.name;
|
|
431
|
-
}
|
|
432
|
-
set name(value) {
|
|
433
|
-
this.zipObject.name = value;
|
|
434
|
-
}
|
|
435
|
-
get isDirectory() {
|
|
436
|
-
return this.zipObject.dir;
|
|
437
|
-
}
|
|
438
|
-
constructor(zipObject, binaryFormat) {
|
|
439
|
-
this.zipObject = zipObject;
|
|
440
|
-
this.binaryFormat = binaryFormat;
|
|
441
|
-
}
|
|
442
|
-
getContentText() {
|
|
443
|
-
return this.zipObject.async('text');
|
|
444
|
-
}
|
|
445
|
-
getContentBase64() {
|
|
446
|
-
return this.zipObject.async('binarystring');
|
|
447
|
-
}
|
|
448
|
-
getContentBinary(outputType) {
|
|
449
|
-
const zipOutputType = JsZipHelper.toJsZipOutputType(outputType ?? this.binaryFormat);
|
|
450
|
-
return this.zipObject.async(zipOutputType);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
class Zip {
|
|
455
|
-
static async load(file) {
|
|
456
|
-
const zip = await JSZip.loadAsync(file);
|
|
457
|
-
return new Zip(zip, file.constructor);
|
|
458
|
-
}
|
|
459
|
-
constructor(zip, binaryFormat) {
|
|
460
|
-
this.zip = zip;
|
|
461
|
-
this.binaryFormat = binaryFormat;
|
|
462
|
-
}
|
|
463
|
-
getFile(path) {
|
|
464
|
-
const internalZipObject = this.zip.files[path];
|
|
465
|
-
if (!internalZipObject) return null;
|
|
466
|
-
return new ZipObject(internalZipObject, this.binaryFormat);
|
|
467
|
-
}
|
|
468
|
-
setFile(path, content) {
|
|
469
|
-
this.zip.file(path, content);
|
|
470
|
-
}
|
|
471
|
-
isFileExist(path) {
|
|
472
|
-
return !!this.zip.files[path];
|
|
473
|
-
}
|
|
474
|
-
listFiles() {
|
|
475
|
-
return Object.keys(this.zip.files);
|
|
476
|
-
}
|
|
477
|
-
async export(outputType) {
|
|
478
|
-
const zipOutputType = JsZipHelper.toJsZipOutputType(outputType ?? this.binaryFormat);
|
|
479
|
-
const output = await this.zip.generateAsync({
|
|
480
|
-
type: zipOutputType,
|
|
481
|
-
compression: "DEFLATE",
|
|
482
|
-
compressionOptions: {
|
|
483
|
-
level: 6 // between 1 (best speed) and 9 (best compression)
|
|
484
|
-
}
|
|
485
|
-
});
|
|
486
|
-
return output;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
419
|
const XmlNodeType = Object.freeze({
|
|
491
420
|
Text: "Text",
|
|
492
421
|
General: "General",
|
|
@@ -830,8 +759,28 @@ let Query$1 = class Query {
|
|
|
830
759
|
if (!node) return null;
|
|
831
760
|
return (node.childNodes || []).find(child => predicate(child));
|
|
832
761
|
}
|
|
833
|
-
|
|
834
|
-
|
|
762
|
+
findByPath(root, nodeType, ...path) {
|
|
763
|
+
if (!root) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
let curNode = root;
|
|
767
|
+
for (let i = 0; i < path.length; i++) {
|
|
768
|
+
const curIndex = path[i];
|
|
769
|
+
if (typeof curIndex === 'string') {
|
|
770
|
+
curNode = xml.query.findChild(curNode, n => n.nodeName === curIndex);
|
|
771
|
+
}
|
|
772
|
+
if (typeof curIndex === 'number') {
|
|
773
|
+
const curNodeType = i == path.length - 1 ? nodeType : XmlNodeType.General;
|
|
774
|
+
curNode = curNode.childNodes.filter(c => c.nodeType === curNodeType)[curIndex];
|
|
775
|
+
}
|
|
776
|
+
if (!curNode) {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (curNode.nodeType !== nodeType) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
return curNode;
|
|
835
784
|
}
|
|
836
785
|
|
|
837
786
|
/**
|
|
@@ -872,7 +821,7 @@ let Modify$1 = class Modify {
|
|
|
872
821
|
insertBefore(newNode, referenceNode) {
|
|
873
822
|
if (!newNode) throw new InternalArgumentMissingError("newNode");
|
|
874
823
|
if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
|
|
875
|
-
if (!referenceNode.parentNode) throw new Error(`'
|
|
824
|
+
if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
|
|
876
825
|
const childNodes = referenceNode.parentNode.childNodes;
|
|
877
826
|
const beforeNodeIndex = childNodes.indexOf(referenceNode);
|
|
878
827
|
xml.modify.insertChild(referenceNode.parentNode, newNode, beforeNodeIndex);
|
|
@@ -887,7 +836,7 @@ let Modify$1 = class Modify {
|
|
|
887
836
|
insertAfter(newNode, referenceNode) {
|
|
888
837
|
if (!newNode) throw new InternalArgumentMissingError("newNode");
|
|
889
838
|
if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
|
|
890
|
-
if (!referenceNode.parentNode) throw new Error(`'
|
|
839
|
+
if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
|
|
891
840
|
const childNodes = referenceNode.parentNode.childNodes;
|
|
892
841
|
const referenceNodeIndex = childNodes.indexOf(referenceNode);
|
|
893
842
|
xml.modify.insertChild(referenceNode.parentNode, newNode, referenceNodeIndex + 1);
|
|
@@ -1024,7 +973,7 @@ let Modify$1 = class Modify {
|
|
|
1024
973
|
* `false` then the original child node is the first child of `right`.
|
|
1025
974
|
*/
|
|
1026
975
|
splitByChild(parent, child, removeChild) {
|
|
1027
|
-
if (child.parentNode != parent) throw new Error(`Node '
|
|
976
|
+
if (child.parentNode != parent) throw new Error(`Node 'child' is not a direct child of 'parent'.`);
|
|
1028
977
|
|
|
1029
978
|
// create childless clone 'left'
|
|
1030
979
|
const left = xml.create.cloneNode(parent, false);
|
|
@@ -1113,6 +1062,104 @@ function recursiveRemoveEmptyTextNodes(node) {
|
|
|
1113
1062
|
}
|
|
1114
1063
|
const xml = new XmlUtils();
|
|
1115
1064
|
|
|
1065
|
+
const TagDisposition = Object.freeze({
|
|
1066
|
+
Open: "Open",
|
|
1067
|
+
Close: "Close",
|
|
1068
|
+
SelfClosed: "SelfClosed"
|
|
1069
|
+
});
|
|
1070
|
+
const TagPlacement = Object.freeze({
|
|
1071
|
+
TextNode: "TextNode",
|
|
1072
|
+
Attribute: "Attribute"
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
function tagRegex(delimiters, global = false) {
|
|
1076
|
+
const tagOptionsPattern = `${Regex.escape(delimiters.tagOptionsStart)}(?<tagOptions>.*?)${Regex.escape(delimiters.tagOptionsEnd)}`;
|
|
1077
|
+
const tagPattern = `${Regex.escape(delimiters.tagStart)}(?<tagName>.*?)(${tagOptionsPattern})?${Regex.escape(delimiters.tagEnd)}`;
|
|
1078
|
+
const flags = global ? 'gm' : 'm';
|
|
1079
|
+
return new RegExp(tagPattern, flags);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
class JsZipHelper {
|
|
1083
|
+
static toJsZipOutputType(binaryOrType) {
|
|
1084
|
+
if (!binaryOrType) throw new InternalArgumentMissingError("binaryOrType");
|
|
1085
|
+
let binaryType;
|
|
1086
|
+
if (typeof binaryOrType === 'function') {
|
|
1087
|
+
binaryType = binaryOrType;
|
|
1088
|
+
} else {
|
|
1089
|
+
binaryType = binaryOrType.constructor;
|
|
1090
|
+
}
|
|
1091
|
+
if (Binary.isBlobConstructor(binaryType)) return 'blob';
|
|
1092
|
+
if (Binary.isArrayBufferConstructor(binaryType)) return 'arraybuffer';
|
|
1093
|
+
if (Binary.isBufferConstructor(binaryType)) return 'nodebuffer';
|
|
1094
|
+
throw new Error(`Binary type '${binaryType.name}' is not supported.`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
class ZipObject {
|
|
1099
|
+
get name() {
|
|
1100
|
+
return this.zipObject.name;
|
|
1101
|
+
}
|
|
1102
|
+
set name(value) {
|
|
1103
|
+
this.zipObject.name = value;
|
|
1104
|
+
}
|
|
1105
|
+
get isDirectory() {
|
|
1106
|
+
return this.zipObject.dir;
|
|
1107
|
+
}
|
|
1108
|
+
constructor(zipObject, binaryFormat) {
|
|
1109
|
+
this.zipObject = zipObject;
|
|
1110
|
+
this.binaryFormat = binaryFormat;
|
|
1111
|
+
}
|
|
1112
|
+
getContentText() {
|
|
1113
|
+
return this.zipObject.async('text');
|
|
1114
|
+
}
|
|
1115
|
+
getContentBase64() {
|
|
1116
|
+
return this.zipObject.async('binarystring');
|
|
1117
|
+
}
|
|
1118
|
+
getContentBinary(outputType) {
|
|
1119
|
+
const zipOutputType = JsZipHelper.toJsZipOutputType(outputType ?? this.binaryFormat);
|
|
1120
|
+
return this.zipObject.async(zipOutputType);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
class Zip {
|
|
1125
|
+
static async load(file) {
|
|
1126
|
+
const zip = await JSZip.loadAsync(file);
|
|
1127
|
+
return new Zip(zip, file.constructor);
|
|
1128
|
+
}
|
|
1129
|
+
constructor(zip, binaryFormat) {
|
|
1130
|
+
this.zip = zip;
|
|
1131
|
+
this.binaryFormat = binaryFormat;
|
|
1132
|
+
}
|
|
1133
|
+
getFile(path) {
|
|
1134
|
+
if (path && path.startsWith('/')) {
|
|
1135
|
+
path = path.substring(1);
|
|
1136
|
+
}
|
|
1137
|
+
const internalZipObject = this.zip.files[path];
|
|
1138
|
+
if (!internalZipObject) return null;
|
|
1139
|
+
return new ZipObject(internalZipObject, this.binaryFormat);
|
|
1140
|
+
}
|
|
1141
|
+
setFile(path, content) {
|
|
1142
|
+
this.zip.file(path, content);
|
|
1143
|
+
}
|
|
1144
|
+
isFileExist(path) {
|
|
1145
|
+
return !!this.zip.files[path];
|
|
1146
|
+
}
|
|
1147
|
+
listFiles() {
|
|
1148
|
+
return Object.keys(this.zip.files);
|
|
1149
|
+
}
|
|
1150
|
+
async export(outputType) {
|
|
1151
|
+
const zipOutputType = JsZipHelper.toJsZipOutputType(outputType ?? this.binaryFormat);
|
|
1152
|
+
const output = await this.zip.generateAsync({
|
|
1153
|
+
type: zipOutputType,
|
|
1154
|
+
compression: "DEFLATE",
|
|
1155
|
+
compressionOptions: {
|
|
1156
|
+
level: 6 // between 1 (best speed) and 9 (best compression)
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
return output;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1116
1163
|
/**
|
|
1117
1164
|
* The types of relationships that can be created in a docx file.
|
|
1118
1165
|
* A non-comprehensive list.
|
|
@@ -1132,20 +1179,35 @@ const RelType = Object.freeze({
|
|
|
1132
1179
|
Table: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table'
|
|
1133
1180
|
});
|
|
1134
1181
|
class Relationship {
|
|
1135
|
-
static fromXml(xml) {
|
|
1182
|
+
static fromXml(partDir, xml) {
|
|
1136
1183
|
return new Relationship({
|
|
1137
1184
|
id: xml.attributes?.['Id'],
|
|
1138
1185
|
type: xml.attributes?.['Type'],
|
|
1139
|
-
target: Relationship.normalizeRelTarget(xml.attributes?.['Target']),
|
|
1186
|
+
target: Relationship.normalizeRelTarget(partDir, xml.attributes?.['Target']),
|
|
1140
1187
|
targetMode: xml.attributes?.['TargetMode']
|
|
1141
1188
|
});
|
|
1142
1189
|
}
|
|
1143
|
-
static normalizeRelTarget(target) {
|
|
1190
|
+
static normalizeRelTarget(partDir, target) {
|
|
1144
1191
|
if (!target) {
|
|
1145
1192
|
return target;
|
|
1146
1193
|
}
|
|
1194
|
+
|
|
1195
|
+
// Remove leading slashes from input
|
|
1196
|
+
if (partDir.startsWith('/')) {
|
|
1197
|
+
partDir = partDir.substring(1);
|
|
1198
|
+
}
|
|
1199
|
+
if (target.startsWith('/')) {
|
|
1200
|
+
target = target.substring(1);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Convert target to relative path
|
|
1204
|
+
if (target.startsWith(partDir)) {
|
|
1205
|
+
target = target.substring(partDir.length);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Remove leading slashes from output
|
|
1147
1209
|
if (target.startsWith('/')) {
|
|
1148
|
-
|
|
1210
|
+
target = target.substring(1);
|
|
1149
1211
|
}
|
|
1150
1212
|
return target;
|
|
1151
1213
|
}
|
|
@@ -1156,11 +1218,11 @@ class Relationship {
|
|
|
1156
1218
|
const node = xml.create.generalNode('Relationship');
|
|
1157
1219
|
node.attributes = {};
|
|
1158
1220
|
|
|
1159
|
-
//
|
|
1221
|
+
// Set only non-empty attributes
|
|
1160
1222
|
for (const propKey of Object.keys(this)) {
|
|
1161
1223
|
const value = this[propKey];
|
|
1162
1224
|
if (value && typeof value === 'string') {
|
|
1163
|
-
const attrName = propKey[0].toUpperCase() + propKey.
|
|
1225
|
+
const attrName = propKey[0].toUpperCase() + propKey.substring(1);
|
|
1164
1226
|
node.attributes[attrName] = value;
|
|
1165
1227
|
}
|
|
1166
1228
|
}
|
|
@@ -1365,20 +1427,20 @@ class RelsFile {
|
|
|
1365
1427
|
* Returns the rel ID.
|
|
1366
1428
|
*/
|
|
1367
1429
|
async add(relTarget, relType, relTargetMode) {
|
|
1368
|
-
//
|
|
1430
|
+
// If relTarget is an internal file it should be relative to the part dir
|
|
1369
1431
|
if (this.partDir && relTarget.startsWith(this.partDir)) {
|
|
1370
|
-
relTarget = relTarget.
|
|
1432
|
+
relTarget = relTarget.substring(this.partDir.length + 1);
|
|
1371
1433
|
}
|
|
1372
1434
|
|
|
1373
|
-
//
|
|
1435
|
+
// Parse rels file
|
|
1374
1436
|
await this.parseRelsFile();
|
|
1375
1437
|
|
|
1376
|
-
//
|
|
1438
|
+
// Already exists?
|
|
1377
1439
|
const relTargetKey = this.getRelTargetKey(relType, relTarget);
|
|
1378
1440
|
let relId = this.relTargets[relTargetKey];
|
|
1379
1441
|
if (relId) return relId;
|
|
1380
1442
|
|
|
1381
|
-
//
|
|
1443
|
+
// Create rel node
|
|
1382
1444
|
relId = this.getNextRelId();
|
|
1383
1445
|
const rel = new Relationship({
|
|
1384
1446
|
id: relId,
|
|
@@ -1387,11 +1449,11 @@ class RelsFile {
|
|
|
1387
1449
|
targetMode: relTargetMode
|
|
1388
1450
|
});
|
|
1389
1451
|
|
|
1390
|
-
//
|
|
1452
|
+
// Update lookups
|
|
1391
1453
|
this.rels[relId] = rel;
|
|
1392
1454
|
this.relTargets[relTargetKey] = relId;
|
|
1393
1455
|
|
|
1394
|
-
//
|
|
1456
|
+
// Return
|
|
1395
1457
|
return relId;
|
|
1396
1458
|
}
|
|
1397
1459
|
async list() {
|
|
@@ -1410,20 +1472,20 @@ class RelsFile {
|
|
|
1410
1472
|
* Called automatically by the holding `Docx` before exporting.
|
|
1411
1473
|
*/
|
|
1412
1474
|
async save() {
|
|
1413
|
-
//
|
|
1475
|
+
// Not change - no need to save
|
|
1414
1476
|
if (!this.rels) return;
|
|
1415
1477
|
|
|
1416
|
-
//
|
|
1478
|
+
// Create rels xml
|
|
1417
1479
|
const root = this.createRootNode();
|
|
1418
1480
|
root.childNodes = Object.values(this.rels).map(rel => rel.toXml());
|
|
1419
1481
|
|
|
1420
|
-
//
|
|
1482
|
+
// Serialize and save
|
|
1421
1483
|
const xmlContent = xml.parser.serializeFile(root);
|
|
1422
1484
|
this.zip.setFile(this.relsFilePath, xmlContent);
|
|
1423
1485
|
}
|
|
1424
1486
|
|
|
1425
1487
|
//
|
|
1426
|
-
//
|
|
1488
|
+
// Private methods
|
|
1427
1489
|
//
|
|
1428
1490
|
|
|
1429
1491
|
getNextRelId() {
|
|
@@ -1435,10 +1497,10 @@ class RelsFile {
|
|
|
1435
1497
|
return relId;
|
|
1436
1498
|
}
|
|
1437
1499
|
async parseRelsFile() {
|
|
1438
|
-
//
|
|
1500
|
+
// Already parsed
|
|
1439
1501
|
if (this.rels) return;
|
|
1440
1502
|
|
|
1441
|
-
//
|
|
1503
|
+
// Parse xml
|
|
1442
1504
|
let root;
|
|
1443
1505
|
const relsFile = this.zip.getFile(this.relsFilePath);
|
|
1444
1506
|
if (relsFile) {
|
|
@@ -1448,24 +1510,23 @@ class RelsFile {
|
|
|
1448
1510
|
root = this.createRootNode();
|
|
1449
1511
|
}
|
|
1450
1512
|
|
|
1451
|
-
//
|
|
1513
|
+
// Parse relationship nodes
|
|
1452
1514
|
this.rels = {};
|
|
1453
1515
|
this.relTargets = {};
|
|
1454
1516
|
for (const relNode of root.childNodes) {
|
|
1455
|
-
const
|
|
1517
|
+
const genRelNode = relNode;
|
|
1518
|
+
const attributes = genRelNode.attributes;
|
|
1456
1519
|
if (!attributes) continue;
|
|
1457
1520
|
const idAttr = attributes['Id'];
|
|
1458
1521
|
if (!idAttr) continue;
|
|
1459
1522
|
|
|
1460
|
-
//
|
|
1461
|
-
const rel = Relationship.fromXml(
|
|
1523
|
+
// Store rel
|
|
1524
|
+
const rel = Relationship.fromXml(this.partDir, genRelNode);
|
|
1462
1525
|
this.rels[idAttr] = rel;
|
|
1463
1526
|
|
|
1464
|
-
//
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if (typeAttr && targetAttr) {
|
|
1468
|
-
const relTargetKey = this.getRelTargetKey(typeAttr, targetAttr);
|
|
1527
|
+
// Create rel target lookup
|
|
1528
|
+
if (rel.type && rel.target) {
|
|
1529
|
+
const relTargetKey = this.getRelTargetKey(rel.type, rel.target);
|
|
1469
1530
|
this.relTargets[relTargetKey] = idAttr;
|
|
1470
1531
|
}
|
|
1471
1532
|
}
|
|
@@ -1521,7 +1582,7 @@ class OpenXmlPart {
|
|
|
1521
1582
|
async getText() {
|
|
1522
1583
|
const xmlDocument = await this.xmlRoot();
|
|
1523
1584
|
|
|
1524
|
-
//
|
|
1585
|
+
// Ugly but good enough...
|
|
1525
1586
|
const xmlString = xml.parser.serializeFile(xmlDocument);
|
|
1526
1587
|
const domDocument = xml.parser.domParse(xmlString);
|
|
1527
1588
|
return domDocument.documentElement.textContent;
|
|
@@ -1625,7 +1686,7 @@ class Docx {
|
|
|
1625
1686
|
try {
|
|
1626
1687
|
zip = await Zip.load(file);
|
|
1627
1688
|
} catch {
|
|
1628
|
-
throw new MalformedFileError(
|
|
1689
|
+
throw new MalformedFileError("Failed to load zip file.");
|
|
1629
1690
|
}
|
|
1630
1691
|
|
|
1631
1692
|
// Load the docx file
|
|
@@ -1638,7 +1699,7 @@ class Docx {
|
|
|
1638
1699
|
*/
|
|
1639
1700
|
static async open(zip) {
|
|
1640
1701
|
const mainDocumentPath = await Docx.getMainDocumentPath(zip);
|
|
1641
|
-
if (!mainDocumentPath) throw new MalformedFileError(
|
|
1702
|
+
if (!mainDocumentPath) throw new MalformedFileError("Cannot find main document path.");
|
|
1642
1703
|
return new Docx(mainDocumentPath, zip);
|
|
1643
1704
|
}
|
|
1644
1705
|
static async getMainDocumentPath(zip) {
|
|
@@ -1705,11 +1766,12 @@ class W {
|
|
|
1705
1766
|
Table = 'w:tbl';
|
|
1706
1767
|
TableRow = 'w:tr';
|
|
1707
1768
|
TableCell = 'w:tc';
|
|
1769
|
+
Drawing = 'w:drawing';
|
|
1708
1770
|
NumberProperties = 'w:numPr';
|
|
1709
1771
|
}
|
|
1710
1772
|
|
|
1711
1773
|
/**
|
|
1712
|
-
* Drawing Markup Language node names.
|
|
1774
|
+
* Drawing Markup Language main namespace node names.
|
|
1713
1775
|
*
|
|
1714
1776
|
* These elements are part of the main drawingML namespace:
|
|
1715
1777
|
* xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main".
|
|
@@ -1720,6 +1782,68 @@ class A {
|
|
|
1720
1782
|
Run = 'a:r';
|
|
1721
1783
|
RunProperties = 'a:rPr';
|
|
1722
1784
|
Text = 'a:t';
|
|
1785
|
+
Graphic = 'a:graphic';
|
|
1786
|
+
GraphicData = 'a:graphicData';
|
|
1787
|
+
/**
|
|
1788
|
+
* Binary large image (or) picture.
|
|
1789
|
+
*/
|
|
1790
|
+
Blip = 'a:blip';
|
|
1791
|
+
AlphaModFix = 'a:alphaModFix';
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Drawing Markup Language "wordprocessing drawing" namespace node names.
|
|
1796
|
+
*
|
|
1797
|
+
* These elements are part of the wordprocessingDrawing namespace:
|
|
1798
|
+
* xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing".
|
|
1799
|
+
*/
|
|
1800
|
+
class Wp {
|
|
1801
|
+
/**
|
|
1802
|
+
* docPr stands for "Drawing Object Non-Visual Properties", which isn't
|
|
1803
|
+
* exactly a good acronym but that's how it's called nevertheless.
|
|
1804
|
+
*/
|
|
1805
|
+
DocPr = 'wp:docPr';
|
|
1806
|
+
/**
|
|
1807
|
+
* Inline DrawingML Object.
|
|
1808
|
+
*
|
|
1809
|
+
* see: http://officeopenxml.com/drwPicInline.php
|
|
1810
|
+
*/
|
|
1811
|
+
Inline = 'wp:inline';
|
|
1812
|
+
/**
|
|
1813
|
+
* Anchor for Floating DrawingML Object.
|
|
1814
|
+
*
|
|
1815
|
+
* see: http://officeopenxml.com/drwPicFloating.php
|
|
1816
|
+
*/
|
|
1817
|
+
FloatingAnchor = 'wp:anchor';
|
|
1818
|
+
/**
|
|
1819
|
+
* Drawing extent.
|
|
1820
|
+
*/
|
|
1821
|
+
Extent = 'wp:extent';
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* Drawing Markup Language "picture" namespace node names.
|
|
1826
|
+
*
|
|
1827
|
+
* These elements are part of the picture namespace:
|
|
1828
|
+
* xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture".
|
|
1829
|
+
*/
|
|
1830
|
+
class Pic {
|
|
1831
|
+
Pic = 'pic:pic';
|
|
1832
|
+
/**
|
|
1833
|
+
* Non-visual picture properties.
|
|
1834
|
+
*/
|
|
1835
|
+
NvPicPr = 'pic:nvPicPr';
|
|
1836
|
+
CnVPr = 'pic:cNvPr';
|
|
1837
|
+
/**
|
|
1838
|
+
* Binary large image (or) picture fill.
|
|
1839
|
+
*/
|
|
1840
|
+
BlipFill = 'pic:blipFill';
|
|
1841
|
+
/**
|
|
1842
|
+
* Shape properties.
|
|
1843
|
+
*/
|
|
1844
|
+
SpPr = 'pic:spPr';
|
|
1845
|
+
Xfrm = 'a:xfrm';
|
|
1846
|
+
Ext = 'a:ext';
|
|
1723
1847
|
}
|
|
1724
1848
|
|
|
1725
1849
|
/**
|
|
@@ -1737,9 +1861,19 @@ class OmlNode {
|
|
|
1737
1861
|
static W = new W();
|
|
1738
1862
|
|
|
1739
1863
|
/**
|
|
1740
|
-
* Drawing Markup Language node names.
|
|
1864
|
+
* Drawing Markup Language main namespace node names.
|
|
1741
1865
|
*/
|
|
1742
1866
|
static A = new A();
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Drawing Markup Language "wordprocessing drawing" namespace node names.
|
|
1870
|
+
*/
|
|
1871
|
+
static Wp = new Wp();
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* Drawing Markup Language "picture" namespace node names.
|
|
1875
|
+
*/
|
|
1876
|
+
static Pic = new Pic();
|
|
1743
1877
|
}
|
|
1744
1878
|
class OmlAttribute {
|
|
1745
1879
|
static SpacePreserve = 'xml:space';
|
|
@@ -1810,9 +1944,12 @@ class Query {
|
|
|
1810
1944
|
}
|
|
1811
1945
|
isListParagraph(paragraphNode) {
|
|
1812
1946
|
const paragraphProperties = officeMarkup.query.findParagraphPropertiesNode(paragraphNode);
|
|
1813
|
-
const listNumberProperties = xml.query.
|
|
1947
|
+
const listNumberProperties = xml.query.findByPath(paragraphProperties, XmlNodeType.General, OmlNode.W.NumberProperties);
|
|
1814
1948
|
return !!listNumberProperties;
|
|
1815
1949
|
}
|
|
1950
|
+
isInlineDrawingNode(node) {
|
|
1951
|
+
return node.nodeName === OmlNode.Wp.Inline && node.parentNode?.nodeName === OmlNode.W.Drawing;
|
|
1952
|
+
}
|
|
1816
1953
|
findParagraphPropertiesNode(paragraphNode) {
|
|
1817
1954
|
if (!officeMarkup.query.isParagraphNode(paragraphNode)) throw new Error(`Expected paragraph node but received a '${paragraphNode.nodeName}' node.`);
|
|
1818
1955
|
return xml.query.findChild(paragraphNode, officeMarkup.query.isParagraphPropertiesNode);
|
|
@@ -1836,7 +1973,7 @@ class Query {
|
|
|
1836
1973
|
*/
|
|
1837
1974
|
containingTextNode(node) {
|
|
1838
1975
|
if (!node) return null;
|
|
1839
|
-
if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument
|
|
1976
|
+
if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument node. Expected a XmlTextNode.`);
|
|
1840
1977
|
return xml.query.findParent(node, officeMarkup.query.isTextNode);
|
|
1841
1978
|
}
|
|
1842
1979
|
|
|
@@ -1953,7 +2090,7 @@ class Modify {
|
|
|
1953
2090
|
splitParagraphByTextNode(paragraph, textNode, removeTextNode) {
|
|
1954
2091
|
// input validation
|
|
1955
2092
|
const containingParagraph = officeMarkup.query.containingParagraphNode(textNode);
|
|
1956
|
-
if (containingParagraph != paragraph) throw new Error(`Node '
|
|
2093
|
+
if (containingParagraph != paragraph) throw new Error(`Node 'textNode' is not a contained in the specified paragraph.`);
|
|
1957
2094
|
const runNode = officeMarkup.query.containingRunNode(textNode);
|
|
1958
2095
|
const wordTextNode = officeMarkup.query.containingTextNode(textNode);
|
|
1959
2096
|
|
|
@@ -2102,17 +2239,36 @@ class Modify {
|
|
|
2102
2239
|
node.attributes[OmlAttribute.SpacePreserve] = 'preserve';
|
|
2103
2240
|
}
|
|
2104
2241
|
}
|
|
2105
|
-
removeTag(
|
|
2106
|
-
|
|
2107
|
-
|
|
2242
|
+
removeTag(tag) {
|
|
2243
|
+
if (tag.placement === TagPlacement.TextNode) {
|
|
2244
|
+
const wordTextNode = officeMarkup.query.containingTextNode(tag.xmlTextNode);
|
|
2245
|
+
const runNode = officeMarkup.query.containingRunNode(tag.xmlTextNode);
|
|
2246
|
+
|
|
2247
|
+
// Remove the word text node
|
|
2248
|
+
xml.modify.remove(wordTextNode);
|
|
2108
2249
|
|
|
2109
|
-
|
|
2110
|
-
|
|
2250
|
+
// Remove the run node if it's empty
|
|
2251
|
+
if (officeMarkup.query.isEmptyRun(runNode)) {
|
|
2252
|
+
xml.modify.remove(runNode);
|
|
2253
|
+
}
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
if (tag.placement === TagPlacement.Attribute) {
|
|
2257
|
+
if (!tag.xmlNode.attributes || !(tag.attributeName in tag.xmlNode.attributes)) {
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// Remove the tag from the attribute value
|
|
2262
|
+
tag.xmlNode.attributes[tag.attributeName] = tag.xmlNode.attributes[tag.attributeName].replace(tag.rawText, "");
|
|
2111
2263
|
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2264
|
+
// Remove the attribute if it's empty
|
|
2265
|
+
if (tag.xmlNode.attributes[tag.attributeName] === "") {
|
|
2266
|
+
delete tag.xmlNode.attributes[tag.attributeName];
|
|
2267
|
+
}
|
|
2268
|
+
return;
|
|
2115
2269
|
}
|
|
2270
|
+
const anyTag = tag;
|
|
2271
|
+
throw new Error(`Unexpected tag placement "${anyTag.placement}" for tag "${anyTag.rawText}".`);
|
|
2116
2272
|
}
|
|
2117
2273
|
}
|
|
2118
2274
|
|
|
@@ -2134,7 +2290,7 @@ class Xlsx {
|
|
|
2134
2290
|
try {
|
|
2135
2291
|
zip = await Zip.load(file);
|
|
2136
2292
|
} catch {
|
|
2137
|
-
throw new MalformedFileError(
|
|
2293
|
+
throw new MalformedFileError("Failed to load zip file.");
|
|
2138
2294
|
}
|
|
2139
2295
|
|
|
2140
2296
|
// Load the xlsx file
|
|
@@ -2147,7 +2303,7 @@ class Xlsx {
|
|
|
2147
2303
|
*/
|
|
2148
2304
|
static async open(zip) {
|
|
2149
2305
|
const mainDocumentPath = await Xlsx.getMainDocumentPath(zip);
|
|
2150
|
-
if (!mainDocumentPath) throw new MalformedFileError(
|
|
2306
|
+
if (!mainDocumentPath) throw new MalformedFileError("Cannot find main document path.");
|
|
2151
2307
|
return new Xlsx(mainDocumentPath, zip);
|
|
2152
2308
|
}
|
|
2153
2309
|
static async getMainDocumentPath(zip) {
|
|
@@ -2200,7 +2356,86 @@ class Xlsx {
|
|
|
2200
2356
|
}
|
|
2201
2357
|
}
|
|
2202
2358
|
|
|
2203
|
-
|
|
2359
|
+
const drawingDescriptionAttributeName = "descr";
|
|
2360
|
+
class AttributesDelimiterSearcher {
|
|
2361
|
+
visitedNodes = new Set();
|
|
2362
|
+
constructor(delimiters) {
|
|
2363
|
+
if (!delimiters) throw new InternalArgumentMissingError("delimiters");
|
|
2364
|
+
this.delimiters = delimiters;
|
|
2365
|
+
this.tagRegex = tagRegex(delimiters, true);
|
|
2366
|
+
}
|
|
2367
|
+
processNode(it, delimiters) {
|
|
2368
|
+
// Ignore irrelevant nodes
|
|
2369
|
+
if (!this.shouldSearchNode(it)) {
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// Search delimiters in attributes
|
|
2374
|
+
this.findDelimiters(it, delimiters);
|
|
2375
|
+
}
|
|
2376
|
+
shouldSearchNode(it) {
|
|
2377
|
+
if (this.visitedNodes.has(it.node)) {
|
|
2378
|
+
return false;
|
|
2379
|
+
}
|
|
2380
|
+
this.visitedNodes.add(it.node);
|
|
2381
|
+
if (!xml.query.isGeneralNode(it.node)) return false;
|
|
2382
|
+
if (Object.keys(it.node.attributes || {}).length === 0) return false;
|
|
2383
|
+
|
|
2384
|
+
// Currently we only support description attributes of drawing objects
|
|
2385
|
+
if (!this.isDrawingPropertiesNode(it.node)) {
|
|
2386
|
+
return false;
|
|
2387
|
+
}
|
|
2388
|
+
if (!it.node.attributes[drawingDescriptionAttributeName]) {
|
|
2389
|
+
return false;
|
|
2390
|
+
}
|
|
2391
|
+
return true;
|
|
2392
|
+
}
|
|
2393
|
+
isDrawingPropertiesNode(node) {
|
|
2394
|
+
// Node is drawing properties
|
|
2395
|
+
if (node.nodeName !== OmlNode.Wp.DocPr) {
|
|
2396
|
+
return false;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
// Parent is drawing
|
|
2400
|
+
if (!node.parentNode) {
|
|
2401
|
+
return false;
|
|
2402
|
+
}
|
|
2403
|
+
const parent = xml.query.findParentByName(node, OmlNode.W.Drawing);
|
|
2404
|
+
return !!parent;
|
|
2405
|
+
}
|
|
2406
|
+
findDelimiters(it, delimiters) {
|
|
2407
|
+
// Currently we only support description attributes of drawing objects
|
|
2408
|
+
this.findDelimitersInAttribute(it.node, drawingDescriptionAttributeName, delimiters);
|
|
2409
|
+
}
|
|
2410
|
+
findDelimitersInAttribute(node, attributeName, delimiters) {
|
|
2411
|
+
const attrValue = node.attributes?.[attributeName];
|
|
2412
|
+
if (!attrValue) {
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
const matches = attrValue.matchAll(this.tagRegex);
|
|
2416
|
+
for (const match of matches) {
|
|
2417
|
+
const tag = match[0];
|
|
2418
|
+
const openDelimiterIndex = match.index;
|
|
2419
|
+
const closeDelimiterIndex = openDelimiterIndex + tag.length - this.delimiters.tagEnd.length;
|
|
2420
|
+
const openDelimiter = this.createCurrentDelimiterMark(openDelimiterIndex, true, node, attributeName);
|
|
2421
|
+
const closeDelimiter = this.createCurrentDelimiterMark(closeDelimiterIndex, false, node, attributeName);
|
|
2422
|
+
delimiters.push(openDelimiter);
|
|
2423
|
+
delimiters.push(closeDelimiter);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
createCurrentDelimiterMark(index, isOpen, xmlNode, attributeName) {
|
|
2427
|
+
return {
|
|
2428
|
+
placement: TagPlacement.Attribute,
|
|
2429
|
+
isOpen: isOpen,
|
|
2430
|
+
index: index,
|
|
2431
|
+
attributeName: attributeName,
|
|
2432
|
+
xmlNode: xmlNode
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
class TextNodesDelimiterSearcher {
|
|
2438
|
+
lookForOpenDelimiter = true;
|
|
2204
2439
|
/**
|
|
2205
2440
|
* The index of the current delimiter character being matched.
|
|
2206
2441
|
*
|
|
@@ -2208,30 +2443,55 @@ class MatchState {
|
|
|
2208
2443
|
* are now looking for the character `{`. If it is 1, then we are looking
|
|
2209
2444
|
* for `!`.
|
|
2210
2445
|
*/
|
|
2211
|
-
|
|
2446
|
+
lookForDelimiterIndex = 0;
|
|
2212
2447
|
/**
|
|
2213
|
-
* The list of text nodes containing the delimiter characters.
|
|
2448
|
+
* The list of text nodes containing the delimiter characters of the current match.
|
|
2214
2449
|
*/
|
|
2215
|
-
|
|
2450
|
+
matchOpenNodes = [];
|
|
2216
2451
|
/**
|
|
2217
|
-
* The index of the first character of the delimiter, in the text node it
|
|
2452
|
+
* The index of the first character of the current delimiter match, in the text node it
|
|
2218
2453
|
* was found at.
|
|
2219
2454
|
*
|
|
2220
2455
|
* Example: If the delimiter is `{!`, and the text node content is `abc{!xyz`,
|
|
2221
2456
|
* then the firstMatchIndex is 3.
|
|
2222
2457
|
*/
|
|
2223
2458
|
firstMatchIndex = -1;
|
|
2224
|
-
|
|
2225
|
-
this.
|
|
2226
|
-
this.
|
|
2459
|
+
constructor(startDelimiter, endDelimiter) {
|
|
2460
|
+
this.startDelimiter = startDelimiter;
|
|
2461
|
+
this.endDelimiter = endDelimiter;
|
|
2462
|
+
}
|
|
2463
|
+
processNode(it, delimiters) {
|
|
2464
|
+
// Reset match state on paragraph transition
|
|
2465
|
+
if (officeMarkup.query.isParagraphNode(it.node)) {
|
|
2466
|
+
this.resetMatch();
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
// Reset match state on inline drawing
|
|
2470
|
+
if (officeMarkup.query.isInlineDrawingNode(it.node)) {
|
|
2471
|
+
this.resetMatch();
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// Ignore non-text nodes
|
|
2475
|
+
if (!this.shouldSearchNode(it)) {
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// Search delimiters in text nodes
|
|
2480
|
+
this.findDelimiters(it, delimiters);
|
|
2481
|
+
}
|
|
2482
|
+
resetMatch() {
|
|
2483
|
+
this.lookForDelimiterIndex = 0;
|
|
2484
|
+
this.matchOpenNodes = [];
|
|
2227
2485
|
this.firstMatchIndex = -1;
|
|
2228
2486
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2487
|
+
shouldSearchNode(it) {
|
|
2488
|
+
if (!xml.query.isTextNode(it.node)) return false;
|
|
2489
|
+
if (!it.node.textContent) return false;
|
|
2490
|
+
if (!it.node.parentNode) return false;
|
|
2491
|
+
if (!officeMarkup.query.isTextNode(it.node.parentNode)) return false;
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
findDelimiters(it, delimiters) {
|
|
2235
2495
|
//
|
|
2236
2496
|
// Performance note:
|
|
2237
2497
|
//
|
|
@@ -2243,57 +2503,38 @@ class DelimiterSearcher {
|
|
|
2243
2503
|
// complexity and effort.
|
|
2244
2504
|
//
|
|
2245
2505
|
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
if (officeMarkup.query.isParagraphNode(it.node)) {
|
|
2253
|
-
match.reset();
|
|
2254
|
-
}
|
|
2506
|
+
// Search delimiters in text nodes
|
|
2507
|
+
this.matchOpenNodes.push(it.node);
|
|
2508
|
+
let textIndex = 0;
|
|
2509
|
+
while (textIndex < it.node.textContent.length) {
|
|
2510
|
+
const delimiterPattern = this.lookForOpenDelimiter ? this.startDelimiter : this.endDelimiter;
|
|
2511
|
+
const char = it.node.textContent[textIndex];
|
|
2255
2512
|
|
|
2256
|
-
//
|
|
2257
|
-
if (
|
|
2258
|
-
|
|
2513
|
+
// No match
|
|
2514
|
+
if (char !== delimiterPattern[this.lookForDelimiterIndex]) {
|
|
2515
|
+
textIndex = this.noMatch(it, textIndex);
|
|
2516
|
+
textIndex++;
|
|
2259
2517
|
continue;
|
|
2260
2518
|
}
|
|
2261
2519
|
|
|
2262
|
-
//
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
const delimiterPattern = lookForOpenDelimiter ? this.startDelimiter : this.endDelimiter;
|
|
2267
|
-
const char = it.node.textContent[textIndex];
|
|
2268
|
-
|
|
2269
|
-
// No match
|
|
2270
|
-
if (char !== delimiterPattern[match.delimiterIndex]) {
|
|
2271
|
-
textIndex = this.noMatch(it, textIndex, match);
|
|
2272
|
-
textIndex++;
|
|
2273
|
-
continue;
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
// First match
|
|
2277
|
-
if (match.firstMatchIndex === -1) {
|
|
2278
|
-
match.firstMatchIndex = textIndex;
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
// Partial match
|
|
2282
|
-
if (match.delimiterIndex !== delimiterPattern.length - 1) {
|
|
2283
|
-
match.delimiterIndex++;
|
|
2284
|
-
textIndex++;
|
|
2285
|
-
continue;
|
|
2286
|
-
}
|
|
2520
|
+
// First match
|
|
2521
|
+
if (this.firstMatchIndex === -1) {
|
|
2522
|
+
this.firstMatchIndex = textIndex;
|
|
2523
|
+
}
|
|
2287
2524
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2525
|
+
// Partial match
|
|
2526
|
+
if (this.lookForDelimiterIndex !== delimiterPattern.length - 1) {
|
|
2527
|
+
this.lookForDelimiterIndex++;
|
|
2290
2528
|
textIndex++;
|
|
2529
|
+
continue;
|
|
2291
2530
|
}
|
|
2292
|
-
|
|
2531
|
+
|
|
2532
|
+
// Full delimiter match
|
|
2533
|
+
textIndex = this.fullMatch(it, textIndex, delimiters);
|
|
2534
|
+
textIndex++;
|
|
2293
2535
|
}
|
|
2294
|
-
return delimiters;
|
|
2295
2536
|
}
|
|
2296
|
-
noMatch(it, textIndex
|
|
2537
|
+
noMatch(it, textIndex) {
|
|
2297
2538
|
//
|
|
2298
2539
|
// Go back to first open node
|
|
2299
2540
|
//
|
|
@@ -2302,57 +2543,76 @@ class DelimiterSearcher {
|
|
|
2302
2543
|
// For instance:
|
|
2303
2544
|
// Delimiter is '{!' and template text contains the string '{{!'
|
|
2304
2545
|
//
|
|
2305
|
-
if (
|
|
2306
|
-
const node = first(
|
|
2546
|
+
if (this.firstMatchIndex !== -1) {
|
|
2547
|
+
const node = first(this.matchOpenNodes);
|
|
2307
2548
|
it.setCurrent(node);
|
|
2308
|
-
textIndex =
|
|
2549
|
+
textIndex = this.firstMatchIndex;
|
|
2309
2550
|
}
|
|
2310
2551
|
|
|
2311
2552
|
// Update state
|
|
2312
|
-
|
|
2553
|
+
this.resetMatch();
|
|
2313
2554
|
if (textIndex < it.node.textContent.length - 1) {
|
|
2314
|
-
|
|
2555
|
+
this.matchOpenNodes.push(it.node);
|
|
2315
2556
|
}
|
|
2316
2557
|
return textIndex;
|
|
2317
2558
|
}
|
|
2318
|
-
fullMatch(it, textIndex,
|
|
2559
|
+
fullMatch(it, textIndex, delimiters) {
|
|
2319
2560
|
// Move all delimiters characters to the same text node
|
|
2320
|
-
if (
|
|
2321
|
-
const firstNode = first(
|
|
2322
|
-
const lastNode = last(
|
|
2561
|
+
if (this.matchOpenNodes.length > 1) {
|
|
2562
|
+
const firstNode = first(this.matchOpenNodes);
|
|
2563
|
+
const lastNode = last(this.matchOpenNodes);
|
|
2323
2564
|
officeMarkup.modify.joinTextNodesRange(firstNode, lastNode);
|
|
2324
2565
|
textIndex += firstNode.textContent.length - it.node.textContent.length;
|
|
2325
2566
|
it.setCurrent(firstNode);
|
|
2326
2567
|
}
|
|
2327
2568
|
|
|
2328
2569
|
// Store delimiter
|
|
2329
|
-
const delimiterMark = this.
|
|
2570
|
+
const delimiterMark = this.createCurrentDelimiterMark();
|
|
2330
2571
|
delimiters.push(delimiterMark);
|
|
2331
2572
|
|
|
2332
2573
|
// Update state
|
|
2333
|
-
lookForOpenDelimiter = !lookForOpenDelimiter;
|
|
2334
|
-
|
|
2574
|
+
this.lookForOpenDelimiter = !this.lookForOpenDelimiter;
|
|
2575
|
+
this.resetMatch();
|
|
2335
2576
|
if (textIndex < it.node.textContent.length - 1) {
|
|
2336
|
-
|
|
2577
|
+
this.matchOpenNodes.push(it.node);
|
|
2337
2578
|
}
|
|
2338
|
-
return
|
|
2339
|
-
}
|
|
2340
|
-
shouldSearchNode(it) {
|
|
2341
|
-
if (!xml.query.isTextNode(it.node)) return false;
|
|
2342
|
-
if (!it.node.textContent) return false;
|
|
2343
|
-
if (!it.node.parentNode) return false;
|
|
2344
|
-
if (!officeMarkup.query.isTextNode(it.node.parentNode)) return false;
|
|
2345
|
-
return true;
|
|
2579
|
+
return textIndex;
|
|
2346
2580
|
}
|
|
2347
|
-
|
|
2581
|
+
createCurrentDelimiterMark() {
|
|
2348
2582
|
return {
|
|
2349
|
-
|
|
2350
|
-
isOpen:
|
|
2351
|
-
|
|
2583
|
+
placement: TagPlacement.TextNode,
|
|
2584
|
+
isOpen: this.lookForOpenDelimiter,
|
|
2585
|
+
index: this.firstMatchIndex,
|
|
2586
|
+
xmlTextNode: this.matchOpenNodes[0]
|
|
2352
2587
|
};
|
|
2353
2588
|
}
|
|
2354
2589
|
}
|
|
2355
2590
|
|
|
2591
|
+
class DelimiterSearcher {
|
|
2592
|
+
constructor(delimiters, maxXmlDepth) {
|
|
2593
|
+
if (!delimiters) {
|
|
2594
|
+
throw new InternalArgumentMissingError("delimiters");
|
|
2595
|
+
}
|
|
2596
|
+
if (!maxXmlDepth) {
|
|
2597
|
+
throw new InternalArgumentMissingError("maxXmlDepth");
|
|
2598
|
+
}
|
|
2599
|
+
this.delimiters = delimiters;
|
|
2600
|
+
this.maxXmlDepth = maxXmlDepth;
|
|
2601
|
+
}
|
|
2602
|
+
findDelimiters(node) {
|
|
2603
|
+
const delimiters = [];
|
|
2604
|
+
const it = new XmlTreeIterator(node, this.maxXmlDepth);
|
|
2605
|
+
const attributeSearcher = new AttributesDelimiterSearcher(this.delimiters);
|
|
2606
|
+
const textSearcher = new TextNodesDelimiterSearcher(this.delimiters.tagStart, this.delimiters.tagEnd);
|
|
2607
|
+
while (it.node) {
|
|
2608
|
+
attributeSearcher.processNode(it, delimiters);
|
|
2609
|
+
textSearcher.processNode(it, delimiters);
|
|
2610
|
+
it.next();
|
|
2611
|
+
}
|
|
2612
|
+
return delimiters;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2356
2616
|
class ScopeData {
|
|
2357
2617
|
static defaultResolver(args) {
|
|
2358
2618
|
let result;
|
|
@@ -2394,59 +2654,141 @@ class ScopeData {
|
|
|
2394
2654
|
}
|
|
2395
2655
|
}
|
|
2396
2656
|
|
|
2397
|
-
const TagDisposition = Object.freeze({
|
|
2398
|
-
Open: "Open",
|
|
2399
|
-
Close: "Close",
|
|
2400
|
-
SelfClosed: "SelfClosed"
|
|
2401
|
-
});
|
|
2402
|
-
|
|
2403
2657
|
class TagParser {
|
|
2404
2658
|
constructor(delimiters) {
|
|
2405
2659
|
if (!delimiters) throw new InternalArgumentMissingError("delimiters");
|
|
2406
2660
|
this.delimiters = delimiters;
|
|
2407
|
-
|
|
2408
|
-
this.tagRegex = new RegExp(`^${Regex.escape(delimiters.tagStart)}(?<tagName>.*?)(${tagOptionsRegex})?${Regex.escape(delimiters.tagEnd)}`, 'm');
|
|
2661
|
+
this.tagRegex = tagRegex(delimiters);
|
|
2409
2662
|
}
|
|
2410
2663
|
parse(delimiters) {
|
|
2411
2664
|
const tags = [];
|
|
2412
|
-
let
|
|
2413
|
-
let
|
|
2665
|
+
let openedTextDelimiter;
|
|
2666
|
+
let openedAttributeDelimiter;
|
|
2414
2667
|
for (let i = 0; i < delimiters.length; i++) {
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
if (!openedTag && !delimiter.isOpen) {
|
|
2419
|
-
const closeTagText = delimiter.xmlTextNode.textContent;
|
|
2420
|
-
throw new MissingStartDelimiterError(closeTagText);
|
|
2668
|
+
if (delimiters[i].placement === TagPlacement.TextNode) {
|
|
2669
|
+
openedTextDelimiter = this.processDelimiter(delimiters, i, openedTextDelimiter, tags);
|
|
2670
|
+
continue;
|
|
2421
2671
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
const openTagText = openedDelimiter.xmlTextNode.textContent;
|
|
2426
|
-
throw new MissingCloseDelimiterError(openTagText);
|
|
2672
|
+
if (delimiters[i].placement === TagPlacement.Attribute) {
|
|
2673
|
+
openedAttributeDelimiter = this.processDelimiter(delimiters, i, openedAttributeDelimiter, tags);
|
|
2674
|
+
continue;
|
|
2427
2675
|
}
|
|
2676
|
+
throw new Error(`Unexpected delimiter placement value "${delimiters[i].placement}"`);
|
|
2677
|
+
}
|
|
2678
|
+
return tags;
|
|
2679
|
+
}
|
|
2680
|
+
processDelimiter(delimiters, i, openedDelimiter, tags) {
|
|
2681
|
+
const delimiter = delimiters[i];
|
|
2682
|
+
|
|
2683
|
+
// Close before open
|
|
2684
|
+
if (!openedDelimiter && !delimiter.isOpen) {
|
|
2685
|
+
const closeTagText = this.getPartialTagText(delimiter);
|
|
2686
|
+
throw new MissingStartDelimiterError(closeTagText);
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// Open before close
|
|
2690
|
+
if (openedDelimiter && delimiter.isOpen) {
|
|
2691
|
+
const openTagText = this.getPartialTagText(openedDelimiter);
|
|
2692
|
+
throw new MissingCloseDelimiterError(openTagText);
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// Valid open
|
|
2696
|
+
if (!openedDelimiter && delimiter.isOpen) {
|
|
2697
|
+
openedDelimiter = delimiter;
|
|
2698
|
+
}
|
|
2428
2699
|
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2700
|
+
// Valid close
|
|
2701
|
+
if (openedDelimiter && !delimiter.isOpen) {
|
|
2702
|
+
// Create the tag
|
|
2703
|
+
const partialTag = this.processDelimiterPair(openedDelimiter, delimiter, i, delimiters);
|
|
2704
|
+
const tag = this.populateTagFields(partialTag);
|
|
2705
|
+
tags.push(tag);
|
|
2706
|
+
openedDelimiter = null;
|
|
2707
|
+
}
|
|
2708
|
+
return openedDelimiter;
|
|
2709
|
+
}
|
|
2710
|
+
getPartialTagText(delimiter) {
|
|
2711
|
+
if (delimiter.placement === TagPlacement.TextNode) {
|
|
2712
|
+
return delimiter.xmlTextNode.textContent;
|
|
2713
|
+
}
|
|
2714
|
+
if (delimiter.placement === TagPlacement.Attribute) {
|
|
2715
|
+
return delimiter.xmlNode.attributes[delimiter.attributeName];
|
|
2716
|
+
}
|
|
2717
|
+
throw new Error(`Unexpected delimiter placement value "${delimiter.placement}"`);
|
|
2718
|
+
}
|
|
2719
|
+
processDelimiterPair(openDelimiter, closeDelimiter, closeDelimiterIndex, allDelimiters) {
|
|
2720
|
+
if (openDelimiter.placement === TagPlacement.TextNode && closeDelimiter.placement === TagPlacement.TextNode) {
|
|
2721
|
+
return this.processTextNodeDelimiterPair(openDelimiter, closeDelimiter, closeDelimiterIndex, allDelimiters);
|
|
2722
|
+
}
|
|
2723
|
+
if (openDelimiter.placement === TagPlacement.Attribute && closeDelimiter.placement === TagPlacement.Attribute) {
|
|
2724
|
+
return this.processAttributeDelimiterPair(openDelimiter, closeDelimiter);
|
|
2725
|
+
}
|
|
2726
|
+
throw new Error(`Unexpected delimiter placement values. Open delimiter: "${openDelimiter.placement}", Close delimiter: "${closeDelimiter.placement}"`);
|
|
2727
|
+
}
|
|
2728
|
+
processTextNodeDelimiterPair(openDelimiter, closeDelimiter, closeDelimiterIndex, allDelimiters) {
|
|
2729
|
+
// Verify tag delimiters are in the same paragraph
|
|
2730
|
+
const openTextNode = openDelimiter.xmlTextNode;
|
|
2731
|
+
const closeTextNode = closeDelimiter.xmlTextNode;
|
|
2732
|
+
const sameNode = openTextNode === closeTextNode;
|
|
2733
|
+
if (!sameNode) {
|
|
2734
|
+
const startParagraph = officeMarkup.query.containingParagraphNode(openTextNode);
|
|
2735
|
+
const endParagraph = officeMarkup.query.containingParagraphNode(closeTextNode);
|
|
2736
|
+
if (startParagraph !== endParagraph) {
|
|
2737
|
+
throw new MissingCloseDelimiterError(openTextNode.textContent);
|
|
2433
2738
|
}
|
|
2739
|
+
}
|
|
2434
2740
|
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
tags.push(openedTag);
|
|
2445
|
-
openedTag = null;
|
|
2446
|
-
openedDelimiter = null;
|
|
2741
|
+
// Verify no inline drawing in the middle
|
|
2742
|
+
const startRun = officeMarkup.query.containingRunNode(openTextNode);
|
|
2743
|
+
const endRun = officeMarkup.query.containingRunNode(closeTextNode);
|
|
2744
|
+
let currentRun = startRun;
|
|
2745
|
+
while (currentRun && currentRun !== endRun) {
|
|
2746
|
+
const drawing = currentRun.childNodes?.find(child => child.nodeName === OmlNode.W.Drawing);
|
|
2747
|
+
if (!drawing) {
|
|
2748
|
+
currentRun = currentRun.nextSibling;
|
|
2749
|
+
continue;
|
|
2447
2750
|
}
|
|
2751
|
+
const inline = drawing.childNodes?.find(child => child.nodeName === OmlNode.Wp.Inline);
|
|
2752
|
+
if (!inline) {
|
|
2753
|
+
currentRun = currentRun.nextSibling;
|
|
2754
|
+
continue;
|
|
2755
|
+
}
|
|
2756
|
+
throw new MissingCloseDelimiterError(openTextNode.textContent);
|
|
2448
2757
|
}
|
|
2449
|
-
|
|
2758
|
+
|
|
2759
|
+
// Normalize the underlying xml structure
|
|
2760
|
+
// (make sure the tag's node only includes the tag's text)
|
|
2761
|
+
this.normalizeTextTagNodes(openDelimiter, closeDelimiter, closeDelimiterIndex, allDelimiters);
|
|
2762
|
+
|
|
2763
|
+
// Create the tag
|
|
2764
|
+
const tag = {
|
|
2765
|
+
placement: TagPlacement.TextNode,
|
|
2766
|
+
xmlTextNode: openDelimiter.xmlTextNode,
|
|
2767
|
+
rawText: openDelimiter.xmlTextNode.textContent
|
|
2768
|
+
};
|
|
2769
|
+
return tag;
|
|
2770
|
+
}
|
|
2771
|
+
processAttributeDelimiterPair(openDelimiter, closeDelimiter) {
|
|
2772
|
+
// Verify tag delimiters are in the same attribute
|
|
2773
|
+
const openNode = openDelimiter.xmlNode;
|
|
2774
|
+
const closeNode = closeDelimiter.xmlNode;
|
|
2775
|
+
if (openNode !== closeNode) {
|
|
2776
|
+
throw new MissingCloseDelimiterError(openNode.attributes[openDelimiter.attributeName]);
|
|
2777
|
+
}
|
|
2778
|
+
if (openDelimiter.attributeName !== closeDelimiter.attributeName) {
|
|
2779
|
+
throw new MissingCloseDelimiterError(openNode.attributes[openDelimiter.attributeName]);
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
// Create the tag
|
|
2783
|
+
const attrValue = openNode.attributes[openDelimiter.attributeName];
|
|
2784
|
+
const tagText = attrValue.substring(openDelimiter.index, closeDelimiter.index + this.delimiters.tagEnd.length);
|
|
2785
|
+
const tag = {
|
|
2786
|
+
placement: TagPlacement.Attribute,
|
|
2787
|
+
xmlNode: openNode,
|
|
2788
|
+
attributeName: openDelimiter.attributeName,
|
|
2789
|
+
rawText: tagText
|
|
2790
|
+
};
|
|
2791
|
+
return tag;
|
|
2450
2792
|
}
|
|
2451
2793
|
|
|
2452
2794
|
/**
|
|
@@ -2457,19 +2799,12 @@ class TagParser {
|
|
|
2457
2799
|
* Text node before: "some text {some tag} some more text"
|
|
2458
2800
|
* Text nodes after: [ "some text ", "{some tag}", " some more text" ]
|
|
2459
2801
|
*/
|
|
2460
|
-
|
|
2802
|
+
normalizeTextTagNodes(openDelimiter, closeDelimiter, closeDelimiterIndex, allDelimiters) {
|
|
2461
2803
|
let startTextNode = openDelimiter.xmlTextNode;
|
|
2462
2804
|
let endTextNode = closeDelimiter.xmlTextNode;
|
|
2463
2805
|
const sameNode = startTextNode === endTextNode;
|
|
2464
|
-
if (!sameNode) {
|
|
2465
|
-
const startParagraph = officeMarkup.query.containingParagraphNode(startTextNode);
|
|
2466
|
-
const endParagraph = officeMarkup.query.containingParagraphNode(endTextNode);
|
|
2467
|
-
if (startParagraph !== endParagraph) {
|
|
2468
|
-
throw new MissingCloseDelimiterError(startTextNode.textContent);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
2806
|
|
|
2472
|
-
//
|
|
2807
|
+
// Trim start
|
|
2473
2808
|
if (openDelimiter.index > 0) {
|
|
2474
2809
|
officeMarkup.modify.splitTextNode(startTextNode, openDelimiter.index, true);
|
|
2475
2810
|
if (sameNode) {
|
|
@@ -2477,7 +2812,7 @@ class TagParser {
|
|
|
2477
2812
|
}
|
|
2478
2813
|
}
|
|
2479
2814
|
|
|
2480
|
-
//
|
|
2815
|
+
// Trim end
|
|
2481
2816
|
if (closeDelimiter.index < endTextNode.textContent.length - 1) {
|
|
2482
2817
|
endTextNode = officeMarkup.modify.splitTextNode(endTextNode, closeDelimiter.index + this.delimiters.tagEnd.length, true);
|
|
2483
2818
|
if (sameNode) {
|
|
@@ -2485,43 +2820,46 @@ class TagParser {
|
|
|
2485
2820
|
}
|
|
2486
2821
|
}
|
|
2487
2822
|
|
|
2488
|
-
//
|
|
2823
|
+
// Join nodes
|
|
2489
2824
|
if (!sameNode) {
|
|
2490
2825
|
officeMarkup.modify.joinTextNodesRange(startTextNode, endTextNode);
|
|
2491
2826
|
endTextNode = startTextNode;
|
|
2492
2827
|
}
|
|
2493
2828
|
|
|
2494
|
-
//
|
|
2829
|
+
// Update offsets of next delimiters
|
|
2495
2830
|
for (let i = closeDelimiterIndex + 1; i < allDelimiters.length; i++) {
|
|
2496
2831
|
let updated = false;
|
|
2497
2832
|
const curDelimiter = allDelimiters[i];
|
|
2498
|
-
if (curDelimiter.xmlTextNode === openDelimiter.xmlTextNode) {
|
|
2833
|
+
if (curDelimiter.placement === TagPlacement.TextNode && curDelimiter.xmlTextNode === openDelimiter.xmlTextNode) {
|
|
2499
2834
|
curDelimiter.index -= openDelimiter.index;
|
|
2500
2835
|
updated = true;
|
|
2501
2836
|
}
|
|
2502
|
-
if (curDelimiter.xmlTextNode === closeDelimiter.xmlTextNode) {
|
|
2837
|
+
if (curDelimiter.placement === TagPlacement.TextNode && curDelimiter.xmlTextNode === closeDelimiter.xmlTextNode) {
|
|
2503
2838
|
curDelimiter.index -= closeDelimiter.index + this.delimiters.tagEnd.length;
|
|
2504
2839
|
updated = true;
|
|
2505
2840
|
}
|
|
2506
2841
|
if (!updated) break;
|
|
2507
2842
|
}
|
|
2508
2843
|
|
|
2509
|
-
//
|
|
2844
|
+
// Update references
|
|
2510
2845
|
openDelimiter.xmlTextNode = startTextNode;
|
|
2511
2846
|
closeDelimiter.xmlTextNode = endTextNode;
|
|
2512
2847
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2848
|
+
populateTagFields(partialTag) {
|
|
2849
|
+
if (!partialTag.rawText) {
|
|
2850
|
+
throw new InternalError("tag.rawText is required");
|
|
2851
|
+
}
|
|
2852
|
+
const tag = partialTag;
|
|
2853
|
+
const tagParts = tag.rawText.match(this.tagRegex);
|
|
2516
2854
|
const tagName = (tagParts.groups?.["tagName"] || '').trim();
|
|
2517
2855
|
|
|
2518
|
-
// Ignoring empty tags
|
|
2856
|
+
// Ignoring empty tags
|
|
2519
2857
|
if (!tagName?.length) {
|
|
2520
2858
|
tag.disposition = TagDisposition.SelfClosed;
|
|
2521
|
-
return;
|
|
2859
|
+
return tag;
|
|
2522
2860
|
}
|
|
2523
2861
|
|
|
2524
|
-
// Tag options
|
|
2862
|
+
// Tag options
|
|
2525
2863
|
const tagOptionsText = (tagParts.groups?.["tagOptions"] || '').trim();
|
|
2526
2864
|
if (tagOptionsText) {
|
|
2527
2865
|
try {
|
|
@@ -2531,23 +2869,24 @@ class TagParser {
|
|
|
2531
2869
|
}
|
|
2532
2870
|
}
|
|
2533
2871
|
|
|
2534
|
-
// Container open tag
|
|
2872
|
+
// Container open tag
|
|
2535
2873
|
if (tagName.startsWith(this.delimiters.containerTagOpen)) {
|
|
2536
2874
|
tag.disposition = TagDisposition.Open;
|
|
2537
2875
|
tag.name = tagName.slice(this.delimiters.containerTagOpen.length).trim();
|
|
2538
|
-
return;
|
|
2876
|
+
return tag;
|
|
2539
2877
|
}
|
|
2540
2878
|
|
|
2541
|
-
// Container close tag
|
|
2879
|
+
// Container close tag
|
|
2542
2880
|
if (tagName.startsWith(this.delimiters.containerTagClose)) {
|
|
2543
2881
|
tag.disposition = TagDisposition.Close;
|
|
2544
2882
|
tag.name = tagName.slice(this.delimiters.containerTagClose.length).trim();
|
|
2545
|
-
return;
|
|
2883
|
+
return tag;
|
|
2546
2884
|
}
|
|
2547
2885
|
|
|
2548
|
-
// Self-closed tag
|
|
2886
|
+
// Self-closed tag
|
|
2549
2887
|
tag.disposition = TagDisposition.SelfClosed;
|
|
2550
2888
|
tag.name = tagName;
|
|
2889
|
+
return tag;
|
|
2551
2890
|
}
|
|
2552
2891
|
}
|
|
2553
2892
|
|
|
@@ -2584,12 +2923,228 @@ class TemplatePlugin {
|
|
|
2584
2923
|
}
|
|
2585
2924
|
}
|
|
2586
2925
|
|
|
2926
|
+
function nameFromId(imageId) {
|
|
2927
|
+
return `Picture ${imageId}`;
|
|
2928
|
+
}
|
|
2929
|
+
function pixelsToEmu(pixels) {
|
|
2930
|
+
// https://stackoverflow.com/questions/20194403/openxml-distance-size-units
|
|
2931
|
+
// https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vml-units#other-units-of-measurement
|
|
2932
|
+
// https://en.wikipedia.org/wiki/Office_Open_XML_file_formats#DrawingML
|
|
2933
|
+
// http://www.java2s.com/Code/CSharp/2D-Graphics/ConvertpixelstoEMUEMUtopixels.htm
|
|
2934
|
+
|
|
2935
|
+
return Math.round(pixels * 9525);
|
|
2936
|
+
}
|
|
2937
|
+
function transparencyPercentToAlpha(transparencyPercent) {
|
|
2938
|
+
if (transparencyPercent < 0 || transparencyPercent > 100) {
|
|
2939
|
+
throw new TemplateDataError(`Transparency percent must be between 0 and 100, but was ${transparencyPercent}.`);
|
|
2940
|
+
}
|
|
2941
|
+
return Math.round((100 - transparencyPercent) * 1000);
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
function createImage(imageId, relId, content) {
|
|
2945
|
+
// http://officeopenxml.com/drwPicInline.php
|
|
2946
|
+
|
|
2947
|
+
//
|
|
2948
|
+
// Performance note:
|
|
2949
|
+
//
|
|
2950
|
+
// I've tried to improve the markup generation performance by parsing
|
|
2951
|
+
// the string once and caching the result (and of course customizing it
|
|
2952
|
+
// per image) but it made no change whatsoever (in both cases 1000 items
|
|
2953
|
+
// loop takes around 8 seconds on my machine) so I'm sticking with this
|
|
2954
|
+
// approach which I find to be more readable.
|
|
2955
|
+
//
|
|
2956
|
+
|
|
2957
|
+
const name = nameFromId(imageId);
|
|
2958
|
+
const markupText = `
|
|
2959
|
+
<w:drawing>
|
|
2960
|
+
<wp:inline distT="0" distB="0" distL="0" distR="0">
|
|
2961
|
+
<wp:extent cx="${pixelsToEmu(content.width)}" cy="${pixelsToEmu(content.height)}"/>
|
|
2962
|
+
<wp:effectExtent l="0" t="0" r="0" b="0"/>
|
|
2963
|
+
${docProperties(imageId, name, content)}
|
|
2964
|
+
<wp:cNvGraphicFramePr>
|
|
2965
|
+
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
|
|
2966
|
+
</wp:cNvGraphicFramePr>
|
|
2967
|
+
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
|
2968
|
+
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
2969
|
+
${pictureMarkup(imageId, relId, name, content)}
|
|
2970
|
+
</a:graphicData>
|
|
2971
|
+
</a:graphic>
|
|
2972
|
+
</wp:inline>
|
|
2973
|
+
</w:drawing>
|
|
2974
|
+
`;
|
|
2975
|
+
const markupXml = xml.parser.parse(markupText);
|
|
2976
|
+
xml.modify.removeEmptyTextNodes(markupXml); // remove whitespace
|
|
2977
|
+
|
|
2978
|
+
return markupXml;
|
|
2979
|
+
}
|
|
2980
|
+
function docProperties(imageId, name, content) {
|
|
2981
|
+
if (content.altText) {
|
|
2982
|
+
return `<wp:docPr id="${imageId}" name="${name}" descr="${content.altText}"/>`;
|
|
2983
|
+
}
|
|
2984
|
+
return `
|
|
2985
|
+
<wp:docPr id="${imageId}" name="${name}">
|
|
2986
|
+
<a:extLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
|
2987
|
+
<a:ext uri="{C183D7F6-B498-43B3-948B-1728B52AA6E4}">
|
|
2988
|
+
<adec:decorative xmlns:adec="http://schemas.microsoft.com/office/drawing/2017/decorative" val="1"/>
|
|
2989
|
+
</a:ext>
|
|
2990
|
+
</a:extLst>
|
|
2991
|
+
</wp:docPr>
|
|
2992
|
+
`;
|
|
2993
|
+
}
|
|
2994
|
+
function pictureMarkup(imageId, relId, name, content) {
|
|
2995
|
+
// http://officeopenxml.com/drwPic.php
|
|
2996
|
+
|
|
2997
|
+
// Legend:
|
|
2998
|
+
// nvPicPr - non-visual picture properties - id, name, etc.
|
|
2999
|
+
// blipFill - binary large image (or) picture fill - image size, image fill, etc.
|
|
3000
|
+
// spPr - shape properties - frame size, frame fill, etc.
|
|
3001
|
+
|
|
3002
|
+
return `
|
|
3003
|
+
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
3004
|
+
<pic:nvPicPr>
|
|
3005
|
+
<pic:cNvPr id="${imageId}" name="${name}"/>
|
|
3006
|
+
<pic:cNvPicPr>
|
|
3007
|
+
<a:picLocks noChangeAspect="1" noChangeArrowheads="1"/>
|
|
3008
|
+
</pic:cNvPicPr>
|
|
3009
|
+
</pic:nvPicPr>
|
|
3010
|
+
<pic:blipFill>
|
|
3011
|
+
<a:blip r:embed="${relId}">
|
|
3012
|
+
${transparencyMarkup(content.transparencyPercent)}
|
|
3013
|
+
<a:extLst>
|
|
3014
|
+
<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
|
|
3015
|
+
<a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/>
|
|
3016
|
+
</a:ext>
|
|
3017
|
+
</a:extLst>
|
|
3018
|
+
</a:blip>
|
|
3019
|
+
<a:srcRect/>
|
|
3020
|
+
<a:stretch>
|
|
3021
|
+
<a:fillRect/>
|
|
3022
|
+
</a:stretch>
|
|
3023
|
+
</pic:blipFill>
|
|
3024
|
+
<pic:spPr bwMode="auto">
|
|
3025
|
+
<a:xfrm>
|
|
3026
|
+
<a:off x="0" y="0"/>
|
|
3027
|
+
<a:ext cx="${pixelsToEmu(content.width)}" cy="${pixelsToEmu(content.height)}"/>
|
|
3028
|
+
</a:xfrm>
|
|
3029
|
+
<a:prstGeom prst="rect">
|
|
3030
|
+
<a:avLst/>
|
|
3031
|
+
</a:prstGeom>
|
|
3032
|
+
<a:noFill/>
|
|
3033
|
+
<a:ln>
|
|
3034
|
+
<a:noFill/>
|
|
3035
|
+
</a:ln>
|
|
3036
|
+
</pic:spPr>
|
|
3037
|
+
</pic:pic>
|
|
3038
|
+
`;
|
|
3039
|
+
}
|
|
3040
|
+
function transparencyMarkup(transparencyPercent) {
|
|
3041
|
+
if (transparencyPercent === null || transparencyPercent === undefined) {
|
|
3042
|
+
return '';
|
|
3043
|
+
}
|
|
3044
|
+
const alpha = transparencyPercentToAlpha(transparencyPercent);
|
|
3045
|
+
return `<a:alphaModFix amt="${alpha}" />`;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function updateImage(drawingContainerNode, imageId, relId, content) {
|
|
3049
|
+
const inlineNode = xml.query.findByPath(drawingContainerNode, XmlNodeType.General, OmlNode.Wp.Inline);
|
|
3050
|
+
const floatingNode = xml.query.findByPath(drawingContainerNode, XmlNodeType.General, OmlNode.Wp.FloatingAnchor);
|
|
3051
|
+
const drawingNode = inlineNode || floatingNode;
|
|
3052
|
+
if (!inlineNode && !floatingNode) {
|
|
3053
|
+
throw new MalformedFileError("Invalid drawing container node. Expected inline or floating anchor node.");
|
|
3054
|
+
}
|
|
3055
|
+
const pictureNode = xml.query.findByPath(drawingNode, XmlNodeType.General, OmlNode.A.Graphic, OmlNode.A.GraphicData, OmlNode.Pic.Pic);
|
|
3056
|
+
if (!pictureNode) {
|
|
3057
|
+
throw new MalformedFileError("Invalid drawing container node. Expected picture node.");
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
// Set rel ID
|
|
3061
|
+
setRelId(pictureNode, relId);
|
|
3062
|
+
|
|
3063
|
+
// Update non-visual properties
|
|
3064
|
+
updateNonVisualProps(drawingNode, pictureNode, imageId, content);
|
|
3065
|
+
|
|
3066
|
+
// Update size
|
|
3067
|
+
updateSize(drawingNode, pictureNode, content);
|
|
3068
|
+
|
|
3069
|
+
// Update transparency
|
|
3070
|
+
updateTransparency(pictureNode, content);
|
|
3071
|
+
}
|
|
3072
|
+
function setRelId(pictureNode, relId) {
|
|
3073
|
+
const blipNode = xml.query.findByPath(pictureNode, XmlNodeType.General, OmlNode.Pic.BlipFill, OmlNode.A.Blip);
|
|
3074
|
+
pictureNode.attributes["r:embed"] = relId;
|
|
3075
|
+
blipNode.attributes["r:embed"] = relId;
|
|
3076
|
+
}
|
|
3077
|
+
function updateNonVisualProps(drawingNode, pictureNode, imageId, content) {
|
|
3078
|
+
const docPrNode = xml.query.findByPath(drawingNode, XmlNodeType.General, OmlNode.Wp.DocPr);
|
|
3079
|
+
if (!docPrNode) {
|
|
3080
|
+
throw new MalformedFileError("Cannot find doc properties node.");
|
|
3081
|
+
}
|
|
3082
|
+
const nvPicPrNode = xml.query.findByPath(pictureNode, XmlNodeType.General, OmlNode.Pic.NvPicPr, OmlNode.Pic.CnVPr);
|
|
3083
|
+
if (!nvPicPrNode) {
|
|
3084
|
+
throw new MalformedFileError("Cannot find non-visual picture properties node.");
|
|
3085
|
+
}
|
|
3086
|
+
docPrNode.attributes["id"] = imageId.toString();
|
|
3087
|
+
nvPicPrNode.attributes["id"] = imageId.toString();
|
|
3088
|
+
const imageName = nameFromId(imageId);
|
|
3089
|
+
docPrNode.attributes["name"] = imageName;
|
|
3090
|
+
nvPicPrNode.attributes["name"] = imageName;
|
|
3091
|
+
if (content.altText) {
|
|
3092
|
+
docPrNode.attributes["descr"] = content.altText;
|
|
3093
|
+
nvPicPrNode.attributes["descr"] = content.altText;
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
function updateSize(drawingNode, pictureNode, content) {
|
|
3097
|
+
if (typeof content.width !== 'number' && typeof content.height !== 'number') {
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
const drawingExtentNode = xml.query.findByPath(drawingNode, XmlNodeType.General, OmlNode.Wp.Extent);
|
|
3101
|
+
if (!drawingExtentNode) {
|
|
3102
|
+
throw new MalformedFileError("Cannot find drawing extent node.");
|
|
3103
|
+
}
|
|
3104
|
+
const pictureExtentNode = xml.query.findByPath(pictureNode, XmlNodeType.General, OmlNode.Pic.SpPr, OmlNode.Pic.Xfrm, OmlNode.Pic.Ext);
|
|
3105
|
+
if (!pictureExtentNode) {
|
|
3106
|
+
throw new MalformedFileError("Cannot find picture extent node.");
|
|
3107
|
+
}
|
|
3108
|
+
if (typeof content.width === 'number') {
|
|
3109
|
+
const widthEmu = pixelsToEmu(content.width);
|
|
3110
|
+
drawingExtentNode.attributes["cx"] = widthEmu.toString();
|
|
3111
|
+
pictureExtentNode.attributes["cx"] = widthEmu.toString();
|
|
3112
|
+
}
|
|
3113
|
+
if (typeof content.height === 'number') {
|
|
3114
|
+
const heightEmu = pixelsToEmu(content.height);
|
|
3115
|
+
drawingExtentNode.attributes["cy"] = heightEmu.toString();
|
|
3116
|
+
pictureExtentNode.attributes["cy"] = heightEmu.toString();
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
function updateTransparency(pictureNode, content) {
|
|
3120
|
+
if (content.transparencyPercent === null || content.transparencyPercent === undefined) {
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
const blipNode = xml.query.findByPath(pictureNode, XmlNodeType.General, OmlNode.Pic.BlipFill, OmlNode.A.Blip);
|
|
3124
|
+
if (!blipNode) {
|
|
3125
|
+
throw new MalformedFileError("Cannot find blip node.");
|
|
3126
|
+
}
|
|
3127
|
+
let alphaNode = xml.query.findByPath(blipNode, XmlNodeType.General, OmlNode.A.AlphaModFix);
|
|
3128
|
+
|
|
3129
|
+
// If the alpha node is not present, create it
|
|
3130
|
+
if (!alphaNode) {
|
|
3131
|
+
alphaNode = xml.create.generalNode(OmlNode.A.AlphaModFix, {
|
|
3132
|
+
attributes: {}
|
|
3133
|
+
});
|
|
3134
|
+
xml.modify.insertChild(blipNode, alphaNode, 0);
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// Set the alpha value
|
|
3138
|
+
const alpha = transparencyPercentToAlpha(content.transparencyPercent);
|
|
3139
|
+
alphaNode.attributes["amt"] = alpha.toString();
|
|
3140
|
+
}
|
|
3141
|
+
|
|
2587
3142
|
class ImagePlugin extends TemplatePlugin {
|
|
2588
3143
|
contentType = 'image';
|
|
2589
3144
|
async simpleTagReplacements(tag, data, context) {
|
|
2590
3145
|
const content = data.getScopeData();
|
|
2591
3146
|
if (!content || !content.source) {
|
|
2592
|
-
officeMarkup.modify.removeTag(tag
|
|
3147
|
+
officeMarkup.modify.removeTag(tag);
|
|
2593
3148
|
return;
|
|
2594
3149
|
}
|
|
2595
3150
|
|
|
@@ -2599,12 +3154,25 @@ class ImagePlugin extends TemplatePlugin {
|
|
|
2599
3154
|
const relId = await context.currentPart.rels.add(mediaFilePath, relType);
|
|
2600
3155
|
await context.docx.contentTypes.ensureContentType(content.format);
|
|
2601
3156
|
|
|
2602
|
-
//
|
|
3157
|
+
// Generate a unique image ID
|
|
2603
3158
|
const imageId = await this.getNextImageId(context);
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
3159
|
+
|
|
3160
|
+
// For text tags, create xml markup from scratch
|
|
3161
|
+
if (tag.placement === TagPlacement.TextNode) {
|
|
3162
|
+
const imageXml = createImage(imageId, relId, content);
|
|
3163
|
+
const wordTextNode = officeMarkup.query.containingTextNode(tag.xmlTextNode);
|
|
3164
|
+
xml.modify.insertAfter(imageXml, wordTextNode);
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
// For attribute tags, modify the existing markup
|
|
3168
|
+
if (tag.placement === TagPlacement.Attribute) {
|
|
3169
|
+
const drawingNode = xml.query.findParentByName(tag.xmlNode, OmlNode.W.Drawing);
|
|
3170
|
+
if (!drawingNode) {
|
|
3171
|
+
throw new TemplateSyntaxError(`Cannot find placeholder image for tag "${tag.rawText}".`);
|
|
3172
|
+
}
|
|
3173
|
+
updateImage(drawingNode, imageId, relId, content);
|
|
3174
|
+
}
|
|
3175
|
+
officeMarkup.modify.removeTag(tag);
|
|
2608
3176
|
}
|
|
2609
3177
|
async getNextImageId(context) {
|
|
2610
3178
|
// Init plugin context.
|
|
@@ -2629,10 +3197,8 @@ class ImagePlugin extends TemplatePlugin {
|
|
|
2629
3197
|
const maxDepth = context.options.maxXmlDepth;
|
|
2630
3198
|
|
|
2631
3199
|
// Get all existing doc props IDs
|
|
2632
|
-
// (docPr stands for "Drawing Object Non-Visual Properties", which isn't
|
|
2633
|
-
// exactly a good acronym but that's how it's called nevertheless)
|
|
2634
3200
|
const docProps = xml.query.descendants(partRoot, maxDepth, node => {
|
|
2635
|
-
return xml.query.isGeneralNode(node) && node.nodeName ===
|
|
3201
|
+
return xml.query.isGeneralNode(node) && node.nodeName === OmlNode.Wp.DocPr;
|
|
2636
3202
|
});
|
|
2637
3203
|
|
|
2638
3204
|
// Start counting from the current max
|
|
@@ -2641,128 +3207,17 @@ class ImagePlugin extends TemplatePlugin {
|
|
|
2641
3207
|
lastIdMap[lastIdKey] = maxId + 1;
|
|
2642
3208
|
return lastIdMap[lastIdKey];
|
|
2643
3209
|
}
|
|
2644
|
-
createMarkup(imageId, relId, content) {
|
|
2645
|
-
// http://officeopenxml.com/drwPicInline.php
|
|
2646
|
-
|
|
2647
|
-
//
|
|
2648
|
-
// Performance note:
|
|
2649
|
-
//
|
|
2650
|
-
// I've tried to improve the markup generation performance by parsing
|
|
2651
|
-
// the string once and caching the result (and of course customizing it
|
|
2652
|
-
// per image) but it made no change whatsoever (in both cases 1000 items
|
|
2653
|
-
// loop takes around 8 seconds on my machine) so I'm sticking with this
|
|
2654
|
-
// approach which I find to be more readable.
|
|
2655
|
-
//
|
|
2656
|
-
|
|
2657
|
-
const name = `Picture ${imageId}`;
|
|
2658
|
-
const markupText = `
|
|
2659
|
-
<w:drawing>
|
|
2660
|
-
<wp:inline distT="0" distB="0" distL="0" distR="0">
|
|
2661
|
-
<wp:extent cx="${this.pixelsToEmu(content.width)}" cy="${this.pixelsToEmu(content.height)}"/>
|
|
2662
|
-
<wp:effectExtent l="0" t="0" r="0" b="0"/>
|
|
2663
|
-
${this.docProperties(imageId, name, content)}
|
|
2664
|
-
<wp:cNvGraphicFramePr>
|
|
2665
|
-
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
|
|
2666
|
-
</wp:cNvGraphicFramePr>
|
|
2667
|
-
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
|
2668
|
-
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
2669
|
-
${this.pictureMarkup(imageId, relId, name, content)}
|
|
2670
|
-
</a:graphicData>
|
|
2671
|
-
</a:graphic>
|
|
2672
|
-
</wp:inline>
|
|
2673
|
-
</w:drawing>
|
|
2674
|
-
`;
|
|
2675
|
-
const markupXml = xml.parser.parse(markupText);
|
|
2676
|
-
xml.modify.removeEmptyTextNodes(markupXml); // remove whitespace
|
|
2677
|
-
|
|
2678
|
-
return markupXml;
|
|
2679
|
-
}
|
|
2680
|
-
docProperties(imageId, name, content) {
|
|
2681
|
-
if (content.altText) {
|
|
2682
|
-
return `<wp:docPr id="${imageId}" name="${name}" descr="${content.altText}"/>`;
|
|
2683
|
-
}
|
|
2684
|
-
return `
|
|
2685
|
-
<wp:docPr id="${imageId}" name="${name}">
|
|
2686
|
-
<a:extLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
|
2687
|
-
<a:ext uri="{C183D7F6-B498-43B3-948B-1728B52AA6E4}">
|
|
2688
|
-
<adec:decorative xmlns:adec="http://schemas.microsoft.com/office/drawing/2017/decorative" val="1"/>
|
|
2689
|
-
</a:ext>
|
|
2690
|
-
</a:extLst>
|
|
2691
|
-
</wp:docPr>
|
|
2692
|
-
`;
|
|
2693
|
-
}
|
|
2694
|
-
pictureMarkup(imageId, relId, name, content) {
|
|
2695
|
-
// http://officeopenxml.com/drwPic.php
|
|
2696
|
-
|
|
2697
|
-
// Legend:
|
|
2698
|
-
// nvPicPr - non-visual picture properties - id, name, etc.
|
|
2699
|
-
// blipFill - binary large image (or) picture fill - image size, image fill, etc.
|
|
2700
|
-
// spPr - shape properties - frame size, frame fill, etc.
|
|
2701
|
-
|
|
2702
|
-
return `
|
|
2703
|
-
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
2704
|
-
<pic:nvPicPr>
|
|
2705
|
-
<pic:cNvPr id="${imageId}" name="${name}"/>
|
|
2706
|
-
<pic:cNvPicPr>
|
|
2707
|
-
<a:picLocks noChangeAspect="1" noChangeArrowheads="1"/>
|
|
2708
|
-
</pic:cNvPicPr>
|
|
2709
|
-
</pic:nvPicPr>
|
|
2710
|
-
<pic:blipFill>
|
|
2711
|
-
<a:blip r:embed="${relId}">
|
|
2712
|
-
${this.transparencyMarkup(content.transparencyPercent)}
|
|
2713
|
-
<a:extLst>
|
|
2714
|
-
<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
|
|
2715
|
-
<a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/>
|
|
2716
|
-
</a:ext>
|
|
2717
|
-
</a:extLst>
|
|
2718
|
-
</a:blip>
|
|
2719
|
-
<a:srcRect/>
|
|
2720
|
-
<a:stretch>
|
|
2721
|
-
<a:fillRect/>
|
|
2722
|
-
</a:stretch>
|
|
2723
|
-
</pic:blipFill>
|
|
2724
|
-
<pic:spPr bwMode="auto">
|
|
2725
|
-
<a:xfrm>
|
|
2726
|
-
<a:off x="0" y="0"/>
|
|
2727
|
-
<a:ext cx="${this.pixelsToEmu(content.width)}" cy="${this.pixelsToEmu(content.height)}"/>
|
|
2728
|
-
</a:xfrm>
|
|
2729
|
-
<a:prstGeom prst="rect">
|
|
2730
|
-
<a:avLst/>
|
|
2731
|
-
</a:prstGeom>
|
|
2732
|
-
<a:noFill/>
|
|
2733
|
-
<a:ln>
|
|
2734
|
-
<a:noFill/>
|
|
2735
|
-
</a:ln>
|
|
2736
|
-
</pic:spPr>
|
|
2737
|
-
</pic:pic>
|
|
2738
|
-
`;
|
|
2739
|
-
}
|
|
2740
|
-
transparencyMarkup(transparencyPercent) {
|
|
2741
|
-
if (transparencyPercent === null || transparencyPercent === undefined) {
|
|
2742
|
-
return '';
|
|
2743
|
-
}
|
|
2744
|
-
if (transparencyPercent < 0 || transparencyPercent > 100) {
|
|
2745
|
-
throw new TemplateDataError(`Transparency percent must be between 0 and 100, but was ${transparencyPercent}.`);
|
|
2746
|
-
}
|
|
2747
|
-
const alpha = Math.round((100 - transparencyPercent) * 1000);
|
|
2748
|
-
return `<a:alphaModFix amt="${alpha}" />`;
|
|
2749
|
-
}
|
|
2750
|
-
pixelsToEmu(pixels) {
|
|
2751
|
-
// https://stackoverflow.com/questions/20194403/openxml-distance-size-units
|
|
2752
|
-
// https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vml-units#other-units-of-measurement
|
|
2753
|
-
// https://en.wikipedia.org/wiki/Office_Open_XML_file_formats#DrawingML
|
|
2754
|
-
// http://www.java2s.com/Code/CSharp/2D-Graphics/ConvertpixelstoEMUEMUtopixels.htm
|
|
2755
|
-
|
|
2756
|
-
return Math.round(pixels * 9525);
|
|
2757
|
-
}
|
|
2758
3210
|
}
|
|
2759
3211
|
|
|
2760
3212
|
class LinkPlugin extends TemplatePlugin {
|
|
2761
3213
|
contentType = 'link';
|
|
2762
3214
|
async simpleTagReplacements(tag, data, context) {
|
|
3215
|
+
if (tag.placement !== TagPlacement.TextNode) {
|
|
3216
|
+
throw new TemplateSyntaxError(`Link tag "${tag.rawText}" must be placed in a text node but was placed in ${tag.placement}`);
|
|
3217
|
+
}
|
|
2763
3218
|
const content = data.getScopeData();
|
|
2764
3219
|
if (!content || !content.target) {
|
|
2765
|
-
officeMarkup.modify.removeTag(tag
|
|
3220
|
+
officeMarkup.modify.removeTag(tag);
|
|
2766
3221
|
return;
|
|
2767
3222
|
}
|
|
2768
3223
|
|
|
@@ -3157,6 +3612,12 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
3157
3612
|
// vars
|
|
3158
3613
|
const openTag = tags[0];
|
|
3159
3614
|
const closeTag = last(tags);
|
|
3615
|
+
if (openTag.placement !== TagPlacement.TextNode) {
|
|
3616
|
+
throw new TemplateSyntaxError(`Loop opening tag "${openTag.rawText}" must be placed in a text node but was placed in ${openTag.placement}`);
|
|
3617
|
+
}
|
|
3618
|
+
if (closeTag.placement !== TagPlacement.TextNode) {
|
|
3619
|
+
throw new TemplateSyntaxError(`Loop closing tag "${closeTag.rawText}" must be placed in a text node but was placed in ${closeTag.placement}`);
|
|
3620
|
+
}
|
|
3160
3621
|
|
|
3161
3622
|
// select the suitable strategy
|
|
3162
3623
|
const loopStrategy = this.loopStrategies.find(strategy => strategy.isApplicable(openTag, closeTag, isCondition));
|
|
@@ -3243,6 +3704,9 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
3243
3704
|
class RawXmlPlugin extends TemplatePlugin {
|
|
3244
3705
|
contentType = 'rawXml';
|
|
3245
3706
|
simpleTagReplacements(tag, data) {
|
|
3707
|
+
if (tag.placement !== TagPlacement.TextNode) {
|
|
3708
|
+
throw new TemplateSyntaxError(`RawXml tag "${tag.rawText}" must be placed in a text node but was placed in ${tag.placement}`);
|
|
3709
|
+
}
|
|
3246
3710
|
const value = data.getScopeData();
|
|
3247
3711
|
const replaceNode = value?.replaceParagraph ? officeMarkup.query.containingParagraphNode(tag.xmlTextNode) : officeMarkup.query.containingTextNode(tag.xmlTextNode);
|
|
3248
3712
|
if (typeof value?.xml === 'string') {
|
|
@@ -3252,7 +3716,7 @@ class RawXmlPlugin extends TemplatePlugin {
|
|
|
3252
3716
|
if (value?.replaceParagraph) {
|
|
3253
3717
|
xml.modify.remove(replaceNode);
|
|
3254
3718
|
} else {
|
|
3255
|
-
officeMarkup.modify.removeTag(tag
|
|
3719
|
+
officeMarkup.modify.removeTag(tag);
|
|
3256
3720
|
}
|
|
3257
3721
|
}
|
|
3258
3722
|
}
|
|
@@ -3266,20 +3730,44 @@ class TextPlugin extends TemplatePlugin {
|
|
|
3266
3730
|
*/
|
|
3267
3731
|
simpleTagReplacements(tag, data) {
|
|
3268
3732
|
const value = data.getScopeData();
|
|
3269
|
-
const
|
|
3733
|
+
const strValue = stringValue(value);
|
|
3734
|
+
if (tag.placement === TagPlacement.TextNode) {
|
|
3735
|
+
this.replaceInTextNode(tag, strValue);
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
if (tag.placement === TagPlacement.Attribute) {
|
|
3739
|
+
this.replaceInAttribute(tag, strValue);
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
const anyTag = tag;
|
|
3743
|
+
throw new TemplateSyntaxError(`Unexpected tag placement "${anyTag.placement}" for tag "${anyTag.rawText}".`);
|
|
3744
|
+
}
|
|
3745
|
+
replaceInTextNode(tag, text) {
|
|
3746
|
+
const lines = text.split('\n');
|
|
3270
3747
|
if (lines.length < 2) {
|
|
3271
|
-
this.replaceSingleLine(tag
|
|
3748
|
+
this.replaceSingleLine(tag, lines.length ? lines[0] : '');
|
|
3272
3749
|
} else {
|
|
3273
3750
|
this.replaceMultiLine(tag.xmlTextNode, lines);
|
|
3274
3751
|
}
|
|
3275
3752
|
}
|
|
3276
|
-
|
|
3753
|
+
replaceInAttribute(tag, text) {
|
|
3277
3754
|
// Set text
|
|
3755
|
+
tag.xmlNode.attributes[tag.attributeName] = tag.xmlNode.attributes[tag.attributeName].replace(tag.rawText, text);
|
|
3756
|
+
|
|
3757
|
+
// Remove the attribute if it's empty
|
|
3758
|
+
if (!text) {
|
|
3759
|
+
officeMarkup.modify.removeTag(tag);
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
replaceSingleLine(tag, text) {
|
|
3764
|
+
// Set text
|
|
3765
|
+
const textNode = tag.xmlTextNode;
|
|
3278
3766
|
textNode.textContent = text;
|
|
3279
3767
|
|
|
3280
3768
|
// Clean up if the text node is now empty
|
|
3281
3769
|
if (!text) {
|
|
3282
|
-
officeMarkup.modify.removeTag(
|
|
3770
|
+
officeMarkup.modify.removeTag(tag);
|
|
3283
3771
|
return;
|
|
3284
3772
|
}
|
|
3285
3773
|
|
|
@@ -3672,15 +4160,15 @@ async function updateChart(chartPart, chartData) {
|
|
|
3672
4160
|
// Get the chart node
|
|
3673
4161
|
const root = await chartPart.xmlRoot();
|
|
3674
4162
|
if (root.nodeName !== "c:chartSpace") {
|
|
3675
|
-
throw new
|
|
4163
|
+
throw new MalformedFileError(`Unexpected chart root node "${root.nodeName}"`);
|
|
3676
4164
|
}
|
|
3677
4165
|
const chartWrapperNode = root.childNodes?.find(child => child.nodeName === "c:chart");
|
|
3678
4166
|
if (!chartWrapperNode) {
|
|
3679
|
-
throw new
|
|
4167
|
+
throw new MalformedFileError("Chart node not found");
|
|
3680
4168
|
}
|
|
3681
4169
|
const plotAreaNode = chartWrapperNode.childNodes?.find(child => child.nodeName === "c:plotArea");
|
|
3682
4170
|
if (!plotAreaNode) {
|
|
3683
|
-
throw new
|
|
4171
|
+
throw new MalformedFileError("Plot area node not found");
|
|
3684
4172
|
}
|
|
3685
4173
|
const chartNode = plotAreaNode.childNodes?.find(child => Object.values(chartTypes).includes(child.nodeName));
|
|
3686
4174
|
if (!chartNode) {
|
|
@@ -4450,13 +4938,16 @@ function parseXmlNode(xmlString) {
|
|
|
4450
4938
|
class ChartPlugin extends TemplatePlugin {
|
|
4451
4939
|
contentType = 'chart';
|
|
4452
4940
|
async simpleTagReplacements(tag, data, context) {
|
|
4941
|
+
if (tag.placement !== TagPlacement.TextNode) {
|
|
4942
|
+
throw new TemplateSyntaxError(`Chart tag "${tag.rawText}" must be placed in a text node but was placed in ${tag.placement}`);
|
|
4943
|
+
}
|
|
4453
4944
|
const chartNode = xml.query.findParentByName(tag.xmlTextNode, "c:chart");
|
|
4454
4945
|
if (!chartNode) {
|
|
4455
|
-
throw new TemplateSyntaxError(
|
|
4946
|
+
throw new TemplateSyntaxError(`Chart tag "${tag.rawText}" must be placed in chart title`);
|
|
4456
4947
|
}
|
|
4457
4948
|
const content = data.getScopeData();
|
|
4458
4949
|
if (!content) {
|
|
4459
|
-
officeMarkup.modify.removeTag(tag
|
|
4950
|
+
officeMarkup.modify.removeTag(tag);
|
|
4460
4951
|
return;
|
|
4461
4952
|
}
|
|
4462
4953
|
|
|
@@ -4464,7 +4955,7 @@ class ChartPlugin extends TemplatePlugin {
|
|
|
4464
4955
|
if (content.title) {
|
|
4465
4956
|
updateTitle(tag, content.title);
|
|
4466
4957
|
} else {
|
|
4467
|
-
officeMarkup.modify.removeTag(tag
|
|
4958
|
+
officeMarkup.modify.removeTag(tag);
|
|
4468
4959
|
}
|
|
4469
4960
|
if (!chartHasData(content)) {
|
|
4470
4961
|
return;
|
|
@@ -4654,7 +5145,7 @@ class Delimiters {
|
|
|
4654
5145
|
constructor(initial) {
|
|
4655
5146
|
Object.assign(this, initial);
|
|
4656
5147
|
this.encodeAndValidate();
|
|
4657
|
-
if (this.containerTagOpen === this.containerTagClose) throw new Error(
|
|
5148
|
+
if (this.containerTagOpen === this.containerTagClose) throw new Error(`containerTagOpen can not be equal to containerTagClose`);
|
|
4658
5149
|
}
|
|
4659
5150
|
encodeAndValidate() {
|
|
4660
5151
|
const keys = ['tagStart', 'tagEnd', 'containerTagOpen', 'containerTagClose'];
|
|
@@ -4697,19 +5188,17 @@ class TemplateHandler {
|
|
|
4697
5188
|
/**
|
|
4698
5189
|
* Version number of the `easy-template-x` library.
|
|
4699
5190
|
*/
|
|
4700
|
-
version = "
|
|
5191
|
+
version = "7.0.0" ;
|
|
4701
5192
|
constructor(options) {
|
|
4702
5193
|
this.options = new TemplateHandlerOptions(options);
|
|
5194
|
+
const delimiters = this.options.delimiters;
|
|
4703
5195
|
|
|
4704
5196
|
//
|
|
4705
|
-
//
|
|
5197
|
+
// This is the library's composition root
|
|
4706
5198
|
//
|
|
4707
5199
|
|
|
4708
|
-
const delimiterSearcher = new DelimiterSearcher();
|
|
4709
|
-
|
|
4710
|
-
delimiterSearcher.endDelimiter = this.options.delimiters.tagEnd;
|
|
4711
|
-
delimiterSearcher.maxXmlDepth = this.options.maxXmlDepth;
|
|
4712
|
-
const tagParser = new TagParser(this.options.delimiters);
|
|
5200
|
+
const delimiterSearcher = new DelimiterSearcher(delimiters, this.options.maxXmlDepth);
|
|
5201
|
+
const tagParser = new TagParser(delimiters);
|
|
4713
5202
|
this.compiler = new TemplateCompiler(delimiterSearcher, tagParser, this.options.plugins, {
|
|
4714
5203
|
skipEmptyTags: this.options.skipEmptyTags,
|
|
4715
5204
|
defaultContentType: this.options.defaultContentType,
|
|
@@ -4733,14 +5222,14 @@ class TemplateHandler {
|
|
|
4733
5222
|
}
|
|
4734
5223
|
|
|
4735
5224
|
//
|
|
4736
|
-
//
|
|
5225
|
+
// Public methods
|
|
4737
5226
|
//
|
|
4738
5227
|
|
|
4739
5228
|
async process(templateFile, data) {
|
|
4740
|
-
//
|
|
5229
|
+
// Load the docx file
|
|
4741
5230
|
const docx = await Docx.load(templateFile);
|
|
4742
5231
|
|
|
4743
|
-
//
|
|
5232
|
+
// Prepare context
|
|
4744
5233
|
const scopeData = new ScopeData(data);
|
|
4745
5234
|
scopeData.scopeDataResolver = this.options.scopeDataResolver;
|
|
4746
5235
|
const context = {
|
|
@@ -4755,18 +5244,18 @@ class TemplateHandler {
|
|
|
4755
5244
|
for (const part of contentParts) {
|
|
4756
5245
|
context.currentPart = part;
|
|
4757
5246
|
|
|
4758
|
-
//
|
|
5247
|
+
// Extensions - before compilation
|
|
4759
5248
|
await this.callExtensions(this.options.extensions?.beforeCompilation, scopeData, context);
|
|
4760
5249
|
|
|
4761
|
-
//
|
|
5250
|
+
// Compilation (do replacements)
|
|
4762
5251
|
const xmlRoot = await part.xmlRoot();
|
|
4763
5252
|
await this.compiler.compile(xmlRoot, scopeData, context);
|
|
4764
5253
|
|
|
4765
|
-
//
|
|
5254
|
+
// Extensions - after compilation
|
|
4766
5255
|
await this.callExtensions(this.options.extensions?.afterCompilation, scopeData, context);
|
|
4767
5256
|
}
|
|
4768
5257
|
|
|
4769
|
-
//
|
|
5258
|
+
// Export the result
|
|
4770
5259
|
return docx.export();
|
|
4771
5260
|
}
|
|
4772
5261
|
async parseTags(templateFile) {
|
|
@@ -4828,7 +5317,7 @@ class TemplateHandler {
|
|
|
4828
5317
|
}
|
|
4829
5318
|
|
|
4830
5319
|
//
|
|
4831
|
-
//
|
|
5320
|
+
// Private methods
|
|
4832
5321
|
//
|
|
4833
5322
|
|
|
4834
5323
|
async callExtensions(extensions, scopeData, context) {
|
|
@@ -4874,6 +5363,7 @@ exports.TEXT_NODE_NAME = TEXT_NODE_NAME;
|
|
|
4874
5363
|
exports.TagDisposition = TagDisposition;
|
|
4875
5364
|
exports.TagOptionsParseError = TagOptionsParseError;
|
|
4876
5365
|
exports.TagParser = TagParser;
|
|
5366
|
+
exports.TagPlacement = TagPlacement;
|
|
4877
5367
|
exports.TemplateCompiler = TemplateCompiler;
|
|
4878
5368
|
exports.TemplateDataError = TemplateDataError;
|
|
4879
5369
|
exports.TemplateExtension = TemplateExtension;
|