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
@@ -15,10 +15,11 @@
15
15
  import type { Document } from '../core/Document.js';
16
16
  import type { Paragraph, ParagraphContent } from '../elements/Paragraph.js';
17
17
  import { Revision, RevisionType } from '../elements/Revision.js';
18
- import type { Run } from '../elements/Run.js';
18
+ import type { Run, RunFormatting } from '../elements/Run.js';
19
19
  import { isRunContent, isHyperlinkContent } from '../elements/RevisionContent.js';
20
20
  import { ChangeCategory } from './ChangelogGenerator.js';
21
21
  import { SelectionCriteria } from './RevisionAwareProcessor.js';
22
+ import { mergeParagraphsWithDeletedMarks } from './InMemoryRevisionAcceptor.js';
22
23
 
23
24
  /**
24
25
  * Result of selective revision acceptance.
@@ -62,9 +63,23 @@ export class SelectiveRevisionAcceptor {
62
63
  const hasFullApi = typeof (doc as any).getAllParagraphs === 'function';
63
64
 
64
65
  if (hasFullApi) {
66
+ const acceptedMarkDeletions: Paragraph[] = [];
65
67
  this.walkAllParagraphs(doc, (paragraph) => {
66
- this.processSelectiveParagraph(paragraph, criteria, 'accept', accepted, remaining);
68
+ this.processSelectiveParagraph(
69
+ paragraph,
70
+ criteria,
71
+ 'accept',
72
+ accepted,
73
+ remaining,
74
+ acceptedMarkDeletions
75
+ );
67
76
  });
77
+ // Accepted paragraph-mark deletions combine the paragraph with its
78
+ // following sibling (ECMA-376 §17.13.5.15) — same merge the all-or-
79
+ // nothing acceptor performs.
80
+ if (acceptedMarkDeletions.length > 0) {
81
+ mergeParagraphsWithDeletedMarks(doc, new Set(acceptedMarkDeletions));
82
+ }
68
83
  } else {
69
84
  // Fallback: Filter revisions from RevisionManager only (for backward compatibility)
70
85
  const revisionManager = doc.getRevisionManager();
@@ -257,7 +272,8 @@ export class SelectiveRevisionAcceptor {
257
272
  criteria: SelectionCriteria,
258
273
  action: 'accept' | 'reject',
259
274
  processedIds: string[],
260
- remainingIds: string[]
275
+ remainingIds: string[],
276
+ acceptedMarkDeletions?: Paragraph[]
261
277
  ): void {
262
278
  const content = paragraph.getContent();
263
279
  const newContent: ParagraphContent[] = [];
@@ -267,15 +283,18 @@ export class SelectiveRevisionAcceptor {
267
283
  const revisionId = item.getId().toString();
268
284
 
269
285
  if (this.matchesCriteria(item, criteria)) {
270
- // This revision matches the criteria - process it
271
- processedIds.push(revisionId);
272
-
273
286
  if (action === 'accept') {
274
287
  // Accept: Transform based on revision type
288
+ processedIds.push(revisionId);
275
289
  this.acceptRevisionItem(item, newContent);
276
- } else {
290
+ } else if (this.rejectRevisionItem(item, newContent, paragraph)) {
277
291
  // Reject: Transform opposite of accept
278
- this.rejectRevisionItem(item, newContent);
292
+ processedIds.push(revisionId);
293
+ } else {
294
+ // The revision was kept in place (its previous-property
295
+ // snapshot could not be restored), so report it as remaining
296
+ // rather than rejected — it must stay in the RevisionManager.
297
+ remainingIds.push(revisionId);
279
298
  }
280
299
  } else {
281
300
  // This revision doesn't match - keep it
@@ -291,13 +310,18 @@ export class SelectiveRevisionAcceptor {
291
310
  // Replace paragraph content with the transformed content
292
311
  paragraph.setContent(newContent);
293
312
 
294
- // Handle paragraph mark revision markers (w:del/w:ins in w:pPr/w:rPr)
295
- // Both accept and reject clear the marker these are metadata-only markers (no content),
296
- // so there is no content to add or remove, only the marker itself to clear.
313
+ // Handle paragraph mark revision markers (w:del/w:ins in w:pPr/w:rPr).
314
+ // Rejecting a mark deletion just restores the mark (clear the marker);
315
+ // ACCEPTING one means the paragraph's contents combine with the following
316
+ // paragraph (ECMA-376 §17.13.5.15), so the paragraph is recorded for the
317
+ // post-walk merge pass in addition to clearing the marker.
297
318
  const formatting = paragraph.getFormatting();
298
319
  if (formatting.paragraphMarkDeletion) {
299
320
  const del = formatting.paragraphMarkDeletion;
300
321
  if (this.matchesMarkerCriteria(del, criteria)) {
322
+ if (action === 'accept') {
323
+ acceptedMarkDeletions?.push(paragraph);
324
+ }
301
325
  paragraph.clearParagraphMarkDeletion();
302
326
  processedIds.push(del.id.toString());
303
327
  } else {
@@ -415,8 +439,17 @@ export class SelectiveRevisionAcceptor {
415
439
  * Reject a single revision item (opposite of accept).
416
440
  * - Rejecting an insertion removes the content
417
441
  * - Rejecting a deletion keeps the content (unwraps it)
442
+ * - Rejecting a run/paragraph property change restores the previous
443
+ * properties snapshot (ECMA-376 §17.13.5.30/§17.13.5.31)
444
+ *
445
+ * @returns true if the revision was rejected, false if it was kept in
446
+ * place because its previous-property snapshot could not be restored
418
447
  */
419
- private static rejectRevisionItem(revision: Revision, newContent: ParagraphContent[]): void {
448
+ private static rejectRevisionItem(
449
+ revision: Revision,
450
+ newContent: ParagraphContent[],
451
+ paragraph: Paragraph
452
+ ): boolean {
420
453
  const revisionType = revision.getType();
421
454
  const childContent = revision.getContent();
422
455
 
@@ -424,7 +457,7 @@ export class SelectiveRevisionAcceptor {
424
457
  case 'insert':
425
458
  case 'moveTo':
426
459
  // Reject insertion: Remove the inserted content entirely
427
- break;
460
+ return true;
428
461
 
429
462
  case 'delete':
430
463
  case 'moveFrom':
@@ -436,19 +469,46 @@ export class SelectiveRevisionAcceptor {
436
469
  newContent.push(child);
437
470
  }
438
471
  }
439
- break;
472
+ return true;
473
+
474
+ case 'runPropertiesChange': {
475
+ const previous = revision.getPreviousProperties();
476
+ if (!previous) {
477
+ // No snapshot to restore from — keep the tracked change so the
478
+ // prior formatting is not silently lost.
479
+ newContent.push(revision);
480
+ return false;
481
+ }
482
+ for (const child of childContent) {
483
+ if (isRunContent(child)) {
484
+ const run = child as Run;
485
+ // w:rPrChange carries the complete pre-change run properties,
486
+ // so rejecting replaces (not merges) the run's formatting.
487
+ // Run.formatting is private; written directly the same way
488
+ // DocumentParser populates parsed properties.
489
+ (run as unknown as { formatting: RunFormatting }).formatting = {
490
+ ...(previous as RunFormatting),
491
+ };
492
+ newContent.push(run);
493
+ } else if (isHyperlinkContent(child)) {
494
+ newContent.push(child);
495
+ }
496
+ }
497
+ return true;
498
+ }
440
499
 
441
- case 'runPropertiesChange':
442
- case 'paragraphPropertiesChange':
443
- case 'tablePropertiesChange':
444
- case 'tableExceptionPropertiesChange':
445
- case 'tableRowPropertiesChange':
446
- case 'tableCellPropertiesChange':
447
- case 'sectionPropertiesChange':
448
- case 'numberingChange':
449
- // Rejecting property changes: Would need to restore old properties
450
- // For now, just keep content without the change metadata
451
- // (Full implementation would restore previousProperties)
500
+ case 'paragraphPropertiesChange': {
501
+ const previous = revision.getPreviousProperties();
502
+ if (!previous) {
503
+ newContent.push(revision);
504
+ return false;
505
+ }
506
+ // The snapshot uses the same key names as ParagraphFormatting
507
+ // (see Revision.PROPERTY_KEY_TO_ELEMENT), so the recorded values
508
+ // are written back onto the containing paragraph. Only recorded
509
+ // keys are touched: internal bookkeeping fields (revision markers,
510
+ // paraId) are not part of a w:pPrChange snapshot.
511
+ Object.assign(paragraph.formatting, previous);
452
512
  for (const child of childContent) {
453
513
  if (isRunContent(child)) {
454
514
  newContent.push(child as Run);
@@ -456,11 +516,26 @@ export class SelectiveRevisionAcceptor {
456
516
  newContent.push(child);
457
517
  }
458
518
  }
459
- break;
519
+ return true;
520
+ }
521
+
522
+ case 'tablePropertiesChange':
523
+ case 'tableExceptionPropertiesChange':
524
+ case 'tableRowPropertiesChange':
525
+ case 'tableCellPropertiesChange':
526
+ case 'sectionPropertiesChange':
527
+ case 'numberingChange':
528
+ // The element these snapshots belong to (table/row/cell/section/
529
+ // numbering definition) is not reachable from paragraph content,
530
+ // so the previous properties cannot be restored here. Keep the
531
+ // revision in place rather than discarding the snapshot.
532
+ newContent.push(revision);
533
+ return false;
460
534
 
461
535
  default:
462
536
  // Unknown type - keep the revision as-is for safety
463
537
  newContent.push(revision);
538
+ return true;
464
539
  }
465
540
  }
466
541
 
@@ -477,8 +552,69 @@ export class SelectiveRevisionAcceptor {
477
552
  criteria: SelectionCriteria,
478
553
  action: 'accept' | 'reject'
479
554
  ): SelectiveAcceptResult {
480
- // Preview is the same as the actual operation but without side effects
481
- return action === 'accept' ? this.accept(doc, criteria) : this.reject(doc, criteria);
555
+ const processed: string[] = [];
556
+ const remaining: string[] = [];
557
+
558
+ // Delegating to accept()/reject() would mutate paragraph content and
559
+ // splice revisions out of the RevisionManager, so preview classifies
560
+ // with the same traversal and criteria but never transforms anything.
561
+ const hasFullApi = typeof (doc as any).getAllParagraphs === 'function';
562
+
563
+ if (hasFullApi) {
564
+ this.walkAllParagraphs(doc, (paragraph) => {
565
+ for (const item of paragraph.getContent()) {
566
+ if (item instanceof Revision) {
567
+ if (this.matchesCriteria(item, criteria)) {
568
+ processed.push(item.getId().toString());
569
+ } else {
570
+ remaining.push(item.getId().toString());
571
+ }
572
+ }
573
+ }
574
+
575
+ const formatting = paragraph.getFormatting();
576
+ if (formatting.paragraphMarkDeletion) {
577
+ const del = formatting.paragraphMarkDeletion;
578
+ if (this.matchesMarkerCriteria(del, criteria)) {
579
+ processed.push(del.id.toString());
580
+ } else {
581
+ remaining.push(del.id.toString());
582
+ }
583
+ }
584
+ if (formatting.paragraphMarkInsertion) {
585
+ const ins = formatting.paragraphMarkInsertion;
586
+ if (this.matchesMarkerCriteria(ins, criteria)) {
587
+ processed.push(ins.id.toString());
588
+ } else {
589
+ remaining.push(ins.id.toString());
590
+ }
591
+ }
592
+ });
593
+ } else {
594
+ // Fallback: Filter revisions from RevisionManager only (for backward compatibility)
595
+ const revisionManager = doc.getRevisionManager();
596
+ if (revisionManager) {
597
+ for (const rev of revisionManager.getAllRevisions()) {
598
+ if (this.matchesCriteria(rev, criteria)) {
599
+ processed.push(rev.getId().toString());
600
+ } else {
601
+ remaining.push(rev.getId().toString());
602
+ }
603
+ }
604
+ }
605
+ }
606
+
607
+ return {
608
+ accepted: action === 'accept' ? processed : [],
609
+ rejected: action === 'reject' ? processed : [],
610
+ remaining,
611
+ summary: {
612
+ totalProcessed: processed.length + remaining.length,
613
+ acceptedCount: action === 'accept' ? processed.length : 0,
614
+ rejectedCount: action === 'reject' ? processed.length : 0,
615
+ remainingCount: remaining.length,
616
+ },
617
+ };
482
618
  }
483
619
 
484
620
  /**
@@ -5,16 +5,19 @@ import { RevisionWalker } from './RevisionWalker.js';
5
5
  /**
6
6
  * Markers covered: w:ins, w:del, w:moveFrom, w:moveTo,
7
7
  * w:rPrChange, w:pPrChange, w:tblPrChange, w:tblPrExChange,
8
- * w:trPrChange, w:tcPrChange, w:sectPrChange, w:numberingChange,
9
- * w:cellIns, w:cellDel, w:cellMerge, and any *RangeStart/End markers.
8
+ * w:trPrChange, w:tcPrChange, w:sectPrChange, w:tblGridChange,
9
+ * w:numberingChange, w:cellIns, w:cellDel, w:cellMerge, and any
10
+ * *RangeStart/End markers.
10
11
  */
11
12
  const REVISION_MARKER_PATTERN =
12
- /<w:(?:ins|del|moveFrom|moveTo|rPrChange|pPrChange|tblPrChange|tblPrExChange|trPrChange|tcPrChange|sectPrChange|numberingChange|cellIns|cellDel|cellMerge|moveFromRangeStart|moveFromRangeEnd|moveToRangeStart|moveToRangeEnd|customXmlInsRangeStart|customXmlInsRangeEnd|customXmlDelRangeStart|customXmlDelRangeEnd|customXmlMoveFromRangeStart|customXmlMoveFromRangeEnd|customXmlMoveToRangeStart|customXmlMoveToRangeEnd)\b/;
13
+ /<w:(?:ins|del|moveFrom|moveTo|rPrChange|pPrChange|tblPrChange|tblPrExChange|trPrChange|tcPrChange|sectPrChange|tblGridChange|numberingChange|cellIns|cellDel|cellMerge|moveFromRangeStart|moveFromRangeEnd|moveToRangeStart|moveToRangeEnd|customXmlInsRangeStart|customXmlInsRangeEnd|customXmlDelRangeStart|customXmlDelRangeEnd|customXmlMoveFromRangeStart|customXmlMoveFromRangeEnd|customXmlMoveToRangeStart|customXmlMoveToRangeEnd)\b/;
13
14
 
14
15
  /**
15
- * Accepts all tracked changes in a Word document per Microsoft's OpenXML SDK pattern
16
+ * Applies tracked changes in a Word document per Microsoft's OpenXML SDK
17
+ * pattern, in either direction (see `mode`).
16
18
  *
17
- * This implementation uses DOM-based tree walking for reliability:
19
+ * This implementation uses DOM-based tree walking for reliability. In 'accept'
20
+ * mode (the post-edit document):
18
21
  * 1. Insertions (<w:ins>): Keep content, remove wrapper tags
19
22
  * 2. Deletions (<w:del>): Remove entirely (content and tags)
20
23
  * 3. Move From (<w:moveFrom>): Remove entirely (source of move)
@@ -22,17 +25,27 @@ const REVISION_MARKER_PATTERN =
22
25
  * 5. Property changes: Remove all *Change elements
23
26
  * 6. Range markers: Remove all boundary markers
24
27
  *
28
+ * In 'reject' mode every content decision is inverted and property changes
29
+ * restore the previous formatting (see RevisionWalker).
30
+ *
25
31
  * Also cleans up metadata in people.xml, settings.xml, and core.xml
26
32
  *
27
33
  * @see https://learn.microsoft.com/en-us/office/open-xml/how-to-accept-all-revisions
28
34
  */
29
- class RevisionAcceptor {
35
+ class RevisionProcessor {
30
36
  private zipHandler: ZipHandler;
31
37
  /** Feature flag for DOM-based processing (default: true) */
32
38
  private useDomBasedProcessing = true;
39
+ /**
40
+ * Processing direction. 'accept' yields the post-edit document; 'reject'
41
+ * reverts to the original pre-edit document (the exact inverse). Reject is
42
+ * DOM-only — the regex fallback cannot restore previous formatting.
43
+ */
44
+ private mode: 'accept' | 'reject';
33
45
 
34
- constructor(zipHandler: ZipHandler) {
46
+ constructor(zipHandler: ZipHandler, mode: 'accept' | 'reject' = 'accept') {
35
47
  this.zipHandler = zipHandler;
48
+ this.mode = mode;
36
49
  }
37
50
 
38
51
  /**
@@ -43,9 +56,10 @@ class RevisionAcceptor {
43
56
  }
44
57
 
45
58
  /**
46
- * Main method to accept all revisions in the document
59
+ * Main entry point: apply every tracked change in the document in the
60
+ * configured direction (accept or reject).
47
61
  */
48
- public async acceptAllRevisions(): Promise<void> {
62
+ public async process(): Promise<void> {
49
63
  // Process document.xml
50
64
  await this.processDocumentPart('word/document.xml');
51
65
 
@@ -91,11 +105,13 @@ class RevisionAcceptor {
91
105
  // byte-for-byte passthrough preservation in downstream consumers
92
106
  // (e.g., comments.xml round-trip with no tracked changes inside).
93
107
  const xml = this.zipHandler.getFileAsString(partPath);
94
- if (!xml || !RevisionAcceptor.containsRevisionMarkup(xml)) {
108
+ if (!xml || !RevisionProcessor.containsRevisionMarkup(xml)) {
95
109
  return;
96
110
  }
97
111
 
98
- if (this.useDomBasedProcessing) {
112
+ // Reject must restore previous formatting from *Change snapshots, which the
113
+ // regex fallback cannot do — always take the DOM path when rejecting.
114
+ if (this.useDomBasedProcessing || this.mode === 'reject') {
99
115
  return this.processDocumentPartDOM(partPath);
100
116
  }
101
117
  return this.processDocumentPartRegex(partPath);
@@ -126,14 +142,22 @@ class RevisionAcceptor {
126
142
 
127
143
  // Step 2: Process revisions using DOM walker
128
144
  const processed = RevisionWalker.processTree(parsed, {
145
+ mode: this.mode,
129
146
  acceptInsertions: true,
130
147
  acceptDeletions: true,
131
148
  acceptMoves: true,
132
149
  acceptPropertyChanges: true,
133
150
  });
134
151
 
135
- // Step 3: Handle image relationship ID remapping
136
- this.remapImageRelationshipsInTree(processed);
152
+ // Step 3: Handle image relationship ID remapping.
153
+ // Only relevant when accepting: unwrapping an inserted image can surface a
154
+ // relationship ID that Word reused across tracked-change contexts, so a
155
+ // fresh unique ID is assigned. Rejecting discards insertions and restores
156
+ // the original content, whose relationship IDs are already unique, so
157
+ // remapping would needlessly rewrite valid references.
158
+ if (this.mode === 'accept') {
159
+ this.remapImageRelationshipsInTree(processed);
160
+ }
137
161
 
138
162
  // Step 4: Convert back to XML
139
163
  const outputXml =
@@ -768,8 +792,34 @@ class RevisionAcceptor {
768
792
  * Convenience function to accept all revisions in a document
769
793
  */
770
794
  export async function acceptAllRevisions(zipHandler: ZipHandler): Promise<void> {
771
- const acceptor = new RevisionAcceptor(zipHandler);
772
- await acceptor.acceptAllRevisions();
795
+ const acceptor = new RevisionProcessor(zipHandler);
796
+ await acceptor.process();
797
+ }
798
+
799
+ /**
800
+ * Convenience function to reject all revisions in a document, reverting it to
801
+ * its original pre-edit state (the exact inverse of {@link acceptAllRevisions}):
802
+ *
803
+ * 1. Insertions (`w:ins`): removed — inserted content is discarded
804
+ * 2. Deletions (`w:del`): unwrapped and `w:delText` restored to `w:t` — deleted
805
+ * content reappears
806
+ * 3. MoveFrom (`w:moveFrom`): unwrapped — the original source content is restored
807
+ * 4. MoveTo (`w:moveTo`): removed — the moved-to destination is discarded
808
+ * 5. Property changes (`w:rPrChange`, `w:pPrChange`, ...): previous formatting
809
+ * stored inside the change is restored, then the marker is removed
810
+ * 6. Inserted rows/tables are removed; deleted rows/tables are kept
811
+ * 7. Range markers are removed and revision metadata is cleaned up
812
+ *
813
+ * Limitations (shared with {@link acceptAllRevisions}): cell-level markers
814
+ * (`w:cellIns`/`w:cellDel`/`w:cellMerge`) are left in place, and the legacy
815
+ * `w:numberingChange` is dropped rather than reverted (it embeds no recoverable
816
+ * previous value).
817
+ *
818
+ * @param zipHandler - The ZipHandler containing the DOCX package
819
+ */
820
+ export async function rejectAllRevisions(zipHandler: ZipHandler): Promise<void> {
821
+ const rejector = new RevisionProcessor(zipHandler, 'reject');
822
+ await rejector.process();
773
823
  }
774
824
 
775
825
  /**
@@ -786,6 +836,6 @@ export async function acceptAllRevisions(zipHandler: ZipHandler): Promise<void>
786
836
  * @param zipHandler - The ZipHandler containing the DOCX package
787
837
  */
788
838
  export function cleanupRevisionMetadata(zipHandler: ZipHandler): void {
789
- const acceptor = new RevisionAcceptor(zipHandler);
790
- acceptor.cleanupMetadata();
839
+ const processor = new RevisionProcessor(zipHandler);
840
+ processor.cleanupMetadata();
791
841
  }
@@ -78,25 +78,31 @@ const BIT_POSITIONS: Record<keyof CnfStyleFlags, number> = {
78
78
 
79
79
  /**
80
80
  * Priority order for conditional formatting resolution
81
- * More specific conditions (corners) override less specific (edges, banding)
82
- * Per ECMA-376, this is the order in which conditionals should be applied
81
+ *
82
+ * ECMA-376 §17.7.6.6 applies conditional formats in sequence (banding, then
83
+ * first/last row, first/last column, then corners top-left through
84
+ * bottom-right) with subsequent formats overriding earlier ones. First-match
85
+ * consumers need the opposite: the last-applied (winning) member of each
86
+ * group listed first, so when two conditionals in the same group co-apply
87
+ * (e.g. firstRow + lastRow in a single-row table) resolution picks the one
88
+ * Word actually renders.
83
89
  */
84
90
  export const CONDITIONAL_PRIORITY_ORDER: (keyof CnfStyleFlags)[] = [
85
- // Corner cells (most specific)
86
- 'nwCell',
87
- 'neCell',
88
- 'swCell',
91
+ // Corner cells (most specific; spec applies nw, ne, sw, se — se wins)
89
92
  'seCell',
90
- // Edge rows/columns
91
- 'firstRow',
93
+ 'swCell',
94
+ 'neCell',
95
+ 'nwCell',
96
+ // Edge rows/columns (within each pair, last is applied after first)
92
97
  'lastRow',
93
- 'firstCol',
98
+ 'firstRow',
94
99
  'lastCol',
95
- // Banding (least specific)
96
- 'band1Horz',
100
+ 'firstCol',
101
+ // Banding (least specific; even banding is applied after odd)
97
102
  'band2Horz',
98
- 'band1Vert',
103
+ 'band1Horz',
99
104
  'band2Vert',
105
+ 'band1Vert',
100
106
  ];
101
107
 
102
108
  /**
@@ -67,19 +67,30 @@ class TrackedChangesStripper {
67
67
  // STEP 2: Process insertions - keep content, remove wrapper
68
68
  content = this.processInsertions(content);
69
69
 
70
- // STEP 3: Process deletions - remove entirely
70
+ // STEP 3: Remove rows whose w:trPr carries a row-deletion marker
71
+ // (self-closing <w:del/> per ECMA-376 §17.13.5.14 marks the ENTIRE
72
+ // row as deleted). Must run before the marker itself is stripped in
73
+ // STEP 4, otherwise the row would silently survive as un-deleted.
74
+ content = this.removeDeletedRows(content);
75
+
76
+ // STEP 4: Remove self-closing revision markers (paragraph-mark
77
+ // deletions in w:pPr/w:rPr per ECMA-376 §17.13.5.15, etc.). Must run
78
+ // BEFORE the block-removal steps: their [^>]* also matches the
79
+ // trailing '/' of `<w:del .../>`, which would turn the marker into an
80
+ // opening tag and swallow everything up to the next closing tag,
81
+ // corrupting the part.
82
+ content = this.removeSelfClosingRevisionTags(content);
83
+
84
+ // STEP 5: Process deletions - remove entirely
71
85
  content = this.processDeletions(content);
72
86
 
73
- // STEP 4: Process move operations
87
+ // STEP 6: Process move operations
74
88
  content = this.processMoveFrom(content);
75
89
  content = this.processMoveTo(content);
76
90
 
77
- // STEP 5: Remove all property change tracking elements
91
+ // STEP 7: Remove all property change tracking elements
78
92
  content = this.removePropertyChanges(content);
79
93
 
80
- // STEP 6: Remove any remaining self-closing revision tags
81
- content = this.removeSelfClosingRevisionTags(content);
82
-
83
94
  // Update the file in the zip
84
95
  this.zipHandler.updateFile(partPath, content);
85
96
  }
@@ -160,6 +171,30 @@ class TrackedChangesStripper {
160
171
  return result;
161
172
  }
162
173
 
174
+ /**
175
+ * Remove entire <w:tr> rows marked as tracked deletions.
176
+ *
177
+ * Per ECMA-376 Part 1 §17.13.5.14, a self-closing `<w:del/>` inside
178
+ * `<w:trPr>` marks the whole row as deleted — stripping just the marker
179
+ * would resurrect the row instead of removing it. Mirrors the row-level
180
+ * pass in acceptRevisions.ts: the negative lookaheads keep the match
181
+ * anchored to a single row (it cannot slip past a cell boundary or a
182
+ * nested-table row and latch onto a later row's trPr-with-del).
183
+ */
184
+ private removeDeletedRows(xml: string): string {
185
+ const rowDelPattern =
186
+ /<w:tr\b[^>]*>(?:(?!<w:tc\b|<w:tr\b|<\/w:tr>)[\s\S])*?<w:trPr\b(?:\s[^>]*)?>(?:(?!<\/w:trPr>)[\s\S])*?<w:del\b[^>]*\/>(?:(?!<\/w:trPr>)[\s\S])*?<\/w:trPr>(?:(?!<w:tr\b|<\/w:tr>)[\s\S])*?<\/w:tr>/g;
187
+
188
+ let result = xml;
189
+ let previousLength = 0;
190
+ while (result.length !== previousLength) {
191
+ previousLength = result.length;
192
+ result = result.replace(rowDelPattern, '');
193
+ }
194
+
195
+ return result;
196
+ }
197
+
163
198
  /**
164
199
  * Process <w:del> elements - remove entire element
165
200
  * Uses iterative replacement to handle nested deletions
@@ -241,13 +276,15 @@ class TrackedChangesStripper {
241
276
 
242
277
  /**
243
278
  * Remove self-closing revision tags
279
+ * `\b` (not `\s+`) so attribute-less markers like `<w:del/>` are also
280
+ * caught while `<w:delText>` is not.
244
281
  */
245
282
  private removeSelfClosingRevisionTags(xml: string): string {
246
283
  const patterns = [
247
- /<w:ins\s+[^>]*\/>/g,
248
- /<w:del\s+[^>]*\/>/g,
249
- /<w:moveFrom\s+[^>]*\/>/g,
250
- /<w:moveTo\s+[^>]*\/>/g,
284
+ /<w:ins\b[^>]*\/>/g,
285
+ /<w:del\b[^>]*\/>/g,
286
+ /<w:moveFrom\b[^>]*\/>/g,
287
+ /<w:moveTo\b[^>]*\/>/g,
251
288
  ];
252
289
 
253
290
  let result = xml;
@@ -10,6 +10,12 @@
10
10
  * For simple objects (like formatting options), this is more efficient
11
11
  * than JSON.parse(JSON.stringify()) and doesn't lose non-serializable values.
12
12
  *
13
+ * Cyclic and shared references are handled via an internal seen-cache: an
14
+ * object encountered more than once during a single clone is cloned exactly
15
+ * once, so circular graphs cannot overflow the stack and shared sub-objects
16
+ * (including shared Date and RegExp instances) retain a single shared identity
17
+ * in the result.
18
+ *
13
19
  * @param obj - Object to clone
14
20
  * @returns Deep cloned copy of the object
15
21
  *
@@ -23,21 +29,50 @@
23
29
  * ```
24
30
  */
25
31
  export function deepClone<T>(obj: T): T {
32
+ // Map of already-cloned source objects to their clones, scoped to this call.
33
+ // Guards against infinite recursion on cyclic input and preserves the
34
+ // identity of references shared by multiple parents within the same graph.
35
+ return cloneInternal(obj, new WeakMap<object, unknown>());
36
+ }
37
+
38
+ function cloneInternal<T>(obj: T, seen: WeakMap<object, unknown>): T {
26
39
  // Handle primitive types and null
27
40
  if (obj === null || typeof obj !== 'object') {
28
41
  return obj;
29
42
  }
30
43
 
31
- // Handle Date
44
+ // Return the existing clone for any object already seen in this call. Placed
45
+ // before the Date/RegExp branches so a single Date/RegExp instance shared by
46
+ // multiple nodes resolves to one shared clone (the JSDoc shared-identity
47
+ // guarantee), not a fresh copy per reference.
48
+ const existing = seen.get(obj as object);
49
+ if (existing !== undefined) {
50
+ return existing as T;
51
+ }
52
+
53
+ // Handle Date (immutable value copy; cannot form a cycle). Registered in
54
+ // `seen` so repeat references to the same instance share this clone.
32
55
  if (obj instanceof Date) {
33
- return new Date(obj.getTime()) as T;
56
+ const dateCopy = new Date(obj.getTime());
57
+ seen.set(obj as object, dateCopy);
58
+ return dateCopy as T;
59
+ }
60
+
61
+ // Handle RegExp (immutable value copy; cannot form a cycle). Registered in
62
+ // `seen` so repeat references to the same instance share this clone.
63
+ if (obj instanceof RegExp) {
64
+ const regexpCopy = new RegExp(obj.source, obj.flags);
65
+ seen.set(obj as object, regexpCopy);
66
+ return regexpCopy as T;
34
67
  }
35
68
 
36
69
  // Handle Array
37
70
  if (Array.isArray(obj)) {
38
71
  const arrCopy: unknown[] = [];
72
+ // Register before recursing so self/back references resolve to this clone.
73
+ seen.set(obj as object, arrCopy);
39
74
  for (let i = 0; i < obj.length; i++) {
40
- arrCopy[i] = deepClone(obj[i]);
75
+ arrCopy[i] = cloneInternal(obj[i], seen);
41
76
  }
42
77
  return arrCopy as T;
43
78
  }
@@ -45,8 +80,9 @@ export function deepClone<T>(obj: T): T {
45
80
  // Handle Map
46
81
  if (obj instanceof Map) {
47
82
  const mapCopy = new Map();
83
+ seen.set(obj, mapCopy);
48
84
  obj.forEach((value, key) => {
49
- mapCopy.set(deepClone(key), deepClone(value));
85
+ mapCopy.set(cloneInternal(key, seen), cloneInternal(value, seen));
50
86
  });
51
87
  return mapCopy as T;
52
88
  }
@@ -54,23 +90,21 @@ export function deepClone<T>(obj: T): T {
54
90
  // Handle Set
55
91
  if (obj instanceof Set) {
56
92
  const setCopy = new Set();
93
+ seen.set(obj, setCopy);
57
94
  obj.forEach((value) => {
58
- setCopy.add(deepClone(value));
95
+ setCopy.add(cloneInternal(value, seen));
59
96
  });
60
97
  return setCopy as T;
61
98
  }
62
99
 
63
- // Handle RegExp
64
- if (obj instanceof RegExp) {
65
- return new RegExp(obj.source, obj.flags) as T;
66
- }
67
-
68
100
  // Handle plain objects
69
101
  const objCopy = Object.create(Object.getPrototypeOf(obj)) as Record<string, unknown>;
70
- for (const key in obj) {
71
- if (obj.hasOwnProperty(key)) {
72
- objCopy[key] = deepClone((obj as Record<string, unknown>)[key]);
73
- }
102
+ seen.set(obj as object, objCopy);
103
+ // Object.keys avoids the prototype-builtins shadowing risk of
104
+ // obj.hasOwnProperty(key) (matches sibling deepEqual.ts) and yields the same
105
+ // own-enumerable string keys the previous for..in + hasOwnProperty loop did.
106
+ for (const key of Object.keys(obj as object)) {
107
+ objCopy[key] = cloneInternal((obj as Record<string, unknown>)[key], seen);
74
108
  }
75
109
 
76
110
  return objCopy as T;