@stll/folio-core 0.54.1 → 0.55.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 (251) hide show
  1. package/dist/ai-edits/apply.d.ts +1 -1
  2. package/dist/ai-edits/apply.js +40 -32
  3. package/dist/ai-edits/headless.d.ts +1 -2
  4. package/dist/ai-edits/headless.js +13 -27
  5. package/dist/ai-edits/read.d.ts +1 -0
  6. package/dist/ai-edits/read.js +7 -2
  7. package/dist/ai-edits/table-mutation-plan.d.ts +2 -1
  8. package/dist/ai-edits/table-mutation-plan.js +39 -1
  9. package/dist/ai-suggestions/apply.d.ts +1 -1
  10. package/dist/ai-suggestions/apply.js +3 -2
  11. package/dist/compare/compare.js +2 -1
  12. package/dist/compare/content-types.d.ts +3 -15
  13. package/dist/controller/canonicalComposition.d.ts +36 -0
  14. package/dist/controller/canonicalComposition.js +168 -0
  15. package/dist/controller/canonicalInput.d.ts +45 -0
  16. package/dist/controller/canonicalInput.js +338 -0
  17. package/dist/controller/canonicalOperations.d.ts +22 -0
  18. package/dist/controller/canonicalOperations.js +85 -0
  19. package/dist/controller/canonicalReview.d.ts +12 -0
  20. package/dist/controller/canonicalReview.js +19 -0
  21. package/dist/controller/canonicalSession.d.ts +149 -0
  22. package/dist/controller/canonicalSession.js +1038 -0
  23. package/dist/controller/canonicalStoryEditor.d.ts +34 -0
  24. package/dist/controller/canonicalStoryEditor.js +82 -0
  25. package/dist/controller/canonicalStructure.d.ts +16 -0
  26. package/dist/controller/canonicalStructure.js +269 -0
  27. package/dist/controller/folioEditor.d.ts +1 -1
  28. package/dist/controller/folioEditor.js +9 -0
  29. package/dist/controller/headerFooterEditorManager.d.ts +4 -0
  30. package/dist/controller/headerFooterEditorManager.js +71 -3
  31. package/dist/controller/hiddenEditorApi.d.ts +35 -0
  32. package/dist/controller/hiddenEditorApi.js +25 -8
  33. package/dist/controller/hiddenEditorManager.d.ts +11 -2
  34. package/dist/controller/hiddenEditorManager.js +456 -30
  35. package/dist/controller/layoutPipeline.d.ts +1 -1
  36. package/dist/controller/layoutPipeline.js +2 -0
  37. package/dist/controller/noteEditorManager.d.ts +4 -0
  38. package/dist/controller/noteEditorManager.js +113 -15
  39. package/dist/docx/attributeRemainder.js +1 -0
  40. package/dist/docx/bookmarkIds.d.ts +11 -0
  41. package/dist/docx/bookmarkIds.js +33 -0
  42. package/dist/docx/canonicalSessionInput.d.ts +11 -0
  43. package/dist/docx/canonicalSessionInput.js +27 -0
  44. package/dist/docx/canonicalStoryRepack.d.ts +10 -0
  45. package/dist/docx/canonicalStoryRepack.js +41 -0
  46. package/dist/docx/contentTypeRegistry.d.ts +8 -0
  47. package/dist/docx/contentTypeRegistry.js +64 -0
  48. package/dist/docx/documentParser.d.ts +14 -1
  49. package/dist/docx/documentParser.js +17 -4
  50. package/dist/docx/drawingIdNormalization.js +23 -8
  51. package/dist/docx/ensureParaIds.d.ts +12 -1
  52. package/dist/docx/ensureParaIds.js +173 -213
  53. package/dist/docx/foldedListNumberFields.d.ts +85 -0
  54. package/dist/docx/foldedListNumberFields.js +267 -0
  55. package/dist/docx/headerFooterVerbatim.d.ts +14 -1
  56. package/dist/docx/headerFooterVerbatim.js +95 -2
  57. package/dist/docx/listNumberingInstances.js +11 -9
  58. package/dist/docx/noteIds.d.ts +9 -0
  59. package/dist/docx/noteIds.js +17 -0
  60. package/dist/docx/noteReferenceMark.d.ts +3 -1
  61. package/dist/docx/noteReferenceMark.js +15 -14
  62. package/dist/docx/numberingIds.d.ts +14 -0
  63. package/dist/docx/numberingIds.js +16 -0
  64. package/dist/docx/numberingParser.d.ts +3 -1
  65. package/dist/docx/numberingParser.js +17 -3
  66. package/dist/docx/numericIdAllocator.d.ts +12 -0
  67. package/dist/docx/numericIdAllocator.js +33 -0
  68. package/dist/docx/numericIdNormalization.d.ts +16 -0
  69. package/dist/docx/numericIdNormalization.js +174 -0
  70. package/dist/docx/packageParts.d.ts +3 -1
  71. package/dist/docx/packageParts.js +65 -16
  72. package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
  73. package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
  74. package/dist/docx/paragraphParser.js +101 -53
  75. package/dist/docx/paragraphPropertySource.d.ts +15 -1
  76. package/dist/docx/paragraphPropertySource.js +76 -22
  77. package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
  78. package/dist/docx/parser.js +19 -6
  79. package/dist/docx/rasterMime.d.ts +64 -0
  80. package/dist/docx/rasterMime.js +116 -0
  81. package/dist/docx/replyToComment.js +13 -6
  82. package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
  83. package/dist/docx/reviewHistoryNamespace.js +4 -0
  84. package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
  85. package/dist/docx/reviewResolutionProvenance.js +153 -0
  86. package/dist/docx/rezip.d.ts +7 -3
  87. package/dist/docx/rezip.js +121 -52
  88. package/dist/docx/runConsolidator.d.ts +2 -58
  89. package/dist/docx/runConsolidator.js +3 -175
  90. package/dist/docx/runParser.d.ts +2 -1
  91. package/dist/docx/runParser.js +156 -116
  92. package/dist/docx/saveDiagnostics.d.ts +11 -0
  93. package/dist/docx/saveDiagnostics.js +0 -0
  94. package/dist/docx/selectiveSave.d.ts +2 -1
  95. package/dist/docx/selectiveSave.js +14 -1
  96. package/dist/docx/selectiveXmlPatch.d.ts +16 -8
  97. package/dist/docx/selectiveXmlPatch.js +74 -2
  98. package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
  99. package/dist/docx/serializer/headerFooterSerializer.js +19 -11
  100. package/dist/docx/serializer/noteSerializer.d.ts +2 -2
  101. package/dist/docx/serializer/noteSerializer.js +24 -8
  102. package/dist/docx/serializer/paragraphSerializer.js +41 -4
  103. package/dist/docx/serializer/partNamespaces.js +6 -3
  104. package/dist/docx/serializer/runSerializer.js +8 -5
  105. package/dist/docx/serializer/trackedChangeAttributes.js +7 -1
  106. package/dist/docx/server/build.js +14 -3
  107. package/dist/docx/server/createBilingualDocument.js +10 -9
  108. package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
  109. package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
  110. package/dist/docx/settingsParser.d.ts +1 -2
  111. package/dist/docx/settingsParser.js +5 -6
  112. package/dist/docx/storyBlockReplay.d.ts +11 -0
  113. package/dist/docx/storyBlockReplay.js +367 -0
  114. package/dist/docx/streamingXmlParser.d.ts +31 -1
  115. package/dist/docx/streamingXmlParser.js +136 -21
  116. package/dist/docx/structuralXmlPatch.js +8 -1
  117. package/dist/docx/unzip.d.ts +3 -1
  118. package/dist/docx/unzip.js +36 -16
  119. package/dist/docx/verbatimCapture.d.ts +3 -1
  120. package/dist/docx/verbatimCapture.js +49 -6
  121. package/dist/i18n/messages/ar.gen.d.ts +5 -0
  122. package/dist/i18n/messages/ar.gen.js +656 -0
  123. package/dist/i18n/messages/cs.gen.d.ts +5 -0
  124. package/dist/i18n/messages/cs.gen.js +656 -0
  125. package/dist/i18n/messages/de.gen.d.ts +5 -0
  126. package/dist/i18n/messages/de.gen.js +656 -0
  127. package/dist/i18n/messages/en.gen.d.ts +5 -0
  128. package/dist/i18n/messages/en.gen.js +656 -0
  129. package/dist/i18n/messages/es.gen.d.ts +5 -0
  130. package/dist/i18n/messages/es.gen.js +656 -0
  131. package/dist/i18n/messages/et.gen.d.ts +5 -0
  132. package/dist/i18n/messages/et.gen.js +656 -0
  133. package/dist/i18n/messages/fr.gen.d.ts +5 -0
  134. package/dist/i18n/messages/fr.gen.js +656 -0
  135. package/dist/i18n/messages/he.gen.d.ts +5 -0
  136. package/dist/i18n/messages/he.gen.js +656 -0
  137. package/dist/i18n/messages/hi.gen.d.ts +5 -0
  138. package/dist/i18n/messages/hi.gen.js +656 -0
  139. package/dist/i18n/messages/hu.gen.d.ts +5 -0
  140. package/dist/i18n/messages/hu.gen.js +656 -0
  141. package/dist/i18n/messages/locales.d.ts +5 -0
  142. package/dist/i18n/messages/locales.js +22 -0
  143. package/dist/i18n/messages/lt.gen.d.ts +5 -0
  144. package/dist/i18n/messages/lt.gen.js +656 -0
  145. package/dist/i18n/messages/lv.gen.d.ts +5 -0
  146. package/dist/i18n/messages/lv.gen.js +656 -0
  147. package/dist/i18n/messages/pl.gen.d.ts +5 -0
  148. package/dist/i18n/messages/pl.gen.js +656 -0
  149. package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
  150. package/dist/i18n/messages/pt-BR.gen.js +656 -0
  151. package/dist/i18n/messages/sk.gen.d.ts +5 -0
  152. package/dist/i18n/messages/sk.gen.js +656 -0
  153. package/dist/i18n/messages/tr.gen.d.ts +5 -0
  154. package/dist/i18n/messages/tr.gen.js +656 -0
  155. package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
  156. package/dist/i18n/messages/zh-CN.gen.js +656 -0
  157. package/dist/i18n/messages.d.ts +2 -3
  158. package/dist/i18n/messages.js +1 -19
  159. package/dist/internal/noteMarkerAttrs.d.ts +8 -0
  160. package/dist/internal/noteMarkerAttrs.js +16 -0
  161. package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
  162. package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
  163. package/dist/internal/prosemirrorAttrBoundary.js +50 -0
  164. package/dist/layout-bridge/convert/fixedTableColumnWidths.d.ts +6 -0
  165. package/dist/layout-bridge/convert/fixedTableColumnWidths.js +23 -0
  166. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  167. package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
  168. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  169. package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
  170. package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
  171. package/dist/layout-bridge/convert/noteReferences.js +1 -6
  172. package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
  173. package/dist/layout-bridge/convert/tableConversion.js +4 -1
  174. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
  175. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
  176. package/dist/layout-painter/index.js +2 -5
  177. package/dist/managers/DocumentLoaderManager.d.ts +1 -0
  178. package/dist/managers/DocumentLoaderManager.js +8 -1
  179. package/dist/markdown/renderBlock.js +15 -1
  180. package/dist/markdown/renderParagraph.js +9 -13
  181. package/dist/markdown/renderRuns.js +3 -2
  182. package/dist/markdown/renderTable.js +2 -0
  183. package/dist/markdown/types.d.ts +3 -9
  184. package/dist/paged-layout/editorScrollRoot.d.ts +15 -0
  185. package/dist/paged-layout/editorScrollRoot.js +72 -0
  186. package/dist/paged-layout/scrollToPmPosition.js +8 -22
  187. package/dist/prosemirror/attrs/index.d.ts +2 -12
  188. package/dist/prosemirror/attrs/index.js +22 -48
  189. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
  190. package/dist/prosemirror/canonicalCommands.d.ts +97 -0
  191. package/dist/prosemirror/canonicalCommands.js +28 -0
  192. package/dist/prosemirror/commands/clearParagraphIndent.d.ts +6 -0
  193. package/dist/prosemirror/commands/clearParagraphIndent.js +25 -0
  194. package/dist/prosemirror/commands/pageBreak.js +15 -1
  195. package/dist/prosemirror/commentIdAllocator.d.ts +15 -22
  196. package/dist/prosemirror/commentIdAllocator.js +50 -25
  197. package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
  198. package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
  199. package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
  200. package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
  201. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -3
  202. package/dist/prosemirror/conversion/toProseDoc.js +51 -11
  203. package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
  204. package/dist/prosemirror/executeEditorCommand.js +26 -0
  205. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  206. package/dist/prosemirror/extensions/core/HistoryExtension.js +97 -5
  207. package/dist/prosemirror/extensions/core/ParagraphExtension.js +53 -20
  208. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +22 -28
  209. package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
  210. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
  211. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
  212. package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
  213. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  214. package/dist/prosemirror/extensions/marks/markUtils.js +70 -8
  215. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
  216. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
  217. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
  218. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -6
  219. package/dist/prosemirror/listMarker.d.ts +1 -0
  220. package/dist/prosemirror/listMarker.js +16 -7
  221. package/dist/prosemirror/listNumbering.js +26 -5
  222. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  223. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  224. package/dist/prosemirror/markupViewNotes.js +3 -2
  225. package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
  226. package/dist/prosemirror/noteReferenceReview.js +5 -3
  227. package/dist/prosemirror/plugins/revisionIds.d.ts +23 -19
  228. package/dist/prosemirror/plugins/revisionIds.js +110 -29
  229. package/dist/prosemirror/plugins/suggestionMode.d.ts +2 -2
  230. package/dist/prosemirror/plugins/suggestionMode.js +246 -42
  231. package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
  232. package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
  233. package/dist/prosemirror/runFormattingFromMarks.js +15 -1
  234. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
  235. package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
  236. package/dist/prosemirror/schema/marks.d.ts +2 -0
  237. package/dist/prosemirror/schema/nodes.d.ts +10 -2
  238. package/dist/prosemirror/storyListNumbering.js +11 -9
  239. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
  240. package/dist/prosemirror/textInput.d.ts +6 -1
  241. package/dist/prosemirror/textInput.js +14 -3
  242. package/dist/prosemirror/trackedRevisionPath.js +2 -1
  243. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
  244. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
  245. package/dist/prosemirror/utils/visualLineNavigation.js +6 -8
  246. package/dist/prosemirror/validation.js +4 -0
  247. package/dist/types/content.d.ts +2 -2
  248. package/dist/utils/createDocument.js +2 -2
  249. package/dist/utils/mergeDocumentContent.d.ts +2 -2
  250. package/dist/utils/mergeDocumentContent.js +11 -8
  251. package/package.json +14 -2
@@ -1,8 +1,10 @@
1
1
  import { deterministicHexId } from "../utils/hexId.js";
2
- import { isXmlNameBoundary, spliceXml } from "./selectiveXmlPatch.js";
2
+ import { resolvePackageRelationshipTarget } from "./packageParts.js";
3
+ import { spliceXml } from "./selectiveXmlPatch.js";
3
4
  import { loadDocxArchive } from "./server/boundedArchive.js";
4
- import { matchCloseTag, matchOpenTag, resolveWordprocessingPrefixes } from "./wordprocessingPrefixes.js";
5
- import { WORDPROCESSINGML_NAMESPACE_URIS, getChildElements, getLocalName, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
5
+ import { scanStreamingXmlElements } from "./streamingXmlParser.js";
6
+ import { resolveNamespaceUri } from "./xmlNamespaceContext.js";
7
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, getAttribute, getChildElements, getLocalName, getNamespaceUri, parseXmlDocument, resolveAttributeNamespaceUri } from "./xmlParser.js";
6
8
  import { TaggedError } from "better-result";
7
9
  import JSZip from "jszip";
8
10
  //#region src/docx/ensureParaIds.ts
@@ -27,7 +29,7 @@ import JSZip from "jszip";
27
29
  * so documents carrying features folio's parser does not model come back
28
30
  * with those features byte-identical. Contract:
29
31
  *
30
- * - Parts covered: `word/document.xml`, `word/header*.xml`, `word/footer*.xml`,
32
+ * - Parts covered: the officeDocument relationship target, `word/header*.xml`, `word/footer*.xml`,
31
33
  * `word/footnotes.xml`, `word/endnotes.xml`. Paragraphs nested in table
32
34
  * cells and in `mc:Choice` text boxes are plain `<w:p>` elements inside
33
35
  * those parts and are covered by the same scan. The comments part mints its
@@ -50,12 +52,10 @@ import JSZip from "jszip";
50
52
  * declarations and a `mc:Ignorable` listing `w14` when missing — non-Word
51
53
  * producers declare neither, and absent `mc:Ignorable` handling is what
52
54
  * makes pre-2010 consumers choke on the new attributes.
53
- * - Prefixes are aliases. Each part is scanned under the prefixes its root
54
- * binds to WordprocessingML (a default namespace included), `w14` and `mc`
55
- * (see `resolveWordprocessingPrefixes`), and a minted id is written under
56
- * the part's own `w14` prefix, which is the one `mc:Ignorable` lists. A
57
- * part whose bindings a string scan cannot follow (a nested element that
58
- * rebinds one of them) is refused with {@link EnsureParaIdsError}.
55
+ * - Prefixes are aliases: elements and attributes resolve against their
56
+ * in-scope namespace URI, including Strict and Transitional profiles and
57
+ * nested rebinding. Minted attributes use a root prefix with no conflicting
58
+ * nested binding; a fresh prefix is declared when necessary.
59
59
  * - Idempotent: a document that already has full coverage is returned as the
60
60
  * original bytes, untouched (`alreadyComplete: true`). A body with
61
61
  * paragraphs the scan did not see is refused, never reported complete.
@@ -63,6 +63,11 @@ import JSZip from "jszip";
63
63
  * When normalization would rewrite the package, it fails unless the caller
64
64
  * explicitly allows signature invalidation after warning the user.
65
65
  */
66
+ const ENSURE_PARA_IDS_REASONS = {
67
+ NORMALIZATION_FAILED: "normalization-failed",
68
+ NAMESPACE_INVALID: "namespace-invalid",
69
+ SIGNED_PACKAGE: "signed-package"
70
+ };
66
71
  /** A malformed or unsupported package prevented paragraph-ID normalization. */
67
72
  var EnsureParaIdsError = class extends TaggedError("EnsureParaIdsError") {};
68
73
  const W14_NAMESPACE_URI = "http://schemas.microsoft.com/office/word/2010/wordml";
@@ -71,53 +76,15 @@ const MC_IGNORABLE_W14 = "w14";
71
76
  const DIGITAL_SIGNATURE_PART_PREFIX = "_xmlsignatures/";
72
77
  /** Word reads an all-zero `w14:paraId` as "no id assigned". */
73
78
  const RESERVED_ZERO_ID_PATTERN = /^0*$/u;
74
- const DOCUMENT_PART = "word/document.xml";
79
+ const PACKAGE_RELATIONSHIP_URI = "http://schemas.openxmlformats.org/package/2006/relationships";
75
80
  const TARGET_PART_PATTERNS = [
76
- /^word\/document\.xml$/u,
77
81
  /^word\/header\d*\.xml$/u,
78
82
  /^word\/footer\d*\.xml$/u,
79
83
  /^word\/footnotes\.xml$/u,
80
84
  /^word\/endnotes\.xml$/u
81
85
  ];
82
- const OPAQUE_XML_REGIONS = [
83
- {
84
- open: "<!--",
85
- close: "-->"
86
- },
87
- {
88
- open: "<![CDATA[",
89
- close: "]]>"
90
- },
91
- {
92
- open: "<?",
93
- close: "?>"
94
- }
95
- ];
96
- /**
97
- * Every prefixed `paraId` counts toward uniqueness: the parser accepts the
98
- * WordprocessingML fallback, comment parts link replies via `w15:paraId`, and
99
- * a producer may spell any of them under a prefix of its own. Over-collecting
100
- * only steers a minted id away from a value; it never keeps one.
101
- */
102
- const ANY_PARA_ID_PATTERN = /\s[^\s=<>/"':]+:paraId=(?<quote>["'])(?<id>[\s\S]*?)\k<quote>/gu;
103
- const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
104
- /** An attribute `localName` under any of `prefixes`, with `quote` and `value` groups. */
105
- const prefixedAttributePattern = (prefixes, localName) => {
106
- const names = prefixes.filter((prefix) => prefix !== "").map(escapeRegExp);
107
- return names.length === 0 ? /(?!)/u : new RegExp(`\\s(?:${names.join("|")}):${localName}=(?<quote>["'])(?<value>[\\s\\S]*?)\\k<quote>`, "u");
108
- };
109
- const partSpelling = (xml, partPath) => {
110
- const resolution = resolveWordprocessingPrefixes(xml);
111
- if (resolution.type === "unsupported") throw createEnsureParaIdsError(`Cannot normalize paragraph ids in ${partPath}: ${resolution.reason}`);
112
- const { prefixes } = resolution;
113
- const attributePrefixes = [...prefixes.w14, ...prefixes.main];
114
- return {
115
- prefixes,
116
- paraId: prefixedAttributePattern(attributePrefixes, "paraId"),
117
- textId: prefixedAttributePattern(attributePrefixes, "textId"),
118
- w14Prefix: (prefixes.w14Declared ? prefixes.w14[0] : void 0) ?? MC_IGNORABLE_W14
119
- };
120
- };
86
+ /** Reserve package ids even in parts whose content the pass leaves untouched. */
87
+ const ANY_PARA_ID_PATTERN = /\s[^\s=<>/"':]+:paraId\s*=\s*(?<quote>["'])(?<id>[\s\S]*?)\k<quote>/gu;
121
88
  /**
122
89
  * Stamp the edits into the part through the splice owner, which refuses a
123
90
  * result that would leave a comment range with only one half. These edits
@@ -135,84 +102,98 @@ const applySplices = (xml, edits, partPath) => {
135
102
  return patched;
136
103
  };
137
104
  const createEnsureParaIdsError = (message, cause) => new EnsureParaIdsError({
105
+ reason: ENSURE_PARA_IDS_REASONS.NORMALIZATION_FAILED,
138
106
  message,
139
107
  ...cause === void 0 ? {} : { cause }
140
108
  });
141
- const skipOpaqueXmlRegion = (xml, start, partPath) => {
142
- for (const { open, close } of OPAQUE_XML_REGIONS) {
143
- if (!xml.startsWith(open, start)) continue;
144
- const closeStart = xml.indexOf(close, start + open.length);
145
- if (closeStart === -1) throw createEnsureParaIdsError(`Unterminated ${open} region in ${partPath}`);
146
- return closeStart + close.length;
147
- }
148
- return null;
149
- };
150
- const attributeValueRange = (match, absoluteOffset, group) => {
151
- const quote = match.groups["quote"];
152
- const value = match.groups[group];
153
- const start = absoluteOffset + match.index + match[0].indexOf(quote) + 1;
154
- return {
155
- start,
156
- end: start + value.length
157
- };
158
- };
159
109
  const collectExistingParaIds = (xml, into) => {
160
110
  for (const match of xml.matchAll(ANY_PARA_ID_PATTERN)) {
161
111
  const id = match.groups["id"];
162
112
  if (id.length > 0) into.add(id.toUpperCase());
163
113
  }
164
114
  };
165
- /**
166
- * Every paragraph open tag of a part in document order, under whichever
167
- * WordprocessingML prefix the part binds, with whether it sits in
168
- * `mc:Fallback` (under the part's own `mc` prefix).
169
- */
170
- function* paragraphOpenTags(xml, partPath, { prefixes }) {
171
- let fallbackDepth = 0;
172
- let pos = 0;
173
- while (pos < xml.length) {
174
- const tagStart = xml.indexOf("<", pos);
175
- if (tagStart === -1) return;
176
- const opaqueEnd = skipOpaqueXmlRegion(xml, tagStart, partPath);
177
- if (opaqueEnd !== null) {
178
- pos = opaqueEnd;
179
- continue;
115
+ /** Resolve every element and attribute in its own scope; retain source offsets for splices. */
116
+ const partSpelling = (xml, partPath) => {
117
+ let root;
118
+ let rootNameEnd = 0;
119
+ let ignorable;
120
+ const paragraphs = [];
121
+ const fallbacks = /* @__PURE__ */ new WeakSet();
122
+ const bindings = /* @__PURE__ */ new Map();
123
+ if (scanStreamingXmlElements(xml, ({ element, attributeValueSpans: spans, nameEnd, parent }) => {
124
+ const unboundElement = (element.name ?? "").includes(":") && getNamespaceUri(element) === void 0;
125
+ const unboundAttribute = Object.keys(element.attributes ?? {}).some((attribute) => attribute.includes(":") && !attribute.startsWith("xmlns:") && resolveAttributeNamespaceUri(element, attribute) === void 0);
126
+ if (unboundElement || unboundAttribute) throw new EnsureParaIdsError({
127
+ reason: ENSURE_PARA_IDS_REASONS.NAMESPACE_INVALID,
128
+ message: `Undeclared namespace prefix in ${partPath}`
129
+ });
130
+ if (parent === void 0) {
131
+ if (root !== void 0) throw createEnsureParaIdsError(`Multiple roots in ${partPath}`);
132
+ root = element;
133
+ rootNameEnd = nameEnd;
180
134
  }
181
- if (matchOpenTag(xml, tagStart, prefixes.mc, "Fallback") !== -1) {
182
- const tagEnd = xml.indexOf(">", tagStart);
183
- if (tagEnd === -1) throw createEnsureParaIdsError(`Unterminated mc:Fallback tag in ${partPath}`);
184
- if (xml[tagEnd - 1] !== "/") fallbackDepth += 1;
185
- pos = tagEnd + 1;
186
- continue;
135
+ for (const [name, value] of Object.entries(element.attributes ?? {})) {
136
+ if (name !== "xmlns" && !name.startsWith("xmlns:")) continue;
137
+ const prefix = name === "xmlns" ? "" : name.slice(6);
138
+ let uris = bindings.get(prefix);
139
+ if (uris === void 0) {
140
+ uris = /* @__PURE__ */ new Set();
141
+ bindings.set(prefix, uris);
142
+ }
143
+ uris.add(String(value));
187
144
  }
188
- const fallbackClose = matchCloseTag(xml, tagStart, prefixes.mc, "Fallback");
189
- if (fallbackClose !== -1) {
190
- fallbackDepth = Math.max(0, fallbackDepth - 1);
191
- pos = tagStart + fallbackClose;
192
- continue;
145
+ const inFallback = parent !== void 0 && fallbacks.has(parent) || getLocalName(element.name ?? "") === "Fallback" && getNamespaceUri(element) === MC_NAMESPACE_URI;
146
+ if (inFallback) fallbacks.add(element);
147
+ let paraId;
148
+ let textId;
149
+ for (const [name, span] of spans) {
150
+ const namespace = resolveAttributeNamespaceUri(element, name);
151
+ const localName = getLocalName(name);
152
+ const value = String(element.attributes?.[name] ?? "");
153
+ if (element === root && namespace === MC_NAMESPACE_URI && localName === "Ignorable") ignorable = {
154
+ ...span,
155
+ value
156
+ };
157
+ if (namespace !== W14_NAMESPACE_URI && !WORDPROCESSINGML_NAMESPACE_URIS.has(namespace ?? "")) continue;
158
+ if (localName === "paraId") {
159
+ if (paraId === void 0 || namespace === W14_NAMESPACE_URI) paraId = {
160
+ ...span,
161
+ value
162
+ };
163
+ }
164
+ if (localName === "textId") textId = {
165
+ ...span,
166
+ value
167
+ };
193
168
  }
194
- const nameLength = matchOpenTag(xml, tagStart, prefixes.main, "p");
195
- if (nameLength === -1) {
196
- pos = tagStart + 1;
197
- continue;
169
+ if (getLocalName(element.name ?? "") === "p" && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "")) paragraphs.push({
170
+ nameEnd,
171
+ inFallback,
172
+ paraId,
173
+ textId
174
+ });
175
+ }).status === "unsupported" || root === void 0) throw createEnsureParaIdsError(`Malformed XML in ${partPath}`);
176
+ const rootElement = root;
177
+ const prefixFor = (uri, preferred) => {
178
+ for (const [prefix, boundUri] of rootElement.namespaceScope?.bindings ?? []) {
179
+ if (prefix === "" || boundUri !== uri) continue;
180
+ if (uri !== W14_NAMESPACE_URI || bindings.get(prefix)?.size === 1) return prefix;
198
181
  }
199
- const tagEnd = xml.indexOf(">", tagStart);
200
- if (tagEnd === -1) throw createEnsureParaIdsError(`Unterminated paragraph tag in ${partPath}`);
201
- pos = tagEnd + 1;
202
- yield {
203
- tagStart,
204
- tagEnd,
205
- nameEnd: tagStart + nameLength,
206
- inFallback: fallbackDepth > 0
207
- };
208
- }
209
- }
210
- const collectFallbackParaIds = (xml, partPath, spelling, into) => {
211
- for (const { tagStart, tagEnd, inFallback } of paragraphOpenTags(xml, partPath, spelling)) {
212
- if (!inFallback) continue;
213
- const id = spelling.paraId.exec(xml.slice(tagStart, tagEnd + 1))?.groups?.["value"];
214
- if (id) into.add(id.toUpperCase());
215
- }
182
+ let candidate = preferred;
183
+ for (let suffix = 1; bindings.has(candidate); suffix += 1) candidate = `${preferred}_${suffix}`;
184
+ return candidate;
185
+ };
186
+ return {
187
+ root,
188
+ rootNameEnd,
189
+ ignorable,
190
+ paragraphs,
191
+ w14Prefix: prefixFor(W14_NAMESPACE_URI, MC_IGNORABLE_W14),
192
+ mcPrefix: prefixFor(MC_NAMESPACE_URI, "mc")
193
+ };
194
+ };
195
+ const collectFallbackParaIds = (spelling, into) => {
196
+ for (const { inFallback, paraId } of spelling.paragraphs) if (inFallback && paraId?.value) into.add(paraId.value.toUpperCase());
216
197
  };
217
198
  /**
218
199
  * Deterministic fresh id for the paragraph at `ordinal` in `partPath`,
@@ -225,34 +206,27 @@ const mintParaId = (context, partPath, ordinal) => {
225
206
  context.taken.add(id);
226
207
  return id;
227
208
  };
228
- /**
229
- * One forward scan over a part: every paragraph open tag outside
230
- * `mc:Fallback` either keeps its paraId (valid, first occurrence) or gets a
231
- * splice edit minting / replacing one. `seen` spans parts so the
232
- * first-occurrence rule is document-wide across the scan order.
233
- */
234
- const scanPart = (xml, partPath, spelling, context, seen) => {
209
+ const scanPart = ({ partPath, spelling, context, seen }) => {
235
210
  const edits = [];
211
+ const minted = [];
236
212
  const w14 = spelling.w14Prefix;
237
213
  let assigned = 0;
238
214
  let deduplicated = 0;
239
215
  let ordinal = 0;
240
- for (const { tagStart, tagEnd, nameEnd, inFallback } of paragraphOpenTags(xml, partPath, spelling)) {
216
+ for (const { nameEnd, inFallback, paraId, textId } of spelling.paragraphs) {
241
217
  if (inFallback) continue;
242
218
  ordinal += 1;
243
- const openTag = xml.slice(tagStart, tagEnd + 1);
244
- const paraIdMatch = spelling.paraId.exec(openTag);
245
- if (!paraIdMatch) {
219
+ if (paraId === void 0) {
246
220
  const id = mintParaId(context, partPath, ordinal);
247
- const textIdMatch = spelling.textId.exec(openTag);
248
- if (textIdMatch) {
221
+ if (textId !== void 0) {
249
222
  edits.push({
250
223
  start: nameEnd,
251
224
  end: nameEnd,
252
225
  text: ` ${w14}:paraId="${id}"`
253
226
  });
254
227
  edits.push({
255
- ...attributeValueRange(textIdMatch, tagStart, "value"),
228
+ start: textId.start,
229
+ end: textId.end,
256
230
  text: id
257
231
  });
258
232
  } else edits.push({
@@ -261,10 +235,11 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
261
235
  text: ` ${w14}:paraId="${id}" ${w14}:textId="${id}"`
262
236
  });
263
237
  assigned += 1;
238
+ minted.push(id);
264
239
  seen.add(id);
265
240
  continue;
266
241
  }
267
- const value = paraIdMatch.groups["value"].toUpperCase();
242
+ const value = paraId.value.toUpperCase();
268
243
  const unassigned = RESERVED_ZERO_ID_PATTERN.test(value);
269
244
  if (!unassigned && !seen.has(value)) {
270
245
  seen.add(value);
@@ -272,12 +247,13 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
272
247
  }
273
248
  const id = mintParaId(context, partPath, ordinal);
274
249
  edits.push({
275
- ...attributeValueRange(paraIdMatch, tagStart, "value"),
250
+ start: paraId.start,
251
+ end: paraId.end,
276
252
  text: id
277
253
  });
278
- const textIdMatch = spelling.textId.exec(openTag);
279
- if (textIdMatch) edits.push({
280
- ...attributeValueRange(textIdMatch, tagStart, "value"),
254
+ if (textId !== void 0) edits.push({
255
+ start: textId.start,
256
+ end: textId.end,
281
257
  text: id
282
258
  });
283
259
  else edits.push({
@@ -287,13 +263,14 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
287
263
  });
288
264
  if (unassigned) assigned += 1;
289
265
  else deduplicated += 1;
266
+ minted.push(id);
290
267
  seen.add(id);
291
268
  }
292
269
  return {
293
270
  edits,
271
+ minted,
294
272
  assigned,
295
- deduplicated,
296
- paragraphs: ordinal
273
+ deduplicated
297
274
  };
298
275
  };
299
276
  /**
@@ -301,71 +278,42 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
301
278
  * `xmlns:mc` and lists `w14` in `mc:Ignorable`, so consumers that predate the
302
279
  * 2010 extensions skip the new attributes instead of rejecting the part.
303
280
  */
304
- const ensureRootNamespaces = (xml, partPath, { prefixes, w14Prefix }) => {
305
- let pos = 0;
306
- let rootStart = -1;
307
- while (pos < xml.length) {
308
- const lt = xml.indexOf("<", pos);
309
- if (lt === -1) break;
310
- const opaqueEnd = skipOpaqueXmlRegion(xml, lt, partPath);
311
- if (opaqueEnd !== null) {
312
- pos = opaqueEnd;
313
- continue;
314
- }
315
- if (xml[lt + 1] === "!") {
316
- const skipTo = xml.indexOf(">", lt);
317
- if (skipTo === -1) throw createEnsureParaIdsError(`Unterminated prolog in ${partPath}`);
318
- pos = skipTo + 1;
319
- continue;
320
- }
321
- rootStart = lt;
322
- break;
323
- }
324
- if (rootStart === -1) throw createEnsureParaIdsError(`No root element in ${partPath}`);
325
- const rootEnd = xml.indexOf(">", rootStart);
326
- if (rootEnd === -1) throw createEnsureParaIdsError(`Unterminated root element in ${partPath}`);
327
- const rootTag = xml.slice(rootStart, rootEnd + 1);
328
- if (!prefixes.mcDeclared && prefixes.mc.length === 0) throw createEnsureParaIdsError(`xmlns:mc has an unexpected namespace URI in ${partPath}`);
329
- if (!prefixes.w14Declared && prefixes.w14.length === 0) throw createEnsureParaIdsError(`xmlns:w14 has an unexpected namespace URI in ${partPath}`);
281
+ const ensureRootNamespaces = ({ root, rootNameEnd, ignorable, w14Prefix, mcPrefix }) => {
330
282
  const edits = [];
331
283
  const declarations = [];
332
- const mcPrefix = (prefixes.mcDeclared ? prefixes.mc[0] : void 0) ?? "mc";
333
- if (!prefixes.mcDeclared) declarations.push(` xmlns:mc="${MC_NAMESPACE_URI}"`);
334
- if (!prefixes.w14Declared) declarations.push(` xmlns:w14="${W14_NAMESPACE_URI}"`);
335
- const ignorable = prefixedAttributePattern(prefixes.mcDeclared ? prefixes.mc : [mcPrefix], "Ignorable").exec(rootTag);
336
- if (ignorable) {
337
- const tokens = ignorable.groups["value"].split(/\s+/u).filter((token) => token.length > 0);
338
- if (!tokens.includes(w14Prefix)) {
339
- const { end } = attributeValueRange(ignorable, rootStart, "value");
340
- const text = tokens.length === 0 ? w14Prefix : ` ${w14Prefix}`;
341
- edits.push({
342
- start: end,
343
- end,
344
- text
345
- });
346
- }
347
- } else declarations.push(` ${mcPrefix}:Ignorable="${w14Prefix}"`);
348
- if (declarations.length > 0) {
349
- let nameEnd = rootStart + 1;
350
- while (nameEnd < xml.length && !isXmlNameBoundary(xml[nameEnd])) nameEnd += 1;
351
- edits.push({
352
- start: nameEnd,
353
- end: nameEnd,
354
- text: declarations.join("")
284
+ if (resolveNamespaceUri(root.namespaceScope, mcPrefix) !== MC_NAMESPACE_URI) declarations.push(` xmlns:${mcPrefix}="${MC_NAMESPACE_URI}"`);
285
+ if (resolveNamespaceUri(root.namespaceScope, w14Prefix) !== W14_NAMESPACE_URI) declarations.push(` xmlns:${w14Prefix}="${W14_NAMESPACE_URI}"`);
286
+ if (ignorable !== void 0) {
287
+ const tokens = ignorable.value.split(/\s+/u).filter((token) => token.length > 0);
288
+ if (!tokens.includes(w14Prefix)) edits.push({
289
+ start: ignorable.end,
290
+ end: ignorable.end,
291
+ text: tokens.length === 0 ? w14Prefix : ` ${w14Prefix}`
355
292
  });
356
- }
293
+ } else declarations.push(` ${mcPrefix}:Ignorable="${w14Prefix}"`);
294
+ if (declarations.length > 0) edits.push({
295
+ start: rootNameEnd,
296
+ end: rootNameEnd,
297
+ text: declarations.join("")
298
+ });
357
299
  return edits;
358
300
  };
359
- const hasWordParagraph = (element) => getLocalName(element.name ?? "") === "p" && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "") || getChildElements(element).some(hasWordParagraph);
360
- /**
361
- * A body the string scan saw no paragraph in is either genuinely empty or
362
- * spelled in a way the scan missed. Only the namespace-aware parser can tell
363
- * which, and it only has to be asked in that rare case: `alreadyComplete`
364
- * must never describe paragraphs nobody looked at.
365
- */
366
- const assertNoUnscannedParagraphs = (xml, partPath) => {
367
- const root = parseXmlDocument(xml);
368
- if (root !== null && hasWordParagraph(root)) throw createEnsureParaIdsError(`Cannot normalize paragraph ids in ${partPath}: its paragraphs are not spelled in a way the scan can patch`);
301
+ const mainDocumentPart = (rels, entries) => {
302
+ const root = parseXmlDocument(rels);
303
+ if (root === null || getLocalName(root.name ?? "") !== "Relationships" || getNamespaceUri(root) !== PACKAGE_RELATIONSHIP_URI) throw createEnsureParaIdsError("Malformed package relationships");
304
+ const documents = getChildElements(root).filter((element) => {
305
+ if (getLocalName(element.name ?? "") !== "Relationship" || getNamespaceUri(element) !== PACKAGE_RELATIONSHIP_URI) return false;
306
+ const type = getAttribute(element, null, "Type");
307
+ return [...OFFICE_RELATIONSHIP_NAMESPACE_URIS].some((uri) => type === `${uri}/officeDocument`);
308
+ });
309
+ const document = documents.at(0);
310
+ if (documents.length !== 1 || document === void 0 || getAttribute(document, null, "TargetMode") === "External") throw createEnsureParaIdsError("Package must have one internal officeDocument relationship");
311
+ const target = getAttribute(document, null, "Target");
312
+ const partPath = target === null ? void 0 : resolvePackageRelationshipTarget(target, "_rels/.rels");
313
+ if (partPath === void 0) throw createEnsureParaIdsError("Invalid officeDocument target");
314
+ const name = entries.find((entry) => entry.toLowerCase() === partPath);
315
+ if (name === void 0) throw createEnsureParaIdsError("officeDocument part not found");
316
+ return name;
369
317
  };
370
318
  /** OPC part names are case-insensitive; compare lowercased. */
371
319
  const isTargetPart = (path) => TARGET_PART_PATTERNS.some((pattern) => pattern.test(path.toLowerCase()));
@@ -378,12 +326,11 @@ const hasDigitalSignatureParts = (zip) => Object.values(zip.files).some(({ dir,
378
326
  */
379
327
  const ensureParaIdsInternal = async (docx, options) => {
380
328
  const archive = await loadDocxArchive(docx);
381
- const xmlPartNames = archive.entries.filter((relativePath) => {
382
- const lower = relativePath.toLowerCase();
383
- return lower.startsWith("word/") && lower.endsWith(".xml");
384
- });
385
- const documentPartName = xmlPartNames.find((name) => name.toLowerCase() === DOCUMENT_PART);
386
- if (documentPartName === void 0) throw createEnsureParaIdsError("word/document.xml not found: not a WordprocessingML package");
329
+ const relsPath = archive.entries.find((name) => name.toLowerCase() === "_rels/.rels");
330
+ const rels = relsPath === void 0 ? null : await archive.readEntryString(relsPath);
331
+ if (rels === null) throw createEnsureParaIdsError("Package relationships not found");
332
+ const documentPartName = mainDocumentPart(rels, archive.entries);
333
+ const xmlPartNames = archive.entries.filter((name) => name.toLowerCase().endsWith(".xml") || name === documentPartName);
387
334
  const partTexts = /* @__PURE__ */ new Map();
388
335
  for (const name of xmlPartNames) {
389
336
  const text = await archive.readEntryString(name);
@@ -391,39 +338,51 @@ const ensureParaIdsInternal = async (docx, options) => {
391
338
  }
392
339
  const taken = /* @__PURE__ */ new Set();
393
340
  for (const text of partTexts.values()) collectExistingParaIds(text, taken);
341
+ const documentXml = partTexts.get(documentPartName);
342
+ if (documentXml === void 0) throw createEnsureParaIdsError("officeDocument part is unreadable");
394
343
  const context = {
395
344
  taken,
396
- docKey: deterministicHexId(partTexts.get(documentPartName))
345
+ docKey: deterministicHexId(documentXml)
397
346
  };
398
347
  const targetParts = [documentPartName, ...xmlPartNames.filter((name) => name !== documentPartName && isTargetPart(name)).sort((a, b) => a.localeCompare(b))];
399
348
  const spellings = /* @__PURE__ */ new Map();
400
349
  const seen = /* @__PURE__ */ new Set();
401
- for (const [partPath, xml] of partTexts) if (isTargetPart(partPath)) {
350
+ for (const [partPath, xml] of partTexts) if (partPath === documentPartName || isTargetPart(partPath)) {
402
351
  const spelling = partSpelling(xml, partPath);
403
352
  spellings.set(partPath, spelling);
404
- collectFallbackParaIds(xml, partPath, spelling, seen);
353
+ collectFallbackParaIds(spelling, seen);
405
354
  } else collectExistingParaIds(xml, seen);
406
355
  const updates = /* @__PURE__ */ new Map();
356
+ const mintedParaIds = [];
407
357
  let assigned = 0;
408
358
  let deduplicated = 0;
409
359
  for (const partPath of targetParts) {
410
360
  const xml = partTexts.get(partPath);
411
361
  const spelling = spellings.get(partPath);
412
- const scan = scanPart(xml, partPath, spelling, context, seen);
413
- if (partPath === documentPartName && scan.paragraphs === 0) assertNoUnscannedParagraphs(xml, partPath);
362
+ const scan = scanPart({
363
+ partPath,
364
+ spelling,
365
+ context,
366
+ seen
367
+ });
414
368
  if (scan.edits.length === 0) continue;
415
369
  assigned += scan.assigned;
416
370
  deduplicated += scan.deduplicated;
417
- updates.set(partPath, applySplices(xml, [...scan.edits, ...ensureRootNamespaces(xml, partPath, spelling)], partPath));
371
+ for (const id of scan.minted) mintedParaIds.push(id);
372
+ updates.set(partPath, applySplices(xml, [...scan.edits, ...ensureRootNamespaces(spelling)], partPath));
418
373
  }
419
374
  if (updates.size === 0) return {
420
375
  docx: toUint8Array(docx),
421
376
  assigned: 0,
422
377
  deduplicated: 0,
423
- alreadyComplete: true
378
+ alreadyComplete: true,
379
+ mintedParaIds: []
424
380
  };
425
381
  const zip = await JSZip.loadAsync(docx);
426
- if (hasDigitalSignatureParts(zip) && options.allowSignedPackageMutation !== true) throw createEnsureParaIdsError("Refusing to normalize a digitally signed package because rewriting OOXML invalidates its signatures. Warn the user and pass allowSignedPackageMutation only if invalidation is acceptable.");
382
+ if (hasDigitalSignatureParts(zip) && options.allowSignedPackageMutation !== true) throw new EnsureParaIdsError({
383
+ reason: ENSURE_PARA_IDS_REASONS.SIGNED_PACKAGE,
384
+ message: "Refusing to normalize a digitally signed package because rewriting OOXML invalidates its signatures. Warn the user and pass allowSignedPackageMutation only if invalidation is acceptable."
385
+ });
427
386
  for (const [partPath, content] of updates) {
428
387
  const sourceEntry = zip.file(partPath);
429
388
  if (sourceEntry === null) throw createEnsureParaIdsError(`Package part disappeared during normalization: ${partPath}`);
@@ -441,7 +400,8 @@ const ensureParaIdsInternal = async (docx, options) => {
441
400
  }),
442
401
  assigned,
443
402
  deduplicated,
444
- alreadyComplete: false
403
+ alreadyComplete: false,
404
+ mintedParaIds
445
405
  };
446
406
  };
447
407
  /**
@@ -458,4 +418,4 @@ const ensureParaIds = async (docx, options = {}) => {
458
418
  }
459
419
  };
460
420
  //#endregion
461
- export { EnsureParaIdsError, ensureParaIds };
421
+ export { ENSURE_PARA_IDS_REASONS, EnsureParaIdsError, ensureParaIds };
@@ -0,0 +1,85 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ //#region src/docx/foldedListNumberFields.d.ts
3
+ /** Whether `content` is markup that may stand between a field and the tab that follows it. */
4
+ declare const isListNumberGapMarker: (content: document_d_exports.ParagraphContent) => boolean;
5
+ /** Whether `content` is a `LISTNUM` field, by its parsed type or its instruction. */
6
+ declare const isListNumberField: (content: document_d_exports.ParagraphContent) => content is document_d_exports.ComplexField;
7
+ /** A run that holds one tab and nothing else. */
8
+ declare const isTabOnlyRun: (content: document_d_exports.ParagraphContent) => content is document_d_exports.Run;
9
+ /** Whether `value` is what a capture stands for: a complex field, or a run. */
10
+ declare const isFoldedListNumber: (value: unknown) => value is document_d_exports.FoldedListNumber;
11
+ /** What `content` stands for when it is a capture the fold left, else nothing. */
12
+ declare const foldedListNumberOf: (content: document_d_exports.ParagraphContent) => document_d_exports.FoldedListNumber | undefined;
13
+ /** Whether `content` is a capture the fold left in place of a field or its tab. */
14
+ declare const isFoldedListNumberCapture: (content: document_d_exports.ParagraphContent) => boolean;
15
+ type ListNumberFieldFold = {
16
+ /** The content, each folded field and its tab replaced by a capture. */
17
+ content: document_d_exports.ParagraphContent[];
18
+ /** The cached display of each folded field that has one, in source order. */
19
+ cached: string[];
20
+ /** Every `LISTNUM` field the content holds, folded or not. */
21
+ fieldCount: number;
22
+ };
23
+ /**
24
+ * Replace the `LISTNUM` fields that open a paragraph, and the tab after each,
25
+ * by captures of the markup they were read from.
26
+ *
27
+ * Only what stands ahead of the first thing the line shows is folded: a field
28
+ * after text is on the line where the text is. `sourceMarkupOf` answers the
29
+ * markup; a field it has none for is left as the field it is, and ends the
30
+ * fold. Bookmark and comment markers between a field and its tab stay where
31
+ * they are, and the tab is still the field's when only they separate the two.
32
+ */
33
+ declare const foldListNumberFields: (content: readonly document_d_exports.ParagraphContent[], sourceMarkupOf: (item: document_d_exports.ComplexField | document_d_exports.Run) => string | undefined) => ListNumberFieldFold;
34
+ /** One item of a paragraph, as far as the fold needs to know it. */
35
+ type ListNumberFoldItem =
36
+ /** A capture of a field, with the text its cached result shows. */
37
+ {
38
+ kind: "field";
39
+ cached: string;
40
+ } |
41
+ /** A capture of the tab after a field. */
42
+ {
43
+ kind: "tab";
44
+ } |
45
+ /** Markup that shows nothing. */
46
+ {
47
+ kind: "hidden";
48
+ } |
49
+ /** Anything the line shows. */
50
+ {
51
+ kind: "shown";
52
+ };
53
+ type ListNumberFoldPlan = {
54
+ /** The items' indices in the order they are to stand in. */
55
+ order: number[];
56
+ /** Indices of the captures that stay hidden; every other capture goes on the line. */
57
+ hidden: ReadonlySet<number>;
58
+ /** What the marker shows after its own text, or nothing. */
59
+ suffix: string | undefined;
60
+ };
61
+ /**
62
+ * Decide which captures of a paragraph stay hidden, and what its marker shows.
63
+ *
64
+ * `markerShowsFields` says whether the paragraph's marker shows folded fields
65
+ * at all. When it does not, no capture is hidden.
66
+ *
67
+ * When it does, the captures it shows are the ones that open the paragraph:
68
+ * those ahead of the first thing the line shows, each field followed at most
69
+ * by its own tab. Text put in front of them was put in front of the body, not
70
+ * of the marker, so if the first capture no longer opens the paragraph, it
71
+ * and the captures that stand with it go back to the start. Every capture
72
+ * further in is on the line.
73
+ */
74
+ declare const planListNumberFold: (items: readonly ListNumberFoldItem[], markerShowsFields: boolean) => ListNumberFoldPlan;
75
+ /** The item a capture stands for, which shows on the line; any other item is itself. */
76
+ declare const unfoldedListNumberContent: (content: document_d_exports.ParagraphContent) => document_d_exports.ParagraphContent;
77
+ /**
78
+ * Bring `paragraph` to the one form the fold allows: the captures its marker
79
+ * shows stand at its start, every other capture is the field or the tab it
80
+ * stood for, and the marker shows exactly the fields still hidden behind it.
81
+ * A capture under a tracked change is never hidden.
82
+ */
83
+ declare const normalizeFoldedListNumbers: (paragraph: document_d_exports.Paragraph) => void;
84
+ //#endregion
85
+ export { ListNumberFieldFold, ListNumberFoldItem, ListNumberFoldPlan, foldListNumberFields, foldedListNumberOf, isFoldedListNumber, isFoldedListNumberCapture, isListNumberField, isListNumberGapMarker, isTabOnlyRun, normalizeFoldedListNumbers, planListNumberFold, unfoldedListNumberContent };