@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
@@ -6,8 +6,11 @@
6
6
  */
7
7
  import { OOXML, W } from './namespaces.js';
8
8
  import { parseXml, serializeXml } from './xml.js';
9
- import { getParagraphRuns, getParagraphText } from './text.js';
9
+ import { getParagraphRuns, getParagraphText, splitRunAtVisibleOffset } from './text.js';
10
10
  import { getParagraphBookmarkId } from './bookmarks.js';
11
+ import { childElements, getLeafText, isW } from './dom-helpers.js';
12
+ import { getAttributeSafe } from './xml-helpers.js';
13
+ import { createRevisionContainer, prepareElementForDeletion, } from './track-changes-emitter.js';
11
14
  // ── Relationship types ──────────────────────────────────────────────────
12
15
  const REL_TYPE_COMMENTS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments';
13
16
  const REL_TYPE_COMMENTS_EXTENDED = 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended';
@@ -16,6 +19,8 @@ const REL_TYPE_PEOPLE = 'http://schemas.microsoft.com/office/2011/relationships/
16
19
  const CT_COMMENTS = 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml';
17
20
  const CT_COMMENTS_EXTENDED = 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml';
18
21
  const CT_PEOPLE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.people+xml';
22
+ // XML Namespaces namespace — used when binding/declaring prefixes via setAttributeNS.
23
+ const XMLNS_NS = 'http://www.w3.org/2000/xmlns/';
19
24
  // ── Minimal XML templates ───────────────────────────────────────────────
20
25
  const COMMENTS_XML_TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
21
26
  `<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"` +
@@ -150,20 +155,24 @@ async function ensureRelationships(zip, newParts) {
150
155
  * - Adds comment entry to comments.xml
151
156
  * - Adds author to people.xml if not present
152
157
  */
153
- export async function addComment(documentXml, zip, params) {
158
+ export async function addComment(documentXml, zip, params, ctx) {
154
159
  const { paragraphEl, author, text, initials } = params;
160
+ const visibleLen = getParagraphText(paragraphEl).length;
155
161
  const start = params.start ?? 0;
156
- const end = params.end ?? getParagraphText(paragraphEl).length;
162
+ const end = params.end ?? visibleLen;
157
163
  if (start > end) {
158
164
  throw new Error(`Invalid comment range: start (${start}) must be <= end (${end})`);
159
165
  }
166
+ if (start < 0 || end > visibleLen) {
167
+ throw new Error(`Invalid comment range: [${start}, ${end}) is outside paragraph visible text [0, ${visibleLen})`);
168
+ }
160
169
  // Load comments.xml
161
170
  const commentsXml = await zip.readText('word/comments.xml');
162
171
  const commentsDoc = parseXml(commentsXml);
163
172
  // Allocate next comment ID
164
173
  const commentId = allocateNextCommentId(commentsDoc);
165
174
  // Insert range markers and reference in document body
166
- insertCommentMarkers(documentXml, paragraphEl, commentId, start, end);
175
+ insertCommentMarkers(documentXml, paragraphEl, commentId, start, end, ctx);
167
176
  // Add comment element to comments.xml
168
177
  const paraId = generateParaId();
169
178
  addCommentElement(commentsDoc, {
@@ -183,8 +192,11 @@ export async function addComment(documentXml, zip, params) {
183
192
  *
184
193
  * Replies don't have range markers in the document body.
185
194
  * Thread linkage is stored in commentsExtended.xml via paraIdParent.
195
+ * `ctx` is accepted for API consistency with other comment mutations, but this
196
+ * primitive only updates comment side parts for replies, so no body revision
197
+ * markup is emitted here.
186
198
  */
187
- export async function addCommentReply(_documentXml, zip, params) {
199
+ export async function addCommentReply(_documentXml, zip, params, _ctx) {
188
200
  const { parentCommentId, author, text, initials } = params;
189
201
  // Load comments.xml
190
202
  const commentsXml = await zip.readText('word/comments.xml');
@@ -219,7 +231,7 @@ function allocateNextCommentId(commentsDoc) {
219
231
  let maxId = -1;
220
232
  for (let i = 0; i < commentEls.length; i++) {
221
233
  const el = commentEls.item(i);
222
- const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
234
+ const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
223
235
  if (idStr) {
224
236
  const id = parseInt(idStr, 10);
225
237
  if (id > maxId)
@@ -232,19 +244,42 @@ function findCommentParaId(commentsDoc, commentId) {
232
244
  const commentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
233
245
  for (let i = 0; i < commentEls.length; i++) {
234
246
  const el = commentEls.item(i);
235
- const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
247
+ const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
236
248
  if (idStr && parseInt(idStr, 10) === commentId) {
237
249
  // paraId is on the w:p child inside the comment
238
250
  const paras = el.getElementsByTagNameNS(OOXML.W_NS, W.p);
239
251
  if (paras.length > 0) {
240
252
  const p = paras.item(0);
241
- return p.getAttributeNS(OOXML.W14_NS, 'paraId') ?? p.getAttribute('w14:paraId') ?? null;
253
+ return getAttributeSafe(p, OOXML.W14_NS, 'paraId', 'w14', { bareFallback: false });
242
254
  }
243
255
  }
244
256
  }
245
257
  return null;
246
258
  }
259
+ /**
260
+ * Ensure a comment-related document root binds the w14 and w15 prefixes before any
261
+ * w14:* / w15:* attribute is written into it. Real-world docx files often ship a
262
+ * pre-existing comments.xml (or commentsExtended.xml / people.xml) that omits one or
263
+ * both declarations; without a real namespace binding, xmldom would reject the
264
+ * round-tripped XML with `NamespaceError: prefix is non-null and namespace is null`.
265
+ *
266
+ * Uses `setAttributeNS(XMLNS_NS, …)` so the prefix is actually bound on the live DOM
267
+ * (not just serialized as a literal attribute) — that means subsequent `createElementNS`
268
+ * / `setAttributeNS` / `lookupNamespaceURI` calls on the same Document resolve correctly
269
+ * without depending on a serialize/reparse round trip. Idempotent — guards on the real
270
+ * binding via `lookupNamespaceURI`, not on a same-named plain attribute.
271
+ */
272
+ function ensureCommentPartNamespaceAliases(commentsDoc) {
273
+ const root = commentsDoc.documentElement;
274
+ if (root.lookupNamespaceURI('w14') !== OOXML.W14_NS) {
275
+ root.setAttributeNS(XMLNS_NS, 'xmlns:w14', OOXML.W14_NS);
276
+ }
277
+ if (root.lookupNamespaceURI('w15') !== OOXML.W15_NS) {
278
+ root.setAttributeNS(XMLNS_NS, 'xmlns:w15', OOXML.W15_NS);
279
+ }
280
+ }
247
281
  function addCommentElement(commentsDoc, params) {
282
+ ensureCommentPartNamespaceAliases(commentsDoc);
248
283
  const root = commentsDoc.documentElement;
249
284
  const commentEl = commentsDoc.createElementNS(OOXML.W_NS, 'w:comment');
250
285
  commentEl.setAttribute('w:id', String(params.id));
@@ -253,7 +288,9 @@ function addCommentElement(commentsDoc, params) {
253
288
  commentEl.setAttribute('w:initials', params.initials);
254
289
  // Comment body: <w:p w14:paraId="..."><w:pPr><w:pStyle w:val="CommentText"/></w:pPr><w:r><w:annotationRef/></w:r><w:r><w:t>text</w:t></w:r></w:p>
255
290
  const p = commentsDoc.createElementNS(OOXML.W_NS, 'w:p');
256
- p.setAttribute('w14:paraId', params.paraId);
291
+ // Use setAttributeNS so the attribute carries a real namespace URI — otherwise xmldom
292
+ // serializes a prefix it cannot resolve and reparse throws NamespaceError (#154).
293
+ p.setAttributeNS(OOXML.W14_NS, 'w14:paraId', params.paraId);
257
294
  // Annotation reference run
258
295
  const refRun = commentsDoc.createElementNS(OOXML.W_NS, 'w:r');
259
296
  const annotRef = commentsDoc.createElementNS(OOXML.W_NS, 'w:annotationRef');
@@ -271,10 +308,8 @@ function addCommentElement(commentsDoc, params) {
271
308
  commentEl.appendChild(p);
272
309
  root.appendChild(commentEl);
273
310
  }
274
- function insertCommentMarkers(documentXml, paragraphEl, commentId, start, end) {
275
- // Find the runs in the paragraph and map string offsets to DOM positions
311
+ function insertCommentMarkers(documentXml, paragraphEl, commentId, start, end, ctx) {
276
312
  const runs = getParagraphRuns(paragraphEl);
277
- // Create marker elements
278
313
  const rangeStart = documentXml.createElementNS(OOXML.W_NS, 'w:commentRangeStart');
279
314
  rangeStart.setAttribute('w:id', String(commentId));
280
315
  const rangeEnd = documentXml.createElementNS(OOXML.W_NS, 'w:commentRangeEnd');
@@ -288,93 +323,180 @@ function insertCommentMarkers(documentXml, paragraphEl, commentId, start, end) {
288
323
  const commentRef = documentXml.createElementNS(OOXML.W_NS, 'w:commentReference');
289
324
  commentRef.setAttribute('w:id', String(commentId));
290
325
  refRun.appendChild(commentRef);
291
- // Map string offsets to run positions
326
+ const refAnchor = ctx ? createRevisionContainer(documentXml, 'ins', ctx) : refRun;
327
+ if (ctx) {
328
+ refAnchor.appendChild(refRun);
329
+ }
330
+ if (runs.length === 0) {
331
+ paragraphEl.appendChild(rangeStart);
332
+ paragraphEl.appendChild(rangeEnd);
333
+ paragraphEl.appendChild(refAnchor);
334
+ return;
335
+ }
336
+ const mapping = mapOffsetsToRuns(runs, start, end);
337
+ const { startRunIdx, startOffset, endRunIdx, endOffset } = mapping;
338
+ // Collapsed range (start === end): insert both markers at the same boundary.
339
+ // Splitting again here would create an empty <w:r> inside the marker pair —
340
+ // replaceParagraphTextRange() avoids this by deleting the temporary run, which
341
+ // we can't do for comments. Handle the boundary directly.
342
+ if (startRunIdx === endRunIdx && startOffset === endOffset) {
343
+ insertCollapsedRangeMarkers(runs[startRunIdx], startOffset, rangeStart, rangeEnd, refAnchor);
344
+ return;
345
+ }
346
+ // Split boundary runs at the exact visible-text offsets so the markers can sit
347
+ // on true sub-paragraph boundaries instead of being snapped to whole-run edges.
348
+ // Choreography mirrors replaceParagraphTextRange() in text.ts:404.
349
+ let startRunEl = runs[startRunIdx].r;
350
+ let endRunEl = runs[endRunIdx].r;
351
+ if (startRunIdx === endRunIdx) {
352
+ const runLen = runs[startRunIdx].text.length;
353
+ if (endOffset < runLen) {
354
+ const { left } = splitRunAtVisibleOffset(startRunEl, endOffset);
355
+ startRunEl = left;
356
+ endRunEl = left;
357
+ }
358
+ if (startOffset > 0) {
359
+ const { right } = splitRunAtVisibleOffset(startRunEl, startOffset);
360
+ startRunEl = right;
361
+ endRunEl = right;
362
+ }
363
+ }
364
+ else {
365
+ if (startOffset > 0) {
366
+ const { right } = splitRunAtVisibleOffset(startRunEl, startOffset);
367
+ startRunEl = right;
368
+ }
369
+ const endLen = runs[endRunIdx].text.length;
370
+ if (endOffset < endLen) {
371
+ const { left } = splitRunAtVisibleOffset(endRunEl, endOffset);
372
+ endRunEl = left;
373
+ }
374
+ }
375
+ // Insert relative to each run's parent so anchors inside w:hyperlink, w:ins,
376
+ // w:del, w:sdtContent, etc. keep the markers inside the wrapper.
377
+ const startParent = startRunEl.parentNode;
378
+ const endParent = endRunEl.parentNode;
379
+ if (!startParent || !endParent) {
380
+ throw new Error('Split run has no parent');
381
+ }
382
+ startParent.insertBefore(rangeStart, startRunEl);
383
+ endParent.insertBefore(rangeEnd, endRunEl.nextSibling);
384
+ endParent.insertBefore(refAnchor, rangeEnd.nextSibling);
385
+ }
386
+ function mapOffsetsToRuns(runs, start, end) {
387
+ // Map visible-text offsets to (runIndex, offsetInRun). Caller validates that
388
+ // 0 <= start <= end <= sum(runs[i].text.length).
292
389
  let pos = 0;
293
390
  let startRunIdx = -1;
294
391
  let endRunIdx = -1;
392
+ let startOffset = 0;
393
+ let endOffset = 0;
295
394
  for (let i = 0; i < runs.length; i++) {
296
- const runEnd = pos + runs[i].text.length;
297
- if (startRunIdx < 0 && start < runEnd)
395
+ const len = runs[i].text.length;
396
+ if (startRunIdx === -1 && start >= pos && start <= pos + len) {
298
397
  startRunIdx = i;
299
- if (endRunIdx < 0 && end <= runEnd)
398
+ startOffset = start - pos;
399
+ }
400
+ if (endRunIdx === -1 && end >= pos && end <= pos + len) {
300
401
  endRunIdx = i;
301
- pos = runEnd;
302
- }
303
- // Fallback: if offsets don't map cleanly, wrap the whole paragraph
304
- if (startRunIdx < 0)
305
- startRunIdx = 0;
306
- if (endRunIdx < 0)
307
- endRunIdx = runs.length - 1;
308
- // Insert commentRangeStart before the start run
309
- if (runs.length > 0 && startRunIdx < runs.length) {
310
- paragraphEl.insertBefore(rangeStart, runs[startRunIdx].r);
311
- }
312
- else {
313
- // No runs — insert at end of paragraph
314
- paragraphEl.appendChild(rangeStart);
402
+ endOffset = end - pos;
403
+ break;
404
+ }
405
+ pos += len;
315
406
  }
316
- // Insert commentRangeEnd and reference run after the end run
317
- if (runs.length > 0 && endRunIdx < runs.length) {
318
- const afterEndRun = runs[endRunIdx].r.nextSibling;
319
- paragraphEl.insertBefore(rangeEnd, afterEndRun);
320
- paragraphEl.insertBefore(refRun, afterEndRun);
321
- }
322
- else {
323
- paragraphEl.appendChild(rangeEnd);
324
- paragraphEl.appendChild(refRun);
407
+ if (startRunIdx === -1 || endRunIdx === -1) {
408
+ throw new Error(`Could not map offsets [${start}, ${end}) to runs`);
325
409
  }
410
+ return { startRunIdx, startOffset, endRunIdx, endOffset };
411
+ }
412
+ function insertCollapsedRangeMarkers(run, offsetInRun, rangeStart, rangeEnd, refAnchor) {
413
+ const runEl = run.r;
414
+ const parent = runEl.parentNode;
415
+ if (!parent)
416
+ throw new Error('Run has no parent');
417
+ const runLen = run.text.length;
418
+ if (offsetInRun === 0) {
419
+ // Insert before the run.
420
+ parent.insertBefore(rangeStart, runEl);
421
+ parent.insertBefore(rangeEnd, runEl);
422
+ parent.insertBefore(refAnchor, runEl);
423
+ return;
424
+ }
425
+ if (offsetInRun === runLen) {
426
+ // Insert after the run. Capture nextSibling once because insertBefore
427
+ // shifts it (rangeStart would otherwise land last instead of first).
428
+ const ref = runEl.nextSibling;
429
+ parent.insertBefore(rangeStart, ref);
430
+ parent.insertBefore(rangeEnd, ref);
431
+ parent.insertBefore(refAnchor, ref);
432
+ return;
433
+ }
434
+ // Mid-run: split once so the markers sit between the two halves.
435
+ const { right } = splitRunAtVisibleOffset(runEl, offsetInRun);
436
+ parent.insertBefore(rangeStart, right);
437
+ parent.insertBefore(rangeEnd, right);
438
+ parent.insertBefore(refAnchor, right);
326
439
  }
327
440
  async function linkReplyInCommentsExtended(zip, replyParaId, parentParaId) {
328
441
  const extXml = await zip.readText('word/commentsExtended.xml');
329
442
  const extDoc = parseXml(extXml);
443
+ ensureCommentPartNamespaceAliases(extDoc);
330
444
  const root = extDoc.documentElement;
331
445
  const exEl = extDoc.createElementNS(OOXML.W15_NS, 'w15:commentEx');
332
- exEl.setAttribute('w15:paraId', replyParaId);
333
- exEl.setAttribute('w15:paraIdParent', parentParaId);
334
- exEl.setAttribute('w15:done', '0');
446
+ exEl.setAttributeNS(OOXML.W15_NS, 'w15:paraId', replyParaId);
447
+ exEl.setAttributeNS(OOXML.W15_NS, 'w15:paraIdParent', parentParaId);
448
+ exEl.setAttributeNS(OOXML.W15_NS, 'w15:done', '0');
335
449
  root.appendChild(exEl);
336
450
  zip.writeText('word/commentsExtended.xml', serializeXml(extDoc));
337
451
  }
338
452
  async function ensureCommentExEntry(zip, paraId) {
339
453
  const extXml = await zip.readText('word/commentsExtended.xml');
340
454
  const extDoc = parseXml(extXml);
455
+ ensureCommentPartNamespaceAliases(extDoc);
341
456
  const root = extDoc.documentElement;
342
457
  // Check if entry already exists
343
458
  const existing = root.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx');
344
459
  for (let i = 0; i < existing.length; i++) {
345
460
  const el = existing.item(i);
346
- const pid = el.getAttributeNS(OOXML.W15_NS, 'paraId') ?? el.getAttribute('w15:paraId');
461
+ const pid = getAttributeSafe(el, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
347
462
  if (pid === paraId)
348
463
  return; // Already present
349
464
  }
350
465
  const exEl = extDoc.createElementNS(OOXML.W15_NS, 'w15:commentEx');
351
- exEl.setAttribute('w15:paraId', paraId);
352
- exEl.setAttribute('w15:done', '0');
466
+ exEl.setAttributeNS(OOXML.W15_NS, 'w15:paraId', paraId);
467
+ exEl.setAttributeNS(OOXML.W15_NS, 'w15:done', '0');
353
468
  root.appendChild(exEl);
354
469
  zip.writeText('word/commentsExtended.xml', serializeXml(extDoc));
355
470
  }
356
471
  async function ensureAuthorInPeople(zip, author) {
357
472
  const peopleXml = await zip.readText('word/people.xml');
358
473
  const peopleDoc = parseXml(peopleXml);
474
+ ensureCommentPartNamespaceAliases(peopleDoc);
359
475
  const root = peopleDoc.documentElement;
360
476
  // Check if author already exists
361
477
  const persons = root.getElementsByTagNameNS(OOXML.W15_NS, 'person');
362
478
  for (let i = 0; i < persons.length; i++) {
363
479
  const el = persons.item(i);
364
- const name = el.getAttributeNS(OOXML.W15_NS, 'author') ?? el.getAttribute('w15:author');
480
+ const name = getAttributeSafe(el, OOXML.W15_NS, 'author', 'w15', { bareFallback: false });
365
481
  if (name === author)
366
482
  return; // Already present
367
483
  }
368
484
  const personEl = peopleDoc.createElementNS(OOXML.W15_NS, 'w15:person');
369
- personEl.setAttribute('w15:author', author);
485
+ personEl.setAttributeNS(OOXML.W15_NS, 'w15:author', author);
370
486
  // Add a presenceInfo child (required by Word)
371
487
  const presenceInfo = peopleDoc.createElementNS(OOXML.W15_NS, 'w15:presenceInfo');
372
- presenceInfo.setAttribute('w15:providerId', 'None');
373
- presenceInfo.setAttribute('w15:userId', author);
488
+ presenceInfo.setAttributeNS(OOXML.W15_NS, 'w15:providerId', 'None');
489
+ presenceInfo.setAttributeNS(OOXML.W15_NS, 'w15:userId', author);
374
490
  personEl.appendChild(presenceInfo);
375
491
  root.appendChild(personEl);
376
492
  zip.writeText('word/people.xml', serializeXml(peopleDoc));
377
493
  }
494
+ var FieldState;
495
+ (function (FieldState) {
496
+ FieldState[FieldState["OUTSIDE_FIELD"] = 0] = "OUTSIDE_FIELD";
497
+ FieldState[FieldState["IN_FIELD_CODE"] = 1] = "IN_FIELD_CODE";
498
+ FieldState[FieldState["IN_FIELD_RESULT"] = 2] = "IN_FIELD_RESULT";
499
+ })(FieldState || (FieldState = {}));
378
500
  /**
379
501
  * Read all comments from a document, building a threaded tree.
380
502
  *
@@ -390,27 +512,30 @@ export async function getComments(zip, documentXml) {
390
512
  const commentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
391
513
  if (commentEls.length === 0)
392
514
  return [];
515
+ const rangeMetadata = resolveCommentRangeMetadata(documentXml);
393
516
  // Build a map of commentId → { paraId, Comment }
394
517
  const byParaId = new Map();
395
518
  const byId = new Map();
396
519
  for (let i = 0; i < commentEls.length; i++) {
397
520
  const el = commentEls.item(i);
398
- const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
521
+ const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
399
522
  const id = idStr ? parseInt(idStr, 10) : -1;
400
523
  if (id < 0)
401
524
  continue;
402
- const author = el.getAttributeNS(OOXML.W_NS, 'author') ?? el.getAttribute('w:author') ?? '';
403
- const date = el.getAttributeNS(OOXML.W_NS, 'date') ?? el.getAttribute('w:date') ?? '';
404
- const initials = el.getAttributeNS(OOXML.W_NS, 'initials') ?? el.getAttribute('w:initials') ?? '';
525
+ const author = getAttributeSafe(el, OOXML.W_NS, 'author', 'w', { bareFallback: false }) ?? '';
526
+ const date = getAttributeSafe(el, OOXML.W_NS, 'date', 'w', { bareFallback: false }) ?? '';
527
+ const initials = getAttributeSafe(el, OOXML.W_NS, 'initials', 'w', { bareFallback: false }) ?? '';
405
528
  // Extract text from <w:t> elements, skipping annotationRef runs
406
529
  const text = extractCommentText(el);
407
- // Get paraId from first <w:p> child
530
+ // Get paraId from first <w:p> child (namespace-aware to handle non-`w` prefixes)
408
531
  const paras = el.getElementsByTagNameNS(OOXML.W_NS, W.p);
409
532
  let paragraphId = null;
410
533
  if (paras.length > 0) {
411
534
  const p = paras.item(0);
412
- paragraphId = p.getAttributeNS(OOXML.W14_NS, 'paraId') ?? p.getAttribute('w14:paraId') ?? null;
535
+ paragraphId = getAttributeSafe(p, OOXML.W14_NS, 'paraId', 'w14', { bareFallback: false });
413
536
  }
537
+ const startPoint = rangeMetadata.startById.get(id);
538
+ const endPoint = rangeMetadata.endById.get(id);
414
539
  const comment = {
415
540
  id,
416
541
  author,
@@ -418,35 +543,18 @@ export async function getComments(zip, documentXml) {
418
543
  initials,
419
544
  text,
420
545
  paragraphId,
421
- anchoredParagraphId: null,
546
+ anchoredParagraphId: startPoint?.paragraphId ?? null,
547
+ endParagraphId: endPoint?.paragraphId ?? startPoint?.paragraphId ?? null,
548
+ startRunIndex: startPoint?.runIndex,
549
+ startCharOffset: startPoint?.charOffset,
550
+ endRunIndex: endPoint?.runIndex,
551
+ endCharOffset: endPoint?.charOffset,
422
552
  replies: [],
423
553
  };
424
554
  byId.set(id, comment);
425
555
  if (paragraphId)
426
556
  byParaId.set(paragraphId, comment);
427
557
  }
428
- // Resolve anchoredParagraphId by scanning documentXml for commentRangeStart elements
429
- const rangeStarts = documentXml.getElementsByTagNameNS(OOXML.W_NS, W.commentRangeStart);
430
- for (let i = 0; i < rangeStarts.length; i++) {
431
- const rs = rangeStarts.item(i);
432
- const cidStr = rs.getAttributeNS(OOXML.W_NS, 'id') ?? rs.getAttribute('w:id');
433
- if (!cidStr)
434
- continue;
435
- const cid = parseInt(cidStr, 10);
436
- const comment = byId.get(cid);
437
- if (!comment)
438
- continue;
439
- // Walk up to find enclosing <w:p>
440
- let parent = rs.parentNode;
441
- while (parent && parent.nodeType === 1) {
442
- const pel = parent;
443
- if (pel.localName === W.p && pel.namespaceURI === OOXML.W_NS) {
444
- comment.anchoredParagraphId = getParagraphBookmarkId(pel);
445
- break;
446
- }
447
- parent = parent.parentNode;
448
- }
449
- }
450
558
  // Build thread tree from commentsExtended.xml
451
559
  const extText = await zip.readTextOrNull('word/commentsExtended.xml');
452
560
  if (extText) {
@@ -454,8 +562,8 @@ export async function getComments(zip, documentXml) {
454
562
  const exEls = extDoc.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx');
455
563
  for (let i = 0; i < exEls.length; i++) {
456
564
  const ex = exEls.item(i);
457
- const childParaId = ex.getAttributeNS(OOXML.W15_NS, 'paraId') ?? ex.getAttribute('w15:paraId');
458
- const parentParaId = ex.getAttributeNS(OOXML.W15_NS, 'paraIdParent') ?? ex.getAttribute('w15:paraIdParent');
565
+ const childParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
566
+ const parentParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraIdParent', 'w15', { bareFallback: false });
459
567
  if (!childParaId || !parentParaId)
460
568
  continue;
461
569
  const child = byParaId.get(childParaId);
@@ -472,8 +580,8 @@ export async function getComments(zip, documentXml) {
472
580
  const exEls = extDoc.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx');
473
581
  for (let i = 0; i < exEls.length; i++) {
474
582
  const ex = exEls.item(i);
475
- const childParaId = ex.getAttributeNS(OOXML.W15_NS, 'paraId') ?? ex.getAttribute('w15:paraId');
476
- const parentParaId = ex.getAttributeNS(OOXML.W15_NS, 'paraIdParent') ?? ex.getAttribute('w15:paraIdParent');
583
+ const childParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
584
+ const parentParaId = getAttributeSafe(ex, OOXML.W15_NS, 'paraIdParent', 'w15', { bareFallback: false });
477
585
  if (childParaId && parentParaId) {
478
586
  replyParaIds.add(childParaId);
479
587
  }
@@ -487,6 +595,155 @@ export async function getComments(zip, documentXml) {
487
595
  }
488
596
  return roots;
489
597
  }
598
+ function resolveCommentRangeMetadata(documentXml) {
599
+ const startById = new Map();
600
+ const endById = new Map();
601
+ const root = documentXml.documentElement;
602
+ if (!root)
603
+ return { startById, endById };
604
+ const paragraphList = root.getElementsByTagNameNS(OOXML.W_NS, W.p);
605
+ for (let i = 0; i < paragraphList.length; i++) {
606
+ resolveCommentRangeMetadataInParagraph(paragraphList.item(i), startById, endById);
607
+ }
608
+ return { startById, endById };
609
+ }
610
+ function resolveCommentRangeMetadataInParagraph(paragraph, startById, endById) {
611
+ const walkState = {
612
+ charPos: 0,
613
+ fieldState: FieldState.OUTSIDE_FIELD,
614
+ allRuns: [],
615
+ currentRunIndex: 0,
616
+ startMarkers: [],
617
+ endMarkers: [],
618
+ };
619
+ walkParagraphForCommentMarkers(paragraph, paragraph, walkState);
620
+ const paragraphId = getParagraphBookmarkId(paragraph);
621
+ for (const marker of walkState.startMarkers) {
622
+ if (startById.has(marker.id))
623
+ continue;
624
+ startById.set(marker.id, {
625
+ paragraphId,
626
+ ...resolveMarkerToRunBoundary(walkState.allRuns, marker, 'start'),
627
+ });
628
+ }
629
+ for (const marker of walkState.endMarkers) {
630
+ if (endById.has(marker.id))
631
+ continue;
632
+ endById.set(marker.id, {
633
+ paragraphId,
634
+ ...resolveMarkerToRunBoundary(walkState.allRuns, marker, 'end'),
635
+ });
636
+ }
637
+ }
638
+ function walkParagraphForCommentMarkers(rootParagraph, node, state) {
639
+ for (const child of childElements(node)) {
640
+ if (child !== rootParagraph && isW(child, W.p))
641
+ continue;
642
+ if (isW(child, W.commentRangeStart)) {
643
+ recordParagraphLevelMarker(child, state.startMarkers, state);
644
+ continue;
645
+ }
646
+ if (isW(child, W.commentRangeEnd)) {
647
+ recordParagraphLevelMarker(child, state.endMarkers, state);
648
+ continue;
649
+ }
650
+ if (isW(child, W.r)) {
651
+ walkRunForCommentMarkers(child, state);
652
+ continue;
653
+ }
654
+ walkParagraphForCommentMarkers(rootParagraph, child, state);
655
+ }
656
+ }
657
+ function walkRunForCommentMarkers(run, state) {
658
+ const runIndex = state.currentRunIndex;
659
+ state.currentRunIndex += 1;
660
+ const runVisibleStart = state.charPos;
661
+ for (const child of childElements(run)) {
662
+ if (isW(child, W.commentRangeStart)) {
663
+ recordInRunMarker(child, state.startMarkers, runIndex, state.charPos - runVisibleStart);
664
+ continue;
665
+ }
666
+ if (isW(child, W.commentRangeEnd)) {
667
+ recordInRunMarker(child, state.endMarkers, runIndex, state.charPos - runVisibleStart);
668
+ continue;
669
+ }
670
+ if (!child.namespaceURI || child.namespaceURI !== OOXML.W_NS)
671
+ continue;
672
+ if (child.localName === W.fldChar) {
673
+ const type = getWordAttribute(child, 'fldCharType') ?? '';
674
+ if (type === 'begin')
675
+ state.fieldState = FieldState.IN_FIELD_CODE;
676
+ else if (type === 'separate')
677
+ state.fieldState = FieldState.IN_FIELD_RESULT;
678
+ else if (type === 'end')
679
+ state.fieldState = FieldState.OUTSIDE_FIELD;
680
+ continue;
681
+ }
682
+ if (state.fieldState === FieldState.IN_FIELD_CODE)
683
+ continue;
684
+ if (child.localName === W.t) {
685
+ state.charPos += (getLeafText(child) ?? '').length;
686
+ continue;
687
+ }
688
+ if (child.localName === W.tab || child.localName === W.br) {
689
+ state.charPos += 1;
690
+ }
691
+ }
692
+ state.allRuns.push({ visibleLength: state.charPos - runVisibleStart });
693
+ }
694
+ function recordInRunMarker(markerEl, bucket, runIndex, charOffset) {
695
+ const id = getCommentMarkerId(markerEl);
696
+ if (id == null)
697
+ return;
698
+ bucket.push({ id, inside: { runIndex, charOffset } });
699
+ }
700
+ function recordParagraphLevelMarker(markerEl, bucket, state) {
701
+ const id = getCommentMarkerId(markerEl);
702
+ if (id == null)
703
+ return;
704
+ bucket.push({ id, between: { afterRunIndex: state.currentRunIndex - 1 } });
705
+ }
706
+ function getCommentMarkerId(markerEl) {
707
+ const idStr = getAttributeSafe(markerEl, OOXML.W_NS, 'id', 'w', { bareFallback: false });
708
+ if (!idStr)
709
+ return null;
710
+ const id = parseInt(idStr, 10);
711
+ return Number.isNaN(id) ? null : id;
712
+ }
713
+ function getWordAttribute(el, localName) {
714
+ return getAttributeSafe(el, OOXML.W_NS, localName, 'w');
715
+ }
716
+ function resolveMarkerToRunBoundary(allRuns, marker, boundary) {
717
+ // Marker seen INSIDE a run already carries the exact runIndex + charOffset.
718
+ if (marker.inside) {
719
+ return { runIndex: marker.inside.runIndex, charOffset: marker.inside.charOffset };
720
+ }
721
+ if (!marker.between)
722
+ return {};
723
+ if (allRuns.length === 0)
724
+ return {};
725
+ const { afterRunIndex } = marker.between;
726
+ const lastIndex = allRuns.length - 1;
727
+ if (boundary === 'start') {
728
+ // Marker sits between run `afterRunIndex` and run `afterRunIndex + 1`.
729
+ // For a `start` marker, the range opens at offset 0 of the next run if it exists.
730
+ const nextIdx = afterRunIndex + 1;
731
+ if (nextIdx <= lastIndex) {
732
+ return { runIndex: nextIdx, charOffset: 0 };
733
+ }
734
+ // Marker is past the last run — clamp to end of last run.
735
+ return { runIndex: lastIndex, charOffset: allRuns[lastIndex].visibleLength };
736
+ }
737
+ // boundary === 'end': range closes at the end of the previous run if one exists.
738
+ if (afterRunIndex < 0) {
739
+ // Marker is before the first run — empty range starting at run 0.
740
+ return { runIndex: 0, charOffset: 0 };
741
+ }
742
+ if (afterRunIndex <= lastIndex) {
743
+ return { runIndex: afterRunIndex, charOffset: allRuns[afterRunIndex].visibleLength };
744
+ }
745
+ return { runIndex: lastIndex, charOffset: allRuns[lastIndex].visibleLength };
746
+ }
490
747
  /**
491
748
  * Get a single comment by ID, searching the full tree including replies.
492
749
  */
@@ -514,7 +771,7 @@ function findCommentById(comments, id) {
514
771
  * commentReference from document.xml (element-level; run removed only if empty)
515
772
  * - Transitive cascade: deleting any node also deletes all descendants
516
773
  */
517
- export async function deleteComment(documentXml, zip, params) {
774
+ export async function deleteComment(documentXml, zip, params, ctx) {
518
775
  const { commentId } = params;
519
776
  const commentsText = await zip.readTextOrNull('word/comments.xml');
520
777
  if (!commentsText)
@@ -535,7 +792,7 @@ export async function deleteComment(documentXml, zip, params) {
535
792
  const allCommentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
536
793
  for (let i = 0; i < allCommentEls.length; i++) {
537
794
  const el = allCommentEls.item(i);
538
- const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
795
+ const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
539
796
  const id = idStr ? parseInt(idStr, 10) : -1;
540
797
  if (id < 0)
541
798
  continue;
@@ -552,8 +809,8 @@ export async function deleteComment(documentXml, zip, params) {
552
809
  const childrenOf = new Map();
553
810
  for (let i = 0; i < exEls.length; i++) {
554
811
  const ex = exEls.item(i);
555
- const childPid = ex.getAttributeNS(OOXML.W15_NS, 'paraId') ?? ex.getAttribute('w15:paraId');
556
- const parentPid = ex.getAttributeNS(OOXML.W15_NS, 'paraIdParent') ?? ex.getAttribute('w15:paraIdParent');
812
+ const childPid = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
813
+ const parentPid = getAttributeSafe(ex, OOXML.W15_NS, 'paraIdParent', 'w15', { bareFallback: false });
557
814
  if (childPid && parentPid) {
558
815
  const arr = childrenOf.get(parentPid);
559
816
  if (arr)
@@ -584,7 +841,7 @@ export async function deleteComment(documentXml, zip, params) {
584
841
  const elsToRemove = [];
585
842
  for (let i = 0; i < allCommentEls.length; i++) {
586
843
  const el = allCommentEls.item(i);
587
- const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
844
+ const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
588
845
  const id = idStr ? parseInt(idStr, 10) : -1;
589
846
  if (idsToDelete.has(id))
590
847
  elsToRemove.push(el);
@@ -600,7 +857,7 @@ export async function deleteComment(documentXml, zip, params) {
600
857
  const exToRemove = [];
601
858
  for (let i = 0; i < exEls.length; i++) {
602
859
  const ex = exEls.item(i);
603
- const pid = ex.getAttributeNS(OOXML.W15_NS, 'paraId') ?? ex.getAttribute('w15:paraId');
860
+ const pid = getAttributeSafe(ex, OOXML.W15_NS, 'paraId', 'w15', { bareFallback: false });
604
861
  if (pid && paraIdsToDelete.has(pid))
605
862
  exToRemove.push(ex);
606
863
  }
@@ -611,14 +868,14 @@ export async function deleteComment(documentXml, zip, params) {
611
868
  }
612
869
  // 3. Remove range markers and commentReference from document.xml (for root comments)
613
870
  for (const cid of idsToDelete) {
614
- removeCommentMarkersFromDocument(documentXml, cid);
871
+ removeCommentMarkersFromDocument(documentXml, cid, ctx);
615
872
  }
616
873
  }
617
874
  function findCommentElementById(commentsDoc, commentId) {
618
875
  const commentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment);
619
876
  for (let i = 0; i < commentEls.length; i++) {
620
877
  const el = commentEls.item(i);
621
- const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
878
+ const idStr = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
622
879
  if (idStr && parseInt(idStr, 10) === commentId)
623
880
  return el;
624
881
  }
@@ -629,16 +886,16 @@ function getCommentElParaId(commentEl) {
629
886
  if (paras.length === 0)
630
887
  return null;
631
888
  const p = paras.item(0);
632
- return p.getAttributeNS(OOXML.W14_NS, 'paraId') ?? p.getAttribute('w14:paraId') ?? null;
889
+ return getAttributeSafe(p, OOXML.W14_NS, 'paraId', 'w14', { bareFallback: false });
633
890
  }
634
- function removeCommentMarkersFromDocument(documentXml, commentId) {
891
+ function removeCommentMarkersFromDocument(documentXml, commentId, ctx) {
635
892
  const cidStr = String(commentId);
636
893
  // Remove commentRangeStart elements
637
894
  const rangeStarts = documentXml.getElementsByTagNameNS(OOXML.W_NS, W.commentRangeStart);
638
895
  const startsToRemove = [];
639
896
  for (let i = 0; i < rangeStarts.length; i++) {
640
897
  const el = rangeStarts.item(i);
641
- const id = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
898
+ const id = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
642
899
  if (id === cidStr)
643
900
  startsToRemove.push(el);
644
901
  }
@@ -649,18 +906,19 @@ function removeCommentMarkersFromDocument(documentXml, commentId) {
649
906
  const endsToRemove = [];
650
907
  for (let i = 0; i < rangeEnds.length; i++) {
651
908
  const el = rangeEnds.item(i);
652
- const id = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
909
+ const id = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
653
910
  if (id === cidStr)
654
911
  endsToRemove.push(el);
655
912
  }
656
913
  for (const el of endsToRemove)
657
914
  el.parentNode?.removeChild(el);
658
- // Remove commentReference elements (safe: remove element, then run only if empty)
915
+ // Remove commentReference elements, or preserve their containing run under
916
+ // a deletion wrapper when tracked changes are requested.
659
917
  const refs = documentXml.getElementsByTagNameNS(OOXML.W_NS, W.commentReference);
660
918
  const refsToRemove = [];
661
919
  for (let i = 0; i < refs.length; i++) {
662
920
  const el = refs.item(i);
663
- const id = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id');
921
+ const id = getAttributeSafe(el, OOXML.W_NS, 'id', 'w', { bareFallback: false });
664
922
  if (id === cidStr)
665
923
  refsToRemove.push(el);
666
924
  }
@@ -668,10 +926,29 @@ function removeCommentMarkersFromDocument(documentXml, commentId) {
668
926
  const run = ref.parentNode;
669
927
  if (!run)
670
928
  continue;
929
+ if (ctx) {
930
+ const parent = run.parentNode;
931
+ if (!parent)
932
+ continue;
933
+ const deletion = createRevisionContainer(documentXml, 'del', ctx);
934
+ parent.replaceChild(deletion, run);
935
+ deletion.appendChild(prepareElementForDeletion(run));
936
+ continue;
937
+ }
671
938
  run.removeChild(ref);
672
939
  // Remove run only if it has no visible content after removing the reference
673
940
  if (!hasVisibleRunContent(run)) {
674
- run.parentNode?.removeChild(run);
941
+ const runParent = run.parentNode;
942
+ runParent?.removeChild(run);
943
+ // If the run lived inside a tracked-change wrapper (e.g., the comment
944
+ // was added with ctx earlier and is now being deleted without ctx),
945
+ // the wrapper is left orphaned with no content. Clean it up.
946
+ if (runParent && isW(runParent, 'ins')) {
947
+ runParent.parentNode?.removeChild(runParent);
948
+ }
949
+ else if (runParent && isW(runParent, 'del')) {
950
+ runParent.parentNode?.removeChild(runParent);
951
+ }
675
952
  }
676
953
  }
677
954
  }