@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.
Files changed (91) hide show
  1. package/dist/ai-edits/apply.d.ts +15 -0
  2. package/dist/ai-edits/apply.js +134 -25
  3. package/dist/ai-edits/headless.d.ts +47 -3
  4. package/dist/ai-edits/headless.js +53 -3
  5. package/dist/ai-edits/index.d.ts +1 -1
  6. package/dist/ai-edits/snapshot.d.ts +19 -1
  7. package/dist/ai-edits/snapshot.js +25 -3
  8. package/dist/ai-edits/table-geometry.d.ts +56 -0
  9. package/dist/ai-edits/table-geometry.js +217 -0
  10. package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
  11. package/dist/ai-edits/table-row-column-mutations.js +12 -4
  12. package/dist/ai-edits/table-template.d.ts +60 -0
  13. package/dist/ai-edits/table-template.js +176 -0
  14. package/dist/ai-edits/types.d.ts +7 -0
  15. package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
  16. package/dist/compare/__fixtures__/body-sequence.js +94 -6
  17. package/dist/compare/compare.d.ts +14 -7
  18. package/dist/compare/compare.js +136 -38
  19. package/dist/compare/plan.d.ts +31 -1
  20. package/dist/compare/plan.js +233 -66
  21. package/dist/compare/types.d.ts +9 -7
  22. package/dist/compare/types.js +7 -5
  23. package/dist/compare/verification.d.ts +31 -12
  24. package/dist/compare/verification.js +88 -17
  25. package/dist/compat/eigenpal.d.ts +4 -4
  26. package/dist/compat/eigenpal.js +2 -2
  27. package/dist/display-list/primitives.d.ts +1 -1
  28. package/dist/document-operations.d.ts +10 -1
  29. package/dist/document-operations.js +3 -2
  30. package/dist/docx/appVersionNormalization.d.ts +49 -0
  31. package/dist/docx/appVersionNormalization.js +74 -0
  32. package/dist/docx/blockContentParser.js +3 -2
  33. package/dist/docx/drawingUtils.js +4 -3
  34. package/dist/docx/paragraphParser.js +3 -2
  35. package/dist/docx/rezip.d.ts +10 -4
  36. package/dist/docx/rezip.js +54 -18
  37. package/dist/docx/runParser.js +9 -8
  38. package/dist/docx/sdtProperties.js +4 -3
  39. package/dist/docx/selectiveSave.js +7 -6
  40. package/dist/docx/serializer/commentSerializer.d.ts +1 -1
  41. package/dist/docx/serializer/commentSerializer.js +43 -19
  42. package/dist/docx/serializer/documentSerializer.d.ts +3 -1
  43. package/dist/docx/serializer/documentSerializer.js +42 -78
  44. package/dist/docx/serializer/fontTableSerializer.js +8 -5
  45. package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
  46. package/dist/docx/serializer/headerFooterSerializer.js +34 -29
  47. package/dist/docx/serializer/noteSerializer.js +34 -30
  48. package/dist/docx/serializer/numberingSerializer.js +8 -4
  49. package/dist/docx/serializer/paragraphSerializer.js +3 -3
  50. package/dist/docx/serializer/partNamespaces.d.ts +78 -0
  51. package/dist/docx/serializer/partNamespaces.js +324 -0
  52. package/dist/docx/serializer/runSerializer.js +1 -1
  53. package/dist/docx/serializer/settingsSerializer.js +8 -2
  54. package/dist/docx/serializer/stylesSerializer.js +8 -5
  55. package/dist/docx/serializer/tableSerializer.js +83 -18
  56. package/dist/docx/serializer/themeSerializer.js +10 -2
  57. package/dist/docx/server/build.d.ts +1 -1
  58. package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
  59. package/dist/docx/strictValueEncodings.gen.js +275 -0
  60. package/dist/docx/tableParser.d.ts +0 -6
  61. package/dist/docx/tableParser.js +52 -7
  62. package/dist/docx/transitionalSpelling.d.ts +23 -0
  63. package/dist/docx/transitionalSpelling.js +36 -0
  64. package/dist/docx/universalMeasure.d.ts +20 -0
  65. package/dist/docx/universalMeasure.js +32 -0
  66. package/dist/docx/verbatimCapture.d.ts +20 -0
  67. package/dist/docx/verbatimCapture.js +131 -0
  68. package/dist/docx/vmlImageParser.js +4 -3
  69. package/dist/docx/watermarkParser.js +4 -3
  70. package/dist/docx/xmlParser.d.ts +23 -2
  71. package/dist/docx/xmlParser.js +43 -11
  72. package/dist/index.d.ts +4 -4
  73. package/dist/index.js +2 -2
  74. package/dist/model.d.ts +3 -3
  75. package/dist/model.js +2 -2
  76. package/dist/prosemirror/commands/comments.js +2 -1
  77. package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
  78. package/dist/prosemirror/containerFinalParagraph.js +92 -0
  79. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
  80. package/dist/prosemirror/conversion/fromProseDoc.js +15 -4
  81. package/dist/prosemirror/conversion/toProseDoc.js +12 -3
  82. package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
  83. package/dist/prosemirror/schema/marks.d.ts +1 -1
  84. package/dist/prosemirror/schema/nodes.d.ts +22 -0
  85. package/dist/server.d.ts +1 -1
  86. package/dist/types/block-id.d.ts +12 -1
  87. package/dist/types/block-id.js +17 -1
  88. package/dist/utils/canonicalJson.d.ts +14 -0
  89. package/dist/utils/canonicalJson.js +19 -0
  90. package/dist/utils/formatToStyle.d.ts +1 -1
  91. 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 NAMESPACES = {
8
- wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
9
- mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
10
- o: "urn:schemas-microsoft-com:office:office",
11
- r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
12
- m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
13
- v: "urn:schemas-microsoft-com:vml",
14
- a: "http://schemas.openxmlformats.org/drawingml/2006/main",
15
- pic: "http://schemas.openxmlformats.org/drawingml/2006/picture",
16
- wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
17
- wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
18
- w10: "urn:schemas-microsoft-com:office:word",
19
- w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
20
- w14: "http://schemas.microsoft.com/office/word/2010/wordml",
21
- w15: "http://schemas.microsoft.com/office/word/2012/wordml",
22
- w16: "http://schemas.microsoft.com/office/word/2018/wordml",
23
- w16cex: "http://schemas.microsoft.com/office/word/2018/wordml/cex",
24
- w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
25
- w16sdtdh: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
26
- w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
27
- wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
28
- wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<${rootTag} ${nsDecl}>${contentXml}</${rootTag}>`;
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 NAMESPACES = {
6
- wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
7
- mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
8
- o: "urn:schemas-microsoft-com:office:office",
9
- r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
10
- m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
11
- v: "urn:schemas-microsoft-com:vml",
12
- a: "http://schemas.openxmlformats.org/drawingml/2006/main",
13
- pic: "http://schemas.openxmlformats.org/drawingml/2006/picture",
14
- wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
15
- wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
16
- w10: "urn:schemas-microsoft-com:office:word",
17
- w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
18
- w14: "http://schemas.microsoft.com/office/word/2010/wordml",
19
- w15: "http://schemas.microsoft.com/office/word/2012/wordml",
20
- w16: "http://schemas.microsoft.com/office/word/2018/wordml",
21
- w16cex: "http://schemas.microsoft.com/office/word/2018/wordml/cex",
22
- w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
23
- w16sdtdh: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
24
- w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
25
- wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
26
- wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
27
- };
28
- function buildNamespaceDeclarations() {
29
- return Object.entries(NAMESPACES).map(([prefix, uri]) => `xmlns:${prefix}="${uri}"`).join(" ");
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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w:${elementName}s ${nsDecl} mc:Ignorable="w14 w15 wp14">` + serializeRequiredNoteSeparators(elementName) + serializedNotes + `</w:${elementName}s>`;
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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w:footnotes ${buildNamespaceDeclarations()} mc:Ignorable="w14 w15 wp14">${footnotes.map((fn) => serializeNote("footnote", fn)).join("")}</w:footnotes>`;
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 `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w:endnotes ${buildNamespaceDeclarations()} mc:Ignorable="w14 w15 wp14">${endnotes.map((en) => serializeNote("endnote", en)).join("")}</w:endnotes>`;
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
- const abstractNums = numbering.abstractNums.map(serializeAbstractNum).join("");
91
- const nums = numbering.nums.map(serializeNum).join("");
92
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w:numbering xmlns:w="${W_NS}">${abstractNums}${nums}</w:numbering>`;
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("bidi", formatting.bidi);
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 };
@@ -525,7 +525,7 @@ function serializeShapeContent(content) {
525
525
  "<wps:wsp>",
526
526
  `<wps:cNvSpPr${isTextBox ? " txBox=\"1\"" : ""}/>`,
527
527
  spPr,
528
- textBody,
528
+ textBody === "" ? "<wps:bodyPr/>" : textBody,
529
529
  "</wps:wsp>"
530
530
  ].join(""),
531
531
  "</a:graphicData>",