@docx4j/core-ts 0.1.0 → 0.1.2
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 +410 -10
- package/dist/model/content/Body.d.mts +109 -4
- package/dist/model/content/Body.mjs +300 -29
- package/dist/model/content/Comment.d.mts +78 -0
- package/dist/model/content/Comment.mjs +429 -0
- package/dist/model/content/ContentControl.d.mts +173 -0
- package/dist/model/content/ContentControl.mjs +638 -0
- package/dist/model/content/Font.d.mts +48 -8
- package/dist/model/content/Font.mjs +55 -13
- package/dist/model/content/InlinePicture.d.mts +103 -0
- package/dist/model/content/InlinePicture.mjs +361 -0
- package/dist/model/content/List.d.mts +253 -0
- package/dist/model/content/List.mjs +778 -0
- package/dist/model/content/Paragraph.d.mts +213 -16
- package/dist/model/content/Paragraph.mjs +611 -57
- package/dist/model/content/Range.d.mts +48 -4
- package/dist/model/content/Range.mjs +161 -4
- package/dist/model/content/Table.d.mts +130 -0
- package/dist/model/content/Table.mjs +429 -0
- package/dist/model/content/TrackedChange.d.mts +96 -0
- package/dist/model/content/TrackedChange.mjs +352 -0
- package/dist/model/content/comments.d.mts +122 -0
- package/dist/model/content/comments.mjs +248 -0
- package/dist/model/content/index.d.mts +13 -3
- package/dist/model/content/index.mjs +15 -3
- package/dist/model/content/ooxml.d.mts +17 -0
- package/dist/model/content/ooxml.mjs +214 -0
- package/dist/model/content/styles.d.mts +21 -0
- package/dist/model/content/styles.mjs +86 -0
- package/dist/model/content/tracking.d.mts +104 -0
- package/dist/model/content/tracking.mjs +313 -0
- package/dist/model/content/tree.d.mts +56 -9
- package/dist/model/content/tree.mjs +104 -31
- package/dist/model/customxml/CustomXmlPart.d.mts +158 -0
- package/dist/model/customxml/CustomXmlPart.mjs +415 -0
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +68 -0
- package/dist/model/customxml/CustomXmlPartCollection.mjs +183 -0
- package/dist/model/customxml/XmlMapping.d.mts +46 -0
- package/dist/model/customxml/XmlMapping.mjs +98 -0
- package/dist/model/customxml/bindings.d.mts +41 -0
- package/dist/model/customxml/bindings.mjs +230 -0
- package/dist/model/customxml/index.d.mts +7 -0
- package/dist/model/customxml/index.mjs +9 -0
- package/dist/model/customxml/insert.d.mts +20 -0
- package/dist/model/customxml/insert.mjs +22 -0
- package/dist/model/customxml/kinds.d.mts +103 -0
- package/dist/model/customxml/kinds.mjs +267 -0
- package/dist/model/customxml/xpath.d.mts +52 -0
- package/dist/model/customxml/xpath.mjs +205 -0
- package/dist/model/fonts/CJKToEnglish.d.mts +2 -0
- package/dist/model/fonts/CJKToEnglish.mjs +16 -0
- package/dist/model/fonts/FontFallback.d.mts +58 -0
- package/dist/model/fonts/FontFallback.mjs +219 -0
- package/dist/model/fonts/IdentityPlusMapper.d.mts +7 -0
- package/dist/model/fonts/IdentityPlusMapper.mjs +19 -0
- package/dist/model/fonts/LanguageTagToScriptMapping.d.mts +7 -0
- package/dist/model/fonts/LanguageTagToScriptMapping.mjs +67 -0
- package/dist/model/fonts/Mapper.d.mts +190 -0
- package/dist/model/fonts/Mapper.mjs +481 -0
- package/dist/model/fonts/PhysicalFont.d.mts +34 -0
- package/dist/model/fonts/PhysicalFont.mjs +62 -0
- package/dist/model/fonts/RunFontSelector.d.mts +194 -0
- package/dist/model/fonts/RunFontSelector.mjs +441 -0
- package/dist/model/fonts/ThemeFonts.d.mts +17 -0
- package/dist/model/fonts/ThemeFonts.mjs +76 -0
- package/dist/model/fonts/defaultTheme.d.mts +37 -0
- package/dist/model/fonts/defaultTheme.mjs +57 -0
- package/dist/model/fonts/families.generated.d.mts +11 -0
- package/dist/model/fonts/families.generated.mjs +17 -0
- package/dist/model/fonts/fontsInUse.d.mts +39 -0
- package/dist/model/fonts/fontsInUse.mjs +183 -0
- package/dist/model/fonts/index.d.mts +14 -0
- package/dist/model/fonts/index.mjs +29 -0
- package/dist/model/fonts/lookup.d.mts +5 -0
- package/dist/model/fonts/lookup.mjs +12 -0
- package/dist/model/fonts/registry.d.mts +31 -0
- package/dist/model/fonts/registry.mjs +103 -0
- package/dist/model/fonts/scripts.d.mts +2 -0
- package/dist/model/fonts/scripts.mjs +78 -0
- package/dist/model/fonts/substitutions.generated.d.mts +30 -0
- package/dist/model/fonts/substitutions.generated.mjs +12 -0
- package/dist/model/fonts/themes.generated.d.mts +6 -0
- package/dist/model/fonts/themes.generated.mjs +11 -0
- package/dist/model/index.d.mts +5 -0
- package/dist/model/index.mjs +13 -1
- package/dist/model/listnumbering/Emulator.d.mts +187 -0
- package/dist/model/listnumbering/Emulator.mjs +332 -0
- package/dist/model/listnumbering/definitions.d.mts +251 -0
- package/dist/model/listnumbering/definitions.mjs +571 -0
- package/dist/model/listnumbering/formats.d.mts +25 -0
- package/dist/model/listnumbering/formats.mjs +320 -0
- package/dist/model/listnumbering/index.d.mts +4 -0
- package/dist/model/listnumbering/index.mjs +15 -0
- package/dist/model/listnumbering/state.d.mts +97 -0
- package/dist/model/listnumbering/state.mjs +179 -0
- package/dist/model/properties/PropertyResolver.d.mts +216 -0
- package/dist/model/properties/PropertyResolver.mjs +524 -0
- package/dist/model/properties/catalogue.d.mts +179 -0
- package/dist/model/properties/catalogue.mjs +860 -0
- package/dist/model/properties/index.d.mts +4 -0
- package/dist/model/properties/index.mjs +9 -0
- package/dist/model/properties/log.d.mts +7 -0
- package/dist/model/properties/log.mjs +25 -0
- package/dist/model/properties/styleUtil.d.mts +112 -0
- package/dist/model/properties/styleUtil.mjs +359 -0
- package/dist/node/index.d.mts +1 -0
- package/dist/node/index.mjs +12 -0
- package/dist/office-js/document.d.mts +111 -0
- package/dist/office-js/document.mjs +267 -0
- package/dist/office-js/enums.d.mts +187 -0
- package/dist/office-js/enums.mjs +176 -0
- package/dist/office-js/errors.d.mts +29 -0
- package/dist/office-js/errors.mjs +39 -0
- package/dist/office-js/extras.d.mts +5 -0
- package/dist/office-js/extras.mjs +29 -0
- package/dist/office-js/fragment.d.mts +10 -0
- package/dist/office-js/fragment.mjs +23 -0
- package/dist/office-js/index.d.mts +183 -0
- package/dist/office-js/index.mjs +54 -0
- package/dist/office-js/proxy.d.mts +72 -0
- package/dist/office-js/proxy.mjs +279 -0
- package/dist/office-js/run.d.mts +32 -0
- package/dist/office-js/run.mjs +66 -0
- package/dist/office-js/supported.d.mts +7 -0
- package/dist/office-js/supported.generated.d.mts +1 -0
- package/dist/office-js/supported.generated.mjs +235 -0
- package/dist/office-js/supported.mjs +53 -0
- package/dist/office-js/toApiScript.d.mts +25 -0
- package/dist/office-js/toApiScript.mjs +394 -0
- package/dist/opc/ContentTypes.d.mts +11 -0
- package/dist/opc/ContentTypes.mjs +14 -0
- package/dist/opc/DirectoryPartStore.d.mts +47 -0
- package/dist/opc/DirectoryPartStore.mjs +123 -0
- package/dist/opc/exceptions.d.mts +30 -0
- package/dist/opc/exceptions.mjs +38 -0
- package/dist/opc/index.d.mts +1 -1
- package/dist/opc/index.mjs +1 -1
- package/dist/packages/OpcPackage.d.mts +19 -1
- package/dist/packages/OpcPackage.mjs +29 -0
- package/dist/packages/PresentationMLPackage.d.mts +71 -1
- package/dist/packages/PresentationMLPackage.mjs +179 -0
- package/dist/packages/SpreadsheetMLPackage.d.mts +30 -1
- package/dist/packages/SpreadsheetMLPackage.mjs +78 -0
- package/dist/packages/WordprocessingMLPackage.d.mts +124 -2
- package/dist/packages/WordprocessingMLPackage.mjs +309 -2
- package/dist/packages/index.d.mts +1 -1
- package/dist/packages/index.mjs +1 -1
- package/dist/parts/DefaultXmlPart.d.mts +18 -0
- package/dist/parts/DefaultXmlPart.mjs +34 -5
- package/dist/parts/Namespaces.d.mts +18 -0
- package/dist/parts/Namespaces.mjs +20 -0
- package/dist/parts/Part.d.mts +6 -0
- package/dist/parts/Part.mjs +13 -0
- package/dist/parts/PartRegistry.mjs +13 -1
- package/dist/parts/XmlPart.d.mts +33 -0
- package/dist/parts/XmlPart.mjs +105 -2
- package/dist/parts/pml/defaults.d.mts +6 -0
- package/dist/parts/pml/defaults.mjs +53 -0
- package/dist/parts/pml/index.d.mts +66 -3
- package/dist/parts/pml/index.mjs +206 -7
- package/dist/parts/pml/placeholders.d.mts +71 -0
- package/dist/parts/pml/placeholders.mjs +141 -0
- package/dist/parts/sml/index.d.mts +80 -1
- package/dist/parts/sml/index.mjs +154 -1
- package/dist/parts/wml/comments.d.mts +1 -0
- package/dist/parts/wml/comments.mjs +179 -0
- package/dist/parts/wml/defaultNumbering.d.mts +1 -0
- package/dist/parts/wml/defaultNumbering.mjs +216 -0
- package/dist/parts/wml/index.d.mts +121 -0
- package/dist/parts/wml/index.mjs +291 -0
- package/dist/xml/dom.mjs +1 -2
- package/package.json +25 -4
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
// CR-002 phase E, section 3.5: Word.CustomXmlPart and Word.CustomXmlNode as views over the
|
|
2
|
+
// custom XML data storage part's DOM (CR-001's CustomXmlDataStoragePart) and its properties part.
|
|
3
|
+
// Views, as Body and Paragraph are: the DOM is the state, nothing is cached but the prefix
|
|
4
|
+
// manager. Reading never marks the part for re-marshalling; every mutation does (markModified).
|
|
5
|
+
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
6
|
+
import { parseXml, serializeXml, childElements } from '../../xml/dom.mjs';
|
|
7
|
+
import { canonicalXPathOf, parsePrefixMappings, formatPrefixMappings, xmlOf } from './xpath.mjs';
|
|
8
|
+
/** The namespaces of the property stores Word reports as built in (core, extended, cover page). */
|
|
9
|
+
const BUILT_IN_NAMESPACES = new Set([
|
|
10
|
+
'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
|
|
11
|
+
'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
|
12
|
+
'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties',
|
|
13
|
+
'http://schemas.microsoft.com/office/2006/coverPageProps',
|
|
14
|
+
'http://purl.org/dc/elements/1.1/',
|
|
15
|
+
]);
|
|
16
|
+
const NODE_TYPES = {
|
|
17
|
+
1: 'Element', 2: 'Attribute', 3: 'Text', 4: 'CData', 7: 'ProcessingInstruction', 8: 'Comment', 9: 'Document',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Office JS `Word.CustomXmlPrefixMappingCollection`: the prefixes an XPath over this part may use.
|
|
21
|
+
* Seeded from the namespace declarations of the document element, so that a part Word wrote can be
|
|
22
|
+
* queried without declaring anything.
|
|
23
|
+
*/
|
|
24
|
+
export class CustomXmlPrefixMappingCollection {
|
|
25
|
+
constructor(seed = {}) {
|
|
26
|
+
this.map = new Map();
|
|
27
|
+
for (const [prefix, uri] of Object.entries(seed))
|
|
28
|
+
if (prefix !== '')
|
|
29
|
+
this.map.set(prefix, uri);
|
|
30
|
+
}
|
|
31
|
+
get items() {
|
|
32
|
+
return [...this.map].map(([prefix, namespaceUri]) => ({ prefix, namespaceUri }));
|
|
33
|
+
}
|
|
34
|
+
addNamespace(prefix, namespaceUri) {
|
|
35
|
+
this.map.set(prefix, namespaceUri);
|
|
36
|
+
}
|
|
37
|
+
/** The URI a prefix stands for, '' when it stands for none (Office JS lookupNamespace). */
|
|
38
|
+
lookupNamespace(prefix) {
|
|
39
|
+
var _a;
|
|
40
|
+
return (_a = this.map.get(prefix)) !== null && _a !== void 0 ? _a : '';
|
|
41
|
+
}
|
|
42
|
+
/** The first prefix bound to a URI, '' when none is (Office JS lookupPrefix). */
|
|
43
|
+
lookupPrefix(namespaceUri) {
|
|
44
|
+
for (const [prefix, uri] of this.map)
|
|
45
|
+
if (uri === namespaceUri)
|
|
46
|
+
return prefix;
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
/** The mappings as an `xmlns:ns0='...'` string and as a map, for the engine. */
|
|
50
|
+
toMap() {
|
|
51
|
+
return Object.fromEntries(this.map);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A subset of Office JS `Word.CustomXmlPart` over a `CustomXmlDataStoragePart` (the customer's XML,
|
|
56
|
+
* a DOM) and its properties part (the `ds:datastoreItem` with the itemID and the schema
|
|
57
|
+
* references). The properties part is read from its DOM, never unmarshalled, so it stays byte for
|
|
58
|
+
* byte (as `Load` does for the itemID).
|
|
59
|
+
*/
|
|
60
|
+
export class CustomXmlPart {
|
|
61
|
+
constructor(
|
|
62
|
+
/** The data part (extension: docx4j's part is the object here). */
|
|
63
|
+
part, owner,
|
|
64
|
+
/** The itemID as the properties part spells it, '{...}'. */
|
|
65
|
+
itemID,
|
|
66
|
+
/** The `ds:schemaRef` URIs of the properties part. */
|
|
67
|
+
schemaRefs = []) {
|
|
68
|
+
this.part = part;
|
|
69
|
+
this.owner = owner;
|
|
70
|
+
this.itemID = itemID;
|
|
71
|
+
this.schemaRefs = schemaRefs;
|
|
72
|
+
}
|
|
73
|
+
/** The itemID, as Office JS reports it ('{...}'). */
|
|
74
|
+
get id() {
|
|
75
|
+
return this.itemID;
|
|
76
|
+
}
|
|
77
|
+
/** The DOM; throws when the part has not been parsed yet (`await pkg.customXmlParts.load()`). */
|
|
78
|
+
get document() {
|
|
79
|
+
const document = this.part.parsedDocument;
|
|
80
|
+
if (!document) {
|
|
81
|
+
throw new Docx4JException(`Custom XML part ${this.part.partName} is not parsed yet: await pkg.customXmlParts.load() once`);
|
|
82
|
+
}
|
|
83
|
+
return document;
|
|
84
|
+
}
|
|
85
|
+
/** The document element as a node view. */
|
|
86
|
+
get documentElement() {
|
|
87
|
+
const root = this.document.documentElement;
|
|
88
|
+
if (!root)
|
|
89
|
+
throw new Docx4JException(`Custom XML part ${this.part.partName} has no document element`);
|
|
90
|
+
return new CustomXmlNode(root, this);
|
|
91
|
+
}
|
|
92
|
+
/** The namespace of the document element ('' when it is in none). */
|
|
93
|
+
get namespaceUri() {
|
|
94
|
+
var _a, _b;
|
|
95
|
+
return (_b = (_a = this.document.documentElement) === null || _a === void 0 ? void 0 : _a.namespaceURI) !== null && _b !== void 0 ? _b : '';
|
|
96
|
+
}
|
|
97
|
+
/** True for Word's own property stores (core, extended, custom and cover-page properties). */
|
|
98
|
+
get builtIn() {
|
|
99
|
+
return BUILT_IN_NAMESPACES.has(this.namespaceUri);
|
|
100
|
+
}
|
|
101
|
+
/** The prefixes an XPath over this part may use; seeded from the document element's declarations. */
|
|
102
|
+
get namespaceManager() {
|
|
103
|
+
var _a;
|
|
104
|
+
return ((_a = this.prefixes) !== null && _a !== void 0 ? _a : (this.prefixes = new CustomXmlPrefixMappingCollection(declaredNamespaces(this.document.documentElement))));
|
|
105
|
+
}
|
|
106
|
+
/** The `ds:schemaRef` URIs of the properties part (Office JS schemaCollection). */
|
|
107
|
+
get schemaCollection() {
|
|
108
|
+
return [...this.schemaRefs];
|
|
109
|
+
}
|
|
110
|
+
/** The part's XML. Synchronous: a DOM part does not go through the marshaller. */
|
|
111
|
+
getXml() {
|
|
112
|
+
var _a;
|
|
113
|
+
return serializeXml((_a = this.document.documentElement) !== null && _a !== void 0 ? _a : this.document);
|
|
114
|
+
}
|
|
115
|
+
/** Replaces the part's XML; the part is re-marshalled on save. */
|
|
116
|
+
setXml(xml) {
|
|
117
|
+
this.part.setDocument(parseXml(xml));
|
|
118
|
+
this.prefixes = undefined;
|
|
119
|
+
}
|
|
120
|
+
/** The nodes an XPath selects; `namespaceMappings` is Word's `xmlns:ns0='...'` string, else the namespace manager's. */
|
|
121
|
+
selectNodes(xpath, namespaceMappings) {
|
|
122
|
+
return this.selectFrom(this.document, xpath, namespaceMappings);
|
|
123
|
+
}
|
|
124
|
+
selectSingleNode(xpath, namespaceMappings) {
|
|
125
|
+
return this.selectNodes(xpath, namespaceMappings)[0];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Inserts `xml` as a child of the node the XPath selects, at `index` (appended when omitted).
|
|
129
|
+
* Departure from Office JS, which puts `namespaceMappings` second: here it is last and optional,
|
|
130
|
+
* as CR-002 section 3.5 specifies (section 12).
|
|
131
|
+
*/
|
|
132
|
+
insertElement(xpath, xml, namespaceMappings, index) {
|
|
133
|
+
const parent = this.requireNode(xpath, namespaceMappings);
|
|
134
|
+
return parent.appendChildNode(xml, undefined, undefined, undefined, index);
|
|
135
|
+
}
|
|
136
|
+
/** Replaces the element the XPath selects with `xml`. */
|
|
137
|
+
updateElement(xpath, xml, namespaceMappings) {
|
|
138
|
+
const node = this.requireNode(xpath, namespaceMappings);
|
|
139
|
+
const parent = node.node.parentNode;
|
|
140
|
+
if (!parent)
|
|
141
|
+
throw new Docx4JException(`${xpath} selects the document element; use setXml to replace the whole part`);
|
|
142
|
+
parent.replaceChild(importedFragment(this.document, xml), node.node);
|
|
143
|
+
this.touch();
|
|
144
|
+
}
|
|
145
|
+
/** Removes the element the XPath selects. */
|
|
146
|
+
deleteElement(xpath, namespaceMappings) {
|
|
147
|
+
const node = this.requireNode(xpath, namespaceMappings);
|
|
148
|
+
node.delete();
|
|
149
|
+
}
|
|
150
|
+
/** Adds an attribute to the element the XPath selects. */
|
|
151
|
+
insertAttribute(xpath, name, value, namespaceMappings) {
|
|
152
|
+
this.updateAttribute(xpath, name, value, namespaceMappings);
|
|
153
|
+
}
|
|
154
|
+
/** Sets an attribute of the element the XPath selects (`prefix:name` is written in that prefix's namespace). */
|
|
155
|
+
updateAttribute(xpath, name, value, namespaceMappings) {
|
|
156
|
+
var _a;
|
|
157
|
+
const node = this.requireNode(xpath, namespaceMappings);
|
|
158
|
+
const element = node.node;
|
|
159
|
+
if (element.nodeType !== 1)
|
|
160
|
+
throw new Docx4JException(`${xpath} does not select an element`);
|
|
161
|
+
const prefix = name.includes(':') ? name.substring(0, name.indexOf(':')) : '';
|
|
162
|
+
const uri = prefix ? ((_a = this.mappingsFor(namespaceMappings)[prefix]) !== null && _a !== void 0 ? _a : this.namespaceManager.lookupNamespace(prefix)) : '';
|
|
163
|
+
if (prefix && uri)
|
|
164
|
+
element.setAttributeNS(uri, name, value);
|
|
165
|
+
else
|
|
166
|
+
element.setAttribute(name, value);
|
|
167
|
+
this.touch();
|
|
168
|
+
}
|
|
169
|
+
/** Removes an attribute of the element the XPath selects. */
|
|
170
|
+
deleteAttribute(xpath, name, namespaceMappings) {
|
|
171
|
+
var _a;
|
|
172
|
+
const node = this.requireNode(xpath, namespaceMappings);
|
|
173
|
+
const element = node.node;
|
|
174
|
+
if (element.nodeType !== 1)
|
|
175
|
+
throw new Docx4JException(`${xpath} does not select an element`);
|
|
176
|
+
const prefix = name.includes(':') ? name.substring(0, name.indexOf(':')) : '';
|
|
177
|
+
const uri = prefix ? ((_a = this.mappingsFor(namespaceMappings)[prefix]) !== null && _a !== void 0 ? _a : this.namespaceManager.lookupNamespace(prefix)) : '';
|
|
178
|
+
if (prefix && uri)
|
|
179
|
+
element.removeAttributeNS(uri, name.substring(name.indexOf(':') + 1));
|
|
180
|
+
else
|
|
181
|
+
element.removeAttribute(name);
|
|
182
|
+
this.touch();
|
|
183
|
+
}
|
|
184
|
+
/** Removes the part, its properties part and the relationships; bound controls lose their mapping. */
|
|
185
|
+
delete() {
|
|
186
|
+
this.owner.removePart(this);
|
|
187
|
+
}
|
|
188
|
+
// --- internals, shared with CustomXmlNode ---
|
|
189
|
+
/** Marks the part for re-marshalling: a DOM read for a query alone leaves it byte for byte. */
|
|
190
|
+
touch() {
|
|
191
|
+
this.part.markModified();
|
|
192
|
+
}
|
|
193
|
+
get engine() {
|
|
194
|
+
return this.owner.xpathEngine;
|
|
195
|
+
}
|
|
196
|
+
/** The prefix map to use: the given string, else the namespace manager's. */
|
|
197
|
+
mappingsFor(namespaceMappings) {
|
|
198
|
+
return namespaceMappings === undefined ? this.namespaceManager.toMap() : parsePrefixMappings(namespaceMappings);
|
|
199
|
+
}
|
|
200
|
+
selectFrom(context, xpath, namespaceMappings) {
|
|
201
|
+
return this.engine.select(xpath, context, this.mappingsFor(namespaceMappings)).map((node) => new CustomXmlNode(node, this));
|
|
202
|
+
}
|
|
203
|
+
requireNode(xpath, namespaceMappings) {
|
|
204
|
+
const node = this.selectSingleNode(xpath, namespaceMappings);
|
|
205
|
+
if (!node)
|
|
206
|
+
throw new Docx4JException(`${xpath} selects nothing in custom XML part ${this.part.partName}`);
|
|
207
|
+
return node;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* A subset of Office JS `Word.CustomXmlNode`: a view over one DOM node of a custom XML part.
|
|
212
|
+
* `xpath` is the canonical `/ns0:a[1]/ns0:b[2]` form Word writes, which is what
|
|
213
|
+
* `XmlMapping.setMappingByNode` stores.
|
|
214
|
+
*/
|
|
215
|
+
export class CustomXmlNode {
|
|
216
|
+
constructor(
|
|
217
|
+
/** The DOM node (extension). */
|
|
218
|
+
node,
|
|
219
|
+
/** The part the node belongs to. */
|
|
220
|
+
ownerPart) {
|
|
221
|
+
this.node = node;
|
|
222
|
+
this.ownerPart = ownerPart;
|
|
223
|
+
}
|
|
224
|
+
/** The local name; for an attribute, its name. */
|
|
225
|
+
get baseName() {
|
|
226
|
+
var _a, _b;
|
|
227
|
+
if (this.node.nodeType === 2)
|
|
228
|
+
return (_a = this.node.localName) !== null && _a !== void 0 ? _a : this.node.name;
|
|
229
|
+
return (_b = this.node.localName) !== null && _b !== void 0 ? _b : this.node.nodeName;
|
|
230
|
+
}
|
|
231
|
+
get namespaceUri() {
|
|
232
|
+
var _a;
|
|
233
|
+
return (_a = this.node.namespaceURI) !== null && _a !== void 0 ? _a : '';
|
|
234
|
+
}
|
|
235
|
+
get nodeType() {
|
|
236
|
+
var _a;
|
|
237
|
+
return (_a = NODE_TYPES[this.node.nodeType]) !== null && _a !== void 0 ? _a : 'Other';
|
|
238
|
+
}
|
|
239
|
+
/** The node's value: an attribute's value, a text node's data, an element's text. */
|
|
240
|
+
get nodeValue() {
|
|
241
|
+
var _a;
|
|
242
|
+
return this.node.nodeType === 1 ? this.text : ((_a = this.node.nodeValue) !== null && _a !== void 0 ? _a : '');
|
|
243
|
+
}
|
|
244
|
+
set nodeValue(value) {
|
|
245
|
+
if (this.node.nodeType === 1) {
|
|
246
|
+
this.text = value;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
this.node.nodeValue = value;
|
|
250
|
+
this.ownerPart.touch();
|
|
251
|
+
}
|
|
252
|
+
/** The node's text content; setting replaces an element's children with one text node. */
|
|
253
|
+
get text() {
|
|
254
|
+
var _a;
|
|
255
|
+
return (_a = this.node.textContent) !== null && _a !== void 0 ? _a : '';
|
|
256
|
+
}
|
|
257
|
+
set text(value) {
|
|
258
|
+
this.node.textContent = value;
|
|
259
|
+
this.ownerPart.touch();
|
|
260
|
+
}
|
|
261
|
+
/** The node's XML. */
|
|
262
|
+
get xml() {
|
|
263
|
+
return xmlOf(this.node);
|
|
264
|
+
}
|
|
265
|
+
/** The canonical XPath of this node, as Word writes it into `w:dataBinding`. */
|
|
266
|
+
get xpath() {
|
|
267
|
+
return canonicalXPathOf(this.node).xpath;
|
|
268
|
+
}
|
|
269
|
+
/** The prefix mappings that XPath needs (empty when the node is in no namespace). */
|
|
270
|
+
get prefixMappings() {
|
|
271
|
+
return canonicalXPathOf(this.node).prefixMappings;
|
|
272
|
+
}
|
|
273
|
+
get attributes() {
|
|
274
|
+
const attributes = this.node.attributes;
|
|
275
|
+
const out = [];
|
|
276
|
+
for (let i = 0; attributes && i < attributes.length; i++) {
|
|
277
|
+
const attr = attributes.item(i);
|
|
278
|
+
if (attr && attr.name !== 'xmlns' && !attr.name.startsWith('xmlns:'))
|
|
279
|
+
out.push(new CustomXmlNode(attr, this.ownerPart));
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
get childNodes() {
|
|
284
|
+
const out = [];
|
|
285
|
+
for (let child = this.node.firstChild; child; child = child.nextSibling)
|
|
286
|
+
out.push(new CustomXmlNode(child, this.ownerPart));
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
/** The element children only (extension: what a reader of a data store usually wants). */
|
|
290
|
+
get childElements() {
|
|
291
|
+
return childElements(this.node).map((el) => new CustomXmlNode(el, this.ownerPart));
|
|
292
|
+
}
|
|
293
|
+
get parentNode() {
|
|
294
|
+
const parent = this.node.nodeType === 2 ? this.node.ownerElement : this.node.parentNode;
|
|
295
|
+
return parent && parent.nodeType !== 9 ? new CustomXmlNode(parent, this.ownerPart) : undefined;
|
|
296
|
+
}
|
|
297
|
+
get firstChild() {
|
|
298
|
+
return this.node.firstChild ? new CustomXmlNode(this.node.firstChild, this.ownerPart) : undefined;
|
|
299
|
+
}
|
|
300
|
+
get lastChild() {
|
|
301
|
+
return this.node.lastChild ? new CustomXmlNode(this.node.lastChild, this.ownerPart) : undefined;
|
|
302
|
+
}
|
|
303
|
+
get nextSibling() {
|
|
304
|
+
return this.node.nextSibling ? new CustomXmlNode(this.node.nextSibling, this.ownerPart) : undefined;
|
|
305
|
+
}
|
|
306
|
+
get previousSibling() {
|
|
307
|
+
return this.node.previousSibling ? new CustomXmlNode(this.node.previousSibling, this.ownerPart) : undefined;
|
|
308
|
+
}
|
|
309
|
+
hasChildNodes() {
|
|
310
|
+
return this.node.firstChild !== null;
|
|
311
|
+
}
|
|
312
|
+
selectNodes(xpath, namespaceMappings) {
|
|
313
|
+
return this.ownerPart.selectFrom(this.node, xpath, namespaceMappings);
|
|
314
|
+
}
|
|
315
|
+
selectSingleNode(xpath, namespaceMappings) {
|
|
316
|
+
return this.selectNodes(xpath, namespaceMappings)[0];
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Appends a child. Both forms of CR-002 open question 8 are taken: the XML text form
|
|
320
|
+
* `appendChildNode('<price>$20</price>')`, which is what an agent produces, and Office JS's
|
|
321
|
+
* `appendChildNode(name, namespaceUri, nodeType, nodeValue)`.
|
|
322
|
+
*/
|
|
323
|
+
appendChildNode(xml, namespaceUri, nodeType, nodeValue, index) {
|
|
324
|
+
var _a;
|
|
325
|
+
const created = this.create(xml, namespaceUri, nodeType, nodeValue);
|
|
326
|
+
if (created.nodeType === 2) {
|
|
327
|
+
this.node.setAttributeNode(created);
|
|
328
|
+
this.ownerPart.touch();
|
|
329
|
+
return new CustomXmlNode(created, this.ownerPart);
|
|
330
|
+
}
|
|
331
|
+
const children = childElements(this.node);
|
|
332
|
+
const before = index === undefined ? null : (_a = children[index]) !== null && _a !== void 0 ? _a : null;
|
|
333
|
+
this.node.insertBefore(created, before);
|
|
334
|
+
this.ownerPart.touch();
|
|
335
|
+
return new CustomXmlNode(created, this.ownerPart);
|
|
336
|
+
}
|
|
337
|
+
/** Inserts a node before a sibling (appended when the sibling is omitted), as Office JS. */
|
|
338
|
+
insertNodeBefore(xml, nextSibling) {
|
|
339
|
+
const created = this.create(xml);
|
|
340
|
+
this.node.insertBefore(created, nextSibling ? nextSibling.node : null);
|
|
341
|
+
this.ownerPart.touch();
|
|
342
|
+
return new CustomXmlNode(created, this.ownerPart);
|
|
343
|
+
}
|
|
344
|
+
removeChild(child) {
|
|
345
|
+
if (child.node.nodeType === 2) {
|
|
346
|
+
this.node.removeAttributeNode(child.node);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
this.node.removeChild(child.node);
|
|
350
|
+
}
|
|
351
|
+
this.ownerPart.touch();
|
|
352
|
+
}
|
|
353
|
+
replaceChildNode(oldNode, xml) {
|
|
354
|
+
const created = this.create(xml);
|
|
355
|
+
this.node.replaceChild(created, oldNode.node);
|
|
356
|
+
this.ownerPart.touch();
|
|
357
|
+
return new CustomXmlNode(created, this.ownerPart);
|
|
358
|
+
}
|
|
359
|
+
/** Removes this node from its parent. */
|
|
360
|
+
delete() {
|
|
361
|
+
const parent = this.parentNode;
|
|
362
|
+
if (!parent)
|
|
363
|
+
throw new Docx4JException('The document element cannot be deleted; delete the part instead');
|
|
364
|
+
parent.removeChild(this);
|
|
365
|
+
}
|
|
366
|
+
create(xml, namespaceUri, nodeType, nodeValue) {
|
|
367
|
+
const document = this.ownerPart.document;
|
|
368
|
+
if (nodeType !== undefined || namespaceUri !== undefined) {
|
|
369
|
+
const name = xml !== null && xml !== void 0 ? xml : '';
|
|
370
|
+
if (name === '')
|
|
371
|
+
throw new Docx4JException('A node needs a name');
|
|
372
|
+
if (nodeType === 'Attribute') {
|
|
373
|
+
const attr = namespaceUri ? document.createAttributeNS(namespaceUri, name) : document.createAttribute(name);
|
|
374
|
+
attr.value = nodeValue !== null && nodeValue !== void 0 ? nodeValue : '';
|
|
375
|
+
return attr;
|
|
376
|
+
}
|
|
377
|
+
if (nodeType === 'Text')
|
|
378
|
+
return document.createTextNode(nodeValue !== null && nodeValue !== void 0 ? nodeValue : '');
|
|
379
|
+
const element = namespaceUri ? document.createElementNS(namespaceUri, name) : document.createElement(name);
|
|
380
|
+
if (nodeValue !== undefined && nodeValue !== '')
|
|
381
|
+
element.appendChild(document.createTextNode(nodeValue));
|
|
382
|
+
return element;
|
|
383
|
+
}
|
|
384
|
+
if (xml === undefined)
|
|
385
|
+
throw new Docx4JException('Pass the XML of the node, or its name, namespace, type and value');
|
|
386
|
+
return importedFragment(document, xml);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/** Parses an XML fragment into a node of this document. */
|
|
390
|
+
function importedFragment(document, xml) {
|
|
391
|
+
const parsed = parseXml(xml);
|
|
392
|
+
const root = parsed.documentElement;
|
|
393
|
+
if (!root)
|
|
394
|
+
throw new Docx4JException(`Not a well-formed XML element: ${xml}`);
|
|
395
|
+
const imported = document.importNode ? document.importNode(root, true) : root;
|
|
396
|
+
return imported;
|
|
397
|
+
}
|
|
398
|
+
/** The namespace declarations of an element, as a prefix map. */
|
|
399
|
+
function declaredNamespaces(element) {
|
|
400
|
+
const out = {};
|
|
401
|
+
if (!element)
|
|
402
|
+
return out;
|
|
403
|
+
const attributes = element.attributes;
|
|
404
|
+
for (let i = 0; attributes && i < attributes.length; i++) {
|
|
405
|
+
const attr = attributes.item(i);
|
|
406
|
+
if (!attr)
|
|
407
|
+
continue;
|
|
408
|
+
if (attr.name === 'xmlns')
|
|
409
|
+
out[''] = attr.value;
|
|
410
|
+
else if (attr.name.startsWith('xmlns:'))
|
|
411
|
+
out[attr.name.substring(6)] = attr.value;
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
export { formatPrefixMappings, parsePrefixMappings };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Part } from '../../parts/Part.mjs';
|
|
2
|
+
import { CustomXmlDataStoragePart } from '../../parts/DefaultXmlPart.mjs';
|
|
3
|
+
import type { Body } from '../content/Body.mjs';
|
|
4
|
+
import { CustomXmlPart, type CustomXmlPartOwner } from './CustomXmlPart.mjs';
|
|
5
|
+
import type { CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
6
|
+
import type { XPathEngine } from './xpath.mjs';
|
|
7
|
+
import { type BindingResult } from './bindings.mjs';
|
|
8
|
+
/**
|
|
9
|
+
* What the collection needs of its package. A structural interface, so that this module does not
|
|
10
|
+
* import `WordprocessingMLPackage` (which imports it): the package hands itself in.
|
|
11
|
+
*/
|
|
12
|
+
export interface CustomXmlHost {
|
|
13
|
+
readonly xpathEngine: XPathEngine;
|
|
14
|
+
readonly customXmlDataStorageParts: Map<string, CustomXmlDataStoragePart>;
|
|
15
|
+
/** The bodies whose content controls carry bindings; unmarshals the parts (main, headers, footers). */
|
|
16
|
+
getBoundBodies(): Promise<Body[]>;
|
|
17
|
+
/** The same, of the parts that are already unmarshalled: nothing is read from the container. */
|
|
18
|
+
boundBodies(): Body[];
|
|
19
|
+
/** Where a new custom XML part's relationship goes: the main document part (Word drops parts related from the package alone). */
|
|
20
|
+
customXmlRelationshipSource(): Part;
|
|
21
|
+
}
|
|
22
|
+
/** A subset of Office JS `Word.CustomXmlPartCollection`. */
|
|
23
|
+
export declare class CustomXmlPartCollection implements CustomXmlPartOwner, CustomXmlPartLookup {
|
|
24
|
+
private readonly host;
|
|
25
|
+
private readonly views;
|
|
26
|
+
constructor(host: CustomXmlHost);
|
|
27
|
+
get xpathEngine(): XPathEngine;
|
|
28
|
+
/**
|
|
29
|
+
* Parses every custom XML part's DOM and its properties part, and warms the XPath engine. One
|
|
30
|
+
* await, and the collection and the node views are synchronous from then on (section 3.6).
|
|
31
|
+
*/
|
|
32
|
+
load(): Promise<CustomXmlPart[]>;
|
|
33
|
+
/** The parts, in the order they were registered. Call `load()` once first: a DOM has to be parsed. */
|
|
34
|
+
get items(): CustomXmlPart[];
|
|
35
|
+
/** The parts whose document element is in that namespace (Office JS getByNamespace). */
|
|
36
|
+
getByNamespace(namespaceUri: string): CustomXmlPart[];
|
|
37
|
+
/** The part with that itemID; the comparison ignores case and accepts the id with or without braces. */
|
|
38
|
+
getItem(id: string): CustomXmlPart | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* A new custom XML part holding this XML, with its properties part and a fresh itemID, related
|
|
41
|
+
* from the main document part (docx4j's `AbstractMigrator.addPropertiesPart` recipe: Word
|
|
42
|
+
* silently drops a custom XML part that only the package relates to).
|
|
43
|
+
*/
|
|
44
|
+
add(xml: string, options?: {
|
|
45
|
+
schemaRefs?: string[];
|
|
46
|
+
}): CustomXmlPart;
|
|
47
|
+
/**
|
|
48
|
+
* Removes a part, its properties part and the relationships (Office JS CustomXmlPart.delete).
|
|
49
|
+
* Bindings to it are removed from the controls of the parts that are already unmarshalled; a
|
|
50
|
+
* part that is not loaded keeps its `w:dataBinding`, which then names a part that is gone.
|
|
51
|
+
*/
|
|
52
|
+
removePart(view: CustomXmlPart): void;
|
|
53
|
+
/**
|
|
54
|
+
* docx4j `BindingHandler.applyBindings`: pushes the custom XML values into the bound controls,
|
|
55
|
+
* which is what Word does when it opens the document. Unmarshals the main document part (and the
|
|
56
|
+
* headers and footers it has) and parses the custom XML parts.
|
|
57
|
+
*/
|
|
58
|
+
applyBindings(): Promise<BindingResult>;
|
|
59
|
+
/**
|
|
60
|
+
* docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
|
|
61
|
+
* back into the custom XML. The reverse of `applyBindings`, and what makes an edit to a bound
|
|
62
|
+
* control show in Word (CR-002 section 12).
|
|
63
|
+
*/
|
|
64
|
+
updateFromContentControls(): Promise<BindingResult>;
|
|
65
|
+
private controlsOf;
|
|
66
|
+
/** The view of a part: its itemID and schema references, read from the properties part's DOM. */
|
|
67
|
+
private viewFor;
|
|
68
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// CR-002 phase E, section 3.5: `pkg.customXmlParts`, Office JS's Word.CustomXmlPartCollection over
|
|
2
|
+
// CR-001's `pkg.customXmlDataStorageParts`, plus docx4j's two binding directions.
|
|
3
|
+
//
|
|
4
|
+
// Asynchronous only where something is parsed or unmarshalled (section 3's rule): `load()` parses
|
|
5
|
+
// every custom XML part's DOM and warms the XPath engine, and `applyBindings` /
|
|
6
|
+
// `updateFromContentControls` unmarshal the parts whose controls they read. Everything else is
|
|
7
|
+
// synchronous, as Office JS is.
|
|
8
|
+
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
9
|
+
import { Namespaces } from '../../parts/Namespaces.mjs';
|
|
10
|
+
import { PartName } from '../../opc/PartName.mjs';
|
|
11
|
+
import { CustomXmlDataStoragePart } from '../../parts/DefaultXmlPart.mjs';
|
|
12
|
+
import { CustomXmlDataStoragePropertiesPart, DS_NS } from '../../parts/customXml/index.mjs';
|
|
13
|
+
import { parseXml, decodeXmlText } from '../../xml/dom.mjs';
|
|
14
|
+
import { createDatastoreItem } from '@docx4j/generated-objects-ts/factory/org_docx4j_customXmlProperties';
|
|
15
|
+
import { CustomXmlPart } from './CustomXmlPart.mjs';
|
|
16
|
+
import { applyBindingsTo, updateFromControls } from './bindings.mjs';
|
|
17
|
+
/** A subset of Office JS `Word.CustomXmlPartCollection`. */
|
|
18
|
+
export class CustomXmlPartCollection {
|
|
19
|
+
constructor(host) {
|
|
20
|
+
this.host = host;
|
|
21
|
+
this.views = new Map();
|
|
22
|
+
}
|
|
23
|
+
get xpathEngine() {
|
|
24
|
+
return this.host.xpathEngine;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parses every custom XML part's DOM and its properties part, and warms the XPath engine. One
|
|
28
|
+
* await, and the collection and the node views are synchronous from then on (section 3.6).
|
|
29
|
+
*/
|
|
30
|
+
async load() {
|
|
31
|
+
for (const part of this.host.customXmlDataStorageParts.values()) {
|
|
32
|
+
await part.parseDocument();
|
|
33
|
+
if (!this.views.has(part))
|
|
34
|
+
this.views.set(part, await this.viewFor(part));
|
|
35
|
+
}
|
|
36
|
+
await this.host.xpathEngine.ready();
|
|
37
|
+
return this.items;
|
|
38
|
+
}
|
|
39
|
+
/** The parts, in the order they were registered. Call `load()` once first: a DOM has to be parsed. */
|
|
40
|
+
get items() {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const part of this.host.customXmlDataStorageParts.values()) {
|
|
43
|
+
const view = this.views.get(part);
|
|
44
|
+
if (!view) {
|
|
45
|
+
throw new Docx4JException(`Custom XML part ${part.partName} is not parsed yet: await pkg.customXmlParts.load() once`);
|
|
46
|
+
}
|
|
47
|
+
out.push(view);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
/** The parts whose document element is in that namespace (Office JS getByNamespace). */
|
|
52
|
+
getByNamespace(namespaceUri) {
|
|
53
|
+
return this.items.filter((part) => part.namespaceUri === namespaceUri);
|
|
54
|
+
}
|
|
55
|
+
/** The part with that itemID; the comparison ignores case and accepts the id with or without braces. */
|
|
56
|
+
getItem(id) {
|
|
57
|
+
const key = normaliseId(id);
|
|
58
|
+
return this.items.find((part) => normaliseId(part.id) === key);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A new custom XML part holding this XML, with its properties part and a fresh itemID, related
|
|
62
|
+
* from the main document part (docx4j's `AbstractMigrator.addPropertiesPart` recipe: Word
|
|
63
|
+
* silently drops a custom XML part that only the package relates to).
|
|
64
|
+
*/
|
|
65
|
+
add(xml, options = {}) {
|
|
66
|
+
var _a;
|
|
67
|
+
const document = parseXml(xml);
|
|
68
|
+
const root = document.documentElement;
|
|
69
|
+
if (!root)
|
|
70
|
+
throw new Docx4JException('A custom XML part needs a document element');
|
|
71
|
+
const dataPart = new CustomXmlDataStoragePart('/customXml/item1.xml');
|
|
72
|
+
dataPart.setDocument(document);
|
|
73
|
+
this.host.customXmlRelationshipSource().addTargetPart(dataPart, 'RENAME_IF_NAME_EXISTS');
|
|
74
|
+
const itemID = `{${uuid().toUpperCase()}}`;
|
|
75
|
+
const propsName = PartName.of(`/customXml/itemProps${suffixOf(dataPart.partName)}.xml`);
|
|
76
|
+
const props = new CustomXmlDataStoragePropertiesPart(propsName);
|
|
77
|
+
const refs = (_a = options.schemaRefs) !== null && _a !== void 0 ? _a : (root.namespaceURI ? [root.namespaceURI] : []);
|
|
78
|
+
props.setContents(createDatastoreItem({
|
|
79
|
+
itemID,
|
|
80
|
+
...(refs.length > 0 ? { schemaRefs: { schemaRef: refs.map((uri) => ({ uri })) } } : {}),
|
|
81
|
+
}));
|
|
82
|
+
dataPart.addTargetPart(props, 'RENAME_IF_NAME_EXISTS');
|
|
83
|
+
dataPart.itemId = itemID.toLowerCase();
|
|
84
|
+
this.host.customXmlDataStorageParts.set(dataPart.itemId, dataPart);
|
|
85
|
+
const view = new CustomXmlPart(dataPart, this, itemID, refs);
|
|
86
|
+
this.views.set(dataPart, view);
|
|
87
|
+
return view;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Removes a part, its properties part and the relationships (Office JS CustomXmlPart.delete).
|
|
91
|
+
* Bindings to it are removed from the controls of the parts that are already unmarshalled; a
|
|
92
|
+
* part that is not loaded keeps its `w:dataBinding`, which then names a part that is gone.
|
|
93
|
+
*/
|
|
94
|
+
removePart(view) {
|
|
95
|
+
var _a, _b;
|
|
96
|
+
const part = view.part;
|
|
97
|
+
const source = this.host.customXmlRelationshipSource();
|
|
98
|
+
(_a = source.relationshipsPart) === null || _a === void 0 ? void 0 : _a.removePart(part.partName);
|
|
99
|
+
(_b = part.package) === null || _b === void 0 ? void 0 : _b.parts.remove(part.partName);
|
|
100
|
+
if (part.itemId)
|
|
101
|
+
this.host.customXmlDataStorageParts.delete(part.itemId);
|
|
102
|
+
this.views.delete(part);
|
|
103
|
+
for (const control of this.controlsOf(this.host.boundBodies())) {
|
|
104
|
+
if (normaliseId(control.xmlMapping.storeItemID) === normaliseId(view.id))
|
|
105
|
+
control.xmlMapping.delete();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* docx4j `BindingHandler.applyBindings`: pushes the custom XML values into the bound controls,
|
|
110
|
+
* which is what Word does when it opens the document. Unmarshals the main document part (and the
|
|
111
|
+
* headers and footers it has) and parses the custom XML parts.
|
|
112
|
+
*/
|
|
113
|
+
async applyBindings() {
|
|
114
|
+
await this.load();
|
|
115
|
+
return applyBindingsTo(this.controlsOf(await this.host.getBoundBodies()));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
|
|
119
|
+
* back into the custom XML. The reverse of `applyBindings`, and what makes an edit to a bound
|
|
120
|
+
* control show in Word (CR-002 section 12).
|
|
121
|
+
*/
|
|
122
|
+
async updateFromContentControls() {
|
|
123
|
+
await this.load();
|
|
124
|
+
return updateFromControls(this.controlsOf(await this.host.getBoundBodies()));
|
|
125
|
+
}
|
|
126
|
+
controlsOf(bodies) {
|
|
127
|
+
return bodies.flatMap((body) => body.contentControls);
|
|
128
|
+
}
|
|
129
|
+
/** The view of a part: its itemID and schema references, read from the properties part's DOM. */
|
|
130
|
+
async viewFor(part) {
|
|
131
|
+
var _a, _b;
|
|
132
|
+
const rels = part.relationshipsPart;
|
|
133
|
+
const rel = rels === null || rels === void 0 ? void 0 : rels.getRelationshipByType(Namespaces.CUSTOM_XML_DATA_STORAGE_PROPERTIES);
|
|
134
|
+
const props = rel && rels ? rels.getPart(rel) : undefined;
|
|
135
|
+
let itemID = (_a = part.itemId) !== null && _a !== void 0 ? _a : '';
|
|
136
|
+
const schemaRefs = [];
|
|
137
|
+
if (props) {
|
|
138
|
+
// Read the DOM rather than unmarshalling, so the properties part stays byte for byte (as Load does).
|
|
139
|
+
const document = parseXml(decodeXmlText(await props.getBytes()));
|
|
140
|
+
const root = document.documentElement;
|
|
141
|
+
if (root) {
|
|
142
|
+
itemID = root.getAttributeNS(DS_NS, 'itemID') || root.getAttribute('ds:itemID') || itemID;
|
|
143
|
+
const refs = root.getElementsByTagNameNS(DS_NS, 'schemaRef');
|
|
144
|
+
for (let i = 0; i < refs.length; i++) {
|
|
145
|
+
const uri = (_b = refs[i].getAttributeNS(DS_NS, 'uri')) !== null && _b !== void 0 ? _b : refs[i].getAttribute('ds:uri');
|
|
146
|
+
if (uri)
|
|
147
|
+
schemaRefs.push(uri);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return new CustomXmlPart(part, this, itemID, schemaRefs);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** An itemID without braces, lower-cased: how docx4j keys the map and how Office JS compares ids. */
|
|
155
|
+
function normaliseId(id) {
|
|
156
|
+
return id.replace(/[{}]/g, '').toLowerCase();
|
|
157
|
+
}
|
|
158
|
+
/** `itemProps<N>.xml` for `item<N>.xml`, so the two parts of a store keep the same number. */
|
|
159
|
+
function suffixOf(partName) {
|
|
160
|
+
var _a;
|
|
161
|
+
const m = /item(\d*)\.xml$/.exec(partName.name);
|
|
162
|
+
return (_a = m === null || m === void 0 ? void 0 : m[1]) !== null && _a !== void 0 ? _a : '';
|
|
163
|
+
}
|
|
164
|
+
/** A version 4 UUID, as docx4j's `UUID.randomUUID()` gives (crypto when the runtime has it). */
|
|
165
|
+
function uuid() {
|
|
166
|
+
const c = globalThis.crypto;
|
|
167
|
+
if (c === null || c === void 0 ? void 0 : c.randomUUID)
|
|
168
|
+
return c.randomUUID();
|
|
169
|
+
const hex = [];
|
|
170
|
+
for (let i = 0; i < 36; i++) {
|
|
171
|
+
if (i === 8 || i === 13 || i === 18 || i === 23) {
|
|
172
|
+
hex.push('-');
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (i === 14) {
|
|
176
|
+
hex.push('4');
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const r = Math.floor(Math.random() * 16);
|
|
180
|
+
hex.push((i === 19 ? (r & 0x3) | 0x8 : r).toString(16));
|
|
181
|
+
}
|
|
182
|
+
return hex.join('');
|
|
183
|
+
}
|