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
@@ -3,11 +3,13 @@
3
3
  */
4
4
 
5
5
  import { Paragraph } from './Paragraph.js';
6
+ import { Run } from './Run.js';
6
7
  import { TableRow, RowFormatting } from './TableRow.js';
7
8
  import { TableCell, CellFormatting } from './TableCell.js';
8
9
  import { Revision } from './Revision.js';
9
10
  import { XMLBuilder, XMLElement } from '../xml/XMLBuilder.js';
10
11
  import { deepClone } from '../utils/deepClone.js';
12
+ import { formatDateForXml } from '../utils/dateFormatting.js';
11
13
  import { TableGridChange } from './TableGridChange.js';
12
14
  import {
13
15
  TableAlignment as CommonTableAlignment,
@@ -2069,22 +2071,10 @@ export class Table {
2069
2071
  if (this.rows.length <= 1 && !this.trackingContext?.isEnabled()) {
2070
2072
  return false;
2071
2073
  }
2072
- // When tracking enabled, mark cells with cellDel and wrap content in w:del
2074
+ // When tracking enabled, record the whole-row tracked deletion instead
2075
+ // of removing the row
2073
2076
  if (this.trackingContext?.isEnabled()) {
2074
- const author = this.trackingContext.getAuthor();
2075
- const row = this.rows[index]!;
2076
- for (const cell of row.getCells()) {
2077
- const cellDelRevision = Revision.createTableCellDelete(author, []);
2078
- cell.setCellRevision(cellDelRevision);
2079
- // Wrap paragraph runs in w:del so content appears as deleted
2080
- for (const para of cell.getParagraphs()) {
2081
- const runs = para.getRuns();
2082
- if (runs.length > 0) {
2083
- const deletion = Revision.createDeletion(author, runs);
2084
- para.addRevision(deletion);
2085
- }
2086
- }
2087
- }
2077
+ this.markRowAsTrackedDeletion(this.rows[index]!);
2088
2078
  return true;
2089
2079
  }
2090
2080
  this.rows.splice(index, 1);
@@ -2109,6 +2099,47 @@ export class Table {
2109
2099
  return true;
2110
2100
  }
2111
2101
 
2102
+ /**
2103
+ * Marks a row as a tracked deletion. Per ECMA-376 §17.13.5.14 a tracked
2104
+ * whole-row deletion is recorded as `w:del` inside `w:trPr` — not as
2105
+ * per-cell `w:cellDel`, which tracks cell-structure changes and is never
2106
+ * resolved into a row removal by the revision acceptor. The cell text is
2107
+ * additionally wrapped in `w:del` so Word renders it struck through until
2108
+ * the revision is resolved.
2109
+ */
2110
+ private markRowAsTrackedDeletion(row: TableRow): void {
2111
+ const author = this.trackingContext!.getAuthor();
2112
+ const manager = this.trackingContext!.getRevisionManager();
2113
+ row.setRowDeletion({
2114
+ id: String(manager.consumeNextId()),
2115
+ author,
2116
+ date: formatDateForXml(new Date()),
2117
+ });
2118
+ for (const cell of row.getCells()) {
2119
+ for (const para of cell.getParagraphs()) {
2120
+ this.wrapParagraphRunsInTrackedDeletion(para, author);
2121
+ }
2122
+ }
2123
+ }
2124
+
2125
+ /**
2126
+ * Replaces each live run in the paragraph with a delete revision wrapping
2127
+ * it. Replacement (not append) matters: appending the revision would leave
2128
+ * the original run live alongside its `w:del` copy, so the text would
2129
+ * serialize twice and survive an accept. Items already inside revisions
2130
+ * are left alone to avoid double-wrapping.
2131
+ */
2132
+ private wrapParagraphRunsInTrackedDeletion(para: Paragraph, author: string): void {
2133
+ const manager = this.trackingContext!.getRevisionManager();
2134
+ for (const item of para.getContent()) {
2135
+ if (item instanceof Run) {
2136
+ const deletion = Revision.createDeletion(author, item);
2137
+ manager.register(deletion);
2138
+ para.replaceContent(item, [deletion]);
2139
+ }
2140
+ }
2141
+ }
2142
+
2112
2143
  /**
2113
2144
  * Inserts a row at a specific position
2114
2145
  *
@@ -2146,13 +2177,16 @@ export class Table {
2146
2177
  // Insert the row
2147
2178
  this.rows.splice(index, 0, row);
2148
2179
 
2149
- // When tracking enabled, mark every cell in the new row with cellIns
2180
+ // When tracking enabled, mark the row as a tracked insertion. Per
2181
+ // ECMA-376 §17.13.5.19 a whole-row insertion is recorded as w:ins inside
2182
+ // w:trPr — per-cell w:cellIns tracks cell-structure changes and would not
2183
+ // let Word or the revision acceptor resolve the row as inserted.
2150
2184
  if (this.trackingContext?.isEnabled()) {
2151
- const author = this.trackingContext.getAuthor();
2152
- for (const cell of row.getCells()) {
2153
- const revision = Revision.createTableCellInsert(author, []);
2154
- cell.setCellRevision(revision);
2155
- }
2185
+ row.setRowInsertion({
2186
+ id: String(this.trackingContext.getRevisionManager().consumeNextId()),
2187
+ author: this.trackingContext.getAuthor(),
2188
+ date: formatDateForXml(new Date()),
2189
+ });
2156
2190
  }
2157
2191
 
2158
2192
  return row;
@@ -2198,6 +2232,26 @@ export class Table {
2198
2232
  row.insertCellAt(idx, cell);
2199
2233
  }
2200
2234
  }
2235
+
2236
+ // Keep w:tblGrid in sync — toXML() trusts formatting.tableGrid for the
2237
+ // gridCol count whenever it is set (every loaded table has one), so a
2238
+ // stale grid makes rows consume more columns than the grid defines and
2239
+ // Word misassigns fixed-layout widths (ECMA-376 §17.4.49)
2240
+ const grid = this.formatting.tableGrid;
2241
+ if (grid) {
2242
+ const at = index === undefined || index >= grid.length ? grid.length : Math.max(0, index);
2243
+ const left = at > 0 ? grid[at - 1] : undefined;
2244
+ const right = at < grid.length ? grid[at] : undefined;
2245
+ // Mirror a neighbor's width; fall back to 1 inch when the grid has no
2246
+ // usable neighbor (e.g. an empty w:tblGrid) so a numeric width is always
2247
+ // inserted rather than `undefined`.
2248
+ const width =
2249
+ left !== undefined && right !== undefined
2250
+ ? Math.round((left + right) / 2)
2251
+ : (left ?? right ?? 1440);
2252
+ grid.splice(at, 0, width);
2253
+ }
2254
+
2201
2255
  return this;
2202
2256
  }
2203
2257
 
@@ -2220,15 +2274,25 @@ export class Table {
2220
2274
  return false;
2221
2275
  }
2222
2276
 
2223
- // When tracking enabled, mark cells with cellDel instead of removing
2277
+ // When tracking enabled, mark cells with cellDel instead of removing.
2278
+ // Column deletions keep the cell-level marker (w:cellDel §17.13.5.1) —
2279
+ // a row-level w:del would claim entire rows were deleted.
2224
2280
  if (this.trackingContext?.isEnabled()) {
2225
2281
  const author = this.trackingContext.getAuthor();
2282
+ const manager = this.trackingContext.getRevisionManager();
2226
2283
  let marked = false;
2227
2284
  for (const row of this.rows) {
2228
2285
  const cells = row.getCells();
2229
2286
  if (index < cells.length) {
2287
+ const cell = cells[index]!;
2230
2288
  const revision = Revision.createTableCellDelete(author, []);
2231
- cells[index]!.setCellRevision(revision);
2289
+ // Register so each marker gets a unique w:id — unregistered
2290
+ // revisions all serialize as w:id="0", which fails validation
2291
+ manager.register(revision);
2292
+ cell.setCellRevision(revision);
2293
+ for (const para of cell.getParagraphs()) {
2294
+ this.wrapParagraphRunsInTrackedDeletion(para, author);
2295
+ }
2232
2296
  marked = true;
2233
2297
  }
2234
2298
  }
@@ -2244,6 +2308,14 @@ export class Table {
2244
2308
  }
2245
2309
  }
2246
2310
 
2311
+ // Keep w:tblGrid in sync — leaving the deleted column's width in
2312
+ // formatting.tableGrid shifts every surviving column onto the wrong
2313
+ // gridCol width in fixed-layout tables (ECMA-376 §17.4.49). The tracked
2314
+ // branch above keeps cells in place, so the grid must not shrink there.
2315
+ if (removed && this.formatting.tableGrid && index < this.formatting.tableGrid.length) {
2316
+ this.formatting.tableGrid.splice(index, 1);
2317
+ }
2318
+
2247
2319
  return removed;
2248
2320
  }
2249
2321
 
@@ -2738,6 +2810,28 @@ export class Table {
2738
2810
  }
2739
2811
  }
2740
2812
  }
2813
+ } else if (endCol > startCol) {
2814
+ // Per ECMA-376 §17.4.17 a gridSpan cell consumes the columns it spans,
2815
+ // so the absorbed w:tc elements must leave the row — keeping them
2816
+ // widens the table grid and misaligns every other row. Their content
2817
+ // moves into the surviving cell first, matching Word's merge behavior.
2818
+ // vMerge 'continue' cells at startCol stay (required by §17.4.84), and
2819
+ // the tracked branch above keeps cells so w:cellMerge markers survive.
2820
+ const absorbedCount = endCol - startCol;
2821
+ for (let row = startRow; row <= endRow; row++) {
2822
+ const tableRow = this.rows[row];
2823
+ if (!tableRow) continue;
2824
+ for (let i = 0; i < absorbedCount; i++) {
2825
+ const absorbed = tableRow.removeCellAt(startCol + 1);
2826
+ if (!absorbed) break;
2827
+ for (const para of absorbed.getParagraphs()) {
2828
+ // Placeholder-empty paragraphs would only pad the merged cell
2829
+ // with blank lines Word's merge does not create
2830
+ if (para.getContent().length === 0) continue;
2831
+ cell.addParagraph(para);
2832
+ }
2833
+ }
2834
+ }
2741
2835
  }
2742
2836
 
2743
2837
  return this;
@@ -2848,12 +2942,13 @@ export class Table {
2848
2942
  const fromCell = this.getCell(fromRow, fromCol);
2849
2943
  const toCell = this.getCell(toRow, toCol);
2850
2944
 
2851
- if (!fromCell || !toCell) {
2945
+ if (!fromCell || !toCell || fromCell === toCell) {
2852
2946
  return this;
2853
2947
  }
2854
2948
 
2855
- // Copy all paragraphs from source to target
2856
- const paragraphs = fromCell.getParagraphs();
2949
+ // Detach paragraphs from the source before re-homing them so the same
2950
+ // Paragraph instances never live in two cells at once
2951
+ const paragraphs = fromCell._detachAllParagraphs();
2857
2952
  for (const para of paragraphs) {
2858
2953
  toCell.addParagraph(para);
2859
2954
  }
@@ -2865,11 +2960,11 @@ export class Table {
2865
2960
  if (formatting.width) toCell.setWidth(formatting.width);
2866
2961
  if (formatting.verticalAlignment) toCell.setVerticalAlignment(formatting.verticalAlignment);
2867
2962
 
2868
- // Clear source cell (replace with empty paragraph)
2963
+ // Clear source cell by writing an empty cell into the row's live array —
2964
+ // getCells() returns a defensive copy, so assigning into it never persists
2869
2965
  const row = this.getRow(fromRow);
2870
2966
  if (row) {
2871
- const cells = row.getCells();
2872
- cells[fromCol] = new TableCell();
2967
+ row.setCellAt(fromCol, new TableCell());
2873
2968
  }
2874
2969
 
2875
2970
  return this;
@@ -2895,17 +2990,18 @@ export class Table {
2895
2990
  return this;
2896
2991
  }
2897
2992
 
2898
- const cells1 = row1Obj.getCells();
2899
- const cells2 = row2Obj.getCells();
2993
+ const cell1 = row1Obj.getCell(col1);
2994
+ const cell2 = row2Obj.getCell(col2);
2900
2995
 
2901
- if (col1 >= cells1.length || col2 >= cells2.length) {
2996
+ if (!cell1 || !cell2 || cell1 === cell2) {
2902
2997
  return this;
2903
2998
  }
2904
2999
 
2905
- // Swap cells
2906
- const temp = cells1[col1];
2907
- cells1[col1] = cells2[col2]!;
2908
- cells2[col2] = temp!;
3000
+ // Swap through the rows' live cell arrays — getCells() returns a
3001
+ // defensive copy, so swapping inside it never persists. setCellAt also
3002
+ // re-points each cell's parent-row reference.
3003
+ row1Obj.setCellAt(col1, cell2);
3004
+ row2Obj.setCellAt(col2, cell1);
2909
3005
 
2910
3006
  return this;
2911
3007
  }
@@ -2976,22 +3072,10 @@ export class Table {
2976
3072
 
2977
3073
  const actualCount = Math.min(count, this.rows.length - startIndex);
2978
3074
 
2979
- // When tracking enabled, mark each row's cells with cellDel + w:del
3075
+ // When tracking enabled, record each row as a whole-row tracked deletion
2980
3076
  if (this.trackingContext?.isEnabled()) {
2981
- const author = this.trackingContext.getAuthor();
2982
3077
  for (let i = startIndex; i < startIndex + actualCount; i++) {
2983
- const row = this.rows[i]!;
2984
- for (const cell of row.getCells()) {
2985
- const cellDelRevision = Revision.createTableCellDelete(author, []);
2986
- cell.setCellRevision(cellDelRevision);
2987
- for (const para of cell.getParagraphs()) {
2988
- const runs = para.getRuns();
2989
- if (runs.length > 0) {
2990
- const deletion = Revision.createDeletion(author, runs);
2991
- para.addRevision(deletion);
2992
- }
2993
- }
2994
- }
3078
+ this.markRowAsTrackedDeletion(this.rows[i]!);
2995
3079
  }
2996
3080
  return true;
2997
3081
  }
@@ -3322,6 +3406,23 @@ export class Table {
3322
3406
  clonedTable.addRow(row.clone());
3323
3407
  }
3324
3408
 
3409
+ // Tracked revision history (w:tblPrChange / w:tblGridChange) lives outside
3410
+ // formatting, so deepClone above cannot carry it — copy explicitly for
3411
+ // parity with TableRow.clone()/TableCell.clone().
3412
+ if (this.tblPrChange) {
3413
+ clonedTable.setTblPrChange(deepClone(this.tblPrChange));
3414
+ }
3415
+ if (this.tblGridChange) {
3416
+ clonedTable.setTblGridChange(
3417
+ new TableGridChange({
3418
+ id: this.tblGridChange.getId(),
3419
+ author: this.tblGridChange.getAuthor(),
3420
+ date: deepClone(this.tblGridChange.getDate()),
3421
+ previousGrid: deepClone(this.tblGridChange.getPreviousGrid()),
3422
+ })
3423
+ );
3424
+ }
3425
+
3325
3426
  return clonedTable;
3326
3427
  }
3327
3428
 
@@ -5,8 +5,10 @@
5
5
  import { deepClone } from '../utils/deepClone.js';
6
6
  import { formatDateForXml } from '../utils/dateFormatting.js';
7
7
  import { XMLBuilder, XMLElement } from '../xml/XMLBuilder.js';
8
+ import { Hyperlink } from './Hyperlink.js';
8
9
  import { Paragraph, TextDirection } from './Paragraph.js';
9
10
  import { Revision } from './Revision.js';
11
+ import { Run } from './Run.js';
10
12
  import {
11
13
  BorderStyle as CommonBorderStyle,
12
14
  FullBorderStyle as CommonFullBorderStyle,
@@ -242,19 +244,32 @@ export class TableCell {
242
244
  return false;
243
245
  }
244
246
 
245
- // When tracking enabled, wrap content in w:del instead of removing
247
+ // When tracking enabled, wrap content in w:del instead of removing.
248
+ // Each run is REPLACED by its delete revision — appending the revision
249
+ // would leave the original run live alongside its w:del copy, so the
250
+ // text would serialize twice and survive an accept.
246
251
  if (this.trackingContext?.isEnabled()) {
247
252
  const paragraph = this.paragraphs[index]!;
248
- const runs = paragraph.getRuns();
249
- if (runs.length > 0) {
250
- const author = this.trackingContext.getAuthor();
251
- const deletion = Revision.createDeletion(author, runs);
252
- this.trackingContext.getRevisionManager().register(deletion);
253
- paragraph.addRevision(deletion);
253
+ const author = this.trackingContext.getAuthor();
254
+ const manager = this.trackingContext.getRevisionManager();
255
+ for (const item of paragraph.getContent()) {
256
+ if (item instanceof Run || item instanceof Hyperlink) {
257
+ const deletion = Revision.createDeletion(author, item);
258
+ manager.register(deletion);
259
+ paragraph.replaceContent(item, [deletion]);
260
+ }
254
261
  }
255
262
  return true;
256
263
  }
257
264
 
265
+ // Word repairs files where a nested table is the final child of w:tc —
266
+ // every cell must end with w:p. Refuse to remove the last paragraph when
267
+ // raw block content sits immediately before it and nothing would follow.
268
+ const isLastParagraph = index === this.paragraphs.length - 1;
269
+ if (isLastParagraph && this.rawNestedContent.some((item) => item.position === index)) {
270
+ return false;
271
+ }
272
+
258
273
  const removed = this.paragraphs.splice(index, 1);
259
274
  const removedPara = removed[0];
260
275
  if (removedPara) {
@@ -309,14 +324,20 @@ export class TableCell {
309
324
  paragraph._setStylesManager(stylesManager);
310
325
  }
311
326
 
312
- // When tracking enabled, wrap paragraph content in w:ins revision
327
+ // When tracking enabled, wrap paragraph content in w:ins revisions.
328
+ // Each top-level Run/Hyperlink is REPLACED by its insert revision in
329
+ // place — wrapping paragraph.getRuns() and appending the revision would
330
+ // leave the originals live alongside the w:ins copy, so the text would
331
+ // serialize twice and survive a reject.
313
332
  if (this.trackingContext?.isEnabled()) {
314
- const runs = paragraph.getRuns();
315
- if (runs.length > 0) {
316
- const author = this.trackingContext.getAuthor();
317
- const insertion = Revision.createInsertion(author, runs);
318
- this.trackingContext.getRevisionManager().register(insertion);
319
- paragraph.addRevision(insertion);
333
+ const author = this.trackingContext.getAuthor();
334
+ const manager = this.trackingContext.getRevisionManager();
335
+ for (const item of paragraph.getContent()) {
336
+ if (item instanceof Run || item instanceof Hyperlink) {
337
+ const insertion = Revision.createInsertion(author, item);
338
+ manager.register(insertion);
339
+ paragraph.replaceContent(item, [insertion]);
340
+ }
320
341
  }
321
342
  }
322
343
 
@@ -1288,6 +1309,22 @@ export class TableCell {
1288
1309
  return this._parentRow;
1289
1310
  }
1290
1311
 
1312
+ /**
1313
+ * Removes every paragraph from this cell and returns them, bypassing
1314
+ * tracked-change wrapping. Used when content is moved wholesale to another
1315
+ * cell — the paragraphs must leave this cell's array first so the same
1316
+ * Paragraph instances never live in two cells at once.
1317
+ * @internal
1318
+ */
1319
+ _detachAllParagraphs(): Paragraph[] {
1320
+ const detached = this.paragraphs;
1321
+ this.paragraphs = [];
1322
+ for (const para of detached) {
1323
+ para._setParentCell(undefined);
1324
+ }
1325
+ return detached;
1326
+ }
1327
+
1291
1328
  /**
1292
1329
  * Gets the table style ID by traversing up the parent chain.
1293
1330
  * @returns Table style ID or undefined if not in a table or no style set
@@ -1707,6 +1744,7 @@ export class TableCell {
1707
1744
  }
1708
1745
 
1709
1746
  // Insert any remaining raw content after all paragraphs
1747
+ const rawContentEndsCell = rawIndex < sortedRaw.length;
1710
1748
  while (rawIndex < sortedRaw.length) {
1711
1749
  const rawItem = sortedRaw[rawIndex];
1712
1750
  if (rawItem) {
@@ -1718,9 +1756,11 @@ export class TableCell {
1718
1756
  rawIndex++;
1719
1757
  }
1720
1758
 
1721
- // If we only have raw content and no paragraphs, we need at least one empty paragraph
1722
- // per ECMA-376 (table cell must contain at least one block-level element)
1723
- if (this.paragraphs.length === 0) {
1759
+ // Word requires every cell to end with w:p (it repairs files where a
1760
+ // nested w:tbl is the final child of w:tc), so close with an empty
1761
+ // paragraph whenever raw block content ended up last — this also covers
1762
+ // cells that have raw content but no paragraphs at all
1763
+ if (rawContentEndsCell) {
1724
1764
  cellChildren.push(new Paragraph().toXML());
1725
1765
  }
1726
1766
  } else {
@@ -247,11 +247,10 @@ export class TableOfContents {
247
247
 
248
248
  // Add specific styles switch OR heading levels switch
249
249
  if (this.includeStyles && this.includeStyles.length > 0) {
250
- // Use \t switch to specify exact styles
251
- // Format: \t "StyleName,Level," for each style
252
- for (const style of this.includeStyles) {
253
- instruction += ` \\t "${style.styleName},${style.level},"`;
254
- }
250
+ // Per ECMA-376 §17.16.5.68, \t takes a single field-argument with all
251
+ // StyleName,Level doublets Word honors only one \t instance
252
+ const doublets = this.includeStyles.map((s) => `${s.styleName},${s.level}`).join(',');
253
+ instruction += ` \\t "${doublets}"`;
255
254
  } else {
256
255
  // Use \o switch for heading levels
257
256
  instruction += ` \\o "1-${this.levels}"`;
@@ -273,11 +272,13 @@ export class TableOfContents {
273
272
  }
274
273
 
275
274
  // Add tab leader switch
276
- if (this.tabLeader !== 'dot') {
275
+ // Per ECMA-376 §17.16.5.68, \p takes the literal separator character(s)
276
+ // between entry and page number, not an ST_TabTlc letter code.
277
+ // 'none' omits the switch so Word uses its default separator.
278
+ if (this.tabLeader !== 'dot' && this.tabLeader !== 'none') {
277
279
  const leaderMap = {
278
- hyphen: 'h',
279
- underscore: 'u',
280
- none: 'n',
280
+ hyphen: '-',
281
+ underscore: '_',
281
282
  };
282
283
  instruction += ` \\p "${leaderMap[this.tabLeader]}"`;
283
284
  }
@@ -250,6 +250,32 @@ export class TableRow {
250
250
  cell._setParentRow(this);
251
251
  }
252
252
 
253
+ /**
254
+ * Replaces the cell at the specified index in the row's live cell array.
255
+ * getCells() returns a defensive copy, so callers cannot persist a
256
+ * replacement by writing into that copy — this is the write-through path.
257
+ * @param index - Position to replace (0-based)
258
+ * @returns The replaced cell, or undefined if index is out of bounds
259
+ */
260
+ setCellAt(index: number, cell: TableCell): TableCell | undefined {
261
+ if (index < 0 || index >= this.cells.length) return undefined;
262
+ const previous = this.cells[index];
263
+ this.cells[index] = cell;
264
+ cell._setParentRow(this);
265
+ // During a swap the outgoing cell may already have been re-homed to
266
+ // another row (or re-inserted elsewhere in this row) — only orphan it
267
+ // when it genuinely left the table.
268
+ if (
269
+ previous &&
270
+ previous !== cell &&
271
+ previous._getParentRow() === this &&
272
+ !this.cells.includes(previous)
273
+ ) {
274
+ previous._setParentRow(undefined);
275
+ }
276
+ return previous;
277
+ }
278
+
253
279
  /**
254
280
  * Removes and returns the cell at the specified index
255
281
  * @param index - Position to remove (0-based)
@@ -283,9 +309,14 @@ export class TableRow {
283
309
  * Calculates the total grid span of the row (considering column spans)
284
310
  *
285
311
  * For tables with merged cells, this returns the number of logical columns
286
- * this row spans based on the columnSpan values of each cell.
312
+ * this row spans based on the columnSpan values of each cell. Per ECMA-376
313
+ * §17.4.14/§17.4.15, w:gridBefore/w:gridAfter consume grid columns before
314
+ * the first and after the last cell, so they count toward the row's grid
315
+ * footprint — otherwise auto-generated w:tblGrid would declare fewer
316
+ * columns than offset rows actually occupy.
287
317
  *
288
- * @returns Total grid span (sum of all cell spans, where unspanned cells count as 1)
318
+ * @returns Total grid span (gridBefore + sum of all cell spans + gridAfter,
319
+ * where unspanned cells count as 1)
289
320
  *
290
321
  * @example
291
322
  * ```typescript
@@ -298,11 +329,12 @@ export class TableRow {
298
329
  * ```
299
330
  */
300
331
  getTotalGridSpan(): number {
301
- let totalSpan = 0;
332
+ let totalSpan = this.formatting.gridBefore ?? 0;
302
333
  for (const cell of this.cells) {
303
334
  const formatting = cell.getFormatting();
304
335
  totalSpan += formatting.columnSpan || 1;
305
336
  }
337
+ totalSpan += this.formatting.gridAfter ?? 0;
306
338
  return totalSpan;
307
339
  }
308
340
 
@@ -390,58 +390,73 @@ export class TextBox {
390
390
  relativeHeight: 251658240,
391
391
  };
392
392
 
393
- // Position H (horizontal)
394
- if (this.position) {
395
- const posH = this.position.horizontal;
396
- const posHChildren: XMLElement[] = [];
397
-
398
- if (posH.offset !== undefined) {
399
- posHChildren.push({
400
- name: 'wp:posOffset',
401
- children: [posH.offset.toString()],
402
- });
403
- } else if (posH.alignment) {
404
- posHChildren.push({
405
- name: 'wp:align',
406
- children: [posH.alignment],
407
- });
408
- }
393
+ // simplePos (required first child per CT_Anchor)
394
+ children.push({
395
+ name: 'wp:simplePos',
396
+ attributes: { x: '0', y: '0' },
397
+ selfClosing: true,
398
+ });
409
399
 
410
- children.push({
411
- name: 'wp:positionH',
412
- attributes: {
413
- relativeFrom: posH.anchor,
414
- },
415
- children: posHChildren,
400
+ // Position H (horizontal) — required child of wp:anchor per CT_Anchor
401
+ // (minOccurs=1), so fall back to a zero page offset when no explicit
402
+ // position is configured (anchor-only floating text boxes)
403
+ const posH = this.position?.horizontal;
404
+ const posHChildren: XMLElement[] = [];
405
+
406
+ if (posH?.offset !== undefined) {
407
+ posHChildren.push({
408
+ name: 'wp:posOffset',
409
+ children: [posH.offset.toString()],
410
+ });
411
+ } else if (posH?.alignment) {
412
+ posHChildren.push({
413
+ name: 'wp:align',
414
+ children: [posH.alignment],
415
+ });
416
+ } else {
417
+ posHChildren.push({
418
+ name: 'wp:posOffset',
419
+ children: ['0'],
416
420
  });
417
421
  }
418
422
 
419
- // Position V (vertical)
420
- if (this.position) {
421
- const posV = this.position.vertical;
422
- const posVChildren: XMLElement[] = [];
423
+ children.push({
424
+ name: 'wp:positionH',
425
+ attributes: {
426
+ relativeFrom: posH?.anchor ?? 'page',
427
+ },
428
+ children: posHChildren,
429
+ });
423
430
 
424
- if (posV.offset !== undefined) {
425
- posVChildren.push({
426
- name: 'wp:posOffset',
427
- children: [posV.offset.toString()],
428
- });
429
- } else if (posV.alignment) {
430
- posVChildren.push({
431
- name: 'wp:align',
432
- children: [posV.alignment],
433
- });
434
- }
431
+ // Position V (vertical) — required child of wp:anchor per CT_Anchor
432
+ const posV = this.position?.vertical;
433
+ const posVChildren: XMLElement[] = [];
435
434
 
436
- children.push({
437
- name: 'wp:positionV',
438
- attributes: {
439
- relativeFrom: posV.anchor,
440
- },
441
- children: posVChildren,
435
+ if (posV?.offset !== undefined) {
436
+ posVChildren.push({
437
+ name: 'wp:posOffset',
438
+ children: [posV.offset.toString()],
439
+ });
440
+ } else if (posV?.alignment) {
441
+ posVChildren.push({
442
+ name: 'wp:align',
443
+ children: [posV.alignment],
444
+ });
445
+ } else {
446
+ posVChildren.push({
447
+ name: 'wp:posOffset',
448
+ children: ['0'],
442
449
  });
443
450
  }
444
451
 
452
+ children.push({
453
+ name: 'wp:positionV',
454
+ attributes: {
455
+ relativeFrom: posV?.anchor ?? 'page',
456
+ },
457
+ children: posVChildren,
458
+ });
459
+
445
460
  // Extent (size)
446
461
  children.push({
447
462
  name: 'wp:extent',
@@ -357,8 +357,10 @@ export class AbstractNumbering {
357
357
  children.push(level.toXML());
358
358
  });
359
359
 
360
- // If no levels defined, add a default level 0
361
- if (sortedLevels.length === 0) {
360
+ // If no levels defined, add a default level 0 — except for numStyleLink
361
+ // definitions, which are pure style references per ECMA-376 §17.9.21 and
362
+ // must carry no w:lvl children (the linked style supplies the levels)
363
+ if (sortedLevels.length === 0 && !this.numStyleLink) {
362
364
  children.push(NumberingLevel.createDecimalLevel(0).toXML());
363
365
  }
364
366