@usejunior/docx-core 0.9.1 → 0.11.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 (335) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +2 -0
  3. package/README.md +2 -2
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/atomizer.d.ts +28 -8
  6. package/dist/atomizer.d.ts.map +1 -1
  7. package/dist/atomizer.js +96 -25
  8. package/dist/atomizer.js.map +1 -1
  9. package/dist/baselines/atomizer/auxiliaryIdCollision.d.ts +99 -0
  10. package/dist/baselines/atomizer/auxiliaryIdCollision.d.ts.map +1 -0
  11. package/dist/baselines/atomizer/auxiliaryIdCollision.js +415 -0
  12. package/dist/baselines/atomizer/auxiliaryIdCollision.js.map +1 -0
  13. package/dist/baselines/atomizer/documentReconstructor.d.ts.map +1 -1
  14. package/dist/baselines/atomizer/documentReconstructor.js +333 -112
  15. package/dist/baselines/atomizer/documentReconstructor.js.map +1 -1
  16. package/dist/baselines/atomizer/formattingFidelity.d.ts +99 -0
  17. package/dist/baselines/atomizer/formattingFidelity.d.ts.map +1 -0
  18. package/dist/baselines/atomizer/formattingFidelity.js +449 -0
  19. package/dist/baselines/atomizer/formattingFidelity.js.map +1 -0
  20. package/dist/baselines/atomizer/inPlaceModifier-bookmarks.d.ts +37 -0
  21. package/dist/baselines/atomizer/inPlaceModifier-bookmarks.d.ts.map +1 -0
  22. package/dist/baselines/atomizer/inPlaceModifier-bookmarks.js +189 -0
  23. package/dist/baselines/atomizer/inPlaceModifier-bookmarks.js.map +1 -0
  24. package/dist/baselines/atomizer/inPlaceModifier-containers.d.ts +74 -0
  25. package/dist/baselines/atomizer/inPlaceModifier-containers.d.ts.map +1 -0
  26. package/dist/baselines/atomizer/inPlaceModifier-containers.js +171 -0
  27. package/dist/baselines/atomizer/inPlaceModifier-containers.js.map +1 -0
  28. package/dist/baselines/atomizer/inPlaceModifier-deletion.d.ts +88 -0
  29. package/dist/baselines/atomizer/inPlaceModifier-deletion.d.ts.map +1 -0
  30. package/dist/baselines/atomizer/inPlaceModifier-deletion.js +326 -0
  31. package/dist/baselines/atomizer/inPlaceModifier-deletion.js.map +1 -0
  32. package/dist/baselines/atomizer/inPlaceModifier-postprocess.d.ts +85 -0
  33. package/dist/baselines/atomizer/inPlaceModifier-postprocess.d.ts.map +1 -0
  34. package/dist/baselines/atomizer/inPlaceModifier-postprocess.js +402 -0
  35. package/dist/baselines/atomizer/inPlaceModifier-postprocess.js.map +1 -0
  36. package/dist/baselines/atomizer/inPlaceModifier-presplit.d.ts +39 -0
  37. package/dist/baselines/atomizer/inPlaceModifier-presplit.d.ts.map +1 -0
  38. package/dist/baselines/atomizer/inPlaceModifier-presplit.js +265 -0
  39. package/dist/baselines/atomizer/inPlaceModifier-presplit.js.map +1 -0
  40. package/dist/baselines/atomizer/inPlaceModifier-shared.d.ts +62 -0
  41. package/dist/baselines/atomizer/inPlaceModifier-shared.d.ts.map +1 -0
  42. package/dist/baselines/atomizer/inPlaceModifier-shared.js +139 -0
  43. package/dist/baselines/atomizer/inPlaceModifier-shared.js.map +1 -0
  44. package/dist/baselines/atomizer/inPlaceModifier-wrappers.d.ts +198 -0
  45. package/dist/baselines/atomizer/inPlaceModifier-wrappers.d.ts.map +1 -0
  46. package/dist/baselines/atomizer/inPlaceModifier-wrappers.js +475 -0
  47. package/dist/baselines/atomizer/inPlaceModifier-wrappers.js.map +1 -0
  48. package/dist/baselines/atomizer/inPlaceModifier.d.ts +6 -290
  49. package/dist/baselines/atomizer/inPlaceModifier.d.ts.map +1 -1
  50. package/dist/baselines/atomizer/inPlaceModifier.js +23 -1828
  51. package/dist/baselines/atomizer/inPlaceModifier.js.map +1 -1
  52. package/dist/baselines/atomizer/pipeline.d.ts +36 -2
  53. package/dist/baselines/atomizer/pipeline.d.ts.map +1 -1
  54. package/dist/baselines/atomizer/pipeline.js +216 -144
  55. package/dist/baselines/atomizer/pipeline.js.map +1 -1
  56. package/dist/baselines/atomizer/trackChangesAcceptorAst.d.ts.map +1 -1
  57. package/dist/baselines/atomizer/trackChangesAcceptorAst.js +199 -173
  58. package/dist/baselines/atomizer/trackChangesAcceptorAst.js.map +1 -1
  59. package/dist/baselines/wmlcomparer/DotnetCli.d.ts.map +1 -1
  60. package/dist/baselines/wmlcomparer/DotnetCli.js +7 -0
  61. package/dist/baselines/wmlcomparer/DotnetCli.js.map +1 -1
  62. package/dist/cli/compare-two.d.ts.map +1 -1
  63. package/dist/cli/compare-two.js +3 -1
  64. package/dist/cli/compare-two.js.map +1 -1
  65. package/dist/cli/conformance-adapter.d.ts +3 -0
  66. package/dist/cli/conformance-adapter.d.ts.map +1 -0
  67. package/dist/cli/conformance-adapter.js +93 -0
  68. package/dist/cli/conformance-adapter.js.map +1 -0
  69. package/dist/cli/index.d.ts.map +1 -1
  70. package/dist/cli/index.js +5 -1
  71. package/dist/cli/index.js.map +1 -1
  72. package/dist/compare-types.d.ts +197 -0
  73. package/dist/compare-types.d.ts.map +1 -0
  74. package/dist/compare-types.js +2 -0
  75. package/dist/compare-types.js.map +1 -0
  76. package/dist/core-types.d.ts +5 -1
  77. package/dist/core-types.d.ts.map +1 -1
  78. package/dist/core-types.js +5 -1
  79. package/dist/core-types.js.map +1 -1
  80. package/dist/footnotes.d.ts +8 -3
  81. package/dist/footnotes.d.ts.map +1 -1
  82. package/dist/footnotes.js +8 -3
  83. package/dist/footnotes.js.map +1 -1
  84. package/dist/generation/compile.d.ts +21 -0
  85. package/dist/generation/compile.d.ts.map +1 -0
  86. package/dist/generation/compile.js +46 -0
  87. package/dist/generation/compile.js.map +1 -0
  88. package/dist/generation/context.d.ts +42 -0
  89. package/dist/generation/context.d.ts.map +1 -0
  90. package/dist/generation/context.js +65 -0
  91. package/dist/generation/context.js.map +1 -0
  92. package/dist/generation/emit/comments-part.d.ts +36 -0
  93. package/dist/generation/emit/comments-part.d.ts.map +1 -0
  94. package/dist/generation/emit/comments-part.js +116 -0
  95. package/dist/generation/emit/comments-part.js.map +1 -0
  96. package/dist/generation/emit/document-part.d.ts +24 -0
  97. package/dist/generation/emit/document-part.d.ts.map +1 -0
  98. package/dist/generation/emit/document-part.js +60 -0
  99. package/dist/generation/emit/document-part.js.map +1 -0
  100. package/dist/generation/emit/emit-context.d.ts +26 -0
  101. package/dist/generation/emit/emit-context.d.ts.map +1 -0
  102. package/dist/generation/emit/emit-context.js +19 -0
  103. package/dist/generation/emit/emit-context.js.map +1 -0
  104. package/dist/generation/emit/header-footer-part.d.ts +23 -0
  105. package/dist/generation/emit/header-footer-part.d.ts.map +1 -0
  106. package/dist/generation/emit/header-footer-part.js +57 -0
  107. package/dist/generation/emit/header-footer-part.js.map +1 -0
  108. package/dist/generation/emit/numbering-part.d.ts +29 -0
  109. package/dist/generation/emit/numbering-part.d.ts.map +1 -0
  110. package/dist/generation/emit/numbering-part.js +100 -0
  111. package/dist/generation/emit/numbering-part.js.map +1 -0
  112. package/dist/generation/emit/package-parts.d.ts +24 -0
  113. package/dist/generation/emit/package-parts.d.ts.map +1 -0
  114. package/dist/generation/emit/package-parts.js +121 -0
  115. package/dist/generation/emit/package-parts.js.map +1 -0
  116. package/dist/generation/emit/paragraph.d.ts +24 -0
  117. package/dist/generation/emit/paragraph.d.ts.map +1 -0
  118. package/dist/generation/emit/paragraph.js +63 -0
  119. package/dist/generation/emit/paragraph.js.map +1 -0
  120. package/dist/generation/emit/properties.d.ts +34 -0
  121. package/dist/generation/emit/properties.d.ts.map +1 -0
  122. package/dist/generation/emit/properties.js +138 -0
  123. package/dist/generation/emit/properties.js.map +1 -0
  124. package/dist/generation/emit/run.d.ts +15 -0
  125. package/dist/generation/emit/run.d.ts.map +1 -0
  126. package/dist/generation/emit/run.js +71 -0
  127. package/dist/generation/emit/run.js.map +1 -0
  128. package/dist/generation/emit/section.d.ts +29 -0
  129. package/dist/generation/emit/section.d.ts.map +1 -0
  130. package/dist/generation/emit/section.js +117 -0
  131. package/dist/generation/emit/section.js.map +1 -0
  132. package/dist/generation/emit/settings-part.d.ts +13 -0
  133. package/dist/generation/emit/settings-part.d.ts.map +1 -0
  134. package/dist/generation/emit/settings-part.js +24 -0
  135. package/dist/generation/emit/settings-part.js.map +1 -0
  136. package/dist/generation/emit/styles-part.d.ts +16 -0
  137. package/dist/generation/emit/styles-part.d.ts.map +1 -0
  138. package/dist/generation/emit/styles-part.js +80 -0
  139. package/dist/generation/emit/styles-part.js.map +1 -0
  140. package/dist/generation/emit/table.d.ts +26 -0
  141. package/dist/generation/emit/table.d.ts.map +1 -0
  142. package/dist/generation/emit/table.js +196 -0
  143. package/dist/generation/emit/table.js.map +1 -0
  144. package/dist/generation/errors.d.ts +22 -0
  145. package/dist/generation/errors.d.ts.map +1 -0
  146. package/dist/generation/errors.js +29 -0
  147. package/dist/generation/errors.js.map +1 -0
  148. package/dist/generation/index.d.ts +13 -0
  149. package/dist/generation/index.d.ts.map +1 -0
  150. package/dist/generation/index.js +12 -0
  151. package/dist/generation/index.js.map +1 -0
  152. package/dist/generation/ordering.d.ts +46 -0
  153. package/dist/generation/ordering.d.ts.map +1 -0
  154. package/dist/generation/ordering.js +119 -0
  155. package/dist/generation/ordering.js.map +1 -0
  156. package/dist/generation/recipes.d.ts +47 -0
  157. package/dist/generation/recipes.d.ts.map +1 -0
  158. package/dist/generation/recipes.js +84 -0
  159. package/dist/generation/recipes.js.map +1 -0
  160. package/dist/generation/structural-checks.d.ts +24 -0
  161. package/dist/generation/structural-checks.d.ts.map +1 -0
  162. package/dist/generation/structural-checks.js +318 -0
  163. package/dist/generation/structural-checks.js.map +1 -0
  164. package/dist/generation/types.d.ts +217 -0
  165. package/dist/generation/types.d.ts.map +1 -0
  166. package/dist/generation/types.js +16 -0
  167. package/dist/generation/types.js.map +1 -0
  168. package/dist/generation/validate-spec.d.ts +27 -0
  169. package/dist/generation/validate-spec.d.ts.map +1 -0
  170. package/dist/generation/validate-spec.js +307 -0
  171. package/dist/generation/validate-spec.js.map +1 -0
  172. package/dist/index.d.ts +9 -150
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.js +14 -0
  175. package/dist/index.js.map +1 -1
  176. package/dist/integration/generation-probes.d.ts +15 -0
  177. package/dist/integration/generation-probes.d.ts.map +1 -0
  178. package/dist/integration/generation-probes.js +84 -0
  179. package/dist/integration/generation-probes.js.map +1 -0
  180. package/dist/integration/libreoffice-oracle.d.ts +49 -0
  181. package/dist/integration/libreoffice-oracle.d.ts.map +1 -0
  182. package/dist/integration/libreoffice-oracle.js +290 -0
  183. package/dist/integration/libreoffice-oracle.js.map +1 -0
  184. package/dist/integration/synthetic-docx-fixture.d.ts +72 -0
  185. package/dist/integration/synthetic-docx-fixture.d.ts.map +1 -1
  186. package/dist/integration/synthetic-docx-fixture.js +131 -4
  187. package/dist/integration/synthetic-docx-fixture.js.map +1 -1
  188. package/dist/primitives/accept_changes.d.ts +4 -3
  189. package/dist/primitives/accept_changes.d.ts.map +1 -1
  190. package/dist/primitives/accept_changes.js +163 -77
  191. package/dist/primitives/accept_changes.js.map +1 -1
  192. package/dist/primitives/comments.d.ts +12 -3
  193. package/dist/primitives/comments.d.ts.map +1 -1
  194. package/dist/primitives/comments.js +374 -97
  195. package/dist/primitives/comments.js.map +1 -1
  196. package/dist/primitives/content_fingerprint.d.ts +29 -0
  197. package/dist/primitives/content_fingerprint.d.ts.map +1 -0
  198. package/dist/primitives/content_fingerprint.js +63 -0
  199. package/dist/primitives/content_fingerprint.js.map +1 -0
  200. package/dist/primitives/document.d.ts +94 -15
  201. package/dist/primitives/document.d.ts.map +1 -1
  202. package/dist/primitives/document.js +373 -36
  203. package/dist/primitives/document.js.map +1 -1
  204. package/dist/primitives/document_view-comments.d.ts +18 -0
  205. package/dist/primitives/document_view-comments.d.ts.map +1 -0
  206. package/dist/primitives/document_view-comments.js +160 -0
  207. package/dist/primitives/document_view-comments.js.map +1 -0
  208. package/dist/primitives/document_view-headings.d.ts +45 -0
  209. package/dist/primitives/document_view-headings.d.ts.map +1 -0
  210. package/dist/primitives/document_view-headings.js +247 -0
  211. package/dist/primitives/document_view-headings.js.map +1 -0
  212. package/dist/primitives/document_view-styles.d.ts +11 -0
  213. package/dist/primitives/document_view-styles.d.ts.map +1 -0
  214. package/dist/primitives/document_view-styles.js +104 -0
  215. package/dist/primitives/document_view-styles.js.map +1 -0
  216. package/dist/primitives/document_view-toon.d.ts +37 -0
  217. package/dist/primitives/document_view-toon.d.ts.map +1 -0
  218. package/dist/primitives/document_view-toon.js +199 -0
  219. package/dist/primitives/document_view-toon.js.map +1 -0
  220. package/dist/primitives/document_view-types.d.ts +152 -0
  221. package/dist/primitives/document_view-types.d.ts.map +1 -0
  222. package/dist/primitives/document_view-types.js +2 -0
  223. package/dist/primitives/document_view-types.js.map +1 -0
  224. package/dist/primitives/document_view.d.ts +8 -106
  225. package/dist/primitives/document_view.d.ts.map +1 -1
  226. package/dist/primitives/document_view.js +153 -312
  227. package/dist/primitives/document_view.js.map +1 -1
  228. package/dist/primitives/dom-helpers.d.ts +9 -0
  229. package/dist/primitives/dom-helpers.d.ts.map +1 -1
  230. package/dist/primitives/dom-helpers.js +10 -1
  231. package/dist/primitives/dom-helpers.js.map +1 -1
  232. package/dist/primitives/footnotes.d.ts +4 -3
  233. package/dist/primitives/footnotes.d.ts.map +1 -1
  234. package/dist/primitives/footnotes.js +232 -44
  235. package/dist/primitives/footnotes.js.map +1 -1
  236. package/dist/primitives/formatting_tags.d.ts +7 -0
  237. package/dist/primitives/formatting_tags.d.ts.map +1 -1
  238. package/dist/primitives/formatting_tags.js +22 -11
  239. package/dist/primitives/formatting_tags.js.map +1 -1
  240. package/dist/primitives/index.d.ts +10 -0
  241. package/dist/primitives/index.d.ts.map +1 -1
  242. package/dist/primitives/index.js +9 -0
  243. package/dist/primitives/index.js.map +1 -1
  244. package/dist/primitives/layout.d.ts +4 -3
  245. package/dist/primitives/layout.d.ts.map +1 -1
  246. package/dist/primitives/layout.js +45 -3
  247. package/dist/primitives/layout.js.map +1 -1
  248. package/dist/primitives/merge_runs.d.ts +21 -3
  249. package/dist/primitives/merge_runs.d.ts.map +1 -1
  250. package/dist/primitives/merge_runs.js +32 -10
  251. package/dist/primitives/merge_runs.js.map +1 -1
  252. package/dist/primitives/minimal_save.d.ts +38 -0
  253. package/dist/primitives/minimal_save.d.ts.map +1 -0
  254. package/dist/primitives/minimal_save.js +323 -0
  255. package/dist/primitives/minimal_save.js.map +1 -0
  256. package/dist/primitives/namespaces.d.ts +47 -0
  257. package/dist/primitives/namespaces.d.ts.map +1 -1
  258. package/dist/primitives/namespaces.js +52 -0
  259. package/dist/primitives/namespaces.js.map +1 -1
  260. package/dist/primitives/reject_changes.d.ts +6 -4
  261. package/dist/primitives/reject_changes.d.ts.map +1 -1
  262. package/dist/primitives/reject_changes.js +187 -91
  263. package/dist/primitives/reject_changes.js.map +1 -1
  264. package/dist/primitives/revision-parts.d.ts +7 -0
  265. package/dist/primitives/revision-parts.d.ts.map +1 -0
  266. package/dist/primitives/revision-parts.js +27 -0
  267. package/dist/primitives/revision-parts.js.map +1 -0
  268. package/dist/primitives/revision-vocabulary.d.ts +7 -0
  269. package/dist/primitives/revision-vocabulary.d.ts.map +1 -0
  270. package/dist/primitives/revision-vocabulary.js +39 -0
  271. package/dist/primitives/revision-vocabulary.js.map +1 -0
  272. package/dist/primitives/schema-corpus-capture.d.ts +19 -0
  273. package/dist/primitives/schema-corpus-capture.d.ts.map +1 -0
  274. package/dist/primitives/schema-corpus-capture.js +29 -0
  275. package/dist/primitives/schema-corpus-capture.js.map +1 -0
  276. package/dist/primitives/sectPrAudit.d.ts +19 -0
  277. package/dist/primitives/sectPrAudit.d.ts.map +1 -0
  278. package/dist/primitives/sectPrAudit.js +165 -0
  279. package/dist/primitives/sectPrAudit.js.map +1 -0
  280. package/dist/primitives/semantic_tags.d.ts +7 -0
  281. package/dist/primitives/semantic_tags.d.ts.map +1 -1
  282. package/dist/primitives/semantic_tags.js +23 -4
  283. package/dist/primitives/semantic_tags.js.map +1 -1
  284. package/dist/primitives/serialize_html.d.ts +37 -0
  285. package/dist/primitives/serialize_html.d.ts.map +1 -0
  286. package/dist/primitives/serialize_html.js +395 -0
  287. package/dist/primitives/serialize_html.js.map +1 -0
  288. package/dist/primitives/serialize_markdown.d.ts +16 -0
  289. package/dist/primitives/serialize_markdown.d.ts.map +1 -0
  290. package/dist/primitives/serialize_markdown.js +300 -0
  291. package/dist/primitives/serialize_markdown.js.map +1 -0
  292. package/dist/primitives/serialize_plaintext.d.ts +15 -0
  293. package/dist/primitives/serialize_plaintext.d.ts.map +1 -0
  294. package/dist/primitives/serialize_plaintext.js +154 -0
  295. package/dist/primitives/serialize_plaintext.js.map +1 -0
  296. package/dist/primitives/styles.d.ts +15 -0
  297. package/dist/primitives/styles.d.ts.map +1 -1
  298. package/dist/primitives/styles.js +33 -22
  299. package/dist/primitives/styles.js.map +1 -1
  300. package/dist/primitives/tables.d.ts.map +1 -1
  301. package/dist/primitives/tables.js +13 -3
  302. package/dist/primitives/tables.js.map +1 -1
  303. package/dist/primitives/text.d.ts +2 -1
  304. package/dist/primitives/text.d.ts.map +1 -1
  305. package/dist/primitives/text.js +116 -12
  306. package/dist/primitives/text.js.map +1 -1
  307. package/dist/primitives/track-changes-emitter.d.ts +148 -0
  308. package/dist/primitives/track-changes-emitter.d.ts.map +1 -0
  309. package/dist/primitives/track-changes-emitter.js +291 -0
  310. package/dist/primitives/track-changes-emitter.js.map +1 -0
  311. package/dist/primitives/validate_ai_revisions.d.ts +35 -0
  312. package/dist/primitives/validate_ai_revisions.d.ts.map +1 -0
  313. package/dist/primitives/validate_ai_revisions.js +323 -0
  314. package/dist/primitives/validate_ai_revisions.js.map +1 -0
  315. package/dist/primitives/xml-helpers.d.ts +29 -0
  316. package/dist/primitives/xml-helpers.d.ts.map +1 -0
  317. package/dist/primitives/xml-helpers.js +35 -0
  318. package/dist/primitives/xml-helpers.js.map +1 -0
  319. package/dist/primitives/xml.d.ts +5 -0
  320. package/dist/primitives/xml.d.ts.map +1 -1
  321. package/dist/primitives/xml.js +5 -0
  322. package/dist/primitives/xml.js.map +1 -1
  323. package/dist/primitives/zip.d.ts +1 -0
  324. package/dist/primitives/zip.d.ts.map +1 -1
  325. package/dist/primitives/zip.js +21 -3
  326. package/dist/primitives/zip.js.map +1 -1
  327. package/dist/shared/field-structure.d.ts +14 -0
  328. package/dist/shared/field-structure.d.ts.map +1 -0
  329. package/dist/shared/field-structure.js +166 -0
  330. package/dist/shared/field-structure.js.map +1 -0
  331. package/dist/shared/ooxml/namespaces.d.ts +4 -1
  332. package/dist/shared/ooxml/namespaces.d.ts.map +1 -1
  333. package/dist/shared/ooxml/namespaces.js +4 -1
  334. package/dist/shared/ooxml/namespaces.js.map +1 -1
  335. package/package.json +13 -9
@@ -1,21 +1,101 @@
1
1
  import { DocxZip } from './zip.js';
2
2
  import { parseXml, serializeXml } from './xml.js';
3
+ import { maybeCaptureEmittedDocumentXml } from './schema-corpus-capture.js';
3
4
  import { OOXML, W } from './namespaces.js';
4
- import { isW, getDirectChildrenByName } from './dom-helpers.js';
5
+ import { createWmlElement, isW, getDirectChildrenByName } from './dom-helpers.js';
5
6
  import { findParagraphByBookmarkId, insertParagraphBookmarks, cleanupInternalBookmarks, getParagraphBookmarkId, insertSingleParagraphBookmark, } from './bookmarks.js';
6
7
  import { getParagraphRuns, getParagraphText, replaceParagraphTextRange } from './text.js';
8
+ import { allocateRevisionId, createRevisionContainer, } from './track-changes-emitter.js';
7
9
  import { buildNodesForDocumentView } from './document_view.js';
10
+ import { serializeToMarkdown } from './serialize_markdown.js';
11
+ import { serializeToHtml } from './serialize_html.js';
12
+ import { serializeToPlainText } from './serialize_plaintext.js';
13
+ import { parseStylesXml } from './styles.js';
14
+ import { parseNumberingXml } from './numbering.js';
8
15
  import { findUniqueSubstringMatch } from './matching.js';
9
16
  import { parseDocumentRels } from './relationships.js';
10
17
  import { setParagraphSpacing, setTableCellPadding, setTableRowHeight, } from './layout.js';
11
18
  import { extractTables, } from './tables.js';
12
19
  import { mergeRuns } from './merge_runs.js';
20
+ import { restoreUntouchedBlocks } from './minimal_save.js';
13
21
  import { simplifyRedlines } from './simplify_redlines.js';
14
22
  import { preventDoubleElevation } from './prevent_double_elevation.js';
15
23
  import { validateDocument } from './validate_document.js';
24
+ import { validateAiRevisions as validateAiRevisionsImpl, } from './validate_ai_revisions.js';
25
+ import { enumerateRevisionStoryPartPaths, REVISION_STORY_PART_PATHS, } from './revision-parts.js';
16
26
  import { acceptChanges as acceptChangesImpl } from './accept_changes.js';
27
+ import { rejectChanges as rejectChangesImpl } from './reject_changes.js';
17
28
  import { bootstrapCommentParts, addComment as addCommentImpl, addCommentReply as addCommentReplyImpl, getComments as getCommentsImpl, getComment as getCommentImpl, deleteComment as deleteCommentImpl, } from './comments.js';
18
29
  import { bootstrapFootnoteParts, getFootnotes as getFootnotesImpl, getFootnote as getFootnoteImpl, addFootnote as addFootnoteImpl, updateFootnoteText as updateFootnoteTextImpl, deleteFootnote as deleteFootnoteImpl, } from './footnotes.js';
30
+ function emptyAcceptChangesResult() {
31
+ return { insertionsAccepted: 0, deletionsAccepted: 0, movesResolved: 0, propertyChangesResolved: 0 };
32
+ }
33
+ function hasAcceptedChanges(result) {
34
+ return (result.insertionsAccepted > 0 ||
35
+ result.deletionsAccepted > 0 ||
36
+ result.movesResolved > 0 ||
37
+ result.propertyChangesResolved > 0);
38
+ }
39
+ function addAcceptChangesResult(total, result) {
40
+ total.insertionsAccepted += result.insertionsAccepted;
41
+ total.deletionsAccepted += result.deletionsAccepted;
42
+ total.movesResolved += result.movesResolved;
43
+ total.propertyChangesResolved += result.propertyChangesResolved;
44
+ }
45
+ function emptyRejectChangesResult() {
46
+ return { insertionsRemoved: 0, deletionsRestored: 0, movesReverted: 0, propertyChangesReverted: 0 };
47
+ }
48
+ function hasRejectedChanges(result) {
49
+ return (result.insertionsRemoved > 0 ||
50
+ result.deletionsRestored > 0 ||
51
+ result.movesReverted > 0 ||
52
+ result.propertyChangesReverted > 0);
53
+ }
54
+ function addRejectChangesResult(total, result) {
55
+ total.insertionsRemoved += result.insertionsRemoved;
56
+ total.deletionsRestored += result.deletionsRestored;
57
+ total.movesReverted += result.movesReverted;
58
+ total.propertyChangesReverted += result.propertyChangesReverted;
59
+ }
60
+ function parseWId(el) {
61
+ const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
62
+ if (!idStr)
63
+ return null;
64
+ const n = parseInt(idStr, 10);
65
+ return Number.isNaN(n) ? null : n;
66
+ }
67
+ function collectLiveFootnoteRefIds(doc) {
68
+ const ids = new Set();
69
+ const refs = doc.getElementsByTagNameNS(OOXML.W_NS, W.footnoteReference);
70
+ for (let i = 0; i < refs.length; i++) {
71
+ const id = parseWId(refs.item(i));
72
+ if (id !== null)
73
+ ids.add(id);
74
+ }
75
+ return ids;
76
+ }
77
+ // Side-effect of accept/reject on document.xml: a body w:footnoteReference that
78
+ // lived inside a removed w:del (accept) or w:ins (reject) is gone afterwards.
79
+ // The corresponding <w:footnote w:id=N> in footnotes.xml is then unreachable —
80
+ // remove it so the side part matches the post-sweep body. Reserved separator /
81
+ // continuationSeparator entries are preserved unconditionally.
82
+ function pruneOrphanedFootnotes(footnotesDoc, liveRefIds) {
83
+ const entries = Array.from(footnotesDoc.getElementsByTagNameNS(OOXML.W_NS, W.footnote));
84
+ let pruned = 0;
85
+ for (const fn of entries) {
86
+ const typ = fn.getAttributeNS(OOXML.W_NS, 'type') ?? fn.getAttribute('w:type');
87
+ if (typ === W.separator || typ === W.continuationSeparator)
88
+ continue;
89
+ const id = parseWId(fn);
90
+ if (id === null)
91
+ continue;
92
+ if (liveRefIds.has(id))
93
+ continue;
94
+ fn.parentNode?.removeChild(fn);
95
+ pruned++;
96
+ }
97
+ return pruned;
98
+ }
19
99
  function prevElementSibling(node) {
20
100
  let cur = node?.previousSibling ?? null;
21
101
  while (cur) {
@@ -228,7 +308,13 @@ export class DocxDocument {
228
308
  relsMap;
229
309
  dirty;
230
310
  documentViewCache;
231
- constructor(zip, documentXml, stylesXml, numberingXml, footnotesXml, relsMap) {
311
+ /**
312
+ * Raw document.xml text as loaded, before normalize()/edits mutate the DOM.
313
+ * Reference for minimal re-serialization in toBuffer(); null for instances
314
+ * not created via load().
315
+ */
316
+ originalDocumentXmlText;
317
+ constructor(zip, documentXml, stylesXml, numberingXml, footnotesXml, relsMap, originalDocumentXmlText = null) {
232
318
  this.zip = zip;
233
319
  this.documentXml = documentXml;
234
320
  this.stylesXml = stylesXml;
@@ -237,6 +323,7 @@ export class DocxDocument {
237
323
  this.relsMap = relsMap;
238
324
  this.dirty = false;
239
325
  this.documentViewCache = null;
326
+ this.originalDocumentXmlText = originalDocumentXmlText;
240
327
  }
241
328
  static async load(buffer) {
242
329
  const zip = await DocxZip.load(buffer);
@@ -253,7 +340,7 @@ export class DocxDocument {
253
340
  // Load document relationships for hyperlink resolution.
254
341
  const relsText = await zip.readTextOrNull('word/_rels/document.xml.rels');
255
342
  const relsMap = relsText ? parseDocumentRels(parseXml(relsText)) : new Map();
256
- return new DocxDocument(zip, doc, stylesXml, numberingXml, footnotesXml, relsMap);
343
+ return new DocxDocument(zip, doc, stylesXml, numberingXml, footnotesXml, relsMap, xml);
257
344
  }
258
345
  getParagraphs() {
259
346
  const body = this.documentXml.getElementsByTagNameNS(OOXML.W_NS, W.body).item(0);
@@ -310,20 +397,96 @@ export class DocxDocument {
310
397
  validate() {
311
398
  return validateDocument(this.documentXml);
312
399
  }
400
+ async validateAiRevisions(aiAuthor, touched) {
401
+ const stories = [{ part: 'word/document.xml', doc: this.documentXml }];
402
+ for (const partPath of enumerateRevisionStoryPartPaths(this.zip)) {
403
+ const xml = await this.zip.readTextOrNull(partPath);
404
+ if (!xml)
405
+ continue;
406
+ stories.push({ part: partPath, doc: parseXml(xml) });
407
+ }
408
+ return validateAiRevisionsImpl({
409
+ aiAuthor,
410
+ stories,
411
+ packageZip: this.zip,
412
+ touched,
413
+ });
414
+ }
313
415
  /**
314
- * Accept all tracked changes in the document body, producing a clean
315
- * document with no revision markup.
416
+ * Accept all tracked changes in document.xml plus supported revisionable
417
+ * side-story parts, producing clean XML with no revision markup.
316
418
  */
317
- acceptChanges() {
318
- const result = acceptChangesImpl(this.documentXml);
319
- if (result.insertionsAccepted > 0 ||
320
- result.deletionsAccepted > 0 ||
321
- result.movesResolved > 0 ||
322
- result.propertyChangesResolved > 0) {
419
+ async acceptChanges() {
420
+ const total = emptyAcceptChangesResult();
421
+ const bodyResult = acceptChangesImpl(this.documentXml);
422
+ addAcceptChangesResult(total, bodyResult);
423
+ // After accepting, footnotes whose body reference lived inside a removed
424
+ // w:del are orphaned. Only worth checking when the body sweep removed
425
+ // deletions (the only operation that can drop a footnoteReference).
426
+ const liveFootnoteRefIds = bodyResult.deletionsAccepted > 0
427
+ ? collectLiveFootnoteRefIds(this.documentXml)
428
+ : null;
429
+ for (const partPath of REVISION_STORY_PART_PATHS) {
430
+ const xml = await this.zip.readTextOrNull(partPath);
431
+ if (!xml)
432
+ continue;
433
+ const partDoc = parseXml(xml);
434
+ const partResult = acceptChangesImpl(partDoc);
435
+ addAcceptChangesResult(total, partResult);
436
+ let footnotesPruned = 0;
437
+ if (partPath === 'word/footnotes.xml' && liveFootnoteRefIds) {
438
+ footnotesPruned = pruneOrphanedFootnotes(partDoc, liveFootnoteRefIds);
439
+ }
440
+ if (hasAcceptedChanges(partResult) || footnotesPruned > 0) {
441
+ this.zip.writeText(partPath, serializeXml(partDoc));
442
+ if (partPath === 'word/footnotes.xml') {
443
+ this.footnotesXml = partDoc;
444
+ }
445
+ }
446
+ }
447
+ if (hasAcceptedChanges(total)) {
323
448
  this.dirty = true;
324
449
  this.documentViewCache = null;
325
450
  }
326
- return result;
451
+ return total;
452
+ }
453
+ /**
454
+ * Reject all tracked changes in document.xml plus supported revisionable
455
+ * side-story parts, restoring their pre-edit state where possible.
456
+ */
457
+ async rejectChanges() {
458
+ const total = emptyRejectChangesResult();
459
+ const bodyResult = rejectChangesImpl(this.documentXml);
460
+ addRejectChangesResult(total, bodyResult);
461
+ // After rejecting, footnotes whose body reference lived inside a removed
462
+ // w:ins are orphaned. Only worth checking when the body sweep removed
463
+ // insertions (the only operation that can drop a footnoteReference).
464
+ const liveFootnoteRefIds = bodyResult.insertionsRemoved > 0
465
+ ? collectLiveFootnoteRefIds(this.documentXml)
466
+ : null;
467
+ for (const partPath of REVISION_STORY_PART_PATHS) {
468
+ const xml = await this.zip.readTextOrNull(partPath);
469
+ if (!xml)
470
+ continue;
471
+ const partDoc = parseXml(xml);
472
+ const partResult = rejectChangesImpl(partDoc);
473
+ addRejectChangesResult(total, partResult);
474
+ let footnotesPruned = 0;
475
+ if (partPath === 'word/footnotes.xml' && liveFootnoteRefIds) {
476
+ footnotesPruned = pruneOrphanedFootnotes(partDoc, liveFootnoteRefIds);
477
+ }
478
+ if (hasRejectedChanges(partResult) || footnotesPruned > 0) {
479
+ this.zip.writeText(partPath, serializeXml(partDoc));
480
+ if (partPath === 'word/footnotes.xml') {
481
+ this.footnotesXml = partDoc;
482
+ }
483
+ }
484
+ }
485
+ if (hasRejectedChanges(total)) {
486
+ this.dirty = true;
487
+ this.documentViewCache = null;
488
+ }
489
+ return total;
327
490
  }
328
491
  removeJuniorBookmarks() {
329
492
  const removed = cleanupInternalBookmarks(this.documentXml);
@@ -360,6 +523,23 @@ export class DocxDocument {
360
523
  const endIdx = typeof limit === 'number' ? Math.min(total, startIdx + limit) : total;
361
524
  return { paragraphs: all.slice(startIdx, endIdx), totalParagraphs: total };
362
525
  }
526
+ /**
527
+ * Parsed `word/numbering.xml` model (abstract numberings + instances), or null when the
528
+ * document has no numbering part. The document view's `numbering` field only carries
529
+ * `num_id`/`ilvl`; semantic converters (DOCX → ODT) need `numFmt`/`lvlText`/`start` to
530
+ * synthesize target-format list styles, so the full model is exposed here.
531
+ */
532
+ getNumberingModel() {
533
+ return this.numberingXml ? parseNumberingXml(this.numberingXml) : null;
534
+ }
535
+ /**
536
+ * Parsed named-style model of the loaded document (empty when the package has no
537
+ * `word/styles.xml`). Semantic converters (DOCX → ODT) resolve heading/body style chains
538
+ * through it to seed their own style templates from the source's definitions.
539
+ */
540
+ getStylesModel() {
541
+ return parseStylesXml(this.stylesXml);
542
+ }
363
543
  buildDocumentView(opts) {
364
544
  const includeSemanticTags = opts?.includeSemanticTags ?? true;
365
545
  const showFormatting = opts?.showFormatting ?? false;
@@ -425,7 +605,7 @@ export class DocxDocument {
425
605
  this.dirty = true;
426
606
  this.documentViewCache = null;
427
607
  }
428
- insertParagraph(params) {
608
+ insertParagraph(params, ctx) {
429
609
  const { positionalAnchorNodeId, relativePosition, newText, newParagraphId: _newParagraphId, styleSourceId } = params;
430
610
  const anchor = findParagraphByBookmarkId(this.documentXml, positionalAnchorNodeId);
431
611
  if (!anchor)
@@ -505,8 +685,86 @@ export class DocxDocument {
505
685
  continue;
506
686
  newP.removeChild(child);
507
687
  }
688
+ // sectPr is the section terminator — must stay on the anchor, not propagate to the new paragraph.
689
+ const clonedPPr = getDirectChildrenByName(newP, W.pPr)[0];
690
+ if (clonedPPr) {
691
+ for (const sectPr of getDirectChildrenByName(clonedPPr, 'sectPr')) {
692
+ clonedPPr.removeChild(sectPr);
693
+ }
694
+ }
508
695
  return newP;
509
696
  }
697
+ function ensureParagraphProperties(paragraph) {
698
+ const existing = getDirectChildrenByName(paragraph, W.pPr)[0];
699
+ if (existing)
700
+ return existing;
701
+ const pPr = createWmlElement(doc, W.pPr);
702
+ paragraph.insertBefore(pPr, paragraph.firstChild);
703
+ return pPr;
704
+ }
705
+ function ensureParagraphRunProperties(pPr) {
706
+ const existing = getDirectChildrenByName(pPr, W.rPr)[0];
707
+ if (existing)
708
+ return existing;
709
+ const rPr = createWmlElement(doc, W.rPr);
710
+ const sectPr = getDirectChildrenByName(pPr, 'sectPr')[0];
711
+ const pPrChange = getDirectChildrenByName(pPr, 'pPrChange')[0];
712
+ const insertBefore = sectPr ?? pPrChange ?? null;
713
+ if (insertBefore) {
714
+ pPr.insertBefore(rPr, insertBefore);
715
+ }
716
+ else {
717
+ pPr.appendChild(rPr);
718
+ }
719
+ return rPr;
720
+ }
721
+ function addParagraphInsertionMarker(paragraph, revisionCtx) {
722
+ const pPr = ensureParagraphProperties(paragraph);
723
+ const rPr = ensureParagraphRunProperties(pPr);
724
+ const marker = createWmlElement(doc, 'ins', {
725
+ 'w:id': String(allocateRevisionId(revisionCtx.idState)),
726
+ 'w:author': revisionCtx.author,
727
+ 'w:date': revisionCtx.date,
728
+ });
729
+ rPr.insertBefore(marker, rPr.firstChild);
730
+ }
731
+ function clearRunPropertyRevisionMarkup(run) {
732
+ const rPr = getDirectChildrenByName(run, W.rPr)[0];
733
+ if (!rPr)
734
+ return;
735
+ for (const child of Array.from(rPr.childNodes)) {
736
+ if (child.nodeType !== 1)
737
+ continue;
738
+ const element = child;
739
+ if (isW(element, 'rPrChange')) {
740
+ rPr.removeChild(element);
741
+ }
742
+ }
743
+ }
744
+ function clearParagraphPropertyRevisionMarkup(paragraph) {
745
+ const pPr = getDirectChildrenByName(paragraph, W.pPr)[0];
746
+ if (!pPr)
747
+ return;
748
+ for (const pPrChange of getDirectChildrenByName(pPr, 'pPrChange')) {
749
+ pPr.removeChild(pPrChange);
750
+ }
751
+ const rPr = getDirectChildrenByName(pPr, W.rPr)[0];
752
+ if (!rPr)
753
+ return;
754
+ for (const child of Array.from(rPr.childNodes)) {
755
+ if (child.nodeType !== 1)
756
+ continue;
757
+ const element = child;
758
+ // CT_ParaRPr revision children: w:ins, w:del, w:moveFrom, w:moveTo, w:rPrChange.
759
+ if (isW(element, 'ins') ||
760
+ isW(element, 'del') ||
761
+ isW(element, 'moveFrom') ||
762
+ isW(element, 'moveTo') ||
763
+ isW(element, 'rPrChange')) {
764
+ rPr.removeChild(element);
765
+ }
766
+ }
767
+ }
510
768
  function getInsertionRefNode() {
511
769
  if (relativePosition === 'BEFORE') {
512
770
  const prev = prevElementSibling(anchorP);
@@ -538,7 +796,17 @@ export class DocxDocument {
538
796
  const newP = cloneParagraphShell(formattingSource);
539
797
  const newRun = cloneRunFormattingOnly(templateRun);
540
798
  appendTextToRun(newRun, paraText);
541
- newP.appendChild(newRun);
799
+ if (ctx) {
800
+ clearRunPropertyRevisionMarkup(newRun);
801
+ clearParagraphPropertyRevisionMarkup(newP);
802
+ addParagraphInsertionMarker(newP, ctx);
803
+ const insertion = createRevisionContainer(doc, 'ins', ctx);
804
+ insertion.appendChild(newRun);
805
+ newP.appendChild(insertion);
806
+ }
807
+ else {
808
+ newP.appendChild(newRun);
809
+ }
542
810
  parent.insertBefore(newP, cursor);
543
811
  const id = insertSingleParagraphBookmark(doc, newP);
544
812
  insertedIds.push(id);
@@ -557,24 +825,24 @@ export class DocxDocument {
557
825
  result.styleSourceFallback = true;
558
826
  return result;
559
827
  }
560
- setParagraphSpacing(mutation) {
561
- const result = setParagraphSpacing(this.documentXml, mutation);
828
+ setParagraphSpacing(mutation, ctx) {
829
+ const result = setParagraphSpacing(this.documentXml, mutation, ctx);
562
830
  if (result.affectedParagraphs > 0) {
563
831
  this.dirty = true;
564
832
  this.documentViewCache = null;
565
833
  }
566
834
  return result;
567
835
  }
568
- setTableRowHeight(mutation) {
569
- const result = setTableRowHeight(this.documentXml, mutation);
836
+ setTableRowHeight(mutation, ctx) {
837
+ const result = setTableRowHeight(this.documentXml, mutation, ctx);
570
838
  if (result.affectedRows > 0) {
571
839
  this.dirty = true;
572
840
  this.documentViewCache = null;
573
841
  }
574
842
  return result;
575
843
  }
576
- setTableCellPadding(mutation) {
577
- const result = setTableCellPadding(this.documentXml, mutation);
844
+ setTableCellPadding(mutation, ctx) {
845
+ const result = setTableCellPadding(this.documentXml, mutation, ctx);
578
846
  if (result.affectedCells > 0) {
579
847
  this.dirty = true;
580
848
  this.documentViewCache = null;
@@ -591,9 +859,11 @@ export class DocxDocument {
591
859
  /**
592
860
  * Merge format-identical adjacent runs only (no redline simplification).
593
861
  * Useful as a pre-processing step before text search when runs may be fragmented.
862
+ * Pass `{ preserveRsidIdentity: true }` from edit pipelines that must not
863
+ * disturb rsid attributes on runs the caller did not touch (#286).
594
864
  */
595
- mergeRunsOnly() {
596
- const result = mergeRuns(this.documentXml);
865
+ mergeRunsOnly(opts = {}) {
866
+ const result = mergeRuns(this.documentXml, opts);
597
867
  if (result.runsMerged > 0) {
598
868
  this.dirty = true;
599
869
  this.documentViewCache = null;
@@ -606,7 +876,7 @@ export class DocxDocument {
606
876
  * Bootstraps comment parts if missing (idempotent).
607
877
  * Returns the allocated comment ID.
608
878
  */
609
- async addComment(params) {
879
+ async addComment(params, ctx) {
610
880
  const p = findParagraphByBookmarkId(this.documentXml, params.paragraphId);
611
881
  if (!p)
612
882
  throw new Error(`Paragraph not found: ${params.paragraphId}`);
@@ -618,7 +888,7 @@ export class DocxDocument {
618
888
  author: params.author,
619
889
  text: params.text,
620
890
  initials: params.initials,
621
- });
891
+ }, ctx);
622
892
  this.dirty = true;
623
893
  this.documentViewCache = null;
624
894
  return result;
@@ -629,14 +899,14 @@ export class DocxDocument {
629
899
  * Bootstraps comment parts if missing (idempotent).
630
900
  * Returns the allocated comment ID and parent ID.
631
901
  */
632
- async addCommentReply(params) {
902
+ async addCommentReply(params, ctx) {
633
903
  await bootstrapCommentParts(this.zip);
634
904
  const result = await addCommentReplyImpl(this.documentXml, this.zip, {
635
905
  parentCommentId: params.parentCommentId,
636
906
  author: params.author,
637
907
  text: params.text,
638
908
  initials: params.initials,
639
- });
909
+ }, ctx);
640
910
  this.dirty = true;
641
911
  this.documentViewCache = null;
642
912
  return result;
@@ -647,8 +917,8 @@ export class DocxDocument {
647
917
  async getComment(commentId) {
648
918
  return getCommentImpl(this.zip, this.documentXml, commentId);
649
919
  }
650
- async deleteComment(params) {
651
- await deleteCommentImpl(this.documentXml, this.zip, params);
920
+ async deleteComment(params, ctx) {
921
+ await deleteCommentImpl(this.documentXml, this.zip, params, ctx);
652
922
  this.dirty = true;
653
923
  this.documentViewCache = null;
654
924
  }
@@ -660,6 +930,47 @@ export class DocxDocument {
660
930
  async getFootnotes() {
661
931
  return getFootnotesImpl(this.zip, this.documentXml);
662
932
  }
933
+ /**
934
+ * Serialize the document to GitHub-Flavored Markdown. Convenience wrapper that wires the
935
+ * structured document view (with inline formatting) and footnotes into
936
+ * {@link serializeToMarkdown}. Markdown is intentionally lossy — see that serializer.
937
+ *
938
+ * Async because footnote extraction reads the footnotes part from the zip.
939
+ */
940
+ async toMarkdown(opts) {
941
+ const { nodes } = this.buildDocumentView({ showFormatting: true });
942
+ const footnotes = await this.getFootnotes();
943
+ return serializeToMarkdown(nodes, footnotes, opts);
944
+ }
945
+ /**
946
+ * Serialize the document to semantic HTML. Convenience wrapper that wires the structured
947
+ * document view (with inline formatting) and footnotes into {@link serializeToHtml}. The
948
+ * default output is a complete `<!DOCTYPE html>` document; pass `{ fragment: true }` for the
949
+ * body-level elements only. This is the semantic tier — exact layout is not reproduced.
950
+ *
951
+ * Async because footnote extraction reads the footnotes part from the zip.
952
+ */
953
+ async toHtml(opts) {
954
+ const { nodes } = this.buildDocumentView({ showFormatting: true });
955
+ const footnotes = await this.getFootnotes();
956
+ return serializeToHtml(nodes, footnotes, opts);
957
+ }
958
+ /**
959
+ * Serialize the document to plain text (no markup). Convenience wrapper that wires the
960
+ * structured document view and footnotes into {@link serializeToPlainText}. All formatting
961
+ * is stripped; block structure survives as blank-line-separated paragraphs, `- ` bullets,
962
+ * and tab-separated table rows. Intentionally lossy — see that serializer.
963
+ *
964
+ * Uses the same `showFormatting: true` view as {@link toMarkdown} so the block structure
965
+ * and injected `[^n]` footnote markers match; the inline tags it produces are then stripped.
966
+ *
967
+ * Async because footnote extraction reads the footnotes part from the zip.
968
+ */
969
+ async toPlainText(opts) {
970
+ const { nodes } = this.buildDocumentView({ showFormatting: true });
971
+ const footnotes = await this.getFootnotes();
972
+ return serializeToPlainText(nodes, footnotes, opts);
973
+ }
663
974
  async getFootnote(noteId) {
664
975
  return getFootnoteImpl(this.zip, this.documentXml, noteId);
665
976
  }
@@ -669,7 +980,7 @@ export class DocxDocument {
669
980
  * Bootstraps footnote parts if missing (idempotent).
670
981
  * Returns the allocated footnote ID.
671
982
  */
672
- async addFootnote(params) {
983
+ async addFootnote(params, ctx) {
673
984
  const p = findParagraphByBookmarkId(this.documentXml, params.paragraphId);
674
985
  if (!p)
675
986
  throw new Error(`Paragraph not found: ${params.paragraphId}`);
@@ -678,7 +989,7 @@ export class DocxDocument {
678
989
  paragraphEl: p,
679
990
  afterText: params.afterText,
680
991
  text: params.text,
681
- });
992
+ }, ctx);
682
993
  await this.refreshFootnotesXml();
683
994
  this.dirty = true;
684
995
  this.documentViewCache = null;
@@ -687,8 +998,8 @@ export class DocxDocument {
687
998
  /**
688
999
  * Update the text content of an existing footnote.
689
1000
  */
690
- async updateFootnoteText(params) {
691
- await updateFootnoteTextImpl(this.zip, params);
1001
+ async updateFootnoteText(params, ctx) {
1002
+ await updateFootnoteTextImpl(this.zip, params, ctx);
692
1003
  await this.refreshFootnotesXml();
693
1004
  this.dirty = true;
694
1005
  this.documentViewCache = null;
@@ -696,8 +1007,8 @@ export class DocxDocument {
696
1007
  /**
697
1008
  * Delete a footnote and its references from the document.
698
1009
  */
699
- async deleteFootnote(params) {
700
- await deleteFootnoteImpl(this.documentXml, this.zip, params);
1010
+ async deleteFootnote(params, ctx) {
1011
+ await deleteFootnoteImpl(this.documentXml, this.zip, params, ctx);
701
1012
  await this.refreshFootnotesXml();
702
1013
  this.dirty = true;
703
1014
  this.documentViewCache = null;
@@ -720,23 +1031,49 @@ export class DocxDocument {
720
1031
  return null;
721
1032
  return parseXml(commentsText);
722
1033
  }
1034
+ /**
1035
+ * Serialize the document to a .docx buffer.
1036
+ *
1037
+ * With `minimalReserialization` (requires `cleanBookmarks`), top-level body
1038
+ * blocks that no edit touched are restored element-for-element from the
1039
+ * original document.xml instead of carrying the open-time normalization
1040
+ * (proofErr stripping, run merging) to disk — so output diffs reflect the
1041
+ * actual edit blast radius. Edited/inserted blocks are emitted as-is.
1042
+ * Falls back to full re-serialization (blocksRestored: 0) when no original
1043
+ * text was captured or reconciliation fails.
1044
+ *
1045
+ * @see https://github.com/UseJunior/safe-docx/issues/408
1046
+ */
723
1047
  async toBuffer(opts) {
724
1048
  // Always write the latest document.xml when saving.
725
1049
  // Important: when cleanBookmarks=true (download), we must NOT mutate session state.
726
1050
  const xmlWithBookmarks = serializeXml(this.documentXml);
1051
+ maybeCaptureEmittedDocumentXml(xmlWithBookmarks);
727
1052
  this.zip.writeText('word/document.xml', xmlWithBookmarks);
728
1053
  if (opts?.cleanBookmarks) {
729
1054
  const cloned = parseXml(xmlWithBookmarks);
730
1055
  const bookmarksRemoved = cleanupInternalBookmarks(cloned);
1056
+ let blocksRestored = 0;
1057
+ if (opts.minimalReserialization && this.originalDocumentXmlText !== null) {
1058
+ try {
1059
+ blocksRestored = restoreUntouchedBlocks(cloned, this.originalDocumentXmlText);
1060
+ }
1061
+ catch {
1062
+ // Reconciliation is best-effort; the fully re-serialized DOM is
1063
+ // always a correct (if non-minimal) save.
1064
+ blocksRestored = 0;
1065
+ }
1066
+ }
731
1067
  const cleanedXml = serializeXml(cloned);
732
1068
  // Temporarily swap document.xml in the zip for output, then restore.
1069
+ maybeCaptureEmittedDocumentXml(cleanedXml);
733
1070
  this.zip.writeText('word/document.xml', cleanedXml);
734
1071
  const buffer = await this.zip.toBuffer();
735
1072
  this.zip.writeText('word/document.xml', xmlWithBookmarks);
736
- return { buffer, bookmarksRemoved };
1073
+ return { buffer, bookmarksRemoved, blocksRestored };
737
1074
  }
738
1075
  const buffer = await this.zip.toBuffer();
739
- return { buffer, bookmarksRemoved: 0 };
1076
+ return { buffer, bookmarksRemoved: 0, blocksRestored: 0 };
740
1077
  }
741
1078
  }
742
1079
  //# sourceMappingURL=document.js.map