@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
@@ -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";
@@ -38,6 +39,10 @@ const serializeNotePart = (elementName, body) => "<?xml version=\"1.0\" encoding
38
39
  * note body.
39
40
  */
40
41
  function serializeBlock(block) {
42
+ return withBlockRangeMarkers(block, serializeBlockBody(block));
43
+ }
44
+ /** The block itself, without the range markers that stood around it. */
45
+ function serializeBlockBody(block) {
41
46
  if (block.type === "paragraph") return serializeParagraph(block);
42
47
  if (block.type === "table") return serializeTable(block, serializeParagraph);
43
48
  return serializeBlockSdt(block, serializeBlock);
@@ -23,10 +23,6 @@ declare function hasParagraphContent(paragraph: document_d_exports.Paragraph): b
23
23
  * Check if a paragraph has formatting
24
24
  */
25
25
  declare function hasParagraphFormatting(paragraph: document_d_exports.Paragraph): boolean;
26
- /**
27
- * Get plain text from a paragraph (for comparison/debugging)
28
- */
29
- declare function getParagraphPlainText(paragraph: document_d_exports.Paragraph): string;
30
26
  /**
31
27
  * Create an empty paragraph
32
28
  */
@@ -44,4 +40,4 @@ declare function isListParagraph(paragraph: document_d_exports.Paragraph): boole
44
40
  */
45
41
  declare function getListLevel(paragraph: document_d_exports.Paragraph): number;
46
42
  //#endregion
47
- export { createEmptyParagraph, createTextParagraph, getListLevel, getParagraphPlainText, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };
43
+ export { createEmptyParagraph, createTextParagraph, getListLevel, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };
@@ -5,6 +5,7 @@ import { DATE_UTC_ATTRIBUTE } from "../trackedChangeInfo.js";
5
5
  import { toTransitionalNamespaceUri } from "../transitionalSpelling.js";
6
6
  import { captureVerbatimXml, sanitizeCapturedXmlElement } from "../verbatimCapture.js";
7
7
  import { NAMESPACES, OOXML_NAMESPACE_SCOPE, cloneElement, getChildElements, getLocalName, parseXml } from "../xmlParser.js";
8
+ import { bookmarkRangeAttributes, markupRangeAttributes, moveBookmarkAttributes } from "./markupRangeAttributes.js";
8
9
  import { serializeRun } from "./runSerializer.js";
9
10
  import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
10
11
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
@@ -21,13 +22,20 @@ function serializeParagraphMarkChange(mark) {
21
22
  return `<w:${mark.kind} ${attrs}/>`;
22
23
  }
23
24
  const RESERVED_PARAGRAPH_PROPERTY_CHILDREN = /* @__PURE__ */ new Set(["pPrChange", "sectPr"]);
25
+ /**
26
+ * Revision records a captured `w:pPr` may not carry into a replay.
27
+ *
28
+ * `w:numberingChange` is not among them: the model holds it
29
+ * (`ParagraphFormatting.numberingChangeXml`) and the serializer writes it
30
+ * back, so both paths keep it. Refusing the capture used to force a rebuild
31
+ * that could not write it, which turned a replay gate into a lost revision.
32
+ */
24
33
  const RESERVED_PARAGRAPH_CAPTURE_CHILDREN = /* @__PURE__ */ new Set([
25
34
  ...RESERVED_PARAGRAPH_PROPERTY_CHILDREN,
26
35
  ...PARAGRAPH_MARK_CHANGE_KINDS,
27
36
  "cellDel",
28
37
  "cellIns",
29
38
  "cellMerge",
30
- "numberingChange",
31
39
  "rPrChange",
32
40
  "tblGridChange",
33
41
  "tblPrChange",
@@ -119,7 +127,11 @@ const PARAGRAPH_MARK_BASE_CHILDREN = /* @__PURE__ */ new Set([
119
127
  "oMath"
120
128
  ]);
121
129
  const PARAGRAPH_NESTED_PROPERTY_CHILDREN = /* @__PURE__ */ new Map([
122
- ["numPr", /* @__PURE__ */ new Set(["ilvl", "numId"])],
130
+ ["numPr", /* @__PURE__ */ new Set([
131
+ "ilvl",
132
+ "numId",
133
+ "numberingChange"
134
+ ])],
123
135
  ["pBdr", /* @__PURE__ */ new Set([
124
136
  "top",
125
137
  "left",
@@ -344,16 +356,13 @@ function serializeHyperlink(hyperlink) {
344
356
  * Serialize bookmark start (w:bookmarkStart)
345
357
  */
346
358
  function serializeBookmarkStart(bookmark) {
347
- const attrs = [`w:id="${bookmark.id}"`, `w:name="${escapeXml(bookmark.name)}"`];
348
- if (bookmark.colFirst !== void 0) attrs.push(`w:colFirst="${bookmark.colFirst}"`);
349
- if (bookmark.colLast !== void 0) attrs.push(`w:colLast="${bookmark.colLast}"`);
350
- return `<w:bookmarkStart ${attrs.join(" ")}/>`;
359
+ return `<w:bookmarkStart ${bookmarkRangeAttributes(bookmark).join(" ")}/>`;
351
360
  }
352
361
  /**
353
362
  * Serialize bookmark end (w:bookmarkEnd)
354
363
  */
355
364
  function serializeBookmarkEnd(bookmark) {
356
- return `<w:bookmarkEnd w:id="${bookmark.id}"/>`;
365
+ return `<w:bookmarkEnd ${markupRangeAttributes(bookmark).join(" ")}/>`;
357
366
  }
358
367
  /** Serialize a simple field without changing its authored OOXML field form. */
359
368
  function serializeSimpleField(field) {
@@ -471,7 +480,7 @@ function serializeInlineSdt(sdt) {
471
480
  })}${props.rawEndPropertiesXml && isSingleWellFormedElement(props.rawEndPropertiesXml, "sdtEndPr") ? props.rawEndPropertiesXml : ""}<w:sdtContent>${contentXml}</w:sdtContent></w:sdt>`;
472
481
  }
473
482
  function serializeMoveRangeStart(tag, marker) {
474
- return `<w:${tag} ${[`w:id="${marker.id}"`, `w:name="${escapeXml(marker.name)}"`].join(" ")}/>`;
483
+ return `<w:${tag} ${moveBookmarkAttributes(marker).join(" ")}/>`;
475
484
  }
476
485
  /**
477
486
  * Serialize a tracked change wrapper (ins/del/moveFrom/moveTo)
@@ -560,17 +569,24 @@ function serializeParagraphContent(content, explicitCommentReferenceIds) {
560
569
  case "simpleField": return serializeSimpleField(content);
561
570
  case "complexField": return serializeComplexField(content);
562
571
  case "inlineSdt": return serializeInlineSdt(content);
563
- case "commentRangeStart": return `<w:commentRangeStart w:id="${content.id}"/>`;
564
- case "commentRangeEnd": return explicitCommentReferenceIds.has(content.id) ? `<w:commentRangeEnd w:id="${content.id}"/>` : `<w:commentRangeEnd w:id="${content.id}"/>${serializeCommentReferenceRun(content.id)}`;
572
+ case "commentRangeStart": return `<w:commentRangeStart ${markupRangeAttributes(content).join(" ")}/>`;
573
+ case "commentRangeEnd": {
574
+ const end = `<w:commentRangeEnd ${markupRangeAttributes(content).join(" ")}/>`;
575
+ return explicitCommentReferenceIds.has(content.id) ? end : `${end}${serializeCommentReferenceRun(content.id)}`;
576
+ }
565
577
  case "commentReference": return serializeCommentReferenceRun(content.id);
566
578
  case "insertion": return serializeTrackedChange("ins", content);
567
579
  case "deletion": return serializeTrackedChange("del", content);
568
580
  case "moveFrom": return serializeTrackedChange("moveFrom", content);
569
581
  case "moveTo": return serializeTrackedChange("moveTo", content);
570
582
  case "moveFromRangeStart": return serializeMoveRangeStart("moveFromRangeStart", content);
571
- case "moveFromRangeEnd": return `<w:moveFromRangeEnd w:id="${content.id}"/>`;
583
+ case "moveFromRangeEnd": return `<w:moveFromRangeEnd ${markupRangeAttributes(content).join(" ")}/>`;
572
584
  case "moveToRangeStart": return serializeMoveRangeStart("moveToRangeStart", content);
573
- case "moveToRangeEnd": return `<w:moveToRangeEnd w:id="${content.id}"/>`;
585
+ case "moveToRangeEnd": return `<w:moveToRangeEnd ${markupRangeAttributes(content).join(" ")}/>`;
586
+ case "bidiWrapper": {
587
+ const tag = content.control === "override" ? "bdo" : "dir";
588
+ return `<w:${tag}${content.direction === void 0 ? "" : ` w:val="${escapeXml(content.direction)}"`}>${content.content.map((child) => serializeParagraphContent(child, explicitCommentReferenceIds)).join("")}</w:${tag}>`;
589
+ }
574
590
  case "mathEquation": return content.ommlXml || "";
575
591
  default: return "";
576
592
  }
@@ -657,28 +673,6 @@ function hasParagraphFormatting(paragraph) {
657
673
  return paragraph.formatting !== void 0 && Object.keys(paragraph.formatting).length > 0;
658
674
  }
659
675
  /**
660
- * Get plain text from a paragraph (for comparison/debugging)
661
- */
662
- function getParagraphPlainText(paragraph) {
663
- const texts = [];
664
- for (const content of paragraph.content) if (content.type === "run") {
665
- for (const item of content.content) if (item.type === "text") texts.push(item.text);
666
- else if (item.type === "tab") texts.push(" ");
667
- else if (item.type === "break") texts.push("\n");
668
- } else if (content.type === "hyperlink") {
669
- for (const child of content.children) if (child.type === "run") {
670
- for (const item of child.content) if (item.type === "text") texts.push(item.text);
671
- }
672
- } else if (content.type === "simpleField" || content.type === "inlineSdt" || content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo") {
673
- for (const item of content.content) if (item.type === "run") {
674
- for (const subItem of item.content) if (subItem.type === "text") texts.push(subItem.text);
675
- }
676
- } else if (content.type === "complexField") {
677
- for (const run of content.fieldResult) for (const item of run.content) if (item.type === "text") texts.push(item.text);
678
- }
679
- return texts.join("");
680
- }
681
- /**
682
676
  * Create an empty paragraph
683
677
  */
684
678
  function createEmptyParagraph(formatting) {
@@ -718,4 +712,4 @@ function getListLevel(paragraph) {
718
712
  return paragraph.formatting?.numPr?.ilvl ?? -1;
719
713
  }
720
714
  //#endregion
721
- export { createEmptyParagraph, createTextParagraph, getListLevel, getParagraphPlainText, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };
715
+ export { createEmptyParagraph, createTextParagraph, getListLevel, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };
@@ -2,13 +2,13 @@ import { isValidHexColor } from "../../utils/colorResolver.js";
2
2
  import { THEME_COLOR_TO_DRAWING_SCHEME } from "../drawingUtils.js";
3
3
  import { serializeGraphicFrameLocks } from "../graphicFrameLocks.js";
4
4
  import { canReplayEditableImageRawXml } from "../imageRawXml.js";
5
- import { requiresXmlSpacePreserve } from "../textWhitespace.js";
6
5
  import { serializeParagraph } from "./paragraphSerializer.js";
7
6
  import { serializeTable } from "./tableSerializer.js";
8
7
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
9
8
  import { getSingularRunPropertyChange, serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
10
9
  import { escapeXml, intAttr } from "./xmlUtils.js";
11
10
  import { panic } from "better-result";
11
+ import { requiresXmlSpacePreserve } from "@stll/docx-core";
12
12
  //#region src/docx/serializer/runSerializer.ts
13
13
  /**
14
14
  * Run Serializer - Serialize runs to OOXML XML
@@ -59,7 +59,7 @@ function serializeRunProperties(formatting, propertyChanges) {
59
59
  * Serialize text content (w:t)
60
60
  */
61
61
  function serializeTextContent(content) {
62
- return `<w:t${content.preserveSpace || requiresXmlSpacePreserve(content.text) ? " xml:space=\"preserve\"" : ""}>${escapeXml(content.text)}</w:t>`;
62
+ return `<w:t${requiresXmlSpacePreserve(content.text) ? " xml:space=\"preserve\"" : ""}>${escapeXml(content.text)}</w:t>`;
63
63
  }
64
64
  /**
65
65
  * Serialize tab content (w:tab)
@@ -88,7 +88,7 @@ function serializeBreakContent(content) {
88
88
  * Serialize symbol content (w:sym)
89
89
  */
90
90
  function serializeSymbolContent(content) {
91
- return `<w:sym w:font="${escapeXml(content.font)}" w:char="${escapeXml(content.char)}"/>`;
91
+ return `<w:sym${content.font === "" ? "" : ` w:font="${escapeXml(content.font)}"`}${content.char === "" ? "" : ` w:char="${escapeXml(content.char)}"`}/>`;
92
92
  }
93
93
  /**
94
94
  * Serialize footnote/endnote reference
@@ -206,11 +206,17 @@ function serializeWrap(wrap) {
206
206
  case "inline": return "<wp:wrapNone/>";
207
207
  }
208
208
  }
209
- /** Build the common a:graphic > pic:pic element for images */
210
- function serializePicGraphic(image, sharedId) {
209
+ /**
210
+ * Build the common a:graphic > pic:pic element for images.
211
+ *
212
+ * Takes the relationship id as a separate argument so the caller has to have
213
+ * one. There is no default: a picture written against an invented id binds to
214
+ * whichever relationship the part happens to hold under that name.
215
+ */
216
+ function serializePicGraphic(image, imageRId, sharedId) {
211
217
  const cx = image.size.width;
212
218
  const cy = image.size.height;
213
- const rId = escapeXml(image.rId || "rId1");
219
+ const rId = escapeXml(imageRId);
214
220
  const id = sharedId;
215
221
  const name = image.filename || `image${id}`;
216
222
  let xfrmAttrs = "";
@@ -287,7 +293,7 @@ function serializeDrawingContent(content) {
287
293
  const anchorDocPrAttrs = `id="${docPrId}" name="${escapeXml(docPrName)}"${docPrDescription}${docPrTitle}`;
288
294
  const anchorDocPr = hlinkClick ? `<wp:docPr ${anchorDocPrAttrs}>${hlinkClick}</wp:docPr>` : `<wp:docPr ${anchorDocPrAttrs}/>`;
289
295
  const graphicFramePr = serializeGraphicFrameLocks(image.frameLocks);
290
- const graphic = serializePicGraphic(image, docPrId);
296
+ const graphic = image.rId === void 0 ? "" : serializePicGraphic(image, image.rId, docPrId);
291
297
  if (!isFloating) return [
292
298
  "<w:drawing>",
293
299
  `<wp:inline${wrapDistanceAttrs}>`,
@@ -1,7 +1,10 @@
1
1
  import { canonicalJson } from "../../utils/canonicalJson.js";
2
2
  import { isValidHexColor } from "../../utils/colorResolver.js";
3
+ import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
4
+ import { TABLE_LOOK_FLAGS } from "../tableLook.js";
3
5
  import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTableRowProperties } from "../tableParser.js";
4
- import { OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
6
+ import { sanitizeCapturedXmlElement } from "../verbatimCapture.js";
7
+ import { NAMESPACES, OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
5
8
  import { serializeBorder } from "./borderSerializer.js";
6
9
  import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
7
10
  import { escapeXml, intAttr } from "./xmlUtils.js";
@@ -140,17 +143,22 @@ function serializeShading(shading) {
140
143
  return `<w:shd ${attrs.join(" ")}/>`;
141
144
  }
142
145
  /**
143
- * Serialize table look flags (w:tblLook)
146
+ * Serialize a `w:tblLook`: what the author wrote, in the order they wrote it.
147
+ *
148
+ * A flag is written when it was authored, true or false. Writing only the true
149
+ * ones drops an explicit `w:lastRow="0"`, and an absent flag is not an off one:
150
+ * it falls back to `w:val`'s bit, so the two spell different tables under the
151
+ * same table style. `w:val` leads, then the flags in `CT_TblLook` order, which
152
+ * is the order Word writes them.
144
153
  */
145
154
  function serializeTableLook(look) {
146
155
  if (!look) return "";
147
156
  const attrs = [];
148
- if (look.firstRow) attrs.push("w:firstRow=\"1\"");
149
- if (look.lastRow) attrs.push("w:lastRow=\"1\"");
150
- if (look.firstColumn) attrs.push("w:firstColumn=\"1\"");
151
- if (look.lastColumn) attrs.push("w:lastColumn=\"1\"");
152
- if (look.noHBand) attrs.push("w:noHBand=\"1\"");
153
- if (look.noVBand) attrs.push("w:noVBand=\"1\"");
157
+ if (look.val !== void 0) attrs.push(`w:val="${escapeXml(look.val)}"`);
158
+ for (const flag of TABLE_LOOK_FLAGS) {
159
+ const stated = look[flag];
160
+ if (stated !== void 0) attrs.push(`w:${flag}="${stated ? "1" : "0"}"`);
161
+ }
154
162
  if (attrs.length === 0) return "";
155
163
  return `<w:tblLook ${attrs.join(" ")}/>`;
156
164
  }
@@ -344,17 +352,24 @@ function serializeTableGrid(table) {
344
352
  if (gridSourceXml !== void 0) {
345
353
  if (canonicalJson(parseTableGrid(parseXml(gridSourceXml, OOXML_NAMESPACE_SCOPE).elements?.[0] ?? null)) === canonicalJson(columnWidths)) return gridSourceXml;
346
354
  }
347
- if (columnWidths && columnWidths.length > 0) return `<w:tblGrid>${columnWidths.map((w) => `<w:gridCol w:w="${intAttr(w)}"/>`).join("")}</w:tblGrid>`;
355
+ const gridChange = replayableGridChangeXml(table.formatting?.gridChangeXml) ?? "";
356
+ if (columnWidths && columnWidths.length > 0) return `<w:tblGrid>${columnWidths.map((w) => `<w:gridCol w:w="${intAttr(w)}"/>`).join("")}${gridChange}</w:tblGrid>`;
348
357
  const columns = gridColumnCount(table);
349
- return columns === 0 ? "<w:tblGrid/>" : `<w:tblGrid>${"<w:gridCol/>".repeat(columns)}</w:tblGrid>`;
350
- }
358
+ return columns === 0 && gridChange === "" ? "<w:tblGrid/>" : `<w:tblGrid>${"<w:gridCol/>".repeat(columns)}${gridChange}</w:tblGrid>`;
359
+ }
360
+ const GRID_CHANGE_ROOT_NAME = /* @__PURE__ */ new Set(["tblGridChange"]);
361
+ const WORDPROCESSINGML_NAMESPACE = /* @__PURE__ */ new Set([NAMESPACES.w]);
362
+ const replayableGridChangeXml = (gridChangeXml) => sanitizeCapturedXmlElement(gridChangeXml, {
363
+ allowedLocalNames: GRID_CHANGE_ROOT_NAME,
364
+ allowedNamespaceUris: WORDPROCESSINGML_NAMESPACE,
365
+ inheritedNamespaceScope: OOXML_NAMESPACE_SCOPE
366
+ });
351
367
  /**
352
368
  * Serialize cell content (paragraphs, nested tables)
353
369
  */
354
370
  function serializeCellContent(content, serializeParagraph) {
355
371
  const parts = [];
356
- for (const item of content) if (item.type === "paragraph") parts.push(serializeParagraph(item));
357
- else parts.push(serializeTable(item, serializeParagraph));
372
+ for (const item of content) parts.push(withBlockRangeMarkers(item, item.type === "paragraph" ? serializeParagraph(item) : serializeTable(item, serializeParagraph)));
358
373
  if (parts.length === 0) parts.push("<w:p/>");
359
374
  return parts.join("");
360
375
  }
@@ -1,10 +1,9 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
2
  //#region src/docx/serializer/textFormattingSerializer.d.ts
3
- type ExhaustiveFields<Source, Classified extends keyof Source> = Exclude<keyof Source, Classified> extends never ? Source : never;
4
3
  type ClassifiedShadingField = "color" | "fill" | "pattern";
5
- type ExhaustiveShadingProperties = ExhaustiveFields<document_d_exports.ShadingProperties, ClassifiedShadingField>;
4
+ type ExhaustiveShadingProperties = document_d_exports.ExhaustiveFields<document_d_exports.ShadingProperties, ClassifiedShadingField>;
6
5
  type ClassifiedTextFormattingField = "bold" | "boldCs" | "italic" | "italicCs" | "underline" | "strike" | "doubleStrike" | "vertAlign" | "smallCaps" | "allCaps" | "hidden" | "noProof" | "color" | "highlight" | "shading" | "fontSize" | "fontSizeCs" | "fontFamily" | "language" | "spacing" | "position" | "scale" | "kerning" | "effect" | "emphasisMark" | "emboss" | "imprint" | "outline" | "shadow" | "rtl" | "cs" | "styleId";
7
- type ExhaustiveTextFormatting = ExhaustiveFields<document_d_exports.TextFormatting, ClassifiedTextFormattingField>;
6
+ type ExhaustiveTextFormatting = document_d_exports.ExhaustiveFields<document_d_exports.TextFormatting, ClassifiedTextFormattingField>;
8
7
  /**
9
8
  * Serialize shading properties (w:shd)
10
9
  */
@@ -26,7 +26,14 @@ const HEADING_LEVELS = [
26
26
  5,
27
27
  6
28
28
  ];
29
- /** Outline levels a `TOC \o` switch may name (ECMA-376 `w:outlineLvl` 0-8). */
29
+ /**
30
+ * The heading levels a `TOC \o` field-argument may name (ECMA-376 17.16.5.68).
31
+ *
32
+ * `\o` selects by *style id* — "each integer corresponds to the style with a
33
+ * style ID of HeadingX" — not by outline level, which is what `\u` reads. The
34
+ * domain here is therefore the 1-9 of `Heading1`…`Heading9`, not the 0-8 of
35
+ * `w:outlineLvl`.
36
+ */
30
37
  const TOC_LEVEL_MIN = 1;
31
38
  const TOC_LEVEL_MAX = 9;
32
39
  /**
@@ -1,4 +1,5 @@
1
1
  import { deterministicHexId } from "../../utils/hexId.js";
2
+ import { createBuiltInStyleIndex, resolveHeadingLevel } from "../builtInStyles.js";
2
3
  import { isNumberingReference } from "../numberingReference.js";
3
4
  import { getParagraphText } from "../paragraphParser.js";
4
5
  import { cloneParagraphWithoutPropertySource } from "../paragraphPropertySource.js";
@@ -77,6 +78,7 @@ function createBilingualDocument(source, options) {
77
78
  const styles = source.package.styles;
78
79
  const numbering = source.package.numbering;
79
80
  const styleById = new Map((styles?.styles ?? []).map((style) => [style.styleId, style]));
81
+ const builtInStyles = createBuiltInStyleIndex(styleById.values(), styles?.docDefaults);
80
82
  const blocks = flattenBlocks(source.package.document.content);
81
83
  const cloner = createNumberingCloner({
82
84
  numbering,
@@ -124,7 +126,7 @@ function createBilingualDocument(source, options) {
124
126
  }
125
127
  const { copy, ref } = copyParagraph(block);
126
128
  rows.push({
127
- kind: classifyParagraph(block, styleById),
129
+ kind: classifyParagraph(block, styleById, builtInStyles),
128
130
  rowId: ref.targetParaId,
129
131
  ...ref
130
132
  });
@@ -235,26 +237,15 @@ const isFieldOnlyParagraph = (paragraph) => {
235
237
  * them would surface as a missing handle rather than as the rule it is.
236
238
  */
237
239
  const isTranslatableParagraph = (paragraph, editableParagraphIds) => paragraph.paraId !== void 0 && editableParagraphIds.has(paragraph.paraId) && !isFieldOnlyParagraph(paragraph);
238
- /** Heading style families across Word UI languages (en, cs/sk, de, fr, pl). */
239
- const HEADING_STYLE_PATTERN = /heading|nadpis|berschrift|titre|nag[łl]/iu;
240
- const classifyParagraph = (paragraph, styleById) => {
240
+ const classifyParagraph = (paragraph, styleById, builtInStyles) => {
241
241
  const formatting = paragraph.formatting;
242
- const style = formatting?.styleId ? styleById.get(formatting.styleId) : void 0;
243
- const outlineLevel = formatting?.outlineLevel ?? resolveInheritedOutlineLevel(style, styleById);
244
- if (outlineLevel !== void 0 && outlineLevel < 9) return "heading";
245
- if (style && (HEADING_STYLE_PATTERN.test(style.styleId) || HEADING_STYLE_PATTERN.test(style.name ?? ""))) return "heading";
242
+ if (resolveHeadingLevel({
243
+ outlineLevel: formatting?.outlineLevel,
244
+ styleId: formatting?.styleId
245
+ }, builtInStyles) !== void 0) return "heading";
246
246
  if (effectiveNumPr(paragraph, styleById) !== void 0) return "listItem";
247
247
  return "paragraph";
248
248
  };
249
- const resolveInheritedOutlineLevel = (style, styleById) => {
250
- const seen = /* @__PURE__ */ new Set();
251
- let current = style;
252
- while (current && !seen.has(current.styleId)) {
253
- seen.add(current.styleId);
254
- if (current.pPr?.outlineLevel !== void 0) return current.pPr.outlineLevel;
255
- current = current.basedOn ? styleById.get(current.basedOn) : void 0;
256
- }
257
- };
258
249
  /** The numbering a paragraph renders with: direct `numPr`, else the style chain's. */
259
250
  const effectiveNumPr = (paragraph, styleById) => {
260
251
  const direct = paragraph.formatting?.numPr;
@@ -703,6 +694,7 @@ const createParaIdMinter = (taken) => {
703
694
  */
704
695
  function readBilingualDocument(document, editableParagraphIds) {
705
696
  const styleById = new Map((document.package.styles?.styles ?? []).map((style) => [style.styleId, style]));
697
+ const builtInStyles = createBuiltInStyleIndex(styleById.values(), document.package.styles?.docDefaults);
706
698
  const rows = [];
707
699
  let missingHandleCount = 0;
708
700
  for (const block of flattenBlocks(document.package.document.content)) {
@@ -777,7 +769,7 @@ function readBilingualDocument(document, editableParagraphIds) {
777
769
  if (targetMissing) missingHandleCount += 1;
778
770
  if (sourceMissing || targetMissing) continue;
779
771
  rows.push({
780
- kind: classifyParagraph(source, styleById),
772
+ kind: classifyParagraph(source, styleById, builtInStyles),
781
773
  rowId: target.paraId,
782
774
  sourceParaId: source.paraId,
783
775
  targetParaId: target.paraId,
@@ -1,6 +1,6 @@
1
1
  import { escapeTableCell } from "../../markdown/escape.js";
2
2
  import { RELATIONSHIP_TYPES, parseRelationships } from "../relsParser.js";
3
- import { findAllDeep, findDeep, getAttribute, getAttributeByNamespaceUri, getLocalName, getNamespaceUri, getTextContent, parseXml } from "../xmlParser.js";
3
+ import { findAllDeep, findDeep, getAttribute, getAttributeByNamespaceUri, getLocalName, getNamespaceUri, getTextContent, parseOnOffValue, parseXml } from "../xmlParser.js";
4
4
  import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
5
5
  //#region src/docx/server/extractDocxText.ts
6
6
  const DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
@@ -68,7 +68,7 @@ const readRunMetrics = (paragraph) => {
68
68
  const properties = findWordChild(run, "rPr");
69
69
  const boldProperty = findWordChild(properties, "b");
70
70
  const boldValue = getWordAttribute(boldProperty, "val");
71
- const bold = boldProperty !== null && boldValue !== "0" && boldValue !== "false";
71
+ const bold = boldProperty !== null && (parseOnOffValue(boldValue) ?? true);
72
72
  const sizeProperty = findWordChild(properties, "sz");
73
73
  const sizeValue = getWordAttribute(sizeProperty, "val");
74
74
  const parsedSize = sizeValue === null ? NaN : Number.parseInt(sizeValue, 10);
@@ -270,8 +270,7 @@ const readTableCell = (cell, { depth, budget }) => {
270
270
  const declaresHeaderRow = (row) => {
271
271
  const header = findWordChild(findWordChild(row, "trPr"), "tblHeader");
272
272
  if (header === null) return false;
273
- const value = getWordAttribute(header, "val");
274
- return value !== "0" && value !== "false";
273
+ return parseOnOffValue(getWordAttribute(header, "val")) ?? true;
275
274
  };
276
275
  const readRowGridOffset = (row, localName) => {
277
276
  const properties = findWordChild(row, "trPr");
@@ -0,0 +1,6 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/shadingParser.d.ts
4
+ declare function parseShading(shd: XmlElement | null): document_d_exports.ShadingProperties | undefined;
5
+ //#endregion
6
+ export { parseShading };
@@ -0,0 +1,32 @@
1
+ import { isValidHexColor } from "../utils/colorResolver.js";
2
+ import { ShadingPatternSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
3
+ import { getAttribute } from "./xmlParser.js";
4
+ //#region src/docx/shadingParser.ts
5
+ /** `ST_HexColor`'s reserved "let the consumer decide" token. */
6
+ const AUTOMATIC_COLOR = "auto";
7
+ const parseHexColor = (value) => {
8
+ if (value === AUTOMATIC_COLOR) return { auto: true };
9
+ if (value && isValidHexColor(value)) return { rgb: value };
10
+ };
11
+ function parseShading(shd) {
12
+ if (!shd) return;
13
+ const props = {};
14
+ const color = parseHexColor(getAttribute(shd, "w", "color"));
15
+ if (color) props.color = color;
16
+ const fill = parseHexColor(getAttribute(shd, "w", "fill"));
17
+ if (fill) props.fill = fill;
18
+ const themeFill = narrowEnum(getAttribute(shd, "w", "themeFill"), ThemeColorSlotSchema);
19
+ if (themeFill) {
20
+ props.fill ??= {};
21
+ props.fill.themeColor = themeFill;
22
+ }
23
+ const themeFillTint = getAttribute(shd, "w", "themeFillTint");
24
+ if (themeFillTint && props.fill) props.fill.themeTint = themeFillTint;
25
+ const themeFillShade = getAttribute(shd, "w", "themeFillShade");
26
+ if (themeFillShade && props.fill) props.fill.themeShade = themeFillShade;
27
+ const pattern = narrowEnum(getAttribute(shd, "w", "val"), ShadingPatternSchema);
28
+ if (pattern) props.pattern = pattern;
29
+ return Object.keys(props).length > 0 ? props : void 0;
30
+ }
31
+ //#endregion
32
+ export { parseShading };
@@ -1,6 +1,6 @@
1
1
  import { parseAnchorPosition, parseAnchorWrap, parseFill, parseOutline } from "./drawingUtils.js";
2
2
  import { ShapeTypeSchema, narrowEnum } from "./parserEnums.js";
3
- import { findAllDeep, findChildByLocalName, findChildren, getAttribute, getChildElements, parseNumericAttribute } from "./xmlParser.js";
3
+ import { findAllDeep, findChildByLocalName, findChildren, getAttribute, getChildElements, parseNumericAttribute, parseOnOffAttribute } from "./xmlParser.js";
4
4
  //#region src/docx/shapeParser.ts
5
5
  /** Convert OOXML rotation (1/60000ths of a degree) to degrees. */
6
6
  function rotToDegrees(rot) {
@@ -26,8 +26,8 @@ function parseTransform(xfrm) {
26
26
  height: parseNumericAttribute(ext, null, "cy") ?? 0
27
27
  };
28
28
  const rotation = rotToDegrees(getAttribute(xfrm, null, "rot"));
29
- const flipH = getAttribute(xfrm, null, "flipH") === "1";
30
- const flipV = getAttribute(xfrm, null, "flipV") === "1";
29
+ const flipH = parseOnOffAttribute(xfrm, null, "flipH") === true;
30
+ const flipV = parseOnOffAttribute(xfrm, null, "flipV") === true;
31
31
  if (rotation === void 0 && !flipH && !flipV) return { size };
32
32
  const transform = {};
33
33
  if (rotation !== void 0) transform.rotation = rotation;