easy-template-x 3.2.1 → 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 (106) hide show
  1. package/README.md +37 -1
  2. package/dist/cjs/easy-template-x.js +150 -95
  3. package/dist/es/easy-template-x.js +144 -86
  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 -58
  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 +24 -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/plugins/loop/loopPlugin.ts +1 -1
  101. package/src/plugins/loop/loopTagOptions.ts +14 -0
  102. package/src/plugins/loop/strategy/loopTableStrategy.ts +22 -3
  103. package/src/plugins/templatePlugin.ts +0 -2
  104. package/src/types.ts +1 -3
  105. package/src/utils/txt.ts +29 -1
  106. package/src/xml/xmlNode.ts +8 -4
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)
@@ -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) {
@@ -536,31 +542,39 @@ const XmlNode = {
536
542
  * The conversion is always deep.
537
543
  */
538
544
  fromDomNode(domNode) {
539
- var _domNode$textContent;
540
545
  let xmlNode;
541
546
 
542
547
  // basic properties
543
548
  switch (domNode.nodeType) {
544
549
  case domNode.TEXT_NODE:
545
- xmlNode = this.createTextNode(domNode.textContent);
546
- break;
550
+ {
551
+ xmlNode = this.createTextNode(domNode.textContent);
552
+ break;
553
+ }
547
554
  case domNode.COMMENT_NODE:
548
- xmlNode = this.createCommentNode((_domNode$textContent = domNode.textContent) === null || _domNode$textContent === void 0 ? void 0 : _domNode$textContent.trim());
549
- break;
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
+ }
550
560
  case domNode.ELEMENT_NODE:
551
- const generalNode = xmlNode = this.createGeneralNode(domNode.nodeName);
552
- const attributes = domNode.attributes;
553
- if (attributes) {
554
- generalNode.attributes = {};
555
- for (let i = 0; i < attributes.length; i++) {
556
- const curAttribute = attributes.item(i);
557
- generalNode.attributes[curAttribute.name] = curAttribute.value;
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
+ }
558
570
  }
571
+ break;
559
572
  }
560
- break;
561
573
  default:
562
- xmlNode = this.createGeneralNode(domNode.nodeName);
563
- break;
574
+ {
575
+ xmlNode = this.createGeneralNode(domNode.nodeName);
576
+ break;
577
+ }
564
578
  }
565
579
 
566
580
  // children
@@ -836,9 +850,11 @@ const XmlNode = {
836
850
  *
837
851
  * * **Note:** Prefer calling with explicit index.
838
852
  */
853
+
839
854
  /**
840
855
  * Remove a child node from it's parent. Returns the removed child.
841
856
  */
857
+
842
858
  function removeChild(parent, childOrIndex) {
843
859
  if (!parent) throw new MissingArgumentError("parent");
844
860
  if (childOrIndex === null || childOrIndex === undefined) throw new MissingArgumentError("childOrIndex");
@@ -1115,7 +1131,7 @@ class ScopeData {
1115
1131
  const curPath = args.strPath.slice();
1116
1132
  while (result === undefined && curPath.length) {
1117
1133
  curPath.pop();
1118
- result = getProp__default["default"](args.data, curPath.concat(lastKey));
1134
+ result = getProp(args.data, curPath.concat(lastKey));
1119
1135
  }
1120
1136
  return result;
1121
1137
  }
@@ -1165,7 +1181,8 @@ class TagParser {
1165
1181
  _defineProperty(this, "tagRegex", void 0);
1166
1182
  if (!docParser) throw new MissingArgumentError("docParser");
1167
1183
  if (!delimiters) throw new MissingArgumentError("delimiters");
1168
- 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');
1169
1186
  }
1170
1187
  parse(delimiters) {
1171
1188
  const tags = [];
@@ -1264,23 +1281,44 @@ class TagParser {
1264
1281
  closeDelimiter.xmlTextNode = endTextNode;
1265
1282
  }
1266
1283
  processTag(tag) {
1284
+ var _tagParts$groups, _tagParts$groups2;
1267
1285
  tag.rawText = tag.xmlTextNode.textContent;
1268
1286
  const tagParts = this.tagRegex.exec(tag.rawText);
1269
- const tagContent = (tagParts[1] || '').trim();
1270
- 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)) {
1271
1291
  tag.disposition = TagDisposition.SelfClosed;
1272
1292
  return;
1273
1293
  }
1274
- 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)) {
1275
1307
  tag.disposition = TagDisposition.Open;
1276
- tag.name = tagContent.slice(this.delimiters.containerTagOpen.length).trim();
1277
- } 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)) {
1278
1314
  tag.disposition = TagDisposition.Close;
1279
- tag.name = tagContent.slice(this.delimiters.containerTagClose.length).trim();
1280
- } else {
1281
- tag.disposition = TagDisposition.SelfClosed;
1282
- tag.name = tagContent;
1315
+ tag.name = tagName.slice(this.delimiters.containerTagClose.length).trim();
1316
+ return;
1283
1317
  }
1318
+
1319
+ // Self-closed tag.
1320
+ tag.disposition = TagDisposition.SelfClosed;
1321
+ tag.name = tagName;
1284
1322
  }
1285
1323
  }
1286
1324
 
@@ -1323,8 +1361,6 @@ class MimeTypeHelper {
1323
1361
  }
1324
1362
  }
1325
1363
 
1326
- /* eslint-disable @typescript-eslint/member-ordering */
1327
-
1328
1364
  class TemplatePlugin {
1329
1365
  constructor() {
1330
1366
  /**
@@ -1877,6 +1913,7 @@ class Docx {
1877
1913
  //
1878
1914
  // static methods
1879
1915
  //
1916
+
1880
1917
  static async open(zip, xmlParser) {
1881
1918
  const mainDocumentPath = await Docx.getMainDocumentPath(zip, xmlParser);
1882
1919
  if (!mainDocumentPath) throw new MalformedFileError('docx');
@@ -2023,6 +2060,7 @@ class DocxParser {
2023
2060
  //
2024
2061
  // constructor
2025
2062
  //
2063
+
2026
2064
  constructor(xmlParser) {
2027
2065
  this.xmlParser = xmlParser;
2028
2066
  }
@@ -2547,6 +2585,12 @@ class LoopParagraphStrategy {
2547
2585
  }
2548
2586
  }
2549
2587
 
2588
+ let LoopOver = /*#__PURE__*/function (LoopOver) {
2589
+ LoopOver["Row"] = "row";
2590
+ LoopOver["Content"] = "content";
2591
+ return LoopOver;
2592
+ }({});
2593
+
2550
2594
  class LoopTableStrategy {
2551
2595
  constructor() {
2552
2596
  _defineProperty(this, "utilities", void 0);
@@ -2555,9 +2599,18 @@ class LoopTableStrategy {
2555
2599
  this.utilities = utilities;
2556
2600
  }
2557
2601
  isApplicable(openTag, closeTag) {
2558
- const containingParagraph = this.utilities.docxParser.containingParagraphNode(openTag.xmlTextNode);
2559
- if (!containingParagraph.parentNode) return false;
2560
- 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;
2561
2614
  }
2562
2615
  splitBefore(openTag, closeTag) {
2563
2616
  const firstRow = this.utilities.docxParser.containingTableRowNode(openTag.xmlTextNode);
@@ -2596,7 +2649,6 @@ class LoopPlugin extends TemplatePlugin {
2596
2649
  _defineProperty(this, "loopStrategies", [new LoopTableStrategy(), new LoopListStrategy(), new LoopParagraphStrategy() // the default strategy
2597
2650
  ]);
2598
2651
  }
2599
-
2600
2652
  setUtilities(utilities) {
2601
2653
  this.utilities = utilities;
2602
2654
  this.loopStrategies.forEach(strategy => strategy.setUtilities(utilities));
@@ -2607,7 +2659,7 @@ class LoopPlugin extends TemplatePlugin {
2607
2659
  // Non array value - treat as a boolean condition.
2608
2660
  const isCondition = !Array.isArray(value);
2609
2661
  if (isCondition) {
2610
- if (!!value) {
2662
+ if (value) {
2611
2663
  value = [{}];
2612
2664
  } else {
2613
2665
  value = [];
@@ -2978,7 +3030,6 @@ class Zip {
2978
3030
  level: 6 // between 1 (best speed) and 9 (best compression)
2979
3031
  }
2980
3032
  });
2981
-
2982
3033
  return output;
2983
3034
  }
2984
3035
  }
@@ -2989,6 +3040,8 @@ class Delimiters {
2989
3040
  _defineProperty(this, "tagEnd", "}");
2990
3041
  _defineProperty(this, "containerTagOpen", "#");
2991
3042
  _defineProperty(this, "containerTagClose", "/");
3043
+ _defineProperty(this, "tagOptionsStart", "[");
3044
+ _defineProperty(this, "tagOptionsEnd", "]");
2992
3045
  Object.assign(this, initial);
2993
3046
  this.encodeAndValidate();
2994
3047
  if (this.containerTagOpen === this.containerTagClose) throw new Error(`${"containerTagOpen"} can not be equal to ${"containerTagClose"}`);
@@ -3211,6 +3264,7 @@ exports.ScopeData = ScopeData;
3211
3264
  exports.TEXT_CONTENT_TYPE = TEXT_CONTENT_TYPE;
3212
3265
  exports.TEXT_NODE_NAME = TEXT_NODE_NAME;
3213
3266
  exports.TagDisposition = TagDisposition;
3267
+ exports.TagOptionsParseError = TagOptionsParseError;
3214
3268
  exports.TagParser = TagParser;
3215
3269
  exports.TemplateCompiler = TemplateCompiler;
3216
3270
  exports.TemplateExtension = TemplateExtension;
@@ -3236,6 +3290,7 @@ exports.inheritsFrom = inheritsFrom;
3236
3290
  exports.isNumber = isNumber;
3237
3291
  exports.isPromiseLike = isPromiseLike;
3238
3292
  exports.last = last;
3293
+ exports.normalizeDoubleQuotes = normalizeDoubleQuotes;
3239
3294
  exports.pushMany = pushMany;
3240
3295
  exports.sha1 = sha1;
3241
3296
  exports.stringValue = stringValue;