docxmlater 11.0.10 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +53 -13
  2. package/dist/core/Document.d.ts +29 -3
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +733 -170
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentGenerator.d.ts +1 -0
  7. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  8. package/dist/core/DocumentGenerator.js +77 -25
  9. package/dist/core/DocumentGenerator.js.map +1 -1
  10. package/dist/core/DocumentParser.d.ts +7 -3
  11. package/dist/core/DocumentParser.d.ts.map +1 -1
  12. package/dist/core/DocumentParser.js +427 -81
  13. package/dist/core/DocumentParser.js.map +1 -1
  14. package/dist/core/RelationshipManager.d.ts.map +1 -1
  15. package/dist/core/RelationshipManager.js +2 -2
  16. package/dist/core/RelationshipManager.js.map +1 -1
  17. package/dist/elements/Comment.d.ts +15 -1
  18. package/dist/elements/Comment.d.ts.map +1 -1
  19. package/dist/elements/Comment.js +67 -12
  20. package/dist/elements/Comment.js.map +1 -1
  21. package/dist/elements/CommentManager.d.ts +4 -0
  22. package/dist/elements/CommentManager.d.ts.map +1 -1
  23. package/dist/elements/CommentManager.js +74 -16
  24. package/dist/elements/CommentManager.js.map +1 -1
  25. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  26. package/dist/elements/EndnoteManager.js.map +1 -1
  27. package/dist/elements/Field.d.ts +10 -1
  28. package/dist/elements/Field.d.ts.map +1 -1
  29. package/dist/elements/Field.js +76 -34
  30. package/dist/elements/Field.js.map +1 -1
  31. package/dist/elements/FieldHelpers.d.ts +1 -0
  32. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  33. package/dist/elements/FieldHelpers.js +8 -4
  34. package/dist/elements/FieldHelpers.js.map +1 -1
  35. package/dist/elements/Footer.d.ts +3 -1
  36. package/dist/elements/Footer.d.ts.map +1 -1
  37. package/dist/elements/Footer.js +39 -53
  38. package/dist/elements/Footer.js.map +1 -1
  39. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  40. package/dist/elements/FootnoteManager.js.map +1 -1
  41. package/dist/elements/Header.d.ts +3 -1
  42. package/dist/elements/Header.d.ts.map +1 -1
  43. package/dist/elements/Header.js +39 -53
  44. package/dist/elements/Header.js.map +1 -1
  45. package/dist/elements/HeaderFooterManager.d.ts +4 -2
  46. package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
  47. package/dist/elements/HeaderFooterManager.js +36 -6
  48. package/dist/elements/HeaderFooterManager.js.map +1 -1
  49. package/dist/elements/Hyperlink.d.ts +6 -1
  50. package/dist/elements/Hyperlink.d.ts.map +1 -1
  51. package/dist/elements/Hyperlink.js +69 -44
  52. package/dist/elements/Hyperlink.js.map +1 -1
  53. package/dist/elements/Image.d.ts.map +1 -1
  54. package/dist/elements/Image.js +33 -6
  55. package/dist/elements/Image.js.map +1 -1
  56. package/dist/elements/ImageManager.d.ts +1 -0
  57. package/dist/elements/ImageManager.d.ts.map +1 -1
  58. package/dist/elements/ImageManager.js +11 -0
  59. package/dist/elements/ImageManager.js.map +1 -1
  60. package/dist/elements/Paragraph.d.ts +3 -0
  61. package/dist/elements/Paragraph.d.ts.map +1 -1
  62. package/dist/elements/Paragraph.js +91 -26
  63. package/dist/elements/Paragraph.js.map +1 -1
  64. package/dist/elements/Revision.d.ts +4 -0
  65. package/dist/elements/Revision.d.ts.map +1 -1
  66. package/dist/elements/Revision.js +369 -29
  67. package/dist/elements/Revision.js.map +1 -1
  68. package/dist/elements/Run.d.ts +1 -0
  69. package/dist/elements/Run.d.ts.map +1 -1
  70. package/dist/elements/Run.js +24 -4
  71. package/dist/elements/Run.js.map +1 -1
  72. package/dist/elements/Section.d.ts.map +1 -1
  73. package/dist/elements/Section.js +17 -7
  74. package/dist/elements/Section.js.map +1 -1
  75. package/dist/elements/Shape.d.ts.map +1 -1
  76. package/dist/elements/Shape.js +50 -42
  77. package/dist/elements/Shape.js.map +1 -1
  78. package/dist/elements/StructuredDocumentTag.d.ts +9 -1
  79. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  80. package/dist/elements/StructuredDocumentTag.js +46 -6
  81. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  82. package/dist/elements/Table.d.ts +2 -0
  83. package/dist/elements/Table.d.ts.map +1 -1
  84. package/dist/elements/Table.js +90 -42
  85. package/dist/elements/Table.js.map +1 -1
  86. package/dist/elements/TableCell.d.ts +1 -0
  87. package/dist/elements/TableCell.d.ts.map +1 -1
  88. package/dist/elements/TableCell.js +32 -13
  89. package/dist/elements/TableCell.js.map +1 -1
  90. package/dist/elements/TableOfContents.d.ts.map +1 -1
  91. package/dist/elements/TableOfContents.js +5 -7
  92. package/dist/elements/TableOfContents.js.map +1 -1
  93. package/dist/elements/TableRow.d.ts +1 -0
  94. package/dist/elements/TableRow.d.ts.map +1 -1
  95. package/dist/elements/TableRow.js +16 -1
  96. package/dist/elements/TableRow.js.map +1 -1
  97. package/dist/elements/TextBox.d.ts.map +1 -1
  98. package/dist/elements/TextBox.js +55 -42
  99. package/dist/elements/TextBox.js.map +1 -1
  100. package/dist/esm/core/Document.js +735 -172
  101. package/dist/esm/core/Document.js.map +1 -1
  102. package/dist/esm/core/DocumentGenerator.js +77 -25
  103. package/dist/esm/core/DocumentGenerator.js.map +1 -1
  104. package/dist/esm/core/DocumentParser.js +427 -81
  105. package/dist/esm/core/DocumentParser.js.map +1 -1
  106. package/dist/esm/core/RelationshipManager.js +2 -2
  107. package/dist/esm/core/RelationshipManager.js.map +1 -1
  108. package/dist/esm/elements/Comment.js +67 -12
  109. package/dist/esm/elements/Comment.js.map +1 -1
  110. package/dist/esm/elements/CommentManager.js +74 -16
  111. package/dist/esm/elements/CommentManager.js.map +1 -1
  112. package/dist/esm/elements/EndnoteManager.js.map +1 -1
  113. package/dist/esm/elements/Field.js +77 -35
  114. package/dist/esm/elements/Field.js.map +1 -1
  115. package/dist/esm/elements/FieldHelpers.js +7 -4
  116. package/dist/esm/elements/FieldHelpers.js.map +1 -1
  117. package/dist/esm/elements/Footer.js +39 -53
  118. package/dist/esm/elements/Footer.js.map +1 -1
  119. package/dist/esm/elements/FootnoteManager.js.map +1 -1
  120. package/dist/esm/elements/Header.js +39 -53
  121. package/dist/esm/elements/Header.js.map +1 -1
  122. package/dist/esm/elements/HeaderFooterManager.js +36 -6
  123. package/dist/esm/elements/HeaderFooterManager.js.map +1 -1
  124. package/dist/esm/elements/Hyperlink.js +69 -44
  125. package/dist/esm/elements/Hyperlink.js.map +1 -1
  126. package/dist/esm/elements/Image.js +33 -6
  127. package/dist/esm/elements/Image.js.map +1 -1
  128. package/dist/esm/elements/ImageManager.js +11 -0
  129. package/dist/esm/elements/ImageManager.js.map +1 -1
  130. package/dist/esm/elements/Paragraph.js +91 -26
  131. package/dist/esm/elements/Paragraph.js.map +1 -1
  132. package/dist/esm/elements/Revision.js +369 -29
  133. package/dist/esm/elements/Revision.js.map +1 -1
  134. package/dist/esm/elements/Run.js +24 -4
  135. package/dist/esm/elements/Run.js.map +1 -1
  136. package/dist/esm/elements/Section.js +17 -7
  137. package/dist/esm/elements/Section.js.map +1 -1
  138. package/dist/esm/elements/Shape.js +50 -42
  139. package/dist/esm/elements/Shape.js.map +1 -1
  140. package/dist/esm/elements/StructuredDocumentTag.js +46 -6
  141. package/dist/esm/elements/StructuredDocumentTag.js.map +1 -1
  142. package/dist/esm/elements/Table.js +90 -42
  143. package/dist/esm/elements/Table.js.map +1 -1
  144. package/dist/esm/elements/TableCell.js +32 -13
  145. package/dist/esm/elements/TableCell.js.map +1 -1
  146. package/dist/esm/elements/TableOfContents.js +5 -7
  147. package/dist/esm/elements/TableOfContents.js.map +1 -1
  148. package/dist/esm/elements/TableRow.js +16 -1
  149. package/dist/esm/elements/TableRow.js.map +1 -1
  150. package/dist/esm/elements/TextBox.js +55 -42
  151. package/dist/esm/elements/TextBox.js.map +1 -1
  152. package/dist/esm/formatting/AbstractNumbering.js +1 -1
  153. package/dist/esm/formatting/AbstractNumbering.js.map +1 -1
  154. package/dist/esm/formatting/NumberingLevel.js +8 -6
  155. package/dist/esm/formatting/NumberingLevel.js.map +1 -1
  156. package/dist/esm/formatting/NumberingManager.js +16 -1
  157. package/dist/esm/formatting/NumberingManager.js.map +1 -1
  158. package/dist/esm/formatting/Style.js +14 -0
  159. package/dist/esm/formatting/Style.js.map +1 -1
  160. package/dist/esm/formatting/StylesManager.js +14 -2
  161. package/dist/esm/formatting/StylesManager.js.map +1 -1
  162. package/dist/esm/helpers/CleanupHelper.js +103 -26
  163. package/dist/esm/helpers/CleanupHelper.js.map +1 -1
  164. package/dist/esm/images/ImageOptimizer.js +52 -4
  165. package/dist/esm/images/ImageOptimizer.js.map +1 -1
  166. package/dist/esm/index.js +1 -0
  167. package/dist/esm/index.js.map +1 -1
  168. package/dist/esm/internal.js +1 -1
  169. package/dist/esm/internal.js.map +1 -1
  170. package/dist/esm/processors/CompatibilityUpgrader.js +7 -1
  171. package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -1
  172. package/dist/esm/processors/InMemoryRevisionAcceptor.js +134 -13
  173. package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -1
  174. package/dist/esm/processors/RevisionWalker.js +223 -39
  175. package/dist/esm/processors/RevisionWalker.js.map +1 -1
  176. package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
  177. package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
  178. package/dist/esm/processors/acceptRevisions.js +20 -11
  179. package/dist/esm/processors/acceptRevisions.js.map +1 -1
  180. package/dist/esm/processors/cnfStyleDecoder.js +7 -7
  181. package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
  182. package/dist/esm/processors/stripTrackedChanges.js +16 -5
  183. package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
  184. package/dist/esm/utils/deepClone.js +24 -11
  185. package/dist/esm/utils/deepClone.js.map +1 -1
  186. package/dist/esm/utils/units.js +1 -1
  187. package/dist/esm/utils/units.js.map +1 -1
  188. package/dist/esm/utils/validation.js +11 -23
  189. package/dist/esm/utils/validation.js.map +1 -1
  190. package/dist/esm/utils/xmlSanitization.js +131 -7
  191. package/dist/esm/utils/xmlSanitization.js.map +1 -1
  192. package/dist/esm/validation/RevisionAutoFixer.js +69 -55
  193. package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
  194. package/dist/esm/validation/RevisionValidator.js +24 -20
  195. package/dist/esm/validation/RevisionValidator.js.map +1 -1
  196. package/dist/esm/xml/XMLBuilder.js +52 -20
  197. package/dist/esm/xml/XMLBuilder.js.map +1 -1
  198. package/dist/esm/xml/XMLParser.js +55 -13
  199. package/dist/esm/xml/XMLParser.js.map +1 -1
  200. package/dist/esm/zip/ZipHandler.js +37 -13
  201. package/dist/esm/zip/ZipHandler.js.map +1 -1
  202. package/dist/esm/zip/ZipReader.js +53 -6
  203. package/dist/esm/zip/ZipReader.js.map +1 -1
  204. package/dist/esm/zip/errors.js +7 -0
  205. package/dist/esm/zip/errors.js.map +1 -1
  206. package/dist/esm/zip/types.js +4 -0
  207. package/dist/esm/zip/types.js.map +1 -1
  208. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  209. package/dist/formatting/AbstractNumbering.js +1 -1
  210. package/dist/formatting/AbstractNumbering.js.map +1 -1
  211. package/dist/formatting/NumberingLevel.d.ts +1 -1
  212. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  213. package/dist/formatting/NumberingLevel.js +8 -6
  214. package/dist/formatting/NumberingLevel.js.map +1 -1
  215. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  216. package/dist/formatting/NumberingManager.js +16 -1
  217. package/dist/formatting/NumberingManager.js.map +1 -1
  218. package/dist/formatting/Style.d.ts.map +1 -1
  219. package/dist/formatting/Style.js +14 -0
  220. package/dist/formatting/Style.js.map +1 -1
  221. package/dist/formatting/StylesManager.d.ts +2 -0
  222. package/dist/formatting/StylesManager.d.ts.map +1 -1
  223. package/dist/formatting/StylesManager.js +14 -2
  224. package/dist/formatting/StylesManager.js.map +1 -1
  225. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  226. package/dist/helpers/CleanupHelper.js +103 -26
  227. package/dist/helpers/CleanupHelper.js.map +1 -1
  228. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  229. package/dist/images/ImageOptimizer.js +52 -4
  230. package/dist/images/ImageOptimizer.js.map +1 -1
  231. package/dist/index.d.ts +3 -1
  232. package/dist/index.d.ts.map +1 -1
  233. package/dist/index.js +4 -2
  234. package/dist/index.js.map +1 -1
  235. package/dist/internal.d.ts +1 -1
  236. package/dist/internal.d.ts.map +1 -1
  237. package/dist/internal.js +2 -1
  238. package/dist/internal.js.map +1 -1
  239. package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
  240. package/dist/processors/CompatibilityUpgrader.js +7 -1
  241. package/dist/processors/CompatibilityUpgrader.js.map +1 -1
  242. package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
  243. package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
  244. package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
  245. package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
  246. package/dist/processors/RevisionWalker.d.ts +9 -3
  247. package/dist/processors/RevisionWalker.d.ts.map +1 -1
  248. package/dist/processors/RevisionWalker.js +223 -39
  249. package/dist/processors/RevisionWalker.js.map +1 -1
  250. package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
  251. package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
  252. package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
  253. package/dist/processors/acceptRevisions.d.ts +1 -0
  254. package/dist/processors/acceptRevisions.d.ts.map +1 -1
  255. package/dist/processors/acceptRevisions.js +21 -11
  256. package/dist/processors/acceptRevisions.js.map +1 -1
  257. package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
  258. package/dist/processors/cnfStyleDecoder.js +7 -7
  259. package/dist/processors/cnfStyleDecoder.js.map +1 -1
  260. package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
  261. package/dist/processors/stripTrackedChanges.js +16 -5
  262. package/dist/processors/stripTrackedChanges.js.map +1 -1
  263. package/dist/utils/deepClone.d.ts.map +1 -1
  264. package/dist/utils/deepClone.js +24 -11
  265. package/dist/utils/deepClone.js.map +1 -1
  266. package/dist/utils/units.d.ts.map +1 -1
  267. package/dist/utils/units.js +1 -1
  268. package/dist/utils/units.js.map +1 -1
  269. package/dist/utils/validation.d.ts.map +1 -1
  270. package/dist/utils/validation.js +11 -23
  271. package/dist/utils/validation.js.map +1 -1
  272. package/dist/utils/xmlSanitization.d.ts +2 -0
  273. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  274. package/dist/utils/xmlSanitization.js +133 -7
  275. package/dist/utils/xmlSanitization.js.map +1 -1
  276. package/dist/validation/RevisionAutoFixer.d.ts +6 -2
  277. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  278. package/dist/validation/RevisionAutoFixer.js +68 -54
  279. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  280. package/dist/validation/RevisionValidator.d.ts +2 -0
  281. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  282. package/dist/validation/RevisionValidator.js +26 -21
  283. package/dist/validation/RevisionValidator.js.map +1 -1
  284. package/dist/xml/XMLBuilder.d.ts +1 -0
  285. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  286. package/dist/xml/XMLBuilder.js +52 -20
  287. package/dist/xml/XMLBuilder.js.map +1 -1
  288. package/dist/xml/XMLParser.d.ts +1 -0
  289. package/dist/xml/XMLParser.d.ts.map +1 -1
  290. package/dist/xml/XMLParser.js +55 -13
  291. package/dist/xml/XMLParser.js.map +1 -1
  292. package/dist/zip/ZipHandler.d.ts.map +1 -1
  293. package/dist/zip/ZipHandler.js +37 -13
  294. package/dist/zip/ZipHandler.js.map +1 -1
  295. package/dist/zip/ZipReader.d.ts.map +1 -1
  296. package/dist/zip/ZipReader.js +52 -5
  297. package/dist/zip/ZipReader.js.map +1 -1
  298. package/dist/zip/errors.d.ts +3 -0
  299. package/dist/zip/errors.d.ts.map +1 -1
  300. package/dist/zip/errors.js +9 -1
  301. package/dist/zip/errors.js.map +1 -1
  302. package/dist/zip/types.d.ts +4 -0
  303. package/dist/zip/types.d.ts.map +1 -1
  304. package/dist/zip/types.js +4 -0
  305. package/dist/zip/types.js.map +1 -1
  306. package/package.json +2 -2
  307. package/src/core/Document.ts +1184 -242
  308. package/src/core/DocumentGenerator.ts +106 -26
  309. package/src/core/DocumentParser.ts +681 -114
  310. package/src/core/RelationshipManager.ts +446 -442
  311. package/src/elements/Comment.ts +123 -18
  312. package/src/elements/CommentManager.ts +121 -22
  313. package/src/elements/EndnoteManager.ts +4 -1
  314. package/src/elements/Field.ts +134 -43
  315. package/src/elements/FieldHelpers.ts +20 -4
  316. package/src/elements/Footer.ts +62 -67
  317. package/src/elements/FootnoteManager.ts +4 -1
  318. package/src/elements/Header.ts +62 -67
  319. package/src/elements/HeaderFooterManager.ts +56 -8
  320. package/src/elements/Hyperlink.ts +150 -55
  321. package/src/elements/Image.ts +51 -7
  322. package/src/elements/ImageManager.ts +24 -0
  323. package/src/elements/Paragraph.ts +161 -40
  324. package/src/elements/Revision.ts +446 -47
  325. package/src/elements/Run.ts +44 -6
  326. package/src/elements/Section.ts +25 -9
  327. package/src/elements/Shape.ts +52 -44
  328. package/src/elements/StructuredDocumentTag.ts +83 -8
  329. package/src/elements/Table.ts +151 -50
  330. package/src/elements/TableCell.ts +57 -17
  331. package/src/elements/TableOfContents.ts +10 -9
  332. package/src/elements/TableRow.ts +35 -3
  333. package/src/elements/TextBox.ts +58 -43
  334. package/src/formatting/AbstractNumbering.ts +4 -2
  335. package/src/formatting/NumberingLevel.ts +50 -12
  336. package/src/formatting/NumberingManager.ts +32 -3
  337. package/src/formatting/Style.ts +14 -0
  338. package/src/formatting/StylesManager.ts +29 -2
  339. package/src/helpers/CleanupHelper.ts +122 -30
  340. package/src/images/ImageOptimizer.ts +64 -5
  341. package/src/index.ts +7 -0
  342. package/src/internal.ts +1 -0
  343. package/src/processors/CompatibilityUpgrader.ts +15 -2
  344. package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
  345. package/src/processors/RevisionWalker.ts +390 -46
  346. package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
  347. package/src/processors/acceptRevisions.ts +67 -17
  348. package/src/processors/cnfStyleDecoder.ts +18 -12
  349. package/src/processors/stripTrackedChanges.ts +47 -10
  350. package/src/utils/deepClone.ts +48 -14
  351. package/src/utils/units.ts +3 -1
  352. package/src/utils/validation.ts +30 -32
  353. package/src/utils/xmlSanitization.ts +218 -22
  354. package/src/validation/RevisionAutoFixer.ts +109 -65
  355. package/src/validation/RevisionValidator.ts +48 -23
  356. package/src/xml/XMLBuilder.ts +82 -22
  357. package/src/xml/XMLParser.ts +87 -20
  358. package/src/zip/ZipHandler.ts +61 -21
  359. package/src/zip/ZipReader.ts +135 -13
  360. package/src/zip/errors.ts +15 -0
  361. package/src/zip/types.ts +44 -2
@@ -145,7 +145,9 @@ export function emusToPixels(emus: number, dpi: number = STANDARD_DPI): number {
145
145
  * @returns Value in twips
146
146
  */
147
147
  export function pointsToTwips(points: number): number {
148
- return points * UNITS.TWIPS_PER_POINT;
148
+ // Round to nearest integer — twips attributes (w:spacing, w:ind) require
149
+ // ST_DecimalNumber-based integer values
150
+ return Math.round(points * UNITS.TWIPS_PER_POINT);
149
151
  }
150
152
 
151
153
  /**
@@ -23,29 +23,28 @@ export function validateDocxStructure(filePaths: string[]): void {
23
23
 
24
24
  /**
25
25
  * Checks if a file path represents a binary file based on extension
26
+ *
27
+ * Uses a text-extension whitelist rather than a binary-extension whitelist:
28
+ * misclassifying text as binary only changes its in-memory representation
29
+ * (Buffer instead of string), but misclassifying binary as text corrupts the
30
+ * bytes through a lossy UTF-8 decode. OOXML packages can embed arbitrary
31
+ * binary parts (.xlsx/.pdf OLE packages, .odttf obfuscated fonts, .emz/.wmz
32
+ * compressed metafiles, extensionless parts), so anything not known to be
33
+ * text must be preserved byte-for-byte.
34
+ *
26
35
  * @param filePath - The file path to check
27
36
  * @returns True if the file is likely binary
28
37
  */
29
38
  export function isBinaryFile(filePath: string): boolean {
30
- const binaryExtensions = [
31
- '.png',
32
- '.jpg',
33
- '.jpeg',
34
- '.gif',
35
- '.bmp',
36
- '.tiff',
37
- '.ico',
38
- '.emf',
39
- '.wmf',
40
- '.bin',
41
- '.dat',
42
- '.ttf',
43
- '.otf',
44
- '.woff',
45
- ];
39
+ const textExtensions = ['.xml', '.rels', '.svg', '.txt', '.html', '.css', '.js'];
46
40
 
47
- const extension = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
48
- return binaryExtensions.includes(extension);
41
+ const dotIndex = filePath.lastIndexOf('.');
42
+ if (dotIndex === -1) {
43
+ return true;
44
+ }
45
+
46
+ const extension = filePath.substring(dotIndex).toLowerCase();
47
+ return !textExtensions.includes(extension);
49
48
  }
50
49
 
51
50
  /**
@@ -55,12 +54,15 @@ export function isBinaryFile(filePath: string): boolean {
55
54
  *
56
55
  * **Security:** This function validates paths to prevent:
57
56
  * - Path traversal attacks (../, ..\, URL-encoded variants)
58
- * - Absolute paths (C:\, /etc/, etc.)
57
+ * - Absolute Windows paths (C:\, D:, etc.) — rejected
59
58
  * - Malicious DOCX files attempting directory escape
60
59
  *
60
+ * Unix absolute paths (/etc/...) are sanitized to archive-relative paths
61
+ * by stripping leading slashes, not rejected.
62
+ *
61
63
  * @param path - The path to normalize
62
64
  * @returns Normalized path
63
- * @throws {Error} If path contains path traversal sequences, absolute paths, or URL-encoded attacks
65
+ * @throws {Error} If path contains path traversal sequences, absolute Windows paths, or URL-encoded attacks
64
66
  */
65
67
  export function normalizePath(path: string): string {
66
68
  // First convert all backslashes to forward slashes for consistent checking
@@ -97,15 +99,6 @@ export function normalizePath(path: string): string {
97
99
  );
98
100
  }
99
101
 
100
- // Security: Prevent Unix absolute paths
101
- // After removing leading slashes, if it starts with / it's suspicious
102
- if (path.startsWith('/') && normalized.startsWith('/')) {
103
- throw new Error(
104
- `Invalid file path: "${path}" appears to be an absolute Unix path. ` +
105
- `Only relative paths are allowed in DOCX archives.`
106
- );
107
- }
108
-
109
102
  return normalized;
110
103
  }
111
104
 
@@ -161,6 +154,11 @@ export function validateTwips(value: number, fieldName = 'value'): void {
161
154
  throw new Error(`${fieldName} must be a finite number, got ${value}`);
162
155
  }
163
156
 
157
+ // ST_TwipsMeasure/ST_SignedTwipsMeasure are ST_DecimalNumber-based — integers only
158
+ if (!Number.isInteger(value)) {
159
+ throw new Error(`${fieldName} must be an integer (in twips), got ${value}`);
160
+ }
161
+
164
162
  // Reasonable range: ±22 inches (31680 twips)
165
163
  const MIN_TWIPS = -31680;
166
164
  const MAX_TWIPS = 31680;
@@ -304,7 +302,7 @@ export function validateAlignment(
304
302
 
305
303
  /**
306
304
  * Validates a font size (in half-points for Word)
307
- * Reasonable range: 2-1638 (1-819 points)
305
+ * Reasonable range: 2-3276 (1-1638 points)
308
306
  * @param size - The font size in half-points to validate
309
307
  * @param fieldName - Name of the field (for error messages)
310
308
  * @throws {Error} If the size is invalid
@@ -318,9 +316,9 @@ export function validateFontSize(size: number, fieldName = 'font size'): void {
318
316
  throw new Error(`${fieldName} must be an integer (in half-points), got ${size}`);
319
317
  }
320
318
 
321
- // Reasonable range: 2-1638 half-points (1-819 points)
319
+ // Word's limit is 1-1638 points, i.e. 2-3276 half-points for w:sz (ST_HpsMeasure)
322
320
  const MIN_SIZE = 2;
323
- const MAX_SIZE = 1638;
321
+ const MAX_SIZE = 3276;
324
322
 
325
323
  if (size < MIN_SIZE || size > MAX_SIZE) {
326
324
  throw new Error(
@@ -9,11 +9,13 @@
9
9
  * - 0x09 (tab), 0x0A (newline), 0x0D (carriage return)
10
10
  * - 0x20-0xD7FF, 0xE000-0xFFFD, 0x10000-0x10FFFF
11
11
  *
12
- * Invalid characters (control characters that must be removed):
12
+ * Invalid characters that must be removed:
13
13
  * - 0x00-0x08 (NULL through BACKSPACE)
14
14
  * - 0x0B-0x0C (VERTICAL TAB and FORM FEED)
15
15
  * - 0x0E-0x1F (SHIFT OUT through UNIT SEPARATOR)
16
16
  * - 0x7F (DELETE)
17
+ * - 0xFFFE-0xFFFF (Unicode noncharacters excluded by the Char production)
18
+ * - Unpaired surrogates (0xD800-0xDFFF outside a valid high/low pair)
17
19
  *
18
20
  * @module xmlSanitization
19
21
  */
@@ -21,26 +23,40 @@
21
23
  import { getGlobalLogger } from './logger.js';
22
24
 
23
25
  /**
24
- * Regular expression matching invalid XML 1.0 control characters.
25
- * Matches: 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F
26
+ * Regular expression matching invalid XML 1.0 characters.
27
+ * Matches: 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F, 0xFFFE, 0xFFFF
26
28
  * Does NOT match valid chars: 0x09 (tab), 0x0A (newline), 0x0D (CR)
27
29
  */
28
- const INVALID_XML_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
30
+ const INVALID_XML_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F￾￿]/g;
29
31
 
30
32
  /**
31
- * Removes invalid XML 1.0 control characters from text.
33
+ * Matches surrogate code units that do not form a valid high/low pair.
34
+ * XML 1.0 Char excludes 0xD800-0xDFFF entirely; only properly paired
35
+ * surrogates (which decode to a single 0x10000-0x10FFFF character) are
36
+ * legal, so lone halves must be stripped or they corrupt the part at
37
+ * UTF-8 encode time.
38
+ */
39
+ const UNPAIRED_SURROGATE_REGEX =
40
+ /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
41
+
42
+ /**
43
+ * Removes invalid XML 1.0 characters from text.
32
44
  *
33
- * Per XML 1.0 spec, characters 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F are invalid
34
- * and cannot appear in XML documents. This function removes them.
45
+ * Per XML 1.0 spec, characters 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F,
46
+ * 0xFFFE, 0xFFFF, and unpaired surrogates are invalid and cannot appear
47
+ * in XML documents. This function removes them.
35
48
  *
36
49
  * Valid control characters are preserved:
37
50
  * - Tab (0x09)
38
51
  * - Line Feed / Newline (0x0A)
39
52
  * - Carriage Return (0x0D)
40
53
  *
54
+ * Properly paired surrogates (astral-plane characters such as emoji)
55
+ * are preserved.
56
+ *
41
57
  * @param text - Input text to sanitize
42
58
  * @param logWarning - If true, logs a warning when invalid chars are found (default: true)
43
- * @returns Sanitized text with invalid control characters removed
59
+ * @returns Sanitized text with invalid characters removed
44
60
  *
45
61
  * @example
46
62
  * ```typescript
@@ -54,31 +70,33 @@ const INVALID_XML_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
54
70
  * ```
55
71
  */
56
72
  export function removeInvalidXmlChars(text: string, logWarning = true): string {
57
- // Reset regex lastIndex for global regex
73
+ // Reset regex lastIndex for global regexes
58
74
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
75
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
59
76
 
60
- if (logWarning && INVALID_XML_CHAR_REGEX.test(text)) {
77
+ if (logWarning && (INVALID_XML_CHAR_REGEX.test(text) || UNPAIRED_SURROGATE_REGEX.test(text))) {
61
78
  // Reset regex lastIndex after test
62
79
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
80
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
63
81
 
64
82
  const invalidChars = findInvalidXmlChars(text);
65
83
  const hexCodes = invalidChars
66
84
  .map((c) => `0x${c.toString(16).toUpperCase().padStart(2, '0')}`)
67
85
  .join(', ');
68
- getGlobalLogger().warn(
69
- `[XMLSanitization] Removing invalid XML control characters: ${hexCodes}`
70
- );
86
+ getGlobalLogger().warn(`[XMLSanitization] Removing invalid XML characters: ${hexCodes}`);
71
87
  }
72
88
 
73
89
  // Reset regex lastIndex before replace
74
90
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
75
- return text.replace(INVALID_XML_CHAR_REGEX, '');
91
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
92
+ return text.replace(INVALID_XML_CHAR_REGEX, '').replace(UNPAIRED_SURROGATE_REGEX, '');
76
93
  }
77
94
 
78
95
  /**
79
- * Finds all invalid XML 1.0 control characters in text.
96
+ * Finds all invalid XML 1.0 characters in text.
80
97
  *
81
- * Returns an array of unique character codes that are invalid per XML 1.0 spec.
98
+ * Returns an array of unique character codes that are invalid per XML 1.0 spec,
99
+ * including 0xFFFE/0xFFFF noncharacters and unpaired surrogate code units.
82
100
  * This is useful for diagnostics and error reporting.
83
101
  *
84
102
  * @param text - Text to scan for invalid characters
@@ -100,12 +118,25 @@ export function findInvalidXmlChars(text: string): number[] {
100
118
  const code = text.charCodeAt(i);
101
119
 
102
120
  // Check if character is in invalid ranges
103
- if (
121
+ let isInvalid =
104
122
  (code >= 0x00 && code <= 0x08) || // NULL through BACKSPACE
105
123
  (code >= 0x0b && code <= 0x0c) || // VERTICAL TAB and FORM FEED
106
124
  (code >= 0x0e && code <= 0x1f) || // SHIFT OUT through UNIT SEPARATOR
107
- code === 0x7f // DELETE
108
- ) {
125
+ code === 0x7f || // DELETE
126
+ code === 0xfffe || // Noncharacter excluded by XML 1.0 Char
127
+ code === 0xffff; // Noncharacter excluded by XML 1.0 Char
128
+
129
+ if (!isInvalid && code >= 0xd800 && code <= 0xdbff) {
130
+ // High surrogate is only valid when followed by a low surrogate
131
+ const next = text.charCodeAt(i + 1); // NaN at end of string fails the range check
132
+ isInvalid = !(next >= 0xdc00 && next <= 0xdfff);
133
+ } else if (!isInvalid && code >= 0xdc00 && code <= 0xdfff) {
134
+ // Low surrogate is only valid when preceded by a high surrogate
135
+ const prev = text.charCodeAt(i - 1); // NaN at start of string fails the range check
136
+ isInvalid = !(prev >= 0xd800 && prev <= 0xdbff);
137
+ }
138
+
139
+ if (isInvalid) {
109
140
  // Only add unique codes
110
141
  if (!invalid.includes(code)) {
111
142
  invalid.push(code);
@@ -117,7 +148,7 @@ export function findInvalidXmlChars(text: string): number[] {
117
148
  }
118
149
 
119
150
  /**
120
- * Checks if text contains any invalid XML 1.0 control characters.
151
+ * Checks if text contains any invalid XML 1.0 characters.
121
152
  *
122
153
  * This is a fast check that returns true/false without identifying specific characters.
123
154
  * Use `findInvalidXmlChars()` if you need to know which characters are invalid.
@@ -133,9 +164,174 @@ export function findInvalidXmlChars(text: string): number[] {
133
164
  * ```
134
165
  */
135
166
  export function hasInvalidXmlChars(text: string): boolean {
136
- // Reset regex lastIndex for global regex
167
+ // Reset regex lastIndex for global regexes
137
168
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
138
- return INVALID_XML_CHAR_REGEX.test(text);
169
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
170
+ return INVALID_XML_CHAR_REGEX.test(text) || UNPAIRED_SURROGATE_REGEX.test(text);
171
+ }
172
+
173
+ /**
174
+ * CT_RPr child element order per ECMA-376 Part 1 §17.3.2.28 (local names,
175
+ * `w:` namespace). Used to repair raw-preserved run-property blocks whose
176
+ * children are out of schema order — Word rejects, for example, a `w:b`
177
+ * that appears after `w:color`/`w:sz` and reports the document as corrupt.
178
+ */
179
+ const CT_RPR_CHILD_ORDER: readonly string[] = [
180
+ 'rStyle',
181
+ 'rFonts',
182
+ 'b',
183
+ 'bCs',
184
+ 'i',
185
+ 'iCs',
186
+ 'caps',
187
+ 'smallCaps',
188
+ 'strike',
189
+ 'dstrike',
190
+ 'outline',
191
+ 'shadow',
192
+ 'emboss',
193
+ 'imprint',
194
+ 'noProof',
195
+ 'snapToGrid',
196
+ 'vanish',
197
+ 'webHidden',
198
+ 'color',
199
+ 'spacing',
200
+ 'w',
201
+ 'kern',
202
+ 'position',
203
+ 'sz',
204
+ 'szCs',
205
+ 'highlight',
206
+ 'u',
207
+ 'effect',
208
+ 'bdr',
209
+ 'shd',
210
+ 'fitText',
211
+ 'vertAlign',
212
+ 'rtl',
213
+ 'cs',
214
+ 'em',
215
+ 'lang',
216
+ 'eastAsianLayout',
217
+ 'specVanish',
218
+ 'oMath',
219
+ ];
220
+
221
+ /**
222
+ * Splits the inner content of an element into its top-level child elements,
223
+ * respecting nesting so that a child's descendants are kept with it. Returns
224
+ * the raw XML fragment for each direct child in document order. Non-element
225
+ * text between children (whitespace) is dropped, which is safe for the
226
+ * element-only content models this helper targets (e.g. `w:rPr`).
227
+ */
228
+ function splitTopLevelElements(inner: string): string[] {
229
+ const children: string[] = [];
230
+ const tagRegex = /<([A-Za-z][\w:.-]*)([^>]*?)(\/?)>|<\/([A-Za-z][\w:.-]*)\s*>/g;
231
+ let depth = 0;
232
+ let start = -1;
233
+ let match: RegExpExecArray | null;
234
+ while ((match = tagRegex.exec(inner)) !== null) {
235
+ const isClosing = match[4] !== undefined;
236
+ const isSelfClosing = match[3] === '/';
237
+ if (isClosing) {
238
+ if (depth > 0) {
239
+ depth--;
240
+ if (depth === 0 && start !== -1) {
241
+ children.push(inner.slice(start, tagRegex.lastIndex));
242
+ start = -1;
243
+ }
244
+ }
245
+ continue;
246
+ }
247
+ if (isSelfClosing) {
248
+ if (depth === 0) {
249
+ children.push(inner.slice(match.index, tagRegex.lastIndex));
250
+ }
251
+ continue;
252
+ }
253
+ // Opening tag of a paired element
254
+ if (depth === 0) start = match.index;
255
+ depth++;
256
+ }
257
+ return children;
258
+ }
259
+
260
+ /**
261
+ * Reorders the direct children of every `<w:rPr>` block in a raw XML part to
262
+ * the ECMA-376 CT_RPr sequence. Unknown/unmodeled children retain their
263
+ * relative position at the end of the block. The transform is idempotent:
264
+ * already-ordered run properties are returned unchanged.
265
+ *
266
+ * Some producers (and older versions of this framework) appended boolean
267
+ * toggles such as `w:b`/`w:bCs` to the end of an existing `w:rPr`, leaving
268
+ * them after `w:color`/`w:sz`. Word treats the out-of-order child as an
269
+ * unexpected element and flags the whole document as corrupt. Numbering
270
+ * definitions are written back verbatim for fidelity, so this repair runs on
271
+ * the preserved part before it is saved.
272
+ *
273
+ * @param xml - Raw XML for a part containing `w:rPr` blocks (e.g. numbering.xml)
274
+ * @returns XML with every `w:rPr` child sequence normalized to schema order
275
+ */
276
+ export function reorderRunPropertyChildren(xml: string): string {
277
+ if (!xml || !xml.includes('<w:rPr')) return xml;
278
+
279
+ const orderIndex = (fragment: string): number => {
280
+ const nameMatch = /^<w:(\w+)/.exec(fragment);
281
+ const name = nameMatch?.[1];
282
+ if (!name) return CT_RPR_CHILD_ORDER.length;
283
+ const idx = CT_RPR_CHILD_ORDER.indexOf(name);
284
+ return idx === -1 ? CT_RPR_CHILD_ORDER.length : idx;
285
+ };
286
+
287
+ // Only match non-empty <w:rPr>...</w:rPr> blocks; self-closing <w:rPr/> has
288
+ // no children to reorder.
289
+ return xml.replace(/<w:rPr(\s[^>]*)?>([\s\S]*?)<\/w:rPr>/g, (full, attrs, inner) => {
290
+ if (!inner || !(inner as string).includes('<')) return full;
291
+ const children = splitTopLevelElements(inner);
292
+ if (children.length < 2) return full;
293
+
294
+ const ordered = children
295
+ .map((frag, i) => ({ frag, i, order: orderIndex(frag) }))
296
+ // Stable sort: ties (including unknown children) keep original order.
297
+ .sort((a, b) => a.order - b.order || a.i - b.i);
298
+
299
+ if (ordered.every((c, i) => c.i === i)) return full; // already in order
300
+
301
+ return `<w:rPr${attrs ?? ''}>${ordered.map((c) => c.frag).join('')}</w:rPr>`;
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Normalizes malformed floating-drawing relative-size elements in a raw anchor
307
+ * fragment. Per ECMA-376 (DrawingML wordprocessing 2010 extensions),
308
+ * `wp14:sizeRelH` must contain a `wp14:pctWidth` child and `wp14:sizeRelV` a
309
+ * `wp14:pctHeight` child. Some producers emit the percentage as bare text
310
+ * content (`<wp14:sizeRelH relativeFrom="margin">0</wp14:sizeRelH>`), which
311
+ * Word rejects as incomplete content. This wraps such text in the required
312
+ * child element. Already-valid nested forms are left untouched because the
313
+ * text-content pattern (`[^<]`) cannot match an element child.
314
+ *
315
+ * @param xml - Raw anchor-extras fragment captured during parsing
316
+ * @returns Fragment with sizeRelH/sizeRelV normalized to schema-valid form
317
+ */
318
+ export function normalizeAnchorSizeRel(xml: string): string {
319
+ if (!xml) return xml;
320
+ return xml
321
+ .replace(
322
+ /(<wp14:sizeRelH\b[^>]*>)([^<]+)(<\/wp14:sizeRelH>)/g,
323
+ (_m, open: string, text: string, close: string) =>
324
+ text.trim() === ''
325
+ ? `${open}${text}${close}`
326
+ : `${open}<wp14:pctWidth>${text.trim()}</wp14:pctWidth>${close}`
327
+ )
328
+ .replace(
329
+ /(<wp14:sizeRelV\b[^>]*>)([^<]+)(<\/wp14:sizeRelV>)/g,
330
+ (_m, open: string, text: string, close: string) =>
331
+ text.trim() === ''
332
+ ? `${open}${text}${close}`
333
+ : `${open}<wp14:pctHeight>${text.trim()}</wp14:pctHeight>${close}`
334
+ );
139
335
  }
140
336
 
141
337
  /**