docxmlater 10.1.4 → 10.1.6

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 (372) hide show
  1. package/README.md +759 -754
  2. package/dist/constants/legacyCompatFlags.js +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.js.map +1 -1
  5. package/dist/core/Document.d.ts +51 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +486 -471
  8. package/dist/core/Document.js.map +1 -1
  9. package/dist/core/DocumentContent.d.ts +9 -9
  10. package/dist/core/DocumentContent.d.ts.map +1 -1
  11. package/dist/core/DocumentContent.js +1 -1
  12. package/dist/core/DocumentContent.js.map +1 -1
  13. package/dist/core/DocumentGenerator.d.ts +11 -11
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +251 -251
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.js.map +1 -1
  18. package/dist/core/DocumentParser.d.ts +15 -15
  19. package/dist/core/DocumentParser.d.ts.map +1 -1
  20. package/dist/core/DocumentParser.js +2123 -2155
  21. package/dist/core/DocumentParser.js.map +1 -1
  22. package/dist/core/DocumentValidator.d.ts.map +1 -1
  23. package/dist/core/DocumentValidator.js +2 -5
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.js.map +1 -1
  26. package/dist/core/RelationshipManager.d.ts.map +1 -1
  27. package/dist/core/RelationshipManager.js +3 -3
  28. package/dist/core/RelationshipManager.js.map +1 -1
  29. package/dist/elements/AlternateContent.js.map +1 -1
  30. package/dist/elements/Bookmark.d.ts.map +1 -1
  31. package/dist/elements/Bookmark.js +3 -1
  32. package/dist/elements/Bookmark.js.map +1 -1
  33. package/dist/elements/BookmarkManager.d.ts.map +1 -1
  34. package/dist/elements/BookmarkManager.js.map +1 -1
  35. package/dist/elements/Comment.d.ts.map +1 -1
  36. package/dist/elements/Comment.js +9 -6
  37. package/dist/elements/Comment.js.map +1 -1
  38. package/dist/elements/CommentManager.d.ts.map +1 -1
  39. package/dist/elements/CommentManager.js +18 -17
  40. package/dist/elements/CommentManager.js.map +1 -1
  41. package/dist/elements/CommonTypes.d.ts +21 -21
  42. package/dist/elements/CommonTypes.d.ts.map +1 -1
  43. package/dist/elements/CommonTypes.js +56 -56
  44. package/dist/elements/CommonTypes.js.map +1 -1
  45. package/dist/elements/CustomXml.js.map +1 -1
  46. package/dist/elements/Endnote.d.ts.map +1 -1
  47. package/dist/elements/Endnote.js +6 -6
  48. package/dist/elements/Endnote.js.map +1 -1
  49. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  50. package/dist/elements/EndnoteManager.js +6 -7
  51. package/dist/elements/EndnoteManager.js.map +1 -1
  52. package/dist/elements/Field.d.ts.map +1 -1
  53. package/dist/elements/Field.js +82 -25
  54. package/dist/elements/Field.js.map +1 -1
  55. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  56. package/dist/elements/FieldHelpers.js.map +1 -1
  57. package/dist/elements/FontManager.d.ts.map +1 -1
  58. package/dist/elements/FontManager.js +1 -1
  59. package/dist/elements/FontManager.js.map +1 -1
  60. package/dist/elements/Footer.js +2 -2
  61. package/dist/elements/Footer.js.map +1 -1
  62. package/dist/elements/Footnote.d.ts.map +1 -1
  63. package/dist/elements/Footnote.js +6 -6
  64. package/dist/elements/Footnote.js.map +1 -1
  65. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  66. package/dist/elements/FootnoteManager.js +6 -7
  67. package/dist/elements/FootnoteManager.js.map +1 -1
  68. package/dist/elements/Header.js +2 -2
  69. package/dist/elements/Header.js.map +1 -1
  70. package/dist/elements/HeaderFooterManager.js.map +1 -1
  71. package/dist/elements/Hyperlink.d.ts +5 -3
  72. package/dist/elements/Hyperlink.d.ts.map +1 -1
  73. package/dist/elements/Hyperlink.js +134 -76
  74. package/dist/elements/Hyperlink.js.map +1 -1
  75. package/dist/elements/Image.d.ts.map +1 -1
  76. package/dist/elements/Image.js +238 -106
  77. package/dist/elements/Image.js.map +1 -1
  78. package/dist/elements/ImageManager.d.ts.map +1 -1
  79. package/dist/elements/ImageManager.js +1 -1
  80. package/dist/elements/ImageManager.js.map +1 -1
  81. package/dist/elements/ImageRun.js +1 -1
  82. package/dist/elements/ImageRun.js.map +1 -1
  83. package/dist/elements/MathElement.js.map +1 -1
  84. package/dist/elements/Paragraph.d.ts +24 -24
  85. package/dist/elements/Paragraph.d.ts.map +1 -1
  86. package/dist/elements/Paragraph.js +181 -188
  87. package/dist/elements/Paragraph.js.map +1 -1
  88. package/dist/elements/PreservedElement.js.map +1 -1
  89. package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
  90. package/dist/elements/PropertyChangeTypes.js +6 -6
  91. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  92. package/dist/elements/RangeMarker.d.ts.map +1 -1
  93. package/dist/elements/RangeMarker.js.map +1 -1
  94. package/dist/elements/Revision.d.ts.map +1 -1
  95. package/dist/elements/Revision.js +4 -5
  96. package/dist/elements/Revision.js.map +1 -1
  97. package/dist/elements/RevisionContent.js.map +1 -1
  98. package/dist/elements/RevisionManager.d.ts.map +1 -1
  99. package/dist/elements/RevisionManager.js +40 -48
  100. package/dist/elements/RevisionManager.js.map +1 -1
  101. package/dist/elements/Run.d.ts +16 -16
  102. package/dist/elements/Run.d.ts.map +1 -1
  103. package/dist/elements/Run.js +256 -238
  104. package/dist/elements/Run.js.map +1 -1
  105. package/dist/elements/Section.d.ts.map +1 -1
  106. package/dist/elements/Section.js +36 -11
  107. package/dist/elements/Section.js.map +1 -1
  108. package/dist/elements/Shape.d.ts.map +1 -1
  109. package/dist/elements/Shape.js.map +1 -1
  110. package/dist/elements/StructuredDocumentTag.d.ts +6 -6
  111. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  112. package/dist/elements/StructuredDocumentTag.js +99 -104
  113. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  114. package/dist/elements/Table.d.ts +11 -11
  115. package/dist/elements/Table.d.ts.map +1 -1
  116. package/dist/elements/Table.js +102 -107
  117. package/dist/elements/Table.js.map +1 -1
  118. package/dist/elements/TableCell.d.ts +10 -10
  119. package/dist/elements/TableCell.d.ts.map +1 -1
  120. package/dist/elements/TableCell.js +105 -106
  121. package/dist/elements/TableCell.js.map +1 -1
  122. package/dist/elements/TableGridChange.d.ts.map +1 -1
  123. package/dist/elements/TableGridChange.js.map +1 -1
  124. package/dist/elements/TableOfContents.d.ts.map +1 -1
  125. package/dist/elements/TableOfContents.js +4 -4
  126. package/dist/elements/TableOfContents.js.map +1 -1
  127. package/dist/elements/TableOfContentsElement.js.map +1 -1
  128. package/dist/elements/TableRow.d.ts.map +1 -1
  129. package/dist/elements/TableRow.js +13 -6
  130. package/dist/elements/TableRow.js.map +1 -1
  131. package/dist/elements/TextBox.d.ts.map +1 -1
  132. package/dist/elements/TextBox.js +3 -5
  133. package/dist/elements/TextBox.js.map +1 -1
  134. package/dist/formatting/AbstractNumbering.d.ts +4 -4
  135. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  136. package/dist/formatting/AbstractNumbering.js +54 -49
  137. package/dist/formatting/AbstractNumbering.js.map +1 -1
  138. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  139. package/dist/formatting/NumberingInstance.js +1 -3
  140. package/dist/formatting/NumberingInstance.js.map +1 -1
  141. package/dist/formatting/NumberingLevel.d.ts +5 -5
  142. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  143. package/dist/formatting/NumberingLevel.js +119 -125
  144. package/dist/formatting/NumberingLevel.js.map +1 -1
  145. package/dist/formatting/NumberingManager.d.ts +1 -0
  146. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  147. package/dist/formatting/NumberingManager.js +27 -9
  148. package/dist/formatting/NumberingManager.js.map +1 -1
  149. package/dist/formatting/Style.d.ts +11 -11
  150. package/dist/formatting/Style.d.ts.map +1 -1
  151. package/dist/formatting/Style.js +219 -247
  152. package/dist/formatting/Style.js.map +1 -1
  153. package/dist/formatting/StylesManager.d.ts +2 -2
  154. package/dist/formatting/StylesManager.d.ts.map +1 -1
  155. package/dist/formatting/StylesManager.js +96 -102
  156. package/dist/formatting/StylesManager.js.map +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts +1 -1
  158. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  159. package/dist/helpers/CleanupHelper.js +6 -6
  160. package/dist/helpers/CleanupHelper.js.map +1 -1
  161. package/dist/images/ImageOptimizer.js +7 -7
  162. package/dist/images/ImageOptimizer.js.map +1 -1
  163. package/dist/index.d.ts +9 -9
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js.map +1 -1
  166. package/dist/managers/DrawingManager.js.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  168. package/dist/tracking/DocumentTrackingContext.js +23 -7
  169. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  170. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  171. package/dist/tracking/TrackingContext.js.map +1 -1
  172. package/dist/types/compatibility-types.js.map +1 -1
  173. package/dist/types/formatting.js.map +1 -1
  174. package/dist/types/list-types.d.ts +6 -6
  175. package/dist/types/list-types.js.map +1 -1
  176. package/dist/types/settings-types.js.map +1 -1
  177. package/dist/types/styleConfig.d.ts +2 -2
  178. package/dist/types/styleConfig.js.map +1 -1
  179. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  180. package/dist/utils/ChangelogGenerator.js +97 -101
  181. package/dist/utils/ChangelogGenerator.js.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js +1 -1
  184. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  186. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  187. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  188. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  189. package/dist/utils/MoveOperationHelper.js +1 -1
  190. package/dist/utils/MoveOperationHelper.js.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  192. package/dist/utils/RevisionAwareProcessor.js +2 -4
  193. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  194. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  195. package/dist/utils/RevisionWalker.js +4 -12
  196. package/dist/utils/RevisionWalker.js.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  198. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  199. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  200. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  201. package/dist/utils/ShadingResolver.js +1 -1
  202. package/dist/utils/ShadingResolver.js.map +1 -1
  203. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  204. package/dist/utils/acceptRevisions.js +23 -12
  205. package/dist/utils/acceptRevisions.js.map +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  207. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  208. package/dist/utils/cnfStyleDecoder.js +40 -40
  209. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  210. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  211. package/dist/utils/corruptionDetection.js.map +1 -1
  212. package/dist/utils/dateFormatting.js.map +1 -1
  213. package/dist/utils/deepClone.js +1 -1
  214. package/dist/utils/deepClone.js.map +1 -1
  215. package/dist/utils/diagnostics.d.ts.map +1 -1
  216. package/dist/utils/diagnostics.js +1 -1
  217. package/dist/utils/diagnostics.js.map +1 -1
  218. package/dist/utils/errorHandling.js.map +1 -1
  219. package/dist/utils/formatting.d.ts.map +1 -1
  220. package/dist/utils/formatting.js +10 -2
  221. package/dist/utils/formatting.js.map +1 -1
  222. package/dist/utils/list-detection.d.ts +2 -2
  223. package/dist/utils/list-detection.d.ts.map +1 -1
  224. package/dist/utils/list-detection.js +21 -23
  225. package/dist/utils/list-detection.js.map +1 -1
  226. package/dist/utils/logger.d.ts.map +1 -1
  227. package/dist/utils/logger.js +12 -7
  228. package/dist/utils/logger.js.map +1 -1
  229. package/dist/utils/parsingHelpers.js.map +1 -1
  230. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  231. package/dist/utils/stripTrackedChanges.js +3 -3
  232. package/dist/utils/stripTrackedChanges.js.map +1 -1
  233. package/dist/utils/textDiff.d.ts +1 -1
  234. package/dist/utils/textDiff.js +8 -8
  235. package/dist/utils/textDiff.js.map +1 -1
  236. package/dist/utils/units.js.map +1 -1
  237. package/dist/utils/validation.d.ts.map +1 -1
  238. package/dist/utils/validation.js +24 -7
  239. package/dist/utils/validation.js.map +1 -1
  240. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  241. package/dist/utils/xmlSanitization.js +3 -3
  242. package/dist/utils/xmlSanitization.js.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  244. package/dist/validation/RevisionAutoFixer.js +5 -5
  245. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  246. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  247. package/dist/validation/RevisionValidator.js +7 -9
  248. package/dist/validation/RevisionValidator.js.map +1 -1
  249. package/dist/validation/ValidationRules.js +3 -3
  250. package/dist/validation/ValidationRules.js.map +1 -1
  251. package/dist/validation/index.js.map +1 -1
  252. package/dist/xml/XMLBuilder.d.ts +1 -1
  253. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  254. package/dist/xml/XMLBuilder.js +98 -100
  255. package/dist/xml/XMLBuilder.js.map +1 -1
  256. package/dist/xml/XMLParser.d.ts.map +1 -1
  257. package/dist/xml/XMLParser.js +61 -66
  258. package/dist/xml/XMLParser.js.map +1 -1
  259. package/dist/zip/ZipHandler.d.ts.map +1 -1
  260. package/dist/zip/ZipHandler.js.map +1 -1
  261. package/dist/zip/ZipReader.d.ts.map +1 -1
  262. package/dist/zip/ZipReader.js +1 -3
  263. package/dist/zip/ZipReader.js.map +1 -1
  264. package/dist/zip/ZipWriter.d.ts +1 -1
  265. package/dist/zip/ZipWriter.d.ts.map +1 -1
  266. package/dist/zip/ZipWriter.js +28 -36
  267. package/dist/zip/ZipWriter.js.map +1 -1
  268. package/dist/zip/types.js +1 -1
  269. package/dist/zip/types.js.map +1 -1
  270. package/package.json +92 -92
  271. package/src/__tests__/helper-methods.test.ts +512 -512
  272. package/src/constants/legacyCompatFlags.ts +138 -138
  273. package/src/constants/limits.ts +50 -50
  274. package/src/core/Document.ts +1010 -1145
  275. package/src/core/DocumentContent.ts +461 -467
  276. package/src/core/DocumentGenerator.ts +1133 -1104
  277. package/src/core/DocumentIdManager.ts +158 -158
  278. package/src/core/DocumentParser.ts +2347 -2716
  279. package/src/core/DocumentValidator.ts +363 -372
  280. package/src/core/Relationship.ts +367 -367
  281. package/src/core/RelationshipManager.ts +429 -428
  282. package/src/elements/AlternateContent.ts +42 -42
  283. package/src/elements/Bookmark.ts +212 -210
  284. package/src/elements/BookmarkManager.ts +247 -250
  285. package/src/elements/Comment.ts +356 -359
  286. package/src/elements/CommentManager.ts +499 -502
  287. package/src/elements/CommonTypes.ts +524 -549
  288. package/src/elements/CustomXml.ts +36 -36
  289. package/src/elements/Endnote.ts +221 -217
  290. package/src/elements/EndnoteManager.ts +246 -249
  291. package/src/elements/Field.ts +1292 -1233
  292. package/src/elements/FieldHelpers.ts +329 -333
  293. package/src/elements/FontManager.ts +336 -339
  294. package/src/elements/Footer.ts +269 -269
  295. package/src/elements/Footnote.ts +221 -217
  296. package/src/elements/FootnoteManager.ts +246 -249
  297. package/src/elements/Header.ts +269 -269
  298. package/src/elements/HeaderFooterManager.ts +219 -219
  299. package/src/elements/Hyperlink.ts +1288 -1193
  300. package/src/elements/Image.ts +1982 -1756
  301. package/src/elements/ImageManager.ts +437 -432
  302. package/src/elements/ImageRun.ts +59 -59
  303. package/src/elements/MathElement.ts +65 -65
  304. package/src/elements/Paragraph.ts +4347 -4287
  305. package/src/elements/PreservedElement.ts +53 -53
  306. package/src/elements/PropertyChangeTypes.ts +458 -442
  307. package/src/elements/RangeMarker.ts +382 -400
  308. package/src/elements/Revision.ts +1198 -1217
  309. package/src/elements/RevisionContent.ts +73 -73
  310. package/src/elements/RevisionManager.ts +1070 -1070
  311. package/src/elements/Run.ts +3103 -3073
  312. package/src/elements/Section.ts +1521 -1421
  313. package/src/elements/Shape.ts +884 -873
  314. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  315. package/src/elements/Table.ts +2468 -2524
  316. package/src/elements/TableCell.ts +1617 -1621
  317. package/src/elements/TableGridChange.ts +149 -151
  318. package/src/elements/TableOfContents.ts +701 -691
  319. package/src/elements/TableOfContentsElement.ts +89 -89
  320. package/src/elements/TableRow.ts +960 -929
  321. package/src/elements/TextBox.ts +766 -768
  322. package/src/formatting/AbstractNumbering.ts +580 -579
  323. package/src/formatting/NumberingInstance.ts +295 -299
  324. package/src/formatting/NumberingLevel.ts +981 -1040
  325. package/src/formatting/NumberingManager.ts +875 -827
  326. package/src/formatting/Style.ts +1785 -1879
  327. package/src/formatting/StylesManager.ts +1090 -1130
  328. package/src/helpers/CleanupHelper.ts +524 -524
  329. package/src/images/ImageOptimizer.ts +274 -274
  330. package/src/index.ts +559 -554
  331. package/src/managers/DrawingManager.ts +319 -319
  332. package/src/tracking/DocumentTrackingContext.ts +687 -674
  333. package/src/tracking/TrackingContext.ts +175 -173
  334. package/src/types/compatibility-types.ts +49 -49
  335. package/src/types/formatting.ts +210 -210
  336. package/src/types/list-types.ts +14 -14
  337. package/src/types/settings-types.ts +59 -59
  338. package/src/types/styleConfig.ts +189 -189
  339. package/src/utils/ChangelogGenerator.ts +1583 -1581
  340. package/src/utils/CompatibilityUpgrader.ts +235 -237
  341. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  342. package/src/utils/MoveOperationHelper.ts +233 -238
  343. package/src/utils/RevisionAwareProcessor.ts +518 -526
  344. package/src/utils/RevisionWalker.ts +427 -457
  345. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  346. package/src/utils/ShadingResolver.ts +105 -107
  347. package/src/utils/acceptRevisions.ts +723 -714
  348. package/src/utils/cnfStyleDecoder.ts +212 -217
  349. package/src/utils/corruptionDetection.ts +346 -345
  350. package/src/utils/dateFormatting.ts +20 -20
  351. package/src/utils/deepClone.ts +77 -78
  352. package/src/utils/diagnostics.ts +125 -129
  353. package/src/utils/errorHandling.ts +80 -80
  354. package/src/utils/formatting.ts +220 -213
  355. package/src/utils/list-detection.ts +32 -42
  356. package/src/utils/logger.ts +412 -404
  357. package/src/utils/parsingHelpers.ts +190 -190
  358. package/src/utils/stripTrackedChanges.ts +356 -353
  359. package/src/utils/textDiff.ts +100 -100
  360. package/src/utils/units.ts +421 -421
  361. package/src/utils/validation.ts +553 -542
  362. package/src/utils/xmlSanitization.ts +179 -182
  363. package/src/validation/RevisionAutoFixer.ts +541 -542
  364. package/src/validation/RevisionValidator.ts +470 -460
  365. package/src/validation/ValidationRules.ts +338 -338
  366. package/src/validation/index.ts +30 -30
  367. package/src/xml/XMLBuilder.ts +857 -871
  368. package/src/xml/XMLParser.ts +877 -919
  369. package/src/zip/ZipHandler.ts +629 -637
  370. package/src/zip/ZipReader.ts +295 -299
  371. package/src/zip/ZipWriter.ts +374 -390
  372. package/src/zip/types.ts +116 -116
@@ -1,827 +1,875 @@
1
- /**
2
- * NumberingManager - Manages numbering definitions and generates numbering.xml
3
- *
4
- * The NumberingManager is responsible for managing all abstract numbering definitions
5
- * and numbering instances in a document, and generating the numbering.xml file.
6
- */
7
-
8
- import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
9
- import { AbstractNumbering } from './AbstractNumbering';
10
- import { NumberingInstance } from './NumberingInstance';
11
- import { NumberingLevel } from './NumberingLevel';
12
- import { defaultLogger } from '../utils/logger';
13
-
14
- /**
15
- * Options for numbering consolidation
16
- */
17
- export interface NumberingConsolidationOptions {
18
- /** AbstractNumIds to exclude from consolidation (e.g., HLP/row-number lists) */
19
- protectedAbstractNumIds?: Set<number>;
20
- }
21
-
22
- /**
23
- * Result of numbering consolidation
24
- */
25
- export interface NumberingConsolidationResult {
26
- abstractNumsRemoved: number;
27
- instancesRemapped: number;
28
- groupsConsolidated: number;
29
- }
30
-
31
- /**
32
- * Manages numbering definitions and instances for a document
33
- */
34
- export class NumberingManager {
35
- private abstractNumberings: Map<number, AbstractNumbering>;
36
- private instances: Map<number, NumberingInstance>;
37
- private nextAbstractNumId: number;
38
- private nextNumId: number;
39
-
40
- // Track if numbering has been modified (for XML preservation)
41
- private _modified = false;
42
-
43
- // Track which specific definitions have been modified (for selective merging)
44
- private _modifiedAbstractNumIds = new Set<number>();
45
- private _modifiedNumIds = new Set<number>();
46
-
47
- // Track which definitions have been removed (for removal from original XML during merge)
48
- private _removedAbstractNumIds = new Set<number>();
49
- private _removedNumIds = new Set<number>();
50
-
51
- /**
52
- * Creates a new numbering manager
53
- * @param initializeDefaults Whether to initialize with default numbering definitions
54
- */
55
- constructor(initializeDefaults = false) {
56
- this.abstractNumberings = new Map();
57
- this.instances = new Map();
58
- this.nextAbstractNumId = 0;
59
- this.nextNumId = 1;
60
-
61
- if (initializeDefaults) {
62
- this.initializeDefaultNumberings();
63
- }
64
- }
65
-
66
- /**
67
- * Initializes default numbering definitions (bullet and numbered lists)
68
- */
69
- private initializeDefaultNumberings(): void {
70
- // Create default bullet list
71
- const bulletAbstract = AbstractNumbering.createBulletList(this.nextAbstractNumId++);
72
- this.addAbstractNumbering(bulletAbstract);
73
-
74
- // Create default numbered list
75
- const numberedAbstract = AbstractNumbering.createNumberedList(this.nextAbstractNumId++);
76
- this.addAbstractNumbering(numberedAbstract);
77
- }
78
-
79
- /**
80
- * Adds an abstract numbering definition
81
- * @param abstractNumbering The abstract numbering to add
82
- */
83
- addAbstractNumbering(abstractNumbering: AbstractNumbering): this {
84
- const id = abstractNumbering.getAbstractNumId();
85
- this.abstractNumberings.set(id, abstractNumbering);
86
-
87
- // Update next ID if necessary
88
- if (id >= this.nextAbstractNumId) {
89
- this.nextAbstractNumId = id + 1;
90
- }
91
-
92
- this._modified = true;
93
- this._modifiedAbstractNumIds.add(id);
94
- return this;
95
- }
96
-
97
- /**
98
- * Gets an abstract numbering by ID
99
- * @param abstractNumId The abstract numbering ID
100
- */
101
- getAbstractNumbering(abstractNumId: number): AbstractNumbering | undefined {
102
- return this.abstractNumberings.get(abstractNumId);
103
- }
104
-
105
- /**
106
- * Gets all abstract numberings
107
- */
108
- getAllAbstractNumberings(): AbstractNumbering[] {
109
- return Array.from(this.abstractNumberings.values()).sort(
110
- (a, b) => a.getAbstractNumId() - b.getAbstractNumId()
111
- );
112
- }
113
-
114
- /**
115
- * Checks if an abstract numbering exists
116
- * @param abstractNumId The abstract numbering ID
117
- */
118
- hasAbstractNumbering(abstractNumId: number): boolean {
119
- return this.abstractNumberings.has(abstractNumId);
120
- }
121
-
122
- /**
123
- * Removes an abstract numbering
124
- * @param abstractNumId The abstract numbering ID
125
- */
126
- removeAbstractNumbering(abstractNumId: number): boolean {
127
- // Also remove all instances referencing this abstract numbering
128
- const instancesToRemove: number[] = [];
129
- this.instances.forEach((instance, numId) => {
130
- if (instance.getAbstractNumId() === abstractNumId) {
131
- instancesToRemove.push(numId);
132
- }
133
- });
134
-
135
- instancesToRemove.forEach(numId => {
136
- this.instances.delete(numId);
137
- this._removedNumIds.add(numId);
138
- });
139
-
140
- const deleted = this.abstractNumberings.delete(abstractNumId);
141
- if (deleted) {
142
- this._modified = true;
143
- this._removedAbstractNumIds.add(abstractNumId);
144
- }
145
- return deleted;
146
- }
147
-
148
- /**
149
- * Adds a numbering instance
150
- * @param instance The numbering instance to add
151
- */
152
- addInstance(instance: NumberingInstance): this {
153
- const numId = instance.getNumId();
154
- const abstractNumId = instance.getAbstractNumId();
155
-
156
- // Verify that the abstract numbering exists
157
- if (!this.hasAbstractNumbering(abstractNumId)) {
158
- throw new Error(`Abstract numbering ${abstractNumId} does not exist`);
159
- }
160
-
161
- this.instances.set(numId, instance);
162
- this._modified = true;
163
- this._modifiedNumIds.add(numId);
164
-
165
- // Update next ID if necessary
166
- if (numId >= this.nextNumId) {
167
- this.nextNumId = numId + 1;
168
- }
169
-
170
- return this;
171
- }
172
-
173
- /**
174
- * Alias for addInstance for backward compatibility
175
- * @param instance The numbering instance to add
176
- */
177
- addNumberingInstance(instance: NumberingInstance): this {
178
- return this.addInstance(instance);
179
- }
180
-
181
- /**
182
- * Gets a numbering instance by ID
183
- * @param numId The numbering instance ID
184
- */
185
- getInstance(numId: number): NumberingInstance | undefined {
186
- return this.instances.get(numId);
187
- }
188
-
189
- /**
190
- * Alias for getInstance for backward compatibility
191
- * @param numId The numbering instance ID
192
- */
193
- getNumberingInstance(numId: number): NumberingInstance | undefined {
194
- return this.getInstance(numId);
195
- }
196
-
197
- /**
198
- * Gets all numbering instances
199
- */
200
- getAllInstances(): NumberingInstance[] {
201
- return Array.from(this.instances.values()).sort(
202
- (a, b) => a.getNumId() - b.getNumId()
203
- );
204
- }
205
-
206
- /**
207
- * Checks if numbering has been modified since loading
208
- * Used for XML preservation optimization
209
- * @returns True if numbering was added or modified
210
- */
211
- isModified(): boolean {
212
- return this._modified;
213
- }
214
-
215
- /**
216
- * Marks an abstract numbering as modified for XML regeneration.
217
- * Use when modifying NumberingLevel properties directly (setLeftIndent, etc.)
218
- * which don't automatically trigger the modified flag.
219
- * @param abstractNumId The abstract numbering ID to mark as modified
220
- */
221
- markAbstractNumberingModified(abstractNumId: number): void {
222
- if (this.abstractNumberings.has(abstractNumId)) {
223
- this._modified = true;
224
- this._modifiedAbstractNumIds.add(abstractNumId);
225
- }
226
- }
227
-
228
- /**
229
- * Resets the modified flag
230
- * Called after parsing to indicate that loaded numbering doesn't count as modifications
231
- */
232
- resetModified(): void {
233
- this._modified = false;
234
- this._modifiedAbstractNumIds.clear();
235
- this._modifiedNumIds.clear();
236
- this._removedAbstractNumIds.clear();
237
- this._removedNumIds.clear();
238
- }
239
-
240
- /**
241
- * Gets the IDs of abstract numberings that have been modified since loading
242
- * Used for selective merging with original numbering.xml
243
- */
244
- getModifiedAbstractNumIds(): Set<number> {
245
- return new Set(this._modifiedAbstractNumIds);
246
- }
247
-
248
- /**
249
- * Gets the IDs of numbering instances that have been modified since loading
250
- * Used for selective merging with original numbering.xml
251
- */
252
- getModifiedNumIds(): Set<number> {
253
- return new Set(this._modifiedNumIds);
254
- }
255
-
256
- /**
257
- * Gets the IDs of abstract numberings that have been removed since loading
258
- * Used for removal from original numbering.xml during merge
259
- */
260
- getRemovedAbstractNumIds(): Set<number> {
261
- return new Set(this._removedAbstractNumIds);
262
- }
263
-
264
- /**
265
- * Gets the IDs of numbering instances that have been removed since loading
266
- * Used for removal from original numbering.xml during merge
267
- */
268
- getRemovedNumIds(): Set<number> {
269
- return new Set(this._removedNumIds);
270
- }
271
-
272
- /**
273
- * Alias for getAllInstances for backward compatibility
274
- */
275
- getAllNumberingInstances(): NumberingInstance[] {
276
- return this.getAllInstances();
277
- }
278
-
279
- /**
280
- * Checks if a numbering instance exists
281
- * @param numId The numbering instance ID
282
- */
283
- hasInstance(numId: number): boolean {
284
- return this.instances.has(numId);
285
- }
286
-
287
- /**
288
- * Removes a numbering instance
289
- * @param numId The numbering instance ID
290
- */
291
- removeInstance(numId: number): boolean {
292
- const deleted = this.instances.delete(numId);
293
- if (deleted) {
294
- this._modified = true;
295
- this._removedNumIds.add(numId);
296
- }
297
- return deleted;
298
- }
299
-
300
- /**
301
- * Creates a new bullet list and returns its numId
302
- * @param levels Number of levels (default: 9)
303
- * @param bullets Array of bullet characters
304
- */
305
- createBulletList(levels = 9, bullets?: string[]): number {
306
- // Create abstract numbering
307
- const abstractNumId = this.nextAbstractNumId++;
308
- // Only pass bullets if it's defined, so defaults are used otherwise
309
- const abstractNumbering = bullets
310
- ? AbstractNumbering.createBulletList(abstractNumId, levels, bullets)
311
- : AbstractNumbering.createBulletList(abstractNumId, levels);
312
- this.addAbstractNumbering(abstractNumbering);
313
-
314
- // Create instance
315
- const numId = this.nextNumId++;
316
- const instance = NumberingInstance.create({ numId, abstractNumId });
317
- this.addInstance(instance);
318
-
319
- return numId;
320
- }
321
-
322
- /**
323
- * Creates a new numbered list and returns its numId
324
- * @param levels Number of levels (default: 9)
325
- * @param formats Array of formats for each level
326
- */
327
- createNumberedList(
328
- levels = 9,
329
- formats?: ('decimal' | 'lowerLetter' | 'lowerRoman')[]
330
- ): number {
331
- // Create abstract numbering
332
- const abstractNumId = this.nextAbstractNumId++;
333
- // Only pass formats if it's defined, so defaults are used otherwise
334
- const abstractNumbering = formats
335
- ? AbstractNumbering.createNumberedList(abstractNumId, levels, formats)
336
- : AbstractNumbering.createNumberedList(abstractNumId, levels);
337
- this.addAbstractNumbering(abstractNumbering);
338
-
339
- // Create instance
340
- const numId = this.nextNumId++;
341
- const instance = NumberingInstance.create({ numId, abstractNumId });
342
- this.addInstance(instance);
343
-
344
- return numId;
345
- }
346
-
347
- /**
348
- * Creates a new multi-level list and returns its numId
349
- */
350
- createMultiLevelList(): number {
351
- // Create abstract numbering
352
- const abstractNumId = this.nextAbstractNumId++;
353
- const abstractNumbering = AbstractNumbering.createMultiLevelList(abstractNumId);
354
- this.addAbstractNumbering(abstractNumbering);
355
-
356
- // Create instance
357
- const numId = this.nextNumId++;
358
- const instance = NumberingInstance.create({ numId, abstractNumId });
359
- this.addInstance(instance);
360
-
361
- return numId;
362
- }
363
-
364
- /**
365
- * Creates a new outline list and returns its numId
366
- */
367
- createOutlineList(): number {
368
- // Create abstract numbering
369
- const abstractNumId = this.nextAbstractNumId++;
370
- const abstractNumbering = AbstractNumbering.createOutlineList(abstractNumId);
371
- this.addAbstractNumbering(abstractNumbering);
372
-
373
- // Create instance
374
- const numId = this.nextNumId++;
375
- const instance = NumberingInstance.create({ numId, abstractNumId });
376
- this.addInstance(instance);
377
-
378
- return numId;
379
- }
380
-
381
- /**
382
- * Creates a custom list with specified levels and returns its numId
383
- * @param levels Array of numbering levels
384
- * @param name Optional name for the list
385
- */
386
- createCustomList(levels: NumberingLevel[], name?: string): number {
387
- // Create abstract numbering
388
- const abstractNumId = this.nextAbstractNumId++;
389
- const abstractNumbering = AbstractNumbering.create({
390
- abstractNumId,
391
- name,
392
- levels,
393
- });
394
- this.addAbstractNumbering(abstractNumbering);
395
-
396
- // Create instance
397
- const numId = this.nextNumId++;
398
- const instance = NumberingInstance.create({ numId, abstractNumId });
399
- this.addInstance(instance);
400
-
401
- return numId;
402
- }
403
-
404
- /**
405
- * Creates a new instance of an existing abstract numbering
406
- * @param abstractNumId The abstract numbering ID to create an instance of
407
- */
408
- createInstance(abstractNumId: number): number {
409
- if (!this.hasAbstractNumbering(abstractNumId)) {
410
- throw new Error(`Abstract numbering ${abstractNumId} does not exist`);
411
- }
412
-
413
- const numId = this.nextNumId++;
414
- const instance = NumberingInstance.create({ numId, abstractNumId });
415
- this.addInstance(instance);
416
-
417
- return numId;
418
- }
419
-
420
- /**
421
- * Gets the framework's standard indentation for a list level
422
- *
423
- * The framework uses a consistent indentation scheme:
424
- * - leftIndent: 720 + (level * 360) twips
425
- * - hangingIndent: 360 twips
426
- *
427
- * Examples:
428
- * - Level 0: 720 twips (0.5 inch) left, 360 twips hanging
429
- * - Level 1: 1080 twips (0.75 inch) left, 360 twips hanging
430
- * - Level 2: 1440 twips (1.0 inch) left, 360 twips hanging
431
- *
432
- * @param level The level (0-8)
433
- * @returns Object with leftIndent and hangingIndent in twips
434
- * @example
435
- * ```typescript
436
- * const indent = manager.getStandardIndentation(0);
437
- * // Returns: { leftIndent: 720, hangingIndent: 360 }
438
- * ```
439
- */
440
- getStandardIndentation(level: number): { leftIndent: number; hangingIndent: number } {
441
- if (level < 0 || level > 8) {
442
- throw new Error(`Invalid level ${level}. Level must be between 0 and 8.`);
443
- }
444
-
445
- return {
446
- leftIndent: 720 + (level * 360),
447
- hangingIndent: 360,
448
- };
449
- }
450
-
451
- /**
452
- * Sets custom indentation for a specific level in a numbering definition
453
- *
454
- * This updates the indentation for a specific level across ALL paragraphs
455
- * that use this numId and level combination.
456
- *
457
- * @param numId The numbering instance ID
458
- * @param level The level to modify (0-8)
459
- * @param leftIndent Left indentation in twips
460
- * @param hangingIndent Hanging indentation in twips (optional, defaults to 360)
461
- * @returns true if successful, false if numId doesn't exist
462
- * @example
463
- * ```typescript
464
- * // Set level 0 to 0.5 inch left, 0.25 inch hanging
465
- * manager.setListIndentation(1, 0, 720, 360);
466
- * ```
467
- */
468
- setListIndentation(
469
- numId: number,
470
- level: number,
471
- leftIndent: number,
472
- hangingIndent = 360
473
- ): boolean {
474
- // Validate level
475
- if (level < 0 || level > 8) {
476
- throw new Error(`Invalid level ${level}. Level must be between 0 and 8.`);
477
- }
478
-
479
- // Validate indents (clamp negatives to 0)
480
- leftIndent = Math.max(0, leftIndent);
481
- hangingIndent = Math.max(0, hangingIndent);
482
-
483
- // Get the numbering instance
484
- const instance = this.getInstance(numId);
485
- if (!instance) {
486
- defaultLogger.warn(`Numbering instance ${numId} does not exist`);
487
- return false;
488
- }
489
-
490
- // Get the abstract numbering
491
- const abstractNum = this.getAbstractNumbering(instance.getAbstractNumId());
492
- if (!abstractNum) {
493
- defaultLogger.warn(`Abstract numbering for instance ${numId} does not exist`);
494
- return false;
495
- }
496
-
497
- // Get the level from the abstract numbering
498
- const numLevel = abstractNum.getLevel(level);
499
- if (!numLevel) {
500
- defaultLogger.warn(`Level ${level} does not exist in abstract numbering`);
501
- return false;
502
- }
503
-
504
- // Update the level's indentation
505
- numLevel.setLeftIndent(leftIndent);
506
- numLevel.setHangingIndent(hangingIndent);
507
-
508
- return true;
509
- }
510
-
511
- /**
512
- * Resets all levels in a numbering definition to standard indentation
513
- *
514
- * This applies the framework's standard indentation formula to all levels:
515
- * - leftIndent: 720 + (level * 360) twips
516
- * - hangingIndent: 360 twips
517
- *
518
- * @param numId The numbering instance ID
519
- * @returns true if successful, false if numId doesn't exist
520
- * @example
521
- * ```typescript
522
- * // Reset list 1 to standard indentation
523
- * manager.normalizeListIndentation(1);
524
- * ```
525
- */
526
- normalizeListIndentation(numId: number): boolean {
527
- // Get the numbering instance
528
- const instance = this.getInstance(numId);
529
- if (!instance) {
530
- defaultLogger.warn(`Numbering instance ${numId} does not exist`);
531
- return false;
532
- }
533
-
534
- // Get the abstract numbering
535
- const abstractNum = this.getAbstractNumbering(instance.getAbstractNumId());
536
- if (!abstractNum) {
537
- defaultLogger.warn(`Abstract numbering for instance ${numId} does not exist`);
538
- return false;
539
- }
540
-
541
- // Get all levels
542
- const levels = abstractNum.getAllLevels();
543
-
544
- // Apply standard indentation to each level
545
- for (const level of levels) {
546
- const standardIndent = this.getStandardIndentation(level.getLevel());
547
- level.setLeftIndent(standardIndent.leftIndent);
548
- level.setHangingIndent(standardIndent.hangingIndent);
549
- }
550
-
551
- return true;
552
- }
553
-
554
- /**
555
- * Normalizes indentation for all lists in the document
556
- *
557
- * Applies standard indentation to every numbering instance:
558
- * - leftIndent: 720 + (level * 360) twips
559
- * - hangingIndent: 360 twips
560
- *
561
- * This ensures consistent spacing across all lists in the document.
562
- *
563
- * @returns Number of numbering instances updated
564
- * @example
565
- * ```typescript
566
- * const count = manager.normalizeAllListIndentation();
567
- * console.log(`Normalized ${count} lists`);
568
- * ```
569
- */
570
- normalizeAllListIndentation(): number {
571
- let count = 0;
572
-
573
- // Iterate over all instances
574
- for (const instance of this.getAllInstances()) {
575
- const success = this.normalizeListIndentation(instance.getNumId());
576
- if (success) {
577
- count++;
578
- }
579
- }
580
-
581
- return count;
582
- }
583
-
584
- /**
585
- * Gets the total number of abstract numberings
586
- */
587
- getAbstractNumberingCount(): number {
588
- return this.abstractNumberings.size;
589
- }
590
-
591
- /**
592
- * Gets the total number of numbering instances
593
- */
594
- getInstanceCount(): number {
595
- return this.instances.size;
596
- }
597
-
598
- /**
599
- * Clears all numbering definitions and instances
600
- */
601
- clear(): this {
602
- this.abstractNumberings.clear();
603
- this.instances.clear();
604
- this.nextAbstractNumId = 0;
605
- this.nextNumId = 1;
606
- return this;
607
- }
608
-
609
- /**
610
- * Removes unused numbering instances and abstract numberings
611
- *
612
- * This method cleans up numbering definitions that are no longer referenced
613
- * by any paragraphs in the document. It removes:
614
- * 1. Instances not in the usedNumIds set
615
- * 2. Abstract numberings not referenced by any remaining instance
616
- *
617
- * @param usedNumIds Set of numIds currently used by paragraphs
618
- * @returns Object with counts of removed instances and abstract numberings
619
- */
620
- cleanupUnusedNumbering(usedNumIds: Set<number>): { instancesRemoved: number; abstractsRemoved: number } {
621
- let instancesRemoved = 0;
622
- let abstractsRemoved = 0;
623
-
624
- // Step 1: Remove unused instances
625
- const instancesToRemove: number[] = [];
626
- this.instances.forEach((_instance, numId) => {
627
- if (!usedNumIds.has(numId)) {
628
- instancesToRemove.push(numId);
629
- }
630
- });
631
-
632
- for (const numId of instancesToRemove) {
633
- this.instances.delete(numId);
634
- this._modified = true;
635
- this._removedNumIds.add(numId);
636
- instancesRemoved++;
637
- }
638
-
639
- // Step 2: Find abstract numberings still referenced by remaining instances
640
- const referencedAbstractNumIds = new Set<number>();
641
- this.instances.forEach(instance => {
642
- referencedAbstractNumIds.add(instance.getAbstractNumId());
643
- });
644
-
645
- // Step 3: Remove unreferenced abstract numberings
646
- const abstractsToRemove: number[] = [];
647
- this.abstractNumberings.forEach((_abstractNum, abstractNumId) => {
648
- if (!referencedAbstractNumIds.has(abstractNumId)) {
649
- abstractsToRemove.push(abstractNumId);
650
- }
651
- });
652
-
653
- for (const abstractNumId of abstractsToRemove) {
654
- this.abstractNumberings.delete(abstractNumId);
655
- this._modified = true;
656
- this._removedAbstractNumIds.add(abstractNumId);
657
- abstractsRemoved++;
658
- }
659
-
660
- return { instancesRemoved, abstractsRemoved };
661
- }
662
-
663
- /**
664
- * Consolidates duplicate abstract numbering definitions
665
- *
666
- * Groups abstractNums by a deterministic fingerprint of their level properties
667
- * (format, text, font, fontSize, color, indentation, alignment, etc.).
668
- * For each group with >1 member, picks the lowest abstractNumId as canonical,
669
- * remaps all instances pointing to non-canonical IDs, and removes duplicates.
670
- *
671
- * This is safe because multiple num instances can reference the same abstractNum —
672
- * each instance maintains its own independent counter via level overrides.
673
- *
674
- * @param options Optional configuration (e.g., protected IDs to skip)
675
- * @returns Summary of what was consolidated
676
- */
677
- consolidateNumbering(options?: NumberingConsolidationOptions): NumberingConsolidationResult {
678
- const protectedIds = options?.protectedAbstractNumIds ?? new Set<number>();
679
-
680
- // 1. Compute fingerprint for each non-protected abstractNum
681
- const fingerprintGroups = new Map<string, number[]>();
682
- for (const abstractNum of this.abstractNumberings.values()) {
683
- const id = abstractNum.getAbstractNumId();
684
- if (protectedIds.has(id)) continue;
685
-
686
- const fingerprint = this._fingerprintAbstractNum(abstractNum);
687
- const group = fingerprintGroups.get(fingerprint);
688
- if (group) {
689
- group.push(id);
690
- } else {
691
- fingerprintGroups.set(fingerprint, [id]);
692
- }
693
- }
694
-
695
- // 2. For each group with >1 member, consolidate
696
- let abstractNumsRemoved = 0;
697
- let instancesRemapped = 0;
698
- let groupsConsolidated = 0;
699
-
700
- for (const [, ids] of fingerprintGroups) {
701
- if (ids.length <= 1) continue;
702
-
703
- // Sort to pick lowest ID as canonical
704
- ids.sort((a, b) => a - b);
705
- const canonicalId = ids[0]!;
706
- const duplicateIds = new Set(ids.slice(1));
707
-
708
- groupsConsolidated++;
709
-
710
- // Remap instances pointing to duplicate abstractNums
711
- for (const instance of this.instances.values()) {
712
- if (duplicateIds.has(instance.getAbstractNumId())) {
713
- instance.setAbstractNumId(canonicalId);
714
- this._modifiedNumIds.add(instance.getNumId());
715
- instancesRemapped++;
716
- }
717
- }
718
-
719
- // Remove duplicate abstractNums
720
- for (const dupId of duplicateIds) {
721
- this.abstractNumberings.delete(dupId);
722
- this._removedAbstractNumIds.add(dupId);
723
- abstractNumsRemoved++;
724
- }
725
- }
726
-
727
- if (abstractNumsRemoved > 0) {
728
- this._modified = true;
729
- }
730
-
731
- return { abstractNumsRemoved, instancesRemapped, groupsConsolidated };
732
- }
733
-
734
- /**
735
- * Computes a deterministic fingerprint for an abstract numbering definition
736
- * based on its multiLevelType and all level properties. Name and abstractNumId
737
- * are excluded since they don't affect rendering.
738
- */
739
- private _fingerprintAbstractNum(abstractNum: AbstractNumbering): string {
740
- const parts: string[] = [
741
- abstractNum.getNumStyleLink() ?? '',
742
- abstractNum.getStyleLink() ?? '',
743
- abstractNum.getMultiLevelType(),
744
- ];
745
-
746
- for (const level of abstractNum.getAllLevels()) {
747
- const props = level.getProperties();
748
- parts.push(
749
- `${props.level}|${props.format}|${props.text}|${props.font}|${props.fontSize}|` +
750
- `${props.color}|${props.leftIndent}|${props.hangingIndent}|${props.alignment}|` +
751
- `${props.start}|${props.bold}|${props.italic}|${props.underline ?? ''}|` +
752
- `${props.suffix}|${props.isLegalNumberingStyle}|${props.lvlRestart ?? ''}`
753
- );
754
- }
755
-
756
- return parts.join('::');
757
- }
758
-
759
- /**
760
- * Generates the complete numbering.xml content
761
- */
762
- generateNumberingXml(): string {
763
- const builder = new XMLBuilder();
764
-
765
- const children: XMLElement[] = [];
766
-
767
- // Add all abstract numberings
768
- const abstractNumberings = this.getAllAbstractNumberings();
769
- abstractNumberings.forEach(abstractNum => {
770
- children.push(abstractNum.toXML());
771
- });
772
-
773
- // Add all numbering instances
774
- const instances = this.getAllInstances();
775
- instances.forEach(instance => {
776
- children.push(instance.toXML());
777
- });
778
-
779
- const numbering = XMLBuilder.w('numbering', {
780
- 'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
781
- 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
782
- }, children);
783
-
784
- builder.element(numbering.name, numbering.attributes, numbering.children);
785
-
786
- // Generate XML with declaration
787
- return builder.build(true);
788
- }
789
-
790
- /**
791
- * Generates the numbering.xml as XMLElement (for API compatibility)
792
- */
793
- toXML(): XMLElement {
794
- const children: XMLElement[] = [];
795
-
796
- // Add all abstract numberings
797
- const abstractNumberings = this.getAllAbstractNumberings();
798
- abstractNumberings.forEach(abstractNum => {
799
- children.push(abstractNum.toXML());
800
- });
801
-
802
- // Add all numbering instances
803
- const instances = this.getAllInstances();
804
- instances.forEach(instance => {
805
- children.push(instance.toXML());
806
- });
807
-
808
- return XMLBuilder.w('numbering', {
809
- 'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
810
- 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
811
- }, children);
812
- }
813
-
814
- /**
815
- * Creates a numbering manager with default numbering definitions
816
- */
817
- static create(): NumberingManager {
818
- return new NumberingManager(false);
819
- }
820
-
821
- /**
822
- * Creates an empty numbering manager
823
- */
824
- static createEmpty(): NumberingManager {
825
- return new NumberingManager(false);
826
- }
827
- }
1
+ /**
2
+ * NumberingManager - Manages numbering definitions and generates numbering.xml
3
+ *
4
+ * The NumberingManager is responsible for managing all abstract numbering definitions
5
+ * and numbering instances in a document, and generating the numbering.xml file.
6
+ */
7
+
8
+ import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
9
+ import { AbstractNumbering } from './AbstractNumbering';
10
+ import { NumberingInstance } from './NumberingInstance';
11
+ import { NumberingLevel } from './NumberingLevel';
12
+ import { defaultLogger } from '../utils/logger';
13
+
14
+ /**
15
+ * Options for numbering consolidation
16
+ */
17
+ export interface NumberingConsolidationOptions {
18
+ /** AbstractNumIds to exclude from consolidation (e.g., HLP/row-number lists) */
19
+ protectedAbstractNumIds?: Set<number>;
20
+ }
21
+
22
+ /**
23
+ * Result of numbering consolidation
24
+ */
25
+ export interface NumberingConsolidationResult {
26
+ abstractNumsRemoved: number;
27
+ instancesRemapped: number;
28
+ groupsConsolidated: number;
29
+ }
30
+
31
+ /**
32
+ * Manages numbering definitions and instances for a document
33
+ */
34
+ export class NumberingManager {
35
+ private abstractNumberings: Map<number, AbstractNumbering>;
36
+ private instances: Map<number, NumberingInstance>;
37
+ private nextAbstractNumId: number;
38
+ private nextNumId: number;
39
+
40
+ // Track if numbering has been modified (for XML preservation)
41
+ private _modified = false;
42
+
43
+ // Track which specific definitions have been modified (for selective merging)
44
+ private _modifiedAbstractNumIds = new Set<number>();
45
+ private _modifiedNumIds = new Set<number>();
46
+
47
+ // Track which definitions have been removed (for removal from original XML during merge)
48
+ private _removedAbstractNumIds = new Set<number>();
49
+ private _removedNumIds = new Set<number>();
50
+
51
+ /**
52
+ * Creates a new numbering manager
53
+ * @param initializeDefaults Whether to initialize with default numbering definitions
54
+ */
55
+ constructor(initializeDefaults = false) {
56
+ this.abstractNumberings = new Map();
57
+ this.instances = new Map();
58
+ this.nextAbstractNumId = 0;
59
+ this.nextNumId = 1;
60
+
61
+ if (initializeDefaults) {
62
+ this.initializeDefaultNumberings();
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Initializes default numbering definitions (bullet and numbered lists)
68
+ */
69
+ private initializeDefaultNumberings(): void {
70
+ // Create default bullet list
71
+ const bulletAbstract = AbstractNumbering.createBulletList(this.nextAbstractNumId++);
72
+ this.addAbstractNumbering(bulletAbstract);
73
+
74
+ // Create default numbered list
75
+ const numberedAbstract = AbstractNumbering.createNumberedList(this.nextAbstractNumId++);
76
+ this.addAbstractNumbering(numberedAbstract);
77
+ }
78
+
79
+ /**
80
+ * Adds an abstract numbering definition
81
+ * @param abstractNumbering The abstract numbering to add
82
+ */
83
+ addAbstractNumbering(abstractNumbering: AbstractNumbering): this {
84
+ const id = abstractNumbering.getAbstractNumId();
85
+ this.abstractNumberings.set(id, abstractNumbering);
86
+
87
+ // Update next ID if necessary
88
+ if (id >= this.nextAbstractNumId) {
89
+ this.nextAbstractNumId = id + 1;
90
+ }
91
+
92
+ this._modified = true;
93
+ this._modifiedAbstractNumIds.add(id);
94
+ return this;
95
+ }
96
+
97
+ /**
98
+ * Gets an abstract numbering by ID
99
+ * @param abstractNumId The abstract numbering ID
100
+ */
101
+ getAbstractNumbering(abstractNumId: number): AbstractNumbering | undefined {
102
+ return this.abstractNumberings.get(abstractNumId);
103
+ }
104
+
105
+ /**
106
+ * Gets all abstract numberings
107
+ */
108
+ getAllAbstractNumberings(): AbstractNumbering[] {
109
+ return Array.from(this.abstractNumberings.values()).sort(
110
+ (a, b) => a.getAbstractNumId() - b.getAbstractNumId()
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Checks if an abstract numbering exists
116
+ * @param abstractNumId The abstract numbering ID
117
+ */
118
+ hasAbstractNumbering(abstractNumId: number): boolean {
119
+ return this.abstractNumberings.has(abstractNumId);
120
+ }
121
+
122
+ /**
123
+ * Removes an abstract numbering
124
+ * @param abstractNumId The abstract numbering ID
125
+ */
126
+ removeAbstractNumbering(abstractNumId: number): boolean {
127
+ // Also remove all instances referencing this abstract numbering
128
+ const instancesToRemove: number[] = [];
129
+ this.instances.forEach((instance, numId) => {
130
+ if (instance.getAbstractNumId() === abstractNumId) {
131
+ instancesToRemove.push(numId);
132
+ }
133
+ });
134
+
135
+ instancesToRemove.forEach((numId) => {
136
+ this.instances.delete(numId);
137
+ this._removedNumIds.add(numId);
138
+ });
139
+
140
+ const deleted = this.abstractNumberings.delete(abstractNumId);
141
+ if (deleted) {
142
+ this._modified = true;
143
+ this._removedAbstractNumIds.add(abstractNumId);
144
+ }
145
+ return deleted;
146
+ }
147
+
148
+ /**
149
+ * Adds a numbering instance
150
+ * @param instance The numbering instance to add
151
+ */
152
+ addInstance(instance: NumberingInstance): this {
153
+ const numId = instance.getNumId();
154
+ const abstractNumId = instance.getAbstractNumId();
155
+
156
+ // Verify that the abstract numbering exists
157
+ if (!this.hasAbstractNumbering(abstractNumId)) {
158
+ throw new Error(`Abstract numbering ${abstractNumId} does not exist`);
159
+ }
160
+
161
+ this.instances.set(numId, instance);
162
+ this._modified = true;
163
+ this._modifiedNumIds.add(numId);
164
+
165
+ // Update next ID if necessary
166
+ if (numId >= this.nextNumId) {
167
+ this.nextNumId = numId + 1;
168
+ }
169
+
170
+ return this;
171
+ }
172
+
173
+ /**
174
+ * Alias for addInstance for backward compatibility
175
+ * @param instance The numbering instance to add
176
+ */
177
+ addNumberingInstance(instance: NumberingInstance): this {
178
+ return this.addInstance(instance);
179
+ }
180
+
181
+ /**
182
+ * Gets a numbering instance by ID
183
+ * @param numId The numbering instance ID
184
+ */
185
+ getInstance(numId: number): NumberingInstance | undefined {
186
+ return this.instances.get(numId);
187
+ }
188
+
189
+ /**
190
+ * Alias for getInstance for backward compatibility
191
+ * @param numId The numbering instance ID
192
+ */
193
+ getNumberingInstance(numId: number): NumberingInstance | undefined {
194
+ return this.getInstance(numId);
195
+ }
196
+
197
+ /**
198
+ * Gets all numbering instances
199
+ */
200
+ getAllInstances(): NumberingInstance[] {
201
+ return Array.from(this.instances.values()).sort((a, b) => a.getNumId() - b.getNumId());
202
+ }
203
+
204
+ /**
205
+ * Checks if numbering has been modified since loading
206
+ * Used for XML preservation optimization
207
+ * @returns True if numbering was added or modified
208
+ */
209
+ isModified(): boolean {
210
+ return this._modified;
211
+ }
212
+
213
+ /**
214
+ * Marks an abstract numbering as modified for XML regeneration.
215
+ * Use when modifying NumberingLevel properties directly (setLeftIndent, etc.)
216
+ * which don't automatically trigger the modified flag.
217
+ * @param abstractNumId The abstract numbering ID to mark as modified
218
+ */
219
+ markAbstractNumberingModified(abstractNumId: number): void {
220
+ if (this.abstractNumberings.has(abstractNumId)) {
221
+ this._modified = true;
222
+ this._modifiedAbstractNumIds.add(abstractNumId);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Resets the modified flag
228
+ * Called after parsing to indicate that loaded numbering doesn't count as modifications
229
+ */
230
+ resetModified(): void {
231
+ this._modified = false;
232
+ this._modifiedAbstractNumIds.clear();
233
+ this._modifiedNumIds.clear();
234
+ this._removedAbstractNumIds.clear();
235
+ this._removedNumIds.clear();
236
+ }
237
+
238
+ /**
239
+ * Gets the IDs of abstract numberings that have been modified since loading
240
+ * Used for selective merging with original numbering.xml
241
+ */
242
+ getModifiedAbstractNumIds(): Set<number> {
243
+ return new Set(this._modifiedAbstractNumIds);
244
+ }
245
+
246
+ /**
247
+ * Gets the IDs of numbering instances that have been modified since loading
248
+ * Used for selective merging with original numbering.xml
249
+ */
250
+ getModifiedNumIds(): Set<number> {
251
+ return new Set(this._modifiedNumIds);
252
+ }
253
+
254
+ /**
255
+ * Gets the IDs of abstract numberings that have been removed since loading
256
+ * Used for removal from original numbering.xml during merge
257
+ */
258
+ getRemovedAbstractNumIds(): Set<number> {
259
+ return new Set(this._removedAbstractNumIds);
260
+ }
261
+
262
+ /**
263
+ * Gets the IDs of numbering instances that have been removed since loading
264
+ * Used for removal from original numbering.xml during merge
265
+ */
266
+ getRemovedNumIds(): Set<number> {
267
+ return new Set(this._removedNumIds);
268
+ }
269
+
270
+ /**
271
+ * Alias for getAllInstances for backward compatibility
272
+ */
273
+ getAllNumberingInstances(): NumberingInstance[] {
274
+ return this.getAllInstances();
275
+ }
276
+
277
+ /**
278
+ * Checks if a numbering instance exists
279
+ * @param numId The numbering instance ID
280
+ */
281
+ hasInstance(numId: number): boolean {
282
+ return this.instances.has(numId);
283
+ }
284
+
285
+ /**
286
+ * Removes a numbering instance
287
+ * @param numId The numbering instance ID
288
+ */
289
+ removeInstance(numId: number): boolean {
290
+ const deleted = this.instances.delete(numId);
291
+ if (deleted) {
292
+ this._modified = true;
293
+ this._removedNumIds.add(numId);
294
+ }
295
+ return deleted;
296
+ }
297
+
298
+ /**
299
+ * Creates a new bullet list and returns its numId
300
+ * @param levels Number of levels (default: 9)
301
+ * @param bullets Array of bullet characters
302
+ */
303
+ createBulletList(levels = 9, bullets?: string[]): number {
304
+ // Create abstract numbering
305
+ const abstractNumId = this.nextAbstractNumId++;
306
+ // Only pass bullets if it's defined, so defaults are used otherwise
307
+ const abstractNumbering = bullets
308
+ ? AbstractNumbering.createBulletList(abstractNumId, levels, bullets)
309
+ : AbstractNumbering.createBulletList(abstractNumId, levels);
310
+ this.addAbstractNumbering(abstractNumbering);
311
+
312
+ // Create instance
313
+ const numId = this.nextNumId++;
314
+ const instance = NumberingInstance.create({ numId, abstractNumId });
315
+ this.addInstance(instance);
316
+
317
+ return numId;
318
+ }
319
+
320
+ /**
321
+ * Creates a new numbered list and returns its numId
322
+ * @param levels Number of levels (default: 9)
323
+ * @param formats Array of formats for each level
324
+ */
325
+ createNumberedList(levels = 9, formats?: ('decimal' | 'lowerLetter' | 'lowerRoman')[]): number {
326
+ // Create abstract numbering
327
+ const abstractNumId = this.nextAbstractNumId++;
328
+ // Only pass formats if it's defined, so defaults are used otherwise
329
+ const abstractNumbering = formats
330
+ ? AbstractNumbering.createNumberedList(abstractNumId, levels, formats)
331
+ : AbstractNumbering.createNumberedList(abstractNumId, levels);
332
+ this.addAbstractNumbering(abstractNumbering);
333
+
334
+ // Create instance
335
+ const numId = this.nextNumId++;
336
+ const instance = NumberingInstance.create({ numId, abstractNumId });
337
+ this.addInstance(instance);
338
+
339
+ return numId;
340
+ }
341
+
342
+ /**
343
+ * Creates a new multi-level list and returns its numId
344
+ */
345
+ createMultiLevelList(): number {
346
+ // Create abstract numbering
347
+ const abstractNumId = this.nextAbstractNumId++;
348
+ const abstractNumbering = AbstractNumbering.createMultiLevelList(abstractNumId);
349
+ this.addAbstractNumbering(abstractNumbering);
350
+
351
+ // Create instance
352
+ const numId = this.nextNumId++;
353
+ const instance = NumberingInstance.create({ numId, abstractNumId });
354
+ this.addInstance(instance);
355
+
356
+ return numId;
357
+ }
358
+
359
+ /**
360
+ * Creates a new outline list and returns its numId
361
+ */
362
+ createOutlineList(): number {
363
+ // Create abstract numbering
364
+ const abstractNumId = this.nextAbstractNumId++;
365
+ const abstractNumbering = AbstractNumbering.createOutlineList(abstractNumId);
366
+ this.addAbstractNumbering(abstractNumbering);
367
+
368
+ // Create instance
369
+ const numId = this.nextNumId++;
370
+ const instance = NumberingInstance.create({ numId, abstractNumId });
371
+ this.addInstance(instance);
372
+
373
+ return numId;
374
+ }
375
+
376
+ /**
377
+ * Creates a custom list with specified levels and returns its numId
378
+ * @param levels Array of numbering levels
379
+ * @param name Optional name for the list
380
+ */
381
+ createCustomList(levels: NumberingLevel[], name?: string): number {
382
+ // Create abstract numbering
383
+ const abstractNumId = this.nextAbstractNumId++;
384
+ const abstractNumbering = AbstractNumbering.create({
385
+ abstractNumId,
386
+ name,
387
+ levels,
388
+ });
389
+ this.addAbstractNumbering(abstractNumbering);
390
+
391
+ // Create instance
392
+ const numId = this.nextNumId++;
393
+ const instance = NumberingInstance.create({ numId, abstractNumId });
394
+ this.addInstance(instance);
395
+
396
+ return numId;
397
+ }
398
+
399
+ /**
400
+ * Creates a new instance of an existing abstract numbering
401
+ * @param abstractNumId The abstract numbering ID to create an instance of
402
+ */
403
+ createInstance(abstractNumId: number): number {
404
+ if (!this.hasAbstractNumbering(abstractNumId)) {
405
+ throw new Error(`Abstract numbering ${abstractNumId} does not exist`);
406
+ }
407
+
408
+ const numId = this.nextNumId++;
409
+ const instance = NumberingInstance.create({ numId, abstractNumId });
410
+ this.addInstance(instance);
411
+
412
+ return numId;
413
+ }
414
+
415
+ /**
416
+ * Creates a new numbering instance that restarts numbering for an existing list
417
+ *
418
+ * This creates a new `<w:num>` referencing the same `<w:abstractNum>` as the
419
+ * given numId, but with a `<w:lvlOverride>/<w:startOverride>` to restart
420
+ * counting at the specified value.
421
+ *
422
+ * @param numId The existing numbering instance ID to base the restart on
423
+ * @param level The level to restart (0-8, default: 0)
424
+ * @param startValue The value to restart from (>= 1, default: 1)
425
+ * @returns The new numId to use with paragraph.setNumbering()
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * const listId = manager.createNumberedList();
430
+ * // ... add items ...
431
+ * const restartId = manager.restartNumbering(listId);
432
+ * // restartId starts counting from 1 again, same list style
433
+ * ```
434
+ */
435
+ restartNumbering(numId: number, level = 0, startValue = 1): number {
436
+ if (level < 0 || level > 8) {
437
+ throw new Error(`Invalid level ${level}. Level must be between 0 and 8.`);
438
+ }
439
+ if (startValue < 1) {
440
+ throw new Error(`Invalid startValue ${startValue}. Start value must be at least 1.`);
441
+ }
442
+
443
+ const existingInstance = this.getInstance(numId);
444
+ if (!existingInstance) {
445
+ throw new Error(`Numbering instance ${numId} does not exist`);
446
+ }
447
+
448
+ const abstractNumId = existingInstance.getAbstractNumId();
449
+ const newNumId = this.nextNumId++;
450
+ const newInstance = NumberingInstance.create({ numId: newNumId, abstractNumId });
451
+ newInstance.setLevelOverride(level, startValue);
452
+ this.addInstance(newInstance);
453
+
454
+ return newNumId;
455
+ }
456
+
457
+ /**
458
+ * Gets the framework's standard indentation for a list level
459
+ *
460
+ * The framework uses a consistent indentation scheme:
461
+ * - leftIndent: 720 + (level * 360) twips
462
+ * - hangingIndent: 360 twips
463
+ *
464
+ * Examples:
465
+ * - Level 0: 720 twips (0.5 inch) left, 360 twips hanging
466
+ * - Level 1: 1080 twips (0.75 inch) left, 360 twips hanging
467
+ * - Level 2: 1440 twips (1.0 inch) left, 360 twips hanging
468
+ *
469
+ * @param level The level (0-8)
470
+ * @returns Object with leftIndent and hangingIndent in twips
471
+ * @example
472
+ * ```typescript
473
+ * const indent = manager.getStandardIndentation(0);
474
+ * // Returns: { leftIndent: 720, hangingIndent: 360 }
475
+ * ```
476
+ */
477
+ getStandardIndentation(level: number): { leftIndent: number; hangingIndent: number } {
478
+ if (level < 0 || level > 8) {
479
+ throw new Error(`Invalid level ${level}. Level must be between 0 and 8.`);
480
+ }
481
+
482
+ return {
483
+ leftIndent: 720 + level * 360,
484
+ hangingIndent: 360,
485
+ };
486
+ }
487
+
488
+ /**
489
+ * Sets custom indentation for a specific level in a numbering definition
490
+ *
491
+ * This updates the indentation for a specific level across ALL paragraphs
492
+ * that use this numId and level combination.
493
+ *
494
+ * @param numId The numbering instance ID
495
+ * @param level The level to modify (0-8)
496
+ * @param leftIndent Left indentation in twips
497
+ * @param hangingIndent Hanging indentation in twips (optional, defaults to 360)
498
+ * @returns true if successful, false if numId doesn't exist
499
+ * @example
500
+ * ```typescript
501
+ * // Set level 0 to 0.5 inch left, 0.25 inch hanging
502
+ * manager.setListIndentation(1, 0, 720, 360);
503
+ * ```
504
+ */
505
+ setListIndentation(
506
+ numId: number,
507
+ level: number,
508
+ leftIndent: number,
509
+ hangingIndent = 360
510
+ ): boolean {
511
+ // Validate level
512
+ if (level < 0 || level > 8) {
513
+ throw new Error(`Invalid level ${level}. Level must be between 0 and 8.`);
514
+ }
515
+
516
+ // Validate indents (clamp negatives to 0)
517
+ leftIndent = Math.max(0, leftIndent);
518
+ hangingIndent = Math.max(0, hangingIndent);
519
+
520
+ // Get the numbering instance
521
+ const instance = this.getInstance(numId);
522
+ if (!instance) {
523
+ defaultLogger.warn(`Numbering instance ${numId} does not exist`);
524
+ return false;
525
+ }
526
+
527
+ // Get the abstract numbering
528
+ const abstractNum = this.getAbstractNumbering(instance.getAbstractNumId());
529
+ if (!abstractNum) {
530
+ defaultLogger.warn(`Abstract numbering for instance ${numId} does not exist`);
531
+ return false;
532
+ }
533
+
534
+ // Get the level from the abstract numbering
535
+ const numLevel = abstractNum.getLevel(level);
536
+ if (!numLevel) {
537
+ defaultLogger.warn(`Level ${level} does not exist in abstract numbering`);
538
+ return false;
539
+ }
540
+
541
+ // Update the level's indentation
542
+ numLevel.setLeftIndent(leftIndent);
543
+ numLevel.setHangingIndent(hangingIndent);
544
+
545
+ return true;
546
+ }
547
+
548
+ /**
549
+ * Resets all levels in a numbering definition to standard indentation
550
+ *
551
+ * This applies the framework's standard indentation formula to all levels:
552
+ * - leftIndent: 720 + (level * 360) twips
553
+ * - hangingIndent: 360 twips
554
+ *
555
+ * @param numId The numbering instance ID
556
+ * @returns true if successful, false if numId doesn't exist
557
+ * @example
558
+ * ```typescript
559
+ * // Reset list 1 to standard indentation
560
+ * manager.normalizeListIndentation(1);
561
+ * ```
562
+ */
563
+ normalizeListIndentation(numId: number): boolean {
564
+ // Get the numbering instance
565
+ const instance = this.getInstance(numId);
566
+ if (!instance) {
567
+ defaultLogger.warn(`Numbering instance ${numId} does not exist`);
568
+ return false;
569
+ }
570
+
571
+ // Get the abstract numbering
572
+ const abstractNum = this.getAbstractNumbering(instance.getAbstractNumId());
573
+ if (!abstractNum) {
574
+ defaultLogger.warn(`Abstract numbering for instance ${numId} does not exist`);
575
+ return false;
576
+ }
577
+
578
+ // Get all levels
579
+ const levels = abstractNum.getAllLevels();
580
+
581
+ // Apply standard indentation to each level
582
+ for (const level of levels) {
583
+ const standardIndent = this.getStandardIndentation(level.getLevel());
584
+ level.setLeftIndent(standardIndent.leftIndent);
585
+ level.setHangingIndent(standardIndent.hangingIndent);
586
+ }
587
+
588
+ return true;
589
+ }
590
+
591
+ /**
592
+ * Normalizes indentation for all lists in the document
593
+ *
594
+ * Applies standard indentation to every numbering instance:
595
+ * - leftIndent: 720 + (level * 360) twips
596
+ * - hangingIndent: 360 twips
597
+ *
598
+ * This ensures consistent spacing across all lists in the document.
599
+ *
600
+ * @returns Number of numbering instances updated
601
+ * @example
602
+ * ```typescript
603
+ * const count = manager.normalizeAllListIndentation();
604
+ * console.log(`Normalized ${count} lists`);
605
+ * ```
606
+ */
607
+ normalizeAllListIndentation(): number {
608
+ let count = 0;
609
+
610
+ // Iterate over all instances
611
+ for (const instance of this.getAllInstances()) {
612
+ const success = this.normalizeListIndentation(instance.getNumId());
613
+ if (success) {
614
+ count++;
615
+ }
616
+ }
617
+
618
+ return count;
619
+ }
620
+
621
+ /**
622
+ * Gets the total number of abstract numberings
623
+ */
624
+ getAbstractNumberingCount(): number {
625
+ return this.abstractNumberings.size;
626
+ }
627
+
628
+ /**
629
+ * Gets the total number of numbering instances
630
+ */
631
+ getInstanceCount(): number {
632
+ return this.instances.size;
633
+ }
634
+
635
+ /**
636
+ * Clears all numbering definitions and instances
637
+ */
638
+ clear(): this {
639
+ this.abstractNumberings.clear();
640
+ this.instances.clear();
641
+ this.nextAbstractNumId = 0;
642
+ this.nextNumId = 1;
643
+ return this;
644
+ }
645
+
646
+ /**
647
+ * Removes unused numbering instances and abstract numberings
648
+ *
649
+ * This method cleans up numbering definitions that are no longer referenced
650
+ * by any paragraphs in the document. It removes:
651
+ * 1. Instances not in the usedNumIds set
652
+ * 2. Abstract numberings not referenced by any remaining instance
653
+ *
654
+ * @param usedNumIds Set of numIds currently used by paragraphs
655
+ * @returns Object with counts of removed instances and abstract numberings
656
+ */
657
+ cleanupUnusedNumbering(usedNumIds: Set<number>): {
658
+ instancesRemoved: number;
659
+ abstractsRemoved: number;
660
+ } {
661
+ let instancesRemoved = 0;
662
+ let abstractsRemoved = 0;
663
+
664
+ // Step 1: Remove unused instances
665
+ const instancesToRemove: number[] = [];
666
+ this.instances.forEach((_instance, numId) => {
667
+ if (!usedNumIds.has(numId)) {
668
+ instancesToRemove.push(numId);
669
+ }
670
+ });
671
+
672
+ for (const numId of instancesToRemove) {
673
+ this.instances.delete(numId);
674
+ this._modified = true;
675
+ this._removedNumIds.add(numId);
676
+ instancesRemoved++;
677
+ }
678
+
679
+ // Step 2: Find abstract numberings still referenced by remaining instances
680
+ const referencedAbstractNumIds = new Set<number>();
681
+ this.instances.forEach((instance) => {
682
+ referencedAbstractNumIds.add(instance.getAbstractNumId());
683
+ });
684
+
685
+ // Step 3: Remove unreferenced abstract numberings
686
+ const abstractsToRemove: number[] = [];
687
+ this.abstractNumberings.forEach((_abstractNum, abstractNumId) => {
688
+ if (!referencedAbstractNumIds.has(abstractNumId)) {
689
+ abstractsToRemove.push(abstractNumId);
690
+ }
691
+ });
692
+
693
+ for (const abstractNumId of abstractsToRemove) {
694
+ this.abstractNumberings.delete(abstractNumId);
695
+ this._modified = true;
696
+ this._removedAbstractNumIds.add(abstractNumId);
697
+ abstractsRemoved++;
698
+ }
699
+
700
+ return { instancesRemoved, abstractsRemoved };
701
+ }
702
+
703
+ /**
704
+ * Consolidates duplicate abstract numbering definitions
705
+ *
706
+ * Groups abstractNums by a deterministic fingerprint of their level properties
707
+ * (format, text, font, fontSize, color, indentation, alignment, etc.).
708
+ * For each group with >1 member, picks the lowest abstractNumId as canonical,
709
+ * remaps all instances pointing to non-canonical IDs, and removes duplicates.
710
+ *
711
+ * This is safe because multiple num instances can reference the same abstractNum —
712
+ * each instance maintains its own independent counter via level overrides.
713
+ *
714
+ * @param options Optional configuration (e.g., protected IDs to skip)
715
+ * @returns Summary of what was consolidated
716
+ */
717
+ consolidateNumbering(options?: NumberingConsolidationOptions): NumberingConsolidationResult {
718
+ const protectedIds = options?.protectedAbstractNumIds ?? new Set<number>();
719
+
720
+ // 1. Compute fingerprint for each non-protected abstractNum
721
+ const fingerprintGroups = new Map<string, number[]>();
722
+ for (const abstractNum of this.abstractNumberings.values()) {
723
+ const id = abstractNum.getAbstractNumId();
724
+ if (protectedIds.has(id)) continue;
725
+
726
+ const fingerprint = this._fingerprintAbstractNum(abstractNum);
727
+ const group = fingerprintGroups.get(fingerprint);
728
+ if (group) {
729
+ group.push(id);
730
+ } else {
731
+ fingerprintGroups.set(fingerprint, [id]);
732
+ }
733
+ }
734
+
735
+ // 2. For each group with >1 member, consolidate
736
+ let abstractNumsRemoved = 0;
737
+ let instancesRemapped = 0;
738
+ let groupsConsolidated = 0;
739
+
740
+ for (const [, ids] of fingerprintGroups) {
741
+ if (ids.length <= 1) continue;
742
+
743
+ // Sort to pick lowest ID as canonical
744
+ ids.sort((a, b) => a - b);
745
+ const canonicalId = ids[0]!;
746
+ const duplicateIds = new Set(ids.slice(1));
747
+
748
+ groupsConsolidated++;
749
+
750
+ // Remap instances pointing to duplicate abstractNums
751
+ for (const instance of this.instances.values()) {
752
+ if (duplicateIds.has(instance.getAbstractNumId())) {
753
+ instance.setAbstractNumId(canonicalId);
754
+ this._modifiedNumIds.add(instance.getNumId());
755
+ instancesRemapped++;
756
+ }
757
+ }
758
+
759
+ // Remove duplicate abstractNums
760
+ for (const dupId of duplicateIds) {
761
+ this.abstractNumberings.delete(dupId);
762
+ this._removedAbstractNumIds.add(dupId);
763
+ abstractNumsRemoved++;
764
+ }
765
+ }
766
+
767
+ if (abstractNumsRemoved > 0) {
768
+ this._modified = true;
769
+ }
770
+
771
+ return { abstractNumsRemoved, instancesRemapped, groupsConsolidated };
772
+ }
773
+
774
+ /**
775
+ * Computes a deterministic fingerprint for an abstract numbering definition
776
+ * based on its multiLevelType and all level properties. Name and abstractNumId
777
+ * are excluded since they don't affect rendering.
778
+ */
779
+ private _fingerprintAbstractNum(abstractNum: AbstractNumbering): string {
780
+ const parts: string[] = [
781
+ abstractNum.getNumStyleLink() ?? '',
782
+ abstractNum.getStyleLink() ?? '',
783
+ abstractNum.getMultiLevelType(),
784
+ ];
785
+
786
+ for (const level of abstractNum.getAllLevels()) {
787
+ const props = level.getProperties();
788
+ parts.push(
789
+ `${props.level}|${props.format}|${props.text}|${props.font}|${props.fontSize}|` +
790
+ `${props.color}|${props.leftIndent}|${props.hangingIndent}|${props.alignment}|` +
791
+ `${props.start}|${props.bold}|${props.italic}|${props.underline ?? ''}|` +
792
+ `${props.suffix}|${props.isLegalNumberingStyle}|${props.lvlRestart ?? ''}`
793
+ );
794
+ }
795
+
796
+ return parts.join('::');
797
+ }
798
+
799
+ /**
800
+ * Generates the complete numbering.xml content
801
+ */
802
+ generateNumberingXml(): string {
803
+ const builder = new XMLBuilder();
804
+
805
+ const children: XMLElement[] = [];
806
+
807
+ // Add all abstract numberings
808
+ const abstractNumberings = this.getAllAbstractNumberings();
809
+ abstractNumberings.forEach((abstractNum) => {
810
+ children.push(abstractNum.toXML());
811
+ });
812
+
813
+ // Add all numbering instances
814
+ const instances = this.getAllInstances();
815
+ instances.forEach((instance) => {
816
+ children.push(instance.toXML());
817
+ });
818
+
819
+ const numbering = XMLBuilder.w(
820
+ 'numbering',
821
+ {
822
+ 'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
823
+ 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
824
+ },
825
+ children
826
+ );
827
+
828
+ builder.element(numbering.name, numbering.attributes, numbering.children);
829
+
830
+ // Generate XML with declaration
831
+ return builder.build(true);
832
+ }
833
+
834
+ /**
835
+ * Generates the numbering.xml as XMLElement (for API compatibility)
836
+ */
837
+ toXML(): XMLElement {
838
+ const children: XMLElement[] = [];
839
+
840
+ // Add all abstract numberings
841
+ const abstractNumberings = this.getAllAbstractNumberings();
842
+ abstractNumberings.forEach((abstractNum) => {
843
+ children.push(abstractNum.toXML());
844
+ });
845
+
846
+ // Add all numbering instances
847
+ const instances = this.getAllInstances();
848
+ instances.forEach((instance) => {
849
+ children.push(instance.toXML());
850
+ });
851
+
852
+ return XMLBuilder.w(
853
+ 'numbering',
854
+ {
855
+ 'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
856
+ 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
857
+ },
858
+ children
859
+ );
860
+ }
861
+
862
+ /**
863
+ * Creates a numbering manager with default numbering definitions
864
+ */
865
+ static create(): NumberingManager {
866
+ return new NumberingManager(false);
867
+ }
868
+
869
+ /**
870
+ * Creates an empty numbering manager
871
+ */
872
+ static createEmpty(): NumberingManager {
873
+ return new NumberingManager(false);
874
+ }
875
+ }