@stll/folio-core 0.45.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/ai-edits/apply.js +59 -19
  2. package/dist/ai-edits/headless.js +1 -1
  3. package/dist/compare/compare.js +2 -5
  4. package/dist/compare/scenario.js +2 -5
  5. package/dist/compare/types.d.ts +9 -1
  6. package/dist/compare/types.js +12 -1
  7. package/dist/compat/eigenpal.d.ts +2 -1
  8. package/dist/compat/eigenpal.js +2 -1
  9. package/dist/content-controls/checkboxDisplay.d.ts +11 -0
  10. package/dist/content-controls/checkboxDisplay.js +40 -0
  11. package/dist/content-controls/findContentControls.js +3 -1
  12. package/dist/content-controls/mutateContentControls.js +17 -2
  13. package/dist/controller/contentControlWidgetController.d.ts +1 -0
  14. package/dist/controller/contentControlWidgetController.js +3 -0
  15. package/dist/controller/hiddenEditorApi.js +3 -1
  16. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  17. package/dist/controller/hiddenEditorManager.js +6 -3
  18. package/dist/display-list/build/buildDisplayList.js +20 -1
  19. package/dist/display-list/build/imagePrimitives.d.ts +1 -1
  20. package/dist/display-list/build/imagePrimitives.js +3 -1
  21. package/dist/display-list/build/paragraphPrimitives.js +21 -2
  22. package/dist/display-list/dom/renderDisplayListToDom.js +10 -2
  23. package/dist/display-list/types.d.ts +17 -0
  24. package/dist/docx/attributeRemainder.d.ts +43 -0
  25. package/dist/docx/attributeRemainder.js +65 -0
  26. package/dist/docx/blockContentParser.d.ts +1 -1
  27. package/dist/docx/blockContentParser.js +87 -63
  28. package/dist/docx/blockPlainText.d.ts +3 -4
  29. package/dist/docx/blockPlainText.js +1 -0
  30. package/dist/docx/bookmarkPlacement.js +2 -0
  31. package/dist/docx/commentAnchorIndex.d.ts +30 -0
  32. package/dist/docx/commentAnchorIndex.js +50 -0
  33. package/dist/docx/commentParser.d.ts +1 -1
  34. package/dist/docx/commentParser.js +54 -37
  35. package/dist/docx/commentRangeIntegrity.d.ts +16 -1
  36. package/dist/docx/commentRangeIntegrity.js +42 -1
  37. package/dist/docx/commentRangeJoin.d.ts +9 -0
  38. package/dist/docx/commentRangeJoin.js +33 -0
  39. package/dist/docx/commentReferenceCompletion.d.ts +9 -0
  40. package/dist/docx/commentReferenceCompletion.js +47 -0
  41. package/dist/docx/commentReferenceNormalization.js +1 -0
  42. package/dist/docx/commentReplyMarkers.js +11 -19
  43. package/dist/docx/compatibility.js +1 -0
  44. package/dist/docx/containerChildren.d.ts +111 -0
  45. package/dist/docx/containerChildren.gen.d.ts +28 -0
  46. package/dist/docx/containerChildren.gen.js +246 -0
  47. package/dist/docx/containerChildren.js +103 -0
  48. package/dist/docx/documentParser.d.ts +1 -1
  49. package/dist/docx/documentParser.js +3 -3
  50. package/dist/docx/ensureParaIds.js +17 -7
  51. package/dist/docx/fieldParser.d.ts +1 -1
  52. package/dist/docx/fieldParser.js +4 -4
  53. package/dist/docx/fieldState.d.ts +26 -0
  54. package/dist/docx/fieldState.js +53 -0
  55. package/dist/docx/footnoteParser.d.ts +1 -1
  56. package/dist/docx/graphicFrameLocks.d.ts +16 -4
  57. package/dist/docx/graphicFrameLocks.js +19 -6
  58. package/dist/docx/headerFooterParser.js +1 -1
  59. package/dist/docx/headerFooterReferenceNormalization.js +1 -0
  60. package/dist/docx/hyperlinkParser.d.ts +27 -8
  61. package/dist/docx/hyperlinkParser.js +76 -18
  62. package/dist/docx/imageParser.js +33 -8
  63. package/dist/docx/inlineWrapperContent.d.ts +85 -0
  64. package/dist/docx/inlineWrapperContent.js +84 -0
  65. package/dist/docx/normalizeBaseDirection.js +10 -1
  66. package/dist/docx/paraIdAttribute.d.ts +21 -0
  67. package/dist/docx/paraIdAttribute.js +64 -0
  68. package/dist/docx/paragraphParser.d.ts +1 -1
  69. package/dist/docx/paragraphParser.js +277 -168
  70. package/dist/docx/paragraphPropertySource.js +5 -1
  71. package/dist/docx/paragraphTextBoxEnrichment.d.ts +1 -1
  72. package/dist/docx/paragraphTextBoxEnrichment.js +6 -52
  73. package/dist/docx/paragraphTraversal.js +7 -4
  74. package/dist/docx/parseWarningMessage.js +2 -1
  75. package/dist/docx/preservedRunContent.d.ts +32 -0
  76. package/dist/docx/preservedRunContent.js +86 -0
  77. package/dist/docx/renderedPageBreakNormalization.js +2 -4
  78. package/dist/docx/rezip.js +74 -18
  79. package/dist/docx/runParser.d.ts +1 -1
  80. package/dist/docx/runParser.js +16 -12
  81. package/dist/docx/sectionParser.js +8 -1
  82. package/dist/docx/selectiveXmlPatch.d.ts +46 -2
  83. package/dist/docx/selectiveXmlPatch.js +86 -39
  84. package/dist/docx/serializer/borderSerializer.js +2 -2
  85. package/dist/docx/serializer/commentSerializer.js +5 -7
  86. package/dist/docx/serializer/documentSerializer.js +8 -11
  87. package/dist/docx/serializer/headerFooterSerializer.js +7 -9
  88. package/dist/docx/serializer/noteSerializer.js +8 -8
  89. package/dist/docx/serializer/paragraphSerializer.js +76 -56
  90. package/dist/docx/serializer/runSerializer.js +25 -16
  91. package/dist/docx/serializer/sectionPropertiesSerializer.js +8 -4
  92. package/dist/docx/serializer/tableSerializer.js +18 -8
  93. package/dist/docx/server/createBilingualDocument.js +3 -1
  94. package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
  95. package/dist/docx/server/materializeYjsDocx.js +9 -1
  96. package/dist/docx/server/migrateYjsAttrSchema.d.ts +55 -0
  97. package/dist/docx/server/migrateYjsAttrSchema.js +95 -0
  98. package/dist/docx/shapeParser.js +5 -5
  99. package/dist/docx/tableParser.d.ts +1 -1
  100. package/dist/docx/tableParser.js +206 -85
  101. package/dist/docx/textBoxParser.d.ts +25 -2
  102. package/dist/docx/textBoxParser.js +52 -2
  103. package/dist/docx/unzip.js +5 -4
  104. package/dist/docx/vmlImageParser.d.ts +17 -1
  105. package/dist/docx/vmlImageParser.js +17 -1
  106. package/dist/docx/xmlEncoding.d.ts +5 -0
  107. package/dist/docx/xmlEncoding.js +12 -0
  108. package/dist/docx/xmlParser.d.ts +10 -1
  109. package/dist/docx/xmlParser.js +14 -1
  110. package/dist/headless-layout.js +10 -1
  111. package/dist/index.d.ts +2 -1
  112. package/dist/index.js +2 -1
  113. package/dist/internal/pageBreakRunSourceDescendantIndex.js +5 -2
  114. package/dist/layout-bridge/convert/toFlowBlocks.js +102 -12
  115. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  116. package/dist/layout-engine/measure/tableCellFloating.js +2 -0
  117. package/dist/layout-engine/types.d.ts +23 -1
  118. package/dist/layout-painter/renderImage.d.ts +5 -1
  119. package/dist/layout-painter/renderImage.js +10 -3
  120. package/dist/layout-painter/renderPage.d.ts +2 -0
  121. package/dist/layout-painter/renderPage.js +4 -0
  122. package/dist/layout-painter/renderParagraph.d.ts +13 -1
  123. package/dist/layout-painter/renderParagraph.js +22 -3
  124. package/dist/managers/autoSaveCodec.d.ts +9 -1
  125. package/dist/managers/autoSaveCodec.js +12 -4
  126. package/dist/markdown/renderBlock.js +1 -0
  127. package/dist/markdown/renderRuns.d.ts +1 -1
  128. package/dist/markdown/renderRuns.js +28 -5
  129. package/dist/markdown/renderTable.js +22 -5
  130. package/dist/markdown/trailers.js +4 -1
  131. package/dist/pdf/images.js +142 -6
  132. package/dist/prosemirror/attrs/index.d.ts +14 -3
  133. package/dist/prosemirror/attrs/index.js +155 -2
  134. package/dist/prosemirror/authoredTransformAttrs.d.ts +28 -0
  135. package/dist/prosemirror/authoredTransformAttrs.js +64 -0
  136. package/dist/prosemirror/commands/contentControls.js +10 -2
  137. package/dist/prosemirror/commands/image.d.ts +9 -2
  138. package/dist/prosemirror/commands/image.js +42 -28
  139. package/dist/prosemirror/commands/pageBreak.js +1 -1
  140. package/dist/prosemirror/commentReferenceAttrs.d.ts +8 -0
  141. package/dist/prosemirror/commentReferenceAttrs.js +33 -0
  142. package/dist/prosemirror/commentReferenceIntegrity.d.ts +47 -0
  143. package/dist/prosemirror/commentReferenceIntegrity.js +43 -0
  144. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  145. package/dist/prosemirror/conversion/fromProseDoc.js +269 -70
  146. package/dist/prosemirror/conversion/index.d.ts +2 -2
  147. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -9
  148. package/dist/prosemirror/conversion/toProseDoc.js +468 -308
  149. package/dist/prosemirror/extensions/StarterKit.js +8 -58
  150. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  151. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
  152. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +45 -27
  153. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -1
  154. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +5 -3
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -0
  156. package/dist/prosemirror/extensions/markRegistry.d.ts +41 -0
  157. package/dist/prosemirror/extensions/markRegistry.js +75 -0
  158. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.d.ts +18 -0
  159. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +63 -0
  160. package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
  161. package/dist/prosemirror/extensions/marks/markUtils.js +47 -4
  162. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.d.ts +11 -0
  163. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +87 -0
  164. package/dist/prosemirror/extensions/nodes/FieldExtension.js +9 -7
  165. package/dist/prosemirror/extensions/nodes/ImageExtension.js +16 -0
  166. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.d.ts +32 -0
  167. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +67 -0
  168. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.d.ts +16 -0
  169. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +60 -0
  170. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +3 -0
  171. package/dist/prosemirror/extensions/nodes/TableExtension.js +3 -2
  172. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  173. package/dist/prosemirror/inlineWrapperStack.d.ts +39 -0
  174. package/dist/prosemirror/inlineWrapperStack.js +75 -0
  175. package/dist/prosemirror/pageBreakRunProjection.d.ts +17 -7
  176. package/dist/prosemirror/pageBreakRunProjection.js +19 -9
  177. package/dist/prosemirror/replacedAnnotations.d.ts +59 -0
  178. package/dist/prosemirror/replacedAnnotations.js +165 -0
  179. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +3 -1
  180. package/dist/prosemirror/runFormattingInlineCarriers.js +5 -1
  181. package/dist/prosemirror/schema/index.d.ts +3 -3
  182. package/dist/prosemirror/schema/marks.d.ts +25 -1
  183. package/dist/prosemirror/schema/nodes.d.ts +90 -3
  184. package/dist/prosemirror/schema/nodes.js +16 -1
  185. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +2 -0
  186. package/dist/prosemirror/trackedRunInlineAtoms.js +2 -0
  187. package/dist/prosemirror/validation.js +16 -2
  188. package/dist/prosemirror/yjsDocumentMetadata.d.ts +73 -0
  189. package/dist/prosemirror/yjsDocumentMetadata.js +171 -0
  190. package/dist/prosemirror/zeroWidthAnchors.js +2 -0
  191. package/dist/render-dom/commentAnchorAttributes.d.ts +37 -0
  192. package/dist/render-dom/commentAnchorAttributes.js +54 -0
  193. package/dist/server.d.ts +3 -1
  194. package/dist/server.js +3 -1
  195. package/dist/types/content.d.ts +2 -2
  196. package/dist/utils/clipboard.d.ts +9 -1
  197. package/dist/utils/clipboard.js +27 -1
  198. package/dist/utils/findReplace.js +1 -1
  199. package/dist/utils/imageLuminance.d.ts +15 -0
  200. package/dist/utils/imageLuminance.js +32 -0
  201. package/dist/utils/mergeDocumentContent.js +7 -17
  202. package/dist/utils/replaceText.js +1 -0
  203. package/package.json +2 -2
  204. package/dist/docx/blockRangeMarkers.d.ts +0 -36
  205. package/dist/docx/blockRangeMarkers.js +0 -59
  206. package/dist/prosemirror/yjsParagraphSourceContract.d.ts +0 -9
  207. package/dist/prosemirror/yjsParagraphSourceContract.js +0 -26
@@ -262,10 +262,11 @@ const invalidTableCellParagraphSourcePayload = (classification, path) => {
262
262
  const tableCellBlockTraversalByType = {
263
263
  blockSdt: "blockSdt",
264
264
  paragraph: "paragraph",
265
+ preservedBlock: "leaf",
265
266
  table: "table"
266
267
  };
267
268
  const tableCellParagraphContentTraversalByType = {
268
- bidiWrapper: "content",
269
+ inlineWrapper: "content",
269
270
  bookmarkEnd: "leaf",
270
271
  bookmarkStart: "leaf",
271
272
  commentRangeEnd: "leaf",
@@ -283,6 +284,7 @@ const tableCellParagraphContentTraversalByType = {
283
284
  moveTo: "content",
284
285
  moveToRangeEnd: "leaf",
285
286
  moveToRangeStart: "leaf",
287
+ preservedInline: "leaf",
286
288
  run: "run",
287
289
  simpleField: "content"
288
290
  };
@@ -294,6 +296,7 @@ const tableCellRunContentTraversalByType = {
294
296
  footnoteRef: "leaf",
295
297
  instrText: "leaf",
296
298
  noBreakHyphen: "leaf",
299
+ preservedXml: "leaf",
297
300
  renderedPageBreak: "leaf",
298
301
  shape: "shape",
299
302
  softHyphen: "leaf",
@@ -453,6 +456,7 @@ function visitDecodedTableCellBlock(value, path, context) {
453
456
  for (const [index, child] of content.entries()) visitDecodedTableCellBlock(child, `${path}.content[${index}]`, context);
454
457
  return;
455
458
  }
459
+ case "leaf": return;
456
460
  default: return traversal;
457
461
  }
458
462
  }
@@ -1,7 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
- import { StyleMap } from "./styleParser.js";
4
3
  import { XmlElement } from "./xmlParser.js";
4
+ import { StyleMap } from "./styleParser.js";
5
5
  import { TableParserFn } from "./textBoxParser.js";
6
6
  //#region src/docx/paragraphTextBoxEnrichment.d.ts
7
7
  declare const enrichParagraphTextBoxes: (paragraph: document_d_exports.Paragraph, paraXml: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, parseTable: TableParserFn) => void;
@@ -1,6 +1,7 @@
1
1
  import { pixelsToEmu } from "../utils/units.js";
2
2
  import { parseParagraph } from "./paragraphParser.js";
3
- import { getTextBoxContentElement, isTextBoxDrawing, parseTextBox, parseTextBoxContent } from "./textBoxParser.js";
3
+ import { getTextBoxContentElement, parseTextBox, parseTextBoxContent, scanRunForTextBoxDrawings } from "./textBoxParser.js";
4
+ import { isVmlPictParsedByRunParser } from "./vmlImageParser.js";
4
5
  import { findDeep, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
5
6
  //#region src/docx/paragraphTextBoxEnrichment.ts
6
7
  const VML_HORIZONTAL_RELATIVES = /* @__PURE__ */ new Set([
@@ -182,7 +183,10 @@ const enrichTextBoxRuns = ({ content, xmlChildren, styles, theme, numbering, rel
182
183
  if (parsedIndex < content.length && parsedContent?.type !== "run") parsedIndex += 1;
183
184
  continue;
184
185
  }
185
- const { textBoxDrawings, vmlTextBoxes, hasNonTextBoxContent } = scanRunForTextBoxDrawings(xmlChild);
186
+ const { textBoxDrawings, vmlTextBoxes, hasNonTextBoxContent } = scanRunForTextBoxDrawings({
187
+ xmlRun: xmlChild,
188
+ claimedByRunParser: (pictElement) => isVmlPictParsedByRunParser(pictElement, rels, media)
189
+ });
186
190
  const parsedRun = parsedContent?.type === "run" ? parsedContent : void 0;
187
191
  const targetRun = parsedRun ?? (hasNonTextBoxContent ? lastConsumedRun : void 0);
188
192
  const targetRunMatchesXml = targetRun !== void 0 && (hasNonTextBoxContent || parsedRun?.content.length === 0);
@@ -254,56 +258,6 @@ const enrichTextBoxRuns = ({ content, xmlChildren, styles, theme, numbering, rel
254
258
  }
255
259
  }
256
260
  };
257
- const scanRunForTextBoxDrawings = (xmlRun) => {
258
- const textBoxDrawings = [];
259
- const vmlTextBoxes = [];
260
- let hasNonTextBoxContent = false;
261
- const visitDrawing = (drawingEl) => {
262
- if (isTextBoxDrawing(drawingEl)) {
263
- textBoxDrawings.push(drawingEl);
264
- return;
265
- }
266
- hasNonTextBoxContent = true;
267
- };
268
- for (const el of getChildElements(xmlRun)) {
269
- const name = getLocalName(el.name ?? "");
270
- if (name === "rPr") continue;
271
- if (name === "drawing") {
272
- visitDrawing(el);
273
- continue;
274
- }
275
- if (name === "pict") {
276
- if (findDeep(el, "v", "textbox")) if (findDeep(el, "v", "imagedata")) hasNonTextBoxContent = true;
277
- else vmlTextBoxes.push(el);
278
- else hasNonTextBoxContent = true;
279
- continue;
280
- }
281
- if (name === "AlternateContent") {
282
- const branches = getChildElements(el);
283
- const choice = branches.find((branch) => getLocalName(branch.name ?? "") === "Choice");
284
- const fallback = branches.find((branch) => getLocalName(branch.name ?? "") === "Fallback");
285
- const tryBranch = (branch) => {
286
- if (!branch) return false;
287
- let found = false;
288
- for (const innerEl of getChildElements(branch)) if (getLocalName(innerEl.name ?? "") === "drawing") {
289
- visitDrawing(innerEl);
290
- found = true;
291
- }
292
- return found;
293
- };
294
- let foundInBranch = tryBranch(choice);
295
- if (!foundInBranch) foundInBranch = tryBranch(fallback);
296
- if (!foundInBranch) hasNonTextBoxContent = true;
297
- continue;
298
- }
299
- hasNonTextBoxContent = true;
300
- }
301
- return {
302
- textBoxDrawings,
303
- vmlTextBoxes,
304
- hasNonTextBoxContent
305
- };
306
- };
307
261
  const parseVmlTextBoxShape = (pictEl, styles, theme, numbering, rels, media, parseTable) => {
308
262
  const shapeEl = findDeep(pictEl, "v", "shape");
309
263
  const textBoxEl = shapeEl ? findDeep(shapeEl, "v", "textbox") : null;
@@ -16,7 +16,7 @@ const visitParagraphRuns = (paragraph, visit) => {
16
16
  case "moveFrom":
17
17
  case "moveTo":
18
18
  case "inlineSdt":
19
- case "bidiWrapper":
19
+ case "inlineWrapper":
20
20
  for (const child of content.content) visitParagraphContent(child);
21
21
  return;
22
22
  case "complexField":
@@ -32,7 +32,8 @@ const visitParagraphRuns = (paragraph, visit) => {
32
32
  case "moveFromRangeEnd":
33
33
  case "moveToRangeStart":
34
34
  case "moveToRangeEnd":
35
- case "mathEquation": return;
35
+ case "mathEquation":
36
+ case "preservedInline": return;
36
37
  default: return content;
37
38
  }
38
39
  };
@@ -69,7 +70,7 @@ const visitInlineContentSlots = (paragraph, visit) => {
69
70
  case "deletion":
70
71
  case "moveFrom":
71
72
  case "moveTo":
72
- case "bidiWrapper":
73
+ case "inlineWrapper":
73
74
  visitContent(item.content);
74
75
  break;
75
76
  case "run":
@@ -83,7 +84,8 @@ const visitInlineContentSlots = (paragraph, visit) => {
83
84
  case "moveFromRangeEnd":
84
85
  case "moveToRangeStart":
85
86
  case "moveToRangeEnd":
86
- case "mathEquation": break;
87
+ case "mathEquation":
88
+ case "preservedInline": break;
87
89
  default: panic(`Unsupported paragraph content: ${JSON.stringify(item)}`);
88
90
  }
89
91
  }
@@ -145,6 +147,7 @@ const visitDocxParagraphs = ({ documentBody, headers, footers, footnotes, endnot
145
147
  visitTable(block);
146
148
  return;
147
149
  }
150
+ if (block.type === "preservedBlock") return;
148
151
  visitBlocks(block.content);
149
152
  };
150
153
  const visitBlocks = (blocks) => {
@@ -36,7 +36,8 @@ const PARSE_WARNING_MESSAGES = {
36
36
  [PARSE_WARNING_CODES.unrecognisedOnOffValue]: (warning) => `Ignored on/off value${quoted(warning.value)}${where(warning)}; ST_OnOff is 1, 0, true, false, on or off.`,
37
37
  [PARSE_WARNING_CODES.borderWithoutValue]: (warning) => `Read a border with no w:val${where(warning)} as having no border style.`,
38
38
  [PARSE_WARNING_CODES.styleSetDuplicateStyleId]: (warning) => `Dropped a style repeating the id${quoted(warning.value)} another style in the set already defines.`,
39
- [PARSE_WARNING_CODES.styleSetInitialStyleMissing]: (warning) => `The style set names initial paragraph style${quoted(warning.value)}, which it does not contain; used the set's default instead${where(warning)}.`
39
+ [PARSE_WARNING_CODES.styleSetInitialStyleMissing]: (warning) => `The style set names initial paragraph style${quoted(warning.value)}, which it does not contain; used the set's default instead${where(warning)}.`,
40
+ [PARSE_WARNING_CODES.pageBreakProjectionApproximated]: (warning) => `${warning.detail ?? "An explicit page break is laid out approximately"}${where(warning)}.`
40
41
  };
41
42
  const formatParseWarning = (warning) => PARSE_WARNING_MESSAGES[warning.code](warning);
42
43
  const formatParseWarnings = (warnings) => warnings.map(formatParseWarning);
@@ -0,0 +1,32 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/preservedRunContent.d.ts
4
+ /**
5
+ * The visible text a preserved run child contributes, or `""` when it shows
6
+ * nothing. Unknown markup contributes nothing on purpose: guessing at the text
7
+ * of an element folio has never seen would put invented words in a document.
8
+ */
9
+ declare const preservedRunChildText: (element: XmlElement) => string;
10
+ /**
11
+ * Capture one unmodelled run child.
12
+ *
13
+ * `captureVerbatimXml` materialises exactly the namespace bindings the
14
+ * fragment's own prefixes need and leaves the canonical ones to the rebuilt
15
+ * root, so ordinary `w:` markup comes back byte for byte while a foreign
16
+ * prefix still arrives bound. Copying the whole root scope onto the fragment
17
+ * instead would rewrite every capture with declarations it does not use.
18
+ */
19
+ declare const preserveRunChild: (element: XmlElement) => document_d_exports.PreservedXmlContent;
20
+ /**
21
+ * One capture from the shared dispatcher's sink, as a member of the
22
+ * container's own inline union.
23
+ *
24
+ * The sink holds markup the schema does not declare for the container at all,
25
+ * so nothing is known about it beyond its bytes: `text` is empty because a
26
+ * name folio has never seen shows nothing folio can read.
27
+ */
28
+ declare const preservedInlineCapture: (xml: string) => document_d_exports.PreservedInline;
29
+ /** Capture one unmodelled inline child of a paragraph or an inline wrapper. */
30
+ declare const preserveInlineChild: (element: XmlElement) => document_d_exports.PreservedInline;
31
+ //#endregion
32
+ export { preserveInlineChild, preserveRunChild, preservedInlineCapture, preservedRunChildText };
@@ -0,0 +1,86 @@
1
+ import { captureVerbatimXml } from "./verbatimCapture.js";
2
+ import { findChildrenByLocalName, getChildElements, getLocalName, getTextContent } from "./xmlParser.js";
3
+ //#region src/docx/preservedRunContent.ts
4
+ /**
5
+ * Where a preserved run child hides text a reader sees, by local name: the
6
+ * child elements whose `w:t` descendants are on the line rather than above,
7
+ * beside, or nowhere.
8
+ *
9
+ * `w:ruby` is the whole list today. Its `w:rt` is the annotation printed above
10
+ * the base and is not the sentence's text; its `w:rubyBase` is.
11
+ */
12
+ const VISIBLE_TEXT_CHILDREN = /* @__PURE__ */ new Map([["ruby", ["rubyBase"]]]);
13
+ /** Cap on the text one preserved child contributes, mirroring `w:t` handling. */
14
+ const MAX_PRESERVED_TEXT_LENGTH = 1e5;
15
+ const collectTextContent = (element, into) => {
16
+ for (const child of getChildElements(element)) {
17
+ if (getLocalName(child.name) === "t") {
18
+ into.push(getTextContent(child));
19
+ continue;
20
+ }
21
+ collectTextContent(child, into);
22
+ }
23
+ };
24
+ /**
25
+ * The visible text a preserved run child contributes, or `""` when it shows
26
+ * nothing. Unknown markup contributes nothing on purpose: guessing at the text
27
+ * of an element folio has never seen would put invented words in a document.
28
+ */
29
+ const preservedRunChildText = (element) => {
30
+ const sources = VISIBLE_TEXT_CHILDREN.get(getLocalName(element.name));
31
+ if (sources === void 0) return "";
32
+ const parts = [];
33
+ for (const localName of sources) for (const source of findChildrenByLocalName(element, localName)) collectTextContent(source, parts);
34
+ return parts.join("").slice(0, MAX_PRESERVED_TEXT_LENGTH);
35
+ };
36
+ /**
37
+ * Capture one unmodelled run child.
38
+ *
39
+ * `captureVerbatimXml` materialises exactly the namespace bindings the
40
+ * fragment's own prefixes need and leaves the canonical ones to the rebuilt
41
+ * root, so ordinary `w:` markup comes back byte for byte while a foreign
42
+ * prefix still arrives bound. Copying the whole root scope onto the fragment
43
+ * instead would rewrite every capture with declarations it does not use.
44
+ */
45
+ const preserveRunChild = (element) => ({
46
+ type: "preservedXml",
47
+ xml: captureVerbatimXml(element),
48
+ text: preservedRunChildText(element)
49
+ });
50
+ /**
51
+ * Where an unmodelled *inline* child hides text a reader sees, by local name.
52
+ *
53
+ * `w:customXml` and `w:smartTag` are transparent wrappers (ECMA-376 §17.5.1,
54
+ * §17.5.1.9): their content is ordinary inline content, so their `w:t`
55
+ * descendants are on the line. Every other inline child folio captures —
56
+ * `w:permStart`, `w:proofErr` and the custom-XML revision ranges — is an
57
+ * empty marker, and an element folio has never seen contributes nothing on
58
+ * purpose, because guessing at its text would put invented words in a
59
+ * document.
60
+ */
61
+ const VISIBLE_TEXT_INLINE_CHILDREN = /* @__PURE__ */ new Set(["customXml", "smartTag"]);
62
+ /**
63
+ * One capture from the shared dispatcher's sink, as a member of the
64
+ * container's own inline union.
65
+ *
66
+ * The sink holds markup the schema does not declare for the container at all,
67
+ * so nothing is known about it beyond its bytes: `text` is empty because a
68
+ * name folio has never seen shows nothing folio can read.
69
+ */
70
+ const preservedInlineCapture = (xml) => ({
71
+ type: "preservedInline",
72
+ xml,
73
+ text: ""
74
+ });
75
+ /** Capture one unmodelled inline child of a paragraph or an inline wrapper. */
76
+ const preserveInlineChild = (element) => {
77
+ const parts = [];
78
+ if (VISIBLE_TEXT_INLINE_CHILDREN.has(getLocalName(element.name))) collectTextContent(element, parts);
79
+ return {
80
+ type: "preservedInline",
81
+ xml: captureVerbatimXml(element),
82
+ text: parts.join("").slice(0, MAX_PRESERVED_TEXT_LENGTH)
83
+ };
84
+ };
85
+ //#endregion
86
+ export { preserveInlineChild, preserveRunChild, preservedInlineCapture, preservedRunChildText };
@@ -37,9 +37,7 @@ const scanHyperlink = (hyperlink, scan) => {
37
37
  };
38
38
  const scanInlineContent = (content, scan) => {
39
39
  for (const child of content) {
40
- let result;
41
- if (child.type === "run") result = scanRun(child, scan);
42
- else if (child.type === "hyperlink") result = scanHyperlink(child, scan);
40
+ const result = scanParagraphContent(child, scan);
43
41
  if (result !== void 0) return result;
44
42
  }
45
43
  };
@@ -48,7 +46,7 @@ const scanParagraphContent = (content, scan) => {
48
46
  if (content.type === "hyperlink") return scanHyperlink(content, scan);
49
47
  if (content.type === "simpleField") return scanInlineContent(content.content, scan);
50
48
  if (content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo") return scanInlineContent(content.content, scan);
51
- if (content.type === "inlineSdt") {
49
+ if (content.type === "inlineSdt" || content.type === "inlineWrapper") {
52
50
  for (const child of content.content) {
53
51
  const result = scanParagraphContent(child, scan);
54
52
  if (result !== void 0) return result;
@@ -29,7 +29,7 @@ import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
29
29
  import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
30
30
  import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
31
31
  import { serializeThemeXml } from "./serializer/themeSerializer.js";
32
- import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChild, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
32
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
33
33
  import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
34
34
  import { panic } from "better-result";
35
35
  import { escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
@@ -443,6 +443,7 @@ function collectHyperlinksWithoutRId(blocks) {
443
443
  break;
444
444
  case "simpleField":
445
445
  case "inlineSdt":
446
+ case "inlineWrapper":
446
447
  case "insertion":
447
448
  case "deletion":
448
449
  case "moveFrom":
@@ -454,7 +455,7 @@ function collectHyperlinksWithoutRId(blocks) {
454
455
  };
455
456
  for (const block of blocks) if (block.type === "paragraph") collectInlineHyperlinks(block.content);
456
457
  else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
457
- else hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
458
+ else if (block.type === "blockSdt") hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
458
459
  return hyperlinks;
459
460
  }
460
461
  /** The selective save boundary must use the same resource census as full repack. */
@@ -1425,7 +1426,26 @@ const seedStylesXmlWith = (missing) => {
1425
1426
  const rootClose = SEED_STYLES_XML.lastIndexOf(STYLES_CLOSE_ROOT);
1426
1427
  return SEED_STYLES_XML.slice(0, rootClose) + missing.map(serializeStyle).join("") + SEED_STYLES_XML.slice(rootClose);
1427
1428
  };
1428
- const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
1429
+ /**
1430
+ * The style ids `word/styles.xml` already defines.
1431
+ *
1432
+ * Read by parsing the part, because the model holds the *decoded* id and the
1433
+ * part holds its XML spelling: `w:styleId='Body A'` is single-quoted,
1434
+ * `w:styleId="Header &amp; Footer"` is escaped, and either may sit behind an
1435
+ * attribute value containing `>`. A reader that misses one of those reports a
1436
+ * style the part already defines as missing, and
1437
+ * {@link serializeAddedStylesIntoZip} appends it — on every save, for ever.
1438
+ */
1439
+ const definedStyleIds = (stylesXml) => {
1440
+ const root = parseXml(stylesXml);
1441
+ const styles = findChildByNamespaceUri(root, WORDPROCESSINGML_NAMESPACE_URIS, "styles") ?? root;
1442
+ const ids = /* @__PURE__ */ new Set();
1443
+ for (const style of findChildrenByNamespaceUri(styles, WORDPROCESSINGML_NAMESPACE_URIS, "style")) {
1444
+ const id = getAttributeByNamespaceUri(style, WORDPROCESSINGML_NAMESPACE_URIS, "styleId") ?? style.attributes?.["w:styleId"];
1445
+ if (id !== void 0 && id !== null && id !== "") ids.add(String(id));
1446
+ }
1447
+ return ids;
1448
+ };
1429
1449
  /**
1430
1450
  * Append styles the model defines but the original `word/styles.xml` lacks.
1431
1451
  * Existing styles stay byte-exact (edits to them are not written here, the
@@ -1471,9 +1491,13 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
1471
1491
  });
1472
1492
  return;
1473
1493
  }
1474
- const existing = /* @__PURE__ */ new Set();
1475
- for (const match of originalXml.matchAll(STYLE_ID_PATTERN)) existing.add(match.groups["id"]);
1476
- const added = styles.styles.filter((style) => !existing.has(style.styleId));
1494
+ const existing = new Set(definedStyleIds(originalXml));
1495
+ const added = [];
1496
+ for (const style of styles.styles) {
1497
+ if (existing.has(style.styleId)) continue;
1498
+ existing.add(style.styleId);
1499
+ added.push(style);
1500
+ }
1477
1501
  if (added.length === 0) return;
1478
1502
  const patched = originalXml.slice(0, rootClose) + added.map(serializeStyle).join("") + originalXml.slice(rootClose);
1479
1503
  newZip.file(file.name, patched, {
@@ -1481,28 +1505,60 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
1481
1505
  compressionOptions: { level: compressionLevel }
1482
1506
  });
1483
1507
  }
1508
+ /**
1509
+ * The XML to write for a patched note part, or null to keep the original.
1510
+ *
1511
+ * A refusal over comment-range balance is not a failure to serialize: the
1512
+ * splice was too narrow to keep a comment's range whole, so the part is
1513
+ * written from the model, which is balanced with itself and with the
1514
+ * `document.xml` this repack rewrites beside it. Word's required separator
1515
+ * notes come back synthesized rather than byte-exact, the price of keeping the
1516
+ * comment anchored to the text it was written about. Any other refusal means a
1517
+ * changed paragraph could not be located in the part at all, which no wider
1518
+ * rewrite fixes.
1519
+ */
1520
+ const notePartXmlFor = ({ patch, originalXml, replacementXml, hasDirtyParagraph, elementName, partName }) => {
1521
+ const keepOriginal = () => {
1522
+ if (hasDirtyParagraph) throw new DocxPackageFidelityError(`Cannot serialize changed ${elementName} paragraphs into ${partName}`);
1523
+ return null;
1524
+ };
1525
+ switch (patch.type) {
1526
+ case "patched": return patch.xml === originalXml ? keepOriginal() : patch.xml;
1527
+ case "refused": switch (patch.reason) {
1528
+ case "comment-range-balance": return replacementXml;
1529
+ case "unroutable-paragraph": return keepOriginal();
1530
+ default: {
1531
+ const unreachable = patch.reason;
1532
+ return panic("Unhandled note-part patch refusal", { reason: unreachable });
1533
+ }
1534
+ }
1535
+ default: return panic("Unhandled note-part patch", { patch });
1536
+ }
1537
+ };
1484
1538
  async function patchNotePartIntoZip({ conventionalLowerPath, currentXml, replacementXml, baselineFrom, elementName, changedNoteParaIds, originalZip, newZip, compressionLevel }) {
1485
1539
  const file = findNotePartEntry(originalZip, conventionalLowerPath);
1486
1540
  if (!file) return;
1487
1541
  const originalXml = await file.async("text");
1488
1542
  const baselineXml = baselineFrom(originalXml);
1489
1543
  const effectiveChangedNoteParaIds = changedNoteParaIds ?? collectChangedNoteParaIds(baselineXml, currentXml);
1490
- const patched = buildPatchedNotePartXml({
1544
+ const currentParaIds = collectParaIds(currentXml);
1545
+ const patchedXml = notePartXmlFor({
1546
+ patch: buildPatchedNotePartXml({
1547
+ originalXml,
1548
+ baselineXml,
1549
+ serializedXml: currentXml,
1550
+ replacementXml,
1551
+ elementName,
1552
+ changedParaIds: effectiveChangedNoteParaIds
1553
+ }),
1491
1554
  originalXml,
1492
- baselineXml,
1493
- serializedXml: currentXml,
1494
1555
  replacementXml,
1556
+ hasDirtyParagraph: [...effectiveChangedNoteParaIds].some((paraId) => currentParaIds.has(paraId)),
1495
1557
  elementName,
1496
- changedParaIds: effectiveChangedNoteParaIds
1558
+ partName: file.name
1497
1559
  });
1498
- const currentParaIds = collectParaIds(currentXml);
1499
- const hasDirtyParagraph = [...effectiveChangedNoteParaIds].some((paraId) => currentParaIds.has(paraId));
1500
- if (patched === null || hasDirtyParagraph && patched === originalXml) {
1501
- if (hasDirtyParagraph) throw new DocxPackageFidelityError(`Cannot serialize changed ${elementName} paragraphs into ${file.name}`);
1502
- return;
1503
- }
1504
- if (patched === originalXml) return;
1505
- newZip.file(file.name, patched, {
1560
+ if (patchedXml === null || patchedXml === originalXml) return;
1561
+ newZip.file(file.name, patchedXml, {
1506
1562
  compression: "DEFLATE",
1507
1563
  compressionOptions: { level: compressionLevel }
1508
1564
  });
@@ -1,6 +1,6 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
- import { StyleMap } from "./styleParser.js";
3
2
  import { XmlElement } from "./xmlParser.js";
3
+ import { StyleMap } from "./styleParser.js";
4
4
  //#region src/docx/runParser.d.ts
5
5
  /**
6
6
  * `w:vertAlign` `baseline` is the reserved value that means "no vertical
@@ -1,9 +1,12 @@
1
1
  import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
2
+ import { NO_MODELLED_ATTRIBUTES, attributeRemainder } from "./attributeRemainder.js";
2
3
  import { parseDiagramPreview } from "./diagramPreview.js";
4
+ import { parseFieldState } from "./fieldState.js";
3
5
  import { isGroupDrawing, parseGroupDrawing } from "./groupDrawingParser.js";
4
6
  import { parseImage } from "./imageParser.js";
5
7
  import { imageRawXmlFingerprint } from "./imageRawXml.js";
6
8
  import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
9
+ import { preserveRunChild } from "./preservedRunContent.js";
7
10
  import { parseShading } from "./shadingParser.js";
8
11
  import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapeParser.js";
9
12
  import { isTextBoxDrawing } from "./textBoxParser.js";
@@ -11,7 +14,7 @@ import { resolveThemeFontRef } from "./themeParser.js";
11
14
  import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
12
15
  import { captureVerbatimXml } from "./verbatimCapture.js";
13
16
  import { parseVmlImageContent, shouldPreserveRawVmlPict } from "./vmlImageParser.js";
14
- import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, selectAlternateContentBranch } from "./xmlParser.js";
17
+ import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
15
18
  import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
16
19
  //#region src/docx/runParser.ts
17
20
  /**
@@ -419,17 +422,14 @@ function parseEndnoteReference(element) {
419
422
  */
420
423
  function parseFieldChar(element) {
421
424
  const fldCharType = getAttribute(element, "w", "fldCharType");
422
- const fldLock = parseOnOffAttribute(element, "w", "fldLock") === true;
423
- const dirty = parseOnOffAttribute(element, "w", "dirty") === true;
424
425
  let charType = "begin";
425
426
  if (fldCharType === "separate") charType = "separate";
426
427
  else if (fldCharType === "end") charType = "end";
427
428
  const content = {
428
429
  type: "fieldChar",
429
- charType
430
+ charType,
431
+ ...parseFieldState(element)
430
432
  };
431
- if (fldLock) content.fldLock = true;
432
- if (dirty) content.dirty = true;
433
433
  const numberingChange = findChild(element, "w", "numberingChange");
434
434
  if (numberingChange) {
435
435
  const original = getAttribute(numberingChange, "w", "original");
@@ -576,10 +576,11 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
576
576
  }
577
577
  case "object": {
578
578
  const objectPreview = parseVmlImageContent(child, rels, media, rootXmlns);
579
- if (objectPreview) contents.push(objectPreview);
579
+ contents.push(objectPreview ?? preserveRunChild(child));
580
580
  break;
581
581
  }
582
582
  case "rPr": break;
583
+ case "commentReference": break;
583
584
  case "lastRenderedPageBreak":
584
585
  contents.push({ type: "renderedPageBreak" });
585
586
  break;
@@ -637,11 +638,9 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
637
638
  if (contents.length === contentsBeforeAlternate && !hasTextBoxBranch) contents.push(preserveOnlyDrawing(captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns))));
638
639
  break;
639
640
  }
640
- case "footnoteRef":
641
- case "endnoteRef": break;
642
- case "separator":
643
- case "continuationSeparator": break;
644
- default: break;
641
+ default:
642
+ contents.push(preserveRunChild(child));
643
+ break;
645
644
  }
646
645
  return contents;
647
646
  }
@@ -668,6 +667,11 @@ function parseRun(node, styles, theme, rels = null, media = null, rootXmlns = {}
668
667
  if (propertyChangesResult) run.propertyChanges = propertyChangesResult;
669
668
  }
670
669
  run.content = parseRunContents(node, rels, media, mergeXmlnsDeclarations(rootXmlns, node));
670
+ const remainder = attributeRemainder({
671
+ element: node,
672
+ modelled: NO_MODELLED_ATTRIBUTES
673
+ });
674
+ if (remainder) run.preservedAttributes = remainder;
671
675
  return run;
672
676
  }
673
677
  /**
@@ -1,3 +1,4 @@
1
+ import { NO_MODELLED_ATTRIBUTES, attributeRemainder } from "./attributeRemainder.js";
1
2
  import { parseBorderSpec } from "./borderParser.js";
2
3
  import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
3
4
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
@@ -159,7 +160,8 @@ function parseSectionProperties(sectPr, context) {
159
160
  if (space !== void 0) props.columnSpace = space;
160
161
  const equalWidth = parseOnOffAttribute(cols, "w", "equalWidth");
161
162
  if (equalWidth !== void 0) props.equalWidth = equalWidth;
162
- if (parseOnOffAttribute(cols, "w", "sep") === true) props.separator = true;
163
+ const separator = parseOnOffAttribute(cols, "w", "sep");
164
+ if (separator !== void 0) props.separator = separator;
163
165
  const colElements = findChildren(cols, "w", "col");
164
166
  if (colElements.length > 0) {
165
167
  props.columns = [];
@@ -310,6 +312,11 @@ function parseSectionProperties(sectPr, context) {
310
312
  return change;
311
313
  });
312
314
  if (propertyChanges.length > 0) props.propertyChanges = propertyChanges;
315
+ const remainder = attributeRemainder({
316
+ element: sectPr,
317
+ modelled: NO_MODELLED_ATTRIBUTES
318
+ });
319
+ if (remainder) props.preservedAttributes = remainder;
313
320
  Object.defineProperty(props, unserializedSectionPropertyChildNames, {
314
321
  enumerable: true,
315
322
  value: unhandledChildNames
@@ -109,6 +109,24 @@ declare function buildPatchedDocumentXml(originalXml: string, serializedXml: str
109
109
  * the caller can fall back to preserving the original part verbatim.
110
110
  */
111
111
  declare function buildPatchedNoteXml(originalXml: string, serializedXml: string, changedIds: Set<string>): string | null;
112
+ /** One region of a part replaced by re-serialized XML. */
113
+ type XmlSplice = {
114
+ start: number;
115
+ end: number;
116
+ newXml: string;
117
+ };
118
+ /**
119
+ * Apply `splices` to `xml`, end-to-start so earlier offsets stay valid.
120
+ *
121
+ * Every selective patch is a splice, and every splice goes through here, so
122
+ * the refusal below is a property of the operation rather than a check each
123
+ * site has to remember. A patch rewrites the regions an edit touched and keeps
124
+ * the rest of the part byte-for-byte, so it can write half a comment range:
125
+ * invalid OOXML that anchors the comment to nothing. Answers null when it
126
+ * would, leaving the caller to rewrite a wider region — ultimately the whole
127
+ * part from the model, which is balanced with itself.
128
+ */
129
+ declare const spliceXml: (xml: string, splices: readonly XmlSplice[]) => string | null;
112
130
  type NoteElementName = "footnote" | "endnote";
113
131
  declare const collectChangedNoteParaIds: (baselineXml: string, currentXml: string) => Set<string>;
114
132
  type BuildPatchedNotePartXmlOptions = {
@@ -119,6 +137,25 @@ type BuildPatchedNotePartXmlOptions = {
119
137
  elementName: NoteElementName;
120
138
  changedParaIds?: ReadonlySet<string>;
121
139
  };
140
+ /** Why a note part could not be patched; see {@link NotePartPatch}. */
141
+ type NotePartPatchRefusal =
142
+ /** A note element, or a changed paragraph, was missing or ambiguous. */
143
+ "unroutable-paragraph" |
144
+ /** Even rewriting whole notes would leave a comment range with one half. */
145
+ "comment-range-balance";
146
+ /**
147
+ * What patching a note part produced. `refused` is not a failure to serialize:
148
+ * the caller writes the part from the model instead, the note-part reading of
149
+ * the fall back to a full repack the document story takes when its own splice
150
+ * is refused.
151
+ */
152
+ type NotePartPatch = {
153
+ type: "patched";
154
+ xml: string;
155
+ } | {
156
+ type: "refused";
157
+ reason: NotePartPatchRefusal;
158
+ };
122
159
  /**
123
160
  * Patch an existing note part from its model serialization.
124
161
  *
@@ -130,8 +167,15 @@ type BuildPatchedNotePartXmlOptions = {
130
167
  * notes, and unaffected equal-shape paragraphs remain byte-exact.
131
168
  * `replacementXml` also supplies synthesized automatic note-reference marks,
132
169
  * which the parsed model intentionally omits.
170
+ *
171
+ * A comment can be anchored on a note's own text, so its range spans that
172
+ * note's paragraphs and an edit inside the span moves a range half from one
173
+ * paragraph to another. Replacing only the dirty paragraph then drops the half
174
+ * it held and leaves the other standing, so {@link spliceXml} refuses the
175
+ * result and the changed notes are rewritten whole instead — model content on
176
+ * both sides of the range, with every other note still byte-exact.
133
177
  */
134
- declare function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, replacementXml, elementName, changedParaIds }: BuildPatchedNotePartXmlOptions): string | null;
178
+ declare function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, replacementXml, elementName, changedParaIds }: BuildPatchedNotePartXmlOptions): NotePartPatch;
135
179
  type ChangedNumberingDefs = {
136
180
  abstractNums: Set<string>;
137
181
  nums: Set<string>;
@@ -178,4 +222,4 @@ declare const patchNumberingDefinitions: ({ originalXml, baselineXml, currentXml
178
222
  */
179
223
  declare function appendNumberingDefs(xml: string, currentXml: string, added: ChangedNumberingDefs): string | null;
180
224
  //#endregion
181
- export { ChangedNumberingDefs, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, patchNumberingDefinitions, validatePatchSafety };
225
+ export { ChangedNumberingDefs, NotePartPatch, NotePartPatchRefusal, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, XmlSplice, appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, patchNumberingDefinitions, spliceXml, validatePatchSafety };