easy-template-x 6.2.2 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +36 -7
  2. package/dist/cjs/easy-template-x.cjs +920 -430
  3. package/dist/es/easy-template-x.mjs +920 -431
  4. package/dist/types/src/compilation/delimiters/attributesDelimiterSearcher.d.ts +15 -0
  5. package/dist/types/src/compilation/delimiters/delimiterMark.d.ts +19 -0
  6. package/dist/types/src/compilation/delimiters/delimiterSearcher.d.ts +9 -0
  7. package/dist/types/src/compilation/delimiters/delimiterSearcher.tests.d.ts +1 -0
  8. package/dist/types/src/compilation/delimiters/index.d.ts +2 -0
  9. package/dist/types/src/compilation/delimiters/textNodesDelimiterSearcher.d.ts +18 -0
  10. package/dist/types/{compilation → src/compilation}/index.d.ts +1 -2
  11. package/dist/types/src/compilation/scopeData.tests.d.ts +1 -0
  12. package/dist/types/src/compilation/tag.d.ts +29 -0
  13. package/dist/types/src/compilation/tagParser.d.ts +16 -0
  14. package/dist/types/src/compilation/tagParser.tests.d.ts +1 -0
  15. package/dist/types/src/compilation/tagUtils.d.ts +2 -0
  16. package/dist/types/{compilation → src/compilation}/templateCompiler.d.ts +1 -1
  17. package/dist/types/src/compilation/templateCompiler.tests.d.ts +1 -0
  18. package/dist/types/src/errors/malformedFileError.d.ts +3 -0
  19. package/dist/types/src/office/contentTypesFile.tests.d.ts +1 -0
  20. package/dist/types/src/office/mediaFiles.tests.d.ts +1 -0
  21. package/dist/types/{office → src/office}/officeMarkup.d.ts +8 -6
  22. package/dist/types/src/office/officeMarkup.tests.d.ts +1 -0
  23. package/dist/types/{office → src/office}/omlNode.d.ts +22 -0
  24. package/dist/types/src/office/rel.tests.d.ts +1 -0
  25. package/dist/types/{office → src/office}/relationship.d.ts +2 -2
  26. package/dist/types/src/plugins/chart/chartDataValidation.tests.d.ts +1 -0
  27. package/dist/types/{plugins → src/plugins}/chart/chartPlugin.d.ts +3 -1
  28. package/dist/types/src/plugins/image/createImage.d.ts +3 -0
  29. package/dist/types/{plugins → src/plugins}/image/imageContent.d.ts +2 -2
  30. package/dist/types/{plugins → src/plugins}/image/imagePlugin.d.ts +3 -6
  31. package/dist/types/src/plugins/image/imageUtils.d.ts +3 -0
  32. package/dist/types/src/plugins/image/updateImage.d.ts +3 -0
  33. package/dist/types/{plugins → src/plugins}/link/linkPlugin.d.ts +3 -1
  34. package/dist/types/{plugins → src/plugins}/loop/loopPlugin.d.ts +3 -1
  35. package/dist/types/{plugins → src/plugins}/loop/strategy/iLoopStrategy.d.ts +3 -3
  36. package/dist/types/{plugins → src/plugins}/loop/strategy/loopListStrategy.d.ts +3 -3
  37. package/dist/types/{plugins → src/plugins}/loop/strategy/loopParagraphStrategy.d.ts +3 -3
  38. package/dist/types/src/plugins/loop/strategy/loopParagraphStrategy.tests.d.ts +1 -0
  39. package/dist/types/{plugins → src/plugins}/loop/strategy/loopTableColumnsStrategy.d.ts +3 -3
  40. package/dist/types/{plugins → src/plugins}/loop/strategy/loopTableRowsStrategy.d.ts +3 -3
  41. package/dist/types/{plugins → src/plugins}/rawXml/rawXmlPlugin.d.ts +2 -1
  42. package/dist/types/{plugins → src/plugins}/text/textPlugin.d.ts +4 -1
  43. package/dist/types/src/templateHandler.tests.d.ts +1 -0
  44. package/dist/types/{xml → src/xml}/xml.d.ts +5 -4
  45. package/dist/types/src/xml/xml.tests.d.ts +1 -0
  46. package/dist/types/{xml → src/xml}/xmlNode.d.ts +1 -1
  47. package/dist/types/test/fixtures/fixtureUtils.d.ts +1 -0
  48. package/dist/types/test/testUtils.d.ts +8 -0
  49. package/package.json +17 -19
  50. package/src/compilation/delimiters/attributesDelimiterSearcher.ts +109 -0
  51. package/src/compilation/delimiters/delimiterMark.ts +32 -0
  52. package/src/compilation/delimiters/delimiterSearcher.tests.ts +571 -0
  53. package/src/compilation/delimiters/delimiterSearcher.ts +41 -0
  54. package/src/compilation/delimiters/index.ts +2 -0
  55. package/src/compilation/delimiters/textNodesDelimiterSearcher.ts +185 -0
  56. package/src/compilation/index.ts +1 -2
  57. package/src/compilation/scopeData.tests.ts +40 -0
  58. package/src/compilation/tag.ts +22 -3
  59. package/src/compilation/tagParser.tests.ts +845 -0
  60. package/src/compilation/tagParser.ts +170 -66
  61. package/src/compilation/tagUtils.ts +9 -0
  62. package/src/compilation/templateCompiler.tests.ts +91 -0
  63. package/src/compilation/templateCompiler.ts +1 -1
  64. package/src/delimiters.ts +1 -1
  65. package/src/errors/malformedFileError.ts +3 -7
  66. package/src/office/contentTypesFile.tests.ts +88 -0
  67. package/src/office/docx.ts +2 -2
  68. package/src/office/mediaFiles.tests.ts +56 -0
  69. package/src/office/officeMarkup.tests.ts +114 -0
  70. package/src/office/officeMarkup.ts +48 -19
  71. package/src/office/omlNode.ts +75 -2
  72. package/src/office/openXmlPart.ts +1 -1
  73. package/src/office/rel.tests.ts +23 -0
  74. package/src/office/relationship.ts +22 -6
  75. package/src/office/relsFile.ts +21 -22
  76. package/src/office/xlsx.ts +2 -2
  77. package/src/plugins/chart/chartDataValidation.tests.ts +109 -0
  78. package/src/plugins/chart/chartPlugin.ts +11 -5
  79. package/src/plugins/chart/updateChart.ts +4 -4
  80. package/src/plugins/image/createImage.ts +115 -0
  81. package/src/plugins/image/imageContent.ts +16 -4
  82. package/src/plugins/image/imagePlugin.ts +28 -138
  83. package/src/plugins/image/imageUtils.ts +22 -0
  84. package/src/plugins/image/updateImage.ts +113 -0
  85. package/src/plugins/link/linkPlugin.ts +10 -3
  86. package/src/plugins/loop/loopPlugin.ts +11 -1
  87. package/src/plugins/loop/strategy/iLoopStrategy.ts +3 -3
  88. package/src/plugins/loop/strategy/loopListStrategy.ts +3 -3
  89. package/src/plugins/loop/strategy/loopParagraphStrategy.tests.ts +64 -0
  90. package/src/plugins/loop/strategy/loopParagraphStrategy.ts +5 -5
  91. package/src/plugins/loop/strategy/loopTableColumnsStrategy.ts +3 -3
  92. package/src/plugins/loop/strategy/loopTableRowsStrategy.ts +3 -3
  93. package/src/plugins/rawXml/rawXmlPlugin.ts +8 -2
  94. package/src/plugins/text/textPlugin.ts +37 -5
  95. package/src/templateHandler.tests.ts +84 -0
  96. package/src/templateHandler.ts +12 -15
  97. package/src/utils/path.ts +8 -1
  98. package/src/xml/xml.tests.ts +260 -0
  99. package/src/xml/xml.ts +55 -24
  100. package/src/xml/xmlNode.ts +1 -1
  101. package/src/zip/jsZipHelper.ts +1 -1
  102. package/src/zip/zip.ts +3 -0
  103. package/dist/types/compilation/delimiterMark.d.ts +0 -8
  104. package/dist/types/compilation/delimiterSearcher.d.ts +0 -12
  105. package/dist/types/compilation/tag.d.ts +0 -15
  106. package/dist/types/compilation/tagParser.d.ts +0 -11
  107. package/dist/types/errors/malformedFileError.d.ts +0 -4
  108. package/src/compilation/delimiterMark.ts +0 -16
  109. package/src/compilation/delimiterSearcher.ts +0 -183
  110. /package/dist/types/{compilation → src/compilation}/scopeData.d.ts +0 -0
  111. /package/dist/types/{compilation → src/compilation}/templateContext.d.ts +0 -0
  112. /package/dist/types/{delimiters.d.ts → src/delimiters.d.ts} +0 -0
  113. /package/dist/types/{errors → src/errors}/index.d.ts +0 -0
  114. /package/dist/types/{errors → src/errors}/internalArgumentMissingError.d.ts +0 -0
  115. /package/dist/types/{errors → src/errors}/internalError.d.ts +0 -0
  116. /package/dist/types/{errors → src/errors}/maxXmlDepthError.d.ts +0 -0
  117. /package/dist/types/{errors → src/errors}/missingCloseDelimiterError.d.ts +0 -0
  118. /package/dist/types/{errors → src/errors}/missingStartDelimiterError.d.ts +0 -0
  119. /package/dist/types/{errors → src/errors}/tagOptionsParseError.d.ts +0 -0
  120. /package/dist/types/{errors → src/errors}/templateDataError.d.ts +0 -0
  121. /package/dist/types/{errors → src/errors}/templateSyntaxError.d.ts +0 -0
  122. /package/dist/types/{errors → src/errors}/unclosedTagError.d.ts +0 -0
  123. /package/dist/types/{errors → src/errors}/unidentifiedFileTypeError.d.ts +0 -0
  124. /package/dist/types/{errors → src/errors}/unknownContentTypeError.d.ts +0 -0
  125. /package/dist/types/{errors → src/errors}/unopenedTagError.d.ts +0 -0
  126. /package/dist/types/{errors → src/errors}/unsupportedFileTypeError.d.ts +0 -0
  127. /package/dist/types/{extensions → src/extensions}/extensionOptions.d.ts +0 -0
  128. /package/dist/types/{extensions → src/extensions}/index.d.ts +0 -0
  129. /package/dist/types/{extensions → src/extensions}/templateExtension.d.ts +0 -0
  130. /package/dist/types/{index.d.ts → src/index.d.ts} +0 -0
  131. /package/dist/types/{mimeType.d.ts → src/mimeType.d.ts} +0 -0
  132. /package/dist/types/{office → src/office}/contentTypesFile.d.ts +0 -0
  133. /package/dist/types/{office → src/office}/docx.d.ts +0 -0
  134. /package/dist/types/{office → src/office}/index.d.ts +0 -0
  135. /package/dist/types/{office → src/office}/mediaFiles.d.ts +0 -0
  136. /package/dist/types/{office → src/office}/openXmlPart.d.ts +0 -0
  137. /package/dist/types/{office → src/office}/relsFile.d.ts +0 -0
  138. /package/dist/types/{office → src/office}/xlsx.d.ts +0 -0
  139. /package/dist/types/{plugins → src/plugins}/chart/chartColors.d.ts +0 -0
  140. /package/dist/types/{plugins → src/plugins}/chart/chartContent.d.ts +0 -0
  141. /package/dist/types/{plugins → src/plugins}/chart/chartData.d.ts +0 -0
  142. /package/dist/types/{plugins → src/plugins}/chart/chartDataValidation.d.ts +0 -0
  143. /package/dist/types/{plugins → src/plugins}/chart/index.d.ts +0 -0
  144. /package/dist/types/{plugins → src/plugins}/chart/updateChart.d.ts +0 -0
  145. /package/dist/types/{plugins → src/plugins}/defaultPlugins.d.ts +0 -0
  146. /package/dist/types/{plugins → src/plugins}/image/index.d.ts +0 -0
  147. /package/dist/types/{plugins → src/plugins}/index.d.ts +0 -0
  148. /package/dist/types/{plugins → src/plugins}/link/index.d.ts +0 -0
  149. /package/dist/types/{plugins → src/plugins}/link/linkContent.d.ts +0 -0
  150. /package/dist/types/{plugins → src/plugins}/loop/index.d.ts +0 -0
  151. /package/dist/types/{plugins → src/plugins}/loop/loopTagOptions.d.ts +0 -0
  152. /package/dist/types/{plugins → src/plugins}/loop/strategy/index.d.ts +0 -0
  153. /package/dist/types/{plugins → src/plugins}/pluginContent.d.ts +0 -0
  154. /package/dist/types/{plugins → src/plugins}/rawXml/index.d.ts +0 -0
  155. /package/dist/types/{plugins → src/plugins}/rawXml/rawXmlContent.d.ts +0 -0
  156. /package/dist/types/{plugins → src/plugins}/templatePlugin.d.ts +0 -0
  157. /package/dist/types/{plugins → src/plugins}/text/index.d.ts +0 -0
  158. /package/dist/types/{templateData.d.ts → src/templateData.d.ts} +0 -0
  159. /package/dist/types/{templateHandler.d.ts → src/templateHandler.d.ts} +0 -0
  160. /package/dist/types/{templateHandlerOptions.d.ts → src/templateHandlerOptions.d.ts} +0 -0
  161. /package/dist/types/{types.d.ts → src/types.d.ts} +0 -0
  162. /package/dist/types/{utils → src/utils}/array.d.ts +0 -0
  163. /package/dist/types/{utils → src/utils}/base64.d.ts +0 -0
  164. /package/dist/types/{utils → src/utils}/binary.d.ts +0 -0
  165. /package/dist/types/{utils → src/utils}/index.d.ts +0 -0
  166. /package/dist/types/{utils → src/utils}/number.d.ts +0 -0
  167. /package/dist/types/{utils → src/utils}/path.d.ts +0 -0
  168. /package/dist/types/{utils → src/utils}/regex.d.ts +0 -0
  169. /package/dist/types/{utils → src/utils}/sha1.d.ts +0 -0
  170. /package/dist/types/{utils → src/utils}/txt.d.ts +0 -0
  171. /package/dist/types/{utils → src/utils}/types.d.ts +0 -0
  172. /package/dist/types/{xml → src/xml}/index.d.ts +0 -0
  173. /package/dist/types/{xml → src/xml}/xmlDepthTracker.d.ts +0 -0
  174. /package/dist/types/{xml → src/xml}/xmlTreeIterator.d.ts +0 -0
  175. /package/dist/types/{zip → src/zip}/index.d.ts +0 -0
  176. /package/dist/types/{zip → src/zip}/jsZipHelper.d.ts +0 -0
  177. /package/dist/types/{zip → src/zip}/zip.d.ts +0 -0
  178. /package/dist/types/{zip → src/zip}/zipObject.d.ts +0 -0
@@ -0,0 +1,109 @@
1
+ import { TagPlacement } from "src/compilation/tag";
2
+ import { tagRegex } from "src/compilation/tagUtils";
3
+ import { Delimiters } from "src/delimiters";
4
+ import { InternalArgumentMissingError } from "src/errors";
5
+ import { OmlNode } from "src/office";
6
+ import { xml, XmlGeneralNode, XmlNode, XmlTreeIterator } from "src/xml";
7
+ import { AttributeDelimiterMark, DelimiterMark } from "./delimiterMark";
8
+
9
+ const drawingDescriptionAttributeName = "descr";
10
+
11
+ export class AttributesDelimiterSearcher {
12
+
13
+ private readonly visitedNodes: Set<XmlNode> = new Set();
14
+
15
+ private readonly delimiters: Delimiters;
16
+ private readonly tagRegex: RegExp;
17
+
18
+ constructor(delimiters: Delimiters) {
19
+ if (!delimiters)
20
+ throw new InternalArgumentMissingError("delimiters");
21
+
22
+ this.delimiters = delimiters;
23
+ this.tagRegex = tagRegex(delimiters, true);
24
+ }
25
+
26
+ public processNode(it: XmlTreeIterator, delimiters: DelimiterMark[]): void {
27
+
28
+ // Ignore irrelevant nodes
29
+ if (!this.shouldSearchNode(it)) {
30
+ return;
31
+ }
32
+
33
+ // Search delimiters in attributes
34
+ this.findDelimiters(it, delimiters);
35
+ }
36
+
37
+ private shouldSearchNode(it: XmlTreeIterator): it is XmlTreeIterator<XmlGeneralNode> {
38
+
39
+ if (this.visitedNodes.has(it.node)) {
40
+ return false;
41
+ }
42
+ this.visitedNodes.add(it.node);
43
+
44
+ if (!xml.query.isGeneralNode(it.node))
45
+ return false;
46
+ if (Object.keys(it.node.attributes || {}).length === 0)
47
+ return false;
48
+
49
+ // Currently we only support description attributes of drawing objects
50
+ if (!this.isDrawingPropertiesNode(it.node)) {
51
+ return false;
52
+ }
53
+ if (!it.node.attributes[drawingDescriptionAttributeName]) {
54
+ return false;
55
+ }
56
+
57
+ return true;
58
+ }
59
+
60
+ private isDrawingPropertiesNode(node: XmlGeneralNode): boolean {
61
+
62
+ // Node is drawing properties
63
+ if (node.nodeName !== OmlNode.Wp.DocPr) {
64
+ return false;
65
+ }
66
+
67
+ // Parent is drawing
68
+ if (!node.parentNode) {
69
+ return false;
70
+ }
71
+ const parent = xml.query.findParentByName(node, OmlNode.W.Drawing);
72
+ return !!parent;
73
+ }
74
+
75
+ private findDelimiters(it: XmlTreeIterator<XmlGeneralNode>, delimiters: DelimiterMark[]): void {
76
+
77
+ // Currently we only support description attributes of drawing objects
78
+ this.findDelimitersInAttribute(it.node, drawingDescriptionAttributeName, delimiters);
79
+ }
80
+
81
+ private findDelimitersInAttribute(node: XmlGeneralNode, attributeName: string, delimiters: DelimiterMark[]): void {
82
+ const attrValue = node.attributes?.[attributeName];
83
+ if (!attrValue) {
84
+ return;
85
+ }
86
+
87
+ const matches = attrValue.matchAll(this.tagRegex);
88
+ for (const match of matches) {
89
+ const tag = match[0];
90
+ const openDelimiterIndex = match.index;
91
+ const closeDelimiterIndex = openDelimiterIndex + tag.length - this.delimiters.tagEnd.length;
92
+
93
+ const openDelimiter = this.createCurrentDelimiterMark(openDelimiterIndex, true, node, attributeName);
94
+ const closeDelimiter = this.createCurrentDelimiterMark(closeDelimiterIndex, false, node, attributeName);
95
+ delimiters.push(openDelimiter);
96
+ delimiters.push(closeDelimiter);
97
+ }
98
+ }
99
+
100
+ private createCurrentDelimiterMark(index: number, isOpen: boolean, xmlNode: XmlGeneralNode, attributeName: string): AttributeDelimiterMark {
101
+ return {
102
+ placement: TagPlacement.Attribute,
103
+ isOpen: isOpen,
104
+ index: index,
105
+ attributeName: attributeName,
106
+ xmlNode: xmlNode,
107
+ };
108
+ }
109
+ }
@@ -0,0 +1,32 @@
1
+ import { OpenXmlPart } from "src/office";
2
+ import { XmlGeneralNode, XmlTextNode } from "src/xml";
3
+ import { TagPlacement } from "src/compilation/tag";
4
+
5
+ export type DelimiterMark = TextNodeDelimiterMark | AttributeDelimiterMark;
6
+
7
+ export interface BaseDelimiterMark {
8
+ openXmlPart?: OpenXmlPart;
9
+ /**
10
+ * Is this an open delimiter or a close delimiter
11
+ */
12
+ isOpen: boolean;
13
+ }
14
+
15
+ export interface TextNodeDelimiterMark extends BaseDelimiterMark {
16
+ placement: typeof TagPlacement.TextNode;
17
+ xmlTextNode: XmlTextNode;
18
+ /**
19
+ * Index inside the text node
20
+ */
21
+ index: number;
22
+ }
23
+
24
+ export interface AttributeDelimiterMark extends BaseDelimiterMark {
25
+ placement: typeof TagPlacement.Attribute;
26
+ xmlNode: XmlGeneralNode;
27
+ attributeName: string;
28
+ /**
29
+ * Index inside the attribute
30
+ */
31
+ index: number;
32
+ }
@@ -0,0 +1,571 @@
1
+ import { DelimiterMark, DelimiterSearcher, TagPlacement } from "src/compilation";
2
+ import { Delimiters } from "src/delimiters";
3
+ import { xml, XmlNodeType } from "src/xml";
4
+ import { parseXml } from "test/testUtils";
5
+ import { describe, expect, test } from "vitest";
6
+
7
+ describe(DelimiterSearcher, () => {
8
+
9
+ describe('single character delimiters', () => {
10
+
11
+ test('simple paragraph', () => {
12
+
13
+ const paragraph = parseXml(`
14
+ <w:p>
15
+ <w:r>
16
+ <w:t>{#loop}{/loop}</w:t>
17
+ </w:r>
18
+ </w:p>
19
+ `);
20
+
21
+ const textNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
22
+ const expected: DelimiterMark[] = [
23
+ {
24
+ placement: TagPlacement.TextNode,
25
+ isOpen: true,
26
+ index: 0,
27
+ xmlTextNode: textNode
28
+ },
29
+ {
30
+ placement: TagPlacement.TextNode,
31
+ isOpen: false,
32
+ index: 6,
33
+ xmlTextNode: textNode
34
+ },
35
+ {
36
+ placement: TagPlacement.TextNode,
37
+ isOpen: true,
38
+ index: 7,
39
+ xmlTextNode: textNode
40
+ },
41
+ {
42
+ placement: TagPlacement.TextNode,
43
+ isOpen: false,
44
+ index: 13,
45
+ xmlTextNode: textNode
46
+ }
47
+ ];
48
+
49
+ const searcher = createDelimiterSearcher({
50
+ tagStart: '{',
51
+ tagEnd: '}'
52
+ });
53
+ const delimiters = searcher.findDelimiters(paragraph);
54
+
55
+ expect(delimiters).toEqual(expected);
56
+ });
57
+
58
+ test('two different text nodes', () => {
59
+
60
+ const paragraph = parseXml(`
61
+ <w:p>
62
+ <w:r>
63
+ <w:t>{#lo</w:t>
64
+ <w:t>op}</w:t>
65
+ </w:r>
66
+ </w:p>
67
+ `);
68
+
69
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
70
+ const secondTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 1, 0);
71
+ const expected: DelimiterMark[] = [
72
+ {
73
+ placement: TagPlacement.TextNode,
74
+ isOpen: true,
75
+ index: 0,
76
+ xmlTextNode: firstTextNode
77
+ },
78
+ {
79
+ placement: TagPlacement.TextNode,
80
+ isOpen: false,
81
+ index: 2,
82
+ xmlTextNode: secondTextNode
83
+ }
84
+ ];
85
+
86
+ const searcher = createDelimiterSearcher({
87
+ tagStart: '{',
88
+ tagEnd: '}'
89
+ });
90
+ const delimiters = searcher.findDelimiters(paragraph);
91
+
92
+ expect(delimiters).toEqual(expected);
93
+ });
94
+
95
+ test('two different run nodes', () => {
96
+
97
+ const paragraph = parseXml(`
98
+ <w:p>
99
+ <w:r>
100
+ <w:t>{</w:t>
101
+ </w:r>
102
+ <w:r>
103
+ <w:t>tag</w:t>
104
+ </w:r>
105
+ <w:r>
106
+ <w:t>}</w:t>
107
+ </w:r>
108
+ </w:p>
109
+ `);
110
+
111
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
112
+ const thirdTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 2, 0, 0);
113
+ const expected: DelimiterMark[] = [
114
+ {
115
+ placement: TagPlacement.TextNode,
116
+ isOpen: true,
117
+ index: 0,
118
+ xmlTextNode: firstTextNode
119
+ },
120
+ {
121
+ placement: TagPlacement.TextNode,
122
+ isOpen: false,
123
+ index: 0,
124
+ xmlTextNode: thirdTextNode
125
+ }
126
+ ];
127
+
128
+ const searcher = createDelimiterSearcher({
129
+ tagStart: '{',
130
+ tagEnd: '}'
131
+ });
132
+ const delimiters = searcher.findDelimiters(paragraph);
133
+
134
+ expect(delimiters).toEqual(expected);
135
+ });
136
+
137
+ test("inline drawing in the middle of a tag", () => {
138
+ const paragraph = parseXml(`
139
+ <w:p>
140
+ <w:r>
141
+ <w:t xml:space="preserve">{Text </w:t>
142
+ </w:r>
143
+ <w:r>
144
+ <w:drawing>
145
+ <wp:inline>
146
+ <wp:docPr descr="{Attribute Tag}"/>
147
+ </wp:inline>
148
+ </w:drawing>
149
+ </w:r>
150
+ <w:r>
151
+ <w:t>Tag}</w:t>
152
+ </w:r>
153
+ </w:p>
154
+ `, false);
155
+
156
+ const openTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
157
+ const closeTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 2, 0, 0);
158
+ const imagePropertiesNode = xml.query.findByPath(paragraph, XmlNodeType.General, 1, 0, 0, 0);
159
+ const expected: DelimiterMark[] = [
160
+ {
161
+ placement: TagPlacement.TextNode,
162
+ isOpen: true,
163
+ index: 0,
164
+ xmlTextNode: openTextNode
165
+ },
166
+ {
167
+ placement: TagPlacement.Attribute,
168
+ isOpen: true,
169
+ index: 0,
170
+ xmlNode: imagePropertiesNode,
171
+ attributeName: 'descr'
172
+ },
173
+ {
174
+ placement: TagPlacement.Attribute,
175
+ isOpen: false,
176
+ index: 14,
177
+ xmlNode: imagePropertiesNode,
178
+ attributeName: 'descr'
179
+ },
180
+ {
181
+ placement: TagPlacement.TextNode,
182
+ isOpen: false,
183
+ index: 3,
184
+ xmlTextNode: closeTextNode
185
+ },
186
+ ];
187
+
188
+ const searcher = createDelimiterSearcher({
189
+ tagStart: '{',
190
+ tagEnd: '}'
191
+ });
192
+
193
+ const delimiters = searcher.findDelimiters(paragraph);
194
+ expect(delimiters).toEqual(expected);
195
+ });
196
+ });
197
+
198
+ describe('multi character delimiters', () => {
199
+
200
+ test('simple paragraph', () => {
201
+
202
+ const paragraph = parseXml(`
203
+ <w:p>
204
+ <w:r>
205
+ <w:t>{{#loop}}{{/loop}}</w:t>
206
+ </w:r>
207
+ </w:p>
208
+ `);
209
+
210
+ const textNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
211
+ const expected: DelimiterMark[] = [
212
+ {
213
+ placement: TagPlacement.TextNode,
214
+ isOpen: true,
215
+ index: 0,
216
+ xmlTextNode: textNode
217
+ },
218
+ {
219
+ placement: TagPlacement.TextNode,
220
+ isOpen: false,
221
+ index: 7,
222
+ xmlTextNode: textNode
223
+ },
224
+ {
225
+ placement: TagPlacement.TextNode,
226
+ isOpen: true,
227
+ index: 9,
228
+ xmlTextNode: textNode
229
+ },
230
+ {
231
+ placement: TagPlacement.TextNode,
232
+ isOpen: false,
233
+ index: 16,
234
+ xmlTextNode: textNode
235
+ }
236
+ ];
237
+
238
+ const searcher = createDelimiterSearcher({
239
+ tagStart: '{{',
240
+ tagEnd: '}}'
241
+ });
242
+ const delimiters = searcher.findDelimiters(paragraph);
243
+
244
+ expect(delimiters).toEqual(expected);
245
+ });
246
+
247
+ test('two different text nodes', () => {
248
+
249
+ const paragraph = parseXml(`
250
+ <w:p>
251
+ <w:r>
252
+ <w:t>{{#lo</w:t>
253
+ <w:t>op}}</w:t>
254
+ </w:r>
255
+ </w:p>
256
+ `);
257
+
258
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
259
+ const secondTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 1, 0);
260
+ const expected: DelimiterMark[] = [
261
+ {
262
+ placement: TagPlacement.TextNode,
263
+ isOpen: true,
264
+ index: 0,
265
+ xmlTextNode: firstTextNode
266
+ },
267
+ {
268
+ placement: TagPlacement.TextNode,
269
+ isOpen: false,
270
+ index: 2,
271
+ xmlTextNode: secondTextNode
272
+ }
273
+ ];
274
+
275
+ const searcher = createDelimiterSearcher({
276
+ tagStart: '{{',
277
+ tagEnd: '}}'
278
+ });
279
+ const delimiters = searcher.findDelimiters(paragraph);
280
+
281
+ expect(delimiters).toEqual(expected);
282
+ });
283
+
284
+ test('two different run nodes', () => {
285
+
286
+ const paragraph = parseXml(`
287
+ <w:p>
288
+ <w:r>
289
+ <w:t>{{</w:t>
290
+ </w:r>
291
+ <w:r>
292
+ <w:t>tag</w:t>
293
+ </w:r>
294
+ <w:r>
295
+ <w:t>}}</w:t>
296
+ </w:r>
297
+ </w:p>
298
+ `);
299
+
300
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
301
+ const thirdTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 2, 0, 0);
302
+ const expected: DelimiterMark[] = [
303
+ {
304
+ placement: TagPlacement.TextNode,
305
+ isOpen: true,
306
+ index: 0,
307
+ xmlTextNode: firstTextNode
308
+ },
309
+ {
310
+ placement: TagPlacement.TextNode,
311
+ isOpen: false,
312
+ index: 0,
313
+ xmlTextNode: thirdTextNode
314
+ }
315
+ ];
316
+
317
+ const searcher = createDelimiterSearcher({
318
+ tagStart: '{{',
319
+ tagEnd: '}}'
320
+ });
321
+ const delimiters = searcher.findDelimiters(paragraph);
322
+
323
+ expect(delimiters).toEqual(expected);
324
+ });
325
+
326
+ test('delimiters splitted across several different run nodes', () => {
327
+
328
+ const paragraph = parseXml(`
329
+ <w:p>
330
+ <w:r>
331
+ <w:t>{</w:t>
332
+ </w:r>
333
+ <w:r>
334
+ <w:t>{{tag}</w:t>
335
+ </w:r>
336
+ <w:r>
337
+ <w:t>}</w:t>
338
+ </w:r>
339
+ <w:r>
340
+ <w:t>}</w:t>
341
+ </w:r>
342
+ </w:p>
343
+ `);
344
+
345
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
346
+ const expected: DelimiterMark[] = [
347
+ {
348
+ placement: TagPlacement.TextNode,
349
+ isOpen: true,
350
+ index: 0,
351
+ xmlTextNode: firstTextNode
352
+ },
353
+ {
354
+ placement: TagPlacement.TextNode,
355
+ isOpen: false,
356
+ index: 6,
357
+ xmlTextNode: firstTextNode
358
+ }
359
+ ];
360
+
361
+ const searcher = createDelimiterSearcher({
362
+ tagStart: '{{{',
363
+ tagEnd: '}}}'
364
+ });
365
+ const delimiters = searcher.findDelimiters(paragraph);
366
+
367
+ expect(delimiters).toEqual(expected);
368
+ });
369
+
370
+ test('delimiters splitted across several different run nodes - with attribute tags of a floating drawing in the middle', () => {
371
+
372
+ const paragraph = parseXml(`
373
+ <w:p>
374
+ <w:r>
375
+ <w:t>{</w:t>
376
+ </w:r>
377
+ <w:r>
378
+ <w:t>{{Text Tag}</w:t>
379
+ </w:r>
380
+ <w:r>
381
+ <w:drawing>
382
+ <wp:anchor>
383
+ <wp:docPr descr="{{{Attribute Tag}}}"/>
384
+ </wp:anchor>
385
+ </w:drawing>
386
+ </w:r>
387
+ <w:r>
388
+ <w:t>}</w:t>
389
+ </w:r>
390
+ <w:r>
391
+ <w:t>}</w:t>
392
+ </w:r>
393
+ </w:p>
394
+ `, false);
395
+
396
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
397
+ const imagePropertiesNode = xml.query.findByPath(paragraph, XmlNodeType.General, 2, 0, 0, 0);
398
+ const expected: DelimiterMark[] = [
399
+ {
400
+ placement: TagPlacement.TextNode,
401
+ isOpen: true,
402
+ index: 0,
403
+ xmlTextNode: firstTextNode
404
+ },
405
+ {
406
+ placement: TagPlacement.Attribute,
407
+ isOpen: true,
408
+ index: 0,
409
+ xmlNode: imagePropertiesNode,
410
+ attributeName: 'descr'
411
+ },
412
+ {
413
+ placement: TagPlacement.Attribute,
414
+ isOpen: false,
415
+ index: 16,
416
+ xmlNode: imagePropertiesNode,
417
+ attributeName: 'descr'
418
+ },
419
+ {
420
+ placement: TagPlacement.TextNode,
421
+ isOpen: false,
422
+ index: 11,
423
+ xmlTextNode: firstTextNode
424
+ }
425
+ ];
426
+
427
+ const searcher = createDelimiterSearcher({
428
+ tagStart: '{{{',
429
+ tagEnd: '}}}'
430
+ });
431
+ const delimiters = searcher.findDelimiters(paragraph);
432
+
433
+ expect(delimiters.length).toEqual(expected.length);
434
+ expect(delimiters).toEqual(expected);
435
+ });
436
+ });
437
+
438
+ describe('text contains multiple delimiter prefixes', () => {
439
+
440
+ test('simple paragraph', () => {
441
+
442
+ const paragraph = parseXml(`
443
+ <w:p>
444
+ <w:r>
445
+ <w:t>{{!#loop!}}{{!/loop!}}</w:t>
446
+ </w:r>
447
+ </w:p>
448
+ `);
449
+
450
+ const textNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
451
+ const expected: DelimiterMark[] = [
452
+ {
453
+ placement: TagPlacement.TextNode,
454
+ isOpen: true,
455
+ index: 1,
456
+ xmlTextNode: textNode
457
+ },
458
+ {
459
+ placement: TagPlacement.TextNode,
460
+ isOpen: false,
461
+ index: 8,
462
+ xmlTextNode: textNode
463
+ },
464
+ {
465
+ placement: TagPlacement.TextNode,
466
+ isOpen: true,
467
+ index: 12,
468
+ xmlTextNode: textNode
469
+ },
470
+ {
471
+ placement: TagPlacement.TextNode,
472
+ isOpen: false,
473
+ index: 19,
474
+ xmlTextNode: textNode
475
+ }
476
+ ];
477
+
478
+ const searcher = createDelimiterSearcher({
479
+ tagStart: '{!',
480
+ tagEnd: '!}'
481
+ });
482
+ const delimiters = searcher.findDelimiters(paragraph);
483
+
484
+ expect(delimiters).toEqual(expected);
485
+ });
486
+
487
+ test('two different text nodes', () => {
488
+
489
+ const paragraph = parseXml(`
490
+ <w:p>
491
+ <w:r>
492
+ <w:t>{!#lo</w:t>
493
+ <w:t>op!}</w:t>
494
+ </w:r>
495
+ </w:p>
496
+ `);
497
+
498
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
499
+ const secondTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 1, 0);
500
+ const expected: DelimiterMark[] = [
501
+ {
502
+ placement: TagPlacement.TextNode,
503
+ isOpen: true,
504
+ index: 0,
505
+ xmlTextNode: firstTextNode
506
+ },
507
+ {
508
+ placement: TagPlacement.TextNode,
509
+ isOpen: false,
510
+ index: 2,
511
+ xmlTextNode: secondTextNode
512
+ }
513
+ ];
514
+
515
+ const searcher = createDelimiterSearcher({
516
+ tagStart: '{!',
517
+ tagEnd: '!}'
518
+ });
519
+ const delimiters = searcher.findDelimiters(paragraph);
520
+
521
+ expect(delimiters).toEqual(expected);
522
+ });
523
+
524
+ test('two different run nodes', () => {
525
+
526
+ const paragraph = parseXml(`
527
+ <w:p>
528
+ <w:r>
529
+ <w:t>{!</w:t>
530
+ </w:r>
531
+ <w:r>
532
+ <w:t>tag</w:t>
533
+ </w:r>
534
+ <w:r>
535
+ <w:t>!}</w:t>
536
+ </w:r>
537
+ </w:p>
538
+ `);
539
+
540
+ const firstTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 0, 0, 0);
541
+ const thirdTextNode = xml.query.findByPath(paragraph, XmlNodeType.Text, 2, 0, 0);
542
+ const expected: DelimiterMark[] = [
543
+ {
544
+ placement: TagPlacement.TextNode,
545
+ isOpen: true,
546
+ index: 0,
547
+ xmlTextNode: firstTextNode
548
+ },
549
+ {
550
+ placement: TagPlacement.TextNode,
551
+ isOpen: false,
552
+ index: 0,
553
+ xmlTextNode: thirdTextNode
554
+ }
555
+ ];
556
+
557
+ const searcher = createDelimiterSearcher({
558
+ tagStart: '{!',
559
+ tagEnd: '!}'
560
+ });
561
+ const delimiters = searcher.findDelimiters(paragraph);
562
+
563
+ expect(delimiters).toEqual(expected);
564
+ });
565
+ });
566
+ });
567
+
568
+ function createDelimiterSearcher(delimitersSetup?: Partial<Delimiters>): DelimiterSearcher {
569
+ const delimiters = new Delimiters(delimitersSetup);
570
+ return new DelimiterSearcher(delimiters, 20);
571
+ }