docxmlater 11.0.10 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +53 -13
  2. package/dist/core/Document.d.ts +29 -3
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +733 -170
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentGenerator.d.ts +1 -0
  7. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  8. package/dist/core/DocumentGenerator.js +77 -25
  9. package/dist/core/DocumentGenerator.js.map +1 -1
  10. package/dist/core/DocumentParser.d.ts +7 -3
  11. package/dist/core/DocumentParser.d.ts.map +1 -1
  12. package/dist/core/DocumentParser.js +427 -81
  13. package/dist/core/DocumentParser.js.map +1 -1
  14. package/dist/core/RelationshipManager.d.ts.map +1 -1
  15. package/dist/core/RelationshipManager.js +2 -2
  16. package/dist/core/RelationshipManager.js.map +1 -1
  17. package/dist/elements/Comment.d.ts +15 -1
  18. package/dist/elements/Comment.d.ts.map +1 -1
  19. package/dist/elements/Comment.js +67 -12
  20. package/dist/elements/Comment.js.map +1 -1
  21. package/dist/elements/CommentManager.d.ts +4 -0
  22. package/dist/elements/CommentManager.d.ts.map +1 -1
  23. package/dist/elements/CommentManager.js +74 -16
  24. package/dist/elements/CommentManager.js.map +1 -1
  25. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  26. package/dist/elements/EndnoteManager.js.map +1 -1
  27. package/dist/elements/Field.d.ts +10 -1
  28. package/dist/elements/Field.d.ts.map +1 -1
  29. package/dist/elements/Field.js +76 -34
  30. package/dist/elements/Field.js.map +1 -1
  31. package/dist/elements/FieldHelpers.d.ts +1 -0
  32. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  33. package/dist/elements/FieldHelpers.js +8 -4
  34. package/dist/elements/FieldHelpers.js.map +1 -1
  35. package/dist/elements/Footer.d.ts +3 -1
  36. package/dist/elements/Footer.d.ts.map +1 -1
  37. package/dist/elements/Footer.js +39 -53
  38. package/dist/elements/Footer.js.map +1 -1
  39. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  40. package/dist/elements/FootnoteManager.js.map +1 -1
  41. package/dist/elements/Header.d.ts +3 -1
  42. package/dist/elements/Header.d.ts.map +1 -1
  43. package/dist/elements/Header.js +39 -53
  44. package/dist/elements/Header.js.map +1 -1
  45. package/dist/elements/HeaderFooterManager.d.ts +4 -2
  46. package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
  47. package/dist/elements/HeaderFooterManager.js +36 -6
  48. package/dist/elements/HeaderFooterManager.js.map +1 -1
  49. package/dist/elements/Hyperlink.d.ts +6 -1
  50. package/dist/elements/Hyperlink.d.ts.map +1 -1
  51. package/dist/elements/Hyperlink.js +69 -44
  52. package/dist/elements/Hyperlink.js.map +1 -1
  53. package/dist/elements/Image.d.ts.map +1 -1
  54. package/dist/elements/Image.js +33 -6
  55. package/dist/elements/Image.js.map +1 -1
  56. package/dist/elements/ImageManager.d.ts +1 -0
  57. package/dist/elements/ImageManager.d.ts.map +1 -1
  58. package/dist/elements/ImageManager.js +11 -0
  59. package/dist/elements/ImageManager.js.map +1 -1
  60. package/dist/elements/Paragraph.d.ts +3 -0
  61. package/dist/elements/Paragraph.d.ts.map +1 -1
  62. package/dist/elements/Paragraph.js +91 -26
  63. package/dist/elements/Paragraph.js.map +1 -1
  64. package/dist/elements/Revision.d.ts +4 -0
  65. package/dist/elements/Revision.d.ts.map +1 -1
  66. package/dist/elements/Revision.js +369 -29
  67. package/dist/elements/Revision.js.map +1 -1
  68. package/dist/elements/Run.d.ts +1 -0
  69. package/dist/elements/Run.d.ts.map +1 -1
  70. package/dist/elements/Run.js +24 -4
  71. package/dist/elements/Run.js.map +1 -1
  72. package/dist/elements/Section.d.ts.map +1 -1
  73. package/dist/elements/Section.js +17 -7
  74. package/dist/elements/Section.js.map +1 -1
  75. package/dist/elements/Shape.d.ts.map +1 -1
  76. package/dist/elements/Shape.js +50 -42
  77. package/dist/elements/Shape.js.map +1 -1
  78. package/dist/elements/StructuredDocumentTag.d.ts +9 -1
  79. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  80. package/dist/elements/StructuredDocumentTag.js +46 -6
  81. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  82. package/dist/elements/Table.d.ts +2 -0
  83. package/dist/elements/Table.d.ts.map +1 -1
  84. package/dist/elements/Table.js +90 -42
  85. package/dist/elements/Table.js.map +1 -1
  86. package/dist/elements/TableCell.d.ts +1 -0
  87. package/dist/elements/TableCell.d.ts.map +1 -1
  88. package/dist/elements/TableCell.js +32 -13
  89. package/dist/elements/TableCell.js.map +1 -1
  90. package/dist/elements/TableOfContents.d.ts.map +1 -1
  91. package/dist/elements/TableOfContents.js +5 -7
  92. package/dist/elements/TableOfContents.js.map +1 -1
  93. package/dist/elements/TableRow.d.ts +1 -0
  94. package/dist/elements/TableRow.d.ts.map +1 -1
  95. package/dist/elements/TableRow.js +16 -1
  96. package/dist/elements/TableRow.js.map +1 -1
  97. package/dist/elements/TextBox.d.ts.map +1 -1
  98. package/dist/elements/TextBox.js +55 -42
  99. package/dist/elements/TextBox.js.map +1 -1
  100. package/dist/esm/core/Document.js +735 -172
  101. package/dist/esm/core/Document.js.map +1 -1
  102. package/dist/esm/core/DocumentGenerator.js +77 -25
  103. package/dist/esm/core/DocumentGenerator.js.map +1 -1
  104. package/dist/esm/core/DocumentParser.js +427 -81
  105. package/dist/esm/core/DocumentParser.js.map +1 -1
  106. package/dist/esm/core/RelationshipManager.js +2 -2
  107. package/dist/esm/core/RelationshipManager.js.map +1 -1
  108. package/dist/esm/elements/Comment.js +67 -12
  109. package/dist/esm/elements/Comment.js.map +1 -1
  110. package/dist/esm/elements/CommentManager.js +74 -16
  111. package/dist/esm/elements/CommentManager.js.map +1 -1
  112. package/dist/esm/elements/EndnoteManager.js.map +1 -1
  113. package/dist/esm/elements/Field.js +77 -35
  114. package/dist/esm/elements/Field.js.map +1 -1
  115. package/dist/esm/elements/FieldHelpers.js +7 -4
  116. package/dist/esm/elements/FieldHelpers.js.map +1 -1
  117. package/dist/esm/elements/Footer.js +39 -53
  118. package/dist/esm/elements/Footer.js.map +1 -1
  119. package/dist/esm/elements/FootnoteManager.js.map +1 -1
  120. package/dist/esm/elements/Header.js +39 -53
  121. package/dist/esm/elements/Header.js.map +1 -1
  122. package/dist/esm/elements/HeaderFooterManager.js +36 -6
  123. package/dist/esm/elements/HeaderFooterManager.js.map +1 -1
  124. package/dist/esm/elements/Hyperlink.js +69 -44
  125. package/dist/esm/elements/Hyperlink.js.map +1 -1
  126. package/dist/esm/elements/Image.js +33 -6
  127. package/dist/esm/elements/Image.js.map +1 -1
  128. package/dist/esm/elements/ImageManager.js +11 -0
  129. package/dist/esm/elements/ImageManager.js.map +1 -1
  130. package/dist/esm/elements/Paragraph.js +91 -26
  131. package/dist/esm/elements/Paragraph.js.map +1 -1
  132. package/dist/esm/elements/Revision.js +369 -29
  133. package/dist/esm/elements/Revision.js.map +1 -1
  134. package/dist/esm/elements/Run.js +24 -4
  135. package/dist/esm/elements/Run.js.map +1 -1
  136. package/dist/esm/elements/Section.js +17 -7
  137. package/dist/esm/elements/Section.js.map +1 -1
  138. package/dist/esm/elements/Shape.js +50 -42
  139. package/dist/esm/elements/Shape.js.map +1 -1
  140. package/dist/esm/elements/StructuredDocumentTag.js +46 -6
  141. package/dist/esm/elements/StructuredDocumentTag.js.map +1 -1
  142. package/dist/esm/elements/Table.js +90 -42
  143. package/dist/esm/elements/Table.js.map +1 -1
  144. package/dist/esm/elements/TableCell.js +32 -13
  145. package/dist/esm/elements/TableCell.js.map +1 -1
  146. package/dist/esm/elements/TableOfContents.js +5 -7
  147. package/dist/esm/elements/TableOfContents.js.map +1 -1
  148. package/dist/esm/elements/TableRow.js +16 -1
  149. package/dist/esm/elements/TableRow.js.map +1 -1
  150. package/dist/esm/elements/TextBox.js +55 -42
  151. package/dist/esm/elements/TextBox.js.map +1 -1
  152. package/dist/esm/formatting/AbstractNumbering.js +1 -1
  153. package/dist/esm/formatting/AbstractNumbering.js.map +1 -1
  154. package/dist/esm/formatting/NumberingLevel.js +8 -6
  155. package/dist/esm/formatting/NumberingLevel.js.map +1 -1
  156. package/dist/esm/formatting/NumberingManager.js +16 -1
  157. package/dist/esm/formatting/NumberingManager.js.map +1 -1
  158. package/dist/esm/formatting/Style.js +14 -0
  159. package/dist/esm/formatting/Style.js.map +1 -1
  160. package/dist/esm/formatting/StylesManager.js +14 -2
  161. package/dist/esm/formatting/StylesManager.js.map +1 -1
  162. package/dist/esm/helpers/CleanupHelper.js +103 -26
  163. package/dist/esm/helpers/CleanupHelper.js.map +1 -1
  164. package/dist/esm/images/ImageOptimizer.js +52 -4
  165. package/dist/esm/images/ImageOptimizer.js.map +1 -1
  166. package/dist/esm/index.js +1 -0
  167. package/dist/esm/index.js.map +1 -1
  168. package/dist/esm/internal.js +1 -1
  169. package/dist/esm/internal.js.map +1 -1
  170. package/dist/esm/processors/CompatibilityUpgrader.js +7 -1
  171. package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -1
  172. package/dist/esm/processors/InMemoryRevisionAcceptor.js +134 -13
  173. package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -1
  174. package/dist/esm/processors/RevisionWalker.js +223 -39
  175. package/dist/esm/processors/RevisionWalker.js.map +1 -1
  176. package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
  177. package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
  178. package/dist/esm/processors/acceptRevisions.js +20 -11
  179. package/dist/esm/processors/acceptRevisions.js.map +1 -1
  180. package/dist/esm/processors/cnfStyleDecoder.js +7 -7
  181. package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
  182. package/dist/esm/processors/stripTrackedChanges.js +16 -5
  183. package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
  184. package/dist/esm/utils/deepClone.js +24 -11
  185. package/dist/esm/utils/deepClone.js.map +1 -1
  186. package/dist/esm/utils/units.js +1 -1
  187. package/dist/esm/utils/units.js.map +1 -1
  188. package/dist/esm/utils/validation.js +11 -23
  189. package/dist/esm/utils/validation.js.map +1 -1
  190. package/dist/esm/utils/xmlSanitization.js +131 -7
  191. package/dist/esm/utils/xmlSanitization.js.map +1 -1
  192. package/dist/esm/validation/RevisionAutoFixer.js +69 -55
  193. package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
  194. package/dist/esm/validation/RevisionValidator.js +24 -20
  195. package/dist/esm/validation/RevisionValidator.js.map +1 -1
  196. package/dist/esm/xml/XMLBuilder.js +52 -20
  197. package/dist/esm/xml/XMLBuilder.js.map +1 -1
  198. package/dist/esm/xml/XMLParser.js +55 -13
  199. package/dist/esm/xml/XMLParser.js.map +1 -1
  200. package/dist/esm/zip/ZipHandler.js +37 -13
  201. package/dist/esm/zip/ZipHandler.js.map +1 -1
  202. package/dist/esm/zip/ZipReader.js +53 -6
  203. package/dist/esm/zip/ZipReader.js.map +1 -1
  204. package/dist/esm/zip/errors.js +7 -0
  205. package/dist/esm/zip/errors.js.map +1 -1
  206. package/dist/esm/zip/types.js +4 -0
  207. package/dist/esm/zip/types.js.map +1 -1
  208. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  209. package/dist/formatting/AbstractNumbering.js +1 -1
  210. package/dist/formatting/AbstractNumbering.js.map +1 -1
  211. package/dist/formatting/NumberingLevel.d.ts +1 -1
  212. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  213. package/dist/formatting/NumberingLevel.js +8 -6
  214. package/dist/formatting/NumberingLevel.js.map +1 -1
  215. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  216. package/dist/formatting/NumberingManager.js +16 -1
  217. package/dist/formatting/NumberingManager.js.map +1 -1
  218. package/dist/formatting/Style.d.ts.map +1 -1
  219. package/dist/formatting/Style.js +14 -0
  220. package/dist/formatting/Style.js.map +1 -1
  221. package/dist/formatting/StylesManager.d.ts +2 -0
  222. package/dist/formatting/StylesManager.d.ts.map +1 -1
  223. package/dist/formatting/StylesManager.js +14 -2
  224. package/dist/formatting/StylesManager.js.map +1 -1
  225. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  226. package/dist/helpers/CleanupHelper.js +103 -26
  227. package/dist/helpers/CleanupHelper.js.map +1 -1
  228. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  229. package/dist/images/ImageOptimizer.js +52 -4
  230. package/dist/images/ImageOptimizer.js.map +1 -1
  231. package/dist/index.d.ts +3 -1
  232. package/dist/index.d.ts.map +1 -1
  233. package/dist/index.js +4 -2
  234. package/dist/index.js.map +1 -1
  235. package/dist/internal.d.ts +1 -1
  236. package/dist/internal.d.ts.map +1 -1
  237. package/dist/internal.js +2 -1
  238. package/dist/internal.js.map +1 -1
  239. package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
  240. package/dist/processors/CompatibilityUpgrader.js +7 -1
  241. package/dist/processors/CompatibilityUpgrader.js.map +1 -1
  242. package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
  243. package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
  244. package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
  245. package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
  246. package/dist/processors/RevisionWalker.d.ts +9 -3
  247. package/dist/processors/RevisionWalker.d.ts.map +1 -1
  248. package/dist/processors/RevisionWalker.js +223 -39
  249. package/dist/processors/RevisionWalker.js.map +1 -1
  250. package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
  251. package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
  252. package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
  253. package/dist/processors/acceptRevisions.d.ts +1 -0
  254. package/dist/processors/acceptRevisions.d.ts.map +1 -1
  255. package/dist/processors/acceptRevisions.js +21 -11
  256. package/dist/processors/acceptRevisions.js.map +1 -1
  257. package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
  258. package/dist/processors/cnfStyleDecoder.js +7 -7
  259. package/dist/processors/cnfStyleDecoder.js.map +1 -1
  260. package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
  261. package/dist/processors/stripTrackedChanges.js +16 -5
  262. package/dist/processors/stripTrackedChanges.js.map +1 -1
  263. package/dist/utils/deepClone.d.ts.map +1 -1
  264. package/dist/utils/deepClone.js +24 -11
  265. package/dist/utils/deepClone.js.map +1 -1
  266. package/dist/utils/units.d.ts.map +1 -1
  267. package/dist/utils/units.js +1 -1
  268. package/dist/utils/units.js.map +1 -1
  269. package/dist/utils/validation.d.ts.map +1 -1
  270. package/dist/utils/validation.js +11 -23
  271. package/dist/utils/validation.js.map +1 -1
  272. package/dist/utils/xmlSanitization.d.ts +2 -0
  273. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  274. package/dist/utils/xmlSanitization.js +133 -7
  275. package/dist/utils/xmlSanitization.js.map +1 -1
  276. package/dist/validation/RevisionAutoFixer.d.ts +6 -2
  277. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  278. package/dist/validation/RevisionAutoFixer.js +68 -54
  279. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  280. package/dist/validation/RevisionValidator.d.ts +2 -0
  281. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  282. package/dist/validation/RevisionValidator.js +26 -21
  283. package/dist/validation/RevisionValidator.js.map +1 -1
  284. package/dist/xml/XMLBuilder.d.ts +1 -0
  285. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  286. package/dist/xml/XMLBuilder.js +52 -20
  287. package/dist/xml/XMLBuilder.js.map +1 -1
  288. package/dist/xml/XMLParser.d.ts +1 -0
  289. package/dist/xml/XMLParser.d.ts.map +1 -1
  290. package/dist/xml/XMLParser.js +55 -13
  291. package/dist/xml/XMLParser.js.map +1 -1
  292. package/dist/zip/ZipHandler.d.ts.map +1 -1
  293. package/dist/zip/ZipHandler.js +37 -13
  294. package/dist/zip/ZipHandler.js.map +1 -1
  295. package/dist/zip/ZipReader.d.ts.map +1 -1
  296. package/dist/zip/ZipReader.js +52 -5
  297. package/dist/zip/ZipReader.js.map +1 -1
  298. package/dist/zip/errors.d.ts +3 -0
  299. package/dist/zip/errors.d.ts.map +1 -1
  300. package/dist/zip/errors.js +9 -1
  301. package/dist/zip/errors.js.map +1 -1
  302. package/dist/zip/types.d.ts +4 -0
  303. package/dist/zip/types.d.ts.map +1 -1
  304. package/dist/zip/types.js +4 -0
  305. package/dist/zip/types.js.map +1 -1
  306. package/package.json +2 -2
  307. package/src/core/Document.ts +1184 -242
  308. package/src/core/DocumentGenerator.ts +106 -26
  309. package/src/core/DocumentParser.ts +681 -114
  310. package/src/core/RelationshipManager.ts +446 -442
  311. package/src/elements/Comment.ts +123 -18
  312. package/src/elements/CommentManager.ts +121 -22
  313. package/src/elements/EndnoteManager.ts +4 -1
  314. package/src/elements/Field.ts +134 -43
  315. package/src/elements/FieldHelpers.ts +20 -4
  316. package/src/elements/Footer.ts +62 -67
  317. package/src/elements/FootnoteManager.ts +4 -1
  318. package/src/elements/Header.ts +62 -67
  319. package/src/elements/HeaderFooterManager.ts +56 -8
  320. package/src/elements/Hyperlink.ts +150 -55
  321. package/src/elements/Image.ts +51 -7
  322. package/src/elements/ImageManager.ts +24 -0
  323. package/src/elements/Paragraph.ts +161 -40
  324. package/src/elements/Revision.ts +446 -47
  325. package/src/elements/Run.ts +44 -6
  326. package/src/elements/Section.ts +25 -9
  327. package/src/elements/Shape.ts +52 -44
  328. package/src/elements/StructuredDocumentTag.ts +83 -8
  329. package/src/elements/Table.ts +151 -50
  330. package/src/elements/TableCell.ts +57 -17
  331. package/src/elements/TableOfContents.ts +10 -9
  332. package/src/elements/TableRow.ts +35 -3
  333. package/src/elements/TextBox.ts +58 -43
  334. package/src/formatting/AbstractNumbering.ts +4 -2
  335. package/src/formatting/NumberingLevel.ts +50 -12
  336. package/src/formatting/NumberingManager.ts +32 -3
  337. package/src/formatting/Style.ts +14 -0
  338. package/src/formatting/StylesManager.ts +29 -2
  339. package/src/helpers/CleanupHelper.ts +122 -30
  340. package/src/images/ImageOptimizer.ts +64 -5
  341. package/src/index.ts +7 -0
  342. package/src/internal.ts +1 -0
  343. package/src/processors/CompatibilityUpgrader.ts +15 -2
  344. package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
  345. package/src/processors/RevisionWalker.ts +390 -46
  346. package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
  347. package/src/processors/acceptRevisions.ts +67 -17
  348. package/src/processors/cnfStyleDecoder.ts +18 -12
  349. package/src/processors/stripTrackedChanges.ts +47 -10
  350. package/src/utils/deepClone.ts +48 -14
  351. package/src/utils/units.ts +3 -1
  352. package/src/utils/validation.ts +30 -32
  353. package/src/utils/xmlSanitization.ts +218 -22
  354. package/src/validation/RevisionAutoFixer.ts +109 -65
  355. package/src/validation/RevisionValidator.ts +48 -23
  356. package/src/xml/XMLBuilder.ts +82 -22
  357. package/src/xml/XMLParser.ts +87 -20
  358. package/src/zip/ZipHandler.ts +61 -21
  359. package/src/zip/ZipReader.ts +135 -13
  360. package/src/zip/errors.ts +15 -0
  361. package/src/zip/types.ts +44 -2
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { Run } from './Run.js';
9
9
  import type { RunFormatting } from './Run.js';
10
- import { XMLElement } from '../xml/XMLBuilder.js';
10
+ import { XMLBuilder, XMLElement } from '../xml/XMLBuilder.js';
11
11
  import type { RevisionLocation } from './PropertyChangeTypes.js';
12
12
  import type { RevisionContent } from './RevisionContent.js';
13
13
  import { isRunContent, isHyperlinkContent } from './RevisionContent.js';
@@ -477,16 +477,22 @@ export class Revision {
477
477
  * - w:date: When the change was made (ST_DateTime, ISO 8601) - OPTIONAL
478
478
  *
479
479
  * **Move Operations:**
480
- * For moveFrom/moveTo, an additional w:moveId attribute links the source and destination:
481
- * ```xml
482
- * <w:moveFrom w:id="3" w:author="Author" w:date="..." w:moveId="move-1">...</w:moveFrom>
483
- * <w:moveTo w:id="4" w:author="Author" w:date="..." w:moveId="move-1">...</w:moveTo>
484
- * ```
480
+ * w:moveFrom/w:moveTo are CT_RunTrackChange and carry only w:id/w:author/w:date.
481
+ * The moveId field stays in-memory pairing metadata (getMovePair/validateMovePairs);
482
+ * on disk, source/destination linkage is expressed solely via the w:name attribute
483
+ * on w:moveFromRangeStart/w:moveToRangeStart (see RangeMarker.toXML()).
485
484
  *
486
485
  * **Content vs Property Changes:**
487
486
  * - Content revisions (insert/delete/move): Contain w:r elements with text runs
488
487
  * - Property revisions (rPrChange/pPrChange): Contain previous property elements (w:rPr, w:pPr)
489
488
  *
489
+ * **Hyperlink Content:**
490
+ * w:hyperlink is not a valid child of CT_RunTrackChange, so hyperlink content is
491
+ * serialized with inverted nesting (<w:hyperlink><w:ins>...</w:ins></w:hyperlink>).
492
+ * Mixed Run + Hyperlink content produces sibling elements (one revision element per
493
+ * run group, one hyperlink-wrapped revision element per link), returned as a single
494
+ * raw-XML fragment element that XMLBuilder emits verbatim.
495
+ *
490
496
  * @returns XMLElement representing the revision in OOXML format, or null for internal-only types
491
497
  * @see ECMA-376 Part 1 §17.13.5 (Revision Identifiers for Paragraph Content)
492
498
  * @see ECMA-376 Part 1 §17.13.5.15 (Inserted Paragraph)
@@ -515,10 +521,9 @@ export class Revision {
515
521
  'w:date': this.formatDate(this.date),
516
522
  };
517
523
 
518
- // Add move-specific attributes
519
- if ((this.type === 'moveFrom' || this.type === 'moveTo') && this.moveId) {
520
- attributes['w:moveId'] = this.moveId;
521
- }
524
+ // moveId is intentionally NOT serialized: ECMA-376 CT_RunTrackChange declares no
525
+ // such attribute, and an undeclared w: attribute fails Open XML schema validation.
526
+ // On-disk move pairing is carried by w:name on the move range start markers.
522
527
 
523
528
  const elementName = this.getElementName();
524
529
  const children: XMLElement[] = [];
@@ -531,43 +536,84 @@ export class Revision {
531
536
  }
532
537
  }
533
538
 
534
- // Check if content contains only a single hyperlink (needs nesting inversion per ECMA-376)
535
- // w:hyperlink is NOT a valid child of w:ins/w:del; instead w:ins/w:del must be inside w:hyperlink
536
- const firstItem = this.content[0];
537
- const singleHyperlink =
538
- this.content.length === 1 && firstItem && isHyperlinkContent(firstItem) ? firstItem : null;
539
+ // Per ECMA-376, w:hyperlink is NOT a valid child of w:ins/w:del (CT_RunTrackChange);
540
+ // the nesting must be inverted so w:ins/w:del sits inside w:hyperlink. Content is
541
+ // therefore split into sibling segments: each consecutive run group gets its own
542
+ // revision element and each hyperlink wraps its own revision element, preserving
543
+ // the link target (r:id/w:anchor) instead of downgrading it to plain runs.
544
+ if (!this.isPropertyChangeType()) {
545
+ const segments: XMLElement[] = [];
546
+ // CT_TrackChange requires a document-unique w:id, so sibling segments
547
+ // cannot share the revision's id. Extra segments derive ids from a high
548
+ // base (deterministic, stable across saves) that cannot collide with the
549
+ // small sequential ids RevisionManager issues.
550
+ const segmentAttributes = (segmentIndex: number): Record<string, string> =>
551
+ segmentIndex === 0
552
+ ? attributes
553
+ : {
554
+ ...attributes,
555
+ 'w:id': String(500000000 + (this.id % 1000000) * 1000 + segmentIndex),
556
+ };
557
+ let pendingRuns: XMLElement[] = [];
558
+ const flushRuns = (): void => {
559
+ if (pendingRuns.length > 0) {
560
+ segments.push({
561
+ name: elementName,
562
+ attributes: segmentAttributes(segments.length),
563
+ children: pendingRuns,
564
+ });
565
+ pendingRuns = [];
566
+ }
567
+ };
539
568
 
540
- if (singleHyperlink && !this.isPropertyChangeType()) {
541
- return this.createHyperlinkWrappedRevisionXml(singleHyperlink, elementName, attributes);
569
+ for (const item of this.content) {
570
+ if (isHyperlinkContent(item)) {
571
+ flushRuns();
572
+ segments.push(
573
+ this.createHyperlinkWrappedRevisionXml(
574
+ item,
575
+ elementName,
576
+ segmentAttributes(segments.length)
577
+ )
578
+ );
579
+ } else if (isRunContent(item)) {
580
+ if (this.type === 'delete' || this.type === 'moveFrom') {
581
+ pendingRuns.push(this.createDeletedRunXml(item));
582
+ } else {
583
+ pendingRuns.push(item.toXML());
584
+ }
585
+ }
586
+ }
587
+ flushRuns();
588
+
589
+ if (segments.length === 0) {
590
+ return { name: elementName, attributes, children: [] };
591
+ }
592
+ if (segments.length === 1) {
593
+ return segments[0]!;
594
+ }
595
+ // Multiple sibling elements cannot share one XML root; emit them as a
596
+ // pre-serialized fragment that XMLBuilder passes through verbatim.
597
+ return {
598
+ name: '__rawXml',
599
+ rawXml: segments.map((segment) => XMLBuilder.elementToString(segment)).join(''),
600
+ };
542
601
  }
543
602
 
544
- // Add content to the revision (handles both Run and Hyperlink)
603
+ // Property change revisions keep their content runs alongside the
604
+ // previous-properties element
545
605
  for (const item of this.content) {
546
606
  if (isHyperlinkContent(item)) {
547
- // For multiple-item revisions containing hyperlinks, extract the runs
548
- // and add them directly (hyperlink wrapper omitted to maintain validity)
549
607
  const hyperlinkXml = item.toXML();
550
608
  if (hyperlinkXml.children) {
551
609
  for (const child of hyperlinkXml.children) {
552
610
  if (typeof child === 'object' && child.name === 'w:r') {
553
- if (this.type === 'delete' || this.type === 'moveFrom') {
554
- children.push(this.convertRunXmlToDeleted(child));
555
- } else {
556
- children.push(child);
557
- }
611
+ children.push(child);
558
612
  }
559
613
  }
560
614
  }
561
615
  } else if (isRunContent(item)) {
562
- // Handle Run content (existing behavior)
563
- if (this.type === 'delete' || this.type === 'moveFrom') {
564
- // For deletions and moveFrom, we need to modify the run XML to use w:delText instead of w:t
565
- const runXml = this.createDeletedRunXml(item);
566
- children.push(runXml);
567
- } else {
568
- // For other types, use normal run XML
569
- children.push(item.toXML());
570
- }
616
+ children.push(item.toXML());
571
617
  }
572
618
  }
573
619
 
@@ -647,8 +693,11 @@ export class Revision {
647
693
  *
648
694
  * **Implementation:**
649
695
  * This method converts the previousProperties object into OOXML elements.
650
- * - Boolean properties (e.g., bold) → <w:b/>
651
- * - Value properties (e.g., font size) <w:sz w:val="24"/>
696
+ * - runPropertiesChange delegates to Run.generateRunPropertiesXML
697
+ * - Other types translate API-style keys to their schema local names
698
+ * (e.g., alignment → w:jc), serialize known object values (spacing,
699
+ * indentation, borders, shading, table widths), and order children
700
+ * per the schema sequence of the containing property element
652
701
  *
653
702
  * @returns XMLElement containing previous properties (w:rPr, w:pPr, etc.)
654
703
  * @see ECMA-376 Part 1 §17.13.5.31 (Run Properties Change)
@@ -692,24 +741,31 @@ export class Revision {
692
741
  break;
693
742
  }
694
743
 
695
- // Build property children from previousProperties
744
+ // Build property children from previousProperties, translating API-style
745
+ // keys to their OOXML local names (keys already given as local names pass
746
+ // through unchanged)
747
+ const keyMap = Revision.PROPERTY_KEY_TO_ELEMENT[this.type] ?? {};
696
748
  const propChildren: XMLElement[] = [];
697
749
  if (this.previousProperties) {
698
750
  for (const [key, value] of Object.entries(this.previousProperties)) {
699
- if (typeof value === 'boolean' && value) {
700
- // Boolean properties (e.g., bold, italic)
701
- propChildren.push({ name: `w:${key}`, attributes: {}, children: [] });
702
- } else if (typeof value === 'string' || typeof value === 'number') {
703
- // Value properties (e.g., font size, color)
704
- propChildren.push({
705
- name: `w:${key}`,
706
- attributes: { 'w:val': value.toString() },
707
- children: [],
708
- });
751
+ const child = this.createPreviousPropertyXml(`w:${keyMap[key] ?? key}`, value);
752
+ if (child) {
753
+ propChildren.push(child);
709
754
  }
710
755
  }
711
756
  }
712
757
 
758
+ // CT_PPrBase, CT_TblPrBase, CT_TrPr, CT_TcPr and CT_SectPr are xsd:sequence
759
+ // types, so children must follow the declared order to stay schema-valid
760
+ const order = Revision.PROPERTY_CHILD_ORDER[propElementName];
761
+ if (order) {
762
+ const rank = (name: string): number => {
763
+ const index = order.indexOf(name);
764
+ return index === -1 ? order.length : index;
765
+ };
766
+ propChildren.sort((a, b) => rank(a.name) - rank(b.name));
767
+ }
768
+
713
769
  return {
714
770
  name: propElementName,
715
771
  attributes: {},
@@ -717,6 +773,349 @@ export class Revision {
717
773
  };
718
774
  }
719
775
 
776
+ /**
777
+ * API-key → OOXML local-name translations for property-change snapshots.
778
+ * previousProperties accepts the same key names as the element formatting
779
+ * APIs (e.g. ParagraphFormatting.alignment); the schema requires the local
780
+ * element names (w:jc), so keys are translated before serialization.
781
+ */
782
+ private static readonly PROPERTY_KEY_TO_ELEMENT: Partial<
783
+ Record<RevisionType, Record<string, string>>
784
+ > = {
785
+ paragraphPropertiesChange: {
786
+ alignment: 'jc',
787
+ style: 'pStyle',
788
+ styleId: 'pStyle',
789
+ indentation: 'ind',
790
+ numbering: 'numPr',
791
+ outlineLevel: 'outlineLvl',
792
+ shading: 'shd',
793
+ borders: 'pBdr',
794
+ },
795
+ tablePropertiesChange: {
796
+ alignment: 'jc',
797
+ style: 'tblStyle',
798
+ styleId: 'tblStyle',
799
+ width: 'tblW',
800
+ indent: 'tblInd',
801
+ borders: 'tblBorders',
802
+ shading: 'shd',
803
+ layout: 'tblLayout',
804
+ cellMargins: 'tblCellMar',
805
+ cellSpacing: 'tblCellSpacing',
806
+ },
807
+ tableExceptionPropertiesChange: {
808
+ alignment: 'jc',
809
+ width: 'tblW',
810
+ indent: 'tblInd',
811
+ borders: 'tblBorders',
812
+ shading: 'shd',
813
+ layout: 'tblLayout',
814
+ cellMargins: 'tblCellMar',
815
+ cellSpacing: 'tblCellSpacing',
816
+ },
817
+ tableRowPropertiesChange: {
818
+ alignment: 'jc',
819
+ height: 'trHeight',
820
+ isHeader: 'tblHeader',
821
+ cellSpacing: 'tblCellSpacing',
822
+ },
823
+ tableCellPropertiesChange: {
824
+ width: 'tcW',
825
+ borders: 'tcBorders',
826
+ shading: 'shd',
827
+ verticalAlignment: 'vAlign',
828
+ margins: 'tcMar',
829
+ },
830
+ sectionPropertiesChange: {
831
+ pageSize: 'pgSz',
832
+ margins: 'pgMar',
833
+ columns: 'cols',
834
+ pageNumbering: 'pgNumType',
835
+ },
836
+ };
837
+
838
+ /**
839
+ * Schema child order for property snapshot containers (xsd:sequence order
840
+ * from the corresponding CT_* types). Unknown children sort after known ones.
841
+ */
842
+ private static readonly PROPERTY_CHILD_ORDER: Record<string, readonly string[]> = {
843
+ 'w:pPr': [
844
+ 'w:pStyle',
845
+ 'w:keepNext',
846
+ 'w:keepLines',
847
+ 'w:pageBreakBefore',
848
+ 'w:framePr',
849
+ 'w:widowControl',
850
+ 'w:numPr',
851
+ 'w:suppressLineNumbers',
852
+ 'w:pBdr',
853
+ 'w:shd',
854
+ 'w:tabs',
855
+ 'w:suppressAutoHyphens',
856
+ 'w:kinsoku',
857
+ 'w:wordWrap',
858
+ 'w:overflowPunct',
859
+ 'w:topLinePunct',
860
+ 'w:autoSpaceDE',
861
+ 'w:autoSpaceDN',
862
+ 'w:bidi',
863
+ 'w:adjustRightInd',
864
+ 'w:snapToGrid',
865
+ 'w:spacing',
866
+ 'w:ind',
867
+ 'w:contextualSpacing',
868
+ 'w:mirrorIndents',
869
+ 'w:suppressOverlap',
870
+ 'w:jc',
871
+ 'w:textDirection',
872
+ 'w:textAlignment',
873
+ 'w:textboxTightWrap',
874
+ 'w:outlineLvl',
875
+ ],
876
+ 'w:tblPr': [
877
+ 'w:tblStyle',
878
+ 'w:tblpPr',
879
+ 'w:tblOverlap',
880
+ 'w:bidiVisual',
881
+ 'w:tblStyleRowBandSize',
882
+ 'w:tblStyleColBandSize',
883
+ 'w:tblW',
884
+ 'w:jc',
885
+ 'w:tblCellSpacing',
886
+ 'w:tblInd',
887
+ 'w:tblBorders',
888
+ 'w:shd',
889
+ 'w:tblLayout',
890
+ 'w:tblCellMar',
891
+ 'w:tblLook',
892
+ 'w:tblCaption',
893
+ 'w:tblDescription',
894
+ ],
895
+ 'w:tblPrEx': [
896
+ 'w:tblW',
897
+ 'w:jc',
898
+ 'w:tblCellSpacing',
899
+ 'w:tblInd',
900
+ 'w:tblBorders',
901
+ 'w:shd',
902
+ 'w:tblLayout',
903
+ 'w:tblCellMar',
904
+ 'w:tblLook',
905
+ ],
906
+ 'w:trPr': [
907
+ 'w:cnfStyle',
908
+ 'w:divId',
909
+ 'w:gridBefore',
910
+ 'w:gridAfter',
911
+ 'w:wBefore',
912
+ 'w:wAfter',
913
+ 'w:cantSplit',
914
+ 'w:trHeight',
915
+ 'w:tblHeader',
916
+ 'w:tblCellSpacing',
917
+ 'w:jc',
918
+ 'w:hidden',
919
+ ],
920
+ 'w:tcPr': [
921
+ 'w:cnfStyle',
922
+ 'w:tcW',
923
+ 'w:gridSpan',
924
+ 'w:hMerge',
925
+ 'w:vMerge',
926
+ 'w:tcBorders',
927
+ 'w:shd',
928
+ 'w:noWrap',
929
+ 'w:tcMar',
930
+ 'w:textDirection',
931
+ 'w:tcFitText',
932
+ 'w:vAlign',
933
+ 'w:hideMark',
934
+ ],
935
+ 'w:sectPr': [
936
+ 'w:headerReference',
937
+ 'w:footerReference',
938
+ 'w:footnotePr',
939
+ 'w:endnotePr',
940
+ 'w:type',
941
+ 'w:pgSz',
942
+ 'w:pgMar',
943
+ 'w:paperSrc',
944
+ 'w:pgBorders',
945
+ 'w:lnNumType',
946
+ 'w:pgNumType',
947
+ 'w:cols',
948
+ 'w:formProt',
949
+ 'w:vAlign',
950
+ 'w:noEndnote',
951
+ 'w:titlePg',
952
+ 'w:textDirection',
953
+ 'w:bidi',
954
+ 'w:rtlGutter',
955
+ 'w:docGrid',
956
+ ],
957
+ 'w:numPr': ['w:ilvl', 'w:numId', 'w:numberingChange', 'w:ins'],
958
+ };
959
+
960
+ /**
961
+ * Serializes one previous-property entry to its OOXML element.
962
+ * Booleans follow the CT_OnOff convention (true = on, false = explicit off
963
+ * via w:val="0") so the snapshot keeps the same tri-state information the
964
+ * main property serializers emit.
965
+ */
966
+ private createPreviousPropertyXml(name: string, value: unknown): XMLElement | null {
967
+ if (typeof value === 'boolean') {
968
+ return { name, attributes: value ? {} : { 'w:val': '0' }, children: [] };
969
+ }
970
+ if (typeof value === 'string' || typeof value === 'number') {
971
+ return { name, attributes: { 'w:val': value.toString() }, children: [] };
972
+ }
973
+ if (value && typeof value === 'object') {
974
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- previousProperties is Record<string, any>
975
+ return this.createObjectPropertyXml(name, value as Record<string, any>);
976
+ }
977
+ return null;
978
+ }
979
+
980
+ /**
981
+ * Serializes object-valued previous properties (complex OOXML structures).
982
+ * Only shapes with a known schema mapping are emitted; unknown objects are
983
+ * skipped because their attribute layout cannot be inferred safely.
984
+ */
985
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- previousProperties is Record<string, any>
986
+ private createObjectPropertyXml(name: string, value: Record<string, any>): XMLElement | null {
987
+ switch (name) {
988
+ case 'w:spacing':
989
+ return {
990
+ name,
991
+ attributes: XMLBuilder.buildAttributes({
992
+ 'w:before': value.before,
993
+ 'w:beforeLines': value.beforeLines,
994
+ 'w:beforeAutospacing':
995
+ value.beforeAutospacing === undefined
996
+ ? undefined
997
+ : value.beforeAutospacing
998
+ ? '1'
999
+ : '0',
1000
+ 'w:after': value.after,
1001
+ 'w:afterLines': value.afterLines,
1002
+ 'w:afterAutospacing':
1003
+ value.afterAutospacing === undefined ? undefined : value.afterAutospacing ? '1' : '0',
1004
+ 'w:line': value.line,
1005
+ 'w:lineRule': value.lineRule,
1006
+ }),
1007
+ children: [],
1008
+ };
1009
+ case 'w:ind':
1010
+ return {
1011
+ name,
1012
+ attributes: XMLBuilder.buildAttributes({
1013
+ 'w:start': value.start,
1014
+ 'w:end': value.end,
1015
+ 'w:left': value.left,
1016
+ 'w:leftChars': value.leftChars,
1017
+ 'w:right': value.right,
1018
+ 'w:rightChars': value.rightChars,
1019
+ 'w:firstLine': value.firstLine,
1020
+ 'w:firstLineChars': value.firstLineChars,
1021
+ 'w:hanging': value.hanging,
1022
+ 'w:hangingChars': value.hangingChars,
1023
+ }),
1024
+ children: [],
1025
+ };
1026
+ case 'w:numPr': {
1027
+ const numPrChildren: XMLElement[] = [];
1028
+ const level = value.level ?? value.ilvl;
1029
+ const numId = value.numId ?? value.id;
1030
+ if (level !== undefined) {
1031
+ numPrChildren.push({
1032
+ name: 'w:ilvl',
1033
+ attributes: { 'w:val': String(level) },
1034
+ children: [],
1035
+ });
1036
+ }
1037
+ if (numId !== undefined) {
1038
+ numPrChildren.push({
1039
+ name: 'w:numId',
1040
+ attributes: { 'w:val': String(numId) },
1041
+ children: [],
1042
+ });
1043
+ }
1044
+ return numPrChildren.length > 0 ? { name, attributes: {}, children: numPrChildren } : null;
1045
+ }
1046
+ case 'w:shd':
1047
+ return XMLBuilder.createShading(value);
1048
+ case 'w:pBdr':
1049
+ case 'w:tblBorders':
1050
+ case 'w:tcBorders': {
1051
+ const sides =
1052
+ name === 'w:pBdr'
1053
+ ? ['top', 'left', 'bottom', 'right', 'between', 'bar']
1054
+ : ['top', 'left', 'bottom', 'right', 'insideH', 'insideV'];
1055
+ const borderChildren: XMLElement[] = [];
1056
+ for (const side of sides) {
1057
+ if (value[side]) {
1058
+ borderChildren.push(XMLBuilder.createBorder(side, value[side]));
1059
+ }
1060
+ }
1061
+ return borderChildren.length > 0
1062
+ ? { name, attributes: {}, children: borderChildren }
1063
+ : null;
1064
+ }
1065
+ case 'w:tblW':
1066
+ case 'w:tcW':
1067
+ case 'w:tblInd':
1068
+ case 'w:tblCellSpacing':
1069
+ return {
1070
+ name,
1071
+ attributes: XMLBuilder.buildAttributes({
1072
+ 'w:w': value.w ?? value.width ?? value.value,
1073
+ 'w:type': value.type ?? 'dxa',
1074
+ }),
1075
+ children: [],
1076
+ };
1077
+ case 'w:trHeight':
1078
+ return {
1079
+ name,
1080
+ attributes: XMLBuilder.buildAttributes({
1081
+ 'w:val': value.value ?? value.val ?? value.height,
1082
+ 'w:hRule': value.rule ?? value.hRule,
1083
+ }),
1084
+ children: [],
1085
+ };
1086
+ case 'w:tcMar':
1087
+ case 'w:tblCellMar':
1088
+ return XMLBuilder.createMargins(name.slice(2), value);
1089
+ case 'w:pgSz':
1090
+ return {
1091
+ name,
1092
+ attributes: XMLBuilder.buildAttributes({
1093
+ 'w:w': value.width ?? value.w,
1094
+ 'w:h': value.height ?? value.h,
1095
+ 'w:orient': value.orientation ?? value.orient,
1096
+ }),
1097
+ children: [],
1098
+ };
1099
+ case 'w:pgMar':
1100
+ return {
1101
+ name,
1102
+ attributes: XMLBuilder.buildAttributes({
1103
+ 'w:top': value.top,
1104
+ 'w:right': value.right,
1105
+ 'w:bottom': value.bottom,
1106
+ 'w:left': value.left,
1107
+ 'w:header': value.header,
1108
+ 'w:footer': value.footer,
1109
+ 'w:gutter': value.gutter,
1110
+ }),
1111
+ children: [],
1112
+ };
1113
+ default:
1114
+ // Unknown object shape — no schema mapping to apply
1115
+ return null;
1116
+ }
1117
+ }
1118
+
720
1119
  /**
721
1120
  * Creates XML for a deleted run (uses w:delText or w:delInstrText instead of w:t)
722
1121
  *
@@ -506,8 +506,10 @@ export class Run {
506
506
  warnToConsole: true, // Enable warnings to help catch data quality issues
507
507
  });
508
508
 
509
- // Use cleaned text if available and cleaning was requested
510
- const cleanedText = validation.cleanedText || text;
509
+ // Use cleaned text if available and cleaning was requested.
510
+ // `??` (not `||`) so a cleaned result of '' (input that was entirely XML
511
+ // markup) is kept rather than falling back to the original markup-laden text.
512
+ const cleanedText = validation.cleanedText ?? text;
511
513
 
512
514
  // Convert undefined/null to empty string for consistent XML generation
513
515
  const normalizedText = cleanedText ?? '';
@@ -774,8 +776,10 @@ export class Run {
774
776
  warnToConsole: true, // Enable warnings to help catch data quality issues
775
777
  });
776
778
 
777
- // Use cleaned text if available and cleaning was requested
778
- const cleanedText = validation.cleanedText || text;
779
+ // Use cleaned text if available and cleaning was requested.
780
+ // `??` (not `||`) so a cleaned result of '' (input that was entirely XML
781
+ // markup) is kept rather than falling back to the original markup-laden text.
782
+ const cleanedText = validation.cleanedText ?? text;
779
783
 
780
784
  // Convert undefined/null to empty string for consistent XML generation
781
785
  const normalizedText = cleanedText ?? '';
@@ -1627,8 +1631,14 @@ export class Run {
1627
1631
  */
1628
1632
  setFont(font: string, size?: number): this {
1629
1633
  const previousFont = this.formatting.font;
1634
+ const previousHAnsi = this.formatting.fontHAnsi;
1635
+ const previousCs = this.formatting.fontCs;
1630
1636
  const previousSize = this.formatting.size;
1631
1637
  this.formatting.font = font;
1638
+ // Mirror into hAnsi/cs so an explicit font choice covers all character
1639
+ // ranges; the serializer no longer back-fills these slots from ascii.
1640
+ this.formatting.fontHAnsi = font;
1641
+ this.formatting.fontCs = font;
1632
1642
  if (size !== undefined) {
1633
1643
  this.formatting.size = size;
1634
1644
  }
@@ -1636,6 +1646,12 @@ export class Run {
1636
1646
  if (previousFont !== font) {
1637
1647
  this.trackingContext.trackRunPropertyChange(this, 'font', previousFont, font);
1638
1648
  }
1649
+ if (previousHAnsi !== font) {
1650
+ this.trackingContext.trackRunPropertyChange(this, 'fontHAnsi', previousHAnsi, font);
1651
+ }
1652
+ if (previousCs !== font) {
1653
+ this.trackingContext.trackRunPropertyChange(this, 'fontCs', previousCs, font);
1654
+ }
1639
1655
  if (size !== undefined && previousSize !== size) {
1640
1656
  this.trackingContext.trackRunPropertyChange(this, 'size', previousSize, size);
1641
1657
  }
@@ -2019,6 +2035,24 @@ export class Run {
2019
2035
  return this;
2020
2036
  }
2021
2037
 
2038
+ /**
2039
+ * Sets only the ASCII font slot (w:rFonts w:ascii) without mirroring into
2040
+ * hAnsi/cs. Use this when reconstructing a run from source XML so the parsed
2041
+ * model carries exactly the rFonts slots the document declared — unlike
2042
+ * setFont(), which intentionally fills hAnsi/cs for programmatic convenience
2043
+ * and would otherwise override style/theme-inherited fonts on round-trip.
2044
+ * @param font - Font name for the ASCII slot
2045
+ * @returns This run for method chaining
2046
+ */
2047
+ setFontAscii(font: string): this {
2048
+ const previousValue = this.formatting.font;
2049
+ this.formatting.font = font;
2050
+ if (this.trackingContext?.isEnabled() && previousValue !== font) {
2051
+ this.trackingContext.trackRunPropertyChange(this, 'font', previousValue, font);
2052
+ }
2053
+ return this;
2054
+ }
2055
+
2022
2056
  /**
2023
2057
  * Sets the High ANSI font (w:rFonts w:hAnsi)
2024
2058
  * @param font - Font name for high ANSI characters
@@ -2822,9 +2856,13 @@ export class Run {
2822
2856
  ) {
2823
2857
  const rFontsAttrs: Record<string, string> = {};
2824
2858
  if (formatting.font) rFontsAttrs['w:ascii'] = formatting.font;
2825
- rFontsAttrs['w:hAnsi'] = formatting.fontHAnsi || formatting.font || '';
2859
+ // Only emit w:hAnsi/w:cs when explicitly set per ECMA-376 §17.3.2.26
2860
+ // each rFonts slot inherits independently from the style/theme chain, so
2861
+ // back-filling them from the ascii font would override style- or
2862
+ // theme-resolved hAnsi/cs fonts and break round-trip fidelity.
2863
+ if (formatting.fontHAnsi !== undefined) rFontsAttrs['w:hAnsi'] = formatting.fontHAnsi;
2826
2864
  if (formatting.fontEastAsia) rFontsAttrs['w:eastAsia'] = formatting.fontEastAsia;
2827
- rFontsAttrs['w:cs'] = formatting.fontCs || formatting.font || '';
2865
+ if (formatting.fontCs !== undefined) rFontsAttrs['w:cs'] = formatting.fontCs;
2828
2866
  if (formatting.fontHint) rFontsAttrs['w:hint'] = formatting.fontHint;
2829
2867
  // Theme font references per ECMA-376 Part 1 §17.3.2.26
2830
2868
  if (formatting.fontAsciiTheme) rFontsAttrs['w:asciiTheme'] = formatting.fontAsciiTheme;