@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
@@ -21,6 +21,10 @@ import { modifyRevisedDocument, ContainerResolutionError } from './inPlaceModifi
21
21
  import { acceptAllChanges, rejectAllChanges, extractTextWithParagraphs, compareTexts, } from './trackChangesAcceptorAst.js';
22
22
  import { virtualizeNumberingLabels, DEFAULT_NUMBERING_OPTIONS, } from './numberingIntegration.js';
23
23
  import { premergeAdjacentRuns } from './premergeRuns.js';
24
+ export { hasFldCharInsideDel, validateFieldStructure, } from '../../shared/field-structure.js';
25
+ import { hasFldCharInsideDel, validateFieldStructure, } from '../../shared/field-structure.js';
26
+ import { AUXILIARY_PARTS, parseEntries, renumberCollidingAuxiliaryIds, restampCollidingCommentParaIds, } from './auxiliaryIdCollision.js';
27
+ import { maybeCaptureEmittedDocumentXml } from '../../primitives/schema-corpus-capture.js';
24
28
  function arraysEqual(a, b) {
25
29
  if (a.length !== b.length)
26
30
  return false;
@@ -215,70 +219,56 @@ function buildFailureSummary(failureDetails) {
215
219
  }
216
220
  return Object.keys(summary).length > 0 ? summary : undefined;
217
221
  }
222
+ // Declared above splitStories so the function body never observes an
223
+ // uninitialized binding under circular imports.
224
+ const serializer = new XMLSerializer();
218
225
  /**
219
- * Validate field structure integrity in document XML.
226
+ * Split a docx into per-story XML fragments for field-closure validation.
227
+ *
228
+ * Each footnote/endnote entry is treated as an isolated story: a complex
229
+ * field whose `begin` and `end` markers straddle stories breaks Word's
230
+ * field state machine. We therefore validate each `<w:footnote>` and
231
+ * `<w:endnote>` entry independently rather than treating the whole
232
+ * `footnotes.xml`/`endnotes.xml` as one stream.
233
+ *
234
+ * Accepts arrays of sidecar XMLs (one per source archive) so callers can
235
+ * validate the union of entries from every archive that may contribute to the
236
+ * final result. Step 12 of `compareDocumentsAtomizer` merges entries from a
237
+ * mode-dependent source archive into the base archive; passing both archives'
238
+ * sidecars guarantees that whichever path the merge takes, the entries it
239
+ * could publish have already been screened. Duplicates (same `w:id` in both
240
+ * archives) yield redundant but harmless validation work.
220
241
  *
221
- * Checks that fldChar begin/end are balanced and that w:instrText only
222
- * appears inside a proper field sequence (between begin and separate).
223
- * Orphaned instrText elements render as visible text in Word.
242
+ * Header/footer stories are not yet covered they require relationship
243
+ * walking to enumerate `headerN.xml`/`footerN.xml`.
244
+ *
245
+ * @conformance ECMA-376 edition 5, Part 4 § 17.16.5
246
+ * @see https://github.com/UseJunior/safe-docx/issues/212
224
247
  */
225
- function validateFieldStructure(documentXml) {
226
- const root = parseDocumentXml(documentXml);
227
- // Walk the document in order, tracking field nesting
228
- const allFldChars = findAllByTagName(root, 'w:fldChar');
229
- const allInstrTexts = findAllByTagName(root, 'w:instrText');
230
- // Quick balance check
231
- let begins = 0;
232
- let ends = 0;
233
- for (const fc of allFldChars) {
234
- const type = fc.getAttribute('w:fldCharType');
235
- if (type === 'begin')
236
- begins++;
237
- else if (type === 'end')
238
- ends++;
239
- }
240
- if (begins !== ends)
241
- return false;
242
- // Check that instrText elements are inside a field (between begin and separate).
243
- // Walk all elements in document order using a recursive scan.
244
- if (allInstrTexts.length === 0)
245
- return true; // No instrText, nothing to validate
246
- // Depth-first scan to check instrText placement
247
- let depth = 0;
248
- const pastSeparatorAtDepth = []; // track separator state per depth
249
- function scan(node) {
250
- for (let child = node.firstChild; child; child = child.nextSibling) {
251
- if (child.nodeType !== 1)
252
- continue; // skip non-elements
253
- const el = child;
254
- if (el.tagName === 'w:fldChar') {
255
- const type = el.getAttribute('w:fldCharType');
256
- if (type === 'begin') {
257
- depth++;
258
- pastSeparatorAtDepth[depth] = 0;
259
- }
260
- else if (type === 'separate') {
261
- if (depth > 0)
262
- pastSeparatorAtDepth[depth] = 1;
263
- }
264
- else if (type === 'end') {
265
- if (depth > 0)
266
- depth--;
267
- }
268
- }
269
- else if (el.tagName === 'w:instrText') {
270
- // instrText must be inside a field (depth > 0) and before the separator
271
- if (depth === 0 || pastSeparatorAtDepth[depth])
272
- return false;
248
+ export function splitStories(documentXml, footnotesXmls, endnotesXmls) {
249
+ const stories = [{ label: 'document', xml: documentXml }];
250
+ const collectEntries = (sidecars, entryTag, labelPrefix) => {
251
+ for (let s = 0; s < sidecars.length; s++) {
252
+ const sidecarXml = sidecars[s];
253
+ if (!sidecarXml)
254
+ continue;
255
+ const doc = parseXml(sidecarXml);
256
+ const entries = doc.getElementsByTagName(entryTag);
257
+ for (let i = 0; i < entries.length; i++) {
258
+ const entry = entries[i];
259
+ const id = entry.getAttribute('w:id') ?? String(i);
260
+ stories.push({
261
+ label: `${labelPrefix}[${s}]:${id}`,
262
+ xml: serializer.serializeToString(entry),
263
+ });
273
264
  }
274
- if (!scan(el))
275
- return false;
276
265
  }
277
- return true;
278
- }
279
- return scan(root);
266
+ };
267
+ collectEntries(footnotesXmls, 'w:footnote', 'footnote');
268
+ collectEntries(endnotesXmls, 'w:endnote', 'endnote');
269
+ return stories;
280
270
  }
281
- function evaluateSafetyChecks(originalTextForRoundTrip, revisedTextForRoundTrip, originalBookmarkDiagnostics, revisedBookmarkDiagnostics, candidateXml) {
271
+ function evaluateSafetyChecks(originalTextForRoundTrip, revisedTextForRoundTrip, originalBookmarkDiagnostics, revisedBookmarkDiagnostics, candidateXml, auxiliarySidecars) {
282
272
  const acceptedXml = acceptAllChanges(candidateXml);
283
273
  const rejectedXml = rejectAllChanges(candidateXml);
284
274
  const acceptedText = extractTextWithParagraphs(acceptedXml);
@@ -289,11 +279,28 @@ function evaluateSafetyChecks(originalTextForRoundTrip, revisedTextForRoundTrip,
289
279
  const rejectTextComparison = compareTexts(originalTextForRoundTrip, rejectedText);
290
280
  const acceptBookmarksOk = bookmarkDiagnosticsSemanticallyEqual(revisedBookmarkDiagnostics, acceptedBookmarkDiagnostics);
291
281
  const rejectBookmarksOk = bookmarkDiagnosticsSemanticallyEqual(originalBookmarkDiagnostics, rejectedBookmarkDiagnostics);
292
- // Validate field structure: after accept-all and reject-all, every
293
- // w:instrText must be inside a proper field sequence (between fldChar
294
- // begin and fldChar separate). Orphaned instrText renders as visible
295
- // text in Word.
296
- const fieldStructureOk = validateFieldStructure(acceptedXml) && validateFieldStructure(rejectedXml);
282
+ // Validate field structure per-story. Each footnote/endnote entry is its own
283
+ // ECMA-376 story; a complex field that crosses a story boundary breaks
284
+ // Word's field state machine even when global begin/end counts balance.
285
+ // Sidecars from BOTH archives are validated because Step 12's auxiliary-part
286
+ // merge picks its base and source archives by reconstruction mode (inplace
287
+ // base = revised; rebuild base = original) and validating only one side
288
+ // would miss field issues that would still ship in the merged result.
289
+ // `acceptAllChanges` / `rejectAllChanges` only transform document.xml, so
290
+ // the sidecar set is identical for both transforms.
291
+ const acceptedStories = splitStories(acceptedXml, auxiliarySidecars.footnotesXmls, auxiliarySidecars.endnotesXmls);
292
+ const rejectedStories = splitStories(rejectedXml, auxiliarySidecars.footnotesXmls, auxiliarySidecars.endnotesXmls);
293
+ // Issue #217 conformance gate on the COMBINED output: w:fldChar MUST NOT
294
+ // appear inside <w:del>. ECMA-376 Part 4 § 17.16.5 makes this fatal for
295
+ // Word's field state machine. The full validateFieldStructure check is run
296
+ // on the accept/reject projections (per-story); on the combined view we
297
+ // only gate the strict no-fldChar-in-del rule because some legacy emit
298
+ // paths (e.g. delInstrText inside <w:moveFrom>) are non-conformant in shape
299
+ // but out of scope for #217.
300
+ const combinedNoFldCharInDel = !hasFldCharInsideDel(candidateXml);
301
+ const fieldStructureOk = combinedNoFldCharInDel &&
302
+ validateFieldStructure(acceptedStories) &&
303
+ validateFieldStructure(rejectedStories);
297
304
  const checks = {
298
305
  acceptText: acceptTextComparison.normalizedIdentical,
299
306
  rejectText: rejectTextComparison.normalizedIdentical,
@@ -369,12 +376,36 @@ export async function compareDocumentsAtomizer(original, revised, options = {})
369
376
  // Step 1: Load DOCX archives
370
377
  const originalArchive = await DocxArchive.load(original);
371
378
  const revisedArchive = await DocxArchive.load(revised);
379
+ // Step 1b: Resolve auxiliary ID collisions. When both sides define
380
+ // different content under the same comment/footnote/endnote w:id or the
381
+ // same comment paraId, rewrite the revised side so no anchor or ancillary
382
+ // row in the merged output can bind to the other document's definition.
383
+ // Must run before any document.xml extraction so every downstream step sees
384
+ // the rewritten archive.
385
+ await renumberCollidingAuxiliaryIds(originalArchive, revisedArchive);
386
+ await restampCollidingCommentParaIds(originalArchive, revisedArchive);
372
387
  // Step 2: Extract document.xml
373
388
  const originalXml = await originalArchive.getDocumentXml();
374
389
  const revisedXml = await revisedArchive.getDocumentXml();
375
390
  // Extract numbering.xml if available
376
391
  const originalNumberingXml = await originalArchive.getNumberingXml() ?? undefined;
377
392
  const revisedNumberingXml = await revisedArchive.getNumberingXml() ?? undefined;
393
+ // Extract footnote/endnote sidecars from BOTH archives for per-story
394
+ // field-closure validation (issue #212). Step 12 picks the base archive by
395
+ // reconstruction mode (inplace = revised, rebuild = original) and merges
396
+ // missing referenced entries from the opposite archive. Validating both
397
+ // archives' sidecars covers the union of entries that could ship without
398
+ // having to duplicate the merge logic at safety-check time.
399
+ const [originalFootnotesXml, originalEndnotesXml, revisedFootnotesXml, revisedEndnotesXml,] = await Promise.all([
400
+ originalArchive.getFile('word/footnotes.xml'),
401
+ originalArchive.getFile('word/endnotes.xml'),
402
+ revisedArchive.getFile('word/footnotes.xml'),
403
+ revisedArchive.getFile('word/endnotes.xml'),
404
+ ]);
405
+ const auxiliarySidecars = {
406
+ footnotesXmls: [originalFootnotesXml, revisedFootnotesXml],
407
+ endnotesXmls: [originalEndnotesXml, revisedEndnotesXml],
408
+ };
378
409
  const originalPart = {
379
410
  uri: 'word/document.xml',
380
411
  contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml',
@@ -383,8 +414,13 @@ export async function compareDocumentsAtomizer(original, revised, options = {})
383
414
  uri: 'word/document.xml',
384
415
  contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml',
385
416
  };
386
- const originalTextForRoundTrip = extractTextWithParagraphs(originalXml);
387
- const revisedTextForRoundTrip = extractTextWithParagraphs(revisedXml);
417
+ // Project each input through the SAME accept/reject operation the candidate is
418
+ // checked under, so the round-trip comparison is like-for-like even when an
419
+ // input already carries its own tracked changes (pre-tracked w:ins / w:del,
420
+ // comment anchors, multi-author stacks). For a clean input these equal the raw
421
+ // extraction, so behavior on the common case is unchanged. (#347)
422
+ const originalTextForRoundTrip = extractTextWithParagraphs(rejectAllChanges(originalXml));
423
+ const revisedTextForRoundTrip = extractTextWithParagraphs(acceptAllChanges(revisedXml));
388
424
  const originalBookmarkDiagnostics = collectBookmarkDiagnostics(originalXml);
389
425
  const revisedBookmarkDiagnostics = collectBookmarkDiagnostics(revisedXml);
390
426
  const runComparisonPass = (atomizeOptions, outputMode) => {
@@ -445,7 +481,7 @@ export async function compareDocumentsAtomizer(original, revised, options = {})
445
481
  }
446
482
  return { mergedAtoms, newDocumentXml, outputMode };
447
483
  };
448
- const evaluateRoundTripSafety = (candidateXml) => evaluateSafetyChecks(originalTextForRoundTrip, revisedTextForRoundTrip, originalBookmarkDiagnostics, revisedBookmarkDiagnostics, candidateXml);
484
+ const evaluateRoundTripSafety = (candidateXml) => evaluateSafetyChecks(originalTextForRoundTrip, revisedTextForRoundTrip, originalBookmarkDiagnostics, revisedBookmarkDiagnostics, candidateXml, auxiliarySidecars);
449
485
  let comparisonResult;
450
486
  let fallbackReason;
451
487
  let fallbackDiagnostics;
@@ -540,6 +576,23 @@ export async function compareDocumentsAtomizer(original, revised, options = {})
540
576
  else {
541
577
  comparisonResult = runComparisonPass({ atomizeParagraphLevelMarkers: true }, 'rebuild');
542
578
  }
579
+ // Rebuild output gets the same safety screening as inplace attempts, whether
580
+ // rebuild was requested directly or reached via inplace fallback. Rebuild is
581
+ // the terminal strategy, so failures are surfaced in diagnostics rather than
582
+ // blocking the output.
583
+ // @see https://github.com/UseJunior/safe-docx/issues/226
584
+ let rebuildSafetyDiagnostics;
585
+ if (comparisonResult.outputMode === 'rebuild') {
586
+ const safety = evaluateRoundTripSafety(comparisonResult.newDocumentXml);
587
+ if (!safety.safe) {
588
+ rebuildSafetyDiagnostics = {
589
+ checks: safety.checks,
590
+ failedChecks: safety.failedChecks,
591
+ failureDetails: safety.failureDetails,
592
+ firstDiffSummary: safety.failureSummary,
593
+ };
594
+ }
595
+ }
543
596
  const { mergedAtoms, newDocumentXml } = comparisonResult;
544
597
  // Step 12: Clone appropriate archive and update document.xml.
545
598
  // Use the revised archive only for true inplace output.
@@ -551,6 +604,7 @@ export async function compareDocumentsAtomizer(original, revised, options = {})
551
604
  // auxiliary part that the revised side introduced (issue #94).
552
605
  const mergeSourceArchive = comparisonResult.outputMode === 'inplace' ? originalArchive : revisedArchive;
553
606
  const resultArchive = await baseArchive.clone();
607
+ maybeCaptureEmittedDocumentXml(newDocumentXml);
554
608
  resultArchive.setDocumentXml(newDocumentXml);
555
609
  // Step 12b: Merge auxiliary part definitions (footnotes, endnotes, comments).
556
610
  // Reconstruction may insert content (deleted in inplace, added in rebuild)
@@ -562,13 +616,14 @@ export async function compareDocumentsAtomizer(original, revised, options = {})
562
616
  // Gated on root comment IDs in the *result* document (not on what the
563
617
  // generic merge appended), so the pass runs even when the original already
564
618
  // contains the root and revised only adds replies under it (issue #108).
565
- const rootCommentIds = collectReferenceIds(newDocumentXml, 'w:commentReference');
619
+ // Comments anchored on footnote/endnote text count as roots too.
620
+ const rootCommentIds = await collectStoryReferenceIds(resultArchive, newDocumentXml, 'w:commentReference', null);
566
621
  if (rootCommentIds.size > 0) {
567
622
  await mergeCommentAncillaryParts(mergeSourceArchive, resultArchive, rootCommentIds);
568
623
  }
569
624
  // Step 13: Save result and compute stats
570
625
  const resultBuffer = await resultArchive.save();
571
- const stats = computeStats(mergedAtoms);
626
+ const stats = computeAtomizerStats(mergedAtoms);
572
627
  return {
573
628
  document: resultBuffer,
574
629
  stats,
@@ -577,37 +632,29 @@ export async function compareDocumentsAtomizer(original, revised, options = {})
577
632
  reconstructionModeUsed: comparisonResult.outputMode,
578
633
  fallbackReason,
579
634
  fallbackDiagnostics,
635
+ rebuildSafetyDiagnostics,
580
636
  };
581
637
  }
582
- const AUXILIARY_PARTS = [
583
- {
584
- label: 'footnote',
585
- partPath: 'word/footnotes.xml',
586
- referenceTag: 'w:footnoteReference',
587
- entryTag: 'w:footnote',
588
- rootTag: 'w:footnotes',
589
- contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml',
590
- relationshipType: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes',
591
- },
592
- {
593
- label: 'endnote',
594
- partPath: 'word/endnotes.xml',
595
- referenceTag: 'w:endnoteReference',
596
- entryTag: 'w:endnote',
597
- rootTag: 'w:endnotes',
598
- contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml',
599
- relationshipType: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes',
600
- },
601
- {
602
- label: 'comment',
603
- partPath: 'word/comments.xml',
604
- referenceTag: 'w:commentReference',
605
- entryTag: 'w:comment',
606
- rootTag: 'w:comments',
607
- contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml',
608
- relationshipType: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
609
- },
610
- ];
638
+ /**
639
+ * Collect reference IDs across every result story that can host anchors: the
640
+ * merged document.xml plus the result archive's footnote/endnote parts (Word
641
+ * allows comments anchored on note text). `excludePartPath` skips the part
642
+ * whose own definitions are being merged — entries can't reference
643
+ * themselves.
644
+ */
645
+ async function collectStoryReferenceIds(resultArchive, documentXml, referenceTag, excludePartPath) {
646
+ const ids = collectReferenceIds(documentXml, referenceTag);
647
+ for (const storyPath of ['word/footnotes.xml', 'word/endnotes.xml']) {
648
+ if (storyPath === excludePartPath)
649
+ continue;
650
+ const storyXml = await resultArchive.getFile(storyPath);
651
+ if (!storyXml)
652
+ continue;
653
+ for (const id of collectReferenceIds(storyXml, referenceTag))
654
+ ids.add(id);
655
+ }
656
+ return ids;
657
+ }
611
658
  /**
612
659
  * Collect reference IDs from document.xml using DOM parsing.
613
660
  */
@@ -622,22 +669,6 @@ function collectReferenceIds(documentXml, referenceTag) {
622
669
  }
623
670
  return ids;
624
671
  }
625
- /**
626
- * Parse an auxiliary part and extract entry elements by ID.
627
- */
628
- function parseEntries(xml, entryTag) {
629
- const doc = parseXml(xml);
630
- const entries = new Map();
631
- const elements = doc.getElementsByTagName(entryTag);
632
- for (let i = 0; i < elements.length; i++) {
633
- const el = elements[i];
634
- const id = el.getAttribute('w:id');
635
- if (id)
636
- entries.set(id, el);
637
- }
638
- return { doc, entries };
639
- }
640
- const serializer = new XMLSerializer();
641
672
  /**
642
673
  * Merge auxiliary part definitions (footnotes, endnotes, comments) from the
643
674
  * source archive into the result archive. The source archive is whichever
@@ -647,7 +678,11 @@ const serializer = new XMLSerializer();
647
678
  */
648
679
  async function mergeAuxiliaryPartDefinitions(sourceArchive, resultArchive, documentXml, descriptor) {
649
680
  const result = { mergedIds: new Set(), createdPart: false };
650
- const referencedIds = collectReferenceIds(documentXml, descriptor.referenceTag);
681
+ // Anchors may live in the merged body or on note text in the result's
682
+ // footnote/endnote stories. AUXILIARY_PARTS merges notes before comments,
683
+ // so by the comment pass the note stories already carry any merged-in
684
+ // comment anchors.
685
+ const referencedIds = await collectStoryReferenceIds(resultArchive, documentXml, descriptor.referenceTag, descriptor.partPath);
651
686
  if (referencedIds.size === 0)
652
687
  return result;
653
688
  const sourcePartXml = await sourceArchive.getFile(descriptor.partPath);
@@ -977,6 +1012,7 @@ const COMMENTS_EXTENDED_DESCRIPTOR = {
977
1012
  rootTag: 'w15:commentsEx',
978
1013
  contentType: 'application/vnd.ms-word.commentsExtended+xml',
979
1014
  relationshipType: 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended',
1015
+ idBearingTags: [], // keyed by w15:paraId, not w:id
980
1016
  };
981
1017
  const PEOPLE_DESCRIPTOR = {
982
1018
  label: 'people',
@@ -986,6 +1022,7 @@ const PEOPLE_DESCRIPTOR = {
986
1022
  rootTag: 'w15:people',
987
1023
  contentType: 'application/vnd.ms-word.people+xml',
988
1024
  relationshipType: 'http://schemas.microsoft.com/office/2011/relationships/people',
1025
+ idBearingTags: [], // keyed by w15:author, not w:id
989
1026
  };
990
1027
  async function mergePeople(sourceArchive, resultArchive, mergedAuthors) {
991
1028
  if (mergedAuthors.size === 0)
@@ -1042,47 +1079,82 @@ async function mergePeople(sourceArchive, resultArchive, mergedAuthors) {
1042
1079
  resultArchive.setFile('word/people.xml', serializer.serializeToString(newDoc));
1043
1080
  await ensureOpcMetadata(resultArchive, PEOPLE_DESCRIPTOR);
1044
1081
  }
1082
+ const fallbackParagraphStatsKeys = new WeakMap();
1083
+ let nextFallbackParagraphStatsKey = 0;
1084
+ function paragraphStatsKey(atom) {
1085
+ if (atom.paragraphIndex !== undefined) {
1086
+ return `${atom.part.uri}:${atom.paragraphIndex}`;
1087
+ }
1088
+ const pAncestor = atom.ancestorElements.find((a) => a.tagName === 'w:p');
1089
+ if (!pAncestor)
1090
+ return undefined;
1091
+ let key = fallbackParagraphStatsKeys.get(pAncestor);
1092
+ if (!key) {
1093
+ key = `${atom.part.uri}:paragraph-ref:${nextFallbackParagraphStatsKey++}`;
1094
+ fallbackParagraphStatsKeys.set(pAncestor, key);
1095
+ }
1096
+ return key;
1097
+ }
1045
1098
  /**
1046
1099
  * Compute comparison statistics from merged atoms.
1100
+ *
1101
+ * Range counts are contiguous same-status runs in the merged atom stream, scoped
1102
+ * to a paragraph. Atom counts remain available under explicit names for callers
1103
+ * that need the old granular benchmark signal.
1047
1104
  */
1048
- function computeStats(mergedAtoms) {
1105
+ export function computeAtomizerStats(mergedAtoms) {
1049
1106
  const reconstructionStats = computeReconstructionStats(mergedAtoms);
1050
- // Count unique paragraphs for modifications
1051
- // A modification is when we have both deleted and inserted atoms in the same paragraph
1052
- const modifiedParagraphs = new Set();
1053
- let currentParagraph = '';
1054
- let hasDeleted = false;
1055
- let hasInserted = false;
1107
+ let insertedRanges = 0;
1108
+ let deletedRanges = 0;
1109
+ let formatChanges = 0;
1110
+ let previousRangeStatus = null;
1111
+ let previousRangeParagraph;
1112
+ const paragraphs = new Map();
1056
1113
  for (const atom of mergedAtoms) {
1057
- // Detect paragraph boundaries
1058
- const pAncestor = atom.ancestorElements.find((a) => a.tagName === 'w:p');
1059
- const paragraphId = pAncestor
1060
- ? `${atom.part.uri}:${atom.ancestorElements.indexOf(pAncestor)}`
1061
- : '';
1062
- if (paragraphId !== currentParagraph) {
1063
- // Check previous paragraph
1064
- if (currentParagraph && hasDeleted && hasInserted) {
1065
- modifiedParagraphs.add(currentParagraph);
1114
+ const paragraphKey = paragraphStatsKey(atom);
1115
+ const status = atom.correlationStatus;
1116
+ const rangeStatus = status === CorrelationStatus.Inserted ||
1117
+ status === CorrelationStatus.Deleted ||
1118
+ status === CorrelationStatus.FormatChanged
1119
+ ? status
1120
+ : null;
1121
+ if (rangeStatus) {
1122
+ if (rangeStatus !== previousRangeStatus || paragraphKey !== previousRangeParagraph) {
1123
+ if (rangeStatus === CorrelationStatus.Inserted)
1124
+ insertedRanges++;
1125
+ if (rangeStatus === CorrelationStatus.Deleted)
1126
+ deletedRanges++;
1127
+ if (rangeStatus === CorrelationStatus.FormatChanged)
1128
+ formatChanges++;
1066
1129
  }
1067
- currentParagraph = paragraphId;
1068
- hasDeleted = false;
1069
- hasInserted = false;
1130
+ previousRangeStatus = rangeStatus;
1131
+ previousRangeParagraph = paragraphKey;
1070
1132
  }
1071
- if (atom.correlationStatus === CorrelationStatus.Deleted) {
1072
- hasDeleted = true;
1133
+ else {
1134
+ previousRangeStatus = null;
1135
+ previousRangeParagraph = undefined;
1073
1136
  }
1074
- else if (atom.correlationStatus === CorrelationStatus.Inserted) {
1075
- hasInserted = true;
1137
+ if (paragraphKey && (status === CorrelationStatus.Deleted || status === CorrelationStatus.Inserted)) {
1138
+ const flags = paragraphs.get(paragraphKey) ?? { hasDeleted: false, hasInserted: false };
1139
+ if (status === CorrelationStatus.Deleted)
1140
+ flags.hasDeleted = true;
1141
+ if (status === CorrelationStatus.Inserted)
1142
+ flags.hasInserted = true;
1143
+ paragraphs.set(paragraphKey, flags);
1076
1144
  }
1077
1145
  }
1078
- // Check last paragraph
1079
- if (currentParagraph && hasDeleted && hasInserted) {
1080
- modifiedParagraphs.add(currentParagraph);
1081
- }
1146
+ const modifiedParagraphs = Array.from(paragraphs.values()).filter((flags) => flags.hasDeleted && flags.hasInserted).length;
1082
1147
  return {
1083
- insertions: reconstructionStats.insertions,
1084
- deletions: reconstructionStats.deletions,
1085
- modifications: modifiedParagraphs.size + reconstructionStats.formatChanges,
1148
+ insertions: insertedRanges,
1149
+ deletions: deletedRanges,
1150
+ modifications: modifiedParagraphs,
1151
+ insertedRanges,
1152
+ deletedRanges,
1153
+ insertedAtoms: reconstructionStats.insertions,
1154
+ deletedAtoms: reconstructionStats.deletions,
1155
+ modifiedParagraphs,
1156
+ formatChanges,
1157
+ formatChangeAtoms: reconstructionStats.formatChanges,
1086
1158
  };
1087
1159
  }
1088
1160
  //# sourceMappingURL=pipeline.js.map