docxmlater 11.0.10 → 12.1.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 (361) hide show
  1. package/README.md +53 -13
  2. package/dist/core/Document.d.ts +29 -3
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +733 -170
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentGenerator.d.ts +1 -0
  7. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  8. package/dist/core/DocumentGenerator.js +77 -25
  9. package/dist/core/DocumentGenerator.js.map +1 -1
  10. package/dist/core/DocumentParser.d.ts +7 -3
  11. package/dist/core/DocumentParser.d.ts.map +1 -1
  12. package/dist/core/DocumentParser.js +427 -81
  13. package/dist/core/DocumentParser.js.map +1 -1
  14. package/dist/core/RelationshipManager.d.ts.map +1 -1
  15. package/dist/core/RelationshipManager.js +2 -2
  16. package/dist/core/RelationshipManager.js.map +1 -1
  17. package/dist/elements/Comment.d.ts +15 -1
  18. package/dist/elements/Comment.d.ts.map +1 -1
  19. package/dist/elements/Comment.js +67 -12
  20. package/dist/elements/Comment.js.map +1 -1
  21. package/dist/elements/CommentManager.d.ts +4 -0
  22. package/dist/elements/CommentManager.d.ts.map +1 -1
  23. package/dist/elements/CommentManager.js +74 -16
  24. package/dist/elements/CommentManager.js.map +1 -1
  25. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  26. package/dist/elements/EndnoteManager.js.map +1 -1
  27. package/dist/elements/Field.d.ts +10 -1
  28. package/dist/elements/Field.d.ts.map +1 -1
  29. package/dist/elements/Field.js +76 -34
  30. package/dist/elements/Field.js.map +1 -1
  31. package/dist/elements/FieldHelpers.d.ts +1 -0
  32. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  33. package/dist/elements/FieldHelpers.js +8 -4
  34. package/dist/elements/FieldHelpers.js.map +1 -1
  35. package/dist/elements/Footer.d.ts +3 -1
  36. package/dist/elements/Footer.d.ts.map +1 -1
  37. package/dist/elements/Footer.js +39 -53
  38. package/dist/elements/Footer.js.map +1 -1
  39. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  40. package/dist/elements/FootnoteManager.js.map +1 -1
  41. package/dist/elements/Header.d.ts +3 -1
  42. package/dist/elements/Header.d.ts.map +1 -1
  43. package/dist/elements/Header.js +39 -53
  44. package/dist/elements/Header.js.map +1 -1
  45. package/dist/elements/HeaderFooterManager.d.ts +4 -2
  46. package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
  47. package/dist/elements/HeaderFooterManager.js +36 -6
  48. package/dist/elements/HeaderFooterManager.js.map +1 -1
  49. package/dist/elements/Hyperlink.d.ts +6 -1
  50. package/dist/elements/Hyperlink.d.ts.map +1 -1
  51. package/dist/elements/Hyperlink.js +69 -44
  52. package/dist/elements/Hyperlink.js.map +1 -1
  53. package/dist/elements/Image.d.ts.map +1 -1
  54. package/dist/elements/Image.js +33 -6
  55. package/dist/elements/Image.js.map +1 -1
  56. package/dist/elements/ImageManager.d.ts +1 -0
  57. package/dist/elements/ImageManager.d.ts.map +1 -1
  58. package/dist/elements/ImageManager.js +11 -0
  59. package/dist/elements/ImageManager.js.map +1 -1
  60. package/dist/elements/Paragraph.d.ts +3 -0
  61. package/dist/elements/Paragraph.d.ts.map +1 -1
  62. package/dist/elements/Paragraph.js +91 -26
  63. package/dist/elements/Paragraph.js.map +1 -1
  64. package/dist/elements/Revision.d.ts +4 -0
  65. package/dist/elements/Revision.d.ts.map +1 -1
  66. package/dist/elements/Revision.js +369 -29
  67. package/dist/elements/Revision.js.map +1 -1
  68. package/dist/elements/Run.d.ts +1 -0
  69. package/dist/elements/Run.d.ts.map +1 -1
  70. package/dist/elements/Run.js +24 -4
  71. package/dist/elements/Run.js.map +1 -1
  72. package/dist/elements/Section.d.ts.map +1 -1
  73. package/dist/elements/Section.js +17 -7
  74. package/dist/elements/Section.js.map +1 -1
  75. package/dist/elements/Shape.d.ts.map +1 -1
  76. package/dist/elements/Shape.js +50 -42
  77. package/dist/elements/Shape.js.map +1 -1
  78. package/dist/elements/StructuredDocumentTag.d.ts +9 -1
  79. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  80. package/dist/elements/StructuredDocumentTag.js +46 -6
  81. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  82. package/dist/elements/Table.d.ts +2 -0
  83. package/dist/elements/Table.d.ts.map +1 -1
  84. package/dist/elements/Table.js +90 -42
  85. package/dist/elements/Table.js.map +1 -1
  86. package/dist/elements/TableCell.d.ts +1 -0
  87. package/dist/elements/TableCell.d.ts.map +1 -1
  88. package/dist/elements/TableCell.js +32 -13
  89. package/dist/elements/TableCell.js.map +1 -1
  90. package/dist/elements/TableOfContents.d.ts.map +1 -1
  91. package/dist/elements/TableOfContents.js +5 -7
  92. package/dist/elements/TableOfContents.js.map +1 -1
  93. package/dist/elements/TableRow.d.ts +1 -0
  94. package/dist/elements/TableRow.d.ts.map +1 -1
  95. package/dist/elements/TableRow.js +16 -1
  96. package/dist/elements/TableRow.js.map +1 -1
  97. package/dist/elements/TextBox.d.ts.map +1 -1
  98. package/dist/elements/TextBox.js +55 -42
  99. package/dist/elements/TextBox.js.map +1 -1
  100. package/dist/esm/core/Document.js +735 -172
  101. package/dist/esm/core/Document.js.map +1 -1
  102. package/dist/esm/core/DocumentGenerator.js +77 -25
  103. package/dist/esm/core/DocumentGenerator.js.map +1 -1
  104. package/dist/esm/core/DocumentParser.js +427 -81
  105. package/dist/esm/core/DocumentParser.js.map +1 -1
  106. package/dist/esm/core/RelationshipManager.js +2 -2
  107. package/dist/esm/core/RelationshipManager.js.map +1 -1
  108. package/dist/esm/elements/Comment.js +67 -12
  109. package/dist/esm/elements/Comment.js.map +1 -1
  110. package/dist/esm/elements/CommentManager.js +74 -16
  111. package/dist/esm/elements/CommentManager.js.map +1 -1
  112. package/dist/esm/elements/EndnoteManager.js.map +1 -1
  113. package/dist/esm/elements/Field.js +77 -35
  114. package/dist/esm/elements/Field.js.map +1 -1
  115. package/dist/esm/elements/FieldHelpers.js +7 -4
  116. package/dist/esm/elements/FieldHelpers.js.map +1 -1
  117. package/dist/esm/elements/Footer.js +39 -53
  118. package/dist/esm/elements/Footer.js.map +1 -1
  119. package/dist/esm/elements/FootnoteManager.js.map +1 -1
  120. package/dist/esm/elements/Header.js +39 -53
  121. package/dist/esm/elements/Header.js.map +1 -1
  122. package/dist/esm/elements/HeaderFooterManager.js +36 -6
  123. package/dist/esm/elements/HeaderFooterManager.js.map +1 -1
  124. package/dist/esm/elements/Hyperlink.js +69 -44
  125. package/dist/esm/elements/Hyperlink.js.map +1 -1
  126. package/dist/esm/elements/Image.js +33 -6
  127. package/dist/esm/elements/Image.js.map +1 -1
  128. package/dist/esm/elements/ImageManager.js +11 -0
  129. package/dist/esm/elements/ImageManager.js.map +1 -1
  130. package/dist/esm/elements/Paragraph.js +91 -26
  131. package/dist/esm/elements/Paragraph.js.map +1 -1
  132. package/dist/esm/elements/Revision.js +369 -29
  133. package/dist/esm/elements/Revision.js.map +1 -1
  134. package/dist/esm/elements/Run.js +24 -4
  135. package/dist/esm/elements/Run.js.map +1 -1
  136. package/dist/esm/elements/Section.js +17 -7
  137. package/dist/esm/elements/Section.js.map +1 -1
  138. package/dist/esm/elements/Shape.js +50 -42
  139. package/dist/esm/elements/Shape.js.map +1 -1
  140. package/dist/esm/elements/StructuredDocumentTag.js +46 -6
  141. package/dist/esm/elements/StructuredDocumentTag.js.map +1 -1
  142. package/dist/esm/elements/Table.js +90 -42
  143. package/dist/esm/elements/Table.js.map +1 -1
  144. package/dist/esm/elements/TableCell.js +32 -13
  145. package/dist/esm/elements/TableCell.js.map +1 -1
  146. package/dist/esm/elements/TableOfContents.js +5 -7
  147. package/dist/esm/elements/TableOfContents.js.map +1 -1
  148. package/dist/esm/elements/TableRow.js +16 -1
  149. package/dist/esm/elements/TableRow.js.map +1 -1
  150. package/dist/esm/elements/TextBox.js +55 -42
  151. package/dist/esm/elements/TextBox.js.map +1 -1
  152. package/dist/esm/formatting/AbstractNumbering.js +1 -1
  153. package/dist/esm/formatting/AbstractNumbering.js.map +1 -1
  154. package/dist/esm/formatting/NumberingLevel.js +8 -6
  155. package/dist/esm/formatting/NumberingLevel.js.map +1 -1
  156. package/dist/esm/formatting/NumberingManager.js +16 -1
  157. package/dist/esm/formatting/NumberingManager.js.map +1 -1
  158. package/dist/esm/formatting/Style.js +14 -0
  159. package/dist/esm/formatting/Style.js.map +1 -1
  160. package/dist/esm/formatting/StylesManager.js +14 -2
  161. package/dist/esm/formatting/StylesManager.js.map +1 -1
  162. package/dist/esm/helpers/CleanupHelper.js +103 -26
  163. package/dist/esm/helpers/CleanupHelper.js.map +1 -1
  164. package/dist/esm/images/ImageOptimizer.js +52 -4
  165. package/dist/esm/images/ImageOptimizer.js.map +1 -1
  166. package/dist/esm/index.js +1 -0
  167. package/dist/esm/index.js.map +1 -1
  168. package/dist/esm/internal.js +1 -1
  169. package/dist/esm/internal.js.map +1 -1
  170. package/dist/esm/processors/CompatibilityUpgrader.js +7 -1
  171. package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -1
  172. package/dist/esm/processors/InMemoryRevisionAcceptor.js +134 -13
  173. package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -1
  174. package/dist/esm/processors/RevisionWalker.js +223 -39
  175. package/dist/esm/processors/RevisionWalker.js.map +1 -1
  176. package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
  177. package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
  178. package/dist/esm/processors/acceptRevisions.js +20 -11
  179. package/dist/esm/processors/acceptRevisions.js.map +1 -1
  180. package/dist/esm/processors/cnfStyleDecoder.js +7 -7
  181. package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
  182. package/dist/esm/processors/stripTrackedChanges.js +16 -5
  183. package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
  184. package/dist/esm/utils/deepClone.js +24 -11
  185. package/dist/esm/utils/deepClone.js.map +1 -1
  186. package/dist/esm/utils/units.js +1 -1
  187. package/dist/esm/utils/units.js.map +1 -1
  188. package/dist/esm/utils/validation.js +11 -23
  189. package/dist/esm/utils/validation.js.map +1 -1
  190. package/dist/esm/utils/xmlSanitization.js +131 -7
  191. package/dist/esm/utils/xmlSanitization.js.map +1 -1
  192. package/dist/esm/validation/RevisionAutoFixer.js +69 -55
  193. package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
  194. package/dist/esm/validation/RevisionValidator.js +24 -20
  195. package/dist/esm/validation/RevisionValidator.js.map +1 -1
  196. package/dist/esm/xml/XMLBuilder.js +52 -20
  197. package/dist/esm/xml/XMLBuilder.js.map +1 -1
  198. package/dist/esm/xml/XMLParser.js +55 -13
  199. package/dist/esm/xml/XMLParser.js.map +1 -1
  200. package/dist/esm/zip/ZipHandler.js +37 -13
  201. package/dist/esm/zip/ZipHandler.js.map +1 -1
  202. package/dist/esm/zip/ZipReader.js +53 -6
  203. package/dist/esm/zip/ZipReader.js.map +1 -1
  204. package/dist/esm/zip/errors.js +7 -0
  205. package/dist/esm/zip/errors.js.map +1 -1
  206. package/dist/esm/zip/types.js +4 -0
  207. package/dist/esm/zip/types.js.map +1 -1
  208. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  209. package/dist/formatting/AbstractNumbering.js +1 -1
  210. package/dist/formatting/AbstractNumbering.js.map +1 -1
  211. package/dist/formatting/NumberingLevel.d.ts +1 -1
  212. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  213. package/dist/formatting/NumberingLevel.js +8 -6
  214. package/dist/formatting/NumberingLevel.js.map +1 -1
  215. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  216. package/dist/formatting/NumberingManager.js +16 -1
  217. package/dist/formatting/NumberingManager.js.map +1 -1
  218. package/dist/formatting/Style.d.ts.map +1 -1
  219. package/dist/formatting/Style.js +14 -0
  220. package/dist/formatting/Style.js.map +1 -1
  221. package/dist/formatting/StylesManager.d.ts +2 -0
  222. package/dist/formatting/StylesManager.d.ts.map +1 -1
  223. package/dist/formatting/StylesManager.js +14 -2
  224. package/dist/formatting/StylesManager.js.map +1 -1
  225. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  226. package/dist/helpers/CleanupHelper.js +103 -26
  227. package/dist/helpers/CleanupHelper.js.map +1 -1
  228. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  229. package/dist/images/ImageOptimizer.js +52 -4
  230. package/dist/images/ImageOptimizer.js.map +1 -1
  231. package/dist/index.d.ts +3 -1
  232. package/dist/index.d.ts.map +1 -1
  233. package/dist/index.js +4 -2
  234. package/dist/index.js.map +1 -1
  235. package/dist/internal.d.ts +1 -1
  236. package/dist/internal.d.ts.map +1 -1
  237. package/dist/internal.js +2 -1
  238. package/dist/internal.js.map +1 -1
  239. package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
  240. package/dist/processors/CompatibilityUpgrader.js +7 -1
  241. package/dist/processors/CompatibilityUpgrader.js.map +1 -1
  242. package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
  243. package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
  244. package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
  245. package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
  246. package/dist/processors/RevisionWalker.d.ts +9 -3
  247. package/dist/processors/RevisionWalker.d.ts.map +1 -1
  248. package/dist/processors/RevisionWalker.js +223 -39
  249. package/dist/processors/RevisionWalker.js.map +1 -1
  250. package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
  251. package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
  252. package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
  253. package/dist/processors/acceptRevisions.d.ts +1 -0
  254. package/dist/processors/acceptRevisions.d.ts.map +1 -1
  255. package/dist/processors/acceptRevisions.js +21 -11
  256. package/dist/processors/acceptRevisions.js.map +1 -1
  257. package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
  258. package/dist/processors/cnfStyleDecoder.js +7 -7
  259. package/dist/processors/cnfStyleDecoder.js.map +1 -1
  260. package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
  261. package/dist/processors/stripTrackedChanges.js +16 -5
  262. package/dist/processors/stripTrackedChanges.js.map +1 -1
  263. package/dist/utils/deepClone.d.ts.map +1 -1
  264. package/dist/utils/deepClone.js +24 -11
  265. package/dist/utils/deepClone.js.map +1 -1
  266. package/dist/utils/units.d.ts.map +1 -1
  267. package/dist/utils/units.js +1 -1
  268. package/dist/utils/units.js.map +1 -1
  269. package/dist/utils/validation.d.ts.map +1 -1
  270. package/dist/utils/validation.js +11 -23
  271. package/dist/utils/validation.js.map +1 -1
  272. package/dist/utils/xmlSanitization.d.ts +2 -0
  273. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  274. package/dist/utils/xmlSanitization.js +133 -7
  275. package/dist/utils/xmlSanitization.js.map +1 -1
  276. package/dist/validation/RevisionAutoFixer.d.ts +6 -2
  277. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  278. package/dist/validation/RevisionAutoFixer.js +68 -54
  279. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  280. package/dist/validation/RevisionValidator.d.ts +2 -0
  281. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  282. package/dist/validation/RevisionValidator.js +26 -21
  283. package/dist/validation/RevisionValidator.js.map +1 -1
  284. package/dist/xml/XMLBuilder.d.ts +1 -0
  285. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  286. package/dist/xml/XMLBuilder.js +52 -20
  287. package/dist/xml/XMLBuilder.js.map +1 -1
  288. package/dist/xml/XMLParser.d.ts +1 -0
  289. package/dist/xml/XMLParser.d.ts.map +1 -1
  290. package/dist/xml/XMLParser.js +55 -13
  291. package/dist/xml/XMLParser.js.map +1 -1
  292. package/dist/zip/ZipHandler.d.ts.map +1 -1
  293. package/dist/zip/ZipHandler.js +37 -13
  294. package/dist/zip/ZipHandler.js.map +1 -1
  295. package/dist/zip/ZipReader.d.ts.map +1 -1
  296. package/dist/zip/ZipReader.js +52 -5
  297. package/dist/zip/ZipReader.js.map +1 -1
  298. package/dist/zip/errors.d.ts +3 -0
  299. package/dist/zip/errors.d.ts.map +1 -1
  300. package/dist/zip/errors.js +9 -1
  301. package/dist/zip/errors.js.map +1 -1
  302. package/dist/zip/types.d.ts +4 -0
  303. package/dist/zip/types.d.ts.map +1 -1
  304. package/dist/zip/types.js +4 -0
  305. package/dist/zip/types.js.map +1 -1
  306. package/package.json +2 -2
  307. package/src/core/Document.ts +1184 -242
  308. package/src/core/DocumentGenerator.ts +106 -26
  309. package/src/core/DocumentParser.ts +681 -114
  310. package/src/core/RelationshipManager.ts +446 -442
  311. package/src/elements/Comment.ts +123 -18
  312. package/src/elements/CommentManager.ts +121 -22
  313. package/src/elements/EndnoteManager.ts +4 -1
  314. package/src/elements/Field.ts +134 -43
  315. package/src/elements/FieldHelpers.ts +20 -4
  316. package/src/elements/Footer.ts +62 -67
  317. package/src/elements/FootnoteManager.ts +4 -1
  318. package/src/elements/Header.ts +62 -67
  319. package/src/elements/HeaderFooterManager.ts +56 -8
  320. package/src/elements/Hyperlink.ts +150 -55
  321. package/src/elements/Image.ts +51 -7
  322. package/src/elements/ImageManager.ts +24 -0
  323. package/src/elements/Paragraph.ts +161 -40
  324. package/src/elements/Revision.ts +446 -47
  325. package/src/elements/Run.ts +44 -6
  326. package/src/elements/Section.ts +25 -9
  327. package/src/elements/Shape.ts +52 -44
  328. package/src/elements/StructuredDocumentTag.ts +83 -8
  329. package/src/elements/Table.ts +151 -50
  330. package/src/elements/TableCell.ts +57 -17
  331. package/src/elements/TableOfContents.ts +10 -9
  332. package/src/elements/TableRow.ts +35 -3
  333. package/src/elements/TextBox.ts +58 -43
  334. package/src/formatting/AbstractNumbering.ts +4 -2
  335. package/src/formatting/NumberingLevel.ts +50 -12
  336. package/src/formatting/NumberingManager.ts +32 -3
  337. package/src/formatting/Style.ts +14 -0
  338. package/src/formatting/StylesManager.ts +29 -2
  339. package/src/helpers/CleanupHelper.ts +122 -30
  340. package/src/images/ImageOptimizer.ts +64 -5
  341. package/src/index.ts +7 -0
  342. package/src/internal.ts +1 -0
  343. package/src/processors/CompatibilityUpgrader.ts +15 -2
  344. package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
  345. package/src/processors/RevisionWalker.ts +390 -46
  346. package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
  347. package/src/processors/acceptRevisions.ts +67 -17
  348. package/src/processors/cnfStyleDecoder.ts +18 -12
  349. package/src/processors/stripTrackedChanges.ts +47 -10
  350. package/src/utils/deepClone.ts +48 -14
  351. package/src/utils/units.ts +3 -1
  352. package/src/utils/validation.ts +30 -32
  353. package/src/utils/xmlSanitization.ts +218 -22
  354. package/src/validation/RevisionAutoFixer.ts +109 -65
  355. package/src/validation/RevisionValidator.ts +48 -23
  356. package/src/xml/XMLBuilder.ts +82 -22
  357. package/src/xml/XMLParser.ts +87 -20
  358. package/src/zip/ZipHandler.ts +61 -21
  359. package/src/zip/ZipReader.ts +135 -13
  360. package/src/zip/errors.ts +15 -0
  361. package/src/zip/types.ts +44 -2
@@ -11,19 +11,43 @@
11
11
  import { ParsedXMLObject } from '../xml/XMLParser.js';
12
12
 
13
13
  /**
14
- * Options for controlling which revision types to accept
14
+ * Options for controlling which revision types to process
15
15
  */
16
16
  export interface RevisionWalkerOptions {
17
- /** Keep content, remove w:ins wrapper (default: true) */
17
+ /**
18
+ * Direction of processing (default: 'accept').
19
+ *
20
+ * - 'accept': keep inserted content, discard deleted content (the post-edit
21
+ * document).
22
+ * - 'reject': discard inserted content, restore deleted content and previous
23
+ * formatting (the original pre-edit document — the exact inverse of accept).
24
+ */
25
+ mode?: 'accept' | 'reject';
26
+ /**
27
+ * Process insertions. In 'accept' mode w:ins is unwrapped (content kept); in
28
+ * 'reject' mode w:ins is removed (content discarded). Inserted rows/tables are
29
+ * removed under 'reject'. (default: true)
30
+ */
18
31
  acceptInsertions?: boolean;
19
- /** Remove w:del and content entirely (default: true) */
32
+ /**
33
+ * Process deletions. In 'accept' mode w:del is removed (content discarded); in
34
+ * 'reject' mode w:del is unwrapped and w:delText restored to w:t. Deleted
35
+ * rows/tables are removed under 'accept'. (default: true)
36
+ */
20
37
  acceptDeletions?: boolean;
21
38
  /** Handle w:moveFrom/w:moveTo (default: true) */
22
39
  acceptMoves?: boolean;
23
- /** Remove *Change elements (default: true) */
40
+ /**
41
+ * Process *Change elements. In 'accept' mode they are removed (current
42
+ * formatting kept); in 'reject' mode the previous formatting stored inside the
43
+ * change is restored. (default: true)
44
+ */
24
45
  acceptPropertyChanges?: boolean;
25
46
  }
26
47
 
48
+ /** Action to take on a revision element at a given level. */
49
+ type RevisionAction = 'unwrap' | 'remove' | 'restore' | 'none';
50
+
27
51
  /**
28
52
  * Structure for tracking element order
29
53
  */
@@ -72,6 +96,34 @@ const REVISION_ELEMENTS = {
72
96
  ],
73
97
  };
74
98
 
99
+ /**
100
+ * Children that are tracked independently of a property-change snapshot and
101
+ * must be carried over (not wiped) when rejecting that change restores the
102
+ * previous properties. Keyed by the wrapper element; `position` is where they
103
+ * sit in the wrapper's ECMA-376 child order relative to the restored snapshot:
104
+ *
105
+ * - CT_PPr (§17.3.1.26): the paragraph-mark run properties (`w:rPr`) and the
106
+ * section properties (`w:sectPr`) follow the base paragraph properties, and a
107
+ * `w:pPrChange` snapshot is a CT_PPrBase that contains neither.
108
+ * - CT_SectPr (§17.6.17): the header/footer references PRECEDE the section base
109
+ * contents, and a `w:sectPrChange` snapshot is a CT_SectPrBase that contains
110
+ * neither — so they must be prepended or the section loses its headers/footers.
111
+ */
112
+ const PRESERVED_ON_RESTORE: Record<string, { keys: string[]; position: 'before' | 'after' }> = {
113
+ 'w:pPr': { keys: ['w:rPr', 'w:sectPr'], position: 'after' },
114
+ 'w:sectPr': { keys: ['w:headerReference', 'w:footerReference'], position: 'before' },
115
+ };
116
+
117
+ /**
118
+ * Deleted run-content elements and the live elements they restore to when a
119
+ * deletion is rejected (§17.3.3.7 w:delText is structurally CT_Text, identical
120
+ * to w:t; w:delInstrText likewise mirrors w:instrText).
121
+ */
122
+ const DELETED_TEXT_RENAMES: Record<string, string> = {
123
+ 'w:delText': 'w:t',
124
+ 'w:delInstrText': 'w:instrText',
125
+ };
126
+
75
127
  /**
76
128
  * DOM-based tree walker for accepting Word document revisions
77
129
  *
@@ -100,6 +152,7 @@ export class RevisionWalker {
100
152
  */
101
153
  static processTree(obj: ParsedXMLObject, options?: RevisionWalkerOptions): ParsedXMLObject {
102
154
  const opts: Required<RevisionWalkerOptions> = {
155
+ mode: options?.mode ?? 'accept',
103
156
  acceptInsertions: options?.acceptInsertions ?? true,
104
157
  acceptDeletions: options?.acceptDeletions ?? true,
105
158
  acceptMoves: options?.acceptMoves ?? true,
@@ -112,6 +165,14 @@ export class RevisionWalker {
112
165
  // Walk and transform the tree
113
166
  RevisionWalker.walkAndTransform(clone, opts);
114
167
 
168
+ // Reject restores deleted content by unwrapping w:del; the runs inside a
169
+ // deletion carry their text in w:delText (and field codes in
170
+ // w:delInstrText). Once unwrapped they are live content again, so convert
171
+ // those back to the normal w:t / w:instrText elements. (§17.3.3.7)
172
+ if (opts.mode === 'reject' && opts.acceptDeletions) {
173
+ RevisionWalker.convertDeletedTextToNormal(clone);
174
+ }
175
+
115
176
  return clone;
116
177
  }
117
178
 
@@ -149,8 +210,29 @@ export class RevisionWalker {
149
210
  // detect it at the table level. The default `w:del` removal in
150
211
  // processRevisions only strips the marker itself, leaving a zombie
151
212
  // empty row; per spec, accepting the deletion removes the entire row.
152
- if (options.acceptDeletions && obj['w:tr']) {
153
- RevisionWalker.filterDeletedRows(obj);
213
+ // Tables are checked from the parent first: a table whose rows are ALL
214
+ // tracked-deleted must be removed entirely (a row-less w:tbl violates
215
+ // §17.4.38's at-least-one-row requirement and Word's Accept All drops
216
+ // the whole table), and only the parent can remove the w:tbl itself.
217
+ // Reject is the mirror image: a row whose w:trPr carries a self-closing
218
+ // w:ins marker was added by the edit and must be removed to revert to the
219
+ // original (gated on acceptInsertions, since this undoes an insertion). A
220
+ // table whose rows are ALL insert-marked is removed entirely, mirroring the
221
+ // accept-side at-least-one-row handling.
222
+ if (options.mode === 'reject') {
223
+ if (options.acceptInsertions && obj['w:tbl']) {
224
+ RevisionWalker.removeFullyMarkedTables(obj, 'w:ins');
225
+ }
226
+ if (options.acceptInsertions && obj['w:tr']) {
227
+ RevisionWalker.filterMarkedRows(obj, 'w:ins');
228
+ }
229
+ } else {
230
+ if (options.acceptDeletions && obj['w:tbl']) {
231
+ RevisionWalker.removeFullyMarkedTables(obj, 'w:del');
232
+ }
233
+ if (options.acceptDeletions && obj['w:tr']) {
234
+ RevisionWalker.filterMarkedRows(obj, 'w:del');
235
+ }
154
236
  }
155
237
 
156
238
  // Get keys to process (excluding metadata keys)
@@ -176,36 +258,112 @@ export class RevisionWalker {
176
258
  }
177
259
 
178
260
  /**
179
- * Remove `<w:tr>` entries whose `<w:trPr>` contains a self-closing
180
- * `<w:del/>` row-level deletion marker. Operates on the parsed-object
261
+ * Remove `<w:tr>` entries whose `<w:trPr>` contains a self-closing row-level
262
+ * tracking marker. `marker` is `w:del` when accepting (drop deleted rows) or
263
+ * `w:ins` when rejecting (drop inserted rows). Operates on the parsed-object
181
264
  * representation (arrays when multiple, single object otherwise).
182
265
  */
183
- private static filterDeletedRows(tbl: any): void {
266
+ private static filterMarkedRows(tbl: any, marker: 'w:del' | 'w:ins'): void {
184
267
  const rows = tbl['w:tr'];
185
- const isRowDeleted = (row: any): boolean => {
268
+ const isRowMarked = (row: any): boolean => {
186
269
  if (!row || typeof row !== 'object') return false;
187
270
  const trPr = row['w:trPr'];
188
271
  if (!trPr || typeof trPr !== 'object') return false;
189
- // w:del at row level is a CT_TrackChange (no children). When absent
272
+ // The row-level marker is a CT_TrackChange (no children). When absent
190
273
  // the parser leaves no key; when present it's a self-closing tag
191
274
  // parsed as an object with only @_w:id/@_w:author/@_w:date keys.
192
- return !!trPr['w:del'];
275
+ return !!trPr[marker];
193
276
  };
194
277
 
195
278
  if (Array.isArray(rows)) {
196
- const kept = rows.filter((r) => !isRowDeleted(r));
197
- if (kept.length !== rows.length) {
198
- if (kept.length === 0) {
199
- delete tbl['w:tr'];
200
- } else {
201
- tbl['w:tr'] = kept;
202
- }
279
+ const removedIndices = new Set<number>();
280
+ for (let i = 0; i < rows.length; i++) {
281
+ if (isRowMarked(rows[i])) removedIndices.add(i);
203
282
  }
204
- } else if (isRowDeleted(rows)) {
283
+ if (removedIndices.size === 0) return;
284
+ const kept = rows.filter((_, i) => !removedIndices.has(i));
285
+ if (kept.length === 0) {
286
+ delete tbl['w:tr'];
287
+ } else {
288
+ tbl['w:tr'] = kept;
289
+ }
290
+ RevisionWalker.dropOrderedChildEntries(tbl, 'w:tr', removedIndices);
291
+ } else if (isRowMarked(rows)) {
205
292
  delete tbl['w:tr'];
293
+ RevisionWalker.dropOrderedChildEntries(tbl, 'w:tr', new Set([0]));
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Remove `<w:tbl>` children whose rows are ALL marked with the given
299
+ * row-level tracking marker (`w:del` when accepting, `w:ins` when rejecting).
300
+ *
301
+ * Runs at the table's parent because filterMarkedRows (invoked on the table
302
+ * itself) has no reference back to the container holding the `w:tbl` key.
303
+ * Skips tables that still carry potential row containers — the wrappers that
304
+ * SURVIVE this direction (w:ins/w:moveTo when accepting deletions,
305
+ * w:del/w:moveFrom when rejecting insertions), plus w:sdt / w:customXml —
306
+ * since their rows only materialize as direct `w:tr` children later, when the
307
+ * wrappers are unwrapped during recursion.
308
+ */
309
+ private static removeFullyMarkedTables(parent: any, marker: 'w:del' | 'w:ins'): void {
310
+ const tables = parent['w:tbl'];
311
+ // Wrappers whose rows survive in this direction must not be treated as
312
+ // "rowless": accept keeps w:ins/w:moveTo content, reject keeps w:del/
313
+ // w:moveFrom content.
314
+ const survivingWrappers: string[] =
315
+ marker === 'w:del' ? ['w:ins', 'w:moveTo'] : ['w:del', 'w:moveFrom'];
316
+ const becomesRowless = (tbl: any): boolean => {
317
+ // Tables with no rows at all are pre-existing structures, not the
318
+ // result of a tracked change — leave them untouched.
319
+ if (!tbl || typeof tbl !== 'object' || tbl['w:tr'] === undefined) return false;
320
+ RevisionWalker.filterMarkedRows(tbl, marker);
321
+ return (
322
+ tbl['w:tr'] === undefined &&
323
+ !survivingWrappers.some((w) => tbl[w]) &&
324
+ !tbl['w:sdt'] &&
325
+ !tbl['w:customXml']
326
+ );
327
+ };
328
+
329
+ if (Array.isArray(tables)) {
330
+ const removedIndices = new Set<number>();
331
+ for (let i = 0; i < tables.length; i++) {
332
+ if (becomesRowless(tables[i])) removedIndices.add(i);
333
+ }
334
+ if (removedIndices.size === 0) return;
335
+ const kept = tables.filter((_: any, i: number) => !removedIndices.has(i));
336
+ if (kept.length === 0) {
337
+ delete parent['w:tbl'];
338
+ } else {
339
+ parent['w:tbl'] = kept;
340
+ }
341
+ RevisionWalker.dropOrderedChildEntries(parent, 'w:tbl', removedIndices);
342
+ } else if (becomesRowless(tables)) {
343
+ delete parent['w:tbl'];
344
+ RevisionWalker.dropOrderedChildEntries(parent, 'w:tbl', new Set([0]));
206
345
  }
207
346
  }
208
347
 
348
+ /**
349
+ * Drop specific {type, index} entries from `_orderedChildren` and
350
+ * re-index the survivors. The serializer maps each entry onto the
351
+ * element array by index, so stale entries left after filtering would
352
+ * shift surviving elements relative to inter-element siblings (e.g. a
353
+ * w:bookmarkEnd between table rows) or silently drop the tail.
354
+ */
355
+ private static dropOrderedChildEntries(
356
+ parent: any,
357
+ type: string,
358
+ removedIndices: Set<number>
359
+ ): void {
360
+ if (!parent._orderedChildren) return;
361
+ parent._orderedChildren = parent._orderedChildren.filter(
362
+ (c: OrderedChildInfo) => c.type !== type || !removedIndices.has(c.index)
363
+ );
364
+ RevisionWalker.reindexOrderedChildren(parent._orderedChildren);
365
+ }
366
+
209
367
  /**
210
368
  * Process revision elements at the current level
211
369
  */
@@ -219,43 +377,63 @@ export class RevisionWalker {
219
377
  );
220
378
 
221
379
  for (const key of keysToProcess) {
222
- // Check if this is a revision element
223
- if (RevisionWalker.shouldUnwrap(key, options)) {
224
- RevisionWalker.unwrapAllElements(parent, key);
225
- } else if (RevisionWalker.shouldRemove(key, options)) {
226
- RevisionWalker.removeAllElements(parent, key);
380
+ switch (RevisionWalker.getActionForKey(key, options)) {
381
+ case 'unwrap':
382
+ RevisionWalker.unwrapAllElements(parent, key);
383
+ break;
384
+ case 'remove':
385
+ RevisionWalker.removeAllElements(parent, key);
386
+ break;
387
+ case 'restore':
388
+ RevisionWalker.restorePropertyChange(parent, key);
389
+ break;
390
+ case 'none':
391
+ break;
227
392
  }
228
393
  }
229
394
  }
230
395
 
231
396
  /**
232
- * Check if an element should be unwrapped (content kept)
397
+ * Resolve what to do with a revision element of the given key, honouring the
398
+ * processing direction. Accept and reject are exact inverses for content
399
+ * revisions; property changes are removed on accept and restored on reject.
233
400
  */
234
- private static shouldUnwrap(key: string, options: Required<RevisionWalkerOptions>): boolean {
235
- if (REVISION_ELEMENTS.UNWRAP.includes(key)) {
236
- if (key === 'w:ins' && !options.acceptInsertions) return false;
237
- if (key === 'w:moveTo' && !options.acceptMoves) return false;
238
- return true;
239
- }
240
- return false;
241
- }
242
-
243
- /**
244
- * Check if an element should be removed (content discarded)
245
- */
246
- private static shouldRemove(key: string, options: Required<RevisionWalkerOptions>): boolean {
247
- if (REVISION_ELEMENTS.REMOVE.includes(key)) {
248
- if (key === 'w:del' && !options.acceptDeletions) return false;
249
- if (key === 'w:moveFrom' && !options.acceptMoves) return false;
250
- return true;
401
+ private static getActionForKey(
402
+ key: string,
403
+ options: Required<RevisionWalkerOptions>
404
+ ): RevisionAction {
405
+ const reject = options.mode === 'reject';
406
+
407
+ // Content revisions are driven off REVISION_ELEMENTS so membership stays a
408
+ // single source of truth (shared with isRevisionElement). On accept, UNWRAP
409
+ // keeps content and REMOVE discards it; reject is the exact inverse.
410
+ const isUnwrap = REVISION_ELEMENTS.UNWRAP.includes(key);
411
+ const isRemove = REVISION_ELEMENTS.REMOVE.includes(key);
412
+ if (isUnwrap || isRemove) {
413
+ // Per-key gating: w:ins ↔ insertions, w:del deletions, w:moveTo/
414
+ // w:moveFrom ↔ moves.
415
+ const gate =
416
+ key === 'w:ins'
417
+ ? options.acceptInsertions
418
+ : key === 'w:del'
419
+ ? options.acceptDeletions
420
+ : options.acceptMoves;
421
+ if (!gate) return 'none';
422
+ const keepContent = reject ? isRemove : isUnwrap;
423
+ return keepContent ? 'unwrap' : 'remove';
251
424
  }
252
425
  if (REVISION_ELEMENTS.PROPERTY_CHANGES.includes(key)) {
253
- return options.acceptPropertyChanges;
426
+ if (!options.acceptPropertyChanges) return 'none';
427
+ // w:numberingChange (CT_TrackChangeNumbering) is a legacy attribute-only
428
+ // marker with no embedded previous-properties element, so there is
429
+ // nothing to restore — drop it in both directions.
430
+ if (reject && key !== 'w:numberingChange') return 'restore';
431
+ return 'remove';
254
432
  }
255
433
  if (REVISION_ELEMENTS.RANGE_MARKERS.includes(key)) {
256
- return true; // Always remove range markers
434
+ return 'remove'; // Always remove range markers
257
435
  }
258
- return false;
436
+ return 'none';
259
437
  }
260
438
 
261
439
  /**
@@ -414,6 +592,172 @@ export class RevisionWalker {
414
592
  delete parent[key];
415
593
  }
416
594
 
595
+ /**
596
+ * Restore the previous formatting recorded by a property-change element
597
+ * (reject direction). A change such as `w:rPrChange` lives inside the
598
+ * properties wrapper it tracks (`w:rPr`) and embeds the complete previous
599
+ * wrapper as a child:
600
+ *
601
+ * <w:rPr><w:b/><w:rPrChange><w:rPr><w:i/></w:rPr></w:rPrChange></w:rPr>
602
+ *
603
+ * Rejecting it replaces the current direct formatting (`w:b`) with the
604
+ * embedded previous formatting (`w:i`) and drops the change marker. Children
605
+ * that are tracked independently of the snapshot — e.g. a paragraph's mark
606
+ * run properties / section properties, or a section's header/footer
607
+ * references (see {@link PRESERVED_ON_RESTORE}) — are carried over from the
608
+ * current wrapper in their correct schema position rather than discarded.
609
+ *
610
+ * @param parent - the properties wrapper containing the change element
611
+ * @param changeKey - the change element key, e.g. 'w:rPrChange'
612
+ */
613
+ private static restorePropertyChange(parent: any, changeKey: string): void {
614
+ const changeRaw = parent[changeKey];
615
+ if (!changeRaw) return;
616
+ const changeEl = Array.isArray(changeRaw) ? changeRaw[0] : changeRaw;
617
+
618
+ // 'w:rPrChange' -> 'w:rPr', 'w:tblGridChange' -> 'w:tblGrid', etc.
619
+ const previousKey = changeKey.replace(/Change$/, '');
620
+ const previousRaw =
621
+ changeEl && typeof changeEl === 'object' ? changeEl[previousKey] : undefined;
622
+
623
+ // Malformed change with no embedded previous-properties snapshot: there is
624
+ // nothing to restore, so drop just the marker and leave the current
625
+ // formatting intact rather than emptying the wrapper.
626
+ if (previousRaw === undefined) {
627
+ RevisionWalker.removeAllElements(parent, changeKey);
628
+ return;
629
+ }
630
+ const previousEl = Array.isArray(previousRaw) ? previousRaw[0] : previousRaw;
631
+
632
+ const preserve = PRESERVED_ON_RESTORE[previousKey];
633
+ const preserveKeys = preserve?.keys ?? [];
634
+
635
+ // Snapshot children become the restored formatting (minus any preserved
636
+ // keys it might redundantly carry).
637
+ const snapshotChildren =
638
+ previousEl && typeof previousEl === 'object'
639
+ ? RevisionWalker.getOrderedChildList(previousEl).filter(
640
+ (c) => !preserveKeys.includes(c.type)
641
+ )
642
+ : [];
643
+
644
+ // Preserved children are read from the CURRENT wrapper in their existing
645
+ // document order (captured before the wrapper is cleared below).
646
+ const preservedChildren = preserveKeys.length
647
+ ? RevisionWalker.getOrderedChildList(parent).filter(
648
+ (c) => preserveKeys.includes(c.type) && c.type !== changeKey
649
+ )
650
+ : [];
651
+
652
+ const newChildren =
653
+ preserve?.position === 'before'
654
+ ? [...preservedChildren, ...snapshotChildren]
655
+ : [...snapshotChildren, ...preservedChildren];
656
+
657
+ RevisionWalker.rebuildChildren(parent, newChildren);
658
+ }
659
+
660
+ /**
661
+ * Replace all non-metadata children of `parent` with the supplied ordered
662
+ * `{type, element}` list, regrouping per-type arrays and rebuilding
663
+ * `_orderedChildren` so the serializer emits them in the given order.
664
+ * Attributes (`@_*`) and `#text` are left untouched.
665
+ */
666
+ private static rebuildChildren(parent: any, ordered: { type: string; element: any }[]): void {
667
+ for (const k of Object.keys(parent)) {
668
+ if (!k.startsWith('@_') && k !== '#text' && k !== '_orderedChildren') {
669
+ delete parent[k];
670
+ }
671
+ }
672
+
673
+ const grouped = new Map<string, any[]>();
674
+ for (const { type, element } of ordered) {
675
+ if (!grouped.has(type)) grouped.set(type, []);
676
+ grouped.get(type)!.push(element);
677
+ }
678
+ for (const [type, elements] of grouped) {
679
+ parent[type] = elements.length === 1 ? elements[0] : elements;
680
+ }
681
+
682
+ const orderedChildren: OrderedChildInfo[] = [];
683
+ const counters = new Map<string, number>();
684
+ for (const { type } of ordered) {
685
+ const idx = counters.get(type) || 0;
686
+ counters.set(type, idx + 1);
687
+ orderedChildren.push({ type, index: idx });
688
+ }
689
+ if (orderedChildren.length > 0) {
690
+ parent._orderedChildren = orderedChildren;
691
+ } else {
692
+ delete parent._orderedChildren;
693
+ }
694
+ }
695
+
696
+ /**
697
+ * Flatten an element's children into an ordered `{type, element}` list,
698
+ * honouring `_orderedChildren` when present and falling back to key order.
699
+ */
700
+ private static getOrderedChildList(el: any): { type: string; element: any }[] {
701
+ const result: { type: string; element: any }[] = [];
702
+ const ordered = el._orderedChildren as OrderedChildInfo[] | undefined;
703
+
704
+ if (Array.isArray(ordered) && ordered.length > 0) {
705
+ const counters = new Map<string, number>();
706
+ for (const entry of ordered) {
707
+ const { type } = entry;
708
+ const idx = counters.get(type) || 0;
709
+ counters.set(type, idx + 1);
710
+ const value = el[type];
711
+ if (value === undefined) continue;
712
+ const arr = Array.isArray(value) ? value : [value];
713
+ if (idx < arr.length) result.push({ type, element: arr[idx] });
714
+ }
715
+ return result;
716
+ }
717
+
718
+ for (const key of Object.keys(el)) {
719
+ if (key.startsWith('@_') || key === '#text' || key === '_orderedChildren') continue;
720
+ const value = el[key];
721
+ const arr = Array.isArray(value) ? value : [value];
722
+ for (const element of arr) result.push({ type: key, element });
723
+ }
724
+ return result;
725
+ }
726
+
727
+ /**
728
+ * Convert restored deleted text back to live text throughout the tree
729
+ * (reject direction). After w:del unwrapping, the runs that were deleted
730
+ * still hold their text in w:delText / w:delInstrText; rename those to the
731
+ * normal w:t / w:instrText so the content is real again, fixing any
732
+ * `_orderedChildren` type references as we go.
733
+ */
734
+ private static convertDeletedTextToNormal(obj: any): void {
735
+ if (obj === null || typeof obj !== 'object') return;
736
+
737
+ if (Array.isArray(obj)) {
738
+ for (const item of obj) RevisionWalker.convertDeletedTextToNormal(item);
739
+ return;
740
+ }
741
+
742
+ // When this node carries any deleted-text element, rebuild its children
743
+ // from the document-ordered list with the deleted-text keys renamed. Going
744
+ // through the ordered list keeps text in its original position even if a
745
+ // run holds both a w:delText and a live w:t (CT_R permits interleaving).
746
+ const hasDeletedText = Object.keys(obj).some((k) => k in DELETED_TEXT_RENAMES);
747
+ if (hasDeletedText) {
748
+ const ordered = RevisionWalker.getOrderedChildList(obj).map((child) => ({
749
+ type: DELETED_TEXT_RENAMES[child.type] ?? child.type,
750
+ element: child.element,
751
+ }));
752
+ RevisionWalker.rebuildChildren(obj, ordered);
753
+ }
754
+
755
+ for (const key of Object.keys(obj)) {
756
+ if (key.startsWith('@_') || key === '#text' || key === '_orderedChildren') continue;
757
+ RevisionWalker.convertDeletedTextToNormal(obj[key]);
758
+ }
759
+ }
760
+
417
761
  /**
418
762
  * Merge a child value into the parent, handling arrays properly
419
763
  */