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.
- package/README.md +36 -7
- package/dist/cjs/easy-template-x.cjs +920 -430
- package/dist/es/easy-template-x.mjs +920 -431
- package/dist/types/src/compilation/delimiters/attributesDelimiterSearcher.d.ts +15 -0
- package/dist/types/src/compilation/delimiters/delimiterMark.d.ts +19 -0
- package/dist/types/src/compilation/delimiters/delimiterSearcher.d.ts +9 -0
- package/dist/types/src/compilation/delimiters/delimiterSearcher.tests.d.ts +1 -0
- package/dist/types/src/compilation/delimiters/index.d.ts +2 -0
- package/dist/types/src/compilation/delimiters/textNodesDelimiterSearcher.d.ts +18 -0
- package/dist/types/{compilation → src/compilation}/index.d.ts +1 -2
- package/dist/types/src/compilation/scopeData.tests.d.ts +1 -0
- package/dist/types/src/compilation/tag.d.ts +29 -0
- package/dist/types/src/compilation/tagParser.d.ts +16 -0
- package/dist/types/src/compilation/tagParser.tests.d.ts +1 -0
- package/dist/types/src/compilation/tagUtils.d.ts +2 -0
- package/dist/types/{compilation → src/compilation}/templateCompiler.d.ts +1 -1
- package/dist/types/src/compilation/templateCompiler.tests.d.ts +1 -0
- package/dist/types/src/errors/malformedFileError.d.ts +3 -0
- package/dist/types/src/office/contentTypesFile.tests.d.ts +1 -0
- package/dist/types/src/office/mediaFiles.tests.d.ts +1 -0
- package/dist/types/{office → src/office}/officeMarkup.d.ts +8 -6
- package/dist/types/src/office/officeMarkup.tests.d.ts +1 -0
- package/dist/types/{office → src/office}/omlNode.d.ts +22 -0
- package/dist/types/src/office/rel.tests.d.ts +1 -0
- package/dist/types/{office → src/office}/relationship.d.ts +2 -2
- package/dist/types/src/plugins/chart/chartDataValidation.tests.d.ts +1 -0
- package/dist/types/{plugins → src/plugins}/chart/chartPlugin.d.ts +3 -1
- package/dist/types/src/plugins/image/createImage.d.ts +3 -0
- package/dist/types/{plugins → src/plugins}/image/imageContent.d.ts +2 -2
- package/dist/types/{plugins → src/plugins}/image/imagePlugin.d.ts +3 -6
- package/dist/types/src/plugins/image/imageUtils.d.ts +3 -0
- package/dist/types/src/plugins/image/updateImage.d.ts +3 -0
- package/dist/types/{plugins → src/plugins}/link/linkPlugin.d.ts +3 -1
- package/dist/types/{plugins → src/plugins}/loop/loopPlugin.d.ts +3 -1
- package/dist/types/{plugins → src/plugins}/loop/strategy/iLoopStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopListStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopParagraphStrategy.d.ts +3 -3
- package/dist/types/src/plugins/loop/strategy/loopParagraphStrategy.tests.d.ts +1 -0
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopTableColumnsStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/loop/strategy/loopTableRowsStrategy.d.ts +3 -3
- package/dist/types/{plugins → src/plugins}/rawXml/rawXmlPlugin.d.ts +2 -1
- package/dist/types/{plugins → src/plugins}/text/textPlugin.d.ts +4 -1
- package/dist/types/src/templateHandler.tests.d.ts +1 -0
- package/dist/types/{xml → src/xml}/xml.d.ts +5 -4
- package/dist/types/src/xml/xml.tests.d.ts +1 -0
- package/dist/types/{xml → src/xml}/xmlNode.d.ts +1 -1
- package/dist/types/test/fixtures/fixtureUtils.d.ts +1 -0
- package/dist/types/test/testUtils.d.ts +8 -0
- package/package.json +17 -19
- package/src/compilation/delimiters/attributesDelimiterSearcher.ts +109 -0
- package/src/compilation/delimiters/delimiterMark.ts +32 -0
- package/src/compilation/delimiters/delimiterSearcher.tests.ts +571 -0
- package/src/compilation/delimiters/delimiterSearcher.ts +41 -0
- package/src/compilation/delimiters/index.ts +2 -0
- package/src/compilation/delimiters/textNodesDelimiterSearcher.ts +185 -0
- package/src/compilation/index.ts +1 -2
- package/src/compilation/scopeData.tests.ts +40 -0
- package/src/compilation/tag.ts +22 -3
- package/src/compilation/tagParser.tests.ts +845 -0
- package/src/compilation/tagParser.ts +170 -66
- package/src/compilation/tagUtils.ts +9 -0
- package/src/compilation/templateCompiler.tests.ts +91 -0
- package/src/compilation/templateCompiler.ts +1 -1
- package/src/delimiters.ts +1 -1
- package/src/errors/malformedFileError.ts +3 -7
- package/src/office/contentTypesFile.tests.ts +88 -0
- package/src/office/docx.ts +2 -2
- package/src/office/mediaFiles.tests.ts +56 -0
- package/src/office/officeMarkup.tests.ts +114 -0
- package/src/office/officeMarkup.ts +48 -19
- package/src/office/omlNode.ts +75 -2
- package/src/office/openXmlPart.ts +1 -1
- package/src/office/rel.tests.ts +23 -0
- package/src/office/relationship.ts +22 -6
- package/src/office/relsFile.ts +21 -22
- package/src/office/xlsx.ts +2 -2
- package/src/plugins/chart/chartDataValidation.tests.ts +109 -0
- package/src/plugins/chart/chartPlugin.ts +11 -5
- package/src/plugins/chart/updateChart.ts +4 -4
- package/src/plugins/image/createImage.ts +115 -0
- package/src/plugins/image/imageContent.ts +16 -4
- package/src/plugins/image/imagePlugin.ts +28 -138
- package/src/plugins/image/imageUtils.ts +22 -0
- package/src/plugins/image/updateImage.ts +113 -0
- package/src/plugins/link/linkPlugin.ts +10 -3
- package/src/plugins/loop/loopPlugin.ts +11 -1
- package/src/plugins/loop/strategy/iLoopStrategy.ts +3 -3
- package/src/plugins/loop/strategy/loopListStrategy.ts +3 -3
- package/src/plugins/loop/strategy/loopParagraphStrategy.tests.ts +64 -0
- package/src/plugins/loop/strategy/loopParagraphStrategy.ts +5 -5
- package/src/plugins/loop/strategy/loopTableColumnsStrategy.ts +3 -3
- package/src/plugins/loop/strategy/loopTableRowsStrategy.ts +3 -3
- package/src/plugins/rawXml/rawXmlPlugin.ts +8 -2
- package/src/plugins/text/textPlugin.ts +37 -5
- package/src/templateHandler.tests.ts +84 -0
- package/src/templateHandler.ts +12 -15
- package/src/utils/path.ts +8 -1
- package/src/xml/xml.tests.ts +260 -0
- package/src/xml/xml.ts +55 -24
- package/src/xml/xmlNode.ts +1 -1
- package/src/zip/jsZipHelper.ts +1 -1
- package/src/zip/zip.ts +3 -0
- package/dist/types/compilation/delimiterMark.d.ts +0 -8
- package/dist/types/compilation/delimiterSearcher.d.ts +0 -12
- package/dist/types/compilation/tag.d.ts +0 -15
- package/dist/types/compilation/tagParser.d.ts +0 -11
- package/dist/types/errors/malformedFileError.d.ts +0 -4
- package/src/compilation/delimiterMark.ts +0 -16
- package/src/compilation/delimiterSearcher.ts +0 -183
- /package/dist/types/{compilation → src/compilation}/scopeData.d.ts +0 -0
- /package/dist/types/{compilation → src/compilation}/templateContext.d.ts +0 -0
- /package/dist/types/{delimiters.d.ts → src/delimiters.d.ts} +0 -0
- /package/dist/types/{errors → src/errors}/index.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/internalArgumentMissingError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/internalError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/maxXmlDepthError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/missingCloseDelimiterError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/missingStartDelimiterError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/tagOptionsParseError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/templateDataError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/templateSyntaxError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unclosedTagError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unidentifiedFileTypeError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unknownContentTypeError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unopenedTagError.d.ts +0 -0
- /package/dist/types/{errors → src/errors}/unsupportedFileTypeError.d.ts +0 -0
- /package/dist/types/{extensions → src/extensions}/extensionOptions.d.ts +0 -0
- /package/dist/types/{extensions → src/extensions}/index.d.ts +0 -0
- /package/dist/types/{extensions → src/extensions}/templateExtension.d.ts +0 -0
- /package/dist/types/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/types/{mimeType.d.ts → src/mimeType.d.ts} +0 -0
- /package/dist/types/{office → src/office}/contentTypesFile.d.ts +0 -0
- /package/dist/types/{office → src/office}/docx.d.ts +0 -0
- /package/dist/types/{office → src/office}/index.d.ts +0 -0
- /package/dist/types/{office → src/office}/mediaFiles.d.ts +0 -0
- /package/dist/types/{office → src/office}/openXmlPart.d.ts +0 -0
- /package/dist/types/{office → src/office}/relsFile.d.ts +0 -0
- /package/dist/types/{office → src/office}/xlsx.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartColors.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartData.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/chartDataValidation.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/chart/updateChart.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/defaultPlugins.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/image/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/link/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/link/linkContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/loop/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/loop/loopTagOptions.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/loop/strategy/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/pluginContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/rawXml/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/rawXml/rawXmlContent.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/templatePlugin.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/text/index.d.ts +0 -0
- /package/dist/types/{templateData.d.ts → src/templateData.d.ts} +0 -0
- /package/dist/types/{templateHandler.d.ts → src/templateHandler.d.ts} +0 -0
- /package/dist/types/{templateHandlerOptions.d.ts → src/templateHandlerOptions.d.ts} +0 -0
- /package/dist/types/{types.d.ts → src/types.d.ts} +0 -0
- /package/dist/types/{utils → src/utils}/array.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/base64.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/binary.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/index.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/number.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/path.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/regex.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/sha1.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/txt.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/types.d.ts +0 -0
- /package/dist/types/{xml → src/xml}/index.d.ts +0 -0
- /package/dist/types/{xml → src/xml}/xmlDepthTracker.d.ts +0 -0
- /package/dist/types/{xml → src/xml}/xmlTreeIterator.d.ts +0 -0
- /package/dist/types/{zip → src/zip}/index.d.ts +0 -0
- /package/dist/types/{zip → src/zip}/jsZipHelper.d.ts +0 -0
- /package/dist/types/{zip → src/zip}/zip.d.ts +0 -0
- /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 {
|
|
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.
|
|
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
|
|
124
|
+
throw new Error(`'Invalid argument node. Expected a XmlTextNode.`);
|
|
120
125
|
|
|
121
|
-
return xml.query.findParent(node, officeMarkup.query.isTextNode)
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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 '
|
|
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(
|
|
432
|
+
public removeTag(tag: Tag): void {
|
|
428
433
|
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
433
|
-
|
|
439
|
+
// Remove the word text node
|
|
440
|
+
xml.modify.remove(wordTextNode);
|
|
434
441
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
|
package/src/office/omlNode.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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.
|
|
79
|
+
const attrName = propKey[0].toUpperCase() + propKey.substring(1);
|
|
64
80
|
node.attributes[attrName] = value;
|
|
65
81
|
}
|
|
66
82
|
}
|
package/src/office/relsFile.ts
CHANGED
|
@@ -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
|
-
//
|
|
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.
|
|
36
|
+
relTarget = relTarget.substring(this.partDir.length + 1);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// Parse rels file
|
|
40
40
|
await this.parseRelsFile();
|
|
41
41
|
|
|
42
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
57
|
+
// Update lookups
|
|
58
58
|
this.rels[relId] = rel;
|
|
59
59
|
this.relTargets[relTargetKey] = relId;
|
|
60
60
|
|
|
61
|
-
//
|
|
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
|
-
//
|
|
83
|
+
// Not change - no need to save
|
|
84
84
|
if (!this.rels)
|
|
85
85
|
return;
|
|
86
86
|
|
|
87
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
113
|
+
// Already parsed
|
|
114
114
|
if (this.rels)
|
|
115
115
|
return;
|
|
116
116
|
|
|
117
|
-
//
|
|
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
|
-
//
|
|
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 =
|
|
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
|
-
//
|
|
141
|
-
const rel = Relationship.fromXml(
|
|
141
|
+
// Store rel
|
|
142
|
+
const rel = Relationship.fromXml(this.partDir, genRelNode);
|
|
142
143
|
this.rels[idAttr] = rel;
|
|
143
144
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
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
|
}
|
package/src/office/xlsx.ts
CHANGED
|
@@ -21,7 +21,7 @@ export class Xlsx {
|
|
|
21
21
|
try {
|
|
22
22
|
zip = await Zip.load(file);
|
|
23
23
|
} catch {
|
|
24
|
-
throw new MalformedFileError(
|
|
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(
|
|
38
|
+
throw new MalformedFileError("Cannot find main document path.");
|
|
39
39
|
|
|
40
40
|
return new Xlsx(mainDocumentPath, zip);
|
|
41
41
|
}
|