@stll/folio-core 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/ai-edits/headless.js +6 -5
  2. package/dist/ai-edits/index.d.ts +2 -2
  3. package/dist/ai-edits/index.js +2 -2
  4. package/dist/ai-edits/snapshot.js +13 -9
  5. package/dist/compare/content-alignment.js +16 -1
  6. package/dist/compare/inline-atoms.js +1 -1
  7. package/dist/compare/style-resources.js +6 -0
  8. package/dist/compat/eigenpal.d.ts +2 -2
  9. package/dist/controller/layoutPipeline.d.ts +2 -1
  10. package/dist/controller/layoutPipeline.js +5 -2
  11. package/dist/controller/layoutSession.d.ts +2 -1
  12. package/dist/controller/layoutSession.js +1 -0
  13. package/dist/docx/appVersionNormalization.d.ts +0 -18
  14. package/dist/docx/blockContentParser.js +10 -1
  15. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  16. package/dist/docx/blockRangeMarkers.js +59 -0
  17. package/dist/docx/bookmarkParser.d.ts +2 -20
  18. package/dist/docx/bookmarkParser.js +6 -30
  19. package/dist/docx/borderParser.d.ts +13 -0
  20. package/dist/docx/borderParser.js +71 -0
  21. package/dist/docx/builtInStyles.d.ts +165 -0
  22. package/dist/docx/builtInStyles.js +239 -0
  23. package/dist/docx/commentIdNormalization.d.ts +10 -0
  24. package/dist/docx/commentIdNormalization.js +33 -0
  25. package/dist/docx/commentParser.d.ts +2 -1
  26. package/dist/docx/commentParser.js +34 -7
  27. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  28. package/dist/docx/commentReferenceNormalization.js +23 -14
  29. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  30. package/dist/docx/danglingRelationshipReferences.js +30 -0
  31. package/dist/docx/defaultParagraphStyle.d.ts +39 -0
  32. package/dist/docx/defaultParagraphStyle.js +54 -0
  33. package/dist/docx/documentParser.d.ts +2 -1
  34. package/dist/docx/documentParser.js +2 -2
  35. package/dist/docx/drawingUtils.d.ts +8 -1
  36. package/dist/docx/drawingUtils.js +12 -3
  37. package/dist/docx/fieldParser.js +3 -5
  38. package/dist/docx/footnoteParser.d.ts +3 -2
  39. package/dist/docx/footnoteParser.js +19 -2
  40. package/dist/docx/groupDrawingParser.js +1 -1
  41. package/dist/docx/headerFooterRefParser.d.ts +15 -3
  42. package/dist/docx/headerFooterRefParser.js +51 -14
  43. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  44. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  45. package/dist/docx/hyperlinkParser.js +11 -15
  46. package/dist/docx/imageParser.d.ts +1 -1
  47. package/dist/docx/imageParser.js +22 -18
  48. package/dist/docx/imageRawXml.js +5 -5
  49. package/dist/docx/markupRangeMarker.d.ts +15 -0
  50. package/dist/docx/markupRangeMarker.js +44 -0
  51. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  52. package/dist/docx/noteReferenceStyles.js +70 -0
  53. package/dist/docx/numberingParser.js +2 -1
  54. package/dist/docx/numberingReference.d.ts +21 -0
  55. package/dist/docx/numberingReference.js +21 -0
  56. package/dist/docx/numberingReferenceNormalization.d.ts +14 -2
  57. package/dist/docx/numberingReferenceNormalization.js +51 -9
  58. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  59. package/dist/docx/paragraphParser.js +69 -101
  60. package/dist/docx/paragraphPropertySource.js +1 -0
  61. package/dist/docx/paragraphTraversal.d.ts +37 -1
  62. package/dist/docx/paragraphTraversal.js +84 -1
  63. package/dist/docx/parseContext.d.ts +37 -0
  64. package/dist/docx/parseContext.js +67 -0
  65. package/dist/docx/parseWarningMessage.d.ts +6 -0
  66. package/dist/docx/parseWarningMessage.js +44 -0
  67. package/dist/docx/parser.js +86 -24
  68. package/dist/docx/relsParser.d.ts +28 -11
  69. package/dist/docx/relsParser.js +26 -13
  70. package/dist/docx/revisionIdNormalization.js +81 -7
  71. package/dist/docx/rezip.js +85 -27
  72. package/dist/docx/runConsolidator.js +1 -2
  73. package/dist/docx/runParser.d.ts +8 -1
  74. package/dist/docx/runParser.js +30 -48
  75. package/dist/docx/sectionParser.d.ts +2 -1
  76. package/dist/docx/sectionParser.js +21 -65
  77. package/dist/docx/serializer/borderSerializer.d.ts +1 -2
  78. package/dist/docx/serializer/commentSerializer.js +22 -9
  79. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  80. package/dist/docx/serializer/documentSerializer.js +6 -16
  81. package/dist/docx/serializer/headerFooterSerializer.js +5 -0
  82. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  83. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  84. package/dist/docx/serializer/noteSerializer.js +5 -0
  85. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  86. package/dist/docx/serializer/paragraphSerializer.js +29 -35
  87. package/dist/docx/serializer/runSerializer.js +13 -7
  88. package/dist/docx/serializer/tableSerializer.js +28 -13
  89. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  90. package/dist/docx/server/build.js +8 -1
  91. package/dist/docx/server/createBilingualDocument.js +15 -22
  92. package/dist/docx/server/extractDocxText.js +3 -4
  93. package/dist/docx/shadingParser.d.ts +6 -0
  94. package/dist/docx/shadingParser.js +32 -0
  95. package/dist/docx/shapeParser.js +3 -3
  96. package/dist/docx/styleParser.js +15 -89
  97. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  98. package/dist/docx/styleReferenceResolution.js +51 -0
  99. package/dist/docx/tableLook.d.ts +57 -0
  100. package/dist/docx/tableLook.js +63 -0
  101. package/dist/docx/tableParser.d.ts +7 -9
  102. package/dist/docx/tableParser.js +64 -110
  103. package/dist/docx/textBoxParser.js +4 -4
  104. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  105. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  106. package/dist/docx/transitionalSpelling.d.ts +13 -2
  107. package/dist/docx/transitionalSpelling.js +23 -1
  108. package/dist/docx/verbatimCapture.js +4 -11
  109. package/dist/docx/vmlImageParser.js +2 -2
  110. package/dist/docx/watermarkParser.js +2 -2
  111. package/dist/docx/xmlParser.d.ts +22 -32
  112. package/dist/docx/xmlParser.js +36 -21
  113. package/dist/index.d.ts +2 -2
  114. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +2 -0
  115. package/dist/internal/pageBreakRunSourceDescendantIndex.js +9 -6
  116. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  117. package/dist/internal/paragraphFormattingSerialization.js +26 -6
  118. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  119. package/dist/layout-bridge/convert/templatePreviewFlow.d.ts +19 -11
  120. package/dist/layout-bridge/convert/templatePreviewFlow.js +103 -38
  121. package/dist/layout-bridge/convert/toFlowBlocks.js +12 -3
  122. package/dist/layout-engine/index.d.ts +2 -2
  123. package/dist/layout-engine/index.js +2 -2
  124. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  125. package/dist/layout-engine/types.d.ts +8 -2
  126. package/dist/layout-engine/types.js +35 -2
  127. package/dist/markdown/index.js +1 -1
  128. package/dist/markdown/internals.d.ts +6 -1
  129. package/dist/markdown/internals.js +14 -1
  130. package/dist/markdown/renderBlock.js +35 -21
  131. package/dist/markdown/renderParagraph.js +14 -5
  132. package/dist/markdown/renderRuns.js +4 -3
  133. package/dist/markdown/renderTable.js +4 -3
  134. package/dist/markdown/trailers.js +41 -7
  135. package/dist/markdown/types.d.ts +3 -7
  136. package/dist/prosemirror/attrs/index.js +2 -5
  137. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  138. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  139. package/dist/prosemirror/commands/index.d.ts +3 -3
  140. package/dist/prosemirror/commands/index.js +2 -2
  141. package/dist/prosemirror/commands/pageBreak.js +12 -1
  142. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  143. package/dist/prosemirror/commands/paragraph.js +2 -2
  144. package/dist/prosemirror/commentIdAllocator.js +2 -7
  145. package/dist/prosemirror/conversion/fromProseDoc.js +131 -41
  146. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  147. package/dist/prosemirror/conversion/toProseDoc.js +402 -328
  148. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  149. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  150. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  151. package/dist/prosemirror/extensions/features/ListExtension.js +42 -4
  152. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  153. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  154. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  155. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  156. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  157. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  158. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  159. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  160. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  161. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  162. package/dist/prosemirror/extensions/types.d.ts +2 -2
  163. package/dist/prosemirror/index.d.ts +3 -3
  164. package/dist/prosemirror/index.js +3 -3
  165. package/dist/prosemirror/insertOperations.d.ts +9 -2
  166. package/dist/prosemirror/insertOperations.js +9 -4
  167. package/dist/prosemirror/listMarker.js +2 -1
  168. package/dist/prosemirror/numberedRefFields.js +2 -1
  169. package/dist/prosemirror/pageBreakRunProjection.d.ts +11 -3
  170. package/dist/prosemirror/pageBreakRunProjection.js +16 -8
  171. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +159 -0
  172. package/dist/prosemirror/paragraphFormattingProvenance.js +106 -0
  173. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  174. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  175. package/dist/prosemirror/plugins/index.d.ts +2 -2
  176. package/dist/prosemirror/plugins/index.js +2 -2
  177. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  178. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  179. package/dist/prosemirror/plugins/templatePreviewValues.d.ts +42 -1
  180. package/dist/prosemirror/plugins/templatePreviewValues.js +217 -14
  181. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  182. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  183. package/dist/prosemirror/schema/nodes.d.ts +31 -0
  184. package/dist/prosemirror/styles/resolvedStyleAttrs.js +4 -1
  185. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  186. package/dist/prosemirror/styles/styleResolver.js +15 -6
  187. package/dist/prosemirror/utils/visualLineNavigation.d.ts +22 -2
  188. package/dist/prosemirror/utils/visualLineNavigation.js +80 -63
  189. package/dist/style-engine/styleEngine.d.ts +4 -1
  190. package/dist/style-engine/styleEngine.js +3 -0
  191. package/dist/style-sets/extract.js +35 -11
  192. package/dist/style-sets/stellaStyle.js +46 -39
  193. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  194. package/dist/style-sets/styleSetNormalization.js +99 -0
  195. package/dist/types/content.d.ts +2 -2
  196. package/dist/utils/createDocument.js +145 -20
  197. package/dist/utils/headingCollector.d.ts +8 -5
  198. package/dist/utils/headingCollector.js +23 -25
  199. package/dist/utils/tableOfContentsStyle.js +9 -2
  200. package/package.json +3 -2
  201. package/dist/docx/textWhitespace.d.ts +0 -4
  202. package/dist/docx/textWhitespace.js +0 -4
  203. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  204. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  205. package/dist/markdown/headings.d.ts +0 -13
  206. package/dist/markdown/headings.js +0 -20
@@ -1,34 +1,39 @@
1
1
  import { toArrayBuffer } from "../utils/docxInput.js";
2
2
  import { loadFontsWithMapping } from "../utils/fontLoader.js";
3
3
  import { MAX_PACKAGE_TIFF_PIXELS, convertTiffToPngDataUrl, isTiffMimeType } from "../utils/tiffConverter.js";
4
+ import { DUPLICATE_COMMENT_ID_WARNING, normalizeCommentIds } from "./commentIdNormalization.js";
4
5
  import { parseComments } from "./commentParser.js";
5
- import { normalizeCommentReferences } from "./commentReferenceNormalization.js";
6
+ import { DANGLING_COMMENT_REFERENCE_WARNING, UNBALANCED_COMMENT_RANGE_WARNING, normalizeCommentReferences } from "./commentReferenceNormalization.js";
6
7
  import { detectDocxConformanceClass } from "./conformance.js";
7
8
  import { parseCoreProperties } from "./corePropertiesParser.js";
9
+ import { countDanglingRelationshipReferences } from "./danglingRelationshipReferences.js";
8
10
  import { extractAllTemplateVariables, parseDocumentBody } from "./documentParser.js";
9
11
  import { normalizeDrawingIds } from "./drawingIdNormalization.js";
10
12
  import { DocxEncryptionError } from "./encryption/errors.js";
11
13
  import { parseFontTable } from "./fontTableParser.js";
12
14
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
13
15
  import { parseFooter, parseHeader } from "./headerFooterParser.js";
14
- import { normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
16
+ import { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
15
17
  import { assignHeaderFooterVerbatimXml, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
16
18
  import { extractMetafileRaster, isMetafileMimeType } from "./metafileRaster.js";
17
19
  import { renderEmfSvg } from "./metafileSvg.js";
18
20
  import { DocxModelValidationError, formatDocumentModelIssues, validateFolioDocumentModel } from "./modelValidation.js";
19
21
  import { parseNumbering } from "./numberingParser.js";
20
- import { normalizeNumberingReferences } from "./numberingReferenceNormalization.js";
22
+ import { UNNUMBERED_PARAGRAPH_WARNING, UNNUMBERED_STYLE_WARNING, normalizeNumberingReferences, normalizeStyleNumberingReferences } from "./numberingReferenceNormalization.js";
21
23
  import { assignDocumentParagraphPropertySourceContract } from "./paragraphPropertySource.js";
24
+ import { createParseWarningCollector } from "./parseContext.js";
25
+ import { formatParseWarnings } from "./parseWarningMessage.js";
22
26
  import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
23
27
  import { normalizeRenderedPageBreakHints } from "./renderedPageBreakNormalization.js";
24
28
  import { parseSettings } from "./settingsParser.js";
25
29
  import { parseStylesPackage } from "./styleParser.js";
26
30
  import { applyThemeFontLang, parseTheme } from "./themeParser.js";
27
- import { normalizeTrackedMoveRanges } from "./trackedMoveRangeNormalization.js";
31
+ import { UNBALANCED_MOVE_RANGE_WARNING, normalizeTrackedMoveRanges } from "./trackedMoveRangeNormalization.js";
28
32
  import { getMediaMimeType, mediaToDataUrl, unzipDocx } from "./unzip.js";
29
33
  import { enforcePackageVmlPreviewBudget } from "./vmlPreview.js";
30
34
  import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
31
35
  import { TaggedError } from "better-result";
36
+ import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
32
37
  //#region src/docx/parser.ts
33
38
  /**
34
39
  * Main Parser Orchestrator - Unified parseDocx function
@@ -69,7 +74,7 @@ const sha256Hex = async (buffer) => {
69
74
  async function parseDocx(input, options = {}) {
70
75
  const buffer = input instanceof ArrayBuffer ? input : await toArrayBuffer(input);
71
76
  const { onProgress = () => {}, preloadFonts = true, parseHeadersFooters = true, parseNotes = true, detectVariables = true, password, unzipLimits, mediaResolver } = options;
72
- const warnings = [];
77
+ const { context: parseContext, warnings: collectedWarnings } = createParseWarningCollector();
73
78
  try {
74
79
  const timeStage = (_name, fn) => fn();
75
80
  const timeStageAsync = async (_name, fn) => await fn();
@@ -80,8 +85,11 @@ async function parseDocx(input, options = {}) {
80
85
  password,
81
86
  extractAllXml: false
82
87
  }));
83
- if (raw.wasEncrypted) warnings.push("Document was opened from password-protected storage; saving writes an unencrypted .docx file.");
84
- warnings.push(...raw.warnings);
88
+ if (raw.wasEncrypted) parseContext.warn({ code: PARSE_WARNING_CODES.packageDecrypted });
89
+ for (const message of raw.warnings) parseContext.warn({
90
+ code: PARSE_WARNING_CODES.packageArchive,
91
+ detail: message
92
+ });
85
93
  onProgress("Extracted DOCX", 10);
86
94
  onProgress("Parsing relationships...", 10);
87
95
  const rels = timeStage("relationships", () => raw.documentRels ? parseRelationships(raw.documentRels) : /* @__PURE__ */ new Map());
@@ -113,8 +121,8 @@ async function parseDocx(input, options = {}) {
113
121
  onProgress("Parsing document body...", 40);
114
122
  let documentBody = { content: [] };
115
123
  timeStage("documentBody", () => {
116
- if (raw.documentXml) documentBody = parseDocumentBody(raw.documentXml, styles, theme, numbering, rels, media);
117
- else warnings.push("No document.xml found in DOCX");
124
+ if (raw.documentXml) documentBody = parseDocumentBody(raw.documentXml, styles, theme, numbering, rels, media, parseContext.scoped({ part: "word/document.xml" }));
125
+ else parseContext.warn({ code: PARSE_WARNING_CODES.documentPartMissing });
118
126
  });
119
127
  onProgress("Parsed document body", 55);
120
128
  let headers;
@@ -130,13 +138,19 @@ async function parseDocx(input, options = {}) {
130
138
  let endnotes;
131
139
  if (parseNotes) {
132
140
  onProgress("Parsing footnotes/endnotes...", 65);
133
- const notes = timeStage("footnotesEndnotes", () => parseNotesContent(raw, styles, theme, numbering, rels, media));
141
+ const notes = timeStage("footnotesEndnotes", () => parseNotesContent(raw, styles, theme, numbering, rels, media, parseContext));
134
142
  footnotes = notes.footnotes;
135
143
  endnotes = notes.endnotes;
136
144
  onProgress("Parsed footnotes/endnotes", 75);
137
145
  } else onProgress("Skipping footnotes/endnotes", 75);
138
146
  onProgress("Parsing comments...", 75);
139
- const comments = timeStage("comments", () => parseComments(raw.commentsXml, styles, theme, rels, media, raw.commentsExtensibleXml, raw.commentsExtendedXml));
147
+ const commentsContext = parseContext.scoped({ part: "word/comments.xml" });
148
+ const comments = timeStage("comments", () => parseComments(raw.commentsXml, styles, theme, rels, media, raw.commentsExtensibleXml, raw.commentsExtendedXml, commentsContext));
149
+ const commentIdNormalization = normalizeCommentIds(comments);
150
+ if (commentIdNormalization.droppedDuplicateComments > 0) commentsContext.warn({
151
+ code: DUPLICATE_COMMENT_ID_WARNING,
152
+ count: commentIdNormalization.droppedDuplicateComments
153
+ });
140
154
  if (comments.length > 0) documentBody.comments = comments;
141
155
  normalizeDrawingIds({
142
156
  documentBody,
@@ -162,15 +176,27 @@ async function parseDocx(input, options = {}) {
162
176
  ...footnotes !== void 0 ? { footnotes } : {},
163
177
  ...endnotes !== void 0 ? { endnotes } : {}
164
178
  });
165
- if (commentReferenceNormalization.removedDanglingReferences > 0) warnings.push(`Removed ${commentReferenceNormalization.removedDanglingReferences} dangling comment reference marker(s) whose comments.xml entries are missing.`);
166
- if (commentReferenceNormalization.reanchoredUnbalancedRanges > 0) warnings.push(`Re-anchored ${commentReferenceNormalization.reanchoredUnbalancedRanges} unbalanced comment range marker(s) as point comments.`);
179
+ if (commentReferenceNormalization.removedDanglingReferences > 0) parseContext.warn({
180
+ code: DANGLING_COMMENT_REFERENCE_WARNING,
181
+ count: commentReferenceNormalization.removedDanglingReferences
182
+ });
183
+ if (commentReferenceNormalization.reanchoredUnbalancedRanges > 0) parseContext.warn({
184
+ code: UNBALANCED_COMMENT_RANGE_WARNING,
185
+ count: commentReferenceNormalization.reanchoredUnbalancedRanges
186
+ });
167
187
  const headerFooterReferenceNormalization = normalizeHeaderFooterReferences({
168
188
  documentBody,
169
189
  ...headers !== void 0 ? { headers } : {},
170
190
  ...footers !== void 0 ? { footers } : {}
171
191
  });
172
- if (headerFooterReferenceNormalization.removedDanglingHeaderReferences > 0) warnings.push(`Removed ${headerFooterReferenceNormalization.removedDanglingHeaderReferences} dangling header reference(s) whose header parts are missing.`);
173
- if (headerFooterReferenceNormalization.removedDanglingFooterReferences > 0) warnings.push(`Removed ${headerFooterReferenceNormalization.removedDanglingFooterReferences} dangling footer reference(s) whose footer parts are missing.`);
192
+ if (headerFooterReferenceNormalization.removedDanglingHeaderReferences > 0) parseContext.warn({
193
+ code: DANGLING_HEADER_REFERENCE_WARNING,
194
+ count: headerFooterReferenceNormalization.removedDanglingHeaderReferences
195
+ });
196
+ if (headerFooterReferenceNormalization.removedDanglingFooterReferences > 0) parseContext.warn({
197
+ code: DANGLING_FOOTER_REFERENCE_WARNING,
198
+ count: headerFooterReferenceNormalization.removedDanglingFooterReferences
199
+ });
174
200
  const numberingReferenceNormalization = normalizeNumberingReferences({
175
201
  documentBody,
176
202
  numbering,
@@ -179,7 +205,33 @@ async function parseDocx(input, options = {}) {
179
205
  ...footnotes !== void 0 ? { footnotes } : {},
180
206
  ...endnotes !== void 0 ? { endnotes } : {}
181
207
  });
182
- if (numberingReferenceNormalization.removedMissingNumberingReferences > 0) warnings.push(`Removed ${numberingReferenceNormalization.removedMissingNumberingReferences} numbering reference(s) whose numbering definitions are missing.`);
208
+ if (numberingReferenceNormalization.unnumberedDanglingReferences > 0) parseContext.warn({
209
+ code: UNNUMBERED_PARAGRAPH_WARNING,
210
+ count: numberingReferenceNormalization.unnumberedDanglingReferences
211
+ });
212
+ const styleNumberingNormalization = normalizeStyleNumberingReferences({
213
+ styles: styleDefinitions?.styles ?? [],
214
+ numbering
215
+ });
216
+ for (const styleId of styleNumberingNormalization.unnumberedStyleIds) parseContext.warn({
217
+ code: UNNUMBERED_STYLE_WARNING,
218
+ value: styleId,
219
+ at: `style "${styleId}"`
220
+ });
221
+ const danglingReferences = countDanglingRelationshipReferences({
222
+ content: documentBody.content,
223
+ relationships: rels
224
+ });
225
+ if (danglingReferences.drawings > 0) parseContext.warn({
226
+ code: PARSE_WARNING_CODES.danglingRelationshipId,
227
+ element: "w:drawing",
228
+ count: danglingReferences.drawings
229
+ });
230
+ if (danglingReferences.hyperlinks > 0) parseContext.warn({
231
+ code: PARSE_WARNING_CODES.danglingRelationshipId,
232
+ element: "w:hyperlink",
233
+ count: danglingReferences.hyperlinks
234
+ });
183
235
  const trackedMoveRangeNormalization = normalizeTrackedMoveRanges({
184
236
  documentBody,
185
237
  ...headers !== void 0 ? { headers } : {},
@@ -187,7 +239,10 @@ async function parseDocx(input, options = {}) {
187
239
  ...footnotes !== void 0 ? { footnotes } : {},
188
240
  ...endnotes !== void 0 ? { endnotes } : {}
189
241
  });
190
- if (trackedMoveRangeNormalization.removedUnbalancedMoveRangeMarkers > 0) warnings.push(`Removed ${trackedMoveRangeNormalization.removedUnbalancedMoveRangeMarkers} unbalanced tracked move range marker(s).`);
242
+ if (trackedMoveRangeNormalization.removedUnbalancedMoveRangeMarkers > 0) parseContext.warn({
243
+ code: UNBALANCED_MOVE_RANGE_WARNING,
244
+ count: trackedMoveRangeNormalization.removedUnbalancedMoveRangeMarkers
245
+ });
191
246
  let templateVariables;
192
247
  if (detectVariables) {
193
248
  onProgress("Detecting template variables...", 75);
@@ -230,8 +285,15 @@ async function parseDocx(input, options = {}) {
230
285
  const validation = validateFolioDocumentModel(document);
231
286
  const parsedCompleteModel = parseHeadersFooters && parseNotes;
232
287
  if (!validation.valid && parsedCompleteModel) throw new DocxModelValidationError("Parsed DOCX produced an invalid document model", validation.issues);
233
- warnings.push(...formatDocumentModelIssues(validation.issues));
234
- if (warnings.length > 0) document.warnings = warnings;
288
+ for (const issue of formatDocumentModelIssues(validation.issues)) parseContext.warn({
289
+ code: PARSE_WARNING_CODES.documentModelIssue,
290
+ detail: issue
291
+ });
292
+ const parseWarnings = collectedWarnings();
293
+ if (parseWarnings.length > 0) {
294
+ document.parseWarnings = parseWarnings;
295
+ document.warnings = formatParseWarnings(parseWarnings);
296
+ }
235
297
  onProgress("Complete", 100);
236
298
  return document;
237
299
  } catch (error) {
@@ -410,7 +472,7 @@ function parseHeadersAndFooters(raw, styles, theme, numbering, rels, media) {
410
472
  if (headerXml) {
411
473
  const headerRelsPath = getRelationshipsPathForPart(partPath);
412
474
  const headerRelsXml = getMapCaseInsensitive(raw.allXml, headerRelsPath);
413
- const headerRels = headerRelsXml ? parseRelationships(headerRelsXml) : rels;
475
+ const headerRels = headerRelsXml ? parseRelationships(headerRelsXml) : /* @__PURE__ */ new Map();
414
476
  const header = parseHeader(headerXml, "default", styles, theme, numbering, headerRels, media);
415
477
  const watermark = header.watermark;
416
478
  if (watermark?.kind === "picture") {
@@ -429,7 +491,7 @@ function parseHeadersAndFooters(raw, styles, theme, numbering, rels, media) {
429
491
  if (footerXml) {
430
492
  const footerRelsPath = getRelationshipsPathForPart(partPath);
431
493
  const footerRelsXml = getMapCaseInsensitive(raw.allXml, footerRelsPath);
432
- const footer = parseFooter(footerXml, "default", styles, theme, numbering, footerRelsXml ? parseRelationships(footerRelsXml) : rels, media);
494
+ const footer = parseFooter(footerXml, "default", styles, theme, numbering, footerRelsXml ? parseRelationships(footerRelsXml) : /* @__PURE__ */ new Map(), media);
433
495
  footers.set(rId, footer);
434
496
  }
435
497
  }
@@ -441,13 +503,13 @@ function parseHeadersAndFooters(raw, styles, theme, numbering, rels, media) {
441
503
  /**
442
504
  * Parse footnotes and endnotes from raw content
443
505
  */
444
- function parseNotesContent(raw, styles, theme, numbering, rels, media) {
506
+ function parseNotesContent(raw, styles, theme, numbering, rels, media, context) {
445
507
  const relsForNotePart = (partPath) => {
446
508
  const xml = getMapCaseInsensitive(raw.allXml, getRelationshipsPathForPart(partPath));
447
509
  return xml ? parseRelationships(xml) : rels;
448
510
  };
449
- const footnoteMap = parseFootnotes(raw.footnotesXml, styles, theme, numbering, relsForNotePart("word/footnotes.xml"), media);
450
- const endnoteMap = parseEndnotes(raw.endnotesXml, styles, theme, numbering, relsForNotePart("word/endnotes.xml"), media);
511
+ const footnoteMap = parseFootnotes(raw.footnotesXml, styles, theme, numbering, relsForNotePart("word/footnotes.xml"), media, context?.scoped({ part: "word/footnotes.xml" }));
512
+ const endnoteMap = parseEndnotes(raw.endnotesXml, styles, theme, numbering, relsForNotePart("word/endnotes.xml"), media, context?.scoped({ part: "word/endnotes.xml" }));
451
513
  return {
452
514
  footnotes: footnoteMap.getNormalFootnotes(),
453
515
  endnotes: endnoteMap.getNormalEndnotes()
@@ -107,21 +107,38 @@ declare function getHeaders(map: document_d_exports.RelationshipMap): document_d
107
107
  */
108
108
  declare function getFooters(map: document_d_exports.RelationshipMap): document_d_exports.Relationship[];
109
109
  /**
110
- * Resolve a relationship ID to a target path
110
+ * What a relationship id names.
111
+ *
112
+ * The three cases are kept apart because collapsing any of them into a string
113
+ * turns absence into a lookup key: an id the author never wrote, an id whose
114
+ * target the package no longer holds, and an id that resolves are different
115
+ * facts, and only the last one may be read as a part.
116
+ */
117
+ type RelationshipResolution = {
118
+ status: "resolved";
119
+ relationship: document_d_exports.Relationship;
120
+ } | {
121
+ status: "absent";
122
+ } | {
123
+ status: "dangling";
124
+ id: string;
125
+ };
126
+ /**
127
+ * Resolve a relationship id against a relationship map.
111
128
  *
112
- * @param map - RelationshipMap to search
113
- * @param rId - Relationship ID (e.g., "rId1")
114
- * @returns Target path or undefined if not found
129
+ * The only sanctioned way to turn an `r:id`, `r:embed` or `r:link` into a part.
130
+ * An empty attribute is absence, not an id: `ST_RelationshipId` is an NCName,
131
+ * so `""` can never name a relationship, and no map can hold it as a key.
115
132
  */
116
- declare function resolveTarget(map: document_d_exports.RelationshipMap, rId: string): string | undefined;
133
+ declare function resolveRelationshipId(map: document_d_exports.RelationshipMap | null | undefined, rId: string | undefined): RelationshipResolution;
117
134
  /**
118
- * Resolve a relationship ID to a full relationship
135
+ * Resolve a relationship id and require it to name a relationship of one type.
119
136
  *
120
- * @param map - RelationshipMap to search
121
- * @param rId - Relationship ID (e.g., "rId1")
122
- * @returns Relationship or undefined if not found
137
+ * A reference of the wrong type is reported as dangling: the part it names
138
+ * exists, but not as the thing the reference asked for, and reading it anyway
139
+ * is how a missing image comes back as `styles.xml`.
123
140
  */
124
- declare function resolveRelationship(map: document_d_exports.RelationshipMap, rId: string): document_d_exports.Relationship | undefined;
141
+ declare function resolveRelationshipIdOfType(map: document_d_exports.RelationshipMap | null | undefined, rId: string | undefined, type: document_d_exports.RelationshipType): RelationshipResolution;
125
142
  /**
126
143
  * Resolve a relative target path to an absolute path within the DOCX
127
144
  *
@@ -158,4 +175,4 @@ declare function parsePackageRelationships(relsXml: string): document_d_exports.
158
175
  */
159
176
  declare function formatRelationships(map: document_d_exports.RelationshipMap): string;
160
177
  //#endregion
161
- export { RELATIONSHIP_TYPES, filterByType, formatRelationships, getFooters, getHeaders, getHyperlinks, getImages, getRelationshipTypeName, isExternalHyperlink, isFooterRelationship, isHeaderRelationship, isImageRelationship, parseDocumentRelationships, parsePackageRelationships, parseRelationships, resolveRelationship, resolveRelativePath, resolveTarget };
178
+ export { RELATIONSHIP_TYPES, RelationshipResolution, filterByType, formatRelationships, getFooters, getHeaders, getHyperlinks, getImages, getRelationshipTypeName, isExternalHyperlink, isFooterRelationship, isHeaderRelationship, isImageRelationship, parseDocumentRelationships, parsePackageRelationships, parseRelationships, resolveRelationshipId, resolveRelationshipIdOfType, resolveRelativePath };
@@ -155,24 +155,37 @@ function getFooters(map) {
155
155
  return filterByType(map, RELATIONSHIP_TYPES.footer);
156
156
  }
157
157
  /**
158
- * Resolve a relationship ID to a target path
158
+ * Resolve a relationship id against a relationship map.
159
159
  *
160
- * @param map - RelationshipMap to search
161
- * @param rId - Relationship ID (e.g., "rId1")
162
- * @returns Target path or undefined if not found
160
+ * The only sanctioned way to turn an `r:id`, `r:embed` or `r:link` into a part.
161
+ * An empty attribute is absence, not an id: `ST_RelationshipId` is an NCName,
162
+ * so `""` can never name a relationship, and no map can hold it as a key.
163
163
  */
164
- function resolveTarget(map, rId) {
165
- return map.get(rId)?.target;
164
+ function resolveRelationshipId(map, rId) {
165
+ if (rId === void 0 || rId.length === 0) return { status: "absent" };
166
+ const relationship = map?.get(rId);
167
+ return relationship === void 0 ? {
168
+ status: "dangling",
169
+ id: rId
170
+ } : {
171
+ status: "resolved",
172
+ relationship
173
+ };
166
174
  }
167
175
  /**
168
- * Resolve a relationship ID to a full relationship
176
+ * Resolve a relationship id and require it to name a relationship of one type.
169
177
  *
170
- * @param map - RelationshipMap to search
171
- * @param rId - Relationship ID (e.g., "rId1")
172
- * @returns Relationship or undefined if not found
178
+ * A reference of the wrong type is reported as dangling: the part it names
179
+ * exists, but not as the thing the reference asked for, and reading it anyway
180
+ * is how a missing image comes back as `styles.xml`.
173
181
  */
174
- function resolveRelationship(map, rId) {
175
- return map.get(rId);
182
+ function resolveRelationshipIdOfType(map, rId, type) {
183
+ const resolved = resolveRelationshipId(map, rId);
184
+ if (resolved.status !== "resolved" || resolved.relationship.type === type) return resolved;
185
+ return {
186
+ status: "dangling",
187
+ id: resolved.relationship.id
188
+ };
176
189
  }
177
190
  /**
178
191
  * Resolve a relative target path to an absolute path within the DOCX
@@ -232,4 +245,4 @@ function formatRelationships(map) {
232
245
  return lines.join("\n");
233
246
  }
234
247
  //#endregion
235
- export { RELATIONSHIP_TYPES, filterByType, formatRelationships, getFooters, getHeaders, getHyperlinks, getImages, getRelationshipTypeName, isExternalHyperlink, isFooterRelationship, isHeaderRelationship, isImageRelationship, parseDocumentRelationships, parsePackageRelationships, parseRelationships, resolveRelationship, resolveRelativePath, resolveTarget };
248
+ export { RELATIONSHIP_TYPES, filterByType, formatRelationships, getFooters, getHeaders, getHyperlinks, getImages, getRelationshipTypeName, isExternalHyperlink, isFooterRelationship, isHeaderRelationship, isImageRelationship, parseDocumentRelationships, parsePackageRelationships, parseRelationships, resolveRelationshipId, resolveRelationshipIdOfType, resolveRelativePath };
@@ -31,16 +31,79 @@ const REVISION_ELEMENT_NAMES = /* @__PURE__ */ new Set([
31
31
  "trPrChange"
32
32
  ]);
33
33
  const REVISION_ELEMENT_CANDIDATE = new RegExp(`<(?:[^\\s<>/:]+:)?(?:${[...REVISION_ELEMENT_NAMES].join("|")})(?:[\\s/>])`, "u");
34
- const revisionAttribute = (element) => {
35
- if (!element.name || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "") || !REVISION_ELEMENT_NAMES.has(getLocalName(element.name))) return null;
34
+ /**
35
+ * The rest of the annotation id space.
36
+ *
37
+ * A comment, a bookmark, a protected range and a tracked change all draw their
38
+ * `w:id` from one space: Word allocates from a single counter, which is why a
39
+ * package carrying several kinds almost never repeats a value across them. So
40
+ * an id this pass mints must avoid these as well, or a renumbered `w:ins`
41
+ * lands on a live comment.
42
+ *
43
+ * They are only ever reserved, never claimed. A comment id legitimately
44
+ * appears four times (`w:comment`, both range markers and the reference) and a
45
+ * bookmark id twice, so feeding them to the uniqueness machinery would reject
46
+ * a package Word wrote. Their pairing is also why they are not revision
47
+ * elements: renumbering one end of a range would unpair it.
48
+ */
49
+ const ANNOTATION_ELEMENT_NAMES = /* @__PURE__ */ new Set([
50
+ "bookmarkEnd",
51
+ "bookmarkStart",
52
+ "comment",
53
+ "commentRangeEnd",
54
+ "commentRangeStart",
55
+ "commentReference",
56
+ "customXmlDelRangeEnd",
57
+ "customXmlDelRangeStart",
58
+ "customXmlInsRangeEnd",
59
+ "customXmlInsRangeStart",
60
+ "customXmlMoveFromRangeEnd",
61
+ "customXmlMoveFromRangeStart",
62
+ "customXmlMoveToRangeEnd",
63
+ "customXmlMoveToRangeStart",
64
+ "moveFromRangeEnd",
65
+ "moveFromRangeStart",
66
+ "moveToRangeEnd",
67
+ "moveToRangeStart",
68
+ "permEnd",
69
+ "permStart"
70
+ ]);
71
+ const ANNOTATION_ELEMENT_CANDIDATE = new RegExp(`<(?:[^\\s<>/:]+:)?(?:${[...ANNOTATION_ELEMENT_NAMES].join("|")})(?:[\\s/>])`, "u");
72
+ const ID_KINDS = {
73
+ revision: "revision",
74
+ annotation: "annotation"
75
+ };
76
+ /** One lookup for both halves of the space, so an element is classified once. */
77
+ const ID_KIND_BY_ELEMENT_NAME = new Map([...[...REVISION_ELEMENT_NAMES].map((name) => [name, ID_KINDS.revision]), ...[...ANNOTATION_ELEMENT_NAMES].map((name) => [name, ID_KINDS.annotation])]);
78
+ /**
79
+ * An element's `w:id` and which half of the annotation space it belongs to.
80
+ *
81
+ * One classifier rather than two, because it runs on every element of every
82
+ * scanned part: resolving the namespace and the local name twice to ask two
83
+ * questions measured 18% on a 17.6 MiB package.
84
+ *
85
+ * `w:permStart` types its id as a string, so a protected range named
86
+ * `everyone` yields nothing. That is correct: a value the allocator can never
87
+ * mint is not one it has to avoid.
88
+ */
89
+ const identifiedElement = (element) => {
90
+ if (!element.name || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "")) return null;
91
+ const localName = getLocalName(element.name);
92
+ const kind = ID_KIND_BY_ELEMENT_NAME.get(localName);
93
+ if (kind === void 0) return null;
36
94
  const attribute = findAttributeByNamespaceUri(element, WORDPROCESSINGML_NAMESPACE_URIS, "id");
37
95
  if (!attribute) return null;
38
96
  const id = Number(attribute.value);
39
97
  return Number.isSafeInteger(id) && id >= 0 ? {
98
+ kind,
40
99
  name: attribute.name,
41
100
  id
42
101
  } : null;
43
102
  };
103
+ const revisionAttribute = (element) => {
104
+ const identified = identifiedElement(element);
105
+ return identified?.kind === ID_KINDS.revision ? identified : null;
106
+ };
44
107
  /**
45
108
  * Keep physical tracked-change element ids unique across a package.
46
109
  *
@@ -57,11 +120,10 @@ const normalizeRevisionIdsInXmlParts = (parts) => {
57
120
  assertXmlResourceLimits(xml);
58
121
  const ids = [];
59
122
  if (rewriteStreamingXmlDecimalAttributes(xml, (element) => {
60
- const attribute = revisionAttribute(element);
61
- if (attribute) {
62
- ids.push(attribute.id);
63
- reserved.add(attribute.id);
64
- }
123
+ const identified = identifiedElement(element);
124
+ if (identified === null) return null;
125
+ if (identified.kind === ID_KINDS.revision) ids.push(identified.id);
126
+ reserved.add(identified.id);
65
127
  return null;
66
128
  }).status === "unsupported") throw new XmlResourceLimitError({
67
129
  message: `Revision-id normalization could not safely scan ${path}`,
@@ -73,6 +135,18 @@ const normalizeRevisionIdsInXmlParts = (parts) => {
73
135
  const firstSeen = /* @__PURE__ */ new Set();
74
136
  for (const [path, ids] of occurrencesByPath) for (const id of ids) if (firstSeen.has(id)) repeatedPaths.add(path);
75
137
  else firstSeen.add(id);
138
+ if (repeatedPaths.size > 0) for (const [path, xml] of parts) {
139
+ if (occurrencesByPath.has(path) || !ANNOTATION_ELEMENT_CANDIDATE.test(xml)) continue;
140
+ assertXmlResourceLimits(xml);
141
+ if (rewriteStreamingXmlDecimalAttributes(xml, (element) => {
142
+ const identified = identifiedElement(element);
143
+ if (identified !== null) reserved.add(identified.id);
144
+ return null;
145
+ }).status === "unsupported") throw new XmlResourceLimitError({
146
+ message: `Revision-id normalization could not safely scan ${path}`,
147
+ limit: "syntax"
148
+ });
149
+ }
76
150
  let nextId = 0;
77
151
  const allocate = () => {
78
152
  while (reserved.has(nextId)) nextId += 1;
@@ -7,9 +7,12 @@ import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
7
7
  import { normalizeDrawingIds } from "./drawingIdNormalization.js";
8
8
  import { rebindDrawingImageRelationship } from "./drawingRelationships.js";
9
9
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
10
+ import { parseHeaderFooterType } from "./headerFooterRefParser.js";
10
11
  import { assertValidFolioDocumentModel } from "./modelValidation.js";
11
12
  import { isNewDataUrlDrawing } from "./newImage.js";
13
+ import { missingNoteReferenceStyles, noteReferenceNeeds } from "./noteReferenceStyles.js";
12
14
  import { parseNumbering } from "./numberingParser.js";
15
+ import { isNumberingReference } from "./numberingReference.js";
13
16
  import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
14
17
  import { normalizeParaIdRangeInXmlParts } from "./paraIdRangeNormalization.js";
15
18
  import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
@@ -113,7 +116,7 @@ const extractHeaderFooterReferences = (xml) => {
113
116
  const rId = getAttributeByNamespaceUri(node, OFFICE_RELATIONSHIP_NAMESPACE_URIS, "id");
114
117
  if (rId) references.push({
115
118
  element,
116
- type: getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "type") ?? "default",
119
+ type: parseHeaderFooterType(getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "type")),
117
120
  rId
118
121
  });
119
122
  }
@@ -1387,6 +1390,42 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
1387
1390
  }
1388
1391
  const STYLES_PART_PATH = "word/styles.xml";
1389
1392
  const STYLES_CLOSE_ROOT = "</w:styles>";
1393
+ /**
1394
+ * The style table a package folio creates starts from: `docDefaults` and
1395
+ * `Normal`, and nothing else. A document that carries its own style table
1396
+ * replaces this part wholesale.
1397
+ */
1398
+ const SEED_STYLES_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1399
+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1400
+ <w:docDefaults>
1401
+ <w:rPrDefault>
1402
+ <w:rPr>
1403
+ <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/>
1404
+ <w:sz w:val="22"/>
1405
+ </w:rPr>
1406
+ </w:rPrDefault>
1407
+ <w:pPrDefault>
1408
+ <w:pPr>
1409
+ <w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
1410
+ </w:pPr>
1411
+ </w:pPrDefault>
1412
+ </w:docDefaults>
1413
+ <w:style w:type="paragraph" w:default="1" w:styleId="Normal">
1414
+ <w:name w:val="Normal"/>
1415
+ </w:style>
1416
+ </w:styles>`;
1417
+ /**
1418
+ * The seed part plus the reference styles this package's comments and notes
1419
+ * need. A document with no style table of its own never reaches
1420
+ * {@link styleDefinitionsToSerialize}, so without this its reference marks
1421
+ * would carry a `w:rStyle` naming nothing — the defect
1422
+ * `noteReferenceStyles.ts` exists to remove, on the one path that skips it.
1423
+ */
1424
+ const seedStylesXmlWith = (missing) => {
1425
+ if (missing.length === 0) return SEED_STYLES_XML;
1426
+ const rootClose = SEED_STYLES_XML.lastIndexOf(STYLES_CLOSE_ROOT);
1427
+ return SEED_STYLES_XML.slice(0, rootClose) + missing.map(serializeStyle).join("") + SEED_STYLES_XML.slice(rootClose);
1428
+ };
1390
1429
  const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
1391
1430
  /**
1392
1431
  * Append styles the model defines but the original `word/styles.xml` lacks.
@@ -1395,6 +1434,27 @@ const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
1395
1434
  * per-language clones a bilingual transform adds, are emitted before the root
1396
1435
  * close so paragraphs referencing them resolve on reopen.
1397
1436
  */
1437
+ /**
1438
+ * The styles to write into a package folio is authoring: the model's, plus any
1439
+ * reference character style the serializers are about to emit for this
1440
+ * package's comments and notes but the style table does not define. See
1441
+ * `noteReferenceStyles.ts` — the reference mark would otherwise carry a
1442
+ * `w:rStyle` pointing at nothing.
1443
+ *
1444
+ * Only for a package folio writes from scratch. Repacking a document someone
1445
+ * else authored preserves `word/styles.xml` byte for byte, and adding a
1446
+ * definition there would rewrite a part the user never edited: their document,
1447
+ * their style table, missing reference style included.
1448
+ */
1449
+ const styleDefinitionsToSerialize = (doc) => {
1450
+ const styles = doc.package.styles;
1451
+ if (!styles) return;
1452
+ const missing = missingNoteReferenceStyles(styles, noteReferenceNeeds(doc.package));
1453
+ return missing.length === 0 ? styles : {
1454
+ ...styles,
1455
+ styles: [...styles.styles, ...missing]
1456
+ };
1457
+ };
1398
1458
  async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compressionLevel) {
1399
1459
  const styles = doc.package.styles;
1400
1460
  if (!styles || styles.styles.length === 0) return;
@@ -1576,25 +1636,7 @@ const createEmptyDocxZip = ({ creator, application }) => {
1576
1636
  </w:sectPr>
1577
1637
  </w:body>
1578
1638
  </w:document>`);
1579
- zip.file("word/styles.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1580
- <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1581
- <w:docDefaults>
1582
- <w:rPrDefault>
1583
- <w:rPr>
1584
- <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/>
1585
- <w:sz w:val="22"/>
1586
- </w:rPr>
1587
- </w:rPrDefault>
1588
- <w:pPrDefault>
1589
- <w:pPr>
1590
- <w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
1591
- </w:pPr>
1592
- </w:pPrDefault>
1593
- </w:docDefaults>
1594
- <w:style w:type="paragraph" w:default="1" w:styleId="Normal">
1595
- <w:name w:val="Normal"/>
1596
- </w:style>
1597
- </w:styles>`);
1639
+ zip.file(STYLES_PART_PATH, SEED_STYLES_XML);
1598
1640
  const now = (/* @__PURE__ */ new Date()).toISOString();
1599
1641
  const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${escapeXml(creator)}</dc:creator>`;
1600
1642
  zip.file("docProps/core.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -1645,10 +1687,11 @@ const createDocumentSeedZip = async (doc, properties) => {
1645
1687
  const relationships = ["<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>"];
1646
1688
  const overrides = [];
1647
1689
  let nextRelationshipId = 2;
1648
- if (doc.package.styles) {
1690
+ const styleDefinitions = styleDefinitionsToSerialize(doc);
1691
+ if (styleDefinitions) {
1649
1692
  assertStyleNumberingReferences(doc);
1650
- zip.file("word/styles.xml", serializeStylesXml(doc.package.styles));
1651
- }
1693
+ zip.file(STYLES_PART_PATH, serializeStylesXml(styleDefinitions));
1694
+ } else zip.file(STYLES_PART_PATH, seedStylesXmlWith(missingNoteReferenceStyles(void 0, noteReferenceNeeds(doc.package))));
1652
1695
  const numbering = doc.package.numbering;
1653
1696
  if (numbering && (numbering.abstractNums.length > 0 || numbering.nums.length > 0)) {
1654
1697
  zip.file("word/numbering.xml", serializeNumberingXml(numbering));
@@ -1684,17 +1727,32 @@ const createDocumentSeedZip = async (doc, properties) => {
1684
1727
  };
1685
1728
  const relationshipXml = (id, type, target) => `<Relationship Id="rId${id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/${type}" Target="${target}"/>`;
1686
1729
  const overrideXml = (partName, contentType) => `<Override PartName="${partName}" ContentType="${contentType}"/>`;
1730
+ /**
1731
+ * A package this build assembles from a Document must define the numbering its
1732
+ * styles name: Word reads a styles.xml reference nothing resolves as no
1733
+ * numbering at all, silently dropping the style's list.
1734
+ *
1735
+ * The panic is an invariant over Folio's own output, not input validation. A
1736
+ * source file can carry a dangling style reference, and it never arrives here:
1737
+ * `normalizeStyleNumberingReferences` rewrites one to the "no numbering"
1738
+ * sentinel at every boundary that mints a Document from user styles (`parseDocx`
1739
+ * and the style-set extraction entry points). What reaches this point is what
1740
+ * Folio itself put in the model, so a miss is a defect in Folio.
1741
+ */
1687
1742
  const assertStyleNumberingReferences = (doc) => {
1688
1743
  const referenced = /* @__PURE__ */ new Set();
1689
1744
  for (const style of doc.package.styles?.styles ?? []) {
1690
1745
  const numId = style.pPr?.numPr?.numId;
1691
- if (numId !== void 0) referenced.add(numId);
1746
+ if (isNumberingReference(numId)) referenced.add(numId);
1692
1747
  }
1693
1748
  if (referenced.size === 0) return;
1694
- const available = new Set(doc.package.numbering?.nums.map((numbering) => numbering.numId) ?? []);
1695
- for (const numId of referenced) if (!available.has(numId)) panic(`Style references missing numbering definition ${numId}`);
1749
+ const available = new Map(doc.package.numbering?.nums.map((numbering) => [numbering.numId, numbering]) ?? []);
1696
1750
  const availableAbstract = new Set(doc.package.numbering?.abstractNums.map((numbering) => numbering.abstractNumId) ?? []);
1697
- for (const numbering of doc.package.numbering?.nums ?? []) if (!availableAbstract.has(numbering.abstractNumId)) panic(`Numbering definition ${numbering.numId} references missing abstract numbering`);
1751
+ for (const numId of referenced) {
1752
+ const numbering = available.get(numId);
1753
+ if (!numbering) panic(`Style references missing numbering definition ${numId}`);
1754
+ if (!availableAbstract.has(numbering.abstractNumId)) panic(`Numbering definition ${numbering.numId} references missing abstract numbering`);
1755
+ }
1698
1756
  };
1699
1757
  //#endregion
1700
1758
  export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, collectHyperlinksWithoutRId, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, hasUnmaterializedInlineResources, isDocxBuffer, notePartRelsPath, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx, withoutAttachedTemplate };
@@ -114,8 +114,7 @@ function mergeRunContent(content1, content2) {
114
114
  if (lastText?.type === "text" && firstText?.type === "text") {
115
115
  result[result.length - 1] = {
116
116
  type: "text",
117
- text: lastText.text + firstText.text,
118
- ...lastText.preserveSpace || firstText.preserveSpace ? { preserveSpace: true } : {}
117
+ text: lastText.text + firstText.text
119
118
  };
120
119
  for (let i = 1; i < content2.length; i++) result.push(content2[i]);
121
120
  } else for (const c of content2) result.push(c);