docxmlater 10.1.3 → 10.1.5

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 (371) 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 +50 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +483 -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.map +1 -1
  146. package/dist/formatting/NumberingManager.js +9 -9
  147. package/dist/formatting/NumberingManager.js.map +1 -1
  148. package/dist/formatting/Style.d.ts +11 -11
  149. package/dist/formatting/Style.d.ts.map +1 -1
  150. package/dist/formatting/Style.js +219 -247
  151. package/dist/formatting/Style.js.map +1 -1
  152. package/dist/formatting/StylesManager.d.ts +2 -2
  153. package/dist/formatting/StylesManager.d.ts.map +1 -1
  154. package/dist/formatting/StylesManager.js +96 -102
  155. package/dist/formatting/StylesManager.js.map +1 -1
  156. package/dist/helpers/CleanupHelper.d.ts +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  158. package/dist/helpers/CleanupHelper.js +6 -6
  159. package/dist/helpers/CleanupHelper.js.map +1 -1
  160. package/dist/images/ImageOptimizer.js +7 -7
  161. package/dist/images/ImageOptimizer.js.map +1 -1
  162. package/dist/index.d.ts +9 -9
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js.map +1 -1
  165. package/dist/managers/DrawingManager.js.map +1 -1
  166. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.js +23 -7
  168. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  169. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  170. package/dist/tracking/TrackingContext.js.map +1 -1
  171. package/dist/types/compatibility-types.js.map +1 -1
  172. package/dist/types/formatting.js.map +1 -1
  173. package/dist/types/list-types.d.ts +6 -6
  174. package/dist/types/list-types.js.map +1 -1
  175. package/dist/types/settings-types.js.map +1 -1
  176. package/dist/types/styleConfig.d.ts +2 -2
  177. package/dist/types/styleConfig.js.map +1 -1
  178. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  179. package/dist/utils/ChangelogGenerator.js +97 -101
  180. package/dist/utils/ChangelogGenerator.js.map +1 -1
  181. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.js +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  184. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  186. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  187. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  188. package/dist/utils/MoveOperationHelper.js +1 -1
  189. package/dist/utils/MoveOperationHelper.js.map +1 -1
  190. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.js +2 -4
  192. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  193. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  194. package/dist/utils/RevisionWalker.js +4 -12
  195. package/dist/utils/RevisionWalker.js.map +1 -1
  196. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  198. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  199. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  200. package/dist/utils/ShadingResolver.js +1 -1
  201. package/dist/utils/ShadingResolver.js.map +1 -1
  202. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  203. package/dist/utils/acceptRevisions.js +23 -12
  204. package/dist/utils/acceptRevisions.js.map +1 -1
  205. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  207. package/dist/utils/cnfStyleDecoder.js +40 -40
  208. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  209. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  210. package/dist/utils/corruptionDetection.js.map +1 -1
  211. package/dist/utils/dateFormatting.js.map +1 -1
  212. package/dist/utils/deepClone.js +1 -1
  213. package/dist/utils/deepClone.js.map +1 -1
  214. package/dist/utils/diagnostics.d.ts.map +1 -1
  215. package/dist/utils/diagnostics.js +1 -1
  216. package/dist/utils/diagnostics.js.map +1 -1
  217. package/dist/utils/errorHandling.js.map +1 -1
  218. package/dist/utils/formatting.d.ts.map +1 -1
  219. package/dist/utils/formatting.js +10 -2
  220. package/dist/utils/formatting.js.map +1 -1
  221. package/dist/utils/list-detection.d.ts +2 -2
  222. package/dist/utils/list-detection.d.ts.map +1 -1
  223. package/dist/utils/list-detection.js +21 -23
  224. package/dist/utils/list-detection.js.map +1 -1
  225. package/dist/utils/logger.d.ts.map +1 -1
  226. package/dist/utils/logger.js +12 -7
  227. package/dist/utils/logger.js.map +1 -1
  228. package/dist/utils/parsingHelpers.js.map +1 -1
  229. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  230. package/dist/utils/stripTrackedChanges.js +3 -3
  231. package/dist/utils/stripTrackedChanges.js.map +1 -1
  232. package/dist/utils/textDiff.d.ts +1 -1
  233. package/dist/utils/textDiff.js +8 -8
  234. package/dist/utils/textDiff.js.map +1 -1
  235. package/dist/utils/units.js.map +1 -1
  236. package/dist/utils/validation.d.ts.map +1 -1
  237. package/dist/utils/validation.js +24 -7
  238. package/dist/utils/validation.js.map +1 -1
  239. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  240. package/dist/utils/xmlSanitization.js +3 -3
  241. package/dist/utils/xmlSanitization.js.map +1 -1
  242. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.js +5 -5
  244. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  245. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  246. package/dist/validation/RevisionValidator.js +7 -9
  247. package/dist/validation/RevisionValidator.js.map +1 -1
  248. package/dist/validation/ValidationRules.js +3 -3
  249. package/dist/validation/ValidationRules.js.map +1 -1
  250. package/dist/validation/index.js.map +1 -1
  251. package/dist/xml/XMLBuilder.d.ts +1 -1
  252. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  253. package/dist/xml/XMLBuilder.js +98 -100
  254. package/dist/xml/XMLBuilder.js.map +1 -1
  255. package/dist/xml/XMLParser.d.ts.map +1 -1
  256. package/dist/xml/XMLParser.js +61 -66
  257. package/dist/xml/XMLParser.js.map +1 -1
  258. package/dist/zip/ZipHandler.d.ts.map +1 -1
  259. package/dist/zip/ZipHandler.js.map +1 -1
  260. package/dist/zip/ZipReader.d.ts.map +1 -1
  261. package/dist/zip/ZipReader.js +1 -3
  262. package/dist/zip/ZipReader.js.map +1 -1
  263. package/dist/zip/ZipWriter.d.ts +1 -1
  264. package/dist/zip/ZipWriter.d.ts.map +1 -1
  265. package/dist/zip/ZipWriter.js +28 -36
  266. package/dist/zip/ZipWriter.js.map +1 -1
  267. package/dist/zip/types.js +1 -1
  268. package/dist/zip/types.js.map +1 -1
  269. package/package.json +92 -92
  270. package/src/__tests__/helper-methods.test.ts +512 -512
  271. package/src/constants/legacyCompatFlags.ts +138 -138
  272. package/src/constants/limits.ts +50 -50
  273. package/src/core/Document.ts +985 -1145
  274. package/src/core/DocumentContent.ts +461 -467
  275. package/src/core/DocumentGenerator.ts +1133 -1104
  276. package/src/core/DocumentIdManager.ts +158 -158
  277. package/src/core/DocumentParser.ts +2347 -2716
  278. package/src/core/DocumentValidator.ts +363 -372
  279. package/src/core/Relationship.ts +367 -367
  280. package/src/core/RelationshipManager.ts +429 -428
  281. package/src/elements/AlternateContent.ts +42 -42
  282. package/src/elements/Bookmark.ts +212 -210
  283. package/src/elements/BookmarkManager.ts +247 -250
  284. package/src/elements/Comment.ts +356 -359
  285. package/src/elements/CommentManager.ts +499 -502
  286. package/src/elements/CommonTypes.ts +524 -549
  287. package/src/elements/CustomXml.ts +36 -36
  288. package/src/elements/Endnote.ts +221 -217
  289. package/src/elements/EndnoteManager.ts +246 -249
  290. package/src/elements/Field.ts +1292 -1233
  291. package/src/elements/FieldHelpers.ts +329 -333
  292. package/src/elements/FontManager.ts +336 -339
  293. package/src/elements/Footer.ts +269 -269
  294. package/src/elements/Footnote.ts +221 -217
  295. package/src/elements/FootnoteManager.ts +246 -249
  296. package/src/elements/Header.ts +269 -269
  297. package/src/elements/HeaderFooterManager.ts +219 -219
  298. package/src/elements/Hyperlink.ts +1288 -1193
  299. package/src/elements/Image.ts +1982 -1756
  300. package/src/elements/ImageManager.ts +437 -432
  301. package/src/elements/ImageRun.ts +59 -59
  302. package/src/elements/MathElement.ts +65 -65
  303. package/src/elements/Paragraph.ts +4347 -4287
  304. package/src/elements/PreservedElement.ts +53 -53
  305. package/src/elements/PropertyChangeTypes.ts +458 -442
  306. package/src/elements/RangeMarker.ts +382 -400
  307. package/src/elements/Revision.ts +1198 -1217
  308. package/src/elements/RevisionContent.ts +73 -73
  309. package/src/elements/RevisionManager.ts +1070 -1070
  310. package/src/elements/Run.ts +3103 -3073
  311. package/src/elements/Section.ts +1521 -1421
  312. package/src/elements/Shape.ts +884 -873
  313. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  314. package/src/elements/Table.ts +2468 -2524
  315. package/src/elements/TableCell.ts +1617 -1621
  316. package/src/elements/TableGridChange.ts +149 -151
  317. package/src/elements/TableOfContents.ts +701 -691
  318. package/src/elements/TableOfContentsElement.ts +89 -89
  319. package/src/elements/TableRow.ts +960 -929
  320. package/src/elements/TextBox.ts +766 -768
  321. package/src/formatting/AbstractNumbering.ts +580 -579
  322. package/src/formatting/NumberingInstance.ts +295 -299
  323. package/src/formatting/NumberingLevel.ts +981 -1040
  324. package/src/formatting/NumberingManager.ts +833 -827
  325. package/src/formatting/Style.ts +1785 -1879
  326. package/src/formatting/StylesManager.ts +1090 -1130
  327. package/src/helpers/CleanupHelper.ts +524 -524
  328. package/src/images/ImageOptimizer.ts +274 -274
  329. package/src/index.ts +559 -554
  330. package/src/managers/DrawingManager.ts +319 -319
  331. package/src/tracking/DocumentTrackingContext.ts +687 -674
  332. package/src/tracking/TrackingContext.ts +175 -173
  333. package/src/types/compatibility-types.ts +49 -49
  334. package/src/types/formatting.ts +210 -210
  335. package/src/types/list-types.ts +14 -14
  336. package/src/types/settings-types.ts +59 -59
  337. package/src/types/styleConfig.ts +189 -189
  338. package/src/utils/ChangelogGenerator.ts +1583 -1581
  339. package/src/utils/CompatibilityUpgrader.ts +235 -237
  340. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  341. package/src/utils/MoveOperationHelper.ts +233 -238
  342. package/src/utils/RevisionAwareProcessor.ts +518 -526
  343. package/src/utils/RevisionWalker.ts +427 -457
  344. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  345. package/src/utils/ShadingResolver.ts +105 -107
  346. package/src/utils/acceptRevisions.ts +723 -714
  347. package/src/utils/cnfStyleDecoder.ts +212 -217
  348. package/src/utils/corruptionDetection.ts +346 -345
  349. package/src/utils/dateFormatting.ts +20 -20
  350. package/src/utils/deepClone.ts +77 -78
  351. package/src/utils/diagnostics.ts +125 -129
  352. package/src/utils/errorHandling.ts +80 -80
  353. package/src/utils/formatting.ts +220 -213
  354. package/src/utils/list-detection.ts +32 -42
  355. package/src/utils/logger.ts +412 -404
  356. package/src/utils/parsingHelpers.ts +190 -190
  357. package/src/utils/stripTrackedChanges.ts +356 -353
  358. package/src/utils/textDiff.ts +100 -100
  359. package/src/utils/units.ts +421 -421
  360. package/src/utils/validation.ts +553 -542
  361. package/src/utils/xmlSanitization.ts +179 -182
  362. package/src/validation/RevisionAutoFixer.ts +541 -542
  363. package/src/validation/RevisionValidator.ts +470 -460
  364. package/src/validation/ValidationRules.ts +338 -338
  365. package/src/validation/index.ts +30 -30
  366. package/src/xml/XMLBuilder.ts +857 -871
  367. package/src/xml/XMLParser.ts +877 -919
  368. package/src/zip/ZipHandler.ts +629 -637
  369. package/src/zip/ZipReader.ts +295 -299
  370. package/src/zip/ZipWriter.ts +374 -390
  371. package/src/zip/types.ts +116 -116
@@ -1,1217 +1,1198 @@
1
- /**
2
- * Revision - Represents tracked changes in a Word document
3
- *
4
- * Track changes allow tracking of insertions, deletions, and modifications
5
- * to document content, showing who made changes and when.
6
- */
7
-
8
- import { Run } from './Run';
9
- import type { RunFormatting } from './Run';
10
- import { XMLElement } from '../xml/XMLBuilder';
11
- import type { RevisionLocation } from './PropertyChangeTypes';
12
- import type { RevisionContent } from './RevisionContent';
13
- import { isRunContent, isHyperlinkContent } from './RevisionContent';
14
- import { formatDateForXml } from '../utils/dateFormatting';
15
-
16
- /**
17
- * Revision type - All OpenXML WordprocessingML revision types
18
- */
19
- export type RevisionType =
20
- // Content changes
21
- | 'insert' // w:ins - Inserted content
22
- | 'delete' // w:del - Deleted content
23
- // Property changes
24
- | 'runPropertiesChange' // w:rPrChange - Run formatting change (bold, italic, font, etc.)
25
- | 'paragraphPropertiesChange' // w:pPrChange - Paragraph formatting change
26
- | 'tablePropertiesChange' // w:tblPrChange - Table formatting change
27
- | 'tableExceptionPropertiesChange' // w:tblPrExChange - Table exception properties change
28
- | 'tableRowPropertiesChange' // w:trPrChange - Table row properties change
29
- | 'tableCellPropertiesChange' // w:tcPrChange - Table cell properties change
30
- | 'sectionPropertiesChange' // w:sectPrChange - Section properties change
31
- // Move operations
32
- | 'moveFrom' // w:moveFrom - Content moved from this location
33
- | 'moveTo' // w:moveTo - Content moved to this location
34
- // Table operations
35
- | 'tableCellInsert' // w:cellIns - Table cell inserted
36
- | 'tableCellDelete' // w:cellDel - Table cell deleted
37
- | 'tableCellMerge' // w:cellMerge - Table cells merged
38
- // Numbering
39
- | 'numberingChange' // w:numberingChange - List numbering changed
40
- // Hyperlink changes
41
- | 'hyperlinkChange' // Hyperlink URL, text, or formatting change
42
- // Rich content changes (new tracking types)
43
- | 'imageChange' // Image insertion, deletion, or property change
44
- | 'fieldChange' // Field insertion, deletion, or value change
45
- | 'commentChange' // Comment insertion, deletion, or content change
46
- | 'bookmarkChange' // Bookmark creation, deletion, or range change
47
- | 'contentControlChange'; // Content control insertion, deletion, or property change
48
-
49
- /**
50
- * Field context for revisions inside complex fields
51
- * Provides information about the parent field when a revision appears in a field result
52
- */
53
- export interface FieldContext {
54
- /** Reference to the parent ComplexField (if revision is in field result) */
55
- field?: import('./Field').ComplexField;
56
- /** Field instruction (e.g., "HYPERLINK", "TOC", "MERGEFIELD") */
57
- instruction?: string;
58
- /** Position within field: 'instruction' or 'result' */
59
- position: 'instruction' | 'result';
60
- }
61
-
62
- /**
63
- * Revision properties
64
- */
65
- export interface RevisionProperties {
66
- /** Unique revision ID (assigned by RevisionManager) */
67
- id?: number;
68
- /** Author who made the change */
69
- author: string;
70
- /** Date when the change was made */
71
- date?: Date;
72
- /** Type of revision */
73
- type: RevisionType;
74
- /** Content affected by the revision (Run, Hyperlink, or arrays thereof) */
75
- content: RevisionContent | RevisionContent[];
76
- /** Previous properties (for property change revisions) */
77
- previousProperties?: Record<string, any>;
78
- /** New properties (for property change revisions) */
79
- newProperties?: Record<string, any>;
80
- /** Move ID (for moveFrom/moveTo operations) */
81
- moveId?: string;
82
- /** Destination location (for moveFrom) or source location (for moveTo) */
83
- moveLocation?: string;
84
- /** Location of this revision within the document structure */
85
- location?: RevisionLocation;
86
- /** Field context if revision is inside a complex field */
87
- fieldContext?: FieldContext;
88
- }
89
-
90
- /**
91
- * Represents a tracked change (revision) in a document
92
- */
93
- export class Revision {
94
- private id: number;
95
- private author: string;
96
- private date: Date;
97
- private type: RevisionType;
98
- private content: RevisionContent[];
99
- private previousProperties?: Record<string, any>;
100
- private newProperties?: Record<string, any>;
101
- private moveId?: string;
102
- private moveLocation?: string;
103
- private isFieldInstruction = false;
104
- private location?: RevisionLocation;
105
- private fieldContext?: FieldContext;
106
-
107
- /**
108
- * Creates a new Revision
109
- * @param properties - Revision properties
110
- */
111
- constructor(properties: RevisionProperties) {
112
- this.id = properties.id ?? 0; // Will be assigned by RevisionManager
113
- this.author = properties.author;
114
- this.date = properties.date || new Date();
115
- this.type = properties.type;
116
- this.content = Array.isArray(properties.content) ? properties.content : [properties.content];
117
- this.previousProperties = properties.previousProperties;
118
- this.newProperties = properties.newProperties;
119
- this.moveId = properties.moveId;
120
- this.moveLocation = properties.moveLocation;
121
- this.location = properties.location;
122
- this.fieldContext = properties.fieldContext;
123
- }
124
-
125
- /**
126
- * Gets the revision ID
127
- */
128
- getId(): number {
129
- return this.id;
130
- }
131
-
132
- /**
133
- * Sets the revision ID (used by RevisionManager)
134
- * @internal
135
- */
136
- setId(id: number): void {
137
- this.id = id;
138
- }
139
-
140
- /**
141
- * Gets the author
142
- */
143
- getAuthor(): string {
144
- return this.author;
145
- }
146
-
147
- /**
148
- * Sets the author
149
- */
150
- setAuthor(author: string): this {
151
- this.author = author;
152
- return this;
153
- }
154
-
155
- /**
156
- * Gets the revision date
157
- */
158
- getDate(): Date {
159
- return this.date;
160
- }
161
-
162
- /**
163
- * Sets the revision date
164
- */
165
- setDate(date: Date): this {
166
- this.date = date;
167
- return this;
168
- }
169
-
170
- /**
171
- * Gets the revision type
172
- */
173
- getType(): RevisionType {
174
- return this.type;
175
- }
176
-
177
- /**
178
- * Gets all content items in this revision
179
- * @returns Array of RevisionContent (Run or Hyperlink objects)
180
- */
181
- getContent(): RevisionContent[] {
182
- return [...this.content];
183
- }
184
-
185
- /**
186
- * Gets only the Run objects from this revision (backward compatible)
187
- * @returns Array of Run objects
188
- */
189
- getRuns(): Run[] {
190
- return this.content.filter((item): item is Run => isRunContent(item));
191
- }
192
-
193
- /**
194
- * Gets only the Hyperlink objects from this revision
195
- * @returns Array of Hyperlink objects
196
- */
197
- getHyperlinks(): import('./Hyperlink').Hyperlink[] {
198
- return this.content.filter((item): item is import('./Hyperlink').Hyperlink => isHyperlinkContent(item));
199
- }
200
-
201
- /**
202
- * Adds a run to this revision
203
- */
204
- addRun(run: Run): this {
205
- this.content.push(run);
206
- return this;
207
- }
208
-
209
- /**
210
- * Adds a hyperlink to this revision
211
- */
212
- addHyperlink(hyperlink: import('./Hyperlink').Hyperlink): this {
213
- this.content.push(hyperlink);
214
- return this;
215
- }
216
-
217
- /**
218
- * Adds content (Run or Hyperlink) to this revision
219
- */
220
- addContent(item: RevisionContent): this {
221
- this.content.push(item);
222
- return this;
223
- }
224
-
225
- /**
226
- * Gets the combined text content from all Runs and Hyperlinks in this revision.
227
- * This is used by isParagraphBlank() to detect text inside revision elements.
228
- * @returns Combined text string from all content items
229
- */
230
- getText(): string {
231
- return this.content
232
- .filter((item): item is Run | import('./Hyperlink').Hyperlink =>
233
- isRunContent(item) || isHyperlinkContent(item)
234
- )
235
- .map((item) => item.getText())
236
- .join('');
237
- }
238
-
239
- /**
240
- * Gets the previous properties (for property change revisions)
241
- */
242
- getPreviousProperties(): Record<string, any> | undefined {
243
- return this.previousProperties;
244
- }
245
-
246
- /**
247
- * Gets the new properties (for property change revisions)
248
- */
249
- getNewProperties(): Record<string, any> | undefined {
250
- return this.newProperties;
251
- }
252
-
253
- /**
254
- * Gets the move ID (for moveFrom/moveTo operations)
255
- */
256
- getMoveId(): string | undefined {
257
- return this.moveId;
258
- }
259
-
260
- /**
261
- * Gets the move location
262
- */
263
- getMoveLocation(): string | undefined {
264
- return this.moveLocation;
265
- }
266
-
267
- /**
268
- * Gets the location of this revision within the document
269
- * @returns Location information or undefined if not set
270
- */
271
- getLocation(): RevisionLocation | undefined {
272
- return this.location;
273
- }
274
-
275
- /**
276
- * Sets the location of this revision within the document
277
- * @param location - Location information
278
- * @returns This revision for chaining
279
- */
280
- setLocation(location: RevisionLocation): this {
281
- this.location = location;
282
- return this;
283
- }
284
-
285
- /**
286
- * Gets the field context if this revision is inside a complex field
287
- * @returns Field context information or undefined if not inside a field
288
- */
289
- getFieldContext(): FieldContext | undefined {
290
- return this.fieldContext;
291
- }
292
-
293
- /**
294
- * Sets the field context for this revision
295
- * @param context - Field context information
296
- * @returns This revision for chaining
297
- */
298
- setFieldContext(context: FieldContext): this {
299
- this.fieldContext = context;
300
- return this;
301
- }
302
-
303
- /**
304
- * Checks if this revision is inside a complex field
305
- * @returns True if the revision is inside a field result or instruction section
306
- */
307
- isInsideField(): boolean {
308
- return this.fieldContext !== undefined;
309
- }
310
-
311
- /**
312
- * Checks if this revision is inside a field result section
313
- * @returns True if the revision is in the result section of a complex field
314
- */
315
- isInsideFieldResult(): boolean {
316
- return this.fieldContext?.position === 'result';
317
- }
318
-
319
- /**
320
- * Checks if this revision is inside a field instruction section
321
- * @returns True if the revision is in the instruction section of a complex field
322
- */
323
- isInsideFieldInstruction(): boolean {
324
- return this.fieldContext?.position === 'instruction';
325
- }
326
-
327
- /**
328
- * Gets the parent field if this revision is inside a complex field
329
- * @returns The parent ComplexField or undefined
330
- */
331
- getParentField(): import('./Field').ComplexField | undefined {
332
- return this.fieldContext?.field;
333
- }
334
-
335
- /**
336
- * Marks this revision as a field instruction deletion
337
- * When true, uses w:delInstrText instead of w:delText
338
- */
339
- setAsFieldInstruction(): this {
340
- this.isFieldInstruction = true;
341
- return this;
342
- }
343
-
344
- /**
345
- * Checks if this is a field instruction deletion
346
- */
347
- isFieldInstructionDeletion(): boolean {
348
- return this.isFieldInstruction;
349
- }
350
-
351
- /**
352
- * Formats a date to ISO 8601 format for XML
353
- * Per ECMA-376, revision dates must be in ISO 8601 format (e.g., "2024-01-01T12:00:00Z")
354
- * Uses formatDateForXml() to strip milliseconds which Word does not accept.
355
- * @param date - Date to format
356
- * @returns ISO 8601 formatted date string without milliseconds
357
- */
358
- private formatDate(date: Date): string {
359
- return formatDateForXml(date);
360
- }
361
-
362
- /**
363
- * Gets the XML element name for this revision type
364
- * Maps internal revision types to OOXML WordprocessingML element names per ECMA-376
365
- *
366
- * Mappings:
367
- * - insert w:ins (inserted content)
368
- * - delete → w:del (deleted content)
369
- * - runPropertiesChange → w:rPrChange (run formatting change)
370
- * - paragraphPropertiesChange → w:pPrChange (paragraph formatting change)
371
- * - tablePropertiesChange → w:tblPrChange (table formatting change)
372
- * - tableRowPropertiesChange → w:trPrChange (table row properties change)
373
- * - tableCellPropertiesChange → w:tcPrChange (table cell properties change)
374
- * - sectionPropertiesChange → w:sectPrChange (section properties change)
375
- * - moveFrom → w:moveFrom (source location of moved content)
376
- * - moveTo → w:moveTo (destination location of moved content)
377
- * - tableCellInsert → w:cellIns (inserted table cell)
378
- * - tableCellDelete → w:cellDel (deleted table cell)
379
- * - tableCellMerge → w:cellMerge (merged table cells)
380
- * - numberingChange → w:numberingChange (list numbering changed)
381
- *
382
- * @returns OOXML element name (e.g., "w:ins", "w:del")
383
- */
384
- private getElementName(): string {
385
- switch (this.type) {
386
- case 'insert':
387
- return 'w:ins';
388
- case 'delete':
389
- return 'w:del';
390
- case 'runPropertiesChange':
391
- return 'w:rPrChange';
392
- case 'paragraphPropertiesChange':
393
- return 'w:pPrChange';
394
- case 'tablePropertiesChange':
395
- return 'w:tblPrChange';
396
- case 'tableExceptionPropertiesChange':
397
- return 'w:tblPrExChange';
398
- case 'tableRowPropertiesChange':
399
- return 'w:trPrChange';
400
- case 'tableCellPropertiesChange':
401
- return 'w:tcPrChange';
402
- case 'sectionPropertiesChange':
403
- return 'w:sectPrChange';
404
- case 'moveFrom':
405
- return 'w:moveFrom';
406
- case 'moveTo':
407
- return 'w:moveTo';
408
- case 'tableCellInsert':
409
- return 'w:cellIns';
410
- case 'tableCellDelete':
411
- return 'w:cellDel';
412
- case 'tableCellMerge':
413
- return 'w:cellMerge';
414
- case 'numberingChange':
415
- return 'w:numberingChange';
416
- // Internal tracking types - no OOXML element equivalent
417
- // These are used for changelog generation and internal tracking,
418
- // not for XML serialization. OOXML tracks these as insert/delete pairs.
419
- case 'hyperlinkChange':
420
- case 'imageChange':
421
- case 'fieldChange':
422
- case 'commentChange':
423
- case 'bookmarkChange':
424
- case 'contentControlChange':
425
- throw new Error(
426
- `Revision type '${this.type}' is an internal tracking type and cannot be serialized to OOXML XML. ` +
427
- `OOXML does not have a native element for this type. ` +
428
- `Use insert/delete revision pairs for tracking changes to ${this.type.replace('Change', '')}s.`
429
- );
430
- default:
431
- // TypeScript exhaustiveness check - this should never be reached
432
- // if all RevisionType values are handled above
433
- const _exhaustiveCheck: never = this.type;
434
- throw new Error(`Unknown revision type: ${_exhaustiveCheck}`);
435
- }
436
- }
437
-
438
- /**
439
- * Generates XML for this revision per OOXML WordprocessingML specification (ECMA-376)
440
- *
441
- * **XML Structure:**
442
- *
443
- * Content revisions (w:ins, w:del, w:moveFrom, w:moveTo):
444
- * ```xml
445
- * <w:ins w:id="0" w:author="Author Name" w:date="2024-01-01T12:00:00Z">
446
- * <w:r>
447
- * <w:t>Inserted text</w:t>
448
- * </w:r>
449
- * </w:ins>
450
- * ```
451
- *
452
- * Deletion revisions use w:delText instead of w:t:
453
- * ```xml
454
- * <w:del w:id="1" w:author="Author Name" w:date="2024-01-01T12:00:00Z">
455
- * <w:r>
456
- * <w:delText>Deleted text</w:delText>
457
- * </w:r>
458
- * </w:del>
459
- * ```
460
- *
461
- * Property change revisions (w:rPrChange, w:pPrChange, etc.):
462
- * ```xml
463
- * <w:rPrChange w:id="2" w:author="Author Name" w:date="2024-01-01T12:00:00Z">
464
- * <w:rPr>
465
- * <w:b/> <!-- Previous bold setting -->
466
- * <w:sz w:val="24"/> <!-- Previous font size -->
467
- * </w:rPr>
468
- * </w:rPrChange>
469
- * ```
470
- *
471
- * **Required Attributes (per ECMA-376):**
472
- * - w:id: Unique revision identifier (ST_DecimalNumber) - REQUIRED
473
- * - w:author: Author who made the change (ST_String) - REQUIRED
474
- * - w:date: When the change was made (ST_DateTime, ISO 8601) - OPTIONAL
475
- *
476
- * **Move Operations:**
477
- * For moveFrom/moveTo, an additional w:moveId attribute links the source and destination:
478
- * ```xml
479
- * <w:moveFrom w:id="3" w:author="Author" w:date="..." w:moveId="move-1">...</w:moveFrom>
480
- * <w:moveTo w:id="4" w:author="Author" w:date="..." w:moveId="move-1">...</w:moveTo>
481
- * ```
482
- *
483
- * **Content vs Property Changes:**
484
- * - Content revisions (insert/delete/move): Contain w:r elements with text runs
485
- * - Property revisions (rPrChange/pPrChange): Contain previous property elements (w:rPr, w:pPr)
486
- *
487
- * @returns XMLElement representing the revision in OOXML format, or null for internal-only types
488
- * @see ECMA-376 Part 1 §17.13.5 (Revision Identifiers for Paragraph Content)
489
- * @see ECMA-376 Part 1 §17.13.5.15 (Inserted Paragraph)
490
- * @see ECMA-376 Part 1 §17.13.5.14 (Deleted Paragraph)
491
- */
492
- toXML(): XMLElement | null {
493
- // Internal tracking types have no OOXML equivalent and cannot be serialized
494
- // They are used for changelog generation and internal tracking only
495
- const INTERNAL_TRACKING_TYPES: RevisionType[] = [
496
- 'hyperlinkChange',
497
- 'imageChange',
498
- 'fieldChange',
499
- 'commentChange',
500
- 'bookmarkChange',
501
- 'contentControlChange',
502
- ];
503
-
504
- if (INTERNAL_TRACKING_TYPES.includes(this.type)) {
505
- // Return null for internal types - callers should skip these
506
- return null;
507
- }
508
-
509
- const attributes: Record<string, string> = {
510
- 'w:id': this.id.toString(),
511
- 'w:author': this.author,
512
- 'w:date': this.formatDate(this.date),
513
- };
514
-
515
- // Add move-specific attributes
516
- if ((this.type === 'moveFrom' || this.type === 'moveTo') && this.moveId) {
517
- attributes['w:moveId'] = this.moveId;
518
- }
519
-
520
- const elementName = this.getElementName();
521
- const children: XMLElement[] = [];
522
-
523
- // Handle different revision types
524
- if (this.isPropertyChangeType()) {
525
- // Property change revisions contain the previous properties
526
- if (this.previousProperties) {
527
- children.push(this.createPropertiesElement());
528
- }
529
- }
530
-
531
- // Check if content contains only a single hyperlink (needs nesting inversion per ECMA-376)
532
- // w:hyperlink is NOT a valid child of w:ins/w:del; instead w:ins/w:del must be inside w:hyperlink
533
- const firstItem = this.content[0];
534
- const singleHyperlink = this.content.length === 1 && firstItem && isHyperlinkContent(firstItem)
535
- ? firstItem : null;
536
-
537
- if (singleHyperlink && !this.isPropertyChangeType()) {
538
- return this.createHyperlinkWrappedRevisionXml(singleHyperlink, elementName, attributes);
539
- }
540
-
541
- // Add content to the revision (handles both Run and Hyperlink)
542
- for (const item of this.content) {
543
- if (isHyperlinkContent(item)) {
544
- // For multiple-item revisions containing hyperlinks, extract the runs
545
- // and add them directly (hyperlink wrapper omitted to maintain validity)
546
- const hyperlinkXml = item.toXML();
547
- if (hyperlinkXml.children) {
548
- for (const child of hyperlinkXml.children) {
549
- if (typeof child === 'object' && child.name === 'w:r') {
550
- if (this.type === 'delete' || this.type === 'moveFrom') {
551
- children.push(this.convertRunXmlToDeleted(child));
552
- } else {
553
- children.push(child);
554
- }
555
- }
556
- }
557
- }
558
- } else if (isRunContent(item)) {
559
- // Handle Run content (existing behavior)
560
- if (this.type === 'delete' || this.type === 'moveFrom') {
561
- // For deletions and moveFrom, we need to modify the run XML to use w:delText instead of w:t
562
- const runXml = this.createDeletedRunXml(item);
563
- children.push(runXml);
564
- } else {
565
- // For other types, use normal run XML
566
- children.push(item.toXML());
567
- }
568
- }
569
- }
570
-
571
- return {
572
- name: elementName,
573
- attributes,
574
- children,
575
- };
576
- }
577
-
578
- /**
579
- * Checks if this is a property change revision type
580
- *
581
- * Property change revisions track formatting changes, not content changes.
582
- * They contain previous property elements (w:rPr, w:pPr, etc.) instead of text runs.
583
- *
584
- * **Property Change Types:**
585
- * - runPropertiesChange: Run formatting (bold, italic, font, color, etc.)
586
- * - paragraphPropertiesChange: Paragraph formatting (alignment, spacing, indentation, etc.)
587
- * - tablePropertiesChange: Table formatting
588
- * - tableRowPropertiesChange: Table row properties
589
- * - tableCellPropertiesChange: Table cell properties
590
- * - sectionPropertiesChange: Section properties (page size, margins, etc.)
591
- * - numberingChange: List numbering properties
592
- *
593
- * **Content Change Types (NOT property changes):**
594
- * - insert: Added text
595
- * - delete: Removed text
596
- * - moveFrom: Moved text source
597
- * - moveTo: Moved text destination
598
- * - tableCellInsert: Added table cell
599
- * - tableCellDelete: Removed table cell
600
- * - tableCellMerge: Merged table cells
601
- *
602
- * @returns true if this revision tracks a property/formatting change, false otherwise
603
- */
604
- private isPropertyChangeType(): boolean {
605
- return [
606
- 'runPropertiesChange',
607
- 'paragraphPropertiesChange',
608
- 'tablePropertiesChange',
609
- 'tableExceptionPropertiesChange',
610
- 'tableRowPropertiesChange',
611
- 'tableCellPropertiesChange',
612
- 'sectionPropertiesChange',
613
- 'numberingChange',
614
- ].includes(this.type);
615
- }
616
-
617
- /**
618
- * Creates XML element for previous properties in property change revisions
619
- *
620
- * **Purpose:**
621
- * Property change revisions (w:rPrChange, w:pPrChange, etc.) must contain a child element
622
- * with the PREVIOUS state of the properties before the change. This allows Word to show
623
- * what changed and enables accepting/rejecting the change.
624
- *
625
- * **Structure:**
626
- * ```xml
627
- * <w:rPrChange w:id="0" w:author="Author" w:date="...">
628
- * <w:rPr>
629
- * <!-- Previous run properties -->
630
- * <w:b/> <!-- Was bold -->
631
- * <w:sz w:val="24"/> <!-- Was 12pt (24 half-points) -->
632
- * </w:rPr>
633
- * </w:rPrChange>
634
- * ```
635
- *
636
- * **Property Element Mapping:**
637
- * - runPropertiesChange → w:rPr (run properties)
638
- * - paragraphPropertiesChange → w:pPr (paragraph properties)
639
- * - tablePropertiesChange → w:tblPr (table properties)
640
- * - tableRowPropertiesChange → w:trPr (table row properties)
641
- * - tableCellPropertiesChange → w:tcPr (table cell properties)
642
- * - sectionPropertiesChange → w:sectPr (section properties)
643
- * - numberingChange → w:numPr (numbering properties)
644
- *
645
- * **Implementation:**
646
- * This method converts the previousProperties object into OOXML elements.
647
- * - Boolean properties (e.g., bold) → <w:b/>
648
- * - Value properties (e.g., font size) → <w:sz w:val="24"/>
649
- *
650
- * @returns XMLElement containing previous properties (w:rPr, w:pPr, etc.)
651
- * @see ECMA-376 Part 1 §17.13.5.31 (Run Properties Change)
652
- * @see ECMA-376 Part 1 §17.13.5.29 (Paragraph Properties Change)
653
- */
654
- private createPropertiesElement(): XMLElement {
655
- // For runPropertiesChange, delegate to Run.generateRunPropertiesXML for correct
656
- // ECMA-376 element names and ordering (e.g., bold→w:b, font→w:rFonts, size→w:sz)
657
- if (this.type === 'runPropertiesChange' && this.previousProperties) {
658
- const rPr = Run.generateRunPropertiesXML(this.previousProperties as RunFormatting);
659
- return rPr || { name: 'w:rPr', attributes: {}, children: [] };
660
- }
661
-
662
- // The property element name depends on the revision type
663
- let propElementName = 'w:rPr';
664
-
665
- switch (this.type) {
666
- case 'runPropertiesChange':
667
- propElementName = 'w:rPr';
668
- break;
669
- case 'paragraphPropertiesChange':
670
- propElementName = 'w:pPr';
671
- break;
672
- case 'tablePropertiesChange':
673
- propElementName = 'w:tblPr';
674
- break;
675
- case 'tableExceptionPropertiesChange':
676
- propElementName = 'w:tblPrEx';
677
- break;
678
- case 'tableRowPropertiesChange':
679
- propElementName = 'w:trPr';
680
- break;
681
- case 'tableCellPropertiesChange':
682
- propElementName = 'w:tcPr';
683
- break;
684
- case 'sectionPropertiesChange':
685
- propElementName = 'w:sectPr';
686
- break;
687
- case 'numberingChange':
688
- propElementName = 'w:numPr';
689
- break;
690
- }
691
-
692
- // Build property children from previousProperties
693
- const propChildren: XMLElement[] = [];
694
- if (this.previousProperties) {
695
- for (const [key, value] of Object.entries(this.previousProperties)) {
696
- if (typeof value === 'boolean' && value) {
697
- // Boolean properties (e.g., bold, italic)
698
- propChildren.push({ name: `w:${key}`, attributes: {}, children: [] });
699
- } else if (typeof value === 'string' || typeof value === 'number') {
700
- // Value properties (e.g., font size, color)
701
- propChildren.push({
702
- name: `w:${key}`,
703
- attributes: { 'w:val': value.toString() },
704
- children: [],
705
- });
706
- }
707
- }
708
- }
709
-
710
- return {
711
- name: propElementName,
712
- attributes: {},
713
- children: propChildren,
714
- };
715
- }
716
-
717
- /**
718
- * Creates XML for a deleted run (uses w:delText or w:delInstrText instead of w:t)
719
- *
720
- * **OOXML Requirement:**
721
- * Per ECMA-376, deleted text must use w:delText element instead of w:t element.
722
- * For deleted field instructions, w:delInstrText must be used instead.
723
- * This is required for proper rendering in Microsoft Word's Track Changes mode.
724
- *
725
- * **Transformation:**
726
- * ```xml
727
- * <!-- Normal run (NOT in deletion) -->
728
- * <w:r>
729
- * <w:rPr><w:b/></w:rPr>
730
- * <w:t>Text</w:t>
731
- * </w:r>
732
- *
733
- * <!-- Deleted run (inside w:del) -->
734
- * <w:r>
735
- * <w:rPr><w:b/></w:rPr>
736
- * <w:delText>Text</w:delText>
737
- * </w:r>
738
- *
739
- * <!-- Deleted field instruction (inside w:del) -->
740
- * <w:r>
741
- * <w:delInstrText>MERGEFIELD Name</w:delInstrText>
742
- * </w:r>
743
- * ```
744
- *
745
- * **Why This Matters:**
746
- * - w:delText tells Word to render with strikethrough in Track Changes mode
747
- * - w:delInstrText is specifically for deleted field codes
748
- * - w:t would render as normal text even inside w:del element
749
- * - Word will reject documents with w:t inside deletions as malformed
750
- *
751
- * **Implementation:**
752
- * This method gets the run's normal XML and replaces all w:t elements with w:delText
753
- * or w:delInstrText (for field instructions) while preserving all other properties
754
- * (formatting, spacing attributes, etc.)
755
- *
756
- * @param run - Run containing deleted text
757
- * @returns XMLElement with w:delText or w:delInstrText instead of w:t
758
- * @see ECMA-376 Part 1 §17.13.5.14 (Deleted Run Content)
759
- * @see ECMA-376 Part 1 §22.1.2.27 (w:delText element)
760
- * @see ECMA-376 Part 1 §22.1.2.26 (w:delInstrText element)
761
- */
762
- private createDeletedRunXml(run: Run): XMLElement {
763
- // Get the regular run XML
764
- const runXml = run.toXML();
765
-
766
- // Determine which element to use for deleted text
767
- // w:delInstrText for field instructions, w:delText for regular text
768
- const deletedTextElement = this.isFieldInstruction ? 'w:delInstrText' : 'w:delText';
769
-
770
- // We need to replace text elements with their deleted counterparts:
771
- // - w:t -> w:delText (or w:delInstrText if isFieldInstruction)
772
- // - w:instrText -> w:delInstrText (always, regardless of isFieldInstruction flag)
773
- if (runXml.children) {
774
- const modifiedChildren = runXml.children.map(child => {
775
- if (typeof child === 'object') {
776
- if (child.name === 'w:t') {
777
- // Replace w:t with appropriate deleted text element
778
- return {
779
- ...child,
780
- name: deletedTextElement,
781
- };
782
- } else if (child.name === 'w:instrText') {
783
- // Replace w:instrText with w:delInstrText
784
- // Per ECMA-376 §22.1.2.26, deleted field instructions must use w:delInstrText
785
- return {
786
- ...child,
787
- name: 'w:delInstrText',
788
- };
789
- }
790
- }
791
- return child;
792
- });
793
-
794
- return {
795
- ...runXml,
796
- children: modifiedChildren,
797
- };
798
- }
799
-
800
- return runXml;
801
- }
802
-
803
- /**
804
- * Creates XML for a deleted hyperlink (transforms nested runs to use w:delText)
805
- *
806
- * **OOXML Requirement:**
807
- * Per ECMA-376, when a hyperlink is inside a w:del element, its nested runs must
808
- * use w:delText instead of w:t. This transforms the hyperlink's internal run
809
- * content to comply with Word's Track Changes requirements.
810
- *
811
- * **XML Structure:**
812
- * ```xml
813
- * <w:del w:id="1" w:author="Author" w:date="...">
814
- * <w:hyperlink r:id="rId1">
815
- * <w:r>
816
- * <w:delText>Link text</w:delText> <!-- Transformed from w:t -->
817
- * </w:r>
818
- * </w:hyperlink>
819
- * </w:del>
820
- * ```
821
- *
822
- * @param hyperlink - Hyperlink containing deleted content
823
- * @returns XMLElement with nested runs transformed to use w:delText
824
- */
825
- /**
826
- * Creates a hyperlink-wrapped revision XML element.
827
- * Per ECMA-376, w:hyperlink is NOT a valid child of w:ins/w:del.
828
- * Instead, w:ins/w:del must be inside w:hyperlink:
829
- * <w:hyperlink r:id="rId1"><w:ins ...><w:r>...</w:r></w:ins></w:hyperlink>
830
- */
831
- private createHyperlinkWrappedRevisionXml(
832
- hyperlink: import('./Hyperlink').Hyperlink,
833
- revisionElementName: string,
834
- revisionAttributes: Record<string, string>
835
- ): XMLElement {
836
- const hyperlinkXml = hyperlink.toXML();
837
-
838
- // Extract runs from the hyperlink
839
- const runs: XMLElement[] = [];
840
- if (hyperlinkXml.children) {
841
- for (const child of hyperlinkXml.children) {
842
- if (typeof child === 'object' && child.name === 'w:r') {
843
- if (this.type === 'delete' || this.type === 'moveFrom') {
844
- runs.push(this.convertRunXmlToDeleted(child));
845
- } else {
846
- runs.push(child);
847
- }
848
- }
849
- }
850
- }
851
-
852
- // Build: <w:hyperlink ...><w:ins/w:del ...><w:r>...</w:r></w:ins/w:del></w:hyperlink>
853
- const revisionElement: XMLElement = {
854
- name: revisionElementName,
855
- attributes: revisionAttributes,
856
- children: runs,
857
- };
858
-
859
- return {
860
- name: hyperlinkXml.name,
861
- attributes: hyperlinkXml.attributes,
862
- children: [revisionElement],
863
- };
864
- }
865
-
866
- private createDeletedHyperlinkXml(hyperlink: import('./Hyperlink').Hyperlink): XMLElement {
867
- // Get the hyperlink's normal XML
868
- const hyperlinkXml = hyperlink.toXML();
869
-
870
- // Transform nested runs: w:t -> w:delText (or w:delInstrText for field instructions)
871
- if (hyperlinkXml.children) {
872
- hyperlinkXml.children = hyperlinkXml.children.map(child => {
873
- if (typeof child === 'object' && child.name === 'w:r') {
874
- // Transform the run's text elements
875
- return this.convertRunXmlToDeleted(child);
876
- }
877
- return child;
878
- });
879
- }
880
-
881
- return hyperlinkXml;
882
- }
883
-
884
- /**
885
- * Converts a run XMLElement to use deleted text elements
886
- * Helper for createDeletedHyperlinkXml
887
- */
888
- private convertRunXmlToDeleted(runXml: XMLElement): XMLElement {
889
- const deletedTextElement = this.isFieldInstruction ? 'w:delInstrText' : 'w:delText';
890
-
891
- if (!runXml.children) return runXml;
892
-
893
- const modifiedChildren = runXml.children.map(child => {
894
- if (typeof child === 'object' && child.name === 'w:t') {
895
- return {
896
- ...child,
897
- name: deletedTextElement,
898
- };
899
- }
900
- return child;
901
- });
902
-
903
- return {
904
- ...runXml,
905
- children: modifiedChildren,
906
- };
907
- }
908
-
909
- /**
910
- * Creates an insertion revision
911
- * @param author - Author who made the insertion
912
- * @param content - Inserted content (Run, Hyperlink, or arrays thereof)
913
- * @param date - Optional date (defaults to now)
914
- * @returns New Revision instance
915
- */
916
- static createInsertion(
917
- author: string,
918
- content: RevisionContent | RevisionContent[],
919
- date?: Date
920
- ): Revision {
921
- return new Revision({
922
- author,
923
- type: 'insert',
924
- content,
925
- date,
926
- });
927
- }
928
-
929
- /**
930
- * Creates a deletion revision
931
- * @param author - Author who made the deletion
932
- * @param content - Deleted content (Run, Hyperlink, or arrays thereof)
933
- * @param date - Optional date (defaults to now)
934
- * @returns New Revision instance
935
- */
936
- static createDeletion(
937
- author: string,
938
- content: RevisionContent | RevisionContent[],
939
- date?: Date
940
- ): Revision {
941
- return new Revision({
942
- author,
943
- type: 'delete',
944
- content,
945
- date,
946
- });
947
- }
948
-
949
- /**
950
- * Creates a field instruction deletion revision
951
- * Uses w:delInstrText instead of w:delText for field codes
952
- * @param author - Author who made the deletion
953
- * @param content - Deleted field instruction content (Run or array of Runs)
954
- * @param date - Optional date (defaults to now)
955
- * @returns New Revision instance
956
- */
957
- static createFieldInstructionDeletion(
958
- author: string,
959
- content: Run | Run[],
960
- date?: Date
961
- ): Revision {
962
- const revision = new Revision({
963
- author,
964
- type: 'delete',
965
- content,
966
- date,
967
- });
968
- revision.setAsFieldInstruction();
969
- return revision;
970
- }
971
-
972
- /**
973
- * Creates a revision from text
974
- * Convenience method that creates a Run from the text
975
- * @param type - Revision type
976
- * @param author - Author who made the change
977
- * @param text - Text content
978
- * @param date - Optional date (defaults to now)
979
- * @returns New Revision instance
980
- */
981
- static fromText(
982
- type: RevisionType,
983
- author: string,
984
- text: string,
985
- date?: Date
986
- ): Revision {
987
- const run = new Run(text);
988
- return new Revision({
989
- author,
990
- type,
991
- content: run,
992
- date,
993
- });
994
- }
995
-
996
- /**
997
- * Creates a run properties change revision
998
- * @param author - Author who made the change
999
- * @param content - Content with changed formatting
1000
- * @param previousProperties - Previous run properties
1001
- * @param date - Optional date (defaults to now)
1002
- * @returns New Revision instance
1003
- */
1004
- static createRunPropertiesChange(
1005
- author: string,
1006
- content: Run | Run[],
1007
- previousProperties: Record<string, any>,
1008
- date?: Date
1009
- ): Revision {
1010
- return new Revision({
1011
- author,
1012
- type: 'runPropertiesChange',
1013
- content,
1014
- previousProperties,
1015
- date,
1016
- });
1017
- }
1018
-
1019
- /**
1020
- * Creates a paragraph properties change revision
1021
- * @param author - Author who made the change
1022
- * @param content - Paragraph content
1023
- * @param previousProperties - Previous paragraph properties
1024
- * @param date - Optional date (defaults to now)
1025
- * @returns New Revision instance
1026
- */
1027
- static createParagraphPropertiesChange(
1028
- author: string,
1029
- content: Run | Run[],
1030
- previousProperties: Record<string, any>,
1031
- date?: Date
1032
- ): Revision {
1033
- return new Revision({
1034
- author,
1035
- type: 'paragraphPropertiesChange',
1036
- content,
1037
- previousProperties,
1038
- date,
1039
- });
1040
- }
1041
-
1042
- /**
1043
- * Creates a table properties change revision
1044
- * @param author - Author who made the change
1045
- * @param content - Table content
1046
- * @param previousProperties - Previous table properties
1047
- * @param date - Optional date (defaults to now)
1048
- * @returns New Revision instance
1049
- */
1050
- static createTablePropertiesChange(
1051
- author: string,
1052
- content: Run | Run[],
1053
- previousProperties: Record<string, any>,
1054
- date?: Date
1055
- ): Revision {
1056
- return new Revision({
1057
- author,
1058
- type: 'tablePropertiesChange',
1059
- content,
1060
- previousProperties,
1061
- date,
1062
- });
1063
- }
1064
-
1065
- /**
1066
- * Creates a table exception properties change revision
1067
- * Tracks changes to table properties that override style defaults
1068
- * @param author - Author who made the change
1069
- * @param content - Table content
1070
- * @param previousProperties - Previous table exception properties
1071
- * @param date - Optional date (defaults to now)
1072
- * @returns New Revision instance
1073
- */
1074
- static createTableExceptionPropertiesChange(
1075
- author: string,
1076
- content: Run | Run[],
1077
- previousProperties: Record<string, any>,
1078
- date?: Date
1079
- ): Revision {
1080
- return new Revision({
1081
- author,
1082
- type: 'tableExceptionPropertiesChange',
1083
- content,
1084
- previousProperties,
1085
- date,
1086
- });
1087
- }
1088
-
1089
- /**
1090
- * Creates a moveFrom revision (source of moved content)
1091
- * @param author - Author who moved the content
1092
- * @param content - Content that was moved
1093
- * @param moveId - Unique move operation ID (links moveFrom and moveTo)
1094
- * @param date - Optional date (defaults to now)
1095
- * @returns New Revision instance
1096
- */
1097
- static createMoveFrom(
1098
- author: string,
1099
- content: Run | Run[],
1100
- moveId: string,
1101
- date?: Date
1102
- ): Revision {
1103
- return new Revision({
1104
- author,
1105
- type: 'moveFrom',
1106
- content,
1107
- moveId,
1108
- date,
1109
- });
1110
- }
1111
-
1112
- /**
1113
- * Creates a moveTo revision (destination of moved content)
1114
- * @param author - Author who moved the content
1115
- * @param content - Content that was moved
1116
- * @param moveId - Unique move operation ID (links moveFrom and moveTo)
1117
- * @param date - Optional date (defaults to now)
1118
- * @returns New Revision instance
1119
- */
1120
- static createMoveTo(
1121
- author: string,
1122
- content: Run | Run[],
1123
- moveId: string,
1124
- date?: Date
1125
- ): Revision {
1126
- return new Revision({
1127
- author,
1128
- type: 'moveTo',
1129
- content,
1130
- moveId,
1131
- date,
1132
- });
1133
- }
1134
-
1135
- /**
1136
- * Creates a table cell insertion revision
1137
- * @param author - Author who inserted the cell
1138
- * @param content - Cell content
1139
- * @param date - Optional date (defaults to now)
1140
- * @returns New Revision instance
1141
- */
1142
- static createTableCellInsert(
1143
- author: string,
1144
- content: Run | Run[],
1145
- date?: Date
1146
- ): Revision {
1147
- return new Revision({
1148
- author,
1149
- type: 'tableCellInsert',
1150
- content,
1151
- date,
1152
- });
1153
- }
1154
-
1155
- /**
1156
- * Creates a table cell deletion revision
1157
- * @param author - Author who deleted the cell
1158
- * @param content - Cell content
1159
- * @param date - Optional date (defaults to now)
1160
- * @returns New Revision instance
1161
- */
1162
- static createTableCellDelete(
1163
- author: string,
1164
- content: Run | Run[],
1165
- date?: Date
1166
- ): Revision {
1167
- return new Revision({
1168
- author,
1169
- type: 'tableCellDelete',
1170
- content,
1171
- date,
1172
- });
1173
- }
1174
-
1175
- /**
1176
- * Creates a table cell merge revision
1177
- * @param author - Author who merged cells
1178
- * @param content - Merged cell content
1179
- * @param date - Optional date (defaults to now)
1180
- * @returns New Revision instance
1181
- */
1182
- static createTableCellMerge(
1183
- author: string,
1184
- content: Run | Run[],
1185
- date?: Date
1186
- ): Revision {
1187
- return new Revision({
1188
- author,
1189
- type: 'tableCellMerge',
1190
- content,
1191
- date,
1192
- });
1193
- }
1194
-
1195
- /**
1196
- * Creates a numbering change revision
1197
- * @param author - Author who changed the numbering
1198
- * @param content - Content with changed numbering
1199
- * @param previousProperties - Previous numbering properties
1200
- * @param date - Optional date (defaults to now)
1201
- * @returns New Revision instance
1202
- */
1203
- static createNumberingChange(
1204
- author: string,
1205
- content: Run | Run[],
1206
- previousProperties: Record<string, any>,
1207
- date?: Date
1208
- ): Revision {
1209
- return new Revision({
1210
- author,
1211
- type: 'numberingChange',
1212
- content,
1213
- previousProperties,
1214
- date,
1215
- });
1216
- }
1217
- }
1
+ /**
2
+ * Revision - Represents tracked changes in a Word document
3
+ *
4
+ * Track changes allow tracking of insertions, deletions, and modifications
5
+ * to document content, showing who made changes and when.
6
+ */
7
+
8
+ import { Run } from './Run';
9
+ import type { RunFormatting } from './Run';
10
+ import { XMLElement } from '../xml/XMLBuilder';
11
+ import type { RevisionLocation } from './PropertyChangeTypes';
12
+ import type { RevisionContent } from './RevisionContent';
13
+ import { isRunContent, isHyperlinkContent } from './RevisionContent';
14
+ import { formatDateForXml } from '../utils/dateFormatting';
15
+
16
+ /**
17
+ * Revision type - All OpenXML WordprocessingML revision types
18
+ */
19
+ export type RevisionType =
20
+ // Content changes
21
+ | 'insert' // w:ins - Inserted content
22
+ | 'delete' // w:del - Deleted content
23
+ // Property changes
24
+ | 'runPropertiesChange' // w:rPrChange - Run formatting change (bold, italic, font, etc.)
25
+ | 'paragraphPropertiesChange' // w:pPrChange - Paragraph formatting change
26
+ | 'tablePropertiesChange' // w:tblPrChange - Table formatting change
27
+ | 'tableExceptionPropertiesChange' // w:tblPrExChange - Table exception properties change
28
+ | 'tableRowPropertiesChange' // w:trPrChange - Table row properties change
29
+ | 'tableCellPropertiesChange' // w:tcPrChange - Table cell properties change
30
+ | 'sectionPropertiesChange' // w:sectPrChange - Section properties change
31
+ // Move operations
32
+ | 'moveFrom' // w:moveFrom - Content moved from this location
33
+ | 'moveTo' // w:moveTo - Content moved to this location
34
+ // Table operations
35
+ | 'tableCellInsert' // w:cellIns - Table cell inserted
36
+ | 'tableCellDelete' // w:cellDel - Table cell deleted
37
+ | 'tableCellMerge' // w:cellMerge - Table cells merged
38
+ // Numbering
39
+ | 'numberingChange' // w:numberingChange - List numbering changed
40
+ // Hyperlink changes
41
+ | 'hyperlinkChange' // Hyperlink URL, text, or formatting change
42
+ // Rich content changes (new tracking types)
43
+ | 'imageChange' // Image insertion, deletion, or property change
44
+ | 'fieldChange' // Field insertion, deletion, or value change
45
+ | 'commentChange' // Comment insertion, deletion, or content change
46
+ | 'bookmarkChange' // Bookmark creation, deletion, or range change
47
+ | 'contentControlChange'; // Content control insertion, deletion, or property change
48
+
49
+ /**
50
+ * Field context for revisions inside complex fields
51
+ * Provides information about the parent field when a revision appears in a field result
52
+ */
53
+ export interface FieldContext {
54
+ /** Reference to the parent ComplexField (if revision is in field result) */
55
+ field?: import('./Field').ComplexField;
56
+ /** Field instruction (e.g., "HYPERLINK", "TOC", "MERGEFIELD") */
57
+ instruction?: string;
58
+ /** Position within field: 'instruction' or 'result' */
59
+ position: 'instruction' | 'result';
60
+ }
61
+
62
+ /**
63
+ * Revision properties
64
+ */
65
+ export interface RevisionProperties {
66
+ /** Unique revision ID (assigned by RevisionManager) */
67
+ id?: number;
68
+ /** Author who made the change */
69
+ author: string;
70
+ /** Date when the change was made */
71
+ date?: Date;
72
+ /** Type of revision */
73
+ type: RevisionType;
74
+ /** Content affected by the revision (Run, Hyperlink, or arrays thereof) */
75
+ content: RevisionContent | RevisionContent[];
76
+ /** Previous properties (for property change revisions) */
77
+ previousProperties?: Record<string, any>;
78
+ /** New properties (for property change revisions) */
79
+ newProperties?: Record<string, any>;
80
+ /** Move ID (for moveFrom/moveTo operations) */
81
+ moveId?: string;
82
+ /** Destination location (for moveFrom) or source location (for moveTo) */
83
+ moveLocation?: string;
84
+ /** Location of this revision within the document structure */
85
+ location?: RevisionLocation;
86
+ /** Field context if revision is inside a complex field */
87
+ fieldContext?: FieldContext;
88
+ }
89
+
90
+ /**
91
+ * Represents a tracked change (revision) in a document
92
+ */
93
+ export class Revision {
94
+ private id: number;
95
+ private author: string;
96
+ private date: Date;
97
+ private type: RevisionType;
98
+ private content: RevisionContent[];
99
+ private previousProperties?: Record<string, any>;
100
+ private newProperties?: Record<string, any>;
101
+ private moveId?: string;
102
+ private moveLocation?: string;
103
+ private isFieldInstruction = false;
104
+ private location?: RevisionLocation;
105
+ private fieldContext?: FieldContext;
106
+
107
+ /**
108
+ * Creates a new Revision
109
+ * @param properties - Revision properties
110
+ */
111
+ constructor(properties: RevisionProperties) {
112
+ this.id = properties.id ?? 0; // Will be assigned by RevisionManager
113
+ this.author = properties.author;
114
+ this.date = properties.date || new Date();
115
+ this.type = properties.type;
116
+ this.content = Array.isArray(properties.content) ? properties.content : [properties.content];
117
+ this.previousProperties = properties.previousProperties;
118
+ this.newProperties = properties.newProperties;
119
+ this.moveId = properties.moveId;
120
+ this.moveLocation = properties.moveLocation;
121
+ this.location = properties.location;
122
+ this.fieldContext = properties.fieldContext;
123
+ }
124
+
125
+ /**
126
+ * Gets the revision ID
127
+ */
128
+ getId(): number {
129
+ return this.id;
130
+ }
131
+
132
+ /**
133
+ * Sets the revision ID (used by RevisionManager)
134
+ * @internal
135
+ */
136
+ setId(id: number): void {
137
+ this.id = id;
138
+ }
139
+
140
+ /**
141
+ * Gets the author
142
+ */
143
+ getAuthor(): string {
144
+ return this.author;
145
+ }
146
+
147
+ /**
148
+ * Sets the author
149
+ */
150
+ setAuthor(author: string): this {
151
+ this.author = author;
152
+ return this;
153
+ }
154
+
155
+ /**
156
+ * Gets the revision date
157
+ */
158
+ getDate(): Date {
159
+ return this.date;
160
+ }
161
+
162
+ /**
163
+ * Sets the revision date
164
+ */
165
+ setDate(date: Date): this {
166
+ this.date = date;
167
+ return this;
168
+ }
169
+
170
+ /**
171
+ * Gets the revision type
172
+ */
173
+ getType(): RevisionType {
174
+ return this.type;
175
+ }
176
+
177
+ /**
178
+ * Gets all content items in this revision
179
+ * @returns Array of RevisionContent (Run or Hyperlink objects)
180
+ */
181
+ getContent(): RevisionContent[] {
182
+ return [...this.content];
183
+ }
184
+
185
+ /**
186
+ * Gets only the Run objects from this revision (backward compatible)
187
+ * @returns Array of Run objects
188
+ */
189
+ getRuns(): Run[] {
190
+ return this.content.filter((item): item is Run => isRunContent(item));
191
+ }
192
+
193
+ /**
194
+ * Gets only the Hyperlink objects from this revision
195
+ * @returns Array of Hyperlink objects
196
+ */
197
+ getHyperlinks(): import('./Hyperlink').Hyperlink[] {
198
+ return this.content.filter((item): item is import('./Hyperlink').Hyperlink =>
199
+ isHyperlinkContent(item)
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Adds a run to this revision
205
+ */
206
+ addRun(run: Run): this {
207
+ this.content.push(run);
208
+ return this;
209
+ }
210
+
211
+ /**
212
+ * Adds a hyperlink to this revision
213
+ */
214
+ addHyperlink(hyperlink: import('./Hyperlink').Hyperlink): this {
215
+ this.content.push(hyperlink);
216
+ return this;
217
+ }
218
+
219
+ /**
220
+ * Adds content (Run or Hyperlink) to this revision
221
+ */
222
+ addContent(item: RevisionContent): this {
223
+ this.content.push(item);
224
+ return this;
225
+ }
226
+
227
+ /**
228
+ * Gets the combined text content from all Runs and Hyperlinks in this revision.
229
+ * This is used by isParagraphBlank() to detect text inside revision elements.
230
+ * @returns Combined text string from all content items
231
+ */
232
+ getText(): string {
233
+ return this.content
234
+ .filter(
235
+ (item): item is Run | import('./Hyperlink').Hyperlink =>
236
+ isRunContent(item) || isHyperlinkContent(item)
237
+ )
238
+ .map((item) => item.getText())
239
+ .join('');
240
+ }
241
+
242
+ /**
243
+ * Gets the previous properties (for property change revisions)
244
+ */
245
+ getPreviousProperties(): Record<string, any> | undefined {
246
+ return this.previousProperties;
247
+ }
248
+
249
+ /**
250
+ * Gets the new properties (for property change revisions)
251
+ */
252
+ getNewProperties(): Record<string, any> | undefined {
253
+ return this.newProperties;
254
+ }
255
+
256
+ /**
257
+ * Gets the move ID (for moveFrom/moveTo operations)
258
+ */
259
+ getMoveId(): string | undefined {
260
+ return this.moveId;
261
+ }
262
+
263
+ /**
264
+ * Gets the move location
265
+ */
266
+ getMoveLocation(): string | undefined {
267
+ return this.moveLocation;
268
+ }
269
+
270
+ /**
271
+ * Gets the location of this revision within the document
272
+ * @returns Location information or undefined if not set
273
+ */
274
+ getLocation(): RevisionLocation | undefined {
275
+ return this.location;
276
+ }
277
+
278
+ /**
279
+ * Sets the location of this revision within the document
280
+ * @param location - Location information
281
+ * @returns This revision for chaining
282
+ */
283
+ setLocation(location: RevisionLocation): this {
284
+ this.location = location;
285
+ return this;
286
+ }
287
+
288
+ /**
289
+ * Gets the field context if this revision is inside a complex field
290
+ * @returns Field context information or undefined if not inside a field
291
+ */
292
+ getFieldContext(): FieldContext | undefined {
293
+ return this.fieldContext;
294
+ }
295
+
296
+ /**
297
+ * Sets the field context for this revision
298
+ * @param context - Field context information
299
+ * @returns This revision for chaining
300
+ */
301
+ setFieldContext(context: FieldContext): this {
302
+ this.fieldContext = context;
303
+ return this;
304
+ }
305
+
306
+ /**
307
+ * Checks if this revision is inside a complex field
308
+ * @returns True if the revision is inside a field result or instruction section
309
+ */
310
+ isInsideField(): boolean {
311
+ return this.fieldContext !== undefined;
312
+ }
313
+
314
+ /**
315
+ * Checks if this revision is inside a field result section
316
+ * @returns True if the revision is in the result section of a complex field
317
+ */
318
+ isInsideFieldResult(): boolean {
319
+ return this.fieldContext?.position === 'result';
320
+ }
321
+
322
+ /**
323
+ * Checks if this revision is inside a field instruction section
324
+ * @returns True if the revision is in the instruction section of a complex field
325
+ */
326
+ isInsideFieldInstruction(): boolean {
327
+ return this.fieldContext?.position === 'instruction';
328
+ }
329
+
330
+ /**
331
+ * Gets the parent field if this revision is inside a complex field
332
+ * @returns The parent ComplexField or undefined
333
+ */
334
+ getParentField(): import('./Field').ComplexField | undefined {
335
+ return this.fieldContext?.field;
336
+ }
337
+
338
+ /**
339
+ * Marks this revision as a field instruction deletion
340
+ * When true, uses w:delInstrText instead of w:delText
341
+ */
342
+ setAsFieldInstruction(): this {
343
+ this.isFieldInstruction = true;
344
+ return this;
345
+ }
346
+
347
+ /**
348
+ * Checks if this is a field instruction deletion
349
+ */
350
+ isFieldInstructionDeletion(): boolean {
351
+ return this.isFieldInstruction;
352
+ }
353
+
354
+ /**
355
+ * Formats a date to ISO 8601 format for XML
356
+ * Per ECMA-376, revision dates must be in ISO 8601 format (e.g., "2024-01-01T12:00:00Z")
357
+ * Uses formatDateForXml() to strip milliseconds which Word does not accept.
358
+ * @param date - Date to format
359
+ * @returns ISO 8601 formatted date string without milliseconds
360
+ */
361
+ private formatDate(date: Date): string {
362
+ return formatDateForXml(date);
363
+ }
364
+
365
+ /**
366
+ * Gets the XML element name for this revision type
367
+ * Maps internal revision types to OOXML WordprocessingML element names per ECMA-376
368
+ *
369
+ * Mappings:
370
+ * - insert → w:ins (inserted content)
371
+ * - delete → w:del (deleted content)
372
+ * - runPropertiesChange → w:rPrChange (run formatting change)
373
+ * - paragraphPropertiesChange → w:pPrChange (paragraph formatting change)
374
+ * - tablePropertiesChange → w:tblPrChange (table formatting change)
375
+ * - tableRowPropertiesChange → w:trPrChange (table row properties change)
376
+ * - tableCellPropertiesChange → w:tcPrChange (table cell properties change)
377
+ * - sectionPropertiesChange → w:sectPrChange (section properties change)
378
+ * - moveFrom → w:moveFrom (source location of moved content)
379
+ * - moveTo → w:moveTo (destination location of moved content)
380
+ * - tableCellInsert → w:cellIns (inserted table cell)
381
+ * - tableCellDelete → w:cellDel (deleted table cell)
382
+ * - tableCellMerge w:cellMerge (merged table cells)
383
+ * - numberingChange → w:numberingChange (list numbering changed)
384
+ *
385
+ * @returns OOXML element name (e.g., "w:ins", "w:del")
386
+ */
387
+ private getElementName(): string {
388
+ switch (this.type) {
389
+ case 'insert':
390
+ return 'w:ins';
391
+ case 'delete':
392
+ return 'w:del';
393
+ case 'runPropertiesChange':
394
+ return 'w:rPrChange';
395
+ case 'paragraphPropertiesChange':
396
+ return 'w:pPrChange';
397
+ case 'tablePropertiesChange':
398
+ return 'w:tblPrChange';
399
+ case 'tableExceptionPropertiesChange':
400
+ return 'w:tblPrExChange';
401
+ case 'tableRowPropertiesChange':
402
+ return 'w:trPrChange';
403
+ case 'tableCellPropertiesChange':
404
+ return 'w:tcPrChange';
405
+ case 'sectionPropertiesChange':
406
+ return 'w:sectPrChange';
407
+ case 'moveFrom':
408
+ return 'w:moveFrom';
409
+ case 'moveTo':
410
+ return 'w:moveTo';
411
+ case 'tableCellInsert':
412
+ return 'w:cellIns';
413
+ case 'tableCellDelete':
414
+ return 'w:cellDel';
415
+ case 'tableCellMerge':
416
+ return 'w:cellMerge';
417
+ case 'numberingChange':
418
+ return 'w:numberingChange';
419
+ // Internal tracking types - no OOXML element equivalent
420
+ // These are used for changelog generation and internal tracking,
421
+ // not for XML serialization. OOXML tracks these as insert/delete pairs.
422
+ case 'hyperlinkChange':
423
+ case 'imageChange':
424
+ case 'fieldChange':
425
+ case 'commentChange':
426
+ case 'bookmarkChange':
427
+ case 'contentControlChange':
428
+ throw new Error(
429
+ `Revision type '${this.type}' is an internal tracking type and cannot be serialized to OOXML XML. ` +
430
+ `OOXML does not have a native element for this type. ` +
431
+ `Use insert/delete revision pairs for tracking changes to ${this.type.replace('Change', '')}s.`
432
+ );
433
+ default:
434
+ // TypeScript exhaustiveness check - this should never be reached
435
+ // if all RevisionType values are handled above
436
+ const _exhaustiveCheck: never = this.type;
437
+ throw new Error(`Unknown revision type: ${_exhaustiveCheck}`);
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Generates XML for this revision per OOXML WordprocessingML specification (ECMA-376)
443
+ *
444
+ * **XML Structure:**
445
+ *
446
+ * Content revisions (w:ins, w:del, w:moveFrom, w:moveTo):
447
+ * ```xml
448
+ * <w:ins w:id="0" w:author="Author Name" w:date="2024-01-01T12:00:00Z">
449
+ * <w:r>
450
+ * <w:t>Inserted text</w:t>
451
+ * </w:r>
452
+ * </w:ins>
453
+ * ```
454
+ *
455
+ * Deletion revisions use w:delText instead of w:t:
456
+ * ```xml
457
+ * <w:del w:id="1" w:author="Author Name" w:date="2024-01-01T12:00:00Z">
458
+ * <w:r>
459
+ * <w:delText>Deleted text</w:delText>
460
+ * </w:r>
461
+ * </w:del>
462
+ * ```
463
+ *
464
+ * Property change revisions (w:rPrChange, w:pPrChange, etc.):
465
+ * ```xml
466
+ * <w:rPrChange w:id="2" w:author="Author Name" w:date="2024-01-01T12:00:00Z">
467
+ * <w:rPr>
468
+ * <w:b/> <!-- Previous bold setting -->
469
+ * <w:sz w:val="24"/> <!-- Previous font size -->
470
+ * </w:rPr>
471
+ * </w:rPrChange>
472
+ * ```
473
+ *
474
+ * **Required Attributes (per ECMA-376):**
475
+ * - w:id: Unique revision identifier (ST_DecimalNumber) - REQUIRED
476
+ * - w:author: Author who made the change (ST_String) - REQUIRED
477
+ * - w:date: When the change was made (ST_DateTime, ISO 8601) - OPTIONAL
478
+ *
479
+ * **Move Operations:**
480
+ * For moveFrom/moveTo, an additional w:moveId attribute links the source and destination:
481
+ * ```xml
482
+ * <w:moveFrom w:id="3" w:author="Author" w:date="..." w:moveId="move-1">...</w:moveFrom>
483
+ * <w:moveTo w:id="4" w:author="Author" w:date="..." w:moveId="move-1">...</w:moveTo>
484
+ * ```
485
+ *
486
+ * **Content vs Property Changes:**
487
+ * - Content revisions (insert/delete/move): Contain w:r elements with text runs
488
+ * - Property revisions (rPrChange/pPrChange): Contain previous property elements (w:rPr, w:pPr)
489
+ *
490
+ * @returns XMLElement representing the revision in OOXML format, or null for internal-only types
491
+ * @see ECMA-376 Part 1 §17.13.5 (Revision Identifiers for Paragraph Content)
492
+ * @see ECMA-376 Part 1 §17.13.5.15 (Inserted Paragraph)
493
+ * @see ECMA-376 Part 1 §17.13.5.14 (Deleted Paragraph)
494
+ */
495
+ toXML(): XMLElement | null {
496
+ // Internal tracking types have no OOXML equivalent and cannot be serialized
497
+ // They are used for changelog generation and internal tracking only
498
+ const INTERNAL_TRACKING_TYPES: RevisionType[] = [
499
+ 'hyperlinkChange',
500
+ 'imageChange',
501
+ 'fieldChange',
502
+ 'commentChange',
503
+ 'bookmarkChange',
504
+ 'contentControlChange',
505
+ ];
506
+
507
+ if (INTERNAL_TRACKING_TYPES.includes(this.type)) {
508
+ // Return null for internal types - callers should skip these
509
+ return null;
510
+ }
511
+
512
+ const attributes: Record<string, string> = {
513
+ 'w:id': this.id.toString(),
514
+ 'w:author': this.author,
515
+ 'w:date': this.formatDate(this.date),
516
+ };
517
+
518
+ // Add move-specific attributes
519
+ if ((this.type === 'moveFrom' || this.type === 'moveTo') && this.moveId) {
520
+ attributes['w:moveId'] = this.moveId;
521
+ }
522
+
523
+ const elementName = this.getElementName();
524
+ const children: XMLElement[] = [];
525
+
526
+ // Handle different revision types
527
+ if (this.isPropertyChangeType()) {
528
+ // Property change revisions contain the previous properties
529
+ if (this.previousProperties) {
530
+ children.push(this.createPropertiesElement());
531
+ }
532
+ }
533
+
534
+ // Check if content contains only a single hyperlink (needs nesting inversion per ECMA-376)
535
+ // w:hyperlink is NOT a valid child of w:ins/w:del; instead w:ins/w:del must be inside w:hyperlink
536
+ const firstItem = this.content[0];
537
+ const singleHyperlink =
538
+ this.content.length === 1 && firstItem && isHyperlinkContent(firstItem) ? firstItem : null;
539
+
540
+ if (singleHyperlink && !this.isPropertyChangeType()) {
541
+ return this.createHyperlinkWrappedRevisionXml(singleHyperlink, elementName, attributes);
542
+ }
543
+
544
+ // Add content to the revision (handles both Run and Hyperlink)
545
+ for (const item of this.content) {
546
+ if (isHyperlinkContent(item)) {
547
+ // For multiple-item revisions containing hyperlinks, extract the runs
548
+ // and add them directly (hyperlink wrapper omitted to maintain validity)
549
+ const hyperlinkXml = item.toXML();
550
+ if (hyperlinkXml.children) {
551
+ for (const child of hyperlinkXml.children) {
552
+ if (typeof child === 'object' && child.name === 'w:r') {
553
+ if (this.type === 'delete' || this.type === 'moveFrom') {
554
+ children.push(this.convertRunXmlToDeleted(child));
555
+ } else {
556
+ children.push(child);
557
+ }
558
+ }
559
+ }
560
+ }
561
+ } else if (isRunContent(item)) {
562
+ // Handle Run content (existing behavior)
563
+ if (this.type === 'delete' || this.type === 'moveFrom') {
564
+ // For deletions and moveFrom, we need to modify the run XML to use w:delText instead of w:t
565
+ const runXml = this.createDeletedRunXml(item);
566
+ children.push(runXml);
567
+ } else {
568
+ // For other types, use normal run XML
569
+ children.push(item.toXML());
570
+ }
571
+ }
572
+ }
573
+
574
+ return {
575
+ name: elementName,
576
+ attributes,
577
+ children,
578
+ };
579
+ }
580
+
581
+ /**
582
+ * Checks if this is a property change revision type
583
+ *
584
+ * Property change revisions track formatting changes, not content changes.
585
+ * They contain previous property elements (w:rPr, w:pPr, etc.) instead of text runs.
586
+ *
587
+ * **Property Change Types:**
588
+ * - runPropertiesChange: Run formatting (bold, italic, font, color, etc.)
589
+ * - paragraphPropertiesChange: Paragraph formatting (alignment, spacing, indentation, etc.)
590
+ * - tablePropertiesChange: Table formatting
591
+ * - tableRowPropertiesChange: Table row properties
592
+ * - tableCellPropertiesChange: Table cell properties
593
+ * - sectionPropertiesChange: Section properties (page size, margins, etc.)
594
+ * - numberingChange: List numbering properties
595
+ *
596
+ * **Content Change Types (NOT property changes):**
597
+ * - insert: Added text
598
+ * - delete: Removed text
599
+ * - moveFrom: Moved text source
600
+ * - moveTo: Moved text destination
601
+ * - tableCellInsert: Added table cell
602
+ * - tableCellDelete: Removed table cell
603
+ * - tableCellMerge: Merged table cells
604
+ *
605
+ * @returns true if this revision tracks a property/formatting change, false otherwise
606
+ */
607
+ private isPropertyChangeType(): boolean {
608
+ return [
609
+ 'runPropertiesChange',
610
+ 'paragraphPropertiesChange',
611
+ 'tablePropertiesChange',
612
+ 'tableExceptionPropertiesChange',
613
+ 'tableRowPropertiesChange',
614
+ 'tableCellPropertiesChange',
615
+ 'sectionPropertiesChange',
616
+ 'numberingChange',
617
+ ].includes(this.type);
618
+ }
619
+
620
+ /**
621
+ * Creates XML element for previous properties in property change revisions
622
+ *
623
+ * **Purpose:**
624
+ * Property change revisions (w:rPrChange, w:pPrChange, etc.) must contain a child element
625
+ * with the PREVIOUS state of the properties before the change. This allows Word to show
626
+ * what changed and enables accepting/rejecting the change.
627
+ *
628
+ * **Structure:**
629
+ * ```xml
630
+ * <w:rPrChange w:id="0" w:author="Author" w:date="...">
631
+ * <w:rPr>
632
+ * <!-- Previous run properties -->
633
+ * <w:b/> <!-- Was bold -->
634
+ * <w:sz w:val="24"/> <!-- Was 12pt (24 half-points) -->
635
+ * </w:rPr>
636
+ * </w:rPrChange>
637
+ * ```
638
+ *
639
+ * **Property Element Mapping:**
640
+ * - runPropertiesChange → w:rPr (run properties)
641
+ * - paragraphPropertiesChange → w:pPr (paragraph properties)
642
+ * - tablePropertiesChange → w:tblPr (table properties)
643
+ * - tableRowPropertiesChange → w:trPr (table row properties)
644
+ * - tableCellPropertiesChange → w:tcPr (table cell properties)
645
+ * - sectionPropertiesChange → w:sectPr (section properties)
646
+ * - numberingChange w:numPr (numbering properties)
647
+ *
648
+ * **Implementation:**
649
+ * This method converts the previousProperties object into OOXML elements.
650
+ * - Boolean properties (e.g., bold) → <w:b/>
651
+ * - Value properties (e.g., font size) → <w:sz w:val="24"/>
652
+ *
653
+ * @returns XMLElement containing previous properties (w:rPr, w:pPr, etc.)
654
+ * @see ECMA-376 Part 1 §17.13.5.31 (Run Properties Change)
655
+ * @see ECMA-376 Part 1 §17.13.5.29 (Paragraph Properties Change)
656
+ */
657
+ private createPropertiesElement(): XMLElement {
658
+ // For runPropertiesChange, delegate to Run.generateRunPropertiesXML for correct
659
+ // ECMA-376 element names and ordering (e.g., bold→w:b, font→w:rFonts, size→w:sz)
660
+ if (this.type === 'runPropertiesChange' && this.previousProperties) {
661
+ const rPr = Run.generateRunPropertiesXML(this.previousProperties as RunFormatting);
662
+ return rPr || { name: 'w:rPr', attributes: {}, children: [] };
663
+ }
664
+
665
+ // The property element name depends on the revision type
666
+ let propElementName = 'w:rPr';
667
+
668
+ switch (this.type) {
669
+ case 'runPropertiesChange':
670
+ propElementName = 'w:rPr';
671
+ break;
672
+ case 'paragraphPropertiesChange':
673
+ propElementName = 'w:pPr';
674
+ break;
675
+ case 'tablePropertiesChange':
676
+ propElementName = 'w:tblPr';
677
+ break;
678
+ case 'tableExceptionPropertiesChange':
679
+ propElementName = 'w:tblPrEx';
680
+ break;
681
+ case 'tableRowPropertiesChange':
682
+ propElementName = 'w:trPr';
683
+ break;
684
+ case 'tableCellPropertiesChange':
685
+ propElementName = 'w:tcPr';
686
+ break;
687
+ case 'sectionPropertiesChange':
688
+ propElementName = 'w:sectPr';
689
+ break;
690
+ case 'numberingChange':
691
+ propElementName = 'w:numPr';
692
+ break;
693
+ }
694
+
695
+ // Build property children from previousProperties
696
+ const propChildren: XMLElement[] = [];
697
+ if (this.previousProperties) {
698
+ for (const [key, value] of Object.entries(this.previousProperties)) {
699
+ if (typeof value === 'boolean' && value) {
700
+ // Boolean properties (e.g., bold, italic)
701
+ propChildren.push({ name: `w:${key}`, attributes: {}, children: [] });
702
+ } else if (typeof value === 'string' || typeof value === 'number') {
703
+ // Value properties (e.g., font size, color)
704
+ propChildren.push({
705
+ name: `w:${key}`,
706
+ attributes: { 'w:val': value.toString() },
707
+ children: [],
708
+ });
709
+ }
710
+ }
711
+ }
712
+
713
+ return {
714
+ name: propElementName,
715
+ attributes: {},
716
+ children: propChildren,
717
+ };
718
+ }
719
+
720
+ /**
721
+ * Creates XML for a deleted run (uses w:delText or w:delInstrText instead of w:t)
722
+ *
723
+ * **OOXML Requirement:**
724
+ * Per ECMA-376, deleted text must use w:delText element instead of w:t element.
725
+ * For deleted field instructions, w:delInstrText must be used instead.
726
+ * This is required for proper rendering in Microsoft Word's Track Changes mode.
727
+ *
728
+ * **Transformation:**
729
+ * ```xml
730
+ * <!-- Normal run (NOT in deletion) -->
731
+ * <w:r>
732
+ * <w:rPr><w:b/></w:rPr>
733
+ * <w:t>Text</w:t>
734
+ * </w:r>
735
+ *
736
+ * <!-- Deleted run (inside w:del) -->
737
+ * <w:r>
738
+ * <w:rPr><w:b/></w:rPr>
739
+ * <w:delText>Text</w:delText>
740
+ * </w:r>
741
+ *
742
+ * <!-- Deleted field instruction (inside w:del) -->
743
+ * <w:r>
744
+ * <w:delInstrText>MERGEFIELD Name</w:delInstrText>
745
+ * </w:r>
746
+ * ```
747
+ *
748
+ * **Why This Matters:**
749
+ * - w:delText tells Word to render with strikethrough in Track Changes mode
750
+ * - w:delInstrText is specifically for deleted field codes
751
+ * - w:t would render as normal text even inside w:del element
752
+ * - Word will reject documents with w:t inside deletions as malformed
753
+ *
754
+ * **Implementation:**
755
+ * This method gets the run's normal XML and replaces all w:t elements with w:delText
756
+ * or w:delInstrText (for field instructions) while preserving all other properties
757
+ * (formatting, spacing attributes, etc.)
758
+ *
759
+ * @param run - Run containing deleted text
760
+ * @returns XMLElement with w:delText or w:delInstrText instead of w:t
761
+ * @see ECMA-376 Part 1 §17.13.5.14 (Deleted Run Content)
762
+ * @see ECMA-376 Part 1 §22.1.2.27 (w:delText element)
763
+ * @see ECMA-376 Part 1 §22.1.2.26 (w:delInstrText element)
764
+ */
765
+ private createDeletedRunXml(run: Run): XMLElement {
766
+ // Get the regular run XML
767
+ const runXml = run.toXML();
768
+
769
+ // Determine which element to use for deleted text
770
+ // w:delInstrText for field instructions, w:delText for regular text
771
+ const deletedTextElement = this.isFieldInstruction ? 'w:delInstrText' : 'w:delText';
772
+
773
+ // We need to replace text elements with their deleted counterparts:
774
+ // - w:t -> w:delText (or w:delInstrText if isFieldInstruction)
775
+ // - w:instrText -> w:delInstrText (always, regardless of isFieldInstruction flag)
776
+ if (runXml.children) {
777
+ const modifiedChildren = runXml.children.map((child) => {
778
+ if (typeof child === 'object') {
779
+ if (child.name === 'w:t') {
780
+ // Replace w:t with appropriate deleted text element
781
+ return {
782
+ ...child,
783
+ name: deletedTextElement,
784
+ };
785
+ } else if (child.name === 'w:instrText') {
786
+ // Replace w:instrText with w:delInstrText
787
+ // Per ECMA-376 §22.1.2.26, deleted field instructions must use w:delInstrText
788
+ return {
789
+ ...child,
790
+ name: 'w:delInstrText',
791
+ };
792
+ }
793
+ }
794
+ return child;
795
+ });
796
+
797
+ return {
798
+ ...runXml,
799
+ children: modifiedChildren,
800
+ };
801
+ }
802
+
803
+ return runXml;
804
+ }
805
+
806
+ /**
807
+ * Creates XML for a deleted hyperlink (transforms nested runs to use w:delText)
808
+ *
809
+ * **OOXML Requirement:**
810
+ * Per ECMA-376, when a hyperlink is inside a w:del element, its nested runs must
811
+ * use w:delText instead of w:t. This transforms the hyperlink's internal run
812
+ * content to comply with Word's Track Changes requirements.
813
+ *
814
+ * **XML Structure:**
815
+ * ```xml
816
+ * <w:del w:id="1" w:author="Author" w:date="...">
817
+ * <w:hyperlink r:id="rId1">
818
+ * <w:r>
819
+ * <w:delText>Link text</w:delText> <!-- Transformed from w:t -->
820
+ * </w:r>
821
+ * </w:hyperlink>
822
+ * </w:del>
823
+ * ```
824
+ *
825
+ * @param hyperlink - Hyperlink containing deleted content
826
+ * @returns XMLElement with nested runs transformed to use w:delText
827
+ */
828
+ /**
829
+ * Creates a hyperlink-wrapped revision XML element.
830
+ * Per ECMA-376, w:hyperlink is NOT a valid child of w:ins/w:del.
831
+ * Instead, w:ins/w:del must be inside w:hyperlink:
832
+ * <w:hyperlink r:id="rId1"><w:ins ...><w:r>...</w:r></w:ins></w:hyperlink>
833
+ */
834
+ private createHyperlinkWrappedRevisionXml(
835
+ hyperlink: import('./Hyperlink').Hyperlink,
836
+ revisionElementName: string,
837
+ revisionAttributes: Record<string, string>
838
+ ): XMLElement {
839
+ const hyperlinkXml = hyperlink.toXML();
840
+
841
+ // Extract runs from the hyperlink
842
+ const runs: XMLElement[] = [];
843
+ if (hyperlinkXml.children) {
844
+ for (const child of hyperlinkXml.children) {
845
+ if (typeof child === 'object' && child.name === 'w:r') {
846
+ if (this.type === 'delete' || this.type === 'moveFrom') {
847
+ runs.push(this.convertRunXmlToDeleted(child));
848
+ } else {
849
+ runs.push(child);
850
+ }
851
+ }
852
+ }
853
+ }
854
+
855
+ // Build: <w:hyperlink ...><w:ins/w:del ...><w:r>...</w:r></w:ins/w:del></w:hyperlink>
856
+ const revisionElement: XMLElement = {
857
+ name: revisionElementName,
858
+ attributes: revisionAttributes,
859
+ children: runs,
860
+ };
861
+
862
+ return {
863
+ name: hyperlinkXml.name,
864
+ attributes: hyperlinkXml.attributes,
865
+ children: [revisionElement],
866
+ };
867
+ }
868
+
869
+ private createDeletedHyperlinkXml(hyperlink: import('./Hyperlink').Hyperlink): XMLElement {
870
+ // Get the hyperlink's normal XML
871
+ const hyperlinkXml = hyperlink.toXML();
872
+
873
+ // Transform nested runs: w:t -> w:delText (or w:delInstrText for field instructions)
874
+ if (hyperlinkXml.children) {
875
+ hyperlinkXml.children = hyperlinkXml.children.map((child) => {
876
+ if (typeof child === 'object' && child.name === 'w:r') {
877
+ // Transform the run's text elements
878
+ return this.convertRunXmlToDeleted(child);
879
+ }
880
+ return child;
881
+ });
882
+ }
883
+
884
+ return hyperlinkXml;
885
+ }
886
+
887
+ /**
888
+ * Converts a run XMLElement to use deleted text elements
889
+ * Helper for createDeletedHyperlinkXml
890
+ */
891
+ private convertRunXmlToDeleted(runXml: XMLElement): XMLElement {
892
+ const deletedTextElement = this.isFieldInstruction ? 'w:delInstrText' : 'w:delText';
893
+
894
+ if (!runXml.children) return runXml;
895
+
896
+ const modifiedChildren = runXml.children.map((child) => {
897
+ if (typeof child === 'object' && child.name === 'w:t') {
898
+ return {
899
+ ...child,
900
+ name: deletedTextElement,
901
+ };
902
+ }
903
+ return child;
904
+ });
905
+
906
+ return {
907
+ ...runXml,
908
+ children: modifiedChildren,
909
+ };
910
+ }
911
+
912
+ /**
913
+ * Creates an insertion revision
914
+ * @param author - Author who made the insertion
915
+ * @param content - Inserted content (Run, Hyperlink, or arrays thereof)
916
+ * @param date - Optional date (defaults to now)
917
+ * @returns New Revision instance
918
+ */
919
+ static createInsertion(
920
+ author: string,
921
+ content: RevisionContent | RevisionContent[],
922
+ date?: Date
923
+ ): Revision {
924
+ return new Revision({
925
+ author,
926
+ type: 'insert',
927
+ content,
928
+ date,
929
+ });
930
+ }
931
+
932
+ /**
933
+ * Creates a deletion revision
934
+ * @param author - Author who made the deletion
935
+ * @param content - Deleted content (Run, Hyperlink, or arrays thereof)
936
+ * @param date - Optional date (defaults to now)
937
+ * @returns New Revision instance
938
+ */
939
+ static createDeletion(
940
+ author: string,
941
+ content: RevisionContent | RevisionContent[],
942
+ date?: Date
943
+ ): Revision {
944
+ return new Revision({
945
+ author,
946
+ type: 'delete',
947
+ content,
948
+ date,
949
+ });
950
+ }
951
+
952
+ /**
953
+ * Creates a field instruction deletion revision
954
+ * Uses w:delInstrText instead of w:delText for field codes
955
+ * @param author - Author who made the deletion
956
+ * @param content - Deleted field instruction content (Run or array of Runs)
957
+ * @param date - Optional date (defaults to now)
958
+ * @returns New Revision instance
959
+ */
960
+ static createFieldInstructionDeletion(
961
+ author: string,
962
+ content: Run | Run[],
963
+ date?: Date
964
+ ): Revision {
965
+ const revision = new Revision({
966
+ author,
967
+ type: 'delete',
968
+ content,
969
+ date,
970
+ });
971
+ revision.setAsFieldInstruction();
972
+ return revision;
973
+ }
974
+
975
+ /**
976
+ * Creates a revision from text
977
+ * Convenience method that creates a Run from the text
978
+ * @param type - Revision type
979
+ * @param author - Author who made the change
980
+ * @param text - Text content
981
+ * @param date - Optional date (defaults to now)
982
+ * @returns New Revision instance
983
+ */
984
+ static fromText(type: RevisionType, author: string, text: string, date?: Date): Revision {
985
+ const run = new Run(text);
986
+ return new Revision({
987
+ author,
988
+ type,
989
+ content: run,
990
+ date,
991
+ });
992
+ }
993
+
994
+ /**
995
+ * Creates a run properties change revision
996
+ * @param author - Author who made the change
997
+ * @param content - Content with changed formatting
998
+ * @param previousProperties - Previous run properties
999
+ * @param date - Optional date (defaults to now)
1000
+ * @returns New Revision instance
1001
+ */
1002
+ static createRunPropertiesChange(
1003
+ author: string,
1004
+ content: Run | Run[],
1005
+ previousProperties: Record<string, any>,
1006
+ date?: Date
1007
+ ): Revision {
1008
+ return new Revision({
1009
+ author,
1010
+ type: 'runPropertiesChange',
1011
+ content,
1012
+ previousProperties,
1013
+ date,
1014
+ });
1015
+ }
1016
+
1017
+ /**
1018
+ * Creates a paragraph properties change revision
1019
+ * @param author - Author who made the change
1020
+ * @param content - Paragraph content
1021
+ * @param previousProperties - Previous paragraph properties
1022
+ * @param date - Optional date (defaults to now)
1023
+ * @returns New Revision instance
1024
+ */
1025
+ static createParagraphPropertiesChange(
1026
+ author: string,
1027
+ content: Run | Run[],
1028
+ previousProperties: Record<string, any>,
1029
+ date?: Date
1030
+ ): Revision {
1031
+ return new Revision({
1032
+ author,
1033
+ type: 'paragraphPropertiesChange',
1034
+ content,
1035
+ previousProperties,
1036
+ date,
1037
+ });
1038
+ }
1039
+
1040
+ /**
1041
+ * Creates a table properties change revision
1042
+ * @param author - Author who made the change
1043
+ * @param content - Table content
1044
+ * @param previousProperties - Previous table properties
1045
+ * @param date - Optional date (defaults to now)
1046
+ * @returns New Revision instance
1047
+ */
1048
+ static createTablePropertiesChange(
1049
+ author: string,
1050
+ content: Run | Run[],
1051
+ previousProperties: Record<string, any>,
1052
+ date?: Date
1053
+ ): Revision {
1054
+ return new Revision({
1055
+ author,
1056
+ type: 'tablePropertiesChange',
1057
+ content,
1058
+ previousProperties,
1059
+ date,
1060
+ });
1061
+ }
1062
+
1063
+ /**
1064
+ * Creates a table exception properties change revision
1065
+ * Tracks changes to table properties that override style defaults
1066
+ * @param author - Author who made the change
1067
+ * @param content - Table content
1068
+ * @param previousProperties - Previous table exception properties
1069
+ * @param date - Optional date (defaults to now)
1070
+ * @returns New Revision instance
1071
+ */
1072
+ static createTableExceptionPropertiesChange(
1073
+ author: string,
1074
+ content: Run | Run[],
1075
+ previousProperties: Record<string, any>,
1076
+ date?: Date
1077
+ ): Revision {
1078
+ return new Revision({
1079
+ author,
1080
+ type: 'tableExceptionPropertiesChange',
1081
+ content,
1082
+ previousProperties,
1083
+ date,
1084
+ });
1085
+ }
1086
+
1087
+ /**
1088
+ * Creates a moveFrom revision (source of moved content)
1089
+ * @param author - Author who moved the content
1090
+ * @param content - Content that was moved
1091
+ * @param moveId - Unique move operation ID (links moveFrom and moveTo)
1092
+ * @param date - Optional date (defaults to now)
1093
+ * @returns New Revision instance
1094
+ */
1095
+ static createMoveFrom(
1096
+ author: string,
1097
+ content: Run | Run[],
1098
+ moveId: string,
1099
+ date?: Date
1100
+ ): Revision {
1101
+ return new Revision({
1102
+ author,
1103
+ type: 'moveFrom',
1104
+ content,
1105
+ moveId,
1106
+ date,
1107
+ });
1108
+ }
1109
+
1110
+ /**
1111
+ * Creates a moveTo revision (destination of moved content)
1112
+ * @param author - Author who moved the content
1113
+ * @param content - Content that was moved
1114
+ * @param moveId - Unique move operation ID (links moveFrom and moveTo)
1115
+ * @param date - Optional date (defaults to now)
1116
+ * @returns New Revision instance
1117
+ */
1118
+ static createMoveTo(author: string, content: Run | Run[], moveId: string, date?: Date): Revision {
1119
+ return new Revision({
1120
+ author,
1121
+ type: 'moveTo',
1122
+ content,
1123
+ moveId,
1124
+ date,
1125
+ });
1126
+ }
1127
+
1128
+ /**
1129
+ * Creates a table cell insertion revision
1130
+ * @param author - Author who inserted the cell
1131
+ * @param content - Cell content
1132
+ * @param date - Optional date (defaults to now)
1133
+ * @returns New Revision instance
1134
+ */
1135
+ static createTableCellInsert(author: string, content: Run | Run[], date?: Date): Revision {
1136
+ return new Revision({
1137
+ author,
1138
+ type: 'tableCellInsert',
1139
+ content,
1140
+ date,
1141
+ });
1142
+ }
1143
+
1144
+ /**
1145
+ * Creates a table cell deletion revision
1146
+ * @param author - Author who deleted the cell
1147
+ * @param content - Cell content
1148
+ * @param date - Optional date (defaults to now)
1149
+ * @returns New Revision instance
1150
+ */
1151
+ static createTableCellDelete(author: string, content: Run | Run[], date?: Date): Revision {
1152
+ return new Revision({
1153
+ author,
1154
+ type: 'tableCellDelete',
1155
+ content,
1156
+ date,
1157
+ });
1158
+ }
1159
+
1160
+ /**
1161
+ * Creates a table cell merge revision
1162
+ * @param author - Author who merged cells
1163
+ * @param content - Merged cell content
1164
+ * @param date - Optional date (defaults to now)
1165
+ * @returns New Revision instance
1166
+ */
1167
+ static createTableCellMerge(author: string, content: Run | Run[], date?: Date): Revision {
1168
+ return new Revision({
1169
+ author,
1170
+ type: 'tableCellMerge',
1171
+ content,
1172
+ date,
1173
+ });
1174
+ }
1175
+
1176
+ /**
1177
+ * Creates a numbering change revision
1178
+ * @param author - Author who changed the numbering
1179
+ * @param content - Content with changed numbering
1180
+ * @param previousProperties - Previous numbering properties
1181
+ * @param date - Optional date (defaults to now)
1182
+ * @returns New Revision instance
1183
+ */
1184
+ static createNumberingChange(
1185
+ author: string,
1186
+ content: Run | Run[],
1187
+ previousProperties: Record<string, any>,
1188
+ date?: Date
1189
+ ): Revision {
1190
+ return new Revision({
1191
+ author,
1192
+ type: 'numberingChange',
1193
+ content,
1194
+ previousProperties,
1195
+ date,
1196
+ });
1197
+ }
1198
+ }