@portone/docx-editor 0.3.0 → 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.
- package/CHANGELOG.md +46 -0
- package/dist/DocxEditor.js +1 -0
- package/dist/core.d.ts +1 -1
- package/dist/docx/comments/writing.js +14 -8
- package/dist/docx/exportDocx.js +20 -4
- package/dist/docx/formatting/attrs.d.ts +20 -2
- package/dist/docx/formatting/attrs.js +57 -2
- package/dist/docx/formatting/context.d.ts +10 -2
- package/dist/docx/formatting/context.js +23 -5
- package/dist/docx/formatting/resolve.d.ts +10 -4
- package/dist/docx/formatting/resolve.js +31 -11
- package/dist/docx/formatting/styles.d.ts +25 -2
- package/dist/docx/formatting/styles.js +84 -3
- package/dist/docx/hyperlink.d.ts +14 -4
- package/dist/docx/hyperlink.js +4 -1
- package/dist/docx/importDocx.js +46 -37
- package/dist/docx/importTable.d.ts +2 -2
- package/dist/docx/importTable.js +52 -55
- package/dist/docx/invariants.js +26 -13
- package/dist/docx/newLists.d.ts +30 -0
- package/dist/docx/newLists.js +24 -1
- package/dist/docx/numberingPlanner.d.ts +6 -3
- package/dist/docx/numberingPlanner.js +38 -7
- package/dist/docx/session.d.ts +1 -1
- package/dist/docx/session.js +11 -1
- package/dist/docx/tableFormatting/conditions.d.ts +76 -0
- package/dist/docx/tableFormatting/conditions.js +196 -0
- package/dist/docx/tableFormatting/editing.d.ts +7 -3
- package/dist/docx/tableFormatting/editing.js +4 -5
- package/dist/docx/tableFormatting/reading.d.ts +49 -19
- package/dist/docx/tableFormatting/reading.js +109 -35
- package/dist/docx/tableFormatting.d.ts +1 -0
- package/dist/docx/tableFormatting.js +1 -0
- package/dist/docx/tableTemplate.js +12 -12
- package/dist/editor/commands/formatting/propertyCommands.js +6 -1
- package/dist/editor/commands/listCommands.d.ts +6 -3
- package/dist/editor/commands/listCommands.js +73 -28
- package/dist/editor/commands/paragraphCommands.js +6 -1
- package/dist/editor/createEditor.js +1 -1
- package/dist/editor/editorDocument.d.ts +7 -6
- package/dist/editor/editorDocument.js +18 -6
- package/dist/editor/externalClipboard.d.ts +13 -2
- package/dist/editor/externalClipboard.js +64 -34
- package/dist/editor/paragraphEdits.d.ts +16 -1
- package/dist/editor/paragraphEdits.js +13 -5
- package/dist/editor/paragraphPlacement.d.ts +11 -0
- package/dist/editor/paragraphPlacement.js +18 -0
- package/dist/editor/plugins/imagePaste.js +7 -4
- package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
- package/dist/editor/plugins/numberingDecorations.js +43 -2
- package/dist/editor/plugins/paragraphDisplay.js +9 -3
- package/dist/editor/plugins/tableDisplay.js +19 -5
- package/dist/model/format.d.ts +50 -0
- package/dist/model/format.js +65 -0
- package/dist/numbering/listRegistry.d.ts +37 -0
- package/dist/numbering/listRegistry.js +112 -0
- package/dist/numbering/listTemplate.d.ts +26 -17
- package/dist/numbering/listTemplate.js +66 -20
- package/dist/numbering/markers.d.ts +15 -6
- package/dist/numbering/markers.js +26 -59
- package/dist/numbering/parseNumbering.d.ts +70 -5
- package/dist/numbering/parseNumbering.js +100 -26
- package/dist/numbering/spellers.d.ts +15 -0
- package/dist/numbering/spellers.js +96 -0
- package/dist/numbering/writeNumbering.d.ts +5 -3
- package/dist/numbering/writeNumbering.js +15 -15
- package/dist/ooxml/conformance.d.ts +28 -0
- package/dist/ooxml/conformance.js +20 -0
- package/dist/ooxml/errors.d.ts +5 -5
- package/dist/ooxml/names.d.ts +9 -0
- package/dist/ooxml/names.js +8 -1
- package/dist/ooxml/partSplice.d.ts +1 -1
- package/dist/ooxml/partSplice.js +10 -3
- package/dist/ooxml/xml.d.ts +8 -9
- package/dist/ooxml/xml.js +6 -6
- package/dist/page/PageGuides.d.ts +1 -1
- package/dist/page/PageGuides.js +0 -13
- package/dist/page/pageLayout.d.ts +0 -10
- package/dist/page/pageLayout.js +3 -10
- package/dist/page/usePageLayout.d.ts +0 -7
- package/dist/page/usePageLayout.js +2 -9
- package/dist/schema/attrRoles.js +8 -1
- package/dist/schema/docxSchema.js +32 -3
- package/dist/styles/classNames.d.ts +13 -2
- package/dist/styles/classNames.js +13 -2
- package/dist/styles.css +16 -18
- package/dist/table/cellFormatting.js +1 -2
- package/dist/table/gridBorders.d.ts +11 -15
- package/dist/table/gridBorders.js +32 -18
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,
|
|
16
|
-
if (
|
|
17
|
-
const
|
|
18
|
-
const additions = [...
|
|
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
|
-
|
|
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,
|
|
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
|
+
};
|
package/dist/ooxml/errors.d.ts
CHANGED
|
@@ -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-
|
|
29
|
-
* - `
|
|
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-
|
|
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;
|
package/dist/ooxml/names.d.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
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";
|
|
9
16
|
/** The prefix every WordprocessingML element and attribute this package writes carries */
|
|
10
17
|
export declare const W_PREFIX = "w";
|
|
11
18
|
export declare const NAMESPACES: {
|
|
@@ -19,6 +26,8 @@ export declare const NAMESPACES: {
|
|
|
19
26
|
readonly pic: "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
20
27
|
};
|
|
21
28
|
export type KnownPrefix = keyof typeof NAMESPACES;
|
|
29
|
+
/** Whether this is a prefix the package has a namespace for, rather than one a document brought */
|
|
30
|
+
export declare function isKnownPrefix(prefix: string): prefix is KnownPrefix;
|
|
22
31
|
/** The name `local` goes out under (`qualify("w15", "commentEx")` -> `w15:commentEx`) */
|
|
23
32
|
export declare function qualify(prefix: KnownPrefix, local: string): string;
|
|
24
33
|
/** `wName("val")` -> `w:val`. The one place the written WordprocessingML prefix is decided */
|
package/dist/ooxml/names.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/ooxml/names.ts
|
|
2
|
-
|
|
2
|
+
var W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
3
|
+
var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
3
4
|
var W_PREFIX = "w";
|
|
4
5
|
var NAMESPACES = {
|
|
5
6
|
w: W_NS,
|
|
@@ -11,6 +12,9 @@ var NAMESPACES = {
|
|
|
11
12
|
a: "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
12
13
|
pic: "http://schemas.openxmlformats.org/drawingml/2006/picture"
|
|
13
14
|
};
|
|
15
|
+
function isKnownPrefix(prefix) {
|
|
16
|
+
return Object.hasOwn(NAMESPACES, prefix);
|
|
17
|
+
}
|
|
14
18
|
function qualify(prefix, local) {
|
|
15
19
|
return `${prefix}:${local}`;
|
|
16
20
|
}
|
|
@@ -22,7 +26,10 @@ function xmlnsDecl(prefix) {
|
|
|
22
26
|
}
|
|
23
27
|
export {
|
|
24
28
|
NAMESPACES,
|
|
29
|
+
R_NS,
|
|
30
|
+
W_NS,
|
|
25
31
|
W_PREFIX,
|
|
32
|
+
isKnownPrefix,
|
|
26
33
|
qualify,
|
|
27
34
|
wName,
|
|
28
35
|
xmlnsDecl
|
|
@@ -62,6 +62,6 @@ export interface RootDeclarations {
|
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* The part with its root binding every prefix named and its `mc:Ignorable` naming every token
|
|
65
|
-
* asked for, each declared once:
|
|
65
|
+
* asked for, each declared once: a matching declaration is preserved, and a conflicting binding is refused.
|
|
66
66
|
*/
|
|
67
67
|
export declare function ensureRootDeclarations(xml: string, declarations: RootDeclarations): string;
|
package/dist/ooxml/partSplice.js
CHANGED
|
@@ -121,12 +121,19 @@ function withDeclarations(openTag, { namespaces, ignorable }) {
|
|
|
121
121
|
"the root element's attributes could not be read"
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
|
-
const declared = new
|
|
124
|
+
const declared = new Map(attrs);
|
|
125
125
|
const additions = [];
|
|
126
126
|
for (const [prefix, namespace] of Object.entries(namespaces)) {
|
|
127
|
-
if (namespace
|
|
128
|
-
|
|
127
|
+
if (namespace === void 0) continue;
|
|
128
|
+
const name = `xmlns:${prefix}`;
|
|
129
|
+
const existing = declared.get(name);
|
|
130
|
+
if (existing !== void 0 && existing !== namespace) {
|
|
131
|
+
throw new DocxExportError(
|
|
132
|
+
"unsupported-content",
|
|
133
|
+
`the part root binds ${prefix} to a namespace the writer cannot use`
|
|
134
|
+
);
|
|
129
135
|
}
|
|
136
|
+
if (existing === void 0) additions.push([name, namespace]);
|
|
130
137
|
}
|
|
131
138
|
const ignoring = IGNORABLE.exec(openTag);
|
|
132
139
|
let tokens = null;
|
package/dist/ooxml/xml.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
/**
|
|
4
|
-
* The relationship namespace. It is both where the `r:embed` attributes live and the base
|
|
5
|
-
* every relationship type name is built on
|
|
6
|
-
*/
|
|
7
|
-
export declare const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
1
|
+
import { R_NS, W_NS } from "./names";
|
|
2
|
+
export { R_NS, W_NS };
|
|
8
3
|
/**
|
|
9
4
|
* The prefixes whose meaning the editor depends on, and the namespace each one has to carry.
|
|
10
5
|
*
|
|
@@ -58,8 +53,12 @@ export declare function withXmlParser<T>(parser: XmlParser | undefined, work: ()
|
|
|
58
53
|
export declare function parseXml(source: string): Document;
|
|
59
54
|
/**
|
|
60
55
|
* Gathers the namespace prefixes used in the fragment and declares them.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
56
|
+
*
|
|
57
|
+
* A prefix `NAMESPACES` knows is bound to the namespace it stands for, so an attribute read off
|
|
58
|
+
* the fragment by namespace answers what it would in the part the fragment came out of. A prefix
|
|
59
|
+
* it does not know is bound to a placeholder, which carries no meaning but keeps the parser going:
|
|
60
|
+
* a fragment naming `o:`, `v:` or `wne:` would otherwise fail to parse and be refused whole
|
|
61
|
+
* (`ooxml/fragment`), taking the node that held it with it.
|
|
63
62
|
*
|
|
64
63
|
* An attribute standing at the very start of the string counts too, since a fragment may be an
|
|
65
64
|
* attribute list of its own (`attrString`) rather than an element.
|
package/dist/ooxml/xml.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/ooxml/xml.ts
|
|
2
2
|
import { DocxImportError } from "./errors.js";
|
|
3
|
-
|
|
4
|
-
var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
3
|
+
import { isKnownPrefix, NAMESPACES, R_NS, W_NS, W_PREFIX } from "./names.js";
|
|
5
4
|
var RESERVED_PREFIXES = /* @__PURE__ */ new Map([
|
|
6
5
|
["w", W_NS],
|
|
7
6
|
["r", R_NS]
|
|
@@ -74,7 +73,7 @@ function parseXml(source) {
|
|
|
74
73
|
return doc;
|
|
75
74
|
}
|
|
76
75
|
function namespaceDecls(xml) {
|
|
77
|
-
const prefixes = /* @__PURE__ */ new Set([
|
|
76
|
+
const prefixes = /* @__PURE__ */ new Set([W_PREFIX]);
|
|
78
77
|
for (const [, prefix] of xml.matchAll(/<\/?([A-Za-z_][\w.-]*):/g)) {
|
|
79
78
|
prefixes.add(prefix);
|
|
80
79
|
}
|
|
@@ -85,9 +84,10 @@ function namespaceDecls(xml) {
|
|
|
85
84
|
}
|
|
86
85
|
prefixes.delete("xml");
|
|
87
86
|
prefixes.delete("xmlns");
|
|
88
|
-
return Array.from(prefixes).map(
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
return Array.from(prefixes).map((prefix) => {
|
|
88
|
+
const namespace = isKnownPrefix(prefix) ? NAMESPACES[prefix] : `urn:docx-editor:${prefix}`;
|
|
89
|
+
return `xmlns:${prefix}="${namespace}"`;
|
|
90
|
+
}).join(" ");
|
|
91
91
|
}
|
|
92
92
|
function elementChildren(el) {
|
|
93
93
|
return Array.from(el.children);
|
package/dist/page/PageGuides.js
CHANGED
|
@@ -6,9 +6,6 @@ import {
|
|
|
6
6
|
} from "../docx/headersFooters.js";
|
|
7
7
|
import { editorClassNames } from "../styles/classNames.js";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
function badgeTitle(badge) {
|
|
10
|
-
return badge.exactPage ? `Page ${badge.page}` : `Page ${badge.page} (approximate)`;
|
|
11
|
-
}
|
|
12
9
|
function PageGuides({
|
|
13
10
|
overlay,
|
|
14
11
|
headersFooters
|
|
@@ -37,16 +34,6 @@ function PageGuides({
|
|
|
37
34
|
},
|
|
38
35
|
mark.page
|
|
39
36
|
)),
|
|
40
|
-
overlay.badges.map((badge) => /* @__PURE__ */ jsx(
|
|
41
|
-
"span",
|
|
42
|
-
{
|
|
43
|
-
className: editorClassNames.pageBadge,
|
|
44
|
-
style: { top: `${badge.top}px` },
|
|
45
|
-
title: badgeTitle(badge),
|
|
46
|
-
children: badge.page
|
|
47
|
-
},
|
|
48
|
-
badge.page
|
|
49
|
-
)),
|
|
50
37
|
headersFooters && overlay.pages.flatMap((page) => {
|
|
51
38
|
if (page.crossed) return [];
|
|
52
39
|
const number = displayPageNumber(headersFooters, page.page);
|
|
@@ -63,11 +63,6 @@ export interface PageSplit {
|
|
|
63
63
|
page: number;
|
|
64
64
|
/** Whether the document records a break at this place */
|
|
65
65
|
forced: boolean;
|
|
66
|
-
/**
|
|
67
|
-
* Whether every place before this one was recorded by the document, so the number can
|
|
68
|
-
* be trusted
|
|
69
|
-
*/
|
|
70
|
-
exactPage: boolean;
|
|
71
66
|
/**
|
|
72
67
|
* Whether the block is taller than one page, so it cannot be pushed and the text
|
|
73
68
|
* crosses this place
|
|
@@ -79,11 +74,6 @@ export interface PageStart {
|
|
|
79
74
|
page: number;
|
|
80
75
|
/** Where this page's body starts, measured from the top of the body */
|
|
81
76
|
bodyStart: number;
|
|
82
|
-
/**
|
|
83
|
-
* Whether every place before this one was recorded by the document, so the number can
|
|
84
|
-
* be trusted
|
|
85
|
-
*/
|
|
86
|
-
exactPage: boolean;
|
|
87
77
|
/**
|
|
88
78
|
* Whether text crosses over from the previous page, so this page continues with no top
|
|
89
79
|
* margin
|