docxmlater 10.1.3 → 10.1.5

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 (371) 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 +50 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +483 -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.map +1 -1
  146. package/dist/formatting/NumberingManager.js +9 -9
  147. package/dist/formatting/NumberingManager.js.map +1 -1
  148. package/dist/formatting/Style.d.ts +11 -11
  149. package/dist/formatting/Style.d.ts.map +1 -1
  150. package/dist/formatting/Style.js +219 -247
  151. package/dist/formatting/Style.js.map +1 -1
  152. package/dist/formatting/StylesManager.d.ts +2 -2
  153. package/dist/formatting/StylesManager.d.ts.map +1 -1
  154. package/dist/formatting/StylesManager.js +96 -102
  155. package/dist/formatting/StylesManager.js.map +1 -1
  156. package/dist/helpers/CleanupHelper.d.ts +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  158. package/dist/helpers/CleanupHelper.js +6 -6
  159. package/dist/helpers/CleanupHelper.js.map +1 -1
  160. package/dist/images/ImageOptimizer.js +7 -7
  161. package/dist/images/ImageOptimizer.js.map +1 -1
  162. package/dist/index.d.ts +9 -9
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js.map +1 -1
  165. package/dist/managers/DrawingManager.js.map +1 -1
  166. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.js +23 -7
  168. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  169. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  170. package/dist/tracking/TrackingContext.js.map +1 -1
  171. package/dist/types/compatibility-types.js.map +1 -1
  172. package/dist/types/formatting.js.map +1 -1
  173. package/dist/types/list-types.d.ts +6 -6
  174. package/dist/types/list-types.js.map +1 -1
  175. package/dist/types/settings-types.js.map +1 -1
  176. package/dist/types/styleConfig.d.ts +2 -2
  177. package/dist/types/styleConfig.js.map +1 -1
  178. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  179. package/dist/utils/ChangelogGenerator.js +97 -101
  180. package/dist/utils/ChangelogGenerator.js.map +1 -1
  181. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.js +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  184. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  186. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  187. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  188. package/dist/utils/MoveOperationHelper.js +1 -1
  189. package/dist/utils/MoveOperationHelper.js.map +1 -1
  190. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.js +2 -4
  192. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  193. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  194. package/dist/utils/RevisionWalker.js +4 -12
  195. package/dist/utils/RevisionWalker.js.map +1 -1
  196. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  198. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  199. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  200. package/dist/utils/ShadingResolver.js +1 -1
  201. package/dist/utils/ShadingResolver.js.map +1 -1
  202. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  203. package/dist/utils/acceptRevisions.js +23 -12
  204. package/dist/utils/acceptRevisions.js.map +1 -1
  205. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  207. package/dist/utils/cnfStyleDecoder.js +40 -40
  208. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  209. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  210. package/dist/utils/corruptionDetection.js.map +1 -1
  211. package/dist/utils/dateFormatting.js.map +1 -1
  212. package/dist/utils/deepClone.js +1 -1
  213. package/dist/utils/deepClone.js.map +1 -1
  214. package/dist/utils/diagnostics.d.ts.map +1 -1
  215. package/dist/utils/diagnostics.js +1 -1
  216. package/dist/utils/diagnostics.js.map +1 -1
  217. package/dist/utils/errorHandling.js.map +1 -1
  218. package/dist/utils/formatting.d.ts.map +1 -1
  219. package/dist/utils/formatting.js +10 -2
  220. package/dist/utils/formatting.js.map +1 -1
  221. package/dist/utils/list-detection.d.ts +2 -2
  222. package/dist/utils/list-detection.d.ts.map +1 -1
  223. package/dist/utils/list-detection.js +21 -23
  224. package/dist/utils/list-detection.js.map +1 -1
  225. package/dist/utils/logger.d.ts.map +1 -1
  226. package/dist/utils/logger.js +12 -7
  227. package/dist/utils/logger.js.map +1 -1
  228. package/dist/utils/parsingHelpers.js.map +1 -1
  229. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  230. package/dist/utils/stripTrackedChanges.js +3 -3
  231. package/dist/utils/stripTrackedChanges.js.map +1 -1
  232. package/dist/utils/textDiff.d.ts +1 -1
  233. package/dist/utils/textDiff.js +8 -8
  234. package/dist/utils/textDiff.js.map +1 -1
  235. package/dist/utils/units.js.map +1 -1
  236. package/dist/utils/validation.d.ts.map +1 -1
  237. package/dist/utils/validation.js +24 -7
  238. package/dist/utils/validation.js.map +1 -1
  239. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  240. package/dist/utils/xmlSanitization.js +3 -3
  241. package/dist/utils/xmlSanitization.js.map +1 -1
  242. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.js +5 -5
  244. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  245. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  246. package/dist/validation/RevisionValidator.js +7 -9
  247. package/dist/validation/RevisionValidator.js.map +1 -1
  248. package/dist/validation/ValidationRules.js +3 -3
  249. package/dist/validation/ValidationRules.js.map +1 -1
  250. package/dist/validation/index.js.map +1 -1
  251. package/dist/xml/XMLBuilder.d.ts +1 -1
  252. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  253. package/dist/xml/XMLBuilder.js +98 -100
  254. package/dist/xml/XMLBuilder.js.map +1 -1
  255. package/dist/xml/XMLParser.d.ts.map +1 -1
  256. package/dist/xml/XMLParser.js +61 -66
  257. package/dist/xml/XMLParser.js.map +1 -1
  258. package/dist/zip/ZipHandler.d.ts.map +1 -1
  259. package/dist/zip/ZipHandler.js.map +1 -1
  260. package/dist/zip/ZipReader.d.ts.map +1 -1
  261. package/dist/zip/ZipReader.js +1 -3
  262. package/dist/zip/ZipReader.js.map +1 -1
  263. package/dist/zip/ZipWriter.d.ts +1 -1
  264. package/dist/zip/ZipWriter.d.ts.map +1 -1
  265. package/dist/zip/ZipWriter.js +28 -36
  266. package/dist/zip/ZipWriter.js.map +1 -1
  267. package/dist/zip/types.js +1 -1
  268. package/dist/zip/types.js.map +1 -1
  269. package/package.json +92 -92
  270. package/src/__tests__/helper-methods.test.ts +512 -512
  271. package/src/constants/legacyCompatFlags.ts +138 -138
  272. package/src/constants/limits.ts +50 -50
  273. package/src/core/Document.ts +985 -1145
  274. package/src/core/DocumentContent.ts +461 -467
  275. package/src/core/DocumentGenerator.ts +1133 -1104
  276. package/src/core/DocumentIdManager.ts +158 -158
  277. package/src/core/DocumentParser.ts +2347 -2716
  278. package/src/core/DocumentValidator.ts +363 -372
  279. package/src/core/Relationship.ts +367 -367
  280. package/src/core/RelationshipManager.ts +429 -428
  281. package/src/elements/AlternateContent.ts +42 -42
  282. package/src/elements/Bookmark.ts +212 -210
  283. package/src/elements/BookmarkManager.ts +247 -250
  284. package/src/elements/Comment.ts +356 -359
  285. package/src/elements/CommentManager.ts +499 -502
  286. package/src/elements/CommonTypes.ts +524 -549
  287. package/src/elements/CustomXml.ts +36 -36
  288. package/src/elements/Endnote.ts +221 -217
  289. package/src/elements/EndnoteManager.ts +246 -249
  290. package/src/elements/Field.ts +1292 -1233
  291. package/src/elements/FieldHelpers.ts +329 -333
  292. package/src/elements/FontManager.ts +336 -339
  293. package/src/elements/Footer.ts +269 -269
  294. package/src/elements/Footnote.ts +221 -217
  295. package/src/elements/FootnoteManager.ts +246 -249
  296. package/src/elements/Header.ts +269 -269
  297. package/src/elements/HeaderFooterManager.ts +219 -219
  298. package/src/elements/Hyperlink.ts +1288 -1193
  299. package/src/elements/Image.ts +1982 -1756
  300. package/src/elements/ImageManager.ts +437 -432
  301. package/src/elements/ImageRun.ts +59 -59
  302. package/src/elements/MathElement.ts +65 -65
  303. package/src/elements/Paragraph.ts +4347 -4287
  304. package/src/elements/PreservedElement.ts +53 -53
  305. package/src/elements/PropertyChangeTypes.ts +458 -442
  306. package/src/elements/RangeMarker.ts +382 -400
  307. package/src/elements/Revision.ts +1198 -1217
  308. package/src/elements/RevisionContent.ts +73 -73
  309. package/src/elements/RevisionManager.ts +1070 -1070
  310. package/src/elements/Run.ts +3103 -3073
  311. package/src/elements/Section.ts +1521 -1421
  312. package/src/elements/Shape.ts +884 -873
  313. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  314. package/src/elements/Table.ts +2468 -2524
  315. package/src/elements/TableCell.ts +1617 -1621
  316. package/src/elements/TableGridChange.ts +149 -151
  317. package/src/elements/TableOfContents.ts +701 -691
  318. package/src/elements/TableOfContentsElement.ts +89 -89
  319. package/src/elements/TableRow.ts +960 -929
  320. package/src/elements/TextBox.ts +766 -768
  321. package/src/formatting/AbstractNumbering.ts +580 -579
  322. package/src/formatting/NumberingInstance.ts +295 -299
  323. package/src/formatting/NumberingLevel.ts +981 -1040
  324. package/src/formatting/NumberingManager.ts +833 -827
  325. package/src/formatting/Style.ts +1785 -1879
  326. package/src/formatting/StylesManager.ts +1090 -1130
  327. package/src/helpers/CleanupHelper.ts +524 -524
  328. package/src/images/ImageOptimizer.ts +274 -274
  329. package/src/index.ts +559 -554
  330. package/src/managers/DrawingManager.ts +319 -319
  331. package/src/tracking/DocumentTrackingContext.ts +687 -674
  332. package/src/tracking/TrackingContext.ts +175 -173
  333. package/src/types/compatibility-types.ts +49 -49
  334. package/src/types/formatting.ts +210 -210
  335. package/src/types/list-types.ts +14 -14
  336. package/src/types/settings-types.ts +59 -59
  337. package/src/types/styleConfig.ts +189 -189
  338. package/src/utils/ChangelogGenerator.ts +1583 -1581
  339. package/src/utils/CompatibilityUpgrader.ts +235 -237
  340. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  341. package/src/utils/MoveOperationHelper.ts +233 -238
  342. package/src/utils/RevisionAwareProcessor.ts +518 -526
  343. package/src/utils/RevisionWalker.ts +427 -457
  344. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  345. package/src/utils/ShadingResolver.ts +105 -107
  346. package/src/utils/acceptRevisions.ts +723 -714
  347. package/src/utils/cnfStyleDecoder.ts +212 -217
  348. package/src/utils/corruptionDetection.ts +346 -345
  349. package/src/utils/dateFormatting.ts +20 -20
  350. package/src/utils/deepClone.ts +77 -78
  351. package/src/utils/diagnostics.ts +125 -129
  352. package/src/utils/errorHandling.ts +80 -80
  353. package/src/utils/formatting.ts +220 -213
  354. package/src/utils/list-detection.ts +32 -42
  355. package/src/utils/logger.ts +412 -404
  356. package/src/utils/parsingHelpers.ts +190 -190
  357. package/src/utils/stripTrackedChanges.ts +356 -353
  358. package/src/utils/textDiff.ts +100 -100
  359. package/src/utils/units.ts +421 -421
  360. package/src/utils/validation.ts +553 -542
  361. package/src/utils/xmlSanitization.ts +179 -182
  362. package/src/validation/RevisionAutoFixer.ts +541 -542
  363. package/src/validation/RevisionValidator.ts +470 -460
  364. package/src/validation/ValidationRules.ts +338 -338
  365. package/src/validation/index.ts +30 -30
  366. package/src/xml/XMLBuilder.ts +857 -871
  367. package/src/xml/XMLParser.ts +877 -919
  368. package/src/zip/ZipHandler.ts +629 -637
  369. package/src/zip/ZipReader.ts +295 -299
  370. package/src/zip/ZipWriter.ts +374 -390
  371. package/src/zip/types.ts +116 -116
@@ -1,827 +1,833 @@
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
+ * Gets the framework's standard indentation for a list level
417
+ *
418
+ * The framework uses a consistent indentation scheme:
419
+ * - leftIndent: 720 + (level * 360) twips
420
+ * - hangingIndent: 360 twips
421
+ *
422
+ * Examples:
423
+ * - Level 0: 720 twips (0.5 inch) left, 360 twips hanging
424
+ * - Level 1: 1080 twips (0.75 inch) left, 360 twips hanging
425
+ * - Level 2: 1440 twips (1.0 inch) left, 360 twips hanging
426
+ *
427
+ * @param level The level (0-8)
428
+ * @returns Object with leftIndent and hangingIndent in twips
429
+ * @example
430
+ * ```typescript
431
+ * const indent = manager.getStandardIndentation(0);
432
+ * // Returns: { leftIndent: 720, hangingIndent: 360 }
433
+ * ```
434
+ */
435
+ getStandardIndentation(level: number): { leftIndent: number; hangingIndent: number } {
436
+ if (level < 0 || level > 8) {
437
+ throw new Error(`Invalid level ${level}. Level must be between 0 and 8.`);
438
+ }
439
+
440
+ return {
441
+ leftIndent: 720 + level * 360,
442
+ hangingIndent: 360,
443
+ };
444
+ }
445
+
446
+ /**
447
+ * Sets custom indentation for a specific level in a numbering definition
448
+ *
449
+ * This updates the indentation for a specific level across ALL paragraphs
450
+ * that use this numId and level combination.
451
+ *
452
+ * @param numId The numbering instance ID
453
+ * @param level The level to modify (0-8)
454
+ * @param leftIndent Left indentation in twips
455
+ * @param hangingIndent Hanging indentation in twips (optional, defaults to 360)
456
+ * @returns true if successful, false if numId doesn't exist
457
+ * @example
458
+ * ```typescript
459
+ * // Set level 0 to 0.5 inch left, 0.25 inch hanging
460
+ * manager.setListIndentation(1, 0, 720, 360);
461
+ * ```
462
+ */
463
+ setListIndentation(
464
+ numId: number,
465
+ level: number,
466
+ leftIndent: number,
467
+ hangingIndent = 360
468
+ ): boolean {
469
+ // Validate level
470
+ if (level < 0 || level > 8) {
471
+ throw new Error(`Invalid level ${level}. Level must be between 0 and 8.`);
472
+ }
473
+
474
+ // Validate indents (clamp negatives to 0)
475
+ leftIndent = Math.max(0, leftIndent);
476
+ hangingIndent = Math.max(0, hangingIndent);
477
+
478
+ // Get the numbering instance
479
+ const instance = this.getInstance(numId);
480
+ if (!instance) {
481
+ defaultLogger.warn(`Numbering instance ${numId} does not exist`);
482
+ return false;
483
+ }
484
+
485
+ // Get the abstract numbering
486
+ const abstractNum = this.getAbstractNumbering(instance.getAbstractNumId());
487
+ if (!abstractNum) {
488
+ defaultLogger.warn(`Abstract numbering for instance ${numId} does not exist`);
489
+ return false;
490
+ }
491
+
492
+ // Get the level from the abstract numbering
493
+ const numLevel = abstractNum.getLevel(level);
494
+ if (!numLevel) {
495
+ defaultLogger.warn(`Level ${level} does not exist in abstract numbering`);
496
+ return false;
497
+ }
498
+
499
+ // Update the level's indentation
500
+ numLevel.setLeftIndent(leftIndent);
501
+ numLevel.setHangingIndent(hangingIndent);
502
+
503
+ return true;
504
+ }
505
+
506
+ /**
507
+ * Resets all levels in a numbering definition to standard indentation
508
+ *
509
+ * This applies the framework's standard indentation formula to all levels:
510
+ * - leftIndent: 720 + (level * 360) twips
511
+ * - hangingIndent: 360 twips
512
+ *
513
+ * @param numId The numbering instance ID
514
+ * @returns true if successful, false if numId doesn't exist
515
+ * @example
516
+ * ```typescript
517
+ * // Reset list 1 to standard indentation
518
+ * manager.normalizeListIndentation(1);
519
+ * ```
520
+ */
521
+ normalizeListIndentation(numId: number): boolean {
522
+ // Get the numbering instance
523
+ const instance = this.getInstance(numId);
524
+ if (!instance) {
525
+ defaultLogger.warn(`Numbering instance ${numId} does not exist`);
526
+ return false;
527
+ }
528
+
529
+ // Get the abstract numbering
530
+ const abstractNum = this.getAbstractNumbering(instance.getAbstractNumId());
531
+ if (!abstractNum) {
532
+ defaultLogger.warn(`Abstract numbering for instance ${numId} does not exist`);
533
+ return false;
534
+ }
535
+
536
+ // Get all levels
537
+ const levels = abstractNum.getAllLevels();
538
+
539
+ // Apply standard indentation to each level
540
+ for (const level of levels) {
541
+ const standardIndent = this.getStandardIndentation(level.getLevel());
542
+ level.setLeftIndent(standardIndent.leftIndent);
543
+ level.setHangingIndent(standardIndent.hangingIndent);
544
+ }
545
+
546
+ return true;
547
+ }
548
+
549
+ /**
550
+ * Normalizes indentation for all lists in the document
551
+ *
552
+ * Applies standard indentation to every numbering instance:
553
+ * - leftIndent: 720 + (level * 360) twips
554
+ * - hangingIndent: 360 twips
555
+ *
556
+ * This ensures consistent spacing across all lists in the document.
557
+ *
558
+ * @returns Number of numbering instances updated
559
+ * @example
560
+ * ```typescript
561
+ * const count = manager.normalizeAllListIndentation();
562
+ * console.log(`Normalized ${count} lists`);
563
+ * ```
564
+ */
565
+ normalizeAllListIndentation(): number {
566
+ let count = 0;
567
+
568
+ // Iterate over all instances
569
+ for (const instance of this.getAllInstances()) {
570
+ const success = this.normalizeListIndentation(instance.getNumId());
571
+ if (success) {
572
+ count++;
573
+ }
574
+ }
575
+
576
+ return count;
577
+ }
578
+
579
+ /**
580
+ * Gets the total number of abstract numberings
581
+ */
582
+ getAbstractNumberingCount(): number {
583
+ return this.abstractNumberings.size;
584
+ }
585
+
586
+ /**
587
+ * Gets the total number of numbering instances
588
+ */
589
+ getInstanceCount(): number {
590
+ return this.instances.size;
591
+ }
592
+
593
+ /**
594
+ * Clears all numbering definitions and instances
595
+ */
596
+ clear(): this {
597
+ this.abstractNumberings.clear();
598
+ this.instances.clear();
599
+ this.nextAbstractNumId = 0;
600
+ this.nextNumId = 1;
601
+ return this;
602
+ }
603
+
604
+ /**
605
+ * Removes unused numbering instances and abstract numberings
606
+ *
607
+ * This method cleans up numbering definitions that are no longer referenced
608
+ * by any paragraphs in the document. It removes:
609
+ * 1. Instances not in the usedNumIds set
610
+ * 2. Abstract numberings not referenced by any remaining instance
611
+ *
612
+ * @param usedNumIds Set of numIds currently used by paragraphs
613
+ * @returns Object with counts of removed instances and abstract numberings
614
+ */
615
+ cleanupUnusedNumbering(usedNumIds: Set<number>): {
616
+ instancesRemoved: number;
617
+ abstractsRemoved: number;
618
+ } {
619
+ let instancesRemoved = 0;
620
+ let abstractsRemoved = 0;
621
+
622
+ // Step 1: Remove unused instances
623
+ const instancesToRemove: number[] = [];
624
+ this.instances.forEach((_instance, numId) => {
625
+ if (!usedNumIds.has(numId)) {
626
+ instancesToRemove.push(numId);
627
+ }
628
+ });
629
+
630
+ for (const numId of instancesToRemove) {
631
+ this.instances.delete(numId);
632
+ this._modified = true;
633
+ this._removedNumIds.add(numId);
634
+ instancesRemoved++;
635
+ }
636
+
637
+ // Step 2: Find abstract numberings still referenced by remaining instances
638
+ const referencedAbstractNumIds = new Set<number>();
639
+ this.instances.forEach((instance) => {
640
+ referencedAbstractNumIds.add(instance.getAbstractNumId());
641
+ });
642
+
643
+ // Step 3: Remove unreferenced abstract numberings
644
+ const abstractsToRemove: number[] = [];
645
+ this.abstractNumberings.forEach((_abstractNum, abstractNumId) => {
646
+ if (!referencedAbstractNumIds.has(abstractNumId)) {
647
+ abstractsToRemove.push(abstractNumId);
648
+ }
649
+ });
650
+
651
+ for (const abstractNumId of abstractsToRemove) {
652
+ this.abstractNumberings.delete(abstractNumId);
653
+ this._modified = true;
654
+ this._removedAbstractNumIds.add(abstractNumId);
655
+ abstractsRemoved++;
656
+ }
657
+
658
+ return { instancesRemoved, abstractsRemoved };
659
+ }
660
+
661
+ /**
662
+ * Consolidates duplicate abstract numbering definitions
663
+ *
664
+ * Groups abstractNums by a deterministic fingerprint of their level properties
665
+ * (format, text, font, fontSize, color, indentation, alignment, etc.).
666
+ * For each group with >1 member, picks the lowest abstractNumId as canonical,
667
+ * remaps all instances pointing to non-canonical IDs, and removes duplicates.
668
+ *
669
+ * This is safe because multiple num instances can reference the same abstractNum
670
+ * each instance maintains its own independent counter via level overrides.
671
+ *
672
+ * @param options Optional configuration (e.g., protected IDs to skip)
673
+ * @returns Summary of what was consolidated
674
+ */
675
+ consolidateNumbering(options?: NumberingConsolidationOptions): NumberingConsolidationResult {
676
+ const protectedIds = options?.protectedAbstractNumIds ?? new Set<number>();
677
+
678
+ // 1. Compute fingerprint for each non-protected abstractNum
679
+ const fingerprintGroups = new Map<string, number[]>();
680
+ for (const abstractNum of this.abstractNumberings.values()) {
681
+ const id = abstractNum.getAbstractNumId();
682
+ if (protectedIds.has(id)) continue;
683
+
684
+ const fingerprint = this._fingerprintAbstractNum(abstractNum);
685
+ const group = fingerprintGroups.get(fingerprint);
686
+ if (group) {
687
+ group.push(id);
688
+ } else {
689
+ fingerprintGroups.set(fingerprint, [id]);
690
+ }
691
+ }
692
+
693
+ // 2. For each group with >1 member, consolidate
694
+ let abstractNumsRemoved = 0;
695
+ let instancesRemapped = 0;
696
+ let groupsConsolidated = 0;
697
+
698
+ for (const [, ids] of fingerprintGroups) {
699
+ if (ids.length <= 1) continue;
700
+
701
+ // Sort to pick lowest ID as canonical
702
+ ids.sort((a, b) => a - b);
703
+ const canonicalId = ids[0]!;
704
+ const duplicateIds = new Set(ids.slice(1));
705
+
706
+ groupsConsolidated++;
707
+
708
+ // Remap instances pointing to duplicate abstractNums
709
+ for (const instance of this.instances.values()) {
710
+ if (duplicateIds.has(instance.getAbstractNumId())) {
711
+ instance.setAbstractNumId(canonicalId);
712
+ this._modifiedNumIds.add(instance.getNumId());
713
+ instancesRemapped++;
714
+ }
715
+ }
716
+
717
+ // Remove duplicate abstractNums
718
+ for (const dupId of duplicateIds) {
719
+ this.abstractNumberings.delete(dupId);
720
+ this._removedAbstractNumIds.add(dupId);
721
+ abstractNumsRemoved++;
722
+ }
723
+ }
724
+
725
+ if (abstractNumsRemoved > 0) {
726
+ this._modified = true;
727
+ }
728
+
729
+ return { abstractNumsRemoved, instancesRemapped, groupsConsolidated };
730
+ }
731
+
732
+ /**
733
+ * Computes a deterministic fingerprint for an abstract numbering definition
734
+ * based on its multiLevelType and all level properties. Name and abstractNumId
735
+ * are excluded since they don't affect rendering.
736
+ */
737
+ private _fingerprintAbstractNum(abstractNum: AbstractNumbering): string {
738
+ const parts: string[] = [
739
+ abstractNum.getNumStyleLink() ?? '',
740
+ abstractNum.getStyleLink() ?? '',
741
+ abstractNum.getMultiLevelType(),
742
+ ];
743
+
744
+ for (const level of abstractNum.getAllLevels()) {
745
+ const props = level.getProperties();
746
+ parts.push(
747
+ `${props.level}|${props.format}|${props.text}|${props.font}|${props.fontSize}|` +
748
+ `${props.color}|${props.leftIndent}|${props.hangingIndent}|${props.alignment}|` +
749
+ `${props.start}|${props.bold}|${props.italic}|${props.underline ?? ''}|` +
750
+ `${props.suffix}|${props.isLegalNumberingStyle}|${props.lvlRestart ?? ''}`
751
+ );
752
+ }
753
+
754
+ return parts.join('::');
755
+ }
756
+
757
+ /**
758
+ * Generates the complete numbering.xml content
759
+ */
760
+ generateNumberingXml(): string {
761
+ const builder = new XMLBuilder();
762
+
763
+ const children: XMLElement[] = [];
764
+
765
+ // Add all abstract numberings
766
+ const abstractNumberings = this.getAllAbstractNumberings();
767
+ abstractNumberings.forEach((abstractNum) => {
768
+ children.push(abstractNum.toXML());
769
+ });
770
+
771
+ // Add all numbering instances
772
+ const instances = this.getAllInstances();
773
+ instances.forEach((instance) => {
774
+ children.push(instance.toXML());
775
+ });
776
+
777
+ const numbering = XMLBuilder.w(
778
+ 'numbering',
779
+ {
780
+ 'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
781
+ 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
782
+ },
783
+ children
784
+ );
785
+
786
+ builder.element(numbering.name, numbering.attributes, numbering.children);
787
+
788
+ // Generate XML with declaration
789
+ return builder.build(true);
790
+ }
791
+
792
+ /**
793
+ * Generates the numbering.xml as XMLElement (for API compatibility)
794
+ */
795
+ toXML(): XMLElement {
796
+ const children: XMLElement[] = [];
797
+
798
+ // Add all abstract numberings
799
+ const abstractNumberings = this.getAllAbstractNumberings();
800
+ abstractNumberings.forEach((abstractNum) => {
801
+ children.push(abstractNum.toXML());
802
+ });
803
+
804
+ // Add all numbering instances
805
+ const instances = this.getAllInstances();
806
+ instances.forEach((instance) => {
807
+ children.push(instance.toXML());
808
+ });
809
+
810
+ return XMLBuilder.w(
811
+ 'numbering',
812
+ {
813
+ 'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
814
+ 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
815
+ },
816
+ children
817
+ );
818
+ }
819
+
820
+ /**
821
+ * Creates a numbering manager with default numbering definitions
822
+ */
823
+ static create(): NumberingManager {
824
+ return new NumberingManager(false);
825
+ }
826
+
827
+ /**
828
+ * Creates an empty numbering manager
829
+ */
830
+ static createEmpty(): NumberingManager {
831
+ return new NumberingManager(false);
832
+ }
833
+ }