@stll/folio-core 0.35.0 → 0.36.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/dist/ai-edits/apply.js +370 -118
- package/dist/ai-edits/headless.js +16 -10
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/snapshot.js +6 -1
- package/dist/ai-edits/types.d.ts +19 -6
- package/dist/ai-edits/word-diff.d.ts +18 -3
- package/dist/ai-edits/word-diff.js +556 -67
- package/dist/compare/compare.js +7 -16
- package/dist/compare/plan.js +8 -5
- package/dist/compare/verification.d.ts +8 -9
- package/dist/compare/verification.js +51 -46
- package/dist/compat/eigenpal.d.ts +2 -2
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/headerFooterEditorManager.js +5 -5
- package/dist/controller/hiddenEditorApi.js +1 -1
- package/dist/controller/noteEditorManager.js +2 -2
- package/dist/document-operations.d.ts +5 -3
- package/dist/document-operations.js +32 -5
- package/dist/docx/commentParser.js +2 -4
- package/dist/docx/commentRangeIntegrity.js +2 -4
- package/dist/docx/documentClone.d.ts +2 -0
- package/dist/docx/documentClone.js +2 -0
- package/dist/docx/headerFooterParser.js +2 -4
- package/dist/docx/normalizeBaseDirection.js +5 -7
- package/dist/docx/paragraphParser.js +27 -29
- package/dist/docx/paragraphPropertySource.d.ts +49 -0
- package/dist/docx/paragraphPropertySource.js +113 -0
- package/dist/docx/runConsolidator.js +3 -4
- package/dist/docx/runParser.js +2 -15
- package/dist/docx/sectionParser.js +1 -15
- package/dist/docx/serializer/paragraphSerializer.js +287 -30
- package/dist/docx/serializer/runSerializer.js +2 -10
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
- package/dist/docx/serializer/tableSerializer.js +4 -23
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +9 -0
- package/dist/docx/serializer/trackedChangeAttributes.js +21 -0
- package/dist/docx/server/createBilingualDocument.js +8 -10
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
- package/dist/docx/server/validateDocxConformance.js +17 -13
- package/dist/docx/tableParser.js +1 -37
- package/dist/docx/trackedChangeInfo.d.ts +11 -0
- package/dist/docx/trackedChangeInfo.js +38 -0
- package/dist/docx/verbatimCapture.d.ts +20 -9
- package/dist/docx/verbatimCapture.js +208 -8
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +6 -1
- package/dist/docx/xmlSafety.d.ts +1 -1
- package/dist/docx/xmlSafety.js +157 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/layout-bridge/convert/headerFooterLayout.js +4 -4
- package/dist/layout-bridge/convert/toFlowBlocks.js +12 -7
- package/dist/markdown/renderBlock.js +7 -10
- package/dist/prosemirror/attrs/index.js +15 -0
- package/dist/prosemirror/commands/comments.js +365 -32
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +26 -4
- package/dist/prosemirror/commands/propertyChangeScope.js +65 -3
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -4
- package/dist/prosemirror/conversion/fromProseDoc.js +130 -18
- package/dist/prosemirror/conversion/toProseDoc.js +27 -12
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +48 -4
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +20 -7
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
- package/dist/prosemirror/paragraphAlignment.d.ts +12 -0
- package/dist/prosemirror/paragraphAlignment.js +13 -0
- package/dist/prosemirror/plugins/suggestionMode.js +1 -1
- package/dist/prosemirror/schema/marks.d.ts +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +12 -1
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/redline.js +4 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/utils/mergeDocumentContent.js +3 -3
- package/dist/utils/replaceText.js +2 -1
- package/dist/version-comparison.js +6 -4
- package/dist/watermark/index.js +2 -4
- package/package.json +7 -2
|
@@ -98,6 +98,20 @@ const validateRequiredParts = (archive, report) => {
|
|
|
98
98
|
});
|
|
99
99
|
return false;
|
|
100
100
|
};
|
|
101
|
+
const XML_SAFETY_ISSUE = {
|
|
102
|
+
"doctype-forbidden": {
|
|
103
|
+
code: "xml-doctype-forbidden",
|
|
104
|
+
message: "XML package parts must not declare a document type."
|
|
105
|
+
},
|
|
106
|
+
"entity-forbidden": {
|
|
107
|
+
code: "xml-not-well-formed",
|
|
108
|
+
message: "An XML package part contains an invalid entity reference."
|
|
109
|
+
},
|
|
110
|
+
"not-well-formed": {
|
|
111
|
+
code: "xml-not-well-formed",
|
|
112
|
+
message: "An XML package part is not well formed."
|
|
113
|
+
}
|
|
114
|
+
};
|
|
101
115
|
const validateXmlParts = async (archive, report) => {
|
|
102
116
|
const xmlByPath = /* @__PURE__ */ new Map();
|
|
103
117
|
let hasInvalidXml = false;
|
|
@@ -106,27 +120,17 @@ const validateXmlParts = async (archive, report) => {
|
|
|
106
120
|
if (xml === null) continue;
|
|
107
121
|
xmlByPath.set(part, xml);
|
|
108
122
|
const safetyIssue = getDocxXmlSafetyIssue(xml);
|
|
109
|
-
if (safetyIssue
|
|
123
|
+
if (safetyIssue !== null) {
|
|
110
124
|
hasInvalidXml = true;
|
|
125
|
+
const issue = XML_SAFETY_ISSUE[safetyIssue];
|
|
111
126
|
addIssue(report, {
|
|
112
127
|
check: "xml-well-formedness",
|
|
113
|
-
|
|
114
|
-
message: "XML package parts must not declare a document type.",
|
|
128
|
+
...issue,
|
|
115
129
|
severity: "error",
|
|
116
130
|
part
|
|
117
131
|
});
|
|
118
132
|
continue;
|
|
119
133
|
}
|
|
120
|
-
if (safetyIssue === "not-well-formed") {
|
|
121
|
-
hasInvalidXml = true;
|
|
122
|
-
addIssue(report, {
|
|
123
|
-
check: "xml-well-formedness",
|
|
124
|
-
code: "xml-not-well-formed",
|
|
125
|
-
message: "An XML package part is not well formed.",
|
|
126
|
-
severity: "error",
|
|
127
|
-
part
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
134
|
}
|
|
131
135
|
if (hasInvalidXml) {
|
|
132
136
|
report.checks.set("xml-well-formedness", "failed");
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -3,9 +3,9 @@ import { appendBookmarkMarkerToLastParagraphInBlocks, appendBookmarkMarkerToLast
|
|
|
3
3
|
import { parseParagraph } from "./paragraphParser.js";
|
|
4
4
|
import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
|
|
5
5
|
import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
6
|
+
import { parsePropertyChangeInfo, parseTrackedChangeInfo } from "./trackedChangeInfo.js";
|
|
6
7
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
7
8
|
import { cloneElement, findChild, findChildByLocalName, findChildren, getAttribute, getChildElements, getLocalName, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue } from "./xmlParser.js";
|
|
8
|
-
import { normalizeRevisionId } from "@stll/docx-core/model";
|
|
9
9
|
//#region src/docx/tableParser.ts
|
|
10
10
|
/**
|
|
11
11
|
* Sanity cap on `w:gridSpan` (and the derived table column count). Word's
|
|
@@ -36,42 +36,6 @@ function parseTableMeasurement(element) {
|
|
|
36
36
|
function parseWidth(element) {
|
|
37
37
|
return parseTableMeasurement(element);
|
|
38
38
|
}
|
|
39
|
-
function parseTrackedChangeInfo(node) {
|
|
40
|
-
const rawId = getAttribute(node, "w", "id");
|
|
41
|
-
const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
|
|
42
|
-
const author = (getAttribute(node, "w", "author") ?? "").trim();
|
|
43
|
-
const date = (getAttribute(node, "w", "date") ?? "").trim();
|
|
44
|
-
const initials = (getAttribute(node, "w", "initials") ?? "").trim();
|
|
45
|
-
const info = {
|
|
46
|
-
id: normalizeRevisionId(parsedId),
|
|
47
|
-
author: author.length > 0 ? author : "Unknown"
|
|
48
|
-
};
|
|
49
|
-
if (date.length > 0) info.date = date;
|
|
50
|
-
if (initials.length > 0) info.initials = initials;
|
|
51
|
-
const utcDate = utcDateAttribute(node);
|
|
52
|
-
if (utcDate) info.utcDate = utcDate;
|
|
53
|
-
return info;
|
|
54
|
-
}
|
|
55
|
-
/** `w16du:dateUtc` under whatever prefix the document bound it to. */
|
|
56
|
-
const utcDateAttribute = (node) => {
|
|
57
|
-
for (const [attribute, value] of Object.entries(node.attributes ?? {})) {
|
|
58
|
-
if (typeof value !== "string" || getLocalName(attribute) !== "dateUtc") continue;
|
|
59
|
-
const trimmed = value.trim();
|
|
60
|
-
if (trimmed.length > 0) return {
|
|
61
|
-
attribute,
|
|
62
|
-
value: trimmed
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
};
|
|
67
|
-
function parsePropertyChangeInfo(node) {
|
|
68
|
-
const base = parseTrackedChangeInfo(node);
|
|
69
|
-
const rsid = (getAttribute(node, "w", "rsid") ?? "").trim();
|
|
70
|
-
return rsid.length > 0 ? {
|
|
71
|
-
...base,
|
|
72
|
-
rsid
|
|
73
|
-
} : base;
|
|
74
|
-
}
|
|
75
39
|
/**
|
|
76
40
|
* Parse a single border specification
|
|
77
41
|
*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/trackedChangeInfo.d.ts
|
|
4
|
+
declare const DATE_UTC_NAMESPACE_URI = "http://schemas.microsoft.com/office/word/2023/wordml/word16du";
|
|
5
|
+
declare const DATE_UTC_ATTRIBUTE = "w16du:dateUtc";
|
|
6
|
+
/** Parse the metadata shared by every WordprocessingML tracked-change element. */
|
|
7
|
+
declare const parseTrackedChangeInfo: (node: XmlElement) => document_d_exports.TrackedChangeInfo;
|
|
8
|
+
/** Parse the shared metadata plus the optional property-change revision session. */
|
|
9
|
+
declare const parsePropertyChangeInfo: (node: XmlElement) => document_d_exports.PropertyChangeInfo;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { DATE_UTC_ATTRIBUTE, DATE_UTC_NAMESPACE_URI, parsePropertyChangeInfo, parseTrackedChangeInfo };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, findAttributeByNamespaceUri, getAttributeByNamespaceUri } from "./xmlParser.js";
|
|
2
|
+
import { normalizeRevisionId } from "@stll/docx-core/model";
|
|
3
|
+
//#region src/docx/trackedChangeInfo.ts
|
|
4
|
+
const DATE_UTC_NAMESPACE_URI = "http://schemas.microsoft.com/office/word/2023/wordml/word16du";
|
|
5
|
+
const DATE_UTC_ATTRIBUTE = "w16du:dateUtc";
|
|
6
|
+
const DATE_UTC_NAMESPACE_URIS = /* @__PURE__ */ new Set([DATE_UTC_NAMESPACE_URI]);
|
|
7
|
+
/** Parse the metadata shared by every WordprocessingML tracked-change element. */
|
|
8
|
+
const parseTrackedChangeInfo = (node) => {
|
|
9
|
+
const rawId = getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "id");
|
|
10
|
+
const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
|
|
11
|
+
const author = (getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "author") ?? "").trim();
|
|
12
|
+
const date = (getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "date") ?? "").trim();
|
|
13
|
+
const initials = (getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "initials") ?? "").trim();
|
|
14
|
+
const info = {
|
|
15
|
+
id: normalizeRevisionId(parsedId),
|
|
16
|
+
author: author.length > 0 ? author : "Unknown"
|
|
17
|
+
};
|
|
18
|
+
if (date.length > 0) info.date = date;
|
|
19
|
+
if (initials.length > 0) info.initials = initials;
|
|
20
|
+
const utcDate = findAttributeByNamespaceUri(node, DATE_UTC_NAMESPACE_URIS, "dateUtc");
|
|
21
|
+
const utcDateValue = utcDate?.value.trim() ?? "";
|
|
22
|
+
if (utcDate && utcDateValue.length > 0) info.utcDate = {
|
|
23
|
+
attribute: DATE_UTC_ATTRIBUTE,
|
|
24
|
+
value: utcDateValue
|
|
25
|
+
};
|
|
26
|
+
return info;
|
|
27
|
+
};
|
|
28
|
+
/** Parse the shared metadata plus the optional property-change revision session. */
|
|
29
|
+
const parsePropertyChangeInfo = (node) => {
|
|
30
|
+
const info = parseTrackedChangeInfo(node);
|
|
31
|
+
const rsid = (getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "rsid") ?? "").trim();
|
|
32
|
+
return rsid.length > 0 ? {
|
|
33
|
+
...info,
|
|
34
|
+
rsid
|
|
35
|
+
} : info;
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { DATE_UTC_ATTRIBUTE, DATE_UTC_NAMESPACE_URI, parsePropertyChangeInfo, parseTrackedChangeInfo };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { XmlElement } from "./xmlParser.js";
|
|
1
|
+
import { XmlElement, XmlNamespaceScope } from "./xmlParser.js";
|
|
2
2
|
//#region src/docx/verbatimCapture.d.ts
|
|
3
3
|
declare const UntranslatableStrictNamespaceError_base: import("better-result").TaggedErrorClass<"UntranslatableStrictNamespaceError">;
|
|
4
4
|
/** A Strict fragment uses a namespace that has no Transitional counterpart. */
|
|
@@ -6,15 +6,26 @@ declare class UntranslatableStrictNamespaceError extends UntranslatableStrictNam
|
|
|
6
6
|
message: string;
|
|
7
7
|
uri: string;
|
|
8
8
|
}> {}
|
|
9
|
+
declare const captureVerbatimXml: (element: XmlElement) => string;
|
|
10
|
+
type SanitizeCapturedXmlOptions = {
|
|
11
|
+
allowedLocalNames: ReadonlySet<string>;
|
|
12
|
+
allowedNamespaceUris: ReadonlySet<string>;
|
|
13
|
+
inheritedNamespaceScope?: XmlNamespaceScope;
|
|
14
|
+
requiredNamespaceBindings?: ReadonlyMap<string, string>;
|
|
15
|
+
transform?: (element: XmlElement) => XmlElement;
|
|
16
|
+
validate?: (element: XmlElement) => boolean;
|
|
17
|
+
};
|
|
18
|
+
/** Validate a complete XML part immediately before package output. */
|
|
19
|
+
declare const isSafeCapturedXmlDocument: (xml: string) => boolean;
|
|
9
20
|
/**
|
|
10
|
-
*
|
|
21
|
+
* Re-parse and serialize one captured element before replaying it.
|
|
11
22
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
23
|
+
* The parser deliberately ignores declarations, processing instructions,
|
|
24
|
+
* comments, and document types. Returning the caller's source string after a
|
|
25
|
+
* successful parse would therefore replay tokens that were never validated.
|
|
26
|
+
* This function returns only the parsed root serialized through the capture
|
|
27
|
+
* owner, and rejects siblings, unexpected roots, and namespace shadowing.
|
|
17
28
|
*/
|
|
18
|
-
declare const
|
|
29
|
+
declare const sanitizeCapturedXmlElement: (xml: string | undefined, options: SanitizeCapturedXmlOptions) => string | null;
|
|
19
30
|
//#endregion
|
|
20
|
-
export { UntranslatableStrictNamespaceError, captureVerbatimXml };
|
|
31
|
+
export { UntranslatableStrictNamespaceError, captureVerbatimXml, isSafeCapturedXmlDocument, sanitizeCapturedXmlElement };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { TRANSITIONAL_NAMESPACE_BY_STRICT_URI } from "./strictValueEncodings.gen.js";
|
|
2
2
|
import { isStrictNamespaceUri, toTransitionalNamespaceUri, transitionalSlotEncoding } from "./transitionalSpelling.js";
|
|
3
3
|
import { roundHalfAwayFromZero, universalMeasureAs } from "./universalMeasure.js";
|
|
4
|
-
import { elementToXml, getLocalName } from "./xmlParser.js";
|
|
4
|
+
import { NAMESPACES, OOXML_NAMESPACE_SCOPE, cloneElement, elementToXml, getChildElements, getLocalName, getNamespacePrefix, parseXml } from "./xmlParser.js";
|
|
5
|
+
import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
|
|
6
|
+
import { getDocxXmlSafetyIssue } from "./xmlSafety.js";
|
|
5
7
|
import { TaggedError } from "better-result";
|
|
6
8
|
//#region src/docx/verbatimCapture.ts
|
|
7
9
|
/**
|
|
@@ -24,6 +26,15 @@ import { TaggedError } from "better-result";
|
|
|
24
26
|
/** A Strict fragment uses a namespace that has no Transitional counterpart. */
|
|
25
27
|
var UntranslatableStrictNamespaceError = class extends TaggedError("UntranslatableStrictNamespaceError") {};
|
|
26
28
|
const TRANSITIONAL_URIS = new Set(TRANSITIONAL_NAMESPACE_BY_STRICT_URI.values());
|
|
29
|
+
const XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
|
|
30
|
+
const XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
|
|
31
|
+
const XSI_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance";
|
|
32
|
+
const NCNAME = /^[\p{L}_][\p{L}\p{N}._\-\u00b7\p{M}]*$/u;
|
|
33
|
+
const MC_QNAME_LIST_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
34
|
+
"PreserveAttributes",
|
|
35
|
+
"PreserveElements",
|
|
36
|
+
"ProcessContent"
|
|
37
|
+
]);
|
|
27
38
|
/**
|
|
28
39
|
* Drawing extensions the repository has no schema for.
|
|
29
40
|
*
|
|
@@ -71,12 +82,14 @@ const originOf = (element, inherited) => {
|
|
|
71
82
|
});
|
|
72
83
|
return TRANSITIONAL_URIS.has(uri) ? "transitional" : inherited;
|
|
73
84
|
};
|
|
74
|
-
const convertAttributes = (element, namespaceUri, localName) => {
|
|
85
|
+
const convertAttributes = (element, namespaceUri, localName, convertValues) => {
|
|
75
86
|
const attributes = element.attributes ?? {};
|
|
76
87
|
let converted;
|
|
77
88
|
for (const [name, raw] of Object.entries(attributes)) {
|
|
78
89
|
if (typeof raw !== "string") continue;
|
|
79
|
-
|
|
90
|
+
let next = raw;
|
|
91
|
+
if (name === "xmlns" || name.startsWith("xmlns:")) next = toTransitionalNamespaceUri(raw);
|
|
92
|
+
else if (convertValues) next = transitionalValue(raw, namespaceUri, transitionalSlotEncoding(element.namespaceUri, localName, getLocalName(name)));
|
|
80
93
|
if (next === raw) continue;
|
|
81
94
|
converted ??= { ...attributes };
|
|
82
95
|
converted[name] = next;
|
|
@@ -93,7 +106,7 @@ const toTransitional = (element, inherited) => {
|
|
|
93
106
|
const namespaceUri = element.namespaceUri === void 0 ? "" : toTransitionalNamespaceUri(element.namespaceUri);
|
|
94
107
|
const localName = getLocalName(element.name);
|
|
95
108
|
const textEncoding = transitionalSlotEncoding(element.namespaceUri, localName);
|
|
96
|
-
const attributes =
|
|
109
|
+
const attributes = element.attributes ? convertAttributes(element, namespaceUri, localName, origin === "strict") : void 0;
|
|
97
110
|
let elements;
|
|
98
111
|
for (const [index, child] of (element.elements ?? []).entries()) {
|
|
99
112
|
const next = origin === "strict" && child.type === "text" && typeof child.text === "string" ? childWithValue(child, transitionalValue(child.text, namespaceUri, textEncoding)) : toTransitional(child, origin);
|
|
@@ -113,6 +126,45 @@ const scopeDeclaresStrict = (scope) => {
|
|
|
113
126
|
for (let current = scope; current; current = current.parent) for (const uri of current.bindings.values()) if (uri.startsWith("http://purl.oclc.org/ooxml/")) return true;
|
|
114
127
|
return false;
|
|
115
128
|
};
|
|
129
|
+
const resolveNamespaceBinding = (scope, prefix) => {
|
|
130
|
+
if (prefix === "xml") return XML_NAMESPACE_URI;
|
|
131
|
+
for (let current = scope; current; current = current.parent) {
|
|
132
|
+
const value = current.bindings.get(prefix);
|
|
133
|
+
if (value !== void 0) return value;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const namespaceValueKind = (element, attributeName) => {
|
|
137
|
+
const prefix = getNamespacePrefix(attributeName);
|
|
138
|
+
const namespaceUri = prefix === null ? "" : toTransitionalNamespaceUri(resolveNamespaceBinding(element.namespaceScope, prefix) ?? "");
|
|
139
|
+
const localName = getLocalName(attributeName);
|
|
140
|
+
if (namespaceUri === XSI_NAMESPACE_URI && localName === "type") return "qname";
|
|
141
|
+
if (namespaceUri === NAMESPACES.mc) {
|
|
142
|
+
if (localName === "Ignorable" || localName === "MustUnderstand") return "prefix-list";
|
|
143
|
+
if (MC_QNAME_LIST_ATTRIBUTES.has(localName)) return "qname-list";
|
|
144
|
+
}
|
|
145
|
+
if (prefix === null && localName === "Requires" && getLocalName(element.name ?? "") === "Choice" && toTransitionalNamespaceUri(element.namespaceUri ?? "") === NAMESPACES.mc) return "prefix-list";
|
|
146
|
+
return null;
|
|
147
|
+
};
|
|
148
|
+
const referencedNamespacePrefixes = (element, attributeName, value) => {
|
|
149
|
+
const kind = namespaceValueKind(element, attributeName);
|
|
150
|
+
if (kind === null) return [];
|
|
151
|
+
const tokens = value.trim() === "" ? [] : value.trim().split(/\s+/u);
|
|
152
|
+
if (kind === "qname" && tokens.length !== 1) return null;
|
|
153
|
+
const prefixes = [];
|
|
154
|
+
for (const token of tokens) {
|
|
155
|
+
if (kind === "prefix-list") {
|
|
156
|
+
if (!NCNAME.test(token) || resolveNamespaceBinding(element.namespaceScope, token) === void 0) return null;
|
|
157
|
+
prefixes.push(token);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const parts = token.split(":");
|
|
161
|
+
if (parts.length > 2 || parts.some((part) => !NCNAME.test(part))) return null;
|
|
162
|
+
const prefix = parts.length === 2 ? parts[0] : "";
|
|
163
|
+
if (prefix !== "" && resolveNamespaceBinding(element.namespaceScope, prefix) === void 0) return null;
|
|
164
|
+
prefixes.push(prefix);
|
|
165
|
+
}
|
|
166
|
+
return prefixes;
|
|
167
|
+
};
|
|
116
168
|
/**
|
|
117
169
|
* Serialize parsed markup for replay inside a rebuilt Transitional part.
|
|
118
170
|
*
|
|
@@ -122,10 +174,158 @@ const scopeDeclaresStrict = (scope) => {
|
|
|
122
174
|
* anywhere in scope or in the fragment itself, is serialized once and returned
|
|
123
175
|
* unchanged.
|
|
124
176
|
*/
|
|
177
|
+
const materializeInheritedNamespaceBindings = (element) => {
|
|
178
|
+
const additions = {};
|
|
179
|
+
const rootAttributes = element.attributes ?? {};
|
|
180
|
+
const addInheritedBinding = (node, prefix) => {
|
|
181
|
+
if (prefix === "xml" || prefix === "xmlns") return;
|
|
182
|
+
const attributeName = prefix === "" ? "xmlns" : `xmlns:${prefix}`;
|
|
183
|
+
if (rootAttributes[attributeName] !== void 0 || additions[attributeName] !== void 0) return;
|
|
184
|
+
const namespaceUri = resolveNamespaceBinding(node.namespaceScope, prefix);
|
|
185
|
+
if (namespaceUri === void 0) return;
|
|
186
|
+
const canonicalNamespace = OOXML_NAMESPACE_SCOPE.bindings.get(prefix);
|
|
187
|
+
if (canonicalNamespace !== void 0 && toTransitionalNamespaceUri(namespaceUri) === canonicalNamespace) return;
|
|
188
|
+
additions[attributeName] = namespaceUri;
|
|
189
|
+
};
|
|
190
|
+
const visit = (node, inheritedDeclarations) => {
|
|
191
|
+
const declarations = new Set(inheritedDeclarations);
|
|
192
|
+
for (const name of Object.keys(node.attributes ?? {})) if (name === "xmlns") declarations.add("");
|
|
193
|
+
else if (name.startsWith("xmlns:")) declarations.add(name.slice(6));
|
|
194
|
+
const elementPrefix = node.name ? getNamespacePrefix(node.name) : null;
|
|
195
|
+
if (elementPrefix !== null && !declarations.has(elementPrefix)) addInheritedBinding(node, elementPrefix);
|
|
196
|
+
else if (elementPrefix === null && !declarations.has("")) addInheritedBinding(node, "");
|
|
197
|
+
for (const [name, value] of Object.entries(node.attributes ?? {})) {
|
|
198
|
+
if (name === "xmlns" || name.startsWith("xmlns:")) continue;
|
|
199
|
+
const attributePrefix = getNamespacePrefix(name);
|
|
200
|
+
if (attributePrefix !== null && !declarations.has(attributePrefix)) addInheritedBinding(node, attributePrefix);
|
|
201
|
+
const prefixes = referencedNamespacePrefixes(node, name, String(value));
|
|
202
|
+
if (prefixes === null) continue;
|
|
203
|
+
for (const prefix of prefixes) if (!declarations.has(prefix)) addInheritedBinding(node, prefix);
|
|
204
|
+
}
|
|
205
|
+
for (const child of node.elements ?? []) if (child.type === "element") visit(child, declarations);
|
|
206
|
+
};
|
|
207
|
+
visit(element, /* @__PURE__ */ new Set());
|
|
208
|
+
return Object.keys(additions).length === 0 ? element : cloneElement(element, { attributes: {
|
|
209
|
+
...rootAttributes,
|
|
210
|
+
...additions
|
|
211
|
+
} });
|
|
212
|
+
};
|
|
125
213
|
const captureVerbatimXml = (element) => {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
214
|
+
const replayableElement = materializeInheritedNamespaceBindings(element);
|
|
215
|
+
const xml = elementToXml(replayableElement);
|
|
216
|
+
if (!xml.includes("http://purl.oclc.org/ooxml/") && !scopeDeclaresStrict(replayableElement.namespaceScope)) return xml;
|
|
217
|
+
return elementToXml(toTransitional(replayableElement, "transitional"));
|
|
218
|
+
};
|
|
219
|
+
const hasRequiredNamespaceBindings = (element, required) => {
|
|
220
|
+
if (!required) return true;
|
|
221
|
+
for (const [prefix, namespaceUri] of required) {
|
|
222
|
+
const resolved = resolveNamespaceBinding(element.namespaceScope, prefix);
|
|
223
|
+
if (resolved === void 0 || toTransitionalNamespaceUri(resolved) !== namespaceUri) return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
};
|
|
227
|
+
const validNamespaceDeclaration = (name, value) => {
|
|
228
|
+
if (name === "xmlns") return value !== XML_NAMESPACE_URI && value !== XMLNS_NAMESPACE_URI;
|
|
229
|
+
const prefix = name.slice(6);
|
|
230
|
+
if (prefix.toLowerCase().startsWith("xml") && prefix !== "xml") return false;
|
|
231
|
+
if (prefix === "xml") return value === XML_NAMESPACE_URI;
|
|
232
|
+
return value !== "" && value !== XML_NAMESPACE_URI && value !== XMLNS_NAMESPACE_URI;
|
|
233
|
+
};
|
|
234
|
+
const hasBoundNamespaces = (root) => {
|
|
235
|
+
const pending = [root];
|
|
236
|
+
while (pending.length > 0) {
|
|
237
|
+
const element = pending.pop();
|
|
238
|
+
if (!element || element.type !== "element" || !element.name) return false;
|
|
239
|
+
const elementPrefix = getNamespacePrefix(element.name);
|
|
240
|
+
if (elementPrefix === "xmlns" || elementPrefix !== null && elementPrefix !== "xml" && resolveNamespaceBinding(element.namespaceScope, elementPrefix) === void 0) return false;
|
|
241
|
+
const expandedAttributeNames = /* @__PURE__ */ new Set();
|
|
242
|
+
for (const [name, rawValue] of Object.entries(element.attributes ?? {})) {
|
|
243
|
+
const value = String(rawValue ?? "");
|
|
244
|
+
if (name === "xmlns" || name.startsWith("xmlns:")) {
|
|
245
|
+
if (!validNamespaceDeclaration(name, value)) return false;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const prefix = getNamespacePrefix(name);
|
|
249
|
+
if (prefix === "xmlns" || prefix !== null && prefix !== "xml" && resolveNamespaceBinding(element.namespaceScope, prefix) === void 0) return false;
|
|
250
|
+
const expandedName = `${toTransitionalNamespaceUri((prefix === null ? "" : resolveNamespaceBinding(element.namespaceScope, prefix)) ?? "")}\u0000${getLocalName(name)}`;
|
|
251
|
+
if (expandedAttributeNames.has(expandedName)) return false;
|
|
252
|
+
expandedAttributeNames.add(expandedName);
|
|
253
|
+
if (referencedNamespacePrefixes(element, name, value) === null) return false;
|
|
254
|
+
}
|
|
255
|
+
for (const child of getChildElements(element)) pending.push(child);
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
};
|
|
259
|
+
const parsedReplayEnvelope = (xml, inheritedNamespaceScope) => {
|
|
260
|
+
let wrapper;
|
|
261
|
+
try {
|
|
262
|
+
wrapper = parseXml(`<folio-replay-envelope>${xml}</folio-replay-envelope>`, inheritedNamespaceScope).elements?.at(0);
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
if (!wrapper || wrapper.type !== "element") return null;
|
|
267
|
+
let root = null;
|
|
268
|
+
for (const child of wrapper.elements ?? []) {
|
|
269
|
+
if (child.type === "text") {
|
|
270
|
+
if (String(child.text ?? "").trim() !== "") return null;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (child.type !== "element" || root !== null) return null;
|
|
274
|
+
root = child;
|
|
275
|
+
}
|
|
276
|
+
return root;
|
|
277
|
+
};
|
|
278
|
+
const withinXmlResourceLimits = (xml) => {
|
|
279
|
+
try {
|
|
280
|
+
assertXmlResourceLimits(xml);
|
|
281
|
+
return true;
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const EMPTY_NAMESPACE_SCOPE = { bindings: /* @__PURE__ */ new Map() };
|
|
287
|
+
/** Validate a complete XML part immediately before package output. */
|
|
288
|
+
const isSafeCapturedXmlDocument = (xml) => {
|
|
289
|
+
const trimmed = xml.trim();
|
|
290
|
+
if (trimmed === "" || !withinXmlResourceLimits(xml) || getDocxXmlSafetyIssue(xml) !== null) return false;
|
|
291
|
+
const root = parsedReplayEnvelope(trimmed, EMPTY_NAMESPACE_SCOPE);
|
|
292
|
+
return root !== null && hasBoundNamespaces(root);
|
|
293
|
+
};
|
|
294
|
+
const captureAllowedElement = (element, { allowedLocalNames, allowedNamespaceUris, inheritedNamespaceScope, requiredNamespaceBindings, transform, validate }) => {
|
|
295
|
+
let passesDomainValidation = true;
|
|
296
|
+
try {
|
|
297
|
+
passesDomainValidation = validate?.(element) ?? true;
|
|
298
|
+
} catch {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
if (element.type !== "element" || !element.name || !allowedLocalNames.has(getLocalName(element.name)) || !allowedNamespaceUris.has(toTransitionalNamespaceUri(element.namespaceUri ?? "")) || !hasBoundNamespaces(element) || !hasRequiredNamespaceBindings(element, requiredNamespaceBindings) || !passesDomainValidation) return null;
|
|
302
|
+
try {
|
|
303
|
+
const replayElement = transform?.(element) ?? element;
|
|
304
|
+
if (!(transform === void 0 ? true : validate?.(replayElement) ?? true) || !hasBoundNamespaces(replayElement) || !hasRequiredNamespaceBindings(replayElement, requiredNamespaceBindings)) return null;
|
|
305
|
+
const captured = captureVerbatimXml(replayElement);
|
|
306
|
+
const capturedRoot = parsedReplayEnvelope(captured, inheritedNamespaceScope ?? OOXML_NAMESPACE_SCOPE);
|
|
307
|
+
if (capturedRoot === null || !hasBoundNamespaces(capturedRoot) || !hasRequiredNamespaceBindings(capturedRoot, requiredNamespaceBindings) || !(validate?.(capturedRoot) ?? true)) return null;
|
|
308
|
+
return captured;
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* Re-parse and serialize one captured element before replaying it.
|
|
315
|
+
*
|
|
316
|
+
* The parser deliberately ignores declarations, processing instructions,
|
|
317
|
+
* comments, and document types. Returning the caller's source string after a
|
|
318
|
+
* successful parse would therefore replay tokens that were never validated.
|
|
319
|
+
* This function returns only the parsed root serialized through the capture
|
|
320
|
+
* owner, and rejects siblings, unexpected roots, and namespace shadowing.
|
|
321
|
+
*/
|
|
322
|
+
const sanitizeCapturedXmlElement = (xml, options) => {
|
|
323
|
+
if (xml === void 0) return null;
|
|
324
|
+
const trimmed = xml.trim();
|
|
325
|
+
if (!trimmed) return null;
|
|
326
|
+
if (!withinXmlResourceLimits(xml) || getDocxXmlSafetyIssue(xml) !== null) return null;
|
|
327
|
+
const root = parsedReplayEnvelope(trimmed, options.inheritedNamespaceScope ?? OOXML_NAMESPACE_SCOPE);
|
|
328
|
+
return root ? captureAllowedElement(root, options) : null;
|
|
129
329
|
};
|
|
130
330
|
//#endregion
|
|
131
|
-
export { UntranslatableStrictNamespaceError, captureVerbatimXml };
|
|
331
|
+
export { UntranslatableStrictNamespaceError, captureVerbatimXml, isSafeCapturedXmlDocument, sanitizeCapturedXmlElement };
|
package/dist/docx/xmlParser.d.ts
CHANGED
|
@@ -117,6 +117,8 @@ declare const WORDPROCESSINGML_NAMESPACE_URIS: ReadonlySet<string>;
|
|
|
117
117
|
* from a foreign namespace is not accepted.
|
|
118
118
|
*/
|
|
119
119
|
declare function findChildByNamespaceUri(parent: XmlElement | null | undefined, namespaceUris: ReadonlySet<string>, localName: string): XmlElement | null;
|
|
120
|
+
/** All direct children whose local name and resolved namespace both match. */
|
|
121
|
+
declare function findChildrenByNamespaceUri(parent: XmlElement | null | undefined, namespaceUris: ReadonlySet<string>, localName: string): XmlElement[];
|
|
120
122
|
type XmlAttributeMatch = {
|
|
121
123
|
name: string;
|
|
122
124
|
value: string;
|
|
@@ -367,4 +369,4 @@ declare function cloneWithXmlnsDeclarations(element: XmlElement, xmlnsDecls: Rec
|
|
|
367
369
|
*/
|
|
368
370
|
declare function cloneElement(element: XmlElement, overrides: Partial<XmlElement>): XmlElement;
|
|
369
371
|
//#endregion
|
|
370
|
-
export { NAMESPACES, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, attachXmlNamespaceContext, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
|
372
|
+
export { NAMESPACES, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, attachXmlNamespaceContext, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findChildrenByNamespaceUri, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
package/dist/docx/xmlParser.js
CHANGED
|
@@ -227,6 +227,11 @@ function findChildByNamespaceUri(parent, namespaceUris, localName) {
|
|
|
227
227
|
for (const child of parent.elements) if (child.type === "element" && hasLocalName(child.name, localName) && namespaceUris.has(child.namespaceUri ?? "")) return child;
|
|
228
228
|
return null;
|
|
229
229
|
}
|
|
230
|
+
/** All direct children whose local name and resolved namespace both match. */
|
|
231
|
+
function findChildrenByNamespaceUri(parent, namespaceUris, localName) {
|
|
232
|
+
if (!parent?.elements) return [];
|
|
233
|
+
return parent.elements.filter((child) => child.type === "element" && hasLocalName(child.name, localName) && namespaceUris.has(child.namespaceUri ?? ""));
|
|
234
|
+
}
|
|
230
235
|
/** Find an attribute whose prefix resolves to one of the accepted namespace URIs. */
|
|
231
236
|
function findAttributeByNamespaceUri(element, namespaceUris, localName) {
|
|
232
237
|
if (!element?.attributes) return null;
|
|
@@ -783,4 +788,4 @@ function cloneElement(element, overrides) {
|
|
|
783
788
|
return clone;
|
|
784
789
|
}
|
|
785
790
|
//#endregion
|
|
786
|
-
export { NAMESPACES, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, attachXmlNamespaceContext, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
|
791
|
+
export { NAMESPACES, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, attachXmlNamespaceContext, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findChildrenByNamespaceUri, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
package/dist/docx/xmlSafety.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/docx/xmlSafety.d.ts
|
|
2
|
-
type DocxXmlSafetyIssue = "doctype-forbidden" | "not-well-formed";
|
|
2
|
+
type DocxXmlSafetyIssue = "doctype-forbidden" | "entity-forbidden" | "not-well-formed";
|
|
3
3
|
declare const getDocxXmlSafetyIssue: (xml: string) => DocxXmlSafetyIssue | null;
|
|
4
4
|
//#endregion
|
|
5
5
|
export { DocxXmlSafetyIssue, getDocxXmlSafetyIssue };
|