docxmlater 11.0.9 → 12.0.1

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 (354) hide show
  1. package/README.md +51 -13
  2. package/dist/core/Document.d.ts +28 -2
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +725 -170
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentGenerator.d.ts +1 -0
  7. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  8. package/dist/core/DocumentGenerator.js +77 -25
  9. package/dist/core/DocumentGenerator.js.map +1 -1
  10. package/dist/core/DocumentParser.d.ts +7 -3
  11. package/dist/core/DocumentParser.d.ts.map +1 -1
  12. package/dist/core/DocumentParser.js +431 -85
  13. package/dist/core/DocumentParser.js.map +1 -1
  14. package/dist/core/RelationshipManager.d.ts.map +1 -1
  15. package/dist/core/RelationshipManager.js +2 -2
  16. package/dist/core/RelationshipManager.js.map +1 -1
  17. package/dist/elements/Comment.d.ts +15 -1
  18. package/dist/elements/Comment.d.ts.map +1 -1
  19. package/dist/elements/Comment.js +67 -12
  20. package/dist/elements/Comment.js.map +1 -1
  21. package/dist/elements/CommentManager.d.ts +4 -0
  22. package/dist/elements/CommentManager.d.ts.map +1 -1
  23. package/dist/elements/CommentManager.js +74 -16
  24. package/dist/elements/CommentManager.js.map +1 -1
  25. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  26. package/dist/elements/EndnoteManager.js.map +1 -1
  27. package/dist/elements/Field.d.ts +10 -1
  28. package/dist/elements/Field.d.ts.map +1 -1
  29. package/dist/elements/Field.js +76 -34
  30. package/dist/elements/Field.js.map +1 -1
  31. package/dist/elements/FieldHelpers.d.ts +1 -0
  32. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  33. package/dist/elements/FieldHelpers.js +8 -4
  34. package/dist/elements/FieldHelpers.js.map +1 -1
  35. package/dist/elements/Footer.d.ts +3 -1
  36. package/dist/elements/Footer.d.ts.map +1 -1
  37. package/dist/elements/Footer.js +39 -53
  38. package/dist/elements/Footer.js.map +1 -1
  39. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  40. package/dist/elements/FootnoteManager.js.map +1 -1
  41. package/dist/elements/Header.d.ts +3 -1
  42. package/dist/elements/Header.d.ts.map +1 -1
  43. package/dist/elements/Header.js +39 -53
  44. package/dist/elements/Header.js.map +1 -1
  45. package/dist/elements/HeaderFooterManager.d.ts +4 -2
  46. package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
  47. package/dist/elements/HeaderFooterManager.js +36 -6
  48. package/dist/elements/HeaderFooterManager.js.map +1 -1
  49. package/dist/elements/Hyperlink.d.ts +6 -1
  50. package/dist/elements/Hyperlink.d.ts.map +1 -1
  51. package/dist/elements/Hyperlink.js +69 -44
  52. package/dist/elements/Hyperlink.js.map +1 -1
  53. package/dist/elements/Image.d.ts.map +1 -1
  54. package/dist/elements/Image.js +33 -6
  55. package/dist/elements/Image.js.map +1 -1
  56. package/dist/elements/ImageManager.d.ts +1 -0
  57. package/dist/elements/ImageManager.d.ts.map +1 -1
  58. package/dist/elements/ImageManager.js +11 -0
  59. package/dist/elements/ImageManager.js.map +1 -1
  60. package/dist/elements/Paragraph.d.ts +3 -0
  61. package/dist/elements/Paragraph.d.ts.map +1 -1
  62. package/dist/elements/Paragraph.js +91 -26
  63. package/dist/elements/Paragraph.js.map +1 -1
  64. package/dist/elements/Revision.d.ts +4 -0
  65. package/dist/elements/Revision.d.ts.map +1 -1
  66. package/dist/elements/Revision.js +369 -29
  67. package/dist/elements/Revision.js.map +1 -1
  68. package/dist/elements/Run.d.ts +1 -0
  69. package/dist/elements/Run.d.ts.map +1 -1
  70. package/dist/elements/Run.js +24 -4
  71. package/dist/elements/Run.js.map +1 -1
  72. package/dist/elements/Section.d.ts.map +1 -1
  73. package/dist/elements/Section.js +17 -7
  74. package/dist/elements/Section.js.map +1 -1
  75. package/dist/elements/Shape.d.ts.map +1 -1
  76. package/dist/elements/Shape.js +50 -42
  77. package/dist/elements/Shape.js.map +1 -1
  78. package/dist/elements/StructuredDocumentTag.d.ts +9 -1
  79. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  80. package/dist/elements/StructuredDocumentTag.js +46 -6
  81. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  82. package/dist/elements/Table.d.ts +2 -0
  83. package/dist/elements/Table.d.ts.map +1 -1
  84. package/dist/elements/Table.js +90 -42
  85. package/dist/elements/Table.js.map +1 -1
  86. package/dist/elements/TableCell.d.ts +1 -0
  87. package/dist/elements/TableCell.d.ts.map +1 -1
  88. package/dist/elements/TableCell.js +32 -13
  89. package/dist/elements/TableCell.js.map +1 -1
  90. package/dist/elements/TableOfContents.d.ts.map +1 -1
  91. package/dist/elements/TableOfContents.js +5 -7
  92. package/dist/elements/TableOfContents.js.map +1 -1
  93. package/dist/elements/TableRow.d.ts +1 -0
  94. package/dist/elements/TableRow.d.ts.map +1 -1
  95. package/dist/elements/TableRow.js +16 -1
  96. package/dist/elements/TableRow.js.map +1 -1
  97. package/dist/elements/TextBox.d.ts.map +1 -1
  98. package/dist/elements/TextBox.js +55 -42
  99. package/dist/elements/TextBox.js.map +1 -1
  100. package/dist/esm/core/Document.js +726 -171
  101. package/dist/esm/core/Document.js.map +1 -1
  102. package/dist/esm/core/DocumentGenerator.js +77 -25
  103. package/dist/esm/core/DocumentGenerator.js.map +1 -1
  104. package/dist/esm/core/DocumentParser.js +431 -85
  105. package/dist/esm/core/DocumentParser.js.map +1 -1
  106. package/dist/esm/core/RelationshipManager.js +2 -2
  107. package/dist/esm/core/RelationshipManager.js.map +1 -1
  108. package/dist/esm/elements/Comment.js +67 -12
  109. package/dist/esm/elements/Comment.js.map +1 -1
  110. package/dist/esm/elements/CommentManager.js +74 -16
  111. package/dist/esm/elements/CommentManager.js.map +1 -1
  112. package/dist/esm/elements/EndnoteManager.js.map +1 -1
  113. package/dist/esm/elements/Field.js +77 -35
  114. package/dist/esm/elements/Field.js.map +1 -1
  115. package/dist/esm/elements/FieldHelpers.js +7 -4
  116. package/dist/esm/elements/FieldHelpers.js.map +1 -1
  117. package/dist/esm/elements/Footer.js +39 -53
  118. package/dist/esm/elements/Footer.js.map +1 -1
  119. package/dist/esm/elements/FootnoteManager.js.map +1 -1
  120. package/dist/esm/elements/Header.js +39 -53
  121. package/dist/esm/elements/Header.js.map +1 -1
  122. package/dist/esm/elements/HeaderFooterManager.js +36 -6
  123. package/dist/esm/elements/HeaderFooterManager.js.map +1 -1
  124. package/dist/esm/elements/Hyperlink.js +69 -44
  125. package/dist/esm/elements/Hyperlink.js.map +1 -1
  126. package/dist/esm/elements/Image.js +33 -6
  127. package/dist/esm/elements/Image.js.map +1 -1
  128. package/dist/esm/elements/ImageManager.js +11 -0
  129. package/dist/esm/elements/ImageManager.js.map +1 -1
  130. package/dist/esm/elements/Paragraph.js +91 -26
  131. package/dist/esm/elements/Paragraph.js.map +1 -1
  132. package/dist/esm/elements/Revision.js +369 -29
  133. package/dist/esm/elements/Revision.js.map +1 -1
  134. package/dist/esm/elements/Run.js +24 -4
  135. package/dist/esm/elements/Run.js.map +1 -1
  136. package/dist/esm/elements/Section.js +17 -7
  137. package/dist/esm/elements/Section.js.map +1 -1
  138. package/dist/esm/elements/Shape.js +50 -42
  139. package/dist/esm/elements/Shape.js.map +1 -1
  140. package/dist/esm/elements/StructuredDocumentTag.js +46 -6
  141. package/dist/esm/elements/StructuredDocumentTag.js.map +1 -1
  142. package/dist/esm/elements/Table.js +90 -42
  143. package/dist/esm/elements/Table.js.map +1 -1
  144. package/dist/esm/elements/TableCell.js +32 -13
  145. package/dist/esm/elements/TableCell.js.map +1 -1
  146. package/dist/esm/elements/TableOfContents.js +5 -7
  147. package/dist/esm/elements/TableOfContents.js.map +1 -1
  148. package/dist/esm/elements/TableRow.js +16 -1
  149. package/dist/esm/elements/TableRow.js.map +1 -1
  150. package/dist/esm/elements/TextBox.js +55 -42
  151. package/dist/esm/elements/TextBox.js.map +1 -1
  152. package/dist/esm/formatting/AbstractNumbering.js +1 -1
  153. package/dist/esm/formatting/AbstractNumbering.js.map +1 -1
  154. package/dist/esm/formatting/NumberingLevel.js +8 -6
  155. package/dist/esm/formatting/NumberingLevel.js.map +1 -1
  156. package/dist/esm/formatting/NumberingManager.js +16 -1
  157. package/dist/esm/formatting/NumberingManager.js.map +1 -1
  158. package/dist/esm/formatting/Style.js +14 -0
  159. package/dist/esm/formatting/Style.js.map +1 -1
  160. package/dist/esm/formatting/StylesManager.js +14 -2
  161. package/dist/esm/formatting/StylesManager.js.map +1 -1
  162. package/dist/esm/helpers/CleanupHelper.js +103 -26
  163. package/dist/esm/helpers/CleanupHelper.js.map +1 -1
  164. package/dist/esm/images/ImageOptimizer.js +52 -4
  165. package/dist/esm/images/ImageOptimizer.js.map +1 -1
  166. package/dist/esm/index.js +1 -0
  167. package/dist/esm/index.js.map +1 -1
  168. package/dist/esm/internal.js +1 -1
  169. package/dist/esm/internal.js.map +1 -1
  170. package/dist/esm/processors/CompatibilityUpgrader.js +7 -1
  171. package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -1
  172. package/dist/esm/processors/InMemoryRevisionAcceptor.js +134 -13
  173. package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -1
  174. package/dist/esm/processors/RevisionWalker.js +58 -8
  175. package/dist/esm/processors/RevisionWalker.js.map +1 -1
  176. package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
  177. package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
  178. package/dist/esm/processors/cnfStyleDecoder.js +7 -7
  179. package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
  180. package/dist/esm/processors/stripTrackedChanges.js +16 -5
  181. package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
  182. package/dist/esm/utils/deepClone.js +24 -11
  183. package/dist/esm/utils/deepClone.js.map +1 -1
  184. package/dist/esm/utils/units.js +1 -1
  185. package/dist/esm/utils/units.js.map +1 -1
  186. package/dist/esm/utils/validation.js +11 -23
  187. package/dist/esm/utils/validation.js.map +1 -1
  188. package/dist/esm/utils/xmlSanitization.js +131 -7
  189. package/dist/esm/utils/xmlSanitization.js.map +1 -1
  190. package/dist/esm/validation/RevisionAutoFixer.js +69 -55
  191. package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
  192. package/dist/esm/validation/RevisionValidator.js +24 -20
  193. package/dist/esm/validation/RevisionValidator.js.map +1 -1
  194. package/dist/esm/xml/XMLBuilder.js +52 -20
  195. package/dist/esm/xml/XMLBuilder.js.map +1 -1
  196. package/dist/esm/xml/XMLParser.js +55 -13
  197. package/dist/esm/xml/XMLParser.js.map +1 -1
  198. package/dist/esm/zip/ZipHandler.js +37 -13
  199. package/dist/esm/zip/ZipHandler.js.map +1 -1
  200. package/dist/esm/zip/ZipReader.js +53 -6
  201. package/dist/esm/zip/ZipReader.js.map +1 -1
  202. package/dist/esm/zip/errors.js +7 -0
  203. package/dist/esm/zip/errors.js.map +1 -1
  204. package/dist/esm/zip/types.js +4 -0
  205. package/dist/esm/zip/types.js.map +1 -1
  206. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  207. package/dist/formatting/AbstractNumbering.js +1 -1
  208. package/dist/formatting/AbstractNumbering.js.map +1 -1
  209. package/dist/formatting/NumberingLevel.d.ts +1 -1
  210. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  211. package/dist/formatting/NumberingLevel.js +8 -6
  212. package/dist/formatting/NumberingLevel.js.map +1 -1
  213. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  214. package/dist/formatting/NumberingManager.js +16 -1
  215. package/dist/formatting/NumberingManager.js.map +1 -1
  216. package/dist/formatting/Style.d.ts.map +1 -1
  217. package/dist/formatting/Style.js +14 -0
  218. package/dist/formatting/Style.js.map +1 -1
  219. package/dist/formatting/StylesManager.d.ts +2 -0
  220. package/dist/formatting/StylesManager.d.ts.map +1 -1
  221. package/dist/formatting/StylesManager.js +14 -2
  222. package/dist/formatting/StylesManager.js.map +1 -1
  223. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  224. package/dist/helpers/CleanupHelper.js +103 -26
  225. package/dist/helpers/CleanupHelper.js.map +1 -1
  226. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  227. package/dist/images/ImageOptimizer.js +52 -4
  228. package/dist/images/ImageOptimizer.js.map +1 -1
  229. package/dist/index.d.ts +3 -1
  230. package/dist/index.d.ts.map +1 -1
  231. package/dist/index.js +4 -2
  232. package/dist/index.js.map +1 -1
  233. package/dist/internal.d.ts +1 -1
  234. package/dist/internal.d.ts.map +1 -1
  235. package/dist/internal.js +2 -1
  236. package/dist/internal.js.map +1 -1
  237. package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
  238. package/dist/processors/CompatibilityUpgrader.js +7 -1
  239. package/dist/processors/CompatibilityUpgrader.js.map +1 -1
  240. package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
  241. package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
  242. package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
  243. package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
  244. package/dist/processors/RevisionWalker.d.ts +2 -0
  245. package/dist/processors/RevisionWalker.d.ts.map +1 -1
  246. package/dist/processors/RevisionWalker.js +58 -8
  247. package/dist/processors/RevisionWalker.js.map +1 -1
  248. package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
  249. package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
  250. package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
  251. package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
  252. package/dist/processors/cnfStyleDecoder.js +7 -7
  253. package/dist/processors/cnfStyleDecoder.js.map +1 -1
  254. package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
  255. package/dist/processors/stripTrackedChanges.js +16 -5
  256. package/dist/processors/stripTrackedChanges.js.map +1 -1
  257. package/dist/utils/deepClone.d.ts.map +1 -1
  258. package/dist/utils/deepClone.js +24 -11
  259. package/dist/utils/deepClone.js.map +1 -1
  260. package/dist/utils/units.d.ts.map +1 -1
  261. package/dist/utils/units.js +1 -1
  262. package/dist/utils/units.js.map +1 -1
  263. package/dist/utils/validation.d.ts.map +1 -1
  264. package/dist/utils/validation.js +11 -23
  265. package/dist/utils/validation.js.map +1 -1
  266. package/dist/utils/xmlSanitization.d.ts +2 -0
  267. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  268. package/dist/utils/xmlSanitization.js +133 -7
  269. package/dist/utils/xmlSanitization.js.map +1 -1
  270. package/dist/validation/RevisionAutoFixer.d.ts +6 -2
  271. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  272. package/dist/validation/RevisionAutoFixer.js +68 -54
  273. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  274. package/dist/validation/RevisionValidator.d.ts +2 -0
  275. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  276. package/dist/validation/RevisionValidator.js +26 -21
  277. package/dist/validation/RevisionValidator.js.map +1 -1
  278. package/dist/xml/XMLBuilder.d.ts +1 -0
  279. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  280. package/dist/xml/XMLBuilder.js +52 -20
  281. package/dist/xml/XMLBuilder.js.map +1 -1
  282. package/dist/xml/XMLParser.d.ts +1 -0
  283. package/dist/xml/XMLParser.d.ts.map +1 -1
  284. package/dist/xml/XMLParser.js +55 -13
  285. package/dist/xml/XMLParser.js.map +1 -1
  286. package/dist/zip/ZipHandler.d.ts.map +1 -1
  287. package/dist/zip/ZipHandler.js +37 -13
  288. package/dist/zip/ZipHandler.js.map +1 -1
  289. package/dist/zip/ZipReader.d.ts.map +1 -1
  290. package/dist/zip/ZipReader.js +52 -5
  291. package/dist/zip/ZipReader.js.map +1 -1
  292. package/dist/zip/errors.d.ts +3 -0
  293. package/dist/zip/errors.d.ts.map +1 -1
  294. package/dist/zip/errors.js +9 -1
  295. package/dist/zip/errors.js.map +1 -1
  296. package/dist/zip/types.d.ts +4 -0
  297. package/dist/zip/types.d.ts.map +1 -1
  298. package/dist/zip/types.js +4 -0
  299. package/dist/zip/types.js.map +1 -1
  300. package/package.json +2 -2
  301. package/src/core/Document.ts +1159 -240
  302. package/src/core/DocumentGenerator.ts +106 -26
  303. package/src/core/DocumentParser.ts +692 -120
  304. package/src/core/RelationshipManager.ts +446 -442
  305. package/src/elements/Comment.ts +123 -18
  306. package/src/elements/CommentManager.ts +121 -22
  307. package/src/elements/EndnoteManager.ts +4 -1
  308. package/src/elements/Field.ts +134 -43
  309. package/src/elements/FieldHelpers.ts +20 -4
  310. package/src/elements/Footer.ts +62 -67
  311. package/src/elements/FootnoteManager.ts +4 -1
  312. package/src/elements/Header.ts +62 -67
  313. package/src/elements/HeaderFooterManager.ts +56 -8
  314. package/src/elements/Hyperlink.ts +150 -55
  315. package/src/elements/Image.ts +51 -7
  316. package/src/elements/ImageManager.ts +24 -0
  317. package/src/elements/Paragraph.ts +161 -40
  318. package/src/elements/Revision.ts +446 -47
  319. package/src/elements/Run.ts +44 -6
  320. package/src/elements/Section.ts +25 -9
  321. package/src/elements/Shape.ts +52 -44
  322. package/src/elements/StructuredDocumentTag.ts +83 -8
  323. package/src/elements/Table.ts +151 -50
  324. package/src/elements/TableCell.ts +57 -17
  325. package/src/elements/TableOfContents.ts +10 -9
  326. package/src/elements/TableRow.ts +35 -3
  327. package/src/elements/TextBox.ts +58 -43
  328. package/src/formatting/AbstractNumbering.ts +4 -2
  329. package/src/formatting/NumberingLevel.ts +50 -12
  330. package/src/formatting/NumberingManager.ts +32 -3
  331. package/src/formatting/Style.ts +14 -0
  332. package/src/formatting/StylesManager.ts +29 -2
  333. package/src/helpers/CleanupHelper.ts +122 -30
  334. package/src/images/ImageOptimizer.ts +64 -5
  335. package/src/index.ts +7 -0
  336. package/src/internal.ts +1 -0
  337. package/src/processors/CompatibilityUpgrader.ts +15 -2
  338. package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
  339. package/src/processors/RevisionWalker.ts +83 -7
  340. package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
  341. package/src/processors/cnfStyleDecoder.ts +18 -12
  342. package/src/processors/stripTrackedChanges.ts +47 -10
  343. package/src/utils/deepClone.ts +48 -14
  344. package/src/utils/units.ts +3 -1
  345. package/src/utils/validation.ts +30 -32
  346. package/src/utils/xmlSanitization.ts +218 -22
  347. package/src/validation/RevisionAutoFixer.ts +109 -65
  348. package/src/validation/RevisionValidator.ts +48 -23
  349. package/src/xml/XMLBuilder.ts +82 -22
  350. package/src/xml/XMLParser.ts +87 -20
  351. package/src/zip/ZipHandler.ts +61 -21
  352. package/src/zip/ZipReader.ts +135 -13
  353. package/src/zip/errors.ts +15 -0
  354. package/src/zip/types.ts +44 -2
@@ -1,442 +1,446 @@
1
- /**
2
- * RelationshipManager - Manages collections of relationships
3
- *
4
- * Handles relationship creation, tracking, and XML generation for various
5
- * document parts (document.xml, header.xml, footer.xml, etc.)
6
- */
7
-
8
- import { Relationship, RelationshipType } from './Relationship.js';
9
- import { XMLParser } from '../xml/XMLParser.js';
10
- import { sanitizeHyperlinkUrl } from '../utils/validation.js';
11
- import { InvalidDocxError, CorruptedArchiveError } from '../zip/errors.js';
12
-
13
- /**
14
- * Manages relationships for a document or document part
15
- */
16
- export class RelationshipManager {
17
- private relationships: Map<string, Relationship>;
18
- private nextId: number;
19
-
20
- /**
21
- * Creates a new relationship manager
22
- */
23
- constructor() {
24
- this.relationships = new Map();
25
- this.nextId = 1;
26
- }
27
-
28
- /**
29
- * Adds a relationship
30
- * @param relationship The relationship to add
31
- * @returns The relationship that was added
32
- */
33
- addRelationship(relationship: Relationship): Relationship {
34
- this.relationships.set(relationship.getId(), relationship);
35
-
36
- // Update next ID if necessary
37
- const idMatch = /^rId(\d+)$/.exec(relationship.getId());
38
- if (idMatch?.[1]) {
39
- const idNum = parseInt(idMatch[1], 10);
40
- if (idNum >= this.nextId) {
41
- this.nextId = idNum + 1;
42
- }
43
- }
44
-
45
- return relationship;
46
- }
47
-
48
- /**
49
- * Gets a relationship by ID
50
- * @param id The relationship ID
51
- */
52
- getRelationship(id: string): Relationship | undefined {
53
- return this.relationships.get(id);
54
- }
55
-
56
- /**
57
- * Gets all relationships
58
- */
59
- getAllRelationships(): Relationship[] {
60
- return Array.from(this.relationships.values());
61
- }
62
-
63
- /**
64
- * Gets relationships of a specific type
65
- * @param type The relationship type
66
- */
67
- getRelationshipsByType(type: string | RelationshipType): Relationship[] {
68
- return this.getAllRelationships().filter((rel) => rel.getType() === type);
69
- }
70
-
71
- /**
72
- * Checks if a relationship exists
73
- * @param id The relationship ID
74
- */
75
- hasRelationship(id: string): boolean {
76
- return this.relationships.has(id);
77
- }
78
-
79
- /**
80
- * Removes a relationship
81
- * @param id The relationship ID
82
- * @returns True if removed, false if not found
83
- */
84
- removeRelationship(id: string): boolean {
85
- return this.relationships.delete(id);
86
- }
87
-
88
- /**
89
- * Gets the number of relationships
90
- */
91
- getCount(): number {
92
- return this.relationships.size;
93
- }
94
-
95
- /**
96
- * Clears all relationships
97
- */
98
- clear(): this {
99
- this.relationships.clear();
100
- this.nextId = 1;
101
- return this;
102
- }
103
-
104
- /**
105
- * Generates a new unique relationship ID
106
- * @returns New relationship ID (e.g., 'rId1', 'rId2')
107
- */
108
- generateId(): string {
109
- return `rId${this.nextId++}`;
110
- }
111
-
112
- /**
113
- * Adds a styles relationship
114
- * @returns The created relationship
115
- */
116
- addStyles(): Relationship {
117
- const id = this.generateId();
118
- return this.addRelationship(Relationship.createStyles(id));
119
- }
120
-
121
- /**
122
- * Adds a numbering relationship
123
- * @returns The created relationship
124
- */
125
- addNumbering(): Relationship {
126
- const id = this.generateId();
127
- return this.addRelationship(Relationship.createNumbering(id));
128
- }
129
-
130
- /**
131
- * Adds a fontTable relationship
132
- * @returns The created relationship
133
- */
134
- addFontTable(): Relationship {
135
- const id = this.generateId();
136
- return this.addRelationship(Relationship.createFontTable(id));
137
- }
138
-
139
- /**
140
- * Adds a settings relationship
141
- * @returns The created relationship
142
- */
143
- addSettings(): Relationship {
144
- const id = this.generateId();
145
- return this.addRelationship(Relationship.createSettings(id));
146
- }
147
-
148
- /**
149
- * Adds a webSettings relationship
150
- * @returns The created relationship
151
- */
152
- addWebSettings(): Relationship {
153
- const id = this.generateId();
154
- return this.addRelationship(Relationship.createWebSettings(id));
155
- }
156
-
157
- /**
158
- * Adds a theme relationship
159
- * @returns The created relationship
160
- */
161
- addTheme(): Relationship {
162
- const id = this.generateId();
163
- return this.addRelationship(Relationship.createTheme(id));
164
- }
165
-
166
- /**
167
- * Adds an image relationship
168
- * @param target Image path relative to the part (e.g., 'media/image1.png')
169
- * @returns The created relationship
170
- */
171
- addImage(target: string): Relationship {
172
- const id = this.generateId();
173
- return this.addRelationship(Relationship.createImage(id, target));
174
- }
175
-
176
- /**
177
- * Adds a header relationship
178
- * @param target Header file path (e.g., 'header1.xml')
179
- * @returns The created relationship
180
- */
181
- addHeader(target: string): Relationship {
182
- const id = this.generateId();
183
- return this.addRelationship(Relationship.createHeader(id, target));
184
- }
185
-
186
- /**
187
- * Adds a footer relationship
188
- * @param target Footer file path (e.g., 'footer1.xml')
189
- * @returns The created relationship
190
- */
191
- addFooter(target: string): Relationship {
192
- const id = this.generateId();
193
- return this.addRelationship(Relationship.createFooter(id, target));
194
- }
195
-
196
- /**
197
- * Adds a hyperlink relationship
198
- * @param url The hyperlink URL
199
- * @returns The created relationship
200
- */
201
- addHyperlink(url: string): Relationship {
202
- const id = this.generateId();
203
- return this.addRelationship(Relationship.createHyperlink(id, url));
204
- }
205
-
206
- /**
207
- * Updates the target URL of an existing hyperlink relationship
208
- *
209
- * This method modifies an existing relationship's target in-place, maintaining
210
- * the same relationship ID. This is crucial for proper OpenXML compliance
211
- * per ECMA-376 §17.16.22, as it prevents orphaned relationships.
212
- *
213
- * @param relationshipId The ID of the relationship to update
214
- * @param newUrl The new URL to set
215
- * @returns True if updated, false if relationship not found
216
- */
217
- updateHyperlinkTarget(relationshipId: string, newUrl: string): boolean {
218
- const relationship = this.getRelationship(relationshipId);
219
- if (!relationship) {
220
- return false;
221
- }
222
-
223
- // Verify this is a hyperlink relationship
224
- if (relationship.getType() !== RelationshipType.HYPERLINK) {
225
- throw new InvalidDocxError(
226
- `Relationship ${relationshipId} is not a hyperlink relationship. ` +
227
- `Type is ${relationship.getType()}, expected ${RelationshipType.HYPERLINK}`
228
- );
229
- }
230
-
231
- // Update the target URL
232
- relationship.setTarget(newUrl);
233
- return true;
234
- }
235
-
236
- /**
237
- * Finds a hyperlink relationship by its target URL
238
- *
239
- * @param targetUrl The URL to search for
240
- * @returns The matching relationship, or undefined if not found
241
- */
242
- findHyperlinkByTarget(targetUrl: string): Relationship | undefined {
243
- return this.getAllRelationships().find(
244
- (rel) => rel.getType() === RelationshipType.HYPERLINK && rel.getTarget() === targetUrl
245
- );
246
- }
247
-
248
- /**
249
- * Gets or creates a hyperlink relationship for the given URL
250
- *
251
- * This method ensures we don't create duplicate relationships for the same URL.
252
- * If a relationship already exists for the URL, it returns the existing one.
253
- * Otherwise, it creates a new relationship.
254
- *
255
- * @param url The hyperlink URL
256
- * @returns The existing or newly created relationship
257
- */
258
- getOrCreateHyperlink(url: string): Relationship {
259
- // Check if relationship already exists for this URL
260
- const existing = this.findHyperlinkByTarget(url);
261
- if (existing) {
262
- return existing;
263
- }
264
-
265
- // Create new relationship
266
- return this.addHyperlink(url);
267
- }
268
-
269
- /**
270
- * Removes orphaned hyperlink relationships
271
- *
272
- * This method removes hyperlink relationships that are no longer referenced
273
- * by any hyperlink in the document. Call this after updating URLs to clean
274
- * up any orphaned relationships.
275
- *
276
- * @param referencedIds Set of relationship IDs that are still in use
277
- * @returns Number of relationships removed
278
- */
279
- removeOrphanedHyperlinks(referencedIds: Set<string>): number {
280
- let removed = 0;
281
- const toRemove: string[] = [];
282
-
283
- // Find orphaned relationships
284
- for (const rel of this.getAllRelationships()) {
285
- if (rel.getType() === RelationshipType.HYPERLINK && !referencedIds.has(rel.getId())) {
286
- toRemove.push(rel.getId());
287
- }
288
- }
289
-
290
- // Remove orphaned relationships
291
- for (const id of toRemove) {
292
- if (this.removeRelationship(id)) {
293
- removed++;
294
- }
295
- }
296
-
297
- return removed;
298
- }
299
-
300
- /**
301
- * Adds a comments relationship
302
- * @returns The created relationship
303
- */
304
- addComments(): Relationship {
305
- const id = this.generateId();
306
- return this.addRelationship(Relationship.createComments(id));
307
- }
308
-
309
- /**
310
- * Adds a footnotes relationship
311
- * @returns The created relationship
312
- */
313
- addFootnotes(): Relationship {
314
- const id = this.generateId();
315
- return this.addRelationship(Relationship.createFootnotes(id));
316
- }
317
-
318
- /**
319
- * Adds an endnotes relationship
320
- * @returns The created relationship
321
- */
322
- addEndnotes(): Relationship {
323
- const id = this.generateId();
324
- return this.addRelationship(Relationship.createEndnotes(id));
325
- }
326
-
327
- /**
328
- * Adds a people relationship (track changes authors)
329
- * @returns The created relationship
330
- */
331
- addPeople(): Relationship {
332
- const id = this.generateId();
333
- return this.addRelationship(Relationship.createPeople(id));
334
- }
335
-
336
- /**
337
- * Generates the relationships XML file content
338
- * @returns Complete XML string for .rels file
339
- */
340
- generateXml(): string {
341
- const relationships = this.getAllRelationships();
342
-
343
- let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
344
- xml += '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\n';
345
-
346
- for (const rel of relationships) {
347
- xml += rel.toXML() + '\n';
348
- }
349
-
350
- xml += '</Relationships>';
351
-
352
- return xml;
353
- }
354
-
355
- /**
356
- * Creates a new relationship manager with common document relationships
357
- * @returns RelationshipManager with styles, numbering, fontTable, settings, and theme relationships
358
- */
359
- static createForDocument(): RelationshipManager {
360
- const manager = new RelationshipManager();
361
- manager.addStyles();
362
- manager.addNumbering();
363
- manager.addFontTable();
364
- manager.addSettings();
365
- manager.addWebSettings();
366
- manager.addTheme();
367
- return manager;
368
- }
369
-
370
- /**
371
- * Creates an empty relationship manager
372
- * @returns Empty RelationshipManager
373
- */
374
- static create(): RelationshipManager {
375
- return new RelationshipManager();
376
- }
377
-
378
- /**
379
- * Parses relationships from XML string and creates a populated manager
380
- * @param xml The relationships XML content (.rels file)
381
- * @returns RelationshipManager with parsed relationships
382
- */
383
- static fromXml(xml: string): RelationshipManager {
384
- const manager = new RelationshipManager();
385
-
386
- // Prevent ReDoS: validate input size (typical .rels files are < 10KB, max 10MB)
387
- if (xml.length > 10000000) {
388
- throw new CorruptedArchiveError(
389
- 'Relationships XML file too large (>10MB). Possible malicious input or corrupted file.'
390
- );
391
- }
392
-
393
- // Use XMLParser to extract all Relationship elements
394
- const relationshipElements = XMLParser.extractElements(xml, 'Relationship');
395
-
396
- // Prevent infinite loops: check relationship count
397
- if (relationshipElements.length > 1000) {
398
- throw new CorruptedArchiveError(
399
- 'Too many relationships in XML file (>1000). Possible malicious input.'
400
- );
401
- }
402
-
403
- // Process each relationship element
404
- for (const relationshipElement of relationshipElements) {
405
- // Extract attributes using XMLParser
406
- const id = XMLParser.extractAttribute(relationshipElement, 'Id');
407
- const type = XMLParser.extractAttribute(relationshipElement, 'Type');
408
- const target = XMLParser.extractAttribute(relationshipElement, 'Target');
409
- const targetMode = XMLParser.extractAttribute(relationshipElement, 'TargetMode');
410
-
411
- // Only create relationship if all required attributes present
412
- if (id && type && target) {
413
- // Validate targetMode before type assertion
414
- const validatedTargetMode =
415
- targetMode === 'Internal' || targetMode === 'External' || targetMode === undefined
416
- ? targetMode
417
- : undefined;
418
-
419
- // Sanitize hyperlink URLs (strip browser extension prefixes)
420
- let sanitizedTarget = target;
421
- if (type.endsWith('/hyperlink')) {
422
- const result = sanitizeHyperlinkUrl(target);
423
- if (result) {
424
- sanitizedTarget = result.url;
425
- }
426
- }
427
-
428
- // Create and add relationship
429
- const relationship = Relationship.create({
430
- id,
431
- type,
432
- target: sanitizedTarget,
433
- targetMode: validatedTargetMode || 'Internal',
434
- });
435
-
436
- manager.addRelationship(relationship);
437
- }
438
- }
439
-
440
- return manager;
441
- }
442
- }
1
+ /**
2
+ * RelationshipManager - Manages collections of relationships
3
+ *
4
+ * Handles relationship creation, tracking, and XML generation for various
5
+ * document parts (document.xml, header.xml, footer.xml, etc.)
6
+ */
7
+
8
+ import { Relationship, RelationshipType } from './Relationship.js';
9
+ import { XMLParser } from '../xml/XMLParser.js';
10
+ import { sanitizeHyperlinkUrl } from '../utils/validation.js';
11
+ import { InvalidDocxError, CorruptedArchiveError } from '../zip/errors.js';
12
+
13
+ /**
14
+ * Manages relationships for a document or document part
15
+ */
16
+ export class RelationshipManager {
17
+ private relationships: Map<string, Relationship>;
18
+ private nextId: number;
19
+
20
+ /**
21
+ * Creates a new relationship manager
22
+ */
23
+ constructor() {
24
+ this.relationships = new Map();
25
+ this.nextId = 1;
26
+ }
27
+
28
+ /**
29
+ * Adds a relationship
30
+ * @param relationship The relationship to add
31
+ * @returns The relationship that was added
32
+ */
33
+ addRelationship(relationship: Relationship): Relationship {
34
+ this.relationships.set(relationship.getId(), relationship);
35
+
36
+ // Update next ID if necessary
37
+ const idMatch = /^rId(\d+)$/.exec(relationship.getId());
38
+ if (idMatch?.[1]) {
39
+ const idNum = parseInt(idMatch[1], 10);
40
+ if (idNum >= this.nextId) {
41
+ this.nextId = idNum + 1;
42
+ }
43
+ }
44
+
45
+ return relationship;
46
+ }
47
+
48
+ /**
49
+ * Gets a relationship by ID
50
+ * @param id The relationship ID
51
+ */
52
+ getRelationship(id: string): Relationship | undefined {
53
+ return this.relationships.get(id);
54
+ }
55
+
56
+ /**
57
+ * Gets all relationships
58
+ */
59
+ getAllRelationships(): Relationship[] {
60
+ return Array.from(this.relationships.values());
61
+ }
62
+
63
+ /**
64
+ * Gets relationships of a specific type
65
+ * @param type The relationship type
66
+ */
67
+ getRelationshipsByType(type: string | RelationshipType): Relationship[] {
68
+ return this.getAllRelationships().filter((rel) => rel.getType() === type);
69
+ }
70
+
71
+ /**
72
+ * Checks if a relationship exists
73
+ * @param id The relationship ID
74
+ */
75
+ hasRelationship(id: string): boolean {
76
+ return this.relationships.has(id);
77
+ }
78
+
79
+ /**
80
+ * Removes a relationship
81
+ * @param id The relationship ID
82
+ * @returns True if removed, false if not found
83
+ */
84
+ removeRelationship(id: string): boolean {
85
+ return this.relationships.delete(id);
86
+ }
87
+
88
+ /**
89
+ * Gets the number of relationships
90
+ */
91
+ getCount(): number {
92
+ return this.relationships.size;
93
+ }
94
+
95
+ /**
96
+ * Clears all relationships
97
+ */
98
+ clear(): this {
99
+ this.relationships.clear();
100
+ this.nextId = 1;
101
+ return this;
102
+ }
103
+
104
+ /**
105
+ * Generates a new unique relationship ID
106
+ * @returns New relationship ID (e.g., 'rId1', 'rId2')
107
+ */
108
+ generateId(): string {
109
+ return `rId${this.nextId++}`;
110
+ }
111
+
112
+ /**
113
+ * Adds a styles relationship
114
+ * @returns The created relationship
115
+ */
116
+ addStyles(): Relationship {
117
+ const id = this.generateId();
118
+ return this.addRelationship(Relationship.createStyles(id));
119
+ }
120
+
121
+ /**
122
+ * Adds a numbering relationship
123
+ * @returns The created relationship
124
+ */
125
+ addNumbering(): Relationship {
126
+ const id = this.generateId();
127
+ return this.addRelationship(Relationship.createNumbering(id));
128
+ }
129
+
130
+ /**
131
+ * Adds a fontTable relationship
132
+ * @returns The created relationship
133
+ */
134
+ addFontTable(): Relationship {
135
+ const id = this.generateId();
136
+ return this.addRelationship(Relationship.createFontTable(id));
137
+ }
138
+
139
+ /**
140
+ * Adds a settings relationship
141
+ * @returns The created relationship
142
+ */
143
+ addSettings(): Relationship {
144
+ const id = this.generateId();
145
+ return this.addRelationship(Relationship.createSettings(id));
146
+ }
147
+
148
+ /**
149
+ * Adds a webSettings relationship
150
+ * @returns The created relationship
151
+ */
152
+ addWebSettings(): Relationship {
153
+ const id = this.generateId();
154
+ return this.addRelationship(Relationship.createWebSettings(id));
155
+ }
156
+
157
+ /**
158
+ * Adds a theme relationship
159
+ * @returns The created relationship
160
+ */
161
+ addTheme(): Relationship {
162
+ const id = this.generateId();
163
+ return this.addRelationship(Relationship.createTheme(id));
164
+ }
165
+
166
+ /**
167
+ * Adds an image relationship
168
+ * @param target Image path relative to the part (e.g., 'media/image1.png')
169
+ * @returns The created relationship
170
+ */
171
+ addImage(target: string): Relationship {
172
+ const id = this.generateId();
173
+ return this.addRelationship(Relationship.createImage(id, target));
174
+ }
175
+
176
+ /**
177
+ * Adds a header relationship
178
+ * @param target Header file path (e.g., 'header1.xml')
179
+ * @returns The created relationship
180
+ */
181
+ addHeader(target: string): Relationship {
182
+ const id = this.generateId();
183
+ return this.addRelationship(Relationship.createHeader(id, target));
184
+ }
185
+
186
+ /**
187
+ * Adds a footer relationship
188
+ * @param target Footer file path (e.g., 'footer1.xml')
189
+ * @returns The created relationship
190
+ */
191
+ addFooter(target: string): Relationship {
192
+ const id = this.generateId();
193
+ return this.addRelationship(Relationship.createFooter(id, target));
194
+ }
195
+
196
+ /**
197
+ * Adds a hyperlink relationship
198
+ * @param url The hyperlink URL
199
+ * @returns The created relationship
200
+ */
201
+ addHyperlink(url: string): Relationship {
202
+ const id = this.generateId();
203
+ return this.addRelationship(Relationship.createHyperlink(id, url));
204
+ }
205
+
206
+ /**
207
+ * Updates the target URL of an existing hyperlink relationship
208
+ *
209
+ * This method modifies an existing relationship's target in-place, maintaining
210
+ * the same relationship ID. This is crucial for proper OpenXML compliance
211
+ * per ECMA-376 §17.16.22, as it prevents orphaned relationships.
212
+ *
213
+ * @param relationshipId The ID of the relationship to update
214
+ * @param newUrl The new URL to set
215
+ * @returns True if updated, false if relationship not found
216
+ */
217
+ updateHyperlinkTarget(relationshipId: string, newUrl: string): boolean {
218
+ const relationship = this.getRelationship(relationshipId);
219
+ if (!relationship) {
220
+ return false;
221
+ }
222
+
223
+ // Verify this is a hyperlink relationship
224
+ if (relationship.getType() !== RelationshipType.HYPERLINK) {
225
+ throw new InvalidDocxError(
226
+ `Relationship ${relationshipId} is not a hyperlink relationship. ` +
227
+ `Type is ${relationship.getType()}, expected ${RelationshipType.HYPERLINK}`
228
+ );
229
+ }
230
+
231
+ // Update the target URL
232
+ relationship.setTarget(newUrl);
233
+ return true;
234
+ }
235
+
236
+ /**
237
+ * Finds a hyperlink relationship by its target URL
238
+ *
239
+ * @param targetUrl The URL to search for
240
+ * @returns The matching relationship, or undefined if not found
241
+ */
242
+ findHyperlinkByTarget(targetUrl: string): Relationship | undefined {
243
+ return this.getAllRelationships().find(
244
+ (rel) => rel.getType() === RelationshipType.HYPERLINK && rel.getTarget() === targetUrl
245
+ );
246
+ }
247
+
248
+ /**
249
+ * Gets or creates a hyperlink relationship for the given URL
250
+ *
251
+ * This method ensures we don't create duplicate relationships for the same URL.
252
+ * If a relationship already exists for the URL, it returns the existing one.
253
+ * Otherwise, it creates a new relationship.
254
+ *
255
+ * @param url The hyperlink URL
256
+ * @returns The existing or newly created relationship
257
+ */
258
+ getOrCreateHyperlink(url: string): Relationship {
259
+ // Check if relationship already exists for this URL
260
+ const existing = this.findHyperlinkByTarget(url);
261
+ if (existing) {
262
+ return existing;
263
+ }
264
+
265
+ // Create new relationship
266
+ return this.addHyperlink(url);
267
+ }
268
+
269
+ /**
270
+ * Removes orphaned hyperlink relationships
271
+ *
272
+ * This method removes hyperlink relationships that are no longer referenced
273
+ * by any hyperlink in the document. Call this after updating URLs to clean
274
+ * up any orphaned relationships.
275
+ *
276
+ * @param referencedIds Set of relationship IDs that are still in use
277
+ * @returns Number of relationships removed
278
+ */
279
+ removeOrphanedHyperlinks(referencedIds: Set<string>): number {
280
+ let removed = 0;
281
+ const toRemove: string[] = [];
282
+
283
+ // Find orphaned relationships
284
+ for (const rel of this.getAllRelationships()) {
285
+ if (rel.getType() === RelationshipType.HYPERLINK && !referencedIds.has(rel.getId())) {
286
+ toRemove.push(rel.getId());
287
+ }
288
+ }
289
+
290
+ // Remove orphaned relationships
291
+ for (const id of toRemove) {
292
+ if (this.removeRelationship(id)) {
293
+ removed++;
294
+ }
295
+ }
296
+
297
+ return removed;
298
+ }
299
+
300
+ /**
301
+ * Adds a comments relationship
302
+ * @returns The created relationship
303
+ */
304
+ addComments(): Relationship {
305
+ const id = this.generateId();
306
+ return this.addRelationship(Relationship.createComments(id));
307
+ }
308
+
309
+ /**
310
+ * Adds a footnotes relationship
311
+ * @returns The created relationship
312
+ */
313
+ addFootnotes(): Relationship {
314
+ const id = this.generateId();
315
+ return this.addRelationship(Relationship.createFootnotes(id));
316
+ }
317
+
318
+ /**
319
+ * Adds an endnotes relationship
320
+ * @returns The created relationship
321
+ */
322
+ addEndnotes(): Relationship {
323
+ const id = this.generateId();
324
+ return this.addRelationship(Relationship.createEndnotes(id));
325
+ }
326
+
327
+ /**
328
+ * Adds a people relationship (track changes authors)
329
+ * @returns The created relationship
330
+ */
331
+ addPeople(): Relationship {
332
+ const id = this.generateId();
333
+ return this.addRelationship(Relationship.createPeople(id));
334
+ }
335
+
336
+ /**
337
+ * Generates the relationships XML file content
338
+ * @returns Complete XML string for .rels file
339
+ */
340
+ generateXml(): string {
341
+ const relationships = this.getAllRelationships();
342
+
343
+ let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
344
+ xml += '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\n';
345
+
346
+ for (const rel of relationships) {
347
+ xml += rel.toXML() + '\n';
348
+ }
349
+
350
+ xml += '</Relationships>';
351
+
352
+ return xml;
353
+ }
354
+
355
+ /**
356
+ * Creates a new relationship manager with common document relationships
357
+ * @returns RelationshipManager with styles, numbering, fontTable, settings, and theme relationships
358
+ */
359
+ static createForDocument(): RelationshipManager {
360
+ const manager = new RelationshipManager();
361
+ manager.addStyles();
362
+ manager.addNumbering();
363
+ manager.addFontTable();
364
+ manager.addSettings();
365
+ manager.addWebSettings();
366
+ manager.addTheme();
367
+ return manager;
368
+ }
369
+
370
+ /**
371
+ * Creates an empty relationship manager
372
+ * @returns Empty RelationshipManager
373
+ */
374
+ static create(): RelationshipManager {
375
+ return new RelationshipManager();
376
+ }
377
+
378
+ /**
379
+ * Parses relationships from XML string and creates a populated manager
380
+ * @param xml The relationships XML content (.rels file)
381
+ * @returns RelationshipManager with parsed relationships
382
+ */
383
+ static fromXml(xml: string): RelationshipManager {
384
+ const manager = new RelationshipManager();
385
+
386
+ // Prevent ReDoS: validate input size (typical .rels files are < 10KB, max 10MB)
387
+ if (xml.length > 10000000) {
388
+ throw new CorruptedArchiveError(
389
+ 'Relationships XML file too large (>10MB). Possible malicious input or corrupted file.'
390
+ );
391
+ }
392
+
393
+ // Use XMLParser to extract all Relationship elements
394
+ const relationshipElements = XMLParser.extractElements(xml, 'Relationship');
395
+
396
+ // The 10MB size cap above is the primary bound on the work performed here;
397
+ // this count check is a far-above-normal backstop. Link- and image-heavy
398
+ // documents (each unique hyperlink URL or image is one OPC relationship)
399
+ // legitimately exceed several thousand entries, so the limit must stay well
400
+ // clear of any realistic part.
401
+ if (relationshipElements.length > 100000) {
402
+ throw new CorruptedArchiveError(
403
+ 'Too many relationships in XML file (>100000). Possible malicious input or corrupted file.'
404
+ );
405
+ }
406
+
407
+ // Process each relationship element
408
+ for (const relationshipElement of relationshipElements) {
409
+ // Extract attributes using XMLParser
410
+ const id = XMLParser.extractAttribute(relationshipElement, 'Id');
411
+ const type = XMLParser.extractAttribute(relationshipElement, 'Type');
412
+ const target = XMLParser.extractAttribute(relationshipElement, 'Target');
413
+ const targetMode = XMLParser.extractAttribute(relationshipElement, 'TargetMode');
414
+
415
+ // Only create relationship if all required attributes present
416
+ if (id && type && target) {
417
+ // Validate targetMode before type assertion
418
+ const validatedTargetMode =
419
+ targetMode === 'Internal' || targetMode === 'External' || targetMode === undefined
420
+ ? targetMode
421
+ : undefined;
422
+
423
+ // Sanitize hyperlink URLs (strip browser extension prefixes)
424
+ let sanitizedTarget = target;
425
+ if (type.endsWith('/hyperlink')) {
426
+ const result = sanitizeHyperlinkUrl(target);
427
+ if (result) {
428
+ sanitizedTarget = result.url;
429
+ }
430
+ }
431
+
432
+ // Create and add relationship
433
+ const relationship = Relationship.create({
434
+ id,
435
+ type,
436
+ target: sanitizedTarget,
437
+ targetMode: validatedTargetMode || 'Internal',
438
+ });
439
+
440
+ manager.addRelationship(relationship);
441
+ }
442
+ }
443
+
444
+ return manager;
445
+ }
446
+ }