@stll/folio-core 0.54.2 → 0.56.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 (267) hide show
  1. package/dist/ai-edits/apply.js +185 -33
  2. package/dist/ai-edits/headless.js +3 -2
  3. package/dist/ai-edits/types.d.ts +7 -1
  4. package/dist/compare/compare.js +1 -0
  5. package/dist/compare/content-types.d.ts +3 -15
  6. package/dist/compare/reproducible-package.d.ts +0 -17
  7. package/dist/compare/reproducible-package.js +12 -18
  8. package/dist/controller/canonicalClipboard.d.ts +23 -0
  9. package/dist/controller/canonicalClipboard.js +476 -0
  10. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  11. package/dist/controller/canonicalClipboardResources.js +382 -0
  12. package/dist/controller/canonicalComposition.d.ts +53 -0
  13. package/dist/controller/canonicalComposition.js +296 -0
  14. package/dist/controller/canonicalInlineProjection.d.ts +113 -0
  15. package/dist/controller/canonicalInlineProjection.js +393 -0
  16. package/dist/controller/canonicalInput.d.ts +54 -0
  17. package/dist/controller/canonicalInput.js +377 -0
  18. package/dist/controller/canonicalInputTimer.d.ts +7 -0
  19. package/dist/controller/canonicalInputTimer.js +11 -0
  20. package/dist/controller/canonicalOperations.d.ts +22 -0
  21. package/dist/controller/canonicalOperations.js +85 -0
  22. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  23. package/dist/controller/canonicalPublicOperations.js +556 -0
  24. package/dist/controller/canonicalReview.d.ts +12 -0
  25. package/dist/controller/canonicalReview.js +19 -0
  26. package/dist/controller/canonicalSession.d.ts +167 -0
  27. package/dist/controller/canonicalSession.js +1186 -0
  28. package/dist/controller/canonicalStoryEditor.d.ts +36 -0
  29. package/dist/controller/canonicalStoryEditor.js +87 -0
  30. package/dist/controller/canonicalStructure.d.ts +16 -0
  31. package/dist/controller/canonicalStructure.js +322 -0
  32. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  33. package/dist/controller/canonicalTocSelection.js +43 -0
  34. package/dist/controller/folioEditor.d.ts +3 -10
  35. package/dist/controller/folioEditor.js +14 -4
  36. package/dist/controller/headerFooterEditorManager.d.ts +5 -0
  37. package/dist/controller/headerFooterEditorManager.js +72 -3
  38. package/dist/controller/hiddenEditorApi.d.ts +45 -0
  39. package/dist/controller/hiddenEditorApi.js +29 -8
  40. package/dist/controller/hiddenEditorManager.d.ts +13 -2
  41. package/dist/controller/hiddenEditorManager.js +639 -31
  42. package/dist/controller/layoutPipeline.d.ts +1 -1
  43. package/dist/controller/layoutPipeline.js +2 -0
  44. package/dist/controller/noteEditorManager.d.ts +5 -0
  45. package/dist/controller/noteEditorManager.js +114 -15
  46. package/dist/document-operations.d.ts +8 -1
  47. package/dist/document-operations.js +6 -4
  48. package/dist/docx/attributeRemainder.js +1 -0
  49. package/dist/docx/blockPlainText.d.ts +1 -1
  50. package/dist/docx/blockPlainText.js +11 -3
  51. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  52. package/dist/docx/canonicalResourceSave.js +27 -0
  53. package/dist/docx/canonicalSave.d.ts +29 -0
  54. package/dist/docx/canonicalSave.js +101 -0
  55. package/dist/docx/canonicalSessionInput.d.ts +11 -0
  56. package/dist/docx/canonicalSessionInput.js +27 -0
  57. package/dist/docx/canonicalStoryRepack.d.ts +10 -0
  58. package/dist/docx/canonicalStoryRepack.js +74 -0
  59. package/dist/docx/contentTypeRegistry.d.ts +8 -0
  60. package/dist/docx/contentTypeRegistry.js +64 -0
  61. package/dist/docx/documentParser.d.ts +1 -13
  62. package/dist/docx/documentParser.js +1 -47
  63. package/dist/docx/ensureParaIds.d.ts +7 -1
  64. package/dist/docx/ensureParaIds.js +173 -215
  65. package/dist/docx/foldedListNumberFields.d.ts +85 -0
  66. package/dist/docx/foldedListNumberFields.js +267 -0
  67. package/dist/docx/footnoteParser.d.ts +1 -11
  68. package/dist/docx/footnoteParser.js +1 -16
  69. package/dist/docx/headerFooterParser.d.ts +1 -9
  70. package/dist/docx/headerFooterParser.js +1 -12
  71. package/dist/docx/headerFooterVerbatim.d.ts +39 -1
  72. package/dist/docx/headerFooterVerbatim.js +173 -2
  73. package/dist/docx/imageParser.d.ts +13 -1
  74. package/dist/docx/imageParser.js +20 -11
  75. package/dist/docx/metadataPrivacy.js +6 -1
  76. package/dist/docx/noteIds.d.ts +5 -0
  77. package/dist/docx/noteIds.js +6 -2
  78. package/dist/docx/noteReferenceMark.d.ts +3 -1
  79. package/dist/docx/noteReferenceMark.js +15 -14
  80. package/dist/docx/numberingParser.d.ts +3 -1
  81. package/dist/docx/numberingParser.js +17 -3
  82. package/dist/docx/packageParts.d.ts +3 -1
  83. package/dist/docx/packageParts.js +74 -19
  84. package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
  85. package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
  86. package/dist/docx/paragraphParser.js +101 -53
  87. package/dist/docx/paragraphPropertySource.d.ts +15 -1
  88. package/dist/docx/paragraphPropertySource.js +77 -22
  89. package/dist/docx/parser.js +3 -1
  90. package/dist/docx/removeHeaderFooterParts.js +17 -6
  91. package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
  92. package/dist/docx/reviewHistoryNamespace.js +4 -0
  93. package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
  94. package/dist/docx/reviewResolutionProvenance.js +153 -0
  95. package/dist/docx/rezip.d.ts +54 -6
  96. package/dist/docx/rezip.js +835 -257
  97. package/dist/docx/runConsolidator.d.ts +2 -58
  98. package/dist/docx/runConsolidator.js +3 -175
  99. package/dist/docx/runParser.d.ts +2 -1
  100. package/dist/docx/runParser.js +156 -116
  101. package/dist/docx/saveDiagnostics.d.ts +22 -0
  102. package/dist/docx/saveDiagnostics.js +0 -0
  103. package/dist/docx/selectiveSave.d.ts +3 -1
  104. package/dist/docx/selectiveSave.js +36 -4
  105. package/dist/docx/selectiveXmlPatch.d.ts +16 -8
  106. package/dist/docx/selectiveXmlPatch.js +81 -7
  107. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  108. package/dist/docx/serializer/documentSerializer.js +29 -10
  109. package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
  110. package/dist/docx/serializer/headerFooterSerializer.js +19 -11
  111. package/dist/docx/serializer/noteSerializer.d.ts +2 -2
  112. package/dist/docx/serializer/noteSerializer.js +24 -8
  113. package/dist/docx/serializer/paragraphSerializer.js +41 -4
  114. package/dist/docx/serializer/partNamespaces.js +2 -1
  115. package/dist/docx/serializer/runSerializer.js +3 -1
  116. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  117. package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
  118. package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
  119. package/dist/docx/settingsParser.d.ts +1 -2
  120. package/dist/docx/settingsParser.js +5 -6
  121. package/dist/docx/storyBlockReplay.d.ts +31 -0
  122. package/dist/docx/storyBlockReplay.js +471 -0
  123. package/dist/docx/storyPlainText.d.ts +20 -0
  124. package/dist/docx/storyPlainText.js +23 -0
  125. package/dist/docx/streamingXmlParser.d.ts +22 -1
  126. package/dist/docx/streamingXmlParser.js +49 -3
  127. package/dist/docx/structuralXmlPatch.js +18 -23
  128. package/dist/docx/styleParser.d.ts +5 -1
  129. package/dist/docx/styleParser.js +1 -1
  130. package/dist/docx/tableParser.d.ts +1 -8
  131. package/dist/docx/tableParser.js +1 -28
  132. package/dist/docx/textBoxParser.d.ts +1 -5
  133. package/dist/docx/textBoxParser.js +1 -20
  134. package/dist/docx/unzip.js +6 -1
  135. package/dist/docx/verbatimCapture.d.ts +3 -1
  136. package/dist/docx/verbatimCapture.js +49 -6
  137. package/dist/i18n/messages/ar.gen.d.ts +5 -0
  138. package/dist/i18n/messages/ar.gen.js +656 -0
  139. package/dist/i18n/messages/cs.gen.d.ts +5 -0
  140. package/dist/i18n/messages/cs.gen.js +656 -0
  141. package/dist/i18n/messages/de.gen.d.ts +5 -0
  142. package/dist/i18n/messages/de.gen.js +656 -0
  143. package/dist/i18n/messages/en.gen.d.ts +5 -0
  144. package/dist/i18n/messages/en.gen.js +656 -0
  145. package/dist/i18n/messages/es.gen.d.ts +5 -0
  146. package/dist/i18n/messages/es.gen.js +656 -0
  147. package/dist/i18n/messages/et.gen.d.ts +5 -0
  148. package/dist/i18n/messages/et.gen.js +656 -0
  149. package/dist/i18n/messages/fr.gen.d.ts +5 -0
  150. package/dist/i18n/messages/fr.gen.js +656 -0
  151. package/dist/i18n/messages/he.gen.d.ts +5 -0
  152. package/dist/i18n/messages/he.gen.js +656 -0
  153. package/dist/i18n/messages/hi.gen.d.ts +5 -0
  154. package/dist/i18n/messages/hi.gen.js +656 -0
  155. package/dist/i18n/messages/hu.gen.d.ts +5 -0
  156. package/dist/i18n/messages/hu.gen.js +656 -0
  157. package/dist/i18n/messages/locales.d.ts +5 -0
  158. package/dist/i18n/messages/locales.js +22 -0
  159. package/dist/i18n/messages/lt.gen.d.ts +5 -0
  160. package/dist/i18n/messages/lt.gen.js +656 -0
  161. package/dist/i18n/messages/lv.gen.d.ts +5 -0
  162. package/dist/i18n/messages/lv.gen.js +656 -0
  163. package/dist/i18n/messages/pl.gen.d.ts +5 -0
  164. package/dist/i18n/messages/pl.gen.js +656 -0
  165. package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
  166. package/dist/i18n/messages/pt-BR.gen.js +656 -0
  167. package/dist/i18n/messages/sk.gen.d.ts +5 -0
  168. package/dist/i18n/messages/sk.gen.js +656 -0
  169. package/dist/i18n/messages/tr.gen.d.ts +5 -0
  170. package/dist/i18n/messages/tr.gen.js +656 -0
  171. package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
  172. package/dist/i18n/messages/zh-CN.gen.js +656 -0
  173. package/dist/i18n/messages.d.ts +2 -3
  174. package/dist/i18n/messages.js +1 -19
  175. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  176. package/dist/internal/acceptedBlockProjection.js +45 -0
  177. package/dist/internal/noteMarkerAttrs.d.ts +8 -0
  178. package/dist/internal/noteMarkerAttrs.js +16 -0
  179. package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
  180. package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
  181. package/dist/internal/prosemirrorAttrBoundary.js +50 -0
  182. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  183. package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
  184. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  185. package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
  186. package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
  187. package/dist/layout-bridge/convert/noteReferences.js +1 -6
  188. package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
  189. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
  190. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
  191. package/dist/managers/DocumentLoaderManager.d.ts +1 -0
  192. package/dist/managers/DocumentLoaderManager.js +9 -1
  193. package/dist/markdown/escape.d.ts +3 -1
  194. package/dist/markdown/escape.js +9 -2
  195. package/dist/markdown/renderBlock.js +20 -121
  196. package/dist/markdown/renderParagraph.js +13 -15
  197. package/dist/markdown/renderRuns.d.ts +7 -1
  198. package/dist/markdown/renderRuns.js +35 -15
  199. package/dist/markdown/renderTable.js +2 -0
  200. package/dist/markdown/types.d.ts +3 -9
  201. package/dist/prosemirror/attrs/index.d.ts +2 -12
  202. package/dist/prosemirror/attrs/index.js +22 -48
  203. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
  204. package/dist/prosemirror/canonicalCommands.d.ts +121 -0
  205. package/dist/prosemirror/canonicalCommands.js +35 -0
  206. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  207. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  208. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  209. package/dist/prosemirror/clipboardIntent.js +9 -0
  210. package/dist/prosemirror/commands/pageBreak.js +15 -1
  211. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  212. package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
  213. package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
  214. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  215. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  216. package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
  217. package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
  218. package/dist/prosemirror/conversion/toProseDoc.d.ts +11 -4
  219. package/dist/prosemirror/conversion/toProseDoc.js +55 -14
  220. package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
  221. package/dist/prosemirror/executeEditorCommand.js +26 -0
  222. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  223. package/dist/prosemirror/extensions/core/ParagraphExtension.js +88 -48
  224. package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
  225. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
  226. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
  227. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
  228. package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
  229. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  230. package/dist/prosemirror/extensions/marks/markUtils.js +72 -8
  231. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
  232. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
  233. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
  234. package/dist/prosemirror/insertOperations.js +2 -1
  235. package/dist/prosemirror/listMarker.d.ts +1 -0
  236. package/dist/prosemirror/listMarker.js +16 -7
  237. package/dist/prosemirror/listNumbering.js +26 -5
  238. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  239. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  240. package/dist/prosemirror/markupViewNotes.js +3 -2
  241. package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
  242. package/dist/prosemirror/noteReferenceReview.js +5 -3
  243. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  244. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  245. package/dist/prosemirror/plugins/suggestionMode.js +57 -7
  246. package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
  247. package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
  248. package/dist/prosemirror/runFormattingFromMarks.js +15 -1
  249. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
  250. package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
  251. package/dist/prosemirror/schema/marks.d.ts +2 -0
  252. package/dist/prosemirror/schema/nodes.d.ts +10 -2
  253. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
  254. package/dist/prosemirror/textInput.js +5 -1
  255. package/dist/prosemirror/trackedRevisionPath.js +2 -1
  256. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
  257. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
  258. package/dist/prosemirror/validation.js +4 -0
  259. package/dist/types/canonicalCapabilities.d.ts +215 -0
  260. package/dist/types/canonicalCapabilities.js +212 -0
  261. package/dist/types/canonicalSave.d.ts +11 -0
  262. package/dist/types/canonicalSave.js +0 -0
  263. package/dist/types/content.d.ts +2 -2
  264. package/dist/types/docxSerialization.d.ts +14 -0
  265. package/dist/types/docxSerialization.js +7 -0
  266. package/dist/utils/createDocument.js +2 -2
  267. package/package.json +16 -4
@@ -1,9 +1,12 @@
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";
9
+ import { writeZipPart } from "@stll/docx-core/zip";
7
10
  import JSZip from "jszip";
8
11
  //#region src/docx/ensureParaIds.ts
9
12
  /**
@@ -27,7 +30,7 @@ import JSZip from "jszip";
27
30
  * so documents carrying features folio's parser does not model come back
28
31
  * with those features byte-identical. Contract:
29
32
  *
30
- * - Parts covered: `word/document.xml`, `word/header*.xml`, `word/footer*.xml`,
33
+ * - Parts covered: the officeDocument relationship target, `word/header*.xml`, `word/footer*.xml`,
31
34
  * `word/footnotes.xml`, `word/endnotes.xml`. Paragraphs nested in table
32
35
  * cells and in `mc:Choice` text boxes are plain `<w:p>` elements inside
33
36
  * those parts and are covered by the same scan. The comments part mints its
@@ -50,12 +53,10 @@ import JSZip from "jszip";
50
53
  * declarations and a `mc:Ignorable` listing `w14` when missing — non-Word
51
54
  * producers declare neither, and absent `mc:Ignorable` handling is what
52
55
  * 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}.
56
+ * - Prefixes are aliases: elements and attributes resolve against their
57
+ * in-scope namespace URI, including Strict and Transitional profiles and
58
+ * nested rebinding. Minted attributes use a root prefix with no conflicting
59
+ * nested binding; a fresh prefix is declared when necessary.
59
60
  * - Idempotent: a document that already has full coverage is returned as the
60
61
  * original bytes, untouched (`alreadyComplete: true`). A body with
61
62
  * paragraphs the scan did not see is refused, never reported complete.
@@ -63,6 +64,11 @@ import JSZip from "jszip";
63
64
  * When normalization would rewrite the package, it fails unless the caller
64
65
  * explicitly allows signature invalidation after warning the user.
65
66
  */
67
+ const ENSURE_PARA_IDS_REASONS = {
68
+ NORMALIZATION_FAILED: "normalization-failed",
69
+ NAMESPACE_INVALID: "namespace-invalid",
70
+ SIGNED_PACKAGE: "signed-package"
71
+ };
66
72
  /** A malformed or unsupported package prevented paragraph-ID normalization. */
67
73
  var EnsureParaIdsError = class extends TaggedError("EnsureParaIdsError") {};
68
74
  const W14_NAMESPACE_URI = "http://schemas.microsoft.com/office/word/2010/wordml";
@@ -71,53 +77,15 @@ const MC_IGNORABLE_W14 = "w14";
71
77
  const DIGITAL_SIGNATURE_PART_PREFIX = "_xmlsignatures/";
72
78
  /** Word reads an all-zero `w14:paraId` as "no id assigned". */
73
79
  const RESERVED_ZERO_ID_PATTERN = /^0*$/u;
74
- const DOCUMENT_PART = "word/document.xml";
80
+ const PACKAGE_RELATIONSHIP_URI = "http://schemas.openxmlformats.org/package/2006/relationships";
75
81
  const TARGET_PART_PATTERNS = [
76
- /^word\/document\.xml$/u,
77
82
  /^word\/header\d*\.xml$/u,
78
83
  /^word\/footer\d*\.xml$/u,
79
84
  /^word\/footnotes\.xml$/u,
80
85
  /^word\/endnotes\.xml$/u
81
86
  ];
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
- };
87
+ /** Reserve package ids even in parts whose content the pass leaves untouched. */
88
+ const ANY_PARA_ID_PATTERN = /\s[^\s=<>/"':]+:paraId\s*=\s*(?<quote>["'])(?<id>[\s\S]*?)\k<quote>/gu;
121
89
  /**
122
90
  * Stamp the edits into the part through the splice owner, which refuses a
123
91
  * result that would leave a comment range with only one half. These edits
@@ -135,84 +103,98 @@ const applySplices = (xml, edits, partPath) => {
135
103
  return patched;
136
104
  };
137
105
  const createEnsureParaIdsError = (message, cause) => new EnsureParaIdsError({
106
+ reason: ENSURE_PARA_IDS_REASONS.NORMALIZATION_FAILED,
138
107
  message,
139
108
  ...cause === void 0 ? {} : { cause }
140
109
  });
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
110
  const collectExistingParaIds = (xml, into) => {
160
111
  for (const match of xml.matchAll(ANY_PARA_ID_PATTERN)) {
161
112
  const id = match.groups["id"];
162
113
  if (id.length > 0) into.add(id.toUpperCase());
163
114
  }
164
115
  };
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;
116
+ /** Resolve every element and attribute in its own scope; retain source offsets for splices. */
117
+ const partSpelling = (xml, partPath) => {
118
+ let root;
119
+ let rootNameEnd = 0;
120
+ let ignorable;
121
+ const paragraphs = [];
122
+ const fallbacks = /* @__PURE__ */ new WeakSet();
123
+ const bindings = /* @__PURE__ */ new Map();
124
+ if (scanStreamingXmlElements(xml, ({ element, attributeValueSpans: spans, nameEnd, parent }) => {
125
+ const unboundElement = (element.name ?? "").includes(":") && getNamespaceUri(element) === void 0;
126
+ const unboundAttribute = Object.keys(element.attributes ?? {}).some((attribute) => attribute.includes(":") && !attribute.startsWith("xmlns:") && resolveAttributeNamespaceUri(element, attribute) === void 0);
127
+ if (unboundElement || unboundAttribute) throw new EnsureParaIdsError({
128
+ reason: ENSURE_PARA_IDS_REASONS.NAMESPACE_INVALID,
129
+ message: `Undeclared namespace prefix in ${partPath}`
130
+ });
131
+ if (parent === void 0) {
132
+ if (root !== void 0) throw createEnsureParaIdsError(`Multiple roots in ${partPath}`);
133
+ root = element;
134
+ rootNameEnd = nameEnd;
180
135
  }
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;
136
+ for (const [name, value] of Object.entries(element.attributes ?? {})) {
137
+ if (name !== "xmlns" && !name.startsWith("xmlns:")) continue;
138
+ const prefix = name === "xmlns" ? "" : name.slice(6);
139
+ let uris = bindings.get(prefix);
140
+ if (uris === void 0) {
141
+ uris = /* @__PURE__ */ new Set();
142
+ bindings.set(prefix, uris);
143
+ }
144
+ uris.add(String(value));
187
145
  }
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;
146
+ const inFallback = parent !== void 0 && fallbacks.has(parent) || getLocalName(element.name ?? "") === "Fallback" && getNamespaceUri(element) === MC_NAMESPACE_URI;
147
+ if (inFallback) fallbacks.add(element);
148
+ let paraId;
149
+ let textId;
150
+ for (const [name, span] of spans) {
151
+ const namespace = resolveAttributeNamespaceUri(element, name);
152
+ const localName = getLocalName(name);
153
+ const value = String(element.attributes?.[name] ?? "");
154
+ if (element === root && namespace === MC_NAMESPACE_URI && localName === "Ignorable") ignorable = {
155
+ ...span,
156
+ value
157
+ };
158
+ if (namespace !== W14_NAMESPACE_URI && !WORDPROCESSINGML_NAMESPACE_URIS.has(namespace ?? "")) continue;
159
+ if (localName === "paraId") {
160
+ if (paraId === void 0 || namespace === W14_NAMESPACE_URI) paraId = {
161
+ ...span,
162
+ value
163
+ };
164
+ }
165
+ if (localName === "textId") textId = {
166
+ ...span,
167
+ value
168
+ };
193
169
  }
194
- const nameLength = matchOpenTag(xml, tagStart, prefixes.main, "p");
195
- if (nameLength === -1) {
196
- pos = tagStart + 1;
197
- continue;
170
+ if (getLocalName(element.name ?? "") === "p" && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "")) paragraphs.push({
171
+ nameEnd,
172
+ inFallback,
173
+ paraId,
174
+ textId
175
+ });
176
+ }).status === "unsupported" || root === void 0) throw createEnsureParaIdsError(`Malformed XML in ${partPath}`);
177
+ const rootElement = root;
178
+ const prefixFor = (uri, preferred) => {
179
+ for (const [prefix, boundUri] of rootElement.namespaceScope?.bindings ?? []) {
180
+ if (prefix === "" || boundUri !== uri) continue;
181
+ if (uri !== W14_NAMESPACE_URI || bindings.get(prefix)?.size === 1) return prefix;
198
182
  }
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
- }
183
+ let candidate = preferred;
184
+ for (let suffix = 1; bindings.has(candidate); suffix += 1) candidate = `${preferred}_${suffix}`;
185
+ return candidate;
186
+ };
187
+ return {
188
+ root,
189
+ rootNameEnd,
190
+ ignorable,
191
+ paragraphs,
192
+ w14Prefix: prefixFor(W14_NAMESPACE_URI, MC_IGNORABLE_W14),
193
+ mcPrefix: prefixFor(MC_NAMESPACE_URI, "mc")
194
+ };
195
+ };
196
+ const collectFallbackParaIds = (spelling, into) => {
197
+ for (const { inFallback, paraId } of spelling.paragraphs) if (inFallback && paraId?.value) into.add(paraId.value.toUpperCase());
216
198
  };
217
199
  /**
218
200
  * Deterministic fresh id for the paragraph at `ordinal` in `partPath`,
@@ -225,35 +207,27 @@ const mintParaId = (context, partPath, ordinal) => {
225
207
  context.taken.add(id);
226
208
  return id;
227
209
  };
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) => {
210
+ const scanPart = ({ partPath, spelling, context, seen }) => {
235
211
  const edits = [];
236
212
  const minted = [];
237
213
  const w14 = spelling.w14Prefix;
238
214
  let assigned = 0;
239
215
  let deduplicated = 0;
240
216
  let ordinal = 0;
241
- for (const { tagStart, tagEnd, nameEnd, inFallback } of paragraphOpenTags(xml, partPath, spelling)) {
217
+ for (const { nameEnd, inFallback, paraId, textId } of spelling.paragraphs) {
242
218
  if (inFallback) continue;
243
219
  ordinal += 1;
244
- const openTag = xml.slice(tagStart, tagEnd + 1);
245
- const paraIdMatch = spelling.paraId.exec(openTag);
246
- if (!paraIdMatch) {
220
+ if (paraId === void 0) {
247
221
  const id = mintParaId(context, partPath, ordinal);
248
- const textIdMatch = spelling.textId.exec(openTag);
249
- if (textIdMatch) {
222
+ if (textId !== void 0) {
250
223
  edits.push({
251
224
  start: nameEnd,
252
225
  end: nameEnd,
253
226
  text: ` ${w14}:paraId="${id}"`
254
227
  });
255
228
  edits.push({
256
- ...attributeValueRange(textIdMatch, tagStart, "value"),
229
+ start: textId.start,
230
+ end: textId.end,
257
231
  text: id
258
232
  });
259
233
  } else edits.push({
@@ -266,7 +240,7 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
266
240
  seen.add(id);
267
241
  continue;
268
242
  }
269
- const value = paraIdMatch.groups["value"].toUpperCase();
243
+ const value = paraId.value.toUpperCase();
270
244
  const unassigned = RESERVED_ZERO_ID_PATTERN.test(value);
271
245
  if (!unassigned && !seen.has(value)) {
272
246
  seen.add(value);
@@ -274,12 +248,13 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
274
248
  }
275
249
  const id = mintParaId(context, partPath, ordinal);
276
250
  edits.push({
277
- ...attributeValueRange(paraIdMatch, tagStart, "value"),
251
+ start: paraId.start,
252
+ end: paraId.end,
278
253
  text: id
279
254
  });
280
- const textIdMatch = spelling.textId.exec(openTag);
281
- if (textIdMatch) edits.push({
282
- ...attributeValueRange(textIdMatch, tagStart, "value"),
255
+ if (textId !== void 0) edits.push({
256
+ start: textId.start,
257
+ end: textId.end,
283
258
  text: id
284
259
  });
285
260
  else edits.push({
@@ -296,8 +271,7 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
296
271
  edits,
297
272
  minted,
298
273
  assigned,
299
- deduplicated,
300
- paragraphs: ordinal
274
+ deduplicated
301
275
  };
302
276
  };
303
277
  /**
@@ -305,71 +279,42 @@ const scanPart = (xml, partPath, spelling, context, seen) => {
305
279
  * `xmlns:mc` and lists `w14` in `mc:Ignorable`, so consumers that predate the
306
280
  * 2010 extensions skip the new attributes instead of rejecting the part.
307
281
  */
308
- const ensureRootNamespaces = (xml, partPath, { prefixes, w14Prefix }) => {
309
- let pos = 0;
310
- let rootStart = -1;
311
- while (pos < xml.length) {
312
- const lt = xml.indexOf("<", pos);
313
- if (lt === -1) break;
314
- const opaqueEnd = skipOpaqueXmlRegion(xml, lt, partPath);
315
- if (opaqueEnd !== null) {
316
- pos = opaqueEnd;
317
- continue;
318
- }
319
- if (xml[lt + 1] === "!") {
320
- const skipTo = xml.indexOf(">", lt);
321
- if (skipTo === -1) throw createEnsureParaIdsError(`Unterminated prolog in ${partPath}`);
322
- pos = skipTo + 1;
323
- continue;
324
- }
325
- rootStart = lt;
326
- break;
327
- }
328
- if (rootStart === -1) throw createEnsureParaIdsError(`No root element in ${partPath}`);
329
- const rootEnd = xml.indexOf(">", rootStart);
330
- if (rootEnd === -1) throw createEnsureParaIdsError(`Unterminated root element in ${partPath}`);
331
- const rootTag = xml.slice(rootStart, rootEnd + 1);
332
- if (!prefixes.mcDeclared && prefixes.mc.length === 0) throw createEnsureParaIdsError(`xmlns:mc has an unexpected namespace URI in ${partPath}`);
333
- if (!prefixes.w14Declared && prefixes.w14.length === 0) throw createEnsureParaIdsError(`xmlns:w14 has an unexpected namespace URI in ${partPath}`);
282
+ const ensureRootNamespaces = ({ root, rootNameEnd, ignorable, w14Prefix, mcPrefix }) => {
334
283
  const edits = [];
335
284
  const declarations = [];
336
- const mcPrefix = (prefixes.mcDeclared ? prefixes.mc[0] : void 0) ?? "mc";
337
- if (!prefixes.mcDeclared) declarations.push(` xmlns:mc="${MC_NAMESPACE_URI}"`);
338
- if (!prefixes.w14Declared) declarations.push(` xmlns:w14="${W14_NAMESPACE_URI}"`);
339
- const ignorable = prefixedAttributePattern(prefixes.mcDeclared ? prefixes.mc : [mcPrefix], "Ignorable").exec(rootTag);
340
- if (ignorable) {
341
- const tokens = ignorable.groups["value"].split(/\s+/u).filter((token) => token.length > 0);
342
- if (!tokens.includes(w14Prefix)) {
343
- const { end } = attributeValueRange(ignorable, rootStart, "value");
344
- const text = tokens.length === 0 ? w14Prefix : ` ${w14Prefix}`;
345
- edits.push({
346
- start: end,
347
- end,
348
- text
349
- });
350
- }
351
- } else declarations.push(` ${mcPrefix}:Ignorable="${w14Prefix}"`);
352
- if (declarations.length > 0) {
353
- let nameEnd = rootStart + 1;
354
- while (nameEnd < xml.length && !isXmlNameBoundary(xml[nameEnd])) nameEnd += 1;
355
- edits.push({
356
- start: nameEnd,
357
- end: nameEnd,
358
- text: declarations.join("")
285
+ if (resolveNamespaceUri(root.namespaceScope, mcPrefix) !== MC_NAMESPACE_URI) declarations.push(` xmlns:${mcPrefix}="${MC_NAMESPACE_URI}"`);
286
+ if (resolveNamespaceUri(root.namespaceScope, w14Prefix) !== W14_NAMESPACE_URI) declarations.push(` xmlns:${w14Prefix}="${W14_NAMESPACE_URI}"`);
287
+ if (ignorable !== void 0) {
288
+ const tokens = ignorable.value.split(/\s+/u).filter((token) => token.length > 0);
289
+ if (!tokens.includes(w14Prefix)) edits.push({
290
+ start: ignorable.end,
291
+ end: ignorable.end,
292
+ text: tokens.length === 0 ? w14Prefix : ` ${w14Prefix}`
359
293
  });
360
- }
294
+ } else declarations.push(` ${mcPrefix}:Ignorable="${w14Prefix}"`);
295
+ if (declarations.length > 0) edits.push({
296
+ start: rootNameEnd,
297
+ end: rootNameEnd,
298
+ text: declarations.join("")
299
+ });
361
300
  return edits;
362
301
  };
363
- const hasWordParagraph = (element) => getLocalName(element.name ?? "") === "p" && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "") || getChildElements(element).some(hasWordParagraph);
364
- /**
365
- * A body the string scan saw no paragraph in is either genuinely empty or
366
- * spelled in a way the scan missed. Only the namespace-aware parser can tell
367
- * which, and it only has to be asked in that rare case: `alreadyComplete`
368
- * must never describe paragraphs nobody looked at.
369
- */
370
- const assertNoUnscannedParagraphs = (xml, partPath) => {
371
- const root = parseXmlDocument(xml);
372
- 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`);
302
+ const mainDocumentPart = (rels, entries) => {
303
+ const root = parseXmlDocument(rels);
304
+ if (root === null || getLocalName(root.name ?? "") !== "Relationships" || getNamespaceUri(root) !== PACKAGE_RELATIONSHIP_URI) throw createEnsureParaIdsError("Malformed package relationships");
305
+ const documents = getChildElements(root).filter((element) => {
306
+ if (getLocalName(element.name ?? "") !== "Relationship" || getNamespaceUri(element) !== PACKAGE_RELATIONSHIP_URI) return false;
307
+ const type = getAttribute(element, null, "Type");
308
+ return [...OFFICE_RELATIONSHIP_NAMESPACE_URIS].some((uri) => type === `${uri}/officeDocument`);
309
+ });
310
+ const document = documents.at(0);
311
+ if (documents.length !== 1 || document === void 0 || getAttribute(document, null, "TargetMode") === "External") throw createEnsureParaIdsError("Package must have one internal officeDocument relationship");
312
+ const target = getAttribute(document, null, "Target");
313
+ const partPath = target === null ? void 0 : resolvePackageRelationshipTarget(target, "_rels/.rels");
314
+ if (partPath === void 0) throw createEnsureParaIdsError("Invalid officeDocument target");
315
+ const name = entries.find((entry) => entry.toLowerCase() === partPath);
316
+ if (name === void 0) throw createEnsureParaIdsError("officeDocument part not found");
317
+ return name;
373
318
  };
374
319
  /** OPC part names are case-insensitive; compare lowercased. */
375
320
  const isTargetPart = (path) => TARGET_PART_PATTERNS.some((pattern) => pattern.test(path.toLowerCase()));
@@ -382,12 +327,11 @@ const hasDigitalSignatureParts = (zip) => Object.values(zip.files).some(({ dir,
382
327
  */
383
328
  const ensureParaIdsInternal = async (docx, options) => {
384
329
  const archive = await loadDocxArchive(docx);
385
- const xmlPartNames = archive.entries.filter((relativePath) => {
386
- const lower = relativePath.toLowerCase();
387
- return lower.startsWith("word/") && lower.endsWith(".xml");
388
- });
389
- const documentPartName = xmlPartNames.find((name) => name.toLowerCase() === DOCUMENT_PART);
390
- if (documentPartName === void 0) throw createEnsureParaIdsError("word/document.xml not found: not a WordprocessingML package");
330
+ const relsPath = archive.entries.find((name) => name.toLowerCase() === "_rels/.rels");
331
+ const rels = relsPath === void 0 ? null : await archive.readEntryString(relsPath);
332
+ if (rels === null) throw createEnsureParaIdsError("Package relationships not found");
333
+ const documentPartName = mainDocumentPart(rels, archive.entries);
334
+ const xmlPartNames = archive.entries.filter((name) => name.toLowerCase().endsWith(".xml") || name === documentPartName);
391
335
  const partTexts = /* @__PURE__ */ new Map();
392
336
  for (const name of xmlPartNames) {
393
337
  const text = await archive.readEntryString(name);
@@ -395,17 +339,19 @@ const ensureParaIdsInternal = async (docx, options) => {
395
339
  }
396
340
  const taken = /* @__PURE__ */ new Set();
397
341
  for (const text of partTexts.values()) collectExistingParaIds(text, taken);
342
+ const documentXml = partTexts.get(documentPartName);
343
+ if (documentXml === void 0) throw createEnsureParaIdsError("officeDocument part is unreadable");
398
344
  const context = {
399
345
  taken,
400
- docKey: deterministicHexId(partTexts.get(documentPartName))
346
+ docKey: deterministicHexId(documentXml)
401
347
  };
402
348
  const targetParts = [documentPartName, ...xmlPartNames.filter((name) => name !== documentPartName && isTargetPart(name)).sort((a, b) => a.localeCompare(b))];
403
349
  const spellings = /* @__PURE__ */ new Map();
404
350
  const seen = /* @__PURE__ */ new Set();
405
- for (const [partPath, xml] of partTexts) if (isTargetPart(partPath)) {
351
+ for (const [partPath, xml] of partTexts) if (partPath === documentPartName || isTargetPart(partPath)) {
406
352
  const spelling = partSpelling(xml, partPath);
407
353
  spellings.set(partPath, spelling);
408
- collectFallbackParaIds(xml, partPath, spelling, seen);
354
+ collectFallbackParaIds(spelling, seen);
409
355
  } else collectExistingParaIds(xml, seen);
410
356
  const updates = /* @__PURE__ */ new Map();
411
357
  const mintedParaIds = [];
@@ -414,13 +360,17 @@ const ensureParaIdsInternal = async (docx, options) => {
414
360
  for (const partPath of targetParts) {
415
361
  const xml = partTexts.get(partPath);
416
362
  const spelling = spellings.get(partPath);
417
- const scan = scanPart(xml, partPath, spelling, context, seen);
418
- if (partPath === documentPartName && scan.paragraphs === 0) assertNoUnscannedParagraphs(xml, partPath);
363
+ const scan = scanPart({
364
+ partPath,
365
+ spelling,
366
+ context,
367
+ seen
368
+ });
419
369
  if (scan.edits.length === 0) continue;
420
370
  assigned += scan.assigned;
421
371
  deduplicated += scan.deduplicated;
422
372
  for (const id of scan.minted) mintedParaIds.push(id);
423
- updates.set(partPath, applySplices(xml, [...scan.edits, ...ensureRootNamespaces(xml, partPath, spelling)], partPath));
373
+ updates.set(partPath, applySplices(xml, [...scan.edits, ...ensureRootNamespaces(spelling)], partPath));
424
374
  }
425
375
  if (updates.size === 0) return {
426
376
  docx: toUint8Array(docx),
@@ -430,14 +380,22 @@ const ensureParaIdsInternal = async (docx, options) => {
430
380
  mintedParaIds: []
431
381
  };
432
382
  const zip = await JSZip.loadAsync(docx);
433
- 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.");
383
+ if (hasDigitalSignatureParts(zip) && options.allowSignedPackageMutation !== true) throw new EnsureParaIdsError({
384
+ reason: ENSURE_PARA_IDS_REASONS.SIGNED_PACKAGE,
385
+ 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."
386
+ });
434
387
  for (const [partPath, content] of updates) {
435
388
  const sourceEntry = zip.file(partPath);
436
389
  if (sourceEntry === null) throw createEnsureParaIdsError(`Package part disappeared during normalization: ${partPath}`);
437
- zip.file(partPath, content, {
438
- compression: "DEFLATE",
439
- compressionOptions: { level: 6 },
440
- date: sourceEntry.date
390
+ writeZipPart({
391
+ zip,
392
+ path: partPath,
393
+ data: content,
394
+ options: {
395
+ compression: "DEFLATE",
396
+ compressionOptions: { level: 6 },
397
+ date: sourceEntry.date
398
+ }
441
399
  });
442
400
  }
443
401
  return {
@@ -466,4 +424,4 @@ const ensureParaIds = async (docx, options = {}) => {
466
424
  }
467
425
  };
468
426
  //#endregion
469
- export { EnsureParaIdsError, ensureParaIds };
427
+ 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 };