docxmlater 11.0.10 → 12.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +51 -13
  2. package/dist/core/Document.d.ts +28 -2
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +725 -170
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentGenerator.d.ts +1 -0
  7. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  8. package/dist/core/DocumentGenerator.js +77 -25
  9. package/dist/core/DocumentGenerator.js.map +1 -1
  10. package/dist/core/DocumentParser.d.ts +7 -3
  11. package/dist/core/DocumentParser.d.ts.map +1 -1
  12. package/dist/core/DocumentParser.js +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 +726 -171
  101. package/dist/esm/core/Document.js.map +1 -1
  102. package/dist/esm/core/DocumentGenerator.js +77 -25
  103. package/dist/esm/core/DocumentGenerator.js.map +1 -1
  104. package/dist/esm/core/DocumentParser.js +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 +58 -8
  175. package/dist/esm/processors/RevisionWalker.js.map +1 -1
  176. package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
  177. package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
  178. package/dist/esm/processors/cnfStyleDecoder.js +7 -7
  179. package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
  180. package/dist/esm/processors/stripTrackedChanges.js +16 -5
  181. package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
  182. package/dist/esm/utils/deepClone.js +24 -11
  183. package/dist/esm/utils/deepClone.js.map +1 -1
  184. package/dist/esm/utils/units.js +1 -1
  185. package/dist/esm/utils/units.js.map +1 -1
  186. package/dist/esm/utils/validation.js +11 -23
  187. package/dist/esm/utils/validation.js.map +1 -1
  188. package/dist/esm/utils/xmlSanitization.js +131 -7
  189. package/dist/esm/utils/xmlSanitization.js.map +1 -1
  190. package/dist/esm/validation/RevisionAutoFixer.js +69 -55
  191. package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
  192. package/dist/esm/validation/RevisionValidator.js +24 -20
  193. package/dist/esm/validation/RevisionValidator.js.map +1 -1
  194. package/dist/esm/xml/XMLBuilder.js +52 -20
  195. package/dist/esm/xml/XMLBuilder.js.map +1 -1
  196. package/dist/esm/xml/XMLParser.js +55 -13
  197. package/dist/esm/xml/XMLParser.js.map +1 -1
  198. package/dist/esm/zip/ZipHandler.js +37 -13
  199. package/dist/esm/zip/ZipHandler.js.map +1 -1
  200. package/dist/esm/zip/ZipReader.js +53 -6
  201. package/dist/esm/zip/ZipReader.js.map +1 -1
  202. package/dist/esm/zip/errors.js +7 -0
  203. package/dist/esm/zip/errors.js.map +1 -1
  204. package/dist/esm/zip/types.js +4 -0
  205. package/dist/esm/zip/types.js.map +1 -1
  206. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  207. package/dist/formatting/AbstractNumbering.js +1 -1
  208. package/dist/formatting/AbstractNumbering.js.map +1 -1
  209. package/dist/formatting/NumberingLevel.d.ts +1 -1
  210. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  211. package/dist/formatting/NumberingLevel.js +8 -6
  212. package/dist/formatting/NumberingLevel.js.map +1 -1
  213. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  214. package/dist/formatting/NumberingManager.js +16 -1
  215. package/dist/formatting/NumberingManager.js.map +1 -1
  216. package/dist/formatting/Style.d.ts.map +1 -1
  217. package/dist/formatting/Style.js +14 -0
  218. package/dist/formatting/Style.js.map +1 -1
  219. package/dist/formatting/StylesManager.d.ts +2 -0
  220. package/dist/formatting/StylesManager.d.ts.map +1 -1
  221. package/dist/formatting/StylesManager.js +14 -2
  222. package/dist/formatting/StylesManager.js.map +1 -1
  223. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  224. package/dist/helpers/CleanupHelper.js +103 -26
  225. package/dist/helpers/CleanupHelper.js.map +1 -1
  226. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  227. package/dist/images/ImageOptimizer.js +52 -4
  228. package/dist/images/ImageOptimizer.js.map +1 -1
  229. package/dist/index.d.ts +3 -1
  230. package/dist/index.d.ts.map +1 -1
  231. package/dist/index.js +4 -2
  232. package/dist/index.js.map +1 -1
  233. package/dist/internal.d.ts +1 -1
  234. package/dist/internal.d.ts.map +1 -1
  235. package/dist/internal.js +2 -1
  236. package/dist/internal.js.map +1 -1
  237. package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
  238. package/dist/processors/CompatibilityUpgrader.js +7 -1
  239. package/dist/processors/CompatibilityUpgrader.js.map +1 -1
  240. package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
  241. package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
  242. package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
  243. package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
  244. package/dist/processors/RevisionWalker.d.ts +2 -0
  245. package/dist/processors/RevisionWalker.d.ts.map +1 -1
  246. package/dist/processors/RevisionWalker.js +58 -8
  247. package/dist/processors/RevisionWalker.js.map +1 -1
  248. package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
  249. package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
  250. package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
  251. package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
  252. package/dist/processors/cnfStyleDecoder.js +7 -7
  253. package/dist/processors/cnfStyleDecoder.js.map +1 -1
  254. package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
  255. package/dist/processors/stripTrackedChanges.js +16 -5
  256. package/dist/processors/stripTrackedChanges.js.map +1 -1
  257. package/dist/utils/deepClone.d.ts.map +1 -1
  258. package/dist/utils/deepClone.js +24 -11
  259. package/dist/utils/deepClone.js.map +1 -1
  260. package/dist/utils/units.d.ts.map +1 -1
  261. package/dist/utils/units.js +1 -1
  262. package/dist/utils/units.js.map +1 -1
  263. package/dist/utils/validation.d.ts.map +1 -1
  264. package/dist/utils/validation.js +11 -23
  265. package/dist/utils/validation.js.map +1 -1
  266. package/dist/utils/xmlSanitization.d.ts +2 -0
  267. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  268. package/dist/utils/xmlSanitization.js +133 -7
  269. package/dist/utils/xmlSanitization.js.map +1 -1
  270. package/dist/validation/RevisionAutoFixer.d.ts +6 -2
  271. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  272. package/dist/validation/RevisionAutoFixer.js +68 -54
  273. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  274. package/dist/validation/RevisionValidator.d.ts +2 -0
  275. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  276. package/dist/validation/RevisionValidator.js +26 -21
  277. package/dist/validation/RevisionValidator.js.map +1 -1
  278. package/dist/xml/XMLBuilder.d.ts +1 -0
  279. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  280. package/dist/xml/XMLBuilder.js +52 -20
  281. package/dist/xml/XMLBuilder.js.map +1 -1
  282. package/dist/xml/XMLParser.d.ts +1 -0
  283. package/dist/xml/XMLParser.d.ts.map +1 -1
  284. package/dist/xml/XMLParser.js +55 -13
  285. package/dist/xml/XMLParser.js.map +1 -1
  286. package/dist/zip/ZipHandler.d.ts.map +1 -1
  287. package/dist/zip/ZipHandler.js +37 -13
  288. package/dist/zip/ZipHandler.js.map +1 -1
  289. package/dist/zip/ZipReader.d.ts.map +1 -1
  290. package/dist/zip/ZipReader.js +52 -5
  291. package/dist/zip/ZipReader.js.map +1 -1
  292. package/dist/zip/errors.d.ts +3 -0
  293. package/dist/zip/errors.d.ts.map +1 -1
  294. package/dist/zip/errors.js +9 -1
  295. package/dist/zip/errors.js.map +1 -1
  296. package/dist/zip/types.d.ts +4 -0
  297. package/dist/zip/types.d.ts.map +1 -1
  298. package/dist/zip/types.js +4 -0
  299. package/dist/zip/types.js.map +1 -1
  300. package/package.json +2 -2
  301. package/src/core/Document.ts +1159 -240
  302. package/src/core/DocumentGenerator.ts +106 -26
  303. package/src/core/DocumentParser.ts +681 -114
  304. package/src/core/RelationshipManager.ts +446 -442
  305. package/src/elements/Comment.ts +123 -18
  306. package/src/elements/CommentManager.ts +121 -22
  307. package/src/elements/EndnoteManager.ts +4 -1
  308. package/src/elements/Field.ts +134 -43
  309. package/src/elements/FieldHelpers.ts +20 -4
  310. package/src/elements/Footer.ts +62 -67
  311. package/src/elements/FootnoteManager.ts +4 -1
  312. package/src/elements/Header.ts +62 -67
  313. package/src/elements/HeaderFooterManager.ts +56 -8
  314. package/src/elements/Hyperlink.ts +150 -55
  315. package/src/elements/Image.ts +51 -7
  316. package/src/elements/ImageManager.ts +24 -0
  317. package/src/elements/Paragraph.ts +161 -40
  318. package/src/elements/Revision.ts +446 -47
  319. package/src/elements/Run.ts +44 -6
  320. package/src/elements/Section.ts +25 -9
  321. package/src/elements/Shape.ts +52 -44
  322. package/src/elements/StructuredDocumentTag.ts +83 -8
  323. package/src/elements/Table.ts +151 -50
  324. package/src/elements/TableCell.ts +57 -17
  325. package/src/elements/TableOfContents.ts +10 -9
  326. package/src/elements/TableRow.ts +35 -3
  327. package/src/elements/TextBox.ts +58 -43
  328. package/src/formatting/AbstractNumbering.ts +4 -2
  329. package/src/formatting/NumberingLevel.ts +50 -12
  330. package/src/formatting/NumberingManager.ts +32 -3
  331. package/src/formatting/Style.ts +14 -0
  332. package/src/formatting/StylesManager.ts +29 -2
  333. package/src/helpers/CleanupHelper.ts +122 -30
  334. package/src/images/ImageOptimizer.ts +64 -5
  335. package/src/index.ts +7 -0
  336. package/src/internal.ts +1 -0
  337. package/src/processors/CompatibilityUpgrader.ts +15 -2
  338. package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
  339. package/src/processors/RevisionWalker.ts +83 -7
  340. package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
  341. package/src/processors/cnfStyleDecoder.ts +18 -12
  342. package/src/processors/stripTrackedChanges.ts +47 -10
  343. package/src/utils/deepClone.ts +48 -14
  344. package/src/utils/units.ts +3 -1
  345. package/src/utils/validation.ts +30 -32
  346. package/src/utils/xmlSanitization.ts +218 -22
  347. package/src/validation/RevisionAutoFixer.ts +109 -65
  348. package/src/validation/RevisionValidator.ts +48 -23
  349. package/src/xml/XMLBuilder.ts +82 -22
  350. package/src/xml/XMLParser.ts +87 -20
  351. package/src/zip/ZipHandler.ts +61 -21
  352. package/src/zip/ZipReader.ts +135 -13
  353. package/src/zip/errors.ts +15 -0
  354. package/src/zip/types.ts +44 -2
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/utils/validation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validation utilities for DOCX files\n */\n\nimport { REQUIRED_DOCX_FILES } from '../zip/types.js';\nimport { MissingRequiredFileError } from '../zip/errors.js';\nimport { defaultLogger } from './logger.js';\n\n/**\n * Validates that all required DOCX files are present\n * @param filePaths - Array of file paths in the archive\n * @throws {MissingRequiredFileError} If a required file is missing\n */\nexport function validateDocxStructure(filePaths: string[]): void {\n const fileSet = new Set(filePaths);\n\n for (const requiredFile of REQUIRED_DOCX_FILES) {\n if (!fileSet.has(requiredFile)) {\n throw new MissingRequiredFileError(requiredFile);\n }\n }\n}\n\n/**\n * Checks if a file path represents a binary file based on extension\n * @param filePath - The file path to check\n * @returns True if the file is likely binary\n */\nexport function isBinaryFile(filePath: string): boolean {\n const binaryExtensions = [\n '.png',\n '.jpg',\n '.jpeg',\n '.gif',\n '.bmp',\n '.tiff',\n '.ico',\n '.emf',\n '.wmf',\n '.bin',\n '.dat',\n '.ttf',\n '.otf',\n '.woff',\n ];\n\n const extension = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();\n return binaryExtensions.includes(extension);\n}\n\n/**\n * Normalizes a file path for consistent comparisons\n * Converts backslashes to forward slashes and removes leading slashes\n * Also validates against path traversal attacks\n *\n * **Security:** This function validates paths to prevent:\n * - Path traversal attacks (../, ..\\, URL-encoded variants)\n * - Absolute paths (C:\\, /etc/, etc.)\n * - Malicious DOCX files attempting directory escape\n *\n * @param path - The path to normalize\n * @returns Normalized path\n * @throws {Error} If path contains path traversal sequences, absolute paths, or URL-encoded attacks\n */\nexport function normalizePath(path: string): string {\n // First convert all backslashes to forward slashes for consistent checking\n const normalized = path.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n\n // Security: Reject URL-encoded path traversal attempts\n // Attackers might try: %2e%2e%2f (%2e = . and %2f = /)\n if (/%2[eE]|%2[fF]|%5[cC]/.test(path)) {\n throw new Error(\n `Invalid file path: \"${path}\" contains URL-encoded characters (%2E, %2F, %5C). ` +\n `This could be an attempt to bypass path validation. ` +\n `Only plain characters are allowed in DOCX file paths.`\n );\n }\n\n // Security: Prevent path traversal attacks\n // Check AFTER normalization when all paths use forward slashes\n // This catches: ../, /.., or standalone \"..\"\n if (normalized.includes('../') || normalized.includes('/..') || normalized === '..') {\n throw new Error(\n `Invalid file path: \"${path}\" contains path traversal sequence (..). ` +\n `This could be a malicious DOCX file attempting directory traversal. ` +\n `DOCX archives must only contain relative paths within the archive.`\n );\n }\n\n // Security: Prevent absolute paths (Windows drive letters)\n // Examples: C:/, C:\\, D:, etc.\n if (/^[a-zA-Z]:/.test(normalized)) {\n throw new Error(\n `Invalid file path: \"${path}\" appears to be an absolute Windows path. ` +\n `Absolute paths are not allowed in DOCX archives. ` +\n `Only relative paths within the archive are permitted.`\n );\n }\n\n // Security: Prevent Unix absolute paths\n // After removing leading slashes, if it starts with / it's suspicious\n if (path.startsWith('/') && normalized.startsWith('/')) {\n throw new Error(\n `Invalid file path: \"${path}\" appears to be an absolute Unix path. ` +\n `Only relative paths are allowed in DOCX archives.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Validates that a buffer contains a valid ZIP file signature\n * ZIP files start with the signature 'PK' (0x50 0x4B)\n * @param buffer - The buffer to validate\n * @returns True if the buffer appears to be a ZIP file\n */\nexport function isValidZipBuffer(buffer: Buffer): boolean {\n if (buffer.length < 4) {\n return false;\n }\n\n // Check for ZIP signature: PK\\x03\\x04 or PK\\x05\\x06 (for empty archives)\n return (\n buffer[0] === 0x50 &&\n buffer[1] === 0x4b &&\n ((buffer[2] === 0x03 && buffer[3] === 0x04) || (buffer[2] === 0x05 && buffer[3] === 0x06))\n );\n}\n\n/**\n * Checks if a string is valid UTF-8 text\n * @param content - The content to check\n * @returns True if the content is valid text\n */\nexport function isTextContent(content: Buffer | string): boolean {\n if (typeof content === 'string') {\n return true;\n }\n\n // Try to decode as UTF-8 and check for null bytes\n try {\n const text = content.toString('utf8');\n // Binary files often contain null bytes\n return !text.includes('\\0');\n } catch {\n return false;\n }\n}\n\n/**\n * Validates a twips value (used for spacing, indentation, margins)\n * Twips: 1/20th of a point, 1440 twips = 1 inch\n * Reasonable range: -31680 to 31680 (±22 inches)\n * @param value - The twips value to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the value is invalid\n */\nexport function validateTwips(value: number, fieldName = 'value'): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${fieldName} must be a finite number, got ${value}`);\n }\n\n // Reasonable range: ±22 inches (31680 twips)\n const MIN_TWIPS = -31680;\n const MAX_TWIPS = 31680;\n\n if (value < MIN_TWIPS || value > MAX_TWIPS) {\n throw new Error(\n `${fieldName} out of range: ${value} twips (allowed: ${MIN_TWIPS} to ${MAX_TWIPS}, ±22 inches)`\n );\n }\n}\n\n/**\n * Normalizes a color to uppercase 6-character hex format\n * Accepts 3-character or 6-character hex colors with or without '#' prefix\n * Follows Microsoft Word convention of uppercase hex colors\n *\n * @param color - Color to normalize (e.g., '#F00', 'FF0000', '#FF0000', 'f00')\n * @returns Normalized color (e.g., 'FF0000')\n * @throws Error if color format is invalid\n *\n * @example\n * ```typescript\n * normalizeColor('#F00') // Returns: 'FF0000'\n * normalizeColor('FF0000') // Returns: 'FF0000'\n * normalizeColor('#ff0000') // Returns: 'FF0000'\n * normalizeColor('f00') // Returns: 'FF0000'\n * ```\n */\nexport function normalizeColor(color: string): string {\n const hex = color.replace(/^#/, '');\n\n // Validate hex format\n if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(hex)) {\n throw new Error(\n `Invalid color format: \"${color}\". Expected 3 or 6-character hex ` +\n `(e.g., \"FF0000\", \"#FF0000\", \"F00\", or \"#F00\")`\n );\n }\n\n // Expand 3-character to 6-character\n if (hex.length === 3) {\n return (\n hex.charAt(0) +\n hex.charAt(0) +\n hex.charAt(1) +\n hex.charAt(1) +\n hex.charAt(2) +\n hex.charAt(2)\n ).toUpperCase();\n }\n\n return hex.toUpperCase();\n}\n\n/**\n * Validates a hexadecimal color value\n * Must be 6 characters (RRGGBB format)\n * @param color - The color hex string to validate (without #)\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the color is invalid\n */\nexport function validateColor(color: string, fieldName = 'color'): void {\n if (typeof color !== 'string') {\n throw new Error(`${fieldName} must be a string, got ${typeof color}`);\n }\n\n // Allow both with and without # prefix\n const cleanColor = color.startsWith('#') ? color.substring(1) : color;\n\n if (!/^[0-9A-Fa-f]{6}$/.test(cleanColor)) {\n throw new Error(\n `${fieldName} must be a 6-digit hex color (e.g., 'FF0000' or '#FF0000'), got '${color}'`\n );\n }\n}\n\n/**\n * Alias for validateColor for backwards compatibility\n */\nexport const validateHexColor = validateColor;\n\n/**\n * Validates a numbering ID (must be non-negative integer)\n * @param numId - The numbering ID to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the ID is invalid\n */\nexport function validateNumberingId(numId: number, fieldName = 'numbering ID'): void {\n if (!Number.isInteger(numId)) {\n throw new Error(`${fieldName} must be an integer, got ${numId}`);\n }\n\n if (numId < 0) {\n throw new Error(`${fieldName} must be non-negative, got ${numId}`);\n }\n\n // Word supports numbering IDs up to 2147483647\n const MAX_NUM_ID = 2147483647;\n if (numId > MAX_NUM_ID) {\n throw new Error(`${fieldName} exceeds maximum value ${MAX_NUM_ID}, got ${numId}`);\n }\n}\n\n/**\n * Validates a numbering level (0-8 for Word)\n * @param level - The level to validate\n * @param fieldName - Name of the field (for error messages)\n * @param maxLevel - Maximum allowed level (default 8)\n * @throws {Error} If the level is invalid\n */\nexport function validateLevel(level: number, fieldName = 'level', maxLevel = 8): void {\n if (!Number.isInteger(level)) {\n throw new Error(`${fieldName} must be an integer, got ${level}`);\n }\n\n if (level < 0 || level > maxLevel) {\n throw new Error(`${fieldName} must be between 0 and ${maxLevel}, got ${level}`);\n }\n}\n\n/**\n * Validates an alignment value against allowed values\n * @param alignment - The alignment value to validate\n * @param allowed - Array of allowed alignment values\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the alignment is invalid\n */\nexport function validateAlignment(\n alignment: string,\n allowed: readonly string[],\n fieldName = 'alignment'\n): void {\n if (typeof alignment !== 'string') {\n throw new Error(`${fieldName} must be a string, got ${typeof alignment}`);\n }\n\n if (!allowed.includes(alignment)) {\n throw new Error(`Invalid ${fieldName}: '${alignment}' (allowed: ${allowed.join(', ')})`);\n }\n}\n\n/**\n * Validates a font size (in half-points for Word)\n * Reasonable range: 2-1638 (1-819 points)\n * @param size - The font size in half-points to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the size is invalid\n */\nexport function validateFontSize(size: number, fieldName = 'font size'): void {\n if (!Number.isFinite(size)) {\n throw new Error(`${fieldName} must be a finite number, got ${size}`);\n }\n\n if (!Number.isInteger(size)) {\n throw new Error(`${fieldName} must be an integer (in half-points), got ${size}`);\n }\n\n // Reasonable range: 2-1638 half-points (1-819 points)\n const MIN_SIZE = 2;\n const MAX_SIZE = 1638;\n\n if (size < MIN_SIZE || size > MAX_SIZE) {\n throw new Error(\n `${fieldName} out of range: ${size} half-points (allowed: ${MIN_SIZE}-${MAX_SIZE}, or ${MIN_SIZE / 2}-${MAX_SIZE / 2} points)`\n );\n }\n}\n\n/**\n * Validates that a string is not empty\n * @param value - The string to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the string is empty or not a string\n */\nexport function validateNonEmptyString(value: string, fieldName = 'value'): void {\n if (typeof value !== 'string') {\n throw new Error(`${fieldName} must be a string, got ${typeof value}`);\n }\n\n if (value.trim().length === 0) {\n throw new Error(`${fieldName} cannot be empty`);\n }\n}\n\n/**\n * Validates a percentage value (0-100)\n * @param value - The percentage to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the percentage is invalid\n */\nexport function validatePercentage(value: number, fieldName = 'percentage'): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${fieldName} must be a finite number, got ${value}`);\n }\n\n if (value < 0 || value > 100) {\n throw new Error(`${fieldName} must be between 0 and 100, got ${value}`);\n }\n}\n\n/**\n * Validates EMUs (English Metric Units) value\n * Used for image dimensions: 914400 EMUs = 1 inch\n * Reasonable range: 0 to 50 million (about 55 inches)\n * @param value - The EMUs value to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the value is invalid\n */\nexport function validateEmus(value: number, fieldName = 'EMUs'): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${fieldName} must be a finite number, got ${value}`);\n }\n\n if (!Number.isInteger(value)) {\n throw new Error(`${fieldName} must be an integer, got ${value}`);\n }\n\n if (value < 0) {\n throw new Error(`${fieldName} must be non-negative, got ${value}`);\n }\n\n // Reasonable maximum: 50 million EMUs (about 55 inches)\n const MAX_EMUS = 50000000;\n if (value > MAX_EMUS) {\n throw new Error(`${fieldName} exceeds maximum ${MAX_EMUS} (about 55 inches), got ${value}`);\n }\n}\n\n/**\n * Result of text validation for XML-like content\n */\nexport interface TextValidationResult {\n isValid: boolean;\n hasXmlPatterns: boolean;\n warnings: string[];\n cleanedText?: string;\n}\n\n/**\n * Detects XML-like patterns in text that might cause display issues\n *\n * This function checks for patterns that look like XML markup which,\n * when properly escaped in XML output, will display as literal text\n * in Word documents rather than being interpreted as markup.\n *\n * @param text - The text to validate\n * @param context - Optional context for better warning messages (e.g., \"hyperlink text\")\n * @returns Validation result with warnings and optional cleaned text\n */\nexport function detectXmlInText(text: string, context?: string): TextValidationResult {\n const warnings: string[] = [];\n let hasXmlPatterns = false;\n\n // Check for common XML element patterns\n const xmlElementPattern = /<\\/?w:[^>]+>|<w:[^>]+\\/>/g;\n const escapedXmlPattern = /&lt;.*?&gt;|&quot;|&apos;/g;\n\n // Check for specific problematic patterns we've seen\n const problematicPatterns = [\n /<w:t\\s+xml:space=\"preserve\">/,\n /<w:t\\s+xml:space=[\"']preserve[\"']>/,\n /<\\/w:t>/,\n /&lt;w:t\\s+xml:space=&quot;preserve&quot;&gt;/,\n ];\n\n // Check for any XML-like tags\n if (xmlElementPattern.test(text)) {\n hasXmlPatterns = true;\n const contextStr = context ? ` in ${context}` : '';\n warnings.push(\n `Text${contextStr} contains XML-like markup: \"${text.substring(0, 100)}${text.length > 100 ? '...' : ''}\". ` +\n `This will be displayed as literal text in the document. ` +\n `If you intended to add formatting, use the appropriate API methods instead.`\n );\n }\n\n // Check for already-escaped XML entities\n if (escapedXmlPattern.test(text)) {\n hasXmlPatterns = true;\n const contextStr = context ? ` in ${context}` : '';\n warnings.push(\n `Text${contextStr} contains escaped XML entities (e.g., &lt;, &gt;, &quot;). ` +\n `These will appear as literal characters in the document.`\n );\n }\n\n // Check for specific known problematic patterns\n for (const pattern of problematicPatterns) {\n if (pattern.test(text)) {\n hasXmlPatterns = true;\n const contextStr = context ? ` in ${context}` : '';\n warnings.push(\n `Text${contextStr} contains a known problematic XML pattern that suggests ` +\n `the text may have been corrupted by previous processing.`\n );\n break;\n }\n }\n\n return {\n isValid: true, // Text is always \"valid\" - we just warn about potential issues\n hasXmlPatterns,\n warnings,\n };\n}\n\n/**\n * Cleans XML-like patterns from text\n *\n * This function removes or cleans various XML patterns that might\n * appear in text content, typically from corrupted or improperly\n * processed documents.\n *\n * @param text - The text to clean\n * @param aggressive - If true, removes all angle brackets; if false, only removes clear XML tags\n * @returns Cleaned text with XML patterns removed\n */\nexport function cleanXmlFromText(text: string, aggressive = false): string {\n let cleaned = text;\n\n // First, unescape any HTML/XML entities\n cleaned = cleaned\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, '&');\n\n // Remove specific Word XML patterns\n // This targets patterns like <w:t xml:space=\"preserve\">\n cleaned = cleaned.replace(/<w:[^>]+>/g, '');\n cleaned = cleaned.replace(/<\\/w:[^>]+>/g, '');\n\n // Remove any remaining XML-like tags if aggressive mode\n if (aggressive) {\n cleaned = cleaned.replace(/<[^>]+>/g, '');\n }\n\n // Clean up any double spaces left behind\n cleaned = cleaned.replace(/\\s+/g, ' ').trim();\n\n return cleaned;\n}\n\n/**\n * Validates text for use in Run or Hyperlink elements\n *\n * This is the main validation function that should be called when\n * setting text content in Run or Hyperlink elements. It provides\n * warnings about problematic content and optionally cleans the text.\n *\n * @param text - The text to validate\n * @param options - Validation options\n * @returns Validation result with warnings and optionally cleaned text\n */\nexport function validateRunText(\n text: string,\n options: {\n context?: string;\n autoClean?: boolean;\n aggressive?: boolean;\n warnToConsole?: boolean;\n } = {}\n): TextValidationResult {\n const { context, autoClean = false, aggressive = false, warnToConsole = true } = options;\n\n // Detect XML patterns\n const result = detectXmlInText(text, context);\n\n // If auto-cleaning is enabled and XML patterns were found\n if (autoClean && result.hasXmlPatterns) {\n result.cleanedText = cleanXmlFromText(text, aggressive);\n\n // Add a note about cleaning\n result.warnings.push(\n `Text has been automatically cleaned. ` +\n `Original: \"${text.substring(0, 50)}${text.length > 50 ? '...' : ''}\" ` +\n `Cleaned: \"${result.cleanedText.substring(0, 50)}${result.cleanedText.length > 50 ? '...' : ''}\"`\n );\n }\n\n // Log warnings to console in development if requested\n if (warnToConsole && result.warnings.length > 0 && typeof console !== 'undefined') {\n const contextStr = context ? ` [${context}]` : '';\n defaultLogger.warn(`DocXML Text Validation Warning${contextStr}:`);\n result.warnings.forEach((warning) => defaultLogger.warn(` - ${warning}`));\n }\n\n return result;\n}\n\n/**\n * Result of URL sanitization\n */\nexport interface SanitizeHyperlinkUrlResult {\n /** The sanitized URL */\n url: string;\n /** List of fixes that were applied */\n fixes: string[];\n}\n\n/**\n * Sanitizes hyperlink URLs by removing browser extension prefixes and fixing\n * broken protocols that result from the stripping.\n *\n * Known corruptions:\n * - Chrome extensions (e.g. Adobe Acrobat) wrap URLs with `chrome-extension://[id]/`\n * - Firefox extensions wrap URLs with `moz-extension://[uuid]/`\n * - Edge extensions wrap URLs with `extension://[id]/`\n * - After stripping, protocol may be broken (`https:/` instead of `https://`)\n *\n * @param url - The URL to sanitize\n * @returns Object with sanitized URL and list of fixes, or null if no changes needed\n */\nexport function sanitizeHyperlinkUrl(url: string): SanitizeHyperlinkUrlResult | null {\n if (!url) return null;\n\n const fixes: string[] = [];\n let sanitized = url;\n\n // Strip known browser extension URL prefixes\n const extensionPrefixes = [\n { pattern: /^chrome-extension:\\/\\/[a-z]{32}\\//i, name: 'Chrome extension' },\n {\n pattern: /^moz-extension:\\/\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\//i,\n name: 'Firefox extension',\n },\n { pattern: /^extension:\\/\\/[a-z0-9]{32}\\//i, name: 'Edge extension' },\n ];\n\n for (const { pattern, name } of extensionPrefixes) {\n const match = pattern.exec(sanitized);\n if (match) {\n sanitized = sanitized.substring(match[0].length);\n fixes.push(`Stripped ${name} URL prefix`);\n break;\n }\n }\n\n // Fix broken protocol after stripping (https:/ -> https://, http:/ -> http://)\n const brokenProtocol = /^(https?:\\/)([^/])/i.exec(sanitized);\n if (brokenProtocol) {\n sanitized =\n brokenProtocol[1] + '/' + brokenProtocol[2] + sanitized.substring(brokenProtocol[0].length);\n fixes.push('Fixed broken protocol (added missing slash)');\n }\n\n if (fixes.length === 0) return null;\n\n return { url: sanitized, fixes };\n}\n"]}
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/utils/validation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validation utilities for DOCX files\n */\n\nimport { REQUIRED_DOCX_FILES } from '../zip/types.js';\nimport { MissingRequiredFileError } from '../zip/errors.js';\nimport { defaultLogger } from './logger.js';\n\n/**\n * Validates that all required DOCX files are present\n * @param filePaths - Array of file paths in the archive\n * @throws {MissingRequiredFileError} If a required file is missing\n */\nexport function validateDocxStructure(filePaths: string[]): void {\n const fileSet = new Set(filePaths);\n\n for (const requiredFile of REQUIRED_DOCX_FILES) {\n if (!fileSet.has(requiredFile)) {\n throw new MissingRequiredFileError(requiredFile);\n }\n }\n}\n\n/**\n * Checks if a file path represents a binary file based on extension\n *\n * Uses a text-extension whitelist rather than a binary-extension whitelist:\n * misclassifying text as binary only changes its in-memory representation\n * (Buffer instead of string), but misclassifying binary as text corrupts the\n * bytes through a lossy UTF-8 decode. OOXML packages can embed arbitrary\n * binary parts (.xlsx/.pdf OLE packages, .odttf obfuscated fonts, .emz/.wmz\n * compressed metafiles, extensionless parts), so anything not known to be\n * text must be preserved byte-for-byte.\n *\n * @param filePath - The file path to check\n * @returns True if the file is likely binary\n */\nexport function isBinaryFile(filePath: string): boolean {\n const textExtensions = ['.xml', '.rels', '.svg', '.txt', '.html', '.css', '.js'];\n\n const dotIndex = filePath.lastIndexOf('.');\n if (dotIndex === -1) {\n return true;\n }\n\n const extension = filePath.substring(dotIndex).toLowerCase();\n return !textExtensions.includes(extension);\n}\n\n/**\n * Normalizes a file path for consistent comparisons\n * Converts backslashes to forward slashes and removes leading slashes\n * Also validates against path traversal attacks\n *\n * **Security:** This function validates paths to prevent:\n * - Path traversal attacks (../, ..\\, URL-encoded variants)\n * - Absolute Windows paths (C:\\, D:, etc.) — rejected\n * - Malicious DOCX files attempting directory escape\n *\n * Unix absolute paths (/etc/...) are sanitized to archive-relative paths\n * by stripping leading slashes, not rejected.\n *\n * @param path - The path to normalize\n * @returns Normalized path\n * @throws {Error} If path contains path traversal sequences, absolute Windows paths, or URL-encoded attacks\n */\nexport function normalizePath(path: string): string {\n // First convert all backslashes to forward slashes for consistent checking\n const normalized = path.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n\n // Security: Reject URL-encoded path traversal attempts\n // Attackers might try: %2e%2e%2f (%2e = . and %2f = /)\n if (/%2[eE]|%2[fF]|%5[cC]/.test(path)) {\n throw new Error(\n `Invalid file path: \"${path}\" contains URL-encoded characters (%2E, %2F, %5C). ` +\n `This could be an attempt to bypass path validation. ` +\n `Only plain characters are allowed in DOCX file paths.`\n );\n }\n\n // Security: Prevent path traversal attacks\n // Check AFTER normalization when all paths use forward slashes\n // This catches: ../, /.., or standalone \"..\"\n if (normalized.includes('../') || normalized.includes('/..') || normalized === '..') {\n throw new Error(\n `Invalid file path: \"${path}\" contains path traversal sequence (..). ` +\n `This could be a malicious DOCX file attempting directory traversal. ` +\n `DOCX archives must only contain relative paths within the archive.`\n );\n }\n\n // Security: Prevent absolute paths (Windows drive letters)\n // Examples: C:/, C:\\, D:, etc.\n if (/^[a-zA-Z]:/.test(normalized)) {\n throw new Error(\n `Invalid file path: \"${path}\" appears to be an absolute Windows path. ` +\n `Absolute paths are not allowed in DOCX archives. ` +\n `Only relative paths within the archive are permitted.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Validates that a buffer contains a valid ZIP file signature\n * ZIP files start with the signature 'PK' (0x50 0x4B)\n * @param buffer - The buffer to validate\n * @returns True if the buffer appears to be a ZIP file\n */\nexport function isValidZipBuffer(buffer: Buffer): boolean {\n if (buffer.length < 4) {\n return false;\n }\n\n // Check for ZIP signature: PK\\x03\\x04 or PK\\x05\\x06 (for empty archives)\n return (\n buffer[0] === 0x50 &&\n buffer[1] === 0x4b &&\n ((buffer[2] === 0x03 && buffer[3] === 0x04) || (buffer[2] === 0x05 && buffer[3] === 0x06))\n );\n}\n\n/**\n * Checks if a string is valid UTF-8 text\n * @param content - The content to check\n * @returns True if the content is valid text\n */\nexport function isTextContent(content: Buffer | string): boolean {\n if (typeof content === 'string') {\n return true;\n }\n\n // Try to decode as UTF-8 and check for null bytes\n try {\n const text = content.toString('utf8');\n // Binary files often contain null bytes\n return !text.includes('\\0');\n } catch {\n return false;\n }\n}\n\n/**\n * Validates a twips value (used for spacing, indentation, margins)\n * Twips: 1/20th of a point, 1440 twips = 1 inch\n * Reasonable range: -31680 to 31680 (±22 inches)\n * @param value - The twips value to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the value is invalid\n */\nexport function validateTwips(value: number, fieldName = 'value'): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${fieldName} must be a finite number, got ${value}`);\n }\n\n // ST_TwipsMeasure/ST_SignedTwipsMeasure are ST_DecimalNumber-based — integers only\n if (!Number.isInteger(value)) {\n throw new Error(`${fieldName} must be an integer (in twips), got ${value}`);\n }\n\n // Reasonable range: ±22 inches (31680 twips)\n const MIN_TWIPS = -31680;\n const MAX_TWIPS = 31680;\n\n if (value < MIN_TWIPS || value > MAX_TWIPS) {\n throw new Error(\n `${fieldName} out of range: ${value} twips (allowed: ${MIN_TWIPS} to ${MAX_TWIPS}, ±22 inches)`\n );\n }\n}\n\n/**\n * Normalizes a color to uppercase 6-character hex format\n * Accepts 3-character or 6-character hex colors with or without '#' prefix\n * Follows Microsoft Word convention of uppercase hex colors\n *\n * @param color - Color to normalize (e.g., '#F00', 'FF0000', '#FF0000', 'f00')\n * @returns Normalized color (e.g., 'FF0000')\n * @throws Error if color format is invalid\n *\n * @example\n * ```typescript\n * normalizeColor('#F00') // Returns: 'FF0000'\n * normalizeColor('FF0000') // Returns: 'FF0000'\n * normalizeColor('#ff0000') // Returns: 'FF0000'\n * normalizeColor('f00') // Returns: 'FF0000'\n * ```\n */\nexport function normalizeColor(color: string): string {\n const hex = color.replace(/^#/, '');\n\n // Validate hex format\n if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(hex)) {\n throw new Error(\n `Invalid color format: \"${color}\". Expected 3 or 6-character hex ` +\n `(e.g., \"FF0000\", \"#FF0000\", \"F00\", or \"#F00\")`\n );\n }\n\n // Expand 3-character to 6-character\n if (hex.length === 3) {\n return (\n hex.charAt(0) +\n hex.charAt(0) +\n hex.charAt(1) +\n hex.charAt(1) +\n hex.charAt(2) +\n hex.charAt(2)\n ).toUpperCase();\n }\n\n return hex.toUpperCase();\n}\n\n/**\n * Validates a hexadecimal color value\n * Must be 6 characters (RRGGBB format)\n * @param color - The color hex string to validate (without #)\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the color is invalid\n */\nexport function validateColor(color: string, fieldName = 'color'): void {\n if (typeof color !== 'string') {\n throw new Error(`${fieldName} must be a string, got ${typeof color}`);\n }\n\n // Allow both with and without # prefix\n const cleanColor = color.startsWith('#') ? color.substring(1) : color;\n\n if (!/^[0-9A-Fa-f]{6}$/.test(cleanColor)) {\n throw new Error(\n `${fieldName} must be a 6-digit hex color (e.g., 'FF0000' or '#FF0000'), got '${color}'`\n );\n }\n}\n\n/**\n * Alias for validateColor for backwards compatibility\n */\nexport const validateHexColor = validateColor;\n\n/**\n * Validates a numbering ID (must be non-negative integer)\n * @param numId - The numbering ID to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the ID is invalid\n */\nexport function validateNumberingId(numId: number, fieldName = 'numbering ID'): void {\n if (!Number.isInteger(numId)) {\n throw new Error(`${fieldName} must be an integer, got ${numId}`);\n }\n\n if (numId < 0) {\n throw new Error(`${fieldName} must be non-negative, got ${numId}`);\n }\n\n // Word supports numbering IDs up to 2147483647\n const MAX_NUM_ID = 2147483647;\n if (numId > MAX_NUM_ID) {\n throw new Error(`${fieldName} exceeds maximum value ${MAX_NUM_ID}, got ${numId}`);\n }\n}\n\n/**\n * Validates a numbering level (0-8 for Word)\n * @param level - The level to validate\n * @param fieldName - Name of the field (for error messages)\n * @param maxLevel - Maximum allowed level (default 8)\n * @throws {Error} If the level is invalid\n */\nexport function validateLevel(level: number, fieldName = 'level', maxLevel = 8): void {\n if (!Number.isInteger(level)) {\n throw new Error(`${fieldName} must be an integer, got ${level}`);\n }\n\n if (level < 0 || level > maxLevel) {\n throw new Error(`${fieldName} must be between 0 and ${maxLevel}, got ${level}`);\n }\n}\n\n/**\n * Validates an alignment value against allowed values\n * @param alignment - The alignment value to validate\n * @param allowed - Array of allowed alignment values\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the alignment is invalid\n */\nexport function validateAlignment(\n alignment: string,\n allowed: readonly string[],\n fieldName = 'alignment'\n): void {\n if (typeof alignment !== 'string') {\n throw new Error(`${fieldName} must be a string, got ${typeof alignment}`);\n }\n\n if (!allowed.includes(alignment)) {\n throw new Error(`Invalid ${fieldName}: '${alignment}' (allowed: ${allowed.join(', ')})`);\n }\n}\n\n/**\n * Validates a font size (in half-points for Word)\n * Reasonable range: 2-3276 (1-1638 points)\n * @param size - The font size in half-points to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the size is invalid\n */\nexport function validateFontSize(size: number, fieldName = 'font size'): void {\n if (!Number.isFinite(size)) {\n throw new Error(`${fieldName} must be a finite number, got ${size}`);\n }\n\n if (!Number.isInteger(size)) {\n throw new Error(`${fieldName} must be an integer (in half-points), got ${size}`);\n }\n\n // Word's limit is 1-1638 points, i.e. 2-3276 half-points for w:sz (ST_HpsMeasure)\n const MIN_SIZE = 2;\n const MAX_SIZE = 3276;\n\n if (size < MIN_SIZE || size > MAX_SIZE) {\n throw new Error(\n `${fieldName} out of range: ${size} half-points (allowed: ${MIN_SIZE}-${MAX_SIZE}, or ${MIN_SIZE / 2}-${MAX_SIZE / 2} points)`\n );\n }\n}\n\n/**\n * Validates that a string is not empty\n * @param value - The string to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the string is empty or not a string\n */\nexport function validateNonEmptyString(value: string, fieldName = 'value'): void {\n if (typeof value !== 'string') {\n throw new Error(`${fieldName} must be a string, got ${typeof value}`);\n }\n\n if (value.trim().length === 0) {\n throw new Error(`${fieldName} cannot be empty`);\n }\n}\n\n/**\n * Validates a percentage value (0-100)\n * @param value - The percentage to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the percentage is invalid\n */\nexport function validatePercentage(value: number, fieldName = 'percentage'): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${fieldName} must be a finite number, got ${value}`);\n }\n\n if (value < 0 || value > 100) {\n throw new Error(`${fieldName} must be between 0 and 100, got ${value}`);\n }\n}\n\n/**\n * Validates EMUs (English Metric Units) value\n * Used for image dimensions: 914400 EMUs = 1 inch\n * Reasonable range: 0 to 50 million (about 55 inches)\n * @param value - The EMUs value to validate\n * @param fieldName - Name of the field (for error messages)\n * @throws {Error} If the value is invalid\n */\nexport function validateEmus(value: number, fieldName = 'EMUs'): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${fieldName} must be a finite number, got ${value}`);\n }\n\n if (!Number.isInteger(value)) {\n throw new Error(`${fieldName} must be an integer, got ${value}`);\n }\n\n if (value < 0) {\n throw new Error(`${fieldName} must be non-negative, got ${value}`);\n }\n\n // Reasonable maximum: 50 million EMUs (about 55 inches)\n const MAX_EMUS = 50000000;\n if (value > MAX_EMUS) {\n throw new Error(`${fieldName} exceeds maximum ${MAX_EMUS} (about 55 inches), got ${value}`);\n }\n}\n\n/**\n * Result of text validation for XML-like content\n */\nexport interface TextValidationResult {\n isValid: boolean;\n hasXmlPatterns: boolean;\n warnings: string[];\n cleanedText?: string;\n}\n\n/**\n * Detects XML-like patterns in text that might cause display issues\n *\n * This function checks for patterns that look like XML markup which,\n * when properly escaped in XML output, will display as literal text\n * in Word documents rather than being interpreted as markup.\n *\n * @param text - The text to validate\n * @param context - Optional context for better warning messages (e.g., \"hyperlink text\")\n * @returns Validation result with warnings and optional cleaned text\n */\nexport function detectXmlInText(text: string, context?: string): TextValidationResult {\n const warnings: string[] = [];\n let hasXmlPatterns = false;\n\n // Check for common XML element patterns\n const xmlElementPattern = /<\\/?w:[^>]+>|<w:[^>]+\\/>/g;\n const escapedXmlPattern = /&lt;.*?&gt;|&quot;|&apos;/g;\n\n // Check for specific problematic patterns we've seen\n const problematicPatterns = [\n /<w:t\\s+xml:space=\"preserve\">/,\n /<w:t\\s+xml:space=[\"']preserve[\"']>/,\n /<\\/w:t>/,\n /&lt;w:t\\s+xml:space=&quot;preserve&quot;&gt;/,\n ];\n\n // Check for any XML-like tags\n if (xmlElementPattern.test(text)) {\n hasXmlPatterns = true;\n const contextStr = context ? ` in ${context}` : '';\n warnings.push(\n `Text${contextStr} contains XML-like markup: \"${text.substring(0, 100)}${text.length > 100 ? '...' : ''}\". ` +\n `This will be displayed as literal text in the document. ` +\n `If you intended to add formatting, use the appropriate API methods instead.`\n );\n }\n\n // Check for already-escaped XML entities\n if (escapedXmlPattern.test(text)) {\n hasXmlPatterns = true;\n const contextStr = context ? ` in ${context}` : '';\n warnings.push(\n `Text${contextStr} contains escaped XML entities (e.g., &lt;, &gt;, &quot;). ` +\n `These will appear as literal characters in the document.`\n );\n }\n\n // Check for specific known problematic patterns\n for (const pattern of problematicPatterns) {\n if (pattern.test(text)) {\n hasXmlPatterns = true;\n const contextStr = context ? ` in ${context}` : '';\n warnings.push(\n `Text${contextStr} contains a known problematic XML pattern that suggests ` +\n `the text may have been corrupted by previous processing.`\n );\n break;\n }\n }\n\n return {\n isValid: true, // Text is always \"valid\" - we just warn about potential issues\n hasXmlPatterns,\n warnings,\n };\n}\n\n/**\n * Cleans XML-like patterns from text\n *\n * This function removes or cleans various XML patterns that might\n * appear in text content, typically from corrupted or improperly\n * processed documents.\n *\n * @param text - The text to clean\n * @param aggressive - If true, removes all angle brackets; if false, only removes clear XML tags\n * @returns Cleaned text with XML patterns removed\n */\nexport function cleanXmlFromText(text: string, aggressive = false): string {\n let cleaned = text;\n\n // First, unescape any HTML/XML entities\n cleaned = cleaned\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, '&');\n\n // Remove specific Word XML patterns\n // This targets patterns like <w:t xml:space=\"preserve\">\n cleaned = cleaned.replace(/<w:[^>]+>/g, '');\n cleaned = cleaned.replace(/<\\/w:[^>]+>/g, '');\n\n // Remove any remaining XML-like tags if aggressive mode\n if (aggressive) {\n cleaned = cleaned.replace(/<[^>]+>/g, '');\n }\n\n // Clean up any double spaces left behind\n cleaned = cleaned.replace(/\\s+/g, ' ').trim();\n\n return cleaned;\n}\n\n/**\n * Validates text for use in Run or Hyperlink elements\n *\n * This is the main validation function that should be called when\n * setting text content in Run or Hyperlink elements. It provides\n * warnings about problematic content and optionally cleans the text.\n *\n * @param text - The text to validate\n * @param options - Validation options\n * @returns Validation result with warnings and optionally cleaned text\n */\nexport function validateRunText(\n text: string,\n options: {\n context?: string;\n autoClean?: boolean;\n aggressive?: boolean;\n warnToConsole?: boolean;\n } = {}\n): TextValidationResult {\n const { context, autoClean = false, aggressive = false, warnToConsole = true } = options;\n\n // Detect XML patterns\n const result = detectXmlInText(text, context);\n\n // If auto-cleaning is enabled and XML patterns were found\n if (autoClean && result.hasXmlPatterns) {\n result.cleanedText = cleanXmlFromText(text, aggressive);\n\n // Add a note about cleaning\n result.warnings.push(\n `Text has been automatically cleaned. ` +\n `Original: \"${text.substring(0, 50)}${text.length > 50 ? '...' : ''}\" ` +\n `Cleaned: \"${result.cleanedText.substring(0, 50)}${result.cleanedText.length > 50 ? '...' : ''}\"`\n );\n }\n\n // Log warnings to console in development if requested\n if (warnToConsole && result.warnings.length > 0 && typeof console !== 'undefined') {\n const contextStr = context ? ` [${context}]` : '';\n defaultLogger.warn(`DocXML Text Validation Warning${contextStr}:`);\n result.warnings.forEach((warning) => defaultLogger.warn(` - ${warning}`));\n }\n\n return result;\n}\n\n/**\n * Result of URL sanitization\n */\nexport interface SanitizeHyperlinkUrlResult {\n /** The sanitized URL */\n url: string;\n /** List of fixes that were applied */\n fixes: string[];\n}\n\n/**\n * Sanitizes hyperlink URLs by removing browser extension prefixes and fixing\n * broken protocols that result from the stripping.\n *\n * Known corruptions:\n * - Chrome extensions (e.g. Adobe Acrobat) wrap URLs with `chrome-extension://[id]/`\n * - Firefox extensions wrap URLs with `moz-extension://[uuid]/`\n * - Edge extensions wrap URLs with `extension://[id]/`\n * - After stripping, protocol may be broken (`https:/` instead of `https://`)\n *\n * @param url - The URL to sanitize\n * @returns Object with sanitized URL and list of fixes, or null if no changes needed\n */\nexport function sanitizeHyperlinkUrl(url: string): SanitizeHyperlinkUrlResult | null {\n if (!url) return null;\n\n const fixes: string[] = [];\n let sanitized = url;\n\n // Strip known browser extension URL prefixes\n const extensionPrefixes = [\n { pattern: /^chrome-extension:\\/\\/[a-z]{32}\\//i, name: 'Chrome extension' },\n {\n pattern: /^moz-extension:\\/\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\//i,\n name: 'Firefox extension',\n },\n { pattern: /^extension:\\/\\/[a-z0-9]{32}\\//i, name: 'Edge extension' },\n ];\n\n for (const { pattern, name } of extensionPrefixes) {\n const match = pattern.exec(sanitized);\n if (match) {\n sanitized = sanitized.substring(match[0].length);\n fixes.push(`Stripped ${name} URL prefix`);\n break;\n }\n }\n\n // Fix broken protocol after stripping (https:/ -> https://, http:/ -> http://)\n const brokenProtocol = /^(https?:\\/)([^/])/i.exec(sanitized);\n if (brokenProtocol) {\n sanitized =\n brokenProtocol[1] + '/' + brokenProtocol[2] + sanitized.substring(brokenProtocol[0].length);\n fixes.push('Fixed broken protocol (added missing slash)');\n }\n\n if (fixes.length === 0) return null;\n\n return { url: sanitized, fixes };\n}\n"]}
@@ -1,26 +1,41 @@
1
1
  import { getGlobalLogger } from './logger.js';
2
- const INVALID_XML_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
2
+ const INVALID_XML_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F￾￿]/g;
3
+ const UNPAIRED_SURROGATE_REGEX = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
3
4
  export function removeInvalidXmlChars(text, logWarning = true) {
4
5
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
5
- if (logWarning && INVALID_XML_CHAR_REGEX.test(text)) {
6
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
7
+ if (logWarning && (INVALID_XML_CHAR_REGEX.test(text) || UNPAIRED_SURROGATE_REGEX.test(text))) {
6
8
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
9
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
7
10
  const invalidChars = findInvalidXmlChars(text);
8
11
  const hexCodes = invalidChars
9
12
  .map((c) => `0x${c.toString(16).toUpperCase().padStart(2, '0')}`)
10
13
  .join(', ');
11
- getGlobalLogger().warn(`[XMLSanitization] Removing invalid XML control characters: ${hexCodes}`);
14
+ getGlobalLogger().warn(`[XMLSanitization] Removing invalid XML characters: ${hexCodes}`);
12
15
  }
13
16
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
14
- return text.replace(INVALID_XML_CHAR_REGEX, '');
17
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
18
+ return text.replace(INVALID_XML_CHAR_REGEX, '').replace(UNPAIRED_SURROGATE_REGEX, '');
15
19
  }
16
20
  export function findInvalidXmlChars(text) {
17
21
  const invalid = [];
18
22
  for (let i = 0; i < text.length; i++) {
19
23
  const code = text.charCodeAt(i);
20
- if ((code >= 0x00 && code <= 0x08) ||
24
+ let isInvalid = (code >= 0x00 && code <= 0x08) ||
21
25
  (code >= 0x0b && code <= 0x0c) ||
22
26
  (code >= 0x0e && code <= 0x1f) ||
23
- code === 0x7f) {
27
+ code === 0x7f ||
28
+ code === 0xfffe ||
29
+ code === 0xffff;
30
+ if (!isInvalid && code >= 0xd800 && code <= 0xdbff) {
31
+ const next = text.charCodeAt(i + 1);
32
+ isInvalid = !(next >= 0xdc00 && next <= 0xdfff);
33
+ }
34
+ else if (!isInvalid && code >= 0xdc00 && code <= 0xdfff) {
35
+ const prev = text.charCodeAt(i - 1);
36
+ isInvalid = !(prev >= 0xd800 && prev <= 0xdbff);
37
+ }
38
+ if (isInvalid) {
24
39
  if (!invalid.includes(code)) {
25
40
  invalid.push(code);
26
41
  }
@@ -30,7 +45,116 @@ export function findInvalidXmlChars(text) {
30
45
  }
31
46
  export function hasInvalidXmlChars(text) {
32
47
  INVALID_XML_CHAR_REGEX.lastIndex = 0;
33
- return INVALID_XML_CHAR_REGEX.test(text);
48
+ UNPAIRED_SURROGATE_REGEX.lastIndex = 0;
49
+ return INVALID_XML_CHAR_REGEX.test(text) || UNPAIRED_SURROGATE_REGEX.test(text);
50
+ }
51
+ const CT_RPR_CHILD_ORDER = [
52
+ 'rStyle',
53
+ 'rFonts',
54
+ 'b',
55
+ 'bCs',
56
+ 'i',
57
+ 'iCs',
58
+ 'caps',
59
+ 'smallCaps',
60
+ 'strike',
61
+ 'dstrike',
62
+ 'outline',
63
+ 'shadow',
64
+ 'emboss',
65
+ 'imprint',
66
+ 'noProof',
67
+ 'snapToGrid',
68
+ 'vanish',
69
+ 'webHidden',
70
+ 'color',
71
+ 'spacing',
72
+ 'w',
73
+ 'kern',
74
+ 'position',
75
+ 'sz',
76
+ 'szCs',
77
+ 'highlight',
78
+ 'u',
79
+ 'effect',
80
+ 'bdr',
81
+ 'shd',
82
+ 'fitText',
83
+ 'vertAlign',
84
+ 'rtl',
85
+ 'cs',
86
+ 'em',
87
+ 'lang',
88
+ 'eastAsianLayout',
89
+ 'specVanish',
90
+ 'oMath',
91
+ ];
92
+ function splitTopLevelElements(inner) {
93
+ const children = [];
94
+ const tagRegex = /<([A-Za-z][\w:.-]*)([^>]*?)(\/?)>|<\/([A-Za-z][\w:.-]*)\s*>/g;
95
+ let depth = 0;
96
+ let start = -1;
97
+ let match;
98
+ while ((match = tagRegex.exec(inner)) !== null) {
99
+ const isClosing = match[4] !== undefined;
100
+ const isSelfClosing = match[3] === '/';
101
+ if (isClosing) {
102
+ if (depth > 0) {
103
+ depth--;
104
+ if (depth === 0 && start !== -1) {
105
+ children.push(inner.slice(start, tagRegex.lastIndex));
106
+ start = -1;
107
+ }
108
+ }
109
+ continue;
110
+ }
111
+ if (isSelfClosing) {
112
+ if (depth === 0) {
113
+ children.push(inner.slice(match.index, tagRegex.lastIndex));
114
+ }
115
+ continue;
116
+ }
117
+ if (depth === 0)
118
+ start = match.index;
119
+ depth++;
120
+ }
121
+ return children;
122
+ }
123
+ export function reorderRunPropertyChildren(xml) {
124
+ if (!xml || !xml.includes('<w:rPr'))
125
+ return xml;
126
+ const orderIndex = (fragment) => {
127
+ const nameMatch = /^<w:(\w+)/.exec(fragment);
128
+ const name = nameMatch?.[1];
129
+ if (!name)
130
+ return CT_RPR_CHILD_ORDER.length;
131
+ const idx = CT_RPR_CHILD_ORDER.indexOf(name);
132
+ return idx === -1 ? CT_RPR_CHILD_ORDER.length : idx;
133
+ };
134
+ return xml.replace(/<w:rPr(\s[^>]*)?>([\s\S]*?)<\/w:rPr>/g, (full, attrs, inner) => {
135
+ if (!inner || !inner.includes('<'))
136
+ return full;
137
+ const children = splitTopLevelElements(inner);
138
+ if (children.length < 2)
139
+ return full;
140
+ const ordered = children
141
+ .map((frag, i) => ({ frag, i, order: orderIndex(frag) }))
142
+ .sort((a, b) => a.order - b.order || a.i - b.i);
143
+ if (ordered.every((c, i) => c.i === i))
144
+ return full;
145
+ return `<w:rPr${attrs ?? ''}>${ordered.map((c) => c.frag).join('')}</w:rPr>`;
146
+ });
147
+ }
148
+ export function normalizeAnchorSizeRel(xml) {
149
+ if (!xml)
150
+ return xml;
151
+ return xml
152
+ .replace(/(<wp14:sizeRelH\b[^>]*>)([^<]+)(<\/wp14:sizeRelH>)/g, (_m, open, text, close) => text.trim() === ''
153
+ ? `${open}${text}${close}`
154
+ : `${open}<wp14:pctWidth>${text.trim()}</wp14:pctWidth>${close}`)
155
+ .replace(/(<wp14:sizeRelV\b[^>]*>)([^<]+)(<\/wp14:sizeRelV>)/g, (_m, open, text, close) => text.trim() === ''
156
+ ? `${open}${text}${close}`
157
+ : `${open}<wp14:pctHeight>${text.trim()}</wp14:pctHeight>${close}`);
34
158
  }
35
159
  export const XML_CONTROL_CHARS = {
36
160
  NULL: 0x00,
@@ -1 +1 @@
1
- {"version":3,"file":"xmlSanitization.js","sourceRoot":"","sources":["../../../src/utils/xmlSanitization.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAO9C,MAAM,sBAAsB,GAAG,mCAAmC,CAAC;AA4BnE,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,UAAU,GAAG,IAAI;IAEnE,sBAAsB,CAAC,SAAS,GAAG,CAAC,CAAC;IAErC,IAAI,UAAU,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAEpD,sBAAsB,CAAC,SAAS,GAAG,CAAC,CAAC;QAErC,MAAM,YAAY,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,YAAY;aAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;aAChE,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,eAAe,EAAE,CAAC,IAAI,CACpB,8DAA8D,QAAQ,EAAE,CACzE,CAAC;IACJ,CAAC;IAGD,sBAAsB,CAAC,SAAS,GAAG,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;AAClD,CAAC;AAoBD,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAGhC,IACE,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAC9B,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAC9B,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAC9B,IAAI,KAAK,IAAI,EACb,CAAC;YAED,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAkBD,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAE7C,sBAAsB,CAAC,SAAS,GAAG,CAAC,CAAC;IACrC,OAAO,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3C,CAAC;AAKD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAE/B,IAAI,EAAE,IAAI;IAEV,GAAG,EAAE,IAAI;IAET,GAAG,EAAE,IAAI;IAET,GAAG,EAAE,IAAI;IAET,GAAG,EAAE,IAAI;IAET,GAAG,EAAE,IAAI;IAET,GAAG,EAAE,IAAI;IAET,GAAG,EAAE,IAAI;IAET,EAAE,EAAE,IAAI;IAER,GAAG,EAAE,IAAI;IAET,EAAE,EAAE,IAAI;IAER,EAAE,EAAE,IAAI;IAER,EAAE,EAAE,IAAI;IAER,EAAE,EAAE,IAAI;IAER,EAAE,EAAE,IAAI;IAER,EAAE,EAAE,IAAI;IAER,GAAG,EAAE,IAAI;CACD,CAAC","sourcesContent":["/**\n * XML Sanitization Utilities\n *\n * Provides functions for validating and sanitizing text content per XML 1.0 specification.\n * Per XML 1.0, certain control characters are invalid and must be removed before\n * including text in XML documents.\n *\n * Valid characters in XML 1.0:\n * - 0x09 (tab), 0x0A (newline), 0x0D (carriage return)\n * - 0x20-0xD7FF, 0xE000-0xFFFD, 0x10000-0x10FFFF\n *\n * Invalid characters (control characters that must be removed):\n * - 0x00-0x08 (NULL through BACKSPACE)\n * - 0x0B-0x0C (VERTICAL TAB and FORM FEED)\n * - 0x0E-0x1F (SHIFT OUT through UNIT SEPARATOR)\n * - 0x7F (DELETE)\n *\n * @module xmlSanitization\n */\n\nimport { getGlobalLogger } from './logger.js';\n\n/**\n * Regular expression matching invalid XML 1.0 control characters.\n * Matches: 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F\n * Does NOT match valid chars: 0x09 (tab), 0x0A (newline), 0x0D (CR)\n */\nconst INVALID_XML_CHAR_REGEX = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g;\n\n/**\n * Removes invalid XML 1.0 control characters from text.\n *\n * Per XML 1.0 spec, characters 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F are invalid\n * and cannot appear in XML documents. This function removes them.\n *\n * Valid control characters are preserved:\n * - Tab (0x09)\n * - Line Feed / Newline (0x0A)\n * - Carriage Return (0x0D)\n *\n * @param text - Input text to sanitize\n * @param logWarning - If true, logs a warning when invalid chars are found (default: true)\n * @returns Sanitized text with invalid control characters removed\n *\n * @example\n * ```typescript\n * // Remove NULL byte from text\n * const clean = removeInvalidXmlChars(\"Hello\\x00World\");\n * // Returns: \"HelloWorld\"\n *\n * // Tab and newline are preserved\n * const preserved = removeInvalidXmlChars(\"Hello\\tWorld\\n\");\n * // Returns: \"Hello\\tWorld\\n\"\n * ```\n */\nexport function removeInvalidXmlChars(text: string, logWarning = true): string {\n // Reset regex lastIndex for global regex\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n\n if (logWarning && INVALID_XML_CHAR_REGEX.test(text)) {\n // Reset regex lastIndex after test\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n\n const invalidChars = findInvalidXmlChars(text);\n const hexCodes = invalidChars\n .map((c) => `0x${c.toString(16).toUpperCase().padStart(2, '0')}`)\n .join(', ');\n getGlobalLogger().warn(\n `[XMLSanitization] Removing invalid XML control characters: ${hexCodes}`\n );\n }\n\n // Reset regex lastIndex before replace\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n return text.replace(INVALID_XML_CHAR_REGEX, '');\n}\n\n/**\n * Finds all invalid XML 1.0 control characters in text.\n *\n * Returns an array of unique character codes that are invalid per XML 1.0 spec.\n * This is useful for diagnostics and error reporting.\n *\n * @param text - Text to scan for invalid characters\n * @returns Array of unique invalid character codes found, or empty array if text is valid\n *\n * @example\n * ```typescript\n * const invalid = findInvalidXmlChars(\"Hello\\x00\\x08World\");\n * // Returns: [0, 8] - NULL and BACKSPACE codes\n *\n * const valid = findInvalidXmlChars(\"Hello\\tWorld\");\n * // Returns: [] - tab is valid\n * ```\n */\nexport function findInvalidXmlChars(text: string): number[] {\n const invalid: number[] = [];\n\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n\n // Check if character is in invalid ranges\n if (\n (code >= 0x00 && code <= 0x08) || // NULL through BACKSPACE\n (code >= 0x0b && code <= 0x0c) || // VERTICAL TAB and FORM FEED\n (code >= 0x0e && code <= 0x1f) || // SHIFT OUT through UNIT SEPARATOR\n code === 0x7f // DELETE\n ) {\n // Only add unique codes\n if (!invalid.includes(code)) {\n invalid.push(code);\n }\n }\n }\n\n return invalid;\n}\n\n/**\n * Checks if text contains any invalid XML 1.0 control characters.\n *\n * This is a fast check that returns true/false without identifying specific characters.\n * Use `findInvalidXmlChars()` if you need to know which characters are invalid.\n *\n * @param text - Text to check\n * @returns true if text contains invalid characters, false otherwise\n *\n * @example\n * ```typescript\n * hasInvalidXmlChars(\"Hello\\x00World\"); // true - NULL byte\n * hasInvalidXmlChars(\"Hello\\tWorld\"); // false - tab is valid\n * hasInvalidXmlChars(\"Normal text\"); // false\n * ```\n */\nexport function hasInvalidXmlChars(text: string): boolean {\n // Reset regex lastIndex for global regex\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n return INVALID_XML_CHAR_REGEX.test(text);\n}\n\n/**\n * Character code constants for documentation and testing.\n */\nexport const XML_CONTROL_CHARS = {\n /** NULL (0x00) - Invalid */\n NULL: 0x00,\n /** Start of Heading (0x01) - Invalid */\n SOH: 0x01,\n /** Start of Text (0x02) - Invalid */\n STX: 0x02,\n /** End of Text (0x03) - Invalid */\n ETX: 0x03,\n /** End of Transmission (0x04) - Invalid */\n EOT: 0x04,\n /** Enquiry (0x05) - Invalid */\n ENQ: 0x05,\n /** Acknowledge (0x06) - Invalid */\n ACK: 0x06,\n /** Bell (0x07) - Invalid */\n BEL: 0x07,\n /** Backspace (0x08) - Invalid */\n BS: 0x08,\n /** Horizontal Tab (0x09) - VALID */\n TAB: 0x09,\n /** Line Feed / Newline (0x0A) - VALID */\n LF: 0x0a,\n /** Vertical Tab (0x0B) - Invalid */\n VT: 0x0b,\n /** Form Feed (0x0C) - Invalid */\n FF: 0x0c,\n /** Carriage Return (0x0D) - VALID */\n CR: 0x0d,\n /** Shift Out (0x0E) - Invalid */\n SO: 0x0e,\n /** Unit Separator (0x1F) - Invalid */\n US: 0x1f,\n /** Delete (0x7F) - Invalid */\n DEL: 0x7f,\n} as const;\n"]}
1
+ {"version":3,"file":"xmlSanitization.js","sourceRoot":"","sources":["../../../src/utils/xmlSanitization.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * XML Sanitization Utilities\n *\n * Provides functions for validating and sanitizing text content per XML 1.0 specification.\n * Per XML 1.0, certain control characters are invalid and must be removed before\n * including text in XML documents.\n *\n * Valid characters in XML 1.0:\n * - 0x09 (tab), 0x0A (newline), 0x0D (carriage return)\n * - 0x20-0xD7FF, 0xE000-0xFFFD, 0x10000-0x10FFFF\n *\n * Invalid characters that must be removed:\n * - 0x00-0x08 (NULL through BACKSPACE)\n * - 0x0B-0x0C (VERTICAL TAB and FORM FEED)\n * - 0x0E-0x1F (SHIFT OUT through UNIT SEPARATOR)\n * - 0x7F (DELETE)\n * - 0xFFFE-0xFFFF (Unicode noncharacters excluded by the Char production)\n * - Unpaired surrogates (0xD800-0xDFFF outside a valid high/low pair)\n *\n * @module xmlSanitization\n */\n\nimport { getGlobalLogger } from './logger.js';\n\n/**\n * Regular expression matching invalid XML 1.0 characters.\n * Matches: 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F, 0xFFFE, 0xFFFF\n * Does NOT match valid chars: 0x09 (tab), 0x0A (newline), 0x0D (CR)\n */\nconst INVALID_XML_CHAR_REGEX = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F￾￿]/g;\n\n/**\n * Matches surrogate code units that do not form a valid high/low pair.\n * XML 1.0 Char excludes 0xD800-0xDFFF entirely; only properly paired\n * surrogates (which decode to a single 0x10000-0x10FFFF character) are\n * legal, so lone halves must be stripped or they corrupt the part at\n * UTF-8 encode time.\n */\nconst UNPAIRED_SURROGATE_REGEX =\n /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g;\n\n/**\n * Removes invalid XML 1.0 characters from text.\n *\n * Per XML 1.0 spec, characters 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F,\n * 0xFFFE, 0xFFFF, and unpaired surrogates are invalid and cannot appear\n * in XML documents. This function removes them.\n *\n * Valid control characters are preserved:\n * - Tab (0x09)\n * - Line Feed / Newline (0x0A)\n * - Carriage Return (0x0D)\n *\n * Properly paired surrogates (astral-plane characters such as emoji)\n * are preserved.\n *\n * @param text - Input text to sanitize\n * @param logWarning - If true, logs a warning when invalid chars are found (default: true)\n * @returns Sanitized text with invalid characters removed\n *\n * @example\n * ```typescript\n * // Remove NULL byte from text\n * const clean = removeInvalidXmlChars(\"Hello\\x00World\");\n * // Returns: \"HelloWorld\"\n *\n * // Tab and newline are preserved\n * const preserved = removeInvalidXmlChars(\"Hello\\tWorld\\n\");\n * // Returns: \"Hello\\tWorld\\n\"\n * ```\n */\nexport function removeInvalidXmlChars(text: string, logWarning = true): string {\n // Reset regex lastIndex for global regexes\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n UNPAIRED_SURROGATE_REGEX.lastIndex = 0;\n\n if (logWarning && (INVALID_XML_CHAR_REGEX.test(text) || UNPAIRED_SURROGATE_REGEX.test(text))) {\n // Reset regex lastIndex after test\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n UNPAIRED_SURROGATE_REGEX.lastIndex = 0;\n\n const invalidChars = findInvalidXmlChars(text);\n const hexCodes = invalidChars\n .map((c) => `0x${c.toString(16).toUpperCase().padStart(2, '0')}`)\n .join(', ');\n getGlobalLogger().warn(`[XMLSanitization] Removing invalid XML characters: ${hexCodes}`);\n }\n\n // Reset regex lastIndex before replace\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n UNPAIRED_SURROGATE_REGEX.lastIndex = 0;\n return text.replace(INVALID_XML_CHAR_REGEX, '').replace(UNPAIRED_SURROGATE_REGEX, '');\n}\n\n/**\n * Finds all invalid XML 1.0 characters in text.\n *\n * Returns an array of unique character codes that are invalid per XML 1.0 spec,\n * including 0xFFFE/0xFFFF noncharacters and unpaired surrogate code units.\n * This is useful for diagnostics and error reporting.\n *\n * @param text - Text to scan for invalid characters\n * @returns Array of unique invalid character codes found, or empty array if text is valid\n *\n * @example\n * ```typescript\n * const invalid = findInvalidXmlChars(\"Hello\\x00\\x08World\");\n * // Returns: [0, 8] - NULL and BACKSPACE codes\n *\n * const valid = findInvalidXmlChars(\"Hello\\tWorld\");\n * // Returns: [] - tab is valid\n * ```\n */\nexport function findInvalidXmlChars(text: string): number[] {\n const invalid: number[] = [];\n\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n\n // Check if character is in invalid ranges\n let isInvalid =\n (code >= 0x00 && code <= 0x08) || // NULL through BACKSPACE\n (code >= 0x0b && code <= 0x0c) || // VERTICAL TAB and FORM FEED\n (code >= 0x0e && code <= 0x1f) || // SHIFT OUT through UNIT SEPARATOR\n code === 0x7f || // DELETE\n code === 0xfffe || // Noncharacter excluded by XML 1.0 Char\n code === 0xffff; // Noncharacter excluded by XML 1.0 Char\n\n if (!isInvalid && code >= 0xd800 && code <= 0xdbff) {\n // High surrogate is only valid when followed by a low surrogate\n const next = text.charCodeAt(i + 1); // NaN at end of string fails the range check\n isInvalid = !(next >= 0xdc00 && next <= 0xdfff);\n } else if (!isInvalid && code >= 0xdc00 && code <= 0xdfff) {\n // Low surrogate is only valid when preceded by a high surrogate\n const prev = text.charCodeAt(i - 1); // NaN at start of string fails the range check\n isInvalid = !(prev >= 0xd800 && prev <= 0xdbff);\n }\n\n if (isInvalid) {\n // Only add unique codes\n if (!invalid.includes(code)) {\n invalid.push(code);\n }\n }\n }\n\n return invalid;\n}\n\n/**\n * Checks if text contains any invalid XML 1.0 characters.\n *\n * This is a fast check that returns true/false without identifying specific characters.\n * Use `findInvalidXmlChars()` if you need to know which characters are invalid.\n *\n * @param text - Text to check\n * @returns true if text contains invalid characters, false otherwise\n *\n * @example\n * ```typescript\n * hasInvalidXmlChars(\"Hello\\x00World\"); // true - NULL byte\n * hasInvalidXmlChars(\"Hello\\tWorld\"); // false - tab is valid\n * hasInvalidXmlChars(\"Normal text\"); // false\n * ```\n */\nexport function hasInvalidXmlChars(text: string): boolean {\n // Reset regex lastIndex for global regexes\n INVALID_XML_CHAR_REGEX.lastIndex = 0;\n UNPAIRED_SURROGATE_REGEX.lastIndex = 0;\n return INVALID_XML_CHAR_REGEX.test(text) || UNPAIRED_SURROGATE_REGEX.test(text);\n}\n\n/**\n * CT_RPr child element order per ECMA-376 Part 1 §17.3.2.28 (local names,\n * `w:` namespace). Used to repair raw-preserved run-property blocks whose\n * children are out of schema order — Word rejects, for example, a `w:b`\n * that appears after `w:color`/`w:sz` and reports the document as corrupt.\n */\nconst CT_RPR_CHILD_ORDER: readonly string[] = [\n 'rStyle',\n 'rFonts',\n 'b',\n 'bCs',\n 'i',\n 'iCs',\n 'caps',\n 'smallCaps',\n 'strike',\n 'dstrike',\n 'outline',\n 'shadow',\n 'emboss',\n 'imprint',\n 'noProof',\n 'snapToGrid',\n 'vanish',\n 'webHidden',\n 'color',\n 'spacing',\n 'w',\n 'kern',\n 'position',\n 'sz',\n 'szCs',\n 'highlight',\n 'u',\n 'effect',\n 'bdr',\n 'shd',\n 'fitText',\n 'vertAlign',\n 'rtl',\n 'cs',\n 'em',\n 'lang',\n 'eastAsianLayout',\n 'specVanish',\n 'oMath',\n];\n\n/**\n * Splits the inner content of an element into its top-level child elements,\n * respecting nesting so that a child's descendants are kept with it. Returns\n * the raw XML fragment for each direct child in document order. Non-element\n * text between children (whitespace) is dropped, which is safe for the\n * element-only content models this helper targets (e.g. `w:rPr`).\n */\nfunction splitTopLevelElements(inner: string): string[] {\n const children: string[] = [];\n const tagRegex = /<([A-Za-z][\\w:.-]*)([^>]*?)(\\/?)>|<\\/([A-Za-z][\\w:.-]*)\\s*>/g;\n let depth = 0;\n let start = -1;\n let match: RegExpExecArray | null;\n while ((match = tagRegex.exec(inner)) !== null) {\n const isClosing = match[4] !== undefined;\n const isSelfClosing = match[3] === '/';\n if (isClosing) {\n if (depth > 0) {\n depth--;\n if (depth === 0 && start !== -1) {\n children.push(inner.slice(start, tagRegex.lastIndex));\n start = -1;\n }\n }\n continue;\n }\n if (isSelfClosing) {\n if (depth === 0) {\n children.push(inner.slice(match.index, tagRegex.lastIndex));\n }\n continue;\n }\n // Opening tag of a paired element\n if (depth === 0) start = match.index;\n depth++;\n }\n return children;\n}\n\n/**\n * Reorders the direct children of every `<w:rPr>` block in a raw XML part to\n * the ECMA-376 CT_RPr sequence. Unknown/unmodeled children retain their\n * relative position at the end of the block. The transform is idempotent:\n * already-ordered run properties are returned unchanged.\n *\n * Some producers (and older versions of this framework) appended boolean\n * toggles such as `w:b`/`w:bCs` to the end of an existing `w:rPr`, leaving\n * them after `w:color`/`w:sz`. Word treats the out-of-order child as an\n * unexpected element and flags the whole document as corrupt. Numbering\n * definitions are written back verbatim for fidelity, so this repair runs on\n * the preserved part before it is saved.\n *\n * @param xml - Raw XML for a part containing `w:rPr` blocks (e.g. numbering.xml)\n * @returns XML with every `w:rPr` child sequence normalized to schema order\n */\nexport function reorderRunPropertyChildren(xml: string): string {\n if (!xml || !xml.includes('<w:rPr')) return xml;\n\n const orderIndex = (fragment: string): number => {\n const nameMatch = /^<w:(\\w+)/.exec(fragment);\n const name = nameMatch?.[1];\n if (!name) return CT_RPR_CHILD_ORDER.length;\n const idx = CT_RPR_CHILD_ORDER.indexOf(name);\n return idx === -1 ? CT_RPR_CHILD_ORDER.length : idx;\n };\n\n // Only match non-empty <w:rPr>...</w:rPr> blocks; self-closing <w:rPr/> has\n // no children to reorder.\n return xml.replace(/<w:rPr(\\s[^>]*)?>([\\s\\S]*?)<\\/w:rPr>/g, (full, attrs, inner) => {\n if (!inner || !(inner as string).includes('<')) return full;\n const children = splitTopLevelElements(inner);\n if (children.length < 2) return full;\n\n const ordered = children\n .map((frag, i) => ({ frag, i, order: orderIndex(frag) }))\n // Stable sort: ties (including unknown children) keep original order.\n .sort((a, b) => a.order - b.order || a.i - b.i);\n\n if (ordered.every((c, i) => c.i === i)) return full; // already in order\n\n return `<w:rPr${attrs ?? ''}>${ordered.map((c) => c.frag).join('')}</w:rPr>`;\n });\n}\n\n/**\n * Normalizes malformed floating-drawing relative-size elements in a raw anchor\n * fragment. Per ECMA-376 (DrawingML wordprocessing 2010 extensions),\n * `wp14:sizeRelH` must contain a `wp14:pctWidth` child and `wp14:sizeRelV` a\n * `wp14:pctHeight` child. Some producers emit the percentage as bare text\n * content (`<wp14:sizeRelH relativeFrom=\"margin\">0</wp14:sizeRelH>`), which\n * Word rejects as incomplete content. This wraps such text in the required\n * child element. Already-valid nested forms are left untouched because the\n * text-content pattern (`[^<]`) cannot match an element child.\n *\n * @param xml - Raw anchor-extras fragment captured during parsing\n * @returns Fragment with sizeRelH/sizeRelV normalized to schema-valid form\n */\nexport function normalizeAnchorSizeRel(xml: string): string {\n if (!xml) return xml;\n return xml\n .replace(\n /(<wp14:sizeRelH\\b[^>]*>)([^<]+)(<\\/wp14:sizeRelH>)/g,\n (_m, open: string, text: string, close: string) =>\n text.trim() === ''\n ? `${open}${text}${close}`\n : `${open}<wp14:pctWidth>${text.trim()}</wp14:pctWidth>${close}`\n )\n .replace(\n /(<wp14:sizeRelV\\b[^>]*>)([^<]+)(<\\/wp14:sizeRelV>)/g,\n (_m, open: string, text: string, close: string) =>\n text.trim() === ''\n ? `${open}${text}${close}`\n : `${open}<wp14:pctHeight>${text.trim()}</wp14:pctHeight>${close}`\n );\n}\n\n/**\n * Character code constants for documentation and testing.\n */\nexport const XML_CONTROL_CHARS = {\n /** NULL (0x00) - Invalid */\n NULL: 0x00,\n /** Start of Heading (0x01) - Invalid */\n SOH: 0x01,\n /** Start of Text (0x02) - Invalid */\n STX: 0x02,\n /** End of Text (0x03) - Invalid */\n ETX: 0x03,\n /** End of Transmission (0x04) - Invalid */\n EOT: 0x04,\n /** Enquiry (0x05) - Invalid */\n ENQ: 0x05,\n /** Acknowledge (0x06) - Invalid */\n ACK: 0x06,\n /** Bell (0x07) - Invalid */\n BEL: 0x07,\n /** Backspace (0x08) - Invalid */\n BS: 0x08,\n /** Horizontal Tab (0x09) - VALID */\n TAB: 0x09,\n /** Line Feed / Newline (0x0A) - VALID */\n LF: 0x0a,\n /** Vertical Tab (0x0B) - Invalid */\n VT: 0x0b,\n /** Form Feed (0x0C) - Invalid */\n FF: 0x0c,\n /** Carriage Return (0x0D) - VALID */\n CR: 0x0d,\n /** Shift Out (0x0E) - Invalid */\n SO: 0x0e,\n /** Unit Separator (0x1F) - Invalid */\n US: 0x1f,\n /** Delete (0x7F) - Invalid */\n DEL: 0x7f,\n} as const;\n"]}
@@ -1,5 +1,5 @@
1
1
  import { REVISION_RULES, } from './ValidationRules.js';
2
- import { RevisionValidator } from './RevisionValidator.js';
2
+ import { CONTENTLESS_REVISION_TYPES, RevisionValidator, revisionHasContent, } from './RevisionValidator.js';
3
3
  export class RevisionAutoFixer {
4
4
  static fix(doc, options) {
5
5
  const actions = [];
@@ -33,7 +33,10 @@ export class RevisionAutoFixer {
33
33
  actions.push(...this.fixMissingAuthors(revisions, defaultAuthor, options?.dryRun));
34
34
  }
35
35
  if (shouldProcess('REV003') || shouldProcess('REV004')) {
36
- actions.push(...this.fixOrphanedMoveMarkers(revisionManager, revisions, options?.dryRun));
36
+ actions.push(...this.fixOrphanedMoveMarkers(revisionManager, revisions, options?.dryRun, doc, {
37
+ moveFrom: shouldProcess('REV003'),
38
+ moveTo: shouldProcess('REV004'),
39
+ }));
37
40
  }
38
41
  if (shouldProcess('REV101')) {
39
42
  actions.push(...this.fixMissingDates(revisions, options?.dryRun));
@@ -42,7 +45,7 @@ export class RevisionAutoFixer {
42
45
  actions.push(...this.fixInvalidDates(revisions, options?.dryRun));
43
46
  }
44
47
  if (shouldProcess('REV103')) {
45
- actions.push(...this.fixEmptyRevisions(revisionManager, revisions, options?.dryRun));
48
+ actions.push(...this.fixEmptyRevisions(revisionManager, revisions, options?.dryRun, doc));
46
49
  }
47
50
  if (shouldProcess('REV104')) {
48
51
  actions.push(...this.fixNonSequentialIds(revisions, options?.dryRun));
@@ -175,8 +178,18 @@ export class RevisionAutoFixer {
175
178
  }
176
179
  return actions;
177
180
  }
178
- static fixOrphanedMoveMarkers(revisionManager, revisions, dryRun) {
181
+ static removeRevisionFromContent(doc, revision) {
182
+ for (const paragraph of doc.getAllParagraphs()) {
183
+ if (paragraph.replaceContent(revision, [])) {
184
+ return true;
185
+ }
186
+ }
187
+ return false;
188
+ }
189
+ static fixOrphanedMoveMarkers(revisionManager, revisions, dryRun, doc, ruleFilter) {
179
190
  const actions = [];
191
+ const fixMoveFrom = ruleFilter?.moveFrom ?? true;
192
+ const fixMoveTo = ruleFilter?.moveTo ?? true;
180
193
  const moveFromIds = new Map();
181
194
  const moveToIds = new Map();
182
195
  for (const rev of revisions) {
@@ -190,70 +203,68 @@ export class RevisionAutoFixer {
190
203
  moveToIds.set(moveId, rev);
191
204
  }
192
205
  }
193
- for (const [moveId, rev] of moveFromIds) {
194
- if (!moveToIds.has(moveId)) {
195
- const issue = {
196
- code: REVISION_RULES.ORPHANED_MOVE_FROM.code,
197
- severity: 'error',
198
- message: `Orphaned moveFrom with moveId="${moveId}"`,
199
- location: { revisionId: rev.getId() },
200
- autoFixable: true,
201
- };
202
- actions.push({
203
- issue,
204
- action: `Removed orphaned moveFrom (ID: ${rev.getId()}, moveId: ${moveId})`,
205
- before: { type: 'moveFrom', moveId },
206
- after: null,
207
- success: true,
208
- });
209
- if (!dryRun) {
210
- revisionManager.removeById(rev.getId());
206
+ if (fixMoveFrom) {
207
+ for (const [moveId, rev] of moveFromIds) {
208
+ if (!moveToIds.has(moveId)) {
209
+ const issue = {
210
+ code: REVISION_RULES.ORPHANED_MOVE_FROM.code,
211
+ severity: 'error',
212
+ message: `Orphaned moveFrom with moveId="${moveId}"`,
213
+ location: { revisionId: rev.getId() },
214
+ autoFixable: true,
215
+ };
216
+ actions.push({
217
+ issue,
218
+ action: `Removed orphaned moveFrom (ID: ${rev.getId()}, moveId: ${moveId})`,
219
+ before: { type: 'moveFrom', moveId },
220
+ after: null,
221
+ success: true,
222
+ });
223
+ if (!dryRun) {
224
+ revisionManager.removeById(rev.getId());
225
+ if (doc) {
226
+ this.removeRevisionFromContent(doc, rev);
227
+ }
228
+ }
211
229
  }
212
230
  }
213
231
  }
214
- for (const [moveId, rev] of moveToIds) {
215
- if (!moveFromIds.has(moveId)) {
216
- const issue = {
217
- code: REVISION_RULES.ORPHANED_MOVE_TO.code,
218
- severity: 'error',
219
- message: `Orphaned moveTo with moveId="${moveId}"`,
220
- location: { revisionId: rev.getId() },
221
- autoFixable: true,
222
- };
223
- actions.push({
224
- issue,
225
- action: `Removed orphaned moveTo (ID: ${rev.getId()}, moveId: ${moveId})`,
226
- before: { type: 'moveTo', moveId },
227
- after: null,
228
- success: true,
229
- });
230
- if (!dryRun) {
231
- revisionManager.removeById(rev.getId());
232
+ if (fixMoveTo) {
233
+ for (const [moveId, rev] of moveToIds) {
234
+ if (!moveFromIds.has(moveId)) {
235
+ const issue = {
236
+ code: REVISION_RULES.ORPHANED_MOVE_TO.code,
237
+ severity: 'error',
238
+ message: `Orphaned moveTo with moveId="${moveId}"`,
239
+ location: { revisionId: rev.getId() },
240
+ autoFixable: true,
241
+ };
242
+ actions.push({
243
+ issue,
244
+ action: `Removed orphaned moveTo (ID: ${rev.getId()}, moveId: ${moveId})`,
245
+ before: { type: 'moveTo', moveId },
246
+ after: null,
247
+ success: true,
248
+ });
249
+ if (!dryRun) {
250
+ revisionManager.removeById(rev.getId());
251
+ if (doc) {
252
+ this.removeRevisionFromContent(doc, rev);
253
+ }
254
+ }
232
255
  }
233
256
  }
234
257
  }
235
258
  return actions;
236
259
  }
237
- static fixEmptyRevisions(revisionManager, revisions, dryRun) {
260
+ static fixEmptyRevisions(revisionManager, revisions, dryRun, doc) {
238
261
  const actions = [];
239
- const propertyChangeTypes = [
240
- 'runPropertiesChange',
241
- 'paragraphPropertiesChange',
242
- 'tablePropertiesChange',
243
- 'tableExceptionPropertiesChange',
244
- 'tableRowPropertiesChange',
245
- 'tableCellPropertiesChange',
246
- 'sectionPropertiesChange',
247
- 'numberingChange',
248
- ];
249
262
  for (const rev of revisions) {
250
263
  const type = rev.getType();
251
- if (propertyChangeTypes.includes(type)) {
264
+ if (CONTENTLESS_REVISION_TYPES.has(type)) {
252
265
  continue;
253
266
  }
254
- const runs = rev.getRuns();
255
- const hasContent = runs.length > 0 && runs.some((r) => r.getText().length > 0);
256
- if (!hasContent) {
267
+ if (!revisionHasContent(rev)) {
257
268
  const issue = {
258
269
  code: REVISION_RULES.EMPTY_REVISION.code,
259
270
  severity: 'warning',
@@ -270,6 +281,9 @@ export class RevisionAutoFixer {
270
281
  });
271
282
  if (!dryRun) {
272
283
  revisionManager.removeById(rev.getId());
284
+ if (doc) {
285
+ this.removeRevisionFromContent(doc, rev);
286
+ }
273
287
  }
274
288
  }
275
289
  }