@stll/folio-core 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/dist/ai-edits/headless.js +6 -5
  2. package/dist/ai-edits/index.d.ts +2 -2
  3. package/dist/ai-edits/index.js +2 -2
  4. package/dist/ai-edits/snapshot.js +13 -9
  5. package/dist/compare/content-alignment.js +16 -1
  6. package/dist/compare/inline-atoms.js +1 -1
  7. package/dist/compare/style-resources.js +6 -0
  8. package/dist/docx/appVersionNormalization.d.ts +0 -18
  9. package/dist/docx/blockContentParser.js +8 -0
  10. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  11. package/dist/docx/blockRangeMarkers.js +59 -0
  12. package/dist/docx/bookmarkParser.d.ts +2 -20
  13. package/dist/docx/bookmarkParser.js +6 -30
  14. package/dist/docx/borderParser.d.ts +13 -0
  15. package/dist/docx/borderParser.js +71 -0
  16. package/dist/docx/builtInStyles.d.ts +165 -0
  17. package/dist/docx/builtInStyles.js +239 -0
  18. package/dist/docx/commentIdNormalization.d.ts +3 -1
  19. package/dist/docx/commentIdNormalization.js +18 -1
  20. package/dist/docx/commentParser.d.ts +2 -1
  21. package/dist/docx/commentParser.js +34 -7
  22. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  23. package/dist/docx/commentReferenceNormalization.js +23 -14
  24. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  25. package/dist/docx/danglingRelationshipReferences.js +30 -0
  26. package/dist/docx/defaultParagraphStyle.d.ts +18 -1
  27. package/dist/docx/defaultParagraphStyle.js +23 -1
  28. package/dist/docx/documentParser.d.ts +2 -1
  29. package/dist/docx/documentParser.js +2 -2
  30. package/dist/docx/drawingUtils.d.ts +8 -1
  31. package/dist/docx/drawingUtils.js +12 -3
  32. package/dist/docx/fieldParser.js +3 -5
  33. package/dist/docx/footnoteParser.d.ts +3 -2
  34. package/dist/docx/footnoteParser.js +19 -4
  35. package/dist/docx/groupDrawingParser.js +1 -1
  36. package/dist/docx/headerFooterRefParser.d.ts +4 -3
  37. package/dist/docx/headerFooterRefParser.js +42 -12
  38. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  39. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  40. package/dist/docx/hyperlinkParser.js +11 -15
  41. package/dist/docx/imageParser.d.ts +1 -1
  42. package/dist/docx/imageParser.js +22 -18
  43. package/dist/docx/imageRawXml.js +5 -5
  44. package/dist/docx/markupRangeMarker.d.ts +15 -0
  45. package/dist/docx/markupRangeMarker.js +44 -0
  46. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  47. package/dist/docx/noteReferenceStyles.js +70 -0
  48. package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
  49. package/dist/docx/numberingReferenceNormalization.js +20 -1
  50. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  51. package/dist/docx/paragraphParser.js +66 -99
  52. package/dist/docx/paragraphPropertySource.js +1 -0
  53. package/dist/docx/paragraphTraversal.d.ts +37 -1
  54. package/dist/docx/paragraphTraversal.js +84 -1
  55. package/dist/docx/parseContext.d.ts +37 -0
  56. package/dist/docx/parseContext.js +67 -0
  57. package/dist/docx/parseWarningMessage.d.ts +6 -0
  58. package/dist/docx/parseWarningMessage.js +44 -0
  59. package/dist/docx/parser.js +81 -27
  60. package/dist/docx/relsParser.d.ts +28 -11
  61. package/dist/docx/relsParser.js +26 -13
  62. package/dist/docx/revisionIdNormalization.js +81 -7
  63. package/dist/docx/rezip.js +63 -22
  64. package/dist/docx/runConsolidator.js +1 -2
  65. package/dist/docx/runParser.d.ts +8 -1
  66. package/dist/docx/runParser.js +30 -48
  67. package/dist/docx/sectionParser.d.ts +2 -1
  68. package/dist/docx/sectionParser.js +21 -65
  69. package/dist/docx/serializer/borderSerializer.d.ts +1 -2
  70. package/dist/docx/serializer/commentSerializer.js +22 -9
  71. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  72. package/dist/docx/serializer/documentSerializer.js +6 -16
  73. package/dist/docx/serializer/headerFooterSerializer.js +5 -0
  74. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  75. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  76. package/dist/docx/serializer/noteSerializer.js +5 -0
  77. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  78. package/dist/docx/serializer/paragraphSerializer.js +29 -35
  79. package/dist/docx/serializer/runSerializer.js +13 -7
  80. package/dist/docx/serializer/tableSerializer.js +28 -13
  81. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  82. package/dist/docx/server/build.js +8 -1
  83. package/dist/docx/server/createBilingualDocument.js +10 -18
  84. package/dist/docx/server/extractDocxText.js +3 -4
  85. package/dist/docx/shadingParser.d.ts +6 -0
  86. package/dist/docx/shadingParser.js +32 -0
  87. package/dist/docx/shapeParser.js +3 -3
  88. package/dist/docx/styleParser.js +13 -87
  89. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  90. package/dist/docx/styleReferenceResolution.js +51 -0
  91. package/dist/docx/tableLook.d.ts +57 -0
  92. package/dist/docx/tableLook.js +63 -0
  93. package/dist/docx/tableParser.d.ts +7 -9
  94. package/dist/docx/tableParser.js +64 -110
  95. package/dist/docx/textBoxParser.js +4 -4
  96. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  97. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  98. package/dist/docx/transitionalSpelling.d.ts +13 -2
  99. package/dist/docx/transitionalSpelling.js +23 -1
  100. package/dist/docx/verbatimCapture.js +4 -11
  101. package/dist/docx/vmlImageParser.js +2 -2
  102. package/dist/docx/watermarkParser.js +2 -2
  103. package/dist/docx/xmlParser.d.ts +22 -32
  104. package/dist/docx/xmlParser.js +36 -21
  105. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  106. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  107. package/dist/internal/paragraphFormattingSerialization.js +26 -6
  108. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  109. package/dist/layout-engine/index.d.ts +2 -2
  110. package/dist/layout-engine/index.js +2 -2
  111. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  112. package/dist/layout-engine/types.d.ts +8 -2
  113. package/dist/layout-engine/types.js +35 -2
  114. package/dist/markdown/index.js +1 -1
  115. package/dist/markdown/internals.d.ts +6 -1
  116. package/dist/markdown/internals.js +14 -1
  117. package/dist/markdown/renderBlock.js +35 -21
  118. package/dist/markdown/renderParagraph.js +14 -5
  119. package/dist/markdown/renderRuns.js +4 -3
  120. package/dist/markdown/renderTable.js +4 -3
  121. package/dist/markdown/trailers.js +41 -7
  122. package/dist/markdown/types.d.ts +3 -7
  123. package/dist/prosemirror/attrs/index.js +2 -5
  124. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  125. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  126. package/dist/prosemirror/commands/index.d.ts +3 -3
  127. package/dist/prosemirror/commands/index.js +2 -2
  128. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  129. package/dist/prosemirror/commands/paragraph.js +2 -2
  130. package/dist/prosemirror/commentIdAllocator.js +2 -7
  131. package/dist/prosemirror/conversion/fromProseDoc.js +129 -40
  132. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  133. package/dist/prosemirror/conversion/toProseDoc.js +375 -316
  134. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  135. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  136. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  137. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  138. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  139. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  140. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  141. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  142. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  143. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  144. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  145. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  146. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  147. package/dist/prosemirror/extensions/types.d.ts +2 -2
  148. package/dist/prosemirror/index.d.ts +3 -3
  149. package/dist/prosemirror/index.js +3 -3
  150. package/dist/prosemirror/insertOperations.d.ts +9 -2
  151. package/dist/prosemirror/insertOperations.js +9 -4
  152. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +159 -0
  153. package/dist/prosemirror/paragraphFormattingProvenance.js +106 -0
  154. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  155. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  156. package/dist/prosemirror/plugins/index.d.ts +2 -2
  157. package/dist/prosemirror/plugins/index.js +2 -2
  158. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  159. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  160. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  161. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  162. package/dist/prosemirror/schema/nodes.d.ts +31 -0
  163. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  164. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  165. package/dist/prosemirror/styles/styleResolver.js +12 -0
  166. package/dist/style-engine/styleEngine.d.ts +3 -0
  167. package/dist/style-engine/styleEngine.js +3 -0
  168. package/dist/style-sets/extract.js +1 -23
  169. package/dist/style-sets/stellaStyle.js +46 -39
  170. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  171. package/dist/style-sets/styleSetNormalization.js +99 -0
  172. package/dist/types/content.d.ts +2 -2
  173. package/dist/utils/createDocument.js +145 -20
  174. package/dist/utils/headingCollector.d.ts +8 -5
  175. package/dist/utils/headingCollector.js +23 -25
  176. package/dist/utils/tableOfContentsStyle.js +9 -2
  177. package/package.json +2 -2
  178. package/dist/docx/textWhitespace.d.ts +0 -4
  179. package/dist/docx/textWhitespace.js +0 -4
  180. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  181. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  182. package/dist/markdown/headings.d.ts +0 -13
  183. package/dist/markdown/headings.js +0 -20
@@ -17,6 +17,23 @@ declare const BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING: {
17
17
  lineSpacing: number;
18
18
  lineSpacingRule: "auto";
19
19
  };
20
+ type MintDefaultParagraphStyleOptions = {
21
+ takenStyleIds: ReadonlySet<string>;
22
+ /** Whether the source declared `w:docDefaults`, which the set carries over. */
23
+ hasDocDefaults: boolean;
24
+ };
25
+ /**
26
+ * The default paragraph style a set needs when its source declared none.
27
+ *
28
+ * The id only has to be free, because the set is what defines it. The
29
+ * formatting has to be the built-in template's whenever the source had no
30
+ * `w:docDefaults`, because that is what the source itself rendered as: a
31
+ * consumer applies its built-in Normal only where no default paragraph style
32
+ * exists, and this minted style is one. Where the source did declare
33
+ * `w:docDefaults`, the set carries them and they remain authoritative, so the
34
+ * minted style states nothing.
35
+ */
36
+ declare const mintDefaultParagraphStyle: ({ takenStyleIds, hasDocDefaults }: MintDefaultParagraphStyleOptions) => document_d_exports.Style;
20
37
  declare const resolveDefaultParagraphStyle: (styles: Iterable<document_d_exports.Style>) => document_d_exports.Style | undefined;
21
38
  //#endregion
22
- export { BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_ID, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_NAME, resolveDefaultParagraphStyle };
39
+ export { BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_ID, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_NAME, mintDefaultParagraphStyle, resolveDefaultParagraphStyle };
@@ -16,6 +16,28 @@ const BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING = {
16
16
  lineSpacing: 259,
17
17
  lineSpacingRule: "auto"
18
18
  };
19
+ /**
20
+ * The default paragraph style a set needs when its source declared none.
21
+ *
22
+ * The id only has to be free, because the set is what defines it. The
23
+ * formatting has to be the built-in template's whenever the source had no
24
+ * `w:docDefaults`, because that is what the source itself rendered as: a
25
+ * consumer applies its built-in Normal only where no default paragraph style
26
+ * exists, and this minted style is one. Where the source did declare
27
+ * `w:docDefaults`, the set carries them and they remain authoritative, so the
28
+ * minted style states nothing.
29
+ */
30
+ const mintDefaultParagraphStyle = ({ takenStyleIds, hasDocDefaults }) => {
31
+ let styleId = BUILT_IN_DEFAULT_PARAGRAPH_STYLE_ID;
32
+ for (let suffix = 1; takenStyleIds.has(styleId); suffix += 1) styleId = `${BUILT_IN_DEFAULT_PARAGRAPH_STYLE_ID}${suffix}`;
33
+ return {
34
+ styleId,
35
+ type: "paragraph",
36
+ name: BUILT_IN_DEFAULT_PARAGRAPH_STYLE_NAME,
37
+ default: true,
38
+ ...hasDocDefaults ? {} : { pPr: { ...BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING } }
39
+ };
40
+ };
19
41
  const resolveDefaultParagraphStyle = (styles) => {
20
42
  let flagged;
21
43
  let namedBuiltIn;
@@ -29,4 +51,4 @@ const resolveDefaultParagraphStyle = (styles) => {
29
51
  return flagged ?? namedBuiltIn ?? idBuiltIn;
30
52
  };
31
53
  //#endregion
32
- export { BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_ID, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_NAME, resolveDefaultParagraphStyle };
54
+ export { BUILT_IN_DEFAULT_PARAGRAPH_FORMATTING, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_ID, BUILT_IN_DEFAULT_PARAGRAPH_STYLE_NAME, mintDefaultParagraphStyle, resolveDefaultParagraphStyle };
@@ -1,6 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
3
  import { StyleMap } from "./styleParser.js";
4
+ import { ParseContext } from "./parseContext.js";
4
5
  //#region src/docx/documentParser.d.ts
5
6
  /**
6
7
  * Extract template variables from text
@@ -27,7 +28,7 @@ declare function extractAllTemplateVariables(content: document_d_exports.BlockCo
27
28
  * @param media - Media files
28
29
  * @returns DocumentBody with content, sections, and template variables
29
30
  */
30
- declare function parseDocumentBody(xml: string, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null): document_d_exports.DocumentBody;
31
+ declare function parseDocumentBody(xml: string, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null, context?: ParseContext): document_d_exports.DocumentBody;
31
32
  /**
32
33
  * Get all paragraphs from document body (flattened)
33
34
  */
@@ -119,7 +119,7 @@ function canonicalizeLeadingBodySectionProperties(bodyElement, body) {
119
119
  * @param media - Media files
120
120
  * @returns DocumentBody with content, sections, and template variables
121
121
  */
122
- function parseDocumentBody(xml, styles = null, theme = null, numbering = null, rels = null, media = null) {
122
+ function parseDocumentBody(xml, styles = null, theme = null, numbering = null, rels = null, media = null, context) {
123
123
  const result = { content: [] };
124
124
  if (!xml) return result;
125
125
  const streamed = parseStreamingXml(xml);
@@ -129,7 +129,7 @@ function parseDocumentBody(xml, styles = null, theme = null, numbering = null, r
129
129
  if (!bodyEl) return result;
130
130
  result.content = parseBlockContent(bodyEl, styles, theme, numbering, rels, media, { rootXmlns: collectXmlnsDeclarations(documentEl) });
131
131
  const finalSectPr = findChild(bodyEl, "w", "sectPr");
132
- if (finalSectPr) result.finalSectionProperties = parseSectionProperties(finalSectPr);
132
+ if (finalSectPr) result.finalSectionProperties = parseSectionProperties(finalSectPr, context);
133
133
  canonicalizeLeadingBodySectionProperties(bodyEl, result);
134
134
  result.sections = buildSections(result.content, result.finalSectionProperties);
135
135
  return result;
@@ -67,6 +67,13 @@ declare function parseWrapElement(wrapEl: XmlElement | null, behindDoc: boolean,
67
67
  /**
68
68
  * Parse wrap from an anchor element (finds wrap child internally).
69
69
  */
70
+ /**
71
+ * Read `wp:anchor/@behindDoc`, the flag that puts an anchored object behind the
72
+ * body text. The attribute is xsd:boolean, so `1`, `0`, `true` and `false` are
73
+ * all legal spellings and producers differ: Word writes `1`, others `true`.
74
+ * Absent means in front of the text.
75
+ */
76
+ declare function parseAnchorBehindDoc(anchor: XmlElement): boolean;
70
77
  declare function parseAnchorWrap(anchor: XmlElement): document_d_exports.ImageWrap | undefined;
71
78
  /**
72
79
  * Resolve a ColorValue to a CSS hex string using default theme colors.
@@ -74,4 +81,4 @@ declare function parseAnchorWrap(anchor: XmlElement): document_d_exports.ImageWr
74
81
  */
75
82
  declare function resolveColorValueToHex(color: document_d_exports.ColorValue | undefined): string | undefined;
76
83
  //#endregion
77
- export { THEME_COLOR_TO_DRAWING_SCHEME, WRAP_ELEMENT_NAMES, parseAnchorPosition, parseAnchorWrap, parseColorElement, parseFill, parseOutline, parsePositionH, parsePositionV, parseWrapElement, resolveColorValueToHex };
84
+ export { THEME_COLOR_TO_DRAWING_SCHEME, WRAP_ELEMENT_NAMES, parseAnchorBehindDoc, parseAnchorPosition, parseAnchorWrap, parseColorElement, parseFill, parseOutline, parsePositionH, parsePositionV, parseWrapElement, resolveColorValueToHex };
@@ -1,6 +1,6 @@
1
1
  import { ImageHorizontalAlignmentSchema, ImageHorizontalRelativeToSchema, ImageVerticalAlignmentSchema, ImageVerticalRelativeToSchema, ImageWrapTextSchema, ShapeOutlineStyleSchema, narrowEnum } from "./parserEnums.js";
2
2
  import { captureVerbatimXml } from "./verbatimCapture.js";
3
- import { findByFullName, findChildByLocalName, findChildrenByLocalName, getAttribute, getChildElements, getTextContent, parseNumericAttribute } from "./xmlParser.js";
3
+ import { findByFullName, findChildByLocalName, findChildrenByLocalName, getAttribute, getChildElements, getTextContent, parseNumericAttribute, parseOnOffValue } from "./xmlParser.js";
4
4
  //#region src/docx/drawingUtils.ts
5
5
  /**
6
6
  * Map OOXML scheme names to standard theme color slots.
@@ -361,9 +361,18 @@ function parseWrapElement(wrapEl, behindDoc, anchorDistances) {
361
361
  /**
362
362
  * Parse wrap from an anchor element (finds wrap child internally).
363
363
  */
364
+ /**
365
+ * Read `wp:anchor/@behindDoc`, the flag that puts an anchored object behind the
366
+ * body text. The attribute is xsd:boolean, so `1`, `0`, `true` and `false` are
367
+ * all legal spellings and producers differ: Word writes `1`, others `true`.
368
+ * Absent means in front of the text.
369
+ */
370
+ function parseAnchorBehindDoc(anchor) {
371
+ return parseOnOffValue(getAttribute(anchor, null, "behindDoc")) ?? false;
372
+ }
364
373
  function parseAnchorWrap(anchor) {
365
374
  const children = getChildElements(anchor);
366
- const behindDoc = getAttribute(anchor, null, "behindDoc") === "1";
375
+ const behindDoc = parseAnchorBehindDoc(anchor);
367
376
  const wrapEl = children.find((el) => WRAP_ELEMENT_NAMES.includes(el.name ?? ""));
368
377
  const distT = parseNumericAttribute(anchor, null, "distT");
369
378
  const distB = parseNumericAttribute(anchor, null, "distB");
@@ -409,4 +418,4 @@ function resolveColorValueToHex(color) {
409
418
  if (color.themeColor) return `#${DEFAULT_THEME_COLOR_HEX[color.themeColor] ?? "000000"}`;
410
419
  }
411
420
  //#endregion
412
- export { THEME_COLOR_TO_DRAWING_SCHEME, WRAP_ELEMENT_NAMES, parseAnchorPosition, parseAnchorWrap, parseColorElement, parseFill, parseOutline, parsePositionH, parsePositionV, parseWrapElement, resolveColorValueToHex };
421
+ export { THEME_COLOR_TO_DRAWING_SCHEME, WRAP_ELEMENT_NAMES, parseAnchorBehindDoc, parseAnchorPosition, parseAnchorWrap, parseColorElement, parseFill, parseOutline, parsePositionH, parsePositionV, parseWrapElement, resolveColorValueToHex };
@@ -1,7 +1,7 @@
1
1
  import { formatOoxmlCounter } from "./ooxmlCounterFormatter.js";
2
2
  import { FieldTypeSchema, narrowEnum } from "./parserEnums.js";
3
3
  import { parseRun } from "./runParser.js";
4
- import { findChildren, getAttribute } from "./xmlParser.js";
4
+ import { findChildren, getAttribute, parseOnOffAttribute } from "./xmlParser.js";
5
5
  //#region src/docx/fieldParser.ts
6
6
  /**
7
7
  * All known field types from OOXML specification
@@ -164,10 +164,8 @@ function parseSimpleField(node, styles, theme) {
164
164
  fieldType: parseFieldType(instruction),
165
165
  content: []
166
166
  };
167
- const fldLock = getAttribute(node, "w", "fldLock");
168
- if (fldLock === "1" || fldLock === "true") field.fldLock = true;
169
- const dirty = getAttribute(node, "w", "dirty");
170
- if (dirty === "1" || dirty === "true") field.dirty = true;
167
+ if (parseOnOffAttribute(node, "w", "fldLock") === true) field.fldLock = true;
168
+ if (parseOnOffAttribute(node, "w", "dirty") === true) field.dirty = true;
171
169
  const children = findChildren(node, "w", "r");
172
170
  for (const child of children) {
173
171
  const run = parseRun(child, styles, theme);
@@ -1,6 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
3
  import { StyleMap } from "./styleParser.js";
4
+ import { ParseContext } from "./parseContext.js";
4
5
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
5
6
  //#region src/docx/footnoteParser.d.ts
6
7
  /**
@@ -52,7 +53,7 @@ type EndnoteMap = {
52
53
  * @param media - Media files for images
53
54
  * @returns FootnoteMap with all footnotes
54
55
  */
55
- declare function parseFootnotes(footnotesXml: string | null, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null): FootnoteMap;
56
+ declare function parseFootnotes(footnotesXml: string | null, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null, context?: ParseContext): FootnoteMap;
56
57
  /**
57
58
  * Parse endnotes.xml
58
59
  *
@@ -64,7 +65,7 @@ declare function parseFootnotes(footnotesXml: string | null, styles?: StyleMap |
64
65
  * @param media - Media files for images
65
66
  * @returns EndnoteMap with all endnotes
66
67
  */
67
- declare function parseEndnotes(endnotesXml: string | null, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null): EndnoteMap;
68
+ declare function parseEndnotes(endnotesXml: string | null, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null, context?: ParseContext): EndnoteMap;
68
69
  /**
69
70
  * Get plain text content of a footnote.
70
71
  *
@@ -4,6 +4,7 @@ import { parseParagraph } from "./paragraphParser.js";
4
4
  import { parseSdtProperties } from "./sdtProperties.js";
5
5
  import { parseTable } from "./tableParser.js";
6
6
  import { findChild, findChildren, getAttributes, getChildElements, getLocalName, parseXml } from "./xmlParser.js";
7
+ import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
7
8
  //#region src/docx/footnoteParser.ts
8
9
  /**
9
10
  * Parse note type attribute
@@ -69,7 +70,7 @@ function parseFootnote(element, styles, theme, numbering, rels, media) {
69
70
  * @param media - Media files for images
70
71
  * @returns FootnoteMap with all footnotes
71
72
  */
72
- function parseFootnotes(footnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null) {
73
+ function parseFootnotes(footnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null, context) {
73
74
  const byId = /* @__PURE__ */ new Map();
74
75
  const footnotes = [];
75
76
  if (!footnotesXml) return createFootnoteMap(byId, footnotes);
@@ -78,7 +79,14 @@ function parseFootnotes(footnotesXml, styles = null, theme = null, numbering = n
78
79
  const footnoteElements = findChildren(rootElement, "w", "footnote");
79
80
  for (const fnEl of footnoteElements) {
80
81
  const footnote = parseFootnote(fnEl, styles, theme, numbering, rels, media);
81
- if (byId.has(footnote.id)) continue;
82
+ if (byId.has(footnote.id)) {
83
+ context?.warn({
84
+ code: PARSE_WARNING_CODES.duplicateNoteId,
85
+ element: "w:footnote",
86
+ at: `w:id ${String(footnote.id)}`
87
+ });
88
+ continue;
89
+ }
82
90
  byId.set(footnote.id, footnote);
83
91
  footnotes.push(footnote);
84
92
  }
@@ -130,7 +138,7 @@ function parseEndnote(element, styles, theme, numbering, rels, media) {
130
138
  * @param media - Media files for images
131
139
  * @returns EndnoteMap with all endnotes
132
140
  */
133
- function parseEndnotes(endnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null) {
141
+ function parseEndnotes(endnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null, context) {
134
142
  const byId = /* @__PURE__ */ new Map();
135
143
  const endnotes = [];
136
144
  if (!endnotesXml) return createEndnoteMap(byId, endnotes);
@@ -139,7 +147,14 @@ function parseEndnotes(endnotesXml, styles = null, theme = null, numbering = nul
139
147
  const endnoteElements = findChildren(rootElement, "w", "endnote");
140
148
  for (const enEl of endnoteElements) {
141
149
  const endnote = parseEndnote(enEl, styles, theme, numbering, rels, media);
142
- if (byId.has(endnote.id)) continue;
150
+ if (byId.has(endnote.id)) {
151
+ context?.warn({
152
+ code: PARSE_WARNING_CODES.duplicateNoteId,
153
+ element: "w:endnote",
154
+ at: `w:id ${String(endnote.id)}`
155
+ });
156
+ continue;
157
+ }
143
158
  byId.set(endnote.id, endnote);
144
159
  endnotes.push(endnote);
145
160
  }
@@ -121,7 +121,7 @@ const renderPicture = (picture, index, rels, media) => {
121
121
  if (width <= 0 || height <= 0) return "";
122
122
  const blipFill = findChildByLocalName(picture, "blipFill");
123
123
  const blip = findChildByLocalName(blipFill, "blip");
124
- const { src } = resolveImageData(getAttribute(blip, "r", "embed") ?? getAttribute(blip, "r", "link") ?? "", rels, media);
124
+ const { src } = resolveImageData(getAttribute(blip, "r", "embed") ?? getAttribute(blip, "r", "link") ?? void 0, rels, media);
125
125
  if (!src) return "";
126
126
  const sourceRect = findChildByLocalName(blipFill, "srcRect");
127
127
  const left = Math.max(0, numericAttr(sourceRect, "l")) / CROP_SCALE;
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { ParseContext } from "./parseContext.js";
2
3
  import { XmlElement } from "./xmlParser.js";
3
4
  //#region src/docx/headerFooterRefParser.d.ts
4
5
  /**
@@ -11,15 +12,15 @@ import { XmlElement } from "./xmlParser.js";
11
12
  * header/footer reference types has to read them through here, or a
12
13
  * normalisation at this boundary looks like a lost reference downstream.
13
14
  */
14
- declare function parseHeaderFooterType(typeAttr: string | null): document_d_exports.HeaderFooterType;
15
+ declare function parseHeaderFooterType(typeAttr: string | null, context?: ParseContext): document_d_exports.HeaderFooterType;
15
16
  /**
16
17
  * Parse a header reference from sectPr (w:headerReference)
17
18
  */
18
- declare function parseHeaderReference(element: XmlElement): document_d_exports.HeaderReference;
19
+ declare function parseHeaderReference(element: XmlElement, context?: ParseContext): document_d_exports.HeaderReference | null;
19
20
  /**
20
21
  * Parse a footer reference from sectPr (w:footerReference)
21
22
  */
22
- declare function parseFooterReference(element: XmlElement): document_d_exports.FooterReference;
23
+ declare function parseFooterReference(element: XmlElement, context?: ParseContext): document_d_exports.FooterReference | null;
23
24
  /**
24
25
  * Parse all header references from a sectPr element
25
26
  */
@@ -1,6 +1,14 @@
1
1
  import { findChildren, getAttribute } from "./xmlParser.js";
2
+ import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
2
3
  //#region src/docx/headerFooterRefParser.ts
3
4
  /**
5
+ * Header/Footer Reference Parser
6
+ *
7
+ * Parses header/footer references (w:headerReference, w:footerReference) that
8
+ * appear in section properties. Extracted from headerFooterParser to break the
9
+ * circular dependency: headerFooterParser -> paragraphParser -> sectionParser -> headerFooterParser.
10
+ */
11
+ /**
4
12
  * Read a `w:type` attribute as one of ECMA-376's three `ST_HdrFtr` values.
5
13
  *
6
14
  * The enumeration is `even`, `default` and `first` (17.18.36); `default` is
@@ -10,32 +18,48 @@ import { findChildren, getAttribute } from "./xmlParser.js";
10
18
  * header/footer reference types has to read them through here, or a
11
19
  * normalisation at this boundary looks like a lost reference downstream.
12
20
  */
13
- function parseHeaderFooterType(typeAttr) {
21
+ function parseHeaderFooterType(typeAttr, context) {
14
22
  switch (typeAttr) {
15
23
  case "first": return "first";
16
24
  case "even": return "even";
17
- default: return "default";
25
+ case "default":
26
+ case null: return "default";
27
+ default:
28
+ context?.warn({
29
+ code: PARSE_WARNING_CODES.headerFooterTypeOutsideEnum,
30
+ value: typeAttr,
31
+ element: "w:type"
32
+ });
33
+ return "default";
18
34
  }
19
35
  }
20
- function parseHeaderFooterReference(element) {
21
- const typeAttr = getAttribute(element, "w", "type");
22
- const rId = getAttribute(element, "r", "id") ?? "";
36
+ /**
37
+ * A reference with no `r:id` names no part, so it is not a reference.
38
+ *
39
+ * Coercing the missing attribute to `""` used to put the empty string in the
40
+ * model, where it became a part-map key on one side and an `r:id=""` the
41
+ * schema rejects on the other. Null here keeps the reference out of the model
42
+ * entirely, which is what the source said.
43
+ */
44
+ function parseHeaderFooterReference(element, context) {
45
+ const rId = getAttribute(element, "r", "id");
46
+ if (rId === null || rId.length === 0) return null;
23
47
  return {
24
- type: parseHeaderFooterType(typeAttr),
48
+ type: parseHeaderFooterType(getAttribute(element, "w", "type"), context?.scoped({ at: `r:id "${rId}"` })),
25
49
  rId
26
50
  };
27
51
  }
28
52
  /**
29
53
  * Parse a header reference from sectPr (w:headerReference)
30
54
  */
31
- function parseHeaderReference(element) {
32
- return parseHeaderFooterReference(element);
55
+ function parseHeaderReference(element, context) {
56
+ return parseHeaderFooterReference(element, context);
33
57
  }
34
58
  /**
35
59
  * Parse a footer reference from sectPr (w:footerReference)
36
60
  */
37
- function parseFooterReference(element) {
38
- return parseHeaderFooterReference(element);
61
+ function parseFooterReference(element, context) {
62
+ return parseHeaderFooterReference(element, context);
39
63
  }
40
64
  /**
41
65
  * Parse all header references from a sectPr element
@@ -43,7 +67,10 @@ function parseFooterReference(element) {
43
67
  function parseHeaderReferences(sectPr) {
44
68
  const refs = [];
45
69
  const headerRefElements = findChildren(sectPr, "w", "headerReference");
46
- for (const el of headerRefElements) refs.push(parseHeaderReference(el));
70
+ for (const el of headerRefElements) {
71
+ const ref = parseHeaderReference(el);
72
+ if (ref) refs.push(ref);
73
+ }
47
74
  return refs;
48
75
  }
49
76
  /**
@@ -52,7 +79,10 @@ function parseHeaderReferences(sectPr) {
52
79
  function parseFooterReferences(sectPr) {
53
80
  const refs = [];
54
81
  const footerRefElements = findChildren(sectPr, "w", "footerReference");
55
- for (const el of footerRefElements) refs.push(parseFooterReference(el));
82
+ for (const el of footerRefElements) {
83
+ const ref = parseFooterReference(el);
84
+ if (ref) refs.push(ref);
85
+ }
56
86
  return refs;
57
87
  }
58
88
  //#endregion
@@ -1,5 +1,8 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  //#region src/docx/headerFooterReferenceNormalization.d.ts
3
+ /** The codes this normalisation is reported under, owned here, not at the caller. */
4
+ declare const DANGLING_HEADER_REFERENCE_WARNING: "dangling-header-reference";
5
+ declare const DANGLING_FOOTER_REFERENCE_WARNING: "dangling-footer-reference";
3
6
  type NormalizeHeaderFooterReferencesInput = {
4
7
  documentBody: document_d_exports.DocumentBody;
5
8
  headers?: Map<string, document_d_exports.HeaderFooter>;
@@ -11,4 +14,4 @@ type NormalizeHeaderFooterReferencesResult = {
11
14
  };
12
15
  declare const normalizeHeaderFooterReferences: ({ documentBody, headers, footers }: NormalizeHeaderFooterReferencesInput) => NormalizeHeaderFooterReferencesResult;
13
16
  //#endregion
14
- export { normalizeHeaderFooterReferences };
17
+ export { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, normalizeHeaderFooterReferences };
@@ -1,4 +1,8 @@
1
+ import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
1
2
  //#region src/docx/headerFooterReferenceNormalization.ts
3
+ /** The codes this normalisation is reported under, owned here, not at the caller. */
4
+ const DANGLING_HEADER_REFERENCE_WARNING = PARSE_WARNING_CODES.danglingHeaderReference;
5
+ const DANGLING_FOOTER_REFERENCE_WARNING = PARSE_WARNING_CODES.danglingFooterReference;
2
6
  const normalizeHeaderFooterReferences = ({ documentBody, headers, footers }) => {
3
7
  const seenSectionProperties = /* @__PURE__ */ new Set();
4
8
  let removedDanglingHeaderReferences = 0;
@@ -61,4 +65,4 @@ const removeDanglingReferences = (references, validParts) => {
61
65
  };
62
66
  };
63
67
  //#endregion
64
- export { normalizeHeaderFooterReferences };
68
+ export { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, normalizeHeaderFooterReferences };
@@ -1,6 +1,7 @@
1
1
  import { sanitizeExternalUrl, sanitizeLinkTarget } from "../utils/urlSecurity.js";
2
+ import { RELATIONSHIP_TYPES, resolveRelationshipIdOfType } from "./relsParser.js";
2
3
  import { parseRun } from "./runParser.js";
3
- import { getAttribute, getChildElements, getLocalName, mergeXmlnsDeclarations, parseNumericAttribute } from "./xmlParser.js";
4
+ import { getAttribute, getChildElements, getLocalName, mergeXmlnsDeclarations, parseNumericAttribute, parseOnOffAttribute } from "./xmlParser.js";
4
5
  //#region src/docx/hyperlinkParser.ts
5
6
  /**
6
7
  * Parse bookmark start (w:bookmarkStart)
@@ -49,11 +50,9 @@ function parseHyperlink(node, rels, styles = null, theme = null, media = null, r
49
50
  const rId = getAttribute(node, "r", "id");
50
51
  if (rId) {
51
52
  hyperlink.rId = rId;
52
- if (rels) {
53
- const rel = rels.get(rId);
54
- if (rel) {
55
- if (sanitizeExternalUrl(rel.target)) hyperlink.href = rel.target;
56
- }
53
+ const resolved = resolveRelationshipIdOfType(rels, rId, RELATIONSHIP_TYPES.hyperlink);
54
+ if (resolved.status === "resolved") {
55
+ if (sanitizeExternalUrl(resolved.relationship.target)) hyperlink.href = resolved.relationship.target;
57
56
  }
58
57
  }
59
58
  const anchor = getAttribute(node, "w", "anchor");
@@ -65,8 +64,7 @@ function parseHyperlink(node, rels, styles = null, theme = null, media = null, r
65
64
  if (tooltip) hyperlink.tooltip = tooltip;
66
65
  const tgtFrame = getAttribute(node, "w", "tgtFrame");
67
66
  if (tgtFrame) hyperlink.target = sanitizeLinkTarget(tgtFrame);
68
- const history = getAttribute(node, "w", "history");
69
- if (history === "1" || history === "true") hyperlink.history = true;
67
+ if (parseOnOffAttribute(node, "w", "history") === true) hyperlink.history = true;
70
68
  const docLocation = getAttribute(node, "w", "docLocation");
71
69
  if (docLocation) hyperlink.docLocation = docLocation;
72
70
  const inScopeXmlns = mergeXmlnsDeclarations(rootXmlns, node);
@@ -168,13 +166,11 @@ function getHyperlinkRuns(hyperlink) {
168
166
  * @returns The resolved URL or undefined
169
167
  */
170
168
  function resolveHyperlinkUrl(hyperlink, rels) {
171
- if (hyperlink.rId) {
172
- const rel = rels.get(hyperlink.rId);
173
- if (rel) {
174
- if (sanitizeExternalUrl(rel.target)) {
175
- hyperlink.href = rel.target;
176
- return hyperlink.href;
177
- }
169
+ const resolved = resolveRelationshipIdOfType(rels, hyperlink.rId, RELATIONSHIP_TYPES.hyperlink);
170
+ if (resolved.status === "resolved") {
171
+ if (sanitizeExternalUrl(resolved.relationship.target)) {
172
+ hyperlink.href = resolved.relationship.target;
173
+ return hyperlink.href;
178
174
  }
179
175
  }
180
176
  if (hyperlink.anchor && !hyperlink.href) {
@@ -9,7 +9,7 @@ import { XmlElement } from "./xmlParser.js";
9
9
  * @param media - Media files map
10
10
  * @returns Object with src (data URL or blob), mimeType, and filename
11
11
  */
12
- declare function resolveImageData(rId: string, rels: document_d_exports.RelationshipMap | undefined, media: Map<string, document_d_exports.MediaFile> | undefined): {
12
+ declare function resolveImageData(rId: string | undefined, rels: document_d_exports.RelationshipMap | undefined, media: Map<string, document_d_exports.MediaFile> | undefined): {
13
13
  src?: string;
14
14
  mimeType?: string;
15
15
  filename?: string;
@@ -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 };