docxmlater 10.0.1 → 10.0.3

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 (395) hide show
  1. package/README.md +3 -2
  2. package/dist/constants/legacyCompatFlags.d.ts.map +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.d.ts +0 -27
  5. package/dist/constants/limits.d.ts.map +1 -1
  6. package/dist/constants/limits.js +13 -13
  7. package/dist/constants/limits.js.map +1 -1
  8. package/dist/core/Document.d.ts +24 -19
  9. package/dist/core/Document.d.ts.map +1 -1
  10. package/dist/core/Document.js +272 -71
  11. package/dist/core/Document.js.map +1 -1
  12. package/dist/core/DocumentContent.d.ts.map +1 -1
  13. package/dist/core/DocumentContent.js.map +1 -1
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +59 -24
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.d.ts.map +1 -1
  18. package/dist/core/DocumentIdManager.js.map +1 -1
  19. package/dist/core/DocumentParser.d.ts +6 -6
  20. package/dist/core/DocumentParser.d.ts.map +1 -1
  21. package/dist/core/DocumentParser.js +60 -54
  22. package/dist/core/DocumentParser.js.map +1 -1
  23. package/dist/core/DocumentValidator.d.ts.map +1 -1
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.d.ts.map +1 -1
  26. package/dist/core/Relationship.js +1 -1
  27. package/dist/core/Relationship.js.map +1 -1
  28. package/dist/core/RelationshipManager.js +3 -3
  29. package/dist/core/RelationshipManager.js.map +1 -1
  30. package/dist/elements/AlternateContent.js.map +1 -1
  31. package/dist/elements/Bookmark.d.ts.map +1 -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.js +1 -1
  36. package/dist/elements/Comment.js.map +1 -1
  37. package/dist/elements/CommentManager.d.ts.map +1 -1
  38. package/dist/elements/CommentManager.js +8 -2
  39. package/dist/elements/CommentManager.js.map +1 -1
  40. package/dist/elements/CommonTypes.d.ts.map +1 -1
  41. package/dist/elements/CommonTypes.js +1 -2
  42. package/dist/elements/CommonTypes.js.map +1 -1
  43. package/dist/elements/CustomXml.js.map +1 -1
  44. package/dist/elements/Endnote.d.ts.map +1 -1
  45. package/dist/elements/Endnote.js.map +1 -1
  46. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  47. package/dist/elements/EndnoteManager.js.map +1 -1
  48. package/dist/elements/Field.d.ts.map +1 -1
  49. package/dist/elements/Field.js +31 -28
  50. package/dist/elements/Field.js.map +1 -1
  51. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  52. package/dist/elements/FieldHelpers.js +6 -6
  53. package/dist/elements/FieldHelpers.js.map +1 -1
  54. package/dist/elements/FontManager.d.ts.map +1 -1
  55. package/dist/elements/FontManager.js.map +1 -1
  56. package/dist/elements/Footer.js.map +1 -1
  57. package/dist/elements/Footnote.d.ts.map +1 -1
  58. package/dist/elements/Footnote.js.map +1 -1
  59. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  60. package/dist/elements/FootnoteManager.js.map +1 -1
  61. package/dist/elements/Header.js.map +1 -1
  62. package/dist/elements/HeaderFooterManager.js.map +1 -1
  63. package/dist/elements/Hyperlink.d.ts.map +1 -1
  64. package/dist/elements/Hyperlink.js +5 -5
  65. package/dist/elements/Hyperlink.js.map +1 -1
  66. package/dist/elements/Image.d.ts +2 -2
  67. package/dist/elements/Image.d.ts.map +1 -1
  68. package/dist/elements/Image.js +21 -5
  69. package/dist/elements/Image.js.map +1 -1
  70. package/dist/elements/ImageManager.d.ts.map +1 -1
  71. package/dist/elements/ImageManager.js +2 -2
  72. package/dist/elements/ImageManager.js.map +1 -1
  73. package/dist/elements/ImageRun.js.map +1 -1
  74. package/dist/elements/MathElement.js.map +1 -1
  75. package/dist/elements/Paragraph.d.ts.map +1 -1
  76. package/dist/elements/Paragraph.js +128 -117
  77. package/dist/elements/Paragraph.js.map +1 -1
  78. package/dist/elements/PreservedElement.js.map +1 -1
  79. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  80. package/dist/elements/RangeMarker.js.map +1 -1
  81. package/dist/elements/Revision.d.ts +1 -0
  82. package/dist/elements/Revision.d.ts.map +1 -1
  83. package/dist/elements/Revision.js +44 -5
  84. package/dist/elements/Revision.js.map +1 -1
  85. package/dist/elements/RevisionContent.js.map +1 -1
  86. package/dist/elements/RevisionManager.d.ts.map +1 -1
  87. package/dist/elements/RevisionManager.js.map +1 -1
  88. package/dist/elements/Run.d.ts.map +1 -1
  89. package/dist/elements/Run.js +1 -3
  90. package/dist/elements/Run.js.map +1 -1
  91. package/dist/elements/Section.d.ts.map +1 -1
  92. package/dist/elements/Section.js +127 -118
  93. package/dist/elements/Section.js.map +1 -1
  94. package/dist/elements/Shape.d.ts.map +1 -1
  95. package/dist/elements/Shape.js +21 -0
  96. package/dist/elements/Shape.js.map +1 -1
  97. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  98. package/dist/elements/StructuredDocumentTag.js +20 -8
  99. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  100. package/dist/elements/Table.d.ts +2 -2
  101. package/dist/elements/Table.d.ts.map +1 -1
  102. package/dist/elements/Table.js +29 -35
  103. package/dist/elements/Table.js.map +1 -1
  104. package/dist/elements/TableCell.d.ts +2 -2
  105. package/dist/elements/TableCell.d.ts.map +1 -1
  106. package/dist/elements/TableCell.js +63 -67
  107. package/dist/elements/TableCell.js.map +1 -1
  108. package/dist/elements/TableGridChange.js.map +1 -1
  109. package/dist/elements/TableOfContents.d.ts +6 -6
  110. package/dist/elements/TableOfContents.d.ts.map +1 -1
  111. package/dist/elements/TableOfContents.js.map +1 -1
  112. package/dist/elements/TableOfContentsElement.js.map +1 -1
  113. package/dist/elements/TableRow.d.ts.map +1 -1
  114. package/dist/elements/TableRow.js +65 -47
  115. package/dist/elements/TableRow.js.map +1 -1
  116. package/dist/elements/TextBox.d.ts.map +1 -1
  117. package/dist/elements/TextBox.js +1 -1
  118. package/dist/elements/TextBox.js.map +1 -1
  119. package/dist/formatting/AbstractNumbering.d.ts +1 -1
  120. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  121. package/dist/formatting/AbstractNumbering.js +11 -11
  122. package/dist/formatting/AbstractNumbering.js.map +1 -1
  123. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  124. package/dist/formatting/NumberingInstance.js +4 -4
  125. package/dist/formatting/NumberingInstance.js.map +1 -1
  126. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  127. package/dist/formatting/NumberingLevel.js +26 -26
  128. package/dist/formatting/NumberingLevel.js.map +1 -1
  129. package/dist/formatting/NumberingManager.d.ts +1 -1
  130. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  131. package/dist/formatting/NumberingManager.js.map +1 -1
  132. package/dist/formatting/Style.d.ts.map +1 -1
  133. package/dist/formatting/Style.js +87 -95
  134. package/dist/formatting/Style.js.map +1 -1
  135. package/dist/formatting/StylesManager.d.ts +3 -3
  136. package/dist/formatting/StylesManager.d.ts.map +1 -1
  137. package/dist/formatting/StylesManager.js.map +1 -1
  138. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  139. package/dist/helpers/CleanupHelper.js +1 -7
  140. package/dist/helpers/CleanupHelper.js.map +1 -1
  141. package/dist/images/ImageOptimizer.js.map +1 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/managers/DrawingManager.d.ts.map +1 -1
  144. package/dist/managers/DrawingManager.js.map +1 -1
  145. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  146. package/dist/tracking/TrackingContext.js.map +1 -1
  147. package/dist/types/compatibility-types.js.map +1 -1
  148. package/dist/types/formatting.js.map +1 -1
  149. package/dist/types/list-types.d.ts +4 -4
  150. package/dist/types/list-types.d.ts.map +1 -1
  151. package/dist/types/list-types.js.map +1 -1
  152. package/dist/types/settings-types.js.map +1 -1
  153. package/dist/types/styleConfig.js.map +1 -1
  154. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  155. package/dist/utils/ChangelogGenerator.js.map +1 -1
  156. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  157. package/dist/utils/CompatibilityUpgrader.js +7 -7
  158. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  159. package/dist/utils/InMemoryRevisionAcceptor.js +1 -1
  160. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  161. package/dist/utils/MoveOperationHelper.js.map +1 -1
  162. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  163. package/dist/utils/RevisionWalker.js.map +1 -1
  164. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  165. package/dist/utils/ShadingResolver.js +1 -1
  166. package/dist/utils/ShadingResolver.js.map +1 -1
  167. package/dist/utils/acceptRevisions.d.ts +0 -28
  168. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  169. package/dist/utils/acceptRevisions.js +5 -7
  170. package/dist/utils/acceptRevisions.js.map +1 -1
  171. package/dist/utils/cnfStyleDecoder.js +1 -1
  172. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  173. package/dist/utils/corruptionDetection.js.map +1 -1
  174. package/dist/utils/dateFormatting.js.map +1 -1
  175. package/dist/utils/deepClone.d.ts +0 -1
  176. package/dist/utils/deepClone.d.ts.map +1 -1
  177. package/dist/utils/deepClone.js +0 -7
  178. package/dist/utils/deepClone.js.map +1 -1
  179. package/dist/utils/diagnostics.d.ts +2 -2
  180. package/dist/utils/diagnostics.d.ts.map +1 -1
  181. package/dist/utils/diagnostics.js.map +1 -1
  182. package/dist/utils/errorHandling.js.map +1 -1
  183. package/dist/utils/formatting.js.map +1 -1
  184. package/dist/utils/list-detection.d.ts +2 -2
  185. package/dist/utils/list-detection.d.ts.map +1 -1
  186. package/dist/utils/list-detection.js +3 -3
  187. package/dist/utils/list-detection.js.map +1 -1
  188. package/dist/utils/logger.d.ts +2 -4
  189. package/dist/utils/logger.d.ts.map +1 -1
  190. package/dist/utils/logger.js +0 -2
  191. package/dist/utils/logger.js.map +1 -1
  192. package/dist/utils/parsingHelpers.js.map +1 -1
  193. package/dist/utils/stripTrackedChanges.d.ts +0 -19
  194. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  195. package/dist/utils/stripTrackedChanges.js +0 -2
  196. package/dist/utils/stripTrackedChanges.js.map +1 -1
  197. package/dist/utils/textDiff.js.map +1 -1
  198. package/dist/utils/units.js.map +1 -1
  199. package/dist/utils/validation.d.ts.map +1 -1
  200. package/dist/utils/validation.js.map +1 -1
  201. package/dist/utils/xmlSanitization.js.map +1 -1
  202. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  203. package/dist/validation/RevisionValidator.js.map +1 -1
  204. package/dist/validation/ValidationRules.js.map +1 -1
  205. package/dist/validation/index.js.map +1 -1
  206. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  207. package/dist/xml/XMLBuilder.js +10 -0
  208. package/dist/xml/XMLBuilder.js.map +1 -1
  209. package/dist/xml/XMLParser.d.ts.map +1 -1
  210. package/dist/xml/XMLParser.js +4 -5
  211. package/dist/xml/XMLParser.js.map +1 -1
  212. package/dist/zip/ZipHandler.js.map +1 -1
  213. package/dist/zip/ZipReader.js.map +1 -1
  214. package/dist/zip/ZipWriter.js.map +1 -1
  215. package/dist/zip/errors.js.map +1 -1
  216. package/dist/zip/types.js.map +1 -1
  217. package/package.json +34 -4
  218. package/src/__tests__/helper-methods.test.ts +512 -0
  219. package/src/constants/legacyCompatFlags.ts +138 -0
  220. package/src/constants/limits.ts +50 -0
  221. package/src/core/CLAUDE.md +109 -0
  222. package/src/core/Document.ts +15569 -0
  223. package/src/core/DocumentContent.ts +467 -0
  224. package/src/core/DocumentGenerator.ts +1104 -0
  225. package/src/core/DocumentIdManager.ts +158 -0
  226. package/src/core/DocumentParser.ts +10107 -0
  227. package/src/core/DocumentValidator.ts +372 -0
  228. package/src/core/Relationship.ts +367 -0
  229. package/src/core/RelationshipManager.ts +428 -0
  230. package/src/elements/AlternateContent.ts +42 -0
  231. package/src/elements/Bookmark.ts +210 -0
  232. package/src/elements/BookmarkManager.ts +250 -0
  233. package/src/elements/CLAUDE.md +126 -0
  234. package/src/elements/Comment.ts +359 -0
  235. package/src/elements/CommentManager.ts +502 -0
  236. package/src/elements/CommonTypes.ts +549 -0
  237. package/src/elements/CustomXml.ts +36 -0
  238. package/src/elements/Endnote.ts +217 -0
  239. package/src/elements/EndnoteManager.ts +249 -0
  240. package/src/elements/Field.ts +1233 -0
  241. package/src/elements/FieldHelpers.ts +333 -0
  242. package/src/elements/FontManager.ts +339 -0
  243. package/src/elements/Footer.ts +269 -0
  244. package/src/elements/Footnote.ts +217 -0
  245. package/src/elements/FootnoteManager.ts +249 -0
  246. package/src/elements/Header.ts +269 -0
  247. package/src/elements/HeaderFooterManager.ts +219 -0
  248. package/src/elements/Hyperlink.ts +1146 -0
  249. package/src/elements/Image.ts +1756 -0
  250. package/src/elements/ImageManager.ts +432 -0
  251. package/src/elements/ImageRun.ts +59 -0
  252. package/src/elements/MathElement.ts +65 -0
  253. package/src/elements/Paragraph.ts +4227 -0
  254. package/src/elements/PreservedElement.ts +53 -0
  255. package/src/elements/PropertyChangeTypes.ts +442 -0
  256. package/src/elements/RangeMarker.ts +400 -0
  257. package/src/elements/Revision.ts +1217 -0
  258. package/src/elements/RevisionContent.ts +73 -0
  259. package/src/elements/RevisionManager.ts +1070 -0
  260. package/src/elements/Run.ts +3068 -0
  261. package/src/elements/Section.ts +1421 -0
  262. package/src/elements/Shape.ts +873 -0
  263. package/src/elements/StructuredDocumentTag.ts +978 -0
  264. package/src/elements/Table.ts +2524 -0
  265. package/src/elements/TableCell.ts +1586 -0
  266. package/src/elements/TableGridChange.ts +151 -0
  267. package/src/elements/TableOfContents.ts +691 -0
  268. package/src/elements/TableOfContentsElement.ts +89 -0
  269. package/src/elements/TableRow.ts +906 -0
  270. package/src/elements/TextBox.ts +768 -0
  271. package/src/formatting/AbstractNumbering.ts +548 -0
  272. package/src/formatting/CLAUDE.md +74 -0
  273. package/src/formatting/NumberingInstance.ts +212 -0
  274. package/src/formatting/NumberingLevel.ts +1006 -0
  275. package/src/formatting/NumberingManager.ts +827 -0
  276. package/src/formatting/Style.ts +1833 -0
  277. package/src/formatting/StylesManager.ts +1005 -0
  278. package/src/helpers/CleanupHelper.ts +524 -0
  279. package/src/images/ImageOptimizer.ts +274 -0
  280. package/src/index.ts +554 -0
  281. package/src/managers/CLAUDE.md +47 -0
  282. package/src/managers/DrawingManager.ts +319 -0
  283. package/src/tracking/DocumentTrackingContext.ts +643 -0
  284. package/src/tracking/TrackingContext.ts +173 -0
  285. package/src/types/compatibility-types.ts +49 -0
  286. package/src/types/formatting.ts +210 -0
  287. package/src/types/list-types.ts +152 -0
  288. package/src/types/settings-types.ts +59 -0
  289. package/src/types/styleConfig.ts +189 -0
  290. package/src/utils/CLAUDE.md +153 -0
  291. package/src/utils/ChangelogGenerator.ts +1581 -0
  292. package/src/utils/CompatibilityUpgrader.ts +237 -0
  293. package/src/utils/InMemoryRevisionAcceptor.ts +668 -0
  294. package/src/utils/MoveOperationHelper.ts +238 -0
  295. package/src/utils/RevisionAwareProcessor.ts +526 -0
  296. package/src/utils/RevisionWalker.ts +457 -0
  297. package/src/utils/SelectiveRevisionAcceptor.ts +613 -0
  298. package/src/utils/ShadingResolver.ts +107 -0
  299. package/src/utils/acceptRevisions.ts +714 -0
  300. package/src/utils/cnfStyleDecoder.ts +217 -0
  301. package/src/utils/corruptionDetection.ts +345 -0
  302. package/src/utils/dateFormatting.ts +20 -0
  303. package/src/utils/deepClone.ts +78 -0
  304. package/src/utils/diagnostics.ts +129 -0
  305. package/src/utils/errorHandling.ts +80 -0
  306. package/src/utils/formatting.ts +213 -0
  307. package/src/utils/list-detection.ts +274 -0
  308. package/src/utils/logger.ts +404 -0
  309. package/src/utils/parsingHelpers.ts +190 -0
  310. package/src/utils/stripTrackedChanges.ts +353 -0
  311. package/src/utils/textDiff.ts +100 -0
  312. package/src/utils/units.ts +421 -0
  313. package/src/utils/validation.ts +542 -0
  314. package/src/utils/xmlSanitization.ts +182 -0
  315. package/src/validation/RevisionAutoFixer.ts +542 -0
  316. package/src/validation/RevisionValidator.ts +460 -0
  317. package/src/validation/ValidationRules.ts +338 -0
  318. package/src/validation/index.ts +30 -0
  319. package/src/xml/CLAUDE.md +65 -0
  320. package/src/xml/XMLBuilder.ts +871 -0
  321. package/src/xml/XMLParser.ts +919 -0
  322. package/src/zip/CLAUDE.md +55 -0
  323. package/src/zip/ZipHandler.ts +637 -0
  324. package/src/zip/ZipReader.ts +299 -0
  325. package/src/zip/ZipWriter.ts +390 -0
  326. package/src/zip/errors.ts +69 -0
  327. package/src/zip/types.ts +116 -0
  328. package/dist/core/ListNormalizer.d.ts +0 -23
  329. package/dist/core/ListNormalizer.d.ts.map +0 -1
  330. package/dist/core/ListNormalizer.js +0 -624
  331. package/dist/core/ListNormalizer.js.map +0 -1
  332. package/dist/images/index.d.ts +0 -2
  333. package/dist/images/index.d.ts.map +0 -1
  334. package/dist/images/index.js +0 -8
  335. package/dist/images/index.js.map +0 -1
  336. package/dist/ms-doc/cfb/CFBReader.d.ts +0 -35
  337. package/dist/ms-doc/cfb/CFBReader.d.ts.map +0 -1
  338. package/dist/ms-doc/cfb/CFBReader.js +0 -360
  339. package/dist/ms-doc/cfb/CFBReader.js.map +0 -1
  340. package/dist/ms-doc/converter/DocToDocxConverter.d.ts +0 -55
  341. package/dist/ms-doc/converter/DocToDocxConverter.d.ts.map +0 -1
  342. package/dist/ms-doc/converter/DocToDocxConverter.js +0 -324
  343. package/dist/ms-doc/converter/DocToDocxConverter.js.map +0 -1
  344. package/dist/ms-doc/fib/FIB.d.ts +0 -18
  345. package/dist/ms-doc/fib/FIB.d.ts.map +0 -1
  346. package/dist/ms-doc/fib/FIB.js +0 -342
  347. package/dist/ms-doc/fib/FIB.js.map +0 -1
  348. package/dist/ms-doc/fields/FieldParser.d.ts +0 -31
  349. package/dist/ms-doc/fields/FieldParser.d.ts.map +0 -1
  350. package/dist/ms-doc/fields/FieldParser.js +0 -266
  351. package/dist/ms-doc/fields/FieldParser.js.map +0 -1
  352. package/dist/ms-doc/images/PictureExtractor.d.ts +0 -22
  353. package/dist/ms-doc/images/PictureExtractor.d.ts.map +0 -1
  354. package/dist/ms-doc/images/PictureExtractor.js +0 -233
  355. package/dist/ms-doc/images/PictureExtractor.js.map +0 -1
  356. package/dist/ms-doc/index.d.ts +0 -20
  357. package/dist/ms-doc/index.d.ts.map +0 -1
  358. package/dist/ms-doc/index.js +0 -59
  359. package/dist/ms-doc/index.js.map +0 -1
  360. package/dist/ms-doc/properties/SPRM.d.ts +0 -210
  361. package/dist/ms-doc/properties/SPRM.d.ts.map +0 -1
  362. package/dist/ms-doc/properties/SPRM.js +0 -633
  363. package/dist/ms-doc/properties/SPRM.js.map +0 -1
  364. package/dist/ms-doc/sections/SectionParser.d.ts +0 -25
  365. package/dist/ms-doc/sections/SectionParser.d.ts.map +0 -1
  366. package/dist/ms-doc/sections/SectionParser.js +0 -214
  367. package/dist/ms-doc/sections/SectionParser.js.map +0 -1
  368. package/dist/ms-doc/styles/StyleSheet.d.ts +0 -23
  369. package/dist/ms-doc/styles/StyleSheet.d.ts.map +0 -1
  370. package/dist/ms-doc/styles/StyleSheet.js +0 -268
  371. package/dist/ms-doc/styles/StyleSheet.js.map +0 -1
  372. package/dist/ms-doc/subdocuments/SubdocumentParser.d.ts +0 -61
  373. package/dist/ms-doc/subdocuments/SubdocumentParser.d.ts.map +0 -1
  374. package/dist/ms-doc/subdocuments/SubdocumentParser.js +0 -208
  375. package/dist/ms-doc/subdocuments/SubdocumentParser.js.map +0 -1
  376. package/dist/ms-doc/tables/TableParser.d.ts +0 -29
  377. package/dist/ms-doc/tables/TableParser.d.ts.map +0 -1
  378. package/dist/ms-doc/tables/TableParser.js +0 -176
  379. package/dist/ms-doc/tables/TableParser.js.map +0 -1
  380. package/dist/ms-doc/text/PieceTable.d.ts +0 -21
  381. package/dist/ms-doc/text/PieceTable.d.ts.map +0 -1
  382. package/dist/ms-doc/text/PieceTable.js +0 -171
  383. package/dist/ms-doc/text/PieceTable.js.map +0 -1
  384. package/dist/ms-doc/types/Constants.d.ts +0 -99
  385. package/dist/ms-doc/types/Constants.d.ts.map +0 -1
  386. package/dist/ms-doc/types/Constants.js +0 -102
  387. package/dist/ms-doc/types/Constants.js.map +0 -1
  388. package/dist/ms-doc/types/DocTypes.d.ts +0 -368
  389. package/dist/ms-doc/types/DocTypes.d.ts.map +0 -1
  390. package/dist/ms-doc/types/DocTypes.js +0 -3
  391. package/dist/ms-doc/types/DocTypes.js.map +0 -1
  392. package/dist/tracking/index.d.ts +0 -3
  393. package/dist/tracking/index.d.ts.map +0 -1
  394. package/dist/tracking/index.js +0 -6
  395. package/dist/tracking/index.js.map +0 -1
@@ -0,0 +1,1146 @@
1
+ /**
2
+ * Hyperlink - Represents a hyperlink in a Word document
3
+ *
4
+ * Hyperlinks can be external (to websites, files) or internal (to bookmarks within the document).
5
+ * They are represented using the `<w:hyperlink>` element.
6
+ *
7
+ * ## Important: Relationship ID Requirement
8
+ *
9
+ * **External hyperlinks REQUIRE a relationship ID to be set before XML generation.**
10
+ * Per ECMA-376 Part 1 §17.16.22, `<w:hyperlink>` elements with external targets must have
11
+ * an `r:id` attribute that references a relationship in `word/_rels/document.xml.rels`.
12
+ *
13
+ * ### Correct Usage Pattern:
14
+ *
15
+ * ```typescript
16
+ * // RECOMMENDED: Use Document.save() - automatically handles relationships
17
+ * const doc = Document.create();
18
+ * const para = doc.createParagraph();
19
+ * para.addHyperlink(Hyperlink.createExternal('https://example.com', 'Link'));
20
+ * await doc.save('document.docx'); // ✅ Relationships auto-registered
21
+ * ```
22
+ *
23
+ * ### Manual Relationship Registration (Advanced):
24
+ *
25
+ * ```typescript
26
+ * const link = Hyperlink.createExternal('https://example.com', 'Link');
27
+ * const relationship = relationshipManager.addHyperlink('https://example.com');
28
+ * link.setRelationshipId(relationship.getId());
29
+ * link.toXML(); // ✅ Now valid
30
+ * ```
31
+ *
32
+ * ### What NOT to Do:
33
+ *
34
+ * ```typescript
35
+ * const link = Hyperlink.createExternal('https://example.com', 'Link');
36
+ * link.toXML(); // ❌ ERROR: Missing relationship ID
37
+ * ```
38
+ *
39
+ * ## Internal Hyperlinks
40
+ *
41
+ * Internal hyperlinks (bookmarks) do NOT require relationships:
42
+ *
43
+ * ```typescript
44
+ * const link = Hyperlink.createInternal('Section1', 'Go to Section 1');
45
+ * link.toXML(); // ✅ Valid - uses w:anchor attribute
46
+ * ```
47
+ *
48
+ * @see {@link https://www.ecma-international.org/publications-and-standards/standards/ecma-376/ | ECMA-376 Part 1 §17.16.22}
49
+ */
50
+
51
+ import { XMLElement } from "../xml/XMLBuilder";
52
+ import { Run, RunFormatting } from "./Run";
53
+ import { Revision } from "./Revision";
54
+ import { validateRunText } from "../utils/validation";
55
+ import { defaultLogger } from "../utils/logger";
56
+
57
+ /**
58
+ * Hyperlink properties
59
+ */
60
+ export interface HyperlinkProperties {
61
+ /** Hyperlink URL (for external links) */
62
+ url?: string;
63
+ /** Bookmark anchor (for internal links) */
64
+ anchor?: string;
65
+ /** Display text (optional for empty/invisible hyperlinks) */
66
+ text?: string;
67
+ /** Text formatting */
68
+ formatting?: RunFormatting;
69
+ /** Tooltip text */
70
+ tooltip?: string;
71
+ /** Relationship ID (set by Document when saving) */
72
+ relationshipId?: string;
73
+ /** Whether this is an empty/invisible hyperlink with no display text */
74
+ isEmpty?: boolean;
75
+ /** Target frame attribute (e.g., "_blank" for new window) */
76
+ tgtFrame?: string;
77
+ /** History tracking attribute */
78
+ history?: string;
79
+ }
80
+
81
+ /**
82
+ * Represents a hyperlink
83
+ */
84
+ export class Hyperlink {
85
+ private url?: string;
86
+ private anchor?: string;
87
+ private text: string;
88
+ private run: Run;
89
+ private tooltip?: string;
90
+ private relationshipId?: string;
91
+ private formatting: RunFormatting;
92
+ /** Whether this is an empty/invisible hyperlink with no display text */
93
+ private _isEmpty = false;
94
+ /** Target frame attribute (e.g., "_blank" for new window) */
95
+ private tgtFrame?: string;
96
+ /** History tracking attribute */
97
+ private history?: string;
98
+ /** Tracking context for automatic change tracking */
99
+ private trackingContext?: import('../tracking/TrackingContext').TrackingContext;
100
+ /** Parent paragraph reference for automatic tracking */
101
+ private _parentParagraph?: import('./Paragraph').Paragraph;
102
+
103
+ /**
104
+ * Creates a new hyperlink
105
+ *
106
+ * **Note:** A hyperlink must have either a URL (external) or anchor (internal), but not both.
107
+ * If both are provided, the URL takes precedence and a warning is logged.
108
+ *
109
+ * @param properties Hyperlink properties
110
+ */
111
+ constructor(properties: HyperlinkProperties) {
112
+ this.url = properties.url;
113
+ this.anchor = properties.anchor;
114
+ this.tooltip = properties.tooltip;
115
+ this.relationshipId = properties.relationshipId;
116
+ this.tgtFrame = properties.tgtFrame;
117
+ this.history = properties.history;
118
+ this._isEmpty = properties.isEmpty ?? false;
119
+
120
+ // VALIDATION: Warn about hybrid links (url + anchor)
121
+ if (this.url && this.anchor) {
122
+ defaultLogger.warn(
123
+ `DocXML Warning: Hyperlink has both URL ("${this.url}") and anchor ("${this.anchor}"). ` +
124
+ `This is ambiguous per ECMA-376 spec. URL will take precedence. ` +
125
+ `Use Hyperlink.createExternal() or Hyperlink.createInternal() to avoid ambiguity.`
126
+ );
127
+ }
128
+
129
+ // Handle empty/invisible hyperlinks (no display text)
130
+ if (this._isEmpty) {
131
+ this.text = "";
132
+ this.formatting = {};
133
+ this.run = new Run("", {});
134
+ return;
135
+ }
136
+
137
+ // Text fallback: properties.text → url → 'Link'
138
+ // NOTE: Do NOT use anchor (bookmark ID) as display text - it should only be used for navigation
139
+ // Using bookmark IDs as visible text causes TOC corruption (Issue: TOC shows "HEADING=II.MNKE7E8NA385_" instead of proper headings)
140
+ this.text = properties.text || this.url || "Link";
141
+
142
+ // Validate text for XML patterns
143
+ // Default to auto-cleaning XML patterns unless explicitly disabled (matches Run behavior)
144
+ const validation = validateRunText(this.text, {
145
+ context: "Hyperlink text",
146
+ autoClean: properties.formatting?.cleanXmlFromText !== false,
147
+ warnToConsole: true,
148
+ });
149
+
150
+ // Use cleaned text if available and cleaning was requested
151
+ if (validation.cleanedText) {
152
+ this.text = validation.cleanedText;
153
+ }
154
+
155
+ // Create run with default hyperlink styling (Verdana 12pt blue underlined)
156
+ this.formatting = {
157
+ font: "Verdana",
158
+ size: 12,
159
+ color: "0000FF", // Standard hyperlink blue
160
+ underline: "single",
161
+ ...properties.formatting,
162
+ };
163
+
164
+ this.run = new Run(this.text, this.formatting);
165
+ }
166
+
167
+ /**
168
+ * Sets the tracking context for automatic change tracking.
169
+ * Called by Document when track changes is enabled.
170
+ * @internal
171
+ */
172
+ _setTrackingContext(context: import('../tracking/TrackingContext').TrackingContext): void {
173
+ this.trackingContext = context;
174
+ }
175
+
176
+ /**
177
+ * Sets the parent paragraph reference for automatic tracking.
178
+ * Called by Paragraph when hyperlink is added.
179
+ * @internal
180
+ */
181
+ _setParentParagraph(paragraph: import('./Paragraph').Paragraph): void {
182
+ this._parentParagraph = paragraph;
183
+ }
184
+
185
+ /**
186
+ * Gets the parent paragraph reference.
187
+ * @internal
188
+ */
189
+ _getParentParagraph(): import('./Paragraph').Paragraph | undefined {
190
+ return this._parentParagraph;
191
+ }
192
+
193
+ /**
194
+ * Gets the hyperlink URL
195
+ */
196
+ getUrl(): string | undefined {
197
+ return this.url;
198
+ }
199
+
200
+ /**
201
+ * Gets the complete URL including any anchor fragment.
202
+ *
203
+ * For external links that also have an anchor (e.g., internal bookmark within external page),
204
+ * this returns the URL with the anchor appended as a fragment.
205
+ * For internal-only links (anchor without URL), returns undefined.
206
+ *
207
+ * Note: As of v7.2.0, DocumentParser automatically combines external URLs with anchors
208
+ * during parsing, so getUrl() typically returns the full URL. This method is provided
209
+ * for cases where URL and anchor are set separately via the API.
210
+ *
211
+ * @returns The complete URL with fragment, or undefined for internal-only links
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * // External link with anchor fragment
216
+ * const link = new Hyperlink({ url: 'https://example.com/', anchor: '!/view?id=123', text: 'Link' });
217
+ * link.getUrl(); // 'https://example.com/'
218
+ * link.getAnchor(); // '!/view?id=123'
219
+ * link.getFullUrl(); // 'https://example.com/#!/view?id=123'
220
+ *
221
+ * // External link without anchor
222
+ * const link2 = Hyperlink.createExternal('https://example.com/page', 'Link');
223
+ * link2.getFullUrl(); // 'https://example.com/page'
224
+ *
225
+ * // Internal link (bookmark reference)
226
+ * const link3 = Hyperlink.createInternal('Section1', 'Go to Section 1');
227
+ * link3.getFullUrl(); // undefined
228
+ * ```
229
+ */
230
+ getFullUrl(): string | undefined {
231
+ if (this.url && this.anchor) {
232
+ return this.url + '#' + this.anchor;
233
+ }
234
+ return this.url;
235
+ }
236
+
237
+ /**
238
+ * Gets the anchor (for internal links)
239
+ */
240
+ getAnchor(): string | undefined {
241
+ return this.anchor;
242
+ }
243
+
244
+ /**
245
+ * Returns whether this is an empty/invisible hyperlink (has no display text).
246
+ * Empty hyperlinks are self-closing elements in the XML.
247
+ */
248
+ isEmpty(): boolean {
249
+ return this._isEmpty;
250
+ }
251
+
252
+ /**
253
+ * Gets the target frame attribute (e.g., "_blank" for new window)
254
+ */
255
+ getTgtFrame(): string | undefined {
256
+ return this.tgtFrame;
257
+ }
258
+
259
+ /**
260
+ * Gets the history tracking attribute
261
+ */
262
+ getHistory(): string | undefined {
263
+ return this.history;
264
+ }
265
+
266
+ /**
267
+ * Gets the display text
268
+ *
269
+ * This method delegates to the internal run to ensure the returned text
270
+ * is always accurate and matches what will be in the generated XML,
271
+ * per ECMA-376 Part 1 §17.16.22.
272
+ *
273
+ * @returns The display text including any special characters (tabs, breaks, etc.)
274
+ */
275
+ getText(): string {
276
+ return this.run.getText();
277
+ }
278
+
279
+ /**
280
+ * Sets the display text
281
+ */
282
+ setText(text: string): this {
283
+ // Validate text for XML patterns
284
+ // Default to auto-cleaning unless explicitly disabled (matches Run behavior)
285
+ const validation = validateRunText(text, {
286
+ context: "Hyperlink.setText",
287
+ autoClean: this.formatting.cleanXmlFromText !== false,
288
+ warnToConsole: true,
289
+ });
290
+
291
+ // Use cleaned text if available
292
+ const cleanedText = validation.cleanedText || text;
293
+
294
+ const previousValue = this.text;
295
+ this.text = cleanedText;
296
+ this.run.setText(cleanedText); // Run.setText also validates
297
+ if (this.trackingContext?.isEnabled() && previousValue !== cleanedText) {
298
+ this.trackingContext.trackHyperlinkChange(this, 'text', previousValue, cleanedText);
299
+ }
300
+ return this;
301
+ }
302
+
303
+ /**
304
+ * Sets the internal run directly (for advanced use cases like TOC parsing)
305
+ * Used by DocumentParser to preserve run content (tabs, breaks, etc.)
306
+ * @param run - The run to use for this hyperlink
307
+ */
308
+ setRun(run: Run): this {
309
+ this.run = run;
310
+ this.text = run.getText();
311
+ return this;
312
+ }
313
+
314
+ /**
315
+ * Gets the tooltip
316
+ */
317
+ getTooltip(): string | undefined {
318
+ return this.tooltip;
319
+ }
320
+
321
+ /**
322
+ * Sets the tooltip
323
+ */
324
+ setTooltip(tooltip: string): this {
325
+ const previousValue = this.tooltip;
326
+ this.tooltip = tooltip;
327
+ if (this.trackingContext?.isEnabled() && previousValue !== tooltip) {
328
+ this.trackingContext.trackHyperlinkChange(this, 'tooltip', previousValue, tooltip);
329
+ }
330
+ return this;
331
+ }
332
+
333
+ /**
334
+ * Gets the relationship ID
335
+ */
336
+ getRelationshipId(): string | undefined {
337
+ return this.relationshipId;
338
+ }
339
+
340
+ /**
341
+ * Sets the relationship ID (called by Document during save)
342
+ */
343
+ setRelationshipId(id: string): this {
344
+ this.relationshipId = id;
345
+ return this;
346
+ }
347
+
348
+ /**
349
+ * Sets or updates the hyperlink URL
350
+ *
351
+ * When URL is updated, we mark that the relationship needs updating.
352
+ * The actual relationship update happens during Document.save() to ensure
353
+ * proper coordination with the RelationshipManager.
354
+ *
355
+ * **Important:** This method maintains the relationship ID but flags it for update.
356
+ * The RelationshipManager will update the existing relationship's target URL
357
+ * during save, preventing orphaned relationships per ECMA-376 §17.16.22.
358
+ *
359
+ * @param url - The new URL (or undefined to clear)
360
+ * @returns This hyperlink for chaining
361
+ * @throws {Error} If clearing URL would create empty hyperlink (no URL and no anchor)
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const link = Hyperlink.createExternal('https://old.com', 'Link');
366
+ * link.setUrl('https://new.com'); // Marks for relationship update
367
+ * await doc.save('updated.docx'); // Updates relationship target
368
+ * ```
369
+ */
370
+ setUrl(url: string | undefined): this {
371
+ // Validate that clearing URL doesn't create empty hyperlink
372
+ if (!url && !this.anchor) {
373
+ throw new Error(
374
+ `Cannot set URL to undefined: Hyperlink "${this.run.getText()}" has no anchor. ` +
375
+ `Clearing the URL would create an invalid hyperlink per ECMA-376 §17.16.22. ` +
376
+ `Either provide a new URL or delete the hyperlink entirely.`
377
+ );
378
+ }
379
+
380
+ // Save old URL before updating (for text fallback logic)
381
+ const oldUrl = this.url;
382
+
383
+ // Skip if URL unchanged (optimization)
384
+ if (oldUrl === url) {
385
+ return this;
386
+ }
387
+
388
+ // If tracking enabled AND has parent paragraph, create revision pair
389
+ // OOXML has no w:hyperlinkChange element - Word tracks hyperlink changes as delete/insert pairs
390
+ if (this.trackingContext?.isEnabled() && this._parentParagraph) {
391
+ const author = this.trackingContext.getAuthor();
392
+
393
+ // Clone current state for deletion (before applying changes)
394
+ const oldHyperlink = this.clone();
395
+
396
+ // Apply the change to this hyperlink
397
+ this.url = url;
398
+ this.relationshipId = undefined;
399
+ if (this.run.getText() === oldUrl) {
400
+ this.text = url || this.anchor || "Link";
401
+ this.run.setText(this.text);
402
+ }
403
+
404
+ // Create delete/insert revision pair
405
+ const deletion = Revision.createDeletion(author, [oldHyperlink]);
406
+ const insertion = Revision.createInsertion(author, [this]);
407
+
408
+ // Replace this hyperlink with the revision pair in parent paragraph
409
+ this._parentParagraph.replaceContent(this, [deletion, insertion]);
410
+
411
+ // Clear parent reference since we're now inside a revision
412
+ this._parentParagraph = undefined;
413
+
414
+ return this;
415
+ }
416
+
417
+ // Non-tracking path (original behavior)
418
+ this.url = url;
419
+
420
+ // Clear the relationship ID so it will be re-registered during save
421
+ // This ensures the relationship target is updated to point to the new URL
422
+ this.relationshipId = undefined;
423
+
424
+ // Update text ONLY if it was auto-generated from the old URL
425
+ // This preserves user-provided text (even if it's "Link")
426
+ // Use run.getText() to ensure we check the actual current text, not stale cache
427
+ if (this.run.getText() === oldUrl) {
428
+ this.text = url || this.anchor || "Link";
429
+ this.run.setText(this.text);
430
+ }
431
+
432
+ return this;
433
+ }
434
+
435
+ /**
436
+ * Sets the anchor (for internal links)
437
+ * @param anchor Bookmark name to link to
438
+ * @returns This hyperlink for chaining
439
+ * @throws {Error} If clearing anchor would create empty hyperlink (no URL and no anchor)
440
+ * @example
441
+ * ```typescript
442
+ * const link = Hyperlink.createInternal('OldBookmark', 'Go there');
443
+ * link.setAnchor('NewBookmark'); // Update internal link target
444
+ * ```
445
+ */
446
+ setAnchor(anchor: string | undefined): this {
447
+ // Validate that clearing anchor doesn't create empty hyperlink
448
+ if (!anchor && !this.url) {
449
+ throw new Error(
450
+ `Cannot set anchor to undefined: Hyperlink "${this.run.getText()}" has no URL. ` +
451
+ `Clearing the anchor would create an invalid hyperlink per ECMA-376 §17.16.22. ` +
452
+ `Either provide a new anchor or delete the hyperlink entirely.`
453
+ );
454
+ }
455
+
456
+ // Save old anchor before updating
457
+ const oldAnchor = this.anchor;
458
+
459
+ // Skip if anchor unchanged (optimization)
460
+ if (oldAnchor === anchor) {
461
+ return this;
462
+ }
463
+
464
+ // If tracking enabled AND has parent paragraph, create revision pair
465
+ // OOXML has no w:hyperlinkChange element - Word tracks hyperlink changes as delete/insert pairs
466
+ if (this.trackingContext?.isEnabled() && this._parentParagraph) {
467
+ const author = this.trackingContext.getAuthor();
468
+
469
+ // Clone current state for deletion (before applying changes)
470
+ const oldHyperlink = this.clone();
471
+
472
+ // Apply the change to this hyperlink
473
+ this.anchor = anchor;
474
+ if (anchor && this.url) {
475
+ defaultLogger.warn(
476
+ `DocXML Warning: Setting anchor "${anchor}" on hyperlink that has URL "${this.url}". ` +
477
+ `Clearing URL to make this an internal link. Use separate hyperlinks for external and internal links.`
478
+ );
479
+ this.url = undefined;
480
+ this.relationshipId = undefined;
481
+ }
482
+ if (this.run.getText() === oldAnchor) {
483
+ this.text = anchor || this.url || "Link";
484
+ this.run.setText(this.text);
485
+ }
486
+
487
+ // Create delete/insert revision pair
488
+ const deletion = Revision.createDeletion(author, [oldHyperlink]);
489
+ const insertion = Revision.createInsertion(author, [this]);
490
+
491
+ // Replace this hyperlink with the revision pair in parent paragraph
492
+ this._parentParagraph.replaceContent(this, [deletion, insertion]);
493
+
494
+ // Clear parent reference since we're now inside a revision
495
+ this._parentParagraph = undefined;
496
+
497
+ return this;
498
+ }
499
+
500
+ // Non-tracking path (original behavior)
501
+ this.anchor = anchor;
502
+
503
+ // If converting from external to internal, clear URL and relationship
504
+ if (anchor && this.url) {
505
+ defaultLogger.warn(
506
+ `DocXML Warning: Setting anchor "${anchor}" on hyperlink that has URL "${this.url}". ` +
507
+ `Clearing URL to make this an internal link. Use separate hyperlinks for external and internal links.`
508
+ );
509
+ this.url = undefined;
510
+ this.relationshipId = undefined;
511
+ }
512
+
513
+ // Update text ONLY if it was auto-generated from the old anchor
514
+ // Use run.getText() to ensure we check the actual current text, not stale cache
515
+ if (this.run.getText() === oldAnchor) {
516
+ this.text = anchor || this.url || "Link";
517
+ this.run.setText(this.text);
518
+ }
519
+
520
+ return this;
521
+ }
522
+
523
+ /**
524
+ * Gets the run
525
+ */
526
+ getRun(): Run {
527
+ return this.run;
528
+ }
529
+
530
+ /**
531
+ * Sets run formatting
532
+ *
533
+ * @param formatting - The formatting to apply
534
+ * @param options - Optional settings
535
+ * @param options.replace - If true, replaces ALL existing formatting instead of merging.
536
+ * Use this when you want to clear inherited styles like characterStyle.
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * // Merge mode (default): adds/updates properties while preserving others
541
+ * hyperlink.setFormatting({ bold: true });
542
+ *
543
+ * // Replace mode: clears all existing formatting and applies only the new properties
544
+ * hyperlink.setFormatting({ font: "Verdana", size: 12 }, { replace: true });
545
+ * ```
546
+ */
547
+ setFormatting(formatting: RunFormatting, options?: { replace?: boolean }): this {
548
+ // Update stored formatting
549
+ const previousValue = { ...this.formatting };
550
+ if (options?.replace) {
551
+ // Replace mode: new formatting replaces ALL existing properties
552
+ this.formatting = { ...formatting };
553
+ } else {
554
+ // Merge mode (default, backwards-compatible): merge with existing
555
+ this.formatting = { ...this.formatting, ...formatting };
556
+ }
557
+ // Create new run with updated formatting, preserving current text
558
+ const currentText = this.run.getText();
559
+ this.run = new Run(currentText, this.formatting);
560
+ this.text = currentText; // Keep cache in sync
561
+ if (this.trackingContext?.isEnabled()) {
562
+ this.trackingContext.trackHyperlinkChange(this, 'formatting', previousValue, this.formatting);
563
+ }
564
+ return this;
565
+ }
566
+
567
+ /**
568
+ * Gets run formatting (returns this hyperlink for fluent API)
569
+ * @returns This hyperlink for method chaining
570
+ *
571
+ * @example
572
+ * ```typescript
573
+ * hyperlink.getFormatting().setColor('0563C1').setUnderline('single');
574
+ * ```
575
+ */
576
+ getFormatting(): this {
577
+ return this;
578
+ }
579
+
580
+ /**
581
+ * Gets the raw formatting object (for direct access)
582
+ * @returns RunFormatting object
583
+ */
584
+ getRawFormatting(): RunFormatting {
585
+ return this.formatting;
586
+ }
587
+
588
+ // ============================================================================
589
+ // Individual Formatting Getters
590
+ // ============================================================================
591
+
592
+ /**
593
+ * Gets the text color
594
+ * @returns Color hex string or undefined
595
+ */
596
+ getColor(): string | undefined {
597
+ return this.formatting.color;
598
+ }
599
+
600
+ /**
601
+ * Gets the underline style
602
+ * @returns Underline style or undefined
603
+ */
604
+ getUnderline(): string | boolean | undefined {
605
+ return this.formatting.underline;
606
+ }
607
+
608
+ /**
609
+ * Gets whether the hyperlink is bold
610
+ * @returns True if bold, false otherwise
611
+ */
612
+ getBold(): boolean {
613
+ return this.formatting.bold ?? false;
614
+ }
615
+
616
+ /**
617
+ * Gets whether the hyperlink is italic
618
+ * @returns True if italic, false otherwise
619
+ */
620
+ getItalic(): boolean {
621
+ return this.formatting.italic ?? false;
622
+ }
623
+
624
+ /**
625
+ * Gets the font family
626
+ * @returns Font name or undefined
627
+ */
628
+ getFont(): string | undefined {
629
+ return this.formatting.font;
630
+ }
631
+
632
+ /**
633
+ * Gets the font size
634
+ * @returns Font size in points or undefined
635
+ */
636
+ getSize(): number | undefined {
637
+ return this.formatting.size;
638
+ }
639
+
640
+ /**
641
+ * Sets text color
642
+ * @param color Color in hex format (e.g., '0563C1')
643
+ * @returns This hyperlink for chaining
644
+ */
645
+ setColor(color: string): this {
646
+ const previousValue = this.formatting.color;
647
+ this.formatting.color = color;
648
+ this.run = new Run(this.text, this.formatting);
649
+ if (this.trackingContext?.isEnabled() && previousValue !== color) {
650
+ this.trackingContext.trackHyperlinkChange(this, 'color', previousValue, color);
651
+ }
652
+ return this;
653
+ }
654
+
655
+ /**
656
+ * Sets underline style
657
+ * @param underline Underline style ('single', 'double', etc.)
658
+ * @returns This hyperlink for chaining
659
+ */
660
+ setUnderline(underline: boolean | "single" | "double" | "dotted" | "thick" | "dash"): this {
661
+ const previousValue = this.formatting.underline;
662
+ this.formatting.underline = underline;
663
+ this.run = new Run(this.text, this.formatting);
664
+ if (this.trackingContext?.isEnabled() && previousValue !== underline) {
665
+ this.trackingContext.trackHyperlinkChange(this, 'underline', previousValue, underline);
666
+ }
667
+ return this;
668
+ }
669
+
670
+ /**
671
+ * Sets bold formatting
672
+ * @param bold Bold state (default: true)
673
+ * @returns This hyperlink for chaining
674
+ */
675
+ setBold(bold = true): this {
676
+ const previousValue = this.formatting.bold;
677
+ this.formatting.bold = bold;
678
+ this.run = new Run(this.text, this.formatting);
679
+ if (this.trackingContext?.isEnabled() && previousValue !== bold) {
680
+ this.trackingContext.trackHyperlinkChange(this, 'bold', previousValue, bold);
681
+ }
682
+ return this;
683
+ }
684
+
685
+ /**
686
+ * Sets italic formatting
687
+ * @param italic Italic state (default: true)
688
+ * @returns This hyperlink for chaining
689
+ */
690
+ setItalic(italic = true): this {
691
+ const previousValue = this.formatting.italic;
692
+ this.formatting.italic = italic;
693
+ this.run = new Run(this.text, this.formatting);
694
+ if (this.trackingContext?.isEnabled() && previousValue !== italic) {
695
+ this.trackingContext.trackHyperlinkChange(this, 'italic', previousValue, italic);
696
+ }
697
+ return this;
698
+ }
699
+
700
+ /**
701
+ * Sets font family
702
+ * @param font Font name (e.g., 'Arial', 'Verdana')
703
+ * @returns This hyperlink for chaining
704
+ */
705
+ setFont(font: string): this {
706
+ const previousValue = this.formatting.font;
707
+ this.formatting.font = font;
708
+ this.run = new Run(this.text, this.formatting);
709
+ if (this.trackingContext?.isEnabled() && previousValue !== font) {
710
+ this.trackingContext.trackHyperlinkChange(this, 'font', previousValue, font);
711
+ }
712
+ return this;
713
+ }
714
+
715
+ /**
716
+ * Sets font size
717
+ * @param size Font size in points (e.g., 12, 14)
718
+ * @returns This hyperlink for chaining
719
+ */
720
+ setSize(size: number): this {
721
+ const previousValue = this.formatting.size;
722
+ this.formatting.size = size;
723
+ this.run = new Run(this.text, this.formatting);
724
+ if (this.trackingContext?.isEnabled() && previousValue !== size) {
725
+ this.trackingContext.trackHyperlinkChange(this, 'size', previousValue, size);
726
+ }
727
+ return this;
728
+ }
729
+
730
+ /**
731
+ * Validates the hyperlink URL and optionally fixes common issues
732
+ *
733
+ * Performs validation and fixing of hyperlink URLs including:
734
+ * - Checking URL accessibility (HTTP HEAD request for external links)
735
+ * - Fixing common URL issues (missing protocol, double slashes, spaces)
736
+ * - Validating internal bookmark references
737
+ * - Detecting broken links
738
+ *
739
+ * **Note:** This method is async due to network requests for accessibility checks.
740
+ *
741
+ * @param options - Validation options
742
+ * @returns Promise with validation results
743
+ *
744
+ * @example
745
+ * ```typescript
746
+ * // Basic URL fixing without network check
747
+ * const result = await link.validateAndFix({
748
+ * fixCommonIssues: true,
749
+ * checkAccessibility: false
750
+ * });
751
+ * console.log(`Fixed: ${result.fixed.join(', ')}`);
752
+ *
753
+ * // Full validation with accessibility check
754
+ * const validation = await link.validateAndFix({
755
+ * checkAccessibility: true,
756
+ * timeout: 5000
757
+ * });
758
+ * if (!validation.valid) {
759
+ * console.log(`Issues: ${validation.issues.join(', ')}`);
760
+ * }
761
+ *
762
+ * // Batch validate all hyperlinks in document
763
+ * for (const { hyperlink } of doc.getHyperlinks()) {
764
+ * const result = await hyperlink.validateAndFix();
765
+ * if (result.fixed.length > 0) {
766
+ * console.log(`Fixed ${hyperlink.getUrl()}: ${result.fixed.join(', ')}`);
767
+ * }
768
+ * }
769
+ * ```
770
+ */
771
+ async validateAndFix(options?: {
772
+ checkAccessibility?: boolean;
773
+ fixCommonIssues?: boolean;
774
+ timeout?: number;
775
+ bookmarkManager?: { hasBookmark(name: string): boolean };
776
+ }): Promise<{
777
+ valid: boolean;
778
+ issues: string[];
779
+ fixed: string[];
780
+ originalUrl?: string;
781
+ fixedUrl?: string;
782
+ }> {
783
+ const {
784
+ checkAccessibility = false,
785
+ fixCommonIssues = true,
786
+ timeout = 5000,
787
+ bookmarkManager,
788
+ } = options || {};
789
+
790
+ const issues: string[] = [];
791
+ const fixed: string[] = [];
792
+ let fixedUrl = this.url;
793
+ const originalUrl = this.url;
794
+
795
+ // Internal link validation (bookmarks)
796
+ if (this.anchor) {
797
+ if (bookmarkManager) {
798
+ const bookmarkExists = bookmarkManager.hasBookmark(this.anchor);
799
+ if (!bookmarkExists) {
800
+ issues.push(`Internal bookmark "${this.anchor}" not found`);
801
+ }
802
+ }
803
+ return {
804
+ valid: issues.length === 0,
805
+ issues,
806
+ fixed,
807
+ originalUrl,
808
+ };
809
+ }
810
+
811
+ // External link validation
812
+ if (!this.url) {
813
+ issues.push("No URL or anchor specified");
814
+ return { valid: false, issues, fixed, originalUrl };
815
+ }
816
+
817
+ // Fix common issues
818
+ if (fixCommonIssues && fixedUrl) {
819
+ // Fix 1: Add missing protocol
820
+ if (!(/^[a-z]+:\/\//i.exec(fixedUrl))) {
821
+ fixedUrl = "https://" + fixedUrl;
822
+ fixed.push("Added missing protocol (https://)");
823
+ }
824
+
825
+ // Fix 2: Fix double slashes (except after protocol)
826
+ const protocolMatch = /^([a-z]+:\/\/)/i.exec(fixedUrl);
827
+ if (protocolMatch?.[1]) {
828
+ const protocol = protocolMatch[1];
829
+ const rest = fixedUrl.substring(protocol.length);
830
+ const fixedRest = rest.replace(/\/\//g, "/");
831
+ if (rest !== fixedRest) {
832
+ fixedUrl = protocol + fixedRest;
833
+ fixed.push("Fixed double slashes");
834
+ }
835
+ }
836
+
837
+ // Fix 3: Encode spaces
838
+ if (fixedUrl.includes(" ")) {
839
+ fixedUrl = fixedUrl.replace(/ /g, "%20");
840
+ fixed.push("Encoded spaces as %20");
841
+ }
842
+
843
+ // Fix 4: Remove trailing slashes for non-root URLs
844
+ if (/^https?:\/\/[^/]+\/.+\/$/.exec(fixedUrl)) {
845
+ fixedUrl = fixedUrl.replace(/\/$/, "");
846
+ fixed.push("Removed trailing slash");
847
+ }
848
+
849
+ // Fix 5: Fix common typos
850
+ fixedUrl = fixedUrl.replace(/^http:\/\//i, "https://"); // Prefer HTTPS
851
+ if (fixedUrl !== this.url && fixedUrl.startsWith("https://")) {
852
+ fixed.push("Upgraded HTTP to HTTPS");
853
+ }
854
+
855
+ // Update URL if fixes were applied
856
+ if (fixedUrl !== this.url) {
857
+ this.setUrl(fixedUrl);
858
+ }
859
+ }
860
+
861
+ // Check accessibility (HTTP HEAD request)
862
+ if (checkAccessibility && fixedUrl?.match(/^https?:\/\//i)) {
863
+ // Check if fetch is available (Node.js 18+ or browser)
864
+ if (typeof fetch === "undefined") {
865
+ issues.push(
866
+ "Network validation unavailable: fetch API not supported in this environment"
867
+ );
868
+ } else {
869
+ try {
870
+ // Use fetch with AbortController for timeout
871
+ const controller = new AbortController();
872
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
873
+
874
+ const response = await fetch(fixedUrl, {
875
+ method: "HEAD",
876
+ signal: controller.signal,
877
+ redirect: "follow",
878
+ });
879
+
880
+ clearTimeout(timeoutId);
881
+
882
+ if (!response.ok) {
883
+ issues.push(
884
+ `HTTP ${response.status}: ${response.statusText || "Error"}`
885
+ );
886
+ }
887
+ } catch (error: unknown) {
888
+ // Type guard for error objects with name and message properties
889
+ const isErrorWithName = (err: unknown): err is { name: string } => {
890
+ return typeof err === "object" && err !== null && "name" in err;
891
+ };
892
+ const isErrorWithMessage = (
893
+ err: unknown
894
+ ): err is { message: string } => {
895
+ return typeof err === "object" && err !== null && "message" in err;
896
+ };
897
+
898
+ if (isErrorWithName(error) && error.name === "AbortError") {
899
+ issues.push(`Timeout after ${timeout}ms`);
900
+ } else if (
901
+ isErrorWithMessage(error) &&
902
+ error.message?.includes("fetch")
903
+ ) {
904
+ issues.push(`Unreachable: ${error.message}`);
905
+ } else if (isErrorWithMessage(error)) {
906
+ issues.push(`Network error: ${error.message}`);
907
+ } else {
908
+ issues.push("Network error: Unknown error");
909
+ }
910
+ }
911
+ }
912
+ }
913
+
914
+ return {
915
+ valid: issues.length === 0,
916
+ issues,
917
+ fixed,
918
+ originalUrl,
919
+ fixedUrl: fixedUrl !== originalUrl ? fixedUrl : undefined,
920
+ };
921
+ }
922
+
923
+ /**
924
+ * Resets hyperlink formatting to standard style (Calibri, blue, underline)
925
+ * This is useful for fixing corrupted hyperlinks from Google Docs or other sources
926
+ * @returns this for method chaining
927
+ */
928
+ resetToStandardFormatting(): this {
929
+ const standardFormatting: RunFormatting = {
930
+ font: "Verdana",
931
+ color: "0000FF", // Standard hyperlink blue
932
+ underline: "single",
933
+ // Clear any other formatting that might be causing issues
934
+ bold: false,
935
+ italic: false,
936
+ strike: false,
937
+ };
938
+
939
+ this.setFormatting(standardFormatting);
940
+ return this;
941
+ }
942
+
943
+ /**
944
+ * Checks if this is an external link
945
+ */
946
+ isExternal(): boolean {
947
+ return this.url !== undefined;
948
+ }
949
+
950
+ /**
951
+ * Checks if this is an internal link (anchor)
952
+ */
953
+ isInternal(): boolean {
954
+ return this.anchor !== undefined;
955
+ }
956
+
957
+ /**
958
+ * Creates a deep copy of this hyperlink
959
+ *
960
+ * This is useful for preserving the original state before modifications,
961
+ * particularly when creating tracked changes (revisions) where both the
962
+ * old and new states need to be preserved.
963
+ *
964
+ * @returns A new Hyperlink instance with the same properties
965
+ *
966
+ * @example
967
+ * ```typescript
968
+ * // Clone before modifying for tracked changes
969
+ * const originalLink = hyperlink.clone();
970
+ * hyperlink.setUrl('https://new-url.com');
971
+ * hyperlink.setText('New Text');
972
+ *
973
+ * // Now originalLink has old URL/text, hyperlink has new
974
+ * const deletion = Revision.createDeletion(author, [originalLink]);
975
+ * const insertion = Revision.createInsertion(author, [hyperlink]);
976
+ * ```
977
+ */
978
+ clone(): Hyperlink {
979
+ const cloned = new Hyperlink({
980
+ url: this.url,
981
+ anchor: this.anchor,
982
+ text: this.text,
983
+ tooltip: this.tooltip,
984
+ relationshipId: this.relationshipId,
985
+ formatting: { ...this.formatting },
986
+ });
987
+
988
+ // Copy the run with its formatting
989
+ if (this.run) {
990
+ cloned.run = new Run(this.run.getText(), { ...this.run.getFormatting() });
991
+ }
992
+
993
+ return cloned;
994
+ }
995
+
996
+ /**
997
+ * Generates XML for the hyperlink
998
+ *
999
+ * **CRITICAL:** For external links, relationshipId MUST be set before calling toXML().
1000
+ * This happens automatically when saving via Document.save(), but manual usage requires
1001
+ * registering the hyperlink with RelationshipManager first.
1002
+ *
1003
+ * @throws {Error} If external link (has url) is missing relationshipId
1004
+ * @throws {Error} If hyperlink has neither url nor anchor (empty hyperlink)
1005
+ */
1006
+ toXML(): XMLElement {
1007
+ // VALIDATION: Hyperlink must have url OR anchor (unless it's an empty hyperlink with relationshipId)
1008
+ if (!this.url && !this.anchor && !this.relationshipId) {
1009
+ throw new Error(
1010
+ "CRITICAL: Hyperlink must have either a URL (external link), anchor (internal link), or relationshipId. " +
1011
+ "Cannot generate valid XML for hyperlink without destination."
1012
+ );
1013
+ }
1014
+
1015
+ // VALIDATION: External links MUST have relationship ID
1016
+ // Per ECMA-376 Part 1 §17.16.22, <w:hyperlink> with external target requires r:id attribute
1017
+ if (this.url && !this.relationshipId) {
1018
+ throw new Error(
1019
+ `CRITICAL: External hyperlink to "${this.url}" is missing relationship ID. ` +
1020
+ `This would create an invalid OpenXML document per ECMA-376 §17.16.22. ` +
1021
+ `Solution: Use Document.save() which automatically registers relationships, ` +
1022
+ `or manually call relationshipManager.addHyperlink(url) and set the relationship ID.`
1023
+ );
1024
+ }
1025
+
1026
+ const attributes: Record<string, string> = {};
1027
+
1028
+ // External link - add relationship ID
1029
+ if (this.relationshipId) {
1030
+ attributes["r:id"] = this.relationshipId;
1031
+ }
1032
+
1033
+ // Internal link - uses anchor
1034
+ if (this.anchor) {
1035
+ attributes["w:anchor"] = this.anchor;
1036
+ }
1037
+
1038
+ // Tooltip - explicitly escape attribute value for safety
1039
+ // XMLBuilder will handle escaping, but we document this for clarity
1040
+ if (this.tooltip) {
1041
+ // Note: XMLBuilder.elementToString() will escape this via escapeXmlAttribute()
1042
+ // when generating the actual XML string. We store the raw value here.
1043
+ attributes["w:tooltip"] = this.tooltip;
1044
+ }
1045
+
1046
+ // Target frame attribute (e.g., "_blank" for new window)
1047
+ if (this.tgtFrame) {
1048
+ attributes["w:tgtFrame"] = this.tgtFrame;
1049
+ }
1050
+
1051
+ // History tracking attribute
1052
+ if (this.history) {
1053
+ attributes["w:history"] = this.history;
1054
+ }
1055
+
1056
+ // Empty/invisible hyperlinks have no children (self-closing element)
1057
+ if (this._isEmpty) {
1058
+ return {
1059
+ name: "w:hyperlink",
1060
+ attributes,
1061
+ children: [],
1062
+ };
1063
+ }
1064
+
1065
+ // Generate run XML
1066
+ const runXml = this.run.toXML();
1067
+
1068
+ return {
1069
+ name: "w:hyperlink",
1070
+ attributes,
1071
+ children: [runXml],
1072
+ };
1073
+ }
1074
+
1075
+ /**
1076
+ * Creates an external hyperlink
1077
+ * @param url The URL
1078
+ * @param text Display text
1079
+ * @param formatting Optional formatting
1080
+ */
1081
+ static createExternal(
1082
+ url: string,
1083
+ text: string,
1084
+ formatting?: RunFormatting
1085
+ ): Hyperlink {
1086
+ return new Hyperlink({ url, text, formatting });
1087
+ }
1088
+
1089
+ /**
1090
+ * Creates an internal hyperlink (to a bookmark)
1091
+ * @param anchor Bookmark name
1092
+ * @param text Display text
1093
+ * @param formatting Optional formatting
1094
+ */
1095
+ static createInternal(
1096
+ anchor: string,
1097
+ text: string,
1098
+ formatting?: RunFormatting
1099
+ ): Hyperlink {
1100
+ return new Hyperlink({ anchor, text, formatting });
1101
+ }
1102
+
1103
+ /**
1104
+ * Creates a web link (convenience method for URLs)
1105
+ * @param url The URL
1106
+ * @param text Display text (defaults to URL)
1107
+ * @param formatting Optional formatting
1108
+ */
1109
+ static createWebLink(
1110
+ url: string,
1111
+ text?: string,
1112
+ formatting?: RunFormatting
1113
+ ): Hyperlink {
1114
+ return new Hyperlink({
1115
+ url,
1116
+ text: text || url,
1117
+ formatting,
1118
+ });
1119
+ }
1120
+
1121
+ /**
1122
+ * Creates an email link
1123
+ * @param email Email address
1124
+ * @param text Display text (defaults to email)
1125
+ * @param formatting Optional formatting
1126
+ */
1127
+ static createEmail(
1128
+ email: string,
1129
+ text?: string,
1130
+ formatting?: RunFormatting
1131
+ ): Hyperlink {
1132
+ return new Hyperlink({
1133
+ url: `mailto:${email}`,
1134
+ text: text || email,
1135
+ formatting,
1136
+ });
1137
+ }
1138
+
1139
+ /**
1140
+ * Creates a hyperlink with properties
1141
+ * @param properties Hyperlink properties
1142
+ */
1143
+ static create(properties: HyperlinkProperties): Hyperlink {
1144
+ return new Hyperlink(properties);
1145
+ }
1146
+ }