easy-template-x 6.2.0 → 6.2.2
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 +1 -1
- package/dist/cjs/easy-template-x.cjs +192 -106
- package/dist/es/easy-template-x.mjs +190 -107
- package/dist/types/compilation/delimiterSearcher.d.ts +0 -1
- package/dist/types/compilation/templateContext.d.ts +5 -0
- package/dist/types/office/contentTypesFile.d.ts +1 -1
- package/dist/types/office/mediaFiles.d.ts +1 -0
- package/dist/types/plugins/image/imagePlugin.d.ts +1 -0
- package/dist/types/plugins/index.d.ts +1 -0
- package/dist/types/utils/txt.d.ts +1 -0
- package/dist/types/xml/index.d.ts +4 -3
- package/dist/types/xml/xml.d.ts +3 -0
- package/dist/types/xml/xmlTreeIterator.d.ts +10 -0
- package/package.json +1 -1
- package/src/compilation/delimiterSearcher.ts +29 -56
- package/src/compilation/templateContext.ts +9 -0
- package/src/office/contentTypesFile.ts +20 -14
- package/src/office/mediaFiles.ts +19 -12
- package/src/plugins/image/imagePlugin.ts +49 -12
- package/src/plugins/index.ts +1 -0
- package/src/templateHandler.ts +5 -1
- package/src/utils/txt.ts +5 -0
- package/src/xml/index.ts +4 -3
- package/src/xml/xml.ts +22 -3
- package/src/xml/xmlTreeIterator.ts +67 -0
package/README.md
CHANGED
|
@@ -404,6 +404,10 @@ function stringValue(val) {
|
|
|
404
404
|
function normalizeDoubleQuotes(text) {
|
|
405
405
|
return text.replace(nonStandardDoubleQuotesRegex, standardDoubleQuotes);
|
|
406
406
|
}
|
|
407
|
+
function countOccurrences(text, substring) {
|
|
408
|
+
// https://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string
|
|
409
|
+
return (text.match(new RegExp(substring, 'g')) || []).length;
|
|
410
|
+
}
|
|
407
411
|
|
|
408
412
|
class JsZipHelper {
|
|
409
413
|
static toJsZipOutputType(binaryOrType) {
|
|
@@ -491,6 +495,71 @@ const XmlNodeType = Object.freeze({
|
|
|
491
495
|
const TEXT_NODE_NAME = '#text'; // see: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
|
|
492
496
|
const COMMENT_NODE_NAME = '#comment';
|
|
493
497
|
|
|
498
|
+
class XmlDepthTracker {
|
|
499
|
+
depth = 0;
|
|
500
|
+
constructor(maxDepth) {
|
|
501
|
+
this.maxDepth = maxDepth;
|
|
502
|
+
}
|
|
503
|
+
increment() {
|
|
504
|
+
this.depth++;
|
|
505
|
+
if (this.depth > this.maxDepth) {
|
|
506
|
+
throw new MaxXmlDepthError(this.maxDepth);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
decrement() {
|
|
510
|
+
this.depth--;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
class XmlTreeIterator {
|
|
515
|
+
get node() {
|
|
516
|
+
return this._current;
|
|
517
|
+
}
|
|
518
|
+
constructor(initial, maxDepth) {
|
|
519
|
+
if (!initial) {
|
|
520
|
+
throw new InternalError("Initial node is required");
|
|
521
|
+
}
|
|
522
|
+
if (!maxDepth) {
|
|
523
|
+
throw new InternalError("Max depth is required");
|
|
524
|
+
}
|
|
525
|
+
this._current = initial;
|
|
526
|
+
this.depthTracker = new XmlDepthTracker(maxDepth);
|
|
527
|
+
}
|
|
528
|
+
next() {
|
|
529
|
+
if (!this._current) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
this._current = this.findNextNode(this._current);
|
|
533
|
+
return this._current;
|
|
534
|
+
}
|
|
535
|
+
setCurrent(node) {
|
|
536
|
+
this._current = node;
|
|
537
|
+
}
|
|
538
|
+
findNextNode(node) {
|
|
539
|
+
// Children
|
|
540
|
+
if (node.childNodes && node.childNodes.length) {
|
|
541
|
+
this.depthTracker.increment();
|
|
542
|
+
return node.childNodes[0];
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Siblings
|
|
546
|
+
if (node.nextSibling) return node.nextSibling;
|
|
547
|
+
|
|
548
|
+
// Parent sibling
|
|
549
|
+
while (node.parentNode) {
|
|
550
|
+
if (node.parentNode.nextSibling) {
|
|
551
|
+
this.depthTracker.decrement();
|
|
552
|
+
return node.parentNode.nextSibling;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Go up
|
|
556
|
+
this.depthTracker.decrement();
|
|
557
|
+
node = node.parentNode;
|
|
558
|
+
}
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
494
563
|
class XmlUtils {
|
|
495
564
|
parser = new Parser();
|
|
496
565
|
create = new Create();
|
|
@@ -690,16 +759,19 @@ let Query$1 = class Query {
|
|
|
690
759
|
isTextNode(node) {
|
|
691
760
|
if (node.nodeType === XmlNodeType.Text || node.nodeName === TEXT_NODE_NAME) {
|
|
692
761
|
if (!(node.nodeType === XmlNodeType.Text && node.nodeName === TEXT_NODE_NAME)) {
|
|
693
|
-
throw new
|
|
762
|
+
throw new InternalError(`Invalid text node. Type: '${node.nodeType}', Name: '${node.nodeName}'.`);
|
|
694
763
|
}
|
|
695
764
|
return true;
|
|
696
765
|
}
|
|
697
766
|
return false;
|
|
698
767
|
}
|
|
768
|
+
isGeneralNode(node) {
|
|
769
|
+
return node.nodeType === XmlNodeType.General;
|
|
770
|
+
}
|
|
699
771
|
isCommentNode(node) {
|
|
700
772
|
if (node.nodeType === XmlNodeType.Comment || node.nodeName === COMMENT_NODE_NAME) {
|
|
701
773
|
if (!(node.nodeType === XmlNodeType.Comment && node.nodeName === COMMENT_NODE_NAME)) {
|
|
702
|
-
throw new
|
|
774
|
+
throw new InternalError(`Invalid comment node. Type: '${node.nodeType}', Name: '${node.nodeName}'.`);
|
|
703
775
|
}
|
|
704
776
|
return true;
|
|
705
777
|
}
|
|
@@ -778,6 +850,17 @@ let Query$1 = class Query {
|
|
|
778
850
|
range.push(lastNode);
|
|
779
851
|
return range;
|
|
780
852
|
}
|
|
853
|
+
descendants(node, maxDepth, predicate) {
|
|
854
|
+
const result = [];
|
|
855
|
+
const it = new XmlTreeIterator(node, maxDepth);
|
|
856
|
+
while (it.node) {
|
|
857
|
+
if (predicate(it.node)) {
|
|
858
|
+
result.push(it.node);
|
|
859
|
+
}
|
|
860
|
+
it.next();
|
|
861
|
+
}
|
|
862
|
+
return result;
|
|
863
|
+
}
|
|
781
864
|
};
|
|
782
865
|
let Modify$1 = class Modify {
|
|
783
866
|
/**
|
|
@@ -1030,22 +1113,6 @@ function recursiveRemoveEmptyTextNodes(node) {
|
|
|
1030
1113
|
}
|
|
1031
1114
|
const xml = new XmlUtils();
|
|
1032
1115
|
|
|
1033
|
-
class XmlDepthTracker {
|
|
1034
|
-
depth = 0;
|
|
1035
|
-
constructor(maxDepth) {
|
|
1036
|
-
this.maxDepth = maxDepth;
|
|
1037
|
-
}
|
|
1038
|
-
increment() {
|
|
1039
|
-
this.depth++;
|
|
1040
|
-
if (this.depth > this.maxDepth) {
|
|
1041
|
-
throw new MaxXmlDepthError(this.maxDepth);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
decrement() {
|
|
1045
|
-
this.depth--;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
1116
|
/**
|
|
1050
1117
|
* The types of relationships that can be created in a docx file.
|
|
1051
1118
|
* A non-comprehensive list.
|
|
@@ -1149,14 +1216,17 @@ class ContentTypesFile {
|
|
|
1149
1216
|
this.zip = zip;
|
|
1150
1217
|
}
|
|
1151
1218
|
async ensureContentType(mime) {
|
|
1152
|
-
//
|
|
1219
|
+
// Parse the content types file
|
|
1153
1220
|
await this.parseContentTypesFile();
|
|
1154
1221
|
|
|
1155
|
-
// already exists
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
//
|
|
1222
|
+
// Extension already exists
|
|
1223
|
+
//
|
|
1224
|
+
// Multiple extensions may map to the same mime type, but a single
|
|
1225
|
+
// extension must only map to one mime type.
|
|
1159
1226
|
const extension = MimeTypeHelper.getDefaultExtension(mime);
|
|
1227
|
+
if (this.contentTypes[extension]) return;
|
|
1228
|
+
|
|
1229
|
+
// Add new node
|
|
1160
1230
|
const typeNode = xml.create.generalNode('Default');
|
|
1161
1231
|
typeNode.attributes = {
|
|
1162
1232
|
"Extension": extension,
|
|
@@ -1164,13 +1234,13 @@ class ContentTypesFile {
|
|
|
1164
1234
|
};
|
|
1165
1235
|
this.root.childNodes.push(typeNode);
|
|
1166
1236
|
|
|
1167
|
-
//
|
|
1237
|
+
// Update state
|
|
1168
1238
|
this.addedNew = true;
|
|
1169
|
-
this.contentTypes[
|
|
1239
|
+
this.contentTypes[extension] = mime;
|
|
1170
1240
|
}
|
|
1171
|
-
async
|
|
1241
|
+
async xmlString() {
|
|
1172
1242
|
await this.parseContentTypesFile();
|
|
1173
|
-
return
|
|
1243
|
+
return xml.parser.serializeFile(this.root);
|
|
1174
1244
|
}
|
|
1175
1245
|
|
|
1176
1246
|
/**
|
|
@@ -1178,7 +1248,7 @@ class ContentTypesFile {
|
|
|
1178
1248
|
* Called automatically by the holding `Docx` before exporting.
|
|
1179
1249
|
*/
|
|
1180
1250
|
async save() {
|
|
1181
|
-
//
|
|
1251
|
+
// Not change - no need to save
|
|
1182
1252
|
if (!this.addedNew) return;
|
|
1183
1253
|
const xmlContent = xml.parser.serializeFile(this.root);
|
|
1184
1254
|
this.zip.setFile(ContentTypesFile.contentTypesFilePath, xmlContent);
|
|
@@ -1197,7 +1267,9 @@ class ContentTypesFile {
|
|
|
1197
1267
|
const genNode = node;
|
|
1198
1268
|
const contentTypeAttribute = genNode.attributes['ContentType'];
|
|
1199
1269
|
if (!contentTypeAttribute) continue;
|
|
1200
|
-
|
|
1270
|
+
const extensionAttribute = genNode.attributes['Extension'];
|
|
1271
|
+
if (!extensionAttribute) continue;
|
|
1272
|
+
this.contentTypes[extensionAttribute] = contentTypeAttribute;
|
|
1201
1273
|
}
|
|
1202
1274
|
}
|
|
1203
1275
|
}
|
|
@@ -1217,39 +1289,38 @@ class MediaFiles {
|
|
|
1217
1289
|
* Returns the media file path.
|
|
1218
1290
|
*/
|
|
1219
1291
|
async add(mediaFile, mime) {
|
|
1220
|
-
//
|
|
1292
|
+
// Check if already added
|
|
1221
1293
|
if (this.files.has(mediaFile)) return this.files.get(mediaFile);
|
|
1222
1294
|
|
|
1223
|
-
//
|
|
1295
|
+
// Hash existing media files
|
|
1224
1296
|
await this.hashMediaFiles();
|
|
1225
1297
|
|
|
1226
|
-
//
|
|
1298
|
+
// Hash the new file
|
|
1227
1299
|
// Note: Even though hashing the base64 string may seem inefficient
|
|
1228
1300
|
// (requires extra step in some cases) in practice it is significantly
|
|
1229
1301
|
// faster than hashing a 'binarystring'.
|
|
1230
1302
|
const base64 = await Binary.toBase64(mediaFile);
|
|
1231
1303
|
const hash = sha1(base64);
|
|
1232
1304
|
|
|
1233
|
-
//
|
|
1234
|
-
//
|
|
1305
|
+
// Check if file already exists
|
|
1306
|
+
// Note: this can be optimized by keeping both mapping by filename as well as by hash
|
|
1235
1307
|
let path = Object.keys(this.hashes).find(p => this.hashes[p] === hash);
|
|
1236
1308
|
if (path) return path;
|
|
1237
1309
|
|
|
1238
|
-
//
|
|
1310
|
+
// Generate unique media file name
|
|
1311
|
+
const baseFilename = this.baseFilename(mime);
|
|
1239
1312
|
const extension = MimeTypeHelper.getDefaultExtension(mime);
|
|
1240
1313
|
do {
|
|
1241
1314
|
this.nextFileId++;
|
|
1242
|
-
path = `${MediaFiles.mediaDir}
|
|
1315
|
+
path = `${MediaFiles.mediaDir}/${baseFilename}${this.nextFileId}.${extension}`;
|
|
1243
1316
|
} while (this.hashes[path]);
|
|
1244
1317
|
|
|
1245
|
-
//
|
|
1246
|
-
|
|
1318
|
+
// Add media to zip
|
|
1319
|
+
this.zip.setFile(path, mediaFile);
|
|
1247
1320
|
|
|
1248
|
-
//
|
|
1321
|
+
// Add media to our lookups
|
|
1249
1322
|
this.hashes[path] = hash;
|
|
1250
1323
|
this.files.set(mediaFile, path);
|
|
1251
|
-
|
|
1252
|
-
// return
|
|
1253
1324
|
return path;
|
|
1254
1325
|
}
|
|
1255
1326
|
async count() {
|
|
@@ -1265,9 +1336,15 @@ class MediaFiles {
|
|
|
1265
1336
|
if (!filename) continue;
|
|
1266
1337
|
const fileData = await this.zip.getFile(path).getContentBase64();
|
|
1267
1338
|
const fileHash = sha1(fileData);
|
|
1268
|
-
this.hashes[
|
|
1339
|
+
this.hashes[path] = fileHash;
|
|
1269
1340
|
}
|
|
1270
1341
|
}
|
|
1342
|
+
baseFilename(mime) {
|
|
1343
|
+
// Naive heuristic.
|
|
1344
|
+
// May need to be modified if we're going to support more mime types.
|
|
1345
|
+
const parts = mime.split('/');
|
|
1346
|
+
return parts[0];
|
|
1347
|
+
}
|
|
1271
1348
|
}
|
|
1272
1349
|
|
|
1273
1350
|
/**
|
|
@@ -2168,30 +2245,30 @@ class DelimiterSearcher {
|
|
|
2168
2245
|
|
|
2169
2246
|
const delimiters = [];
|
|
2170
2247
|
const match = new MatchState();
|
|
2171
|
-
const
|
|
2248
|
+
const it = new XmlTreeIterator(node, this.maxXmlDepth);
|
|
2172
2249
|
let lookForOpenDelimiter = true;
|
|
2173
|
-
while (node) {
|
|
2250
|
+
while (it.node) {
|
|
2174
2251
|
// Reset state on paragraph transition
|
|
2175
|
-
if (officeMarkup.query.isParagraphNode(node)) {
|
|
2252
|
+
if (officeMarkup.query.isParagraphNode(it.node)) {
|
|
2176
2253
|
match.reset();
|
|
2177
2254
|
}
|
|
2178
2255
|
|
|
2179
2256
|
// Skip irrelevant nodes
|
|
2180
|
-
if (!this.shouldSearchNode(
|
|
2181
|
-
|
|
2257
|
+
if (!this.shouldSearchNode(it)) {
|
|
2258
|
+
it.next();
|
|
2182
2259
|
continue;
|
|
2183
2260
|
}
|
|
2184
2261
|
|
|
2185
2262
|
// Search delimiters in text nodes
|
|
2186
|
-
match.openNodes.push(node);
|
|
2263
|
+
match.openNodes.push(it.node);
|
|
2187
2264
|
let textIndex = 0;
|
|
2188
|
-
while (textIndex < node.textContent.length) {
|
|
2265
|
+
while (textIndex < it.node.textContent.length) {
|
|
2189
2266
|
const delimiterPattern = lookForOpenDelimiter ? this.startDelimiter : this.endDelimiter;
|
|
2190
|
-
const char = node.textContent[textIndex];
|
|
2267
|
+
const char = it.node.textContent[textIndex];
|
|
2191
2268
|
|
|
2192
2269
|
// No match
|
|
2193
2270
|
if (char !== delimiterPattern[match.delimiterIndex]) {
|
|
2194
|
-
|
|
2271
|
+
textIndex = this.noMatch(it, textIndex, match);
|
|
2195
2272
|
textIndex++;
|
|
2196
2273
|
continue;
|
|
2197
2274
|
}
|
|
@@ -2209,14 +2286,14 @@ class DelimiterSearcher {
|
|
|
2209
2286
|
}
|
|
2210
2287
|
|
|
2211
2288
|
// Full delimiter match
|
|
2212
|
-
[
|
|
2289
|
+
[textIndex, lookForOpenDelimiter] = this.fullMatch(it, textIndex, lookForOpenDelimiter, match, delimiters);
|
|
2213
2290
|
textIndex++;
|
|
2214
2291
|
}
|
|
2215
|
-
|
|
2292
|
+
it.next();
|
|
2216
2293
|
}
|
|
2217
2294
|
return delimiters;
|
|
2218
2295
|
}
|
|
2219
|
-
noMatch(
|
|
2296
|
+
noMatch(it, textIndex, match) {
|
|
2220
2297
|
//
|
|
2221
2298
|
// Go back to first open node
|
|
2222
2299
|
//
|
|
@@ -2226,25 +2303,26 @@ class DelimiterSearcher {
|
|
|
2226
2303
|
// Delimiter is '{!' and template text contains the string '{{!'
|
|
2227
2304
|
//
|
|
2228
2305
|
if (match.firstMatchIndex !== -1) {
|
|
2229
|
-
node = first(match.openNodes);
|
|
2306
|
+
const node = first(match.openNodes);
|
|
2307
|
+
it.setCurrent(node);
|
|
2230
2308
|
textIndex = match.firstMatchIndex;
|
|
2231
2309
|
}
|
|
2232
2310
|
|
|
2233
2311
|
// Update state
|
|
2234
2312
|
match.reset();
|
|
2235
|
-
if (textIndex < node.textContent.length - 1) {
|
|
2236
|
-
match.openNodes.push(node);
|
|
2313
|
+
if (textIndex < it.node.textContent.length - 1) {
|
|
2314
|
+
match.openNodes.push(it.node);
|
|
2237
2315
|
}
|
|
2238
|
-
return
|
|
2316
|
+
return textIndex;
|
|
2239
2317
|
}
|
|
2240
|
-
fullMatch(
|
|
2318
|
+
fullMatch(it, textIndex, lookForOpenDelimiter, match, delimiters) {
|
|
2241
2319
|
// Move all delimiters characters to the same text node
|
|
2242
2320
|
if (match.openNodes.length > 1) {
|
|
2243
2321
|
const firstNode = first(match.openNodes);
|
|
2244
2322
|
const lastNode = last(match.openNodes);
|
|
2245
2323
|
officeMarkup.modify.joinTextNodesRange(firstNode, lastNode);
|
|
2246
|
-
textIndex += firstNode.textContent.length - node.textContent.length;
|
|
2247
|
-
|
|
2324
|
+
textIndex += firstNode.textContent.length - it.node.textContent.length;
|
|
2325
|
+
it.setCurrent(firstNode);
|
|
2248
2326
|
}
|
|
2249
2327
|
|
|
2250
2328
|
// Store delimiter
|
|
@@ -2254,41 +2332,18 @@ class DelimiterSearcher {
|
|
|
2254
2332
|
// Update state
|
|
2255
2333
|
lookForOpenDelimiter = !lookForOpenDelimiter;
|
|
2256
2334
|
match.reset();
|
|
2257
|
-
if (textIndex < node.textContent.length - 1) {
|
|
2258
|
-
match.openNodes.push(node);
|
|
2335
|
+
if (textIndex < it.node.textContent.length - 1) {
|
|
2336
|
+
match.openNodes.push(it.node);
|
|
2259
2337
|
}
|
|
2260
|
-
return [
|
|
2338
|
+
return [textIndex, lookForOpenDelimiter];
|
|
2261
2339
|
}
|
|
2262
|
-
shouldSearchNode(
|
|
2263
|
-
if (!xml.query.isTextNode(node)) return false;
|
|
2264
|
-
if (!node.textContent) return false;
|
|
2265
|
-
if (!node.parentNode) return false;
|
|
2266
|
-
if (!officeMarkup.query.isTextNode(node.parentNode)) return false;
|
|
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;
|
|
2267
2345
|
return true;
|
|
2268
2346
|
}
|
|
2269
|
-
findNextNode(node, depth) {
|
|
2270
|
-
// Children
|
|
2271
|
-
if (node.childNodes && node.childNodes.length) {
|
|
2272
|
-
depth.increment();
|
|
2273
|
-
return node.childNodes[0];
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
// Siblings
|
|
2277
|
-
if (node.nextSibling) return node.nextSibling;
|
|
2278
|
-
|
|
2279
|
-
// Parent sibling
|
|
2280
|
-
while (node.parentNode) {
|
|
2281
|
-
if (node.parentNode.nextSibling) {
|
|
2282
|
-
depth.decrement();
|
|
2283
|
-
return node.parentNode.nextSibling;
|
|
2284
|
-
}
|
|
2285
|
-
|
|
2286
|
-
// Go up
|
|
2287
|
-
depth.decrement();
|
|
2288
|
-
node = node.parentNode;
|
|
2289
|
-
}
|
|
2290
|
-
return null;
|
|
2291
|
-
}
|
|
2292
2347
|
createDelimiterMark(match, isOpenDelimiter) {
|
|
2293
2348
|
return {
|
|
2294
2349
|
index: match.firstMatchIndex,
|
|
@@ -2529,17 +2584,6 @@ class TemplatePlugin {
|
|
|
2529
2584
|
}
|
|
2530
2585
|
}
|
|
2531
2586
|
|
|
2532
|
-
/**
|
|
2533
|
-
* Apparently it is not that important for the ID to be unique...
|
|
2534
|
-
* Word displays two images correctly even if they both have the same ID.
|
|
2535
|
-
* Further more, Word will assign each a unique ID upon saving (it assigns
|
|
2536
|
-
* consecutive integers starting with 1).
|
|
2537
|
-
*
|
|
2538
|
-
* Note: The same principal applies to image names.
|
|
2539
|
-
*
|
|
2540
|
-
* Tested in Word v1908
|
|
2541
|
-
*/
|
|
2542
|
-
let nextImageId = 1;
|
|
2543
2587
|
class ImagePlugin extends TemplatePlugin {
|
|
2544
2588
|
contentType = 'image';
|
|
2545
2589
|
async simpleTagReplacements(tag, data, context) {
|
|
@@ -2556,12 +2600,47 @@ class ImagePlugin extends TemplatePlugin {
|
|
|
2556
2600
|
await context.docx.contentTypes.ensureContentType(content.format);
|
|
2557
2601
|
|
|
2558
2602
|
// Create the xml markup
|
|
2559
|
-
const imageId =
|
|
2603
|
+
const imageId = await this.getNextImageId(context);
|
|
2560
2604
|
const imageXml = this.createMarkup(imageId, relId, content);
|
|
2561
2605
|
const wordTextNode = officeMarkup.query.containingTextNode(tag.xmlTextNode);
|
|
2562
2606
|
xml.modify.insertAfter(imageXml, wordTextNode);
|
|
2563
2607
|
officeMarkup.modify.removeTag(tag.xmlTextNode);
|
|
2564
2608
|
}
|
|
2609
|
+
async getNextImageId(context) {
|
|
2610
|
+
// Init plugin context.
|
|
2611
|
+
if (!context.pluginContext[this.contentType]) {
|
|
2612
|
+
context.pluginContext[this.contentType] = {};
|
|
2613
|
+
}
|
|
2614
|
+
const pluginContext = context.pluginContext[this.contentType];
|
|
2615
|
+
if (!pluginContext.lastDrawingObjectId) {
|
|
2616
|
+
pluginContext.lastDrawingObjectId = {};
|
|
2617
|
+
}
|
|
2618
|
+
const lastIdMap = pluginContext.lastDrawingObjectId;
|
|
2619
|
+
const lastIdKey = context.currentPart.path;
|
|
2620
|
+
|
|
2621
|
+
// Get next image ID if already initialized.
|
|
2622
|
+
if (lastIdMap[lastIdKey]) {
|
|
2623
|
+
lastIdMap[lastIdKey]++;
|
|
2624
|
+
return lastIdMap[lastIdKey];
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
// Init next image ID.
|
|
2628
|
+
const partRoot = await context.currentPart.xmlRoot();
|
|
2629
|
+
const maxDepth = context.options.maxXmlDepth;
|
|
2630
|
+
|
|
2631
|
+
// 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
|
+
const docProps = xml.query.descendants(partRoot, maxDepth, node => {
|
|
2635
|
+
return xml.query.isGeneralNode(node) && node.nodeName === 'wp:docPr';
|
|
2636
|
+
});
|
|
2637
|
+
|
|
2638
|
+
// Start counting from the current max
|
|
2639
|
+
const ids = docProps.map(prop => parseInt(prop.attributes.id)).filter(isNumber);
|
|
2640
|
+
const maxId = Math.max(...ids, 0);
|
|
2641
|
+
lastIdMap[lastIdKey] = maxId + 1;
|
|
2642
|
+
return lastIdMap[lastIdKey];
|
|
2643
|
+
}
|
|
2565
2644
|
createMarkup(imageId, relId, content) {
|
|
2566
2645
|
// http://officeopenxml.com/drwPicInline.php
|
|
2567
2646
|
|
|
@@ -4618,7 +4697,7 @@ class TemplateHandler {
|
|
|
4618
4697
|
/**
|
|
4619
4698
|
* Version number of the `easy-template-x` library.
|
|
4620
4699
|
*/
|
|
4621
|
-
version = "6.2.
|
|
4700
|
+
version = "6.2.2" ;
|
|
4622
4701
|
constructor(options) {
|
|
4623
4702
|
this.options = new TemplateHandlerOptions(options);
|
|
4624
4703
|
|
|
@@ -4666,7 +4745,11 @@ class TemplateHandler {
|
|
|
4666
4745
|
scopeData.scopeDataResolver = this.options.scopeDataResolver;
|
|
4667
4746
|
const context = {
|
|
4668
4747
|
docx,
|
|
4669
|
-
currentPart: null
|
|
4748
|
+
currentPart: null,
|
|
4749
|
+
pluginContext: {},
|
|
4750
|
+
options: {
|
|
4751
|
+
maxXmlDepth: this.options.maxXmlDepth
|
|
4752
|
+
}
|
|
4670
4753
|
};
|
|
4671
4754
|
const contentParts = await docx.getContentParts();
|
|
4672
4755
|
for (const part of contentParts) {
|
|
@@ -4759,6 +4842,7 @@ class TemplateHandler {
|
|
|
4759
4842
|
exports.Base64 = Base64;
|
|
4760
4843
|
exports.Binary = Binary;
|
|
4761
4844
|
exports.COMMENT_NODE_NAME = COMMENT_NODE_NAME;
|
|
4845
|
+
exports.ChartPlugin = ChartPlugin;
|
|
4762
4846
|
exports.DelimiterSearcher = DelimiterSearcher;
|
|
4763
4847
|
exports.Delimiters = Delimiters;
|
|
4764
4848
|
exports.Docx = Docx;
|
|
@@ -4806,9 +4890,11 @@ exports.UnsupportedFileTypeError = UnsupportedFileTypeError;
|
|
|
4806
4890
|
exports.Xlsx = Xlsx;
|
|
4807
4891
|
exports.XmlDepthTracker = XmlDepthTracker;
|
|
4808
4892
|
exports.XmlNodeType = XmlNodeType;
|
|
4893
|
+
exports.XmlTreeIterator = XmlTreeIterator;
|
|
4809
4894
|
exports.XmlUtils = XmlUtils;
|
|
4810
4895
|
exports.Zip = Zip;
|
|
4811
4896
|
exports.ZipObject = ZipObject;
|
|
4897
|
+
exports.countOccurrences = countOccurrences;
|
|
4812
4898
|
exports.createDefaultPlugins = createDefaultPlugins;
|
|
4813
4899
|
exports.first = first;
|
|
4814
4900
|
exports.inheritsFrom = inheritsFrom;
|