@beyondwork/docx-react-component 1.0.38 → 1.0.39
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/package.json +41 -31
- package/src/api/public-types.ts +183 -6
- package/src/core/commands/table-structure-commands.ts +31 -2
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-numbering.ts +42 -8
- package/src/io/export/serialize-paragraph-formatting.ts +152 -0
- package/src/io/export/serialize-run-formatting.ts +90 -0
- package/src/io/export/serialize-styles.ts +212 -0
- package/src/io/ooxml/parse-fields.ts +10 -3
- package/src/io/ooxml/parse-numbering.ts +41 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
- package/src/io/ooxml/parse-run-formatting.ts +129 -0
- package/src/io/ooxml/parse-styles.ts +31 -0
- package/src/io/ooxml/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +83 -3
- package/src/runtime/collab/event-types.ts +165 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
- package/src/runtime/collab/runtime-collab-sync.ts +273 -0
- package/src/runtime/document-runtime.ts +134 -18
- package/src/runtime/layout/index.ts +2 -0
- package/src/runtime/layout/inert-layout-facet.ts +2 -0
- package/src/runtime/layout/layout-engine-instance.ts +69 -2
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/page-graph.ts +36 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +342 -28
- package/src/runtime/layout/project-block-fragments.ts +154 -20
- package/src/runtime/layout/public-facet.ts +40 -1
- package/src/runtime/layout/resolve-page-fields.ts +70 -0
- package/src/runtime/layout/resolve-page-previews.ts +185 -0
- package/src/runtime/layout/resolved-formatting-state.ts +30 -26
- package/src/runtime/layout/table-render-plan.ts +21 -1
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/render-kernel.ts +5 -1
- package/src/runtime/resolved-numbering-geometry.ts +9 -1
- package/src/runtime/surface-projection.ts +129 -9
- package/src/runtime/table-schema.ts +11 -0
- package/src/ui/WordReviewEditor.tsx +285 -5
- package/src/ui/editor-command-bag.ts +4 -0
- package/src/ui/editor-runtime-boundary.ts +16 -0
- package/src/ui/editor-shell-view.tsx +4 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +34 -5
- package/src/ui/headless/scoped-chrome-policy.ts +29 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +14 -8
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +7 -10
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +101 -21
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +5 -1
- package/src/ui-tailwind/chrome-overlay/index.ts +0 -6
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +27 -17
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +28 -3
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -78
- package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
- package/src/ui-tailwind/index.ts +1 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +122 -1
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +36 -3
- package/src/ui-tailwind/tw-review-workspace.tsx +132 -54
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +0 -95
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ParagraphIndentation,
|
|
9
9
|
TabStop,
|
|
10
10
|
} from "../../model/canonical-document.ts";
|
|
11
|
+
import { readRunProperties } from "./parse-run-formatting.ts";
|
|
11
12
|
|
|
12
13
|
export interface ParsedParagraphNumberingReference {
|
|
13
14
|
paragraphIndex: number;
|
|
@@ -48,9 +49,32 @@ export function parseNumberingXml(xml: string): NumberingCatalog {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const abstractNumberingId = toCanonicalAbstractNumberingId(rawId);
|
|
52
|
+
const nsidEl = findChildElementOptional(child, "nsid");
|
|
53
|
+
const nsid = nsidEl ? (nsidEl.attributes["w:val"] ?? nsidEl.attributes.val) : undefined;
|
|
54
|
+
const mltEl = findChildElementOptional(child, "multiLevelType");
|
|
55
|
+
const mltRaw = mltEl ? (mltEl.attributes["w:val"] ?? mltEl.attributes.val) : undefined;
|
|
56
|
+
const multiLevelType =
|
|
57
|
+
mltRaw === "singleLevel" || mltRaw === "multilevel" || mltRaw === "hybridMultilevel"
|
|
58
|
+
? mltRaw
|
|
59
|
+
: undefined;
|
|
60
|
+
const tmplEl = findChildElementOptional(child, "tmpl");
|
|
61
|
+
const tplc = tmplEl ? (tmplEl.attributes["w:val"] ?? tmplEl.attributes.val) : undefined;
|
|
62
|
+
const styleLinkEl = findChildElementOptional(child, "styleLink");
|
|
63
|
+
const styleLink = styleLinkEl
|
|
64
|
+
? (styleLinkEl.attributes["w:val"] ?? styleLinkEl.attributes.val)
|
|
65
|
+
: undefined;
|
|
66
|
+
const numStyleLinkEl = findChildElementOptional(child, "numStyleLink");
|
|
67
|
+
const numStyleLink = numStyleLinkEl
|
|
68
|
+
? (numStyleLinkEl.attributes["w:val"] ?? numStyleLinkEl.attributes.val)
|
|
69
|
+
: undefined;
|
|
51
70
|
abstractDefinitions[abstractNumberingId] = {
|
|
52
71
|
abstractNumberingId,
|
|
53
72
|
levels: readLevels(child),
|
|
73
|
+
...(nsid ? { nsid } : {}),
|
|
74
|
+
...(multiLevelType ? { multiLevelType } : {}),
|
|
75
|
+
...(tplc ? { tplc } : {}),
|
|
76
|
+
...(styleLink ? { styleLink } : {}),
|
|
77
|
+
...(numStyleLink ? { numStyleLink } : {}),
|
|
54
78
|
};
|
|
55
79
|
break;
|
|
56
80
|
}
|
|
@@ -219,6 +243,11 @@ function readLevelDefinition(
|
|
|
219
243
|
? "tab"
|
|
220
244
|
: undefined;
|
|
221
245
|
const paragraphGeometry = readLevelParagraphGeometry(levelNode);
|
|
246
|
+
const lvlRestartNode = findChildElementOptional(levelNode, "lvlRestart");
|
|
247
|
+
const rawRestart = lvlRestartNode?.attributes["w:val"] ?? lvlRestartNode?.attributes.val;
|
|
248
|
+
const restartAfterLevel = rawRestart !== undefined ? parseInteger(rawRestart) : undefined;
|
|
249
|
+
const rPrNode = findChildElementOptional(levelNode, "rPr");
|
|
250
|
+
const runProperties = readRunProperties(rPrNode);
|
|
222
251
|
|
|
223
252
|
return {
|
|
224
253
|
level,
|
|
@@ -229,6 +258,8 @@ function readLevelDefinition(
|
|
|
229
258
|
...(isLegalNumbering ? { isLegalNumbering: true } : {}),
|
|
230
259
|
...(suffix ? { suffix } : {}),
|
|
231
260
|
...(paragraphGeometry ? { paragraphGeometry } : {}),
|
|
261
|
+
...(runProperties ? { runProperties } : {}),
|
|
262
|
+
...(restartAfterLevel !== undefined ? { restartAfterLevel } : {}),
|
|
232
263
|
};
|
|
233
264
|
}
|
|
234
265
|
|
|
@@ -263,6 +294,11 @@ function readLevelOverrideDefinition(
|
|
|
263
294
|
? "tab"
|
|
264
295
|
: undefined;
|
|
265
296
|
const paragraphGeometry = readLevelParagraphGeometry(levelNode);
|
|
297
|
+
const lvlRestartNode = findChildElementOptional(levelNode, "lvlRestart");
|
|
298
|
+
const rawRestart = lvlRestartNode?.attributes["w:val"] ?? lvlRestartNode?.attributes.val;
|
|
299
|
+
const restartAfterLevel = rawRestart !== undefined ? parseInteger(rawRestart) : undefined;
|
|
300
|
+
const rPrNode = findChildElementOptional(levelNode, "rPr");
|
|
301
|
+
const runProperties = readRunProperties(rPrNode);
|
|
266
302
|
|
|
267
303
|
const hasExplicitFields =
|
|
268
304
|
startAt !== undefined ||
|
|
@@ -271,7 +307,9 @@ function readLevelOverrideDefinition(
|
|
|
271
307
|
paragraphStyleId !== undefined ||
|
|
272
308
|
isLegalNumbering !== undefined ||
|
|
273
309
|
suffix !== undefined ||
|
|
274
|
-
paragraphGeometry !== undefined
|
|
310
|
+
paragraphGeometry !== undefined ||
|
|
311
|
+
runProperties !== undefined ||
|
|
312
|
+
restartAfterLevel !== undefined;
|
|
275
313
|
|
|
276
314
|
if (!hasExplicitFields) {
|
|
277
315
|
return undefined;
|
|
@@ -286,6 +324,8 @@ function readLevelOverrideDefinition(
|
|
|
286
324
|
...(isLegalNumbering !== undefined ? { isLegalNumbering } : {}),
|
|
287
325
|
...(suffix ? { suffix } : {}),
|
|
288
326
|
...(paragraphGeometry ? { paragraphGeometry } : {}),
|
|
327
|
+
...(runProperties ? { runProperties } : {}),
|
|
328
|
+
...(restartAfterLevel !== undefined ? { restartAfterLevel } : {}),
|
|
289
329
|
};
|
|
290
330
|
}
|
|
291
331
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read `<w:pPr>` (paragraph properties) into a `CanonicalParagraphFormatting` value.
|
|
3
|
+
*
|
|
4
|
+
* Returns `undefined` when the input is `undefined` or when the pPr is empty (no recognized
|
|
5
|
+
* child produces a field). Delegates the paragraph-mark `<w:rPr>` to `readRunProperties`
|
|
6
|
+
* so every rPr in the project is parsed through one code path.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
CanonicalParagraphFormatting,
|
|
11
|
+
ParagraphBorders,
|
|
12
|
+
ParagraphIndentation,
|
|
13
|
+
ParagraphShading,
|
|
14
|
+
ParagraphSpacing,
|
|
15
|
+
TabStop,
|
|
16
|
+
} from "../../model/canonical-document.ts";
|
|
17
|
+
import { readRunProperties } from "./parse-run-formatting.ts";
|
|
18
|
+
import { findChildOptional, localName, readIntAttr, readIntVal, readOnOff } from "./xml-attr-helpers.ts";
|
|
19
|
+
import type { XmlElementNode } from "./xml-element.ts";
|
|
20
|
+
|
|
21
|
+
function readSpacing(node: XmlElementNode): ParagraphSpacing | undefined {
|
|
22
|
+
const out: ParagraphSpacing = {};
|
|
23
|
+
const before = readIntAttr(node, "w:before");
|
|
24
|
+
const after = readIntAttr(node, "w:after");
|
|
25
|
+
const line = readIntAttr(node, "w:line");
|
|
26
|
+
const rule = node.attributes["w:lineRule"] ?? node.attributes.lineRule;
|
|
27
|
+
if (before !== undefined) out.before = before;
|
|
28
|
+
if (after !== undefined) out.after = after;
|
|
29
|
+
if (line !== undefined) out.line = line;
|
|
30
|
+
if (rule) {
|
|
31
|
+
const n = rule.toLowerCase();
|
|
32
|
+
if (n === "auto" || n === "exact") out.lineRule = n;
|
|
33
|
+
else if (n === "atleast") out.lineRule = "atLeast";
|
|
34
|
+
}
|
|
35
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readIndent(node: XmlElementNode): ParagraphIndentation | undefined {
|
|
39
|
+
const out: ParagraphIndentation = {};
|
|
40
|
+
const left = readIntAttr(node, "w:left") ?? readIntAttr(node, "w:start");
|
|
41
|
+
const right = readIntAttr(node, "w:right") ?? readIntAttr(node, "w:end");
|
|
42
|
+
const firstLine = readIntAttr(node, "w:firstLine");
|
|
43
|
+
const hanging = readIntAttr(node, "w:hanging");
|
|
44
|
+
if (left !== undefined) out.left = left;
|
|
45
|
+
if (right !== undefined) out.right = right;
|
|
46
|
+
if (firstLine !== undefined) out.firstLine = firstLine;
|
|
47
|
+
if (hanging !== undefined) out.hanging = hanging;
|
|
48
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readTabStops(node: XmlElementNode): TabStop[] | undefined {
|
|
52
|
+
const tabs: TabStop[] = [];
|
|
53
|
+
for (const c of node.children) {
|
|
54
|
+
if (c.type !== "element" || localName(c.name) !== "tab") continue;
|
|
55
|
+
const pos = Number.parseInt(c.attributes["w:pos"] ?? c.attributes.pos ?? "", 10);
|
|
56
|
+
if (!Number.isFinite(pos)) continue;
|
|
57
|
+
const val = (c.attributes["w:val"] ?? c.attributes.val ?? "left").toLowerCase();
|
|
58
|
+
const leader = (c.attributes["w:leader"] ?? c.attributes.leader ?? "none").toLowerCase();
|
|
59
|
+
const align: TabStop["align"] =
|
|
60
|
+
val === "center" || val === "right" || val === "decimal" || val === "bar" || val === "num" || val === "clear"
|
|
61
|
+
? (val as TabStop["align"])
|
|
62
|
+
: "left";
|
|
63
|
+
const mappedLeader: TabStop["leader"] | undefined =
|
|
64
|
+
leader === "dot" || leader === "hyphen" || leader === "underscore" || leader === "heavy"
|
|
65
|
+
? (leader as TabStop["leader"])
|
|
66
|
+
: leader === "middledot"
|
|
67
|
+
? "middleDot"
|
|
68
|
+
: undefined;
|
|
69
|
+
tabs.push({
|
|
70
|
+
position: pos,
|
|
71
|
+
align,
|
|
72
|
+
...(mappedLeader && mappedLeader !== "none" ? { leader: mappedLeader } : {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return tabs.length > 0 ? tabs : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readBorders(node: XmlElementNode): ParagraphBorders | undefined {
|
|
79
|
+
const out: ParagraphBorders = {};
|
|
80
|
+
const sides = ["top", "bottom", "left", "right", "between", "bar"] as const;
|
|
81
|
+
for (const side of sides) {
|
|
82
|
+
const child = findChildOptional(node, side);
|
|
83
|
+
if (!child) continue;
|
|
84
|
+
const val = child.attributes["w:val"] ?? child.attributes.val;
|
|
85
|
+
const sz = readIntAttr(child, "w:sz");
|
|
86
|
+
const space = readIntAttr(child, "w:space");
|
|
87
|
+
const color = child.attributes["w:color"] ?? child.attributes.color;
|
|
88
|
+
out[side] = {
|
|
89
|
+
...(val ? { value: val } : {}),
|
|
90
|
+
...(sz !== undefined ? { size: sz } : {}),
|
|
91
|
+
...(space !== undefined ? { space } : {}),
|
|
92
|
+
...(color ? { color } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readShading(node: XmlElementNode): ParagraphShading | undefined {
|
|
99
|
+
const val = node.attributes["w:val"] ?? node.attributes.val;
|
|
100
|
+
const fill = node.attributes["w:fill"] ?? node.attributes.fill;
|
|
101
|
+
const color = node.attributes["w:color"] ?? node.attributes.color;
|
|
102
|
+
if (!val && !fill && !color) return undefined;
|
|
103
|
+
return {
|
|
104
|
+
...(val ? { val } : {}),
|
|
105
|
+
...(fill ? { fill } : {}),
|
|
106
|
+
...(color ? { color } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function readParagraphProperties(
|
|
111
|
+
node: XmlElementNode | undefined,
|
|
112
|
+
): CanonicalParagraphFormatting | undefined {
|
|
113
|
+
if (!node) return undefined;
|
|
114
|
+
|
|
115
|
+
const out: CanonicalParagraphFormatting = {};
|
|
116
|
+
|
|
117
|
+
const spacingNode = findChildOptional(node, "spacing");
|
|
118
|
+
if (spacingNode) {
|
|
119
|
+
const spacing = readSpacing(spacingNode);
|
|
120
|
+
if (spacing) out.spacing = spacing;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const indNode = findChildOptional(node, "ind");
|
|
124
|
+
if (indNode) {
|
|
125
|
+
const ind = readIndent(indNode);
|
|
126
|
+
if (ind) out.indentation = ind;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const jcNode = findChildOptional(node, "jc");
|
|
130
|
+
if (jcNode) {
|
|
131
|
+
const raw = jcNode.attributes["w:val"] ?? jcNode.attributes.val;
|
|
132
|
+
if (
|
|
133
|
+
raw === "left" ||
|
|
134
|
+
raw === "center" ||
|
|
135
|
+
raw === "right" ||
|
|
136
|
+
raw === "both" ||
|
|
137
|
+
raw === "distribute" ||
|
|
138
|
+
raw === "start" ||
|
|
139
|
+
raw === "end"
|
|
140
|
+
) {
|
|
141
|
+
out.alignment = raw;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const bordersNode = findChildOptional(node, "pBdr");
|
|
146
|
+
if (bordersNode) {
|
|
147
|
+
const borders = readBorders(bordersNode);
|
|
148
|
+
if (borders) out.borders = borders;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const shdNode = findChildOptional(node, "shd");
|
|
152
|
+
if (shdNode) {
|
|
153
|
+
const shading = readShading(shdNode);
|
|
154
|
+
if (shading) out.shading = shading;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tabsNode = findChildOptional(node, "tabs");
|
|
158
|
+
if (tabsNode) {
|
|
159
|
+
const tabs = readTabStops(tabsNode);
|
|
160
|
+
if (tabs) out.tabStops = tabs;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const keepNext = readOnOff(findChildOptional(node, "keepNext"));
|
|
164
|
+
if (keepNext !== undefined) out.keepNext = keepNext;
|
|
165
|
+
const keepLines = readOnOff(findChildOptional(node, "keepLines"));
|
|
166
|
+
if (keepLines !== undefined) out.keepLines = keepLines;
|
|
167
|
+
const widow = readOnOff(findChildOptional(node, "widowControl"));
|
|
168
|
+
if (widow !== undefined) out.widowControl = widow;
|
|
169
|
+
const pbb = readOnOff(findChildOptional(node, "pageBreakBefore"));
|
|
170
|
+
if (pbb !== undefined) out.pageBreakBefore = pbb;
|
|
171
|
+
const ctx = readOnOff(findChildOptional(node, "contextualSpacing"));
|
|
172
|
+
if (ctx !== undefined) out.contextualSpacing = ctx;
|
|
173
|
+
const bidi = readOnOff(findChildOptional(node, "bidi"));
|
|
174
|
+
if (bidi !== undefined) out.bidi = bidi;
|
|
175
|
+
const suppressLn = readOnOff(findChildOptional(node, "suppressLineNumbers"));
|
|
176
|
+
if (suppressLn !== undefined) out.suppressLineNumbers = suppressLn;
|
|
177
|
+
const suppressAh = readOnOff(findChildOptional(node, "suppressAutoHyphens"));
|
|
178
|
+
if (suppressAh !== undefined) out.suppressAutoHyphens = suppressAh;
|
|
179
|
+
|
|
180
|
+
const outline = readIntVal(findChildOptional(node, "outlineLvl"));
|
|
181
|
+
if (outline !== undefined) out.outlineLevel = outline;
|
|
182
|
+
|
|
183
|
+
const rPrNode = findChildOptional(node, "rPr");
|
|
184
|
+
const markRpr = readRunProperties(rPrNode);
|
|
185
|
+
if (markRpr) out.paragraphMarkRunProperties = markRpr;
|
|
186
|
+
|
|
187
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
188
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { CanonicalRunFormatting } from "../../model/canonical-document.ts";
|
|
2
|
+
import { findChildOptional, readIntVal, readOnOff, readStringAttr } from "./xml-attr-helpers.ts";
|
|
3
|
+
import type { XmlElementNode } from "./xml-element.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read `<w:rPr>` (run properties) into a `CanonicalRunFormatting` value.
|
|
7
|
+
*
|
|
8
|
+
* Returns `undefined` when the input is `undefined` or when the rPr is empty
|
|
9
|
+
* (i.e., no recognized child produces a field). `colorHex` may carry the
|
|
10
|
+
* sentinel `"auto"`; `fontFamily` is the alias of the first-available
|
|
11
|
+
* `fontFamily{Ascii,HAnsi,EastAsia,Cs}`.
|
|
12
|
+
*
|
|
13
|
+
* Caller-side: route every `<w:rPr>` from styles.xml, numbering.xml, and
|
|
14
|
+
* the paragraph mark in document.xml through this helper so the cascade
|
|
15
|
+
* sees the same shape.
|
|
16
|
+
*/
|
|
17
|
+
export function readRunProperties(
|
|
18
|
+
node: XmlElementNode | undefined,
|
|
19
|
+
): CanonicalRunFormatting | undefined {
|
|
20
|
+
if (!node) return undefined;
|
|
21
|
+
|
|
22
|
+
const rPr: CanonicalRunFormatting = {};
|
|
23
|
+
|
|
24
|
+
const bold = readOnOff(findChildOptional(node, "b"));
|
|
25
|
+
if (bold !== undefined) rPr.bold = bold;
|
|
26
|
+
|
|
27
|
+
const italic = readOnOff(findChildOptional(node, "i"));
|
|
28
|
+
if (italic !== undefined) rPr.italic = italic;
|
|
29
|
+
|
|
30
|
+
const strike = readOnOff(findChildOptional(node, "strike"));
|
|
31
|
+
if (strike !== undefined) rPr.strikethrough = strike;
|
|
32
|
+
|
|
33
|
+
const dstrike = readOnOff(findChildOptional(node, "dstrike"));
|
|
34
|
+
if (dstrike !== undefined) rPr.doubleStrikethrough = dstrike;
|
|
35
|
+
|
|
36
|
+
const vanish = readOnOff(findChildOptional(node, "vanish"));
|
|
37
|
+
if (vanish !== undefined) rPr.vanish = vanish;
|
|
38
|
+
|
|
39
|
+
const allCaps = readOnOff(findChildOptional(node, "caps"));
|
|
40
|
+
if (allCaps !== undefined) rPr.allCaps = allCaps;
|
|
41
|
+
|
|
42
|
+
const smallCaps = readOnOff(findChildOptional(node, "smallCaps"));
|
|
43
|
+
if (smallCaps !== undefined) rPr.smallCaps = smallCaps;
|
|
44
|
+
|
|
45
|
+
const u = findChildOptional(node, "u");
|
|
46
|
+
if (u) {
|
|
47
|
+
const val =
|
|
48
|
+
u.attributes["w:val"] ?? u.attributes["val"] ?? "single";
|
|
49
|
+
if (val === "none") {
|
|
50
|
+
rPr.underline = "none";
|
|
51
|
+
} else if (
|
|
52
|
+
val === "single" ||
|
|
53
|
+
val === "double" ||
|
|
54
|
+
val === "thick" ||
|
|
55
|
+
val === "dotted" ||
|
|
56
|
+
val === "dash" ||
|
|
57
|
+
val === "wave"
|
|
58
|
+
) {
|
|
59
|
+
rPr.underline = val;
|
|
60
|
+
} else {
|
|
61
|
+
// Unknown underline type — coerce to "single"
|
|
62
|
+
rPr.underline = "single";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const vertAlign = findChildOptional(node, "vertAlign");
|
|
67
|
+
if (vertAlign) {
|
|
68
|
+
const val =
|
|
69
|
+
vertAlign.attributes["w:val"] ?? vertAlign.attributes["val"];
|
|
70
|
+
if (
|
|
71
|
+
val === "superscript" ||
|
|
72
|
+
val === "subscript" ||
|
|
73
|
+
val === "baseline"
|
|
74
|
+
) {
|
|
75
|
+
rPr.verticalAlign = val;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const rFonts = findChildOptional(node, "rFonts");
|
|
80
|
+
if (rFonts) {
|
|
81
|
+
const ascii = rFonts.attributes["w:ascii"] ?? rFonts.attributes.ascii;
|
|
82
|
+
const hAnsi = rFonts.attributes["w:hAnsi"] ?? rFonts.attributes.hAnsi;
|
|
83
|
+
const eastAsia = rFonts.attributes["w:eastAsia"] ?? rFonts.attributes.eastAsia;
|
|
84
|
+
const cs = rFonts.attributes["w:cs"] ?? rFonts.attributes.cs;
|
|
85
|
+
if (ascii) rPr.fontFamilyAscii = ascii;
|
|
86
|
+
if (hAnsi) rPr.fontFamilyHAnsi = hAnsi;
|
|
87
|
+
if (eastAsia) rPr.fontFamilyEastAsia = eastAsia;
|
|
88
|
+
if (cs) rPr.fontFamilyCs = cs;
|
|
89
|
+
const primary = ascii ?? hAnsi ?? eastAsia ?? cs;
|
|
90
|
+
if (primary) rPr.fontFamily = primary;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sz = readIntVal(findChildOptional(node, "sz"));
|
|
94
|
+
if (sz !== undefined) rPr.fontSizeHalfPoints = sz;
|
|
95
|
+
|
|
96
|
+
const szCs = readIntVal(findChildOptional(node, "szCs"));
|
|
97
|
+
if (szCs !== undefined) rPr.fontSizeCsHalfPoints = szCs;
|
|
98
|
+
|
|
99
|
+
const color = findChildOptional(node, "color");
|
|
100
|
+
if (color) {
|
|
101
|
+
const val = color.attributes["w:val"] ?? color.attributes["val"];
|
|
102
|
+
const theme =
|
|
103
|
+
color.attributes["w:themeColor"] ?? color.attributes["themeColor"];
|
|
104
|
+
if (val) rPr.colorHex = val;
|
|
105
|
+
if (theme) rPr.colorThemeSlot = theme;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const highlight = findChildOptional(node, "highlight");
|
|
109
|
+
if (highlight) {
|
|
110
|
+
const val =
|
|
111
|
+
highlight.attributes["w:val"] ?? highlight.attributes["val"];
|
|
112
|
+
if (val) rPr.highlight = val;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const spacing = readIntVal(findChildOptional(node, "spacing"));
|
|
116
|
+
if (spacing !== undefined) rPr.characterSpacingTwips = spacing;
|
|
117
|
+
|
|
118
|
+
const rStyleNode = findChildOptional(node, "rStyle");
|
|
119
|
+
const rStyle = rStyleNode ? readStringAttr(rStyleNode, "w:val") : undefined;
|
|
120
|
+
if (rStyle) rPr.characterStyleId = rStyle;
|
|
121
|
+
|
|
122
|
+
const lang = findChildOptional(node, "lang");
|
|
123
|
+
if (lang) {
|
|
124
|
+
const val = lang.attributes["w:val"] ?? lang.attributes["val"];
|
|
125
|
+
if (val) rPr.languageCode = val;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Object.keys(rPr).length > 0 ? rPr : undefined;
|
|
129
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import type {
|
|
11
11
|
CellShading,
|
|
12
12
|
CharacterStyleDefinition,
|
|
13
|
+
DocumentDefaults,
|
|
13
14
|
LatentStyleDefinition,
|
|
14
15
|
ParagraphStyleDefinition,
|
|
15
16
|
StylesCatalog,
|
|
@@ -37,6 +38,8 @@ import {
|
|
|
37
38
|
readTableWidth,
|
|
38
39
|
} from "./parse-tables.ts";
|
|
39
40
|
import { toCanonicalNumberingInstanceId } from "./parse-numbering.ts";
|
|
41
|
+
import { readRunProperties } from "./parse-run-formatting.ts";
|
|
42
|
+
import { readParagraphProperties } from "./parse-paragraph-formatting.ts";
|
|
40
43
|
|
|
41
44
|
// ---------------------------------------------------------------------------
|
|
42
45
|
// Inline XML node types (same pattern as parse-numbering.ts)
|
|
@@ -116,11 +119,28 @@ export function parseStylesXml(xml: string): ParseStylesResult {
|
|
|
116
119
|
const characters: Record<string, CharacterStyleDefinition> = {};
|
|
117
120
|
const tables: Record<string, TableStyleDefinition> = {};
|
|
118
121
|
const latentStyles: Record<string, LatentStyleDefinition> = {};
|
|
122
|
+
let docDefaults: DocumentDefaults | undefined;
|
|
119
123
|
|
|
120
124
|
for (const child of stylesElement.children) {
|
|
121
125
|
if (child.type !== "element") continue;
|
|
122
126
|
const local = localName(child.name);
|
|
123
127
|
|
|
128
|
+
if (local === "docDefaults") {
|
|
129
|
+
const pPrDefault = findChildElementOptional(child, "pPrDefault");
|
|
130
|
+
const rPrDefault = findChildElementOptional(child, "rPrDefault");
|
|
131
|
+
const pPrNode = pPrDefault ? findChildElementOptional(pPrDefault, "pPr") : undefined;
|
|
132
|
+
const rPrNode = rPrDefault ? findChildElementOptional(rPrDefault, "rPr") : undefined;
|
|
133
|
+
const paragraph = readParagraphProperties(pPrNode);
|
|
134
|
+
const run = readRunProperties(rPrNode);
|
|
135
|
+
if (paragraph || run) {
|
|
136
|
+
docDefaults = {
|
|
137
|
+
...(paragraph ? { paragraph } : {}),
|
|
138
|
+
...(run ? { run } : {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
124
144
|
if (local === "style") {
|
|
125
145
|
const styleType = child.attributes["w:type"] ?? child.attributes.type;
|
|
126
146
|
const styleId = child.attributes["w:styleId"] ?? child.attributes.styleId;
|
|
@@ -135,6 +155,10 @@ export function parseStylesXml(xml: string): ParseStylesResult {
|
|
|
135
155
|
const nextStyle = readLinkedStyleId(child, "next");
|
|
136
156
|
const outlineLevel = readParagraphStyleOutlineLevel(child);
|
|
137
157
|
const numbering = readParagraphStyleNumbering(child);
|
|
158
|
+
const pPrNode = findChildElementOptional(child, "pPr");
|
|
159
|
+
const rPrNode = findChildElementOptional(child, "rPr");
|
|
160
|
+
const paragraphProperties = readParagraphProperties(pPrNode);
|
|
161
|
+
const runProperties = readRunProperties(rPrNode);
|
|
138
162
|
paragraphs[styleId] = {
|
|
139
163
|
styleId,
|
|
140
164
|
displayName,
|
|
@@ -144,16 +168,21 @@ export function parseStylesXml(xml: string): ParseStylesResult {
|
|
|
144
168
|
...(nextStyle ? { nextStyle } : {}),
|
|
145
169
|
...(outlineLevel !== undefined ? { outlineLevel } : {}),
|
|
146
170
|
...(numbering ? { numbering } : {}),
|
|
171
|
+
...(paragraphProperties ? { paragraphProperties } : {}),
|
|
172
|
+
...(runProperties ? { runProperties } : {}),
|
|
147
173
|
};
|
|
148
174
|
break;
|
|
149
175
|
}
|
|
150
176
|
case "character": {
|
|
177
|
+
const rPrNode = findChildElementOptional(child, "rPr");
|
|
178
|
+
const runProperties = readRunProperties(rPrNode);
|
|
151
179
|
characters[styleId] = {
|
|
152
180
|
styleId,
|
|
153
181
|
displayName,
|
|
154
182
|
kind: "character",
|
|
155
183
|
isDefault,
|
|
156
184
|
...(basedOn ? { basedOn } : {}),
|
|
185
|
+
...(runProperties ? { runProperties } : {}),
|
|
157
186
|
};
|
|
158
187
|
break;
|
|
159
188
|
}
|
|
@@ -194,6 +223,8 @@ export function parseStylesXml(xml: string): ParseStylesResult {
|
|
|
194
223
|
characters,
|
|
195
224
|
tables,
|
|
196
225
|
...(hasLatent ? { latentStyles } : {}),
|
|
226
|
+
fromPackage: true,
|
|
227
|
+
...(docDefaults ? { docDefaults } : {}),
|
|
197
228
|
},
|
|
198
229
|
fromPackage: true,
|
|
199
230
|
diagnostics,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared XML helpers for OOXML parsers.
|
|
3
|
+
*
|
|
4
|
+
* These functions factor out the common pattern of reading namespaced-prefixed
|
|
5
|
+
* attributes (e.g., `w:val`) with fallback to the unprefixed form, handling
|
|
6
|
+
* the OOXML `ST_OnOff` toggle semantics (missing child = undefined, present
|
|
7
|
+
* with no val = true, `val="0"|"false"|"off"` = false), and reading integers.
|
|
8
|
+
*
|
|
9
|
+
* Used by parse-run-formatting.ts, parse-paragraph-formatting.ts, and (later)
|
|
10
|
+
* parse-styles.ts, parse-numbering.ts.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { XmlElementNode } from "./xml-element.ts";
|
|
14
|
+
|
|
15
|
+
export function localName(name: string): string {
|
|
16
|
+
const sep = name.indexOf(":");
|
|
17
|
+
return sep >= 0 ? name.slice(sep + 1) : name;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function findChildOptional(
|
|
21
|
+
node: XmlElementNode,
|
|
22
|
+
local: string,
|
|
23
|
+
): XmlElementNode | undefined {
|
|
24
|
+
return node.children.find(
|
|
25
|
+
(c): c is XmlElementNode => c.type === "element" && localName(c.name) === local,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** ST_OnOff: missing child → undefined; present bare or w:val="1|true|on" → true; w:val="0|false|off" → false. */
|
|
30
|
+
export function readOnOff(node: XmlElementNode | undefined): boolean | undefined {
|
|
31
|
+
if (!node) return undefined;
|
|
32
|
+
const raw = node.attributes["w:val"] ?? node.attributes.val;
|
|
33
|
+
if (raw === undefined) return true;
|
|
34
|
+
const n = raw.toLowerCase();
|
|
35
|
+
if (n === "0" || n === "false" || n === "off") return false;
|
|
36
|
+
if (n === "1" || n === "true" || n === "on") return true;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Read the child's `w:val` attribute as an int. Returns undefined if missing or not a finite integer. */
|
|
41
|
+
export function readIntVal(node: XmlElementNode | undefined): number | undefined {
|
|
42
|
+
if (!node) return undefined;
|
|
43
|
+
const raw = node.attributes["w:val"] ?? node.attributes.val;
|
|
44
|
+
if (raw === undefined) return undefined;
|
|
45
|
+
const v = Number.parseInt(raw, 10);
|
|
46
|
+
return Number.isFinite(v) ? v : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Read an arbitrary attribute from a node as an int, with namespace fallback. */
|
|
50
|
+
export function readIntAttr(node: XmlElementNode, attr: string): number | undefined {
|
|
51
|
+
const raw = node.attributes[attr] ?? node.attributes[attr.replace(/^w:/, "")];
|
|
52
|
+
if (raw === undefined) return undefined;
|
|
53
|
+
const v = Number.parseInt(raw, 10);
|
|
54
|
+
return Number.isFinite(v) ? v : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Read an arbitrary attribute from a node as a string, with namespace fallback. */
|
|
58
|
+
export function readStringAttr(node: XmlElementNode, attr: string): string | undefined {
|
|
59
|
+
return node.attributes[attr] ?? node.attributes[attr.replace(/^w:/, "")];
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface XmlElementNode {
|
|
2
|
+
type: "element";
|
|
3
|
+
name: string;
|
|
4
|
+
attributes: Record<string, string>;
|
|
5
|
+
children: Array<XmlElementNode | XmlTextNode>;
|
|
6
|
+
/** Optional source offset (start) — parsers that track offsets may populate. */
|
|
7
|
+
start?: number;
|
|
8
|
+
/** Optional source offset (end) — parsers that track offsets may populate. */
|
|
9
|
+
end?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface XmlTextNode {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
start?: number;
|
|
16
|
+
end?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type XmlNode = XmlElementNode | XmlTextNode;
|