@portone/docx-editor 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/CONTRIBUTING.md +5 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +13 -29
  5. package/dist/core.d.ts +23 -8
  6. package/dist/core.js +9 -4
  7. package/dist/docx/cloning.d.ts +38 -0
  8. package/dist/docx/cloning.js +74 -0
  9. package/dist/docx/commentOnlyChange.d.ts +12 -7
  10. package/dist/docx/commentOnlyChange.js +9 -156
  11. package/dist/docx/comments/constants.d.ts +0 -1
  12. package/dist/docx/comments/constants.js +0 -2
  13. package/dist/docx/comments/grammar.d.ts +10 -4
  14. package/dist/docx/comments/grammar.js +12 -2
  15. package/dist/docx/comments/{verifying.d.ts → parts.d.ts} +26 -18
  16. package/dist/docx/comments/parts.js +279 -0
  17. package/dist/docx/comments/people.d.ts +11 -2
  18. package/dist/docx/comments/people.js +16 -92
  19. package/dist/docx/comments/policy.d.ts +7 -0
  20. package/dist/docx/comments/policy.js +43 -0
  21. package/dist/docx/comments/reading.js +8 -10
  22. package/dist/docx/comments/writing.d.ts +19 -7
  23. package/dist/docx/comments/writing.js +59 -112
  24. package/dist/docx/documentSettings.d.ts +7 -0
  25. package/dist/docx/documentSettings.js +10 -2
  26. package/dist/docx/exportDocx.d.ts +40 -5
  27. package/dist/docx/exportDocx.js +88 -74
  28. package/dist/docx/exportRefs.d.ts +5 -2
  29. package/dist/docx/exportRefs.js +3 -1
  30. package/dist/docx/fidelity.d.ts +43 -0
  31. package/dist/docx/fidelity.js +64 -0
  32. package/dist/docx/formatting/attrs.d.ts +45 -0
  33. package/dist/docx/formatting/attrs.js +86 -0
  34. package/dist/docx/formatting/context.d.ts +39 -0
  35. package/dist/docx/formatting/context.js +60 -0
  36. package/dist/docx/formatting/direct.d.ts +6 -5
  37. package/dist/docx/formatting/direct.js +52 -78
  38. package/dist/docx/formatting/resolve.d.ts +51 -0
  39. package/dist/docx/formatting/resolve.js +160 -0
  40. package/dist/docx/formatting/runProperties.d.ts +93 -0
  41. package/dist/docx/formatting/runProperties.js +316 -0
  42. package/dist/docx/formatting/styles.d.ts +25 -2
  43. package/dist/docx/formatting/styles.js +87 -6
  44. package/dist/docx/formatting/tabStops.js +7 -22
  45. package/dist/docx/formatting.d.ts +4 -1
  46. package/dist/docx/formatting.js +4 -1
  47. package/dist/docx/headersFooters.js +6 -13
  48. package/dist/docx/hyperlink.d.ts +14 -4
  49. package/dist/docx/hyperlink.js +4 -1
  50. package/dist/docx/identities.d.ts +67 -0
  51. package/dist/docx/identities.js +174 -0
  52. package/dist/docx/importDocx.d.ts +12 -1
  53. package/dist/docx/importDocx.js +90 -88
  54. package/dist/docx/importParagraph.d.ts +1 -1
  55. package/dist/docx/importParagraph.js +4 -3
  56. package/dist/docx/importTable.d.ts +2 -2
  57. package/dist/docx/importTable.js +69 -56
  58. package/dist/docx/invariants.d.ts +33 -0
  59. package/dist/docx/invariants.js +269 -0
  60. package/dist/docx/media.d.ts +6 -4
  61. package/dist/docx/media.js +6 -37
  62. package/dist/docx/newLists.d.ts +50 -0
  63. package/dist/docx/newLists.js +59 -0
  64. package/dist/docx/notes.js +3 -7
  65. package/dist/docx/numberingPlanner.d.ts +11 -0
  66. package/dist/docx/numberingPlanner.js +50 -0
  67. package/dist/docx/packageParts.d.ts +42 -0
  68. package/dist/docx/packageParts.js +135 -0
  69. package/dist/docx/pageGeometry.d.ts +2 -0
  70. package/dist/docx/pageGeometry.js +18 -9
  71. package/dist/docx/paraProps.d.ts +10 -22
  72. package/dist/docx/paraProps.js +47 -76
  73. package/dist/docx/partPlan.d.ts +36 -0
  74. package/dist/docx/partPlan.js +59 -0
  75. package/dist/docx/protectionPolicy.d.ts +127 -0
  76. package/dist/docx/protectionPolicy.js +169 -0
  77. package/dist/docx/relationships.d.ts +1 -1
  78. package/dist/docx/relationships.js +8 -13
  79. package/dist/docx/runProps.d.ts +9 -22
  80. package/dist/docx/runProps.js +15 -168
  81. package/dist/docx/scan.js +20 -52
  82. package/dist/docx/sdt.js +9 -34
  83. package/dist/docx/sdtProps.d.ts +8 -1
  84. package/dist/docx/sdtProps.js +10 -0
  85. package/dist/docx/serializeBlock.d.ts +2 -0
  86. package/dist/docx/serializeBlock.js +8 -5
  87. package/dist/docx/serializeTable.js +32 -15
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +53 -17
  90. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  91. package/dist/docx/tableFormatting/conditions.js +196 -0
  92. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  93. package/dist/docx/tableFormatting/editing.js +111 -143
  94. package/dist/docx/tableFormatting/reading.d.ts +63 -23
  95. package/dist/docx/tableFormatting/reading.js +132 -38
  96. package/dist/docx/tableFormatting.d.ts +1 -0
  97. package/dist/docx/tableFormatting.js +1 -0
  98. package/dist/docx/tableTemplate.js +30 -19
  99. package/dist/download.d.ts +8 -5
  100. package/dist/download.js +2 -0
  101. package/dist/editor/clipboard/inlineFormatting.js +19 -30
  102. package/dist/editor/commands/comments/editing.d.ts +6 -2
  103. package/dist/editor/commands/comments/editing.js +19 -31
  104. package/dist/editor/commands/exportQueries.d.ts +15 -0
  105. package/dist/editor/commands/exportQueries.js +14 -0
  106. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  107. package/dist/editor/commands/fidelityQueries.js +8 -0
  108. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  109. package/dist/editor/commands/formatting/editing.js +26 -108
  110. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  111. package/dist/editor/commands/formatting/propertyCommands.js +108 -0
  112. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  113. package/dist/editor/commands/formatting/shared.js +5 -2
  114. package/dist/editor/commands/indentCommands.js +5 -4
  115. package/dist/editor/commands/index.d.ts +11 -0
  116. package/dist/editor/commands/index.js +5 -0
  117. package/dist/editor/commands/linkCommands.js +5 -6
  118. package/dist/editor/commands/listCommands.d.ts +6 -3
  119. package/dist/editor/commands/listCommands.js +73 -30
  120. package/dist/editor/commands/lockCommands.d.ts +7 -1
  121. package/dist/editor/commands/lockCommands.js +35 -46
  122. package/dist/editor/commands/paragraphCommands.js +33 -31
  123. package/dist/editor/commands/spacingCommands.js +1 -1
  124. package/dist/editor/createEditor.d.ts +17 -29
  125. package/dist/editor/createEditor.js +58 -58
  126. package/dist/editor/documentStyles.d.ts +11 -35
  127. package/dist/editor/documentStyles.js +9 -52
  128. package/dist/editor/editorDocument.d.ts +57 -0
  129. package/dist/editor/editorDocument.js +86 -0
  130. package/dist/editor/externalClipboard.d.ts +13 -2
  131. package/dist/editor/externalClipboard.js +76 -75
  132. package/dist/editor/insertTable.js +4 -3
  133. package/dist/editor/paragraphEdits.d.ts +27 -19
  134. package/dist/editor/paragraphEdits.js +18 -19
  135. package/dist/editor/paragraphPlacement.d.ts +11 -0
  136. package/dist/editor/paragraphPlacement.js +18 -0
  137. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  138. package/dist/editor/plugins/displayDerivation.js +78 -0
  139. package/dist/editor/plugins/imagePaste.js +11 -7
  140. package/dist/editor/plugins/keymap.js +18 -3
  141. package/dist/editor/plugins/numberingDecorations.d.ts +14 -14
  142. package/dist/editor/plugins/numberingDecorations.js +50 -19
  143. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  144. package/dist/editor/plugins/paragraphDisplay.js +57 -0
  145. package/dist/editor/plugins/tabLayout.js +1 -1
  146. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  147. package/dist/editor/plugins/tableDisplay.js +30 -0
  148. package/dist/index.d.ts +2 -0
  149. package/dist/model/format.d.ts +80 -8
  150. package/dist/model/format.js +100 -22
  151. package/dist/model/tabStops.d.ts +9 -0
  152. package/dist/model/tabStops.js +18 -0
  153. package/dist/numbering/listRegistry.d.ts +37 -0
  154. package/dist/numbering/listRegistry.js +112 -0
  155. package/dist/numbering/listTemplate.d.ts +26 -17
  156. package/dist/numbering/listTemplate.js +88 -25
  157. package/dist/numbering/markers.d.ts +15 -6
  158. package/dist/numbering/markers.js +26 -59
  159. package/dist/numbering/parseNumbering.d.ts +84 -6
  160. package/dist/numbering/parseNumbering.js +131 -44
  161. package/dist/numbering/spellers.d.ts +15 -0
  162. package/dist/numbering/spellers.js +96 -0
  163. package/dist/numbering/writeNumbering.d.ts +8 -7
  164. package/dist/numbering/writeNumbering.js +28 -39
  165. package/dist/ooxml/childOrder.d.ts +34 -0
  166. package/dist/ooxml/childOrder.js +496 -0
  167. package/dist/ooxml/conformance.d.ts +28 -0
  168. package/dist/ooxml/conformance.js +20 -0
  169. package/dist/ooxml/element.d.ts +24 -11
  170. package/dist/ooxml/element.js +31 -12
  171. package/dist/ooxml/errors.d.ts +6 -5
  172. package/dist/ooxml/image.d.ts +4 -0
  173. package/dist/ooxml/image.js +2 -5
  174. package/dist/ooxml/names.d.ts +9 -0
  175. package/dist/ooxml/names.js +8 -1
  176. package/dist/ooxml/partSplice.d.ts +67 -0
  177. package/dist/ooxml/partSplice.js +176 -0
  178. package/dist/ooxml/props.d.ts +112 -0
  179. package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
  180. package/dist/ooxml/simpleTypes.d.ts +103 -0
  181. package/dist/ooxml/simpleTypes.js +182 -0
  182. package/dist/ooxml/tabStops.js +8 -27
  183. package/dist/ooxml/tagScan.d.ts +34 -0
  184. package/dist/ooxml/tagScan.js +108 -0
  185. package/dist/ooxml/units.d.ts +25 -13
  186. package/dist/ooxml/units.js +65 -31
  187. package/dist/ooxml/xml.d.ts +35 -15
  188. package/dist/ooxml/xml.js +43 -11
  189. package/dist/page/PageGuides.d.ts +1 -1
  190. package/dist/page/PageGuides.js +0 -13
  191. package/dist/page/blockKinds.d.ts +61 -0
  192. package/dist/page/blockKinds.js +11 -0
  193. package/dist/page/kinds/index.d.ts +6 -0
  194. package/dist/page/kinds/index.js +10 -0
  195. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  196. package/dist/page/kinds/paragraphKind.js +71 -0
  197. package/dist/page/kinds/tableKind.d.ts +10 -0
  198. package/dist/page/kinds/tableKind.js +178 -0
  199. package/dist/page/measureBlocks.d.ts +3 -14
  200. package/dist/page/measureBlocks.js +24 -45
  201. package/dist/page/pageDecorations.d.ts +27 -44
  202. package/dist/page/pageDecorations.js +70 -152
  203. package/dist/page/pageLayout.d.ts +2 -29
  204. package/dist/page/pageLayout.js +39 -16
  205. package/dist/page/usePageLayout.d.ts +2 -24
  206. package/dist/page/usePageLayout.js +5 -48
  207. package/dist/schema/attrRoles.d.ts +25 -18
  208. package/dist/schema/attrRoles.js +110 -68
  209. package/dist/schema/displayDerivation.d.ts +82 -0
  210. package/dist/schema/displayDerivation.js +130 -0
  211. package/dist/schema/docxSchema.d.ts +3 -0
  212. package/dist/schema/docxSchema.js +58 -17
  213. package/dist/schema/editGuard.d.ts +1 -1
  214. package/dist/schema/guards.d.ts +37 -14
  215. package/dist/schema/guards.js +29 -3
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +11 -44
  219. package/dist/schema/locks.js +0 -9
  220. package/dist/schema/preservedGuards.d.ts +26 -6
  221. package/dist/schema/preservedGuards.js +31 -1
  222. package/dist/schema/protection.d.ts +7 -1
  223. package/dist/schema/protection.js +2 -1
  224. package/dist/schema/sourceEquality.d.ts +1 -9
  225. package/dist/schema/sourceEquality.js +1 -27
  226. package/dist/styles/classNames.d.ts +13 -2
  227. package/dist/styles/classNames.js +13 -2
  228. package/dist/styles/inlineStyle.js +15 -6
  229. package/dist/styles.css +16 -18
  230. package/dist/table/cellFormatting.d.ts +8 -1
  231. package/dist/table/cellFormatting.js +11 -20
  232. package/dist/table/commands.js +20 -18
  233. package/dist/table/format.d.ts +3 -0
  234. package/dist/table/format.js +3 -9
  235. package/dist/table/gridBorders.d.ts +24 -30
  236. package/dist/table/gridBorders.js +35 -59
  237. package/dist/table/merge.d.ts +2 -6
  238. package/dist/table/merge.js +5 -5
  239. package/package.json +7 -3
  240. package/dist/docx/comments/contentTypes.d.ts +0 -7
  241. package/dist/docx/comments/contentTypes.js +0 -38
  242. package/dist/docx/comments/verifying.js +0 -206
  243. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  244. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  245. package/dist/docx/propsXml.d.ts +0 -67
  246. package/dist/docx/uniqueControls.d.ts +0 -14
  247. package/dist/docx/uniqueControls.js +0 -62
  248. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  249. package/dist/editor/plugins/commentReservations.js +0 -26
  250. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  251. package/dist/editor/plugins/styledParagraphs.js +0 -65
  252. package/dist/page/tableMeasurements.d.ts +0 -18
  253. package/dist/page/tableMeasurements.js +0 -119
@@ -3,71 +3,31 @@ import { listFor } from "./listTemplate.js";
3
3
  import {
4
4
  levelIndentPt
5
5
  } from "./parseNumbering.js";
6
- var ROMAN = [
7
- [1e3, "M"],
8
- [900, "CM"],
9
- [500, "D"],
10
- [400, "CD"],
11
- [100, "C"],
12
- [90, "XC"],
13
- [50, "L"],
14
- [40, "XL"],
15
- [10, "X"],
16
- [9, "IX"],
17
- [5, "V"],
18
- [4, "IV"],
19
- [1, "I"]
20
- ];
21
- function toRoman(value) {
22
- let rest = value;
23
- let out = "";
24
- for (const [amount, sign] of ROMAN) {
25
- while (rest >= amount) {
26
- out += sign;
27
- rest -= amount;
28
- }
29
- }
30
- return out;
31
- }
32
- function toLetters(value) {
33
- const index = (value - 1) % 26;
34
- const repeat = Math.floor((value - 1) / 26) + 1;
35
- return String.fromCharCode(65 + index).repeat(repeat);
36
- }
6
+ import { spellNumber } from "./spellers.js";
37
7
  var MAX_MARKER_CHARS = 64;
38
- var MAX_SPELLED_VALUE = {
39
- decimal: Number.POSITIVE_INFINITY,
40
- bullet: Number.POSITIVE_INFINITY,
41
- lowerLetter: 26 * MAX_MARKER_CHARS,
42
- upperLetter: 26 * MAX_MARKER_CHARS,
43
- lowerRoman: 1e3 * MAX_MARKER_CHARS
44
- };
45
- function formatNumber(value, format) {
46
- if (value < 1 || value > MAX_SPELLED_VALUE[format]) return `${value}`;
47
- switch (format) {
48
- case "lowerLetter":
49
- return toLetters(value).toLowerCase();
50
- case "upperLetter":
51
- return toLetters(value);
52
- case "lowerRoman":
53
- return toRoman(value).toLowerCase();
54
- default:
55
- return `${value}`;
56
- }
57
- }
58
- function fillLevels(text, list, counters) {
8
+ function fillLevels(text, list, counters, legal) {
59
9
  return text.replaceAll(/%([1-9])/g, (_, digit) => {
60
10
  const ilvl = Number(digit) - 1;
61
11
  const level = list.levels.get(ilvl);
62
12
  if (!level) return "";
63
- return formatNumber(counters.get(ilvl) ?? level.start, level.format);
13
+ const count = counters.get(ilvl) ?? level.start;
14
+ return spellNumber(
15
+ count,
16
+ legal ? "decimal" : level.format,
17
+ MAX_MARKER_CHARS
18
+ );
64
19
  });
65
20
  }
21
+ function restartedBy(level, ilvl) {
22
+ return level?.restartAfterLevel ?? ilvl - 1;
23
+ }
66
24
  function advance(counters, list, ilvl, start) {
67
25
  const current = counters.get(ilvl);
68
26
  counters.set(ilvl, current === void 0 ? start : current + 1);
69
27
  for (const deeper of [...counters.keys(), ...list.levels.keys()]) {
70
- if (deeper > ilvl) counters.delete(deeper);
28
+ if (deeper > ilvl && restartedBy(list.levels.get(deeper), deeper) >= ilvl) {
29
+ counters.delete(deeper);
30
+ }
71
31
  }
72
32
  }
73
33
  var MARKER_SEGMENTER = new Intl.Segmenter();
@@ -87,17 +47,24 @@ function computeMarkers(paragraphs, numbering) {
87
47
  return paragraphs.map((ref) => {
88
48
  if (!ref) return null;
89
49
  const list = listFor(numbering, ref.numId);
90
- const level = list.levels.get(ref.ilvl);
91
- if (!level) return null;
50
+ const level = list?.levels.get(ref.ilvl);
51
+ if (!list || !level) return null;
92
52
  let counters = countersByList.get(ref.numId);
93
53
  if (!counters) {
94
54
  counters = /* @__PURE__ */ new Map();
95
55
  countersByList.set(ref.numId, counters);
96
56
  }
97
57
  advance(counters, list, ref.ilvl, level.start);
98
- const shape = level.format === "bullet" ? level.text : fillLevels(level.text, list, counters);
99
- const text = capped(shape);
100
- return text ? { text, indent: levelIndentPt(level.indent) } : null;
58
+ const shape = level.format === "bullet" ? level.text : fillLevels(level.text, list, counters, level.legal);
59
+ const drawn = capped(shape);
60
+ if (!drawn) return null;
61
+ return {
62
+ text: level.suffix === "space" ? `${drawn} ` : drawn,
63
+ indent: levelIndentPt(level.indent),
64
+ suffix: level.suffix,
65
+ align: level.align,
66
+ run: level.run
67
+ };
101
68
  });
102
69
  }
103
70
  export {
@@ -5,10 +5,11 @@
5
5
  * numbering.xml itself goes out as the original bytes on export, so this reading never
6
6
  * disturbs the file.
7
7
  */
8
+ import type { RunFormat } from "../model/format";
8
9
  import type { TabStopDirective } from "../model/tabStops";
9
- /** The number formats actually used across every fixture */
10
- declare const NUMBER_FORMATS: readonly ["decimal", "bullet", "lowerLetter", "upperLetter", "lowerRoman"];
11
- export type NumberFormat = (typeof NUMBER_FORMATS)[number];
10
+ import { type XmlParser } from "../ooxml/xml";
11
+ import { type NumberFormat } from "./spellers";
12
+ export type { NumberFormat } from "./spellers";
12
13
  /**
13
14
  * The indent a level defines. The unit is twips (1/20 of a point), and a slot that is
14
15
  * not recorded is null
@@ -19,6 +20,10 @@ export interface LevelIndent {
19
20
  hangingTwips: number | null;
20
21
  firstLineTwips: number | null;
21
22
  }
23
+ /** What stands between a level's number and the text of the paragraph (`w:suff`, §17.9.28) */
24
+ export type LevelSuffix = "tab" | "space" | "nothing";
25
+ /** Where a level's number sits in the space kept for it (`w:lvlJc`, §17.9.7) */
26
+ export type LevelAlign = "left" | "center" | "right";
22
27
  export interface NumberingLevel {
23
28
  format: NumberFormat;
24
29
  /**
@@ -31,14 +36,58 @@ export interface NumberingLevel {
31
36
  indent: LevelIndent | null;
32
37
  /** Custom stops contributed by this level's paragraph properties. */
33
38
  tabStops?: readonly TabStopDirective[];
39
+ /**
40
+ * The deepest level whose advance sends this one back to its start (`w:lvlRestart`, §17.9.10),
41
+ * as a level number: any level above that one restarts it too. A level of -1 never restarts, and
42
+ * null is the default, the level right above this one.
43
+ */
44
+ restartAfterLevel: number | null;
45
+ /** Every level in this level's text spelled as a decimal, whatever format it counts in (`w:isLgl`, §17.9.4) */
46
+ legal: boolean;
47
+ suffix: LevelSuffix;
48
+ align: LevelAlign;
49
+ /**
50
+ * The formatting of the number itself (`w:rPr`, §17.9.24). It dresses the number alone and
51
+ * never the text of the paragraph the number stands in front of.
52
+ * Null where the level writes none, and where the caller handed in no reader for it.
53
+ */
54
+ run: RunFormat | null;
34
55
  }
56
+ /**
57
+ * How a level's `w:rPr` is read into display values.
58
+ * The vocabulary of run properties belongs with the reader that holds styles.xml and the theme
59
+ * fonts, which this folder cannot reach, so it is handed in.
60
+ */
61
+ export type ReadLevelRun = (rPr: Element) => RunFormat | null;
35
62
  export interface NumberingList {
36
63
  /** The shape for each level (ilvl) */
37
- levels: Map<number, NumberingLevel>;
64
+ levels: ReadonlyMap<number, NumberingLevel>;
65
+ }
66
+ /**
67
+ * A level a list started while editing is registered with.
68
+ *
69
+ * It is a level minus the two things the writer of a new definition cannot spell where it stands:
70
+ * the formatting of the number (`w:rPr`) and the tab stops its paragraphs contribute (`w:tabs`)
71
+ * are written by `docx/`, which stands above this folder. Leaving them out of what may be
72
+ * registered is what keeps a registered definition from reaching the file with a part of it
73
+ * silently dropped.
74
+ */
75
+ export interface NewListLevel extends NumberingLevel {
76
+ readonly run: null;
77
+ readonly tabStops?: undefined;
78
+ }
79
+ /** A list started while editing, whose definition the export writes into numbering.xml as it stands */
80
+ export interface NewList {
81
+ levels: ReadonlyMap<number, NewListLevel>;
38
82
  }
39
83
  export interface Numbering {
40
84
  /** The list for each numId */
41
85
  lists: Map<number, NumberingList>;
86
+ /**
87
+ * The lists started while editing, by the number each was given. numbering.xml defines none of
88
+ * them yet; the export writes the definition registered here.
89
+ */
90
+ added: ReadonlyMap<number, NewList>;
42
91
  }
43
92
  export declare const EMPTY_NUMBERING: Numbering;
44
93
  /**
@@ -61,5 +110,34 @@ export declare const NO_LEVEL_INDENT: LevelIndentPt;
61
110
  * OOXML rule).
62
111
  */
63
112
  export declare function levelIndentPt(indent: LevelIndent | null): LevelIndentPt;
64
- export declare function parseNumbering(xml: string | null): Numbering;
65
- export {};
113
+ export declare const LEVEL_SUFFIXES: readonly LevelSuffix[];
114
+ /** The justifications a number can take, which are the ones `w:lvlJc` offers a number */
115
+ export declare const LEVEL_ALIGNS: readonly LevelAlign[];
116
+ /**
117
+ * The list each numbering style names, by the style's id.
118
+ *
119
+ * A numbering style is never worn by a paragraph: it is a name on a list, which an abstract
120
+ * definition reaches through `w:numStyleLink` (§17.9.21). The map is built from styles.xml, which
121
+ * this folder cannot read for itself.
122
+ */
123
+ export type NumberingStyleLinks = ReadonlyMap<string, number>;
124
+ /** What a caller may say about reading numbering beyond handing over the XML */
125
+ export interface NumberingOptions {
126
+ /**
127
+ * The parser the numbering XML is read through. Left out, the `DOMParser` global is used, and a
128
+ * runtime carrying none refuses the read with `no-xml-parser`.
129
+ */
130
+ xmlParser?: XmlParser;
131
+ /**
132
+ * The list each numbering style of the document names. Left out, a definition deferring to a
133
+ * numbering style is read through the definition that declares it stands behind that style.
134
+ */
135
+ links?: NumberingStyleLinks;
136
+ /** How the formatting a level puts on its number is read. Left out, no level carries any */
137
+ readRun?: ReadLevelRun;
138
+ }
139
+ /**
140
+ * The lists this numbering XML defines. `null` is the document that has no numbering part, which
141
+ * defines none and asks nothing of a parser.
142
+ */
143
+ export declare function parseNumbering(xml: string | null, options?: NumberingOptions): Numbering;
@@ -1,34 +1,44 @@
1
1
  // src/numbering/parseNumbering.ts
2
+ import {
3
+ ST_DecimalNumber,
4
+ ST_SignedTwipsMeasure,
5
+ ST_TwipsMeasure
6
+ } from "../ooxml/simpleTypes.js";
2
7
  import { readTabStopDirectives } from "../ooxml/tabStops.js";
3
- import { childValue, round, wAttr } from "../ooxml/units.js";
4
- import { childByLocalName, elementChildren, parseXml } from "../ooxml/xml.js";
5
- var NUMBER_FORMATS = [
6
- "decimal",
7
- "bullet",
8
- "lowerLetter",
9
- "upperLetter",
10
- "lowerRoman"
11
- ];
12
- var EMPTY_NUMBERING = { lists: /* @__PURE__ */ new Map() };
13
- function toInteger(value) {
14
- if (value === null) return null;
15
- const parsed = Number.parseInt(value, 10);
16
- return Number.isFinite(parsed) ? parsed : null;
17
- }
18
- function isNumberFormat(value) {
19
- return NUMBER_FORMATS.some((format) => format === value);
20
- }
8
+ import {
9
+ ALIGN_BY_JC,
10
+ childValue,
11
+ isOn,
12
+ twipsToPt,
13
+ wAttr
14
+ } from "../ooxml/units.js";
15
+ import {
16
+ childByLocalName,
17
+ elementChildren,
18
+ parseXml,
19
+ withXmlParser
20
+ } from "../ooxml/xml.js";
21
+ import { isNumberFormat } from "./spellers.js";
22
+ var NO_ADDED_LISTS = /* @__PURE__ */ new Map();
23
+ var EMPTY_NUMBERING = {
24
+ lists: /* @__PURE__ */ new Map(),
25
+ added: NO_ADDED_LISTS
26
+ };
21
27
  function indentOf(lvl) {
22
28
  const pPr = childByLocalName(lvl, "pPr");
23
29
  const ind = pPr ? childByLocalName(pPr, "ind") : null;
24
30
  if (!ind) return null;
25
- const startTwips = toInteger(wAttr(ind, "start") ?? wAttr(ind, "left"));
26
- const endTwips = toInteger(wAttr(ind, "end") ?? wAttr(ind, "right"));
31
+ const startTwips = ST_SignedTwipsMeasure.parse(
32
+ wAttr(ind, "start") ?? wAttr(ind, "left")
33
+ );
34
+ const endTwips = ST_SignedTwipsMeasure.parse(
35
+ wAttr(ind, "end") ?? wAttr(ind, "right")
36
+ );
27
37
  const indent = {
28
38
  startTwips,
29
39
  endTwips,
30
- hangingTwips: toInteger(wAttr(ind, "hanging")),
31
- firstLineTwips: toInteger(wAttr(ind, "firstLine"))
40
+ hangingTwips: ST_TwipsMeasure.parse(wAttr(ind, "hanging")),
41
+ firstLineTwips: ST_TwipsMeasure.parse(wAttr(ind, "firstLine"))
32
42
  };
33
43
  const empty = startTwips === null && endTwips === null && indent.hangingTwips === null && indent.firstLineTwips === null;
34
44
  return empty ? null : indent;
@@ -38,9 +48,6 @@ var NO_LEVEL_INDENT = {
38
48
  endPt: null,
39
49
  textIndentPt: null
40
50
  };
41
- function twipsToPt(twips) {
42
- return twips === null ? null : round(twips / 20);
43
- }
44
51
  function levelIndentPt(indent) {
45
52
  if (!indent) return NO_LEVEL_INDENT;
46
53
  const hangingPt = twipsToPt(indent.hangingTwips);
@@ -50,39 +57,68 @@ function levelIndentPt(indent) {
50
57
  textIndentPt: hangingPt !== null ? -hangingPt : twipsToPt(indent.firstLineTwips)
51
58
  };
52
59
  }
53
- function readLevel(lvl) {
60
+ var LEVEL_SUFFIXES = [
61
+ "tab",
62
+ "space",
63
+ "nothing"
64
+ ];
65
+ var LEVEL_ALIGNS = ["left", "center", "right"];
66
+ function suffixOf(lvl) {
67
+ const suffix = childValue(lvl, "suff");
68
+ return LEVEL_SUFFIXES.find((known) => known === suffix) ?? "tab";
69
+ }
70
+ function alignOf(lvl) {
71
+ const jc = childValue(lvl, "lvlJc");
72
+ const align = jc === null ? void 0 : ALIGN_BY_JC[jc];
73
+ return align === void 0 || align === "justify" ? "left" : align;
74
+ }
75
+ function restartAfterLevelOf(lvl) {
76
+ const restart = ST_DecimalNumber.parse(childValue(lvl, "lvlRestart"));
77
+ return restart === null ? null : restart - 1;
78
+ }
79
+ function readLevel(lvl, readRun) {
54
80
  const format = childValue(lvl, "numFmt");
55
81
  const pPr = childByLocalName(lvl, "pPr");
56
82
  const tabStops = readTabStopDirectives(pPr);
83
+ const rPr = childByLocalName(lvl, "rPr");
57
84
  return {
58
85
  format: isNumberFormat(format) ? format : "decimal",
59
86
  text: childValue(lvl, "lvlText") ?? "",
60
- start: toInteger(childValue(lvl, "start")) ?? 1,
87
+ start: ST_DecimalNumber.parse(childValue(lvl, "start")) ?? 1,
61
88
  indent: indentOf(lvl),
62
- ...tabStops.length === 0 ? {} : { tabStops }
89
+ ...tabStops.length === 0 ? {} : { tabStops },
90
+ restartAfterLevel: restartAfterLevelOf(lvl),
91
+ legal: isOn(lvl, "isLgl"),
92
+ suffix: suffixOf(lvl),
93
+ align: alignOf(lvl),
94
+ run: rPr && readRun ? readRun(rPr) : null
63
95
  };
64
96
  }
65
- function readLevels(parent) {
97
+ function readLevels(parent, readRun) {
66
98
  const levels = /* @__PURE__ */ new Map();
67
99
  for (const child of elementChildren(parent)) {
68
100
  if (child.localName !== "lvl") continue;
69
- const ilvl = toInteger(wAttr(child, "ilvl"));
70
- if (ilvl !== null) levels.set(ilvl, readLevel(child));
101
+ const ilvl = ST_DecimalNumber.parse(wAttr(child, "ilvl"));
102
+ if (ilvl !== null) levels.set(ilvl, readLevel(child, readRun));
71
103
  }
72
104
  return levels;
73
105
  }
74
- function readList(num, abstractLevels) {
75
- const abstractNumId = toInteger(childValue(num, "abstractNumId"));
106
+ function readList(num, abstractLevels, readRun) {
107
+ const abstractNumId = ST_DecimalNumber.parse(
108
+ childValue(num, "abstractNumId")
109
+ );
76
110
  if (abstractNumId === null) return null;
77
111
  const levels = new Map(abstractLevels.get(abstractNumId) ?? []);
78
112
  for (const child of elementChildren(num)) {
79
113
  if (child.localName !== "lvlOverride") continue;
80
- const ilvl = toInteger(wAttr(child, "ilvl"));
114
+ const ilvl = ST_DecimalNumber.parse(wAttr(child, "ilvl"));
81
115
  if (ilvl === null) continue;
82
116
  const replacement = childByLocalName(child, "lvl");
83
- const base = replacement ? readLevel(replacement) : levels.get(ilvl);
117
+ const base = replacement ? readLevel(replacement, readRun) : levels.get(ilvl);
84
118
  if (!base) continue;
85
- const startOverride = toInteger(childValue(child, "startOverride"));
119
+ const startOverride = ST_DecimalNumber.parse(
120
+ childValue(child, "startOverride")
121
+ );
86
122
  levels.set(
87
123
  ilvl,
88
124
  startOverride === null ? base : { ...base, start: startOverride }
@@ -90,26 +126,77 @@ function readList(num, abstractLevels) {
90
126
  }
91
127
  return { levels };
92
128
  }
93
- function parseNumbering(xml) {
129
+ var NO_STYLE_LINKS = /* @__PURE__ */ new Map();
130
+ function levelsOf(id, definitions, resolved) {
131
+ const path = /* @__PURE__ */ new Set();
132
+ let current = id;
133
+ let levels = /* @__PURE__ */ new Map();
134
+ while (!path.has(current)) {
135
+ const known = resolved.get(current);
136
+ if (known) {
137
+ levels = known;
138
+ break;
139
+ }
140
+ path.add(current);
141
+ const el = definitions.byId.get(current);
142
+ if (!el) break;
143
+ levels = readLevels(el, definitions.readRun);
144
+ const styleId = childValue(el, "numStyleLink");
145
+ if (levels.size > 0 || styleId === null) break;
146
+ const named = definitions.links.get(styleId);
147
+ const deferred = (named === void 0 ? void 0 : definitions.ofList.get(named)) ?? definitions.ofStyle.get(styleId);
148
+ if (deferred === void 0) break;
149
+ current = deferred;
150
+ }
151
+ for (const seen of path) resolved.set(seen, levels);
152
+ return levels;
153
+ }
154
+ function parseNumbering(xml, options) {
94
155
  if (xml === null) return EMPTY_NUMBERING;
156
+ return withXmlParser(options?.xmlParser, () => readNumbering(xml, options));
157
+ }
158
+ function readNumbering(xml, options) {
95
159
  const root = parseXml(xml).documentElement;
96
- const abstractLevels = /* @__PURE__ */ new Map();
160
+ const byId = /* @__PURE__ */ new Map();
161
+ const ofStyle = /* @__PURE__ */ new Map();
162
+ const ofList = /* @__PURE__ */ new Map();
97
163
  for (const child of elementChildren(root)) {
98
- if (child.localName !== "abstractNum") continue;
99
- const id = toInteger(wAttr(child, "abstractNumId"));
100
- if (id !== null) abstractLevels.set(id, readLevels(child));
164
+ if (child.localName === "abstractNum") {
165
+ const id = ST_DecimalNumber.parse(wAttr(child, "abstractNumId"));
166
+ if (id === null) continue;
167
+ byId.set(id, child);
168
+ const styleId = childValue(child, "styleLink");
169
+ if (styleId !== null) ofStyle.set(styleId, id);
170
+ } else if (child.localName === "num") {
171
+ const numId = ST_DecimalNumber.parse(wAttr(child, "numId"));
172
+ const id = ST_DecimalNumber.parse(childValue(child, "abstractNumId"));
173
+ if (numId !== null && id !== null) ofList.set(numId, id);
174
+ }
175
+ }
176
+ const definitions = {
177
+ byId,
178
+ ofList,
179
+ ofStyle,
180
+ links: options?.links ?? NO_STYLE_LINKS,
181
+ readRun: options?.readRun
182
+ };
183
+ const abstractLevels = /* @__PURE__ */ new Map();
184
+ for (const id of byId.keys()) {
185
+ abstractLevels.set(id, levelsOf(id, definitions, abstractLevels));
101
186
  }
102
187
  const lists = /* @__PURE__ */ new Map();
103
188
  for (const child of elementChildren(root)) {
104
189
  if (child.localName !== "num") continue;
105
- const numId = toInteger(wAttr(child, "numId"));
106
- const list = numId === null ? null : readList(child, abstractLevels);
190
+ const numId = ST_DecimalNumber.parse(wAttr(child, "numId"));
191
+ const list = numId === null ? null : readList(child, abstractLevels, options?.readRun);
107
192
  if (numId !== null && list) lists.set(numId, list);
108
193
  }
109
- return { lists };
194
+ return { lists, added: NO_ADDED_LISTS };
110
195
  }
111
196
  export {
112
197
  EMPTY_NUMBERING,
198
+ LEVEL_ALIGNS,
199
+ LEVEL_SUFFIXES,
113
200
  NO_LEVEL_INDENT,
114
201
  levelIndentPt,
115
202
  parseNumbering
@@ -0,0 +1,15 @@
1
+ /**
2
+ * How each number format spells the count of a list item.
3
+ *
4
+ * `w:numFmt` names some sixty formats (§17.18.59). The ones here are the ones a marker is drawn
5
+ * in; a level naming any other counts in decimal instead. A format belongs to `NumberFormat`
6
+ * because it has a speller here, so a marker can never be counted in a format nothing can write.
7
+ */
8
+ /** A `w:numFmt` this editor spells out. Every other one is drawn as a decimal */
9
+ export type NumberFormat = "decimal" | "decimalZero" | "bullet" | "lowerLetter" | "upperLetter" | "lowerRoman" | "upperRoman" | "ganada" | "koreanDigital" | "chineseCounting";
10
+ export declare function isNumberFormat(value: string | null): value is NumberFormat;
11
+ /**
12
+ * The count as this format writes it, and as a decimal where spelling it out would run past the
13
+ * length a marker is ever drawn at.
14
+ */
15
+ export declare function spellNumber(count: number, format: NumberFormat, maxChars: number): string;
@@ -0,0 +1,96 @@
1
+ // src/numbering/spellers.ts
2
+ var ROMAN = [
3
+ [1e3, "M"],
4
+ [900, "CM"],
5
+ [500, "D"],
6
+ [400, "CD"],
7
+ [100, "C"],
8
+ [90, "XC"],
9
+ [50, "L"],
10
+ [40, "XL"],
11
+ [10, "X"],
12
+ [9, "IX"],
13
+ [5, "V"],
14
+ [4, "IV"],
15
+ [1, "I"]
16
+ ];
17
+ function toRoman(count) {
18
+ let rest = count;
19
+ let out = "";
20
+ for (const [amount, sign] of ROMAN) {
21
+ while (rest >= amount) {
22
+ out += sign;
23
+ rest -= amount;
24
+ }
25
+ }
26
+ return out;
27
+ }
28
+ function repeated(symbols, count) {
29
+ const index = (count - 1) % symbols.length;
30
+ const times = Math.floor((count - 1) / symbols.length) + 1;
31
+ return (symbols[index] ?? "").repeat(times);
32
+ }
33
+ var LATIN = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
34
+ var GANADA = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
35
+ var KOREAN_DIGITS = "\uC601\uC77C\uC774\uC0BC\uC0AC\uC624\uC721\uCE60\uD314\uAD6C";
36
+ function digits(symbols, count) {
37
+ return Array.from(`${count}`, (digit) => symbols[Number(digit)] ?? "").join(
38
+ ""
39
+ );
40
+ }
41
+ var CHINESE_DIGITS = "\u3007\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D\u5341";
42
+ function chineseCounting(count) {
43
+ if (count <= 10) return CHINESE_DIGITS[count] ?? "";
44
+ if (count >= 100) return digits(CHINESE_DIGITS, count);
45
+ const tens = Math.floor(count / 10);
46
+ const unit = count % 10;
47
+ return (tens === 1 ? "" : CHINESE_DIGITS[tens] ?? "") + CHINESE_DIGITS[10] + (unit === 0 ? "" : CHINESE_DIGITS[unit] ?? "");
48
+ }
49
+ var POSITIONAL = Number.POSITIVE_INFINITY;
50
+ var NUMBER_SPELLERS = {
51
+ decimal: { spell: (count) => `${count}`, countsPerChar: POSITIONAL },
52
+ /** The counts up to nine written with a zero in front (§17.18.59 decimalZero) */
53
+ decimalZero: {
54
+ spell: (count) => count < 10 ? `0${count}` : `${count}`,
55
+ countsPerChar: POSITIONAL
56
+ },
57
+ /**
58
+ * A bullet counts nothing: its `w:lvlText` is the symbol itself. A `%n` slot naming a bullet
59
+ * level has no symbol to put there, so the count goes in as a decimal, as Word draws it.
60
+ */
61
+ bullet: { spell: (count) => `${count}`, countsPerChar: POSITIONAL },
62
+ lowerLetter: {
63
+ spell: (count) => repeated(LATIN, count).toLowerCase(),
64
+ countsPerChar: LATIN.length
65
+ },
66
+ upperLetter: {
67
+ spell: (count) => repeated(LATIN, count),
68
+ countsPerChar: LATIN.length
69
+ },
70
+ lowerRoman: {
71
+ spell: (count) => toRoman(count).toLowerCase(),
72
+ countsPerChar: 1e3
73
+ },
74
+ upperRoman: { spell: toRoman, countsPerChar: 1e3 },
75
+ ganada: {
76
+ spell: (count) => repeated(GANADA, count),
77
+ countsPerChar: GANADA.length
78
+ },
79
+ koreanDigital: {
80
+ spell: (count) => digits(KOREAN_DIGITS, count),
81
+ countsPerChar: POSITIONAL
82
+ },
83
+ chineseCounting: { spell: chineseCounting, countsPerChar: POSITIONAL }
84
+ };
85
+ function isNumberFormat(value) {
86
+ return value !== null && Object.hasOwn(NUMBER_SPELLERS, value);
87
+ }
88
+ function spellNumber(count, format, maxChars) {
89
+ const speller = NUMBER_SPELLERS[format];
90
+ if (count < 1 || count > speller.countsPerChar * maxChars) return `${count}`;
91
+ return speller.spell(count);
92
+ }
93
+ export {
94
+ isNumberFormat,
95
+ spellNumber
96
+ };
@@ -1,13 +1,14 @@
1
1
  /**
2
- * Splices the definitions of newly started lists into the original numbering.xml.
2
+ * Splices the definitions the newly started lists were registered with into the original
3
+ * numbering.xml.
3
4
  *
4
- * Not one character of the original is altered; new elements are merely slotted in at
5
- * two places.
6
- * OOXML requires the definitions (`abstractNum`) to come before the numbers (`num`), so
7
- * the definitions go in front of the first number and the numbers go at the very end.
5
+ * Not one character of the original is altered; new elements are merely slotted in.
6
+ * OOXML requires the definitions (`abstractNum`) to come before the numbers (`num`), and
7
+ * `CHILD_ORDER.numbering` is what places each where it belongs among what the part holds.
8
8
  */
9
+ import type { NewList } from "./parseNumbering";
9
10
  /**
10
- * numbering.xml with a standard template definition added for every list number.
11
+ * numbering.xml with the definition each of these lists was started with added under its number.
11
12
  * When there is nothing to add, the original string is returned unchanged.
12
13
  */
13
- export declare function addListDefinitions(xml: string, numIds: readonly number[]): string;
14
+ export declare function addListDefinitions(xml: string, lists: ReadonlyMap<number, NewList>): string;