@docx4j/core-ts 0.6.0 → 0.7.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 +60 -0
- package/dist/model/content/Body.mjs +6 -17
- package/dist/model/content/Comment.d.mts +2 -2
- package/dist/model/content/Comment.mjs +12 -7
- package/dist/model/content/ContentControl.d.mts +1 -1
- package/dist/model/content/ContentControl.mjs +8 -4
- package/dist/model/content/Font.d.mts +4 -1
- package/dist/model/content/Font.mjs +6 -2
- package/dist/model/content/List.d.mts +6 -3
- package/dist/model/content/List.mjs +15 -8
- package/dist/model/content/Paragraph.d.mts +12 -2
- package/dist/model/content/Paragraph.mjs +31 -11
- package/dist/model/content/Table.mjs +26 -16
- package/dist/model/content/TrackedChange.d.mts +2 -2
- package/dist/model/content/TrackedChange.mjs +6 -3
- package/dist/model/content/index.d.mts +3 -1
- package/dist/model/content/index.mjs +4 -1
- package/dist/model/content/ooxml.mjs +12 -1
- package/dist/model/content/paraIds.d.mts +5 -0
- package/dist/model/content/paraIds.mjs +54 -0
- package/dist/model/content/search.d.mts +2 -0
- package/dist/model/content/search.mjs +51 -7
- package/dist/model/content/story.d.mts +5 -2
- package/dist/model/content/story.mjs +6 -3
- package/dist/model/content/tableRules.mjs +2 -0
- package/dist/model/content/tracking.d.mts +35 -6
- package/dist/model/content/tracking.mjs +93 -12
- package/dist/model/content/tree.d.mts +7 -8
- package/dist/model/content/tree.mjs +7 -9
- package/dist/model/content/wordRange.d.mts +48 -0
- package/dist/model/content/wordRange.mjs +97 -0
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +22 -1
- package/dist/model/customxml/CustomXmlPartCollection.mjs +48 -2
- package/dist/model/customxml/XmlMapping.d.mts +31 -7
- package/dist/model/customxml/XmlMapping.mjs +53 -10
- package/dist/model/customxml/bindings.d.mts +12 -2
- package/dist/model/customxml/bindings.mjs +98 -16
- package/dist/model/customxml/index.d.mts +1 -0
- package/dist/model/customxml/index.mjs +1 -0
- package/dist/model/customxml/opendope.d.mts +4 -1
- package/dist/model/customxml/opendope.mjs +9 -6
- package/dist/model/customxml/pictures.mjs +5 -3
- package/dist/model/customxml/storeItems.d.mts +57 -0
- package/dist/model/customxml/storeItems.mjs +122 -0
- package/dist/model/customxml/xhtml.mjs +15 -20
- package/dist/model/dateZone.d.mts +25 -0
- package/dist/model/dateZone.mjs +57 -0
- package/dist/model/index.d.mts +1 -0
- package/dist/model/index.mjs +1 -0
- package/dist/model/properties/PropertyResolver.d.mts +14 -1
- package/dist/model/properties/PropertyResolver.mjs +18 -3
- package/dist/model/properties/index.d.mts +1 -1
- package/dist/model/properties/log.mjs +2 -2
- package/dist/model/properties/styleUtil.d.mts +3 -1
- package/dist/model/properties/styleUtil.mjs +20 -24
- package/dist/model/text/CaseMapping.d.mts +38 -0
- package/dist/model/text/CaseMapping.mjs +86 -0
- package/dist/model/text/Extractor.d.mts +66 -6
- package/dist/model/text/Extractor.mjs +300 -56
- package/dist/model/text/PlainText.mjs +7 -4
- package/dist/model/text/TextContext.d.mts +103 -8
- package/dist/model/text/TextContext.mjs +228 -53
- package/dist/model/text/TextOptions.d.mts +88 -5
- package/dist/model/text/TextOptions.mjs +158 -17
- package/dist/model/text/TextResult.d.mts +126 -9
- package/dist/model/text/TextResult.mjs +115 -12
- package/dist/model/text/WmlToMarkdown.mjs +32 -7
- package/dist/model/text/caseMapping.generated.d.mts +7 -0
- package/dist/model/text/caseMapping.generated.mjs +214 -0
- package/dist/model/text/index.d.mts +3 -2
- package/dist/model/text/index.mjs +1 -0
- package/dist/model/wordIntegers.d.mts +29 -0
- package/dist/model/wordIntegers.mjs +78 -0
- package/dist/office-js/document.mjs +15 -1
- package/dist/office-js/toApiScript.mjs +8 -7
- package/dist/opc/DirectoryPartStore.d.mts +6 -2
- package/dist/opc/DirectoryPartStore.mjs +11 -4
- package/dist/opc/FlatOpcPartStore.d.mts +4 -2
- package/dist/opc/FlatOpcPartStore.mjs +11 -8
- package/dist/opc/Load.d.mts +32 -0
- package/dist/opc/Load.mjs +115 -5
- package/dist/opc/PartStore.d.mts +34 -1
- package/dist/opc/PartStore.mjs +71 -8
- package/dist/opc/Save.d.mts +17 -2
- package/dist/opc/Save.mjs +100 -4
- package/dist/opc/ZipPartStore.d.mts +4 -2
- package/dist/opc/ZipPartStore.mjs +12 -7
- package/dist/opc/exceptions.d.mts +140 -0
- package/dist/opc/exceptions.mjs +108 -0
- package/dist/opc/index.d.mts +7 -4
- package/dist/opc/index.mjs +5 -3
- package/dist/opc/mce/preprocessor.d.mts +121 -10
- package/dist/opc/mce/preprocessor.mjs +122 -21
- package/dist/opc/mce/understood.d.mts +3 -35
- package/dist/opc/mce/understood.mjs +27 -53
- package/dist/opc/repair/breaks.d.mts +23 -0
- package/dist/opc/repair/breaks.mjs +288 -0
- package/dist/opc/repair/hosts.d.mts +12 -0
- package/dist/opc/repair/hosts.mjs +33 -0
- package/dist/opc/repair/mainRoot.d.mts +19 -0
- package/dist/opc/repair/mainRoot.mjs +168 -0
- package/dist/opc/repair/unknownElements.d.mts +8 -0
- package/dist/opc/repair/unknownElements.mjs +180 -0
- package/dist/opc/repair/wmlNames.generated.d.mts +2 -0
- package/dist/opc/repair/wmlNames.generated.mjs +80 -0
- package/dist/opc/skipped.d.mts +50 -0
- package/dist/opc/skipped.mjs +140 -0
- package/dist/packages/OpcPackage.d.mts +98 -5
- package/dist/packages/OpcPackage.mjs +122 -7
- package/dist/packages/WordprocessingMLPackage.mjs +3 -1
- package/dist/packages/index.d.mts +1 -1
- package/dist/parts/Part.d.mts +5 -1
- package/dist/parts/RelationshipsPart.d.mts +15 -0
- package/dist/parts/RelationshipsPart.mjs +66 -1
- package/dist/parts/XmlPart.d.mts +54 -5
- package/dist/parts/XmlPart.mjs +203 -10
- package/dist/parts/wml/index.d.mts +12 -1
- package/dist/parts/wml/index.mjs +25 -1
- package/package.json +2 -2
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// A w:br outside a w:r, read as Word 365 reads it (CR-021, the port of docx4j CR-073; the rules are
|
|
2
|
+
// docx4j's mc-preprocessor.xslt at 95c6b42d3, its section 8). Word opens a w:br directly in a w:p, a
|
|
3
|
+
// w:hyperlink or another element that holds runs, one inside a w:t or a w:instrText, and one directly
|
|
4
|
+
// in w:body, with no repair, and reads each as a break at that point, all the text kept. The object
|
|
5
|
+
// model has no place for one there: inside a text element the runtime refuses the whole part ("Element
|
|
6
|
+
// text content may not contain START_ELEMENT"), and elsewhere it is a misplaced child (CR-016), unseen
|
|
7
|
+
// by the readers and refused at save. docx4j's load survey found one in 206,351 of 3.7 million
|
|
8
|
+
// documents, 89% of them a web-to-docx generator's blank line, <w:p><w:br/></w:p>.
|
|
9
|
+
//
|
|
10
|
+
// The pass runs over a WordprocessingML part's DOM before anything else does (`XmlPart`, ahead of the
|
|
11
|
+
// package's hook, and `insertOoxml`'s fragment route; CR-021 D1):
|
|
12
|
+
// 1. a w:br in a run container gets a w:r of its own, with the paragraph mark's run properties while no
|
|
13
|
+
// run precedes it in its paragraph, none after one;
|
|
14
|
+
// 2. a w:t, w:instrText, w:delText or w:delInstrText holding elements becomes its stretches of text and
|
|
15
|
+
// its breaks, in order, in the same run, a w:t nested in it giving its text, any other w: element and
|
|
16
|
+
// one in a namespace Word doesn't know going with all it holds (Word check 76, CR-021 section 11; docx4j gives its text);
|
|
17
|
+
// 3. a w:br directly in w:body goes into the next paragraph as its first run, with that mark's
|
|
18
|
+
// properties, else becomes a paragraph of its own.
|
|
19
|
+
// Each rule is decided on the tree as read, as the stylesheet's templates are, before anything moves.
|
|
20
|
+
// A clean part pays one walk over its DOM, which allocates nothing (CR-021 section 2: 0.4% of a load).
|
|
21
|
+
import { childElements } from '../../xml/dom.mjs';
|
|
22
|
+
import { UNDERSTOOD_NAMESPACES } from '../mce/understood.mjs';
|
|
23
|
+
import { skippedElement } from '../skipped.mjs';
|
|
24
|
+
const W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
25
|
+
const XML_NS = 'http://www.w3.org/XML/1998/namespace';
|
|
26
|
+
/** The run containers rule 1 takes a w:br from; named, since a math run (`m:r`) holds a w:br legally. */
|
|
27
|
+
const RUN_CONTAINERS = new Set(['p', 'hyperlink', 'smartTag', 'customXml', 'fldSimple', 'ins', 'del', 'moveFrom', 'moveTo', 'sdtContent', 'dir', 'bdo']);
|
|
28
|
+
/**
|
|
29
|
+
* The paragraph-or-block ancestors: rule 1 takes a w:br only where the nearest is a w:p, so a block-level
|
|
30
|
+
* w:sdtContent or w:customXml is not taken for an inline one.
|
|
31
|
+
*/
|
|
32
|
+
const BLOCKS = new Set(['p', 'body', 'tc', 'txbxContent', 'hdr', 'ftr', 'footnote', 'endnote', 'comment', 'docPartBody']);
|
|
33
|
+
/** The text elements rule 2 splits. */
|
|
34
|
+
const TEXTS = new Set(['t', 'instrText', 'delText', 'delInstrText']);
|
|
35
|
+
/** What only a paragraph mark's run properties hold, left out of a run's. */
|
|
36
|
+
const MARK_ONLY = new Set(['ins', 'del', 'moveFrom', 'moveTo', 'rPrChange']);
|
|
37
|
+
/**
|
|
38
|
+
* Puts each w:br found outside a w:r in a run, as Word reads it, over a part's DOM (CR-021; docx4j CR-073's three
|
|
39
|
+
* rules, above). A part whose root is not WordprocessingML's is left alone. Returns the part's warning, or nothing
|
|
40
|
+
* where the part holds none.
|
|
41
|
+
*/
|
|
42
|
+
export function repairBreaksOutsideRuns(doc, partName) {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const root = doc.documentElement;
|
|
45
|
+
if (!root || root.namespaceURI !== W)
|
|
46
|
+
return [];
|
|
47
|
+
let breaks;
|
|
48
|
+
let texts;
|
|
49
|
+
let textElements = 0;
|
|
50
|
+
for (let n = root; n !== null;) {
|
|
51
|
+
if (n.nodeType === 1) {
|
|
52
|
+
const e = n;
|
|
53
|
+
if (e.namespaceURI === W) {
|
|
54
|
+
const local = e.localName;
|
|
55
|
+
if (local === 'br') {
|
|
56
|
+
const parent = e.parentNode;
|
|
57
|
+
if (parent.namespaceURI === W && (RUN_CONTAINERS.has(parent.localName) || parent.localName === 'body'))
|
|
58
|
+
(breaks !== null && breaks !== void 0 ? breaks : (breaks = [])).push(e);
|
|
59
|
+
}
|
|
60
|
+
else if (TEXTS.has(local) && holdsElement(e)) {
|
|
61
|
+
textElements++;
|
|
62
|
+
if (!insideText(e))
|
|
63
|
+
(texts !== null && texts !== void 0 ? texts : (texts = [])).push(e);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (e.firstChild !== null) {
|
|
67
|
+
n = e.firstChild;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
while (n !== root && n.nextSibling === null)
|
|
72
|
+
n = n.parentNode;
|
|
73
|
+
n = n === root ? null : n.nextSibling;
|
|
74
|
+
}
|
|
75
|
+
if (breaks === undefined && texts === undefined)
|
|
76
|
+
return [];
|
|
77
|
+
// decided on the tree as read; a break inside a text element is rule 2's
|
|
78
|
+
const inRuns = [];
|
|
79
|
+
const inBody = [];
|
|
80
|
+
for (const br of breaks !== null && breaks !== void 0 ? breaks : []) {
|
|
81
|
+
if (insideText(br))
|
|
82
|
+
continue;
|
|
83
|
+
if (br.parentNode.localName === 'body') {
|
|
84
|
+
const next = nextBlock(br);
|
|
85
|
+
const p = next !== undefined && takesBodyBreaks(next) ? next : undefined;
|
|
86
|
+
inBody.push({ br, p, rPr: p && markRunProperties(p) });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const p = paragraphOf(br);
|
|
90
|
+
if (p !== undefined)
|
|
91
|
+
inRuns.push({ br, rPr: runPrecedes(br, p) ? undefined : markRunProperties(p) });
|
|
92
|
+
}
|
|
93
|
+
// (3) first, each paragraph's place for its breaks fixed before any goes in: after its w:pPr, in order
|
|
94
|
+
const places = new Map();
|
|
95
|
+
for (const { br, p, rPr } of inBody) {
|
|
96
|
+
const r = create(br, 'r');
|
|
97
|
+
if (rPr)
|
|
98
|
+
r.appendChild(rPr);
|
|
99
|
+
if (p === undefined) {
|
|
100
|
+
const own = create(br, 'p');
|
|
101
|
+
br.parentNode.insertBefore(own, br);
|
|
102
|
+
own.appendChild(r);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
if (!places.has(p)) {
|
|
106
|
+
const pPr = childElements(p).find((c) => c.namespaceURI === W && c.localName === 'pPr');
|
|
107
|
+
places.set(p, pPr ? pPr.nextSibling : p.firstChild);
|
|
108
|
+
}
|
|
109
|
+
p.insertBefore(r, (_a = places.get(p)) !== null && _a !== void 0 ? _a : null);
|
|
110
|
+
}
|
|
111
|
+
r.appendChild(bare(br));
|
|
112
|
+
}
|
|
113
|
+
// (1)
|
|
114
|
+
for (const { br, rPr } of inRuns) {
|
|
115
|
+
const r = create(br, 'r');
|
|
116
|
+
if (rPr)
|
|
117
|
+
r.appendChild(rPr);
|
|
118
|
+
br.parentNode.insertBefore(r, br);
|
|
119
|
+
r.appendChild(bare(br));
|
|
120
|
+
}
|
|
121
|
+
// (2)
|
|
122
|
+
const skipped = [];
|
|
123
|
+
for (const t of texts !== null && texts !== void 0 ? texts : [])
|
|
124
|
+
splitText(t, skipped, partName);
|
|
125
|
+
const outside = (_b = breaks === null || breaks === void 0 ? void 0 : breaks.length) !== null && _b !== void 0 ? _b : 0;
|
|
126
|
+
return [{
|
|
127
|
+
code: 'br.outside_run',
|
|
128
|
+
partName,
|
|
129
|
+
breaks: outside,
|
|
130
|
+
textElements,
|
|
131
|
+
message: `${partName !== null && partName !== void 0 ? partName : 'a part'}: w:br outside a w:r: ${outside} outside a run, ${textElements} text elements holding elements; read as Word reads them`,
|
|
132
|
+
}, ...skipped];
|
|
133
|
+
}
|
|
134
|
+
function holdsElement(e) {
|
|
135
|
+
for (let c = e.firstChild; c !== null; c = c.nextSibling)
|
|
136
|
+
if (c.nodeType === 1)
|
|
137
|
+
return true;
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
function insideText(node) {
|
|
141
|
+
for (let a = node.parentNode; a !== null && a.nodeType === 1; a = a.parentNode) {
|
|
142
|
+
if (a.namespaceURI === W && TEXTS.has(a.localName))
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
/** The break's paragraph, where its nearest paragraph-or-block ancestor is one. */
|
|
148
|
+
function paragraphOf(br) {
|
|
149
|
+
for (let a = br.parentNode; a !== null && a.nodeType === 1; a = a.parentNode) {
|
|
150
|
+
const e = a;
|
|
151
|
+
if (e.namespaceURI === W && BLOCKS.has(e.localName))
|
|
152
|
+
return e.localName === 'p' ? e : undefined;
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
/** True where a run precedes the break in its paragraph: on its path from the paragraph, an earlier sibling is or holds one. */
|
|
157
|
+
function runPrecedes(br, p) {
|
|
158
|
+
for (let n = br; n !== p; n = n.parentNode) {
|
|
159
|
+
for (let s = n.previousSibling; s !== null; s = s.previousSibling) {
|
|
160
|
+
if (s.nodeType !== 1)
|
|
161
|
+
continue;
|
|
162
|
+
const e = s;
|
|
163
|
+
if ((e.namespaceURI === W && e.localName === 'r') || e.getElementsByTagNameNS(W, 'r').length > 0)
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/** The paragraph mark's run properties as a run's, a copy: without what only a mark has; undefined where none is left. */
|
|
170
|
+
function markRunProperties(p) {
|
|
171
|
+
let rPr;
|
|
172
|
+
for (const pPr of childElements(p)) {
|
|
173
|
+
if (pPr.namespaceURI !== W || pPr.localName !== 'pPr')
|
|
174
|
+
continue;
|
|
175
|
+
for (const markPr of childElements(pPr)) {
|
|
176
|
+
if (markPr.namespaceURI !== W || markPr.localName !== 'rPr')
|
|
177
|
+
continue;
|
|
178
|
+
for (const property of childElements(markPr)) {
|
|
179
|
+
if (property.namespaceURI === W && MARK_ONLY.has(property.localName))
|
|
180
|
+
continue;
|
|
181
|
+
(rPr !== null && rPr !== void 0 ? rPr : (rPr = create(p, 'rPr'))).appendChild(property.cloneNode(true));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return rPr;
|
|
186
|
+
}
|
|
187
|
+
/** The first element after a body-level break that is not one. */
|
|
188
|
+
function nextBlock(br) {
|
|
189
|
+
for (let s = br.nextSibling; s !== null; s = s.nextSibling) {
|
|
190
|
+
if (s.nodeType !== 1)
|
|
191
|
+
continue;
|
|
192
|
+
const e = s;
|
|
193
|
+
if (!(e.namespaceURI === W && e.localName === 'br'))
|
|
194
|
+
return e;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* A w:p the body's breaks before it go into. Not one docx4j splits by its nested-paragraph rule (a w:p in a w:hyperlink
|
|
200
|
+
* or a w:r), which this package doesn't port: there, as in docx4j, each break is a paragraph of its own.
|
|
201
|
+
*/
|
|
202
|
+
function takesBodyBreaks(e) {
|
|
203
|
+
if (e.namespaceURI !== W || e.localName !== 'p')
|
|
204
|
+
return false;
|
|
205
|
+
for (const c of childElements(e)) {
|
|
206
|
+
if (c.namespaceURI !== W || (c.localName !== 'hyperlink' && c.localName !== 'r'))
|
|
207
|
+
continue;
|
|
208
|
+
for (const d of childElements(c)) {
|
|
209
|
+
if (d.namespaceURI !== W)
|
|
210
|
+
continue;
|
|
211
|
+
if (d.localName === 'p')
|
|
212
|
+
return false;
|
|
213
|
+
if (d.localName === 'r' && childElements(d).some((x) => x.namespaceURI === W && x.localName === 'p'))
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
/** The break as the stylesheet writes it: its attributes, no content. */
|
|
220
|
+
function bare(br) {
|
|
221
|
+
while (br.firstChild !== null)
|
|
222
|
+
br.removeChild(br.firstChild);
|
|
223
|
+
return br;
|
|
224
|
+
}
|
|
225
|
+
/** A WordprocessingML element under the prefix the part gives `like`, which is in that namespace. */
|
|
226
|
+
function create(like, local) {
|
|
227
|
+
return like.ownerDocument.createElementNS(W, like.prefix ? `${like.prefix}:${local}` : local);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Rule 2: the text element's stretches of text, each under its name, and its w:brs, in order, in its place. A
|
|
231
|
+
* stretch is XPath's text node (adjacent text and CDATA together), its `xml:space` the nearest ancestor's, and kept
|
|
232
|
+
* only where that preserves it or it holds more than whitespace. Any other element gives its text only as `givesItsText` says.
|
|
233
|
+
*/
|
|
234
|
+
function splitText(t, skipped, partName) {
|
|
235
|
+
const out = [];
|
|
236
|
+
const visit = (holder) => {
|
|
237
|
+
for (let c = holder.firstChild; c !== null;) {
|
|
238
|
+
if (c.nodeType === 3 || c.nodeType === 4) {
|
|
239
|
+
let text = '';
|
|
240
|
+
for (; c !== null && (c.nodeType === 3 || c.nodeType === 4); c = c.nextSibling)
|
|
241
|
+
text += c.data;
|
|
242
|
+
const space = spaceOf(holder);
|
|
243
|
+
if (space === 'preserve' || /[^ \t\r\n]/.test(text)) {
|
|
244
|
+
const piece = create(t, t.localName);
|
|
245
|
+
if (space !== '')
|
|
246
|
+
piece.setAttributeNS(XML_NS, 'xml:space', space);
|
|
247
|
+
piece.appendChild(t.ownerDocument.createTextNode(text));
|
|
248
|
+
out.push(piece);
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (c.nodeType === 1) {
|
|
253
|
+
const e = c;
|
|
254
|
+
if (e.namespaceURI === W && e.localName === 'br')
|
|
255
|
+
out.push(bare(e));
|
|
256
|
+
else if (givesItsText(e))
|
|
257
|
+
visit(e);
|
|
258
|
+
else
|
|
259
|
+
skipped.push(skippedElement(e, partName)); // the runtime never sees it (CR-025 D9)
|
|
260
|
+
}
|
|
261
|
+
c = c.nextSibling;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
visit(t);
|
|
265
|
+
const parent = t.parentNode;
|
|
266
|
+
for (const piece of out)
|
|
267
|
+
parent.insertBefore(piece, t);
|
|
268
|
+
parent.removeChild(t);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Whether an element in a text element gives its text (CR-021 section 11). As Word 365 reads it, a nested text element does, and
|
|
272
|
+
* any other w: element and one in a namespace Word doesn't know go with all they hold (check 76's `76n`, `76u`, `76w`, `76z`). Word
|
|
273
|
+
* refuses an element in no namespace (`76x`, `76y`), so docx4j's reading stands there; a namespace Word knows is unmeasured.
|
|
274
|
+
*/
|
|
275
|
+
function givesItsText(e) {
|
|
276
|
+
const namespace = e.namespaceURI;
|
|
277
|
+
if (namespace === W)
|
|
278
|
+
return TEXTS.has(e.localName);
|
|
279
|
+
return !namespace || UNDERSTOOD_NAMESPACES.has(namespace);
|
|
280
|
+
}
|
|
281
|
+
function spaceOf(holder) {
|
|
282
|
+
var _a;
|
|
283
|
+
for (let a = holder; a !== null && a.nodeType === 1; a = a.parentNode) {
|
|
284
|
+
if (a.hasAttributeNS(XML_NS, 'space'))
|
|
285
|
+
return (_a = a.getAttributeNS(XML_NS, 'space')) !== null && _a !== void 0 ? _a : '';
|
|
286
|
+
}
|
|
287
|
+
return '';
|
|
288
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
2
|
+
export declare const M = "http://schemas.openxmlformats.org/officeDocument/2006/math";
|
|
3
|
+
export declare const MC = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
4
|
+
/** The twenty hosts check 76 measured. */
|
|
5
|
+
export declare const HOSTS: ReadonlySet<string>;
|
|
6
|
+
/** The same names under parents that make them something else, none measured: no host there (WD REQ-308's NOTE). */
|
|
7
|
+
export declare const NOT_A_HOST_UNDER: Readonly<Record<string, readonly (readonly [string, string])[]>>;
|
|
8
|
+
export declare const isW: (e: Element, local: string) => boolean;
|
|
9
|
+
export declare const isUnknown: (e: Element) => boolean;
|
|
10
|
+
export declare const isBranch: (e: Element) => boolean;
|
|
11
|
+
/** The content host `parent` is, through a kept branch (`76t44`, `76t45`); undefined for anything else, another namespace's element above all (`76k`). */
|
|
12
|
+
export declare function hostOf(parent: Element): Element | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The content hosts an unknown w: element is read through in (CR-026 section 3.7), apart from the pass, so that CR-025 section 10's
|
|
2
|
+
// classes can name them without importing the pass, which imports the report.
|
|
3
|
+
import { WML_ELEMENT_NAMES } from './wmlNames.generated.mjs';
|
|
4
|
+
export const W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
5
|
+
export const M = 'http://schemas.openxmlformats.org/officeDocument/2006/math';
|
|
6
|
+
export const MC = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
|
|
7
|
+
/** The twenty hosts check 76 measured. */
|
|
8
|
+
export const HOSTS = new Set(['p', 'body', 'tc', 'r', 'tbl', 'tr', 'sdtContent', 'hyperlink', 'ins', 'del', 'moveFrom', 'moveTo',
|
|
9
|
+
'hdr', 'ftr', 'comment', 'fldSimple', 'smartTag', 'customXml', 'footnote', 'endnote']);
|
|
10
|
+
/** The same names under parents that make them something else, none measured: no host there (WD REQ-308's NOTE). */
|
|
11
|
+
export const NOT_A_HOST_UNDER = {
|
|
12
|
+
ins: [[W, 'numPr'], [W, 'rPr'], [W, 'trPr'], [M, 'r'], [M, 'ctrlPr']],
|
|
13
|
+
del: [[W, 'rPr'], [W, 'trPr'], [M, 'r'], [M, 'ctrlPr']],
|
|
14
|
+
moveFrom: [[W, 'rPr']],
|
|
15
|
+
moveTo: [[W, 'rPr']],
|
|
16
|
+
footnote: [[W, 'footnotePr']],
|
|
17
|
+
endnote: [[W, 'endnotePr']],
|
|
18
|
+
};
|
|
19
|
+
export const isW = (e, local) => e.namespaceURI === W && e.localName === local;
|
|
20
|
+
export const isUnknown = (e) => e.namespaceURI === W && !WML_ELEMENT_NAMES.has(e.localName);
|
|
21
|
+
export const isBranch = (e) => e.namespaceURI === MC && (e.localName === 'Choice' || e.localName === 'Fallback' || e.localName === 'AlternateContent');
|
|
22
|
+
/** The content host `parent` is, through a kept branch (`76t44`, `76t45`); undefined for anything else, another namespace's element above all (`76k`). */
|
|
23
|
+
export function hostOf(parent) {
|
|
24
|
+
var _a;
|
|
25
|
+
let host = parent;
|
|
26
|
+
while (isBranch(host))
|
|
27
|
+
host = host.parentNode;
|
|
28
|
+
if (host.namespaceURI !== W || !HOSTS.has(host.localName))
|
|
29
|
+
return undefined;
|
|
30
|
+
const above = host.parentNode;
|
|
31
|
+
const excluded = (_a = NOT_A_HOST_UNDER[host.localName]) === null || _a === void 0 ? void 0 : _a.some(([ns, local]) => (above === null || above === void 0 ? void 0 : above.namespaceURI) === ns && above.localName === local);
|
|
32
|
+
return excluded ? undefined : host;
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ContentSkippedWarning } from '../skipped.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* `part.unexpected_root` (WD REQ-320): a main document part whose root Word opens and shows nothing of, such as `w:hdr`,
|
|
4
|
+
* `w:styles` or an element in a namespace its `mc:Ignorable` names. It reads as an empty document, and a write through `Body` is
|
|
5
|
+
* refused (CR-028 D10). `root` is the root's qualified name, as the file spells it. The root is also a `part.skipped`, so a save
|
|
6
|
+
* copies the part as written while it is unchanged, and CR-025's gate refuses one that changed.
|
|
7
|
+
*/
|
|
8
|
+
export interface UnexpectedRootWarning {
|
|
9
|
+
code: 'part.unexpected_root';
|
|
10
|
+
partName: string;
|
|
11
|
+
root: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reads the root of a main document part's DOM: a root Word reads becomes a `w:document` holding a `w:body`, a root Word refuses
|
|
16
|
+
* throws `PartUnmarshalException`, and any other gives an empty document with `part.unexpected_root`. A `w:document` and an
|
|
17
|
+
* `mc:AlternateContent` are left alone. Returns the warnings: what a read root dropped, as `part.skipped`, or the unexpected root.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readMainPartRoot(doc: Document, partName: string): (UnexpectedRootWarning | ContentSkippedWarning)[];
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// A main document part whose root isn't w:document, read as WD REQ-318 to REQ-320 have it (CR-028 D10 and D11; docx4j CR-079,
|
|
2
|
+
// python CR-039). Word reads some such roots as a document, refuses some, and opens the rest showing nothing. The pass runs on
|
|
3
|
+
// the DOM ahead of CR-026's and of the runtime, so a root it reads reaches them as a w:document holding a w:body.
|
|
4
|
+
// mc:AlternateContent as the root is REQ-317's, and left as it is.
|
|
5
|
+
import { PartUnmarshalException } from '../exceptions.mjs';
|
|
6
|
+
import { skippedElement, UNMEASURED } from '../skipped.mjs';
|
|
7
|
+
import { UNDERSTOOD_NAMESPACES } from '../mce/understood.mjs';
|
|
8
|
+
import { WML_ELEMENT_NAMES } from './wmlNames.generated.mjs';
|
|
9
|
+
const W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
10
|
+
const MC = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
|
|
11
|
+
const WORD_2003 = 'http://schemas.microsoft.com/office/word/2003/wordml';
|
|
12
|
+
const XML_PACKAGE = 'http://schemas.microsoft.com/office/2006/xmlPackage';
|
|
13
|
+
const XMLNS = 'http://www.w3.org/2000/xmlns/';
|
|
14
|
+
/**
|
|
15
|
+
* Reads the root of a main document part's DOM: a root Word reads becomes a `w:document` holding a `w:body`, a root Word refuses
|
|
16
|
+
* throws `PartUnmarshalException`, and any other gives an empty document with `part.unexpected_root`. A `w:document` and an
|
|
17
|
+
* `mc:AlternateContent` are left alone. Returns the warnings: what a read root dropped, as `part.skipped`, or the unexpected root.
|
|
18
|
+
*/
|
|
19
|
+
export function readMainPartRoot(doc, partName) {
|
|
20
|
+
const root = doc.documentElement;
|
|
21
|
+
if (!root)
|
|
22
|
+
return [];
|
|
23
|
+
if (isW(root, 'document') || (root.namespaceURI === MC && root.localName === 'AlternateContent'))
|
|
24
|
+
return [];
|
|
25
|
+
if (refused(root)) {
|
|
26
|
+
throw new PartUnmarshalException(`${partName}: its root, ${root.nodeName}, is one Word refuses a document for (WD REQ-318)`, partName);
|
|
27
|
+
}
|
|
28
|
+
const body = bodyFrom(root, partName);
|
|
29
|
+
if (body) {
|
|
30
|
+
replaceRoot(doc, root, body.body);
|
|
31
|
+
return body.skipped;
|
|
32
|
+
}
|
|
33
|
+
const skipped = skippedElement(root, partName);
|
|
34
|
+
replaceRoot(doc, root, doc.createElementNS(W, 'w:body'));
|
|
35
|
+
return [{
|
|
36
|
+
code: 'part.unexpected_root',
|
|
37
|
+
partName,
|
|
38
|
+
root: root.nodeName,
|
|
39
|
+
message: `${partName}: its root, ${root.nodeName}, is one Word opens showing nothing of, so it reads as an empty document (WD REQ-320)`,
|
|
40
|
+
}, skipped];
|
|
41
|
+
}
|
|
42
|
+
/** WD REQ-318: a root Word refuses, by its name or by what it holds. */
|
|
43
|
+
function refused(root) {
|
|
44
|
+
const ns = root.namespaceURI;
|
|
45
|
+
const local = root.localName;
|
|
46
|
+
if (ns === W && (local === 'footnotes' || local === 'endnotes' || local === 'tc' || local === 'txbxContent'))
|
|
47
|
+
return true;
|
|
48
|
+
if ((ns === WORD_2003 && local === 'wordDocument') || (ns === XML_PACKAGE && local === 'package'))
|
|
49
|
+
return true;
|
|
50
|
+
if (isW(root, 'sdtContent'))
|
|
51
|
+
return childrenOf(root).some((c) => isW(c, 'tr') || (isW(c, 'sdt') && sdtLevel(c) === 'row'));
|
|
52
|
+
if (ns === W && !WML_ELEMENT_NAMES.has(local)) {
|
|
53
|
+
return childrenOf(root).some((c) => isW(c, 'document') || isW(c, 'r') || (isW(c, 'sdt') && sdtLevel(c) === 'run'));
|
|
54
|
+
}
|
|
55
|
+
if (ns !== null && (ns === W || UNDERSTOOD_NAMESPACES.has(ns)))
|
|
56
|
+
return false;
|
|
57
|
+
return !ignorable(root) && childrenOf(root).some((c) => isW(c, 'document') || isW(c, 'p'));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* WD REQ-319: the `w:body` a root Word reads stands for, made from what it holds, and what that dropped; undefined for a root
|
|
61
|
+
* Word doesn't read (REQ-320).
|
|
62
|
+
*/
|
|
63
|
+
function bodyFrom(root, partName) {
|
|
64
|
+
const doc = root.ownerDocument;
|
|
65
|
+
const wrap = (nodes) => {
|
|
66
|
+
const body = doc.createElementNS(W, 'w:body');
|
|
67
|
+
for (const n of nodes)
|
|
68
|
+
body.appendChild(n);
|
|
69
|
+
return body;
|
|
70
|
+
};
|
|
71
|
+
if (isW(root, 'body'))
|
|
72
|
+
return { body: root, skipped: [] };
|
|
73
|
+
if (isW(root, 'p') || (isW(root, 'sdt') && sdtLevel(root) === 'block'))
|
|
74
|
+
return { body: wrap([root]), skipped: [] };
|
|
75
|
+
if (isW(root, 'sdtContent'))
|
|
76
|
+
return { body: wrap(nodesOf(root)), skipped: [] };
|
|
77
|
+
if (root.namespaceURI !== W || WML_ELEMENT_NAMES.has(root.localName))
|
|
78
|
+
return undefined;
|
|
79
|
+
const children = childrenOf(root);
|
|
80
|
+
const held = children.find((c) => isW(c, 'body'));
|
|
81
|
+
if (held) {
|
|
82
|
+
// that body is the body, its w:sectPr read (mr-15); what stands beside it nothing measures, so it goes and is said to
|
|
83
|
+
const skipped = children.filter((c) => c !== held).map((c) => skippedElement(c, partName, undefined, UNMEASURED));
|
|
84
|
+
return { body: held, skipped };
|
|
85
|
+
}
|
|
86
|
+
if (children.length > 0 && !children.some(readsAsBody))
|
|
87
|
+
return undefined;
|
|
88
|
+
// a w:sectPr directly in it has no effect (mr-11), so it goes, said as CR-026 D10 says a w:sectPr it drops
|
|
89
|
+
const sections = children.filter((c) => isW(c, 'sectPr'));
|
|
90
|
+
const skipped = sections.map((c) => skippedElement(c, partName));
|
|
91
|
+
for (const s of sections)
|
|
92
|
+
root.removeChild(s);
|
|
93
|
+
return { body: wrap(nodesOf(root)), skipped };
|
|
94
|
+
}
|
|
95
|
+
/** What an unknown `w:` root holds that makes Word read it as a body (WD REQ-319). */
|
|
96
|
+
function readsAsBody(c) {
|
|
97
|
+
if (isW(c, 'p') || isW(c, 'tbl') || (isW(c, 'sdt') && sdtLevel(c) === 'block'))
|
|
98
|
+
return true;
|
|
99
|
+
if (c.namespaceURI === MC && c.localName === 'AlternateContent')
|
|
100
|
+
return true;
|
|
101
|
+
return c.namespaceURI === W && !WML_ELEMENT_NAMES.has(c.localName);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Puts a `w:document` holding `body` where `root` stood. The root's namespace declarations are copied onto it, so a prefix only
|
|
105
|
+
* the root bound still resolves, and its `mc:` attributes moved there, so its `mc:Ignorable` still stands and a root kept as the
|
|
106
|
+
* body or its paragraph holds none the model has no place for. `body` may be the root, or hold it.
|
|
107
|
+
*/
|
|
108
|
+
function replaceRoot(doc, root, body) {
|
|
109
|
+
const document = doc.createElementNS(W, 'w:document');
|
|
110
|
+
const moved = [];
|
|
111
|
+
for (let i = 0; i < root.attributes.length; i++) {
|
|
112
|
+
const a = root.attributes.item(i);
|
|
113
|
+
if (a.namespaceURI === XMLNS || a.namespaceURI === MC)
|
|
114
|
+
document.setAttributeNS(a.namespaceURI, a.name, a.value);
|
|
115
|
+
if (a.namespaceURI === MC)
|
|
116
|
+
moved.push(a);
|
|
117
|
+
}
|
|
118
|
+
for (const a of moved)
|
|
119
|
+
root.removeAttributeNode(a);
|
|
120
|
+
if (root.parentNode === doc)
|
|
121
|
+
doc.removeChild(root);
|
|
122
|
+
if (body.parentNode && body.parentNode !== document)
|
|
123
|
+
body.parentNode.removeChild(body);
|
|
124
|
+
document.appendChild(body);
|
|
125
|
+
doc.appendChild(document);
|
|
126
|
+
}
|
|
127
|
+
/** The level a `w:sdt` stands at, by what its `w:sdtContent` holds first that says: an empty one is taken as a block. */
|
|
128
|
+
function sdtLevel(sdt) {
|
|
129
|
+
const content = childrenOf(sdt).find((c) => isW(c, 'sdtContent'));
|
|
130
|
+
for (const c of content ? childrenOf(content) : []) {
|
|
131
|
+
if (isW(c, 'p') || isW(c, 'tbl') || isW(c, 'altChunk'))
|
|
132
|
+
return 'block';
|
|
133
|
+
if (isW(c, 'tr'))
|
|
134
|
+
return 'row';
|
|
135
|
+
if (isW(c, 'tc'))
|
|
136
|
+
return 'cell';
|
|
137
|
+
if (isW(c, 'r') || isW(c, 'hyperlink') || isW(c, 'fldSimple') || isW(c, 'smartTag') || isW(c, 'ins') || isW(c, 'del'))
|
|
138
|
+
return 'run';
|
|
139
|
+
if (isW(c, 'sdt'))
|
|
140
|
+
return sdtLevel(c);
|
|
141
|
+
}
|
|
142
|
+
return 'block';
|
|
143
|
+
}
|
|
144
|
+
/** Whether the root's own namespace is one its `mc:Ignorable` names. */
|
|
145
|
+
function ignorable(root) {
|
|
146
|
+
var _a;
|
|
147
|
+
const ns = root.namespaceURI;
|
|
148
|
+
if (!ns)
|
|
149
|
+
return false;
|
|
150
|
+
const listed = (_a = root.getAttributeNS(MC, 'Ignorable')) !== null && _a !== void 0 ? _a : '';
|
|
151
|
+
return listed.trim().split(/\s+/).some((prefix) => prefix !== '' && root.lookupNamespaceURI(prefix) === ns);
|
|
152
|
+
}
|
|
153
|
+
function isW(el, local) {
|
|
154
|
+
return el.namespaceURI === W && el.localName === local;
|
|
155
|
+
}
|
|
156
|
+
function childrenOf(el) {
|
|
157
|
+
const out = [];
|
|
158
|
+
for (let c = el.firstChild; c; c = c.nextSibling)
|
|
159
|
+
if (c.nodeType === 1)
|
|
160
|
+
out.push(c);
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
function nodesOf(el) {
|
|
164
|
+
const out = [];
|
|
165
|
+
for (let c = el.firstChild; c; c = c.nextSibling)
|
|
166
|
+
out.push(c);
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ContentSkippedWarning } from '../skipped.mjs';
|
|
2
|
+
/** Reads a part's unknown w: elements as Word does, reporting each it takes. What it leaves, the runtime skips and reports. */
|
|
3
|
+
export declare function readThroughUnknownElements(doc: Document, partName?: string): ContentSkippedWarning[];
|
|
4
|
+
/**
|
|
5
|
+
* The elements a caller's fragment holds that Word doesn't know, for `insertOoxml` to refuse (D12): an unknown w: element, and
|
|
6
|
+
* under a w: element one of a namespace the reader doesn't understand. What a w:t holds counts: CR-021's pass would remove it unreported.
|
|
7
|
+
*/
|
|
8
|
+
export declare function unknownElementsIn(doc: Document): Element[];
|