easy-template-x 6.2.1 → 6.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/easy-template-x.cjs +109 -76
- package/dist/es/easy-template-x.mjs +108 -77
- package/dist/types/office/contentTypesFile.d.ts +1 -1
- package/dist/types/office/mediaFiles.d.ts +1 -0
- package/dist/types/office/relationship.d.ts +2 -2
- package/dist/types/plugins/index.d.ts +1 -0
- package/dist/types/utils/txt.d.ts +1 -0
- package/package.json +16 -19
- package/src/compilation/tagParser.ts +1 -1
- package/src/delimiters.ts +1 -1
- package/src/office/contentTypesFile.ts +9 -11
- package/src/office/mediaFiles.ts +19 -12
- package/src/office/officeMarkup.ts +2 -2
- package/src/office/openXmlPart.ts +1 -1
- package/src/office/relationship.ts +22 -6
- package/src/office/relsFile.ts +21 -22
- package/src/plugins/image/imagePlugin.ts +7 -8
- package/src/plugins/index.ts +1 -0
- package/src/templateHandler.ts +9 -9
- package/src/utils/path.ts +8 -1
- package/src/utils/txt.ts +5 -0
- package/src/xml/xml.ts +19 -19
- package/src/zip/jsZipHelper.ts +1 -1
- package/src/zip/zip.ts +3 -0
|
@@ -202,8 +202,16 @@ function isNumber(value) {
|
|
|
202
202
|
class Path {
|
|
203
203
|
static getFilename(path) {
|
|
204
204
|
const lastSlashIndex = path.lastIndexOf('/');
|
|
205
|
-
return path.
|
|
205
|
+
return path.substring(lastSlashIndex + 1);
|
|
206
206
|
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the directory of a path.
|
|
210
|
+
* Exclude the last slash.
|
|
211
|
+
*
|
|
212
|
+
* Example:
|
|
213
|
+
* /folder/subfolder/file.txt -> /folder/subfolder
|
|
214
|
+
*/
|
|
207
215
|
static getDirectory(path) {
|
|
208
216
|
const lastSlashIndex = path.lastIndexOf('/');
|
|
209
217
|
return path.substring(0, lastSlashIndex);
|
|
@@ -404,6 +412,10 @@ function stringValue(val) {
|
|
|
404
412
|
function normalizeDoubleQuotes(text) {
|
|
405
413
|
return text.replace(nonStandardDoubleQuotesRegex, standardDoubleQuotes);
|
|
406
414
|
}
|
|
415
|
+
function countOccurrences(text, substring) {
|
|
416
|
+
// https://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string
|
|
417
|
+
return (text.match(new RegExp(substring, 'g')) || []).length;
|
|
418
|
+
}
|
|
407
419
|
|
|
408
420
|
class JsZipHelper {
|
|
409
421
|
static toJsZipOutputType(binaryOrType) {
|
|
@@ -457,6 +469,9 @@ class Zip {
|
|
|
457
469
|
this.binaryFormat = binaryFormat;
|
|
458
470
|
}
|
|
459
471
|
getFile(path) {
|
|
472
|
+
if (path && path.startsWith('/')) {
|
|
473
|
+
path = path.substring(1);
|
|
474
|
+
}
|
|
460
475
|
const internalZipObject = this.zip.files[path];
|
|
461
476
|
if (!internalZipObject) return null;
|
|
462
477
|
return new ZipObject(internalZipObject, this.binaryFormat);
|
|
@@ -868,7 +883,7 @@ let Modify$1 = class Modify {
|
|
|
868
883
|
insertBefore(newNode, referenceNode) {
|
|
869
884
|
if (!newNode) throw new InternalArgumentMissingError("newNode");
|
|
870
885
|
if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
|
|
871
|
-
if (!referenceNode.parentNode) throw new Error(`'
|
|
886
|
+
if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
|
|
872
887
|
const childNodes = referenceNode.parentNode.childNodes;
|
|
873
888
|
const beforeNodeIndex = childNodes.indexOf(referenceNode);
|
|
874
889
|
xml.modify.insertChild(referenceNode.parentNode, newNode, beforeNodeIndex);
|
|
@@ -883,7 +898,7 @@ let Modify$1 = class Modify {
|
|
|
883
898
|
insertAfter(newNode, referenceNode) {
|
|
884
899
|
if (!newNode) throw new InternalArgumentMissingError("newNode");
|
|
885
900
|
if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
|
|
886
|
-
if (!referenceNode.parentNode) throw new Error(`'
|
|
901
|
+
if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
|
|
887
902
|
const childNodes = referenceNode.parentNode.childNodes;
|
|
888
903
|
const referenceNodeIndex = childNodes.indexOf(referenceNode);
|
|
889
904
|
xml.modify.insertChild(referenceNode.parentNode, newNode, referenceNodeIndex + 1);
|
|
@@ -1020,7 +1035,7 @@ let Modify$1 = class Modify {
|
|
|
1020
1035
|
* `false` then the original child node is the first child of `right`.
|
|
1021
1036
|
*/
|
|
1022
1037
|
splitByChild(parent, child, removeChild) {
|
|
1023
|
-
if (child.parentNode != parent) throw new Error(`Node '
|
|
1038
|
+
if (child.parentNode != parent) throw new Error(`Node 'child' is not a direct child of 'parent'.`);
|
|
1024
1039
|
|
|
1025
1040
|
// create childless clone 'left'
|
|
1026
1041
|
const left = xml.create.cloneNode(parent, false);
|
|
@@ -1128,20 +1143,35 @@ const RelType = Object.freeze({
|
|
|
1128
1143
|
Table: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table'
|
|
1129
1144
|
});
|
|
1130
1145
|
class Relationship {
|
|
1131
|
-
static fromXml(xml) {
|
|
1146
|
+
static fromXml(partDir, xml) {
|
|
1132
1147
|
return new Relationship({
|
|
1133
1148
|
id: xml.attributes?.['Id'],
|
|
1134
1149
|
type: xml.attributes?.['Type'],
|
|
1135
|
-
target: Relationship.normalizeRelTarget(xml.attributes?.['Target']),
|
|
1150
|
+
target: Relationship.normalizeRelTarget(partDir, xml.attributes?.['Target']),
|
|
1136
1151
|
targetMode: xml.attributes?.['TargetMode']
|
|
1137
1152
|
});
|
|
1138
1153
|
}
|
|
1139
|
-
static normalizeRelTarget(target) {
|
|
1154
|
+
static normalizeRelTarget(partDir, target) {
|
|
1140
1155
|
if (!target) {
|
|
1141
1156
|
return target;
|
|
1142
1157
|
}
|
|
1158
|
+
|
|
1159
|
+
// Remove leading slashes from input
|
|
1160
|
+
if (partDir.startsWith('/')) {
|
|
1161
|
+
partDir = partDir.substring(1);
|
|
1162
|
+
}
|
|
1163
|
+
if (target.startsWith('/')) {
|
|
1164
|
+
target = target.substring(1);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Convert target to relative path
|
|
1168
|
+
if (target.startsWith(partDir)) {
|
|
1169
|
+
target = target.substring(partDir.length);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Remove leading slashes from output
|
|
1143
1173
|
if (target.startsWith('/')) {
|
|
1144
|
-
|
|
1174
|
+
target = target.substring(1);
|
|
1145
1175
|
}
|
|
1146
1176
|
return target;
|
|
1147
1177
|
}
|
|
@@ -1152,11 +1182,11 @@ class Relationship {
|
|
|
1152
1182
|
const node = xml.create.generalNode('Relationship');
|
|
1153
1183
|
node.attributes = {};
|
|
1154
1184
|
|
|
1155
|
-
//
|
|
1185
|
+
// Set only non-empty attributes
|
|
1156
1186
|
for (const propKey of Object.keys(this)) {
|
|
1157
1187
|
const value = this[propKey];
|
|
1158
1188
|
if (value && typeof value === 'string') {
|
|
1159
|
-
const attrName = propKey[0].toUpperCase() + propKey.
|
|
1189
|
+
const attrName = propKey[0].toUpperCase() + propKey.substring(1);
|
|
1160
1190
|
node.attributes[attrName] = value;
|
|
1161
1191
|
}
|
|
1162
1192
|
}
|
|
@@ -1215,13 +1245,12 @@ class ContentTypesFile {
|
|
|
1215
1245
|
// Parse the content types file
|
|
1216
1246
|
await this.parseContentTypesFile();
|
|
1217
1247
|
|
|
1218
|
-
// Mime type already exists
|
|
1219
|
-
if (this.contentTypes[mime]) return;
|
|
1220
|
-
|
|
1221
1248
|
// Extension already exists
|
|
1222
|
-
//
|
|
1249
|
+
//
|
|
1250
|
+
// Multiple extensions may map to the same mime type, but a single
|
|
1251
|
+
// extension must only map to one mime type.
|
|
1223
1252
|
const extension = MimeTypeHelper.getDefaultExtension(mime);
|
|
1224
|
-
if (
|
|
1253
|
+
if (this.contentTypes[extension]) return;
|
|
1225
1254
|
|
|
1226
1255
|
// Add new node
|
|
1227
1256
|
const typeNode = xml.create.generalNode('Default');
|
|
@@ -1233,11 +1262,11 @@ class ContentTypesFile {
|
|
|
1233
1262
|
|
|
1234
1263
|
// Update state
|
|
1235
1264
|
this.addedNew = true;
|
|
1236
|
-
this.contentTypes[
|
|
1265
|
+
this.contentTypes[extension] = mime;
|
|
1237
1266
|
}
|
|
1238
|
-
async
|
|
1267
|
+
async xmlString() {
|
|
1239
1268
|
await this.parseContentTypesFile();
|
|
1240
|
-
return
|
|
1269
|
+
return xml.parser.serializeFile(this.root);
|
|
1241
1270
|
}
|
|
1242
1271
|
|
|
1243
1272
|
/**
|
|
@@ -1266,7 +1295,7 @@ class ContentTypesFile {
|
|
|
1266
1295
|
if (!contentTypeAttribute) continue;
|
|
1267
1296
|
const extensionAttribute = genNode.attributes['Extension'];
|
|
1268
1297
|
if (!extensionAttribute) continue;
|
|
1269
|
-
this.contentTypes[
|
|
1298
|
+
this.contentTypes[extensionAttribute] = contentTypeAttribute;
|
|
1270
1299
|
}
|
|
1271
1300
|
}
|
|
1272
1301
|
}
|
|
@@ -1286,39 +1315,38 @@ class MediaFiles {
|
|
|
1286
1315
|
* Returns the media file path.
|
|
1287
1316
|
*/
|
|
1288
1317
|
async add(mediaFile, mime) {
|
|
1289
|
-
//
|
|
1318
|
+
// Check if already added
|
|
1290
1319
|
if (this.files.has(mediaFile)) return this.files.get(mediaFile);
|
|
1291
1320
|
|
|
1292
|
-
//
|
|
1321
|
+
// Hash existing media files
|
|
1293
1322
|
await this.hashMediaFiles();
|
|
1294
1323
|
|
|
1295
|
-
//
|
|
1324
|
+
// Hash the new file
|
|
1296
1325
|
// Note: Even though hashing the base64 string may seem inefficient
|
|
1297
1326
|
// (requires extra step in some cases) in practice it is significantly
|
|
1298
1327
|
// faster than hashing a 'binarystring'.
|
|
1299
1328
|
const base64 = await Binary.toBase64(mediaFile);
|
|
1300
1329
|
const hash = sha1(base64);
|
|
1301
1330
|
|
|
1302
|
-
//
|
|
1303
|
-
//
|
|
1331
|
+
// Check if file already exists
|
|
1332
|
+
// Note: this can be optimized by keeping both mapping by filename as well as by hash
|
|
1304
1333
|
let path = Object.keys(this.hashes).find(p => this.hashes[p] === hash);
|
|
1305
1334
|
if (path) return path;
|
|
1306
1335
|
|
|
1307
|
-
//
|
|
1336
|
+
// Generate unique media file name
|
|
1337
|
+
const baseFilename = this.baseFilename(mime);
|
|
1308
1338
|
const extension = MimeTypeHelper.getDefaultExtension(mime);
|
|
1309
1339
|
do {
|
|
1310
1340
|
this.nextFileId++;
|
|
1311
|
-
path = `${MediaFiles.mediaDir}
|
|
1341
|
+
path = `${MediaFiles.mediaDir}/${baseFilename}${this.nextFileId}.${extension}`;
|
|
1312
1342
|
} while (this.hashes[path]);
|
|
1313
1343
|
|
|
1314
|
-
//
|
|
1315
|
-
|
|
1344
|
+
// Add media to zip
|
|
1345
|
+
this.zip.setFile(path, mediaFile);
|
|
1316
1346
|
|
|
1317
|
-
//
|
|
1347
|
+
// Add media to our lookups
|
|
1318
1348
|
this.hashes[path] = hash;
|
|
1319
1349
|
this.files.set(mediaFile, path);
|
|
1320
|
-
|
|
1321
|
-
// return
|
|
1322
1350
|
return path;
|
|
1323
1351
|
}
|
|
1324
1352
|
async count() {
|
|
@@ -1334,9 +1362,15 @@ class MediaFiles {
|
|
|
1334
1362
|
if (!filename) continue;
|
|
1335
1363
|
const fileData = await this.zip.getFile(path).getContentBase64();
|
|
1336
1364
|
const fileHash = sha1(fileData);
|
|
1337
|
-
this.hashes[
|
|
1365
|
+
this.hashes[path] = fileHash;
|
|
1338
1366
|
}
|
|
1339
1367
|
}
|
|
1368
|
+
baseFilename(mime) {
|
|
1369
|
+
// Naive heuristic.
|
|
1370
|
+
// May need to be modified if we're going to support more mime types.
|
|
1371
|
+
const parts = mime.split('/');
|
|
1372
|
+
return parts[0];
|
|
1373
|
+
}
|
|
1340
1374
|
}
|
|
1341
1375
|
|
|
1342
1376
|
/**
|
|
@@ -1357,20 +1391,20 @@ class RelsFile {
|
|
|
1357
1391
|
* Returns the rel ID.
|
|
1358
1392
|
*/
|
|
1359
1393
|
async add(relTarget, relType, relTargetMode) {
|
|
1360
|
-
//
|
|
1394
|
+
// If relTarget is an internal file it should be relative to the part dir
|
|
1361
1395
|
if (this.partDir && relTarget.startsWith(this.partDir)) {
|
|
1362
|
-
relTarget = relTarget.
|
|
1396
|
+
relTarget = relTarget.substring(this.partDir.length + 1);
|
|
1363
1397
|
}
|
|
1364
1398
|
|
|
1365
|
-
//
|
|
1399
|
+
// Parse rels file
|
|
1366
1400
|
await this.parseRelsFile();
|
|
1367
1401
|
|
|
1368
|
-
//
|
|
1402
|
+
// Already exists?
|
|
1369
1403
|
const relTargetKey = this.getRelTargetKey(relType, relTarget);
|
|
1370
1404
|
let relId = this.relTargets[relTargetKey];
|
|
1371
1405
|
if (relId) return relId;
|
|
1372
1406
|
|
|
1373
|
-
//
|
|
1407
|
+
// Create rel node
|
|
1374
1408
|
relId = this.getNextRelId();
|
|
1375
1409
|
const rel = new Relationship({
|
|
1376
1410
|
id: relId,
|
|
@@ -1379,11 +1413,11 @@ class RelsFile {
|
|
|
1379
1413
|
targetMode: relTargetMode
|
|
1380
1414
|
});
|
|
1381
1415
|
|
|
1382
|
-
//
|
|
1416
|
+
// Update lookups
|
|
1383
1417
|
this.rels[relId] = rel;
|
|
1384
1418
|
this.relTargets[relTargetKey] = relId;
|
|
1385
1419
|
|
|
1386
|
-
//
|
|
1420
|
+
// Return
|
|
1387
1421
|
return relId;
|
|
1388
1422
|
}
|
|
1389
1423
|
async list() {
|
|
@@ -1402,20 +1436,20 @@ class RelsFile {
|
|
|
1402
1436
|
* Called automatically by the holding `Docx` before exporting.
|
|
1403
1437
|
*/
|
|
1404
1438
|
async save() {
|
|
1405
|
-
//
|
|
1439
|
+
// Not change - no need to save
|
|
1406
1440
|
if (!this.rels) return;
|
|
1407
1441
|
|
|
1408
|
-
//
|
|
1442
|
+
// Create rels xml
|
|
1409
1443
|
const root = this.createRootNode();
|
|
1410
1444
|
root.childNodes = Object.values(this.rels).map(rel => rel.toXml());
|
|
1411
1445
|
|
|
1412
|
-
//
|
|
1446
|
+
// Serialize and save
|
|
1413
1447
|
const xmlContent = xml.parser.serializeFile(root);
|
|
1414
1448
|
this.zip.setFile(this.relsFilePath, xmlContent);
|
|
1415
1449
|
}
|
|
1416
1450
|
|
|
1417
1451
|
//
|
|
1418
|
-
//
|
|
1452
|
+
// Private methods
|
|
1419
1453
|
//
|
|
1420
1454
|
|
|
1421
1455
|
getNextRelId() {
|
|
@@ -1427,10 +1461,10 @@ class RelsFile {
|
|
|
1427
1461
|
return relId;
|
|
1428
1462
|
}
|
|
1429
1463
|
async parseRelsFile() {
|
|
1430
|
-
//
|
|
1464
|
+
// Already parsed
|
|
1431
1465
|
if (this.rels) return;
|
|
1432
1466
|
|
|
1433
|
-
//
|
|
1467
|
+
// Parse xml
|
|
1434
1468
|
let root;
|
|
1435
1469
|
const relsFile = this.zip.getFile(this.relsFilePath);
|
|
1436
1470
|
if (relsFile) {
|
|
@@ -1440,24 +1474,23 @@ class RelsFile {
|
|
|
1440
1474
|
root = this.createRootNode();
|
|
1441
1475
|
}
|
|
1442
1476
|
|
|
1443
|
-
//
|
|
1477
|
+
// Parse relationship nodes
|
|
1444
1478
|
this.rels = {};
|
|
1445
1479
|
this.relTargets = {};
|
|
1446
1480
|
for (const relNode of root.childNodes) {
|
|
1447
|
-
const
|
|
1481
|
+
const genRelNode = relNode;
|
|
1482
|
+
const attributes = genRelNode.attributes;
|
|
1448
1483
|
if (!attributes) continue;
|
|
1449
1484
|
const idAttr = attributes['Id'];
|
|
1450
1485
|
if (!idAttr) continue;
|
|
1451
1486
|
|
|
1452
|
-
//
|
|
1453
|
-
const rel = Relationship.fromXml(
|
|
1487
|
+
// Store rel
|
|
1488
|
+
const rel = Relationship.fromXml(this.partDir, genRelNode);
|
|
1454
1489
|
this.rels[idAttr] = rel;
|
|
1455
1490
|
|
|
1456
|
-
//
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
if (typeAttr && targetAttr) {
|
|
1460
|
-
const relTargetKey = this.getRelTargetKey(typeAttr, targetAttr);
|
|
1491
|
+
// Create rel target lookup
|
|
1492
|
+
if (rel.type && rel.target) {
|
|
1493
|
+
const relTargetKey = this.getRelTargetKey(rel.type, rel.target);
|
|
1461
1494
|
this.relTargets[relTargetKey] = idAttr;
|
|
1462
1495
|
}
|
|
1463
1496
|
}
|
|
@@ -1513,7 +1546,7 @@ class OpenXmlPart {
|
|
|
1513
1546
|
async getText() {
|
|
1514
1547
|
const xmlDocument = await this.xmlRoot();
|
|
1515
1548
|
|
|
1516
|
-
//
|
|
1549
|
+
// Ugly but good enough...
|
|
1517
1550
|
const xmlString = xml.parser.serializeFile(xmlDocument);
|
|
1518
1551
|
const domDocument = xml.parser.domParse(xmlString);
|
|
1519
1552
|
return domDocument.documentElement.textContent;
|
|
@@ -1828,7 +1861,7 @@ class Query {
|
|
|
1828
1861
|
*/
|
|
1829
1862
|
containingTextNode(node) {
|
|
1830
1863
|
if (!node) return null;
|
|
1831
|
-
if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument
|
|
1864
|
+
if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument node. Expected a XmlTextNode.`);
|
|
1832
1865
|
return xml.query.findParent(node, officeMarkup.query.isTextNode);
|
|
1833
1866
|
}
|
|
1834
1867
|
|
|
@@ -1945,7 +1978,7 @@ class Modify {
|
|
|
1945
1978
|
splitParagraphByTextNode(paragraph, textNode, removeTextNode) {
|
|
1946
1979
|
// input validation
|
|
1947
1980
|
const containingParagraph = officeMarkup.query.containingParagraphNode(textNode);
|
|
1948
|
-
if (containingParagraph != paragraph) throw new Error(`Node '
|
|
1981
|
+
if (containingParagraph != paragraph) throw new Error(`Node 'textNode' is not a contained in the specified paragraph.`);
|
|
1949
1982
|
const runNode = officeMarkup.query.containingRunNode(textNode);
|
|
1950
1983
|
const wordTextNode = officeMarkup.query.containingTextNode(textNode);
|
|
1951
1984
|
|
|
@@ -2603,19 +2636,17 @@ class ImagePlugin extends TemplatePlugin {
|
|
|
2603
2636
|
if (!context.pluginContext[this.contentType]) {
|
|
2604
2637
|
context.pluginContext[this.contentType] = {};
|
|
2605
2638
|
}
|
|
2606
|
-
if (!context.pluginContext[this.contentType]) {
|
|
2607
|
-
context.pluginContext[this.contentType] = {};
|
|
2608
|
-
}
|
|
2609
2639
|
const pluginContext = context.pluginContext[this.contentType];
|
|
2610
2640
|
if (!pluginContext.lastDrawingObjectId) {
|
|
2611
2641
|
pluginContext.lastDrawingObjectId = {};
|
|
2612
2642
|
}
|
|
2613
2643
|
const lastIdMap = pluginContext.lastDrawingObjectId;
|
|
2644
|
+
const lastIdKey = context.currentPart.path;
|
|
2614
2645
|
|
|
2615
2646
|
// Get next image ID if already initialized.
|
|
2616
|
-
if (lastIdMap[
|
|
2617
|
-
lastIdMap[
|
|
2618
|
-
return lastIdMap[
|
|
2647
|
+
if (lastIdMap[lastIdKey]) {
|
|
2648
|
+
lastIdMap[lastIdKey]++;
|
|
2649
|
+
return lastIdMap[lastIdKey];
|
|
2619
2650
|
}
|
|
2620
2651
|
|
|
2621
2652
|
// Init next image ID.
|
|
@@ -2632,8 +2663,8 @@ class ImagePlugin extends TemplatePlugin {
|
|
|
2632
2663
|
// Start counting from the current max
|
|
2633
2664
|
const ids = docProps.map(prop => parseInt(prop.attributes.id)).filter(isNumber);
|
|
2634
2665
|
const maxId = Math.max(...ids, 0);
|
|
2635
|
-
lastIdMap[
|
|
2636
|
-
return lastIdMap[
|
|
2666
|
+
lastIdMap[lastIdKey] = maxId + 1;
|
|
2667
|
+
return lastIdMap[lastIdKey];
|
|
2637
2668
|
}
|
|
2638
2669
|
createMarkup(imageId, relId, content) {
|
|
2639
2670
|
// http://officeopenxml.com/drwPicInline.php
|
|
@@ -4648,7 +4679,7 @@ class Delimiters {
|
|
|
4648
4679
|
constructor(initial) {
|
|
4649
4680
|
Object.assign(this, initial);
|
|
4650
4681
|
this.encodeAndValidate();
|
|
4651
|
-
if (this.containerTagOpen === this.containerTagClose) throw new Error(
|
|
4682
|
+
if (this.containerTagOpen === this.containerTagClose) throw new Error(`containerTagOpen can not be equal to containerTagClose`);
|
|
4652
4683
|
}
|
|
4653
4684
|
encodeAndValidate() {
|
|
4654
4685
|
const keys = ['tagStart', 'tagEnd', 'containerTagOpen', 'containerTagClose'];
|
|
@@ -4691,12 +4722,12 @@ class TemplateHandler {
|
|
|
4691
4722
|
/**
|
|
4692
4723
|
* Version number of the `easy-template-x` library.
|
|
4693
4724
|
*/
|
|
4694
|
-
version = "6.2.
|
|
4725
|
+
version = "6.2.3" ;
|
|
4695
4726
|
constructor(options) {
|
|
4696
4727
|
this.options = new TemplateHandlerOptions(options);
|
|
4697
4728
|
|
|
4698
4729
|
//
|
|
4699
|
-
//
|
|
4730
|
+
// This is the library's composition root
|
|
4700
4731
|
//
|
|
4701
4732
|
|
|
4702
4733
|
const delimiterSearcher = new DelimiterSearcher();
|
|
@@ -4727,14 +4758,14 @@ class TemplateHandler {
|
|
|
4727
4758
|
}
|
|
4728
4759
|
|
|
4729
4760
|
//
|
|
4730
|
-
//
|
|
4761
|
+
// Public methods
|
|
4731
4762
|
//
|
|
4732
4763
|
|
|
4733
4764
|
async process(templateFile, data) {
|
|
4734
|
-
//
|
|
4765
|
+
// Load the docx file
|
|
4735
4766
|
const docx = await Docx.load(templateFile);
|
|
4736
4767
|
|
|
4737
|
-
//
|
|
4768
|
+
// Prepare context
|
|
4738
4769
|
const scopeData = new ScopeData(data);
|
|
4739
4770
|
scopeData.scopeDataResolver = this.options.scopeDataResolver;
|
|
4740
4771
|
const context = {
|
|
@@ -4749,18 +4780,18 @@ class TemplateHandler {
|
|
|
4749
4780
|
for (const part of contentParts) {
|
|
4750
4781
|
context.currentPart = part;
|
|
4751
4782
|
|
|
4752
|
-
//
|
|
4783
|
+
// Extensions - before compilation
|
|
4753
4784
|
await this.callExtensions(this.options.extensions?.beforeCompilation, scopeData, context);
|
|
4754
4785
|
|
|
4755
|
-
//
|
|
4786
|
+
// Compilation (do replacements)
|
|
4756
4787
|
const xmlRoot = await part.xmlRoot();
|
|
4757
4788
|
await this.compiler.compile(xmlRoot, scopeData, context);
|
|
4758
4789
|
|
|
4759
|
-
//
|
|
4790
|
+
// Extensions - after compilation
|
|
4760
4791
|
await this.callExtensions(this.options.extensions?.afterCompilation, scopeData, context);
|
|
4761
4792
|
}
|
|
4762
4793
|
|
|
4763
|
-
//
|
|
4794
|
+
// Export the result
|
|
4764
4795
|
return docx.export();
|
|
4765
4796
|
}
|
|
4766
4797
|
async parseTags(templateFile) {
|
|
@@ -4822,7 +4853,7 @@ class TemplateHandler {
|
|
|
4822
4853
|
}
|
|
4823
4854
|
|
|
4824
4855
|
//
|
|
4825
|
-
//
|
|
4856
|
+
// Private methods
|
|
4826
4857
|
//
|
|
4827
4858
|
|
|
4828
4859
|
async callExtensions(extensions, scopeData, context) {
|
|
@@ -4836,6 +4867,7 @@ class TemplateHandler {
|
|
|
4836
4867
|
exports.Base64 = Base64;
|
|
4837
4868
|
exports.Binary = Binary;
|
|
4838
4869
|
exports.COMMENT_NODE_NAME = COMMENT_NODE_NAME;
|
|
4870
|
+
exports.ChartPlugin = ChartPlugin;
|
|
4839
4871
|
exports.DelimiterSearcher = DelimiterSearcher;
|
|
4840
4872
|
exports.Delimiters = Delimiters;
|
|
4841
4873
|
exports.Docx = Docx;
|
|
@@ -4887,6 +4919,7 @@ exports.XmlTreeIterator = XmlTreeIterator;
|
|
|
4887
4919
|
exports.XmlUtils = XmlUtils;
|
|
4888
4920
|
exports.Zip = Zip;
|
|
4889
4921
|
exports.ZipObject = ZipObject;
|
|
4922
|
+
exports.countOccurrences = countOccurrences;
|
|
4890
4923
|
exports.createDefaultPlugins = createDefaultPlugins;
|
|
4891
4924
|
exports.first = first;
|
|
4892
4925
|
exports.inheritsFrom = inheritsFrom;
|