@stll/folio-core 0.43.0 → 0.44.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 (183) hide show
  1. package/dist/ai-edits/headless.js +6 -5
  2. package/dist/ai-edits/index.d.ts +2 -2
  3. package/dist/ai-edits/index.js +2 -2
  4. package/dist/ai-edits/snapshot.js +13 -9
  5. package/dist/compare/content-alignment.js +16 -1
  6. package/dist/compare/inline-atoms.js +1 -1
  7. package/dist/compare/style-resources.js +6 -0
  8. package/dist/docx/appVersionNormalization.d.ts +0 -18
  9. package/dist/docx/blockContentParser.js +8 -0
  10. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  11. package/dist/docx/blockRangeMarkers.js +59 -0
  12. package/dist/docx/bookmarkParser.d.ts +2 -20
  13. package/dist/docx/bookmarkParser.js +6 -30
  14. package/dist/docx/borderParser.d.ts +13 -0
  15. package/dist/docx/borderParser.js +71 -0
  16. package/dist/docx/builtInStyles.d.ts +165 -0
  17. package/dist/docx/builtInStyles.js +239 -0
  18. package/dist/docx/commentIdNormalization.d.ts +3 -1
  19. package/dist/docx/commentIdNormalization.js +18 -1
  20. package/dist/docx/commentParser.d.ts +2 -1
  21. package/dist/docx/commentParser.js +34 -7
  22. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  23. package/dist/docx/commentReferenceNormalization.js +23 -14
  24. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  25. package/dist/docx/danglingRelationshipReferences.js +30 -0
  26. package/dist/docx/defaultParagraphStyle.d.ts +18 -1
  27. package/dist/docx/defaultParagraphStyle.js +23 -1
  28. package/dist/docx/documentParser.d.ts +2 -1
  29. package/dist/docx/documentParser.js +2 -2
  30. package/dist/docx/drawingUtils.d.ts +8 -1
  31. package/dist/docx/drawingUtils.js +12 -3
  32. package/dist/docx/fieldParser.js +3 -5
  33. package/dist/docx/footnoteParser.d.ts +3 -2
  34. package/dist/docx/footnoteParser.js +19 -4
  35. package/dist/docx/groupDrawingParser.js +1 -1
  36. package/dist/docx/headerFooterRefParser.d.ts +4 -3
  37. package/dist/docx/headerFooterRefParser.js +42 -12
  38. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  39. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  40. package/dist/docx/hyperlinkParser.js +11 -15
  41. package/dist/docx/imageParser.d.ts +1 -1
  42. package/dist/docx/imageParser.js +22 -18
  43. package/dist/docx/imageRawXml.js +5 -5
  44. package/dist/docx/markupRangeMarker.d.ts +15 -0
  45. package/dist/docx/markupRangeMarker.js +44 -0
  46. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  47. package/dist/docx/noteReferenceStyles.js +70 -0
  48. package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
  49. package/dist/docx/numberingReferenceNormalization.js +20 -1
  50. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  51. package/dist/docx/paragraphParser.js +66 -99
  52. package/dist/docx/paragraphPropertySource.js +1 -0
  53. package/dist/docx/paragraphTraversal.d.ts +37 -1
  54. package/dist/docx/paragraphTraversal.js +84 -1
  55. package/dist/docx/parseContext.d.ts +37 -0
  56. package/dist/docx/parseContext.js +67 -0
  57. package/dist/docx/parseWarningMessage.d.ts +6 -0
  58. package/dist/docx/parseWarningMessage.js +44 -0
  59. package/dist/docx/parser.js +81 -27
  60. package/dist/docx/relsParser.d.ts +28 -11
  61. package/dist/docx/relsParser.js +26 -13
  62. package/dist/docx/revisionIdNormalization.js +81 -7
  63. package/dist/docx/rezip.js +63 -22
  64. package/dist/docx/runConsolidator.js +1 -2
  65. package/dist/docx/runParser.d.ts +8 -1
  66. package/dist/docx/runParser.js +30 -48
  67. package/dist/docx/sectionParser.d.ts +2 -1
  68. package/dist/docx/sectionParser.js +21 -65
  69. package/dist/docx/serializer/borderSerializer.d.ts +1 -2
  70. package/dist/docx/serializer/commentSerializer.js +22 -9
  71. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  72. package/dist/docx/serializer/documentSerializer.js +6 -16
  73. package/dist/docx/serializer/headerFooterSerializer.js +5 -0
  74. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  75. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  76. package/dist/docx/serializer/noteSerializer.js +5 -0
  77. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  78. package/dist/docx/serializer/paragraphSerializer.js +29 -35
  79. package/dist/docx/serializer/runSerializer.js +13 -7
  80. package/dist/docx/serializer/tableSerializer.js +28 -13
  81. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  82. package/dist/docx/server/build.js +8 -1
  83. package/dist/docx/server/createBilingualDocument.js +10 -18
  84. package/dist/docx/server/extractDocxText.js +3 -4
  85. package/dist/docx/shadingParser.d.ts +6 -0
  86. package/dist/docx/shadingParser.js +32 -0
  87. package/dist/docx/shapeParser.js +3 -3
  88. package/dist/docx/styleParser.js +13 -87
  89. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  90. package/dist/docx/styleReferenceResolution.js +51 -0
  91. package/dist/docx/tableLook.d.ts +57 -0
  92. package/dist/docx/tableLook.js +63 -0
  93. package/dist/docx/tableParser.d.ts +7 -9
  94. package/dist/docx/tableParser.js +64 -110
  95. package/dist/docx/textBoxParser.js +4 -4
  96. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  97. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  98. package/dist/docx/transitionalSpelling.d.ts +13 -2
  99. package/dist/docx/transitionalSpelling.js +23 -1
  100. package/dist/docx/verbatimCapture.js +4 -11
  101. package/dist/docx/vmlImageParser.js +2 -2
  102. package/dist/docx/watermarkParser.js +2 -2
  103. package/dist/docx/xmlParser.d.ts +22 -32
  104. package/dist/docx/xmlParser.js +36 -21
  105. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  106. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  107. package/dist/internal/paragraphFormattingSerialization.js +26 -6
  108. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  109. package/dist/layout-engine/index.d.ts +2 -2
  110. package/dist/layout-engine/index.js +2 -2
  111. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  112. package/dist/layout-engine/types.d.ts +8 -2
  113. package/dist/layout-engine/types.js +35 -2
  114. package/dist/markdown/index.js +1 -1
  115. package/dist/markdown/internals.d.ts +6 -1
  116. package/dist/markdown/internals.js +14 -1
  117. package/dist/markdown/renderBlock.js +35 -21
  118. package/dist/markdown/renderParagraph.js +14 -5
  119. package/dist/markdown/renderRuns.js +4 -3
  120. package/dist/markdown/renderTable.js +4 -3
  121. package/dist/markdown/trailers.js +41 -7
  122. package/dist/markdown/types.d.ts +3 -7
  123. package/dist/prosemirror/attrs/index.js +2 -5
  124. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  125. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  126. package/dist/prosemirror/commands/index.d.ts +3 -3
  127. package/dist/prosemirror/commands/index.js +2 -2
  128. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  129. package/dist/prosemirror/commands/paragraph.js +2 -2
  130. package/dist/prosemirror/commentIdAllocator.js +2 -7
  131. package/dist/prosemirror/conversion/fromProseDoc.js +129 -40
  132. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  133. package/dist/prosemirror/conversion/toProseDoc.js +375 -316
  134. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  135. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  136. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  137. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  138. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  139. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  140. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  141. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  142. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  143. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  144. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  145. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  146. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  147. package/dist/prosemirror/extensions/types.d.ts +2 -2
  148. package/dist/prosemirror/index.d.ts +3 -3
  149. package/dist/prosemirror/index.js +3 -3
  150. package/dist/prosemirror/insertOperations.d.ts +9 -2
  151. package/dist/prosemirror/insertOperations.js +9 -4
  152. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +159 -0
  153. package/dist/prosemirror/paragraphFormattingProvenance.js +106 -0
  154. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  155. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  156. package/dist/prosemirror/plugins/index.d.ts +2 -2
  157. package/dist/prosemirror/plugins/index.js +2 -2
  158. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  159. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  160. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  161. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  162. package/dist/prosemirror/schema/nodes.d.ts +31 -0
  163. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  164. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  165. package/dist/prosemirror/styles/styleResolver.js +12 -0
  166. package/dist/style-engine/styleEngine.d.ts +3 -0
  167. package/dist/style-engine/styleEngine.js +3 -0
  168. package/dist/style-sets/extract.js +1 -23
  169. package/dist/style-sets/stellaStyle.js +46 -39
  170. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  171. package/dist/style-sets/styleSetNormalization.js +99 -0
  172. package/dist/types/content.d.ts +2 -2
  173. package/dist/utils/createDocument.js +145 -20
  174. package/dist/utils/headingCollector.d.ts +8 -5
  175. package/dist/utils/headingCollector.js +23 -25
  176. package/dist/utils/tableOfContentsStyle.js +9 -2
  177. package/package.json +2 -2
  178. package/dist/docx/textWhitespace.d.ts +0 -4
  179. package/dist/docx/textWhitespace.js +0 -4
  180. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  181. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  182. package/dist/markdown/headings.d.ts +0 -13
  183. package/dist/markdown/headings.js +0 -20
@@ -10,6 +10,7 @@ import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
10
10
  import { parseHeaderFooterType } from "./headerFooterRefParser.js";
11
11
  import { assertValidFolioDocumentModel } from "./modelValidation.js";
12
12
  import { isNewDataUrlDrawing } from "./newImage.js";
13
+ import { missingNoteReferenceStyles, noteReferenceNeeds } from "./noteReferenceStyles.js";
13
14
  import { parseNumbering } from "./numberingParser.js";
14
15
  import { isNumberingReference } from "./numberingReference.js";
15
16
  import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
@@ -1389,6 +1390,42 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
1389
1390
  }
1390
1391
  const STYLES_PART_PATH = "word/styles.xml";
1391
1392
  const STYLES_CLOSE_ROOT = "</w:styles>";
1393
+ /**
1394
+ * The style table a package folio creates starts from: `docDefaults` and
1395
+ * `Normal`, and nothing else. A document that carries its own style table
1396
+ * replaces this part wholesale.
1397
+ */
1398
+ const SEED_STYLES_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1399
+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1400
+ <w:docDefaults>
1401
+ <w:rPrDefault>
1402
+ <w:rPr>
1403
+ <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/>
1404
+ <w:sz w:val="22"/>
1405
+ </w:rPr>
1406
+ </w:rPrDefault>
1407
+ <w:pPrDefault>
1408
+ <w:pPr>
1409
+ <w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
1410
+ </w:pPr>
1411
+ </w:pPrDefault>
1412
+ </w:docDefaults>
1413
+ <w:style w:type="paragraph" w:default="1" w:styleId="Normal">
1414
+ <w:name w:val="Normal"/>
1415
+ </w:style>
1416
+ </w:styles>`;
1417
+ /**
1418
+ * The seed part plus the reference styles this package's comments and notes
1419
+ * need. A document with no style table of its own never reaches
1420
+ * {@link styleDefinitionsToSerialize}, so without this its reference marks
1421
+ * would carry a `w:rStyle` naming nothing — the defect
1422
+ * `noteReferenceStyles.ts` exists to remove, on the one path that skips it.
1423
+ */
1424
+ const seedStylesXmlWith = (missing) => {
1425
+ if (missing.length === 0) return SEED_STYLES_XML;
1426
+ const rootClose = SEED_STYLES_XML.lastIndexOf(STYLES_CLOSE_ROOT);
1427
+ return SEED_STYLES_XML.slice(0, rootClose) + missing.map(serializeStyle).join("") + SEED_STYLES_XML.slice(rootClose);
1428
+ };
1392
1429
  const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
1393
1430
  /**
1394
1431
  * Append styles the model defines but the original `word/styles.xml` lacks.
@@ -1397,6 +1434,27 @@ const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
1397
1434
  * per-language clones a bilingual transform adds, are emitted before the root
1398
1435
  * close so paragraphs referencing them resolve on reopen.
1399
1436
  */
1437
+ /**
1438
+ * The styles to write into a package folio is authoring: the model's, plus any
1439
+ * reference character style the serializers are about to emit for this
1440
+ * package's comments and notes but the style table does not define. See
1441
+ * `noteReferenceStyles.ts` — the reference mark would otherwise carry a
1442
+ * `w:rStyle` pointing at nothing.
1443
+ *
1444
+ * Only for a package folio writes from scratch. Repacking a document someone
1445
+ * else authored preserves `word/styles.xml` byte for byte, and adding a
1446
+ * definition there would rewrite a part the user never edited: their document,
1447
+ * their style table, missing reference style included.
1448
+ */
1449
+ const styleDefinitionsToSerialize = (doc) => {
1450
+ const styles = doc.package.styles;
1451
+ if (!styles) return;
1452
+ const missing = missingNoteReferenceStyles(styles, noteReferenceNeeds(doc.package));
1453
+ return missing.length === 0 ? styles : {
1454
+ ...styles,
1455
+ styles: [...styles.styles, ...missing]
1456
+ };
1457
+ };
1400
1458
  async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compressionLevel) {
1401
1459
  const styles = doc.package.styles;
1402
1460
  if (!styles || styles.styles.length === 0) return;
@@ -1578,25 +1636,7 @@ const createEmptyDocxZip = ({ creator, application }) => {
1578
1636
  </w:sectPr>
1579
1637
  </w:body>
1580
1638
  </w:document>`);
1581
- zip.file("word/styles.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1582
- <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1583
- <w:docDefaults>
1584
- <w:rPrDefault>
1585
- <w:rPr>
1586
- <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/>
1587
- <w:sz w:val="22"/>
1588
- </w:rPr>
1589
- </w:rPrDefault>
1590
- <w:pPrDefault>
1591
- <w:pPr>
1592
- <w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
1593
- </w:pPr>
1594
- </w:pPrDefault>
1595
- </w:docDefaults>
1596
- <w:style w:type="paragraph" w:default="1" w:styleId="Normal">
1597
- <w:name w:val="Normal"/>
1598
- </w:style>
1599
- </w:styles>`);
1639
+ zip.file(STYLES_PART_PATH, SEED_STYLES_XML);
1600
1640
  const now = (/* @__PURE__ */ new Date()).toISOString();
1601
1641
  const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${escapeXml(creator)}</dc:creator>`;
1602
1642
  zip.file("docProps/core.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -1647,10 +1687,11 @@ const createDocumentSeedZip = async (doc, properties) => {
1647
1687
  const relationships = ["<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>"];
1648
1688
  const overrides = [];
1649
1689
  let nextRelationshipId = 2;
1650
- if (doc.package.styles) {
1690
+ const styleDefinitions = styleDefinitionsToSerialize(doc);
1691
+ if (styleDefinitions) {
1651
1692
  assertStyleNumberingReferences(doc);
1652
- zip.file("word/styles.xml", serializeStylesXml(doc.package.styles));
1653
- }
1693
+ zip.file(STYLES_PART_PATH, serializeStylesXml(styleDefinitions));
1694
+ } else zip.file(STYLES_PART_PATH, seedStylesXmlWith(missingNoteReferenceStyles(void 0, noteReferenceNeeds(doc.package))));
1654
1695
  const numbering = doc.package.numbering;
1655
1696
  if (numbering && (numbering.abstractNums.length > 0 || numbering.nums.length > 0)) {
1656
1697
  zip.file("word/numbering.xml", serializeNumberingXml(numbering));
@@ -114,8 +114,7 @@ function mergeRunContent(content1, content2) {
114
114
  if (lastText?.type === "text" && firstText?.type === "text") {
115
115
  result[result.length - 1] = {
116
116
  type: "text",
117
- text: lastText.text + firstText.text,
118
- ...lastText.preserveSpace || firstText.preserveSpace ? { preserveSpace: true } : {}
117
+ text: lastText.text + firstText.text
119
118
  };
120
119
  for (let i = 1; i < content2.length; i++) result.push(content2[i]);
121
120
  } else for (const c of content2) result.push(c);
@@ -2,6 +2,13 @@ import { document_d_exports } from "../types/document.js";
2
2
  import { StyleMap } from "./styleParser.js";
3
3
  import { XmlElement } from "./xmlParser.js";
4
4
  //#region src/docx/runParser.d.ts
5
+ /**
6
+ * `w:vertAlign` `baseline` is the reserved value that means "no vertical
7
+ * offset", not a third offset beside `superscript` and `subscript`. It is
8
+ * answered here because this module reads the slot, so a consumer deciding
9
+ * whether a run sits on the baseline cannot drift from how it was parsed.
10
+ */
11
+ declare const isBaselineVertAlign: (vertAlign: document_d_exports.TextFormatting["vertAlign"]) => boolean;
5
12
  /**
6
13
  * Parse run formatting properties (w:rPr)
7
14
  *
@@ -74,4 +81,4 @@ declare function hasFieldChar(run: document_d_exports.Run): boolean;
74
81
  */
75
82
  declare function getFieldCharType(run: document_d_exports.Run): "begin" | "separate" | "end" | null;
76
83
  //#endregion
77
- export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, parseRun, parseRunProperties };
84
+ export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, isBaselineVertAlign, parseRun, parseRunProperties };
@@ -1,18 +1,17 @@
1
- import { isValidHexColor } from "../utils/colorResolver.js";
2
1
  import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
3
2
  import { parseDiagramPreview } from "./diagramPreview.js";
4
3
  import { isGroupDrawing, parseGroupDrawing } from "./groupDrawingParser.js";
5
4
  import { parseImage } from "./imageParser.js";
6
5
  import { imageRawXmlFingerprint } from "./imageRawXml.js";
7
- import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema, ShadingPatternSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
6
+ import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
7
+ import { parseShading } from "./shadingParser.js";
8
8
  import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapeParser.js";
9
9
  import { isTextBoxDrawing } from "./textBoxParser.js";
10
- import { requiresXmlSpacePreserve } from "./textWhitespace.js";
11
10
  import { resolveThemeFontRef } from "./themeParser.js";
12
11
  import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
13
12
  import { captureVerbatimXml } from "./verbatimCapture.js";
14
13
  import { parseVmlImageContent, shouldPreserveRawVmlPict } from "./vmlImageParser.js";
15
- import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
14
+ import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, selectAlternateContentBranch } from "./xmlParser.js";
16
15
  import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
17
16
  //#region src/docx/runParser.ts
18
17
  /**
@@ -36,31 +35,6 @@ function parseColorValue(rgb, themeColor, themeTint, themeShade) {
36
35
  if (themeShade) color.themeShade = themeShade;
37
36
  return color;
38
37
  }
39
- /**
40
- * Parse shading properties (w:shd)
41
- */
42
- function parseShadingProperties(shd) {
43
- if (!shd) return;
44
- const props = {};
45
- const color = getAttribute(shd, "w", "color");
46
- if (color === "auto") props.color = { auto: true };
47
- else if (color && isValidHexColor(color)) props.color = { rgb: color };
48
- const fill = getAttribute(shd, "w", "fill");
49
- if (fill === "auto") props.fill = { auto: true };
50
- else if (fill && isValidHexColor(fill)) props.fill = { rgb: fill };
51
- const validatedThemeFill = narrowEnum(getAttribute(shd, "w", "themeFill"), ThemeColorSlotSchema);
52
- if (validatedThemeFill) {
53
- if (!props.fill) props.fill = {};
54
- props.fill.themeColor = validatedThemeFill;
55
- }
56
- const themeFillTint = getAttribute(shd, "w", "themeFillTint");
57
- if (themeFillTint && props.fill) props.fill.themeTint = themeFillTint;
58
- const themeFillShade = getAttribute(shd, "w", "themeFillShade");
59
- if (themeFillShade && props.fill) props.fill.themeShade = themeFillShade;
60
- const pattern = narrowEnum(getAttribute(shd, "w", "val"), ShadingPatternSchema);
61
- if (pattern) props.pattern = pattern;
62
- return Object.keys(props).length > 0 ? props : void 0;
63
- }
64
38
  function collectFirstRunPropertyChildren(rPr) {
65
39
  const children = {};
66
40
  for (const child of rPr.elements ?? []) {
@@ -167,6 +141,13 @@ function collectFirstRunPropertyChildren(rPr) {
167
141
  return children;
168
142
  }
169
143
  /**
144
+ * `w:vertAlign` `baseline` is the reserved value that means "no vertical
145
+ * offset", not a third offset beside `superscript` and `subscript`. It is
146
+ * answered here because this module reads the slot, so a consumer deciding
147
+ * whether a run sits on the baseline cannot drift from how it was parsed.
148
+ */
149
+ const isBaselineVertAlign = (vertAlign) => vertAlign === "baseline";
150
+ /**
170
151
  * Parse run formatting properties (w:rPr)
171
152
  *
172
153
  * Handles ALL rPr properties:
@@ -231,7 +212,7 @@ function parseRunProperties(rPr, theme, _styles) {
231
212
  }
232
213
  const shd = propertyChildren.shd;
233
214
  if (shd) {
234
- const shadingResult = parseShadingProperties(shd);
215
+ const shadingResult = parseShading(shd);
235
216
  if (shadingResult) formatting.shading = shadingResult;
236
217
  }
237
218
  const sz = propertyChildren.sz;
@@ -370,14 +351,10 @@ function parseRunPropertyChanges(rPr, theme, styles, currentFormatting) {
370
351
  * Parse text content (w:t)
371
352
  */
372
353
  function parseTextContent(element) {
373
- const text = getTextContent(element);
374
- const preserveSpace = getAttribute(element, "xml", "space") === "preserve" || requiresXmlSpacePreserve(text);
375
- const content = {
354
+ return {
376
355
  type: "text",
377
- text
356
+ text: getTextContent(element)
378
357
  };
379
- if (preserveSpace) content.preserveSpace = true;
380
- return content;
381
358
  }
382
359
  /**
383
360
  * Parse tab element (w:tab)
@@ -442,8 +419,8 @@ function parseEndnoteReference(element) {
442
419
  */
443
420
  function parseFieldChar(element) {
444
421
  const fldCharType = getAttribute(element, "w", "fldCharType");
445
- const fldLock = getAttribute(element, "w", "fldLock") === "true" || getAttribute(element, "w", "fldLock") === "1";
446
- const dirty = getAttribute(element, "w", "dirty") === "true" || getAttribute(element, "w", "dirty") === "1";
422
+ const fldLock = parseOnOffAttribute(element, "w", "fldLock") === true;
423
+ const dirty = parseOnOffAttribute(element, "w", "dirty") === true;
447
424
  let charType = "begin";
448
425
  if (fldCharType === "separate") charType = "separate";
449
426
  else if (fldCharType === "end") charType = "end";
@@ -473,15 +450,14 @@ function parseInstrText(element) {
473
450
  * Wrap raw XML the model cannot project at all.
474
451
  *
475
452
  * `DrawingContent` always carries an `Image`, so preservation-only content
476
- * gets a placeholder one: the empty `rId` marks it as backed by no
477
- * relationship, which keeps `classifyDrawingSafety` and the serializer on the
478
- * replay path instead of regenerating DrawingML from the placeholder.
453
+ * gets a placeholder one. It names no relationship, which keeps
454
+ * `classifyDrawingSafety` and the serializer on the replay path instead of
455
+ * regenerating DrawingML from the placeholder.
479
456
  */
480
457
  const preserveOnlyDrawing = (rawXml) => ({
481
458
  type: "drawing",
482
459
  image: {
483
460
  type: "image",
484
- rId: "",
485
461
  size: {
486
462
  width: 0,
487
463
  height: 0
@@ -530,13 +506,19 @@ function parseDrawingContent(element, rels, media) {
530
506
  };
531
507
  const image = parseImage(element, rels ?? void 0, media ?? void 0);
532
508
  if (!image) return null;
533
- const drawing = {
509
+ const rawXml = captureVerbatimXml(element);
510
+ if (image.rId === void 0) return {
511
+ type: "drawing",
512
+ image,
513
+ rawXml,
514
+ rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
515
+ };
516
+ return {
534
517
  type: "drawing",
535
- image
518
+ image,
519
+ rawXml,
520
+ rawImageFingerprint: imageRawXmlFingerprint(image)
536
521
  };
537
- drawing.rawXml = captureVerbatimXml(element);
538
- drawing.rawImageFingerprint = imageRawXmlFingerprint(image);
539
- return drawing;
540
522
  }
541
523
  /**
542
524
  * Parse all content within a run element
@@ -750,4 +732,4 @@ function getFieldCharType(run) {
750
732
  return run.content.find((c) => c.type === "fieldChar")?.charType ?? null;
751
733
  }
752
734
  //#endregion
753
- export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, parseRun, parseRunProperties };
735
+ export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, isBaselineVertAlign, parseRun, parseRunProperties };
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { ParseContext } from "./parseContext.js";
2
3
  import { XmlElement } from "./xmlParser.js";
3
4
  //#region src/docx/sectionParser.d.ts
4
5
  declare function getUnserializedSectionPropertyChildNames(props: document_d_exports.SectionProperties): readonly string[] | undefined;
@@ -8,7 +9,7 @@ declare function getUnserializedSectionPropertyChildNames(props: document_d_expo
8
9
  * @param sectPr - The w:sectPr element
9
10
  * @returns SectionProperties object
10
11
  */
11
- declare function parseSectionProperties(sectPr: XmlElement | null): document_d_exports.SectionProperties;
12
+ declare function parseSectionProperties(sectPr: XmlElement | null, context?: ParseContext): document_d_exports.SectionProperties;
12
13
  /**
13
14
  * Get page width in pixels (96 DPI)
14
15
  *
@@ -1,9 +1,10 @@
1
+ import { parseBorderSpec } from "./borderParser.js";
1
2
  import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
2
3
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
3
- import { BorderStyleSchema, NumberFormatSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
4
+ import { NumberFormatSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
4
5
  import { parseSectionReferenceHistory } from "./sectionReferenceHistory.js";
5
6
  import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
6
- import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
7
+ import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute } from "./xmlParser.js";
7
8
  //#region src/docx/sectionParser.ts
8
9
  /**
9
10
  * Sanity cap on `w:cols/@w:num`. Word's column picker tops out well below
@@ -49,49 +50,6 @@ function getUnserializedSectionPropertyChildNames(props) {
49
50
  return Array.isArray(descriptor?.value) ? descriptor.value : void 0;
50
51
  }
51
52
  /**
52
- * Parse a color element/attribute for page borders/background
53
- */
54
- function parseColorValue(colorStr, themeColor, themeTint, themeShade) {
55
- if (!colorStr && !themeColor) return;
56
- const color = {};
57
- if (colorStr && colorStr !== "auto") color.rgb = colorStr;
58
- else if (colorStr === "auto") color.auto = true;
59
- const validatedThemeColor = narrowEnum(themeColor, ThemeColorSlotSchema);
60
- if (validatedThemeColor) color.themeColor = validatedThemeColor;
61
- if (themeTint) color.themeTint = themeTint;
62
- if (themeShade) color.themeShade = themeShade;
63
- return Object.keys(color).length > 0 ? color : void 0;
64
- }
65
- /**
66
- * Parse a border element for page borders
67
- */
68
- function parseBorderSpec(element) {
69
- if (!element) return;
70
- const rawStyle = getAttribute(element, "w", "val") ?? "none";
71
- const border = { style: narrowEnum(rawStyle, BorderStyleSchema) ?? rawStyle };
72
- const sz = parseNumericAttribute(element, "w", "sz");
73
- if (sz !== void 0) border.size = sz;
74
- const space = parseNumericAttribute(element, "w", "space");
75
- if (space !== void 0) border.space = space;
76
- const color = parseColorValue(getAttribute(element, "w", "color"), getAttribute(element, "w", "themeColor"), getAttribute(element, "w", "themeTint"), getAttribute(element, "w", "themeShade"));
77
- if (color) border.color = color;
78
- const shadow = getAttribute(element, "w", "shadow");
79
- if (shadow === "1" || shadow === "true") border.shadow = true;
80
- const frame = getAttribute(element, "w", "frame");
81
- if (frame === "1" || frame === "true") border.frame = true;
82
- const artRelationshipId = getAttribute(element, "w", "id")?.trim();
83
- if (artRelationshipId) border.artRelationshipId = artRelationshipId;
84
- const topLeftArtRelationshipId = getAttribute(element, "w", "topLeft")?.trim();
85
- if (topLeftArtRelationshipId) border.topLeftArtRelationshipId = topLeftArtRelationshipId;
86
- const topRightArtRelationshipId = getAttribute(element, "w", "topRight")?.trim();
87
- if (topRightArtRelationshipId) border.topRightArtRelationshipId = topRightArtRelationshipId;
88
- const bottomLeftArtRelationshipId = getAttribute(element, "w", "bottomLeft")?.trim();
89
- if (bottomLeftArtRelationshipId) border.bottomLeftArtRelationshipId = bottomLeftArtRelationshipId;
90
- const bottomRightArtRelationshipId = getAttribute(element, "w", "bottomRight")?.trim();
91
- if (bottomRightArtRelationshipId) border.bottomRightArtRelationshipId = bottomRightArtRelationshipId;
92
- return border;
93
- }
94
- /**
95
53
  * Parse page orientation
96
54
  */
97
55
  function parseOrientation(orient) {
@@ -160,7 +118,7 @@ function parseLineNumberRestart(restart) {
160
118
  * @param sectPr - The w:sectPr element
161
119
  * @returns SectionProperties object
162
120
  */
163
- function parseSectionProperties(sectPr) {
121
+ function parseSectionProperties(sectPr, context) {
164
122
  const props = {};
165
123
  if (!sectPr) return props;
166
124
  const unhandledChildNames = getChildElements(sectPr).map((child) => getLocalName(child.name ?? "")).filter((name) => !serializedSectionPropertyChildNames.has(name));
@@ -199,11 +157,9 @@ function parseSectionProperties(sectPr) {
199
157
  if (num !== void 0) props.columnCount = Math.min(num, MAX_SECTION_COLUMNS);
200
158
  const space = parseNumericAttribute(cols, "w", "space");
201
159
  if (space !== void 0) props.columnSpace = space;
202
- const equalWidth = getAttribute(cols, "w", "equalWidth");
203
- if (equalWidth === "1" || equalWidth === "true") props.equalWidth = true;
204
- else if (equalWidth === "0" || equalWidth === "false") props.equalWidth = false;
205
- const sep = getAttribute(cols, "w", "sep");
206
- if (sep === "1" || sep === "true") props.separator = true;
160
+ const equalWidth = parseOnOffAttribute(cols, "w", "equalWidth");
161
+ if (equalWidth !== void 0) props.equalWidth = equalWidth;
162
+ if (parseOnOffAttribute(cols, "w", "sep") === true) props.separator = true;
207
163
  const colElements = findChildren(cols, "w", "col");
208
164
  if (colElements.length > 0) {
209
165
  props.columns = [];
@@ -235,15 +191,15 @@ function parseSectionProperties(sectPr) {
235
191
  else addUnhandledChildName("textDirection");
236
192
  }
237
193
  const bidi = findChild(sectPr, "w", "bidi");
238
- if (bidi) props.bidi = parseBooleanElement(bidi);
239
- const headerRefs = findChildren(sectPr, "w", "headerReference");
240
- if (headerRefs.length > 0) props.headerReferences = headerRefs.map((el) => parseHeaderReference(el));
241
- const footerRefs = findChildren(sectPr, "w", "footerReference");
242
- if (footerRefs.length > 0) props.footerReferences = footerRefs.map((el) => parseFooterReference(el));
194
+ if (bidi) props.bidi = parseBooleanElement(bidi, "w", context);
195
+ const headerRefs = findChildren(sectPr, "w", "headerReference").flatMap((el) => parseHeaderReference(el, context) ?? []);
196
+ if (headerRefs.length > 0) props.headerReferences = headerRefs;
197
+ const footerRefs = findChildren(sectPr, "w", "footerReference").flatMap((el) => parseFooterReference(el, context) ?? []);
198
+ if (footerRefs.length > 0) props.footerReferences = footerRefs;
243
199
  const titlePg = findChild(sectPr, "w", "titlePg");
244
- if (titlePg) props.titlePg = parseBooleanElement(titlePg);
200
+ if (titlePg) props.titlePg = parseBooleanElement(titlePg, "w", context);
245
201
  const evenAndOddHeaders = findChild(sectPr, "w", "evenAndOddHeaders");
246
- if (evenAndOddHeaders) props.evenAndOddHeaders = parseBooleanElement(evenAndOddHeaders);
202
+ if (evenAndOddHeaders) props.evenAndOddHeaders = parseBooleanElement(evenAndOddHeaders, "w", context);
247
203
  const lnNumType = findChild(sectPr, "w", "lnNumType");
248
204
  if (lnNumType) {
249
205
  props.lineNumbers = {};
@@ -272,13 +228,13 @@ function parseSectionProperties(sectPr) {
272
228
  const pgBorders = findChild(sectPr, "w", "pgBorders");
273
229
  if (pgBorders) {
274
230
  props.pageBorders = {};
275
- const topBorder = parseBorderSpec(findChild(pgBorders, "w", "top"));
231
+ const topBorder = parseBorderSpec(findChild(pgBorders, "w", "top"), context);
276
232
  if (topBorder) props.pageBorders.top = topBorder;
277
- const bottomBorder = parseBorderSpec(findChild(pgBorders, "w", "bottom"));
233
+ const bottomBorder = parseBorderSpec(findChild(pgBorders, "w", "bottom"), context);
278
234
  if (bottomBorder) props.pageBorders.bottom = bottomBorder;
279
- const leftBorder = parseBorderSpec(findChild(pgBorders, "w", "left"));
235
+ const leftBorder = parseBorderSpec(findChild(pgBorders, "w", "left"), context);
280
236
  if (leftBorder) props.pageBorders.left = leftBorder;
281
- const rightBorder = parseBorderSpec(findChild(pgBorders, "w", "right"));
237
+ const rightBorder = parseBorderSpec(findChild(pgBorders, "w", "right"), context);
282
238
  if (rightBorder) props.pageBorders.right = rightBorder;
283
239
  const display = getAttribute(pgBorders, "w", "display");
284
240
  if (display === "allPages" || display === "firstPage" || display === "notFirstPage") props.pageBorders.display = display;
@@ -315,11 +271,11 @@ function parseSectionProperties(sectPr) {
315
271
  if (Object.keys(enProps).length > 0) props.endnotePr = enProps;
316
272
  }
317
273
  const formProt = findChild(sectPr, "w", "formProt");
318
- if (formProt) props.formProtection = parseBooleanElement(formProt);
274
+ if (formProt) props.formProtection = parseBooleanElement(formProt, "w", context);
319
275
  const noEndnote = findChild(sectPr, "w", "noEndnote");
320
- if (noEndnote) props.noEndnote = parseBooleanElement(noEndnote);
276
+ if (noEndnote) props.noEndnote = parseBooleanElement(noEndnote, "w", context);
321
277
  const rtlGutter = findChild(sectPr, "w", "rtlGutter");
322
- if (rtlGutter) props.rtlGutter = parseBooleanElement(rtlGutter);
278
+ if (rtlGutter) props.rtlGutter = parseBooleanElement(rtlGutter, "w", context);
323
279
  const docGrid = findChild(sectPr, "w", "docGrid");
324
280
  if (docGrid) {
325
281
  props.docGrid = {};
@@ -1,8 +1,7 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
2
  //#region src/docx/serializer/borderSerializer.d.ts
3
- type ExhaustiveFields<Source, Classified extends keyof Source> = Exclude<keyof Source, Classified> extends never ? Source : never;
4
3
  type ClassifiedBorderField = "style" | "color" | "size" | "space" | "shadow" | "frame" | "artRelationshipId" | "topLeftArtRelationshipId" | "topRightArtRelationshipId" | "bottomLeftArtRelationshipId" | "bottomRightArtRelationshipId";
5
- type ExhaustiveBorder = ExhaustiveFields<document_d_exports.BorderSpec, ClassifiedBorderField>;
4
+ type ExhaustiveBorder = document_d_exports.ExhaustiveFields<document_d_exports.BorderSpec, ClassifiedBorderField>;
6
5
  /**
7
6
  * Serialize a single border element (`<w:top .../>`, `<w:left .../>`, ...).
8
7
  *
@@ -61,6 +61,25 @@ const COMMENTS_EXTENDED_BASELINE_PREFIXES = [
61
61
  ];
62
62
  const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
63
63
  /**
64
+ * The order every comment part is written in: top-level comments, then replies.
65
+ *
66
+ * `word/comments.xml` has always been written this way, and the order matters
67
+ * beyond that one part: the save rewrites comments.xml, so the next parse hands
68
+ * the model back in exactly this order. Any other part built by walking the
69
+ * same array — `word/commentsExtended.xml`, and the paraId minting both parts
70
+ * key on — has to walk it in the same order, or the first save writes a part
71
+ * whose order the second save cannot reproduce and saving is not a fixed point.
72
+ */
73
+ const commentPartOrder = (comments) => {
74
+ const topLevel = [];
75
+ const replies = [];
76
+ for (const comment of comments) {
77
+ const { parentId } = comment;
78
+ (parentId === null || parentId === void 0 ? topLevel : replies).push(comment);
79
+ }
80
+ return [...topLevel, ...replies];
81
+ };
82
+ /**
64
83
  * Serialize comments array to comments.xml content. Returns a valid empty
65
84
  * `<w:comments/>` document for an empty array so callers can overwrite an
66
85
  * existing `word/comments.xml` part when the editor has removed the last
@@ -68,13 +87,7 @@ const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"y
68
87
  * the orphaned comment threads on every save.
69
88
  */
70
89
  function serializeComments(comments, sourceBindings) {
71
- const topLevel = [];
72
- const replies = [];
73
- for (const c of comments) {
74
- const { parentId } = c;
75
- (parentId === null || parentId === void 0 ? topLevel : replies).push(c);
76
- }
77
- const body = topLevel.map((comment) => serializeComment(comment)).join("") + replies.map((reply) => serializeComment(reply)).join("");
90
+ const body = commentPartOrder(comments).map((comment) => serializeComment(comment)).join("");
78
91
  return XML_DECLARATION + serializePartElement({
79
92
  partPath: "word/comments.xml",
80
93
  rootName: "w:comments",
@@ -122,7 +135,7 @@ function ensureThreadedCommentParaIds(comments) {
122
135
  if (threaded.size === 0) return;
123
136
  const used = /* @__PURE__ */ new Set();
124
137
  for (const comment of comments) for (const paragraph of comment.content ?? []) if (paragraph.paraId) used.add(paragraph.paraId.toUpperCase());
125
- for (const comment of comments) {
138
+ for (const comment of commentPartOrder(comments)) {
126
139
  if (!threaded.has(comment.id)) continue;
127
140
  const last = (comment.content ?? []).at(-1);
128
141
  if (!last || last.paraId) continue;
@@ -153,7 +166,7 @@ function buildCommentExtendedEntries(comments) {
153
166
  if (paraId) paraIdByCommentId.set(comment.id, paraId);
154
167
  }
155
168
  const entries = [];
156
- for (const comment of comments) {
169
+ for (const comment of commentPartOrder(comments)) {
157
170
  if (!threaded.has(comment.id)) continue;
158
171
  const paraId = paraIdByCommentId.get(comment.id);
159
172
  if (!paraId) continue;
@@ -47,10 +47,6 @@ declare function getDocumentParagraphCount(doc: document_d_exports.Document): nu
47
47
  * Get table count in document
48
48
  */
49
49
  declare function getDocumentTableCount(doc: document_d_exports.Document): number;
50
- /**
51
- * Get plain text from document (for comparison/debugging)
52
- */
53
- declare function getDocumentPlainText(doc: document_d_exports.Document): string;
54
50
  /**
55
51
  * Create an empty document
56
52
  */
@@ -63,4 +59,4 @@ declare function createSimpleDocument(paragraphs: {
63
59
  styleId?: string;
64
60
  }[]): document_d_exports.Document;
65
61
  //#endregion
66
- export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentPlainText, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
62
+ export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
@@ -1,3 +1,4 @@
1
+ import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
1
2
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
2
3
  import { serializeParagraph } from "./paragraphSerializer.js";
3
4
  import { serializePartElement } from "./partNamespaces.js";
@@ -40,6 +41,10 @@ const DOCUMENT_BASELINE_PREFIXES = [
40
41
  * Serialize a single block content item (paragraph, table, or block-level SDT).
41
42
  */
42
43
  function serializeBlockContent(block) {
44
+ return withBlockRangeMarkers(block, serializeBlockBody(block));
45
+ }
46
+ /** The block itself, without the range markers that stood around it. */
47
+ function serializeBlockBody(block) {
43
48
  if (block.type === "paragraph") return serializeParagraph(block);
44
49
  if (block.type === "table") return serializeTable(block, serializeParagraph);
45
50
  return serializeBlockSdt(block, serializeBlockContent);
@@ -127,21 +132,6 @@ function getDocumentTableCount(doc) {
127
132
  return doc.package.document.content.filter((b) => b.type === "table").length;
128
133
  }
129
134
  /**
130
- * Get plain text from document (for comparison/debugging)
131
- */
132
- function getDocumentPlainText(doc) {
133
- const texts = [];
134
- for (const block of doc.package.document.content) if (block.type === "paragraph") {
135
- for (const content of block.content) if (content.type === "run") {
136
- for (const item of content.content) if (item.type === "text") texts.push(item.text);
137
- else if (item.type === "tab") texts.push(" ");
138
- else if (item.type === "break") texts.push("\n");
139
- }
140
- texts.push("\n");
141
- }
142
- return texts.join("");
143
- }
144
- /**
145
135
  * Create an empty document
146
136
  */
147
137
  function createEmptyDocument() {
@@ -164,4 +154,4 @@ function createSimpleDocument(paragraphs) {
164
154
  })) } } };
165
155
  }
166
156
  //#endregion
167
- export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentPlainText, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
157
+ export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
@@ -1,3 +1,4 @@
1
+ import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
1
2
  import { canReplayHeaderFooterVerbatim, getHeaderFooterVerbatimXml } from "../headerFooterVerbatim.js";
2
3
  import { isEmptyParagraph } from "../paragraphParser.js";
3
4
  import { captureVerbatimXml } from "../verbatimCapture.js";
@@ -36,6 +37,10 @@ const HEADER_FOOTER_BASELINE_PREFIXES = [
36
37
  * header/footer.
37
38
  */
38
39
  function serializeBlock(block) {
40
+ return withBlockRangeMarkers(block, serializeBlockBody(block));
41
+ }
42
+ /** The block itself, without the range markers that stood around it. */
43
+ function serializeBlockBody(block) {
39
44
  if (block.type === "paragraph") return serializeParagraph(block);
40
45
  if (block.type === "table") return serializeTable(block, serializeParagraph);
41
46
  return serializeBlockSdt(block, serializeBlock);
@@ -0,0 +1,8 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ //#region src/docx/serializer/markupRangeAttributes.d.ts
3
+ declare const markupRangeAttributes: (marker: document_d_exports.MarkupRangeMarker) => string[];
4
+ declare const bookmarkRangeAttributes: (marker: document_d_exports.BookmarkRangeMarker) => string[];
5
+ /** `w:author` is required, so it is always written; `w:date` only when known. */
6
+ declare const moveBookmarkAttributes: (marker: document_d_exports.MoveBookmarkMarker) => string[];
7
+ //#endregion
8
+ export { bookmarkRangeAttributes, markupRangeAttributes, moveBookmarkAttributes };
@@ -0,0 +1,24 @@
1
+ import { escapeXml } from "./xmlUtils.js";
2
+ //#region src/docx/serializer/markupRangeAttributes.ts
3
+ const attribute = (name, value) => `${name}="${escapeXml(String(value))}"`;
4
+ const markupRangeAttributes = (marker) => {
5
+ const attributes = [attribute("w:id", marker.id)];
6
+ if (marker.displacedByCustomXml !== void 0) attributes.push(attribute("w:displacedByCustomXml", marker.displacedByCustomXml));
7
+ return attributes;
8
+ };
9
+ const bookmarkRangeAttributes = (marker) => {
10
+ const attributes = markupRangeAttributes(marker);
11
+ if (marker.colFirst !== void 0) attributes.push(attribute("w:colFirst", marker.colFirst));
12
+ if (marker.colLast !== void 0) attributes.push(attribute("w:colLast", marker.colLast));
13
+ attributes.push(attribute("w:name", marker.name));
14
+ return attributes;
15
+ };
16
+ /** `w:author` is required, so it is always written; `w:date` only when known. */
17
+ const moveBookmarkAttributes = (marker) => {
18
+ const attributes = bookmarkRangeAttributes(marker);
19
+ attributes.push(attribute("w:author", marker.author.trim() || "Unknown"));
20
+ if (marker.date !== void 0 && marker.date.trim().length > 0) attributes.push(attribute("w:date", marker.date.trim()));
21
+ return attributes;
22
+ };
23
+ //#endregion
24
+ export { bookmarkRangeAttributes, markupRangeAttributes, moveBookmarkAttributes };