@portone/docx-editor 0.3.0 → 0.5.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 (217) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/assets/editor.png +0 -0
  3. package/dist/DocxEditor.d.ts +1 -1
  4. package/dist/DocxEditor.js +45 -25
  5. package/dist/core.d.ts +1 -1
  6. package/dist/docx/cloning.js +4 -9
  7. package/dist/docx/commentOnlyChange.d.ts +5 -3
  8. package/dist/docx/comments/grammar.d.ts +27 -10
  9. package/dist/docx/comments/grammar.js +18 -67
  10. package/dist/docx/comments/model.d.ts +5 -6
  11. package/dist/docx/comments/model.js +0 -9
  12. package/dist/docx/comments/parts.d.ts +16 -10
  13. package/dist/docx/comments/parts.js +65 -13
  14. package/dist/docx/comments/people.d.ts +11 -3
  15. package/dist/docx/comments/people.js +20 -6
  16. package/dist/docx/comments/policy.js +7 -1
  17. package/dist/docx/comments/reading.d.ts +4 -2
  18. package/dist/docx/comments/reading.js +1 -14
  19. package/dist/docx/comments/writing.js +78 -35
  20. package/dist/docx/exportDocx.js +30 -18
  21. package/dist/docx/exportRefs.d.ts +8 -3
  22. package/dist/docx/exportRefs.js +2 -1
  23. package/dist/docx/fidelity.js +24 -10
  24. package/dist/docx/fields.d.ts +42 -0
  25. package/dist/docx/fields.js +76 -0
  26. package/dist/docx/formatting/attrs.d.ts +20 -2
  27. package/dist/docx/formatting/attrs.js +57 -2
  28. package/dist/docx/formatting/context.d.ts +10 -2
  29. package/dist/docx/formatting/context.js +23 -5
  30. package/dist/docx/formatting/resolve.d.ts +10 -4
  31. package/dist/docx/formatting/resolve.js +31 -11
  32. package/dist/docx/formatting/styles.d.ts +25 -2
  33. package/dist/docx/formatting/styles.js +84 -3
  34. package/dist/docx/headersFooters.d.ts +89 -20
  35. package/dist/docx/headersFooters.js +163 -172
  36. package/dist/docx/hyperlink.d.ts +14 -4
  37. package/dist/docx/hyperlink.js +4 -1
  38. package/dist/docx/importDocx.d.ts +1 -1
  39. package/dist/docx/importDocx.js +135 -74
  40. package/dist/docx/importParagraph.d.ts +16 -4
  41. package/dist/docx/importParagraph.js +87 -93
  42. package/dist/docx/importPolicy.d.ts +70 -0
  43. package/dist/docx/importPolicy.js +190 -0
  44. package/dist/docx/importPreserved.d.ts +30 -0
  45. package/dist/docx/importPreserved.js +54 -0
  46. package/dist/docx/importTable.d.ts +2 -2
  47. package/dist/docx/importTable.js +107 -66
  48. package/dist/docx/invariants.js +42 -31
  49. package/dist/docx/newLists.d.ts +30 -0
  50. package/dist/docx/newLists.js +24 -1
  51. package/dist/docx/notes.d.ts +6 -1
  52. package/dist/docx/notes.js +6 -16
  53. package/dist/docx/numberingPlanner.d.ts +6 -3
  54. package/dist/docx/numberingPlanner.js +38 -7
  55. package/dist/docx/pageGeometry.d.ts +5 -14
  56. package/dist/docx/pageGeometry.js +0 -12
  57. package/dist/docx/protectionPolicy.d.ts +13 -5
  58. package/dist/docx/protectionPolicy.js +32 -22
  59. package/dist/docx/scan.d.ts +18 -8
  60. package/dist/docx/scan.js +17 -11
  61. package/dist/docx/sections.d.ts +126 -0
  62. package/dist/docx/sections.js +207 -0
  63. package/dist/docx/serializeBlock.d.ts +13 -4
  64. package/dist/docx/serializeBlock.js +16 -18
  65. package/dist/docx/serializeParagraph.d.ts +4 -0
  66. package/dist/docx/serializeParagraph.js +1 -0
  67. package/dist/docx/serializePreserved.d.ts +14 -0
  68. package/dist/docx/serializePreserved.js +24 -0
  69. package/dist/docx/serializeStory.d.ts +17 -0
  70. package/dist/docx/serializeStory.js +17 -0
  71. package/dist/docx/serializeTable.js +10 -13
  72. package/dist/docx/session.d.ts +24 -7
  73. package/dist/docx/session.js +37 -6
  74. package/dist/docx/story.d.ts +140 -0
  75. package/dist/docx/story.js +237 -0
  76. package/dist/docx/storyProjection.js +6 -1
  77. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  78. package/dist/docx/tableFormatting/conditions.js +196 -0
  79. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  80. package/dist/docx/tableFormatting/editing.js +4 -5
  81. package/dist/docx/tableFormatting/reading.d.ts +49 -19
  82. package/dist/docx/tableFormatting/reading.js +109 -35
  83. package/dist/docx/tableFormatting.d.ts +1 -0
  84. package/dist/docx/tableFormatting.js +1 -0
  85. package/dist/docx/tableTemplate.js +12 -12
  86. package/dist/editor/clipboard/htmlReader.d.ts +30 -0
  87. package/dist/editor/clipboard/htmlReader.js +302 -0
  88. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  89. package/dist/editor/clipboard/internalChannel.js +58 -0
  90. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  91. package/dist/editor/clipboard/normalizers.js +199 -0
  92. package/dist/editor/clipboard/parser.d.ts +34 -0
  93. package/dist/editor/clipboard/parser.js +58 -0
  94. package/dist/editor/clipboard/plugin.d.ts +21 -0
  95. package/dist/editor/clipboard/plugin.js +320 -0
  96. package/dist/editor/clipboard/readContext.d.ts +24 -0
  97. package/dist/editor/clipboard/readContext.js +19 -0
  98. package/dist/editor/clipboard/readers.d.ts +33 -0
  99. package/dist/editor/clipboard/readers.js +37 -0
  100. package/dist/editor/commands/comments/editing.d.ts +26 -6
  101. package/dist/editor/commands/comments/editing.js +175 -105
  102. package/dist/editor/commands/comments/model.d.ts +34 -17
  103. package/dist/editor/commands/comments/model.js +11 -0
  104. package/dist/editor/commands/comments/reading.d.ts +5 -3
  105. package/dist/editor/commands/comments/reading.js +8 -58
  106. package/dist/editor/commands/formatting/propertyCommands.js +6 -1
  107. package/dist/editor/commands/index.d.ts +2 -2
  108. package/dist/editor/commands/index.js +2 -0
  109. package/dist/editor/commands/listCommands.d.ts +12 -3
  110. package/dist/editor/commands/listCommands.js +76 -30
  111. package/dist/editor/commands/noteQueries.d.ts +22 -5
  112. package/dist/editor/commands/noteQueries.js +34 -6
  113. package/dist/editor/commands/paragraphCommands.js +6 -1
  114. package/dist/editor/createEditor.js +14 -4
  115. package/dist/editor/documentStyles.d.ts +26 -4
  116. package/dist/editor/documentStyles.js +9 -4
  117. package/dist/editor/editorDocument.d.ts +7 -6
  118. package/dist/editor/editorDocument.js +18 -6
  119. package/dist/editor/imageFiles.d.ts +2 -2
  120. package/dist/editor/imageFiles.js +2 -0
  121. package/dist/editor/insertTable.d.ts +1 -1
  122. package/dist/editor/insertTable.js +2 -2
  123. package/dist/editor/paragraphEdits.d.ts +16 -1
  124. package/dist/editor/paragraphEdits.js +13 -5
  125. package/dist/editor/paragraphPlacement.d.ts +11 -0
  126. package/dist/editor/paragraphPlacement.js +18 -0
  127. package/dist/editor/plainText.d.ts +12 -0
  128. package/dist/editor/plainText.js +2 -1
  129. package/dist/editor/plugins/columnResize.js +6 -3
  130. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  131. package/dist/editor/plugins/commentComposer.js +46 -0
  132. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  133. package/dist/editor/plugins/commentDecorations.js +89 -15
  134. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  135. package/dist/editor/plugins/documentProjection.js +22 -0
  136. package/dist/editor/plugins/imagePaste.js +20 -10
  137. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  138. package/dist/editor/plugins/linkPanel.js +13 -27
  139. package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
  140. package/dist/editor/plugins/numberingDecorations.js +43 -2
  141. package/dist/editor/plugins/panelState.d.ts +67 -0
  142. package/dist/editor/plugins/panelState.js +54 -0
  143. package/dist/editor/plugins/paragraphDisplay.js +9 -3
  144. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  145. package/dist/editor/plugins/tableContextMenu.js +30 -47
  146. package/dist/editor/plugins/tableDisplay.js +19 -5
  147. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  148. package/dist/editor/plugins/textContextMenu.js +30 -47
  149. package/dist/model/format.d.ts +50 -0
  150. package/dist/model/format.js +65 -0
  151. package/dist/numbering/listRegistry.d.ts +37 -0
  152. package/dist/numbering/listRegistry.js +112 -0
  153. package/dist/numbering/listTemplate.d.ts +26 -17
  154. package/dist/numbering/listTemplate.js +66 -20
  155. package/dist/numbering/markers.d.ts +15 -6
  156. package/dist/numbering/markers.js +26 -59
  157. package/dist/numbering/parseNumbering.d.ts +70 -5
  158. package/dist/numbering/parseNumbering.js +100 -26
  159. package/dist/numbering/spellers.d.ts +15 -0
  160. package/dist/numbering/spellers.js +96 -0
  161. package/dist/numbering/writeNumbering.d.ts +5 -3
  162. package/dist/numbering/writeNumbering.js +15 -15
  163. package/dist/ooxml/conformance.d.ts +28 -0
  164. package/dist/ooxml/conformance.js +20 -0
  165. package/dist/ooxml/errors.d.ts +5 -5
  166. package/dist/ooxml/fragment.d.ts +11 -0
  167. package/dist/ooxml/fragment.js +14 -4
  168. package/dist/ooxml/image.d.ts +3 -4
  169. package/dist/ooxml/image.js +50 -5
  170. package/dist/ooxml/names.d.ts +15 -0
  171. package/dist/ooxml/names.js +10 -1
  172. package/dist/ooxml/partSplice.d.ts +1 -1
  173. package/dist/ooxml/partSplice.js +10 -3
  174. package/dist/ooxml/props.d.ts +8 -0
  175. package/dist/ooxml/props.js +14 -4
  176. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  177. package/dist/ooxml/rangeMarkers.js +24 -0
  178. package/dist/ooxml/xml.d.ts +8 -9
  179. package/dist/ooxml/xml.js +6 -6
  180. package/dist/page/PageGuides.d.ts +8 -4
  181. package/dist/page/PageGuides.js +13 -37
  182. package/dist/page/pageLayout.d.ts +62 -13
  183. package/dist/page/pageLayout.js +91 -37
  184. package/dist/page/usePageLayout.d.ts +14 -11
  185. package/dist/page/usePageLayout.js +21 -29
  186. package/dist/schema/attrRoles.js +44 -19
  187. package/dist/schema/docxSchema.d.ts +9 -1
  188. package/dist/schema/docxSchema.js +220 -139
  189. package/dist/schema/editGuard.d.ts +1 -1
  190. package/dist/schema/guards.js +2 -2
  191. package/dist/schema/index.d.ts +1 -0
  192. package/dist/schema/preservedFragments.d.ts +12 -0
  193. package/dist/schema/preservedFragments.js +38 -0
  194. package/dist/schema/preservedGuards.d.ts +9 -7
  195. package/dist/schema/preservedGuards.js +112 -11
  196. package/dist/schema/protection.d.ts +34 -5
  197. package/dist/schema/protection.js +42 -14
  198. package/dist/schema/rendering.js +3 -1
  199. package/dist/schema/stories.d.ts +49 -0
  200. package/dist/schema/stories.js +78 -0
  201. package/dist/styles/classNames.d.ts +16 -7
  202. package/dist/styles/classNames.js +16 -7
  203. package/dist/styles.css +90 -30
  204. package/dist/table/cellFormatting.js +1 -2
  205. package/dist/table/gridBorders.d.ts +11 -15
  206. package/dist/table/gridBorders.js +32 -18
  207. package/dist/table/resize.d.ts +5 -3
  208. package/dist/ui/CommentsPanel.d.ts +1 -3
  209. package/dist/ui/CommentsPanel.js +24 -15
  210. package/dist/ui/NotesPanel.js +2 -2
  211. package/dist/ui/TextMenu.d.ts +1 -3
  212. package/dist/ui/TextMenu.js +10 -6
  213. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  214. package/dist/ui/comments/CommentComposer.js +26 -3
  215. package/package.json +1 -1
  216. package/dist/editor/externalClipboard.d.ts +0 -11
  217. package/dist/editor/externalClipboard.js +0 -456
@@ -5,24 +5,25 @@ import {
5
5
  ST_TwipsMeasure
6
6
  } from "../ooxml/simpleTypes.js";
7
7
  import { readTabStopDirectives } from "../ooxml/tabStops.js";
8
- import { childValue, twipsToPt, wAttr } from "../ooxml/units.js";
8
+ import {
9
+ ALIGN_BY_JC,
10
+ childValue,
11
+ isOn,
12
+ twipsToPt,
13
+ wAttr
14
+ } from "../ooxml/units.js";
9
15
  import {
10
16
  childByLocalName,
11
17
  elementChildren,
12
18
  parseXml,
13
19
  withXmlParser
14
20
  } from "../ooxml/xml.js";
15
- var NUMBER_FORMATS = [
16
- "decimal",
17
- "bullet",
18
- "lowerLetter",
19
- "upperLetter",
20
- "lowerRoman"
21
- ];
22
- var EMPTY_NUMBERING = { lists: /* @__PURE__ */ new Map() };
23
- function isNumberFormat(value) {
24
- return NUMBER_FORMATS.some((format) => format === value);
25
- }
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
+ };
26
27
  function indentOf(lvl) {
27
28
  const pPr = childByLocalName(lvl, "pPr");
28
29
  const ind = pPr ? childByLocalName(pPr, "ind") : null;
@@ -56,28 +57,53 @@ function levelIndentPt(indent) {
56
57
  textIndentPt: hangingPt !== null ? -hangingPt : twipsToPt(indent.firstLineTwips)
57
58
  };
58
59
  }
59
- 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) {
60
80
  const format = childValue(lvl, "numFmt");
61
81
  const pPr = childByLocalName(lvl, "pPr");
62
82
  const tabStops = readTabStopDirectives(pPr);
83
+ const rPr = childByLocalName(lvl, "rPr");
63
84
  return {
64
85
  format: isNumberFormat(format) ? format : "decimal",
65
86
  text: childValue(lvl, "lvlText") ?? "",
66
87
  start: ST_DecimalNumber.parse(childValue(lvl, "start")) ?? 1,
67
88
  indent: indentOf(lvl),
68
- ...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
69
95
  };
70
96
  }
71
- function readLevels(parent) {
97
+ function readLevels(parent, readRun) {
72
98
  const levels = /* @__PURE__ */ new Map();
73
99
  for (const child of elementChildren(parent)) {
74
100
  if (child.localName !== "lvl") continue;
75
101
  const ilvl = ST_DecimalNumber.parse(wAttr(child, "ilvl"));
76
- if (ilvl !== null) levels.set(ilvl, readLevel(child));
102
+ if (ilvl !== null) levels.set(ilvl, readLevel(child, readRun));
77
103
  }
78
104
  return levels;
79
105
  }
80
- function readList(num, abstractLevels) {
106
+ function readList(num, abstractLevels, readRun) {
81
107
  const abstractNumId = ST_DecimalNumber.parse(
82
108
  childValue(num, "abstractNumId")
83
109
  );
@@ -88,7 +114,7 @@ function readList(num, abstractLevels) {
88
114
  const ilvl = ST_DecimalNumber.parse(wAttr(child, "ilvl"));
89
115
  if (ilvl === null) continue;
90
116
  const replacement = childByLocalName(child, "lvl");
91
- const base = replacement ? readLevel(replacement) : levels.get(ilvl);
117
+ const base = replacement ? readLevel(replacement, readRun) : levels.get(ilvl);
92
118
  if (!base) continue;
93
119
  const startOverride = ST_DecimalNumber.parse(
94
120
  childValue(child, "startOverride")
@@ -100,29 +126,77 @@ function readList(num, abstractLevels) {
100
126
  }
101
127
  return { levels };
102
128
  }
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
+ }
103
154
  function parseNumbering(xml, options) {
104
155
  if (xml === null) return EMPTY_NUMBERING;
105
- return withXmlParser(options?.xmlParser, () => readNumbering(xml));
156
+ return withXmlParser(options?.xmlParser, () => readNumbering(xml, options));
106
157
  }
107
- function readNumbering(xml) {
158
+ function readNumbering(xml, options) {
108
159
  const root = parseXml(xml).documentElement;
109
- const abstractLevels = /* @__PURE__ */ new Map();
160
+ const byId = /* @__PURE__ */ new Map();
161
+ const ofStyle = /* @__PURE__ */ new Map();
162
+ const ofList = /* @__PURE__ */ new Map();
110
163
  for (const child of elementChildren(root)) {
111
- if (child.localName !== "abstractNum") continue;
112
- const id = ST_DecimalNumber.parse(wAttr(child, "abstractNumId"));
113
- 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));
114
186
  }
115
187
  const lists = /* @__PURE__ */ new Map();
116
188
  for (const child of elementChildren(root)) {
117
189
  if (child.localName !== "num") continue;
118
190
  const numId = ST_DecimalNumber.parse(wAttr(child, "numId"));
119
- const list = numId === null ? null : readList(child, abstractLevels);
191
+ const list = numId === null ? null : readList(child, abstractLevels, options?.readRun);
120
192
  if (numId !== null && list) lists.set(numId, list);
121
193
  }
122
- return { lists };
194
+ return { lists, added: NO_ADDED_LISTS };
123
195
  }
124
196
  export {
125
197
  EMPTY_NUMBERING,
198
+ LEVEL_ALIGNS,
199
+ LEVEL_SUFFIXES,
126
200
  NO_LEVEL_INDENT,
127
201
  levelIndentPt,
128
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,12 +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
5
  * Not one character of the original is altered; new elements are merely slotted in.
5
6
  * OOXML requires the definitions (`abstractNum`) to come before the numbers (`num`), and
6
7
  * `CHILD_ORDER.numbering` is what places each where it belongs among what the part holds.
7
8
  */
9
+ import type { NewList } from "./parseNumbering";
8
10
  /**
9
- * 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.
10
12
  * When there is nothing to add, the original string is returned unchanged.
11
13
  */
12
- export declare function addListDefinitions(xml: string, numIds: readonly number[]): string;
14
+ export declare function addListDefinitions(xml: string, lists: ReadonlyMap<number, NewList>): string;
@@ -1,30 +1,30 @@
1
1
  // src/numbering/writeNumbering.ts
2
2
  import { splicePart } from "../ooxml/partSplice.js";
3
+ import { ST_DecimalNumber } from "../ooxml/simpleTypes.js";
3
4
  import { wAttr } from "../ooxml/units.js";
4
5
  import { elementChildren, parseXml } from "../ooxml/xml.js";
5
- import { abstractNumXml, listKindOf, numXml } from "./listTemplate.js";
6
- function maxAbstractNumId(xml) {
7
- let max = 0;
8
- for (const child of elementChildren(parseXml(xml).documentElement)) {
9
- if (child.localName !== "abstractNum") continue;
10
- const id = Number.parseInt(wAttr(child, "abstractNumId") ?? "", 10);
11
- if (Number.isFinite(id)) max = Math.max(max, id);
12
- }
13
- return max;
6
+ import { abstractNumXml, numberingIdAllocator, numXml } from "./listTemplate.js";
7
+ function abstractNumIds(xml) {
8
+ return elementChildren(parseXml(xml).documentElement).flatMap((child) => {
9
+ if (child.localName !== "abstractNum") return [];
10
+ const id = ST_DecimalNumber.parse(wAttr(child, "abstractNumId"));
11
+ return id === null ? [] : [id];
12
+ });
14
13
  }
15
- function addListDefinitions(xml, numIds) {
16
- if (numIds.length === 0) return xml;
17
- const firstAbstractNumId = maxAbstractNumId(xml) + 1;
18
- const additions = [...numIds].sort((a, b) => a - b).map((numId, index) => ({
14
+ function addListDefinitions(xml, lists) {
15
+ if (lists.size === 0) return xml;
16
+ const takeId = numberingIdAllocator(abstractNumIds(xml));
17
+ const additions = [...lists].sort(([left], [right]) => left - right).map(([numId, list]) => ({
19
18
  numId,
20
- abstractNumId: firstAbstractNumId + index
19
+ list,
20
+ abstractNumId: takeId()
21
21
  }));
22
22
  return splicePart(xml, {
23
23
  root: "numbering",
24
24
  insert: [
25
25
  ...additions.map((added) => ({
26
26
  name: "abstractNum",
27
- xml: abstractNumXml(added.abstractNumId, listKindOf(added.numId))
27
+ xml: abstractNumXml(added.abstractNumId, added.list)
28
28
  })),
29
29
  ...additions.map((added) => ({
30
30
  name: "num",
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Which conformance class a package is written in, and whether its main part is one this editor
3
+ * can write back into. Both are settled once, when the file is opened.
4
+ *
5
+ * ECMA-376 defines two document conformance classes, Strict (Part 1 §2.1) and Transitional
6
+ * (Part 4 §2.1). They describe the same vocabulary under different names: the main document part's
7
+ * root namespace and the relationship the package reaches it through differ between the two
8
+ * (Part 1 §11.3.10, Part 4 §9.2.10). This editor supports a subset of Transitional. Strict is
9
+ * refused explicitly so that its markup is never read using Transitional assumptions.
10
+ *
11
+ * A namespace prefix is the producer's own choice and carries no meaning of its own, so readers
12
+ * identify markup by namespace URI and local name. The WordprocessingML writer emits `w`
13
+ * (`ooxml/names`), so the main part's root must bind that prefix to the Transitional namespace.
14
+ */
15
+ /** The WordprocessingML namespace of a Strict main document part (Part 1 §11.3.10) */
16
+ export declare const STRICT_W_NS = "http://purl.oclc.org/ooxml/wordprocessingml/main";
17
+ /** The relationship a Strict package reaches its main document part through (Part 1 §11.3.10) */
18
+ export declare const STRICT_OFFICE_DOCUMENT_REL = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument";
19
+ export type Conformance = "transitional" | "strict";
20
+ /** The class this main part root is written in, null for a root of neither vocabulary */
21
+ export declare function conformanceOf(root: Element): Conformance | null;
22
+ /**
23
+ * Whether the root binds the prefix every writer emits (`w`) to the WordprocessingML namespace.
24
+ *
25
+ * A root has nothing above it to inherit a declaration from, so what it writes itself is the whole
26
+ * answer. A default namespace or another prefix alone does not bind `w`.
27
+ */
28
+ export declare function bindsWritingPrefix(root: Element): boolean;
@@ -0,0 +1,20 @@
1
+ // src/ooxml/conformance.ts
2
+ import { W_NS, W_PREFIX } from "./names.js";
3
+ var STRICT_W_NS = "http://purl.oclc.org/ooxml/wordprocessingml/main";
4
+ var STRICT_OFFICE_DOCUMENT_REL = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument";
5
+ function conformanceOf(root) {
6
+ if (root.namespaceURI === W_NS) return "transitional";
7
+ if (root.namespaceURI === STRICT_W_NS) return "strict";
8
+ return null;
9
+ }
10
+ function bindsWritingPrefix(root) {
11
+ return Array.from(root.attributes).some(
12
+ (attr) => attr.name === `xmlns:${W_PREFIX}` && attr.value === W_NS
13
+ );
14
+ }
15
+ export {
16
+ STRICT_OFFICE_DOCUMENT_REL,
17
+ STRICT_W_NS,
18
+ bindsWritingPrefix,
19
+ conformanceOf
20
+ };
@@ -14,9 +14,10 @@
14
14
  * - `missing-part`: the package has no main document part to read
15
15
  * - `missing-body`: the main part carries no `w:body`
16
16
  * - `malformed-xml`: the XML cannot be parsed, declares a DTD, or its markup is inconsistent
17
+ * - `unsupported-conformance`: the package is an ECMA-376 Strict one, and this editor reads Transitional
17
18
  * - `unsupported-content`: the document holds markup we cannot write back out unchanged
18
19
  */
19
- export type DocxImportErrorCode = "no-xml-parser" | "not-a-docx" | "too-large" | "missing-part" | "missing-body" | "malformed-xml" | "unsupported-content";
20
+ export type DocxImportErrorCode = "no-xml-parser" | "not-a-docx" | "too-large" | "missing-part" | "missing-body" | "malformed-xml" | "unsupported-conformance" | "unsupported-content";
20
21
  /** Thrown when we hit a document whose content cannot be kept safely. We refuse to open it instead of losing it silently */
21
22
  export declare class DocxImportError extends Error {
22
23
  readonly code: DocxImportErrorCode;
@@ -25,14 +26,13 @@ export declare class DocxImportError extends Error {
25
26
  /**
26
27
  * Why a document could not be written back out.
27
28
  *
28
- * - `missing-numbering-part`: a new list needs a numbering.xml the document does not have
29
- * - `missing-content-types`: a new image needs a [Content_Types].xml the package does not have
30
- * - `unsupported-content`: the document holds a node kind we have no way to serialize, or a preserved block standing in two places, which has one original XML to write
29
+ * - `missing-content-types`: a part the writer adds, be it an image, a list definition or a comment, needs a [Content_Types].xml the package does not have
30
+ * - `unsupported-content`: the document holds something no correct file can be written from: a node kind we have no way to serialize, a preserved block standing in two places, which has one original XML to write, or a paragraph in a list nothing defines
31
31
  * - `lost-original`: a node that only carries its original XML has lost it
32
32
  * - `malformed-xml`: an original XML fragment cannot be read well enough to rewrite
33
33
  * - `invalid-table`: the table grid is inconsistent, e.g. a vertical merge outliving its rows
34
34
  */
35
- export type DocxExportErrorCode = "missing-numbering-part" | "missing-content-types" | "unsupported-content" | "lost-original" | "malformed-xml" | "invalid-table";
35
+ export type DocxExportErrorCode = "missing-content-types" | "unsupported-content" | "lost-original" | "malformed-xml" | "invalid-table";
36
36
  /** Thrown when an edited document cannot be written back out without losing or corrupting content */
37
37
  export declare class DocxExportError extends Error {
38
38
  readonly code: DocxExportErrorCode;
@@ -16,6 +16,7 @@
16
16
  * What a raw XML string has to look like to be let into the attr that carries it.
17
17
  *
18
18
  * `element` is a whole element: a properties fragment, a drawing, an annotation reference.
19
+ * `elements` is a run of them standing side by side, as a table carries between its rows.
19
20
  * `attributes` is what stood inside an opening tag, as `attrString` writes it.
20
21
  * `openTag` is an opening tag with everything it wrapped cut away, which the writer puts back by
21
22
  * appending the closing text `closedBy` names. `head` is what may still stand between the two:
@@ -24,6 +25,8 @@
24
25
  export type RawXmlShape = {
25
26
  kind: "element";
26
27
  names: readonly string[] | "any";
28
+ } | {
29
+ kind: "elements";
27
30
  } | {
28
31
  kind: "attributes";
29
32
  } | {
@@ -42,6 +45,14 @@ export declare function ELEMENT(...names: string[]): RawXmlShape;
42
45
  * turn a document the editor reads today into a demoted one.
43
46
  */
44
47
  export declare const ANY_ELEMENT: RawXmlShape;
48
+ /**
49
+ * A run of one or more elements standing side by side, of any name, in any namespace.
50
+ *
51
+ * A table carries the markers that stood between its rows, and a row those between its cells
52
+ * (`docx/importTable`), and there may be several of them in a row: the end of one bookmark and the
53
+ * start of the next stand together with nothing between them.
54
+ */
55
+ export declare const ANY_ELEMENTS: RawXmlShape;
45
56
  /** The attributes of an opening tag, with neither the tag nor anything it held */
46
57
  export declare const ATTRIBUTES: RawXmlShape;
47
58
  /**
@@ -10,17 +10,22 @@ function ELEMENT(...names) {
10
10
  return { kind: "element", names };
11
11
  }
12
12
  var ANY_ELEMENT = { kind: "element", names: "any" };
13
+ var ANY_ELEMENTS = { kind: "elements" };
13
14
  var ATTRIBUTES = { kind: "attributes" };
14
- function loneElement(xml) {
15
+ function wrappedElements(xml) {
15
16
  let root;
16
17
  try {
17
18
  root = parseXml(`<x ${namespaceDecls(xml)}>${xml}</x>`).documentElement;
18
19
  } catch {
19
20
  return null;
20
21
  }
21
- if (root.childNodes.length !== 1) return null;
22
- const el = elementChildren(root)[0];
23
- return el === void 0 || rebindsReservedPrefix(el) ? null : el;
22
+ const elements = elementChildren(root);
23
+ if (root.childNodes.length !== elements.length) return null;
24
+ return elements.some(rebindsReservedPrefix) ? null : elements;
25
+ }
26
+ function loneElement(xml) {
27
+ const elements = wrappedElements(xml);
28
+ return elements?.length === 1 ? elements[0] : null;
24
29
  }
25
30
  function isNamed(el, names) {
26
31
  if (!names.includes(el.localName)) return false;
@@ -54,6 +59,10 @@ function holdsShape(shape, value) {
54
59
  if (el === null) return false;
55
60
  return shape.names === "any" || isNamed(el, shape.names);
56
61
  }
62
+ case "elements": {
63
+ const elements = wrappedElements(value);
64
+ return elements !== null && elements.length > 0;
65
+ }
57
66
  case "attributes": {
58
67
  const el = loneElement(`<y ${value}></y>`);
59
68
  return el !== null && el.childNodes.length === 0;
@@ -70,6 +79,7 @@ function acceptRawXml(shape, value) {
70
79
  }
71
80
  export {
72
81
  ANY_ELEMENT,
82
+ ANY_ELEMENTS,
73
83
  ATTRIBUTES,
74
84
  ELEMENT,
75
85
  acceptRawXml
@@ -67,11 +67,10 @@ export interface DrawingPicture {
67
67
  */
68
68
  export declare function readDrawingPicture(drawing: Element): DrawingPicture | null;
69
69
  /**
70
- * The same drawing XML with both extents set to this size.
70
+ * The same drawing XML with its inline extent and its picture transform's extent set to this size.
71
71
  *
72
- * Only those two elements are touched; every other byte of the original stays where it
73
- * was. Handed the size it was imported with, this gives back the original string
74
- * unchanged, so nothing has to keep track of whether a resize happened.
72
+ * Only those two elements' size attributes are touched; every other byte stays where it was.
73
+ * Values already equal to the requested size keep their original spelling as well.
75
74
  */
76
75
  export declare function withExtent(xml: string, extent: ImageExtent): string;
77
76
  /** A picture that was inserted during editing and has no original XML to go back to */
@@ -1,6 +1,7 @@
1
1
  // src/ooxml/image.ts
2
2
  import { NAMESPACES } from "./names.js";
3
- import { childByLocalName, escapeXml, R_NS } from "./xml.js";
3
+ import { parseAttrs, readTag } from "./tagScan.js";
4
+ import { childByLocalName, escapeXml, localPart, R_NS } from "./xml.js";
4
5
  var EMU_PER_PX = 9525;
5
6
  function emuToPx(emu) {
6
7
  return emu / EMU_PER_PX;
@@ -76,11 +77,55 @@ function readDrawingPicture(drawing) {
76
77
  const descr = childOf(inline, "docPr")?.getAttribute("descr") ?? null;
77
78
  return { relId, extent, alt: descr === "" ? null : descr };
78
79
  }
79
- var EXTENT_TAG = /<([\w.-]+:)?extent\b[^>]*\/>/g;
80
- var EXT_TAG = /<([\w.-]+:)?ext\b[^>]*\/>/g;
80
+ function isPictureSize(path) {
81
+ const names = path.join("/");
82
+ return names === "drawing/inline/extent" || names === "drawing/inline/graphic/graphicData/pic/spPr/xfrm/ext";
83
+ }
84
+ function resizeTag(open, extent) {
85
+ const tag = readTag(open, 0);
86
+ if (!tag) return open;
87
+ const attrs = new Map(
88
+ parseAttrs(
89
+ open.slice(tag.nameEnd, tag.end - (tag.kind === "empty" ? 2 : 1))
90
+ ) ?? []
91
+ );
92
+ return open.replace(
93
+ /(\s+)([^\s=/>]+)(\s*=\s*)(["'])([\s\S]*?)\4/g,
94
+ (whole, gap, name, equals, quote) => {
95
+ if (name !== "cx" && name !== "cy") return whole;
96
+ const size = extent[name];
97
+ if (Number(attrs.get(name)) === size) return whole;
98
+ return `${gap}${name}${equals}${quote}${size}${quote}`;
99
+ }
100
+ );
101
+ }
81
102
  function withExtent(xml, extent) {
82
- const size = `cx="${extent.cx}" cy="${extent.cy}"`;
83
- return xml.replace(EXTENT_TAG, (_match, prefix) => `<${prefix ?? ""}extent ${size}/>`).replace(EXT_TAG, (_match, prefix) => `<${prefix ?? ""}ext ${size}/>`);
103
+ const path = [];
104
+ const parts = [];
105
+ let at = 0;
106
+ let kept = 0;
107
+ for (; ; ) {
108
+ const lt = xml.indexOf("<", at);
109
+ if (lt === -1) break;
110
+ const tag = readTag(xml, lt);
111
+ if (!tag) return xml;
112
+ if (tag.kind === "close") {
113
+ path.pop();
114
+ } else if (tag.kind !== "other") {
115
+ path.push(localPart(tag.name));
116
+ if (isPictureSize(path)) {
117
+ parts.push(
118
+ xml.slice(kept, lt),
119
+ resizeTag(xml.slice(lt, tag.end), extent)
120
+ );
121
+ kept = tag.end;
122
+ }
123
+ if (tag.kind === "empty") path.pop();
124
+ }
125
+ at = tag.end;
126
+ }
127
+ parts.push(xml.slice(kept));
128
+ return parts.join("");
84
129
  }
85
130
  function imageDrawingXml(image) {
86
131
  const size = `cx="${image.extent.cx}" cy="${image.extent.cy}"`;
@@ -6,6 +6,19 @@
6
6
  * here, and the part a fragment is spliced into declares the same binding, so the prefix is
7
7
  * decided once rather than at every call site.
8
8
  */
9
+ /** The wordprocessing namespace that every element we read lives in */
10
+ export declare const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
11
+ /**
12
+ * The relationship namespace. It is both where the `r:embed` attributes live and the base
13
+ * every relationship type name is built on
14
+ */
15
+ export declare const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
16
+ /**
17
+ * The math namespace. Nothing here is written under a prefix of ours - a `m:oMath` travels as the
18
+ * XML it arrived as - so it names the namespace alone and stays out of `NAMESPACES`, which is the
19
+ * list of prefixes the writer spells out.
20
+ */
21
+ export declare const M_NS = "http://schemas.openxmlformats.org/officeDocument/2006/math";
9
22
  /** The prefix every WordprocessingML element and attribute this package writes carries */
10
23
  export declare const W_PREFIX = "w";
11
24
  export declare const NAMESPACES: {
@@ -19,6 +32,8 @@ export declare const NAMESPACES: {
19
32
  readonly pic: "http://schemas.openxmlformats.org/drawingml/2006/picture";
20
33
  };
21
34
  export type KnownPrefix = keyof typeof NAMESPACES;
35
+ /** Whether this is a prefix the package has a namespace for, rather than one a document brought */
36
+ export declare function isKnownPrefix(prefix: string): prefix is KnownPrefix;
22
37
  /** The name `local` goes out under (`qualify("w15", "commentEx")` -> `w15:commentEx`) */
23
38
  export declare function qualify(prefix: KnownPrefix, local: string): string;
24
39
  /** `wName("val")` -> `w:val`. The one place the written WordprocessingML prefix is decided */