docxmlater 10.1.4 → 10.1.6

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 (372) hide show
  1. package/README.md +759 -754
  2. package/dist/constants/legacyCompatFlags.js +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.js.map +1 -1
  5. package/dist/core/Document.d.ts +51 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +486 -471
  8. package/dist/core/Document.js.map +1 -1
  9. package/dist/core/DocumentContent.d.ts +9 -9
  10. package/dist/core/DocumentContent.d.ts.map +1 -1
  11. package/dist/core/DocumentContent.js +1 -1
  12. package/dist/core/DocumentContent.js.map +1 -1
  13. package/dist/core/DocumentGenerator.d.ts +11 -11
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +251 -251
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.js.map +1 -1
  18. package/dist/core/DocumentParser.d.ts +15 -15
  19. package/dist/core/DocumentParser.d.ts.map +1 -1
  20. package/dist/core/DocumentParser.js +2123 -2155
  21. package/dist/core/DocumentParser.js.map +1 -1
  22. package/dist/core/DocumentValidator.d.ts.map +1 -1
  23. package/dist/core/DocumentValidator.js +2 -5
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.js.map +1 -1
  26. package/dist/core/RelationshipManager.d.ts.map +1 -1
  27. package/dist/core/RelationshipManager.js +3 -3
  28. package/dist/core/RelationshipManager.js.map +1 -1
  29. package/dist/elements/AlternateContent.js.map +1 -1
  30. package/dist/elements/Bookmark.d.ts.map +1 -1
  31. package/dist/elements/Bookmark.js +3 -1
  32. package/dist/elements/Bookmark.js.map +1 -1
  33. package/dist/elements/BookmarkManager.d.ts.map +1 -1
  34. package/dist/elements/BookmarkManager.js.map +1 -1
  35. package/dist/elements/Comment.d.ts.map +1 -1
  36. package/dist/elements/Comment.js +9 -6
  37. package/dist/elements/Comment.js.map +1 -1
  38. package/dist/elements/CommentManager.d.ts.map +1 -1
  39. package/dist/elements/CommentManager.js +18 -17
  40. package/dist/elements/CommentManager.js.map +1 -1
  41. package/dist/elements/CommonTypes.d.ts +21 -21
  42. package/dist/elements/CommonTypes.d.ts.map +1 -1
  43. package/dist/elements/CommonTypes.js +56 -56
  44. package/dist/elements/CommonTypes.js.map +1 -1
  45. package/dist/elements/CustomXml.js.map +1 -1
  46. package/dist/elements/Endnote.d.ts.map +1 -1
  47. package/dist/elements/Endnote.js +6 -6
  48. package/dist/elements/Endnote.js.map +1 -1
  49. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  50. package/dist/elements/EndnoteManager.js +6 -7
  51. package/dist/elements/EndnoteManager.js.map +1 -1
  52. package/dist/elements/Field.d.ts.map +1 -1
  53. package/dist/elements/Field.js +82 -25
  54. package/dist/elements/Field.js.map +1 -1
  55. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  56. package/dist/elements/FieldHelpers.js.map +1 -1
  57. package/dist/elements/FontManager.d.ts.map +1 -1
  58. package/dist/elements/FontManager.js +1 -1
  59. package/dist/elements/FontManager.js.map +1 -1
  60. package/dist/elements/Footer.js +2 -2
  61. package/dist/elements/Footer.js.map +1 -1
  62. package/dist/elements/Footnote.d.ts.map +1 -1
  63. package/dist/elements/Footnote.js +6 -6
  64. package/dist/elements/Footnote.js.map +1 -1
  65. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  66. package/dist/elements/FootnoteManager.js +6 -7
  67. package/dist/elements/FootnoteManager.js.map +1 -1
  68. package/dist/elements/Header.js +2 -2
  69. package/dist/elements/Header.js.map +1 -1
  70. package/dist/elements/HeaderFooterManager.js.map +1 -1
  71. package/dist/elements/Hyperlink.d.ts +5 -3
  72. package/dist/elements/Hyperlink.d.ts.map +1 -1
  73. package/dist/elements/Hyperlink.js +134 -76
  74. package/dist/elements/Hyperlink.js.map +1 -1
  75. package/dist/elements/Image.d.ts.map +1 -1
  76. package/dist/elements/Image.js +238 -106
  77. package/dist/elements/Image.js.map +1 -1
  78. package/dist/elements/ImageManager.d.ts.map +1 -1
  79. package/dist/elements/ImageManager.js +1 -1
  80. package/dist/elements/ImageManager.js.map +1 -1
  81. package/dist/elements/ImageRun.js +1 -1
  82. package/dist/elements/ImageRun.js.map +1 -1
  83. package/dist/elements/MathElement.js.map +1 -1
  84. package/dist/elements/Paragraph.d.ts +24 -24
  85. package/dist/elements/Paragraph.d.ts.map +1 -1
  86. package/dist/elements/Paragraph.js +181 -188
  87. package/dist/elements/Paragraph.js.map +1 -1
  88. package/dist/elements/PreservedElement.js.map +1 -1
  89. package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
  90. package/dist/elements/PropertyChangeTypes.js +6 -6
  91. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  92. package/dist/elements/RangeMarker.d.ts.map +1 -1
  93. package/dist/elements/RangeMarker.js.map +1 -1
  94. package/dist/elements/Revision.d.ts.map +1 -1
  95. package/dist/elements/Revision.js +4 -5
  96. package/dist/elements/Revision.js.map +1 -1
  97. package/dist/elements/RevisionContent.js.map +1 -1
  98. package/dist/elements/RevisionManager.d.ts.map +1 -1
  99. package/dist/elements/RevisionManager.js +40 -48
  100. package/dist/elements/RevisionManager.js.map +1 -1
  101. package/dist/elements/Run.d.ts +16 -16
  102. package/dist/elements/Run.d.ts.map +1 -1
  103. package/dist/elements/Run.js +256 -238
  104. package/dist/elements/Run.js.map +1 -1
  105. package/dist/elements/Section.d.ts.map +1 -1
  106. package/dist/elements/Section.js +36 -11
  107. package/dist/elements/Section.js.map +1 -1
  108. package/dist/elements/Shape.d.ts.map +1 -1
  109. package/dist/elements/Shape.js.map +1 -1
  110. package/dist/elements/StructuredDocumentTag.d.ts +6 -6
  111. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  112. package/dist/elements/StructuredDocumentTag.js +99 -104
  113. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  114. package/dist/elements/Table.d.ts +11 -11
  115. package/dist/elements/Table.d.ts.map +1 -1
  116. package/dist/elements/Table.js +102 -107
  117. package/dist/elements/Table.js.map +1 -1
  118. package/dist/elements/TableCell.d.ts +10 -10
  119. package/dist/elements/TableCell.d.ts.map +1 -1
  120. package/dist/elements/TableCell.js +105 -106
  121. package/dist/elements/TableCell.js.map +1 -1
  122. package/dist/elements/TableGridChange.d.ts.map +1 -1
  123. package/dist/elements/TableGridChange.js.map +1 -1
  124. package/dist/elements/TableOfContents.d.ts.map +1 -1
  125. package/dist/elements/TableOfContents.js +4 -4
  126. package/dist/elements/TableOfContents.js.map +1 -1
  127. package/dist/elements/TableOfContentsElement.js.map +1 -1
  128. package/dist/elements/TableRow.d.ts.map +1 -1
  129. package/dist/elements/TableRow.js +13 -6
  130. package/dist/elements/TableRow.js.map +1 -1
  131. package/dist/elements/TextBox.d.ts.map +1 -1
  132. package/dist/elements/TextBox.js +3 -5
  133. package/dist/elements/TextBox.js.map +1 -1
  134. package/dist/formatting/AbstractNumbering.d.ts +4 -4
  135. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  136. package/dist/formatting/AbstractNumbering.js +54 -49
  137. package/dist/formatting/AbstractNumbering.js.map +1 -1
  138. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  139. package/dist/formatting/NumberingInstance.js +1 -3
  140. package/dist/formatting/NumberingInstance.js.map +1 -1
  141. package/dist/formatting/NumberingLevel.d.ts +5 -5
  142. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  143. package/dist/formatting/NumberingLevel.js +119 -125
  144. package/dist/formatting/NumberingLevel.js.map +1 -1
  145. package/dist/formatting/NumberingManager.d.ts +1 -0
  146. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  147. package/dist/formatting/NumberingManager.js +27 -9
  148. package/dist/formatting/NumberingManager.js.map +1 -1
  149. package/dist/formatting/Style.d.ts +11 -11
  150. package/dist/formatting/Style.d.ts.map +1 -1
  151. package/dist/formatting/Style.js +219 -247
  152. package/dist/formatting/Style.js.map +1 -1
  153. package/dist/formatting/StylesManager.d.ts +2 -2
  154. package/dist/formatting/StylesManager.d.ts.map +1 -1
  155. package/dist/formatting/StylesManager.js +96 -102
  156. package/dist/formatting/StylesManager.js.map +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts +1 -1
  158. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  159. package/dist/helpers/CleanupHelper.js +6 -6
  160. package/dist/helpers/CleanupHelper.js.map +1 -1
  161. package/dist/images/ImageOptimizer.js +7 -7
  162. package/dist/images/ImageOptimizer.js.map +1 -1
  163. package/dist/index.d.ts +9 -9
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js.map +1 -1
  166. package/dist/managers/DrawingManager.js.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  168. package/dist/tracking/DocumentTrackingContext.js +23 -7
  169. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  170. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  171. package/dist/tracking/TrackingContext.js.map +1 -1
  172. package/dist/types/compatibility-types.js.map +1 -1
  173. package/dist/types/formatting.js.map +1 -1
  174. package/dist/types/list-types.d.ts +6 -6
  175. package/dist/types/list-types.js.map +1 -1
  176. package/dist/types/settings-types.js.map +1 -1
  177. package/dist/types/styleConfig.d.ts +2 -2
  178. package/dist/types/styleConfig.js.map +1 -1
  179. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  180. package/dist/utils/ChangelogGenerator.js +97 -101
  181. package/dist/utils/ChangelogGenerator.js.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js +1 -1
  184. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  186. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  187. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  188. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  189. package/dist/utils/MoveOperationHelper.js +1 -1
  190. package/dist/utils/MoveOperationHelper.js.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  192. package/dist/utils/RevisionAwareProcessor.js +2 -4
  193. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  194. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  195. package/dist/utils/RevisionWalker.js +4 -12
  196. package/dist/utils/RevisionWalker.js.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  198. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  199. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  200. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  201. package/dist/utils/ShadingResolver.js +1 -1
  202. package/dist/utils/ShadingResolver.js.map +1 -1
  203. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  204. package/dist/utils/acceptRevisions.js +23 -12
  205. package/dist/utils/acceptRevisions.js.map +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  207. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  208. package/dist/utils/cnfStyleDecoder.js +40 -40
  209. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  210. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  211. package/dist/utils/corruptionDetection.js.map +1 -1
  212. package/dist/utils/dateFormatting.js.map +1 -1
  213. package/dist/utils/deepClone.js +1 -1
  214. package/dist/utils/deepClone.js.map +1 -1
  215. package/dist/utils/diagnostics.d.ts.map +1 -1
  216. package/dist/utils/diagnostics.js +1 -1
  217. package/dist/utils/diagnostics.js.map +1 -1
  218. package/dist/utils/errorHandling.js.map +1 -1
  219. package/dist/utils/formatting.d.ts.map +1 -1
  220. package/dist/utils/formatting.js +10 -2
  221. package/dist/utils/formatting.js.map +1 -1
  222. package/dist/utils/list-detection.d.ts +2 -2
  223. package/dist/utils/list-detection.d.ts.map +1 -1
  224. package/dist/utils/list-detection.js +21 -23
  225. package/dist/utils/list-detection.js.map +1 -1
  226. package/dist/utils/logger.d.ts.map +1 -1
  227. package/dist/utils/logger.js +12 -7
  228. package/dist/utils/logger.js.map +1 -1
  229. package/dist/utils/parsingHelpers.js.map +1 -1
  230. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  231. package/dist/utils/stripTrackedChanges.js +3 -3
  232. package/dist/utils/stripTrackedChanges.js.map +1 -1
  233. package/dist/utils/textDiff.d.ts +1 -1
  234. package/dist/utils/textDiff.js +8 -8
  235. package/dist/utils/textDiff.js.map +1 -1
  236. package/dist/utils/units.js.map +1 -1
  237. package/dist/utils/validation.d.ts.map +1 -1
  238. package/dist/utils/validation.js +24 -7
  239. package/dist/utils/validation.js.map +1 -1
  240. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  241. package/dist/utils/xmlSanitization.js +3 -3
  242. package/dist/utils/xmlSanitization.js.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  244. package/dist/validation/RevisionAutoFixer.js +5 -5
  245. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  246. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  247. package/dist/validation/RevisionValidator.js +7 -9
  248. package/dist/validation/RevisionValidator.js.map +1 -1
  249. package/dist/validation/ValidationRules.js +3 -3
  250. package/dist/validation/ValidationRules.js.map +1 -1
  251. package/dist/validation/index.js.map +1 -1
  252. package/dist/xml/XMLBuilder.d.ts +1 -1
  253. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  254. package/dist/xml/XMLBuilder.js +98 -100
  255. package/dist/xml/XMLBuilder.js.map +1 -1
  256. package/dist/xml/XMLParser.d.ts.map +1 -1
  257. package/dist/xml/XMLParser.js +61 -66
  258. package/dist/xml/XMLParser.js.map +1 -1
  259. package/dist/zip/ZipHandler.d.ts.map +1 -1
  260. package/dist/zip/ZipHandler.js.map +1 -1
  261. package/dist/zip/ZipReader.d.ts.map +1 -1
  262. package/dist/zip/ZipReader.js +1 -3
  263. package/dist/zip/ZipReader.js.map +1 -1
  264. package/dist/zip/ZipWriter.d.ts +1 -1
  265. package/dist/zip/ZipWriter.d.ts.map +1 -1
  266. package/dist/zip/ZipWriter.js +28 -36
  267. package/dist/zip/ZipWriter.js.map +1 -1
  268. package/dist/zip/types.js +1 -1
  269. package/dist/zip/types.js.map +1 -1
  270. package/package.json +92 -92
  271. package/src/__tests__/helper-methods.test.ts +512 -512
  272. package/src/constants/legacyCompatFlags.ts +138 -138
  273. package/src/constants/limits.ts +50 -50
  274. package/src/core/Document.ts +1010 -1145
  275. package/src/core/DocumentContent.ts +461 -467
  276. package/src/core/DocumentGenerator.ts +1133 -1104
  277. package/src/core/DocumentIdManager.ts +158 -158
  278. package/src/core/DocumentParser.ts +2347 -2716
  279. package/src/core/DocumentValidator.ts +363 -372
  280. package/src/core/Relationship.ts +367 -367
  281. package/src/core/RelationshipManager.ts +429 -428
  282. package/src/elements/AlternateContent.ts +42 -42
  283. package/src/elements/Bookmark.ts +212 -210
  284. package/src/elements/BookmarkManager.ts +247 -250
  285. package/src/elements/Comment.ts +356 -359
  286. package/src/elements/CommentManager.ts +499 -502
  287. package/src/elements/CommonTypes.ts +524 -549
  288. package/src/elements/CustomXml.ts +36 -36
  289. package/src/elements/Endnote.ts +221 -217
  290. package/src/elements/EndnoteManager.ts +246 -249
  291. package/src/elements/Field.ts +1292 -1233
  292. package/src/elements/FieldHelpers.ts +329 -333
  293. package/src/elements/FontManager.ts +336 -339
  294. package/src/elements/Footer.ts +269 -269
  295. package/src/elements/Footnote.ts +221 -217
  296. package/src/elements/FootnoteManager.ts +246 -249
  297. package/src/elements/Header.ts +269 -269
  298. package/src/elements/HeaderFooterManager.ts +219 -219
  299. package/src/elements/Hyperlink.ts +1288 -1193
  300. package/src/elements/Image.ts +1982 -1756
  301. package/src/elements/ImageManager.ts +437 -432
  302. package/src/elements/ImageRun.ts +59 -59
  303. package/src/elements/MathElement.ts +65 -65
  304. package/src/elements/Paragraph.ts +4347 -4287
  305. package/src/elements/PreservedElement.ts +53 -53
  306. package/src/elements/PropertyChangeTypes.ts +458 -442
  307. package/src/elements/RangeMarker.ts +382 -400
  308. package/src/elements/Revision.ts +1198 -1217
  309. package/src/elements/RevisionContent.ts +73 -73
  310. package/src/elements/RevisionManager.ts +1070 -1070
  311. package/src/elements/Run.ts +3103 -3073
  312. package/src/elements/Section.ts +1521 -1421
  313. package/src/elements/Shape.ts +884 -873
  314. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  315. package/src/elements/Table.ts +2468 -2524
  316. package/src/elements/TableCell.ts +1617 -1621
  317. package/src/elements/TableGridChange.ts +149 -151
  318. package/src/elements/TableOfContents.ts +701 -691
  319. package/src/elements/TableOfContentsElement.ts +89 -89
  320. package/src/elements/TableRow.ts +960 -929
  321. package/src/elements/TextBox.ts +766 -768
  322. package/src/formatting/AbstractNumbering.ts +580 -579
  323. package/src/formatting/NumberingInstance.ts +295 -299
  324. package/src/formatting/NumberingLevel.ts +981 -1040
  325. package/src/formatting/NumberingManager.ts +875 -827
  326. package/src/formatting/Style.ts +1785 -1879
  327. package/src/formatting/StylesManager.ts +1090 -1130
  328. package/src/helpers/CleanupHelper.ts +524 -524
  329. package/src/images/ImageOptimizer.ts +274 -274
  330. package/src/index.ts +559 -554
  331. package/src/managers/DrawingManager.ts +319 -319
  332. package/src/tracking/DocumentTrackingContext.ts +687 -674
  333. package/src/tracking/TrackingContext.ts +175 -173
  334. package/src/types/compatibility-types.ts +49 -49
  335. package/src/types/formatting.ts +210 -210
  336. package/src/types/list-types.ts +14 -14
  337. package/src/types/settings-types.ts +59 -59
  338. package/src/types/styleConfig.ts +189 -189
  339. package/src/utils/ChangelogGenerator.ts +1583 -1581
  340. package/src/utils/CompatibilityUpgrader.ts +235 -237
  341. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  342. package/src/utils/MoveOperationHelper.ts +233 -238
  343. package/src/utils/RevisionAwareProcessor.ts +518 -526
  344. package/src/utils/RevisionWalker.ts +427 -457
  345. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  346. package/src/utils/ShadingResolver.ts +105 -107
  347. package/src/utils/acceptRevisions.ts +723 -714
  348. package/src/utils/cnfStyleDecoder.ts +212 -217
  349. package/src/utils/corruptionDetection.ts +346 -345
  350. package/src/utils/dateFormatting.ts +20 -20
  351. package/src/utils/deepClone.ts +77 -78
  352. package/src/utils/diagnostics.ts +125 -129
  353. package/src/utils/errorHandling.ts +80 -80
  354. package/src/utils/formatting.ts +220 -213
  355. package/src/utils/list-detection.ts +32 -42
  356. package/src/utils/logger.ts +412 -404
  357. package/src/utils/parsingHelpers.ts +190 -190
  358. package/src/utils/stripTrackedChanges.ts +356 -353
  359. package/src/utils/textDiff.ts +100 -100
  360. package/src/utils/units.ts +421 -421
  361. package/src/utils/validation.ts +553 -542
  362. package/src/utils/xmlSanitization.ts +179 -182
  363. package/src/validation/RevisionAutoFixer.ts +541 -542
  364. package/src/validation/RevisionValidator.ts +470 -460
  365. package/src/validation/ValidationRules.ts +338 -338
  366. package/src/validation/index.ts +30 -30
  367. package/src/xml/XMLBuilder.ts +857 -871
  368. package/src/xml/XMLParser.ts +877 -919
  369. package/src/zip/ZipHandler.ts +629 -637
  370. package/src/zip/ZipReader.ts +295 -299
  371. package/src/zip/ZipWriter.ts +374 -390
  372. package/src/zip/types.ts +116 -116
@@ -1,457 +1,427 @@
1
- /**
2
- * RevisionWalker - DOM-based tree walker for accepting tracked changes
3
- *
4
- * Replaces the fragile RegEx-based revision acceptance with a robust
5
- * DOM-based approach that properly handles nested elements and preserves
6
- * element ordering.
7
- *
8
- * @module RevisionWalker
9
- */
10
-
11
- import { ParsedXMLObject } from '../xml/XMLParser';
12
-
13
- /**
14
- * Options for controlling which revision types to accept
15
- */
16
- export interface RevisionWalkerOptions {
17
- /** Keep content, remove w:ins wrapper (default: true) */
18
- acceptInsertions?: boolean;
19
- /** Remove w:del and content entirely (default: true) */
20
- acceptDeletions?: boolean;
21
- /** Handle w:moveFrom/w:moveTo (default: true) */
22
- acceptMoves?: boolean;
23
- /** Remove *Change elements (default: true) */
24
- acceptPropertyChanges?: boolean;
25
- }
26
-
27
- /**
28
- * Structure for tracking element order
29
- */
30
- interface OrderedChildInfo {
31
- type: string;
32
- index: number;
33
- }
34
-
35
- /**
36
- * Revision element categories
37
- */
38
- const REVISION_ELEMENTS = {
39
- /** Elements to unwrap (keep content, remove wrapper) */
40
- UNWRAP: ['w:ins', 'w:moveTo'],
41
-
42
- /** Elements to remove entirely (with content) */
43
- REMOVE: ['w:del', 'w:moveFrom'],
44
-
45
- /** Property change tracking elements */
46
- PROPERTY_CHANGES: [
47
- 'w:rPrChange',
48
- 'w:pPrChange',
49
- 'w:tblPrChange',
50
- 'w:tcPrChange',
51
- 'w:trPrChange',
52
- 'w:sectPrChange',
53
- 'w:tblGridChange',
54
- 'w:numberingChange',
55
- 'w:tblPrExChange',
56
- ],
57
-
58
- /** Range marker elements */
59
- RANGE_MARKERS: [
60
- 'w:moveFromRangeStart',
61
- 'w:moveFromRangeEnd',
62
- 'w:moveToRangeStart',
63
- 'w:moveToRangeEnd',
64
- 'w:customXmlInsRangeStart',
65
- 'w:customXmlInsRangeEnd',
66
- 'w:customXmlDelRangeStart',
67
- 'w:customXmlDelRangeEnd',
68
- 'w:customXmlMoveFromRangeStart',
69
- 'w:customXmlMoveFromRangeEnd',
70
- 'w:customXmlMoveToRangeStart',
71
- 'w:customXmlMoveToRangeEnd',
72
- ],
73
- };
74
-
75
- /**
76
- * DOM-based tree walker for accepting Word document revisions
77
- *
78
- * This class processes a parsed XML object tree (from XMLParser.parseToObject())
79
- * and accepts all tracked changes by:
80
- * - Unwrapping insertions (w:ins, w:moveTo) - keeping content
81
- * - Removing deletions (w:del, w:moveFrom) - discarding content
82
- * - Removing property changes (*Change elements)
83
- * - Removing range markers
84
- *
85
- * Element order is preserved using the _orderedChildren metadata.
86
- */
87
- export class RevisionWalker {
88
- /**
89
- * Process a parsed XML object tree and accept all revisions
90
- *
91
- * @param obj - Parsed XML object from XMLParser.parseToObject()
92
- * @param options - Options controlling which revisions to accept
93
- * @returns New object tree with revisions accepted
94
- *
95
- * @example
96
- * ```typescript
97
- * const parsed = XMLParser.parseToObject(documentXml);
98
- * const clean = RevisionWalker.processTree(parsed);
99
- * ```
100
- */
101
- static processTree(
102
- obj: ParsedXMLObject,
103
- options?: RevisionWalkerOptions
104
- ): ParsedXMLObject {
105
- const opts: Required<RevisionWalkerOptions> = {
106
- acceptInsertions: options?.acceptInsertions ?? true,
107
- acceptDeletions: options?.acceptDeletions ?? true,
108
- acceptMoves: options?.acceptMoves ?? true,
109
- acceptPropertyChanges: options?.acceptPropertyChanges ?? true,
110
- };
111
-
112
- // Deep clone the object to avoid mutating the original
113
- const clone = RevisionWalker.deepClone(obj);
114
-
115
- // Walk and transform the tree
116
- RevisionWalker.walkAndTransform(clone, opts);
117
-
118
- return clone;
119
- }
120
-
121
- /**
122
- * Deep clone an object
123
- */
124
- private static deepClone(obj: any): any {
125
- if (obj === null || typeof obj !== 'object') {
126
- return obj;
127
- }
128
-
129
- if (Array.isArray(obj)) {
130
- return obj.map((item) => RevisionWalker.deepClone(item));
131
- }
132
-
133
- const clone: any = {};
134
- for (const key of Object.keys(obj)) {
135
- clone[key] = RevisionWalker.deepClone(obj[key]);
136
- }
137
- return clone;
138
- }
139
-
140
- /**
141
- * Recursively walk and transform the object tree
142
- * Processes children first (depth-first) to handle nested revisions
143
- */
144
- private static walkAndTransform(
145
- obj: any,
146
- options: Required<RevisionWalkerOptions>
147
- ): void {
148
- if (obj === null || typeof obj !== 'object') {
149
- return;
150
- }
151
-
152
- // Get keys to process (excluding metadata keys)
153
- const keys = Object.keys(obj).filter(
154
- (k) => !k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren'
155
- );
156
-
157
- // First pass: recurse into children (depth-first)
158
- for (const key of keys) {
159
- const value = obj[key];
160
- if (Array.isArray(value)) {
161
- for (const item of value) {
162
- RevisionWalker.walkAndTransform(item, options);
163
- }
164
- } else if (typeof value === 'object' && value !== null) {
165
- RevisionWalker.walkAndTransform(value, options);
166
- }
167
- }
168
-
169
- // Second pass: process revision elements at this level
170
- // We need to iterate carefully because we're modifying the object
171
- RevisionWalker.processRevisions(obj, options);
172
- }
173
-
174
- /**
175
- * Process revision elements at the current level
176
- */
177
- private static processRevisions(
178
- parent: any,
179
- options: Required<RevisionWalkerOptions>
180
- ): void {
181
- if (!parent || typeof parent !== 'object') {
182
- return;
183
- }
184
-
185
- const keysToProcess = Object.keys(parent).filter(
186
- (k) => !k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren'
187
- );
188
-
189
- for (const key of keysToProcess) {
190
- // Check if this is a revision element
191
- if (RevisionWalker.shouldUnwrap(key, options)) {
192
- RevisionWalker.unwrapAllElements(parent, key);
193
- } else if (RevisionWalker.shouldRemove(key, options)) {
194
- RevisionWalker.removeAllElements(parent, key);
195
- }
196
- }
197
- }
198
-
199
- /**
200
- * Check if an element should be unwrapped (content kept)
201
- */
202
- private static shouldUnwrap(
203
- key: string,
204
- options: Required<RevisionWalkerOptions>
205
- ): boolean {
206
- if (REVISION_ELEMENTS.UNWRAP.includes(key)) {
207
- if (key === 'w:ins' && !options.acceptInsertions) return false;
208
- if (key === 'w:moveTo' && !options.acceptMoves) return false;
209
- return true;
210
- }
211
- return false;
212
- }
213
-
214
- /**
215
- * Check if an element should be removed (content discarded)
216
- */
217
- private static shouldRemove(
218
- key: string,
219
- options: Required<RevisionWalkerOptions>
220
- ): boolean {
221
- if (REVISION_ELEMENTS.REMOVE.includes(key)) {
222
- if (key === 'w:del' && !options.acceptDeletions) return false;
223
- if (key === 'w:moveFrom' && !options.acceptMoves) return false;
224
- return true;
225
- }
226
- if (REVISION_ELEMENTS.PROPERTY_CHANGES.includes(key)) {
227
- return options.acceptPropertyChanges;
228
- }
229
- if (REVISION_ELEMENTS.RANGE_MARKERS.includes(key)) {
230
- return true; // Always remove range markers
231
- }
232
- return false;
233
- }
234
-
235
- /**
236
- * Unwrap all elements of a given type, promoting their children to parent
237
- *
238
- * This is the most complex operation in RevisionWalker. When we unwrap a
239
- * revision element like w:ins, we need to:
240
- * 1. Extract the children from inside w:ins
241
- * 2. Insert them at the correct position in the parent's element arrays
242
- * 3. Update _orderedChildren to reflect the new structure
243
- *
244
- * The key challenge is maintaining correct element order. For example:
245
- * Before: <w:tbl><w:tr>Row1</w:tr><w:ins><w:tr>Row2</w:tr></w:ins><w:tr>Row3</w:tr></w:tbl>
246
- * After: <w:tbl><w:tr>Row1</w:tr><w:tr>Row2</w:tr><w:tr>Row3</w:tr></w:tbl>
247
- */
248
- private static unwrapAllElements(parent: any, key: string): void {
249
- const elements = parent[key];
250
- if (!elements) return;
251
-
252
- const elementArray = Array.isArray(elements) ? elements : [elements];
253
-
254
- // Build a complete ordered list of child elements by walking _orderedChildren
255
- // This captures the intended order before we modify anything
256
- const orderedElements: { type: string; element: any }[] = [];
257
-
258
- if (parent._orderedChildren) {
259
- // Track how many of each type we've seen to get correct array index
260
- const typeCounters = new Map<string, number>();
261
- let unwrappedIndex = 0;
262
-
263
- for (const entry of parent._orderedChildren) {
264
- const { type } = entry;
265
-
266
- if (type === key) {
267
- // This is a revision element - extract its children in order
268
- const revElement = elementArray[unwrappedIndex];
269
- unwrappedIndex++;
270
-
271
- if (revElement && typeof revElement === 'object') {
272
- if (revElement._orderedChildren) {
273
- // Use the revision element's _orderedChildren for correct order
274
- const childCounters = new Map<string, number>();
275
- for (const childEntry of revElement._orderedChildren) {
276
- const childType = childEntry.type;
277
- const childIdx = childCounters.get(childType) || 0;
278
- childCounters.set(childType, childIdx + 1);
279
-
280
- const childElements = revElement[childType];
281
- if (childElements) {
282
- const childArray = Array.isArray(childElements)
283
- ? childElements
284
- : [childElements];
285
- if (childIdx < childArray.length) {
286
- orderedElements.push({
287
- type: childType,
288
- element: childArray[childIdx],
289
- });
290
- }
291
- }
292
- }
293
- } else {
294
- // No _orderedChildren, extract children in object key order
295
- const childKeys = Object.keys(revElement).filter(
296
- (k) =>
297
- !k.startsWith('@_') &&
298
- k !== '#text' &&
299
- k !== '_orderedChildren'
300
- );
301
- for (const childKey of childKeys) {
302
- const childValue = revElement[childKey];
303
- const childArray = Array.isArray(childValue)
304
- ? childValue
305
- : [childValue];
306
- for (const child of childArray) {
307
- orderedElements.push({ type: childKey, element: child });
308
- }
309
- }
310
- }
311
- }
312
- } else {
313
- // Regular element - get it from the parent
314
- const idx = typeCounters.get(type) || 0;
315
- typeCounters.set(type, idx + 1);
316
-
317
- const parentElements = parent[type];
318
- if (parentElements) {
319
- const parentArray = Array.isArray(parentElements)
320
- ? parentElements
321
- : [parentElements];
322
- if (idx < parentArray.length) {
323
- orderedElements.push({ type, element: parentArray[idx] });
324
- }
325
- }
326
- }
327
- }
328
- }
329
-
330
- // Remove the revision wrapper
331
- delete parent[key];
332
-
333
- // If we have ordered elements, rebuild the arrays in correct order
334
- if (orderedElements.length > 0) {
335
- // Group elements by type
336
- const rebuiltArrays = new Map<string, any[]>();
337
-
338
- for (const { type, element } of orderedElements) {
339
- if (!rebuiltArrays.has(type)) {
340
- rebuiltArrays.set(type, []);
341
- }
342
- rebuiltArrays.get(type)!.push(element);
343
- }
344
-
345
- // Update parent with rebuilt arrays
346
- for (const [type, elements] of rebuiltArrays) {
347
- if (elements.length === 1) {
348
- parent[type] = elements[0];
349
- } else {
350
- parent[type] = elements;
351
- }
352
- }
353
-
354
- // Rebuild _orderedChildren
355
- const newOrderedChildren: OrderedChildInfo[] = [];
356
- const typeCounters = new Map<string, number>();
357
-
358
- for (const { type } of orderedElements) {
359
- const idx = typeCounters.get(type) || 0;
360
- typeCounters.set(type, idx + 1);
361
- newOrderedChildren.push({ type, index: idx });
362
- }
363
-
364
- parent._orderedChildren = newOrderedChildren;
365
- } else {
366
- // Fallback for when there's no _orderedChildren - just merge
367
- for (const element of elementArray) {
368
- if (!element || typeof element !== 'object') continue;
369
-
370
- const elementKeys = Object.keys(element).filter(
371
- (k) => !k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren'
372
- );
373
-
374
- for (const childKey of elementKeys) {
375
- RevisionWalker.mergeIntoParent(parent, childKey, element[childKey]);
376
- }
377
- }
378
- }
379
- }
380
-
381
- /**
382
- * Remove all elements of a given type (including their content)
383
- */
384
- private static removeAllElements(parent: any, key: string): void {
385
- if (!parent[key]) return;
386
-
387
- // Update _orderedChildren before removing
388
- if (parent._orderedChildren) {
389
- parent._orderedChildren = parent._orderedChildren.filter(
390
- (c: OrderedChildInfo) => c.type !== key
391
- );
392
- // Re-index remaining elements of same types
393
- RevisionWalker.reindexOrderedChildren(parent._orderedChildren);
394
- }
395
-
396
- // Remove the element
397
- delete parent[key];
398
- }
399
-
400
- /**
401
- * Merge a child value into the parent, handling arrays properly
402
- */
403
- private static mergeIntoParent(
404
- parent: any,
405
- childKey: string,
406
- childValue: any
407
- ): void {
408
- if (parent[childKey] === undefined) {
409
- // No existing value, just assign
410
- parent[childKey] = childValue;
411
- } else {
412
- // Existing value, need to merge
413
- const existing = parent[childKey];
414
- const incoming = Array.isArray(childValue) ? childValue : [childValue];
415
-
416
- if (Array.isArray(existing)) {
417
- parent[childKey] = [...existing, ...incoming];
418
- } else {
419
- parent[childKey] = [existing, ...incoming];
420
- }
421
- }
422
- }
423
-
424
- /**
425
- * Re-index _orderedChildren to ensure indices are sequential per type
426
- */
427
- private static reindexOrderedChildren(
428
- orderedChildren: OrderedChildInfo[]
429
- ): void {
430
- const typeCounters = new Map<string, number>();
431
-
432
- for (const entry of orderedChildren) {
433
- const currentIndex = typeCounters.get(entry.type) || 0;
434
- entry.index = currentIndex;
435
- typeCounters.set(entry.type, currentIndex + 1);
436
- }
437
- }
438
-
439
- /**
440
- * Check if an element is a revision-related element (any type)
441
- */
442
- static isRevisionElement(key: string): boolean {
443
- return (
444
- REVISION_ELEMENTS.UNWRAP.includes(key) ||
445
- REVISION_ELEMENTS.REMOVE.includes(key) ||
446
- REVISION_ELEMENTS.PROPERTY_CHANGES.includes(key) ||
447
- REVISION_ELEMENTS.RANGE_MARKERS.includes(key)
448
- );
449
- }
450
-
451
- /**
452
- * Get revision element categories (for external use/testing)
453
- */
454
- static getRevisionElementCategories(): typeof REVISION_ELEMENTS {
455
- return { ...REVISION_ELEMENTS };
456
- }
457
- }
1
+ /**
2
+ * RevisionWalker - DOM-based tree walker for accepting tracked changes
3
+ *
4
+ * Replaces the fragile RegEx-based revision acceptance with a robust
5
+ * DOM-based approach that properly handles nested elements and preserves
6
+ * element ordering.
7
+ *
8
+ * @module RevisionWalker
9
+ */
10
+
11
+ import { ParsedXMLObject } from '../xml/XMLParser';
12
+
13
+ /**
14
+ * Options for controlling which revision types to accept
15
+ */
16
+ export interface RevisionWalkerOptions {
17
+ /** Keep content, remove w:ins wrapper (default: true) */
18
+ acceptInsertions?: boolean;
19
+ /** Remove w:del and content entirely (default: true) */
20
+ acceptDeletions?: boolean;
21
+ /** Handle w:moveFrom/w:moveTo (default: true) */
22
+ acceptMoves?: boolean;
23
+ /** Remove *Change elements (default: true) */
24
+ acceptPropertyChanges?: boolean;
25
+ }
26
+
27
+ /**
28
+ * Structure for tracking element order
29
+ */
30
+ interface OrderedChildInfo {
31
+ type: string;
32
+ index: number;
33
+ }
34
+
35
+ /**
36
+ * Revision element categories
37
+ */
38
+ const REVISION_ELEMENTS = {
39
+ /** Elements to unwrap (keep content, remove wrapper) */
40
+ UNWRAP: ['w:ins', 'w:moveTo'],
41
+
42
+ /** Elements to remove entirely (with content) */
43
+ REMOVE: ['w:del', 'w:moveFrom'],
44
+
45
+ /** Property change tracking elements */
46
+ PROPERTY_CHANGES: [
47
+ 'w:rPrChange',
48
+ 'w:pPrChange',
49
+ 'w:tblPrChange',
50
+ 'w:tcPrChange',
51
+ 'w:trPrChange',
52
+ 'w:sectPrChange',
53
+ 'w:tblGridChange',
54
+ 'w:numberingChange',
55
+ 'w:tblPrExChange',
56
+ ],
57
+
58
+ /** Range marker elements */
59
+ RANGE_MARKERS: [
60
+ 'w:moveFromRangeStart',
61
+ 'w:moveFromRangeEnd',
62
+ 'w:moveToRangeStart',
63
+ 'w:moveToRangeEnd',
64
+ 'w:customXmlInsRangeStart',
65
+ 'w:customXmlInsRangeEnd',
66
+ 'w:customXmlDelRangeStart',
67
+ 'w:customXmlDelRangeEnd',
68
+ 'w:customXmlMoveFromRangeStart',
69
+ 'w:customXmlMoveFromRangeEnd',
70
+ 'w:customXmlMoveToRangeStart',
71
+ 'w:customXmlMoveToRangeEnd',
72
+ ],
73
+ };
74
+
75
+ /**
76
+ * DOM-based tree walker for accepting Word document revisions
77
+ *
78
+ * This class processes a parsed XML object tree (from XMLParser.parseToObject())
79
+ * and accepts all tracked changes by:
80
+ * - Unwrapping insertions (w:ins, w:moveTo) - keeping content
81
+ * - Removing deletions (w:del, w:moveFrom) - discarding content
82
+ * - Removing property changes (*Change elements)
83
+ * - Removing range markers
84
+ *
85
+ * Element order is preserved using the _orderedChildren metadata.
86
+ */
87
+ export class RevisionWalker {
88
+ /**
89
+ * Process a parsed XML object tree and accept all revisions
90
+ *
91
+ * @param obj - Parsed XML object from XMLParser.parseToObject()
92
+ * @param options - Options controlling which revisions to accept
93
+ * @returns New object tree with revisions accepted
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const parsed = XMLParser.parseToObject(documentXml);
98
+ * const clean = RevisionWalker.processTree(parsed);
99
+ * ```
100
+ */
101
+ static processTree(obj: ParsedXMLObject, options?: RevisionWalkerOptions): ParsedXMLObject {
102
+ const opts: Required<RevisionWalkerOptions> = {
103
+ acceptInsertions: options?.acceptInsertions ?? true,
104
+ acceptDeletions: options?.acceptDeletions ?? true,
105
+ acceptMoves: options?.acceptMoves ?? true,
106
+ acceptPropertyChanges: options?.acceptPropertyChanges ?? true,
107
+ };
108
+
109
+ // Deep clone the object to avoid mutating the original
110
+ const clone = RevisionWalker.deepClone(obj);
111
+
112
+ // Walk and transform the tree
113
+ RevisionWalker.walkAndTransform(clone, opts);
114
+
115
+ return clone;
116
+ }
117
+
118
+ /**
119
+ * Deep clone an object
120
+ */
121
+ private static deepClone(obj: any): any {
122
+ if (obj === null || typeof obj !== 'object') {
123
+ return obj;
124
+ }
125
+
126
+ if (Array.isArray(obj)) {
127
+ return obj.map((item) => RevisionWalker.deepClone(item));
128
+ }
129
+
130
+ const clone: any = {};
131
+ for (const key of Object.keys(obj)) {
132
+ clone[key] = RevisionWalker.deepClone(obj[key]);
133
+ }
134
+ return clone;
135
+ }
136
+
137
+ /**
138
+ * Recursively walk and transform the object tree
139
+ * Processes children first (depth-first) to handle nested revisions
140
+ */
141
+ private static walkAndTransform(obj: any, options: Required<RevisionWalkerOptions>): void {
142
+ if (obj === null || typeof obj !== 'object') {
143
+ return;
144
+ }
145
+
146
+ // Get keys to process (excluding metadata keys)
147
+ const keys = Object.keys(obj).filter(
148
+ (k) => !k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren'
149
+ );
150
+
151
+ // First pass: recurse into children (depth-first)
152
+ for (const key of keys) {
153
+ const value = obj[key];
154
+ if (Array.isArray(value)) {
155
+ for (const item of value) {
156
+ RevisionWalker.walkAndTransform(item, options);
157
+ }
158
+ } else if (typeof value === 'object' && value !== null) {
159
+ RevisionWalker.walkAndTransform(value, options);
160
+ }
161
+ }
162
+
163
+ // Second pass: process revision elements at this level
164
+ // We need to iterate carefully because we're modifying the object
165
+ RevisionWalker.processRevisions(obj, options);
166
+ }
167
+
168
+ /**
169
+ * Process revision elements at the current level
170
+ */
171
+ private static processRevisions(parent: any, options: Required<RevisionWalkerOptions>): void {
172
+ if (!parent || typeof parent !== 'object') {
173
+ return;
174
+ }
175
+
176
+ const keysToProcess = Object.keys(parent).filter(
177
+ (k) => !k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren'
178
+ );
179
+
180
+ for (const key of keysToProcess) {
181
+ // Check if this is a revision element
182
+ if (RevisionWalker.shouldUnwrap(key, options)) {
183
+ RevisionWalker.unwrapAllElements(parent, key);
184
+ } else if (RevisionWalker.shouldRemove(key, options)) {
185
+ RevisionWalker.removeAllElements(parent, key);
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Check if an element should be unwrapped (content kept)
192
+ */
193
+ private static shouldUnwrap(key: string, options: Required<RevisionWalkerOptions>): boolean {
194
+ if (REVISION_ELEMENTS.UNWRAP.includes(key)) {
195
+ if (key === 'w:ins' && !options.acceptInsertions) return false;
196
+ if (key === 'w:moveTo' && !options.acceptMoves) return false;
197
+ return true;
198
+ }
199
+ return false;
200
+ }
201
+
202
+ /**
203
+ * Check if an element should be removed (content discarded)
204
+ */
205
+ private static shouldRemove(key: string, options: Required<RevisionWalkerOptions>): boolean {
206
+ if (REVISION_ELEMENTS.REMOVE.includes(key)) {
207
+ if (key === 'w:del' && !options.acceptDeletions) return false;
208
+ if (key === 'w:moveFrom' && !options.acceptMoves) return false;
209
+ return true;
210
+ }
211
+ if (REVISION_ELEMENTS.PROPERTY_CHANGES.includes(key)) {
212
+ return options.acceptPropertyChanges;
213
+ }
214
+ if (REVISION_ELEMENTS.RANGE_MARKERS.includes(key)) {
215
+ return true; // Always remove range markers
216
+ }
217
+ return false;
218
+ }
219
+
220
+ /**
221
+ * Unwrap all elements of a given type, promoting their children to parent
222
+ *
223
+ * This is the most complex operation in RevisionWalker. When we unwrap a
224
+ * revision element like w:ins, we need to:
225
+ * 1. Extract the children from inside w:ins
226
+ * 2. Insert them at the correct position in the parent's element arrays
227
+ * 3. Update _orderedChildren to reflect the new structure
228
+ *
229
+ * The key challenge is maintaining correct element order. For example:
230
+ * Before: <w:tbl><w:tr>Row1</w:tr><w:ins><w:tr>Row2</w:tr></w:ins><w:tr>Row3</w:tr></w:tbl>
231
+ * After: <w:tbl><w:tr>Row1</w:tr><w:tr>Row2</w:tr><w:tr>Row3</w:tr></w:tbl>
232
+ */
233
+ private static unwrapAllElements(parent: any, key: string): void {
234
+ const elements = parent[key];
235
+ if (!elements) return;
236
+
237
+ const elementArray = Array.isArray(elements) ? elements : [elements];
238
+
239
+ // Build a complete ordered list of child elements by walking _orderedChildren
240
+ // This captures the intended order before we modify anything
241
+ const orderedElements: { type: string; element: any }[] = [];
242
+
243
+ if (parent._orderedChildren) {
244
+ // Track how many of each type we've seen to get correct array index
245
+ const typeCounters = new Map<string, number>();
246
+ let unwrappedIndex = 0;
247
+
248
+ for (const entry of parent._orderedChildren) {
249
+ const { type } = entry;
250
+
251
+ if (type === key) {
252
+ // This is a revision element - extract its children in order
253
+ const revElement = elementArray[unwrappedIndex];
254
+ unwrappedIndex++;
255
+
256
+ if (revElement && typeof revElement === 'object') {
257
+ if (revElement._orderedChildren) {
258
+ // Use the revision element's _orderedChildren for correct order
259
+ const childCounters = new Map<string, number>();
260
+ for (const childEntry of revElement._orderedChildren) {
261
+ const childType = childEntry.type;
262
+ const childIdx = childCounters.get(childType) || 0;
263
+ childCounters.set(childType, childIdx + 1);
264
+
265
+ const childElements = revElement[childType];
266
+ if (childElements) {
267
+ const childArray = Array.isArray(childElements) ? childElements : [childElements];
268
+ if (childIdx < childArray.length) {
269
+ orderedElements.push({
270
+ type: childType,
271
+ element: childArray[childIdx],
272
+ });
273
+ }
274
+ }
275
+ }
276
+ } else {
277
+ // No _orderedChildren, extract children in object key order
278
+ const childKeys = Object.keys(revElement).filter(
279
+ (k) => !k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren'
280
+ );
281
+ for (const childKey of childKeys) {
282
+ const childValue = revElement[childKey];
283
+ const childArray = Array.isArray(childValue) ? childValue : [childValue];
284
+ for (const child of childArray) {
285
+ orderedElements.push({ type: childKey, element: child });
286
+ }
287
+ }
288
+ }
289
+ }
290
+ } else {
291
+ // Regular element - get it from the parent
292
+ const idx = typeCounters.get(type) || 0;
293
+ typeCounters.set(type, idx + 1);
294
+
295
+ const parentElements = parent[type];
296
+ if (parentElements) {
297
+ const parentArray = Array.isArray(parentElements) ? parentElements : [parentElements];
298
+ if (idx < parentArray.length) {
299
+ orderedElements.push({ type, element: parentArray[idx] });
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+
306
+ // Remove the revision wrapper
307
+ delete parent[key];
308
+
309
+ // If we have ordered elements, rebuild the arrays in correct order
310
+ if (orderedElements.length > 0) {
311
+ // Group elements by type
312
+ const rebuiltArrays = new Map<string, any[]>();
313
+
314
+ for (const { type, element } of orderedElements) {
315
+ if (!rebuiltArrays.has(type)) {
316
+ rebuiltArrays.set(type, []);
317
+ }
318
+ rebuiltArrays.get(type)!.push(element);
319
+ }
320
+
321
+ // Update parent with rebuilt arrays
322
+ for (const [type, elements] of rebuiltArrays) {
323
+ if (elements.length === 1) {
324
+ parent[type] = elements[0];
325
+ } else {
326
+ parent[type] = elements;
327
+ }
328
+ }
329
+
330
+ // Rebuild _orderedChildren
331
+ const newOrderedChildren: OrderedChildInfo[] = [];
332
+ const typeCounters = new Map<string, number>();
333
+
334
+ for (const { type } of orderedElements) {
335
+ const idx = typeCounters.get(type) || 0;
336
+ typeCounters.set(type, idx + 1);
337
+ newOrderedChildren.push({ type, index: idx });
338
+ }
339
+
340
+ parent._orderedChildren = newOrderedChildren;
341
+ } else {
342
+ // Fallback for when there's no _orderedChildren - just merge
343
+ for (const element of elementArray) {
344
+ if (!element || typeof element !== 'object') continue;
345
+
346
+ const elementKeys = Object.keys(element).filter(
347
+ (k) => !k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren'
348
+ );
349
+
350
+ for (const childKey of elementKeys) {
351
+ RevisionWalker.mergeIntoParent(parent, childKey, element[childKey]);
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Remove all elements of a given type (including their content)
359
+ */
360
+ private static removeAllElements(parent: any, key: string): void {
361
+ if (!parent[key]) return;
362
+
363
+ // Update _orderedChildren before removing
364
+ if (parent._orderedChildren) {
365
+ parent._orderedChildren = parent._orderedChildren.filter(
366
+ (c: OrderedChildInfo) => c.type !== key
367
+ );
368
+ // Re-index remaining elements of same types
369
+ RevisionWalker.reindexOrderedChildren(parent._orderedChildren);
370
+ }
371
+
372
+ // Remove the element
373
+ delete parent[key];
374
+ }
375
+
376
+ /**
377
+ * Merge a child value into the parent, handling arrays properly
378
+ */
379
+ private static mergeIntoParent(parent: any, childKey: string, childValue: any): void {
380
+ if (parent[childKey] === undefined) {
381
+ // No existing value, just assign
382
+ parent[childKey] = childValue;
383
+ } else {
384
+ // Existing value, need to merge
385
+ const existing = parent[childKey];
386
+ const incoming = Array.isArray(childValue) ? childValue : [childValue];
387
+
388
+ if (Array.isArray(existing)) {
389
+ parent[childKey] = [...existing, ...incoming];
390
+ } else {
391
+ parent[childKey] = [existing, ...incoming];
392
+ }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Re-index _orderedChildren to ensure indices are sequential per type
398
+ */
399
+ private static reindexOrderedChildren(orderedChildren: OrderedChildInfo[]): void {
400
+ const typeCounters = new Map<string, number>();
401
+
402
+ for (const entry of orderedChildren) {
403
+ const currentIndex = typeCounters.get(entry.type) || 0;
404
+ entry.index = currentIndex;
405
+ typeCounters.set(entry.type, currentIndex + 1);
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Check if an element is a revision-related element (any type)
411
+ */
412
+ static isRevisionElement(key: string): boolean {
413
+ return (
414
+ REVISION_ELEMENTS.UNWRAP.includes(key) ||
415
+ REVISION_ELEMENTS.REMOVE.includes(key) ||
416
+ REVISION_ELEMENTS.PROPERTY_CHANGES.includes(key) ||
417
+ REVISION_ELEMENTS.RANGE_MARKERS.includes(key)
418
+ );
419
+ }
420
+
421
+ /**
422
+ * Get revision element categories (for external use/testing)
423
+ */
424
+ static getRevisionElementCategories(): typeof REVISION_ELEMENTS {
425
+ return { ...REVISION_ELEMENTS };
426
+ }
427
+ }