docxmlater 10.1.3 → 10.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +759 -754
  2. package/dist/constants/legacyCompatFlags.js +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.js.map +1 -1
  5. package/dist/core/Document.d.ts +50 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +483 -471
  8. package/dist/core/Document.js.map +1 -1
  9. package/dist/core/DocumentContent.d.ts +9 -9
  10. package/dist/core/DocumentContent.d.ts.map +1 -1
  11. package/dist/core/DocumentContent.js +1 -1
  12. package/dist/core/DocumentContent.js.map +1 -1
  13. package/dist/core/DocumentGenerator.d.ts +11 -11
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +251 -251
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.js.map +1 -1
  18. package/dist/core/DocumentParser.d.ts +15 -15
  19. package/dist/core/DocumentParser.d.ts.map +1 -1
  20. package/dist/core/DocumentParser.js +2123 -2155
  21. package/dist/core/DocumentParser.js.map +1 -1
  22. package/dist/core/DocumentValidator.d.ts.map +1 -1
  23. package/dist/core/DocumentValidator.js +2 -5
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.js.map +1 -1
  26. package/dist/core/RelationshipManager.d.ts.map +1 -1
  27. package/dist/core/RelationshipManager.js +3 -3
  28. package/dist/core/RelationshipManager.js.map +1 -1
  29. package/dist/elements/AlternateContent.js.map +1 -1
  30. package/dist/elements/Bookmark.d.ts.map +1 -1
  31. package/dist/elements/Bookmark.js +3 -1
  32. package/dist/elements/Bookmark.js.map +1 -1
  33. package/dist/elements/BookmarkManager.d.ts.map +1 -1
  34. package/dist/elements/BookmarkManager.js.map +1 -1
  35. package/dist/elements/Comment.d.ts.map +1 -1
  36. package/dist/elements/Comment.js +9 -6
  37. package/dist/elements/Comment.js.map +1 -1
  38. package/dist/elements/CommentManager.d.ts.map +1 -1
  39. package/dist/elements/CommentManager.js +18 -17
  40. package/dist/elements/CommentManager.js.map +1 -1
  41. package/dist/elements/CommonTypes.d.ts +21 -21
  42. package/dist/elements/CommonTypes.d.ts.map +1 -1
  43. package/dist/elements/CommonTypes.js +56 -56
  44. package/dist/elements/CommonTypes.js.map +1 -1
  45. package/dist/elements/CustomXml.js.map +1 -1
  46. package/dist/elements/Endnote.d.ts.map +1 -1
  47. package/dist/elements/Endnote.js +6 -6
  48. package/dist/elements/Endnote.js.map +1 -1
  49. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  50. package/dist/elements/EndnoteManager.js +6 -7
  51. package/dist/elements/EndnoteManager.js.map +1 -1
  52. package/dist/elements/Field.d.ts.map +1 -1
  53. package/dist/elements/Field.js +82 -25
  54. package/dist/elements/Field.js.map +1 -1
  55. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  56. package/dist/elements/FieldHelpers.js.map +1 -1
  57. package/dist/elements/FontManager.d.ts.map +1 -1
  58. package/dist/elements/FontManager.js +1 -1
  59. package/dist/elements/FontManager.js.map +1 -1
  60. package/dist/elements/Footer.js +2 -2
  61. package/dist/elements/Footer.js.map +1 -1
  62. package/dist/elements/Footnote.d.ts.map +1 -1
  63. package/dist/elements/Footnote.js +6 -6
  64. package/dist/elements/Footnote.js.map +1 -1
  65. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  66. package/dist/elements/FootnoteManager.js +6 -7
  67. package/dist/elements/FootnoteManager.js.map +1 -1
  68. package/dist/elements/Header.js +2 -2
  69. package/dist/elements/Header.js.map +1 -1
  70. package/dist/elements/HeaderFooterManager.js.map +1 -1
  71. package/dist/elements/Hyperlink.d.ts +5 -3
  72. package/dist/elements/Hyperlink.d.ts.map +1 -1
  73. package/dist/elements/Hyperlink.js +134 -76
  74. package/dist/elements/Hyperlink.js.map +1 -1
  75. package/dist/elements/Image.d.ts.map +1 -1
  76. package/dist/elements/Image.js +238 -106
  77. package/dist/elements/Image.js.map +1 -1
  78. package/dist/elements/ImageManager.d.ts.map +1 -1
  79. package/dist/elements/ImageManager.js +1 -1
  80. package/dist/elements/ImageManager.js.map +1 -1
  81. package/dist/elements/ImageRun.js +1 -1
  82. package/dist/elements/ImageRun.js.map +1 -1
  83. package/dist/elements/MathElement.js.map +1 -1
  84. package/dist/elements/Paragraph.d.ts +24 -24
  85. package/dist/elements/Paragraph.d.ts.map +1 -1
  86. package/dist/elements/Paragraph.js +181 -188
  87. package/dist/elements/Paragraph.js.map +1 -1
  88. package/dist/elements/PreservedElement.js.map +1 -1
  89. package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
  90. package/dist/elements/PropertyChangeTypes.js +6 -6
  91. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  92. package/dist/elements/RangeMarker.d.ts.map +1 -1
  93. package/dist/elements/RangeMarker.js.map +1 -1
  94. package/dist/elements/Revision.d.ts.map +1 -1
  95. package/dist/elements/Revision.js +4 -5
  96. package/dist/elements/Revision.js.map +1 -1
  97. package/dist/elements/RevisionContent.js.map +1 -1
  98. package/dist/elements/RevisionManager.d.ts.map +1 -1
  99. package/dist/elements/RevisionManager.js +40 -48
  100. package/dist/elements/RevisionManager.js.map +1 -1
  101. package/dist/elements/Run.d.ts +16 -16
  102. package/dist/elements/Run.d.ts.map +1 -1
  103. package/dist/elements/Run.js +256 -238
  104. package/dist/elements/Run.js.map +1 -1
  105. package/dist/elements/Section.d.ts.map +1 -1
  106. package/dist/elements/Section.js +36 -11
  107. package/dist/elements/Section.js.map +1 -1
  108. package/dist/elements/Shape.d.ts.map +1 -1
  109. package/dist/elements/Shape.js.map +1 -1
  110. package/dist/elements/StructuredDocumentTag.d.ts +6 -6
  111. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  112. package/dist/elements/StructuredDocumentTag.js +99 -104
  113. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  114. package/dist/elements/Table.d.ts +11 -11
  115. package/dist/elements/Table.d.ts.map +1 -1
  116. package/dist/elements/Table.js +102 -107
  117. package/dist/elements/Table.js.map +1 -1
  118. package/dist/elements/TableCell.d.ts +10 -10
  119. package/dist/elements/TableCell.d.ts.map +1 -1
  120. package/dist/elements/TableCell.js +105 -106
  121. package/dist/elements/TableCell.js.map +1 -1
  122. package/dist/elements/TableGridChange.d.ts.map +1 -1
  123. package/dist/elements/TableGridChange.js.map +1 -1
  124. package/dist/elements/TableOfContents.d.ts.map +1 -1
  125. package/dist/elements/TableOfContents.js +4 -4
  126. package/dist/elements/TableOfContents.js.map +1 -1
  127. package/dist/elements/TableOfContentsElement.js.map +1 -1
  128. package/dist/elements/TableRow.d.ts.map +1 -1
  129. package/dist/elements/TableRow.js +13 -6
  130. package/dist/elements/TableRow.js.map +1 -1
  131. package/dist/elements/TextBox.d.ts.map +1 -1
  132. package/dist/elements/TextBox.js +3 -5
  133. package/dist/elements/TextBox.js.map +1 -1
  134. package/dist/formatting/AbstractNumbering.d.ts +4 -4
  135. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  136. package/dist/formatting/AbstractNumbering.js +54 -49
  137. package/dist/formatting/AbstractNumbering.js.map +1 -1
  138. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  139. package/dist/formatting/NumberingInstance.js +1 -3
  140. package/dist/formatting/NumberingInstance.js.map +1 -1
  141. package/dist/formatting/NumberingLevel.d.ts +5 -5
  142. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  143. package/dist/formatting/NumberingLevel.js +119 -125
  144. package/dist/formatting/NumberingLevel.js.map +1 -1
  145. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  146. package/dist/formatting/NumberingManager.js +9 -9
  147. package/dist/formatting/NumberingManager.js.map +1 -1
  148. package/dist/formatting/Style.d.ts +11 -11
  149. package/dist/formatting/Style.d.ts.map +1 -1
  150. package/dist/formatting/Style.js +219 -247
  151. package/dist/formatting/Style.js.map +1 -1
  152. package/dist/formatting/StylesManager.d.ts +2 -2
  153. package/dist/formatting/StylesManager.d.ts.map +1 -1
  154. package/dist/formatting/StylesManager.js +96 -102
  155. package/dist/formatting/StylesManager.js.map +1 -1
  156. package/dist/helpers/CleanupHelper.d.ts +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  158. package/dist/helpers/CleanupHelper.js +6 -6
  159. package/dist/helpers/CleanupHelper.js.map +1 -1
  160. package/dist/images/ImageOptimizer.js +7 -7
  161. package/dist/images/ImageOptimizer.js.map +1 -1
  162. package/dist/index.d.ts +9 -9
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js.map +1 -1
  165. package/dist/managers/DrawingManager.js.map +1 -1
  166. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.js +23 -7
  168. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  169. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  170. package/dist/tracking/TrackingContext.js.map +1 -1
  171. package/dist/types/compatibility-types.js.map +1 -1
  172. package/dist/types/formatting.js.map +1 -1
  173. package/dist/types/list-types.d.ts +6 -6
  174. package/dist/types/list-types.js.map +1 -1
  175. package/dist/types/settings-types.js.map +1 -1
  176. package/dist/types/styleConfig.d.ts +2 -2
  177. package/dist/types/styleConfig.js.map +1 -1
  178. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  179. package/dist/utils/ChangelogGenerator.js +97 -101
  180. package/dist/utils/ChangelogGenerator.js.map +1 -1
  181. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.js +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  184. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  186. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  187. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  188. package/dist/utils/MoveOperationHelper.js +1 -1
  189. package/dist/utils/MoveOperationHelper.js.map +1 -1
  190. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.js +2 -4
  192. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  193. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  194. package/dist/utils/RevisionWalker.js +4 -12
  195. package/dist/utils/RevisionWalker.js.map +1 -1
  196. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  198. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  199. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  200. package/dist/utils/ShadingResolver.js +1 -1
  201. package/dist/utils/ShadingResolver.js.map +1 -1
  202. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  203. package/dist/utils/acceptRevisions.js +23 -12
  204. package/dist/utils/acceptRevisions.js.map +1 -1
  205. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  207. package/dist/utils/cnfStyleDecoder.js +40 -40
  208. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  209. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  210. package/dist/utils/corruptionDetection.js.map +1 -1
  211. package/dist/utils/dateFormatting.js.map +1 -1
  212. package/dist/utils/deepClone.js +1 -1
  213. package/dist/utils/deepClone.js.map +1 -1
  214. package/dist/utils/diagnostics.d.ts.map +1 -1
  215. package/dist/utils/diagnostics.js +1 -1
  216. package/dist/utils/diagnostics.js.map +1 -1
  217. package/dist/utils/errorHandling.js.map +1 -1
  218. package/dist/utils/formatting.d.ts.map +1 -1
  219. package/dist/utils/formatting.js +10 -2
  220. package/dist/utils/formatting.js.map +1 -1
  221. package/dist/utils/list-detection.d.ts +2 -2
  222. package/dist/utils/list-detection.d.ts.map +1 -1
  223. package/dist/utils/list-detection.js +21 -23
  224. package/dist/utils/list-detection.js.map +1 -1
  225. package/dist/utils/logger.d.ts.map +1 -1
  226. package/dist/utils/logger.js +12 -7
  227. package/dist/utils/logger.js.map +1 -1
  228. package/dist/utils/parsingHelpers.js.map +1 -1
  229. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  230. package/dist/utils/stripTrackedChanges.js +3 -3
  231. package/dist/utils/stripTrackedChanges.js.map +1 -1
  232. package/dist/utils/textDiff.d.ts +1 -1
  233. package/dist/utils/textDiff.js +8 -8
  234. package/dist/utils/textDiff.js.map +1 -1
  235. package/dist/utils/units.js.map +1 -1
  236. package/dist/utils/validation.d.ts.map +1 -1
  237. package/dist/utils/validation.js +24 -7
  238. package/dist/utils/validation.js.map +1 -1
  239. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  240. package/dist/utils/xmlSanitization.js +3 -3
  241. package/dist/utils/xmlSanitization.js.map +1 -1
  242. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.js +5 -5
  244. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  245. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  246. package/dist/validation/RevisionValidator.js +7 -9
  247. package/dist/validation/RevisionValidator.js.map +1 -1
  248. package/dist/validation/ValidationRules.js +3 -3
  249. package/dist/validation/ValidationRules.js.map +1 -1
  250. package/dist/validation/index.js.map +1 -1
  251. package/dist/xml/XMLBuilder.d.ts +1 -1
  252. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  253. package/dist/xml/XMLBuilder.js +98 -100
  254. package/dist/xml/XMLBuilder.js.map +1 -1
  255. package/dist/xml/XMLParser.d.ts.map +1 -1
  256. package/dist/xml/XMLParser.js +61 -66
  257. package/dist/xml/XMLParser.js.map +1 -1
  258. package/dist/zip/ZipHandler.d.ts.map +1 -1
  259. package/dist/zip/ZipHandler.js.map +1 -1
  260. package/dist/zip/ZipReader.d.ts.map +1 -1
  261. package/dist/zip/ZipReader.js +1 -3
  262. package/dist/zip/ZipReader.js.map +1 -1
  263. package/dist/zip/ZipWriter.d.ts +1 -1
  264. package/dist/zip/ZipWriter.d.ts.map +1 -1
  265. package/dist/zip/ZipWriter.js +28 -36
  266. package/dist/zip/ZipWriter.js.map +1 -1
  267. package/dist/zip/types.js +1 -1
  268. package/dist/zip/types.js.map +1 -1
  269. package/package.json +92 -92
  270. package/src/__tests__/helper-methods.test.ts +512 -512
  271. package/src/constants/legacyCompatFlags.ts +138 -138
  272. package/src/constants/limits.ts +50 -50
  273. package/src/core/Document.ts +985 -1145
  274. package/src/core/DocumentContent.ts +461 -467
  275. package/src/core/DocumentGenerator.ts +1133 -1104
  276. package/src/core/DocumentIdManager.ts +158 -158
  277. package/src/core/DocumentParser.ts +2347 -2716
  278. package/src/core/DocumentValidator.ts +363 -372
  279. package/src/core/Relationship.ts +367 -367
  280. package/src/core/RelationshipManager.ts +429 -428
  281. package/src/elements/AlternateContent.ts +42 -42
  282. package/src/elements/Bookmark.ts +212 -210
  283. package/src/elements/BookmarkManager.ts +247 -250
  284. package/src/elements/Comment.ts +356 -359
  285. package/src/elements/CommentManager.ts +499 -502
  286. package/src/elements/CommonTypes.ts +524 -549
  287. package/src/elements/CustomXml.ts +36 -36
  288. package/src/elements/Endnote.ts +221 -217
  289. package/src/elements/EndnoteManager.ts +246 -249
  290. package/src/elements/Field.ts +1292 -1233
  291. package/src/elements/FieldHelpers.ts +329 -333
  292. package/src/elements/FontManager.ts +336 -339
  293. package/src/elements/Footer.ts +269 -269
  294. package/src/elements/Footnote.ts +221 -217
  295. package/src/elements/FootnoteManager.ts +246 -249
  296. package/src/elements/Header.ts +269 -269
  297. package/src/elements/HeaderFooterManager.ts +219 -219
  298. package/src/elements/Hyperlink.ts +1288 -1193
  299. package/src/elements/Image.ts +1982 -1756
  300. package/src/elements/ImageManager.ts +437 -432
  301. package/src/elements/ImageRun.ts +59 -59
  302. package/src/elements/MathElement.ts +65 -65
  303. package/src/elements/Paragraph.ts +4347 -4287
  304. package/src/elements/PreservedElement.ts +53 -53
  305. package/src/elements/PropertyChangeTypes.ts +458 -442
  306. package/src/elements/RangeMarker.ts +382 -400
  307. package/src/elements/Revision.ts +1198 -1217
  308. package/src/elements/RevisionContent.ts +73 -73
  309. package/src/elements/RevisionManager.ts +1070 -1070
  310. package/src/elements/Run.ts +3103 -3073
  311. package/src/elements/Section.ts +1521 -1421
  312. package/src/elements/Shape.ts +884 -873
  313. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  314. package/src/elements/Table.ts +2468 -2524
  315. package/src/elements/TableCell.ts +1617 -1621
  316. package/src/elements/TableGridChange.ts +149 -151
  317. package/src/elements/TableOfContents.ts +701 -691
  318. package/src/elements/TableOfContentsElement.ts +89 -89
  319. package/src/elements/TableRow.ts +960 -929
  320. package/src/elements/TextBox.ts +766 -768
  321. package/src/formatting/AbstractNumbering.ts +580 -579
  322. package/src/formatting/NumberingInstance.ts +295 -299
  323. package/src/formatting/NumberingLevel.ts +981 -1040
  324. package/src/formatting/NumberingManager.ts +833 -827
  325. package/src/formatting/Style.ts +1785 -1879
  326. package/src/formatting/StylesManager.ts +1090 -1130
  327. package/src/helpers/CleanupHelper.ts +524 -524
  328. package/src/images/ImageOptimizer.ts +274 -274
  329. package/src/index.ts +559 -554
  330. package/src/managers/DrawingManager.ts +319 -319
  331. package/src/tracking/DocumentTrackingContext.ts +687 -674
  332. package/src/tracking/TrackingContext.ts +175 -173
  333. package/src/types/compatibility-types.ts +49 -49
  334. package/src/types/formatting.ts +210 -210
  335. package/src/types/list-types.ts +14 -14
  336. package/src/types/settings-types.ts +59 -59
  337. package/src/types/styleConfig.ts +189 -189
  338. package/src/utils/ChangelogGenerator.ts +1583 -1581
  339. package/src/utils/CompatibilityUpgrader.ts +235 -237
  340. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  341. package/src/utils/MoveOperationHelper.ts +233 -238
  342. package/src/utils/RevisionAwareProcessor.ts +518 -526
  343. package/src/utils/RevisionWalker.ts +427 -457
  344. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  345. package/src/utils/ShadingResolver.ts +105 -107
  346. package/src/utils/acceptRevisions.ts +723 -714
  347. package/src/utils/cnfStyleDecoder.ts +212 -217
  348. package/src/utils/corruptionDetection.ts +346 -345
  349. package/src/utils/dateFormatting.ts +20 -20
  350. package/src/utils/deepClone.ts +77 -78
  351. package/src/utils/diagnostics.ts +125 -129
  352. package/src/utils/errorHandling.ts +80 -80
  353. package/src/utils/formatting.ts +220 -213
  354. package/src/utils/list-detection.ts +32 -42
  355. package/src/utils/logger.ts +412 -404
  356. package/src/utils/parsingHelpers.ts +190 -190
  357. package/src/utils/stripTrackedChanges.ts +356 -353
  358. package/src/utils/textDiff.ts +100 -100
  359. package/src/utils/units.ts +421 -421
  360. package/src/utils/validation.ts +553 -542
  361. package/src/utils/xmlSanitization.ts +179 -182
  362. package/src/validation/RevisionAutoFixer.ts +541 -542
  363. package/src/validation/RevisionValidator.ts +470 -460
  364. package/src/validation/ValidationRules.ts +338 -338
  365. package/src/validation/index.ts +30 -30
  366. package/src/xml/XMLBuilder.ts +857 -871
  367. package/src/xml/XMLParser.ts +877 -919
  368. package/src/zip/ZipHandler.ts +629 -637
  369. package/src/zip/ZipReader.ts +295 -299
  370. package/src/zip/ZipWriter.ts +374 -390
  371. package/src/zip/types.ts +116 -116
@@ -1,637 +1,629 @@
1
- /**
2
- * ZipHandler - Main facade for ZIP archive operations
3
- * Provides a unified interface for reading and writing DOCX files
4
- */
5
-
6
- import { ZipReader } from './ZipReader';
7
- import { ZipWriter } from './ZipWriter';
8
- import {
9
- ZipFile,
10
- FileMap,
11
- LoadOptions,
12
- SaveOptions,
13
- AddFileOptions,
14
- SizeLimitOptions,
15
- DEFAULT_SIZE_LIMITS,
16
- } from './types';
17
- import { getGlobalLogger, createScopedLogger, ILogger } from '../utils/logger';
18
-
19
- // Create scoped logger for ZipHandler operations
20
- function getLogger(): ILogger {
21
- return createScopedLogger(getGlobalLogger(), 'ZipHandler');
22
- }
23
-
24
- /**
25
- * Main class for handling ZIP archives (DOCX files)
26
- * Combines reading and writing operations into a single interface
27
- */
28
- export class ZipHandler {
29
- private reader: ZipReader;
30
- private writer: ZipWriter;
31
- private mode: 'read' | 'write' | 'modify' = 'write';
32
-
33
- constructor() {
34
- this.reader = new ZipReader();
35
- this.writer = new ZipWriter();
36
- }
37
-
38
- // ==================== SIZE VALIDATION ====================
39
-
40
- /**
41
- * Validates document size against configured limits
42
- * @param sizeMB - Size in megabytes
43
- * @param limits - Size limit options (merged with defaults)
44
- * @throws Error if size exceeds maximum
45
- */
46
- private validateDocumentSize(sizeMB: number, limits: Required<SizeLimitOptions>): void {
47
- const logger = getLogger();
48
- const { warningSizeMB, maxSizeMB } = limits;
49
-
50
- // Check maximum size (if enabled)
51
- if (maxSizeMB > 0 && sizeMB > maxSizeMB) {
52
- logger.error('Document exceeds maximum size', { sizeMB: sizeMB.toFixed(1), maxSizeMB });
53
- throw new Error(
54
- `Document size (${sizeMB.toFixed(1)}MB) exceeds maximum supported size (${maxSizeMB}MB). ` +
55
- `This would likely cause out-of-memory errors. Consider:\n` +
56
- `- Compressing/optimizing images\n` +
57
- `- Splitting into multiple documents\n` +
58
- `- Processing on a machine with more memory\n` +
59
- `- Increasing maxSizeMB via LoadOptions.sizeLimits (not recommended)`
60
- );
61
- }
62
-
63
- // Warn on large files (if enabled)
64
- if (warningSizeMB > 0 && sizeMB > warningSizeMB) {
65
- logger.warn('Large document detected', { sizeMB: sizeMB.toFixed(1), warningSizeMB });
66
- }
67
- }
68
-
69
- /**
70
- * Gets merged size limits from options and defaults
71
- * @param options - Load options that may contain size limits
72
- * @returns Merged size limits with all values defined
73
- */
74
- private getSizeLimits(options: LoadOptions): Required<SizeLimitOptions> {
75
- return {
76
- ...DEFAULT_SIZE_LIMITS,
77
- ...options.sizeLimits,
78
- };
79
- }
80
-
81
- // ==================== LOADING ====================
82
-
83
- /**
84
- * Loads a DOCX file from the filesystem
85
- * @param filePath - Path to the DOCX file
86
- * @param options - Load options (including configurable size limits)
87
- */
88
- async load(filePath: string, options: LoadOptions = {}): Promise<void> {
89
- const logger = getLogger();
90
- logger.info('Loading DOCX file', { path: filePath });
91
-
92
- // Check file size before loading
93
- const { promises: fs } = await import('fs');
94
- const stats = await fs.stat(filePath);
95
- const sizeMB = stats.size / (1024 * 1024);
96
-
97
- // Validate against configurable limits
98
- const limits = this.getSizeLimits(options);
99
- this.validateDocumentSize(sizeMB, limits);
100
-
101
- await this.reader.loadFromFile(filePath, options);
102
-
103
- // Copy all files from reader to writer for modification
104
- const files = this.reader.getAllFiles();
105
- this.writer.clear();
106
- this.writer.addFiles(files);
107
-
108
- this.mode = 'modify';
109
- logger.info('DOCX file loaded', { fileCount: files.size, sizeMB: sizeMB.toFixed(2) });
110
- }
111
-
112
- /**
113
- * Loads a DOCX file from a buffer
114
- * @param buffer - Buffer containing the DOCX data
115
- * @param options - Load options (including configurable size limits)
116
- */
117
- async loadFromBuffer(buffer: Buffer, options: LoadOptions = {}): Promise<void> {
118
- const logger = getLogger();
119
- logger.info('Loading DOCX from buffer', { bufferSize: buffer.length });
120
-
121
- // Check buffer size before loading
122
- const sizeMB = buffer.length / (1024 * 1024);
123
-
124
- // Validate against configurable limits
125
- const limits = this.getSizeLimits(options);
126
- this.validateDocumentSize(sizeMB, limits);
127
-
128
- await this.reader.loadFromBuffer(buffer, options);
129
-
130
- // Copy all files from reader to writer for modification
131
- const files = this.reader.getAllFiles();
132
- this.writer.clear();
133
- this.writer.addFiles(files);
134
-
135
- this.mode = 'modify';
136
- logger.info('DOCX buffer loaded', { fileCount: files.size, sizeMB: sizeMB.toFixed(2) });
137
- }
138
-
139
- // ==================== FILE OPERATIONS ====================
140
-
141
- /**
142
- * Adds a file to the archive
143
- * @param filePath - Path where the file will be stored in the archive
144
- * @param content - File content (string or Buffer)
145
- * @param options - Options for adding the file
146
- */
147
- addFile(
148
- filePath: string,
149
- content: string | Buffer,
150
- options: AddFileOptions = {}
151
- ): void {
152
- this.writer.addFile(filePath, content, options);
153
- }
154
-
155
- /**
156
- * Adds multiple files to the archive
157
- * @param files - Map of file paths to contents
158
- * @param options - Options for adding files
159
- */
160
- addFiles(files: FileMap, options: AddFileOptions = {}): void {
161
- this.writer.addFiles(files, options);
162
- }
163
-
164
- /**
165
- * Updates an existing file in the archive
166
- * @param filePath - Path to the file to update
167
- * @param content - New content
168
- * @param options - Options for updating the file
169
- * @returns True if the file was updated, false if it didn't exist
170
- */
171
- updateFile(
172
- filePath: string,
173
- content: string | Buffer,
174
- options: AddFileOptions = {}
175
- ): boolean {
176
- if (!this.hasFile(filePath)) {
177
- return false;
178
- }
179
- this.addFile(filePath, content, options);
180
- return true;
181
- }
182
-
183
- /**
184
- * Removes a file from the archive
185
- * @param filePath - Path to the file to remove
186
- * @returns True if the file was removed, false if it didn't exist
187
- */
188
- removeFile(filePath: string): boolean {
189
- return this.writer.removeFile(filePath);
190
- }
191
-
192
- /**
193
- * Gets a specific file from the archive
194
- * @param filePath - Path to the file within the archive
195
- * @returns The file data, or undefined if not found
196
- */
197
- getFile(filePath: string): ZipFile | undefined {
198
- return this.writer.getFile(filePath);
199
- }
200
-
201
- /**
202
- * Gets the content of a specific file as a string
203
- * @param filePath - Path to the file within the archive
204
- * @returns The file content as a UTF-8 string, or undefined if not found
205
- * @throws Error if attempting to convert a binary file to string (Issue #12 fix)
206
- *
207
- * **Encoding Note:**
208
- * - Returns UTF-8 decoded string content for text files
209
- * - All text content in DOCX files must be UTF-8 per OpenXML specification
210
- * - For binary files (images, fonts), throws an error to prevent garbage output
211
- * - Use getFileAsBuffer() for binary files instead
212
- */
213
- getFileAsString(filePath: string): string | undefined {
214
- const file = this.getFile(filePath);
215
- if (!file) {
216
- return undefined;
217
- }
218
-
219
- // Issue #12 fix: Prevent converting binary files to UTF-8 strings
220
- // This produces garbage output for images, fonts, etc.
221
- if (file.isBinary) {
222
- throw new Error(
223
- `Cannot convert binary file "${filePath}" to string. ` +
224
- `Binary files (images, fonts, etc.) cannot be safely converted to UTF-8 strings. ` +
225
- `Use getFileAsBuffer() instead to access the raw bytes.`
226
- );
227
- }
228
-
229
- // After ZipWriter fix (Issue #4), check actual content type instead of flag
230
- // Content from ZipWriter is always Buffer, content from ZipReader may be string
231
- if (Buffer.isBuffer(file.content)) {
232
- // Convert buffer to UTF-8 string
233
- return file.content.toString('utf8');
234
- }
235
-
236
- return file.content;
237
- }
238
-
239
- /**
240
- * Gets the content of a specific file as a buffer
241
- * @param filePath - Path to the file within the archive
242
- * @returns The file content as a Buffer, or undefined if not found
243
- *
244
- * **Encoding Note:**
245
- * - Returns Buffer with UTF-8 encoded content for text files
246
- * - For binary files, returns raw bytes unchanged
247
- * - All text content is guaranteed to be UTF-8 encoded
248
- */
249
- getFileAsBuffer(filePath: string): Buffer | undefined {
250
- const file = this.getFile(filePath);
251
- if (!file) {
252
- return undefined;
253
- }
254
-
255
- // After ZipWriter fix (Issue #4), check actual content type instead of flag
256
- // Content from ZipWriter is always Buffer, content from ZipReader may be string
257
- if (Buffer.isBuffer(file.content)) {
258
- return file.content;
259
- }
260
-
261
- // Encode string content as UTF-8 Buffer (for content from ZipReader)
262
- return Buffer.from(file.content, 'utf8');
263
- }
264
-
265
- /**
266
- * Gets all files from the archive
267
- * @returns Map of file paths to file data
268
- */
269
- getAllFiles(): FileMap {
270
- return this.writer.getAllFiles();
271
- }
272
-
273
- /**
274
- * Gets a list of all file paths in the archive
275
- * @returns Array of file paths
276
- */
277
- getFilePaths(): string[] {
278
- return this.writer.getFilePaths();
279
- }
280
-
281
- /**
282
- * Checks if a file exists in the archive
283
- * @param filePath - Path to check
284
- * @returns True if the file exists
285
- */
286
- hasFile(filePath: string): boolean {
287
- return this.writer.hasFile(filePath);
288
- }
289
-
290
- /**
291
- * Gets the number of files in the archive
292
- * @returns Number of files
293
- */
294
- getFileCount(): number {
295
- return this.writer.getFileCount();
296
- }
297
-
298
- // ==================== HELPER METHODS ====================
299
-
300
- /**
301
- * Renames a file in the archive
302
- * @param oldPath - Current path of the file
303
- * @param newPath - New path for the file
304
- * @returns True if the file was renamed, false if it didn't exist
305
- */
306
- renameFile(oldPath: string, newPath: string): boolean {
307
- const file = this.getFile(oldPath);
308
- if (!file) {
309
- return false;
310
- }
311
- this.addFile(newPath, file.content, {
312
- binary: file.isBinary,
313
- date: file.date,
314
- });
315
- this.removeFile(oldPath);
316
- return true;
317
- }
318
-
319
- /**
320
- * Copies a file within the archive
321
- * @param srcPath - Source file path
322
- * @param destPath - Destination file path
323
- * @returns True if the file was copied, false if source didn't exist
324
- */
325
- copyFile(srcPath: string, destPath: string): boolean {
326
- const file = this.getFile(srcPath);
327
- if (!file) {
328
- return false;
329
- }
330
- this.addFile(destPath, file.content, {
331
- binary: file.isBinary,
332
- date: file.date,
333
- });
334
- return true;
335
- }
336
-
337
- /**
338
- * Moves a file within the archive (copy and delete)
339
- * @param srcPath - Source file path
340
- * @param destPath - Destination file path
341
- * @returns True if the file was moved, false if source didn't exist
342
- */
343
- moveFile(srcPath: string, destPath: string): boolean {
344
- if (!this.copyFile(srcPath, destPath)) {
345
- return false;
346
- }
347
- this.removeFile(srcPath);
348
- return true;
349
- }
350
-
351
- /**
352
- * Checks if a file exists, throws if it doesn't
353
- * @param filePath - Path to check
354
- * @throws {Error} If file doesn't exist
355
- */
356
- existsOrThrow(filePath: string): void {
357
- if (!this.hasFile(filePath)) {
358
- throw new Error(`File not found in archive: ${filePath}`);
359
- }
360
- }
361
-
362
- /**
363
- * Removes multiple files from the archive
364
- * @param filePaths - Array of file paths to remove
365
- * @returns Number of files successfully removed
366
- */
367
- removeFiles(filePaths: string[]): number {
368
- let count = 0;
369
- for (const filePath of filePaths) {
370
- if (this.removeFile(filePath)) {
371
- count++;
372
- }
373
- }
374
- return count;
375
- }
376
-
377
- /**
378
- * Gets all files with a specific extension
379
- * @param extension - File extension (with or without leading dot)
380
- * @returns Array of files with the specified extension
381
- */
382
- getFilesByExtension(extension: string): ZipFile[] {
383
- const ext = extension.startsWith('.') ? extension : `.${extension}`;
384
- const files: ZipFile[] = [];
385
- for (const [path, file] of this.getAllFiles()) {
386
- if (path.toLowerCase().endsWith(ext.toLowerCase())) {
387
- files.push(file);
388
- }
389
- }
390
- return files;
391
- }
392
-
393
- /**
394
- * Gets the total uncompressed size of all files in the archive
395
- * @returns Total size in bytes
396
- */
397
- getTotalSize(): number {
398
- let totalSize = 0;
399
- for (const file of this.getAllFiles().values()) {
400
- totalSize += file.size;
401
- }
402
- return totalSize;
403
- }
404
-
405
- /**
406
- * Gets comprehensive statistics about the archive
407
- * @returns Statistics object
408
- */
409
- getStats(): {
410
- fileCount: number;
411
- totalSize: number;
412
- textFileCount: number;
413
- binaryFileCount: number;
414
- avgFileSize: number;
415
- } {
416
- let textCount = 0;
417
- let binaryCount = 0;
418
- const totalSize = this.getTotalSize();
419
- const fileCount = this.getFileCount();
420
-
421
- for (const file of this.getAllFiles().values()) {
422
- if (file.isBinary) {
423
- binaryCount++;
424
- } else {
425
- textCount++;
426
- }
427
- }
428
-
429
- return {
430
- fileCount,
431
- totalSize,
432
- textFileCount: textCount,
433
- binaryFileCount: binaryCount,
434
- avgFileSize: fileCount > 0 ? Math.round(totalSize / fileCount) : 0,
435
- };
436
- }
437
-
438
- /**
439
- * Checks if the archive is empty
440
- * @returns True if the archive has no files
441
- */
442
- isEmpty(): boolean {
443
- return this.getFileCount() === 0;
444
- }
445
-
446
- /**
447
- * Gets all text (non-binary) files from the archive
448
- * @returns Array of text files
449
- */
450
- getTextFiles(): ZipFile[] {
451
- const files: ZipFile[] = [];
452
- for (const file of this.getAllFiles().values()) {
453
- if (!file.isBinary) {
454
- files.push(file);
455
- }
456
- }
457
- return files;
458
- }
459
-
460
- /**
461
- * Gets all binary files from the archive
462
- * @returns Array of binary files
463
- */
464
- getBinaryFiles(): ZipFile[] {
465
- const files: ZipFile[] = [];
466
- for (const file of this.getAllFiles().values()) {
467
- if (file.isBinary) {
468
- files.push(file);
469
- }
470
- }
471
- return files;
472
- }
473
-
474
- /**
475
- * Gets all media files from the word/media/ directory
476
- * @returns Array of media files
477
- */
478
- getMediaFiles(): ZipFile[] {
479
- const files: ZipFile[] = [];
480
- for (const [path, file] of this.getAllFiles()) {
481
- if (path.startsWith('word/media/')) {
482
- files.push(file);
483
- }
484
- }
485
- return files;
486
- }
487
-
488
- /**
489
- * Exports a file from the archive to the filesystem
490
- * @param internalPath - Path of the file within the archive
491
- * @param outputPath - Path where the file will be saved
492
- */
493
- async exportFile(internalPath: string, outputPath: string): Promise<void> {
494
- const { promises: fs } = await import('fs');
495
- const content = this.getFileAsBuffer(internalPath);
496
- if (!content) {
497
- throw new Error(`File not found in archive: ${internalPath}`);
498
- }
499
- await fs.writeFile(outputPath, content);
500
- }
501
-
502
- /**
503
- * Imports a file from the filesystem into the archive
504
- * @param sourcePath - Path to the file on filesystem
505
- * @param internalPath - Path where the file will be stored in archive
506
- * @param options - Options for adding the file
507
- */
508
- async importFile(
509
- sourcePath: string,
510
- internalPath: string,
511
- options: AddFileOptions = {}
512
- ): Promise<void> {
513
- const { promises: fs } = await import('fs');
514
- const content = await fs.readFile(sourcePath);
515
- this.addFile(internalPath, content, {
516
- ...options,
517
- binary: options.binary !== undefined ? options.binary : true,
518
- });
519
- }
520
-
521
- // ==================== SAVING ====================
522
-
523
- /**
524
- * Saves the archive to a file
525
- * @param filePath - Path where the file will be saved
526
- * @param options - Save options
527
- */
528
- async save(filePath: string, options: SaveOptions = {}): Promise<void> {
529
- const logger = getLogger();
530
- logger.info('Saving DOCX file', { path: filePath, fileCount: this.getFileCount() });
531
- await this.writer.saveToFile(filePath, options);
532
- logger.info('DOCX file saved', { path: filePath });
533
- }
534
-
535
- /**
536
- * Generates the ZIP archive as a buffer
537
- * @param options - Save options
538
- * @returns Buffer containing the ZIP archive
539
- */
540
- async toBuffer(options: SaveOptions = {}): Promise<Buffer> {
541
- const logger = getLogger();
542
- logger.info('Generating DOCX buffer', { fileCount: this.getFileCount() });
543
- const buffer = await this.writer.toBuffer(options);
544
- logger.info('DOCX buffer generated', { bufferSize: buffer.length });
545
- return buffer;
546
- }
547
-
548
- // ==================== VALIDATION ====================
549
-
550
- /**
551
- * Validates the DOCX structure
552
- * @throws {MissingRequiredFileError} If required files are missing
553
- */
554
- validate(): void {
555
- const logger = getLogger();
556
- logger.info('Validating DOCX structure');
557
- this.writer.validate();
558
- logger.info('DOCX structure valid');
559
- }
560
-
561
- // ==================== UTILITY ====================
562
-
563
- /**
564
- * Creates a new empty archive
565
- */
566
- clear(): void {
567
- this.reader.clear();
568
- this.writer.clear();
569
- this.mode = 'write';
570
- }
571
-
572
- /**
573
- * Gets the current mode of the handler
574
- * @returns Current mode ('read', 'write', or 'modify')
575
- */
576
- getMode(): 'read' | 'write' | 'modify' {
577
- return this.mode;
578
- }
579
-
580
- /**
581
- * Checks if the archive has been loaded
582
- * @returns True if loaded
583
- */
584
- isLoaded(): boolean {
585
- return this.reader.isLoaded();
586
- }
587
-
588
- /**
589
- * Creates a clone of this handler with all its files
590
- * @returns A new ZipHandler instance with the same files
591
- */
592
- clone(): ZipHandler {
593
- const newHandler = new ZipHandler();
594
- newHandler.writer = this.writer.clone();
595
- newHandler.mode = this.mode;
596
- return newHandler;
597
- }
598
-
599
- // ==================== CONVENIENCE METHODS ====================
600
-
601
- /**
602
- * Reads a DOCX file, modifies it, and saves it back
603
- * @param inputPath - Path to the input DOCX file
604
- * @param outputPath - Path to save the modified file
605
- * @param modifier - Function that modifies the handler
606
- * @param loadOptions - Options for loading
607
- * @param saveOptions - Options for saving
608
- */
609
- static async modify(
610
- inputPath: string,
611
- outputPath: string,
612
- modifier: (handler: ZipHandler) => void | Promise<void>,
613
- loadOptions: LoadOptions = {},
614
- saveOptions: SaveOptions = {}
615
- ): Promise<void> {
616
- const handler = new ZipHandler();
617
- await handler.load(inputPath, loadOptions);
618
- await modifier(handler);
619
- await handler.save(outputPath, saveOptions);
620
- }
621
-
622
- /**
623
- * Creates a new DOCX file with the provided files
624
- * @param outputPath - Path to save the new file
625
- * @param files - Map of file paths to contents
626
- * @param saveOptions - Options for saving
627
- */
628
- static async create(
629
- outputPath: string,
630
- files: FileMap,
631
- saveOptions: SaveOptions = {}
632
- ): Promise<void> {
633
- const handler = new ZipHandler();
634
- handler.addFiles(files);
635
- await handler.save(outputPath, saveOptions);
636
- }
637
- }
1
+ /**
2
+ * ZipHandler - Main facade for ZIP archive operations
3
+ * Provides a unified interface for reading and writing DOCX files
4
+ */
5
+
6
+ import { ZipReader } from './ZipReader';
7
+ import { ZipWriter } from './ZipWriter';
8
+ import {
9
+ ZipFile,
10
+ FileMap,
11
+ LoadOptions,
12
+ SaveOptions,
13
+ AddFileOptions,
14
+ SizeLimitOptions,
15
+ DEFAULT_SIZE_LIMITS,
16
+ } from './types';
17
+ import { getGlobalLogger, createScopedLogger, ILogger } from '../utils/logger';
18
+
19
+ // Create scoped logger for ZipHandler operations
20
+ function getLogger(): ILogger {
21
+ return createScopedLogger(getGlobalLogger(), 'ZipHandler');
22
+ }
23
+
24
+ /**
25
+ * Main class for handling ZIP archives (DOCX files)
26
+ * Combines reading and writing operations into a single interface
27
+ */
28
+ export class ZipHandler {
29
+ private reader: ZipReader;
30
+ private writer: ZipWriter;
31
+ private mode: 'read' | 'write' | 'modify' = 'write';
32
+
33
+ constructor() {
34
+ this.reader = new ZipReader();
35
+ this.writer = new ZipWriter();
36
+ }
37
+
38
+ // ==================== SIZE VALIDATION ====================
39
+
40
+ /**
41
+ * Validates document size against configured limits
42
+ * @param sizeMB - Size in megabytes
43
+ * @param limits - Size limit options (merged with defaults)
44
+ * @throws Error if size exceeds maximum
45
+ */
46
+ private validateDocumentSize(sizeMB: number, limits: Required<SizeLimitOptions>): void {
47
+ const logger = getLogger();
48
+ const { warningSizeMB, maxSizeMB } = limits;
49
+
50
+ // Check maximum size (if enabled)
51
+ if (maxSizeMB > 0 && sizeMB > maxSizeMB) {
52
+ logger.error('Document exceeds maximum size', { sizeMB: sizeMB.toFixed(1), maxSizeMB });
53
+ throw new Error(
54
+ `Document size (${sizeMB.toFixed(1)}MB) exceeds maximum supported size (${maxSizeMB}MB). ` +
55
+ `This would likely cause out-of-memory errors. Consider:\n` +
56
+ `- Compressing/optimizing images\n` +
57
+ `- Splitting into multiple documents\n` +
58
+ `- Processing on a machine with more memory\n` +
59
+ `- Increasing maxSizeMB via LoadOptions.sizeLimits (not recommended)`
60
+ );
61
+ }
62
+
63
+ // Warn on large files (if enabled)
64
+ if (warningSizeMB > 0 && sizeMB > warningSizeMB) {
65
+ logger.warn('Large document detected', { sizeMB: sizeMB.toFixed(1), warningSizeMB });
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Gets merged size limits from options and defaults
71
+ * @param options - Load options that may contain size limits
72
+ * @returns Merged size limits with all values defined
73
+ */
74
+ private getSizeLimits(options: LoadOptions): Required<SizeLimitOptions> {
75
+ return {
76
+ ...DEFAULT_SIZE_LIMITS,
77
+ ...options.sizeLimits,
78
+ };
79
+ }
80
+
81
+ // ==================== LOADING ====================
82
+
83
+ /**
84
+ * Loads a DOCX file from the filesystem
85
+ * @param filePath - Path to the DOCX file
86
+ * @param options - Load options (including configurable size limits)
87
+ */
88
+ async load(filePath: string, options: LoadOptions = {}): Promise<void> {
89
+ const logger = getLogger();
90
+ logger.info('Loading DOCX file', { path: filePath });
91
+
92
+ // Check file size before loading
93
+ const { promises: fs } = await import('fs');
94
+ const stats = await fs.stat(filePath);
95
+ const sizeMB = stats.size / (1024 * 1024);
96
+
97
+ // Validate against configurable limits
98
+ const limits = this.getSizeLimits(options);
99
+ this.validateDocumentSize(sizeMB, limits);
100
+
101
+ await this.reader.loadFromFile(filePath, options);
102
+
103
+ // Copy all files from reader to writer for modification
104
+ const files = this.reader.getAllFiles();
105
+ this.writer.clear();
106
+ this.writer.addFiles(files);
107
+
108
+ this.mode = 'modify';
109
+ logger.info('DOCX file loaded', { fileCount: files.size, sizeMB: sizeMB.toFixed(2) });
110
+ }
111
+
112
+ /**
113
+ * Loads a DOCX file from a buffer
114
+ * @param buffer - Buffer containing the DOCX data
115
+ * @param options - Load options (including configurable size limits)
116
+ */
117
+ async loadFromBuffer(buffer: Buffer, options: LoadOptions = {}): Promise<void> {
118
+ const logger = getLogger();
119
+ logger.info('Loading DOCX from buffer', { bufferSize: buffer.length });
120
+
121
+ // Check buffer size before loading
122
+ const sizeMB = buffer.length / (1024 * 1024);
123
+
124
+ // Validate against configurable limits
125
+ const limits = this.getSizeLimits(options);
126
+ this.validateDocumentSize(sizeMB, limits);
127
+
128
+ await this.reader.loadFromBuffer(buffer, options);
129
+
130
+ // Copy all files from reader to writer for modification
131
+ const files = this.reader.getAllFiles();
132
+ this.writer.clear();
133
+ this.writer.addFiles(files);
134
+
135
+ this.mode = 'modify';
136
+ logger.info('DOCX buffer loaded', { fileCount: files.size, sizeMB: sizeMB.toFixed(2) });
137
+ }
138
+
139
+ // ==================== FILE OPERATIONS ====================
140
+
141
+ /**
142
+ * Adds a file to the archive
143
+ * @param filePath - Path where the file will be stored in the archive
144
+ * @param content - File content (string or Buffer)
145
+ * @param options - Options for adding the file
146
+ */
147
+ addFile(filePath: string, content: string | Buffer, options: AddFileOptions = {}): void {
148
+ this.writer.addFile(filePath, content, options);
149
+ }
150
+
151
+ /**
152
+ * Adds multiple files to the archive
153
+ * @param files - Map of file paths to contents
154
+ * @param options - Options for adding files
155
+ */
156
+ addFiles(files: FileMap, options: AddFileOptions = {}): void {
157
+ this.writer.addFiles(files, options);
158
+ }
159
+
160
+ /**
161
+ * Updates an existing file in the archive
162
+ * @param filePath - Path to the file to update
163
+ * @param content - New content
164
+ * @param options - Options for updating the file
165
+ * @returns True if the file was updated, false if it didn't exist
166
+ */
167
+ updateFile(filePath: string, content: string | Buffer, options: AddFileOptions = {}): boolean {
168
+ if (!this.hasFile(filePath)) {
169
+ return false;
170
+ }
171
+ this.addFile(filePath, content, options);
172
+ return true;
173
+ }
174
+
175
+ /**
176
+ * Removes a file from the archive
177
+ * @param filePath - Path to the file to remove
178
+ * @returns True if the file was removed, false if it didn't exist
179
+ */
180
+ removeFile(filePath: string): boolean {
181
+ return this.writer.removeFile(filePath);
182
+ }
183
+
184
+ /**
185
+ * Gets a specific file from the archive
186
+ * @param filePath - Path to the file within the archive
187
+ * @returns The file data, or undefined if not found
188
+ */
189
+ getFile(filePath: string): ZipFile | undefined {
190
+ return this.writer.getFile(filePath);
191
+ }
192
+
193
+ /**
194
+ * Gets the content of a specific file as a string
195
+ * @param filePath - Path to the file within the archive
196
+ * @returns The file content as a UTF-8 string, or undefined if not found
197
+ * @throws Error if attempting to convert a binary file to string (Issue #12 fix)
198
+ *
199
+ * **Encoding Note:**
200
+ * - Returns UTF-8 decoded string content for text files
201
+ * - All text content in DOCX files must be UTF-8 per OpenXML specification
202
+ * - For binary files (images, fonts), throws an error to prevent garbage output
203
+ * - Use getFileAsBuffer() for binary files instead
204
+ */
205
+ getFileAsString(filePath: string): string | undefined {
206
+ const file = this.getFile(filePath);
207
+ if (!file) {
208
+ return undefined;
209
+ }
210
+
211
+ // Issue #12 fix: Prevent converting binary files to UTF-8 strings
212
+ // This produces garbage output for images, fonts, etc.
213
+ if (file.isBinary) {
214
+ throw new Error(
215
+ `Cannot convert binary file "${filePath}" to string. ` +
216
+ `Binary files (images, fonts, etc.) cannot be safely converted to UTF-8 strings. ` +
217
+ `Use getFileAsBuffer() instead to access the raw bytes.`
218
+ );
219
+ }
220
+
221
+ // After ZipWriter fix (Issue #4), check actual content type instead of flag
222
+ // Content from ZipWriter is always Buffer, content from ZipReader may be string
223
+ if (Buffer.isBuffer(file.content)) {
224
+ // Convert buffer to UTF-8 string
225
+ return file.content.toString('utf8');
226
+ }
227
+
228
+ return file.content;
229
+ }
230
+
231
+ /**
232
+ * Gets the content of a specific file as a buffer
233
+ * @param filePath - Path to the file within the archive
234
+ * @returns The file content as a Buffer, or undefined if not found
235
+ *
236
+ * **Encoding Note:**
237
+ * - Returns Buffer with UTF-8 encoded content for text files
238
+ * - For binary files, returns raw bytes unchanged
239
+ * - All text content is guaranteed to be UTF-8 encoded
240
+ */
241
+ getFileAsBuffer(filePath: string): Buffer | undefined {
242
+ const file = this.getFile(filePath);
243
+ if (!file) {
244
+ return undefined;
245
+ }
246
+
247
+ // After ZipWriter fix (Issue #4), check actual content type instead of flag
248
+ // Content from ZipWriter is always Buffer, content from ZipReader may be string
249
+ if (Buffer.isBuffer(file.content)) {
250
+ return file.content;
251
+ }
252
+
253
+ // Encode string content as UTF-8 Buffer (for content from ZipReader)
254
+ return Buffer.from(file.content, 'utf8');
255
+ }
256
+
257
+ /**
258
+ * Gets all files from the archive
259
+ * @returns Map of file paths to file data
260
+ */
261
+ getAllFiles(): FileMap {
262
+ return this.writer.getAllFiles();
263
+ }
264
+
265
+ /**
266
+ * Gets a list of all file paths in the archive
267
+ * @returns Array of file paths
268
+ */
269
+ getFilePaths(): string[] {
270
+ return this.writer.getFilePaths();
271
+ }
272
+
273
+ /**
274
+ * Checks if a file exists in the archive
275
+ * @param filePath - Path to check
276
+ * @returns True if the file exists
277
+ */
278
+ hasFile(filePath: string): boolean {
279
+ return this.writer.hasFile(filePath);
280
+ }
281
+
282
+ /**
283
+ * Gets the number of files in the archive
284
+ * @returns Number of files
285
+ */
286
+ getFileCount(): number {
287
+ return this.writer.getFileCount();
288
+ }
289
+
290
+ // ==================== HELPER METHODS ====================
291
+
292
+ /**
293
+ * Renames a file in the archive
294
+ * @param oldPath - Current path of the file
295
+ * @param newPath - New path for the file
296
+ * @returns True if the file was renamed, false if it didn't exist
297
+ */
298
+ renameFile(oldPath: string, newPath: string): boolean {
299
+ const file = this.getFile(oldPath);
300
+ if (!file) {
301
+ return false;
302
+ }
303
+ this.addFile(newPath, file.content, {
304
+ binary: file.isBinary,
305
+ date: file.date,
306
+ });
307
+ this.removeFile(oldPath);
308
+ return true;
309
+ }
310
+
311
+ /**
312
+ * Copies a file within the archive
313
+ * @param srcPath - Source file path
314
+ * @param destPath - Destination file path
315
+ * @returns True if the file was copied, false if source didn't exist
316
+ */
317
+ copyFile(srcPath: string, destPath: string): boolean {
318
+ const file = this.getFile(srcPath);
319
+ if (!file) {
320
+ return false;
321
+ }
322
+ this.addFile(destPath, file.content, {
323
+ binary: file.isBinary,
324
+ date: file.date,
325
+ });
326
+ return true;
327
+ }
328
+
329
+ /**
330
+ * Moves a file within the archive (copy and delete)
331
+ * @param srcPath - Source file path
332
+ * @param destPath - Destination file path
333
+ * @returns True if the file was moved, false if source didn't exist
334
+ */
335
+ moveFile(srcPath: string, destPath: string): boolean {
336
+ if (!this.copyFile(srcPath, destPath)) {
337
+ return false;
338
+ }
339
+ this.removeFile(srcPath);
340
+ return true;
341
+ }
342
+
343
+ /**
344
+ * Checks if a file exists, throws if it doesn't
345
+ * @param filePath - Path to check
346
+ * @throws {Error} If file doesn't exist
347
+ */
348
+ existsOrThrow(filePath: string): void {
349
+ if (!this.hasFile(filePath)) {
350
+ throw new Error(`File not found in archive: ${filePath}`);
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Removes multiple files from the archive
356
+ * @param filePaths - Array of file paths to remove
357
+ * @returns Number of files successfully removed
358
+ */
359
+ removeFiles(filePaths: string[]): number {
360
+ let count = 0;
361
+ for (const filePath of filePaths) {
362
+ if (this.removeFile(filePath)) {
363
+ count++;
364
+ }
365
+ }
366
+ return count;
367
+ }
368
+
369
+ /**
370
+ * Gets all files with a specific extension
371
+ * @param extension - File extension (with or without leading dot)
372
+ * @returns Array of files with the specified extension
373
+ */
374
+ getFilesByExtension(extension: string): ZipFile[] {
375
+ const ext = extension.startsWith('.') ? extension : `.${extension}`;
376
+ const files: ZipFile[] = [];
377
+ for (const [path, file] of this.getAllFiles()) {
378
+ if (path.toLowerCase().endsWith(ext.toLowerCase())) {
379
+ files.push(file);
380
+ }
381
+ }
382
+ return files;
383
+ }
384
+
385
+ /**
386
+ * Gets the total uncompressed size of all files in the archive
387
+ * @returns Total size in bytes
388
+ */
389
+ getTotalSize(): number {
390
+ let totalSize = 0;
391
+ for (const file of this.getAllFiles().values()) {
392
+ totalSize += file.size;
393
+ }
394
+ return totalSize;
395
+ }
396
+
397
+ /**
398
+ * Gets comprehensive statistics about the archive
399
+ * @returns Statistics object
400
+ */
401
+ getStats(): {
402
+ fileCount: number;
403
+ totalSize: number;
404
+ textFileCount: number;
405
+ binaryFileCount: number;
406
+ avgFileSize: number;
407
+ } {
408
+ let textCount = 0;
409
+ let binaryCount = 0;
410
+ const totalSize = this.getTotalSize();
411
+ const fileCount = this.getFileCount();
412
+
413
+ for (const file of this.getAllFiles().values()) {
414
+ if (file.isBinary) {
415
+ binaryCount++;
416
+ } else {
417
+ textCount++;
418
+ }
419
+ }
420
+
421
+ return {
422
+ fileCount,
423
+ totalSize,
424
+ textFileCount: textCount,
425
+ binaryFileCount: binaryCount,
426
+ avgFileSize: fileCount > 0 ? Math.round(totalSize / fileCount) : 0,
427
+ };
428
+ }
429
+
430
+ /**
431
+ * Checks if the archive is empty
432
+ * @returns True if the archive has no files
433
+ */
434
+ isEmpty(): boolean {
435
+ return this.getFileCount() === 0;
436
+ }
437
+
438
+ /**
439
+ * Gets all text (non-binary) files from the archive
440
+ * @returns Array of text files
441
+ */
442
+ getTextFiles(): ZipFile[] {
443
+ const files: ZipFile[] = [];
444
+ for (const file of this.getAllFiles().values()) {
445
+ if (!file.isBinary) {
446
+ files.push(file);
447
+ }
448
+ }
449
+ return files;
450
+ }
451
+
452
+ /**
453
+ * Gets all binary files from the archive
454
+ * @returns Array of binary files
455
+ */
456
+ getBinaryFiles(): ZipFile[] {
457
+ const files: ZipFile[] = [];
458
+ for (const file of this.getAllFiles().values()) {
459
+ if (file.isBinary) {
460
+ files.push(file);
461
+ }
462
+ }
463
+ return files;
464
+ }
465
+
466
+ /**
467
+ * Gets all media files from the word/media/ directory
468
+ * @returns Array of media files
469
+ */
470
+ getMediaFiles(): ZipFile[] {
471
+ const files: ZipFile[] = [];
472
+ for (const [path, file] of this.getAllFiles()) {
473
+ if (path.startsWith('word/media/')) {
474
+ files.push(file);
475
+ }
476
+ }
477
+ return files;
478
+ }
479
+
480
+ /**
481
+ * Exports a file from the archive to the filesystem
482
+ * @param internalPath - Path of the file within the archive
483
+ * @param outputPath - Path where the file will be saved
484
+ */
485
+ async exportFile(internalPath: string, outputPath: string): Promise<void> {
486
+ const { promises: fs } = await import('fs');
487
+ const content = this.getFileAsBuffer(internalPath);
488
+ if (!content) {
489
+ throw new Error(`File not found in archive: ${internalPath}`);
490
+ }
491
+ await fs.writeFile(outputPath, content);
492
+ }
493
+
494
+ /**
495
+ * Imports a file from the filesystem into the archive
496
+ * @param sourcePath - Path to the file on filesystem
497
+ * @param internalPath - Path where the file will be stored in archive
498
+ * @param options - Options for adding the file
499
+ */
500
+ async importFile(
501
+ sourcePath: string,
502
+ internalPath: string,
503
+ options: AddFileOptions = {}
504
+ ): Promise<void> {
505
+ const { promises: fs } = await import('fs');
506
+ const content = await fs.readFile(sourcePath);
507
+ this.addFile(internalPath, content, {
508
+ ...options,
509
+ binary: options.binary !== undefined ? options.binary : true,
510
+ });
511
+ }
512
+
513
+ // ==================== SAVING ====================
514
+
515
+ /**
516
+ * Saves the archive to a file
517
+ * @param filePath - Path where the file will be saved
518
+ * @param options - Save options
519
+ */
520
+ async save(filePath: string, options: SaveOptions = {}): Promise<void> {
521
+ const logger = getLogger();
522
+ logger.info('Saving DOCX file', { path: filePath, fileCount: this.getFileCount() });
523
+ await this.writer.saveToFile(filePath, options);
524
+ logger.info('DOCX file saved', { path: filePath });
525
+ }
526
+
527
+ /**
528
+ * Generates the ZIP archive as a buffer
529
+ * @param options - Save options
530
+ * @returns Buffer containing the ZIP archive
531
+ */
532
+ async toBuffer(options: SaveOptions = {}): Promise<Buffer> {
533
+ const logger = getLogger();
534
+ logger.info('Generating DOCX buffer', { fileCount: this.getFileCount() });
535
+ const buffer = await this.writer.toBuffer(options);
536
+ logger.info('DOCX buffer generated', { bufferSize: buffer.length });
537
+ return buffer;
538
+ }
539
+
540
+ // ==================== VALIDATION ====================
541
+
542
+ /**
543
+ * Validates the DOCX structure
544
+ * @throws {MissingRequiredFileError} If required files are missing
545
+ */
546
+ validate(): void {
547
+ const logger = getLogger();
548
+ logger.info('Validating DOCX structure');
549
+ this.writer.validate();
550
+ logger.info('DOCX structure valid');
551
+ }
552
+
553
+ // ==================== UTILITY ====================
554
+
555
+ /**
556
+ * Creates a new empty archive
557
+ */
558
+ clear(): void {
559
+ this.reader.clear();
560
+ this.writer.clear();
561
+ this.mode = 'write';
562
+ }
563
+
564
+ /**
565
+ * Gets the current mode of the handler
566
+ * @returns Current mode ('read', 'write', or 'modify')
567
+ */
568
+ getMode(): 'read' | 'write' | 'modify' {
569
+ return this.mode;
570
+ }
571
+
572
+ /**
573
+ * Checks if the archive has been loaded
574
+ * @returns True if loaded
575
+ */
576
+ isLoaded(): boolean {
577
+ return this.reader.isLoaded();
578
+ }
579
+
580
+ /**
581
+ * Creates a clone of this handler with all its files
582
+ * @returns A new ZipHandler instance with the same files
583
+ */
584
+ clone(): ZipHandler {
585
+ const newHandler = new ZipHandler();
586
+ newHandler.writer = this.writer.clone();
587
+ newHandler.mode = this.mode;
588
+ return newHandler;
589
+ }
590
+
591
+ // ==================== CONVENIENCE METHODS ====================
592
+
593
+ /**
594
+ * Reads a DOCX file, modifies it, and saves it back
595
+ * @param inputPath - Path to the input DOCX file
596
+ * @param outputPath - Path to save the modified file
597
+ * @param modifier - Function that modifies the handler
598
+ * @param loadOptions - Options for loading
599
+ * @param saveOptions - Options for saving
600
+ */
601
+ static async modify(
602
+ inputPath: string,
603
+ outputPath: string,
604
+ modifier: (handler: ZipHandler) => void | Promise<void>,
605
+ loadOptions: LoadOptions = {},
606
+ saveOptions: SaveOptions = {}
607
+ ): Promise<void> {
608
+ const handler = new ZipHandler();
609
+ await handler.load(inputPath, loadOptions);
610
+ await modifier(handler);
611
+ await handler.save(outputPath, saveOptions);
612
+ }
613
+
614
+ /**
615
+ * Creates a new DOCX file with the provided files
616
+ * @param outputPath - Path to save the new file
617
+ * @param files - Map of file paths to contents
618
+ * @param saveOptions - Options for saving
619
+ */
620
+ static async create(
621
+ outputPath: string,
622
+ files: FileMap,
623
+ saveOptions: SaveOptions = {}
624
+ ): Promise<void> {
625
+ const handler = new ZipHandler();
626
+ handler.addFiles(files);
627
+ await handler.save(outputPath, saveOptions);
628
+ }
629
+ }