@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @portone/docx-editor
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#102](https://github.com/portone-io/docx-editor/pull/102) [`4258530`](https://github.com/portone-io/docx-editor/commit/4258530aaf391ba546a282f73dff7b45f7063829) Thanks [@Deea222](https://github.com/Deea222)! - Draw the table styles and list numbering a document defines, and let a document that never held a list take one.
|
|
8
|
+
|
|
9
|
+
A table style is now displayed part by part: the header row, the closing row, the first and last column, the corners and the banded rows take the shading, the lines and the text formatting the style dresses them with (`w:tblStylePr`), and `w:tblLook` decides which of those parts a table takes.
|
|
10
|
+
The bands are as many rows or columns wide as the style says (`w:tblStyleRowBandSize`, `w:tblStyleColBandSize`), and the header row and the closing row are not banded with the rest.
|
|
11
|
+
Text already in a cell follows its new position after a row edit, and the conditional-formatting markup a document arrived with goes back out untouched.
|
|
12
|
+
|
|
13
|
+
A list that takes its numbers from a numbering style now draws them.
|
|
14
|
+
Such a list holds no numbers of its own: it names a style (`w:numStyleLink`), the style names a list, and that list's definition is where the numbers are, which is how Word writes a list built from its gallery.
|
|
15
|
+
Alongside decimal, bullets, upper and lower letters and lower Roman numerals, a list may now count in upper Roman numerals, decimal numbers with a leading zero, Ganada, Korean digits and the Chinese counting system; a format past those is still shown with decimal numbers.
|
|
16
|
+
Each level is drawn the way it asks to be: where its counting starts over (`w:lvlRestart`), whether the numbers in its text are all spelled as decimals (`w:isLgl`), what stands between its number and the paragraph text (`w:suff`), and where the number sits in the room kept for it (`w:lvlJc`).
|
|
17
|
+
A marker is also drawn in the character formatting its own level writes down (`lvl/rPr`), so bold, italic, color, size and typeface reach the number and never the text of the paragraph it stands in front of.
|
|
18
|
+
All of this is read; the numbering part goes back out exactly as it arrived.
|
|
19
|
+
|
|
20
|
+
A new list no longer needs the document to have arrived with a numbering part.
|
|
21
|
+
The export writes `word/numbering.xml`, relates it from the main part and declares it in the content types, so a document that never held a list can take one; the list commands, which used to report that they did not apply in such a document, now do.
|
|
22
|
+
If an existing numbering relationship points to a missing part, the new definition is written at that location so the document can find the list when it is reopened.
|
|
23
|
+
|
|
24
|
+
A new or pasted list is exported with the definition it was started with, rather than a format inferred from its numbering IDs, and undo restores that registration along with the edit.
|
|
25
|
+
A list whose definition is missing or unsupported is refused as `unsupported-content`, and a package with no `[Content_Types].xml` for the new numbering part to be declared in is refused as `missing-content-types`; `exportProblems` reports either ahead of the write.
|
|
26
|
+
List and abstract definition IDs stay distinct in a document already using the largest safely representable integer, and a value that cannot be written faithfully is rejected rather than silently changed or discarded.
|
|
27
|
+
|
|
28
|
+
An ECMA-376 Strict package is refused with the new `unsupported-conformance` code, and a document whose main part root does not bind `w` to Transitional WordprocessingML is refused as `unsupported-content`.
|
|
29
|
+
Word saves neither by default, and both used to open far enough to fail later and obscurely: a Strict package as `missing-part`, and a document under another prefix as a `malformed-xml` about bookmarks on its first edit.
|
|
30
|
+
A write needing a prefix already bound to a different namespace is refused instead of silently giving new markup the wrong meaning, and a new hyperlink relationship attribute is checked for a declaration that shadows the root binding.
|
|
31
|
+
The comment and hyperlink writers now have the part's root declare the prefix they write under rather than declaring it on every element written, and editing an existing numbering or comment extension part that uses another prefix adds the writer's declaration at the root, so an untouched document still exports byte for byte and the exported XML stays readable.
|
|
32
|
+
|
|
33
|
+
`DocxImportErrorCode` gains `unsupported-conformance`.
|
|
34
|
+
`DocxExportErrorCode` loses `missing-numbering-part`, since nothing can reach it any more, and that is a compile error for a `switch` over the code written to be exhaustive; drop the branch.
|
|
35
|
+
`missing-content-types` now also covers the numbering part a new list is defined in.
|
|
36
|
+
On `./core`, `Numbering` gains `added`, and the new `NewList` and `NewListLevel` types describe the definitions the editor registered.
|
|
37
|
+
Level maps are read-only, and a registered definition carries a restart, legal numbering and a suffix, while marker run formatting and custom tab stops stay outside what can be registered.
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- [#101](https://github.com/portone-io/docx-editor/pull/101) [`1b6c723`](https://github.com/portone-io/docx-editor/commit/1b6c723982eed9c10656d57aca1d496e6baad5ec) Thanks [@Deea222](https://github.com/Deea222)! - Stop drawing the page number on the corner of each page.
|
|
42
|
+
|
|
43
|
+
The page guides laid a small grey number inside the top right corner of every page, over the paper the text sits on.
|
|
44
|
+
It is gone, and the guides now draw the gaps between pages and the header and footer stories alone.
|
|
45
|
+
A document that prints its own page number through a `PAGE` field in a header or footer is unaffected: that number is the document's, not the editor's.
|
|
46
|
+
|
|
47
|
+
The `docx-editor-page-badge` class the number carried is no longer emitted, so a rule of your own written against it in the published stylesheet no longer matches anything.
|
|
48
|
+
|
|
3
49
|
## 0.3.0
|
|
4
50
|
|
|
5
51
|
### Minor Changes
|
package/dist/DocxEditor.js
CHANGED
|
@@ -115,6 +115,7 @@ var IMPORT_REJECTION_REASON = {
|
|
|
115
115
|
"missing-part": "This document is missing the part that holds its body.",
|
|
116
116
|
"missing-body": "This document has no body.",
|
|
117
117
|
"malformed-xml": "The XML inside this document cannot be read.",
|
|
118
|
+
"unsupported-conformance": "This document is saved in a format this editor cannot open. Save it as a Word document and try again.",
|
|
118
119
|
"unsupported-content": "This document holds content that could not be kept as it is."
|
|
119
120
|
};
|
|
120
121
|
function ImportRejection({ error }) {
|
package/dist/core.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type { DocxSession } from "./docx/session";
|
|
|
27
27
|
export { documentNumbering, documentPartPath } from "./docx/session";
|
|
28
28
|
export type { CellFormat, CellVerticalAlign, DocumentDefaults, HighlightName, LineSpacing, NumberingRef, ParagraphAlign, ParagraphFormat, RowFormat, RowHeight, RunFormat, TableFormat, TableWidth, TableWidthType, UnderlineKind, VerticalAlign, } from "./model/format";
|
|
29
29
|
export { toCellFormat, toParagraphFormat, toRowFormat, toRunFormat, toTableFormat, toTableWidth, } from "./model/format";
|
|
30
|
-
export type { LevelIndent, NumberFormat, Numbering, NumberingLevel, NumberingList, NumberingOptions, } from "./numbering/parseNumbering";
|
|
30
|
+
export type { LevelAlign, LevelIndent, LevelSuffix, NewList, NewListLevel, NumberFormat, Numbering, NumberingLevel, NumberingList, NumberingOptions, NumberingStyleLinks, ReadLevelRun, } from "./numbering/parseNumbering";
|
|
31
31
|
export { parseNumbering } from "./numbering/parseNumbering";
|
|
32
32
|
export type { DocxExportErrorCode, DocxImportErrorCode, } from "./ooxml/errors";
|
|
33
33
|
export { DocxExportError, DocxImportError } from "./ooxml/errors";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/docx/comments/writing.ts
|
|
2
|
-
import { elementXml
|
|
2
|
+
import { elementXml } from "../../ooxml/element.js";
|
|
3
3
|
import { NAMESPACES, wName, xmlnsDecl } from "../../ooxml/names.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureRootDeclarations,
|
|
@@ -74,10 +74,13 @@ function extensionsXml(references, comments, originallyReferenced) {
|
|
|
74
74
|
if (comments.extendedXml === null) {
|
|
75
75
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:commentsEx ${xmlnsDecl("w15")}>${pieces.join("")}</w15:commentsEx>`;
|
|
76
76
|
}
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
return ensureRootDeclarations(
|
|
78
|
+
splicePart(comments.extendedXml, {
|
|
79
|
+
root: EXTENSIONS_ROOT,
|
|
80
|
+
replaceChildren: pieces.join("")
|
|
81
|
+
}),
|
|
82
|
+
{ namespaces: { w15: NAMESPACES.w15 } }
|
|
83
|
+
);
|
|
81
84
|
}
|
|
82
85
|
var COMMENTS_ROOT = "comments";
|
|
83
86
|
var EXTENSIONS_ROOT = "commentsEx";
|
|
@@ -117,7 +120,6 @@ function renderedComment(comment, arrivedKeyed, arrived) {
|
|
|
117
120
|
) : comment.commentXml;
|
|
118
121
|
}
|
|
119
122
|
const attrs = [
|
|
120
|
-
xmlnsAttr("w"),
|
|
121
123
|
[wName("id"), comment.id],
|
|
122
124
|
comment.author === null ? null : [wName("author"), comment.author],
|
|
123
125
|
comment.date === null ? null : [wName("date"), comment.date],
|
|
@@ -131,8 +133,9 @@ function renderedComment(comment, arrivedKeyed, arrived) {
|
|
|
131
133
|
[body]
|
|
132
134
|
);
|
|
133
135
|
}
|
|
136
|
+
var COMMENT_MARKUP = { namespaces: { w: NAMESPACES.w } };
|
|
134
137
|
var THREAD_MARKUP = {
|
|
135
|
-
namespaces: { w14: NAMESPACES.w14, mc: NAMESPACES.mc },
|
|
138
|
+
namespaces: { w: NAMESPACES.w, w14: NAMESPACES.w14, mc: NAMESPACES.mc },
|
|
136
139
|
ignorable: ["w14"]
|
|
137
140
|
};
|
|
138
141
|
function currentCommentBodies(references) {
|
|
@@ -196,7 +199,10 @@ function commentsXml(references, comments, originallyReferenced) {
|
|
|
196
199
|
root: COMMENTS_ROOT,
|
|
197
200
|
replaceChildren: pieces.join("")
|
|
198
201
|
});
|
|
199
|
-
return
|
|
202
|
+
return ensureRootDeclarations(
|
|
203
|
+
rewritten,
|
|
204
|
+
hasThreadMetadata ? THREAD_MARKUP : COMMENT_MARKUP
|
|
205
|
+
);
|
|
200
206
|
}
|
|
201
207
|
function planCommentParts(doc, session, context) {
|
|
202
208
|
const bodyChanged = commentsChanged(doc, session);
|
package/dist/docx/exportDocx.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// src/docx/exportDocx.ts
|
|
2
2
|
import { DocxExportError } from "../ooxml/errors.js";
|
|
3
|
+
import {
|
|
4
|
+
ensureRootDeclarations
|
|
5
|
+
} from "../ooxml/partSplice.js";
|
|
3
6
|
import {
|
|
4
7
|
encodeUtf8,
|
|
5
8
|
parseXml,
|
|
9
|
+
R_NS,
|
|
6
10
|
W_NS,
|
|
7
11
|
withXmlParser
|
|
8
12
|
} from "../ooxml/xml.js";
|
|
@@ -48,7 +52,7 @@ function buildDocumentXml(doc, session, refs) {
|
|
|
48
52
|
function localAttribute(el, name) {
|
|
49
53
|
return Array.from(el.attributes).find((attribute) => attribute.localName === name)?.value ?? null;
|
|
50
54
|
}
|
|
51
|
-
function
|
|
55
|
+
function assertMainPart(documentXml, wroteLinks) {
|
|
52
56
|
let root;
|
|
53
57
|
try {
|
|
54
58
|
root = parseXml(documentXml).documentElement;
|
|
@@ -63,6 +67,15 @@ function assertBookmarkPairs(documentXml) {
|
|
|
63
67
|
const used = /* @__PURE__ */ new Set();
|
|
64
68
|
for (const element of root.getElementsByTagName("*")) {
|
|
65
69
|
if (element.namespaceURI !== W_NS) continue;
|
|
70
|
+
if (wroteLinks && element.localName === "hyperlink") {
|
|
71
|
+
const id2 = element.getAttributeNode("r:id");
|
|
72
|
+
if (id2 && id2.namespaceURI !== R_NS) {
|
|
73
|
+
throw new DocxExportError(
|
|
74
|
+
"unsupported-content",
|
|
75
|
+
"a hyperlink's r:id is shadowed by a different relationship namespace"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
66
79
|
if (element.localName !== "bookmarkStart" && element.localName !== "bookmarkEnd") {
|
|
67
80
|
continue;
|
|
68
81
|
}
|
|
@@ -97,6 +110,7 @@ function assertBookmarkPairs(documentXml) {
|
|
|
97
110
|
);
|
|
98
111
|
}
|
|
99
112
|
}
|
|
113
|
+
var LINK_MARKUP = { namespaces: { r: R_NS } };
|
|
100
114
|
var PART_PLANNERS = [
|
|
101
115
|
numberingPlanner,
|
|
102
116
|
commentsPlanner
|
|
@@ -135,12 +149,14 @@ function writeDocx(doc, store, notes, planners) {
|
|
|
135
149
|
contentTypes: contentTypeWriter(store.parts)
|
|
136
150
|
};
|
|
137
151
|
const media = planImageMedia(doc, store, context);
|
|
138
|
-
const
|
|
152
|
+
const links = hyperlinkRefs(context.relationships);
|
|
153
|
+
const body = buildDocumentXml(doc, store, {
|
|
139
154
|
images: media?.refs ?? NO_IMAGE_REFS,
|
|
140
|
-
links
|
|
155
|
+
links,
|
|
141
156
|
notes
|
|
142
157
|
});
|
|
143
|
-
|
|
158
|
+
const documentXml = links.addedRelId() ? ensureRootDeclarations(body, LINK_MARKUP) : body;
|
|
159
|
+
assertMainPart(documentXml, links.addedRelId());
|
|
144
160
|
const parts = runPartPlanners(planners, doc, store, context, media?.parts);
|
|
145
161
|
const rels = context.relationships.part(store.parts.get(relsPath));
|
|
146
162
|
if (rels) parts.set(relsPath, rels);
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* and the clipboard reader all take them from here, so a paragraph is drawn the same whichever of
|
|
5
5
|
* them last touched it.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
8
|
-
import type
|
|
7
|
+
import { type Mark, type Node as PMNode } from "prosemirror-model";
|
|
8
|
+
import { type ParagraphFormat, type RunFormat } from "../../model/format";
|
|
9
9
|
import type { FormattingContext } from "./context";
|
|
10
10
|
import { type ParagraphPlacement, type ResolvedParagraph } from "./resolve";
|
|
11
11
|
/** The two derived paragraph attrs (`format` and `styleRun` in `schema`) */
|
|
@@ -25,3 +25,21 @@ export declare function paragraphAttrsFor(pPr: string | null, context: Formattin
|
|
|
25
25
|
* An inline that is not text and carries no mark is left as it is.
|
|
26
26
|
*/
|
|
27
27
|
export declare function runMarkUnder(mark: Mark | null, isText: boolean, paragraph: ResolvedParagraph, context: FormattingContext): Mark | null;
|
|
28
|
+
/**
|
|
29
|
+
* The paragraph with the values the hierarchy gives it, and the text inside it wearing the run
|
|
30
|
+
* mark that goes with them.
|
|
31
|
+
*
|
|
32
|
+
* The style's run values are also laid on the paragraph itself, so that text carrying no run of
|
|
33
|
+
* its own - typed in the editor - is drawn in them (`styleRun` in `schema`).
|
|
34
|
+
* Where the paragraph stands in a table cell, `placement` is the part of the table it belongs to,
|
|
35
|
+
* which the table style dresses on top of everything else it lays down.
|
|
36
|
+
*/
|
|
37
|
+
export declare function styledParagraph(node: PMNode, context: FormattingContext, placement?: ParagraphPlacement | null): PMNode;
|
|
38
|
+
/** Table display inputs, refreshed from the same style context as the paragraphs inside it. */
|
|
39
|
+
export declare function tableStyleAttrs(tblPr: Element | null, context: FormattingContext): {
|
|
40
|
+
format: import("../..").TableFormat | null;
|
|
41
|
+
styleInside: import("../tableFormatting").InsideBorders | null;
|
|
42
|
+
styleCellMargins: import("../tableFormatting").CellMargins | null;
|
|
43
|
+
styleConditions: Readonly<Partial<Record<"band1Horz" | "band1Vert" | "band2Horz" | "band2Vert" | "firstCol" | "firstRow" | "lastCol" | "lastRow" | "neCell" | "nwCell" | "seCell" | "swCell" | "wholeTable", import("../tableFormatting").CellStyleFormat>>> | null;
|
|
44
|
+
styleBands: import("../tableFormatting").BandSizes | null;
|
|
45
|
+
};
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
// src/docx/formatting/attrs.ts
|
|
2
|
+
import { Fragment } from "prosemirror-model";
|
|
3
|
+
import {
|
|
4
|
+
toBandSizes,
|
|
5
|
+
toCellMargins,
|
|
6
|
+
toInsideBorders,
|
|
7
|
+
toTableStyleConditions
|
|
8
|
+
} from "../../model/format.js";
|
|
2
9
|
import { docxSchema } from "../../schema/index.js";
|
|
10
|
+
import {
|
|
11
|
+
cellConditionsOf,
|
|
12
|
+
layerBandSizes,
|
|
13
|
+
NO_BAND_SIZES,
|
|
14
|
+
readBandSizes,
|
|
15
|
+
readTableFormat,
|
|
16
|
+
tblStyleIdOf
|
|
17
|
+
} from "../tableFormatting.js";
|
|
3
18
|
import {
|
|
4
19
|
resolveParagraph,
|
|
5
|
-
resolveRun
|
|
20
|
+
resolveRun,
|
|
21
|
+
tableStyleFor
|
|
6
22
|
} from "./resolve.js";
|
|
23
|
+
import { layerTableFormat } from "./styles.js";
|
|
7
24
|
function paragraphAttrsOf(paragraph) {
|
|
8
25
|
return { format: paragraph.format, styleRun: paragraph.styleRun };
|
|
9
26
|
}
|
|
@@ -24,8 +41,46 @@ function runMarkUnder(mark, isText, paragraph, context) {
|
|
|
24
41
|
const format = resolveRun(null, paragraph, context);
|
|
25
42
|
return format === null ? null : docxSchema.marks.run.create({ rPr: null, rAttrs: null, format });
|
|
26
43
|
}
|
|
44
|
+
function styledParagraph(node, context, placement = null) {
|
|
45
|
+
const pPr = node.attrs.pPr;
|
|
46
|
+
const paragraph = resolveParagraph(
|
|
47
|
+
typeof pPr === "string" ? pPr : null,
|
|
48
|
+
context,
|
|
49
|
+
placement
|
|
50
|
+
);
|
|
51
|
+
const inline = node.children.map((child) => {
|
|
52
|
+
const mark = runMarkUnder(
|
|
53
|
+
child.marks.find((entry) => entry.type === docxSchema.marks.run) ?? null,
|
|
54
|
+
child.isText,
|
|
55
|
+
paragraph,
|
|
56
|
+
context
|
|
57
|
+
);
|
|
58
|
+
return mark ? child.mark(mark.addToSet(child.marks)) : child;
|
|
59
|
+
});
|
|
60
|
+
return node.type.create(
|
|
61
|
+
{ ...node.attrs, ...paragraphAttrsOf(paragraph) },
|
|
62
|
+
Fragment.fromArray(inline),
|
|
63
|
+
node.marks
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function tableStyleAttrs(tblPr, context) {
|
|
67
|
+
const style = tableStyleFor(tblStyleIdOf(tblPr), context);
|
|
68
|
+
return {
|
|
69
|
+
format: layerTableFormat(style?.table ?? {}, readTableFormat(tblPr)),
|
|
70
|
+
styleInside: toInsideBorders(style?.tableInside),
|
|
71
|
+
styleCellMargins: toCellMargins(style?.tableCellMargins),
|
|
72
|
+
styleConditions: toTableStyleConditions(
|
|
73
|
+
cellConditionsOf(style?.tableConditions ?? {})
|
|
74
|
+
),
|
|
75
|
+
styleBands: toBandSizes(
|
|
76
|
+
layerBandSizes(style?.tableBands ?? NO_BAND_SIZES, readBandSizes(tblPr))
|
|
77
|
+
)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
27
80
|
export {
|
|
28
81
|
paragraphAttrsFor,
|
|
29
82
|
paragraphAttrsOf,
|
|
30
|
-
runMarkUnder
|
|
83
|
+
runMarkUnder,
|
|
84
|
+
styledParagraph,
|
|
85
|
+
tableStyleAttrs
|
|
31
86
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RunFormat } from "../../model/format";
|
|
2
|
-
import { type Numbering } from "../../numbering/parseNumbering";
|
|
2
|
+
import { type Numbering, type NumberingOptions } from "../../numbering/parseNumbering";
|
|
3
3
|
import { type CompatSettings } from "../documentSettings";
|
|
4
4
|
import { type ThemeFonts } from "../theme";
|
|
5
5
|
import { type StyleTable } from "./styles";
|
|
@@ -27,5 +27,13 @@ export interface FormattingContext {
|
|
|
27
27
|
compat: CompatSettings;
|
|
28
28
|
}
|
|
29
29
|
export declare const NO_FORMATTING: FormattingContext;
|
|
30
|
+
/**
|
|
31
|
+
* How a document's numbering part is read against the rest of what the document lays down.
|
|
32
|
+
*
|
|
33
|
+
* The numbering folder cannot reach styles.xml, so the links a definition follows are handed to it
|
|
34
|
+
* from here, and the two callers that read a numbering part - opening a document and asking an
|
|
35
|
+
* open one for its lists - resolve the same links.
|
|
36
|
+
*/
|
|
37
|
+
export declare function numberingOptionsFor(styles: StyleTable, themeFonts: ThemeFonts): NumberingOptions;
|
|
30
38
|
/** The context an opened document resolves against. A document without a styles part lays down nothing */
|
|
31
|
-
export declare function formattingContextOf(styles: Document | null,
|
|
39
|
+
export declare function formattingContextOf(styles: Document | null, numberingXml: string | null, themeFonts: ThemeFonts, compat?: CompatSettings): FormattingContext;
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
// src/docx/formatting/context.ts
|
|
2
2
|
import {
|
|
3
|
-
EMPTY_NUMBERING
|
|
3
|
+
EMPTY_NUMBERING,
|
|
4
|
+
parseNumbering
|
|
4
5
|
} from "../../numbering/parseNumbering.js";
|
|
5
6
|
import { NO_COMPAT } from "../documentSettings.js";
|
|
6
7
|
import { NO_THEME_FONTS } from "../theme.js";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
readDefaultParagraphFormat,
|
|
10
|
+
readRunDefaults,
|
|
11
|
+
readRunFormat
|
|
12
|
+
} from "./direct.js";
|
|
8
13
|
import {
|
|
9
14
|
defaultParagraphStyleIdOf,
|
|
10
15
|
defaultTableStyleIdOf,
|
|
11
16
|
NO_STYLES,
|
|
17
|
+
numberingStyleLinks,
|
|
12
18
|
readStyles
|
|
13
19
|
} from "./styles.js";
|
|
14
20
|
var NO_FORMATTING = {
|
|
@@ -21,12 +27,23 @@ var NO_FORMATTING = {
|
|
|
21
27
|
themeFonts: NO_THEME_FONTS,
|
|
22
28
|
compat: NO_COMPAT
|
|
23
29
|
};
|
|
24
|
-
function
|
|
30
|
+
function numberingOptionsFor(styles, themeFonts) {
|
|
31
|
+
return {
|
|
32
|
+
links: numberingStyleLinks(styles),
|
|
33
|
+
readRun: (rPr) => readRunFormat(rPr, themeFonts)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function formattingContextOf(styles, numberingXml, themeFonts, compat = NO_COMPAT) {
|
|
37
|
+
const table = styles === null ? NO_STYLES : readStyles(styles, themeFonts);
|
|
38
|
+
const numbering = parseNumbering(
|
|
39
|
+
numberingXml,
|
|
40
|
+
numberingOptionsFor(table, themeFonts)
|
|
41
|
+
);
|
|
25
42
|
if (styles === null) {
|
|
26
43
|
return { ...NO_FORMATTING, numbering, themeFonts, compat };
|
|
27
44
|
}
|
|
28
45
|
return {
|
|
29
|
-
styles:
|
|
46
|
+
styles: table,
|
|
30
47
|
defaultParagraphStyleId: defaultParagraphStyleIdOf(styles),
|
|
31
48
|
defaultTableStyleId: defaultTableStyleIdOf(styles),
|
|
32
49
|
paragraphDefaults: readDefaultParagraphFormat(styles),
|
|
@@ -38,5 +55,6 @@ function formattingContextOf(styles, numbering, themeFonts, compat = NO_COMPAT)
|
|
|
38
55
|
}
|
|
39
56
|
export {
|
|
40
57
|
NO_FORMATTING,
|
|
41
|
-
formattingContextOf
|
|
58
|
+
formattingContextOf,
|
|
59
|
+
numberingOptionsFor
|
|
42
60
|
};
|
|
@@ -9,13 +9,12 @@ import { type ParagraphFormat, type RunFormat } from "../../model/format";
|
|
|
9
9
|
import type { TabStop } from "../../model/tabStops";
|
|
10
10
|
import type { TableStyleOverrideType } from "../tableFormatting";
|
|
11
11
|
import type { FormattingContext } from "./context";
|
|
12
|
+
import { type StyleFormat } from "./styles";
|
|
12
13
|
/** Where a paragraph sits when it is inside a table cell. null for body text */
|
|
13
14
|
export interface ParagraphPlacement {
|
|
15
|
+
/** What the table's `w:tblStyle` names. null for a table naming none, which wears the default one */
|
|
14
16
|
tableStyleId: string | null;
|
|
15
|
-
/**
|
|
16
|
-
* The conditional formats the cell takes, lowest first. Not read yet: a table style
|
|
17
|
-
* contributes the values it lays down for the whole table alone
|
|
18
|
-
*/
|
|
17
|
+
/** The parts of the table the cell belongs to, lowest first (`docx/tableFormatting/conditions`) */
|
|
19
18
|
conditions: readonly TableStyleOverrideType[];
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
@@ -38,6 +37,13 @@ export interface ResolvedParagraph {
|
|
|
38
37
|
/** The stops `format.tabStops` holds, each with the layer that laid it down */
|
|
39
38
|
tabStops: readonly LayeredTabStop[];
|
|
40
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The table style an object wearing this one is dressed by.
|
|
42
|
+
*
|
|
43
|
+
* A table that points at no style, or at one that is not defined, falls back on the document's
|
|
44
|
+
* default table style, which is what OOXML applies to an object with no style of its own.
|
|
45
|
+
*/
|
|
46
|
+
export declare function tableStyleFor(styleId: string | null, context: FormattingContext): StyleFormat | undefined;
|
|
41
47
|
export declare function resolveParagraph(pPr: string | null, context: FormattingContext, placement?: ParagraphPlacement | null): ResolvedParagraph;
|
|
42
48
|
/** `format` on a run mark: paragraph.styleRun < character style < the run's own rPr */
|
|
43
49
|
export declare function resolveRun(rPr: string | null, paragraph: ResolvedParagraph, context: FormattingContext): RunFormat | null;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
toParagraphFormat
|
|
4
4
|
} from "../../model/format.js";
|
|
5
|
+
import { listFor } from "../../numbering/listTemplate.js";
|
|
5
6
|
import {
|
|
6
7
|
levelIndentPt
|
|
7
8
|
} from "../../numbering/parseNumbering.js";
|
|
@@ -14,9 +15,17 @@ import {
|
|
|
14
15
|
function directLayer(pPr) {
|
|
15
16
|
return pPr === null ? {} : readParagraphFormat(parsePropsXml(pPr)) ?? {};
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
function tableStyleFor(styleId, context) {
|
|
19
|
+
const named = styleId === null ? void 0 : context.styles.get(styleId);
|
|
20
|
+
const fallback = context.defaultTableStyleId === null ? void 0 : context.styles.get(context.defaultTableStyleId);
|
|
21
|
+
return named ?? fallback;
|
|
22
|
+
}
|
|
23
|
+
function conditionsOf(style, placement) {
|
|
24
|
+
if (!style || !placement) return [];
|
|
25
|
+
return placement.conditions.flatMap((type) => {
|
|
26
|
+
const format = style.tableConditions[type];
|
|
27
|
+
return format ? [format] : [];
|
|
28
|
+
});
|
|
20
29
|
}
|
|
21
30
|
function numberingLevelOf(layers, context) {
|
|
22
31
|
const ref = layers.reduce(
|
|
@@ -24,7 +33,7 @@ function numberingLevelOf(layers, context) {
|
|
|
24
33
|
void 0
|
|
25
34
|
);
|
|
26
35
|
if (!ref) return void 0;
|
|
27
|
-
return context.numbering
|
|
36
|
+
return listFor(context.numbering, ref.numId)?.levels.get(ref.ilvl);
|
|
28
37
|
}
|
|
29
38
|
function numberingLayer(level) {
|
|
30
39
|
return level?.tabStops ? { tabStops: level.tabStops } : {};
|
|
@@ -66,16 +75,23 @@ function orNull(values) {
|
|
|
66
75
|
return Object.keys(values).length === 0 ? null : values;
|
|
67
76
|
}
|
|
68
77
|
function resolveParagraph(pPr, context, placement = null) {
|
|
69
|
-
const tableStyle =
|
|
78
|
+
const tableStyle = placement === null ? void 0 : tableStyleFor(placement.tableStyleId, context);
|
|
79
|
+
const conditions = conditionsOf(tableStyle, placement);
|
|
70
80
|
const style = paragraphStyleFormat(
|
|
71
81
|
pPr,
|
|
72
82
|
context.styles,
|
|
73
83
|
context.defaultParagraphStyleId
|
|
74
84
|
);
|
|
75
85
|
const direct = directLayer(pPr);
|
|
86
|
+
const table = [
|
|
87
|
+
{ values: tableStyle?.paragraph ?? {}, stops: "style" },
|
|
88
|
+
...conditions.map(
|
|
89
|
+
(condition) => ({ values: condition.paragraph, stops: "style" })
|
|
90
|
+
)
|
|
91
|
+
];
|
|
76
92
|
const explicit = [
|
|
77
93
|
{ values: context.paragraphDefaults, stops: "style" },
|
|
78
|
-
|
|
94
|
+
...table,
|
|
79
95
|
{ values: style?.paragraph ?? {}, stops: "style" },
|
|
80
96
|
{ values: direct, stops: "direct" }
|
|
81
97
|
];
|
|
@@ -83,10 +99,11 @@ function resolveParagraph(pPr, context, placement = null) {
|
|
|
83
99
|
explicit.map((layer) => layer.values),
|
|
84
100
|
context
|
|
85
101
|
);
|
|
102
|
+
const belowNumbering = 1 + table.length;
|
|
86
103
|
const layers = [
|
|
87
|
-
...explicit.slice(0,
|
|
104
|
+
...explicit.slice(0, belowNumbering),
|
|
88
105
|
{ values: numberingLayer(level), stops: "numbering" },
|
|
89
|
-
...explicit.slice(
|
|
106
|
+
...explicit.slice(belowNumbering)
|
|
90
107
|
];
|
|
91
108
|
const values = layers.reduce(
|
|
92
109
|
(base, layer) => layerValues(base, layer.values),
|
|
@@ -107,8 +124,10 @@ function resolveParagraph(pPr, context, placement = null) {
|
|
|
107
124
|
...displayDefaults
|
|
108
125
|
} = context.runDefaults;
|
|
109
126
|
const styleRun = {
|
|
110
|
-
...
|
|
111
|
-
|
|
127
|
+
...conditions.reduce(
|
|
128
|
+
(run, condition) => ({ ...run, ...condition.run }),
|
|
129
|
+
{ ...displayDefaults, ...tableStyle?.run }
|
|
130
|
+
),
|
|
112
131
|
...style?.run
|
|
113
132
|
};
|
|
114
133
|
return {
|
|
@@ -136,5 +155,6 @@ function inheritedRunFormat(rPr, paragraph, context) {
|
|
|
136
155
|
export {
|
|
137
156
|
inheritedRunFormat,
|
|
138
157
|
resolveParagraph,
|
|
139
|
-
resolveRun
|
|
158
|
+
resolveRun,
|
|
159
|
+
tableStyleFor
|
|
140
160
|
};
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/** Reads and layers styles.xml values used by the display model. */
|
|
2
|
-
import { type CellMargins, type InsideBorders, type ParagraphFormat, type RunFormat, type TableFormat } from "../../model/format";
|
|
2
|
+
import { type BandSizes, type CellMargins, type InsideBorders, type ParagraphFormat, type RunFormat, type TableFormat, type TableStyleOverrideType } from "../../model/format";
|
|
3
|
+
import type { NumberingStyleLinks } from "../../numbering/parseNumbering";
|
|
4
|
+
import { type ConditionalTableFormat } from "../tableFormatting";
|
|
3
5
|
import { type ThemeFonts } from "../theme";
|
|
4
6
|
import { type ParagraphFormatLayer } from "./tabStops";
|
|
7
|
+
/** The kind of object a style dresses (`w:style/@w:type`, ST_StyleType) */
|
|
8
|
+
export type StyleType = "paragraph" | "character" | "table" | "numbering";
|
|
9
|
+
/** What a table style dresses each part of a table with, by the part it covers */
|
|
10
|
+
export type TableStyleConditionFormats = Readonly<Partial<Record<TableStyleOverrideType, ConditionalTableFormat>>>;
|
|
5
11
|
/** The display values one style passes down to paragraphs, to text, and to tables */
|
|
6
12
|
export interface StyleFormat {
|
|
13
|
+
type: StyleType;
|
|
7
14
|
paragraph: ParagraphFormatLayer;
|
|
8
15
|
run: RunFormat;
|
|
9
16
|
table: TableFormat;
|
|
@@ -11,6 +18,14 @@ export interface StyleFormat {
|
|
|
11
18
|
tableInside: InsideBorders;
|
|
12
19
|
/** The cell margins the style laid down. A side it says nothing about is null */
|
|
13
20
|
tableCellMargins: CellMargins;
|
|
21
|
+
/** How many rows and columns one band of this table style is made of */
|
|
22
|
+
tableBands: BandSizes;
|
|
23
|
+
/**
|
|
24
|
+
* The conditional formats it wrote (`w:tblStylePr`). Each part is layered down the `basedOn`
|
|
25
|
+
* chain on its own, so a style that redresses the header row alone leaves the parts the styles
|
|
26
|
+
* it is based on dressed standing.
|
|
27
|
+
*/
|
|
28
|
+
tableConditions: TableStyleConditionFormats;
|
|
14
29
|
}
|
|
15
30
|
/** A table looked up by style name. The `basedOn` chain has already been layered into the values */
|
|
16
31
|
export type StyleTable = ReadonlyMap<string, StyleFormat>;
|
|
@@ -19,10 +34,18 @@ export declare const NO_STYLES: StyleTable;
|
|
|
19
34
|
* Reads the style chain from styles.xml into effective values for display.
|
|
20
35
|
*
|
|
21
36
|
* A style name is unique across all kinds (paragraph, character, table), so we take them in
|
|
22
|
-
* without telling the kinds apart.
|
|
37
|
+
* without telling the kinds apart, and each one remembers the kind it is.
|
|
23
38
|
* The names a paragraph points at are only ever paragraph styles, so the other kinds are never looked up.
|
|
24
39
|
*/
|
|
25
40
|
export declare function readStyles(styles: Document, themeFonts?: ThemeFonts): StyleTable;
|
|
41
|
+
/**
|
|
42
|
+
* The list each numbering style names.
|
|
43
|
+
*
|
|
44
|
+
* A numbering style carries its list in the `w:numPr` of its paragraph properties, and that is the
|
|
45
|
+
* only way a numbering definition deferring to a style (`w:numStyleLink`) finds the levels it
|
|
46
|
+
* draws. A style naming no list, or naming the empty list `w:numId` 0, links to nothing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function numberingStyleLinks(styles: StyleTable): NumberingStyleLinks;
|
|
26
49
|
/**
|
|
27
50
|
* The default table style, which for most documents is where a plain table gets its cell margins
|
|
28
51
|
* and its lines from
|