@stll/folio-core 0.45.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/ai-edits/apply.js +59 -19
  2. package/dist/ai-edits/headless.js +1 -1
  3. package/dist/compare/compare.js +2 -5
  4. package/dist/compare/scenario.js +2 -5
  5. package/dist/compare/types.d.ts +9 -1
  6. package/dist/compare/types.js +12 -1
  7. package/dist/compat/eigenpal.d.ts +2 -1
  8. package/dist/compat/eigenpal.js +2 -1
  9. package/dist/content-controls/checkboxDisplay.d.ts +11 -0
  10. package/dist/content-controls/checkboxDisplay.js +40 -0
  11. package/dist/content-controls/findContentControls.js +3 -1
  12. package/dist/content-controls/mutateContentControls.js +17 -2
  13. package/dist/controller/contentControlWidgetController.d.ts +1 -0
  14. package/dist/controller/contentControlWidgetController.js +3 -0
  15. package/dist/controller/hiddenEditorApi.js +3 -1
  16. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  17. package/dist/controller/hiddenEditorManager.js +6 -3
  18. package/dist/display-list/build/buildDisplayList.js +20 -1
  19. package/dist/display-list/build/imagePrimitives.d.ts +1 -1
  20. package/dist/display-list/build/imagePrimitives.js +3 -1
  21. package/dist/display-list/build/paragraphPrimitives.js +21 -2
  22. package/dist/display-list/dom/renderDisplayListToDom.js +10 -2
  23. package/dist/display-list/types.d.ts +17 -0
  24. package/dist/docx/attributeRemainder.d.ts +43 -0
  25. package/dist/docx/attributeRemainder.js +65 -0
  26. package/dist/docx/blockContentParser.d.ts +1 -1
  27. package/dist/docx/blockContentParser.js +87 -63
  28. package/dist/docx/blockPlainText.d.ts +3 -4
  29. package/dist/docx/blockPlainText.js +1 -0
  30. package/dist/docx/bookmarkPlacement.js +2 -0
  31. package/dist/docx/commentAnchorIndex.d.ts +30 -0
  32. package/dist/docx/commentAnchorIndex.js +50 -0
  33. package/dist/docx/commentParser.d.ts +1 -1
  34. package/dist/docx/commentParser.js +54 -37
  35. package/dist/docx/commentRangeIntegrity.d.ts +16 -1
  36. package/dist/docx/commentRangeIntegrity.js +42 -1
  37. package/dist/docx/commentRangeJoin.d.ts +9 -0
  38. package/dist/docx/commentRangeJoin.js +33 -0
  39. package/dist/docx/commentReferenceCompletion.d.ts +9 -0
  40. package/dist/docx/commentReferenceCompletion.js +47 -0
  41. package/dist/docx/commentReferenceNormalization.js +1 -0
  42. package/dist/docx/commentReplyMarkers.js +11 -19
  43. package/dist/docx/compatibility.js +1 -0
  44. package/dist/docx/containerChildren.d.ts +111 -0
  45. package/dist/docx/containerChildren.gen.d.ts +28 -0
  46. package/dist/docx/containerChildren.gen.js +246 -0
  47. package/dist/docx/containerChildren.js +103 -0
  48. package/dist/docx/documentParser.d.ts +1 -1
  49. package/dist/docx/documentParser.js +3 -3
  50. package/dist/docx/ensureParaIds.js +17 -7
  51. package/dist/docx/fieldParser.d.ts +1 -1
  52. package/dist/docx/fieldParser.js +4 -4
  53. package/dist/docx/fieldState.d.ts +26 -0
  54. package/dist/docx/fieldState.js +53 -0
  55. package/dist/docx/footnoteParser.d.ts +1 -1
  56. package/dist/docx/graphicFrameLocks.d.ts +16 -4
  57. package/dist/docx/graphicFrameLocks.js +19 -6
  58. package/dist/docx/headerFooterParser.js +1 -1
  59. package/dist/docx/headerFooterReferenceNormalization.js +1 -0
  60. package/dist/docx/hyperlinkParser.d.ts +27 -8
  61. package/dist/docx/hyperlinkParser.js +76 -18
  62. package/dist/docx/imageParser.js +33 -8
  63. package/dist/docx/inlineWrapperContent.d.ts +85 -0
  64. package/dist/docx/inlineWrapperContent.js +84 -0
  65. package/dist/docx/normalizeBaseDirection.js +10 -1
  66. package/dist/docx/paraIdAttribute.d.ts +21 -0
  67. package/dist/docx/paraIdAttribute.js +64 -0
  68. package/dist/docx/paragraphParser.d.ts +1 -1
  69. package/dist/docx/paragraphParser.js +277 -168
  70. package/dist/docx/paragraphPropertySource.js +5 -1
  71. package/dist/docx/paragraphTextBoxEnrichment.d.ts +1 -1
  72. package/dist/docx/paragraphTextBoxEnrichment.js +6 -52
  73. package/dist/docx/paragraphTraversal.js +7 -4
  74. package/dist/docx/parseWarningMessage.js +2 -1
  75. package/dist/docx/preservedRunContent.d.ts +32 -0
  76. package/dist/docx/preservedRunContent.js +86 -0
  77. package/dist/docx/renderedPageBreakNormalization.js +2 -4
  78. package/dist/docx/rezip.js +74 -18
  79. package/dist/docx/runParser.d.ts +1 -1
  80. package/dist/docx/runParser.js +16 -12
  81. package/dist/docx/sectionParser.js +8 -1
  82. package/dist/docx/selectiveXmlPatch.d.ts +46 -2
  83. package/dist/docx/selectiveXmlPatch.js +86 -39
  84. package/dist/docx/serializer/borderSerializer.js +2 -2
  85. package/dist/docx/serializer/commentSerializer.js +5 -7
  86. package/dist/docx/serializer/documentSerializer.js +8 -11
  87. package/dist/docx/serializer/headerFooterSerializer.js +7 -9
  88. package/dist/docx/serializer/noteSerializer.js +8 -8
  89. package/dist/docx/serializer/paragraphSerializer.js +76 -56
  90. package/dist/docx/serializer/runSerializer.js +25 -16
  91. package/dist/docx/serializer/sectionPropertiesSerializer.js +8 -4
  92. package/dist/docx/serializer/tableSerializer.js +18 -8
  93. package/dist/docx/server/createBilingualDocument.js +3 -1
  94. package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
  95. package/dist/docx/server/materializeYjsDocx.js +9 -1
  96. package/dist/docx/server/migrateYjsAttrSchema.d.ts +55 -0
  97. package/dist/docx/server/migrateYjsAttrSchema.js +95 -0
  98. package/dist/docx/shapeParser.js +5 -5
  99. package/dist/docx/tableParser.d.ts +1 -1
  100. package/dist/docx/tableParser.js +206 -85
  101. package/dist/docx/textBoxParser.d.ts +25 -2
  102. package/dist/docx/textBoxParser.js +52 -2
  103. package/dist/docx/unzip.js +5 -4
  104. package/dist/docx/vmlImageParser.d.ts +17 -1
  105. package/dist/docx/vmlImageParser.js +17 -1
  106. package/dist/docx/xmlEncoding.d.ts +5 -0
  107. package/dist/docx/xmlEncoding.js +12 -0
  108. package/dist/docx/xmlParser.d.ts +10 -1
  109. package/dist/docx/xmlParser.js +14 -1
  110. package/dist/headless-layout.js +10 -1
  111. package/dist/index.d.ts +2 -1
  112. package/dist/index.js +2 -1
  113. package/dist/internal/pageBreakRunSourceDescendantIndex.js +5 -2
  114. package/dist/layout-bridge/convert/toFlowBlocks.js +102 -12
  115. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  116. package/dist/layout-engine/measure/tableCellFloating.js +2 -0
  117. package/dist/layout-engine/types.d.ts +23 -1
  118. package/dist/layout-painter/renderImage.d.ts +5 -1
  119. package/dist/layout-painter/renderImage.js +10 -3
  120. package/dist/layout-painter/renderPage.d.ts +2 -0
  121. package/dist/layout-painter/renderPage.js +4 -0
  122. package/dist/layout-painter/renderParagraph.d.ts +13 -1
  123. package/dist/layout-painter/renderParagraph.js +22 -3
  124. package/dist/managers/autoSaveCodec.d.ts +9 -1
  125. package/dist/managers/autoSaveCodec.js +12 -4
  126. package/dist/markdown/renderBlock.js +1 -0
  127. package/dist/markdown/renderRuns.d.ts +1 -1
  128. package/dist/markdown/renderRuns.js +28 -5
  129. package/dist/markdown/renderTable.js +22 -5
  130. package/dist/markdown/trailers.js +4 -1
  131. package/dist/pdf/images.js +142 -6
  132. package/dist/prosemirror/attrs/index.d.ts +14 -3
  133. package/dist/prosemirror/attrs/index.js +155 -2
  134. package/dist/prosemirror/authoredTransformAttrs.d.ts +28 -0
  135. package/dist/prosemirror/authoredTransformAttrs.js +64 -0
  136. package/dist/prosemirror/commands/contentControls.js +10 -2
  137. package/dist/prosemirror/commands/image.d.ts +9 -2
  138. package/dist/prosemirror/commands/image.js +42 -28
  139. package/dist/prosemirror/commands/pageBreak.js +1 -1
  140. package/dist/prosemirror/commentReferenceAttrs.d.ts +8 -0
  141. package/dist/prosemirror/commentReferenceAttrs.js +33 -0
  142. package/dist/prosemirror/commentReferenceIntegrity.d.ts +47 -0
  143. package/dist/prosemirror/commentReferenceIntegrity.js +43 -0
  144. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  145. package/dist/prosemirror/conversion/fromProseDoc.js +269 -70
  146. package/dist/prosemirror/conversion/index.d.ts +2 -2
  147. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -9
  148. package/dist/prosemirror/conversion/toProseDoc.js +468 -308
  149. package/dist/prosemirror/extensions/StarterKit.js +8 -58
  150. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  151. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
  152. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +45 -27
  153. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -1
  154. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +5 -3
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -0
  156. package/dist/prosemirror/extensions/markRegistry.d.ts +41 -0
  157. package/dist/prosemirror/extensions/markRegistry.js +75 -0
  158. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.d.ts +18 -0
  159. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +63 -0
  160. package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
  161. package/dist/prosemirror/extensions/marks/markUtils.js +47 -4
  162. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.d.ts +11 -0
  163. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +87 -0
  164. package/dist/prosemirror/extensions/nodes/FieldExtension.js +9 -7
  165. package/dist/prosemirror/extensions/nodes/ImageExtension.js +16 -0
  166. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.d.ts +32 -0
  167. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +67 -0
  168. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.d.ts +16 -0
  169. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +60 -0
  170. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +3 -0
  171. package/dist/prosemirror/extensions/nodes/TableExtension.js +3 -2
  172. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  173. package/dist/prosemirror/inlineWrapperStack.d.ts +39 -0
  174. package/dist/prosemirror/inlineWrapperStack.js +75 -0
  175. package/dist/prosemirror/pageBreakRunProjection.d.ts +17 -7
  176. package/dist/prosemirror/pageBreakRunProjection.js +19 -9
  177. package/dist/prosemirror/replacedAnnotations.d.ts +59 -0
  178. package/dist/prosemirror/replacedAnnotations.js +165 -0
  179. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +3 -1
  180. package/dist/prosemirror/runFormattingInlineCarriers.js +5 -1
  181. package/dist/prosemirror/schema/index.d.ts +3 -3
  182. package/dist/prosemirror/schema/marks.d.ts +25 -1
  183. package/dist/prosemirror/schema/nodes.d.ts +90 -3
  184. package/dist/prosemirror/schema/nodes.js +16 -1
  185. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +2 -0
  186. package/dist/prosemirror/trackedRunInlineAtoms.js +2 -0
  187. package/dist/prosemirror/validation.js +16 -2
  188. package/dist/prosemirror/yjsDocumentMetadata.d.ts +73 -0
  189. package/dist/prosemirror/yjsDocumentMetadata.js +171 -0
  190. package/dist/prosemirror/zeroWidthAnchors.js +2 -0
  191. package/dist/render-dom/commentAnchorAttributes.d.ts +37 -0
  192. package/dist/render-dom/commentAnchorAttributes.js +54 -0
  193. package/dist/server.d.ts +3 -1
  194. package/dist/server.js +3 -1
  195. package/dist/types/content.d.ts +2 -2
  196. package/dist/utils/clipboard.d.ts +9 -1
  197. package/dist/utils/clipboard.js +27 -1
  198. package/dist/utils/findReplace.js +1 -1
  199. package/dist/utils/imageLuminance.d.ts +15 -0
  200. package/dist/utils/imageLuminance.js +32 -0
  201. package/dist/utils/mergeDocumentContent.js +7 -17
  202. package/dist/utils/replaceText.js +1 -0
  203. package/package.json +2 -2
  204. package/dist/docx/blockRangeMarkers.d.ts +0 -36
  205. package/dist/docx/blockRangeMarkers.js +0 -59
  206. package/dist/prosemirror/yjsParagraphSourceContract.d.ts +0 -9
  207. package/dist/prosemirror/yjsParagraphSourceContract.js +0 -26
@@ -6,7 +6,7 @@ import { countCompressibleSpaces, toPaintedText } from "../../layout-engine/meas
6
6
  import { isFloatingImageRun } from "../../layout-engine/types.js";
7
7
  import { resolveParagraphBorderHorizontalOutsets } from "../../layout-painter/borderStroke.js";
8
8
  import { getAutomaticTextColorForBackground } from "../../layout-painter/documentColors.js";
9
- import { getLeaderChar, getRenderableTextColor, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap } from "../../layout-painter/renderParagraph.js";
9
+ import { UNICODE_BIDI_BY_WRAPPER_CONTROL, getLeaderChar, getRenderableTextColor, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap } from "../../layout-painter/renderParagraph.js";
10
10
  import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
11
11
  import { resolvePhysicalParagraphInlineLayout } from "../../utils/paragraphInlineLayout.js";
12
12
  import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
@@ -278,6 +278,23 @@ const modelRangeOf = (run, context) => run.pmStart === void 0 || run.pmEnd === v
278
278
  story: context.story
279
279
  };
280
280
  /**
281
+ * The base direction one run's glyphs are laid out in.
282
+ *
283
+ * A bidirectional wrapper the author wrote around the run states it, and is
284
+ * nearer than the paragraph's own base direction: an override (`w:bdo`) lays
285
+ * every character out that way, and an embedding (`w:dir`) opens a level in
286
+ * that direction. A wrapper with no `w:val` states nothing, and the paragraph
287
+ * decides as before.
288
+ */
289
+ const runGlyphDirection = (run, paragraphIsRtl) => run.bidiWrapper?.direction ?? (paragraphIsRtl ? "rtl" : "ltr");
290
+ /**
291
+ * The wrapper's control, for the backend that resolves the order itself.
292
+ *
293
+ * The direction alone does not separate the two: an override forces the
294
+ * order, an embedding leaves the algorithm to resolve within it.
295
+ */
296
+ const runUnicodeBidi = (run) => run.bidiWrapper === void 0 ? void 0 : UNICODE_BIDI_BY_WRAPPER_CONTROL[run.bidiWrapper.control];
297
+ /**
281
298
  * One `glyphRun` plus everything painted around it: the run's background rect
282
299
  * first, then the glyphs, then the decorations whose geometry CSS would have
283
300
  * derived from the font.
@@ -342,6 +359,7 @@ const emitGlyphRun = ({ sink, context, run, glyphs, style, paintXPx, baselineYPx
342
359
  thicknessPx: 1,
343
360
  pattern: "solid"
344
361
  } : void 0;
362
+ const unicodeBidi = runUnicodeBidi(run);
345
363
  sink.glyphs.push({
346
364
  kind: "glyphRun",
347
365
  font,
@@ -350,7 +368,8 @@ const emitGlyphRun = ({ sink, context, run, glyphs, style, paintXPx, baselineYPx
350
368
  xPx: paintXPx,
351
369
  baselineYPx: runBaselineYPx,
352
370
  ...glyphRunText(glyphs),
353
- direction: isRtl ? "rtl" : "ltr",
371
+ direction: runGlyphDirection(run, isRtl),
372
+ ...unicodeBidi === void 0 ? {} : { unicodeBidi },
354
373
  ...stroke === void 0 ? {} : { stroke },
355
374
  ...pmRange === void 0 ? {} : { pmRange },
356
375
  ...collapsedEdge === void 0 ? {} : { collapsedEdge }
@@ -1,4 +1,6 @@
1
+ import { writeCommentAnchorIds } from "../../render-dom/commentAnchorAttributes.js";
1
2
  import { bytesToDataUrl } from "../../utils/base64.js";
3
+ import { imageLuminanceFilter } from "../../utils/imageLuminance.js";
2
4
  import { anchorTargetAttrs } from "../../utils/urlSecurity.js";
3
5
  import { STROKE_DASH_FACTORS, walkRegions } from "../primitives.js";
4
6
  import { panic } from "better-result";
@@ -310,6 +312,7 @@ const paintGlyphRun = (run, context) => {
310
312
  span.style.fontStyle = face.italic ? "italic" : "normal";
311
313
  span.style.color = cssColor(run.color);
312
314
  span.style.direction = run.direction;
315
+ if (run.unicodeBidi !== void 0) span.style.unicodeBidi = run.unicodeBidi;
313
316
  if (run.stroke !== void 0) span.style.webkitTextStroke = `${px(run.stroke.thicknessPx)} ${cssColor(run.stroke.color)}`;
314
317
  span.style.fontKerning = run.kerning ? "normal" : "none";
315
318
  if (run.smallCaps) span.style.fontVariant = "small-caps";
@@ -387,13 +390,18 @@ const paintRect = ({ rect, fill, stroke }, context) => {
387
390
  default: panic(`renderDisplayListToDom: unhandled rect stroke painter ${JSON.stringify(painter)}`);
388
391
  }
389
392
  };
390
- const paintImage = ({ image, rect, crop, opacity }, context) => {
393
+ const paintImage = ({ image, rect, crop, opacity, brightness, contrast }, context) => {
391
394
  const source = resolveImage(image, context.images);
392
395
  const clip = createAbsoluteDiv(context, rect);
393
396
  clip.style.overflow = "hidden";
394
397
  const element = context.doc.createElement("img");
395
398
  element.style.position = "absolute";
396
399
  element.style.opacity = String(opacity);
400
+ const filter = imageLuminanceFilter({
401
+ ...brightness === void 0 ? {} : { brightness },
402
+ ...contrast === void 0 ? {} : { contrast }
403
+ });
404
+ if (filter !== void 0) element.style.filter = filter;
397
405
  element.style.left = "0px";
398
406
  element.style.top = "0px";
399
407
  element.style.width = px(rect.widthPx);
@@ -549,7 +557,7 @@ const applyRegionModel = (element, region) => {
549
557
  element.dataset["pmEnd"] = String(model.pmRange.end);
550
558
  }
551
559
  if (model.blockId !== void 0) element.dataset["blockId"] = model.blockId;
552
- if (model.commentIds !== void 0 && model.commentIds.length > 0) element.dataset["commentId"] = String(model.commentIds[0]);
560
+ if (model.commentIds !== void 0) writeCommentAnchorIds(element, model.commentIds);
553
561
  if (model.rowIndex !== void 0) element.dataset["rowIndex"] = String(model.rowIndex);
554
562
  if (model.columnIndex !== void 0) element.dataset["columnIndex"] = String(model.columnIndex);
555
563
  const story = model.story ?? model.pmRange?.story;
@@ -215,6 +215,19 @@ type DisplayGlyphRun = {
215
215
  readonly text: string;
216
216
  readonly advancesPx: readonly number[];
217
217
  readonly direction: "ltr" | "rtl";
218
+ /**
219
+ * How a backend that resolves bidirectional text itself must treat the run.
220
+ *
221
+ * `direction` alone is the base direction, and a backend that hands the text
222
+ * to such a resolver gets the ordinary algorithm: a Latin word inside an
223
+ * `rtl` run reads forwards. That is an embedding, and it is wrong for the
224
+ * run the author wrapped in an override (`w:bdo`), where every character is
225
+ * laid out in `direction` whatever its own script. Absent is the ordinary
226
+ * run, whose base direction the paragraph or the run itself decided; a
227
+ * backend that positions every glyph from `advancesPx` reorders nothing and
228
+ * ignores this.
229
+ */
230
+ readonly unicodeBidi?: "embed" | "bidi-override";
218
231
  /**
219
232
  * Whether the glyphs kern.
220
233
  *
@@ -299,6 +312,10 @@ type DisplayImagePrimitive = {
299
312
  readonly b: number;
300
313
  };
301
314
  readonly opacity: number;
315
+ /** DrawingML picture brightness as a signed percentage. */
316
+ readonly brightness?: number;
317
+ /** DrawingML picture contrast as a signed percentage. */
318
+ readonly contrast?: number;
302
319
  };
303
320
  /** Clips its children to `rect`. Used for cropped images and cell overflow. */
304
321
  type DisplayClipGroup = {
@@ -0,0 +1,43 @@
1
+ import { XmlElement } from "./xmlParser.js";
2
+ import { PreservedAttribute } from "@stll/docx-core/model";
3
+ //#region src/docx/attributeRemainder.d.ts
4
+ /**
5
+ * The modelled set of an element whose serializer writes no attribute at all.
6
+ *
7
+ * `w:r`, `w:tr` and `w:sectPr` are rebuilt as bare start tags, so everything
8
+ * their source wrote is the remainder. Named rather than spelled `new Set()`
9
+ * at each call site, so the claim is visible and one place has to change when
10
+ * one of them gains a modelled attribute.
11
+ */
12
+ declare const NO_MODELLED_ATTRIBUTES: ReadonlySet<string>;
13
+ type AttributeRemainderOptions = {
14
+ /** The element, as the source wrote it. */
15
+ element: XmlElement;
16
+ /**
17
+ * Local names this element's own parser reads.
18
+ *
19
+ * Local names rather than qualified ones, because folio's attribute readers
20
+ * fall back to a local-name match across prefixes: an attribute the parser
21
+ * would read under any binding has to be out of the remainder, or the save
22
+ * writes the value twice under two spellings.
23
+ */
24
+ modelled: ReadonlySet<string>;
25
+ };
26
+ /**
27
+ * Every attribute on the element the model has no field for.
28
+ *
29
+ * @returns the remainder, or `undefined` when the parser read them all — an
30
+ * empty list is never written, so a fully modelled element stays identical
31
+ * in the model.
32
+ */
33
+ declare const attributeRemainder: ({ element, modelled }: AttributeRemainderOptions) => PreservedAttribute[] | undefined;
34
+ /**
35
+ * The element's attributes, modelled first and the remainder after them.
36
+ *
37
+ * @param modelled the fragments the serializer is about to emit, already
38
+ * spelled — they win a collision, because the model is the editable copy
39
+ * @returns the fragments to write, so an element with neither writes nothing
40
+ */
41
+ declare const serializePreservedAttributes: (modelled: readonly string[], preserved: readonly PreservedAttribute[] | undefined) => string[];
42
+ //#endregion
43
+ export { NO_MODELLED_ATTRIBUTES, attributeRemainder, serializePreservedAttributes };
@@ -0,0 +1,65 @@
1
+ import { OOXML_NAMESPACES } from "./serializer/partNamespaces.js";
2
+ import { getLocalName, resolveAttributeNamespaceUri } from "./xmlParser.js";
3
+ import { escapeXmlAttribute } from "@stll/docx-core";
4
+ //#region src/docx/attributeRemainder.ts
5
+ /** Namespace URI to the prefix a rebuilt part binds for it. */
6
+ const CANONICAL_PREFIX = new Map(Object.entries(OOXML_NAMESPACES).map(([prefix, { uri }]) => [uri, prefix]));
7
+ /**
8
+ * The modelled set of an element whose serializer writes no attribute at all.
9
+ *
10
+ * `w:r`, `w:tr` and `w:sectPr` are rebuilt as bare start tags, so everything
11
+ * their source wrote is the remainder. Named rather than spelled `new Set()`
12
+ * at each call site, so the claim is visible and one place has to change when
13
+ * one of them gains a modelled attribute.
14
+ */
15
+ const NO_MODELLED_ATTRIBUTES = /* @__PURE__ */ new Set();
16
+ /**
17
+ * Every attribute on the element the model has no field for.
18
+ *
19
+ * @returns the remainder, or `undefined` when the parser read them all — an
20
+ * empty list is never written, so a fully modelled element stays identical
21
+ * in the model.
22
+ */
23
+ const attributeRemainder = ({ element, modelled }) => {
24
+ const attributes = element.attributes;
25
+ if (!attributes) return;
26
+ const remainder = [];
27
+ for (const [spelling, value] of Object.entries(attributes)) {
28
+ if (value === void 0 || spelling === "xmlns" || spelling.startsWith("xmlns:")) continue;
29
+ const name = getLocalName(spelling);
30
+ if (modelled.has(name)) continue;
31
+ const namespace = resolveAttributeNamespaceUri(element, spelling);
32
+ if (namespace !== void 0 && !CANONICAL_PREFIX.has(namespace)) continue;
33
+ remainder.push({
34
+ ...namespace === void 0 ? {} : { namespace },
35
+ name,
36
+ value: String(value)
37
+ });
38
+ }
39
+ return remainder.length === 0 ? void 0 : remainder;
40
+ };
41
+ /** The name a fragment such as `w14:paraId="1F2E"` writes. */
42
+ const spelledName = (fragment) => fragment.slice(0, fragment.indexOf("="));
43
+ /**
44
+ * The element's attributes, modelled first and the remainder after them.
45
+ *
46
+ * @param modelled the fragments the serializer is about to emit, already
47
+ * spelled — they win a collision, because the model is the editable copy
48
+ * @returns the fragments to write, so an element with neither writes nothing
49
+ */
50
+ const serializePreservedAttributes = (modelled, preserved) => {
51
+ if (preserved === void 0 || preserved.length === 0) return [...modelled];
52
+ const written = new Set(modelled.map(spelledName));
53
+ const fragments = [...modelled];
54
+ for (const { namespace, name, value } of preserved) {
55
+ const prefix = namespace === void 0 ? void 0 : CANONICAL_PREFIX.get(namespace);
56
+ if (namespace !== void 0 && prefix === void 0) continue;
57
+ const spelling = prefix === void 0 ? name : `${prefix}:${name}`;
58
+ if (written.has(spelling)) continue;
59
+ written.add(spelling);
60
+ fragments.push(`${spelling}="${escapeXmlAttribute(value)}"`);
61
+ }
62
+ return fragments;
63
+ };
64
+ //#endregion
65
+ export { NO_MODELLED_ATTRIBUTES, attributeRemainder, serializePreservedAttributes };
@@ -1,7 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
- import { StyleMap } from "./styleParser.js";
4
3
  import { XmlElement } from "./xmlParser.js";
4
+ import { StyleMap } from "./styleParser.js";
5
5
  //#region src/docx/blockContentParser.d.ts
6
6
  type ParseBlockContentOptions = {
7
7
  inHeaderFooter?: boolean;
@@ -1,7 +1,7 @@
1
- import { attachPendingRangeMarkers, attachTrailingRangeMarkers, isBlockRangeMarker } from "./blockRangeMarkers.js";
2
1
  import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
3
2
  import { appendBookmarkMarkerToLastParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInBlocks } from "./bookmarkPlacement.js";
4
3
  import { convertBulletToUnicode } from "./bulletMarkers.js";
4
+ import { CAPTURE, OWNED_ELSEWHERE, dispatchChildren, withPreservedChildren } from "./containerChildren.js";
5
5
  import { isNumberingReference } from "./numberingReference.js";
6
6
  import { formatOoxmlCounter } from "./ooxmlCounterFormatter.js";
7
7
  import { parseParagraph } from "./paragraphParser.js";
@@ -9,7 +9,7 @@ import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
9
9
  import { parseSdtProperties } from "./sdtProperties.js";
10
10
  import { parseTable } from "./tableParser.js";
11
11
  import { captureVerbatimXml } from "./verbatimCapture.js";
12
- import { findChild, getChildElements, getLocalName, mergeXmlnsDeclarations, selectAlternateContentBranch } from "./xmlParser.js";
12
+ import { findChild, getLocalName, mergeXmlnsDeclarations, selectAlternateContentBranch } from "./xmlParser.js";
13
13
  //#region src/docx/blockContentParser.ts
14
14
  const computeListMarker = (paragraph, { numbering, listCounters, abstractCounters, restartedNumIds, previousList }) => {
15
15
  const listRendering = paragraph.listRendering;
@@ -104,72 +104,100 @@ const parseBlockContent = (parent, styles, theme, numbering, rels, media, option
104
104
  }
105
105
  });
106
106
  const parseBlockContentWithState = (parent, styles, theme, numbering, rels, media, state) => {
107
- const content = [];
108
- const children = getChildElements(parent);
107
+ const modelled = [];
109
108
  const pendingBookmarkMarkers = [];
110
- const pendingRangeMarkers = [];
111
- for (const child of children) {
112
- const localName = getLocalName(child.name ?? "");
113
- if (localName === "p") {
114
- const paragraph = parseParagraph(child, styles, theme, numbering, rels, media, state.options);
115
- prependPendingBookmarkMarkers(paragraph, pendingBookmarkMarkers);
116
- enrichParagraphTextBoxes(paragraph, child, styles, theme, numbering, rels, media, parseTable);
117
- computeListMarker(paragraph, {
118
- numbering,
119
- listCounters: state.listCounters,
120
- abstractCounters: state.abstractCounters,
121
- restartedNumIds: state.restartedNumIds,
122
- previousList: state.previousList
123
- });
124
- attachPendingRangeMarkers(paragraph, pendingRangeMarkers);
125
- content.push(paragraph);
126
- continue;
127
- }
128
- if (localName === "tbl") {
129
- const table = parseTable(child, styles, theme, numbering, rels, media, state.options);
130
- if (!table) continue;
131
- if (prependBookmarkMarkersToFirstParagraphInBlocks([table], pendingBookmarkMarkers)) pendingBookmarkMarkers.length = 0;
132
- attachPendingRangeMarkers(table, pendingRangeMarkers);
133
- content.push(table);
134
- continue;
135
- }
136
- if (localName === "sdt") {
137
- const sdtPr = findChild(child, "w", "sdtPr");
138
- const sdtEndPr = findChild(child, "w", "sdtEndPr");
139
- const sdtContent = findChild(child, "w", "sdtContent");
140
- const properties = parseSdtProperties(sdtPr, sdtEndPr);
141
- const captured = captureSdtSiblingMarkers(child);
142
- if (captured.before.length > 0) properties.rawSdtChildrenBeforeContent = captured.before;
143
- if (captured.after.length > 0) properties.rawSdtChildrenAfterContent = captured.after;
144
- const blockSdt = {
145
- type: "blockSdt",
146
- properties,
147
- content: sdtContent ? parseBlockContentWithState(sdtContent, styles, theme, numbering, rels, media, withContainerXmlns(withContainerXmlns(state, child), sdtContent)) : []
148
- };
149
- if (prependBookmarkMarkersToFirstParagraphInBlocks(blockSdt.content, pendingBookmarkMarkers)) pendingBookmarkMarkers.length = 0;
150
- attachPendingRangeMarkers(blockSdt, pendingRangeMarkers);
151
- content.push(blockSdt);
152
- continue;
153
- }
154
- if (localName === "AlternateContent") {
109
+ const content = withPreservedChildren(modelled, dispatchChildren({
110
+ element: parent,
111
+ container: "block-content",
112
+ modelledCount: () => modelled.length,
113
+ undeclared: { AlternateContent: (child) => {
155
114
  const selectedBranch = selectAlternateContentBranch(child);
156
- if (selectedBranch) content.push(...parseBlockContentWithState(selectedBranch, styles, theme, numbering, rels, media, withContainerXmlns(withContainerXmlns(state, child), selectedBranch)));
157
- continue;
115
+ if (!selectedBranch) return;
116
+ modelled.push(...parseBlockContentWithState(selectedBranch, styles, theme, numbering, rels, media, withContainerXmlns(withContainerXmlns(state, child), selectedBranch)));
117
+ } },
118
+ handlers: {
119
+ p: (child) => {
120
+ const paragraph = parseParagraph(child, styles, theme, numbering, rels, media, state.options);
121
+ prependPendingBookmarkMarkers(paragraph, pendingBookmarkMarkers);
122
+ enrichParagraphTextBoxes(paragraph, child, styles, theme, numbering, rels, media, parseTable);
123
+ computeListMarker(paragraph, {
124
+ numbering,
125
+ listCounters: state.listCounters,
126
+ abstractCounters: state.abstractCounters,
127
+ restartedNumIds: state.restartedNumIds,
128
+ previousList: state.previousList
129
+ });
130
+ modelled.push(paragraph);
131
+ },
132
+ tbl: (child) => {
133
+ const table = parseTable(child, styles, theme, numbering, rels, media, state.options);
134
+ if (!table) return;
135
+ if (prependBookmarkMarkersToFirstParagraphInBlocks([table], pendingBookmarkMarkers)) pendingBookmarkMarkers.length = 0;
136
+ modelled.push(table);
137
+ },
138
+ sdt: (child) => {
139
+ const blockSdt = parseBlockSdt(child, styles, theme, numbering, rels, media, state);
140
+ if (prependBookmarkMarkersToFirstParagraphInBlocks(blockSdt.content, pendingBookmarkMarkers)) pendingBookmarkMarkers.length = 0;
141
+ modelled.push(blockSdt);
142
+ },
143
+ bookmarkStart: (child) => {
144
+ collectBookmarkMarker(child, "bookmarkStart", modelled, pendingBookmarkMarkers);
145
+ },
146
+ bookmarkEnd: (child) => {
147
+ collectBookmarkMarker(child, "bookmarkEnd", modelled, pendingBookmarkMarkers);
148
+ },
149
+ sectPr: OWNED_ELSEWHERE,
150
+ tcPr: OWNED_ELSEWHERE,
151
+ altChunk: CAPTURE,
152
+ commentRangeEnd: CAPTURE,
153
+ commentRangeStart: CAPTURE,
154
+ customXml: CAPTURE,
155
+ customXmlDelRangeEnd: CAPTURE,
156
+ customXmlDelRangeStart: CAPTURE,
157
+ customXmlInsRangeEnd: CAPTURE,
158
+ customXmlInsRangeStart: CAPTURE,
159
+ customXmlMoveFromRangeEnd: CAPTURE,
160
+ customXmlMoveFromRangeStart: CAPTURE,
161
+ customXmlMoveToRangeEnd: CAPTURE,
162
+ customXmlMoveToRangeStart: CAPTURE,
163
+ del: CAPTURE,
164
+ ins: CAPTURE,
165
+ moveFrom: CAPTURE,
166
+ moveFromRangeEnd: CAPTURE,
167
+ moveFromRangeStart: CAPTURE,
168
+ moveTo: CAPTURE,
169
+ moveToRangeEnd: CAPTURE,
170
+ moveToRangeStart: CAPTURE,
171
+ permEnd: CAPTURE,
172
+ permStart: CAPTURE,
173
+ proofErr: CAPTURE
158
174
  }
159
- if (localName === "bookmarkStart" || localName === "bookmarkEnd") {
160
- const marker = parseBookmarkMarker(child, localName);
161
- if (!appendBookmarkMarkerToLastParagraphInBlocks(content, marker)) pendingBookmarkMarkers.push(marker);
162
- continue;
163
- }
164
- if (isBlockRangeMarker(localName)) pendingRangeMarkers.push(captureVerbatimXml(child));
165
- }
175
+ }), (xml) => ({
176
+ type: "preservedBlock",
177
+ xml
178
+ }));
166
179
  if (pendingBookmarkMarkers.length > 0) content.push({
167
180
  type: "paragraph",
168
181
  content: [...pendingBookmarkMarkers]
169
182
  });
170
- attachTrailingRangeMarkers(content, pendingRangeMarkers);
171
183
  return content;
172
184
  };
185
+ const parseBlockSdt = (child, styles, theme, numbering, rels, media, state) => {
186
+ const sdtContent = findChild(child, "w", "sdtContent");
187
+ const properties = parseSdtProperties(findChild(child, "w", "sdtPr"), findChild(child, "w", "sdtEndPr"));
188
+ const captured = captureSdtSiblingMarkers(child);
189
+ if (captured.before.length > 0) properties.rawSdtChildrenBeforeContent = captured.before;
190
+ if (captured.after.length > 0) properties.rawSdtChildrenAfterContent = captured.after;
191
+ return {
192
+ type: "blockSdt",
193
+ properties,
194
+ content: sdtContent ? parseBlockContentWithState(sdtContent, styles, theme, numbering, rels, media, withContainerXmlns(withContainerXmlns(state, child), sdtContent)) : []
195
+ };
196
+ };
197
+ const collectBookmarkMarker = (child, localName, content, pending) => {
198
+ const marker = localName === "bookmarkStart" ? parseBookmarkStart(child) : parseBookmarkEnd(child);
199
+ if (!appendBookmarkMarkerToLastParagraphInBlocks(content, marker)) pending.push(marker);
200
+ };
173
201
  /**
174
202
  * Walk a `<w:sdt>` element's direct children and return the verbatim XML
175
203
  * for every child that is NOT `<w:sdtPr>`, `<w:sdtEndPr>`, or
@@ -202,10 +230,6 @@ const captureSdtSiblingMarkers = (sdt) => {
202
230
  after: afterParts.join("")
203
231
  };
204
232
  };
205
- const parseBookmarkMarker = (child, localName) => {
206
- if (localName === "bookmarkStart") return parseBookmarkStart(child);
207
- return parseBookmarkEnd(child);
208
- };
209
233
  const prependPendingBookmarkMarkers = (paragraph, pendingBookmarkMarkers) => {
210
234
  if (pendingBookmarkMarkers.length === 0) return;
211
235
  paragraph.content.unshift(...pendingBookmarkMarkers);
@@ -1,8 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  //#region src/docx/blockPlainText.d.ts
3
- type PlainTextBlock = document_d_exports.Paragraph | document_d_exports.Table | document_d_exports.BlockSdt;
4
3
  /** One entry per block, so callers can join or count lines as they need. */
5
- declare const collectBlockTexts: (blocks: readonly PlainTextBlock[]) => string[];
6
- declare const blockPlainText: (blocks: readonly PlainTextBlock[]) => string;
4
+ declare const collectBlockTexts: (blocks: readonly document_d_exports.BlockContent[]) => string[];
5
+ declare const blockPlainText: (blocks: readonly document_d_exports.BlockContent[]) => string;
7
6
  //#endregion
8
- export { PlainTextBlock, blockPlainText, collectBlockTexts };
7
+ export { blockPlainText, collectBlockTexts };
@@ -31,6 +31,7 @@ const collectBlockTexts = (blocks) => {
31
31
  case "blockSdt":
32
32
  texts.push(...collectBlockTexts(block.content));
33
33
  break;
34
+ case "preservedBlock": break;
34
35
  default: panic(`Unsupported block in plain-text extraction: ${JSON.stringify(block)}`);
35
36
  }
36
37
  return texts;
@@ -25,6 +25,7 @@ const appendBookmarkMarkerToLastParagraph = (block, marker) => {
25
25
  return true;
26
26
  }
27
27
  if (block.type === "table") return appendBookmarkMarkerToLastParagraphInTable(block, marker);
28
+ if (block.type === "preservedBlock") return false;
28
29
  return appendBookmarkMarkerToLastParagraphInBlocks(block.content, marker);
29
30
  };
30
31
  const appendBookmarkMarkerToLastParagraphInTable = (table, marker) => {
@@ -40,6 +41,7 @@ const prependBookmarkMarkersToFirstParagraph = (block, markers) => {
40
41
  return true;
41
42
  }
42
43
  if (block.type === "table") return prependBookmarkMarkersToFirstParagraphInTable(block, markers);
44
+ if (block.type === "preservedBlock") return false;
43
45
  return prependBookmarkMarkersToFirstParagraphInBlocks(block.content, markers);
44
46
  };
45
47
  const prependBookmarkMarkersToFirstParagraphInTable = (table, markers) => {
@@ -0,0 +1,30 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ //#region src/docx/commentAnchorIndex.d.ts
3
+ type CommentMarkerSlot = {
4
+ /** The inline-content array the marker sits in, so a caller can splice it. */
5
+ content: document_d_exports.ParagraphContent[];
6
+ index: number;
7
+ item: Extract<document_d_exports.ParagraphContent, {
8
+ type: "commentRangeStart";
9
+ } | {
10
+ type: "commentRangeEnd";
11
+ } | {
12
+ type: "commentReference";
13
+ }>;
14
+ };
15
+ declare const visitCommentMarkers: (blocks: readonly document_d_exports.BlockContent[], visit: (slot: CommentMarkerSlot) => void) => void;
16
+ /**
17
+ * Comments that open a range anywhere in these blocks.
18
+ *
19
+ * A `commentReference` whose id is absent has no highlight of its own — the
20
+ * point-comment shape the model's `CommentReference` documents — and only that
21
+ * one needs its mark anchored onto neighbouring text. Anchoring one whose range
22
+ * exists stretches that range to wherever the reference sits, which is how a
23
+ * comment on one word came back covering the rest of the sentence. The range
24
+ * and the reference need not share a paragraph, so the question is asked of the
25
+ * whole story: a reference that trails the end of a range three paragraphs back
26
+ * is still not a point comment.
27
+ */
28
+ declare const rangedCommentIds: (blocks: readonly document_d_exports.BlockContent[]) => ReadonlySet<number>;
29
+ //#endregion
30
+ export { CommentMarkerSlot, rangedCommentIds, visitCommentMarkers };
@@ -0,0 +1,50 @@
1
+ import { visitInlineContentSlots } from "./paragraphTraversal.js";
2
+ //#region src/docx/commentAnchorIndex.ts
3
+ const visitCommentMarkers = (blocks, visit) => {
4
+ const visitParagraph = (paragraph) => {
5
+ visitInlineContentSlots(paragraph, ({ content, index, item }) => {
6
+ if (item.type === "commentRangeStart" || item.type === "commentRangeEnd" || item.type === "commentReference") visit({
7
+ content,
8
+ index,
9
+ item
10
+ });
11
+ });
12
+ };
13
+ const visitBlock = (block) => {
14
+ if (block.type === "paragraph") {
15
+ visitParagraph(block);
16
+ return;
17
+ }
18
+ if (block.type === "table") {
19
+ for (const row of block.rows) for (const cell of row.cells) visitBlocks(cell.content);
20
+ return;
21
+ }
22
+ if (block.type === "preservedBlock") return;
23
+ visitBlocks(block.content);
24
+ };
25
+ const visitBlocks = (nested) => {
26
+ for (const block of nested) visitBlock(block);
27
+ };
28
+ visitBlocks(blocks);
29
+ };
30
+ /**
31
+ * Comments that open a range anywhere in these blocks.
32
+ *
33
+ * A `commentReference` whose id is absent has no highlight of its own — the
34
+ * point-comment shape the model's `CommentReference` documents — and only that
35
+ * one needs its mark anchored onto neighbouring text. Anchoring one whose range
36
+ * exists stretches that range to wherever the reference sits, which is how a
37
+ * comment on one word came back covering the rest of the sentence. The range
38
+ * and the reference need not share a paragraph, so the question is asked of the
39
+ * whole story: a reference that trails the end of a range three paragraphs back
40
+ * is still not a point comment.
41
+ */
42
+ const rangedCommentIds = (blocks) => {
43
+ const ids = /* @__PURE__ */ new Set();
44
+ visitCommentMarkers(blocks, ({ item }) => {
45
+ if (item.type === "commentRangeStart") ids.add(item.id);
46
+ });
47
+ return ids;
48
+ };
49
+ //#endregion
50
+ export { rangedCommentIds, visitCommentMarkers };
@@ -1,6 +1,6 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
- import { StyleMap } from "./styleParser.js";
3
2
  import { ParseContext } from "./parseContext.js";
3
+ import { StyleMap } from "./styleParser.js";
4
4
  //#region src/docx/commentParser.d.ts
5
5
  type CommentExtendedInfo = {
6
6
  parentParaId?: string;