@stll/folio-core 0.35.1 → 0.37.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 (236) hide show
  1. package/README.md +59 -1
  2. package/dist/ai-edits/apply.js +878 -377
  3. package/dist/ai-edits/block-identity.d.ts +4 -1
  4. package/dist/ai-edits/block-identity.js +4 -1
  5. package/dist/ai-edits/blockRange.js +14 -14
  6. package/dist/ai-edits/clean-text.d.ts +40 -1
  7. package/dist/ai-edits/clean-text.js +52 -2
  8. package/dist/ai-edits/headless.d.ts +3 -1
  9. package/dist/ai-edits/headless.js +141 -73
  10. package/dist/ai-edits/index.d.ts +3 -3
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.js +38 -20
  13. package/dist/ai-edits/snapshot.d.ts +14 -6
  14. package/dist/ai-edits/snapshot.js +105 -20
  15. package/dist/ai-edits/table-cell-mutations.js +1 -1
  16. package/dist/ai-edits/table-row-column-mutations.js +7 -0
  17. package/dist/ai-edits/table-template.d.ts +11 -1
  18. package/dist/ai-edits/table-template.js +49 -17
  19. package/dist/ai-edits/types.d.ts +62 -61
  20. package/dist/ai-edits/word-diff.d.ts +18 -3
  21. package/dist/ai-edits/word-diff.js +556 -67
  22. package/dist/ai-suggestions/conflict.js +1 -4
  23. package/dist/ai-suggestions/text-positions.d.ts +8 -0
  24. package/dist/ai-suggestions/text-positions.js +27 -5
  25. package/dist/compare/__fixtures__/body-sequence.js +3 -4
  26. package/dist/compare/column-alignment.d.ts +13 -13
  27. package/dist/compare/column-alignment.js +78 -39
  28. package/dist/compare/compare.d.ts +4 -3
  29. package/dist/compare/compare.js +48 -21
  30. package/dist/compare/content-alignment.d.ts +95 -0
  31. package/dist/compare/content-alignment.js +1450 -0
  32. package/dist/compare/content-types.d.ts +100 -0
  33. package/dist/compare/content-types.js +0 -0
  34. package/dist/compare/content.d.ts +209 -0
  35. package/dist/compare/content.js +885 -0
  36. package/dist/compare/formatting.d.ts +5 -5
  37. package/dist/compare/formatting.js +6 -4
  38. package/dist/compare/plan.d.ts +6 -1
  39. package/dist/compare/plan.js +171 -566
  40. package/dist/compare/scenario.d.ts +2 -2
  41. package/dist/compare/types.d.ts +3 -3
  42. package/dist/compare/verification.d.ts +8 -9
  43. package/dist/compare/verification.js +72 -50
  44. package/dist/compat/eigenpal.d.ts +5 -3
  45. package/dist/compat/eigenpal.js +3 -2
  46. package/dist/controller/headerFooterEditorManager.js +18 -10
  47. package/dist/controller/hiddenEditorApi.js +1 -1
  48. package/dist/controller/hiddenEditorManager.d.ts +7 -2
  49. package/dist/controller/hiddenEditorManager.js +15 -6
  50. package/dist/controller/layoutPipeline.js +5 -3
  51. package/dist/controller/noteEditorManager.js +12 -7
  52. package/dist/display-list/build/headerFooterPrimitives.js +1 -0
  53. package/dist/display-list/build/storyPrimitives.d.ts +5 -2
  54. package/dist/display-list/build/storyPrimitives.js +60 -3
  55. package/dist/display-list/build/tablePrimitives.js +30 -0
  56. package/dist/display-list/primitives.d.ts +1 -1
  57. package/dist/document-operations.d.ts +8 -4
  58. package/dist/document-operations.js +104 -12
  59. package/dist/docx/blockContentParser.js +6 -27
  60. package/dist/docx/commentParser.js +2 -4
  61. package/dist/docx/commentRangeIntegrity.js +2 -4
  62. package/dist/docx/documentClone.d.ts +2 -0
  63. package/dist/docx/documentClone.js +2 -0
  64. package/dist/docx/ensureParaIds.js +9 -4
  65. package/dist/docx/headerFooterParser.js +2 -4
  66. package/dist/docx/normalizeBaseDirection.js +5 -7
  67. package/dist/docx/numberingParser.d.ts +3 -1
  68. package/dist/docx/numberingParser.js +4 -1
  69. package/dist/docx/paragraphParser.js +34 -35
  70. package/dist/docx/paragraphPropertySource.d.ts +49 -0
  71. package/dist/docx/paragraphPropertySource.js +113 -0
  72. package/dist/docx/paragraphTraversal.js +30 -34
  73. package/dist/docx/rezip.d.ts +0 -8
  74. package/dist/docx/rezip.js +41 -16
  75. package/dist/docx/runConsolidator.js +2 -4
  76. package/dist/docx/runParser.js +6 -17
  77. package/dist/docx/sectionParser.js +1 -15
  78. package/dist/docx/serializer/paragraphSerializer.js +289 -30
  79. package/dist/docx/serializer/runSerializer.js +6 -12
  80. package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
  81. package/dist/docx/serializer/tableSerializer.js +4 -23
  82. package/dist/docx/serializer/trackedChangeAttributes.d.ts +11 -0
  83. package/dist/docx/serializer/trackedChangeAttributes.js +31 -0
  84. package/dist/docx/server/build.d.ts +1 -1
  85. package/dist/docx/server/createBilingualDocument.js +8 -10
  86. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
  87. package/dist/docx/server/validateDocxConformance.js +17 -13
  88. package/dist/docx/tableParser.js +1 -37
  89. package/dist/docx/trackedChangeInfo.d.ts +11 -0
  90. package/dist/docx/trackedChangeInfo.js +38 -0
  91. package/dist/docx/verbatimCapture.d.ts +20 -9
  92. package/dist/docx/verbatimCapture.js +208 -8
  93. package/dist/docx/xmlParser.d.ts +3 -1
  94. package/dist/docx/xmlParser.js +6 -1
  95. package/dist/docx/xmlSafety.d.ts +1 -1
  96. package/dist/docx/xmlSafety.js +157 -0
  97. package/dist/headless-layout.js +5 -4
  98. package/dist/index.d.ts +5 -3
  99. package/dist/index.js +3 -2
  100. package/dist/internal/headlessRevisionResolution.d.ts +23 -0
  101. package/dist/internal/headlessRevisionResolution.js +155 -0
  102. package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
  103. package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
  104. package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
  105. package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
  106. package/dist/internal/pageBreakRunPartition.d.ts +26 -0
  107. package/dist/internal/pageBreakRunPartition.js +35 -0
  108. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
  109. package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
  110. package/dist/internal/sectionEndpointResolution.d.ts +32 -0
  111. package/dist/internal/sectionEndpointResolution.js +38 -0
  112. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  113. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
  114. package/dist/layout-bridge/convert/headerFooterLayout.js +52 -9
  115. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
  116. package/dist/layout-bridge/convert/toFlowBlocks.js +391 -82
  117. package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
  118. package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
  119. package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
  120. package/dist/layout-bridge/dom/textStreamDom.js +75 -0
  121. package/dist/layout-bridge/headerFooterLayout.js +16 -30
  122. package/dist/layout-engine/index.js +12 -3
  123. package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
  124. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
  125. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  126. package/dist/layout-engine/types.d.ts +7 -0
  127. package/dist/layout-painter/imageLayout.d.ts +1 -1
  128. package/dist/layout-painter/renderTable.js +20 -0
  129. package/dist/markdown/renderBlock.js +7 -10
  130. package/dist/model.d.ts +1 -1
  131. package/dist/paged-layout/rangeProjection.js +6 -9
  132. package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
  133. package/dist/paged-layout/transactionDirtyRange.js +23 -1
  134. package/dist/prosemirror/attrs/index.d.ts +7 -3
  135. package/dist/prosemirror/attrs/index.js +128 -7
  136. package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
  137. package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
  138. package/dist/prosemirror/commands/comments.d.ts +10 -1
  139. package/dist/prosemirror/commands/comments.js +551 -87
  140. package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
  141. package/dist/prosemirror/commands/formatPainter.js +319 -63
  142. package/dist/prosemirror/commands/index.d.ts +2 -2
  143. package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
  144. package/dist/prosemirror/commands/pageBreak.js +18 -36
  145. package/dist/prosemirror/commands/propertyChangeScope.d.ts +27 -3
  146. package/dist/prosemirror/commands/propertyChangeScope.js +85 -14
  147. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -5
  148. package/dist/prosemirror/conversion/fromProseDoc.js +935 -223
  149. package/dist/prosemirror/conversion/runShadingMark.js +2 -2
  150. package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
  151. package/dist/prosemirror/conversion/toProseDoc.js +439 -291
  152. package/dist/prosemirror/extensions/StarterKit.js +4 -0
  153. package/dist/prosemirror/extensions/core/ParagraphExtension.js +54 -6
  154. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
  155. package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
  156. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +19 -6
  157. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
  158. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
  159. package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
  160. package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
  161. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
  162. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
  163. package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
  164. package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
  165. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
  166. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
  167. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
  168. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
  169. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  170. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
  171. package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
  172. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
  173. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  174. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
  175. package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
  176. package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
  177. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
  178. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
  179. package/dist/prosemirror/findReplaceSelection.js +8 -3
  180. package/dist/prosemirror/index.d.ts +3 -3
  181. package/dist/prosemirror/listMarker.d.ts +26 -1
  182. package/dist/prosemirror/listMarker.js +108 -14
  183. package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
  184. package/dist/prosemirror/pageBreakRunProjection.js +47 -0
  185. package/dist/prosemirror/paraText.js +6 -0
  186. package/dist/prosemirror/paragraphAlignment.d.ts +12 -0
  187. package/dist/prosemirror/paragraphAlignment.js +13 -0
  188. package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
  189. package/dist/prosemirror/paragraphSpacing.js +125 -0
  190. package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
  191. package/dist/prosemirror/plugins/documentNumbering.js +18 -0
  192. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
  193. package/dist/prosemirror/plugins/pmTextScan.js +14 -4
  194. package/dist/prosemirror/plugins/suggestionMode.js +9 -11
  195. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
  196. package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
  197. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
  198. package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
  199. package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
  200. package/dist/prosemirror/runFormattingProvenance.js +48 -0
  201. package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
  202. package/dist/prosemirror/runFormattingReconciliation.js +54 -0
  203. package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
  204. package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
  205. package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
  206. package/dist/prosemirror/runStyleFormatting.js +105 -0
  207. package/dist/prosemirror/schema/index.d.ts +3 -3
  208. package/dist/prosemirror/schema/marks.d.ts +84 -9
  209. package/dist/prosemirror/schema/marks.js +73 -1
  210. package/dist/prosemirror/schema/nodes.d.ts +30 -6
  211. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
  212. package/dist/prosemirror/styles/resolvedStyleAttrs.js +45 -23
  213. package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
  214. package/dist/prosemirror/styles/styleResolver.js +3 -3
  215. package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
  216. package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
  217. package/dist/prosemirror/tableGridMutation.d.ts +20 -0
  218. package/dist/prosemirror/tableGridMutation.js +30 -0
  219. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
  220. package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
  221. package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
  222. package/dist/prosemirror/validation.js +23 -4
  223. package/dist/redline.js +4 -1
  224. package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
  225. package/dist/render-dom/RenderedDomContext.js +17 -34
  226. package/dist/server.d.ts +4 -4
  227. package/dist/server.js +2 -2
  228. package/dist/style-engine/styleEngine.d.ts +1 -1
  229. package/dist/utils/clipboard.js +1 -1
  230. package/dist/utils/findReplace.js +38 -21
  231. package/dist/utils/mergeDocumentContent.js +3 -3
  232. package/dist/utils/replaceText.js +2 -1
  233. package/dist/version-comparison.d.ts +38 -44
  234. package/dist/version-comparison.js +270 -399
  235. package/dist/watermark/index.js +2 -4
  236. package/package.json +8 -2
@@ -1,8 +1,8 @@
1
1
  import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
2
2
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
3
3
  import { BorderStyleSchema, NumberFormatSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
4
+ import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
4
5
  import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
5
- import { normalizeRevisionId } from "@stll/docx-core/model";
6
6
  //#region src/docx/sectionParser.ts
7
7
  /**
8
8
  * Sanity cap on `w:cols/@w:num`. Word's column picker tops out well below
@@ -61,20 +61,6 @@ function parseColorValue(colorStr, themeColor, themeTint, themeShade) {
61
61
  if (themeShade) color.themeShade = themeShade;
62
62
  return Object.keys(color).length > 0 ? color : void 0;
63
63
  }
64
- function parsePropertyChangeInfo(node) {
65
- const rawId = getAttribute(node, "w", "id");
66
- const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
67
- const author = (getAttribute(node, "w", "author") ?? "").trim();
68
- const date = (getAttribute(node, "w", "date") ?? "").trim();
69
- const rsid = (getAttribute(node, "w", "rsid") ?? "").trim();
70
- const info = {
71
- id: normalizeRevisionId(parsedId),
72
- author: author.length > 0 ? author : "Unknown"
73
- };
74
- if (date.length > 0) info.date = date;
75
- if (rsid.length > 0) info.rsid = rsid;
76
- return info;
77
- }
78
64
  /**
79
65
  * Parse a border element for page borders
80
66
  */
@@ -1,11 +1,19 @@
1
+ import { canonicalJson } from "../../utils/canonicalJson.js";
1
2
  import { isValidHexColor } from "../../utils/colorResolver.js";
2
3
  import { numPrEqual } from "../numberingParser.js";
4
+ import { getParagraphPropertySource } from "../paragraphPropertySource.js";
3
5
  import { reconcileRawSdtPr } from "../sdtPropertiesPatch.js";
6
+ import { DATE_UTC_ATTRIBUTE } from "../trackedChangeInfo.js";
7
+ import { toTransitionalNamespaceUri } from "../transitionalSpelling.js";
8
+ import { captureVerbatimXml, sanitizeCapturedXmlElement } from "../verbatimCapture.js";
9
+ import { NAMESPACES, OOXML_NAMESPACE_SCOPE, cloneElement, getChildElements, getLocalName, parseXml } from "../xmlParser.js";
4
10
  import { serializeBorder } from "./borderSerializer.js";
5
11
  import { serializeRun, serializeTextFormatting } from "./runSerializer.js";
6
12
  import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
13
+ import { serializeTrackedChangeAttributes, trackedChangeAttributeRecord } from "./trackedChangeAttributes.js";
7
14
  import { escapeXml, intAttr, isSingleWellFormedElement } from "./xmlUtils.js";
8
- import { normalizeRevisionId } from "@stll/docx-core/model";
15
+ import { panic } from "better-result";
16
+ import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
9
17
  //#region src/docx/serializer/paragraphSerializer.ts
10
18
  /**
11
19
  * Serialize paragraph borders (w:pBdr)
@@ -131,16 +139,280 @@ function serializeFrameProperties(frame) {
131
139
  /**
132
140
  * Serialize paragraph formatting properties to w:pPr XML
133
141
  */
134
- function serializeTrackedChangeAttrs(info) {
135
- const parts = [`w:id="${normalizeRevisionId(info.id)}"`, `w:author="${escapeXml(info.author)}"`];
136
- if (info.date !== void 0) parts.push(`w:date="${escapeXml(info.date)}"`);
137
- return parts.join(" ");
138
- }
139
142
  function serializeParagraphMarkChange(mark) {
140
- const attrs = serializeTrackedChangeAttrs(mark.info);
143
+ const attrs = serializeTrackedChangeAttributes(mark.info);
141
144
  return `<w:${mark.kind} ${attrs}/>`;
142
145
  }
143
- const serializeParagraphFormattingWithOptions = (formatting, { propertyChanges, paragraphMarkChange, sectionProperties } = {}) => {
146
+ const RESERVED_PARAGRAPH_PROPERTY_CHILDREN = /* @__PURE__ */ new Set(["pPrChange", "sectPr"]);
147
+ const RESERVED_PARAGRAPH_CAPTURE_CHILDREN = /* @__PURE__ */ new Set([
148
+ ...RESERVED_PARAGRAPH_PROPERTY_CHILDREN,
149
+ ...PARAGRAPH_MARK_CHANGE_KINDS,
150
+ "cellDel",
151
+ "cellIns",
152
+ "cellMerge",
153
+ "numberingChange",
154
+ "rPrChange",
155
+ "tblGridChange",
156
+ "tblPrChange",
157
+ "tcPrChange",
158
+ "trPrChange"
159
+ ]);
160
+ const PARAGRAPH_PROPERTY_ROOT_NAME = /* @__PURE__ */ new Set(["pPr"]);
161
+ const WORDPROCESSINGML_NAMESPACE = /* @__PURE__ */ new Set([NAMESPACES.w]);
162
+ const PARAGRAPH_APPEND_PREFIXES = /* @__PURE__ */ new Map([
163
+ ["w", NAMESPACES.w],
164
+ ["r", NAMESPACES.r],
165
+ ["w15", NAMESPACES.w15]
166
+ ]);
167
+ const PARAGRAPH_PROPERTY_CHILD_ORDER = new Map([
168
+ "pStyle",
169
+ "keepNext",
170
+ "keepLines",
171
+ "pageBreakBefore",
172
+ "framePr",
173
+ "widowControl",
174
+ "numPr",
175
+ "suppressLineNumbers",
176
+ "pBdr",
177
+ "shd",
178
+ "tabs",
179
+ "suppressAutoHyphens",
180
+ "kinsoku",
181
+ "wordWrap",
182
+ "overflowPunct",
183
+ "topLinePunct",
184
+ "autoSpaceDE",
185
+ "autoSpaceDN",
186
+ "bidi",
187
+ "adjustRightInd",
188
+ "snapToGrid",
189
+ "spacing",
190
+ "ind",
191
+ "contextualSpacing",
192
+ "mirrorIndents",
193
+ "suppressOverlap",
194
+ "jc",
195
+ "textDirection",
196
+ "textAlignment",
197
+ "textboxTightWrap",
198
+ "outlineLvl",
199
+ "divId",
200
+ "cnfStyle",
201
+ "rPr"
202
+ ].map((name, index) => [name, index]));
203
+ const PARAGRAPH_MARK_BASE_CHILDREN = /* @__PURE__ */ new Set([
204
+ "rStyle",
205
+ "rFonts",
206
+ "b",
207
+ "bCs",
208
+ "i",
209
+ "iCs",
210
+ "caps",
211
+ "smallCaps",
212
+ "strike",
213
+ "dstrike",
214
+ "outline",
215
+ "shadow",
216
+ "emboss",
217
+ "imprint",
218
+ "snapToGrid",
219
+ "vanish",
220
+ "webHidden",
221
+ "color",
222
+ "spacing",
223
+ "w",
224
+ "kern",
225
+ "position",
226
+ "sz",
227
+ "szCs",
228
+ "noProof",
229
+ "highlight",
230
+ "u",
231
+ "effect",
232
+ "bdr",
233
+ "shd",
234
+ "fitText",
235
+ "vertAlign",
236
+ "rtl",
237
+ "cs",
238
+ "em",
239
+ "lang",
240
+ "eastAsianLayout",
241
+ "specVanish",
242
+ "oMath"
243
+ ]);
244
+ const PARAGRAPH_NESTED_PROPERTY_CHILDREN = /* @__PURE__ */ new Map([
245
+ ["numPr", /* @__PURE__ */ new Set(["ilvl", "numId"])],
246
+ ["pBdr", /* @__PURE__ */ new Set([
247
+ "top",
248
+ "left",
249
+ "bottom",
250
+ "right",
251
+ "between",
252
+ "bar"
253
+ ])],
254
+ ["tabs", /* @__PURE__ */ new Set(["tab"])],
255
+ ["rPr", PARAGRAPH_MARK_BASE_CHILDREN]
256
+ ]);
257
+ const resolveNamespaceBinding = (scope, prefix) => {
258
+ for (let current = scope; current; current = current.parent) {
259
+ const value = current.bindings.get(prefix);
260
+ if (value !== void 0) return value;
261
+ }
262
+ };
263
+ const hasDescendantNamed = (element, namespaceUri, localName) => {
264
+ const pending = [...getChildElements(element)];
265
+ while (pending.length > 0) {
266
+ const child = pending.pop();
267
+ if (!child) continue;
268
+ if (getLocalName(child.name) === localName && toTransitionalNamespaceUri(child.namespaceUri ?? "") === namespaceUri) return true;
269
+ for (const descendant of getChildElements(child)) pending.push(descendant);
270
+ }
271
+ return false;
272
+ };
273
+ const hasInvalidParagraphPropertyRevision = (root) => {
274
+ const pending = [...getChildElements(root)];
275
+ while (pending.length > 0) {
276
+ const element = pending.pop();
277
+ if (!element) continue;
278
+ if (toTransitionalNamespaceUri(element.namespaceUri ?? "") === NAMESPACES.w) {
279
+ const localName = getLocalName(element.name);
280
+ if (RESERVED_PARAGRAPH_CAPTURE_CHILDREN.has(localName)) return true;
281
+ }
282
+ for (const child of getChildElements(element)) pending.push(child);
283
+ }
284
+ return false;
285
+ };
286
+ const hasInvalidParagraphPropertyShape = (root) => {
287
+ const pending = getChildElements(root).map((element) => ({
288
+ element,
289
+ parent: root
290
+ }));
291
+ if ((root.elements ?? []).some((child) => child.type === "text" && String(child.text ?? "").trim() !== "")) return true;
292
+ while (pending.length > 0) {
293
+ const entry = pending.pop();
294
+ if (!entry) continue;
295
+ const { element, parent } = entry;
296
+ if ((element.elements ?? []).some((child) => child.type === "text" && String(child.text ?? "").trim() !== "")) return true;
297
+ if (toTransitionalNamespaceUri(element.namespaceUri ?? "") === NAMESPACES.w && parent !== root) {
298
+ if (toTransitionalNamespaceUri(parent.namespaceUri ?? "") !== NAMESPACES.w) return true;
299
+ if (!PARAGRAPH_NESTED_PROPERTY_CHILDREN.get(getLocalName(parent.name))?.has(getLocalName(element.name))) return true;
300
+ }
301
+ for (const child of getChildElements(element)) pending.push({
302
+ element: child,
303
+ parent: element
304
+ });
305
+ }
306
+ return false;
307
+ };
308
+ const sourceShadowsDateUtcPrefix = (sourceXml) => {
309
+ const root = parseXml(sourceXml, OOXML_NAMESPACE_SCOPE).elements?.at(0);
310
+ if (!root || root.type !== "element") return true;
311
+ const pending = [root];
312
+ while (pending.length > 0) {
313
+ const element = pending.pop();
314
+ if (!element) continue;
315
+ const resolved = resolveNamespaceBinding(element.namespaceScope, "w16du");
316
+ if (resolved !== void 0 && resolved !== "http://schemas.microsoft.com/office/word/2023/wordml/word16du") return true;
317
+ for (const child of getChildElements(element)) pending.push(child);
318
+ }
319
+ return false;
320
+ };
321
+ const hasInvalidParagraphMarkProperties = (root) => {
322
+ for (const child of getChildElements(root)) {
323
+ if (toTransitionalNamespaceUri(child.namespaceUri ?? "") !== NAMESPACES.w) continue;
324
+ const localName = getLocalName(child.name);
325
+ if (!PARAGRAPH_MARK_BASE_CHILDREN.has(localName)) return true;
326
+ }
327
+ return false;
328
+ };
329
+ const replayableParagraphPropertySourceXml = (sourceXml) => {
330
+ return sanitizeCapturedXmlElement(sourceXml, {
331
+ allowedLocalNames: PARAGRAPH_PROPERTY_ROOT_NAME,
332
+ allowedNamespaceUris: WORDPROCESSINGML_NAMESPACE,
333
+ inheritedNamespaceScope: OOXML_NAMESPACE_SCOPE,
334
+ requiredNamespaceBindings: PARAGRAPH_APPEND_PREFIXES,
335
+ validate: (root) => {
336
+ let paragraphMarkProperties = null;
337
+ const seenChildren = /* @__PURE__ */ new Set();
338
+ let previousChildOrder = -1;
339
+ for (const child of getChildElements(root)) {
340
+ const localName = getLocalName(child.name);
341
+ const isWordprocessingChild = toTransitionalNamespaceUri(child.namespaceUri ?? "") === NAMESPACES.w;
342
+ if (isWordprocessingChild) {
343
+ if (RESERVED_PARAGRAPH_PROPERTY_CHILDREN.has(localName)) return false;
344
+ const childOrder = PARAGRAPH_PROPERTY_CHILD_ORDER.get(localName);
345
+ if (childOrder === void 0 || seenChildren.has(localName) || childOrder < previousChildOrder) return false;
346
+ seenChildren.add(localName);
347
+ previousChildOrder = childOrder;
348
+ }
349
+ if (localName !== "rPr" || !isWordprocessingChild) {
350
+ if (hasDescendantNamed(child, NAMESPACES.w, "rPr")) return false;
351
+ continue;
352
+ }
353
+ if (paragraphMarkProperties !== null || toTransitionalNamespaceUri(resolveNamespaceBinding(child.namespaceScope, "w") ?? "") !== NAMESPACES.w) return false;
354
+ paragraphMarkProperties = child;
355
+ }
356
+ return !(paragraphMarkProperties && hasInvalidParagraphMarkProperties(paragraphMarkProperties) || hasInvalidParagraphPropertyShape(root) || hasInvalidParagraphPropertyRevision(root));
357
+ }
358
+ });
359
+ };
360
+ const verifiedParagraphPropertySource = (formatting, source) => {
361
+ if (!source) return null;
362
+ const replayableSource = replayableParagraphPropertySourceXml(source.xml);
363
+ if (replayableSource === null) return null;
364
+ return canonicalJson(formatting ?? {}) === source.formattingJson ? replayableSource : null;
365
+ };
366
+ const withTrailingParagraphPropertyChildren = (sourceXml, children) => {
367
+ const written = children.join("");
368
+ if (written.length === 0) return sourceXml;
369
+ const selfClosing = /^<((?:[A-Za-z_][\w.-]*:)?pPr)\b([^>]*)\/>$/u.exec(sourceXml);
370
+ if (selfClosing) {
371
+ const [, name, attrs] = selfClosing;
372
+ return `<${name}${attrs}>${written}</${name}>`;
373
+ }
374
+ const close = sourceXml.lastIndexOf("</");
375
+ return close === -1 ? sourceXml : `${sourceXml.slice(0, close)}${written}${sourceXml.slice(close)}`;
376
+ };
377
+ const withParagraphMarkChange = (sourceXml, mark) => {
378
+ const root = parseXml(sourceXml, OOXML_NAMESPACE_SCOPE).elements?.at(0);
379
+ if (!root || root.type !== "element") panic("A validated paragraph-property capture could not be parsed for composition");
380
+ const attributes = trackedChangeAttributeRecord(mark.info);
381
+ const markElement = cloneElement(root, {
382
+ name: `w:${mark.kind}`,
383
+ attributes,
384
+ elements: []
385
+ });
386
+ const elements = [...root.elements ?? []];
387
+ const paragraphMarkIndex = elements.findIndex((child) => child.type === "element" && getLocalName(child.name) === "rPr" && toTransitionalNamespaceUri(child.namespaceUri ?? "") === NAMESPACES.w);
388
+ if (paragraphMarkIndex === -1) elements.push(cloneElement(root, {
389
+ name: "w:rPr",
390
+ attributes: {},
391
+ elements: [markElement]
392
+ }));
393
+ else {
394
+ const paragraphMarkProperties = elements[paragraphMarkIndex];
395
+ if (!paragraphMarkProperties || paragraphMarkProperties.type !== "element") panic("A validated paragraph-mark property node disappeared during composition");
396
+ elements[paragraphMarkIndex] = cloneElement(paragraphMarkProperties, { elements: [markElement, ...paragraphMarkProperties.elements ?? []] });
397
+ }
398
+ return captureVerbatimXml(cloneElement(root, { elements }));
399
+ };
400
+ const serializeParagraphFormattingWithOptions = (formatting, { propertyChanges, paragraphMarkChange, propertySource, sectionProperties } = {}) => {
401
+ const serializablePropertyChangeCount = propertyChanges?.length ?? 0;
402
+ if (serializablePropertyChangeCount > 1) panic("A paragraph cannot serialize more than one w:pPrChange", { count: serializablePropertyChangeCount });
403
+ const paragraphMarkXml = paragraphMarkChange ? serializeParagraphMarkChange(paragraphMarkChange) : "";
404
+ const sectionPropertiesXml = serializeSectionProperties(sectionProperties);
405
+ const propertyChangesXml = (propertyChanges ?? []).map((change) => serializeParagraphPropertyChange(change));
406
+ const composedChildrenUseDateUtc = [
407
+ paragraphMarkXml,
408
+ sectionPropertiesXml,
409
+ ...propertyChangesXml
410
+ ].some((xml) => xml.includes(`${DATE_UTC_ATTRIBUTE}=`));
411
+ const verifiedSource = verifiedParagraphPropertySource(formatting, propertySource);
412
+ if (verifiedSource !== null && (!composedChildrenUseDateUtc || !sourceShadowsDateUtcPrefix(verifiedSource))) {
413
+ const sourceWithMark = paragraphMarkChange ? withParagraphMarkChange(verifiedSource, paragraphMarkChange) : verifiedSource;
414
+ return withTrailingParagraphPropertyChildren(sourceWithMark, [sectionPropertiesXml, ...propertyChangesXml]);
415
+ }
144
416
  const parts = [];
145
417
  const pushToggle = (name, value) => {
146
418
  if (value === true) parts.push(`<w:${name}/>`);
@@ -156,13 +428,13 @@ const serializeParagraphFormattingWithOptions = (formatting, { propertyChanges,
156
428
  pushToggle("widowControl", formatting.widowControl);
157
429
  const numPrXml = formatting.numPrFromStyle != null && numPrEqual(formatting.numPr, formatting.numPrFromStyle) ? "" : serializeNumbering(formatting.numPr);
158
430
  if (numPrXml) parts.push(numPrXml);
431
+ pushToggle("suppressLineNumbers", formatting.suppressLineNumbers);
159
432
  const bordersXml = serializeParagraphBorders(formatting.borders);
160
433
  if (bordersXml) parts.push(bordersXml);
161
434
  const shadingXml = serializeShading(formatting.shading);
162
435
  if (shadingXml) parts.push(shadingXml);
163
436
  const tabsXml = serializeTabStops(formatting.tabs);
164
437
  if (tabsXml) parts.push(tabsXml);
165
- pushToggle("suppressLineNumbers", formatting.suppressLineNumbers);
166
438
  pushToggle("suppressAutoHyphens", formatting.suppressAutoHyphens);
167
439
  pushToggle("kinsoku", formatting.kinsoku);
168
440
  pushToggle("overflowPunct", formatting.overflowPunctuation);
@@ -176,12 +448,12 @@ const serializeParagraphFormattingWithOptions = (formatting, { propertyChanges,
176
448
  if (formatting.alignment) parts.push(`<w:jc w:val="${formatting.alignment}"/>`);
177
449
  if (formatting.outlineLevel !== void 0) parts.push(`<w:outlineLvl w:val="${formatting.outlineLevel}"/>`);
178
450
  if (paragraphMarkChange || formatting.runProperties || formatting.runInWithNext) {
179
- const fullInner = `${paragraphMarkChange ? serializeParagraphMarkChange(paragraphMarkChange) : ""}${formatting.runProperties ? extractRPrInner(serializeTextFormatting(formatting.runProperties)) : ""}${formatting.runInWithNext ? "<w:specVanish/>" : ""}`;
451
+ const fullInner = `${paragraphMarkChange ? paragraphMarkXml : ""}${formatting.runProperties ? extractRPrInner(serializeTextFormatting(formatting.runProperties)) : ""}${formatting.runInWithNext ? "<w:specVanish/>" : ""}`;
180
452
  if (fullInner.length > 0) parts.push(`<w:rPr>${fullInner}</w:rPr>`);
181
453
  }
182
- } else if (paragraphMarkChange) parts.push(`<w:rPr>${serializeParagraphMarkChange(paragraphMarkChange)}</w:rPr>`);
183
- parts.push(serializeSectionProperties(sectionProperties));
184
- if (propertyChanges && propertyChanges.length > 0) parts.push(...propertyChanges.map((change) => serializeParagraphPropertyChange(change)));
454
+ } else if (paragraphMarkChange) parts.push(`<w:rPr>${paragraphMarkXml}</w:rPr>`);
455
+ parts.push(sectionPropertiesXml);
456
+ if (propertyChangesXml.length > 0) parts.push(...propertyChangesXml);
185
457
  const inner = parts.join("");
186
458
  if (inner.length === 0) return "";
187
459
  return `<w:pPr>${inner}</w:pPr>`;
@@ -206,17 +478,9 @@ function extractRPrInner(rPrXml) {
206
478
  return rPrXml.slice(7, -8);
207
479
  }
208
480
  function serializeParagraphPropertyChange(change) {
209
- const normalizedId = normalizeRevisionId(change.info.id);
210
- const authorCandidate = typeof change.info.author === "string" ? change.info.author.trim() : "";
211
- const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
212
- const normalizedDate = typeof change.info.date === "string" ? change.info.date.trim() : void 0;
213
- const normalizedRsid = typeof change.info.rsid === "string" ? change.info.rsid.trim() : void 0;
214
- const attrs = [`w:id="${normalizedId}"`, `w:author="${escapeXml(normalizedAuthor)}"`];
215
- if (normalizedDate) attrs.push(`w:date="${escapeXml(normalizedDate)}"`);
216
- if (normalizedRsid) attrs.push(`w:rsid="${escapeXml(normalizedRsid)}"`);
217
481
  const previousPPrInner = extractPPrInner(serializeParagraphFormatting(change.previousFormatting) || "<w:pPr/>");
218
482
  const normalizedPreviousPPr = previousPPrInner.length > 0 ? `<w:pPr>${previousPPrInner}</w:pPr>` : "<w:pPr/>";
219
- return `<w:pPrChange ${attrs.join(" ")}>${normalizedPreviousPPr}</w:pPrChange>`;
483
+ return `<w:pPrChange ${serializeTrackedChangeAttributes(change.info)}>${normalizedPreviousPPr}</w:pPrChange>`;
220
484
  }
221
485
  /** The attribute list of a `w:hyperlink`, without its children. */
222
486
  function hyperlinkAttributes(hyperlink) {
@@ -390,13 +654,7 @@ function trackedChangeTag(change) {
390
654
  }
391
655
  }
392
656
  function serializeTrackedChange(tag, change) {
393
- const info = change.info;
394
- const normalizedId = normalizeRevisionId(info.id);
395
- const authorCandidate = typeof info.author === "string" ? info.author.trim() : "";
396
- const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
397
- const normalizedDate = typeof info.date === "string" ? info.date.trim() : void 0;
398
- const attrs = [`w:id="${normalizedId}"`, `w:author="${escapeXml(normalizedAuthor)}"`];
399
- if (normalizedDate) attrs.push(`w:date="${escapeXml(normalizedDate)}"`);
657
+ const attrs = serializeTrackedChangeAttributes(change.info);
400
658
  const serializeDeletedRun = (run) => {
401
659
  const xml = serializeRun(run);
402
660
  if (!run.content.some((c) => c.type === "drawing" || c.type === "shape")) return rewriteRunTextAsDeleted(xml);
@@ -420,7 +678,7 @@ function serializeTrackedChange(tag, change) {
420
678
  if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") return serializeTrackedChange(trackedChangeTag(item), item);
421
679
  return item.type === "bookmarkStart" ? serializeBookmarkStart(item) : serializeBookmarkEnd(item);
422
680
  };
423
- const open = `<w:${tag} ${attrs.join(" ")}>`;
681
+ const open = `<w:${tag} ${attrs}>`;
424
682
  const close = `</w:${tag}>`;
425
683
  const wrap = (inner) => inner.length === 0 ? "" : `${open}${inner}${close}`;
426
684
  if (change.content.length === 0) return `${open}${close}`;
@@ -497,6 +755,7 @@ function serializeParagraph(paragraph) {
497
755
  parts.push(serializeParagraphFormattingWithOptions(paragraph.formatting, {
498
756
  propertyChanges: paragraph.propertyChanges,
499
757
  paragraphMarkChange: paragraph.pPrMark,
758
+ propertySource: getParagraphPropertySource(paragraph),
500
759
  sectionProperties: paragraph.sectionProperties
501
760
  }));
502
761
  const explicitCommentReferenceIds = /* @__PURE__ */ new Set();
@@ -5,9 +5,9 @@ import { THEME_COLOR_TO_DRAWING_SCHEME } from "../drawingUtils.js";
5
5
  import { requiresXmlSpacePreserve } from "../textWhitespace.js";
6
6
  import { serializeParagraph } from "./paragraphSerializer.js";
7
7
  import { serializeTable } from "./tableSerializer.js";
8
+ import { getSingularRunPropertyChange, serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
8
9
  import { escapeXml, intAttr } from "./xmlUtils.js";
9
10
  import { panic } from "better-result";
10
- import { normalizeRevisionId } from "@stll/docx-core/model";
11
11
  //#region src/docx/serializer/runSerializer.ts
12
12
  /**
13
13
  * Run Serializer - Serialize runs to OOXML XML
@@ -145,7 +145,7 @@ function serializeTextFormatting(formatting) {
145
145
  }
146
146
  parts.push(`<w:u ${uAttrs.join(" ")}/>`);
147
147
  }
148
- if (formatting.effect && formatting.effect !== "none") parts.push(`<w:effect w:val="${formatting.effect}"/>`);
148
+ if (formatting.effect) parts.push(`<w:effect w:val="${formatting.effect}"/>`);
149
149
  const shadingXml = serializeShading(formatting.shading) || customHighlightShadingXml;
150
150
  if (shadingXml) parts.push(shadingXml);
151
151
  if (formatting.vertAlign) parts.push(`<w:vertAlign w:val="${formatting.vertAlign}"/>`);
@@ -169,20 +169,14 @@ function extractRPrInner(rPrXml) {
169
169
  return rPrXml.slice(7, -8);
170
170
  }
171
171
  function serializeRunPropertyChange(change) {
172
- const normalizedId = normalizeRevisionId(change.info.id);
173
- const authorCandidate = typeof change.info.author === "string" ? change.info.author.trim() : "";
174
- const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
175
- const normalizedDate = typeof change.info.date === "string" ? change.info.date.trim() : void 0;
176
- const normalizedRsid = typeof change.info.rsid === "string" ? change.info.rsid.trim() : void 0;
177
- const attrs = [`w:id="${normalizedId}"`, `w:author="${escapeXml(normalizedAuthor)}"`];
178
- if (normalizedDate) attrs.push(`w:date="${escapeXml(normalizedDate)}"`);
179
- if (normalizedRsid) attrs.push(`w:rsid="${escapeXml(normalizedRsid)}"`);
180
172
  const previousRPrXml = serializeTextFormatting(change.previousFormatting) || "<w:rPr/>";
181
- return `<w:rPrChange ${attrs.join(" ")}>${previousRPrXml}</w:rPrChange>`;
173
+ return `<w:rPrChange ${serializeTrackedChangeAttributes(change.info)}>${previousRPrXml}</w:rPrChange>`;
182
174
  }
183
175
  function serializeRunProperties(formatting, propertyChanges) {
184
176
  const currentRPrXml = serializeTextFormatting(formatting);
185
- const combined = `${currentRPrXml ? extractRPrInner(currentRPrXml) : ""}${(propertyChanges ?? []).map(serializeRunPropertyChange).join("")}`;
177
+ const currentInner = currentRPrXml ? extractRPrInner(currentRPrXml) : "";
178
+ const propertyChange = getSingularRunPropertyChange(propertyChanges);
179
+ const combined = `${currentInner}${propertyChange ? serializeRunPropertyChange(propertyChange) : ""}`;
186
180
  if (!combined) return "";
187
181
  return `<w:rPr>${combined}</w:rPr>`;
188
182
  }
@@ -1,7 +1,7 @@
1
1
  import { getUnserializedSectionPropertyChildNames } from "../sectionParser.js";
2
2
  import { serializeBorder } from "./borderSerializer.js";
3
+ import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
3
4
  import { escapeXml, intAttr } from "./xmlUtils.js";
4
- import { normalizeRevisionId } from "@stll/docx-core/model";
5
5
  //#region src/docx/serializer/sectionPropertiesSerializer.ts
6
6
  const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${escapeXml(ref.rId)}"/>`;
7
7
  const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${escapeXml(ref.rId)}"/>`;
@@ -133,16 +133,8 @@ function serializeOnOffElement(value, name) {
133
133
  return value ? `<w:${name}/>` : `<w:${name} w:val="0"/>`;
134
134
  }
135
135
  function serializeSectionPropertyChange(change) {
136
- const normalizedId = normalizeRevisionId(change.info.id);
137
- const authorCandidate = typeof change.info.author === "string" ? change.info.author.trim() : "";
138
- const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
139
- const normalizedDate = typeof change.info.date === "string" ? change.info.date.trim() : void 0;
140
- const normalizedRsid = typeof change.info.rsid === "string" ? change.info.rsid.trim() : void 0;
141
- const attrs = [`w:id="${normalizedId}"`, `w:author="${escapeXml(normalizedAuthor)}"`];
142
- if (normalizedDate) attrs.push(`w:date="${escapeXml(normalizedDate)}"`);
143
- if (normalizedRsid) attrs.push(`w:rsid="${escapeXml(normalizedRsid)}"`);
144
136
  const previousSectPrXml = serializeSectionProperties(change.previousProperties) || "<w:sectPr/>";
145
- return `<w:sectPrChange ${attrs.join(" ")}>${previousSectPrXml}</w:sectPrChange>`;
137
+ return `<w:sectPrChange ${serializeTrackedChangeAttributes(change.info)}>${previousSectPrXml}</w:sectPrChange>`;
146
138
  }
147
139
  function serializeSectionProperties(props) {
148
140
  if (!props) return "";
@@ -3,8 +3,8 @@ import { isValidHexColor } from "../../utils/colorResolver.js";
3
3
  import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTableRowProperties } from "../tableParser.js";
4
4
  import { OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
5
5
  import { serializeBorder } from "./borderSerializer.js";
6
+ import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
6
7
  import { escapeXml, intAttr } from "./xmlUtils.js";
7
- import { normalizeRevisionId } from "@stll/docx-core/model";
8
8
  //#region src/docx/serializer/tableSerializer.ts
9
9
  /**
10
10
  * The captured element with the revision children written back into it.
@@ -69,25 +69,6 @@ const verifiedSourceXml = (formatting, parse) => {
69
69
  const reparsed = parse(parseXml(sourceXml, OOXML_NAMESPACE_SCOPE).elements?.[0] ?? null);
70
70
  return canonicalJson(withoutCaptures(reparsed)) === canonicalJson(withoutCaptures(formatting)) ? sourceXml : null;
71
71
  };
72
- function normalizeTrackedChangeInfo(info) {
73
- const normalizedId = normalizeRevisionId(info.id);
74
- const authorCandidate = typeof info.author === "string" ? info.author.trim() : "";
75
- const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
76
- const normalizedDate = typeof info.date === "string" ? info.date.trim() : void 0;
77
- return {
78
- id: normalizedId,
79
- author: normalizedAuthor,
80
- ...normalizedDate !== void 0 ? { date: normalizedDate } : {}
81
- };
82
- }
83
- function serializeTrackedChangeAttributes(info, rsid) {
84
- const normalized = normalizeTrackedChangeInfo(info);
85
- const attrs = [`w:id="${normalized.id}"`, `w:author="${escapeXml(normalized.author)}"`];
86
- if (normalized.date) attrs.push(`w:date="${escapeXml(normalized.date)}"`);
87
- if (info.utcDate) attrs.push(`${info.utcDate.attribute}="${escapeXml(info.utcDate.value)}"`);
88
- if (rsid && rsid.trim().length > 0) attrs.push(`w:rsid="${escapeXml(rsid.trim())}"`);
89
- return attrs.join(" ");
90
- }
91
72
  /**
92
73
  * Serialize a table measurement (width, height)
93
74
  */
@@ -231,7 +212,7 @@ function extractTblPrInner(tblPrXml) {
231
212
  return tblPrXml.slice(9, -10);
232
213
  }
233
214
  function serializeTablePropertyChange(change) {
234
- const attrs = serializeTrackedChangeAttributes(change.info, change.info.rsid);
215
+ const attrs = serializeTrackedChangeAttributes(change.info);
235
216
  const previousTblPrInner = extractTblPrInner(serializeTableFormatting(change.previousFormatting) || "<w:tblPr/>");
236
217
  return `<w:tblPrChange ${attrs}>${previousTblPrInner.length > 0 ? `<w:tblPr>${previousTblPrInner}</w:tblPr>` : "<w:tblPr/>"}</w:tblPrChange>`;
237
218
  }
@@ -269,7 +250,7 @@ function extractTrPrInner(trPrXml) {
269
250
  return trPrXml.slice(8, -9);
270
251
  }
271
252
  function serializeTableRowPropertyChange(change) {
272
- const attrs = serializeTrackedChangeAttributes(change.info, change.info.rsid);
253
+ const attrs = serializeTrackedChangeAttributes(change.info);
273
254
  const previousTrPrInner = extractTrPrInner(serializeTableRowFormatting(change.previousFormatting) || "<w:trPr/>");
274
255
  return `<w:trPrChange ${attrs}>${previousTrPrInner.length > 0 ? `<w:trPr>${previousTrPrInner}</w:trPr>` : "<w:trPr/>"}</w:trPrChange>`;
275
256
  }
@@ -333,7 +314,7 @@ function extractTcPrInner(tcPrXml) {
333
314
  return tcPrXml.slice(8, -9);
334
315
  }
335
316
  function serializeTableCellPropertyChange(change) {
336
- const attrs = serializeTrackedChangeAttributes(change.info, change.info.rsid);
317
+ const attrs = serializeTrackedChangeAttributes(change.info);
337
318
  const previousTcPrInner = extractTcPrInner(serializeTableCellFormatting(change.previousFormatting) || "<w:tcPr/>");
338
319
  return `<w:tcPrChange ${attrs}>${previousTcPrInner.length > 0 ? `<w:tcPr>${previousTcPrInner}</w:tcPr>` : "<w:tcPr/>"}</w:tcPrChange>`;
339
320
  }
@@ -0,0 +1,11 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ //#region src/docx/serializer/trackedChangeAttributes.d.ts
3
+ type SerializableTrackedChangeInfo = document_d_exports.TrackedChangeInfo | document_d_exports.PropertyChangeInfo;
4
+ /** Enforces the singular `w:rPrChange` child in a run-property container. */
5
+ declare const getSingularRunPropertyChange: (propertyChanges: readonly document_d_exports.RunPropertyChange[] | undefined) => document_d_exports.RunPropertyChange | undefined;
6
+ /** Normalized, unescaped attributes in schema order for an XML element or string serializer. */
7
+ declare const trackedChangeAttributeEntries: (info: SerializableTrackedChangeInfo) => readonly (readonly [string, string])[];
8
+ declare const trackedChangeAttributeRecord: (info: SerializableTrackedChangeInfo) => Record<string, string>;
9
+ declare const serializeTrackedChangeAttributes: (info: SerializableTrackedChangeInfo) => string;
10
+ //#endregion
11
+ export { getSingularRunPropertyChange, serializeTrackedChangeAttributes, trackedChangeAttributeEntries, trackedChangeAttributeRecord };
@@ -0,0 +1,31 @@
1
+ import { DATE_UTC_ATTRIBUTE } from "../trackedChangeInfo.js";
2
+ import { escapeXml } from "./xmlUtils.js";
3
+ import { panic } from "better-result";
4
+ import { normalizeRevisionId } from "@stll/docx-core/model";
5
+ //#region src/docx/serializer/trackedChangeAttributes.ts
6
+ /** Enforces the singular `w:rPrChange` child in a run-property container. */
7
+ const getSingularRunPropertyChange = (propertyChanges) => {
8
+ const propertyChangeCount = propertyChanges?.length ?? 0;
9
+ if (propertyChangeCount > 1) panic("A run-property container cannot serialize more than one w:rPrChange", {
10
+ elementName: "w:rPrChange",
11
+ propertyChangeCount
12
+ });
13
+ return propertyChanges?.at(0);
14
+ };
15
+ /** Normalized, unescaped attributes in schema order for an XML element or string serializer. */
16
+ const trackedChangeAttributeEntries = (info) => {
17
+ const author = typeof info.author === "string" ? info.author.trim() : "";
18
+ const date = typeof info.date === "string" ? info.date.trim() : "";
19
+ const rawUtcDate = info.utcDate;
20
+ const utcDate = typeof rawUtcDate === "object" && rawUtcDate !== null && "value" in rawUtcDate && typeof rawUtcDate.value === "string" ? rawUtcDate.value.trim() : "";
21
+ const rsid = "rsid" in info && typeof info.rsid === "string" ? info.rsid.trim() : "";
22
+ const entries = [["w:id", String(normalizeRevisionId(info.id))], ["w:author", author.length > 0 ? author : "Unknown"]];
23
+ if (date.length > 0) entries.push(["w:date", date]);
24
+ if (utcDate.length > 0) entries.push([DATE_UTC_ATTRIBUTE, utcDate]);
25
+ if (rsid.length > 0) entries.push(["w:rsid", rsid]);
26
+ return entries;
27
+ };
28
+ const trackedChangeAttributeRecord = (info) => Object.fromEntries(trackedChangeAttributeEntries(info));
29
+ const serializeTrackedChangeAttributes = (info) => trackedChangeAttributeEntries(info).map(([name, value]) => `${name}="${escapeXml(value)}"`).join(" ");
30
+ //#endregion
31
+ export { getSingularRunPropertyChange, serializeTrackedChangeAttributes, trackedChangeAttributeEntries, trackedChangeAttributeRecord };
@@ -1,7 +1,7 @@
1
1
  import { ShadingProperties } from "../../types/colors.js";
2
2
  import { document_d_exports } from "../../types/document.js";
3
- import { Hyperlink, Paragraph, ParagraphContent, Run, Table } from "../../types/content.js";
4
3
  import { ParagraphFormatting, TextFormatting } from "../../types/formatting.js";
4
+ import { Hyperlink, Paragraph, ParagraphContent, Run, Table } from "../../types/content.js";
5
5
  //#region src/docx/server/build.d.ts
6
6
  declare const HEADING_LEVELS: readonly [1, 2, 3, 4, 5, 6];
7
7
  type HeadingLevel = (typeof HEADING_LEVELS)[number];