@stll/folio-core 0.33.1 → 0.33.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/dist/ai-edits/apply.d.ts +15 -0
- package/dist/ai-edits/apply.js +134 -25
- package/dist/ai-edits/headless.d.ts +47 -3
- package/dist/ai-edits/headless.js +53 -3
- package/dist/ai-edits/index.d.ts +1 -1
- package/dist/ai-edits/snapshot.d.ts +19 -1
- package/dist/ai-edits/snapshot.js +25 -3
- package/dist/ai-edits/table-geometry.d.ts +56 -0
- package/dist/ai-edits/table-geometry.js +217 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
- package/dist/ai-edits/table-row-column-mutations.js +12 -4
- package/dist/ai-edits/table-template.d.ts +60 -0
- package/dist/ai-edits/table-template.js +176 -0
- package/dist/ai-edits/types.d.ts +7 -0
- package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
- package/dist/compare/__fixtures__/body-sequence.js +94 -6
- package/dist/compare/compare.d.ts +14 -7
- package/dist/compare/compare.js +136 -38
- package/dist/compare/plan.d.ts +31 -1
- package/dist/compare/plan.js +233 -66
- package/dist/compare/types.d.ts +9 -7
- package/dist/compare/types.js +7 -5
- package/dist/compare/verification.d.ts +31 -12
- package/dist/compare/verification.js +88 -17
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +2 -2
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +10 -1
- package/dist/document-operations.js +3 -2
- package/dist/docx/appVersionNormalization.d.ts +49 -0
- package/dist/docx/appVersionNormalization.js +74 -0
- package/dist/docx/blockContentParser.js +3 -2
- package/dist/docx/drawingUtils.js +4 -3
- package/dist/docx/paragraphParser.js +3 -2
- package/dist/docx/rezip.d.ts +10 -4
- package/dist/docx/rezip.js +54 -18
- package/dist/docx/runParser.js +9 -8
- package/dist/docx/sdtProperties.js +4 -3
- package/dist/docx/selectiveSave.js +7 -6
- package/dist/docx/serializer/commentSerializer.d.ts +1 -1
- package/dist/docx/serializer/commentSerializer.js +43 -19
- package/dist/docx/serializer/documentSerializer.d.ts +3 -1
- package/dist/docx/serializer/documentSerializer.js +42 -78
- package/dist/docx/serializer/fontTableSerializer.js +8 -5
- package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
- package/dist/docx/serializer/headerFooterSerializer.js +34 -29
- package/dist/docx/serializer/noteSerializer.js +34 -30
- package/dist/docx/serializer/numberingSerializer.js +8 -4
- package/dist/docx/serializer/paragraphSerializer.js +3 -3
- package/dist/docx/serializer/partNamespaces.d.ts +78 -0
- package/dist/docx/serializer/partNamespaces.js +324 -0
- package/dist/docx/serializer/runSerializer.js +1 -1
- package/dist/docx/serializer/settingsSerializer.js +8 -2
- package/dist/docx/serializer/stylesSerializer.js +8 -5
- package/dist/docx/serializer/tableSerializer.js +83 -18
- package/dist/docx/serializer/themeSerializer.js +10 -2
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
- package/dist/docx/strictValueEncodings.gen.js +275 -0
- package/dist/docx/tableParser.d.ts +0 -6
- package/dist/docx/tableParser.js +52 -7
- package/dist/docx/transitionalSpelling.d.ts +23 -0
- package/dist/docx/transitionalSpelling.js +36 -0
- package/dist/docx/universalMeasure.d.ts +20 -0
- package/dist/docx/universalMeasure.js +32 -0
- package/dist/docx/verbatimCapture.d.ts +20 -0
- package/dist/docx/verbatimCapture.js +131 -0
- package/dist/docx/vmlImageParser.js +4 -3
- package/dist/docx/watermarkParser.js +4 -3
- package/dist/docx/xmlParser.d.ts +23 -2
- package/dist/docx/xmlParser.js +43 -11
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/prosemirror/commands/comments.js +2 -1
- package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
- package/dist/prosemirror/containerFinalParagraph.js +92 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +15 -4
- package/dist/prosemirror/conversion/toProseDoc.js +12 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
- package/dist/prosemirror/schema/marks.d.ts +1 -1
- package/dist/prosemirror/schema/nodes.d.ts +22 -0
- package/dist/server.d.ts +1 -1
- package/dist/types/block-id.d.ts +12 -1
- package/dist/types/block-id.js +17 -1
- package/dist/utils/canonicalJson.d.ts +14 -0
- package/dist/utils/canonicalJson.js +19 -0
- package/dist/utils/formatToStyle.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
import { canReplayHeaderFooterVerbatim, getHeaderFooterVerbatimXml } from "../headerFooterVerbatim.js";
|
|
2
2
|
import { serializeBlockSdt } from "./blockSdtSerializer.js";
|
|
3
3
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
4
|
+
import { serializePartElement } from "./partNamespaces.js";
|
|
4
5
|
import { serializeTable } from "./tableSerializer.js";
|
|
5
6
|
import { escapeXml } from "./xmlUtils.js";
|
|
6
7
|
//#region src/docx/serializer/headerFooterSerializer.ts
|
|
7
|
-
const
|
|
8
|
-
wpc
|
|
9
|
-
mc
|
|
10
|
-
o
|
|
11
|
-
r
|
|
12
|
-
m
|
|
13
|
-
v
|
|
14
|
-
a
|
|
15
|
-
pic
|
|
16
|
-
wp14
|
|
17
|
-
wp
|
|
18
|
-
w10
|
|
19
|
-
w
|
|
20
|
-
w14
|
|
21
|
-
w15
|
|
22
|
-
w16
|
|
23
|
-
w16cex
|
|
24
|
-
w16cid
|
|
25
|
-
w16sdtdh
|
|
26
|
-
w16se
|
|
27
|
-
wpg
|
|
28
|
-
wps
|
|
29
|
-
|
|
30
|
-
function buildNamespaceDeclarations() {
|
|
31
|
-
return Object.entries(NAMESPACES).map(([prefix, uri]) => `xmlns:${prefix}="${uri}"`).join(" ");
|
|
32
|
-
}
|
|
8
|
+
const HEADER_FOOTER_BASELINE_PREFIXES = [
|
|
9
|
+
"wpc",
|
|
10
|
+
"mc",
|
|
11
|
+
"o",
|
|
12
|
+
"r",
|
|
13
|
+
"m",
|
|
14
|
+
"v",
|
|
15
|
+
"a",
|
|
16
|
+
"pic",
|
|
17
|
+
"wp14",
|
|
18
|
+
"wp",
|
|
19
|
+
"w10",
|
|
20
|
+
"w",
|
|
21
|
+
"w14",
|
|
22
|
+
"w15",
|
|
23
|
+
"w16",
|
|
24
|
+
"w16cex",
|
|
25
|
+
"w16cid",
|
|
26
|
+
"w16sdtdh",
|
|
27
|
+
"w16se",
|
|
28
|
+
"wpg",
|
|
29
|
+
"wps"
|
|
30
|
+
];
|
|
33
31
|
/**
|
|
34
32
|
* Serialize a block content item (paragraph, table, or block-level SDT) for
|
|
35
33
|
* header/footer.
|
|
@@ -43,13 +41,14 @@ function serializeBlock(block) {
|
|
|
43
41
|
* Serialize a HeaderFooter object to valid OOXML XML
|
|
44
42
|
*
|
|
45
43
|
* @param hf - HeaderFooter object to serialize
|
|
44
|
+
* @param source - The part being replaced, so a prefix only the source
|
|
45
|
+
* document bound keeps its URI
|
|
46
46
|
* @returns Complete XML string for header*.xml or footer*.xml
|
|
47
47
|
*/
|
|
48
|
-
function serializeHeaderFooter(hf) {
|
|
48
|
+
function serializeHeaderFooter(hf, source) {
|
|
49
49
|
const verbatim = getHeaderFooterVerbatimXml(hf);
|
|
50
50
|
if (verbatim && canReplayHeaderFooterVerbatim(hf)) return verbatim;
|
|
51
51
|
const rootTag = hf.type === "header" ? "w:hdr" : "w:ftr";
|
|
52
|
-
const nsDecl = buildNamespaceDeclarations();
|
|
53
52
|
const watermarkXml = serializeWatermarkParagraph(hf);
|
|
54
53
|
const watermarkInsertIndex = hf.watermarkBlockIndex !== void 0 ? Math.max(0, Math.min(hf.watermarkBlockIndex, hf.content.length)) : 0;
|
|
55
54
|
const blocksXml = hf.content.map((block) => serializeBlock(block));
|
|
@@ -57,7 +56,13 @@ function serializeHeaderFooter(hf) {
|
|
|
57
56
|
if (watermarkXml) contentXml = blocksXml.slice(0, watermarkInsertIndex).join("") + watermarkXml + blocksXml.slice(watermarkInsertIndex).join("");
|
|
58
57
|
else contentXml = blocksXml.join("");
|
|
59
58
|
if (!contentXml) contentXml = "<w:p><w:pPr/></w:p>";
|
|
60
|
-
return
|
|
59
|
+
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
60
|
+
partPath: source?.path ?? `word/${hf.type}.xml`,
|
|
61
|
+
rootName: rootTag,
|
|
62
|
+
baselinePrefixes: HEADER_FOOTER_BASELINE_PREFIXES,
|
|
63
|
+
sourceBindings: source?.bindings,
|
|
64
|
+
body: contentXml
|
|
65
|
+
});
|
|
61
66
|
}
|
|
62
67
|
function serializeWatermarkParagraph(hf) {
|
|
63
68
|
if (hf.rawWatermarkXml) return hf.rawWatermarkXml;
|
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
import { serializeBlockSdt } from "./blockSdtSerializer.js";
|
|
2
2
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
3
|
+
import { serializePartElement } from "./partNamespaces.js";
|
|
3
4
|
import { serializeTable } from "./tableSerializer.js";
|
|
4
5
|
//#region src/docx/serializer/noteSerializer.ts
|
|
5
|
-
const
|
|
6
|
-
wpc
|
|
7
|
-
mc
|
|
8
|
-
o
|
|
9
|
-
r
|
|
10
|
-
m
|
|
11
|
-
v
|
|
12
|
-
a
|
|
13
|
-
pic
|
|
14
|
-
wp14
|
|
15
|
-
wp
|
|
16
|
-
w10
|
|
17
|
-
w
|
|
18
|
-
w14
|
|
19
|
-
w15
|
|
20
|
-
w16
|
|
21
|
-
w16cex
|
|
22
|
-
w16cid
|
|
23
|
-
w16sdtdh
|
|
24
|
-
w16se
|
|
25
|
-
wpg
|
|
26
|
-
wps
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
6
|
+
const NOTE_BASELINE_PREFIXES = [
|
|
7
|
+
"wpc",
|
|
8
|
+
"mc",
|
|
9
|
+
"o",
|
|
10
|
+
"r",
|
|
11
|
+
"m",
|
|
12
|
+
"v",
|
|
13
|
+
"a",
|
|
14
|
+
"pic",
|
|
15
|
+
"wp14",
|
|
16
|
+
"wp",
|
|
17
|
+
"w10",
|
|
18
|
+
"w",
|
|
19
|
+
"w14",
|
|
20
|
+
"w15",
|
|
21
|
+
"w16",
|
|
22
|
+
"w16cex",
|
|
23
|
+
"w16cid",
|
|
24
|
+
"w16sdtdh",
|
|
25
|
+
"w16se",
|
|
26
|
+
"wpg",
|
|
27
|
+
"wps"
|
|
28
|
+
];
|
|
29
|
+
const serializeNotePart = (elementName, body) => "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
30
|
+
partPath: `word/${elementName}s.xml`,
|
|
31
|
+
rootName: `w:${elementName}s`,
|
|
32
|
+
baselinePrefixes: NOTE_BASELINE_PREFIXES,
|
|
33
|
+
sourceBindings: void 0,
|
|
34
|
+
body
|
|
35
|
+
});
|
|
31
36
|
/**
|
|
32
37
|
* Serialize a block content item (paragraph, table, or block-level SDT) for a
|
|
33
38
|
* note body.
|
|
@@ -63,9 +68,8 @@ function insertNoteReferenceMark(xml, elementName) {
|
|
|
63
68
|
return `${xml.slice(0, insertAt)}${referenceRun}${xml.slice(insertAt)}`;
|
|
64
69
|
}
|
|
65
70
|
function serializeNewNotePart(elementName, notes) {
|
|
66
|
-
const nsDecl = buildNamespaceDeclarations();
|
|
67
71
|
const serializedNotes = notes.map((note) => insertNoteReferenceMark(serializeNote(elementName, note), elementName)).join("");
|
|
68
|
-
return
|
|
72
|
+
return serializeNotePart(elementName, serializeRequiredNoteSeparators(elementName) + serializedNotes);
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
71
75
|
* Serialize footnotes to a complete word/footnotes.xml string.
|
|
@@ -74,7 +78,7 @@ function serializeNewNotePart(elementName, notes) {
|
|
|
74
78
|
* @returns Complete footnotes.xml string.
|
|
75
79
|
*/
|
|
76
80
|
function serializeFootnotes(footnotes) {
|
|
77
|
-
return
|
|
81
|
+
return serializeNotePart("footnote", footnotes.map((fn) => serializeNote("footnote", fn)).join(""));
|
|
78
82
|
}
|
|
79
83
|
/**
|
|
80
84
|
* Serialize endnotes to a complete word/endnotes.xml string.
|
|
@@ -83,7 +87,7 @@ function serializeFootnotes(footnotes) {
|
|
|
83
87
|
* @returns Complete endnotes.xml string.
|
|
84
88
|
*/
|
|
85
89
|
function serializeEndnotes(endnotes) {
|
|
86
|
-
return
|
|
90
|
+
return serializeNotePart("endnote", endnotes.map((en) => serializeNote("endnote", en)).join(""));
|
|
87
91
|
}
|
|
88
92
|
/** Serialize a brand-new footnote part, including Word's required separators
|
|
89
93
|
* and the automatic reference mark at the start of every normal note. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { serializePartElement } from "./partNamespaces.js";
|
|
1
2
|
import { serializeTextFormatting } from "./runSerializer.js";
|
|
2
3
|
import { escapeXml, intAttr } from "./xmlUtils.js";
|
|
3
4
|
//#region src/docx/serializer/numberingSerializer.ts
|
|
4
|
-
const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
5
5
|
/**
|
|
6
6
|
* Serialize a level's paragraph properties — the modeled subset is indentation
|
|
7
7
|
* plus tab stops (see `parseLevelParagraphProps`). Returns "" when neither is
|
|
@@ -87,9 +87,13 @@ function serializeNum(instance) {
|
|
|
87
87
|
* change detection and per-definition splicing, never to overwrite the part.
|
|
88
88
|
*/
|
|
89
89
|
function serializeNumberingXml(numbering) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
91
|
+
partPath: "word/numbering.xml",
|
|
92
|
+
rootName: "w:numbering",
|
|
93
|
+
baselinePrefixes: ["w"],
|
|
94
|
+
sourceBindings: void 0,
|
|
95
|
+
body: `${numbering.abstractNums.map(serializeAbstractNum).join("")}${numbering.nums.map(serializeNum).join("")}`
|
|
96
|
+
});
|
|
93
97
|
}
|
|
94
98
|
//#endregion
|
|
95
99
|
export { serializeNumberingXml };
|
|
@@ -150,12 +150,10 @@ function serializeParagraphFormatting(formatting, propertyChanges, pPrMark) {
|
|
|
150
150
|
if (formatting.styleId) parts.push(`<w:pStyle w:val="${escapeXml(formatting.styleId)}"/>`);
|
|
151
151
|
pushToggle("keepNext", formatting.keepNext);
|
|
152
152
|
pushToggle("keepLines", formatting.keepLines);
|
|
153
|
-
pushToggle("contextualSpacing", formatting.contextualSpacing);
|
|
154
153
|
pushToggle("pageBreakBefore", formatting.pageBreakBefore);
|
|
155
154
|
const frameXml = serializeFrameProperties(formatting.frame);
|
|
156
155
|
if (frameXml) parts.push(frameXml);
|
|
157
156
|
pushToggle("widowControl", formatting.widowControl);
|
|
158
|
-
pushToggle("snapToGrid", formatting.snapToGrid);
|
|
159
157
|
const numPrXml = formatting.numPrFromStyle != null && numPrEqual(formatting.numPr, formatting.numPrFromStyle) ? "" : serializeNumbering(formatting.numPr);
|
|
160
158
|
if (numPrXml) parts.push(numPrXml);
|
|
161
159
|
const bordersXml = serializeParagraphBorders(formatting.borders);
|
|
@@ -168,11 +166,13 @@ function serializeParagraphFormatting(formatting, propertyChanges, pPrMark) {
|
|
|
168
166
|
pushToggle("suppressAutoHyphens", formatting.suppressAutoHyphens);
|
|
169
167
|
pushToggle("kinsoku", formatting.kinsoku);
|
|
170
168
|
pushToggle("overflowPunct", formatting.overflowPunctuation);
|
|
169
|
+
pushToggle("bidi", formatting.bidi);
|
|
170
|
+
pushToggle("snapToGrid", formatting.snapToGrid);
|
|
171
171
|
const spacingXml = serializeSpacing(formatting);
|
|
172
172
|
if (spacingXml) parts.push(spacingXml);
|
|
173
173
|
const indXml = serializeIndentation(formatting);
|
|
174
174
|
if (indXml) parts.push(indXml);
|
|
175
|
-
pushToggle("
|
|
175
|
+
pushToggle("contextualSpacing", formatting.contextualSpacing);
|
|
176
176
|
if (formatting.alignment) parts.push(`<w:jc w:val="${formatting.alignment}"/>`);
|
|
177
177
|
if (formatting.outlineLevel !== void 0) parts.push(`<w:outlineLvl w:val="${formatting.outlineLevel}"/>`);
|
|
178
178
|
if (pPrMark || formatting.runProperties || formatting.runInWithNext) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { OoxmlPrefix } from "@stll/docx-utils";
|
|
2
|
+
//#region src/docx/serializer/partNamespaces.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* A namespace a rebuilt WordprocessingML part may declare.
|
|
5
|
+
*
|
|
6
|
+
* `ignorable` marks an extension that decorates baseline elements in place
|
|
7
|
+
* (`w14:paraId`, `w16du:dateUtc`, `wp14:sizeRelH`): a consumer that predates it
|
|
8
|
+
* has to be told through `mc:Ignorable` to skip it, or it rejects the part.
|
|
9
|
+
* Extensions reachable only inside an `mc:AlternateContent` choice or an
|
|
10
|
+
* `a:graphicData` payload (`wps`, `wpg`, `wne`, `cx*`) are already gated by
|
|
11
|
+
* that wrapper, so they stay out of the attribute, as mainstream producers
|
|
12
|
+
* leave them.
|
|
13
|
+
*/
|
|
14
|
+
type OoxmlNamespace = {
|
|
15
|
+
readonly uri: string;
|
|
16
|
+
readonly ignorable: boolean;
|
|
17
|
+
};
|
|
18
|
+
/** Packaging prefixes; they never appear inside a WordprocessingML part. */
|
|
19
|
+
type PackagingPrefix = "ct" | "pr";
|
|
20
|
+
/**
|
|
21
|
+
* A prefix folio can bind on a rebuilt part without consulting the source.
|
|
22
|
+
*
|
|
23
|
+
* Every prefix the parser resolves is a member, so a prefix the parser
|
|
24
|
+
* understands can always be written back, and {@link OOXML_NAMESPACES} is total
|
|
25
|
+
* over the union: a member with no entry, or an entry with no member, does not
|
|
26
|
+
* compile.
|
|
27
|
+
*/
|
|
28
|
+
type OoxmlNamespacePrefix = Exclude<OoxmlPrefix, PackagingPrefix> | "cx" | "cx1" | "cx2" | "cx3" | "cx4" | "cx5" | "cx6" | "cx7" | "cx8" | "aink" | "am3d" | "oel" | "w10" | "w16" | "w16cex" | "w16cid" | "w16du" | "w16sdtdh" | "w16se" | "wpi" | "wne";
|
|
29
|
+
/**
|
|
30
|
+
* Every namespace a rebuilt part can declare, in the order producers write them.
|
|
31
|
+
*
|
|
32
|
+
* URIs shared with the parser are taken from its table rather than retyped, so
|
|
33
|
+
* the two cannot drift.
|
|
34
|
+
*/
|
|
35
|
+
declare const OOXML_NAMESPACES: Readonly<Record<OoxmlNamespacePrefix, OoxmlNamespace>>;
|
|
36
|
+
declare const UnboundNamespacePrefixError_base: import("better-result").TaggedErrorClass<"UnboundNamespacePrefixError">;
|
|
37
|
+
/** A rebuilt part uses a prefix that resolves to no namespace URI. */
|
|
38
|
+
declare class UnboundNamespacePrefixError extends UnboundNamespacePrefixError_base<{
|
|
39
|
+
message: string;
|
|
40
|
+
partPath: string;
|
|
41
|
+
prefixes: readonly string[];
|
|
42
|
+
}> {}
|
|
43
|
+
/**
|
|
44
|
+
* The `xmlns:*` bindings a source part declared on its root element.
|
|
45
|
+
*
|
|
46
|
+
* Reused when the part is rebuilt so a producer-specific prefix that
|
|
47
|
+
* {@link OOXML_NAMESPACES} does not know keeps the URI the document gave it.
|
|
48
|
+
*/
|
|
49
|
+
declare const readRootNamespaceBindings: (partXml: string) => ReadonlyMap<string, string>;
|
|
50
|
+
/** The part a rebuild replaces: where it lives, and what its root element bound. */
|
|
51
|
+
type SourcePart = {
|
|
52
|
+
path: string;
|
|
53
|
+
bindings: ReadonlyMap<string, string>;
|
|
54
|
+
};
|
|
55
|
+
type PartElementOptions = {
|
|
56
|
+
/** Package-relative path of the part, reported when a prefix is unbound. */
|
|
57
|
+
partPath: string;
|
|
58
|
+
/** Qualified name of the root element, for example `w:document`. */
|
|
59
|
+
rootName: string;
|
|
60
|
+
/** Root-element attributes other than the namespace declarations. */
|
|
61
|
+
rootAttributes?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Prefixes declared whether or not the body uses them, so the part keeps a
|
|
64
|
+
* stable shape across saves and unmodeled extensions have somewhere to land.
|
|
65
|
+
*/
|
|
66
|
+
baselinePrefixes: readonly OoxmlNamespacePrefix[];
|
|
67
|
+
/** Root bindings of the part being replaced, when the save has the source. */
|
|
68
|
+
sourceBindings: ReadonlyMap<string, string> | undefined;
|
|
69
|
+
/** Assembled part content, everything between the root tags. */
|
|
70
|
+
body: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Wrap an assembled part body in a root element that declares every prefix the
|
|
74
|
+
* part uses and lists the ignorable ones in `mc:Ignorable`.
|
|
75
|
+
*/
|
|
76
|
+
declare const serializePartElement: ({ partPath, rootName, rootAttributes, baselinePrefixes, sourceBindings, body }: PartElementOptions) => string;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { OOXML_NAMESPACES, OoxmlNamespace, OoxmlNamespacePrefix, PartElementOptions, SourcePart, UnboundNamespacePrefixError, readRootNamespaceBindings, serializePartElement };
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { toTransitionalNamespaceUri } from "../transitionalSpelling.js";
|
|
2
|
+
import { escapeXml } from "./xmlUtils.js";
|
|
3
|
+
import { TaggedError } from "better-result";
|
|
4
|
+
import { OOXML_NS } from "@stll/docx-utils";
|
|
5
|
+
//#region src/docx/serializer/partNamespaces.ts
|
|
6
|
+
/**
|
|
7
|
+
* Namespace declarations for parts folio rebuilds from the model.
|
|
8
|
+
*
|
|
9
|
+
* A rebuilt part carries content the parser preserved verbatim: raw property
|
|
10
|
+
* XML, text-box bodies, drawing extensions, unmodeled attributes. That content
|
|
11
|
+
* can use any prefix its producer bound on the source part's root, so a
|
|
12
|
+
* hand-maintained declaration list on the serializer silently drops bindings
|
|
13
|
+
* and emits an unbound prefix, which makes the part malformed and the package
|
|
14
|
+
* unopenable. The declarations are therefore derived: every prefix the
|
|
15
|
+
* assembled part actually uses is resolved through {@link OOXML_NAMESPACES},
|
|
16
|
+
* falling back to what the source part bound, and a prefix that resolves to
|
|
17
|
+
* nothing fails the save instead of being written unbound.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Every namespace a rebuilt part can declare, in the order producers write them.
|
|
21
|
+
*
|
|
22
|
+
* URIs shared with the parser are taken from its table rather than retyped, so
|
|
23
|
+
* the two cannot drift.
|
|
24
|
+
*/
|
|
25
|
+
const OOXML_NAMESPACES = {
|
|
26
|
+
wpc: {
|
|
27
|
+
uri: OOXML_NS.wpc,
|
|
28
|
+
ignorable: false
|
|
29
|
+
},
|
|
30
|
+
cx: {
|
|
31
|
+
uri: "http://schemas.microsoft.com/office/drawing/2014/chartex",
|
|
32
|
+
ignorable: false
|
|
33
|
+
},
|
|
34
|
+
cx1: {
|
|
35
|
+
uri: "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
|
|
36
|
+
ignorable: false
|
|
37
|
+
},
|
|
38
|
+
cx2: {
|
|
39
|
+
uri: "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
|
|
40
|
+
ignorable: false
|
|
41
|
+
},
|
|
42
|
+
cx3: {
|
|
43
|
+
uri: "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
|
|
44
|
+
ignorable: false
|
|
45
|
+
},
|
|
46
|
+
cx4: {
|
|
47
|
+
uri: "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
|
|
48
|
+
ignorable: false
|
|
49
|
+
},
|
|
50
|
+
cx5: {
|
|
51
|
+
uri: "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
|
|
52
|
+
ignorable: false
|
|
53
|
+
},
|
|
54
|
+
cx6: {
|
|
55
|
+
uri: "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
|
|
56
|
+
ignorable: false
|
|
57
|
+
},
|
|
58
|
+
cx7: {
|
|
59
|
+
uri: "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
|
|
60
|
+
ignorable: false
|
|
61
|
+
},
|
|
62
|
+
cx8: {
|
|
63
|
+
uri: "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
|
|
64
|
+
ignorable: false
|
|
65
|
+
},
|
|
66
|
+
mc: {
|
|
67
|
+
uri: OOXML_NS.mc,
|
|
68
|
+
ignorable: false
|
|
69
|
+
},
|
|
70
|
+
aink: {
|
|
71
|
+
uri: "http://schemas.microsoft.com/office/drawing/2016/ink",
|
|
72
|
+
ignorable: false
|
|
73
|
+
},
|
|
74
|
+
am3d: {
|
|
75
|
+
uri: "http://schemas.microsoft.com/office/drawing/2017/model3d",
|
|
76
|
+
ignorable: false
|
|
77
|
+
},
|
|
78
|
+
o: {
|
|
79
|
+
uri: OOXML_NS.o,
|
|
80
|
+
ignorable: false
|
|
81
|
+
},
|
|
82
|
+
oel: {
|
|
83
|
+
uri: "http://schemas.microsoft.com/office/2019/extlst",
|
|
84
|
+
ignorable: false
|
|
85
|
+
},
|
|
86
|
+
r: {
|
|
87
|
+
uri: OOXML_NS.r,
|
|
88
|
+
ignorable: false
|
|
89
|
+
},
|
|
90
|
+
m: {
|
|
91
|
+
uri: OOXML_NS.m,
|
|
92
|
+
ignorable: false
|
|
93
|
+
},
|
|
94
|
+
v: {
|
|
95
|
+
uri: OOXML_NS.v,
|
|
96
|
+
ignorable: false
|
|
97
|
+
},
|
|
98
|
+
wp14: {
|
|
99
|
+
uri: OOXML_NS.wp14,
|
|
100
|
+
ignorable: true
|
|
101
|
+
},
|
|
102
|
+
wp: {
|
|
103
|
+
uri: OOXML_NS.wp,
|
|
104
|
+
ignorable: false
|
|
105
|
+
},
|
|
106
|
+
w10: {
|
|
107
|
+
uri: "urn:schemas-microsoft-com:office:word",
|
|
108
|
+
ignorable: false
|
|
109
|
+
},
|
|
110
|
+
w: {
|
|
111
|
+
uri: OOXML_NS.w,
|
|
112
|
+
ignorable: false
|
|
113
|
+
},
|
|
114
|
+
w14: {
|
|
115
|
+
uri: OOXML_NS.w14,
|
|
116
|
+
ignorable: true
|
|
117
|
+
},
|
|
118
|
+
w15: {
|
|
119
|
+
uri: OOXML_NS.w15,
|
|
120
|
+
ignorable: true
|
|
121
|
+
},
|
|
122
|
+
w16cex: {
|
|
123
|
+
uri: "http://schemas.microsoft.com/office/word/2018/wordml/cex",
|
|
124
|
+
ignorable: true
|
|
125
|
+
},
|
|
126
|
+
w16cid: {
|
|
127
|
+
uri: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
|
|
128
|
+
ignorable: true
|
|
129
|
+
},
|
|
130
|
+
w16: {
|
|
131
|
+
uri: "http://schemas.microsoft.com/office/word/2018/wordml",
|
|
132
|
+
ignorable: true
|
|
133
|
+
},
|
|
134
|
+
w16du: {
|
|
135
|
+
uri: "http://schemas.microsoft.com/office/word/2023/wordml/word16du",
|
|
136
|
+
ignorable: true
|
|
137
|
+
},
|
|
138
|
+
w16sdtdh: {
|
|
139
|
+
uri: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
|
|
140
|
+
ignorable: true
|
|
141
|
+
},
|
|
142
|
+
w16se: {
|
|
143
|
+
uri: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
|
|
144
|
+
ignorable: true
|
|
145
|
+
},
|
|
146
|
+
wpg: {
|
|
147
|
+
uri: OOXML_NS.wpg,
|
|
148
|
+
ignorable: false
|
|
149
|
+
},
|
|
150
|
+
wpi: {
|
|
151
|
+
uri: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
|
|
152
|
+
ignorable: false
|
|
153
|
+
},
|
|
154
|
+
wne: {
|
|
155
|
+
uri: "http://schemas.microsoft.com/office/word/2006/wordml",
|
|
156
|
+
ignorable: false
|
|
157
|
+
},
|
|
158
|
+
wps: {
|
|
159
|
+
uri: OOXML_NS.wps,
|
|
160
|
+
ignorable: false
|
|
161
|
+
},
|
|
162
|
+
a: {
|
|
163
|
+
uri: OOXML_NS.a,
|
|
164
|
+
ignorable: false
|
|
165
|
+
},
|
|
166
|
+
pic: {
|
|
167
|
+
uri: OOXML_NS.pic,
|
|
168
|
+
ignorable: false
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
/** {@link OOXML_NAMESPACES} keyed for lookup by a prefix read out of markup. */
|
|
172
|
+
const NAMESPACE_TABLE = new Map(Object.entries(OOXML_NAMESPACES));
|
|
173
|
+
/** A rebuilt part uses a prefix that resolves to no namespace URI. */
|
|
174
|
+
var UnboundNamespacePrefixError = class extends TaggedError("UnboundNamespacePrefixError") {};
|
|
175
|
+
const XMLNS_ATTRIBUTE_PREFIX = "xmlns:";
|
|
176
|
+
/** Bound by the XML specification itself; never declared. */
|
|
177
|
+
const RESERVED_PREFIX = "xml";
|
|
178
|
+
const isWhitespace = (character) => character === " " || character === " " || character === "\n" || character === "\r";
|
|
179
|
+
const isNameBoundary = (character) => isWhitespace(character) || character === ">" || character === "/" || character === "=";
|
|
180
|
+
const namePrefix = (qualifiedName) => {
|
|
181
|
+
const colon = qualifiedName.indexOf(":");
|
|
182
|
+
if (colon <= 0) return;
|
|
183
|
+
const prefix = qualifiedName.slice(0, colon);
|
|
184
|
+
return prefix === RESERVED_PREFIX ? void 0 : prefix;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Collect prefixes from element and attribute names in one quote-aware pass.
|
|
188
|
+
*
|
|
189
|
+
* Attribute values are skipped rather than matched, so a URI or a text value
|
|
190
|
+
* containing a colon cannot be mistaken for a namespace prefix.
|
|
191
|
+
*/
|
|
192
|
+
const scanXmlNames = (xml, rootTagOnly) => {
|
|
193
|
+
const used = /* @__PURE__ */ new Set();
|
|
194
|
+
const declared = /* @__PURE__ */ new Map();
|
|
195
|
+
const length = xml.length;
|
|
196
|
+
let index = 0;
|
|
197
|
+
while (index < length) {
|
|
198
|
+
const tagStart = xml.indexOf("<", index);
|
|
199
|
+
if (tagStart === -1) break;
|
|
200
|
+
index = tagStart + 1;
|
|
201
|
+
if (xml.startsWith("?", index)) {
|
|
202
|
+
const end = xml.indexOf("?>", index);
|
|
203
|
+
if (end === -1) break;
|
|
204
|
+
index = end + 2;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (xml.startsWith("!--", index)) {
|
|
208
|
+
const end = xml.indexOf("-->", index);
|
|
209
|
+
if (end === -1) break;
|
|
210
|
+
index = end + 3;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (xml.startsWith("![CDATA[", index)) {
|
|
214
|
+
const end = xml.indexOf("]]>", index);
|
|
215
|
+
if (end === -1) break;
|
|
216
|
+
index = end + 3;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (xml.startsWith("!", index)) {
|
|
220
|
+
const end = xml.indexOf(">", index);
|
|
221
|
+
if (end === -1) break;
|
|
222
|
+
index = end + 1;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (xml.startsWith("/", index)) index += 1;
|
|
226
|
+
const nameStart = index;
|
|
227
|
+
while (index < length && !isNameBoundary(xml[index] ?? ">")) index += 1;
|
|
228
|
+
const elementPrefix = namePrefix(xml.slice(nameStart, index));
|
|
229
|
+
if (elementPrefix) used.add(elementPrefix);
|
|
230
|
+
while (index < length) {
|
|
231
|
+
const character = xml[index] ?? ">";
|
|
232
|
+
if (isWhitespace(character) || character === "/") {
|
|
233
|
+
index += 1;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (character === ">") {
|
|
237
|
+
index += 1;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
const attributeStart = index;
|
|
241
|
+
while (index < length && !isNameBoundary(xml[index] ?? ">")) index += 1;
|
|
242
|
+
const attributeName = xml.slice(attributeStart, index);
|
|
243
|
+
while (index < length && isWhitespace(xml[index] ?? ">")) index += 1;
|
|
244
|
+
let value = "";
|
|
245
|
+
if (xml[index] === "=") {
|
|
246
|
+
index += 1;
|
|
247
|
+
while (index < length && isWhitespace(xml[index] ?? ">")) index += 1;
|
|
248
|
+
const quote = xml[index];
|
|
249
|
+
if (quote === "\"" || quote === "'") {
|
|
250
|
+
const valueEnd = xml.indexOf(quote, index + 1);
|
|
251
|
+
if (valueEnd === -1) {
|
|
252
|
+
index = length;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
value = xml.slice(index + 1, valueEnd);
|
|
256
|
+
index = valueEnd + 1;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (attributeName.startsWith(XMLNS_ATTRIBUTE_PREFIX)) {
|
|
260
|
+
declared.set(attributeName.slice(6), value);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const attributePrefix = namePrefix(attributeName);
|
|
264
|
+
if (attributePrefix) used.add(attributePrefix);
|
|
265
|
+
}
|
|
266
|
+
if (rootTagOnly) break;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
used,
|
|
270
|
+
declared
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* The `xmlns:*` bindings a source part declared on its root element.
|
|
275
|
+
*
|
|
276
|
+
* Reused when the part is rebuilt so a producer-specific prefix that
|
|
277
|
+
* {@link OOXML_NAMESPACES} does not know keeps the URI the document gave it.
|
|
278
|
+
*/
|
|
279
|
+
const readRootNamespaceBindings = (partXml) => scanXmlNames(partXml, true).declared;
|
|
280
|
+
/**
|
|
281
|
+
* Wrap an assembled part body in a root element that declares every prefix the
|
|
282
|
+
* part uses and lists the ignorable ones in `mc:Ignorable`.
|
|
283
|
+
*/
|
|
284
|
+
const serializePartElement = ({ partPath, rootName, rootAttributes, baselinePrefixes, sourceBindings, body }) => {
|
|
285
|
+
const openingTag = `<${rootName}${rootAttributes ? ` ${rootAttributes}` : ""}`;
|
|
286
|
+
const fromRoot = scanXmlNames(`${openingTag}/>`, true);
|
|
287
|
+
const { used, declared } = scanXmlNames(body, false);
|
|
288
|
+
const required = /* @__PURE__ */ new Set([...fromRoot.used, ...used]);
|
|
289
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const prefix of baselinePrefixes) bindings.set(prefix, OOXML_NAMESPACES[prefix].uri);
|
|
291
|
+
const unbound = [];
|
|
292
|
+
for (const prefix of required) {
|
|
293
|
+
if (bindings.has(prefix)) continue;
|
|
294
|
+
const sourceBinding = sourceBindings?.get(prefix);
|
|
295
|
+
const uri = NAMESPACE_TABLE.get(prefix)?.uri ?? (sourceBinding === void 0 ? void 0 : toTransitionalNamespaceUri(sourceBinding));
|
|
296
|
+
if (uri !== void 0) {
|
|
297
|
+
bindings.set(prefix, uri);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (!declared.has(prefix)) unbound.push(prefix);
|
|
301
|
+
}
|
|
302
|
+
if (unbound.length > 0) {
|
|
303
|
+
const prefixes = unbound.sort();
|
|
304
|
+
throw new UnboundNamespacePrefixError({
|
|
305
|
+
message: `${partPath} uses namespace prefixes bound to no URI: ${prefixes.join(", ")}`,
|
|
306
|
+
partPath,
|
|
307
|
+
prefixes
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if ([...bindings.keys()].some((prefix) => NAMESPACE_TABLE.get(prefix)?.ignorable === true)) bindings.set("mc", OOXML_NAMESPACES.mc.uri);
|
|
311
|
+
const baseline = new Set(baselinePrefixes);
|
|
312
|
+
const extra = new Set([...bindings.keys()].filter((prefix) => !baseline.has(prefix)));
|
|
313
|
+
const orderedPrefixes = [
|
|
314
|
+
...baselinePrefixes,
|
|
315
|
+
...[...NAMESPACE_TABLE.keys()].filter((prefix) => extra.has(prefix)),
|
|
316
|
+
...[...extra].filter((prefix) => !NAMESPACE_TABLE.has(prefix)).sort()
|
|
317
|
+
];
|
|
318
|
+
const attributes = orderedPrefixes.map((prefix) => `xmlns:${prefix}="${escapeXml(bindings.get(prefix))}"`);
|
|
319
|
+
const ignorable = orderedPrefixes.filter((prefix) => NAMESPACE_TABLE.get(prefix)?.ignorable === true);
|
|
320
|
+
if (ignorable.length > 0) attributes.push(`mc:Ignorable="${ignorable.join(" ")}"`);
|
|
321
|
+
return `${openingTag} ${attributes.join(" ")}>${body}</${rootName}>`;
|
|
322
|
+
};
|
|
323
|
+
//#endregion
|
|
324
|
+
export { OOXML_NAMESPACES, UnboundNamespacePrefixError, readRootNamespaceBindings, serializePartElement };
|