@stll/folio-core 0.43.0 → 0.45.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 (237) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +7 -5
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/ai-edits/index.js +2 -2
  5. package/dist/ai-edits/snapshot.js +13 -9
  6. package/dist/compare/content-alignment.js +94 -54
  7. package/dist/compare/inline-atoms.js +34 -20
  8. package/dist/compare/style-resources.js +6 -0
  9. package/dist/content-controls/mutateContentControls.js +4 -2
  10. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  11. package/dist/document-operations.js +14 -3
  12. package/dist/docx/appVersionNormalization.d.ts +0 -18
  13. package/dist/docx/blockContentParser.js +8 -0
  14. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  15. package/dist/docx/blockRangeMarkers.js +59 -0
  16. package/dist/docx/bookmarkParser.d.ts +2 -20
  17. package/dist/docx/bookmarkParser.js +6 -30
  18. package/dist/docx/borderParser.d.ts +13 -0
  19. package/dist/docx/borderParser.js +71 -0
  20. package/dist/docx/builtInStyles.d.ts +165 -0
  21. package/dist/docx/builtInStyles.js +239 -0
  22. package/dist/docx/commentIdNormalization.d.ts +3 -1
  23. package/dist/docx/commentIdNormalization.js +18 -1
  24. package/dist/docx/commentParser.d.ts +2 -1
  25. package/dist/docx/commentParser.js +80 -42
  26. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  27. package/dist/docx/commentReferenceNormalization.js +23 -14
  28. package/dist/docx/commentThreadKey.d.ts +18 -0
  29. package/dist/docx/commentThreadKey.js +22 -0
  30. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  31. package/dist/docx/danglingRelationshipReferences.js +30 -0
  32. package/dist/docx/defaultParagraphStyle.d.ts +18 -1
  33. package/dist/docx/defaultParagraphStyle.js +23 -1
  34. package/dist/docx/diagramPreview.js +87 -27
  35. package/dist/docx/documentParser.d.ts +2 -1
  36. package/dist/docx/documentParser.js +2 -2
  37. package/dist/docx/drawingUtils.d.ts +8 -1
  38. package/dist/docx/drawingUtils.js +12 -3
  39. package/dist/docx/fieldParser.js +3 -5
  40. package/dist/docx/footnoteParser.d.ts +3 -2
  41. package/dist/docx/footnoteParser.js +19 -4
  42. package/dist/docx/groupDrawingParser.js +4 -4
  43. package/dist/docx/headerFooterRefParser.d.ts +4 -3
  44. package/dist/docx/headerFooterRefParser.js +42 -12
  45. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  46. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  47. package/dist/docx/hyperlinkParser.js +13 -17
  48. package/dist/docx/imageParser.d.ts +10 -2
  49. package/dist/docx/imageParser.js +80 -30
  50. package/dist/docx/imageRawXml.d.ts +14 -1
  51. package/dist/docx/imageRawXml.js +35 -11
  52. package/dist/docx/markupRangeMarker.d.ts +15 -0
  53. package/dist/docx/markupRangeMarker.js +44 -0
  54. package/dist/docx/mathToMathml.js +12 -14
  55. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  56. package/dist/docx/nonVisualDrawingProps.js +46 -0
  57. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  58. package/dist/docx/noteReferenceStyles.js +70 -0
  59. package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
  60. package/dist/docx/numberingReferenceNormalization.js +20 -1
  61. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  62. package/dist/docx/paragraphParser.js +66 -99
  63. package/dist/docx/paragraphPropertySource.js +1 -0
  64. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  65. package/dist/docx/paragraphTraversal.d.ts +37 -1
  66. package/dist/docx/paragraphTraversal.js +84 -1
  67. package/dist/docx/parseContext.d.ts +37 -0
  68. package/dist/docx/parseContext.js +67 -0
  69. package/dist/docx/parseWarningMessage.d.ts +6 -0
  70. package/dist/docx/parseWarningMessage.js +44 -0
  71. package/dist/docx/parser.js +83 -29
  72. package/dist/docx/previewBudget.d.ts +64 -0
  73. package/dist/docx/previewBudget.js +88 -0
  74. package/dist/docx/relsParser.d.ts +28 -11
  75. package/dist/docx/relsParser.js +26 -13
  76. package/dist/docx/revisionIdNormalization.js +96 -10
  77. package/dist/docx/rezip.js +80 -40
  78. package/dist/docx/runConsolidator.js +1 -2
  79. package/dist/docx/runParser.d.ts +8 -1
  80. package/dist/docx/runParser.js +30 -48
  81. package/dist/docx/sdtPropertiesPatch.js +24 -18
  82. package/dist/docx/sectionParser.d.ts +2 -1
  83. package/dist/docx/sectionParser.js +21 -65
  84. package/dist/docx/sectionReferenceHistory.js +2 -2
  85. package/dist/docx/selectiveSave.js +6 -6
  86. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  87. package/dist/docx/serializer/borderSerializer.d.ts +2 -3
  88. package/dist/docx/serializer/borderSerializer.js +13 -12
  89. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  90. package/dist/docx/serializer/commentSerializer.js +82 -72
  91. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  92. package/dist/docx/serializer/documentSerializer.js +6 -16
  93. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  94. package/dist/docx/serializer/headerFooterSerializer.js +10 -5
  95. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  96. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  97. package/dist/docx/serializer/noteSerializer.js +5 -0
  98. package/dist/docx/serializer/numberingSerializer.js +7 -6
  99. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  100. package/dist/docx/serializer/paragraphSerializer.js +47 -52
  101. package/dist/docx/serializer/partNamespaces.js +2 -2
  102. package/dist/docx/serializer/runSerializer.js +57 -31
  103. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  104. package/dist/docx/serializer/settingsSerializer.js +4 -3
  105. package/dist/docx/serializer/stylesSerializer.js +6 -6
  106. package/dist/docx/serializer/tableSerializer.js +37 -21
  107. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  108. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  109. package/dist/docx/serializer/themeSerializer.js +6 -6
  110. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  111. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  112. package/dist/docx/serializer/xmlUtils.js +1 -13
  113. package/dist/docx/server/boundedArchive.d.ts +12 -0
  114. package/dist/docx/server/boundedArchive.js +20 -1
  115. package/dist/docx/server/build.js +8 -1
  116. package/dist/docx/server/createBilingualDocument.js +10 -18
  117. package/dist/docx/server/extractDocxText.js +3 -4
  118. package/dist/docx/server/validateDocxConformance.js +22 -1
  119. package/dist/docx/shadingParser.d.ts +6 -0
  120. package/dist/docx/shadingParser.js +32 -0
  121. package/dist/docx/shapeParser.js +10 -8
  122. package/dist/docx/styleParser.js +13 -87
  123. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  124. package/dist/docx/styleReferenceResolution.js +51 -0
  125. package/dist/docx/tableLook.d.ts +57 -0
  126. package/dist/docx/tableLook.js +63 -0
  127. package/dist/docx/tableParser.d.ts +7 -9
  128. package/dist/docx/tableParser.js +64 -110
  129. package/dist/docx/textBoxParser.js +11 -6
  130. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  131. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  132. package/dist/docx/transitionalSpelling.d.ts +13 -2
  133. package/dist/docx/transitionalSpelling.js +23 -1
  134. package/dist/docx/unzip.d.ts +23 -0
  135. package/dist/docx/unzip.js +32 -22
  136. package/dist/docx/verbatimCapture.js +5 -12
  137. package/dist/docx/vmlImageParser.js +5 -4
  138. package/dist/docx/vmlPreview.d.ts +1 -3
  139. package/dist/docx/vmlPreview.js +2 -30
  140. package/dist/docx/watermarkParser.js +2 -2
  141. package/dist/docx/xmlParser.d.ts +38 -33
  142. package/dist/docx/xmlParser.js +92 -47
  143. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  144. package/dist/docx/xmlResourceLimits.js +105 -24
  145. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  146. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  147. package/dist/internal/paragraphFormattingSerialization.js +29 -8
  148. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  149. package/dist/layout-engine/index.d.ts +2 -2
  150. package/dist/layout-engine/index.js +2 -2
  151. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  152. package/dist/layout-engine/types.d.ts +8 -2
  153. package/dist/layout-engine/types.js +35 -2
  154. package/dist/layout-painter/renderImage.js +4 -3
  155. package/dist/layout-painter/renderParagraph.js +4 -3
  156. package/dist/managers/autoSaveCodec.js +2 -8
  157. package/dist/markdown/images.js +1 -4
  158. package/dist/markdown/index.js +1 -1
  159. package/dist/markdown/internals.d.ts +6 -1
  160. package/dist/markdown/internals.js +14 -1
  161. package/dist/markdown/renderBlock.js +35 -21
  162. package/dist/markdown/renderParagraph.js +14 -5
  163. package/dist/markdown/renderRuns.js +4 -3
  164. package/dist/markdown/renderTable.js +4 -3
  165. package/dist/markdown/trailers.js +41 -7
  166. package/dist/markdown/types.d.ts +3 -7
  167. package/dist/prosemirror/attrs/index.js +71 -5
  168. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  169. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  170. package/dist/prosemirror/commands/image.js +1 -0
  171. package/dist/prosemirror/commands/index.d.ts +3 -3
  172. package/dist/prosemirror/commands/index.js +2 -2
  173. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  174. package/dist/prosemirror/commands/paragraph.js +2 -2
  175. package/dist/prosemirror/commentIdAllocator.js +2 -7
  176. package/dist/prosemirror/conversion/fromProseDoc.js +197 -68
  177. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  178. package/dist/prosemirror/conversion/toProseDoc.js +458 -335
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  180. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  181. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  182. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  183. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  184. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  185. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  186. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  187. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  188. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  189. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  190. package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -1
  191. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -2
  192. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  193. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  194. package/dist/prosemirror/extensions/types.d.ts +2 -2
  195. package/dist/prosemirror/index.d.ts +3 -3
  196. package/dist/prosemirror/index.js +3 -3
  197. package/dist/prosemirror/insertOperations.d.ts +9 -2
  198. package/dist/prosemirror/insertOperations.js +9 -4
  199. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +162 -0
  200. package/dist/prosemirror/paragraphFormattingProvenance.js +115 -0
  201. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  202. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  203. package/dist/prosemirror/plugins/index.d.ts +2 -2
  204. package/dist/prosemirror/plugins/index.js +2 -2
  205. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  206. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  207. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  208. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  209. package/dist/prosemirror/schema/nodes.d.ts +81 -1
  210. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  211. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  212. package/dist/prosemirror/styles/styleResolver.js +12 -0
  213. package/dist/style-engine/styleEngine.d.ts +3 -0
  214. package/dist/style-engine/styleEngine.js +3 -0
  215. package/dist/style-sets/extract.js +1 -23
  216. package/dist/style-sets/stellaStyle.js +46 -39
  217. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  218. package/dist/style-sets/styleSetNormalization.js +99 -0
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/base64.d.ts +36 -0
  221. package/dist/utils/base64.js +40 -0
  222. package/dist/utils/clipboard.js +2 -1
  223. package/dist/utils/createDocument.js +145 -20
  224. package/dist/utils/headingCollector.d.ts +8 -5
  225. package/dist/utils/headingCollector.js +23 -25
  226. package/dist/utils/tableOfContentsStyle.js +9 -2
  227. package/dist/utils/units.d.ts +10 -1
  228. package/dist/utils/units.js +12 -1
  229. package/dist/utils/urlSecurity.d.ts +8 -2
  230. package/dist/utils/urlSecurity.js +21 -3
  231. package/package.json +2 -2
  232. package/dist/docx/textWhitespace.d.ts +0 -4
  233. package/dist/docx/textWhitespace.js +0 -4
  234. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  235. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  236. package/dist/markdown/headings.d.ts +0 -13
  237. 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,5 +1,7 @@
1
- import { resolveRelativePath } from "./relsParser.js";
2
- import { findChildByNamespaceUri, getAttribute, getLocalName, parseNumericAttribute, parseXmlDocument } from "./xmlParser.js";
1
+ import { bytesToDataUrl } from "../utils/base64.js";
2
+ import { PREVIEW_KINDS } from "./previewBudget.js";
3
+ import { resolveRelationshipIdOfType, resolveRelativePath } from "./relsParser.js";
4
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, findChildByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getLocalName, parseNumericAttribute, parseXmlDocument } from "./xmlParser.js";
3
5
  //#region src/docx/diagramPreview.ts
4
6
  const MAX_PREVIEW_SHAPES = 128;
5
7
  const MAX_PREVIEW_PIXELS = 144e4;
@@ -7,21 +9,47 @@ const MAX_PREVIEW_PAINT_PIXELS = MAX_PREVIEW_PIXELS * 4;
7
9
  const DRAWINGML_NAMESPACE_URIS = /* @__PURE__ */ new Set(["http://schemas.openxmlformats.org/drawingml/2006/main", "http://purl.oclc.org/ooxml/drawingml/main"]);
8
10
  const DIAGRAM_NAMESPACE_URIS = /* @__PURE__ */ new Set(["http://schemas.openxmlformats.org/drawingml/2006/diagram", "http://purl.oclc.org/ooxml/drawingml/diagram"]);
9
11
  const DIAGRAM_DRAWING_NAMESPACE_URIS = /* @__PURE__ */ new Set(["http://schemas.microsoft.com/office/drawing/2008/diagram"]);
12
+ const DIAGRAM_DATA_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData";
13
+ const DIAGRAM_DRAWING_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/office/2007/relationships/diagramDrawing";
14
+ const DOCUMENT_PART_PATH = "word/document.xml";
10
15
  const WORD_DRAWING_NAMESPACE_URIS = /* @__PURE__ */ new Set(["http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", "http://purl.oclc.org/ooxml/drawingml/wordprocessingDrawing"]);
16
+ /**
17
+ * The preview is a megapixel raster, so both checksums run over megabytes.
18
+ * `for (const byte of bytes)` drives the array iterator protocol once per
19
+ * byte, which profiles as the dominant cost of parsing a SmartArt document;
20
+ * indexed loops and a table-driven CRC produce the same numbers without it.
21
+ */
22
+ const CRC32_TABLE = (() => {
23
+ const table = /* @__PURE__ */ new Uint32Array(256);
24
+ for (let index = 0; index < 256; index += 1) {
25
+ let value = index;
26
+ for (let bit = 0; bit < 8; bit += 1) value = value >>> 1 ^ (value & 1 ? 3988292384 : 0);
27
+ table[index] = value >>> 0;
28
+ }
29
+ return table;
30
+ })();
11
31
  const crc32 = (bytes) => {
12
32
  let crc = 4294967295;
13
- for (const byte of bytes) {
14
- crc ^= byte;
15
- for (let bit = 0; bit < 8; bit += 1) crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
16
- }
33
+ for (let index = 0; index < bytes.length; index += 1) crc = crc >>> 8 ^ CRC32_TABLE[(crc ^ bytes[index]) & 255];
17
34
  return (crc ^ 4294967295) >>> 0;
18
35
  };
36
+ /**
37
+ * `a` and `b` stay below 2^31 for 5552 iterations from any legal state, so the
38
+ * modulo runs per block rather than per byte.
39
+ */
40
+ const ADLER32_BLOCK = 5552;
19
41
  const adler32 = (bytes) => {
20
42
  let a = 1;
21
43
  let b = 0;
22
- for (const byte of bytes) {
23
- a = (a + byte) % 65521;
24
- b = (b + a) % 65521;
44
+ let index = 0;
45
+ while (index < bytes.length) {
46
+ const end = Math.min(index + ADLER32_BLOCK, bytes.length);
47
+ for (; index < end; index += 1) {
48
+ a += bytes[index];
49
+ b += a;
50
+ }
51
+ a %= 65521;
52
+ b %= 65521;
25
53
  }
26
54
  return b << 16 | a;
27
55
  };
@@ -88,9 +116,8 @@ const previewPng = (width, height, shapes) => {
88
116
  cursor += length;
89
117
  offset += length;
90
118
  }
91
- const output = new Uint8Array(cursor + 4);
92
- output.set(compressed.subarray(0, cursor));
93
- new DataView(output.buffer).setUint32(cursor, adler32(pixels));
119
+ new DataView(compressed.buffer).setUint32(cursor, adler32(pixels));
120
+ const output = compressed;
94
121
  const signature = new Uint8Array([
95
122
  137,
96
123
  80,
@@ -127,13 +154,38 @@ const extent = (drawing) => {
127
154
  height: parseNumericAttribute(value, null, "cy") ?? 0
128
155
  };
129
156
  };
130
- const cachedDiagramShapes = (rels, media) => {
131
- const matches = [...rels.values()].filter((relationship) => relationship.type === "http://schemas.microsoft.com/office/2007/relationships/diagramDrawing");
132
- if (matches.length !== 1 || !matches[0]?.target) return [];
133
- const path = resolveRelativePath("word/document.xml", matches[0].target);
134
- const file = media.get(path);
135
- if (!file?.data) return [];
136
- const root = parseXmlDocument(new TextDecoder().decode(file.data));
157
+ /** Parse the part a relationship of the given type names, by that relationship's id. */
158
+ const partByRelationshipId = ({ rels, media, rId, type }) => {
159
+ const resolved = resolveRelationshipIdOfType(rels, rId ?? void 0, type);
160
+ if (resolved.status !== "resolved" || !resolved.relationship.target) return null;
161
+ const file = media.get(resolveRelativePath(DOCUMENT_PART_PATH, resolved.relationship.target));
162
+ return file?.data ? parseXmlDocument(new TextDecoder().decode(file.data)) : null;
163
+ };
164
+ /**
165
+ * The drawing cache this diagram points at, reached through its own ids.
166
+ *
167
+ * `dgm:relIds/@r:dm` names the data part, and that part's `dsp:dataModelExt`
168
+ * extension names the cached drawing. Scanning the relationship map for a type
169
+ * instead of following the ids reads the wrong diagram whenever a document has
170
+ * more than one, which is why the scan refused outright on a second match:
171
+ * both diagrams in a two-diagram document then got no preview at all.
172
+ */
173
+ const cachedDiagramShapes = (graphicData, rels, media) => {
174
+ const relIds = findChildByNamespaceUri(graphicData, DIAGRAM_NAMESPACE_URIS, "relIds");
175
+ const data = partByRelationshipId({
176
+ rels,
177
+ media,
178
+ rId: getAttributeByNamespaceUri(relIds, OFFICE_RELATIONSHIP_NAMESPACE_URIS, "dm"),
179
+ type: DIAGRAM_DATA_RELATIONSHIP_TYPE
180
+ });
181
+ if (!data) return [];
182
+ const dataModelExt = descendantsByNamespace(data, DIAGRAM_DRAWING_NAMESPACE_URIS, "dataModelExt").at(0);
183
+ const root = partByRelationshipId({
184
+ rels,
185
+ media,
186
+ rId: getAttribute(dataModelExt, null, "relId"),
187
+ type: DIAGRAM_DRAWING_RELATIONSHIP_TYPE
188
+ });
137
189
  if (!root) return [];
138
190
  const shapes = [];
139
191
  for (const shape of descendantsByNamespace(root, DIAGRAM_DRAWING_NAMESPACE_URIS, "sp").slice(0, MAX_PREVIEW_SHAPES)) {
@@ -155,31 +207,39 @@ const cachedDiagramShapes = (rels, media) => {
155
207
  }
156
208
  return shapes;
157
209
  };
210
+ const matchesNamespace = (element, namespaceUris, localName) => element.namespaceUri !== void 0 && namespaceUris.has(element.namespaceUri) && getLocalName(element.name ?? "") === localName;
158
211
  const descendantsByNamespace = (root, namespaceUris, localName) => {
159
212
  const result = [];
160
213
  const visit = (element) => {
161
- if (element.namespaceUri && namespaceUris.has(element.namespaceUri) && getLocalName(element.name ?? "") === localName) result.push(element);
214
+ if (matchesNamespace(element, namespaceUris, localName)) result.push(element);
162
215
  for (const child of element.elements ?? []) if (child.type === "element") visit(child);
163
216
  };
164
217
  visit(root);
165
218
  return result;
166
219
  };
220
+ /** The first match in document order, without walking the rest of the subtree. */
221
+ const firstDescendantByNamespace = (root, namespaceUris, localName) => {
222
+ if (matchesNamespace(root, namespaceUris, localName)) return root;
223
+ for (const child of root.elements ?? []) {
224
+ if (child.type !== "element") continue;
225
+ const found = firstDescendantByNamespace(child, namespaceUris, localName);
226
+ if (found) return found;
227
+ }
228
+ return null;
229
+ };
167
230
  /** Create a deliberately simple, bounded preview; it is never an editable diagram projection. */
168
231
  const parseDiagramPreview = (drawing, rels, media) => {
169
232
  if (!rels || !media) return null;
170
- const graphicData = descendantsByNamespace(drawing, DRAWINGML_NAMESPACE_URIS, "graphicData").at(0);
233
+ const graphicData = firstDescendantByNamespace(drawing, DRAWINGML_NAMESPACE_URIS, "graphicData");
171
234
  if (!graphicData || !DIAGRAM_NAMESPACE_URIS.has(getAttribute(graphicData, null, "uri") ?? "")) return null;
172
235
  const { width, height } = extent(drawing);
173
236
  if (width <= 0 || height <= 0) return null;
174
- const png = previewPng(width, height, cachedDiagramShapes(rels, media));
175
- let binary = "";
176
- for (let offset = 0; offset < png.length; offset += 32768) binary += String.fromCodePoint(...png.subarray(offset, offset + 32768));
177
237
  return {
178
238
  type: "image",
179
239
  rId: "",
180
- src: `data:image/png;base64,${btoa(binary)}`,
181
- mimeType: "image/png",
182
- filename: "smartart-preview.png",
240
+ src: bytesToDataUrl(previewPng(width, height, cachedDiagramShapes(graphicData, rels, media)), PREVIEW_KINDS.smartArt.mimeType),
241
+ mimeType: PREVIEW_KINDS.smartArt.mimeType,
242
+ filename: PREVIEW_KINDS.smartArt.filename,
183
243
  size: {
184
244
  width,
185
245
  height
@@ -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
  }
@@ -1,6 +1,7 @@
1
1
  import { emuToPixels } from "../utils/units.js";
2
2
  import { parseImage, resolveImageData } from "./imageParser.js";
3
3
  import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, getChildElements, getLocalName, getTextContent, parseNumericAttribute } from "./xmlParser.js";
4
+ import { escapeXmlAttribute, escapeXmlText } from "@stll/docx-core";
4
5
  //#region src/docx/groupDrawingParser.ts
5
6
  const HEX_COLOR = /^[0-9A-Fa-f]{6}$/u;
6
7
  const DEFAULT_TEXT_COLOR = "000000";
@@ -12,7 +13,6 @@ const CROP_SCALE = 1e5;
12
13
  const MAX_PATH_COMMANDS = 1e4;
13
14
  const MAX_TEXT_CHARACTERS = 2e4;
14
15
  const MAX_SVG_CHARACTERS = 1e6;
15
- const escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
16
16
  const numericAttr = (element, name) => {
17
17
  const direct = parseNumericAttribute(element, null, name);
18
18
  if (direct !== void 0) return direct;
@@ -108,7 +108,7 @@ const renderTextBox = (wsp) => {
108
108
  const color = colorFrom(findAllDeep(wsp, "w", "color").at(0) ?? null, DEFAULT_TEXT_COLOR);
109
109
  const fontSize = halfPoints * HALF_POINT_TO_EMU;
110
110
  const maxCharacters = Math.max(1, Math.floor(width / (fontSize * .38)));
111
- const lines = paragraphs.flatMap((paragraph) => wrapLine(getTextContent(paragraph).slice(0, MAX_TEXT_CHARACTERS), maxCharacters)).map(escapeXml);
111
+ const lines = paragraphs.flatMap((paragraph) => wrapLine(getTextContent(paragraph).slice(0, MAX_TEXT_CHARACTERS), maxCharacters)).map(escapeXmlText);
112
112
  if (lines.length === 0) return "";
113
113
  const lineHeight = fontSize * 1.15;
114
114
  const svgFontSize = 1e3;
@@ -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;
@@ -131,7 +131,7 @@ const renderPicture = (picture, index, rels, media) => {
131
131
  const visibleWidth = 1 - left - right;
132
132
  const visibleHeight = 1 - top - bottom;
133
133
  if (visibleWidth <= 0 || visibleHeight <= 0) return "";
134
- const image = `<image x="${x - width * left / visibleWidth}" y="${y - height * top / visibleHeight}" width="${width / visibleWidth}" height="${height / visibleHeight}" href="${escapeXml(src)}" preserveAspectRatio="none"/>`;
134
+ const image = `<image x="${x - width * left / visibleWidth}" y="${y - height * top / visibleHeight}" width="${width / visibleWidth}" height="${height / visibleHeight}" href="${escapeXmlAttribute(src)}" preserveAspectRatio="none"/>`;
135
135
  if (left === 0 && top === 0 && right === 0 && bottom === 0) return image;
136
136
  const clipId = `group-picture-${index}`;
137
137
  return `<defs><clipPath id="${clipId}"><rect x="${x}" y="${y}" width="${width}" height="${height}"/></clipPath></defs><g clip-path="url(#${clipId})">${image}</g>`;
@@ -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 };