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,114 @@
1
+ import { officeMarkup, OfficeMarkup } from "src/office";
2
+ import { XmlTextNode } from "src/xml";
3
+ import { parseXml } from "test/testUtils";
4
+ import { describe, expect, test } from "vitest";
5
+
6
+ describe(OfficeMarkup, () => {
7
+
8
+ describe("modify", () => {
9
+
10
+ describe(officeMarkup.modify.joinTextNodesRange, () => {
11
+
12
+ test('join a range of text nodes from the same run', () => {
13
+ const paragraphNode = parseXml(`
14
+ <w:p>
15
+ <w:r>
16
+ <w:t>1</w:t>
17
+ <w:t>2</w:t>
18
+ <w:t>3</w:t>
19
+ </w:r>
20
+ </w:p>
21
+ `);
22
+ const runNode = paragraphNode.childNodes[0];
23
+ const firstXmlTextNode = runNode.childNodes[0].childNodes[0] as XmlTextNode;
24
+ expect(firstXmlTextNode.textContent).toEqual('1');
25
+ const lastXmlTextNode = runNode.childNodes[2].childNodes[0] as XmlTextNode;
26
+ expect(lastXmlTextNode.textContent).toEqual('3');
27
+
28
+ officeMarkup.modify.joinTextNodesRange(firstXmlTextNode, lastXmlTextNode);
29
+
30
+ expect(runNode.childNodes.length).toEqual(1);
31
+ expect(runNode.childNodes[0].childNodes.length).toEqual(1);
32
+ expect(runNode.childNodes[0].childNodes[0]).toEqual(firstXmlTextNode);
33
+ expect(firstXmlTextNode.textContent).toEqual('123');
34
+ });
35
+
36
+ test('join a range of text nodes from three different runs', () => {
37
+ const paragraphNode = parseXml(`
38
+ <w:p>
39
+ <w:r>
40
+ <w:t>1</w:t>
41
+ <w:t>2</w:t>
42
+ <w:t>3</w:t>
43
+ </w:r>
44
+ <w:r>
45
+ <w:t>4</w:t>
46
+ </w:r>
47
+ <w:r>
48
+ <w:t>5</w:t>
49
+ <w:t>6</w:t>
50
+ </w:r>
51
+ </w:p>
52
+ `);
53
+ const firstRunNode = paragraphNode.childNodes[0];
54
+ const firstXmlTextNode = firstRunNode.childNodes[0].childNodes[0] as XmlTextNode;
55
+ expect(firstXmlTextNode.textContent).toEqual('1');
56
+ const thirdRunNode = paragraphNode.childNodes[2];
57
+ const lastXmlTextNode = thirdRunNode.childNodes[1].childNodes[0] as XmlTextNode;
58
+ expect(lastXmlTextNode.textContent).toEqual('6');
59
+
60
+ officeMarkup.modify.joinTextNodesRange(firstXmlTextNode, lastXmlTextNode);
61
+
62
+ expect(paragraphNode.childNodes.length).toEqual(1);
63
+ expect(firstRunNode.childNodes.length).toEqual(1);
64
+ expect(firstRunNode.childNodes[0].childNodes.length).toEqual(1);
65
+ expect(firstRunNode.childNodes[0].childNodes[0]).toEqual(firstXmlTextNode);
66
+ expect(firstXmlTextNode.textContent).toEqual('123456');
67
+ });
68
+
69
+ test('only join nodes from the specified range', () => {
70
+ const paragraphNode = parseXml(`
71
+ <w:p>
72
+ <w:r>
73
+ <w:t>0</w:t>
74
+ </w:r>
75
+ <w:r>
76
+ <w:t>1</w:t>
77
+ <w:t>2</w:t>
78
+ <w:t>3</w:t>
79
+ </w:r>
80
+ <w:r>
81
+ <w:t>4</w:t>
82
+ </w:r>
83
+ <w:r>
84
+ <w:t>5</w:t>
85
+ <w:t>6</w:t>
86
+ </w:r>
87
+ </w:p>
88
+ `);
89
+
90
+ const fromRunNode = paragraphNode.childNodes[1];
91
+ const fromXmlTextNode = fromRunNode.childNodes[1].childNodes[0] as XmlTextNode;
92
+ expect(fromXmlTextNode.textContent).toEqual('2');
93
+
94
+ const toRunNode = paragraphNode.childNodes[2];
95
+ const toXmlTextNode = toRunNode.childNodes[0].childNodes[0] as XmlTextNode;
96
+ expect(toXmlTextNode.textContent).toEqual('4');
97
+
98
+ // modify
99
+
100
+ officeMarkup.modify.joinTextNodesRange(fromXmlTextNode, toXmlTextNode);
101
+
102
+ // assert
103
+
104
+ expect(paragraphNode.childNodes.length).toEqual(3);
105
+ expect(fromRunNode.childNodes.length).toEqual(2);
106
+ expect(fromRunNode.childNodes[1].childNodes.length).toEqual(1);
107
+ expect(fromRunNode.childNodes[1].childNodes[0]).toEqual(fromXmlTextNode);
108
+ expect(fromXmlTextNode.textContent).toEqual('234');
109
+ });
110
+
111
+ });
112
+ });
113
+
114
+ });
@@ -1,4 +1,5 @@
1
- import { xml, XmlGeneralNode, XmlNode, XmlTextNode } from "src/xml";
1
+ import { Tag, TagPlacement } from "src/compilation/tag";
2
+ import { xml, XmlGeneralNode, XmlNode, XmlNodeType, XmlTextNode } from "src/xml";
2
3
  import { OmlAttribute, OmlNode } from "./omlNode";
3
4
 
4
5
  //
@@ -74,10 +75,14 @@ class Query {
74
75
 
75
76
  public isListParagraph(paragraphNode: XmlNode): boolean {
76
77
  const paragraphProperties = officeMarkup.query.findParagraphPropertiesNode(paragraphNode);
77
- const listNumberProperties = xml.query.findChildByName(paragraphProperties, OmlNode.W.NumberProperties);
78
+ const listNumberProperties = xml.query.findByPath(paragraphProperties, XmlNodeType.General, OmlNode.W.NumberProperties);
78
79
  return !!listNumberProperties;
79
80
  }
80
81
 
82
+ public isInlineDrawingNode(node: XmlNode): boolean {
83
+ return node.nodeName === OmlNode.Wp.Inline && node.parentNode?.nodeName === OmlNode.W.Drawing;
84
+ }
85
+
81
86
  public findParagraphPropertiesNode(paragraphNode: XmlNode): XmlNode {
82
87
  if (!officeMarkup.query.isParagraphNode(paragraphNode))
83
88
  throw new Error(`Expected paragraph node but received a '${paragraphNode.nodeName}' node.`);
@@ -116,43 +121,43 @@ class Query {
116
121
  return null;
117
122
 
118
123
  if (!xml.query.isTextNode(node))
119
- throw new Error(`'Invalid argument ${nameof(node)}. Expected a XmlTextNode.`);
124
+ throw new Error(`'Invalid argument node. Expected a XmlTextNode.`);
120
125
 
121
- return xml.query.findParent(node, officeMarkup.query.isTextNode) as XmlGeneralNode;
126
+ return xml.query.findParent(node, officeMarkup.query.isTextNode);
122
127
  }
123
128
 
124
129
  /**
125
130
  * Search **upwards** for the first run node.
126
131
  */
127
- public containingRunNode(node: XmlNode): XmlNode {
132
+ public containingRunNode(node: XmlNode): XmlGeneralNode {
128
133
  return xml.query.findParent(node, officeMarkup.query.isRunNode);
129
134
  }
130
135
 
131
136
  /**
132
137
  * Search **upwards** for the first paragraph node.
133
138
  */
134
- public containingParagraphNode(node: XmlNode): XmlNode {
139
+ public containingParagraphNode(node: XmlNode): XmlGeneralNode {
135
140
  return xml.query.findParent(node, officeMarkup.query.isParagraphNode);
136
141
  }
137
142
 
138
143
  /**
139
144
  * Search **upwards** for the first "table row" node.
140
145
  */
141
- public containingTableRowNode(node: XmlNode): XmlNode {
146
+ public containingTableRowNode(node: XmlNode): XmlGeneralNode {
142
147
  return xml.query.findParentByName(node, OmlNode.W.TableRow);
143
148
  }
144
149
 
145
150
  /**
146
151
  * Search **upwards** for the first "table cell" node.
147
152
  */
148
- public containingTableCellNode(node: XmlNode): XmlNode {
153
+ public containingTableCellNode(node: XmlNode): XmlGeneralNode {
149
154
  return xml.query.findParent(node, officeMarkup.query.isTableCellNode);
150
155
  }
151
156
 
152
157
  /**
153
158
  * Search **upwards** for the first "table" node.
154
159
  */
155
- public containingTableNode(node: XmlNode): XmlNode {
160
+ public containingTableNode(node: XmlNode): XmlGeneralNode {
156
161
  return xml.query.findParentByName(node, OmlNode.W.Table);
157
162
  }
158
163
 
@@ -262,7 +267,7 @@ class Modify {
262
267
  // input validation
263
268
  const containingParagraph = officeMarkup.query.containingParagraphNode(textNode);
264
269
  if (containingParagraph != paragraph)
265
- throw new Error(`Node '${nameof(textNode)}' is not a descendant of '${nameof(paragraph)}'.`);
270
+ throw new Error(`Node 'textNode' is not a contained in the specified paragraph.`);
266
271
 
267
272
  const runNode = officeMarkup.query.containingRunNode(textNode);
268
273
  const wordTextNode = officeMarkup.query.containingTextNode(textNode);
@@ -342,7 +347,7 @@ class Modify {
342
347
  const totalText: string[] = [];
343
348
 
344
349
  // iterate runs
345
- let curRunNode = firstRunNode;
350
+ let curRunNode: XmlNode = firstRunNode;
346
351
  while (curRunNode) {
347
352
 
348
353
  // iterate text nodes
@@ -424,18 +429,42 @@ class Modify {
424
429
  }
425
430
  }
426
431
 
427
- public removeTag(textNode: XmlTextNode): void {
432
+ public removeTag(tag: Tag): void {
428
433
 
429
- const wordTextNode = officeMarkup.query.containingTextNode(textNode);
430
- const runNode = officeMarkup.query.containingRunNode(textNode);
434
+ if (tag.placement === TagPlacement.TextNode) {
435
+
436
+ const wordTextNode = officeMarkup.query.containingTextNode(tag.xmlTextNode);
437
+ const runNode = officeMarkup.query.containingRunNode(tag.xmlTextNode);
431
438
 
432
- // Remove the word text node
433
- xml.modify.remove(wordTextNode);
439
+ // Remove the word text node
440
+ xml.modify.remove(wordTextNode);
434
441
 
435
- // Remove the run node if it's empty
436
- if (officeMarkup.query.isEmptyRun(runNode)) {
437
- xml.modify.remove(runNode);
442
+ // Remove the run node if it's empty
443
+ if (officeMarkup.query.isEmptyRun(runNode)) {
444
+ xml.modify.remove(runNode);
445
+ }
446
+
447
+ return;
438
448
  }
449
+
450
+ if (tag.placement === TagPlacement.Attribute) {
451
+ if (!tag.xmlNode.attributes || !(tag.attributeName in tag.xmlNode.attributes)) {
452
+ return;
453
+ }
454
+
455
+ // Remove the tag from the attribute value
456
+ tag.xmlNode.attributes[tag.attributeName] = tag.xmlNode.attributes[tag.attributeName].replace(tag.rawText, "");
457
+
458
+ // Remove the attribute if it's empty
459
+ if (tag.xmlNode.attributes[tag.attributeName] === "") {
460
+ delete tag.xmlNode.attributes[tag.attributeName];
461
+ }
462
+
463
+ return;
464
+ }
465
+
466
+ const anyTag = tag as any;
467
+ throw new Error(`Unexpected tag placement "${anyTag.placement}" for tag "${anyTag.rawText}".`);
439
468
  }
440
469
  }
441
470
 
@@ -11,11 +11,12 @@ class W {
11
11
  public readonly Table = 'w:tbl';
12
12
  public readonly TableRow = 'w:tr';
13
13
  public readonly TableCell = 'w:tc';
14
+ public readonly Drawing = 'w:drawing';
14
15
  public readonly NumberProperties = 'w:numPr';
15
16
  }
16
17
 
17
18
  /**
18
- * Drawing Markup Language node names.
19
+ * Drawing Markup Language main namespace node names.
19
20
  *
20
21
  * These elements are part of the main drawingML namespace:
21
22
  * xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main".
@@ -26,6 +27,68 @@ class A {
26
27
  public readonly Run = 'a:r';
27
28
  public readonly RunProperties = 'a:rPr';
28
29
  public readonly Text = 'a:t';
30
+ public readonly Graphic = 'a:graphic';
31
+ public readonly GraphicData = 'a:graphicData';
32
+ /**
33
+ * Binary large image (or) picture.
34
+ */
35
+ public readonly Blip = 'a:blip';
36
+ public readonly AlphaModFix = 'a:alphaModFix';
37
+ }
38
+
39
+ /**
40
+ * Drawing Markup Language "wordprocessing drawing" namespace node names.
41
+ *
42
+ * These elements are part of the wordprocessingDrawing namespace:
43
+ * xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing".
44
+ */
45
+ class Wp {
46
+ /**
47
+ * docPr stands for "Drawing Object Non-Visual Properties", which isn't
48
+ * exactly a good acronym but that's how it's called nevertheless.
49
+ */
50
+ public readonly DocPr = 'wp:docPr';
51
+ /**
52
+ * Inline DrawingML Object.
53
+ *
54
+ * see: http://officeopenxml.com/drwPicInline.php
55
+ */
56
+ public readonly Inline = 'wp:inline';
57
+ /**
58
+ * Anchor for Floating DrawingML Object.
59
+ *
60
+ * see: http://officeopenxml.com/drwPicFloating.php
61
+ */
62
+ public readonly FloatingAnchor = 'wp:anchor';
63
+ /**
64
+ * Drawing extent.
65
+ */
66
+ public readonly Extent = 'wp:extent';
67
+ }
68
+
69
+ /**
70
+ * Drawing Markup Language "picture" namespace node names.
71
+ *
72
+ * These elements are part of the picture namespace:
73
+ * xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture".
74
+ */
75
+ class Pic {
76
+ public readonly Pic = 'pic:pic';
77
+ /**
78
+ * Non-visual picture properties.
79
+ */
80
+ public readonly NvPicPr = 'pic:nvPicPr';
81
+ public readonly CnVPr = 'pic:cNvPr';
82
+ /**
83
+ * Binary large image (or) picture fill.
84
+ */
85
+ public readonly BlipFill = 'pic:blipFill';
86
+ /**
87
+ * Shape properties.
88
+ */
89
+ public readonly SpPr = 'pic:spPr';
90
+ public readonly Xfrm = 'a:xfrm';
91
+ public readonly Ext = 'a:ext';
29
92
  }
30
93
 
31
94
  /**
@@ -44,9 +107,19 @@ export class OmlNode {
44
107
  public static readonly W = new W();
45
108
 
46
109
  /**
47
- * Drawing Markup Language node names.
110
+ * Drawing Markup Language main namespace node names.
48
111
  */
49
112
  public static readonly A = new A();
113
+
114
+ /**
115
+ * Drawing Markup Language "wordprocessing drawing" namespace node names.
116
+ */
117
+ public static readonly Wp = new Wp();
118
+
119
+ /**
120
+ * Drawing Markup Language "picture" namespace node names.
121
+ */
122
+ public static readonly Pic = new Pic();
50
123
  }
51
124
 
52
125
  export class OmlAttribute {
@@ -50,7 +50,7 @@ export class OpenXmlPart {
50
50
  public async getText(): Promise<string> {
51
51
  const xmlDocument = await this.xmlRoot();
52
52
 
53
- // ugly but good enough...
53
+ // Ugly but good enough...
54
54
  const xmlString = xml.parser.serializeFile(xmlDocument);
55
55
  const domDocument = xml.parser.domParse(xmlString);
56
56
 
@@ -0,0 +1,23 @@
1
+ import { MimeType } from "src/mimeType";
2
+ import { RelsFile } from "src/office/relsFile";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ describe(RelsFile, () => {
6
+
7
+ describe(RelsFile.prototype.add, () => {
8
+
9
+ it('returns the same rel for the same target', async () => {
10
+
11
+ const fakeZip: any = {
12
+ getFile: (): any => null
13
+ };
14
+
15
+ const rels = new RelsFile('word/document.xml', fakeZip);
16
+
17
+ const rel1 = await rels.add('my image.jpeg', MimeType.Jpeg);
18
+ const rel2 = await rels.add('my image.jpeg', MimeType.Jpeg);
19
+
20
+ expect(rel1).toEqual(rel2);
21
+ });
22
+ });
23
+ });
@@ -23,22 +23,38 @@ export type RelTargetMode = 'Internal' | 'External';
23
23
 
24
24
  export class Relationship {
25
25
 
26
- public static fromXml(xml: XmlGeneralNode): Relationship {
26
+ public static fromXml(partDir: string, xml: XmlGeneralNode): Relationship {
27
27
  return new Relationship({
28
28
  id: xml.attributes?.['Id'],
29
29
  type: xml.attributes?.['Type'],
30
- target: Relationship.normalizeRelTarget(xml.attributes?.['Target']),
30
+ target: Relationship.normalizeRelTarget(partDir, xml.attributes?.['Target']),
31
31
  targetMode: xml.attributes?.['TargetMode'] as RelTargetMode,
32
32
  });
33
33
  }
34
34
 
35
- public static normalizeRelTarget(target: string): string {
35
+ public static normalizeRelTarget(partDir: string, target: string): string {
36
36
  if (!target) {
37
37
  return target;
38
38
  }
39
+
40
+ // Remove leading slashes from input
41
+ if (partDir.startsWith('/')) {
42
+ partDir = partDir.substring(1);
43
+ }
44
+ if (target.startsWith('/')) {
45
+ target = target.substring(1);
46
+ }
47
+
48
+ // Convert target to relative path
49
+ if (target.startsWith(partDir)) {
50
+ target = target.substring(partDir.length);
51
+ }
52
+
53
+ // Remove leading slashes from output
39
54
  if (target.startsWith('/')) {
40
- return target.substring(1);
55
+ target = target.substring(1);
41
56
  }
57
+
42
58
  return target;
43
59
  }
44
60
 
@@ -56,11 +72,11 @@ export class Relationship {
56
72
  const node = xml.create.generalNode('Relationship');
57
73
  node.attributes = {};
58
74
 
59
- // set only non-empty attributes
75
+ // Set only non-empty attributes
60
76
  for (const propKey of Object.keys(this)) {
61
77
  const value = (this as any)[propKey];
62
78
  if (value && typeof value === 'string') {
63
- const attrName = propKey[0].toUpperCase() + propKey.substr(1);
79
+ const attrName = propKey[0].toUpperCase() + propKey.substring(1);
64
80
  node.attributes[attrName] = value;
65
81
  }
66
82
  }
@@ -31,21 +31,21 @@ export class RelsFile {
31
31
  */
32
32
  public async add(relTarget: string, relType: string, relTargetMode?: RelTargetMode): Promise<string> {
33
33
 
34
- // if relTarget is an internal file it should be relative to the part dir
34
+ // If relTarget is an internal file it should be relative to the part dir
35
35
  if (this.partDir && relTarget.startsWith(this.partDir)) {
36
- relTarget = relTarget.substr(this.partDir.length + 1);
36
+ relTarget = relTarget.substring(this.partDir.length + 1);
37
37
  }
38
38
 
39
- // parse rels file
39
+ // Parse rels file
40
40
  await this.parseRelsFile();
41
41
 
42
- // already exists?
42
+ // Already exists?
43
43
  const relTargetKey = this.getRelTargetKey(relType, relTarget);
44
44
  let relId = this.relTargets[relTargetKey];
45
45
  if (relId)
46
46
  return relId;
47
47
 
48
- // create rel node
48
+ // Create rel node
49
49
  relId = this.getNextRelId();
50
50
  const rel = new Relationship({
51
51
  id: relId,
@@ -54,11 +54,11 @@ export class RelsFile {
54
54
  targetMode: relTargetMode
55
55
  });
56
56
 
57
- // update lookups
57
+ // Update lookups
58
58
  this.rels[relId] = rel;
59
59
  this.relTargets[relTargetKey] = relId;
60
60
 
61
- // return
61
+ // Return
62
62
  return relId;
63
63
  }
64
64
 
@@ -80,21 +80,21 @@ export class RelsFile {
80
80
  */
81
81
  public async save(): Promise<void> {
82
82
 
83
- // not change - no need to save
83
+ // Not change - no need to save
84
84
  if (!this.rels)
85
85
  return;
86
86
 
87
- // create rels xml
87
+ // Create rels xml
88
88
  const root = this.createRootNode();
89
89
  root.childNodes = Object.values(this.rels).map(rel => rel.toXml());
90
90
 
91
- // serialize and save
91
+ // Serialize and save
92
92
  const xmlContent = xml.parser.serializeFile(root);
93
93
  this.zip.setFile(this.relsFilePath, xmlContent);
94
94
  }
95
95
 
96
96
  //
97
- // private methods
97
+ // Private methods
98
98
  //
99
99
 
100
100
  private getNextRelId(): string {
@@ -110,11 +110,11 @@ export class RelsFile {
110
110
 
111
111
  private async parseRelsFile(): Promise<void> {
112
112
 
113
- // already parsed
113
+ // Already parsed
114
114
  if (this.rels)
115
115
  return;
116
116
 
117
- // parse xml
117
+ // Parse xml
118
118
  let root: XmlNode;
119
119
  const relsFile = this.zip.getFile(this.relsFilePath);
120
120
  if (relsFile) {
@@ -124,12 +124,13 @@ export class RelsFile {
124
124
  root = this.createRootNode();
125
125
  }
126
126
 
127
- // parse relationship nodes
127
+ // Parse relationship nodes
128
128
  this.rels = {};
129
129
  this.relTargets = {};
130
130
  for (const relNode of root.childNodes) {
131
+ const genRelNode = relNode as XmlGeneralNode;
131
132
 
132
- const attributes = (relNode as XmlGeneralNode).attributes;
133
+ const attributes = genRelNode.attributes;
133
134
  if (!attributes)
134
135
  continue;
135
136
 
@@ -137,15 +138,13 @@ export class RelsFile {
137
138
  if (!idAttr)
138
139
  continue;
139
140
 
140
- // store rel
141
- const rel = Relationship.fromXml(relNode as XmlGeneralNode);
141
+ // Store rel
142
+ const rel = Relationship.fromXml(this.partDir, genRelNode);
142
143
  this.rels[idAttr] = rel;
143
144
 
144
- // create rel target lookup
145
- const typeAttr = attributes['Type'];
146
- const targetAttr = Relationship.normalizeRelTarget(attributes['Target']);
147
- if (typeAttr && targetAttr) {
148
- const relTargetKey = this.getRelTargetKey(typeAttr, targetAttr);
145
+ // Create rel target lookup
146
+ if (rel.type && rel.target) {
147
+ const relTargetKey = this.getRelTargetKey(rel.type, rel.target);
149
148
  this.relTargets[relTargetKey] = idAttr;
150
149
  }
151
150
  }
@@ -21,7 +21,7 @@ export class Xlsx {
21
21
  try {
22
22
  zip = await Zip.load(file);
23
23
  } catch {
24
- throw new MalformedFileError('xlsx');
24
+ throw new MalformedFileError("Failed to load zip file.");
25
25
  }
26
26
 
27
27
  // Load the xlsx file
@@ -35,7 +35,7 @@ export class Xlsx {
35
35
  public static async open(zip: Zip): Promise<Xlsx> {
36
36
  const mainDocumentPath = await Xlsx.getMainDocumentPath(zip);
37
37
  if (!mainDocumentPath)
38
- throw new MalformedFileError('xlsx');
38
+ throw new MalformedFileError("Cannot find main document path.");
39
39
 
40
40
  return new Xlsx(mainDocumentPath, zip);
41
41
  }