docxmlater 11.0.9 → 12.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +51 -13
  2. package/dist/core/Document.d.ts +28 -2
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +725 -170
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentGenerator.d.ts +1 -0
  7. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  8. package/dist/core/DocumentGenerator.js +77 -25
  9. package/dist/core/DocumentGenerator.js.map +1 -1
  10. package/dist/core/DocumentParser.d.ts +7 -3
  11. package/dist/core/DocumentParser.d.ts.map +1 -1
  12. package/dist/core/DocumentParser.js +431 -85
  13. package/dist/core/DocumentParser.js.map +1 -1
  14. package/dist/core/RelationshipManager.d.ts.map +1 -1
  15. package/dist/core/RelationshipManager.js +2 -2
  16. package/dist/core/RelationshipManager.js.map +1 -1
  17. package/dist/elements/Comment.d.ts +15 -1
  18. package/dist/elements/Comment.d.ts.map +1 -1
  19. package/dist/elements/Comment.js +67 -12
  20. package/dist/elements/Comment.js.map +1 -1
  21. package/dist/elements/CommentManager.d.ts +4 -0
  22. package/dist/elements/CommentManager.d.ts.map +1 -1
  23. package/dist/elements/CommentManager.js +74 -16
  24. package/dist/elements/CommentManager.js.map +1 -1
  25. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  26. package/dist/elements/EndnoteManager.js.map +1 -1
  27. package/dist/elements/Field.d.ts +10 -1
  28. package/dist/elements/Field.d.ts.map +1 -1
  29. package/dist/elements/Field.js +76 -34
  30. package/dist/elements/Field.js.map +1 -1
  31. package/dist/elements/FieldHelpers.d.ts +1 -0
  32. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  33. package/dist/elements/FieldHelpers.js +8 -4
  34. package/dist/elements/FieldHelpers.js.map +1 -1
  35. package/dist/elements/Footer.d.ts +3 -1
  36. package/dist/elements/Footer.d.ts.map +1 -1
  37. package/dist/elements/Footer.js +39 -53
  38. package/dist/elements/Footer.js.map +1 -1
  39. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  40. package/dist/elements/FootnoteManager.js.map +1 -1
  41. package/dist/elements/Header.d.ts +3 -1
  42. package/dist/elements/Header.d.ts.map +1 -1
  43. package/dist/elements/Header.js +39 -53
  44. package/dist/elements/Header.js.map +1 -1
  45. package/dist/elements/HeaderFooterManager.d.ts +4 -2
  46. package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
  47. package/dist/elements/HeaderFooterManager.js +36 -6
  48. package/dist/elements/HeaderFooterManager.js.map +1 -1
  49. package/dist/elements/Hyperlink.d.ts +6 -1
  50. package/dist/elements/Hyperlink.d.ts.map +1 -1
  51. package/dist/elements/Hyperlink.js +69 -44
  52. package/dist/elements/Hyperlink.js.map +1 -1
  53. package/dist/elements/Image.d.ts.map +1 -1
  54. package/dist/elements/Image.js +33 -6
  55. package/dist/elements/Image.js.map +1 -1
  56. package/dist/elements/ImageManager.d.ts +1 -0
  57. package/dist/elements/ImageManager.d.ts.map +1 -1
  58. package/dist/elements/ImageManager.js +11 -0
  59. package/dist/elements/ImageManager.js.map +1 -1
  60. package/dist/elements/Paragraph.d.ts +3 -0
  61. package/dist/elements/Paragraph.d.ts.map +1 -1
  62. package/dist/elements/Paragraph.js +91 -26
  63. package/dist/elements/Paragraph.js.map +1 -1
  64. package/dist/elements/Revision.d.ts +4 -0
  65. package/dist/elements/Revision.d.ts.map +1 -1
  66. package/dist/elements/Revision.js +369 -29
  67. package/dist/elements/Revision.js.map +1 -1
  68. package/dist/elements/Run.d.ts +1 -0
  69. package/dist/elements/Run.d.ts.map +1 -1
  70. package/dist/elements/Run.js +24 -4
  71. package/dist/elements/Run.js.map +1 -1
  72. package/dist/elements/Section.d.ts.map +1 -1
  73. package/dist/elements/Section.js +17 -7
  74. package/dist/elements/Section.js.map +1 -1
  75. package/dist/elements/Shape.d.ts.map +1 -1
  76. package/dist/elements/Shape.js +50 -42
  77. package/dist/elements/Shape.js.map +1 -1
  78. package/dist/elements/StructuredDocumentTag.d.ts +9 -1
  79. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  80. package/dist/elements/StructuredDocumentTag.js +46 -6
  81. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  82. package/dist/elements/Table.d.ts +2 -0
  83. package/dist/elements/Table.d.ts.map +1 -1
  84. package/dist/elements/Table.js +90 -42
  85. package/dist/elements/Table.js.map +1 -1
  86. package/dist/elements/TableCell.d.ts +1 -0
  87. package/dist/elements/TableCell.d.ts.map +1 -1
  88. package/dist/elements/TableCell.js +32 -13
  89. package/dist/elements/TableCell.js.map +1 -1
  90. package/dist/elements/TableOfContents.d.ts.map +1 -1
  91. package/dist/elements/TableOfContents.js +5 -7
  92. package/dist/elements/TableOfContents.js.map +1 -1
  93. package/dist/elements/TableRow.d.ts +1 -0
  94. package/dist/elements/TableRow.d.ts.map +1 -1
  95. package/dist/elements/TableRow.js +16 -1
  96. package/dist/elements/TableRow.js.map +1 -1
  97. package/dist/elements/TextBox.d.ts.map +1 -1
  98. package/dist/elements/TextBox.js +55 -42
  99. package/dist/elements/TextBox.js.map +1 -1
  100. package/dist/esm/core/Document.js +726 -171
  101. package/dist/esm/core/Document.js.map +1 -1
  102. package/dist/esm/core/DocumentGenerator.js +77 -25
  103. package/dist/esm/core/DocumentGenerator.js.map +1 -1
  104. package/dist/esm/core/DocumentParser.js +431 -85
  105. package/dist/esm/core/DocumentParser.js.map +1 -1
  106. package/dist/esm/core/RelationshipManager.js +2 -2
  107. package/dist/esm/core/RelationshipManager.js.map +1 -1
  108. package/dist/esm/elements/Comment.js +67 -12
  109. package/dist/esm/elements/Comment.js.map +1 -1
  110. package/dist/esm/elements/CommentManager.js +74 -16
  111. package/dist/esm/elements/CommentManager.js.map +1 -1
  112. package/dist/esm/elements/EndnoteManager.js.map +1 -1
  113. package/dist/esm/elements/Field.js +77 -35
  114. package/dist/esm/elements/Field.js.map +1 -1
  115. package/dist/esm/elements/FieldHelpers.js +7 -4
  116. package/dist/esm/elements/FieldHelpers.js.map +1 -1
  117. package/dist/esm/elements/Footer.js +39 -53
  118. package/dist/esm/elements/Footer.js.map +1 -1
  119. package/dist/esm/elements/FootnoteManager.js.map +1 -1
  120. package/dist/esm/elements/Header.js +39 -53
  121. package/dist/esm/elements/Header.js.map +1 -1
  122. package/dist/esm/elements/HeaderFooterManager.js +36 -6
  123. package/dist/esm/elements/HeaderFooterManager.js.map +1 -1
  124. package/dist/esm/elements/Hyperlink.js +69 -44
  125. package/dist/esm/elements/Hyperlink.js.map +1 -1
  126. package/dist/esm/elements/Image.js +33 -6
  127. package/dist/esm/elements/Image.js.map +1 -1
  128. package/dist/esm/elements/ImageManager.js +11 -0
  129. package/dist/esm/elements/ImageManager.js.map +1 -1
  130. package/dist/esm/elements/Paragraph.js +91 -26
  131. package/dist/esm/elements/Paragraph.js.map +1 -1
  132. package/dist/esm/elements/Revision.js +369 -29
  133. package/dist/esm/elements/Revision.js.map +1 -1
  134. package/dist/esm/elements/Run.js +24 -4
  135. package/dist/esm/elements/Run.js.map +1 -1
  136. package/dist/esm/elements/Section.js +17 -7
  137. package/dist/esm/elements/Section.js.map +1 -1
  138. package/dist/esm/elements/Shape.js +50 -42
  139. package/dist/esm/elements/Shape.js.map +1 -1
  140. package/dist/esm/elements/StructuredDocumentTag.js +46 -6
  141. package/dist/esm/elements/StructuredDocumentTag.js.map +1 -1
  142. package/dist/esm/elements/Table.js +90 -42
  143. package/dist/esm/elements/Table.js.map +1 -1
  144. package/dist/esm/elements/TableCell.js +32 -13
  145. package/dist/esm/elements/TableCell.js.map +1 -1
  146. package/dist/esm/elements/TableOfContents.js +5 -7
  147. package/dist/esm/elements/TableOfContents.js.map +1 -1
  148. package/dist/esm/elements/TableRow.js +16 -1
  149. package/dist/esm/elements/TableRow.js.map +1 -1
  150. package/dist/esm/elements/TextBox.js +55 -42
  151. package/dist/esm/elements/TextBox.js.map +1 -1
  152. package/dist/esm/formatting/AbstractNumbering.js +1 -1
  153. package/dist/esm/formatting/AbstractNumbering.js.map +1 -1
  154. package/dist/esm/formatting/NumberingLevel.js +8 -6
  155. package/dist/esm/formatting/NumberingLevel.js.map +1 -1
  156. package/dist/esm/formatting/NumberingManager.js +16 -1
  157. package/dist/esm/formatting/NumberingManager.js.map +1 -1
  158. package/dist/esm/formatting/Style.js +14 -0
  159. package/dist/esm/formatting/Style.js.map +1 -1
  160. package/dist/esm/formatting/StylesManager.js +14 -2
  161. package/dist/esm/formatting/StylesManager.js.map +1 -1
  162. package/dist/esm/helpers/CleanupHelper.js +103 -26
  163. package/dist/esm/helpers/CleanupHelper.js.map +1 -1
  164. package/dist/esm/images/ImageOptimizer.js +52 -4
  165. package/dist/esm/images/ImageOptimizer.js.map +1 -1
  166. package/dist/esm/index.js +1 -0
  167. package/dist/esm/index.js.map +1 -1
  168. package/dist/esm/internal.js +1 -1
  169. package/dist/esm/internal.js.map +1 -1
  170. package/dist/esm/processors/CompatibilityUpgrader.js +7 -1
  171. package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -1
  172. package/dist/esm/processors/InMemoryRevisionAcceptor.js +134 -13
  173. package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -1
  174. package/dist/esm/processors/RevisionWalker.js +58 -8
  175. package/dist/esm/processors/RevisionWalker.js.map +1 -1
  176. package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
  177. package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
  178. package/dist/esm/processors/cnfStyleDecoder.js +7 -7
  179. package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
  180. package/dist/esm/processors/stripTrackedChanges.js +16 -5
  181. package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
  182. package/dist/esm/utils/deepClone.js +24 -11
  183. package/dist/esm/utils/deepClone.js.map +1 -1
  184. package/dist/esm/utils/units.js +1 -1
  185. package/dist/esm/utils/units.js.map +1 -1
  186. package/dist/esm/utils/validation.js +11 -23
  187. package/dist/esm/utils/validation.js.map +1 -1
  188. package/dist/esm/utils/xmlSanitization.js +131 -7
  189. package/dist/esm/utils/xmlSanitization.js.map +1 -1
  190. package/dist/esm/validation/RevisionAutoFixer.js +69 -55
  191. package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
  192. package/dist/esm/validation/RevisionValidator.js +24 -20
  193. package/dist/esm/validation/RevisionValidator.js.map +1 -1
  194. package/dist/esm/xml/XMLBuilder.js +52 -20
  195. package/dist/esm/xml/XMLBuilder.js.map +1 -1
  196. package/dist/esm/xml/XMLParser.js +55 -13
  197. package/dist/esm/xml/XMLParser.js.map +1 -1
  198. package/dist/esm/zip/ZipHandler.js +37 -13
  199. package/dist/esm/zip/ZipHandler.js.map +1 -1
  200. package/dist/esm/zip/ZipReader.js +53 -6
  201. package/dist/esm/zip/ZipReader.js.map +1 -1
  202. package/dist/esm/zip/errors.js +7 -0
  203. package/dist/esm/zip/errors.js.map +1 -1
  204. package/dist/esm/zip/types.js +4 -0
  205. package/dist/esm/zip/types.js.map +1 -1
  206. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  207. package/dist/formatting/AbstractNumbering.js +1 -1
  208. package/dist/formatting/AbstractNumbering.js.map +1 -1
  209. package/dist/formatting/NumberingLevel.d.ts +1 -1
  210. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  211. package/dist/formatting/NumberingLevel.js +8 -6
  212. package/dist/formatting/NumberingLevel.js.map +1 -1
  213. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  214. package/dist/formatting/NumberingManager.js +16 -1
  215. package/dist/formatting/NumberingManager.js.map +1 -1
  216. package/dist/formatting/Style.d.ts.map +1 -1
  217. package/dist/formatting/Style.js +14 -0
  218. package/dist/formatting/Style.js.map +1 -1
  219. package/dist/formatting/StylesManager.d.ts +2 -0
  220. package/dist/formatting/StylesManager.d.ts.map +1 -1
  221. package/dist/formatting/StylesManager.js +14 -2
  222. package/dist/formatting/StylesManager.js.map +1 -1
  223. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  224. package/dist/helpers/CleanupHelper.js +103 -26
  225. package/dist/helpers/CleanupHelper.js.map +1 -1
  226. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  227. package/dist/images/ImageOptimizer.js +52 -4
  228. package/dist/images/ImageOptimizer.js.map +1 -1
  229. package/dist/index.d.ts +3 -1
  230. package/dist/index.d.ts.map +1 -1
  231. package/dist/index.js +4 -2
  232. package/dist/index.js.map +1 -1
  233. package/dist/internal.d.ts +1 -1
  234. package/dist/internal.d.ts.map +1 -1
  235. package/dist/internal.js +2 -1
  236. package/dist/internal.js.map +1 -1
  237. package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
  238. package/dist/processors/CompatibilityUpgrader.js +7 -1
  239. package/dist/processors/CompatibilityUpgrader.js.map +1 -1
  240. package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
  241. package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
  242. package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
  243. package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
  244. package/dist/processors/RevisionWalker.d.ts +2 -0
  245. package/dist/processors/RevisionWalker.d.ts.map +1 -1
  246. package/dist/processors/RevisionWalker.js +58 -8
  247. package/dist/processors/RevisionWalker.js.map +1 -1
  248. package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
  249. package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
  250. package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
  251. package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
  252. package/dist/processors/cnfStyleDecoder.js +7 -7
  253. package/dist/processors/cnfStyleDecoder.js.map +1 -1
  254. package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
  255. package/dist/processors/stripTrackedChanges.js +16 -5
  256. package/dist/processors/stripTrackedChanges.js.map +1 -1
  257. package/dist/utils/deepClone.d.ts.map +1 -1
  258. package/dist/utils/deepClone.js +24 -11
  259. package/dist/utils/deepClone.js.map +1 -1
  260. package/dist/utils/units.d.ts.map +1 -1
  261. package/dist/utils/units.js +1 -1
  262. package/dist/utils/units.js.map +1 -1
  263. package/dist/utils/validation.d.ts.map +1 -1
  264. package/dist/utils/validation.js +11 -23
  265. package/dist/utils/validation.js.map +1 -1
  266. package/dist/utils/xmlSanitization.d.ts +2 -0
  267. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  268. package/dist/utils/xmlSanitization.js +133 -7
  269. package/dist/utils/xmlSanitization.js.map +1 -1
  270. package/dist/validation/RevisionAutoFixer.d.ts +6 -2
  271. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  272. package/dist/validation/RevisionAutoFixer.js +68 -54
  273. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  274. package/dist/validation/RevisionValidator.d.ts +2 -0
  275. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  276. package/dist/validation/RevisionValidator.js +26 -21
  277. package/dist/validation/RevisionValidator.js.map +1 -1
  278. package/dist/xml/XMLBuilder.d.ts +1 -0
  279. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  280. package/dist/xml/XMLBuilder.js +52 -20
  281. package/dist/xml/XMLBuilder.js.map +1 -1
  282. package/dist/xml/XMLParser.d.ts +1 -0
  283. package/dist/xml/XMLParser.d.ts.map +1 -1
  284. package/dist/xml/XMLParser.js +55 -13
  285. package/dist/xml/XMLParser.js.map +1 -1
  286. package/dist/zip/ZipHandler.d.ts.map +1 -1
  287. package/dist/zip/ZipHandler.js +37 -13
  288. package/dist/zip/ZipHandler.js.map +1 -1
  289. package/dist/zip/ZipReader.d.ts.map +1 -1
  290. package/dist/zip/ZipReader.js +52 -5
  291. package/dist/zip/ZipReader.js.map +1 -1
  292. package/dist/zip/errors.d.ts +3 -0
  293. package/dist/zip/errors.d.ts.map +1 -1
  294. package/dist/zip/errors.js +9 -1
  295. package/dist/zip/errors.js.map +1 -1
  296. package/dist/zip/types.d.ts +4 -0
  297. package/dist/zip/types.d.ts.map +1 -1
  298. package/dist/zip/types.js +4 -0
  299. package/dist/zip/types.js.map +1 -1
  300. package/package.json +2 -2
  301. package/src/core/Document.ts +1159 -240
  302. package/src/core/DocumentGenerator.ts +106 -26
  303. package/src/core/DocumentParser.ts +692 -120
  304. package/src/core/RelationshipManager.ts +446 -442
  305. package/src/elements/Comment.ts +123 -18
  306. package/src/elements/CommentManager.ts +121 -22
  307. package/src/elements/EndnoteManager.ts +4 -1
  308. package/src/elements/Field.ts +134 -43
  309. package/src/elements/FieldHelpers.ts +20 -4
  310. package/src/elements/Footer.ts +62 -67
  311. package/src/elements/FootnoteManager.ts +4 -1
  312. package/src/elements/Header.ts +62 -67
  313. package/src/elements/HeaderFooterManager.ts +56 -8
  314. package/src/elements/Hyperlink.ts +150 -55
  315. package/src/elements/Image.ts +51 -7
  316. package/src/elements/ImageManager.ts +24 -0
  317. package/src/elements/Paragraph.ts +161 -40
  318. package/src/elements/Revision.ts +446 -47
  319. package/src/elements/Run.ts +44 -6
  320. package/src/elements/Section.ts +25 -9
  321. package/src/elements/Shape.ts +52 -44
  322. package/src/elements/StructuredDocumentTag.ts +83 -8
  323. package/src/elements/Table.ts +151 -50
  324. package/src/elements/TableCell.ts +57 -17
  325. package/src/elements/TableOfContents.ts +10 -9
  326. package/src/elements/TableRow.ts +35 -3
  327. package/src/elements/TextBox.ts +58 -43
  328. package/src/formatting/AbstractNumbering.ts +4 -2
  329. package/src/formatting/NumberingLevel.ts +50 -12
  330. package/src/formatting/NumberingManager.ts +32 -3
  331. package/src/formatting/Style.ts +14 -0
  332. package/src/formatting/StylesManager.ts +29 -2
  333. package/src/helpers/CleanupHelper.ts +122 -30
  334. package/src/images/ImageOptimizer.ts +64 -5
  335. package/src/index.ts +7 -0
  336. package/src/internal.ts +1 -0
  337. package/src/processors/CompatibilityUpgrader.ts +15 -2
  338. package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
  339. package/src/processors/RevisionWalker.ts +83 -7
  340. package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
  341. package/src/processors/cnfStyleDecoder.ts +18 -12
  342. package/src/processors/stripTrackedChanges.ts +47 -10
  343. package/src/utils/deepClone.ts +48 -14
  344. package/src/utils/units.ts +3 -1
  345. package/src/utils/validation.ts +30 -32
  346. package/src/utils/xmlSanitization.ts +218 -22
  347. package/src/validation/RevisionAutoFixer.ts +109 -65
  348. package/src/validation/RevisionValidator.ts +48 -23
  349. package/src/xml/XMLBuilder.ts +82 -22
  350. package/src/xml/XMLParser.ts +87 -20
  351. package/src/zip/ZipHandler.ts +61 -21
  352. package/src/zip/ZipReader.ts +135 -13
  353. package/src/zip/errors.ts +15 -0
  354. package/src/zip/types.ts +44 -2
@@ -9,6 +9,34 @@ import { Run } from './Run.js';
9
9
  import { XMLElement } from '../xml/XMLBuilder.js';
10
10
  import { formatDateForXml } from '../utils/dateFormatting.js';
11
11
 
12
+ /**
13
+ * Inline comment content preserved as raw XML (e.g. w:hyperlink wrappers
14
+ * from a parsed comments.xml). The wrapper is re-emitted verbatim on
15
+ * regeneration so its r:id target survives; the runs parsed from inside
16
+ * it are kept alongside so text extraction still sees their text.
17
+ */
18
+ export interface CommentPreservedContent {
19
+ /** Original XML emitted verbatim when comments.xml is regenerated */
20
+ rawXml: string;
21
+ /** Runs parsed from inside the preserved element (text extraction only) */
22
+ runs: Run[];
23
+ }
24
+
25
+ /** Ordered item of comment paragraph content */
26
+ export type CommentContentItem = Run | CommentPreservedContent;
27
+
28
+ /**
29
+ * One paragraph of comment content. CT_Comment accepts block-level content
30
+ * (ECMA-376 §17.13.4.2), so comments can span multiple paragraphs, each
31
+ * with its own paragraph properties (typically the CommentText style).
32
+ */
33
+ export interface CommentParagraph {
34
+ /** Raw <w:pPr> XML preserved for round-trip */
35
+ pPr?: string;
36
+ /** Paragraph content in document order */
37
+ content: CommentContentItem[];
38
+ }
39
+
12
40
  /**
13
41
  * Comment properties
14
42
  */
@@ -37,9 +65,10 @@ export class Comment {
37
65
  private author: string;
38
66
  private initials: string;
39
67
  private date: Date;
40
- private runs: Run[];
68
+ private paragraphs: CommentParagraph[];
41
69
  private parentId?: number;
42
70
  private done: boolean;
71
+ private onModified: (() => void) | null = null;
43
72
 
44
73
  /**
45
74
  * Creates a new Comment
@@ -53,14 +82,16 @@ export class Comment {
53
82
  this.parentId = properties.parentId;
54
83
  this.done = properties.done ?? false;
55
84
 
56
- // Convert content to runs
85
+ // Convert content to runs in a single paragraph
86
+ let runs: Run[];
57
87
  if (typeof properties.content === 'string') {
58
- this.runs = [new Run(properties.content)];
88
+ runs = [new Run(properties.content)];
59
89
  } else if (Array.isArray(properties.content)) {
60
- this.runs = properties.content;
90
+ runs = properties.content;
61
91
  } else {
62
- this.runs = [properties.content];
92
+ runs = [properties.content];
63
93
  }
94
+ this.paragraphs = [{ content: runs }];
64
95
  }
65
96
 
66
97
  /**
@@ -98,6 +129,23 @@ export class Comment {
98
129
  this.id = id;
99
130
  }
100
131
 
132
+ /**
133
+ * Registers a callback invoked whenever this comment is mutated.
134
+ * Document wires this to its comments dirty flag so edits to parsed
135
+ * comments trigger regeneration of comments.xml instead of being
136
+ * silently discarded by the original-XML passthrough on save.
137
+ * @internal
138
+ */
139
+ setModifiedCallback(callback: () => void): void {
140
+ this.onModified = callback;
141
+ }
142
+
143
+ private notifyModified(): void {
144
+ if (this.onModified) {
145
+ this.onModified();
146
+ }
147
+ }
148
+
101
149
  /**
102
150
  * Gets the author
103
151
  */
@@ -110,6 +158,7 @@ export class Comment {
110
158
  */
111
159
  setAuthor(author: string): this {
112
160
  this.author = author;
161
+ this.notifyModified();
113
162
  return this;
114
163
  }
115
164
 
@@ -125,6 +174,7 @@ export class Comment {
125
174
  */
126
175
  setInitials(initials: string): this {
127
176
  this.initials = initials;
177
+ this.notifyModified();
128
178
  return this;
129
179
  }
130
180
 
@@ -140,6 +190,7 @@ export class Comment {
140
190
  */
141
191
  setDate(date: Date): this {
142
192
  this.date = date;
193
+ this.notifyModified();
143
194
  return this;
144
195
  }
145
196
 
@@ -167,42 +218,84 @@ export class Comment {
167
218
 
168
219
  /**
169
220
  * Marks the comment as resolved/done
170
- * Sets w:done="1" in the XML output
221
+ * Persists as w15:done="1" in word/commentsExtended.xml on save
171
222
  */
172
223
  resolve(): this {
173
224
  this.done = true;
225
+ this.notifyModified();
174
226
  return this;
175
227
  }
176
228
 
177
229
  /**
178
230
  * Marks the comment as unresolved
179
- * Removes w:done attribute from XML output
231
+ * Drops the w15:done flag from word/commentsExtended.xml on save
180
232
  */
181
233
  unresolve(): this {
182
234
  this.done = false;
235
+ this.notifyModified();
183
236
  return this;
184
237
  }
185
238
 
186
239
  /**
187
- * Gets the runs in this comment
240
+ * Gets the runs in this comment (flattened across paragraphs,
241
+ * including runs inside preserved inline content)
188
242
  */
189
243
  getRuns(): Run[] {
190
- return [...this.runs];
244
+ const runs: Run[] = [];
245
+ for (const paragraph of this.paragraphs) {
246
+ for (const item of paragraph.content) {
247
+ if (item instanceof Run) {
248
+ runs.push(item);
249
+ } else {
250
+ runs.push(...item.runs);
251
+ }
252
+ }
253
+ }
254
+ return runs;
191
255
  }
192
256
 
193
257
  /**
194
- * Adds a run to this comment
258
+ * Adds a run to this comment (appended to the last paragraph)
195
259
  */
196
260
  addRun(run: Run): this {
197
- this.runs.push(run);
261
+ let last = this.paragraphs[this.paragraphs.length - 1];
262
+ if (!last) {
263
+ last = { content: [] };
264
+ this.paragraphs.push(last);
265
+ }
266
+ last.content.push(run);
267
+ this.notifyModified();
198
268
  return this;
199
269
  }
200
270
 
271
+ /**
272
+ * Gets the comment content grouped by paragraph
273
+ */
274
+ getParagraphs(): CommentParagraph[] {
275
+ return this.paragraphs.map((paragraph) => ({
276
+ pPr: paragraph.pPr,
277
+ content: [...paragraph.content],
278
+ }));
279
+ }
280
+
281
+ /**
282
+ * Replaces the comment content with parsed paragraphs.
283
+ * Used by DocumentParser to preserve <w:p> boundaries, paragraph
284
+ * properties, and hyperlink wrappers of comments loaded from an
285
+ * existing comments.xml. Not a user mutation — no dirty notification.
286
+ * @internal
287
+ */
288
+ setParagraphs(paragraphs: CommentParagraph[]): void {
289
+ this.paragraphs = paragraphs;
290
+ }
291
+
201
292
  /**
202
293
  * Gets the comment text (combines all runs)
203
294
  */
204
295
  getText(): string {
205
- return this.runs.map((run) => run.getText()).join('');
296
+ return this.getRuns()
297
+ .map((run) => run.getText())
298
+ .join('');
206
299
  }
207
300
 
208
301
  /**
@@ -289,16 +382,28 @@ export class Comment {
289
382
  attributes['w:done'] = '1';
290
383
  }
291
384
 
292
- // Create paragraph containing the comment content
293
- const commentParagraph: XMLElement = {
294
- name: 'w:p',
295
- children: this.runs.map((run) => run.toXML()),
296
- };
385
+ // One <w:p> per stored paragraph collapsing into a single paragraph
386
+ // concatenates words across the lost boundaries and drops the
387
+ // CommentText paragraph style of parsed comments
388
+ const commentParagraphs: XMLElement[] = this.paragraphs.map((paragraph) => {
389
+ const children: XMLElement[] = [];
390
+ if (paragraph.pPr) {
391
+ children.push({ name: '__rawXml', rawXml: paragraph.pPr });
392
+ }
393
+ for (const item of paragraph.content) {
394
+ if (item instanceof Run) {
395
+ children.push(item.toXML());
396
+ } else {
397
+ children.push({ name: '__rawXml', rawXml: item.rawXml });
398
+ }
399
+ }
400
+ return { name: 'w:p', children };
401
+ });
297
402
 
298
403
  return {
299
404
  name: 'w:comment',
300
405
  attributes,
301
- children: [commentParagraph],
406
+ children: commentParagraphs,
302
407
  };
303
408
  }
304
409
 
@@ -42,6 +42,7 @@ export class CommentManager {
42
42
  private nextId = 0;
43
43
  private idProvider: IdProviderCallback | null = null;
44
44
  private idExistsNotifier: IdExistsCallback | null = null;
45
+ private modifiedNotifier: (() => void) | null = null;
45
46
 
46
47
  /**
47
48
  * Sets the centralized ID provider callback.
@@ -56,6 +57,21 @@ export class CommentManager {
56
57
  this.idExistsNotifier = existsNotifier || null;
57
58
  }
58
59
 
60
+ /**
61
+ * Sets the callback invoked when a registered comment is mutated.
62
+ * Document wires this to its comments dirty flag so instance-level edits
63
+ * (resolve, setAuthor, addRun, ...) force comments.xml regeneration
64
+ * instead of being silently discarded by the original-XML passthrough.
65
+ *
66
+ * @param notifier - Callback invoked on any comment mutation
67
+ */
68
+ setModifiedNotifier(notifier: () => void): void {
69
+ this.modifiedNotifier = notifier;
70
+ for (const entry of this.comments.values()) {
71
+ entry.comment.setModifiedCallback(notifier);
72
+ }
73
+ }
74
+
59
75
  /**
60
76
  * Registers a comment with the manager
61
77
  * Assigns a unique ID
@@ -67,6 +83,10 @@ export class CommentManager {
67
83
  const id = this.idProvider ? this.idProvider() : this.nextId++;
68
84
  comment.setId(id);
69
85
 
86
+ if (this.modifiedNotifier) {
87
+ comment.setModifiedCallback(this.modifiedNotifier);
88
+ }
89
+
70
90
  // Store comment
71
91
  const entry: CommentEntry = {
72
92
  comment,
@@ -99,6 +119,10 @@ export class CommentManager {
99
119
  this.idExistsNotifier(id);
100
120
  }
101
121
 
122
+ if (this.modifiedNotifier) {
123
+ comment.setModifiedCallback(this.modifiedNotifier);
124
+ }
125
+
102
126
  // Store comment
103
127
  const entry: CommentEntry = {
104
128
  comment,
@@ -251,6 +275,16 @@ export class CommentManager {
251
275
  this.comments.delete(reply.getId());
252
276
  }
253
277
 
278
+ // A reply is also tracked in its parent's replies array — drop it there
279
+ // too so getReplies()/hasReplies()/getCommentThread() agree with the map
280
+ const parentId = entry.comment.getParentId();
281
+ if (parentId !== undefined) {
282
+ const parentEntry = this.comments.get(parentId);
283
+ if (parentEntry) {
284
+ parentEntry.replies = parentEntry.replies.filter((reply) => reply !== entry.comment);
285
+ }
286
+ }
287
+
254
288
  // Remove the comment itself
255
289
  return this.comments.delete(id);
256
290
  }
@@ -420,31 +454,88 @@ export class CommentManager {
420
454
 
421
455
  // Build XML manually for comments
422
456
  const hasReplies = comments.some((c) => c.isReply());
457
+ const hasResolved = comments.some((c) => c.isResolved());
423
458
  let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
424
459
  xml += '<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"';
425
460
  xml += ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"';
461
+ if (hasResolved) {
462
+ xml += ' xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"';
463
+ }
426
464
  if (hasReplies) {
427
465
  xml += ' xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"';
428
- xml +=
429
- ' xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="w15"';
466
+ }
467
+ if (hasReplies || hasResolved) {
468
+ const ignorable = [hasResolved ? 'w14' : '', hasReplies ? 'w15' : '']
469
+ .filter((ns) => ns.length > 0)
470
+ .join(' ');
471
+ xml += ` xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="${ignorable}"`;
430
472
  }
431
473
  xml += '>\n';
432
474
 
433
- // Add each comment
475
+ // Add each comment. CT_Comment declares no resolved-state attribute —
476
+ // that lives in commentsExtended.xml keyed by the w14:paraId of each
477
+ // comment's last paragraph, so emit those anchors when any comment is
478
+ // resolved (see generateCommentsExtendedXml)
434
479
  for (const comment of comments) {
435
- xml += this.commentToXmlString(comment);
480
+ xml += this.commentToXmlString(
481
+ comment,
482
+ hasResolved ? CommentManager.commentParaId(comment.getId()) : undefined
483
+ );
436
484
  }
437
485
 
438
486
  xml += '</w:comments>';
439
487
  return xml;
440
488
  }
441
489
 
490
+ /**
491
+ * Derives the w14:paraId anchor for a comment's last paragraph.
492
+ * Deterministic so comments.xml and commentsExtended.xml agree without
493
+ * shared state. Values must be 8 hex digits, nonzero, and below
494
+ * 0x80000000 per ST_LongHexNumber conventions for paraId.
495
+ * @param commentId - Comment ID
496
+ * @returns 8-character uppercase hex paraId
497
+ */
498
+ static commentParaId(commentId: number): string {
499
+ return ((commentId + 1) & 0x7fffffff).toString(16).toUpperCase().padStart(8, '0');
500
+ }
501
+
502
+ /**
503
+ * Generates the word/commentsExtended.xml content carrying resolved
504
+ * state (w15:done) and reply threading (w15:paraIdParent).
505
+ * @returns XML string, or null when no comment is resolved (the part is
506
+ * only regenerated when it carries information)
507
+ */
508
+ generateCommentsExtendedXml(): string | null {
509
+ const comments = this.getAllCommentsWithReplies();
510
+ if (!comments.some((c) => c.isResolved())) {
511
+ return null;
512
+ }
513
+
514
+ let xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
515
+ xml += '<w15:commentsEx xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"';
516
+ xml +=
517
+ ' xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w15">\n';
518
+ for (const comment of comments) {
519
+ xml += ` <w15:commentEx w15:paraId="${CommentManager.commentParaId(comment.getId())}"`;
520
+ const parentId = comment.getParentId();
521
+ if (parentId !== undefined && this.comments.has(parentId)) {
522
+ xml += ` w15:paraIdParent="${CommentManager.commentParaId(parentId)}"`;
523
+ }
524
+ xml += ` w15:done="${comment.isResolved() ? '1' : '0'}"/>\n`;
525
+ }
526
+ xml += '</w15:commentsEx>';
527
+ return xml;
528
+ }
529
+
442
530
  /**
443
531
  * Converts a comment to XML string
444
532
  * @param comment - Comment to convert
533
+ * @param paraId - w14:paraId to anchor on the last paragraph (links the
534
+ * comment to its commentsExtended.xml entry), omitted when no comment
535
+ * in the document is resolved
445
536
  * @returns XML string for the comment
446
537
  */
447
- private commentToXmlString(comment: Comment): string {
538
+ private commentToXmlString(comment: Comment, paraId?: string): string {
448
539
  let xml = ` <w:comment w:id="${comment.getId()}"`;
449
540
  xml += ` w:author="${XMLBuilder.escapeXmlAttribute(comment.getAuthor())}"`;
450
541
  xml += ` w:date="${formatDateForXml(comment.getDate())}"`;
@@ -454,19 +545,31 @@ export class CommentManager {
454
545
  xml += ` w15:parentId="${comment.getParentId()}"`;
455
546
  }
456
547
 
457
- // Add done attribute for resolved comments (per ECMA-376)
458
- if (comment.isResolved()) {
459
- xml += ` w:done="1"`;
460
- }
461
-
462
548
  xml += '>\n';
463
549
 
464
- // Add paragraph with runs
465
- xml += ' <w:p>\n';
466
- for (const run of comment.getRuns()) {
467
- xml += this.runToXmlString(run, 6);
550
+ // One <w:p> per stored paragraph collapsing into a single paragraph
551
+ // concatenates words across the lost boundaries and drops paragraph
552
+ // properties (CommentText pStyle) of parsed comments
553
+ const paragraphs = comment.getParagraphs();
554
+ for (let i = 0; i < paragraphs.length; i++) {
555
+ const paragraph = paragraphs[i]!;
556
+ // commentsExtended.xml entries reference the LAST paragraph's paraId
557
+ const isLast = i === paragraphs.length - 1;
558
+ xml += isLast && paraId ? ` <w:p w14:paraId="${paraId}">\n` : ' <w:p>\n';
559
+ if (paragraph.pPr) {
560
+ xml += ` ${paragraph.pPr}\n`;
561
+ }
562
+ for (const item of paragraph.content) {
563
+ if (item instanceof Run) {
564
+ xml += this.runToXmlString(item, 6);
565
+ } else {
566
+ // Preserved inline XML (e.g. w:hyperlink) re-emitted verbatim so
567
+ // its r:id target keeps pointing at word/_rels/comments.xml.rels
568
+ xml += ` ${item.rawXml}\n`;
569
+ }
570
+ }
571
+ xml += ' </w:p>\n';
468
572
  }
469
- xml += ' </w:p>\n';
470
573
 
471
574
  xml += ' </w:comment>\n';
472
575
  return xml;
@@ -480,13 +583,9 @@ export class CommentManager {
480
583
  */
481
584
  private runToXmlString(run: Run, indent: number): string {
482
585
  const spaces = ' '.repeat(indent);
483
- const text = XMLBuilder.escapeXmlText(run.getText());
484
-
485
- let xml = `${spaces}<w:r>\n`;
486
- xml += `${spaces} <w:t xml:space="preserve">${text}</w:t>\n`;
487
- xml += `${spaces}</w:r>\n`;
488
-
489
- return xml;
586
+ // Serialize through Run.toXML() so w:rPr formatting and non-text
587
+ // content (w:annotationRef, tabs, breaks) survive regeneration
588
+ return `${spaces}${XMLBuilder.elementToString(run.toXML())}\n`;
490
589
  }
491
590
 
492
591
  /**
@@ -130,7 +130,10 @@ export class EndnoteManager {
130
130
  }
131
131
 
132
132
  /**
133
- * Removes an endnote
133
+ * Removes an endnote from the model only. Does not touch body
134
+ * w:endnoteReference runs or the document's dirty flags — callers going
135
+ * through a Document should use Document.removeEndnote(), which also
136
+ * strips the matching references and forces endnotes.xml regeneration.
134
137
  * @param id Endnote ID
135
138
  * @returns True if removed, false if not found or if it's a special type
136
139
  */