easy-template-x 3.2.0 → 4.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.
Files changed (108) hide show
  1. package/README.md +38 -2
  2. package/dist/cjs/easy-template-x.js +184 -98
  3. package/dist/es/easy-template-x.js +177 -89
  4. package/dist/types/compilation/delimiterMark.d.ts +6 -6
  5. package/dist/types/compilation/delimiterSearcher.d.ts +16 -16
  6. package/dist/types/compilation/index.d.ts +7 -7
  7. package/dist/types/compilation/scopeData.d.ts +21 -21
  8. package/dist/types/compilation/tag.d.ts +14 -12
  9. package/dist/types/compilation/tagParser.d.ts +13 -13
  10. package/dist/types/compilation/templateCompiler.d.ts +25 -25
  11. package/dist/types/compilation/templateContext.d.ts +5 -5
  12. package/dist/types/delimiters.d.ts +10 -8
  13. package/dist/types/errors/index.d.ts +11 -10
  14. package/dist/types/errors/malformedFileError.d.ts +4 -4
  15. package/dist/types/errors/maxXmlDepthError.d.ts +4 -4
  16. package/dist/types/errors/missingArgumentError.d.ts +4 -4
  17. package/dist/types/errors/missingCloseDelimiterError.d.ts +4 -4
  18. package/dist/types/errors/missingStartDelimiterError.d.ts +4 -4
  19. package/dist/types/errors/tagOptionsParseError.d.ts +5 -0
  20. package/dist/types/errors/unclosedTagError.d.ts +4 -4
  21. package/dist/types/errors/unidentifiedFileTypeError.d.ts +3 -3
  22. package/dist/types/errors/unknownContentTypeError.d.ts +6 -6
  23. package/dist/types/errors/unopenedTagError.d.ts +4 -4
  24. package/dist/types/errors/unsupportedFileTypeError.d.ts +4 -4
  25. package/dist/types/extensions/extensionOptions.d.ts +5 -5
  26. package/dist/types/extensions/index.d.ts +2 -2
  27. package/dist/types/extensions/templateExtension.d.ts +14 -14
  28. package/dist/types/index.d.ts +13 -13
  29. package/dist/types/mimeType.d.ts +11 -11
  30. package/dist/types/office/contentPartType.d.ts +9 -9
  31. package/dist/types/office/contentTypesFile.d.ts +16 -16
  32. package/dist/types/office/docx.d.ts +28 -28
  33. package/dist/types/office/docxParser.d.ts +35 -35
  34. package/dist/types/office/index.d.ts +4 -4
  35. package/dist/types/office/mediaFiles.d.ts +14 -14
  36. package/dist/types/office/relationship.d.ts +11 -11
  37. package/dist/types/office/rels.d.ts +20 -20
  38. package/dist/types/office/xmlPart.d.ts +14 -14
  39. package/dist/types/plugins/defaultPlugins.d.ts +2 -2
  40. package/dist/types/plugins/image/imageContent.d.ts +12 -12
  41. package/dist/types/plugins/image/imagePlugin.d.ts +10 -10
  42. package/dist/types/plugins/image/index.d.ts +2 -2
  43. package/dist/types/plugins/index.d.ts +8 -8
  44. package/dist/types/plugins/link/index.d.ts +2 -2
  45. package/dist/types/plugins/link/linkContent.d.ts +7 -7
  46. package/dist/types/plugins/link/linkPlugin.d.ts +9 -9
  47. package/dist/types/plugins/loop/index.d.ts +1 -1
  48. package/dist/types/plugins/loop/loopPlugin.d.ts +13 -13
  49. package/dist/types/plugins/loop/loopTagOptions.d.ts +7 -0
  50. package/dist/types/plugins/loop/strategy/iLoopStrategy.d.ts +14 -14
  51. package/dist/types/plugins/loop/strategy/index.d.ts +4 -4
  52. package/dist/types/plugins/loop/strategy/loopListStrategy.d.ts +11 -11
  53. package/dist/types/plugins/loop/strategy/loopParagraphStrategy.d.ts +11 -11
  54. package/dist/types/plugins/loop/strategy/loopTableStrategy.d.ts +11 -11
  55. package/dist/types/plugins/pluginContent.d.ts +6 -6
  56. package/dist/types/plugins/rawXml/index.d.ts +2 -2
  57. package/dist/types/plugins/rawXml/rawXmlContent.d.ts +6 -6
  58. package/dist/types/plugins/rawXml/rawXmlPlugin.d.ts +6 -6
  59. package/dist/types/plugins/templatePlugin.d.ts +15 -15
  60. package/dist/types/plugins/text/index.d.ts +1 -1
  61. package/dist/types/plugins/text/textPlugin.d.ts +11 -11
  62. package/dist/types/templateData.d.ts +6 -6
  63. package/dist/types/templateHandler.d.ts +20 -20
  64. package/dist/types/templateHandlerOptions.d.ts +15 -15
  65. package/dist/types/types.d.ts +4 -6
  66. package/dist/types/utils/array.d.ts +6 -6
  67. package/dist/types/utils/base64.d.ts +3 -3
  68. package/dist/types/utils/binary.d.ts +11 -12
  69. package/dist/types/utils/index.d.ts +9 -9
  70. package/dist/types/utils/number.d.ts +1 -1
  71. package/dist/types/utils/path.d.ts +5 -5
  72. package/dist/types/utils/regex.d.ts +3 -3
  73. package/dist/types/utils/sha1.d.ts +1 -1
  74. package/dist/types/utils/txt.d.ts +2 -1
  75. package/dist/types/utils/types.d.ts +3 -3
  76. package/dist/types/xml/index.d.ts +3 -3
  77. package/dist/types/xml/xmlDepthTracker.d.ts +7 -7
  78. package/dist/types/xml/xmlNode.d.ts +58 -49
  79. package/dist/types/xml/xmlParser.d.ts +8 -8
  80. package/dist/types/zip/index.d.ts +2 -2
  81. package/dist/types/zip/jsZipHelper.d.ts +7 -7
  82. package/dist/types/zip/zip.d.ts +13 -13
  83. package/dist/types/zip/zipObject.d.ts +13 -13
  84. package/package.json +25 -19
  85. package/src/compilation/tag.ts +4 -2
  86. package/src/compilation/tagParser.ts +32 -13
  87. package/src/delimiters.ts +3 -1
  88. package/src/errors/index.ts +1 -0
  89. package/src/errors/malformedFileError.ts +1 -4
  90. package/src/errors/maxXmlDepthError.ts +1 -4
  91. package/src/errors/missingArgumentError.ts +1 -4
  92. package/src/errors/missingCloseDelimiterError.ts +1 -4
  93. package/src/errors/missingStartDelimiterError.ts +1 -4
  94. package/src/errors/tagOptionsParseError.ts +12 -0
  95. package/src/errors/unclosedTagError.ts +1 -4
  96. package/src/errors/unidentifiedFileTypeError.ts +1 -4
  97. package/src/errors/unknownContentTypeError.ts +1 -4
  98. package/src/errors/unopenedTagError.ts +1 -4
  99. package/src/errors/unsupportedFileTypeError.ts +1 -4
  100. package/src/office/docx.ts +4 -1
  101. package/src/office/docxParser.ts +3 -1
  102. package/src/plugins/loop/loopPlugin.ts +1 -1
  103. package/src/plugins/loop/loopTagOptions.ts +14 -0
  104. package/src/plugins/loop/strategy/loopTableStrategy.ts +22 -3
  105. package/src/plugins/templatePlugin.ts +0 -2
  106. package/src/types.ts +1 -3
  107. package/src/utils/txt.ts +29 -1
  108. package/src/xml/xmlNode.ts +50 -14
package/README.md CHANGED
@@ -14,6 +14,7 @@ Generate docx documents from templates, in Node or in the browser.
14
14
  - [Loop plugin](#loop-plugin)
15
15
  - [Conditions](#conditions)
16
16
  - [Nested Conditions](#nested-conditions)
17
+ - [Controlling loop behavior](#controlling-loop-behavior)
17
18
  - [Image plugin](#image-plugin)
18
19
  - [Link plugin](#link-plugin)
19
20
  - [Raw xml plugin](#raw-xml-plugin)
@@ -121,7 +122,7 @@ function saveFile(filename, blob) {
121
122
 
122
123
  ## Live Demo
123
124
 
124
- Checkout this [live demo](https://codesandbox.io/s/easy-template-x-demo-x4ppu?fontsize=14&module=%2Findex.ts) on CodeSandbox 😎
125
+ Checkout this [live demo](https://codesandbox.io/p/sandbox/easy-template-x-demo-x4ppu?file=%2Findex.ts) on CodeSandbox 😎
125
126
 
126
127
  ## Plugins
127
128
 
@@ -253,6 +254,39 @@ Output document:
253
254
 
254
255
  _If you are looking for a yet more powerful conditional syntax see the [alternative syntax](#advanced-syntax-and-custom-resolvers) section._
255
256
 
257
+ #### Controlling loop behavior
258
+
259
+ To control the loop (or condition) behavior you can use the `loopOver` option.
260
+
261
+ Given this data:
262
+
263
+ ```javascript
264
+ {
265
+ students: [
266
+ { name: "Alice" },
267
+ { name: "Bob" }
268
+ ]
269
+ }
270
+ ```
271
+
272
+ You can use either this template:
273
+
274
+ ![input template](./docs/assets/loop-over-row-in.png?raw=true)
275
+
276
+ Or this one:
277
+
278
+ ![input template](./docs/assets/loop-over-content-in.png?raw=true)
279
+
280
+ The first will produce this document:
281
+
282
+ ![output document](./docs/assets/loop-over-row-out.png?raw=true)
283
+
284
+ And the second will produce this one:
285
+
286
+ ![output document](./docs/assets/loop-over-content-out.png?raw=true)
287
+
288
+ By default `easy-template-x` will loop over "content" if the opening and closing loop tags are in the same table cell and "row" otherwise.
289
+
256
290
  ### Image plugin
257
291
 
258
292
  Embed images into the document.
@@ -480,7 +514,9 @@ const handler = new TemplateHandler({
480
514
  tagStart: "{",
481
515
  tagEnd: "}",
482
516
  containerTagOpen: "#",
483
- containerTagClose: "/"
517
+ containerTagClose: "/",
518
+ tagOptionsStart: "[",
519
+ tagOptionsEnd: "]"
484
520
  },
485
521
 
486
522
  maxXmlDepth: 20,
@@ -1,15 +1,11 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var xmldom = require('@xmldom/xmldom');
6
4
  var getProp = require('lodash.get');
5
+ var JSON5 = require('json5');
7
6
  var JSZip = require('jszip');
8
7
 
9
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
-
11
- function _interopNamespace(e) {
12
- if (e && e.__esModule) return e;
8
+ function _interopNamespaceDefault(e) {
13
9
  var n = Object.create(null);
14
10
  if (e) {
15
11
  Object.keys(e).forEach(function (k) {
@@ -22,40 +18,34 @@ function _interopNamespace(e) {
22
18
  }
23
19
  });
24
20
  }
25
- n["default"] = e;
21
+ n.default = e;
26
22
  return Object.freeze(n);
27
23
  }
28
24
 
29
- var getProp__default = /*#__PURE__*/_interopDefaultLegacy(getProp);
30
- var JSZip__namespace = /*#__PURE__*/_interopNamespace(JSZip);
25
+ var JSON5__namespace = /*#__PURE__*/_interopNamespaceDefault(JSON5);
26
+ var JSZip__namespace = /*#__PURE__*/_interopNamespaceDefault(JSZip);
31
27
 
32
- function _defineProperty(obj, key, value) {
33
- key = _toPropertyKey(key);
34
- if (key in obj) {
35
- Object.defineProperty(obj, key, {
36
- value: value,
37
- enumerable: true,
38
- configurable: true,
39
- writable: true
40
- });
41
- } else {
42
- obj[key] = value;
43
- }
44
- return obj;
28
+ function _defineProperty(e, r, t) {
29
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
30
+ value: t,
31
+ enumerable: !0,
32
+ configurable: !0,
33
+ writable: !0
34
+ }) : e[r] = t, e;
45
35
  }
46
- function _toPrimitive(input, hint) {
47
- if (typeof input !== "object" || input === null) return input;
48
- var prim = input[Symbol.toPrimitive];
49
- if (prim !== undefined) {
50
- var res = prim.call(input, hint || "default");
51
- if (typeof res !== "object") return res;
36
+ function _toPrimitive(t, r) {
37
+ if ("object" != typeof t || !t) return t;
38
+ var e = t[Symbol.toPrimitive];
39
+ if (void 0 !== e) {
40
+ var i = e.call(t, r || "default");
41
+ if ("object" != typeof i) return i;
52
42
  throw new TypeError("@@toPrimitive must return a primitive value.");
53
43
  }
54
- return (hint === "string" ? String : Number)(input);
44
+ return ("string" === r ? String : Number)(t);
55
45
  }
56
- function _toPropertyKey(arg) {
57
- var key = _toPrimitive(arg, "string");
58
- return typeof key === "symbol" ? key : String(key);
46
+ function _toPropertyKey(t) {
47
+ var i = _toPrimitive(t, "string");
48
+ return "symbol" == typeof i ? i : i + "";
59
49
  }
60
50
 
61
51
  class MalformedFileError extends Error {
@@ -63,9 +53,6 @@ class MalformedFileError extends Error {
63
53
  super(`Malformed file detected. Make sure the file is a valid ${expectedFileType} file.`);
64
54
  _defineProperty(this, "expectedFileType", void 0);
65
55
  this.expectedFileType = expectedFileType;
66
-
67
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
68
- Object.setPrototypeOf(this, MalformedFileError.prototype);
69
56
  }
70
57
  }
71
58
 
@@ -74,9 +61,6 @@ class MaxXmlDepthError extends Error {
74
61
  super(`XML maximum depth reached (max depth: ${maxDepth}).`);
75
62
  _defineProperty(this, "maxDepth", void 0);
76
63
  this.maxDepth = maxDepth;
77
-
78
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
79
- Object.setPrototypeOf(this, MaxXmlDepthError.prototype);
80
64
  }
81
65
  }
82
66
 
@@ -85,9 +69,6 @@ class MissingArgumentError extends Error {
85
69
  super(`Argument '${argName}' is missing.`);
86
70
  _defineProperty(this, "argName", void 0);
87
71
  this.argName = argName;
88
-
89
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
90
- Object.setPrototypeOf(this, MissingArgumentError.prototype);
91
72
  }
92
73
  }
93
74
 
@@ -96,9 +77,6 @@ class MissingCloseDelimiterError extends Error {
96
77
  super(`Close delimiter is missing from '${openDelimiterText}'.`);
97
78
  _defineProperty(this, "openDelimiterText", void 0);
98
79
  this.openDelimiterText = openDelimiterText;
99
-
100
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
101
- Object.setPrototypeOf(this, MissingCloseDelimiterError.prototype);
102
80
  }
103
81
  }
104
82
 
@@ -107,9 +85,16 @@ class MissingStartDelimiterError extends Error {
107
85
  super(`Open delimiter is missing from '${closeDelimiterText}'.`);
108
86
  _defineProperty(this, "closeDelimiterText", void 0);
109
87
  this.closeDelimiterText = closeDelimiterText;
88
+ }
89
+ }
110
90
 
111
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
112
- Object.setPrototypeOf(this, MissingStartDelimiterError.prototype);
91
+ class TagOptionsParseError extends Error {
92
+ constructor(tagRawText, parseError) {
93
+ super(`Failed to parse tag options of '${tagRawText}': ${parseError.message}.`);
94
+ _defineProperty(this, "tagRawText", void 0);
95
+ _defineProperty(this, "parseError", void 0);
96
+ this.tagRawText = tagRawText;
97
+ this.parseError = parseError;
113
98
  }
114
99
  }
115
100
 
@@ -118,18 +103,12 @@ class UnclosedTagError extends Error {
118
103
  super(`Tag '${tagName}' is never closed.`);
119
104
  _defineProperty(this, "tagName", void 0);
120
105
  this.tagName = tagName;
121
-
122
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
123
- Object.setPrototypeOf(this, UnclosedTagError.prototype);
124
106
  }
125
107
  }
126
108
 
127
109
  class UnidentifiedFileTypeError extends Error {
128
110
  constructor() {
129
111
  super(`The filetype for this file could not be identified, is this file corrupted?`);
130
-
131
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
132
- Object.setPrototypeOf(this, UnidentifiedFileTypeError.prototype);
133
112
  }
134
113
  }
135
114
 
@@ -142,9 +121,6 @@ class UnknownContentTypeError extends Error {
142
121
  this.contentType = contentType;
143
122
  this.tagRawText = tagRawText;
144
123
  this.path = path;
145
-
146
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
147
- Object.setPrototypeOf(this, UnknownContentTypeError.prototype);
148
124
  }
149
125
  }
150
126
 
@@ -153,9 +129,6 @@ class UnopenedTagError extends Error {
153
129
  super(`Tag '${tagName}' is closed but was never opened.`);
154
130
  _defineProperty(this, "tagName", void 0);
155
131
  this.tagName = tagName;
156
-
157
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
158
- Object.setPrototypeOf(this, UnopenedTagError.prototype);
159
132
  }
160
133
  }
161
134
 
@@ -164,9 +137,6 @@ class UnsupportedFileTypeError extends Error {
164
137
  super(`Filetype "${fileType}" is not supported.`);
165
138
  _defineProperty(this, "fileType", void 0);
166
139
  this.fileType = fileType;
167
-
168
- // typescript hack: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
169
- Object.setPrototypeOf(this, UnsupportedFileTypeError.prototype);
170
140
  }
171
141
  }
172
142
 
@@ -416,12 +386,48 @@ function utf8Encode(str) {
416
386
  return utfStr;
417
387
  }
418
388
 
389
+ // Copied from: https://gist.github.com/thanpolas/244d9a13151caf5a12e42208b6111aa6
390
+ // And see: https://unicode-table.com/en/sets/quotation-marks/
391
+ const nonStandardDoubleQuotes = ['“',
392
+ // U+201c
393
+ '”',
394
+ // U+201d
395
+ '«',
396
+ // U+00AB
397
+ '»',
398
+ // U+00BB
399
+ '„',
400
+ // U+201E
401
+ '“',
402
+ // U+201C
403
+ '‟',
404
+ // U+201F
405
+ '”',
406
+ // U+201D
407
+ '❝',
408
+ // U+275D
409
+ '❞',
410
+ // U+275E
411
+ '〝',
412
+ // U+301D
413
+ '〞',
414
+ // U+301E
415
+ '〟',
416
+ // U+301F
417
+ '"' // U+FF02
418
+ ];
419
+ const standardDoubleQuotes = '"'; // U+0022
420
+
421
+ const nonStandardDoubleQuotesRegex = new RegExp(nonStandardDoubleQuotes.join('|'), 'g');
419
422
  function stringValue(val) {
420
423
  if (val === null || val === undefined) {
421
424
  return '';
422
425
  }
423
426
  return val.toString();
424
427
  }
428
+ function normalizeDoubleQuotes(text) {
429
+ return text.replace(nonStandardDoubleQuotesRegex, standardDoubleQuotes);
430
+ }
425
431
 
426
432
  class XmlDepthTracker {
427
433
  constructor(maxDepth) {
@@ -442,10 +448,11 @@ class XmlDepthTracker {
442
448
  let XmlNodeType = /*#__PURE__*/function (XmlNodeType) {
443
449
  XmlNodeType["Text"] = "Text";
444
450
  XmlNodeType["General"] = "General";
451
+ XmlNodeType["Comment"] = "Comment";
445
452
  return XmlNodeType;
446
453
  }({});
447
454
  const TEXT_NODE_NAME = '#text'; // see: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName
448
-
455
+ const COMMENT_NODE_NAME = '#comment';
449
456
  const XmlNode = {
450
457
  //
451
458
  // factories
@@ -464,6 +471,13 @@ const XmlNode = {
464
471
  nodeName: name
465
472
  };
466
473
  },
474
+ createCommentNode(text) {
475
+ return {
476
+ nodeType: XmlNodeType.Comment,
477
+ nodeName: COMMENT_NODE_NAME,
478
+ commentContent: text
479
+ };
480
+ },
467
481
  //
468
482
  // serialization
469
483
  //
@@ -494,6 +508,9 @@ const XmlNode = {
494
508
  },
495
509
  serialize(node) {
496
510
  if (this.isTextNode(node)) return this.encodeValue(node.textContent || '');
511
+ if (this.isCommentNode(node)) {
512
+ return `<!-- ${this.encodeValue(node.commentContent || '')} -->`;
513
+ }
497
514
 
498
515
  // attributes
499
516
  let attributes = '';
@@ -528,22 +545,36 @@ const XmlNode = {
528
545
  let xmlNode;
529
546
 
530
547
  // basic properties
531
- if (domNode.nodeType === domNode.TEXT_NODE) {
532
- xmlNode = this.createTextNode(domNode.textContent);
533
- } else {
534
- xmlNode = this.createGeneralNode(domNode.nodeName);
535
-
536
- // attributes
537
- if (domNode.nodeType === domNode.ELEMENT_NODE) {
538
- const attributes = domNode.attributes;
539
- if (attributes) {
540
- xmlNode.attributes = {};
541
- for (let i = 0; i < attributes.length; i++) {
542
- const curAttribute = attributes.item(i);
543
- xmlNode.attributes[curAttribute.name] = curAttribute.value;
548
+ switch (domNode.nodeType) {
549
+ case domNode.TEXT_NODE:
550
+ {
551
+ xmlNode = this.createTextNode(domNode.textContent);
552
+ break;
553
+ }
554
+ case domNode.COMMENT_NODE:
555
+ {
556
+ var _domNode$textContent;
557
+ xmlNode = this.createCommentNode((_domNode$textContent = domNode.textContent) === null || _domNode$textContent === void 0 ? void 0 : _domNode$textContent.trim());
558
+ break;
559
+ }
560
+ case domNode.ELEMENT_NODE:
561
+ {
562
+ const generalNode = xmlNode = this.createGeneralNode(domNode.nodeName);
563
+ const attributes = domNode.attributes;
564
+ if (attributes) {
565
+ generalNode.attributes = {};
566
+ for (let i = 0; i < attributes.length; i++) {
567
+ const curAttribute = attributes.item(i);
568
+ generalNode.attributes[curAttribute.name] = curAttribute.value;
569
+ }
544
570
  }
571
+ break;
572
+ }
573
+ default:
574
+ {
575
+ xmlNode = this.createGeneralNode(domNode.nodeName);
576
+ break;
545
577
  }
546
- }
547
578
  }
548
579
 
549
580
  // children
@@ -579,6 +610,15 @@ const XmlNode = {
579
610
  }
580
611
  return false;
581
612
  },
613
+ isCommentNode(node) {
614
+ if (node.nodeType === XmlNodeType.Comment || node.nodeName === COMMENT_NODE_NAME) {
615
+ if (!(node.nodeType === XmlNodeType.Comment && node.nodeName === COMMENT_NODE_NAME)) {
616
+ throw new Error(`Invalid comment node. Type: '${node.nodeType}', Name: '${node.nodeName}'.`);
617
+ }
618
+ return true;
619
+ }
620
+ return false;
621
+ },
582
622
  cloneNode(node, deep) {
583
623
  if (!node) throw new MissingArgumentError("node");
584
624
  if (!deep) {
@@ -810,9 +850,11 @@ const XmlNode = {
810
850
  *
811
851
  * * **Note:** Prefer calling with explicit index.
812
852
  */
853
+
813
854
  /**
814
855
  * Remove a child node from it's parent. Returns the removed child.
815
856
  */
857
+
816
858
  function removeChild(parent, childOrIndex) {
817
859
  if (!parent) throw new MissingArgumentError("parent");
818
860
  if (childOrIndex === null || childOrIndex === undefined) throw new MissingArgumentError("childOrIndex");
@@ -1089,7 +1131,7 @@ class ScopeData {
1089
1131
  const curPath = args.strPath.slice();
1090
1132
  while (result === undefined && curPath.length) {
1091
1133
  curPath.pop();
1092
- result = getProp__default["default"](args.data, curPath.concat(lastKey));
1134
+ result = getProp(args.data, curPath.concat(lastKey));
1093
1135
  }
1094
1136
  return result;
1095
1137
  }
@@ -1139,7 +1181,8 @@ class TagParser {
1139
1181
  _defineProperty(this, "tagRegex", void 0);
1140
1182
  if (!docParser) throw new MissingArgumentError("docParser");
1141
1183
  if (!delimiters) throw new MissingArgumentError("delimiters");
1142
- this.tagRegex = new RegExp(`^${Regex.escape(delimiters.tagStart)}(.*?)${Regex.escape(delimiters.tagEnd)}`, 'm');
1184
+ const tagOptionsRegex = `${Regex.escape(delimiters.tagOptionsStart)}(?<tagOptions>.*?)${Regex.escape(delimiters.tagOptionsEnd)}`;
1185
+ this.tagRegex = new RegExp(`^${Regex.escape(delimiters.tagStart)}(?<tagName>.*?)(${tagOptionsRegex})?${Regex.escape(delimiters.tagEnd)}`, 'm');
1143
1186
  }
1144
1187
  parse(delimiters) {
1145
1188
  const tags = [];
@@ -1238,23 +1281,44 @@ class TagParser {
1238
1281
  closeDelimiter.xmlTextNode = endTextNode;
1239
1282
  }
1240
1283
  processTag(tag) {
1284
+ var _tagParts$groups, _tagParts$groups2;
1241
1285
  tag.rawText = tag.xmlTextNode.textContent;
1242
1286
  const tagParts = this.tagRegex.exec(tag.rawText);
1243
- const tagContent = (tagParts[1] || '').trim();
1244
- if (!tagContent || !tagContent.length) {
1287
+ const tagName = (((_tagParts$groups = tagParts.groups) === null || _tagParts$groups === void 0 ? void 0 : _tagParts$groups["tagName"]) || '').trim();
1288
+
1289
+ // Ignoring empty tags.
1290
+ if (!(tagName !== null && tagName !== void 0 && tagName.length)) {
1245
1291
  tag.disposition = TagDisposition.SelfClosed;
1246
1292
  return;
1247
1293
  }
1248
- if (tagContent.startsWith(this.delimiters.containerTagOpen)) {
1294
+
1295
+ // Tag options.
1296
+ const tagOptionsText = (((_tagParts$groups2 = tagParts.groups) === null || _tagParts$groups2 === void 0 ? void 0 : _tagParts$groups2["tagOptions"]) || '').trim();
1297
+ if (tagOptionsText) {
1298
+ try {
1299
+ tag.options = JSON5__namespace.parse("{" + normalizeDoubleQuotes(tagOptionsText) + "}");
1300
+ } catch (e) {
1301
+ throw new TagOptionsParseError(tag.rawText, e);
1302
+ }
1303
+ }
1304
+
1305
+ // Container open tag.
1306
+ if (tagName.startsWith(this.delimiters.containerTagOpen)) {
1249
1307
  tag.disposition = TagDisposition.Open;
1250
- tag.name = tagContent.slice(this.delimiters.containerTagOpen.length).trim();
1251
- } else if (tagContent.startsWith(this.delimiters.containerTagClose)) {
1308
+ tag.name = tagName.slice(this.delimiters.containerTagOpen.length).trim();
1309
+ return;
1310
+ }
1311
+
1312
+ // Container close tag.
1313
+ if (tagName.startsWith(this.delimiters.containerTagClose)) {
1252
1314
  tag.disposition = TagDisposition.Close;
1253
- tag.name = tagContent.slice(this.delimiters.containerTagClose.length).trim();
1254
- } else {
1255
- tag.disposition = TagDisposition.SelfClosed;
1256
- tag.name = tagContent;
1315
+ tag.name = tagName.slice(this.delimiters.containerTagClose.length).trim();
1316
+ return;
1257
1317
  }
1318
+
1319
+ // Self-closed tag.
1320
+ tag.disposition = TagDisposition.SelfClosed;
1321
+ tag.name = tagName;
1258
1322
  }
1259
1323
  }
1260
1324
 
@@ -1297,8 +1361,6 @@ class MimeTypeHelper {
1297
1361
  }
1298
1362
  }
1299
1363
 
1300
- /* eslint-disable @typescript-eslint/member-ordering */
1301
-
1302
1364
  class TemplatePlugin {
1303
1365
  constructor() {
1304
1366
  /**
@@ -1851,6 +1913,7 @@ class Docx {
1851
1913
  //
1852
1914
  // static methods
1853
1915
  //
1916
+
1854
1917
  static async open(zip, xmlParser) {
1855
1918
  const mainDocumentPath = await Docx.getMainDocumentPath(zip, xmlParser);
1856
1919
  if (!mainDocumentPath) throw new MalformedFileError('docx');
@@ -1930,7 +1993,8 @@ class Docx {
1930
1993
  // find the last section properties
1931
1994
  // see: http://officeopenxml.com/WPsection.php
1932
1995
  const docRoot = await this.mainDocument.xmlRoot();
1933
- const body = docRoot.childNodes[0];
1996
+ const body = docRoot.childNodes.find(node => node.nodeName == 'w:body');
1997
+ if (body == null) return null;
1934
1998
  const sectionProps = last(body.childNodes.filter(node => node.nodeType === XmlNodeType.General));
1935
1999
  if (sectionProps.nodeName != 'w:sectPr') return null;
1936
2000
 
@@ -1996,6 +2060,7 @@ class DocxParser {
1996
2060
  //
1997
2061
  // constructor
1998
2062
  //
2063
+
1999
2064
  constructor(xmlParser) {
2000
2065
  this.xmlParser = xmlParser;
2001
2066
  }
@@ -2146,7 +2211,10 @@ class DocxParser {
2146
2211
  curWordTextNode = this.firstTextNodeChild(curRunNode);
2147
2212
  }
2148
2213
  while (curWordTextNode) {
2149
- if (curWordTextNode.nodeName !== DocxParser.TEXT_NODE) continue;
2214
+ if (curWordTextNode.nodeName !== DocxParser.TEXT_NODE) {
2215
+ curWordTextNode = curWordTextNode.nextSibling;
2216
+ continue;
2217
+ }
2150
2218
 
2151
2219
  // move text to first node
2152
2220
  const curXmlTextNode = XmlNode.lastTextChild(curWordTextNode);
@@ -2517,6 +2585,12 @@ class LoopParagraphStrategy {
2517
2585
  }
2518
2586
  }
2519
2587
 
2588
+ let LoopOver = /*#__PURE__*/function (LoopOver) {
2589
+ LoopOver["Row"] = "row";
2590
+ LoopOver["Content"] = "content";
2591
+ return LoopOver;
2592
+ }({});
2593
+
2520
2594
  class LoopTableStrategy {
2521
2595
  constructor() {
2522
2596
  _defineProperty(this, "utilities", void 0);
@@ -2525,9 +2599,18 @@ class LoopTableStrategy {
2525
2599
  this.utilities = utilities;
2526
2600
  }
2527
2601
  isApplicable(openTag, closeTag) {
2528
- const containingParagraph = this.utilities.docxParser.containingParagraphNode(openTag.xmlTextNode);
2529
- if (!containingParagraph.parentNode) return false;
2530
- return this.utilities.docxParser.isTableCellNode(containingParagraph.parentNode);
2602
+ const openParagraph = this.utilities.docxParser.containingParagraphNode(openTag.xmlTextNode);
2603
+ if (!openParagraph.parentNode) return false;
2604
+ if (!this.utilities.docxParser.isTableCellNode(openParagraph.parentNode)) return false;
2605
+ const closeParagraph = this.utilities.docxParser.containingParagraphNode(closeTag.xmlTextNode);
2606
+ if (!closeParagraph.parentNode) return false;
2607
+ if (!this.utilities.docxParser.isTableCellNode(closeParagraph.parentNode)) return false;
2608
+ const options = openTag.options;
2609
+ const forceRowLoop = (options === null || options === void 0 ? void 0 : options.loopOver) === LoopOver.Row;
2610
+
2611
+ // If both tags are in the same cell, assume it's a paragraph loop (iterate content, not rows).
2612
+ if (!forceRowLoop && openParagraph.parentNode === closeParagraph.parentNode) return false;
2613
+ return true;
2531
2614
  }
2532
2615
  splitBefore(openTag, closeTag) {
2533
2616
  const firstRow = this.utilities.docxParser.containingTableRowNode(openTag.xmlTextNode);
@@ -2566,7 +2649,6 @@ class LoopPlugin extends TemplatePlugin {
2566
2649
  _defineProperty(this, "loopStrategies", [new LoopTableStrategy(), new LoopListStrategy(), new LoopParagraphStrategy() // the default strategy
2567
2650
  ]);
2568
2651
  }
2569
-
2570
2652
  setUtilities(utilities) {
2571
2653
  this.utilities = utilities;
2572
2654
  this.loopStrategies.forEach(strategy => strategy.setUtilities(utilities));
@@ -2577,7 +2659,7 @@ class LoopPlugin extends TemplatePlugin {
2577
2659
  // Non array value - treat as a boolean condition.
2578
2660
  const isCondition = !Array.isArray(value);
2579
2661
  if (isCondition) {
2580
- if (!!value) {
2662
+ if (value) {
2581
2663
  value = [{}];
2582
2664
  } else {
2583
2665
  value = [];
@@ -2948,7 +3030,6 @@ class Zip {
2948
3030
  level: 6 // between 1 (best speed) and 9 (best compression)
2949
3031
  }
2950
3032
  });
2951
-
2952
3033
  return output;
2953
3034
  }
2954
3035
  }
@@ -2959,6 +3040,8 @@ class Delimiters {
2959
3040
  _defineProperty(this, "tagEnd", "}");
2960
3041
  _defineProperty(this, "containerTagOpen", "#");
2961
3042
  _defineProperty(this, "containerTagClose", "/");
3043
+ _defineProperty(this, "tagOptionsStart", "[");
3044
+ _defineProperty(this, "tagOptionsEnd", "]");
2962
3045
  Object.assign(this, initial);
2963
3046
  this.encodeAndValidate();
2964
3047
  if (this.containerTagOpen === this.containerTagClose) throw new Error(`${"containerTagOpen"} can not be equal to ${"containerTagClose"}`);
@@ -3006,7 +3089,7 @@ class TemplateHandler {
3006
3089
  /**
3007
3090
  * Version number of the `easy-template-x` library.
3008
3091
  */
3009
- _defineProperty(this, "version", "3.2.0" );
3092
+ _defineProperty(this, "version", "3.2.1" );
3010
3093
  _defineProperty(this, "xmlParser", new XmlParser());
3011
3094
  _defineProperty(this, "docxParser", void 0);
3012
3095
  _defineProperty(this, "compiler", void 0);
@@ -3156,6 +3239,7 @@ class TemplateHandler {
3156
3239
 
3157
3240
  exports.Base64 = Base64;
3158
3241
  exports.Binary = Binary;
3242
+ exports.COMMENT_NODE_NAME = COMMENT_NODE_NAME;
3159
3243
  exports.ContentPartType = ContentPartType;
3160
3244
  exports.DelimiterSearcher = DelimiterSearcher;
3161
3245
  exports.Delimiters = Delimiters;
@@ -3180,6 +3264,7 @@ exports.ScopeData = ScopeData;
3180
3264
  exports.TEXT_CONTENT_TYPE = TEXT_CONTENT_TYPE;
3181
3265
  exports.TEXT_NODE_NAME = TEXT_NODE_NAME;
3182
3266
  exports.TagDisposition = TagDisposition;
3267
+ exports.TagOptionsParseError = TagOptionsParseError;
3183
3268
  exports.TagParser = TagParser;
3184
3269
  exports.TemplateCompiler = TemplateCompiler;
3185
3270
  exports.TemplateExtension = TemplateExtension;
@@ -3205,6 +3290,7 @@ exports.inheritsFrom = inheritsFrom;
3205
3290
  exports.isNumber = isNumber;
3206
3291
  exports.isPromiseLike = isPromiseLike;
3207
3292
  exports.last = last;
3293
+ exports.normalizeDoubleQuotes = normalizeDoubleQuotes;
3208
3294
  exports.pushMany = pushMany;
3209
3295
  exports.sha1 = sha1;
3210
3296
  exports.stringValue = stringValue;