@stll/folio-core 0.42.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 (206) 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/compat/eigenpal.d.ts +2 -2
  9. package/dist/controller/layoutPipeline.d.ts +2 -1
  10. package/dist/controller/layoutPipeline.js +5 -2
  11. package/dist/controller/layoutSession.d.ts +2 -1
  12. package/dist/controller/layoutSession.js +1 -0
  13. package/dist/docx/appVersionNormalization.d.ts +0 -18
  14. package/dist/docx/blockContentParser.js +10 -1
  15. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  16. package/dist/docx/blockRangeMarkers.js +59 -0
  17. package/dist/docx/bookmarkParser.d.ts +2 -20
  18. package/dist/docx/bookmarkParser.js +6 -30
  19. package/dist/docx/borderParser.d.ts +13 -0
  20. package/dist/docx/borderParser.js +71 -0
  21. package/dist/docx/builtInStyles.d.ts +165 -0
  22. package/dist/docx/builtInStyles.js +239 -0
  23. package/dist/docx/commentIdNormalization.d.ts +10 -0
  24. package/dist/docx/commentIdNormalization.js +33 -0
  25. package/dist/docx/commentParser.d.ts +2 -1
  26. package/dist/docx/commentParser.js +34 -7
  27. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  28. package/dist/docx/commentReferenceNormalization.js +23 -14
  29. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  30. package/dist/docx/danglingRelationshipReferences.js +30 -0
  31. package/dist/docx/defaultParagraphStyle.d.ts +39 -0
  32. package/dist/docx/defaultParagraphStyle.js +54 -0
  33. package/dist/docx/documentParser.d.ts +2 -1
  34. package/dist/docx/documentParser.js +2 -2
  35. package/dist/docx/drawingUtils.d.ts +8 -1
  36. package/dist/docx/drawingUtils.js +12 -3
  37. package/dist/docx/fieldParser.js +3 -5
  38. package/dist/docx/footnoteParser.d.ts +3 -2
  39. package/dist/docx/footnoteParser.js +19 -2
  40. package/dist/docx/groupDrawingParser.js +1 -1
  41. package/dist/docx/headerFooterRefParser.d.ts +15 -3
  42. package/dist/docx/headerFooterRefParser.js +51 -14
  43. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  44. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  45. package/dist/docx/hyperlinkParser.js +11 -15
  46. package/dist/docx/imageParser.d.ts +1 -1
  47. package/dist/docx/imageParser.js +22 -18
  48. package/dist/docx/imageRawXml.js +5 -5
  49. package/dist/docx/markupRangeMarker.d.ts +15 -0
  50. package/dist/docx/markupRangeMarker.js +44 -0
  51. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  52. package/dist/docx/noteReferenceStyles.js +70 -0
  53. package/dist/docx/numberingParser.js +2 -1
  54. package/dist/docx/numberingReference.d.ts +21 -0
  55. package/dist/docx/numberingReference.js +21 -0
  56. package/dist/docx/numberingReferenceNormalization.d.ts +14 -2
  57. package/dist/docx/numberingReferenceNormalization.js +51 -9
  58. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  59. package/dist/docx/paragraphParser.js +69 -101
  60. package/dist/docx/paragraphPropertySource.js +1 -0
  61. package/dist/docx/paragraphTraversal.d.ts +37 -1
  62. package/dist/docx/paragraphTraversal.js +84 -1
  63. package/dist/docx/parseContext.d.ts +37 -0
  64. package/dist/docx/parseContext.js +67 -0
  65. package/dist/docx/parseWarningMessage.d.ts +6 -0
  66. package/dist/docx/parseWarningMessage.js +44 -0
  67. package/dist/docx/parser.js +86 -24
  68. package/dist/docx/relsParser.d.ts +28 -11
  69. package/dist/docx/relsParser.js +26 -13
  70. package/dist/docx/revisionIdNormalization.js +81 -7
  71. package/dist/docx/rezip.js +85 -27
  72. package/dist/docx/runConsolidator.js +1 -2
  73. package/dist/docx/runParser.d.ts +8 -1
  74. package/dist/docx/runParser.js +30 -48
  75. package/dist/docx/sectionParser.d.ts +2 -1
  76. package/dist/docx/sectionParser.js +21 -65
  77. package/dist/docx/serializer/borderSerializer.d.ts +1 -2
  78. package/dist/docx/serializer/commentSerializer.js +22 -9
  79. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  80. package/dist/docx/serializer/documentSerializer.js +6 -16
  81. package/dist/docx/serializer/headerFooterSerializer.js +5 -0
  82. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  83. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  84. package/dist/docx/serializer/noteSerializer.js +5 -0
  85. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  86. package/dist/docx/serializer/paragraphSerializer.js +29 -35
  87. package/dist/docx/serializer/runSerializer.js +13 -7
  88. package/dist/docx/serializer/tableSerializer.js +28 -13
  89. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  90. package/dist/docx/server/build.js +8 -1
  91. package/dist/docx/server/createBilingualDocument.js +15 -22
  92. package/dist/docx/server/extractDocxText.js +3 -4
  93. package/dist/docx/shadingParser.d.ts +6 -0
  94. package/dist/docx/shadingParser.js +32 -0
  95. package/dist/docx/shapeParser.js +3 -3
  96. package/dist/docx/styleParser.js +15 -89
  97. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  98. package/dist/docx/styleReferenceResolution.js +51 -0
  99. package/dist/docx/tableLook.d.ts +57 -0
  100. package/dist/docx/tableLook.js +63 -0
  101. package/dist/docx/tableParser.d.ts +7 -9
  102. package/dist/docx/tableParser.js +64 -110
  103. package/dist/docx/textBoxParser.js +4 -4
  104. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  105. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  106. package/dist/docx/transitionalSpelling.d.ts +13 -2
  107. package/dist/docx/transitionalSpelling.js +23 -1
  108. package/dist/docx/verbatimCapture.js +4 -11
  109. package/dist/docx/vmlImageParser.js +2 -2
  110. package/dist/docx/watermarkParser.js +2 -2
  111. package/dist/docx/xmlParser.d.ts +22 -32
  112. package/dist/docx/xmlParser.js +36 -21
  113. package/dist/index.d.ts +2 -2
  114. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +2 -0
  115. package/dist/internal/pageBreakRunSourceDescendantIndex.js +9 -6
  116. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  117. package/dist/internal/paragraphFormattingSerialization.js +26 -6
  118. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  119. package/dist/layout-bridge/convert/templatePreviewFlow.d.ts +19 -11
  120. package/dist/layout-bridge/convert/templatePreviewFlow.js +103 -38
  121. package/dist/layout-bridge/convert/toFlowBlocks.js +12 -3
  122. package/dist/layout-engine/index.d.ts +2 -2
  123. package/dist/layout-engine/index.js +2 -2
  124. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  125. package/dist/layout-engine/types.d.ts +8 -2
  126. package/dist/layout-engine/types.js +35 -2
  127. package/dist/markdown/index.js +1 -1
  128. package/dist/markdown/internals.d.ts +6 -1
  129. package/dist/markdown/internals.js +14 -1
  130. package/dist/markdown/renderBlock.js +35 -21
  131. package/dist/markdown/renderParagraph.js +14 -5
  132. package/dist/markdown/renderRuns.js +4 -3
  133. package/dist/markdown/renderTable.js +4 -3
  134. package/dist/markdown/trailers.js +41 -7
  135. package/dist/markdown/types.d.ts +3 -7
  136. package/dist/prosemirror/attrs/index.js +2 -5
  137. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  138. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  139. package/dist/prosemirror/commands/index.d.ts +3 -3
  140. package/dist/prosemirror/commands/index.js +2 -2
  141. package/dist/prosemirror/commands/pageBreak.js +12 -1
  142. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  143. package/dist/prosemirror/commands/paragraph.js +2 -2
  144. package/dist/prosemirror/commentIdAllocator.js +2 -7
  145. package/dist/prosemirror/conversion/fromProseDoc.js +131 -41
  146. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  147. package/dist/prosemirror/conversion/toProseDoc.js +402 -328
  148. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  149. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  150. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  151. package/dist/prosemirror/extensions/features/ListExtension.js +42 -4
  152. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  153. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  154. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  155. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  156. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  157. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  158. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  159. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  160. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  161. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  162. package/dist/prosemirror/extensions/types.d.ts +2 -2
  163. package/dist/prosemirror/index.d.ts +3 -3
  164. package/dist/prosemirror/index.js +3 -3
  165. package/dist/prosemirror/insertOperations.d.ts +9 -2
  166. package/dist/prosemirror/insertOperations.js +9 -4
  167. package/dist/prosemirror/listMarker.js +2 -1
  168. package/dist/prosemirror/numberedRefFields.js +2 -1
  169. package/dist/prosemirror/pageBreakRunProjection.d.ts +11 -3
  170. package/dist/prosemirror/pageBreakRunProjection.js +16 -8
  171. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +159 -0
  172. package/dist/prosemirror/paragraphFormattingProvenance.js +106 -0
  173. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  174. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  175. package/dist/prosemirror/plugins/index.d.ts +2 -2
  176. package/dist/prosemirror/plugins/index.js +2 -2
  177. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  178. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  179. package/dist/prosemirror/plugins/templatePreviewValues.d.ts +42 -1
  180. package/dist/prosemirror/plugins/templatePreviewValues.js +217 -14
  181. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  182. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  183. package/dist/prosemirror/schema/nodes.d.ts +31 -0
  184. package/dist/prosemirror/styles/resolvedStyleAttrs.js +4 -1
  185. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  186. package/dist/prosemirror/styles/styleResolver.js +15 -6
  187. package/dist/prosemirror/utils/visualLineNavigation.d.ts +22 -2
  188. package/dist/prosemirror/utils/visualLineNavigation.js +80 -63
  189. package/dist/style-engine/styleEngine.d.ts +4 -1
  190. package/dist/style-engine/styleEngine.js +3 -0
  191. package/dist/style-sets/extract.js +35 -11
  192. package/dist/style-sets/stellaStyle.js +46 -39
  193. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  194. package/dist/style-sets/styleSetNormalization.js +99 -0
  195. package/dist/types/content.d.ts +2 -2
  196. package/dist/utils/createDocument.js +145 -20
  197. package/dist/utils/headingCollector.d.ts +8 -5
  198. package/dist/utils/headingCollector.js +23 -25
  199. package/dist/utils/tableOfContentsStyle.js +9 -2
  200. package/package.json +3 -2
  201. package/dist/docx/textWhitespace.d.ts +0 -4
  202. package/dist/docx/textWhitespace.js +0 -4
  203. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  204. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  205. package/dist/markdown/headings.d.ts +0 -13
  206. package/dist/markdown/headings.js +0 -20
@@ -1,9 +1,9 @@
1
1
  import { sanitizeImageSrc } from "../utils/sanitizeImageSrc.js";
2
2
  import { emuToPixels } from "../utils/units.js";
3
3
  import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
4
- import { WRAP_ELEMENT_NAMES, parsePositionH, parsePositionV, parseWrapElement } from "./drawingUtils.js";
4
+ import { WRAP_ELEMENT_NAMES, parseAnchorBehindDoc, parsePositionH, parsePositionV, parseWrapElement } from "./drawingUtils.js";
5
5
  import { parseGraphicFrameLocks } from "./graphicFrameLocks.js";
6
- import { resolveTarget } from "./relsParser.js";
6
+ import { RELATIONSHIP_TYPES, resolveRelationshipIdOfType } from "./relsParser.js";
7
7
  import { isTextBoxDrawing } from "./textBoxParser.js";
8
8
  import { findByFullName, findChild, getAttribute, getChildElements, parseNumericAttribute, parseOnOffValue } from "./xmlParser.js";
9
9
  //#region src/docx/imageParser.ts
@@ -178,17 +178,20 @@ function parseImageOpacity(blip) {
178
178
  return Math.max(0, amt / 1e5);
179
179
  }
180
180
  /**
181
- * Extract rId from a:blip element
181
+ * Extract rId from a:blip element.
182
+ *
183
+ * Undefined when the drawing has no blip to read one from: a chart, a diagram
184
+ * or an OLE frame carries an `a:graphic` that is not a picture, and a
185
+ * `wp:inline` may carry no graphic at all.
182
186
  */
183
187
  function extractBlipRId(blip) {
184
- if (!blip) return "";
188
+ if (!blip) return;
185
189
  const rEmbed = getAttribute(blip, "r", "embed");
186
190
  if (rEmbed) return rEmbed;
187
191
  const embed = getAttribute(blip, null, "embed");
188
192
  if (embed) return embed;
189
193
  const rLink = getAttribute(blip, "r", "link");
190
194
  if (rLink) return rLink;
191
- return "";
192
195
  }
193
196
  /**
194
197
  * Find transform (a:xfrm) from picture shape properties
@@ -242,10 +245,9 @@ function getMimeType(path) {
242
245
  * @returns Object with src (data URL or blob), mimeType, and filename
243
246
  */
244
247
  function resolveImageData(rId, rels, media) {
245
- if (!rId || !rels) return {};
246
- const rel = rels.get(rId);
247
- if (!rel) return {};
248
- const targetPath = rel.target;
248
+ const resolved = resolveRelationshipIdOfType(rels, rId, RELATIONSHIP_TYPES.image);
249
+ if (resolved.status !== "resolved") return {};
250
+ const targetPath = resolved.relationship.target;
249
251
  if (!targetPath) return {};
250
252
  const normalizedPath = normalizeMediaPath(targetPath);
251
253
  const filename = targetPath.split("/").pop();
@@ -319,7 +321,7 @@ function parseInline(inlineEl, rels, media) {
319
321
  if (distR !== void 0) wrap.distR = distR;
320
322
  const image = {
321
323
  type: "image",
322
- rId,
324
+ ...rId === void 0 ? {} : { rId },
323
325
  size,
324
326
  wrap
325
327
  };
@@ -337,11 +339,12 @@ function parseInline(inlineEl, rels, media) {
337
339
  if (crop) image.crop = crop;
338
340
  if (opacity !== void 0) image.opacity = opacity;
339
341
  if (frameLocks) image.frameLocks = frameLocks;
340
- if (props.hlinkRId && rels) {
341
- const safeHref = sanitizeExternalUrl(resolveTarget(rels, props.hlinkRId));
342
+ const hlink = resolveRelationshipIdOfType(rels, props.hlinkRId, RELATIONSHIP_TYPES.hyperlink);
343
+ if (hlink.status === "resolved") {
344
+ const safeHref = sanitizeExternalUrl(hlink.relationship.target);
342
345
  if (safeHref) {
343
346
  image.hlinkHref = safeHref;
344
- image.hlinkRId = props.hlinkRId;
347
+ image.hlinkRId = hlink.relationship.id;
345
348
  }
346
349
  }
347
350
  return image;
@@ -359,7 +362,7 @@ function parseAnchor(anchorEl, rels, media) {
359
362
  const padding = parseEffectExtent(findByFullName(anchorEl, "wp:effectExtent"));
360
363
  const props = parseDocProps(findByFullName(anchorEl, "wp:docPr"));
361
364
  const frameLocks = parseGraphicFrameLocks(anchorEl);
362
- const behindDoc = parseOnOffValue(getAttribute(anchorEl, null, "behindDoc")) === true;
365
+ const behindDoc = parseAnchorBehindDoc(anchorEl);
363
366
  const layoutInCell = parseOnOffAttr(anchorEl, "layoutInCell");
364
367
  const allowOverlap = parseOnOffAttr(anchorEl, "allowOverlap");
365
368
  const anchorDistT = parseNumericAttribute(anchorEl, null, "distT");
@@ -391,7 +394,7 @@ function parseAnchor(anchorEl, rels, media) {
391
394
  const transform = parseTransform(findPictureTransform(anchorEl));
392
395
  const image = {
393
396
  type: "image",
394
- rId,
397
+ ...rId === void 0 ? {} : { rId },
395
398
  size,
396
399
  wrap
397
400
  };
@@ -412,11 +415,12 @@ function parseAnchor(anchorEl, rels, media) {
412
415
  if (frameLocks) image.frameLocks = frameLocks;
413
416
  if (layoutInCell !== void 0) image.layoutInCell = layoutInCell;
414
417
  if (allowOverlap !== void 0) image.allowOverlap = allowOverlap;
415
- if (props.hlinkRId && rels) {
416
- const safeHref = sanitizeExternalUrl(resolveTarget(rels, props.hlinkRId));
418
+ const hlink = resolveRelationshipIdOfType(rels, props.hlinkRId, RELATIONSHIP_TYPES.hyperlink);
419
+ if (hlink.status === "resolved") {
420
+ const safeHref = sanitizeExternalUrl(hlink.relationship.target);
417
421
  if (safeHref) {
418
422
  image.hlinkHref = safeHref;
419
- image.hlinkRId = props.hlinkRId;
423
+ image.hlinkRId = hlink.relationship.id;
420
424
  }
421
425
  }
422
426
  return image;
@@ -46,12 +46,12 @@ const DRAWING_SAFETY_CLASSES = {
46
46
  OPAQUE: "opaque"
47
47
  };
48
48
  /**
49
- * Regenerated DrawingML points at `image.rId`, and `serializePicGraphic` falls
50
- * back to `"rId1"` when that id is empty, which rebinds the picture to whichever
51
- * relationship happens to be first. A drawing with no relationship id therefore
52
- * has no faithful regeneration.
49
+ * A drawing with no picture relationship has no picture to regenerate. The
50
+ * serializer writes the anchor back without a graphic, which is faithful for an
51
+ * anchor that never had one and lossy for a chart or an OLE frame, so the
52
+ * drawing counts as opaque and an edit must block the save.
53
53
  */
54
- const canRegenerateDrawing = (drawing) => drawing.image.rId !== "";
54
+ const canRegenerateDrawing = (drawing) => drawing.image.rId !== void 0;
55
55
  /**
56
56
  * Classify a drawing by what the run serializer will do with it.
57
57
  *
@@ -0,0 +1,15 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/markupRangeMarker.d.ts
4
+ declare const parseMarkupRangeMarker: (node: XmlElement) => document_d_exports.MarkupRangeMarker;
5
+ declare const parseBookmarkRangeMarker: (node: XmlElement) => document_d_exports.BookmarkRangeMarker;
6
+ /**
7
+ * `w:author` is required on `CT_MoveBookmark`, so an absent one takes the same
8
+ * `Unknown` fallback a tracked change takes rather than leaving the saved
9
+ * package short of an attribute the schema demands. `w:date` is required too,
10
+ * but inventing a timestamp would state a fact about the document that is not
11
+ * true, so an absent date stays absent.
12
+ */
13
+ declare const parseMoveBookmarkMarker: (node: XmlElement) => document_d_exports.MoveBookmarkMarker;
14
+ //#endregion
15
+ export { parseBookmarkRangeMarker, parseMarkupRangeMarker, parseMoveBookmarkMarker };
@@ -0,0 +1,44 @@
1
+ import { getAttribute, parseNumericAttribute } from "./xmlParser.js";
2
+ //#region src/docx/markupRangeMarker.ts
3
+ /** ST_DisplacedByCustomXml. A value outside it is not a placement folio can honour. */
4
+ const DISPLACED_BY_CUSTOM_XML = /* @__PURE__ */ new Set(["next", "prev"]);
5
+ const parseDisplacedByCustomXml = (node) => {
6
+ const value = getAttribute(node, "w", "displacedByCustomXml") ?? "";
7
+ return DISPLACED_BY_CUSTOM_XML.has(value) ? value : void 0;
8
+ };
9
+ const parseMarkupRangeMarker = (node) => {
10
+ const marker = { id: parseNumericAttribute(node, "w", "id") ?? 0 };
11
+ const displacedByCustomXml = parseDisplacedByCustomXml(node);
12
+ if (displacedByCustomXml !== void 0) marker.displacedByCustomXml = displacedByCustomXml;
13
+ return marker;
14
+ };
15
+ const parseBookmarkRangeMarker = (node) => {
16
+ const marker = {
17
+ ...parseMarkupRangeMarker(node),
18
+ name: getAttribute(node, "w", "name") ?? ""
19
+ };
20
+ const colFirst = parseNumericAttribute(node, "w", "colFirst");
21
+ if (colFirst !== void 0) marker.colFirst = colFirst;
22
+ const colLast = parseNumericAttribute(node, "w", "colLast");
23
+ if (colLast !== void 0) marker.colLast = colLast;
24
+ return marker;
25
+ };
26
+ /**
27
+ * `w:author` is required on `CT_MoveBookmark`, so an absent one takes the same
28
+ * `Unknown` fallback a tracked change takes rather than leaving the saved
29
+ * package short of an attribute the schema demands. `w:date` is required too,
30
+ * but inventing a timestamp would state a fact about the document that is not
31
+ * true, so an absent date stays absent.
32
+ */
33
+ const parseMoveBookmarkMarker = (node) => {
34
+ const author = (getAttribute(node, "w", "author") ?? "").trim();
35
+ const marker = {
36
+ ...parseBookmarkRangeMarker(node),
37
+ author: author.length > 0 ? author : "Unknown"
38
+ };
39
+ const date = (getAttribute(node, "w", "date") ?? "").trim();
40
+ if (date.length > 0) marker.date = date;
41
+ return marker;
42
+ };
43
+ //#endregion
44
+ export { parseBookmarkRangeMarker, parseMarkupRangeMarker, parseMoveBookmarkMarker };
@@ -0,0 +1,29 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ //#region src/docx/noteReferenceStyles.d.ts
3
+ /** The id `commentSerializer` and `paragraphSerializer` write. */
4
+ declare const COMMENT_REFERENCE_STYLE_ID = "CommentReference";
5
+ /** The ids `noteSerializer` writes for the two note kinds. */
6
+ declare const FOOTNOTE_REFERENCE_STYLE_ID = "FootnoteReference";
7
+ declare const ENDNOTE_REFERENCE_STYLE_ID = "EndnoteReference";
8
+ /** Which reference marks a package actually contains. */
9
+ type NoteReferenceNeeds = {
10
+ comments: boolean;
11
+ footnotes: boolean;
12
+ endnotes: boolean;
13
+ };
14
+ /** What a package's content requires, read off the model rather than guessed. */
15
+ declare const noteReferenceNeeds: (pkg: {
16
+ comments?: unknown[] | undefined;
17
+ footnotes?: unknown[] | undefined;
18
+ endnotes?: unknown[] | undefined;
19
+ }) => NoteReferenceNeeds;
20
+ /**
21
+ * The reference styles a package needs and does not already define, by id.
22
+ *
23
+ * Returns the definitions to append rather than a mutated style table: the
24
+ * caller owns the document, and a document that already defines the style
25
+ * (under any id folio recognises) keeps its own.
26
+ */
27
+ declare const missingNoteReferenceStyles: (styles: document_d_exports.StyleDefinitions | undefined, needs: NoteReferenceNeeds) => document_d_exports.Style[];
28
+ //#endregion
29
+ export { COMMENT_REFERENCE_STYLE_ID, ENDNOTE_REFERENCE_STYLE_ID, FOOTNOTE_REFERENCE_STYLE_ID, NoteReferenceNeeds, missingNoteReferenceStyles, noteReferenceNeeds };
@@ -0,0 +1,70 @@
1
+ import { BUILT_IN_STYLE_NAME } from "./builtInStyles.js";
2
+ //#region src/docx/noteReferenceStyles.ts
3
+ /**
4
+ * The character styles folio's serializers write into every document that
5
+ * carries a comment or a note, and the definitions that keep those references
6
+ * from dangling.
7
+ *
8
+ * `commentSerializer`, `paragraphSerializer` and `noteSerializer` each emit
9
+ * `<w:rStyle w:val="…"/>` for the reference mark, because that is what Word
10
+ * writes and what makes the mark superscript. Nothing guaranteed the document
11
+ * *defined* those styles: a package folio assembled from the generic style set
12
+ * referenced `FootnoteReference` while declaring only seven paragraph styles,
13
+ * so the mark rendered as body text. One owner here, so a serializer and the
14
+ * style table cannot disagree about the id.
15
+ */
16
+ /** The id `commentSerializer` and `paragraphSerializer` write. */
17
+ const COMMENT_REFERENCE_STYLE_ID = "CommentReference";
18
+ /** The ids `noteSerializer` writes for the two note kinds. */
19
+ const FOOTNOTE_REFERENCE_STYLE_ID = "FootnoteReference";
20
+ const ENDNOTE_REFERENCE_STYLE_ID = "EndnoteReference";
21
+ /** The default character style Word bases these on, when the package has one. */
22
+ const DEFAULT_CHARACTER_STYLE_ID = "DefaultParagraphFont";
23
+ const referenceStyle = (styleId, name, superscript) => ({
24
+ styleId,
25
+ type: "character",
26
+ name,
27
+ uiPriority: 99,
28
+ semiHidden: true,
29
+ unhideWhenUsed: true,
30
+ rPr: superscript ? { vertAlign: "superscript" } : { fontSize: 16 }
31
+ });
32
+ /**
33
+ * The definition for each reference style, keyed by the need that requires it.
34
+ * Word's comment reference is 8pt body text; the note references are
35
+ * superscript.
36
+ */
37
+ const REFERENCE_STYLES = {
38
+ comments: () => referenceStyle(COMMENT_REFERENCE_STYLE_ID, BUILT_IN_STYLE_NAME.commentReference, false),
39
+ footnotes: () => referenceStyle(FOOTNOTE_REFERENCE_STYLE_ID, BUILT_IN_STYLE_NAME.footnoteReference, true),
40
+ endnotes: () => referenceStyle(ENDNOTE_REFERENCE_STYLE_ID, BUILT_IN_STYLE_NAME.endnoteReference, true)
41
+ };
42
+ /** What a package's content requires, read off the model rather than guessed. */
43
+ const noteReferenceNeeds = (pkg) => ({
44
+ comments: (pkg.comments?.length ?? 0) > 0,
45
+ footnotes: (pkg.footnotes?.length ?? 0) > 0,
46
+ endnotes: (pkg.endnotes?.length ?? 0) > 0
47
+ });
48
+ /**
49
+ * The reference styles a package needs and does not already define, by id.
50
+ *
51
+ * Returns the definitions to append rather than a mutated style table: the
52
+ * caller owns the document, and a document that already defines the style
53
+ * (under any id folio recognises) keeps its own.
54
+ */
55
+ const missingNoteReferenceStyles = (styles, needs) => {
56
+ const defined = new Set((styles?.styles ?? []).map((style) => style.styleId));
57
+ const basedOn = defined.has(DEFAULT_CHARACTER_STYLE_ID) ? DEFAULT_CHARACTER_STYLE_ID : void 0;
58
+ const missing = [];
59
+ for (const [need, create] of Object.entries(REFERENCE_STYLES)) {
60
+ if (!needs[need]) continue;
61
+ const style = create();
62
+ if (!defined.has(style.styleId)) missing.push(basedOn === void 0 ? style : {
63
+ ...style,
64
+ basedOn
65
+ });
66
+ }
67
+ return missing;
68
+ };
69
+ //#endregion
70
+ export { COMMENT_REFERENCE_STYLE_ID, ENDNOTE_REFERENCE_STYLE_ID, FOOTNOTE_REFERENCE_STYLE_ID, missingNoteReferenceStyles, noteReferenceNeeds };
@@ -1,3 +1,4 @@
1
+ import { isNumberingReference } from "./numberingReference.js";
1
2
  import { formatOoxmlCounter, padDecimal } from "./ooxmlCounterFormatter.js";
2
3
  import { LevelSuffixSchema, narrowEnum } from "./parserEnums.js";
3
4
  import { parseRunProperties } from "./runParser.js";
@@ -529,7 +530,7 @@ function createNumberingMap(definitions) {
529
530
  */
530
531
  function computeListRendering(numPr, numbering) {
531
532
  const { numId, ilvl = 0 } = numPr;
532
- if (numId === void 0 || numId === 0) return null;
533
+ if (!isNumberingReference(numId)) return null;
533
534
  const level = numbering.getLevel(numId, ilvl);
534
535
  if (!level) return null;
535
536
  const levelNumFmts = [];
@@ -0,0 +1,21 @@
1
+ //#region src/docx/numberingReference.d.ts
2
+ /**
3
+ * The reserved `w:numId` value that means "no numbering".
4
+ *
5
+ * ECMA-376 (17.9.18 `numId`, 17.9.19 `numPr`) reserves 0: it names no `w:num`,
6
+ * it switches numbering off, and on a style it cancels the numbering the style
7
+ * would otherwise inherit through `w:basedOn` (Word writes it on, for example,
8
+ * a heading-based style that must not be numbered). It must therefore survive a
9
+ * round trip: dropping the `w:numPr` would hand the numbering back.
10
+ */
11
+ declare const NO_NUMBERING_NUM_ID = 0;
12
+ /**
13
+ * Whether a `w:numId` names a numbering definition to resolve.
14
+ *
15
+ * Every lookup or validation against `numbering.nums` goes through this: an
16
+ * absent id has nothing to resolve, and {@link NO_NUMBERING_NUM_ID} is the
17
+ * "none" sentinel, never a dangling reference.
18
+ */
19
+ declare const isNumberingReference: (numId: number | undefined) => numId is number;
20
+ //#endregion
21
+ export { NO_NUMBERING_NUM_ID, isNumberingReference };
@@ -0,0 +1,21 @@
1
+ //#region src/docx/numberingReference.ts
2
+ /**
3
+ * The reserved `w:numId` value that means "no numbering".
4
+ *
5
+ * ECMA-376 (17.9.18 `numId`, 17.9.19 `numPr`) reserves 0: it names no `w:num`,
6
+ * it switches numbering off, and on a style it cancels the numbering the style
7
+ * would otherwise inherit through `w:basedOn` (Word writes it on, for example,
8
+ * a heading-based style that must not be numbered). It must therefore survive a
9
+ * round trip: dropping the `w:numPr` would hand the numbering back.
10
+ */
11
+ const NO_NUMBERING_NUM_ID = 0;
12
+ /**
13
+ * Whether a `w:numId` names a numbering definition to resolve.
14
+ *
15
+ * Every lookup or validation against `numbering.nums` goes through this: an
16
+ * absent id has nothing to resolve, and {@link NO_NUMBERING_NUM_ID} is the
17
+ * "none" sentinel, never a dangling reference.
18
+ */
19
+ const isNumberingReference = (numId) => numId !== void 0 && numId !== 0;
20
+ //#endregion
21
+ export { NO_NUMBERING_NUM_ID, isNumberingReference };
@@ -1,6 +1,9 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
3
  //#region src/docx/numberingReferenceNormalization.d.ts
4
+ /** The codes this normalisation is reported under, owned here, not at the caller. */
5
+ declare const UNNUMBERED_PARAGRAPH_WARNING: "unnumbered-paragraph";
6
+ declare const UNNUMBERED_STYLE_WARNING: "unnumbered-style";
4
7
  type NormalizeNumberingReferencesInput = {
5
8
  documentBody: document_d_exports.DocumentBody;
6
9
  numbering: NumberingMap;
@@ -10,8 +13,17 @@ type NormalizeNumberingReferencesInput = {
10
13
  endnotes?: readonly document_d_exports.Endnote[];
11
14
  };
12
15
  type NormalizeNumberingReferencesResult = {
13
- removedMissingNumberingReferences: number;
16
+ unnumberedDanglingReferences: number;
14
17
  };
15
18
  declare const normalizeNumberingReferences: ({ documentBody, numbering, headers, footers, footnotes, endnotes }: NormalizeNumberingReferencesInput) => NormalizeNumberingReferencesResult;
19
+ type NormalizeStyleNumberingReferencesInput = {
20
+ styles: readonly document_d_exports.Style[];
21
+ numbering: NumberingMap | undefined;
22
+ };
23
+ type NormalizeStyleNumberingReferencesResult = {
24
+ /** Style ids whose dangling reference became the "no numbering" sentinel. */
25
+ unnumberedStyleIds: string[];
26
+ };
27
+ declare const normalizeStyleNumberingReferences: ({ styles, numbering }: NormalizeStyleNumberingReferencesInput) => NormalizeStyleNumberingReferencesResult;
16
28
  //#endregion
17
- export { normalizeNumberingReferences };
29
+ export { UNNUMBERED_PARAGRAPH_WARNING, UNNUMBERED_STYLE_WARNING, normalizeNumberingReferences, normalizeStyleNumberingReferences };
@@ -1,7 +1,37 @@
1
+ import { isNumberingReference } from "./numberingReference.js";
1
2
  import { visitDocxParagraphs } from "./paragraphTraversal.js";
3
+ import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
2
4
  //#region src/docx/numberingReferenceNormalization.ts
5
+ /**
6
+ * Parse-boundary tolerance for `w:numPr` numbering references.
7
+ *
8
+ * A file Word opens must never reach a panic, so a reference that resolves to
9
+ * nothing is repaired here, on both tiers that can carry one: the paragraph and
10
+ * the paragraph style. Downstream code (and `assertStyleNumberingReferences` in
11
+ * particular) can then treat a numbering reference as resolvable.
12
+ *
13
+ * The repair writes the sentinel rather than deleting the `w:numPr`. ECMA-376
14
+ * 17.9.18 reserves `w:numId w:val="0"` for "the removal of numbering properties
15
+ * at a particular level in the style hierarchy"; deleting the element instead
16
+ * removes nothing, it only uncovers the tier below, handing the paragraph its
17
+ * `w:pStyle` numbering or the style its `w:basedOn` numbering. A source that
18
+ * shows no number would come back numbered.
19
+ */
20
+ /**
21
+ * Whether a `w:numId` still reaches a level definition: a `w:num` that names a
22
+ * `w:abstractNum` the numbering part defines. Both hops can dangle, and a
23
+ * `w:num` with no `w:abstractNum` numbers nothing just as a missing one does.
24
+ */
25
+ const resolvesNumbering = (numId, numbering) => {
26
+ if (!numbering) return false;
27
+ const abstractNumId = numbering.getAbstractNumId(numId);
28
+ return abstractNumId !== null && numbering.getAbstract(abstractNumId) !== null;
29
+ };
30
+ /** The codes this normalisation is reported under, owned here, not at the caller. */
31
+ const UNNUMBERED_PARAGRAPH_WARNING = PARSE_WARNING_CODES.unnumberedParagraph;
32
+ const UNNUMBERED_STYLE_WARNING = PARSE_WARNING_CODES.unnumberedStyle;
3
33
  const normalizeNumberingReferences = ({ documentBody, numbering, headers, footers, footnotes, endnotes }) => {
4
- let removedMissingNumberingReferences = 0;
34
+ let unnumberedDanglingReferences = 0;
5
35
  visitDocxParagraphs({
6
36
  documentBody,
7
37
  headers,
@@ -9,14 +39,26 @@ const normalizeNumberingReferences = ({ documentBody, numbering, headers, footer
9
39
  footnotes,
10
40
  endnotes
11
41
  }, (paragraph) => {
12
- const numId = paragraph.formatting?.numPr?.numId;
13
- if (numId !== void 0 && numId !== 0 && !numbering.hasNumbering(numId)) {
14
- delete paragraph.formatting?.numPr;
15
- delete paragraph.listRendering;
16
- removedMissingNumberingReferences += 1;
17
- }
42
+ const formatting = paragraph.formatting;
43
+ const numId = formatting?.numPr?.numId;
44
+ if (!formatting || !isNumberingReference(numId) || resolvesNumbering(numId, numbering)) return;
45
+ formatting.numPr = { numId: 0 };
46
+ delete formatting.numPrFromStyle;
47
+ delete paragraph.listRendering;
48
+ unnumberedDanglingReferences += 1;
18
49
  });
19
- return { removedMissingNumberingReferences };
50
+ return { unnumberedDanglingReferences };
51
+ };
52
+ const normalizeStyleNumberingReferences = ({ styles, numbering }) => {
53
+ const unnumberedStyleIds = [];
54
+ for (const style of styles) {
55
+ const pPr = style.pPr;
56
+ const numId = pPr?.numPr?.numId;
57
+ if (!pPr || !isNumberingReference(numId) || resolvesNumbering(numId, numbering)) continue;
58
+ pPr.numPr = { numId: 0 };
59
+ unnumberedStyleIds.push(style.styleId);
60
+ }
61
+ return { unnumberedStyleIds };
20
62
  };
21
63
  //#endregion
22
- export { normalizeNumberingReferences };
64
+ export { UNNUMBERED_PARAGRAPH_WARNING, UNNUMBERED_STYLE_WARNING, normalizeNumberingReferences, normalizeStyleNumberingReferences };
@@ -1,23 +1,4 @@
1
1
  //#region src/docx/paraIdRangeNormalization.d.ts
2
- /**
3
- * Keep every paragraph id a package carries inside the range the schema gives
4
- * it.
5
- *
6
- * `w14:paraId`, `w14:textId` and the comment-part ids that reference a
7
- * paragraph are `ST_LongHexNumber` with a maximum: the value has to be below
8
- * `0x80000000`, so the ids are 31-bit. Producers exist that write eight hex
9
- * digits without that bound, and folio preserves the ids a document arrives
10
- * with — so a package can carry an out-of-range id in, and a save that copies
11
- * it through hands a consumer a package it will refuse.
12
- *
13
- * {@link paraIdInRange} is the one mapping, and it is a pure function of the
14
- * value alone. That is what lets the parser and the save agree without
15
- * consulting each other: a paragraph's id in the model is the id the file gets,
16
- * so bringing an id into range does not make a document's own identity move
17
- * under it between reading and writing. The package pass below is the same
18
- * mapping applied to every attribute that carries such an id, so a paragraph
19
- * and every reference to it move together.
20
- */
21
2
  /**
22
3
  * `value` when it already fits, and a value derived from it when it does not.
23
4
  *