@stll/folio-core 0.33.0 → 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 (98) hide show
  1. package/dist/ai-edits/apply.d.ts +15 -0
  2. package/dist/ai-edits/apply.js +135 -24
  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 +86 -4
  16. package/dist/compare/__fixtures__/body-sequence.js +169 -12
  17. package/dist/compare/compare.d.ts +15 -8
  18. package/dist/compare/compare.js +138 -34
  19. package/dist/compare/plan.d.ts +31 -1
  20. package/dist/compare/plan.js +277 -28
  21. package/dist/compare/reproducible-package.d.ts +7 -3
  22. package/dist/compare/reproducible-package.js +20 -3
  23. package/dist/compare/types.d.ts +20 -3
  24. package/dist/compare/types.js +13 -1
  25. package/dist/compare/verification.d.ts +49 -2
  26. package/dist/compare/verification.js +115 -1
  27. package/dist/compat/eigenpal.d.ts +5 -5
  28. package/dist/compat/eigenpal.js +3 -3
  29. package/dist/document-operations.d.ts +10 -1
  30. package/dist/document-operations.js +3 -2
  31. package/dist/docx/appVersionNormalization.d.ts +49 -0
  32. package/dist/docx/appVersionNormalization.js +74 -0
  33. package/dist/docx/blockContentParser.js +3 -2
  34. package/dist/docx/drawingUtils.js +4 -3
  35. package/dist/docx/hyperlinkParser.d.ts +9 -1
  36. package/dist/docx/hyperlinkParser.js +19 -13
  37. package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
  38. package/dist/docx/paraIdRangeNormalization.js +64 -0
  39. package/dist/docx/paragraphParser.js +82 -6
  40. package/dist/docx/revisionIdNormalization.d.ts +15 -1
  41. package/dist/docx/revisionIdNormalization.js +22 -4
  42. package/dist/docx/rezip.d.ts +13 -1
  43. package/dist/docx/rezip.js +80 -21
  44. package/dist/docx/runParser.js +9 -8
  45. package/dist/docx/sdtProperties.js +4 -3
  46. package/dist/docx/selectiveSave.js +7 -6
  47. package/dist/docx/serializer/commentSerializer.d.ts +1 -1
  48. package/dist/docx/serializer/commentSerializer.js +43 -19
  49. package/dist/docx/serializer/documentSerializer.d.ts +3 -1
  50. package/dist/docx/serializer/documentSerializer.js +42 -78
  51. package/dist/docx/serializer/fontTableSerializer.js +8 -5
  52. package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
  53. package/dist/docx/serializer/headerFooterSerializer.js +34 -29
  54. package/dist/docx/serializer/noteSerializer.js +34 -30
  55. package/dist/docx/serializer/numberingSerializer.js +8 -4
  56. package/dist/docx/serializer/paragraphSerializer.js +45 -21
  57. package/dist/docx/serializer/partNamespaces.d.ts +78 -0
  58. package/dist/docx/serializer/partNamespaces.js +324 -0
  59. package/dist/docx/serializer/runSerializer.js +1 -1
  60. package/dist/docx/serializer/settingsSerializer.js +8 -2
  61. package/dist/docx/serializer/stylesSerializer.js +8 -5
  62. package/dist/docx/serializer/tableSerializer.js +107 -27
  63. package/dist/docx/serializer/themeSerializer.js +10 -2
  64. package/dist/docx/server/build.d.ts +1 -1
  65. package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
  66. package/dist/docx/strictValueEncodings.gen.js +275 -0
  67. package/dist/docx/tableParser.d.ts +0 -6
  68. package/dist/docx/tableParser.js +52 -7
  69. package/dist/docx/transitionalSpelling.d.ts +23 -0
  70. package/dist/docx/transitionalSpelling.js +36 -0
  71. package/dist/docx/universalMeasure.d.ts +20 -0
  72. package/dist/docx/universalMeasure.js +32 -0
  73. package/dist/docx/verbatimCapture.d.ts +20 -0
  74. package/dist/docx/verbatimCapture.js +131 -0
  75. package/dist/docx/vmlImageParser.js +4 -3
  76. package/dist/docx/watermarkParser.js +4 -3
  77. package/dist/docx/xmlParser.d.ts +23 -2
  78. package/dist/docx/xmlParser.js +43 -11
  79. package/dist/index.d.ts +5 -5
  80. package/dist/index.js +3 -3
  81. package/dist/model.d.ts +3 -3
  82. package/dist/model.js +2 -2
  83. package/dist/prosemirror/commands/comments.js +8 -2
  84. package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
  85. package/dist/prosemirror/containerFinalParagraph.js +92 -0
  86. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
  87. package/dist/prosemirror/conversion/fromProseDoc.js +15 -4
  88. package/dist/prosemirror/conversion/toProseDoc.js +12 -3
  89. package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
  90. package/dist/prosemirror/schema/marks.d.ts +1 -1
  91. package/dist/prosemirror/schema/nodes.d.ts +22 -0
  92. package/dist/server.d.ts +1 -1
  93. package/dist/types/block-id.d.ts +12 -1
  94. package/dist/types/block-id.js +17 -1
  95. package/dist/utils/canonicalJson.d.ts +14 -0
  96. package/dist/utils/canonicalJson.js +19 -0
  97. package/dist/utils/formatToStyle.d.ts +1 -1
  98. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { deterministicHexId } from "../../utils/hexId.js";
2
2
  import { serializeParagraph } from "./paragraphSerializer.js";
3
+ import { serializePartElement } from "./partNamespaces.js";
3
4
  import { serializeTextFormatting } from "./runSerializer.js";
4
5
  import { escapeXml } from "./xmlUtils.js";
5
6
  //#region src/docx/serializer/commentSerializer.ts
@@ -36,12 +37,29 @@ function serializeComment(comment) {
36
37
  xml += "</w:comment>";
37
38
  return xml;
38
39
  }
39
- const COMMENT_EXTENSION_NAMESPACES = {
40
- w14: "http://schemas.microsoft.com/office/word/2010/wordml",
41
- wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
42
- };
43
- const COMMENTS_HEADER = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
44
- <w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ${Object.entries(COMMENT_EXTENSION_NAMESPACES).map(([prefix, namespace]) => `xmlns:${prefix}="${namespace}"`).join(" ")} xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="${Object.keys(COMMENT_EXTENSION_NAMESPACES).join(" ")}">`;
40
+ const COMMENTS_BASELINE_PREFIXES = [
41
+ "wpc",
42
+ "mc",
43
+ "o",
44
+ "r",
45
+ "m",
46
+ "v",
47
+ "wp",
48
+ "w10",
49
+ "w",
50
+ "w14",
51
+ "wp14",
52
+ "wpg",
53
+ "wpi",
54
+ "wne",
55
+ "wps"
56
+ ];
57
+ const COMMENTS_EXTENDED_BASELINE_PREFIXES = [
58
+ "mc",
59
+ "w",
60
+ "w15"
61
+ ];
62
+ const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
45
63
  /**
46
64
  * Serialize comments array to comments.xml content. Returns a valid empty
47
65
  * `<w:comments/>` document for an empty array so callers can overwrite an
@@ -49,18 +67,21 @@ const COMMENTS_HEADER = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
49
67
  * comment — leaving the previous file in place would otherwise re-emit
50
68
  * the orphaned comment threads on every save.
51
69
  */
52
- function serializeComments(comments) {
70
+ function serializeComments(comments, sourceBindings) {
53
71
  const topLevel = [];
54
72
  const replies = [];
55
73
  for (const c of comments) {
56
74
  const { parentId } = c;
57
75
  (parentId === null || parentId === void 0 ? topLevel : replies).push(c);
58
76
  }
59
- let xml = COMMENTS_HEADER;
60
- for (const comment of topLevel) xml += serializeComment(comment);
61
- for (const reply of replies) xml += serializeComment(reply);
62
- xml += "</w:comments>";
63
- return xml;
77
+ const body = topLevel.map((comment) => serializeComment(comment)).join("") + replies.map((reply) => serializeComment(reply)).join("");
78
+ return XML_DECLARATION + serializePartElement({
79
+ partPath: "word/comments.xml",
80
+ rootName: "w:comments",
81
+ baselinePrefixes: COMMENTS_BASELINE_PREFIXES,
82
+ sourceBindings,
83
+ body
84
+ });
64
85
  }
65
86
  /** The `w14:paraId` Word threads a comment by: its LAST paragraph's paraId. */
66
87
  function commentThreadParaId(comment) {
@@ -145,7 +166,6 @@ function buildCommentExtendedEntries(comments) {
145
166
  }
146
167
  return entries.length > 0 ? entries : null;
147
168
  }
148
- const COMMENTS_EXTENDED_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w15:commentsEx xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:w15=\"http://schemas.microsoft.com/office/word/2012/wordml\" mc:Ignorable=\"w15\">";
149
169
  /**
150
170
  * Serialize `commentsExtended.xml` (`w15:commentsEx`) for reply threading and
151
171
  * resolved state, or `null` when no comment needs an entry (see
@@ -155,13 +175,17 @@ const COMMENTS_EXTENDED_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" stand
155
175
  function serializeCommentsExtended(comments) {
156
176
  const entries = buildCommentExtendedEntries(comments);
157
177
  if (!entries) return null;
158
- let xml = COMMENTS_EXTENDED_HEADER;
159
- for (const entry of entries) {
178
+ const body = entries.map((entry) => {
160
179
  const parentAttr = entry.paraIdParent !== void 0 ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
161
- xml += `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parentAttr} w15:done="${entry.done ? "1" : "0"}"/>`;
162
- }
163
- xml += "</w15:commentsEx>";
164
- return xml;
180
+ return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parentAttr} w15:done="${entry.done ? "1" : "0"}"/>`;
181
+ }).join("");
182
+ return XML_DECLARATION + serializePartElement({
183
+ partPath: "word/commentsExtended.xml",
184
+ rootName: "w15:commentsEx",
185
+ baselinePrefixes: COMMENTS_EXTENDED_BASELINE_PREFIXES,
186
+ sourceBindings: void 0,
187
+ body
188
+ });
165
189
  }
166
190
  //#endregion
167
191
  export { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended };
@@ -11,9 +11,11 @@ declare function serializeDocumentBody(body: document_d_exports.DocumentBody): s
11
11
  * Serialize a complete Document to valid document.xml
12
12
  *
13
13
  * @param doc - The document to serialize
14
+ * @param sourceBindings - Root `xmlns:*` of the part being replaced, so a
15
+ * prefix only the source document bound keeps its URI
14
16
  * @returns Complete XML string for document.xml
15
17
  */
16
- declare function serializeDocument(doc: document_d_exports.Document): string;
18
+ declare function serializeDocument(doc: document_d_exports.Document, sourceBindings?: ReadonlyMap<string, string>): string;
17
19
  /**
18
20
  * Serialize just the document body (useful for partial updates)
19
21
  *
@@ -1,78 +1,41 @@
1
1
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
2
2
  import { serializeParagraph } from "./paragraphSerializer.js";
3
+ import { serializePartElement } from "./partNamespaces.js";
3
4
  import { resetAutoIdCounter } from "./runSerializer.js";
4
5
  import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
5
6
  import { serializeTable } from "./tableSerializer.js";
6
7
  //#region src/docx/serializer/documentSerializer.ts
7
8
  /**
8
- * Standard OOXML namespaces for document.xml
9
- */
10
- const NAMESPACES = {
11
- a: "http://schemas.openxmlformats.org/drawingml/2006/main",
12
- wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
13
- cx: "http://schemas.microsoft.com/office/drawing/2014/chartex",
14
- cx1: "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
15
- cx2: "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
16
- cx3: "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
17
- cx4: "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
18
- cx5: "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
19
- cx6: "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
20
- cx7: "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
21
- cx8: "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
22
- mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
23
- aink: "http://schemas.microsoft.com/office/drawing/2016/ink",
24
- am3d: "http://schemas.microsoft.com/office/drawing/2017/model3d",
25
- o: "urn:schemas-microsoft-com:office:office",
26
- oel: "http://schemas.microsoft.com/office/2019/extlst",
27
- pic: "http://schemas.openxmlformats.org/drawingml/2006/picture",
28
- r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
29
- m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
30
- v: "urn:schemas-microsoft-com:vml",
31
- wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
32
- wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
33
- w10: "urn:schemas-microsoft-com:office:word",
34
- w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
35
- w14: "http://schemas.microsoft.com/office/word/2010/wordml",
36
- w15: "http://schemas.microsoft.com/office/word/2012/wordml",
37
- w16cex: "http://schemas.microsoft.com/office/word/2018/wordml/cex",
38
- w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
39
- w16: "http://schemas.microsoft.com/office/word/2018/wordml",
40
- w16sdtdh: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
41
- w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
42
- wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
43
- wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
44
- wne: "http://schemas.microsoft.com/office/word/2006/wordml",
45
- wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
46
- };
47
- /**
48
- * Build namespace declaration string for document element
49
- */
50
- function buildNamespaceDeclarations() {
51
- const declared = {
52
- a: NAMESPACES.a,
53
- wpc: NAMESPACES.wpc,
54
- mc: NAMESPACES.mc,
55
- o: NAMESPACES.o,
56
- pic: NAMESPACES.pic,
57
- r: NAMESPACES.r,
58
- m: NAMESPACES.m,
59
- v: NAMESPACES.v,
60
- wp14: NAMESPACES.wp14,
61
- wp: NAMESPACES.wp,
62
- w10: NAMESPACES.w10,
63
- w: NAMESPACES.w,
64
- w14: NAMESPACES.w14,
65
- w15: NAMESPACES.w15,
66
- w16: NAMESPACES.w16,
67
- w16cex: NAMESPACES.w16cex,
68
- w16cid: NAMESPACES.w16cid,
69
- w16sdtdh: NAMESPACES.w16sdtdh,
70
- w16se: NAMESPACES.w16se,
71
- wpg: NAMESPACES.wpg,
72
- wps: NAMESPACES.wps
73
- };
74
- return Object.entries(declared).map(([prefix, uri]) => `xmlns:${prefix}="${uri}"`).join(" ");
75
- }
9
+ * Prefixes document.xml declares whether or not the body uses them.
10
+ *
11
+ * A canonical `<w:sdtPr>` legitimately carries `<w16sdtdh:dataHash>` /
12
+ * `<w16cex:*>` / `<w16cid:*>` children the parser stores opaquely, and the
13
+ * drawing prefixes host raw-replayed shapes, so the part keeps the shape Word
14
+ * writes even when this particular save emits none of them.
15
+ */
16
+ const DOCUMENT_BASELINE_PREFIXES = [
17
+ "a",
18
+ "wpc",
19
+ "mc",
20
+ "o",
21
+ "pic",
22
+ "r",
23
+ "m",
24
+ "v",
25
+ "wp14",
26
+ "wp",
27
+ "w10",
28
+ "w",
29
+ "w14",
30
+ "w15",
31
+ "w16",
32
+ "w16cex",
33
+ "w16cid",
34
+ "w16sdtdh",
35
+ "w16se",
36
+ "wpg",
37
+ "wps"
38
+ ];
76
39
  /**
77
40
  * Serialize a single block content item (paragraph, table, or block-level SDT).
78
41
  */
@@ -103,19 +66,20 @@ function serializeDocumentBody(body) {
103
66
  * Serialize a complete Document to valid document.xml
104
67
  *
105
68
  * @param doc - The document to serialize
69
+ * @param sourceBindings - Root `xmlns:*` of the part being replaced, so a
70
+ * prefix only the source document bound keeps its URI
106
71
  * @returns Complete XML string for document.xml
107
72
  */
108
- function serializeDocument(doc) {
73
+ function serializeDocument(doc, sourceBindings) {
109
74
  resetAutoIdCounter();
110
- const parts = [];
111
- parts.push("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
112
- const nsDecl = buildNamespaceDeclarations();
113
- parts.push(`<w:document ${nsDecl} mc:Ignorable="w14 w15 wp14">`);
114
- parts.push("<w:body>");
115
- parts.push(serializeDocumentBody(doc.package.document));
116
- parts.push("</w:body>");
117
- parts.push("</w:document>");
118
- return parts.join("");
75
+ const body = `<w:body>${serializeDocumentBody(doc.package.document)}</w:body>`;
76
+ return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + serializePartElement({
77
+ partPath: "word/document.xml",
78
+ rootName: "w:document",
79
+ baselinePrefixes: DOCUMENT_BASELINE_PREFIXES,
80
+ sourceBindings,
81
+ body
82
+ });
119
83
  }
120
84
  /**
121
85
  * Serialize just the document body (useful for partial updates)
@@ -1,10 +1,13 @@
1
+ import { serializePartElement } from "./partNamespaces.js";
1
2
  import { escapeXml } from "./xmlUtils.js";
2
3
  //#region src/docx/serializer/fontTableSerializer.ts
3
- const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
4
- const serializeFontTableXml = (fontTable) => {
5
- const fonts = fontTable.fonts.map(serializeFont).join("");
6
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w:fonts xmlns:w="${W_NS}">${fonts}</w:fonts>`;
7
- };
4
+ const serializeFontTableXml = (fontTable) => "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
5
+ partPath: "word/fontTable.xml",
6
+ rootName: "w:fonts",
7
+ baselinePrefixes: ["w"],
8
+ sourceBindings: void 0,
9
+ body: fontTable.fonts.map(serializeFont).join("")
10
+ });
8
11
  const serializeFont = (font) => {
9
12
  const parts = [];
10
13
  if (font.altName) parts.push(`<w:altName w:val="${escapeXml(font.altName)}"/>`);
@@ -1,11 +1,14 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
+ import { SourcePart } from "./partNamespaces.js";
2
3
  //#region src/docx/serializer/headerFooterSerializer.d.ts
3
4
  /**
4
5
  * Serialize a HeaderFooter object to valid OOXML XML
5
6
  *
6
7
  * @param hf - HeaderFooter object to serialize
8
+ * @param source - The part being replaced, so a prefix only the source
9
+ * document bound keeps its URI
7
10
  * @returns Complete XML string for header*.xml or footer*.xml
8
11
  */
9
- declare function serializeHeaderFooter(hf: document_d_exports.HeaderFooter): string;
12
+ declare function serializeHeaderFooter(hf: document_d_exports.HeaderFooter, source?: SourcePart): string;
10
13
  //#endregion
11
14
  export { serializeHeaderFooter };
@@ -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) {
@@ -210,10 +210,8 @@ function serializeParagraphPropertyChange(change) {
210
210
  const normalizedPreviousPPr = previousPPrInner.length > 0 ? `<w:pPr>${previousPPrInner}</w:pPr>` : "<w:pPr/>";
211
211
  return `<w:pPrChange ${attrs.join(" ")}>${normalizedPreviousPPr}</w:pPrChange>`;
212
212
  }
213
- /**
214
- * Serialize a hyperlink (w:hyperlink)
215
- */
216
- function serializeHyperlink(hyperlink) {
213
+ /** The attribute list of a `w:hyperlink`, without its children. */
214
+ function hyperlinkAttributes(hyperlink) {
217
215
  const attrs = [];
218
216
  if (hyperlink.rId) attrs.push(`r:id="${escapeXml(hyperlink.rId)}"`);
219
217
  if (hyperlink.anchor) attrs.push(`w:anchor="${escapeXml(hyperlink.anchor)}"`);
@@ -222,12 +220,19 @@ function serializeHyperlink(hyperlink) {
222
220
  if (hyperlink.history === true) attrs.push("w:history=\"1\"");
223
221
  else if (hyperlink.history === false) attrs.push("w:history=\"0\"");
224
222
  if (hyperlink.docLocation) attrs.push(`w:docLocation="${escapeXml(hyperlink.docLocation)}"`);
225
- const childrenXml = hyperlink.children.map((child) => {
226
- if (child.type === "run") return serializeRun(child);
227
- if (child.type === "bookmarkStart") return serializeBookmarkStart(child);
228
- return serializeBookmarkEnd(child);
229
- }).join("");
230
- return `<w:hyperlink${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${childrenXml}</w:hyperlink>`;
223
+ return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
224
+ }
225
+ /** One `w:hyperlink` child, with the caller deciding how a run is written. */
226
+ function serializeHyperlinkChild(child, serializeChildRun) {
227
+ if (child.type === "run") return serializeChildRun(child);
228
+ return child.type === "bookmarkStart" ? serializeBookmarkStart(child) : serializeBookmarkEnd(child);
229
+ }
230
+ /**
231
+ * Serialize a hyperlink (w:hyperlink)
232
+ */
233
+ function serializeHyperlink(hyperlink) {
234
+ const childrenXml = hyperlink.children.map((child) => serializeHyperlinkChild(child, serializeRun)).join("");
235
+ return `<w:hyperlink${hyperlinkAttributes(hyperlink)}>${childrenXml}</w:hyperlink>`;
231
236
  }
232
237
  /**
233
238
  * Serialize bookmark start (w:bookmarkStart)
@@ -397,20 +402,39 @@ function serializeTrackedChange(tag, change) {
397
402
  return rewriteRunTextAsDeleted(contentXml);
398
403
  }).join("");
399
404
  };
400
- const contentXml = change.content.map((item) => {
401
- if (item.type === "run") {
402
- if (tag === "del" || tag === "moveFrom") return serializeDeletedRun(item);
403
- return serializeRun(item);
404
- }
405
- if (item.type === "hyperlink") return serializeHyperlink(item);
405
+ const serializeContentRun = (run) => tag === "del" || tag === "moveFrom" ? serializeDeletedRun(run) : serializeRun(run);
406
+ const serializeWrappedItem = (item) => {
407
+ if (item.type === "run") return serializeContentRun(item);
406
408
  if (item.type === "simpleField" || item.type === "complexField") {
407
409
  const xml = item.type === "simpleField" ? serializeSimpleField(item) : serializeComplexField(item);
408
410
  return tag === "del" || tag === "moveFrom" ? rewriteRunTextAsDeleted(xml) : xml;
409
411
  }
410
412
  if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") return serializeTrackedChange(trackedChangeTag(item), item);
411
413
  return item.type === "bookmarkStart" ? serializeBookmarkStart(item) : serializeBookmarkEnd(item);
412
- }).join("");
413
- return `<w:${tag} ${attrs.join(" ")}>${contentXml}</w:${tag}>`;
414
+ };
415
+ const open = `<w:${tag} ${attrs.join(" ")}>`;
416
+ const close = `</w:${tag}>`;
417
+ const wrap = (inner) => inner.length === 0 ? "" : `${open}${inner}${close}`;
418
+ if (change.content.length === 0) return `${open}${close}`;
419
+ const segments = [];
420
+ const pending = [];
421
+ const flushPending = () => {
422
+ if (pending.length > 0) {
423
+ segments.push(wrap(pending.join("")));
424
+ pending.length = 0;
425
+ }
426
+ };
427
+ for (const item of change.content) {
428
+ if (item.type === "hyperlink") {
429
+ flushPending();
430
+ const childrenXml = item.children.map((child) => serializeHyperlinkChild(child, serializeContentRun)).join("");
431
+ segments.push(`<w:hyperlink${hyperlinkAttributes(item)}>${open}${childrenXml}${close}</w:hyperlink>`);
432
+ continue;
433
+ }
434
+ pending.push(serializeWrappedItem(item));
435
+ }
436
+ flushPending();
437
+ return segments.join("");
414
438
  }
415
439
  /** Emit the `<w:commentReference>` run Word places after a comment range end. */
416
440
  function serializeCommentReferenceRun(id) {