@portone/docx-editor 0.2.1 → 0.3.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 +109 -0
- package/CONTRIBUTING.md +5 -1
- package/dist/DocxEditor.d.ts +6 -0
- package/dist/DocxEditor.js +12 -29
- package/dist/core.d.ts +23 -8
- package/dist/core.js +9 -4
- package/dist/docx/cloning.d.ts +38 -0
- package/dist/docx/cloning.js +74 -0
- package/dist/docx/commentOnlyChange.d.ts +12 -7
- package/dist/docx/commentOnlyChange.js +9 -156
- package/dist/docx/comments/constants.d.ts +0 -1
- package/dist/docx/comments/constants.js +0 -2
- package/dist/docx/comments/grammar.d.ts +10 -4
- package/dist/docx/comments/grammar.js +12 -2
- package/dist/docx/comments/{verifying.d.ts → parts.d.ts} +26 -18
- package/dist/docx/comments/parts.js +279 -0
- package/dist/docx/comments/people.d.ts +11 -2
- package/dist/docx/comments/people.js +16 -92
- package/dist/docx/comments/policy.d.ts +7 -0
- package/dist/docx/comments/policy.js +43 -0
- package/dist/docx/comments/reading.js +8 -10
- package/dist/docx/comments/writing.d.ts +19 -7
- package/dist/docx/comments/writing.js +52 -111
- package/dist/docx/documentSettings.d.ts +7 -0
- package/dist/docx/documentSettings.js +10 -2
- package/dist/docx/exportDocx.d.ts +40 -5
- package/dist/docx/exportDocx.js +69 -71
- package/dist/docx/exportRefs.d.ts +5 -2
- package/dist/docx/exportRefs.js +3 -1
- package/dist/docx/fidelity.d.ts +43 -0
- package/dist/docx/fidelity.js +64 -0
- package/dist/docx/formatting/attrs.d.ts +27 -0
- package/dist/docx/formatting/attrs.js +31 -0
- package/dist/docx/formatting/context.d.ts +31 -0
- package/dist/docx/formatting/context.js +42 -0
- package/dist/docx/formatting/direct.d.ts +6 -5
- package/dist/docx/formatting/direct.js +52 -78
- package/dist/docx/formatting/resolve.d.ts +45 -0
- package/dist/docx/formatting/resolve.js +140 -0
- package/dist/docx/formatting/runProperties.d.ts +93 -0
- package/dist/docx/formatting/runProperties.js +316 -0
- package/dist/docx/formatting/styles.js +3 -3
- package/dist/docx/formatting/tabStops.js +7 -22
- package/dist/docx/formatting.d.ts +4 -1
- package/dist/docx/formatting.js +4 -1
- package/dist/docx/headersFooters.js +6 -13
- package/dist/docx/identities.d.ts +67 -0
- package/dist/docx/identities.js +174 -0
- package/dist/docx/importDocx.d.ts +12 -1
- package/dist/docx/importDocx.js +72 -79
- package/dist/docx/importParagraph.d.ts +1 -1
- package/dist/docx/importParagraph.js +4 -3
- package/dist/docx/importTable.d.ts +1 -1
- package/dist/docx/importTable.js +17 -1
- package/dist/docx/invariants.d.ts +33 -0
- package/dist/docx/invariants.js +256 -0
- package/dist/docx/media.d.ts +6 -4
- package/dist/docx/media.js +6 -37
- package/dist/docx/newLists.d.ts +20 -0
- package/dist/docx/newLists.js +36 -0
- package/dist/docx/notes.js +3 -7
- package/dist/docx/numberingPlanner.d.ts +8 -0
- package/dist/docx/numberingPlanner.js +19 -0
- package/dist/docx/packageParts.d.ts +42 -0
- package/dist/docx/packageParts.js +135 -0
- package/dist/docx/pageGeometry.d.ts +2 -0
- package/dist/docx/pageGeometry.js +18 -9
- package/dist/docx/paraProps.d.ts +10 -22
- package/dist/docx/paraProps.js +47 -76
- package/dist/docx/partPlan.d.ts +36 -0
- package/dist/docx/partPlan.js +59 -0
- package/dist/docx/protectionPolicy.d.ts +127 -0
- package/dist/docx/protectionPolicy.js +169 -0
- package/dist/docx/relationships.d.ts +1 -1
- package/dist/docx/relationships.js +8 -13
- package/dist/docx/runProps.d.ts +9 -22
- package/dist/docx/runProps.js +15 -168
- package/dist/docx/scan.js +20 -52
- package/dist/docx/sdt.js +9 -34
- package/dist/docx/sdtProps.d.ts +8 -1
- package/dist/docx/sdtProps.js +10 -0
- package/dist/docx/serializeBlock.d.ts +2 -0
- package/dist/docx/serializeBlock.js +8 -5
- package/dist/docx/serializeTable.js +32 -15
- package/dist/docx/session.d.ts +42 -14
- package/dist/docx/session.js +43 -17
- package/dist/docx/tableFormatting/editing.js +108 -139
- package/dist/docx/tableFormatting/reading.d.ts +16 -6
- package/dist/docx/tableFormatting/reading.js +40 -20
- package/dist/docx/tableTemplate.js +18 -7
- package/dist/download.d.ts +8 -5
- package/dist/download.js +2 -0
- package/dist/editor/clipboard/inlineFormatting.js +19 -30
- package/dist/editor/commands/comments/editing.d.ts +6 -2
- package/dist/editor/commands/comments/editing.js +19 -31
- package/dist/editor/commands/exportQueries.d.ts +15 -0
- package/dist/editor/commands/exportQueries.js +14 -0
- package/dist/editor/commands/fidelityQueries.d.ts +12 -0
- package/dist/editor/commands/fidelityQueries.js +8 -0
- package/dist/editor/commands/formatting/editing.d.ts +2 -2
- package/dist/editor/commands/formatting/editing.js +26 -108
- package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
- package/dist/editor/commands/formatting/propertyCommands.js +103 -0
- package/dist/editor/commands/formatting/shared.d.ts +3 -3
- package/dist/editor/commands/formatting/shared.js +5 -2
- package/dist/editor/commands/indentCommands.js +5 -4
- package/dist/editor/commands/index.d.ts +11 -0
- package/dist/editor/commands/index.js +5 -0
- package/dist/editor/commands/linkCommands.js +5 -6
- package/dist/editor/commands/listCommands.js +8 -10
- package/dist/editor/commands/lockCommands.d.ts +7 -1
- package/dist/editor/commands/lockCommands.js +35 -46
- package/dist/editor/commands/paragraphCommands.js +28 -31
- package/dist/editor/commands/spacingCommands.js +1 -1
- package/dist/editor/createEditor.d.ts +17 -29
- package/dist/editor/createEditor.js +58 -58
- package/dist/editor/documentStyles.d.ts +11 -35
- package/dist/editor/documentStyles.js +9 -52
- package/dist/editor/editorDocument.d.ts +56 -0
- package/dist/editor/editorDocument.js +74 -0
- package/dist/editor/externalClipboard.js +16 -45
- package/dist/editor/insertTable.js +4 -3
- package/dist/editor/paragraphEdits.d.ts +11 -18
- package/dist/editor/paragraphEdits.js +7 -16
- package/dist/editor/plugins/displayDerivation.d.ts +47 -0
- package/dist/editor/plugins/displayDerivation.js +78 -0
- package/dist/editor/plugins/imagePaste.js +4 -3
- package/dist/editor/plugins/keymap.js +18 -3
- package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
- package/dist/editor/plugins/numberingDecorations.js +7 -17
- package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
- package/dist/editor/plugins/paragraphDisplay.js +51 -0
- package/dist/editor/plugins/tabLayout.js +1 -1
- package/dist/editor/plugins/tableDisplay.d.ts +6 -0
- package/dist/editor/plugins/tableDisplay.js +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/model/format.d.ts +30 -8
- package/dist/model/format.js +35 -22
- package/dist/model/tabStops.d.ts +9 -0
- package/dist/model/tabStops.js +18 -0
- package/dist/numbering/listTemplate.js +24 -7
- package/dist/numbering/parseNumbering.d.ts +14 -1
- package/dist/numbering/parseNumbering.js +35 -22
- package/dist/numbering/writeNumbering.d.ts +3 -4
- package/dist/numbering/writeNumbering.js +14 -25
- package/dist/ooxml/childOrder.d.ts +34 -0
- package/dist/ooxml/childOrder.js +496 -0
- package/dist/ooxml/element.d.ts +24 -11
- package/dist/ooxml/element.js +31 -12
- package/dist/ooxml/errors.d.ts +3 -2
- package/dist/ooxml/image.d.ts +4 -0
- package/dist/ooxml/image.js +2 -5
- package/dist/ooxml/partSplice.d.ts +67 -0
- package/dist/ooxml/partSplice.js +169 -0
- package/dist/ooxml/props.d.ts +112 -0
- package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
- package/dist/ooxml/simpleTypes.d.ts +103 -0
- package/dist/ooxml/simpleTypes.js +182 -0
- package/dist/ooxml/tabStops.js +8 -27
- package/dist/ooxml/tagScan.d.ts +34 -0
- package/dist/ooxml/tagScan.js +108 -0
- package/dist/ooxml/units.d.ts +25 -13
- package/dist/ooxml/units.js +65 -31
- package/dist/ooxml/xml.d.ts +27 -6
- package/dist/ooxml/xml.js +37 -5
- package/dist/page/blockKinds.d.ts +61 -0
- package/dist/page/blockKinds.js +11 -0
- package/dist/page/kinds/index.d.ts +6 -0
- package/dist/page/kinds/index.js +10 -0
- package/dist/page/kinds/paragraphKind.d.ts +10 -0
- package/dist/page/kinds/paragraphKind.js +71 -0
- package/dist/page/kinds/tableKind.d.ts +10 -0
- package/dist/page/kinds/tableKind.js +178 -0
- package/dist/page/measureBlocks.d.ts +3 -14
- package/dist/page/measureBlocks.js +24 -45
- package/dist/page/pageDecorations.d.ts +27 -44
- package/dist/page/pageDecorations.js +70 -152
- package/dist/page/pageLayout.d.ts +2 -19
- package/dist/page/pageLayout.js +36 -6
- package/dist/page/usePageLayout.d.ts +2 -17
- package/dist/page/usePageLayout.js +3 -39
- package/dist/schema/attrRoles.d.ts +25 -18
- package/dist/schema/attrRoles.js +102 -67
- package/dist/schema/displayDerivation.d.ts +82 -0
- package/dist/schema/displayDerivation.js +130 -0
- package/dist/schema/docxSchema.d.ts +3 -0
- package/dist/schema/docxSchema.js +26 -14
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.d.ts +37 -14
- package/dist/schema/guards.js +29 -3
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/locks.d.ts +11 -44
- package/dist/schema/locks.js +0 -9
- package/dist/schema/preservedGuards.d.ts +26 -6
- package/dist/schema/preservedGuards.js +31 -1
- package/dist/schema/protection.d.ts +7 -1
- package/dist/schema/protection.js +2 -1
- package/dist/schema/sourceEquality.d.ts +1 -9
- package/dist/schema/sourceEquality.js +1 -27
- package/dist/styles/inlineStyle.js +15 -6
- package/dist/table/cellFormatting.d.ts +8 -1
- package/dist/table/cellFormatting.js +10 -18
- package/dist/table/commands.js +20 -18
- package/dist/table/format.d.ts +3 -0
- package/dist/table/format.js +3 -9
- package/dist/table/gridBorders.d.ts +14 -16
- package/dist/table/gridBorders.js +3 -41
- package/dist/table/merge.d.ts +2 -6
- package/dist/table/merge.js +5 -5
- package/package.json +7 -3
- package/dist/docx/comments/contentTypes.d.ts +0 -7
- package/dist/docx/comments/contentTypes.js +0 -38
- package/dist/docx/comments/verifying.js +0 -206
- package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
- package/dist/docx/formatting/effectiveParagraph.js +0 -81
- package/dist/docx/propsXml.d.ts +0 -67
- package/dist/docx/uniqueControls.d.ts +0 -14
- package/dist/docx/uniqueControls.js +0 -62
- package/dist/editor/plugins/commentReservations.d.ts +0 -5
- package/dist/editor/plugins/commentReservations.js +0 -26
- package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
- package/dist/editor/plugins/styledParagraphs.js +0 -65
- package/dist/page/tableMeasurements.d.ts +0 -18
- package/dist/page/tableMeasurements.js +0 -119
package/dist/ooxml/element.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// src/ooxml/element.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
NAMESPACES,
|
|
4
|
+
qualify,
|
|
5
|
+
W_PREFIX,
|
|
6
|
+
wName
|
|
7
|
+
} from "./names.js";
|
|
3
8
|
import { escapeXml, localPart } from "./xml.js";
|
|
4
9
|
function attrsText(attrs) {
|
|
5
10
|
return attrs.map(([name, value]) => `${name}="${escapeXml(value)}"`).join(" ");
|
|
@@ -13,19 +18,36 @@ function elementXml(name, attrs, children = []) {
|
|
|
13
18
|
function xmlnsAttr(prefix) {
|
|
14
19
|
return [`xmlns:${prefix}`, NAMESPACES[prefix]];
|
|
15
20
|
}
|
|
21
|
+
function localNameUnder(prefix, name) {
|
|
22
|
+
if (!name.includes(":")) return name;
|
|
23
|
+
const local = localPart(name);
|
|
24
|
+
return name === qualify(prefix, local) ? local : null;
|
|
25
|
+
}
|
|
26
|
+
function wLocalName(name) {
|
|
27
|
+
return localNameUnder(W_PREFIX, name);
|
|
28
|
+
}
|
|
29
|
+
function wAttrIndex(attrs, localName) {
|
|
30
|
+
const qualified = attrs.findIndex(([name]) => name === wName(localName));
|
|
31
|
+
if (qualified !== -1) return qualified;
|
|
32
|
+
return attrs.findIndex(([name]) => name === localName);
|
|
33
|
+
}
|
|
34
|
+
function wAttrValue(attrs, localName) {
|
|
35
|
+
const at = wAttrIndex(attrs, localName);
|
|
36
|
+
return at === -1 ? null : attrs[at][1];
|
|
37
|
+
}
|
|
16
38
|
function withAttr(attrs, name, value) {
|
|
17
39
|
if (value === null) return withoutAttrs(attrs, [name]);
|
|
18
|
-
const at = attrs
|
|
40
|
+
const at = wAttrIndex(attrs, name);
|
|
19
41
|
if (at === -1) return [...attrs, [wName(name), value]];
|
|
20
42
|
return attrs.map(
|
|
21
43
|
(attr, index) => index === at ? [attr[0], value] : attr
|
|
22
44
|
);
|
|
23
45
|
}
|
|
24
|
-
function withoutAttrs(attrs, localNames) {
|
|
25
|
-
return attrs.filter(([name]) =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
function withoutAttrs(attrs, localNames, prefix = W_PREFIX) {
|
|
47
|
+
return attrs.filter(([name]) => {
|
|
48
|
+
const local = localNameUnder(prefix, name);
|
|
49
|
+
return local === null || !localNames.includes(local);
|
|
50
|
+
});
|
|
29
51
|
}
|
|
30
52
|
function openTagXml(name, rawAttrs) {
|
|
31
53
|
return rawAttrs ? `<${name} ${rawAttrs}>` : `<${name}>`;
|
|
@@ -33,16 +55,13 @@ function openTagXml(name, rawAttrs) {
|
|
|
33
55
|
function emptyTagXml(name, rawAttrs) {
|
|
34
56
|
return rawAttrs ? `<${name} ${rawAttrs}/>` : `<${name}/>`;
|
|
35
57
|
}
|
|
36
|
-
function attrPairs(el) {
|
|
37
|
-
return el ? Array.from(el.attributes, (attr) => [attr.name, attr.value]) : [];
|
|
38
|
-
}
|
|
39
58
|
export {
|
|
40
|
-
attrPairs,
|
|
41
|
-
attrValue,
|
|
42
59
|
attrsText,
|
|
43
60
|
elementXml,
|
|
44
61
|
emptyTagXml,
|
|
45
62
|
openTagXml,
|
|
63
|
+
wAttrValue,
|
|
64
|
+
wLocalName,
|
|
46
65
|
withAttr,
|
|
47
66
|
withoutAttrs,
|
|
48
67
|
xmlnsAttr
|
package/dist/ooxml/errors.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* Why a document could not be opened.
|
|
10
10
|
*
|
|
11
|
+
* - `no-xml-parser`: the runtime has no XML parser to read the package with. The one code here that is about where the call was made rather than about the file
|
|
11
12
|
* - `not-a-docx`: the bytes are not a readable zip container, its entry names are not a package's, or an entry does not hold what it says it does
|
|
12
13
|
* - `too-large`: the package asks to inflate to more than we open
|
|
13
14
|
* - `missing-part`: the package has no main document part to read
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
* - `malformed-xml`: the XML cannot be parsed, declares a DTD, or its markup is inconsistent
|
|
16
17
|
* - `unsupported-content`: the document holds markup we cannot write back out unchanged
|
|
17
18
|
*/
|
|
18
|
-
export type DocxImportErrorCode = "not-a-docx" | "too-large" | "missing-part" | "missing-body" | "malformed-xml" | "unsupported-content";
|
|
19
|
+
export type DocxImportErrorCode = "no-xml-parser" | "not-a-docx" | "too-large" | "missing-part" | "missing-body" | "malformed-xml" | "unsupported-content";
|
|
19
20
|
/** Thrown when we hit a document whose content cannot be kept safely. We refuse to open it instead of losing it silently */
|
|
20
21
|
export declare class DocxImportError extends Error {
|
|
21
22
|
readonly code: DocxImportErrorCode;
|
|
@@ -26,7 +27,7 @@ export declare class DocxImportError extends Error {
|
|
|
26
27
|
*
|
|
27
28
|
* - `missing-numbering-part`: a new list needs a numbering.xml the document does not have
|
|
28
29
|
* - `missing-content-types`: a new image needs a [Content_Types].xml the package does not have
|
|
29
|
-
* - `unsupported-content`: the document holds a node kind we have no way to serialize
|
|
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
|
|
30
31
|
* - `lost-original`: a node that only carries its original XML has lost it
|
|
31
32
|
* - `malformed-xml`: an original XML fragment cannot be read well enough to rewrite
|
|
32
33
|
* - `invalid-table`: the table grid is inconsistent, e.g. a vertical merge outliving its rows
|
package/dist/ooxml/image.d.ts
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* An imported image holds on to its whole original `<w:drawing>` XML, so an untouched
|
|
12
12
|
* image goes back out byte for byte. A resize rewrites nothing but the two extents.
|
|
13
|
+
*
|
|
14
|
+
* `emuToPx` is imported on its own by a builder working in pixels, so nothing here reads
|
|
15
|
+
* `NAMESPACES` at the top level: a top-level read is an impure statement a bundler keeps,
|
|
16
|
+
* and it would hold the naming layer in a bundle that wanted one multiplication.
|
|
13
17
|
*/
|
|
14
18
|
/**
|
|
15
19
|
* English Metric Units, the unit every DrawingML length is written in.
|
package/dist/ooxml/image.js
CHANGED
|
@@ -43,7 +43,6 @@ function toImageExtent(value) {
|
|
|
43
43
|
const cy = positiveInt(value.cy);
|
|
44
44
|
return cx === null || cy === null ? null : { cx, cy };
|
|
45
45
|
}
|
|
46
|
-
var PICTURE_URI = NAMESPACES.pic;
|
|
47
46
|
function childOf(el, name) {
|
|
48
47
|
return el ? childByLocalName(el, name) : null;
|
|
49
48
|
}
|
|
@@ -63,7 +62,7 @@ function readDrawingPicture(drawing) {
|
|
|
63
62
|
const extent = readExtent(childOf(inline, "extent"));
|
|
64
63
|
if (!extent) return null;
|
|
65
64
|
const graphicData = childOf(childOf(inline, "graphic"), "graphicData");
|
|
66
|
-
if (!graphicData || graphicData.getAttribute("uri") !==
|
|
65
|
+
if (!graphicData || graphicData.getAttribute("uri") !== NAMESPACES.pic) {
|
|
67
66
|
return null;
|
|
68
67
|
}
|
|
69
68
|
const blip = childOf(
|
|
@@ -83,13 +82,11 @@ function withExtent(xml, extent) {
|
|
|
83
82
|
const size = `cx="${extent.cx}" cy="${extent.cy}"`;
|
|
84
83
|
return xml.replace(EXTENT_TAG, (_match, prefix) => `<${prefix ?? ""}extent ${size}/>`).replace(EXT_TAG, (_match, prefix) => `<${prefix ?? ""}ext ${size}/>`);
|
|
85
84
|
}
|
|
86
|
-
var WP_NS = NAMESPACES.wp;
|
|
87
|
-
var A_NS = NAMESPACES.a;
|
|
88
85
|
function imageDrawingXml(image) {
|
|
89
86
|
const size = `cx="${image.extent.cx}" cy="${image.extent.cy}"`;
|
|
90
87
|
const name = `Picture ${image.docPrId}`;
|
|
91
88
|
const descr = image.alt === null ? "" : ` descr="${escapeXml(image.alt)}"`;
|
|
92
|
-
return `<w:drawing><wp:inline xmlns:wp="${
|
|
89
|
+
return `<w:drawing><wp:inline xmlns:wp="${NAMESPACES.wp}" distT="0" distB="0" distL="0" distR="0"><wp:extent ${size}/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${image.docPrId}" name="${name}"${descr}/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${NAMESPACES.a}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${NAMESPACES.a}"><a:graphicData uri="${NAMESPACES.pic}"><pic:pic xmlns:pic="${NAMESPACES.pic}"><pic:nvPicPr><pic:cNvPr id="${image.docPrId}" name="${name}"${descr}/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip xmlns:r="${R_NS}" r:embed="${escapeXml(image.relId)}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext ${size}/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing>`;
|
|
93
90
|
}
|
|
94
91
|
export {
|
|
95
92
|
EMU_PER_PX,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Putting children into the root element of a package part, and leaving every byte around them as
|
|
3
|
+
* the part arrived with.
|
|
4
|
+
*
|
|
5
|
+
* A part is text the writers cut and join, never a tree they build again, so the spots to cut at
|
|
6
|
+
* are found in the text: the root's opening tag past the prolog, and its closing tag counted to
|
|
7
|
+
* by depth. Searching for the closing tag instead would take a `</Relationships>` inside a
|
|
8
|
+
* comment for the end of the part, and miss the root altogether when it is written under a
|
|
9
|
+
* prefix or closes on itself.
|
|
10
|
+
*/
|
|
11
|
+
import type { KnownPrefix } from "./names";
|
|
12
|
+
/** One child to put into a part, named by the local part of its tag so the child order can place it */
|
|
13
|
+
export interface PartChild {
|
|
14
|
+
name: string;
|
|
15
|
+
xml: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What goes into a part's root. Every field is optional and they compose: the children are
|
|
19
|
+
* replaced first, then the inserts are placed among them, then the prepend and the append go on
|
|
20
|
+
* either end, and last the opening tag is rewritten.
|
|
21
|
+
*/
|
|
22
|
+
export interface PartSplice {
|
|
23
|
+
/** The root's local name. Any prefix, a self-closing root, a prolog and comments are all handled */
|
|
24
|
+
root: string;
|
|
25
|
+
/** Children put at the spot `CHILD_ORDER[root]` lays down, ahead of the first child that order puts after them */
|
|
26
|
+
insert?: readonly PartChild[];
|
|
27
|
+
/** Put in as the last children */
|
|
28
|
+
append?: string;
|
|
29
|
+
/** Put in right after the opening tag */
|
|
30
|
+
prepend?: string;
|
|
31
|
+
/** Written in place of every child */
|
|
32
|
+
replaceChildren?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Rewrites the opening tag, handed as it is about to be written: opened when children go into a
|
|
35
|
+
* root that closed on itself, else as it arrived.
|
|
36
|
+
*/
|
|
37
|
+
rootTag?: (open: string) => string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Why the part cannot be spliced around its root, or null when it can. The same refusal
|
|
41
|
+
* `splicePart` throws, so a caller may ask ahead and be answered in the same words.
|
|
42
|
+
*/
|
|
43
|
+
export declare function partRootProblem(xml: string, root: string): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* The part with the children of `splice` put into its root, and every byte outside what was put
|
|
46
|
+
* in standing as it arrived. A root that closed on itself is opened to hold them.
|
|
47
|
+
*
|
|
48
|
+
* Throws `malformed-xml` naming the part when the root is not there or never closes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function splicePart(xml: string, splice: PartSplice): string;
|
|
51
|
+
/**
|
|
52
|
+
* The prefix the root's name carries, colon included, so a child written beside the ones the part
|
|
53
|
+
* holds is spelled the way the root is. Empty for a root written under the default namespace, and
|
|
54
|
+
* for a part with no root, which the splice that follows refuses.
|
|
55
|
+
*/
|
|
56
|
+
export declare function rootPrefixOf(xml: string): string;
|
|
57
|
+
export interface RootDeclarations {
|
|
58
|
+
/** The prefixes the root has to bind, and what to bind each to where it does not yet */
|
|
59
|
+
namespaces: Partial<Record<KnownPrefix, string>>;
|
|
60
|
+
/** The tokens `mc:Ignorable` has to name, added to the ones it names already */
|
|
61
|
+
ignorable?: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The part with its root binding every prefix named and its `mc:Ignorable` naming every token
|
|
65
|
+
* asked for, each declared once: one the root declares already is left as it was written.
|
|
66
|
+
*/
|
|
67
|
+
export declare function ensureRootDeclarations(xml: string, declarations: RootDeclarations): string;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// src/ooxml/partSplice.ts
|
|
2
|
+
import { childOrderOf } from "./childOrder.js";
|
|
3
|
+
import { attrsText } from "./element.js";
|
|
4
|
+
import { DocxExportError } from "./errors.js";
|
|
5
|
+
import { parseAttrs, readTag, rootTagAt } from "./tagScan.js";
|
|
6
|
+
import { localPart } from "./xml.js";
|
|
7
|
+
function closingTagAt(xml, open) {
|
|
8
|
+
let depth = 1;
|
|
9
|
+
let at = open.end;
|
|
10
|
+
for (; ; ) {
|
|
11
|
+
const lt = xml.indexOf("<", at);
|
|
12
|
+
if (lt === -1) return null;
|
|
13
|
+
const tag = readTag(xml, lt);
|
|
14
|
+
if (tag === null) return null;
|
|
15
|
+
if (tag.kind === "open") depth += 1;
|
|
16
|
+
if (tag.kind === "close") {
|
|
17
|
+
depth -= 1;
|
|
18
|
+
if (depth === 0) return tag.name === open.name ? lt : null;
|
|
19
|
+
}
|
|
20
|
+
at = tag.end;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readRoot(xml, root) {
|
|
24
|
+
const openAt = rootTagAt(xml);
|
|
25
|
+
const open = openAt === -1 ? null : readTag(xml, openAt);
|
|
26
|
+
if (open === null || open.kind === "close" || localPart(open.name) !== root) {
|
|
27
|
+
return { problem: `the ${root} part has no ${root} root element` };
|
|
28
|
+
}
|
|
29
|
+
if (open.kind === "empty") return { root: { openAt, open, closeAt: null } };
|
|
30
|
+
const closeAt = closingTagAt(xml, open);
|
|
31
|
+
if (closeAt === null) {
|
|
32
|
+
return { problem: `the ${root} part has no closing ${root} tag` };
|
|
33
|
+
}
|
|
34
|
+
return { root: { openAt, open, closeAt } };
|
|
35
|
+
}
|
|
36
|
+
function partRootProblem(xml, root) {
|
|
37
|
+
const reading = readRoot(xml, root);
|
|
38
|
+
return "problem" in reading ? reading.problem : null;
|
|
39
|
+
}
|
|
40
|
+
function directChildren(inner) {
|
|
41
|
+
const children = [];
|
|
42
|
+
let depth = 0;
|
|
43
|
+
let at = 0;
|
|
44
|
+
for (; ; ) {
|
|
45
|
+
const lt = inner.indexOf("<", at);
|
|
46
|
+
if (lt === -1) return children;
|
|
47
|
+
const tag = readTag(inner, lt);
|
|
48
|
+
if (tag === null) return children;
|
|
49
|
+
if (depth === 0 && (tag.kind === "open" || tag.kind === "empty")) {
|
|
50
|
+
children.push({ name: localPart(tag.name), at: lt });
|
|
51
|
+
}
|
|
52
|
+
if (tag.kind === "open") depth += 1;
|
|
53
|
+
if (tag.kind === "close") depth -= 1;
|
|
54
|
+
at = tag.end;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function insertionAt(children, name, order, root, end) {
|
|
58
|
+
const target = order.indexOf(name);
|
|
59
|
+
if (target === -1) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`${name} is not a child the order of ${root} knows; add it to CHILD_ORDER`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const after = children.find((child) => order.indexOf(child.name) > target);
|
|
65
|
+
return after === void 0 ? end : after.at;
|
|
66
|
+
}
|
|
67
|
+
function withInserted(inner, inserts, root) {
|
|
68
|
+
if (inserts.length === 0) return inner;
|
|
69
|
+
const order = childOrderOf(root);
|
|
70
|
+
const children = directChildren(inner);
|
|
71
|
+
const placed = inserts.map((child) => ({
|
|
72
|
+
at: insertionAt(children, child.name, order, root, inner.length),
|
|
73
|
+
xml: child.xml,
|
|
74
|
+
rank: order.indexOf(child.name)
|
|
75
|
+
})).sort((a, b) => a.at - b.at || a.rank - b.rank);
|
|
76
|
+
let out = "";
|
|
77
|
+
let from = 0;
|
|
78
|
+
for (const { at, xml } of placed) {
|
|
79
|
+
out += inner.slice(from, at) + xml;
|
|
80
|
+
from = at;
|
|
81
|
+
}
|
|
82
|
+
return out + inner.slice(from);
|
|
83
|
+
}
|
|
84
|
+
function splicePart(xml, splice) {
|
|
85
|
+
const reading = readRoot(xml, splice.root);
|
|
86
|
+
if ("problem" in reading) {
|
|
87
|
+
throw new DocxExportError("malformed-xml", reading.problem);
|
|
88
|
+
}
|
|
89
|
+
const { openAt, open, closeAt } = reading.root;
|
|
90
|
+
const inner = closeAt === null ? "" : xml.slice(open.end, closeAt);
|
|
91
|
+
const children = (splice.prepend ?? "") + withInserted(
|
|
92
|
+
splice.replaceChildren ?? inner,
|
|
93
|
+
splice.insert ?? [],
|
|
94
|
+
splice.root
|
|
95
|
+
) + (splice.append ?? "");
|
|
96
|
+
const openTag = xml.slice(openAt, open.end);
|
|
97
|
+
const opened = closeAt === null && children !== "" ? `${openTag.slice(0, -2)}>` : openTag;
|
|
98
|
+
const head = xml.slice(0, openAt) + (splice.rootTag?.(opened) ?? opened);
|
|
99
|
+
if (closeAt !== null) return head + children + xml.slice(closeAt);
|
|
100
|
+
if (children === "") return head + xml.slice(open.end);
|
|
101
|
+
return `${head}${children}</${open.name}>${xml.slice(open.end)}`;
|
|
102
|
+
}
|
|
103
|
+
function rootNameOf(xml) {
|
|
104
|
+
const at = rootTagAt(xml);
|
|
105
|
+
const open = at === -1 ? null : readTag(xml, at);
|
|
106
|
+
return open === null || open.kind === "close" ? null : open.name;
|
|
107
|
+
}
|
|
108
|
+
function rootPrefixOf(xml) {
|
|
109
|
+
const name = rootNameOf(xml) ?? "";
|
|
110
|
+
const colon = name.indexOf(":");
|
|
111
|
+
return colon === -1 ? "" : name.slice(0, colon + 1);
|
|
112
|
+
}
|
|
113
|
+
var IGNORABLE = /\smc:Ignorable\s*=\s*(["'])([^"']*)\1/;
|
|
114
|
+
function withDeclarations(openTag, { namespaces, ignorable }) {
|
|
115
|
+
const tag = readTag(openTag, 0);
|
|
116
|
+
const closeLength = tag?.kind === "empty" ? 2 : 1;
|
|
117
|
+
const attrs = tag === null ? null : parseAttrs(openTag.slice(tag.nameEnd, tag.end - closeLength));
|
|
118
|
+
if (tag === null || attrs === null) {
|
|
119
|
+
throw new DocxExportError(
|
|
120
|
+
"malformed-xml",
|
|
121
|
+
"the root element's attributes could not be read"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const declared = new Set(attrs.map(([name]) => name));
|
|
125
|
+
const additions = [];
|
|
126
|
+
for (const [prefix, namespace] of Object.entries(namespaces)) {
|
|
127
|
+
if (namespace !== void 0 && !declared.has(`xmlns:${prefix}`)) {
|
|
128
|
+
additions.push([`xmlns:${prefix}`, namespace]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const ignoring = IGNORABLE.exec(openTag);
|
|
132
|
+
let tokens = null;
|
|
133
|
+
if (ignorable !== void 0 && ignorable.length > 0) {
|
|
134
|
+
if (ignoring === null) {
|
|
135
|
+
additions.push(["mc:Ignorable", ignorable.join(" ")]);
|
|
136
|
+
} else {
|
|
137
|
+
const had = (attrs.find(([name]) => name === "mc:Ignorable")?.[1] ?? "").split(/\s+/).filter(Boolean);
|
|
138
|
+
const missing = ignorable.filter((token) => !had.includes(token));
|
|
139
|
+
if (missing.length > 0) tokens = [...had, ...missing];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let next = openTag;
|
|
143
|
+
if (ignoring !== null && tokens !== null) {
|
|
144
|
+
const rewritten = ` mc:Ignorable=${ignoring[1]}${tokens.join(" ")}${ignoring[1]}`;
|
|
145
|
+
next = next.slice(0, ignoring.index) + rewritten + next.slice(ignoring.index + ignoring[0].length);
|
|
146
|
+
}
|
|
147
|
+
if (additions.length === 0) return next;
|
|
148
|
+
const end = next.length - closeLength;
|
|
149
|
+
return `${next.slice(0, end)} ${attrsText(additions)}${next.slice(end)}`;
|
|
150
|
+
}
|
|
151
|
+
function ensureRootDeclarations(xml, declarations) {
|
|
152
|
+
const root = rootNameOf(xml);
|
|
153
|
+
if (root === null) {
|
|
154
|
+
throw new DocxExportError(
|
|
155
|
+
"malformed-xml",
|
|
156
|
+
"the part has no root element to declare a namespace on"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return splicePart(xml, {
|
|
160
|
+
root: localPart(root),
|
|
161
|
+
rootTag: (open) => withDeclarations(open, declarations)
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
ensureRootDeclarations,
|
|
166
|
+
partRootProblem,
|
|
167
|
+
rootPrefixOf,
|
|
168
|
+
splicePart
|
|
169
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles the XML fragments that carry formatting, such as `<w:rPr>`, `<w:pPr>`, and `<w:tcPr>`.
|
|
3
|
+
*
|
|
4
|
+
* A fragment is never rebuilt: only the one child a job names is sliced out and swapped, so the
|
|
5
|
+
* rest (borders, shading, margins) stays as the original text wrote it. OOXML lays down the order
|
|
6
|
+
* of the children, so the spot to insert a child that was not there is found by that same order.
|
|
7
|
+
*/
|
|
8
|
+
import { type XmlAttr } from "./element";
|
|
9
|
+
export interface PropsChild {
|
|
10
|
+
/** The name with its namespace prefix stripped off (e.g. `gridSpan`) */
|
|
11
|
+
name: string;
|
|
12
|
+
/** This child's original XML fragment exactly as it was */
|
|
13
|
+
xml: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whatever stood between the child before this one and this one: line breaks a producer laid
|
|
16
|
+
* out, comments, anything that is not an element. Carried so that rewriting one child does not
|
|
17
|
+
* quietly drop the rest of what the fragment said. Absent rather than empty when nothing did.
|
|
18
|
+
*/
|
|
19
|
+
before?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface Props {
|
|
22
|
+
/** The opening tag name exactly as written (e.g. `w:tcPr`) */
|
|
23
|
+
tag: string;
|
|
24
|
+
attrs: string | null;
|
|
25
|
+
children: PropsChild[];
|
|
26
|
+
/** The same, for what stood between the last child and the closing tag */
|
|
27
|
+
tail?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* What stood inside a single element, and "" for one that stood empty or cannot be made out.
|
|
31
|
+
*
|
|
32
|
+
* The opening tag is read rather than scanned for, so a `>` inside an attribute value does not
|
|
33
|
+
* pass for the end of it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function innerXml(xml: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Splits a formatting fragment into its opening tag and its list of children.
|
|
38
|
+
* null if its shape cannot be made out (in which case leaving the original untouched is the safe move).
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseProps(xml: string): Props | null;
|
|
41
|
+
/**
|
|
42
|
+
* With nothing to say, the formatting fragment itself is not written. A fragment holding only
|
|
43
|
+
* whitespace says nothing; one holding a comment does, so it is written back.
|
|
44
|
+
*/
|
|
45
|
+
export declare function renderProps(props: Props): string;
|
|
46
|
+
/**
|
|
47
|
+
* Replaces a single child with new XML.
|
|
48
|
+
*
|
|
49
|
+
* A null `xml` removes that child. A child that was not there goes into the spot `CHILD_ORDER`
|
|
50
|
+
* lays down for this fragment's own tag, so the caller names what it is writing and not where it
|
|
51
|
+
* goes. Naming the element this fragment stands inside picks the right order where one element
|
|
52
|
+
* carries children in a different order under a different parent (`pPr/rPr`).
|
|
53
|
+
*/
|
|
54
|
+
export declare function setChild(props: Props, name: string, xml: string | null, parent?: string): Props;
|
|
55
|
+
export declare function propsChild(children: readonly PropsChild[], name: string): PropsChild | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The opening tag's attributes as pairs, and null when their shape cannot be made out.
|
|
58
|
+
*
|
|
59
|
+
* The pairs are worked out on demand rather than kept on `Props`, because an untouched fragment
|
|
60
|
+
* has to go back out with the spacing, the quoting and the escaping its producer chose. Only a
|
|
61
|
+
* fragment something writes to passes through `withAttrs`, which is where they are written again.
|
|
62
|
+
*/
|
|
63
|
+
export declare function attrsOf(props: Props): XmlAttr[] | null;
|
|
64
|
+
/** The fragment with these attributes written in place of the ones its opening tag carried */
|
|
65
|
+
export declare function withAttrs(props: Props, attrs: readonly XmlAttr[]): Props;
|
|
66
|
+
/**
|
|
67
|
+
* One child of a fragment as it was written: the tag, prefix included, so an edited child keeps
|
|
68
|
+
* the spelling the document chose, and its attributes. A child that is not there has neither, so
|
|
69
|
+
* no tag with attributes hanging off it can be made.
|
|
70
|
+
*/
|
|
71
|
+
export type ChildElement = {
|
|
72
|
+
tag: string;
|
|
73
|
+
attrs: readonly XmlAttr[];
|
|
74
|
+
} | {
|
|
75
|
+
tag: null;
|
|
76
|
+
attrs: readonly [];
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The tag and attributes of one child. A child that is not there reads as no tag and no
|
|
80
|
+
* attributes, and null says its shape cannot be made out, which leaves the caller to back out
|
|
81
|
+
* rather than write over markup it could not read.
|
|
82
|
+
*/
|
|
83
|
+
export declare function childElement(props: Props, name: string): ChildElement | null;
|
|
84
|
+
/**
|
|
85
|
+
* Always writes the element, closing it on its own when it holds nothing. Whitespace alone counts
|
|
86
|
+
* as nothing, the same as `renderProps` reads it, so a pretty-printed fragment emptied of its
|
|
87
|
+
* children collapses rather than keeping the line breaks that stood between them.
|
|
88
|
+
*/
|
|
89
|
+
export declare function renderElement(props: Props): string;
|
|
90
|
+
/**
|
|
91
|
+
* An element whose children go in the order `CHILD_ORDER` lays down, whatever order they are
|
|
92
|
+
* handed in. This is how a fragment written from scratch follows the same order an edited one
|
|
93
|
+
* is held to, and a child the order does not know is refused the same way.
|
|
94
|
+
*/
|
|
95
|
+
export declare function orderedElement(tag: string, attrs: readonly XmlAttr[], children: readonly PropsChild[]): string;
|
|
96
|
+
/**
|
|
97
|
+
* Edits one child down a path of names, parsing only the fragments the path runs through and
|
|
98
|
+
* leaving every other child as the original text wrote it.
|
|
99
|
+
*
|
|
100
|
+
* The callback is handed the child the path names, null for one that is not there, and answers
|
|
101
|
+
* with what it is to become, null to take it away. A container along the path that was not there
|
|
102
|
+
* is written, and one the edit leaves empty is taken away with it. null when a fragment on the
|
|
103
|
+
* path cannot be made out, which leaves the caller to back out.
|
|
104
|
+
*/
|
|
105
|
+
export declare function editChild(props: Props, path: readonly [string, ...string[]], edit: (child: Props | null) => Props | null): Props | null;
|
|
106
|
+
/**
|
|
107
|
+
* Reads a single formatting fragment into an element.
|
|
108
|
+
*
|
|
109
|
+
* A fragment carries no namespace declarations, so they are put back on as it is wrapped.
|
|
110
|
+
* null if its shape cannot be made out, in which case the caller leaves the display values alone.
|
|
111
|
+
*/
|
|
112
|
+
export declare function parsePropsXml(xml: string): Element | null;
|