@beyondwork/docx-react-component 1.0.105 → 1.0.108

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 (193) hide show
  1. package/package.json +19 -5
  2. package/src/api/geometry-overlay-rects.ts +5 -0
  3. package/src/api/package-version.ts +1 -1
  4. package/src/api/page-anchor-id.ts +5 -0
  5. package/src/api/public-types.ts +16 -9
  6. package/src/api/table-node-specs.ts +6 -0
  7. package/src/api/v3/_create.ts +10 -2
  8. package/src/api/v3/_page-anchor-id.ts +52 -0
  9. package/src/api/v3/_runtime-handle.ts +92 -1
  10. package/src/api/v3/ai/_audit-reference.ts +28 -0
  11. package/src/api/v3/ai/_audit-time.ts +5 -0
  12. package/src/api/v3/ai/_pe2-evidence.ts +310 -6
  13. package/src/api/v3/ai/attach.ts +29 -4
  14. package/src/api/v3/ai/bundle.ts +6 -2
  15. package/src/api/v3/ai/inspect.ts +6 -2
  16. package/src/api/v3/ai/replacement.ts +112 -18
  17. package/src/api/v3/ai/resolve.ts +2 -2
  18. package/src/api/v3/ai/review.ts +177 -3
  19. package/src/api/v3/index.ts +8 -0
  20. package/src/api/v3/runtime/collab.ts +462 -0
  21. package/src/api/v3/runtime/document.ts +503 -20
  22. package/src/api/v3/runtime/geometry.ts +97 -0
  23. package/src/api/v3/runtime/layout.ts +744 -0
  24. package/src/api/v3/runtime/perf-probe.ts +14 -0
  25. package/src/api/v3/runtime/viewport.ts +9 -8
  26. package/src/api/v3/ui/_types.ts +202 -55
  27. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  28. package/src/api/v3/ui/debug.ts +115 -2
  29. package/src/api/v3/ui/index.ts +17 -0
  30. package/src/api/v3/ui/overlays.ts +0 -8
  31. package/src/api/v3/ui/surface.ts +56 -0
  32. package/src/api/v3/ui/viewport.ts +119 -9
  33. package/src/core/commands/image-commands.ts +1 -0
  34. package/src/core/commands/index.ts +6 -0
  35. package/src/core/schema/text-schema.ts +43 -5
  36. package/src/core/selection/mapping.ts +8 -1
  37. package/src/core/selection/review-anchors.ts +5 -1
  38. package/src/core/state/text-transaction.ts +8 -2
  39. package/src/io/export/serialize-revisions.ts +149 -1
  40. package/src/io/normalize/normalize-text.ts +6 -0
  41. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  42. package/src/io/ooxml/parse-fields.ts +24 -2
  43. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  44. package/src/io/ooxml/parse-main-document.ts +153 -9
  45. package/src/io/ooxml/parse-numbering.ts +20 -0
  46. package/src/io/ooxml/parse-revisions.ts +19 -8
  47. package/src/io/opc/package-reader.ts +98 -8
  48. package/src/model/anchor.ts +4 -3
  49. package/src/model/canonical-document.ts +220 -2
  50. package/src/model/canonical-hash.ts +221 -0
  51. package/src/model/canonical-layout-inputs.ts +245 -6
  52. package/src/model/layout/index.ts +1 -0
  53. package/src/model/layout/page-graph-types.ts +147 -1
  54. package/src/model/review/revision-types.ts +14 -3
  55. package/src/preservation/store.ts +20 -4
  56. package/src/review/README.md +1 -1
  57. package/src/review/store/revision-actions.ts +14 -2
  58. package/src/runtime/collab/event-types.ts +67 -1
  59. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  60. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  61. package/src/runtime/document-heading-outline.ts +147 -0
  62. package/src/runtime/document-navigation.ts +8 -243
  63. package/src/runtime/document-runtime.ts +279 -115
  64. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  65. package/src/runtime/formatting/layout-inputs.ts +38 -5
  66. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  67. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  68. package/src/runtime/geometry/caret-geometry.ts +5 -6
  69. package/src/runtime/geometry/geometry-facet.ts +60 -10
  70. package/src/runtime/geometry/geometry-index.ts +661 -16
  71. package/src/runtime/geometry/geometry-types.ts +59 -0
  72. package/src/runtime/geometry/hit-test.ts +11 -1
  73. package/src/runtime/geometry/overlay-rects.ts +5 -3
  74. package/src/runtime/geometry/project-anchors.ts +1 -1
  75. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  76. package/src/runtime/layout/index.ts +6 -0
  77. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  78. package/src/runtime/layout/layout-engine-version.ts +188 -16
  79. package/src/runtime/layout/layout-facet-types.ts +6 -0
  80. package/src/runtime/layout/page-graph.ts +23 -4
  81. package/src/runtime/layout/paginated-layout-engine.ts +149 -15
  82. package/src/runtime/layout/project-block-fragments.ts +351 -14
  83. package/src/runtime/layout/public-facet.ts +162 -24
  84. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  85. package/src/runtime/layout/table-row-split.ts +92 -35
  86. package/src/runtime/prerender/cache-envelope.ts +2 -2
  87. package/src/runtime/prerender/cache-key.ts +5 -4
  88. package/src/runtime/prerender/customxml-cache.ts +0 -1
  89. package/src/runtime/render/render-kernel.ts +1 -1
  90. package/src/runtime/revision-runtime.ts +112 -10
  91. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  92. package/src/runtime/scopes/action-validation.ts +22 -2
  93. package/src/runtime/scopes/capabilities.ts +316 -0
  94. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  95. package/src/runtime/scopes/compiler-service.ts +108 -4
  96. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  97. package/src/runtime/scopes/create-issue.ts +5 -5
  98. package/src/runtime/scopes/evidence.ts +91 -0
  99. package/src/runtime/scopes/formatting/apply.ts +2 -0
  100. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  101. package/src/runtime/scopes/index.ts +54 -0
  102. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  103. package/src/runtime/scopes/layout-evidence.ts +374 -0
  104. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  105. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  106. package/src/runtime/scopes/replacement/apply.ts +97 -34
  107. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  108. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  109. package/src/runtime/scopes/visualization.ts +28 -0
  110. package/src/runtime/surface-projection.ts +44 -5
  111. package/src/runtime/telemetry/perf-probe.ts +216 -0
  112. package/src/runtime/virtualized-rendering.ts +36 -1
  113. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  114. package/src/runtime/workflow/coordinator.ts +39 -11
  115. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  116. package/src/runtime/workflow/index.ts +4 -0
  117. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  118. package/src/runtime/workflow/overlay-lanes.ts +386 -0
  119. package/src/runtime/workflow/overlay-store.ts +2 -2
  120. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  121. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  122. package/src/session/_sync-legacy.ts +17 -27
  123. package/src/session/import/loader.ts +6 -4
  124. package/src/session/import/source-package-evidence.ts +186 -2
  125. package/src/session/index.ts +5 -6
  126. package/src/session/session.ts +30 -56
  127. package/src/session/types.ts +8 -13
  128. package/src/shell/session-bootstrap.ts +155 -81
  129. package/src/ui/WordReviewEditor.tsx +520 -12
  130. package/src/ui/editor-shell-view.tsx +14 -4
  131. package/src/ui/editor-surface-controller.tsx +5 -3
  132. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  133. package/src/ui/presence-overlay-lane.ts +130 -0
  134. package/src/ui/ui-controller-factory.ts +17 -0
  135. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  136. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  137. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  138. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  139. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  140. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  141. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  142. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  143. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  144. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  145. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  146. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  147. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  148. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  149. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  150. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  151. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  152. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  153. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  154. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  155. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  156. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  157. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  158. package/src/ui-tailwind/debug/README.md +4 -1
  159. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  160. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  161. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  162. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  163. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  164. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  165. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  166. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  167. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  168. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  169. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  170. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  171. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  172. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  173. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  174. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  175. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  176. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  177. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  178. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  179. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  180. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  181. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  182. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  183. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  184. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  185. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  186. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  187. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  188. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  189. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  190. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  191. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  192. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  193. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -13,6 +13,10 @@
13
13
  * 3. A `TOC` field anywhere in the doc — TOC fields produce
14
14
  * hyperlinks to generated `_Toc####` anchors at render time, so
15
15
  * we blanket-retain `_Toc*` whenever a TOC field is present.
16
+ * 4. A bookmark range that encloses active revision markup. Those
17
+ * bookmarks are part of the review round-trip surface; stripping them
18
+ * after runtime revision export turns structured re-import into plain
19
+ * text.
16
20
  *
17
21
  * Produces a typed `BookmarkReferenceScan` that the parser consults
18
22
  * at every `<w:bookmarkStart>` / `<w:bookmarkEnd>` emission site to
@@ -64,6 +68,11 @@ const TOC_FIELD_RE = /\bTOC\b/;
64
68
  const REFLIKE_FIELD_RE =
65
69
  /\b(?:HYPERLINK|REF|PAGEREF|NOTEREF)\s+([A-Za-z0-9_:.\-]+)/g;
66
70
  const DATA_BINDING_RE = /<(?:\w+:)?dataBinding\b/i;
71
+ const BOOKMARK_START_RE =
72
+ /<(?:\w+:)?bookmarkStart\b(?=[^>]*\bw:id\s*=\s*"([^"]*)")(?=[^>]*\bw:name\s*=\s*"([^"]*)")[^>]*\/>/gi;
73
+ const BOOKMARK_END_RE =
74
+ /<(?:\w+:)?bookmarkEnd\b[^>]*\bw:id\s*=\s*"([^"]*)"[^>]*\/>/gi;
75
+ const ACTIVE_REVISION_RE = /<(?:\w+:)?(?:ins|del)\b/i;
67
76
 
68
77
  /**
69
78
  * Always-retain prefix check — bookmarks whose name starts with
@@ -102,6 +111,8 @@ export function scanBookmarkReferences(
102
111
  }
103
112
  }
104
113
 
114
+ retainRevisionBoundedBookmarks(documentXml, retained);
115
+
105
116
  return {
106
117
  retainedNames: retained,
107
118
  retainAllTocPattern: retainAllToc,
@@ -109,6 +120,50 @@ export function scanBookmarkReferences(
109
120
  };
110
121
  }
111
122
 
123
+ function retainRevisionBoundedBookmarks(
124
+ documentXml: string,
125
+ retained: Set<string>,
126
+ ): void {
127
+ const starts = new Map<
128
+ string,
129
+ {
130
+ name: string;
131
+ endOffset: number;
132
+ }
133
+ >();
134
+
135
+ BOOKMARK_START_RE.lastIndex = 0;
136
+ let startMatch: RegExpExecArray | null;
137
+ while ((startMatch = BOOKMARK_START_RE.exec(documentXml)) !== null) {
138
+ const id = startMatch[1];
139
+ const name = startMatch[2];
140
+ if (!id || !name) {
141
+ continue;
142
+ }
143
+ starts.set(id, {
144
+ name,
145
+ endOffset: startMatch.index + startMatch[0].length,
146
+ });
147
+ }
148
+
149
+ BOOKMARK_END_RE.lastIndex = 0;
150
+ let endMatch: RegExpExecArray | null;
151
+ while ((endMatch = BOOKMARK_END_RE.exec(documentXml)) !== null) {
152
+ const id = endMatch[1];
153
+ if (!id) {
154
+ continue;
155
+ }
156
+ const start = starts.get(id);
157
+ if (!start || start.endOffset > endMatch.index) {
158
+ continue;
159
+ }
160
+ const enclosedXml = documentXml.slice(start.endOffset, endMatch.index);
161
+ if (ACTIVE_REVISION_RE.test(enclosedXml)) {
162
+ retained.add(start.name);
163
+ }
164
+ }
165
+ }
166
+
112
167
  export function isRetainedBookmarkName(
113
168
  name: string,
114
169
  scan: BookmarkReferenceScan,
@@ -332,13 +332,14 @@ import type {
332
332
  } from "../../model/canonical-document.ts";
333
333
  import {
334
334
  MAIN_STORY_KEY,
335
+ createCanonicalFieldId,
335
336
  createHeaderFooterStoryKey,
336
337
  createNoteStoryKey,
337
338
  } from "../../model/canonical-layout-inputs.ts";
338
339
  import { parseFieldSwitches } from "./parse-field-switches.ts";
339
340
 
340
341
  const FIELD_FAMILY_PATTERN =
341
- /^\s*(REF|PAGEREF|NOTEREF|TOC|PAGE|NUMPAGES|DATE|TIME|AUTHOR|FILENAME|MERGEFIELD|IF|SEQ|INDEX|TC|FORMTEXT|FORMCHECKBOX|FORMDROPDOWN|STYLEREF|SECTIONPAGES)\b/i;
342
+ /^\s*(REF|PAGEREF|NOTEREF|TOC|PAGE|NUMPAGES|DATE|TIME|AUTHOR|FILENAME|HYPERLINK|MERGEFIELD|IF|SEQ|INDEX|TC|FORMTEXT|FORMCHECKBOX|FORMDROPDOWN|STYLEREF|SECTIONPAGES)\b/i;
342
343
 
343
344
  const SUPPORTED_FAMILIES = new Set<string>([
344
345
  "REF",
@@ -432,20 +433,31 @@ export function buildFieldRegistry(
432
433
  walkFieldDocument(root, (node, pIdx) => {
433
434
  paragraphIndex = pIdx;
434
435
  if (node.type === "field") {
436
+ const storyKey = MAIN_STORY_KEY;
435
437
  const classification = node.fieldFamily
436
438
  ? { family: node.fieldFamily, supported: isSupportedFieldFamily(node.fieldFamily), target: node.fieldTarget, switches: node.switches }
437
439
  : classifyFieldInstruction(node.instruction);
438
440
  const displayText = flattenFieldText(node.children);
439
441
  const entry: Mutable<FieldRegistryEntry> = {
440
442
  fieldIndex,
443
+ canonicalFieldId:
444
+ node.canonicalFieldId ??
445
+ createCanonicalFieldId({
446
+ fieldIndex,
447
+ storyKey,
448
+ sourceRef: node.sourceRef,
449
+ }),
441
450
  fieldFamily: classification.family,
442
451
  supported: classification.supported,
443
452
  instruction: node.instruction,
444
453
  ...(classification.target ? { fieldTarget: classification.target } : {}),
445
454
  displayText,
446
455
  paragraphIndex,
447
- storyKey: MAIN_STORY_KEY,
456
+ storyKey,
457
+ ...(node.sourceRef !== undefined ? { sourceRef: node.sourceRef } : {}),
448
458
  refreshStatus: node.refreshStatus ?? (classification.supported ? "stale" : "preserve-only"),
459
+ ...(node.locked !== undefined ? { locked: node.locked } : {}),
460
+ ...(node.dirty !== undefined ? { dirty: node.dirty } : {}),
449
461
  ...(classification.switches ? { switches: classification.switches } : {}),
450
462
  };
451
463
  if (classification.supported) {
@@ -469,6 +481,13 @@ export function buildFieldRegistry(
469
481
  const displayText = flattenFieldText(node.children);
470
482
  const entry: Mutable<FieldRegistryEntry> = {
471
483
  fieldIndex,
484
+ canonicalFieldId:
485
+ node.canonicalFieldId ??
486
+ createCanonicalFieldId({
487
+ fieldIndex,
488
+ storyKey,
489
+ sourceRef: node.sourceRef,
490
+ }),
472
491
  fieldFamily: classification.family,
473
492
  supported: classification.supported,
474
493
  instruction: node.instruction,
@@ -476,7 +495,10 @@ export function buildFieldRegistry(
476
495
  displayText,
477
496
  paragraphIndex,
478
497
  storyKey,
498
+ ...(node.sourceRef !== undefined ? { sourceRef: node.sourceRef } : {}),
479
499
  refreshStatus: node.refreshStatus ?? (classification.supported ? "stale" : "preserve-only"),
500
+ ...(node.locked !== undefined ? { locked: node.locked } : {}),
501
+ ...(node.dirty !== undefined ? { dirty: node.dirty } : {}),
480
502
  ...(classification.switches ? { switches: classification.switches } : {}),
481
503
  };
482
504
  if (classification.supported) {
@@ -12,6 +12,7 @@ import type {
12
12
  TableRowNode,
13
13
  TextMark,
14
14
  Mutable,
15
+ CanonicalSourceRef,
15
16
  } from "../../model/canonical-document.ts";
16
17
  import type { LegacyFormFieldNode } from "../../model/canonical-document.ts";
17
18
  import { resolveHighlightColor } from "./highlight-colors.ts";
@@ -58,6 +59,7 @@ import { parseShapeXml, parseVmlXml } from "./parse-shapes.ts";
58
59
 
59
60
  const TAB_ALIGN_VOCAB = new Set<TabStop["align"]>(["left", "center", "right", "decimal", "num", "bar", "clear"]);
60
61
  const TAB_LEADER_VOCAB = new Set<TabStop["leader"]>(["none", "dot", "hyphen", "underscore", "heavy", "middleDot"]);
62
+ let activeHeaderFooterFieldSequenceOrdinal = 0;
61
63
 
62
64
  // ---- Public types ----
63
65
 
@@ -229,6 +231,7 @@ function parseHdrFtrXml(
229
231
  opts: ParseHeaderFooterOpts = { relationships: [] },
230
232
  ): ParsedHeaderFooterDocument {
231
233
  currentSourceXml = xml;
234
+ activeHeaderFooterFieldSequenceOrdinal = 0;
232
235
  let root: XmlElementNode;
233
236
  try {
234
237
  root = parseXml(xml) as XmlElementNode;
@@ -326,11 +329,12 @@ function parseParagraphElement(
326
329
  "complex",
327
330
  activeComplexField.children,
328
331
  activeComplexField.legacyFormField,
332
+ activeComplexField.sourceRef,
329
333
  ),
330
334
  );
331
335
  activeComplexField = null;
332
336
  }
333
- pushFieldNode(children, child, "simple");
337
+ pushFieldNode(children, child, "simple", opts.sourcePartPath);
334
338
  } else if (name === "sdt") {
335
339
  // coord-11 §22 — structured-document-tag wrapping run-level content
336
340
  // inside a header/footer paragraph. Word commonly uses these to
@@ -355,7 +359,7 @@ function parseParagraphElement(
355
359
  } else if (gname === "bookmarkStart" || gname === "bookmarkEnd") {
356
360
  children.push(parseBookmarkElement(grandchild));
357
361
  } else if (gname === "fldSimple") {
358
- pushFieldNode(children, grandchild, "simple");
362
+ pushFieldNode(children, grandchild, "simple", opts.sourcePartPath);
359
363
  }
360
364
  // Nested sdt / other elements ignored — deeper nesting is rare
361
365
  // enough that opaque round-trip via the block-level sdt parser
@@ -372,6 +376,7 @@ function parseParagraphElement(
372
376
  "complex",
373
377
  activeComplexField.children,
374
378
  activeComplexField.legacyFormField,
379
+ activeComplexField.sourceRef,
375
380
  ),
376
381
  );
377
382
  }
@@ -391,6 +396,7 @@ interface ActiveComplexField {
391
396
  instruction: string;
392
397
  children: Array<Extract<InlineNode, { type: "text" | "hard_break" | "tab" }>>;
393
398
  mode: "instruction" | "result";
399
+ sourceRef?: CanonicalSourceRef;
394
400
  legacyFormField?: LegacyFormFieldNode;
395
401
  }
396
402
 
@@ -419,6 +425,7 @@ function appendRunNodes(
419
425
  instruction: "",
420
426
  children: [],
421
427
  mode: "instruction",
428
+ sourceRef: createFieldSequenceSourceRef(opts.sourcePartPath ?? "/word/document.xml", rElement),
422
429
  ...(legacyFormField ? { legacyFormField } : {}),
423
430
  };
424
431
  } else if (fldType === "separate" && activeComplexField) {
@@ -431,6 +438,7 @@ function appendRunNodes(
431
438
  "complex",
432
439
  activeComplexField.children,
433
440
  activeComplexField.legacyFormField,
441
+ activeComplexField.sourceRef,
434
442
  ),
435
443
  );
436
444
  }
@@ -443,7 +451,7 @@ function appendRunNodes(
443
451
  if (activeComplexField) {
444
452
  activeComplexField.instruction += extractTextContent(child);
445
453
  } else {
446
- pushFieldNode(nodes, child, "complex");
454
+ pushFieldNode(nodes, child, "complex", opts.sourcePartPath);
447
455
  }
448
456
  continue;
449
457
  }
@@ -522,7 +530,7 @@ function parseRunElement(
522
530
  } else if (name === "bookmarkStart" || name === "bookmarkEnd") {
523
531
  nodes.push(parseBookmarkElement(child));
524
532
  } else if (name === "instrText") {
525
- pushFieldNode(nodes, child, "complex");
533
+ pushFieldNode(nodes, child, "complex", opts.sourcePartPath);
526
534
  } else if (name === "drawing") {
527
535
  const drawingXml = currentSourceXml.slice(child.start, child.end);
528
536
  const drawingResult = parseDrawingInlineNode(drawingXml, opts);
@@ -824,6 +832,7 @@ function pushFieldNode(
824
832
  nodes: InlineNode[],
825
833
  element: XmlElementNode,
826
834
  fieldType: "simple" | "complex",
835
+ sourcePartPath = "/word/document.xml",
827
836
  ): void {
828
837
  const instruction = readFieldInstruction(element);
829
838
  if (!instruction) {
@@ -832,7 +841,13 @@ function pushFieldNode(
832
841
 
833
842
  const classification = classifyFieldInstruction(instruction);
834
843
 
835
- nodes.push(createFieldNode(instruction, fieldType));
844
+ nodes.push(createFieldNode(
845
+ instruction,
846
+ fieldType,
847
+ [],
848
+ undefined,
849
+ createFieldSequenceSourceRef(sourcePartPath, element),
850
+ ));
836
851
  }
837
852
 
838
853
  function createFieldNode(
@@ -840,6 +855,7 @@ function createFieldNode(
840
855
  fieldType: "simple" | "complex",
841
856
  children: InlineNode[] = [],
842
857
  legacyFormField?: LegacyFormFieldNode,
858
+ sourceRef?: CanonicalSourceRef,
843
859
  ): Extract<InlineNode, { type: "field" }> {
844
860
  const classification = classifyFieldInstruction(instruction);
845
861
  return {
@@ -850,10 +866,27 @@ function createFieldNode(
850
866
  fieldFamily: classification.family,
851
867
  ...(classification.target ? { fieldTarget: classification.target } : {}),
852
868
  refreshStatus: classification.supported ? "stale" : "preserve-only",
869
+ ...(sourceRef ? { sourceRef } : {}),
853
870
  ...(legacyFormField ? { legacyFormField } : {}),
854
871
  };
855
872
  }
856
873
 
874
+ function createFieldSequenceSourceRef(
875
+ partPath: string,
876
+ node: XmlElementNode,
877
+ ): CanonicalSourceRef {
878
+ activeHeaderFooterFieldSequenceOrdinal += 1;
879
+ return {
880
+ sourceId: `part:${partPath}#fieldSequence:${activeHeaderFooterFieldSequenceOrdinal}`,
881
+ partPath,
882
+ storyKind: /\/footer\d+\.xml$/i.test(partPath) ? "footer" : "header",
883
+ element: "fieldSequence",
884
+ ordinal: activeHeaderFooterFieldSequenceOrdinal,
885
+ startOffset: node.start,
886
+ endOffset: node.end,
887
+ };
888
+ }
889
+
857
890
  function readFieldInstruction(element: XmlElementNode): string | undefined {
858
891
  const instruction =
859
892
  readStringAttr(element, "w:instr") ??
@@ -20,6 +20,7 @@ import type {
20
20
  PageSize,
21
21
  PageMargins,
22
22
  ColumnProperties,
23
+ CanonicalSourceRef,
23
24
  PageNumbering,
24
25
  SectionDocumentGrid,
25
26
  SectionLineNumbering,
@@ -28,6 +29,7 @@ import type {
28
29
  UnknownPropertyChild,
29
30
  Mutable,
30
31
  } from "../../model/canonical-document.ts";
32
+ import { sha256TextHex } from "../../model/canonical-hash.ts";
31
33
  import type { OpcRelationship } from "./part-manifest.ts";
32
34
  import { SCOPE_MARKER_BOOKMARK_PREFIX } from "./parse-scope-markers.ts";
33
35
  import {
@@ -313,8 +315,10 @@ export interface ParsedParagraphNode {
313
315
  styleId?: string;
314
316
  numbering?: {
315
317
  numberingInstanceId: string;
318
+ sourceRef?: CanonicalSourceRef;
316
319
  level: number;
317
320
  };
321
+ sourceRef?: CanonicalSourceRef;
318
322
  alignment?: "left" | "center" | "right" | "both" | "distribute";
319
323
  spacing?: ParagraphSpacing;
320
324
  contextualSpacing?: boolean;
@@ -438,6 +442,16 @@ export interface ParsedImageNode {
438
442
  export interface ParsedHyperlinkNode {
439
443
  type: "hyperlink";
440
444
  href: string;
445
+ sourceRef?: CanonicalSourceRef;
446
+ fieldCarrier?: {
447
+ sourceRef?: CanonicalSourceRef;
448
+ fieldFamily?: ReturnType<typeof classifyFieldInstruction>["family"];
449
+ refreshStatus?: "current" | "stale" | "unresolvable" | "preserve-only";
450
+ locked?: boolean;
451
+ dirty?: boolean;
452
+ instructionHash?: string;
453
+ resultHash?: string;
454
+ };
441
455
  children: Array<ParsedTextNode | ParsedBreakNode | ParsedColumnBreakNode | ParsedPageBreakNode | ParsedTabNode | ParsedSymbolNode>;
442
456
  rawXml: string;
443
457
  }
@@ -545,6 +559,7 @@ export interface ParsedFieldInlineNode {
545
559
  contentXml?: string;
546
560
  children?: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
547
561
  rawXml: string;
562
+ sourceRef?: CanonicalSourceRef;
548
563
  legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
549
564
  }
550
565
 
@@ -724,6 +739,7 @@ const HYPERLINK_RELATIONSHIP_TYPE =
724
739
  * not exist; `parseSdtXml` is only callable externally via `parsePictureSdt`.
725
740
  */
726
741
  let activeChartPartLookup: ChartPartLookup | undefined;
742
+ let activeFieldSequenceOrdinal = 0;
727
743
 
728
744
  /**
729
745
  * Phase 1 telemetry — optional bus installed by the runtime before a load.
@@ -838,6 +854,7 @@ export function parseMainDocumentXml(
838
854
  activeCosmeticStripContext = stripContext;
839
855
  const bus = activeParseTelemetryBus;
840
856
  const started = bus?.isEnabled("parse") ? performanceNow() : 0;
857
+ activeFieldSequenceOrdinal = 0;
841
858
  try {
842
859
  const result = parseMainDocumentXmlInner(xml, relationships, mediaParts, sourcePartPath);
843
860
  if (Object.keys(stripContext.counts).length > 0) {
@@ -850,6 +867,7 @@ export function parseMainDocumentXml(
850
867
  } finally {
851
868
  activeChartPartLookup = undefined;
852
869
  activeCosmeticStripContext = null;
870
+ activeFieldSequenceOrdinal = 0;
853
871
  }
854
872
  }
855
873
 
@@ -1266,7 +1284,7 @@ function parseBodyChild(
1266
1284
  switch (localName(child.name)) {
1267
1285
  case "pPr":
1268
1286
  styleId = readParagraphStyleId(child);
1269
- numbering = readParagraphNumbering(child);
1287
+ numbering = readParagraphNumbering(child, sourcePartPath);
1270
1288
  alignment = readParagraphAlignment(child);
1271
1289
  spacing = readParagraphSpacing(child);
1272
1290
  contextualSpacing = readOptionalOnOffParagraphProperty(child, "contextualSpacing");
@@ -1307,7 +1325,7 @@ function parseBodyChild(
1307
1325
  flushActiveComplexField(children, () => {
1308
1326
  activeComplexField = null;
1309
1327
  }, activeComplexField);
1310
- const hyperlink = parseHyperlink(child, sourceXml, relationshipMap);
1328
+ const hyperlink = parseHyperlink(child, sourceXml, relationshipMap, {}, sourcePartPath);
1311
1329
  children.push(hyperlink);
1312
1330
  break;
1313
1331
  }
@@ -1400,6 +1418,7 @@ function parseBodyChild(
1400
1418
  instruction: fieldInstr,
1401
1419
  contentXml: fieldContentXml,
1402
1420
  rawXml: sourceXml.slice(child.start, child.end),
1421
+ sourceRef: createFieldSequenceSourceRef(sourcePartPath, child),
1403
1422
  } as ParsedFieldInlineNode);
1404
1423
  break;
1405
1424
  }
@@ -1481,6 +1500,7 @@ function parseBodyChild(
1481
1500
 
1482
1501
  return {
1483
1502
  type: "paragraph",
1503
+ sourceRef: createElementSourceRef(sourcePartPath, "p", node),
1484
1504
  styleId,
1485
1505
  ...(numbering ? { numbering } : {}),
1486
1506
  ...(alignment ? { alignment } : {}),
@@ -1514,6 +1534,7 @@ function appendParagraphRunNodes(
1514
1534
  instruction: string;
1515
1535
  children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
1516
1536
  mode: "instruction" | "result";
1537
+ sourceRef?: CanonicalSourceRef;
1517
1538
  legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
1518
1539
  } | null,
1519
1540
  sourceXml: string,
@@ -1524,6 +1545,7 @@ function appendParagraphRunNodes(
1524
1545
  instruction: string;
1525
1546
  children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
1526
1547
  mode: "instruction" | "result";
1548
+ sourceRef?: CanonicalSourceRef;
1527
1549
  legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
1528
1550
  } | null {
1529
1551
  const hasFieldMarkers = node.children.some(
@@ -1565,6 +1587,7 @@ function appendParagraphRunNodes(
1565
1587
  instruction: "",
1566
1588
  children: [],
1567
1589
  mode: "instruction",
1590
+ sourceRef: createFieldSequenceSourceRef(sourcePartPath, node),
1568
1591
  ...(legacyFormField ? { legacyFormField } : {}),
1569
1592
  };
1570
1593
  } else if (fldType === "separate" && activeComplexField) {
@@ -1591,6 +1614,7 @@ function appendParagraphRunNodes(
1591
1614
  instruction,
1592
1615
  children: [],
1593
1616
  rawXml: sourceXml.slice(node.start, node.end),
1617
+ sourceRef: createFieldSequenceSourceRef(sourcePartPath, node),
1594
1618
  });
1595
1619
  }
1596
1620
  continue;
@@ -1607,6 +1631,7 @@ function flushActiveComplexField(
1607
1631
  instruction: string;
1608
1632
  children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
1609
1633
  mode: "instruction" | "result";
1634
+ sourceRef?: CanonicalSourceRef;
1610
1635
  legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
1611
1636
  } | null,
1612
1637
  ): void {
@@ -1620,6 +1645,7 @@ function flushActiveComplexField(
1620
1645
  instruction: activeComplexField.instruction,
1621
1646
  children: activeComplexField.children,
1622
1647
  rawXml: "",
1648
+ ...(activeComplexField.sourceRef ? { sourceRef: activeComplexField.sourceRef } : {}),
1623
1649
  ...(activeComplexField.legacyFormField ? { legacyFormField: activeComplexField.legacyFormField } : {}),
1624
1650
  });
1625
1651
  reset();
@@ -2547,6 +2573,7 @@ function readParagraphStyleId(node: XmlElementNode): string | undefined {
2547
2573
 
2548
2574
  function readParagraphNumbering(
2549
2575
  node: XmlElementNode,
2576
+ sourcePartPath: string,
2550
2577
  ): ParsedParagraphNode["numbering"] | undefined {
2551
2578
  const numberingProperties = node.children.find(
2552
2579
  (child): child is XmlElementNode =>
@@ -2572,10 +2599,50 @@ function readParagraphNumbering(
2572
2599
 
2573
2600
  return {
2574
2601
  numberingInstanceId: toCanonicalNumberingInstanceId(rawInstanceId),
2602
+ sourceRef: createElementSourceRef(sourcePartPath, "numPr", numberingProperties),
2575
2603
  level: Number.parseInt(rawLevel, 10),
2576
2604
  };
2577
2605
  }
2578
2606
 
2607
+ function createElementSourceRef(
2608
+ partPath: string,
2609
+ element: string,
2610
+ node: XmlElementNode,
2611
+ ): CanonicalSourceRef {
2612
+ return {
2613
+ sourceId: `part:${partPath}#${element}:${node.start}`,
2614
+ partPath,
2615
+ storyKind: storyKindFromPartPath(partPath),
2616
+ element,
2617
+ startOffset: node.start,
2618
+ endOffset: node.end,
2619
+ };
2620
+ }
2621
+
2622
+ function createFieldSequenceSourceRef(
2623
+ partPath: string,
2624
+ node: XmlElementNode,
2625
+ ): CanonicalSourceRef {
2626
+ activeFieldSequenceOrdinal += 1;
2627
+ return {
2628
+ sourceId: `part:${partPath}#fieldSequence:${activeFieldSequenceOrdinal}`,
2629
+ partPath,
2630
+ storyKind: storyKindFromPartPath(partPath),
2631
+ element: "fieldSequence",
2632
+ ordinal: activeFieldSequenceOrdinal,
2633
+ startOffset: node.start,
2634
+ endOffset: node.end,
2635
+ };
2636
+ }
2637
+
2638
+ function storyKindFromPartPath(partPath: string): string {
2639
+ if (/\/header\d+\.xml$/i.test(partPath)) return "header";
2640
+ if (/\/footer\d+\.xml$/i.test(partPath)) return "footer";
2641
+ if (/\/footnotes\.xml$/i.test(partPath)) return "footnote";
2642
+ if (/\/endnotes\.xml$/i.test(partPath)) return "endnote";
2643
+ return "main";
2644
+ }
2645
+
2579
2646
  function parseRun(
2580
2647
  node: XmlElementNode,
2581
2648
  sourceXml: string,
@@ -2916,10 +2983,16 @@ function parseRevisionContainer(
2916
2983
  break;
2917
2984
  }
2918
2985
  case "hyperlink": {
2919
- const hyperlink = parseHyperlink(child, sourceXml, relationshipMap, {
2920
- allowDeletedText: allowsDeletedText,
2921
- preserveUnsupportedReviewMarkup: true,
2922
- });
2986
+ const hyperlink = parseHyperlink(
2987
+ child,
2988
+ sourceXml,
2989
+ relationshipMap,
2990
+ {
2991
+ allowDeletedText: allowsDeletedText,
2992
+ preserveUnsupportedReviewMarkup: true,
2993
+ },
2994
+ "/word/document.xml",
2995
+ );
2923
2996
  if (hyperlink.type === "opaque_inline") {
2924
2997
  return [
2925
2998
  {
@@ -3020,6 +3093,7 @@ function parseHyperlink(
3020
3093
  allowDeletedText?: boolean;
3021
3094
  preserveUnsupportedReviewMarkup?: boolean;
3022
3095
  } = {},
3096
+ sourcePartPath = "/word/document.xml",
3023
3097
  ): ParsedHyperlinkNode | ParsedOpaqueInlineNode {
3024
3098
  const relationshipId = node.attributes["r:id"] ?? node.attributes.id;
3025
3099
  const anchor = node.attributes["w:anchor"] ?? node.attributes.anchor;
@@ -3058,6 +3132,10 @@ function parseHyperlink(
3058
3132
  // escape the hyperlink scope.
3059
3133
  type FieldBracketMode = "outside" | "instruction" | "result";
3060
3134
  let bracketMode: FieldBracketMode = "outside";
3135
+ let fieldSourceRef: CanonicalSourceRef | undefined;
3136
+ let fieldInstruction = "";
3137
+ let fieldLocked: boolean | undefined;
3138
+ let fieldDirty: boolean | undefined;
3061
3139
 
3062
3140
  for (const child of node.children) {
3063
3141
  if (child.type !== "element") {
@@ -3081,9 +3159,29 @@ function parseHyperlink(
3081
3159
  if (fldChar) {
3082
3160
  const fldType =
3083
3161
  fldChar.attributes["w:fldCharType"] ?? fldChar.attributes.fldCharType;
3084
- if (fldType === "begin") bracketMode = "instruction";
3085
- else if (fldType === "separate") bracketMode = "result";
3086
- else if (fldType === "end") bracketMode = "outside";
3162
+ if (fldType === "begin") {
3163
+ bracketMode = "instruction";
3164
+ fieldSourceRef = createFieldSequenceSourceRef(sourcePartPath, child);
3165
+ fieldInstruction = "";
3166
+ fieldLocked = readOnOffFieldFlag(fldChar, "fldLock");
3167
+ fieldDirty = readOnOffFieldFlag(fldChar, "dirty");
3168
+ } else if (fldType === "separate") {
3169
+ bracketMode = "result";
3170
+ } else if (fldType === "end") {
3171
+ bracketMode = "outside";
3172
+ }
3173
+ continue;
3174
+ }
3175
+
3176
+ const instrText = child.children.find(
3177
+ (c): c is XmlElementNode =>
3178
+ c.type === "element" && localName(c.name) === "instrText",
3179
+ );
3180
+ if (instrText && bracketMode === "instruction") {
3181
+ fieldInstruction += instrText.children
3182
+ .filter((entry): entry is XmlTextNode => entry.type === "text")
3183
+ .map((entry) => entry.text)
3184
+ .join("");
3087
3185
  continue;
3088
3186
  }
3089
3187
 
@@ -3107,14 +3205,60 @@ function parseHyperlink(
3107
3205
  children.push(...run.nodes);
3108
3206
  }
3109
3207
 
3208
+ const fieldClassification = fieldInstruction.trim().length > 0
3209
+ ? classifyFieldInstruction(fieldInstruction)
3210
+ : undefined;
3110
3211
  return {
3111
3212
  type: "hyperlink",
3112
3213
  href,
3214
+ sourceRef: createElementSourceRef(sourcePartPath, "hyperlink", node),
3215
+ ...(fieldSourceRef
3216
+ ? {
3217
+ fieldCarrier: {
3218
+ sourceRef: fieldSourceRef,
3219
+ ...(fieldClassification ? { fieldFamily: fieldClassification.family } : {}),
3220
+ refreshStatus: fieldClassification?.supported
3221
+ ? "stale"
3222
+ : "preserve-only",
3223
+ ...(fieldInstruction ? { instructionHash: sha256TextHex(fieldInstruction) } : {}),
3224
+ ...(children.length > 0 ? { resultHash: sha256TextHex(hyperlinkFieldResultText(children)) } : {}),
3225
+ ...(fieldLocked !== undefined ? { locked: fieldLocked } : {}),
3226
+ ...(fieldDirty !== undefined ? { dirty: fieldDirty } : {}),
3227
+ },
3228
+ }
3229
+ : {}),
3113
3230
  children,
3114
3231
  rawXml: sourceXml.slice(node.start, node.end),
3115
3232
  };
3116
3233
  }
3117
3234
 
3235
+ function hyperlinkFieldResultText(children: readonly ParsedInlineNode[]): string {
3236
+ return children
3237
+ .map((child) => {
3238
+ switch (child.type) {
3239
+ case "text":
3240
+ return child.text;
3241
+ case "tab":
3242
+ return "\t";
3243
+ case "hard_break":
3244
+ return "\n";
3245
+ case "symbol":
3246
+ return child.char;
3247
+ default:
3248
+ return "";
3249
+ }
3250
+ })
3251
+ .join("");
3252
+ }
3253
+
3254
+ function readOnOffFieldFlag(node: XmlElementNode, name: string): boolean | undefined {
3255
+ const raw = node.attributes[`w:${name}`] ?? node.attributes[name];
3256
+ if (raw === undefined) return undefined;
3257
+ const normalized = raw.toLowerCase();
3258
+ if (normalized === "0" || normalized === "false" || normalized === "off") return false;
3259
+ return true;
3260
+ }
3261
+
3118
3262
  function parseRunContentOnly(
3119
3263
  node: XmlElementNode,
3120
3264
  _sourceXml: string,
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ CanonicalSourceRef,
2
3
  NumberingCatalog,
3
4
  NumberingLevelDefinition,
4
5
  NumberingLevelParagraphGeometry,
@@ -93,6 +94,9 @@ export function parseNumberingXml(xml: string, context?: ParseNumberingContext):
93
94
  const numStyleLink = numStyleLinkEl ? readStringAttr(numStyleLinkEl, "w:val") : undefined;
94
95
  abstractDefinitions[abstractNumberingId] = {
95
96
  abstractNumberingId,
97
+ ...(context?.partPath !== undefined
98
+ ? { sourceRef: createNumberingSourceRef(context.partPath, "abstractNum", rawId) }
99
+ : {}),
96
100
  levels: readLevels(child),
97
101
  ...(nsid ? { nsid } : {}),
98
102
  ...(multiLevelType ? { multiLevelType } : {}),
@@ -114,6 +118,9 @@ export function parseNumberingXml(xml: string, context?: ParseNumberingContext):
114
118
  const numberingInstanceId = toCanonicalNumberingInstanceId(rawId);
115
119
  instances[numberingInstanceId] = {
116
120
  numberingInstanceId,
121
+ ...(context?.partPath !== undefined
122
+ ? { sourceRef: createNumberingSourceRef(context.partPath, "num", rawId) }
123
+ : {}),
117
124
  abstractNumberingId: toCanonicalAbstractNumberingId(rawAbstractId),
118
125
  overrides: readOverrides(child),
119
126
  };
@@ -129,6 +136,19 @@ export function parseNumberingXml(xml: string, context?: ParseNumberingContext):
129
136
  };
130
137
  }
131
138
 
139
+ function createNumberingSourceRef(
140
+ partPath: string,
141
+ element: "abstractNum" | "num",
142
+ rawId: string,
143
+ ): CanonicalSourceRef {
144
+ return {
145
+ sourceId: `part:${partPath}#${element}:${rawId}`,
146
+ partPath,
147
+ storyKind: "numbering",
148
+ element,
149
+ };
150
+ }
151
+
132
152
  /**
133
153
  * Read a `<w:numPicBullet>` element into a catalog entry. Extracts the
134
154
  * inner `wp:extent` when present (drawingML path); the raw XML is