@stll/folio-core 0.54.1 → 0.55.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.d.ts +1 -1
- package/dist/ai-edits/apply.js +40 -32
- package/dist/ai-edits/headless.d.ts +1 -2
- package/dist/ai-edits/headless.js +13 -27
- package/dist/ai-edits/read.d.ts +1 -0
- package/dist/ai-edits/read.js +7 -2
- package/dist/ai-edits/table-mutation-plan.d.ts +2 -1
- package/dist/ai-edits/table-mutation-plan.js +39 -1
- package/dist/ai-suggestions/apply.d.ts +1 -1
- package/dist/ai-suggestions/apply.js +3 -2
- package/dist/compare/compare.js +2 -1
- package/dist/compare/content-types.d.ts +3 -15
- package/dist/controller/canonicalComposition.d.ts +36 -0
- package/dist/controller/canonicalComposition.js +168 -0
- package/dist/controller/canonicalInput.d.ts +45 -0
- package/dist/controller/canonicalInput.js +338 -0
- package/dist/controller/canonicalOperations.d.ts +22 -0
- package/dist/controller/canonicalOperations.js +85 -0
- package/dist/controller/canonicalReview.d.ts +12 -0
- package/dist/controller/canonicalReview.js +19 -0
- package/dist/controller/canonicalSession.d.ts +149 -0
- package/dist/controller/canonicalSession.js +1038 -0
- package/dist/controller/canonicalStoryEditor.d.ts +34 -0
- package/dist/controller/canonicalStoryEditor.js +82 -0
- package/dist/controller/canonicalStructure.d.ts +16 -0
- package/dist/controller/canonicalStructure.js +269 -0
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/folioEditor.js +9 -0
- package/dist/controller/headerFooterEditorManager.d.ts +4 -0
- package/dist/controller/headerFooterEditorManager.js +71 -3
- package/dist/controller/hiddenEditorApi.d.ts +35 -0
- package/dist/controller/hiddenEditorApi.js +25 -8
- package/dist/controller/hiddenEditorManager.d.ts +11 -2
- package/dist/controller/hiddenEditorManager.js +456 -30
- package/dist/controller/layoutPipeline.d.ts +1 -1
- package/dist/controller/layoutPipeline.js +2 -0
- package/dist/controller/noteEditorManager.d.ts +4 -0
- package/dist/controller/noteEditorManager.js +113 -15
- package/dist/docx/attributeRemainder.js +1 -0
- package/dist/docx/bookmarkIds.d.ts +11 -0
- package/dist/docx/bookmarkIds.js +33 -0
- package/dist/docx/canonicalSessionInput.d.ts +11 -0
- package/dist/docx/canonicalSessionInput.js +27 -0
- package/dist/docx/canonicalStoryRepack.d.ts +10 -0
- package/dist/docx/canonicalStoryRepack.js +41 -0
- package/dist/docx/contentTypeRegistry.d.ts +8 -0
- package/dist/docx/contentTypeRegistry.js +64 -0
- package/dist/docx/documentParser.d.ts +14 -1
- package/dist/docx/documentParser.js +17 -4
- package/dist/docx/drawingIdNormalization.js +23 -8
- package/dist/docx/ensureParaIds.d.ts +12 -1
- package/dist/docx/ensureParaIds.js +173 -213
- package/dist/docx/foldedListNumberFields.d.ts +85 -0
- package/dist/docx/foldedListNumberFields.js +267 -0
- package/dist/docx/headerFooterVerbatim.d.ts +14 -1
- package/dist/docx/headerFooterVerbatim.js +95 -2
- package/dist/docx/listNumberingInstances.js +11 -9
- package/dist/docx/noteIds.d.ts +9 -0
- package/dist/docx/noteIds.js +17 -0
- package/dist/docx/noteReferenceMark.d.ts +3 -1
- package/dist/docx/noteReferenceMark.js +15 -14
- package/dist/docx/numberingIds.d.ts +14 -0
- package/dist/docx/numberingIds.js +16 -0
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +17 -3
- package/dist/docx/numericIdAllocator.d.ts +12 -0
- package/dist/docx/numericIdAllocator.js +33 -0
- package/dist/docx/numericIdNormalization.d.ts +16 -0
- package/dist/docx/numericIdNormalization.js +174 -0
- package/dist/docx/packageParts.d.ts +3 -1
- package/dist/docx/packageParts.js +65 -16
- package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
- package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
- package/dist/docx/paragraphParser.js +101 -53
- package/dist/docx/paragraphPropertySource.d.ts +15 -1
- package/dist/docx/paragraphPropertySource.js +76 -22
- package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
- package/dist/docx/parser.js +19 -6
- package/dist/docx/rasterMime.d.ts +64 -0
- package/dist/docx/rasterMime.js +116 -0
- package/dist/docx/replyToComment.js +13 -6
- package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
- package/dist/docx/reviewHistoryNamespace.js +4 -0
- package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
- package/dist/docx/reviewResolutionProvenance.js +153 -0
- package/dist/docx/rezip.d.ts +7 -3
- package/dist/docx/rezip.js +121 -52
- package/dist/docx/runConsolidator.d.ts +2 -58
- package/dist/docx/runConsolidator.js +3 -175
- package/dist/docx/runParser.d.ts +2 -1
- package/dist/docx/runParser.js +156 -116
- package/dist/docx/saveDiagnostics.d.ts +11 -0
- package/dist/docx/saveDiagnostics.js +0 -0
- package/dist/docx/selectiveSave.d.ts +2 -1
- package/dist/docx/selectiveSave.js +14 -1
- package/dist/docx/selectiveXmlPatch.d.ts +16 -8
- package/dist/docx/selectiveXmlPatch.js +74 -2
- package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
- package/dist/docx/serializer/headerFooterSerializer.js +19 -11
- package/dist/docx/serializer/noteSerializer.d.ts +2 -2
- package/dist/docx/serializer/noteSerializer.js +24 -8
- package/dist/docx/serializer/paragraphSerializer.js +41 -4
- package/dist/docx/serializer/partNamespaces.js +6 -3
- package/dist/docx/serializer/runSerializer.js +8 -5
- package/dist/docx/serializer/trackedChangeAttributes.js +7 -1
- package/dist/docx/server/build.js +14 -3
- package/dist/docx/server/createBilingualDocument.js +10 -9
- package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
- package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
- package/dist/docx/settingsParser.d.ts +1 -2
- package/dist/docx/settingsParser.js +5 -6
- package/dist/docx/storyBlockReplay.d.ts +11 -0
- package/dist/docx/storyBlockReplay.js +367 -0
- package/dist/docx/streamingXmlParser.d.ts +31 -1
- package/dist/docx/streamingXmlParser.js +136 -21
- package/dist/docx/structuralXmlPatch.js +8 -1
- package/dist/docx/unzip.d.ts +3 -1
- package/dist/docx/unzip.js +36 -16
- package/dist/docx/verbatimCapture.d.ts +3 -1
- package/dist/docx/verbatimCapture.js +49 -6
- package/dist/i18n/messages/ar.gen.d.ts +5 -0
- package/dist/i18n/messages/ar.gen.js +656 -0
- package/dist/i18n/messages/cs.gen.d.ts +5 -0
- package/dist/i18n/messages/cs.gen.js +656 -0
- package/dist/i18n/messages/de.gen.d.ts +5 -0
- package/dist/i18n/messages/de.gen.js +656 -0
- package/dist/i18n/messages/en.gen.d.ts +5 -0
- package/dist/i18n/messages/en.gen.js +656 -0
- package/dist/i18n/messages/es.gen.d.ts +5 -0
- package/dist/i18n/messages/es.gen.js +656 -0
- package/dist/i18n/messages/et.gen.d.ts +5 -0
- package/dist/i18n/messages/et.gen.js +656 -0
- package/dist/i18n/messages/fr.gen.d.ts +5 -0
- package/dist/i18n/messages/fr.gen.js +656 -0
- package/dist/i18n/messages/he.gen.d.ts +5 -0
- package/dist/i18n/messages/he.gen.js +656 -0
- package/dist/i18n/messages/hi.gen.d.ts +5 -0
- package/dist/i18n/messages/hi.gen.js +656 -0
- package/dist/i18n/messages/hu.gen.d.ts +5 -0
- package/dist/i18n/messages/hu.gen.js +656 -0
- package/dist/i18n/messages/locales.d.ts +5 -0
- package/dist/i18n/messages/locales.js +22 -0
- package/dist/i18n/messages/lt.gen.d.ts +5 -0
- package/dist/i18n/messages/lt.gen.js +656 -0
- package/dist/i18n/messages/lv.gen.d.ts +5 -0
- package/dist/i18n/messages/lv.gen.js +656 -0
- package/dist/i18n/messages/pl.gen.d.ts +5 -0
- package/dist/i18n/messages/pl.gen.js +656 -0
- package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
- package/dist/i18n/messages/pt-BR.gen.js +656 -0
- package/dist/i18n/messages/sk.gen.d.ts +5 -0
- package/dist/i18n/messages/sk.gen.js +656 -0
- package/dist/i18n/messages/tr.gen.d.ts +5 -0
- package/dist/i18n/messages/tr.gen.js +656 -0
- package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
- package/dist/i18n/messages/zh-CN.gen.js +656 -0
- package/dist/i18n/messages.d.ts +2 -3
- package/dist/i18n/messages.js +1 -19
- package/dist/internal/noteMarkerAttrs.d.ts +8 -0
- package/dist/internal/noteMarkerAttrs.js +16 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
- package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
- package/dist/internal/prosemirrorAttrBoundary.js +50 -0
- package/dist/layout-bridge/convert/fixedTableColumnWidths.d.ts +6 -0
- package/dist/layout-bridge/convert/fixedTableColumnWidths.js +23 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
- package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
- package/dist/layout-bridge/convert/noteReferences.js +1 -6
- package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
- package/dist/layout-bridge/convert/tableConversion.js +4 -1
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
- package/dist/layout-painter/index.js +2 -5
- package/dist/managers/DocumentLoaderManager.d.ts +1 -0
- package/dist/managers/DocumentLoaderManager.js +8 -1
- package/dist/markdown/renderBlock.js +15 -1
- package/dist/markdown/renderParagraph.js +9 -13
- package/dist/markdown/renderRuns.js +3 -2
- package/dist/markdown/renderTable.js +2 -0
- package/dist/markdown/types.d.ts +3 -9
- package/dist/paged-layout/editorScrollRoot.d.ts +15 -0
- package/dist/paged-layout/editorScrollRoot.js +72 -0
- package/dist/paged-layout/scrollToPmPosition.js +8 -22
- package/dist/prosemirror/attrs/index.d.ts +2 -12
- package/dist/prosemirror/attrs/index.js +22 -48
- package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
- package/dist/prosemirror/canonicalCommands.d.ts +97 -0
- package/dist/prosemirror/canonicalCommands.js +28 -0
- package/dist/prosemirror/commands/clearParagraphIndent.d.ts +6 -0
- package/dist/prosemirror/commands/clearParagraphIndent.js +25 -0
- package/dist/prosemirror/commands/pageBreak.js +15 -1
- package/dist/prosemirror/commentIdAllocator.d.ts +15 -22
- package/dist/prosemirror/commentIdAllocator.js +50 -25
- package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
- package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
- package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -3
- package/dist/prosemirror/conversion/toProseDoc.js +51 -11
- package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
- package/dist/prosemirror/executeEditorCommand.js +26 -0
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/HistoryExtension.js +97 -5
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +53 -20
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +22 -28
- package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
- package/dist/prosemirror/extensions/marks/markUtils.js +70 -8
- package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
- package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -6
- package/dist/prosemirror/listMarker.d.ts +1 -0
- package/dist/prosemirror/listMarker.js +16 -7
- package/dist/prosemirror/listNumbering.js +26 -5
- package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
- package/dist/prosemirror/listRenderingAttrs.js +3 -0
- package/dist/prosemirror/markupViewNotes.js +3 -2
- package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
- package/dist/prosemirror/noteReferenceReview.js +5 -3
- package/dist/prosemirror/plugins/revisionIds.d.ts +23 -19
- package/dist/prosemirror/plugins/revisionIds.js +110 -29
- package/dist/prosemirror/plugins/suggestionMode.d.ts +2 -2
- package/dist/prosemirror/plugins/suggestionMode.js +246 -42
- package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
- package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
- package/dist/prosemirror/runFormattingFromMarks.js +15 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
- package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
- package/dist/prosemirror/schema/marks.d.ts +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +10 -2
- package/dist/prosemirror/storyListNumbering.js +11 -9
- package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
- package/dist/prosemirror/textInput.d.ts +6 -1
- package/dist/prosemirror/textInput.js +14 -3
- package/dist/prosemirror/trackedRevisionPath.js +2 -1
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
- package/dist/prosemirror/utils/visualLineNavigation.js +6 -8
- package/dist/prosemirror/validation.js +4 -0
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/createDocument.js +2 -2
- package/dist/utils/mergeDocumentContent.d.ts +2 -2
- package/dist/utils/mergeDocumentContent.js +11 -8
- package/package.json +14 -2
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
|
+
import { spliceXml } from "./selectiveXmlPatch.js";
|
|
3
|
+
import { getXmlSourceRange, parseStreamingXmlWithSourceRanges } from "./streamingXmlParser.js";
|
|
4
|
+
import { TRANSITIONAL_NAMESPACE_BY_STRICT_URI } from "./strictValueEncodings.gen.js";
|
|
5
|
+
import { captureSourceProfileXml, isSafeCapturedXmlDocument } from "./verbatimCapture.js";
|
|
6
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri } from "./xmlParser.js";
|
|
7
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
8
|
+
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
9
|
+
//#region src/docx/storyBlockReplay.ts
|
|
10
|
+
const BLOCK_ELEMENT_NAMES = {
|
|
11
|
+
paragraph: "p",
|
|
12
|
+
table: "tbl",
|
|
13
|
+
blockSdt: "sdt",
|
|
14
|
+
blockCustomXml: "customXml",
|
|
15
|
+
preservedBlock: null,
|
|
16
|
+
bookmarkStart: "bookmarkStart",
|
|
17
|
+
bookmarkEnd: "bookmarkEnd"
|
|
18
|
+
};
|
|
19
|
+
const modelMatchesElement = ({ value, element, mode }) => {
|
|
20
|
+
if (value === void 0) return false;
|
|
21
|
+
if (mode === "source" && getLocalName(element.name) === "AlternateContent" && getNamespaceUri(element) === "http://schemas.openxmlformats.org/markup-compatibility/2006" && (value.type === "paragraph" || value.type === "table")) return true;
|
|
22
|
+
const name = BLOCK_ELEMENT_NAMES[value.type];
|
|
23
|
+
if (name !== null) return getLocalName(element.name) === name && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "");
|
|
24
|
+
if (value.type !== "preservedBlock") return false;
|
|
25
|
+
const parsed = parseStreamingXmlWithSourceRanges(value.xml, element.namespaceScope);
|
|
26
|
+
if (parsed.status !== "parsed") return false;
|
|
27
|
+
const roots = getChildElements(parsed.value);
|
|
28
|
+
const root = roots.at(0);
|
|
29
|
+
const normalize = (uri) => uri === void 0 ? void 0 : TRANSITIONAL_NAMESPACE_BY_STRICT_URI.get(uri) ?? uri;
|
|
30
|
+
return roots.length === 1 && root !== void 0 && getLocalName(root.name) === getLocalName(element.name) && normalize(getNamespaceUri(root)) === normalize(getNamespaceUri(element));
|
|
31
|
+
};
|
|
32
|
+
const XML_TOKEN = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<(?:"[^"]*"|'[^']*'|[^'">])*>/gu;
|
|
33
|
+
const XML_ATTRIBUTE = /([^\s=<>/]+)(\s*=\s*)(["'])(.*?)\3/gsu;
|
|
34
|
+
const readStory = (xml, scope) => {
|
|
35
|
+
const parsed = parseStreamingXmlWithSourceRanges(xml, scope);
|
|
36
|
+
if (parsed.status !== "parsed") return null;
|
|
37
|
+
const roots = getChildElements(parsed.value);
|
|
38
|
+
const root = roots.at(0);
|
|
39
|
+
if (roots.length !== 1 || !root || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(root) ?? "")) return null;
|
|
40
|
+
if (root.elements?.some((child) => child.type === "text" && String(child.text ?? "").trim() !== "")) return null;
|
|
41
|
+
const children = getChildElements(root);
|
|
42
|
+
const blocks = [];
|
|
43
|
+
for (const element of children) {
|
|
44
|
+
const range = getXmlSourceRange(element);
|
|
45
|
+
if (!range) return null;
|
|
46
|
+
blocks.push({
|
|
47
|
+
element,
|
|
48
|
+
...range
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const range = getXmlSourceRange(root);
|
|
52
|
+
return range ? {
|
|
53
|
+
root,
|
|
54
|
+
blocks,
|
|
55
|
+
range
|
|
56
|
+
} : null;
|
|
57
|
+
};
|
|
58
|
+
const effectiveBindings = (element) => {
|
|
59
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
60
|
+
let scope = element.namespaceScope;
|
|
61
|
+
while (scope) {
|
|
62
|
+
for (const [prefix, uri] of scope.bindings) if (!bindings.has(prefix)) bindings.set(prefix, uri);
|
|
63
|
+
scope = scope.parent;
|
|
64
|
+
}
|
|
65
|
+
return bindings;
|
|
66
|
+
};
|
|
67
|
+
const generatedFragment = ({ xml, element, sourceNamespace, generatedRoot }) => {
|
|
68
|
+
const strict = TRANSITIONAL_NAMESPACE_BY_STRICT_URI.has(sourceNamespace);
|
|
69
|
+
const first = [...xml.matchAll(XML_TOKEN)].at(0);
|
|
70
|
+
if (!first || first.index !== 0 || first[0].startsWith("<!") || first[0].startsWith("<?")) return null;
|
|
71
|
+
const opening = first[0];
|
|
72
|
+
const additions = [];
|
|
73
|
+
const bindings = effectiveBindings(element);
|
|
74
|
+
const markupCompatibilityUri = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
75
|
+
const ignorableUris = /* @__PURE__ */ new Set([markupCompatibilityUri]);
|
|
76
|
+
const inheritedIgnorable = generatedRoot === void 0 ? null : getAttributeByNamespaceUri(generatedRoot, ignorableUris, "Ignorable");
|
|
77
|
+
const localIgnorable = getAttributeByNamespaceUri(element, ignorableUris, "Ignorable");
|
|
78
|
+
const ignorable = [...new Set(`${inheritedIgnorable ?? ""} ${localIgnorable ?? ""}`.trim().split(/\s+/u).filter(Boolean))];
|
|
79
|
+
let ignorableAttribute;
|
|
80
|
+
if (ignorable.length > 0) {
|
|
81
|
+
const localAttribute = Object.keys(element.attributes ?? {}).find((name) => {
|
|
82
|
+
const colon = name.indexOf(":");
|
|
83
|
+
return colon > 0 && getLocalName(name) === "Ignorable" && bindings.get(name.slice(0, colon)) === markupCompatibilityUri;
|
|
84
|
+
});
|
|
85
|
+
const compatibilityPrefix = localAttribute?.slice(0, localAttribute.indexOf(":")) ?? [...bindings].find(([prefix, uri]) => prefix !== "" && uri === markupCompatibilityUri)?.[0];
|
|
86
|
+
if (compatibilityPrefix === void 0 || ignorable.some((prefix) => !bindings.has(prefix))) return null;
|
|
87
|
+
ignorableAttribute = `${compatibilityPrefix}:Ignorable`;
|
|
88
|
+
if (localIgnorable === null) additions.push(` ${ignorableAttribute}="${escapeXmlAttribute(ignorable.join(" "))}"`);
|
|
89
|
+
}
|
|
90
|
+
for (const [prefix, uri] of bindings) {
|
|
91
|
+
const name = prefix === "" ? "xmlns" : `xmlns:${prefix}`;
|
|
92
|
+
if (element.attributes?.[name] !== void 0) continue;
|
|
93
|
+
additions.push(` ${name}="${escapeXmlAttribute(uri)}"`);
|
|
94
|
+
}
|
|
95
|
+
let replacedOpening = opening.replace(XML_ATTRIBUTE, (match, name, assignment, quote) => name === ignorableAttribute ? `${name}${assignment}${quote}${escapeXmlAttribute(ignorable.join(" "))}${quote}` : match);
|
|
96
|
+
const close = opening.endsWith("/>") ? replacedOpening.length - 2 : replacedOpening.length - 1;
|
|
97
|
+
replacedOpening = replacedOpening.slice(0, close) + additions.join("") + replacedOpening.slice(close);
|
|
98
|
+
const materialized = replacedOpening + xml.slice(opening.length);
|
|
99
|
+
if (!isSafeCapturedXmlDocument(materialized)) return null;
|
|
100
|
+
if (!strict) return materialized;
|
|
101
|
+
const parsed = parseStreamingXmlWithSourceRanges(materialized);
|
|
102
|
+
if (parsed.status !== "parsed") return null;
|
|
103
|
+
const root = getChildElements(parsed.value).at(0);
|
|
104
|
+
return root === void 0 ? null : captureSourceProfileXml(root, DOCX_CONFORMANCE_CLASSES.STRICT);
|
|
105
|
+
};
|
|
106
|
+
const paragraphIdentity = (value) => value.type === "paragraph" ? value.paraId?.toUpperCase() : void 0;
|
|
107
|
+
const addCandidate = ({ map, key, index }) => {
|
|
108
|
+
const queue = map.get(key);
|
|
109
|
+
if (queue) queue.indices.push(index);
|
|
110
|
+
else map.set(key, {
|
|
111
|
+
indices: [index],
|
|
112
|
+
cursor: 0
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
const takeCandidate = (queue, used) => {
|
|
116
|
+
if (!queue) return void 0;
|
|
117
|
+
while (queue.cursor < queue.indices.length) {
|
|
118
|
+
const candidate = queue.indices[queue.cursor];
|
|
119
|
+
queue.cursor += 1;
|
|
120
|
+
if (candidate !== void 0 && !used.has(candidate)) return candidate;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
/** Replace only mapped block ranges, retaining authored root syntax and every intervening gap. */
|
|
124
|
+
const replayBlocks = ({ sourceXml, baselineContent, currentContent, serializedXml }, scope) => {
|
|
125
|
+
const source = readStory(sourceXml, scope?.source);
|
|
126
|
+
const generated = readStory(serializedXml, scope?.generated);
|
|
127
|
+
if (!source || !generated || getLocalName(source.root.name) !== getLocalName(generated.root.name) || source.blocks.length !== baselineContent.length || generated.blocks.length !== currentContent.length) return null;
|
|
128
|
+
if (source.blocks.some((block, index) => !modelMatchesElement({
|
|
129
|
+
value: baselineContent[index],
|
|
130
|
+
element: block.element,
|
|
131
|
+
mode: "source"
|
|
132
|
+
})) || generated.blocks.some((block, index) => !modelMatchesElement({
|
|
133
|
+
value: currentContent[index],
|
|
134
|
+
element: block.element,
|
|
135
|
+
mode: "generated"
|
|
136
|
+
}))) return null;
|
|
137
|
+
const generatedRoot = scope?.generatedRoot ?? generated.root;
|
|
138
|
+
const fingerprints = baselineContent.map(canonicalJson);
|
|
139
|
+
const identities = /* @__PURE__ */ new Map();
|
|
140
|
+
const byFingerprint = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const [index, block] of baselineContent.entries()) {
|
|
142
|
+
const fingerprint = fingerprints[index];
|
|
143
|
+
if (fingerprint === void 0) return null;
|
|
144
|
+
addCandidate({
|
|
145
|
+
map: byFingerprint,
|
|
146
|
+
key: fingerprint,
|
|
147
|
+
index
|
|
148
|
+
});
|
|
149
|
+
const identity = paragraphIdentity(block);
|
|
150
|
+
if (identity !== void 0) addCandidate({
|
|
151
|
+
map: identities,
|
|
152
|
+
key: identity,
|
|
153
|
+
index
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const used = /* @__PURE__ */ new Set();
|
|
157
|
+
const fragments = [];
|
|
158
|
+
for (const [index, block] of currentContent.entries()) {
|
|
159
|
+
const fingerprint = canonicalJson(block);
|
|
160
|
+
const identity = paragraphIdentity(block);
|
|
161
|
+
let originalIndex = takeCandidate(identity === void 0 ? void 0 : identities.get(identity), used) ?? takeCandidate(byFingerprint.get(fingerprint), used);
|
|
162
|
+
if (originalIndex === void 0 && (block.type === "table" || block.type === "blockSdt" || block.type === "blockCustomXml") && !used.has(index)) {
|
|
163
|
+
if (baselineContent[index]?.type === block.type) originalIndex = index;
|
|
164
|
+
}
|
|
165
|
+
if (originalIndex !== void 0) used.add(originalIndex);
|
|
166
|
+
const original = originalIndex === void 0 ? void 0 : source.blocks[originalIndex];
|
|
167
|
+
if (originalIndex !== void 0 && original && fingerprints[originalIndex] === fingerprint) {
|
|
168
|
+
fragments.push(sourceXml.slice(original.start, original.end));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const replacement = generated.blocks[index];
|
|
172
|
+
if (!replacement) return null;
|
|
173
|
+
if (original && originalIndex !== void 0) {
|
|
174
|
+
const nested = replayNestedBlock({
|
|
175
|
+
sourceXml,
|
|
176
|
+
serializedXml,
|
|
177
|
+
sourceElement: original.element,
|
|
178
|
+
generatedElement: replacement.element,
|
|
179
|
+
baseline: baselineContent[originalIndex],
|
|
180
|
+
current: block,
|
|
181
|
+
generatedRoot
|
|
182
|
+
});
|
|
183
|
+
if (nested !== null) {
|
|
184
|
+
fragments.push(nested);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const fragment = generatedFragment({
|
|
189
|
+
xml: serializedXml.slice(replacement.start, replacement.end),
|
|
190
|
+
element: replacement.element,
|
|
191
|
+
sourceNamespace: getNamespaceUri(source.root) ?? "",
|
|
192
|
+
generatedRoot
|
|
193
|
+
});
|
|
194
|
+
if (fragment === null) return null;
|
|
195
|
+
fragments.push(fragment);
|
|
196
|
+
}
|
|
197
|
+
const splices = source.blocks.map((block, index) => ({
|
|
198
|
+
start: block.start,
|
|
199
|
+
end: block.end,
|
|
200
|
+
newXml: index === source.blocks.length - 1 ? fragments.slice(index).join("") : fragments[index] ?? ""
|
|
201
|
+
}));
|
|
202
|
+
if (source.blocks.length === 0 && fragments.length > 0) {
|
|
203
|
+
const opening = [...sourceXml.slice(source.range.start, source.range.end).matchAll(XML_TOKEN)].at(0)?.[0];
|
|
204
|
+
if (!opening || !source.root.name) return null;
|
|
205
|
+
if (opening.endsWith("/>")) splices.push({
|
|
206
|
+
start: source.range.start,
|
|
207
|
+
end: source.range.end,
|
|
208
|
+
newXml: `${opening.slice(0, -2)}>${fragments.join("")}</${source.root.name}>`
|
|
209
|
+
});
|
|
210
|
+
else {
|
|
211
|
+
const close = sourceXml.lastIndexOf("</", source.range.end);
|
|
212
|
+
if (close < source.range.start) return null;
|
|
213
|
+
splices.push({
|
|
214
|
+
start: close,
|
|
215
|
+
end: close,
|
|
216
|
+
newXml: fragments.join("")
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return spliceXml(sourceXml, splices);
|
|
221
|
+
};
|
|
222
|
+
const buildStoryBlockReplay = (options) => replayBlocks(options);
|
|
223
|
+
const withoutChildren = (value, key) => Object.fromEntries(Object.entries(value).filter(([name]) => name !== key));
|
|
224
|
+
const wordChildren = (element, name) => getChildElements(element).filter((child) => getLocalName(child.name) === name && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? ""));
|
|
225
|
+
const replayNestedBlock = ({ sourceXml, serializedXml, sourceElement, generatedElement, baseline, current, generatedRoot }) => {
|
|
226
|
+
if (baseline === void 0 || baseline.type !== current.type) return null;
|
|
227
|
+
const sourceRange = getXmlSourceRange(sourceElement);
|
|
228
|
+
if (!sourceRange) return null;
|
|
229
|
+
const splices = [];
|
|
230
|
+
const nestedContent = (sourceContainer, generatedContainer, before, after) => {
|
|
231
|
+
const sourceContentRange = getXmlSourceRange(sourceContainer);
|
|
232
|
+
const generatedContentRange = getXmlSourceRange(generatedContainer);
|
|
233
|
+
if (!sourceContentRange || !generatedContentRange) return false;
|
|
234
|
+
const patched = replayBlocks({
|
|
235
|
+
sourceXml: sourceXml.slice(sourceContentRange.start, sourceContentRange.end),
|
|
236
|
+
serializedXml: serializedXml.slice(generatedContentRange.start, generatedContentRange.end),
|
|
237
|
+
baselineContent: before,
|
|
238
|
+
currentContent: after
|
|
239
|
+
}, {
|
|
240
|
+
source: sourceContainer.namespaceScope,
|
|
241
|
+
generated: generatedContainer.namespaceScope,
|
|
242
|
+
generatedRoot
|
|
243
|
+
});
|
|
244
|
+
if (patched === null) return false;
|
|
245
|
+
splices.push({
|
|
246
|
+
start: sourceContentRange.start - sourceRange.start,
|
|
247
|
+
end: sourceContentRange.end - sourceRange.start,
|
|
248
|
+
newXml: patched
|
|
249
|
+
});
|
|
250
|
+
return true;
|
|
251
|
+
};
|
|
252
|
+
switch (current.type) {
|
|
253
|
+
case "blockSdt":
|
|
254
|
+
case "blockCustomXml": {
|
|
255
|
+
if (baseline.type !== "blockSdt" && baseline.type !== "blockCustomXml" || canonicalJson(withoutChildren(baseline, "content")) !== canonicalJson(withoutChildren(current, "content"))) return null;
|
|
256
|
+
if (current.type === "blockCustomXml") {
|
|
257
|
+
if (!nestedContent(sourceElement, generatedElement, baseline.content, current.content)) return null;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
const sourceChildren = wordChildren(sourceElement, "sdtContent");
|
|
261
|
+
const generatedChildren = wordChildren(generatedElement, "sdtContent");
|
|
262
|
+
const sourceContainer = sourceChildren.at(0);
|
|
263
|
+
const generatedContainer = generatedChildren.at(0);
|
|
264
|
+
if (sourceChildren.length !== 1 || generatedChildren.length !== 1 || !sourceContainer || !generatedContainer || !nestedContent(sourceContainer, generatedContainer, baseline.content, current.content)) return null;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case "table": {
|
|
268
|
+
if (baseline.type !== "table" || baseline.rows.length !== current.rows.length || canonicalJson(withoutChildren(baseline, "rows")) !== canonicalJson(withoutChildren(current, "rows"))) return null;
|
|
269
|
+
const sourceRows = wordChildren(sourceElement, "tr");
|
|
270
|
+
const generatedRows = wordChildren(generatedElement, "tr");
|
|
271
|
+
if (sourceRows.length !== current.rows.length || generatedRows.length !== current.rows.length) return null;
|
|
272
|
+
for (const [rowIndex, row] of current.rows.entries()) {
|
|
273
|
+
const beforeRow = baseline.rows[rowIndex];
|
|
274
|
+
const sourceRow = sourceRows[rowIndex];
|
|
275
|
+
const generatedRow = generatedRows[rowIndex];
|
|
276
|
+
if (beforeRow === void 0 || beforeRow.cells.length !== row.cells.length || canonicalJson(withoutChildren(beforeRow, "cells")) !== canonicalJson(withoutChildren(row, "cells")) || !sourceRow || !generatedRow) return null;
|
|
277
|
+
const sourceCells = wordChildren(sourceRow, "tc");
|
|
278
|
+
const generatedCells = wordChildren(generatedRow, "tc");
|
|
279
|
+
if (sourceCells.length !== row.cells.length || generatedCells.length !== row.cells.length) return null;
|
|
280
|
+
for (const [cellIndex, cell] of row.cells.entries()) {
|
|
281
|
+
const beforeCell = beforeRow.cells[cellIndex];
|
|
282
|
+
const sourceCell = sourceCells[cellIndex];
|
|
283
|
+
const generatedCell = generatedCells[cellIndex];
|
|
284
|
+
if (beforeCell === void 0 || canonicalJson(withoutChildren(beforeCell, "content")) !== canonicalJson(withoutChildren(cell, "content")) || !sourceCell || !generatedCell) return null;
|
|
285
|
+
if (!replayCellContent({
|
|
286
|
+
sourceXml,
|
|
287
|
+
serializedXml,
|
|
288
|
+
sourceCell,
|
|
289
|
+
generatedCell,
|
|
290
|
+
before: beforeCell.content,
|
|
291
|
+
after: cell.content,
|
|
292
|
+
sourceRangeStart: sourceRange.start,
|
|
293
|
+
splices,
|
|
294
|
+
generatedRoot
|
|
295
|
+
})) return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "paragraph":
|
|
301
|
+
case "preservedBlock":
|
|
302
|
+
case "bookmarkStart":
|
|
303
|
+
case "bookmarkEnd": return null;
|
|
304
|
+
default: return current;
|
|
305
|
+
}
|
|
306
|
+
return spliceXml(sourceXml.slice(sourceRange.start, sourceRange.end), splices);
|
|
307
|
+
};
|
|
308
|
+
const replayCellContent = ({ sourceXml, serializedXml, sourceCell, generatedCell, before, after, sourceRangeStart, splices, generatedRoot }) => {
|
|
309
|
+
const sourceRange = getXmlSourceRange(sourceCell);
|
|
310
|
+
const generatedRange = getXmlSourceRange(generatedCell);
|
|
311
|
+
if (!sourceRange || !generatedRange) return false;
|
|
312
|
+
const sourceProperties = wordChildren(sourceCell, "tcPr");
|
|
313
|
+
const generatedProperties = wordChildren(generatedCell, "tcPr");
|
|
314
|
+
if (sourceProperties.length > 1 || generatedProperties.length > 1) return false;
|
|
315
|
+
const sourceProperty = sourceProperties.at(0);
|
|
316
|
+
const generatedProperty = generatedProperties.at(0);
|
|
317
|
+
const sourceView = sourceXml.slice(sourceRange.start, sourceRange.end);
|
|
318
|
+
let generatedView = serializedXml.slice(generatedRange.start, generatedRange.end);
|
|
319
|
+
const baselineContent = [...before];
|
|
320
|
+
const currentContent = [...after];
|
|
321
|
+
if (sourceProperty !== void 0) {
|
|
322
|
+
if (getChildElements(sourceCell).at(0) !== sourceProperty || !generatedProperty || getChildElements(generatedCell).at(0) !== generatedProperty) return false;
|
|
323
|
+
const range = getXmlSourceRange(sourceProperty);
|
|
324
|
+
if (!range) return false;
|
|
325
|
+
const propertyXml = generatedFragment({
|
|
326
|
+
xml: sourceXml.slice(range.start, range.end),
|
|
327
|
+
element: sourceProperty,
|
|
328
|
+
sourceNamespace: getNamespaceUri(sourceCell) ?? ""
|
|
329
|
+
});
|
|
330
|
+
if (propertyXml === null) return false;
|
|
331
|
+
const property = {
|
|
332
|
+
type: "preservedBlock",
|
|
333
|
+
xml: propertyXml
|
|
334
|
+
};
|
|
335
|
+
baselineContent.unshift(property);
|
|
336
|
+
currentContent.unshift(property);
|
|
337
|
+
} else if (generatedProperty !== void 0) {
|
|
338
|
+
const range = getXmlSourceRange(generatedProperty);
|
|
339
|
+
if (!range) return false;
|
|
340
|
+
const stripped = spliceXml(generatedView, [{
|
|
341
|
+
start: range.start - generatedRange.start,
|
|
342
|
+
end: range.end - generatedRange.start,
|
|
343
|
+
newXml: ""
|
|
344
|
+
}]);
|
|
345
|
+
if (stripped === null) return false;
|
|
346
|
+
generatedView = stripped;
|
|
347
|
+
}
|
|
348
|
+
const patched = replayBlocks({
|
|
349
|
+
sourceXml: sourceView,
|
|
350
|
+
serializedXml: generatedView,
|
|
351
|
+
baselineContent,
|
|
352
|
+
currentContent
|
|
353
|
+
}, {
|
|
354
|
+
source: sourceCell.namespaceScope,
|
|
355
|
+
generated: generatedCell.namespaceScope,
|
|
356
|
+
generatedRoot
|
|
357
|
+
});
|
|
358
|
+
if (patched === null) return false;
|
|
359
|
+
splices.push({
|
|
360
|
+
start: sourceRange.start - sourceRangeStart,
|
|
361
|
+
end: sourceRange.end - sourceRangeStart,
|
|
362
|
+
newXml: patched
|
|
363
|
+
});
|
|
364
|
+
return true;
|
|
365
|
+
};
|
|
366
|
+
//#endregion
|
|
367
|
+
export { buildStoryBlockReplay };
|
|
@@ -6,6 +6,12 @@ type ParseXmlResult = {
|
|
|
6
6
|
} | {
|
|
7
7
|
status: "unsupported";
|
|
8
8
|
};
|
|
9
|
+
type XmlSourceRange = {
|
|
10
|
+
readonly start: number;
|
|
11
|
+
readonly end: number;
|
|
12
|
+
};
|
|
13
|
+
/** Exact element source ranges, available only from the opt-in source-range reader. */
|
|
14
|
+
declare const getXmlSourceRange: (element: XmlElement) => XmlSourceRange | undefined;
|
|
9
15
|
/**
|
|
10
16
|
* Parse ordinary OOXML into Folio's existing element representation in one
|
|
11
17
|
* pass. Unsupported or malformed constructs return a sentinel so callers can
|
|
@@ -16,11 +22,23 @@ type AttributeValueSpan = {
|
|
|
16
22
|
end: number;
|
|
17
23
|
};
|
|
18
24
|
type OpenTagVisitor = (element: XmlElement, attributeValueSpans: ReadonlyMap<string, AttributeValueSpan>) => ReadonlyMap<string, string> | null;
|
|
25
|
+
type OpenTagScanVisitor = (...args: Parameters<OpenTagVisitor>) => null;
|
|
26
|
+
type ElementScanOptions = {
|
|
27
|
+
element: XmlElement;
|
|
28
|
+
attributeValueSpans: ReadonlyMap<string, AttributeValueSpan>;
|
|
29
|
+
nameEnd: number;
|
|
30
|
+
parent: XmlElement | undefined;
|
|
31
|
+
};
|
|
32
|
+
type ElementScanVisitor = (options: ElementScanOptions) => void;
|
|
19
33
|
/**
|
|
20
34
|
* @param inheritedNamespaceScope the bindings in scope around `xml`, for a
|
|
21
35
|
* fragment captured out of a part whose root declared them.
|
|
22
36
|
*/
|
|
23
37
|
declare const parseStreamingXml: (xml: string, inheritedNamespaceScope?: XmlNamespaceScope) => ParseXmlResult;
|
|
38
|
+
/** Retain source spans for every element without changing the parsed namespace tree. */
|
|
39
|
+
declare const parseStreamingXmlWithSourceRanges: (xml: string, inheritedNamespaceScope?: XmlNamespaceScope) => ParseXmlResult;
|
|
40
|
+
/** Retain the body tree while collecting identities for import repair. */
|
|
41
|
+
declare const parseStreamingXmlWithIdentityVisitor: (xml: string, visitOpenTag: OpenTagScanVisitor) => ParseXmlResult;
|
|
24
42
|
/** Rewrite selected decimal attribute values while preserving every other source byte. */
|
|
25
43
|
declare const rewriteStreamingXmlDecimalAttributes: (xml: string, visitOpenTag: OpenTagVisitor) => {
|
|
26
44
|
status: "rewritten";
|
|
@@ -28,5 +46,17 @@ declare const rewriteStreamingXmlDecimalAttributes: (xml: string, visitOpenTag:
|
|
|
28
46
|
} | {
|
|
29
47
|
status: "unsupported";
|
|
30
48
|
};
|
|
49
|
+
/** Visit identity attributes and namespace bindings without building an XML tree. */
|
|
50
|
+
declare const scanStreamingXmlNumericIdAttributes: (xml: string, visitOpenTag: OpenTagScanVisitor) => {
|
|
51
|
+
status: "unsupported";
|
|
52
|
+
} | {
|
|
53
|
+
status: "scanned";
|
|
54
|
+
};
|
|
55
|
+
/** Visit namespace-resolved start tags and source spans without building a tree. */
|
|
56
|
+
declare const scanStreamingXmlElements: (xml: string, visitElement: ElementScanVisitor) => {
|
|
57
|
+
status: "unsupported";
|
|
58
|
+
} | {
|
|
59
|
+
status: "scanned";
|
|
60
|
+
};
|
|
31
61
|
//#endregion
|
|
32
|
-
export { parseStreamingXml, rewriteStreamingXmlDecimalAttributes };
|
|
62
|
+
export { getXmlSourceRange, parseStreamingXml, parseStreamingXmlWithIdentityVisitor, parseStreamingXmlWithSourceRanges, rewriteStreamingXmlDecimalAttributes, scanStreamingXmlElements, scanStreamingXmlNumericIdAttributes };
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { EMPTY_NAMESPACE_SCOPE, attachXmlNamespaceContext } from "./xmlNamespaceContext.js";
|
|
2
2
|
import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
|
|
3
|
+
import { isOoxmlNumericIdAttributeName } from "@stll/docx-core";
|
|
3
4
|
//#region src/docx/streamingXmlParser.ts
|
|
5
|
+
const sourceRanges = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
/** Exact element source ranges, available only from the opt-in source-range reader. */
|
|
7
|
+
const getXmlSourceRange = (element) => sourceRanges.get(element);
|
|
4
8
|
const BUILT_IN_ENTITIES = {
|
|
5
9
|
amp: "&",
|
|
6
10
|
apos: "'",
|
|
@@ -8,19 +12,36 @@ const BUILT_IN_ENTITIES = {
|
|
|
8
12
|
lt: "<",
|
|
9
13
|
quot: "\""
|
|
10
14
|
};
|
|
11
|
-
const parseStreamingXmlInternal = (
|
|
15
|
+
const parseStreamingXmlInternal = (options) => {
|
|
16
|
+
const { xml, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE } = options;
|
|
17
|
+
const visitOpenTag = options.visitOpenTag;
|
|
18
|
+
let spanMode = "none";
|
|
19
|
+
if (visitOpenTag !== void 0) spanMode = options.mode === "tree" ? "identities" : "all";
|
|
20
|
+
if (options.visitElement !== void 0) spanMode = "all";
|
|
21
|
+
const openTagOptions = {
|
|
22
|
+
xml,
|
|
23
|
+
start: 0,
|
|
24
|
+
close: 0,
|
|
25
|
+
spanMode,
|
|
26
|
+
retainAttribute: options.mode === "attributes" ? options.retainAttribute : void 0
|
|
27
|
+
};
|
|
12
28
|
const root = { elements: [] };
|
|
13
29
|
const stack = [];
|
|
14
30
|
const replacements = [];
|
|
15
31
|
let cursor = 0;
|
|
16
32
|
let mergeAdjacentText = false;
|
|
33
|
+
const acceptText = (text, merge) => {
|
|
34
|
+
if (options.mode === "tree") return appendText(text, stack, merge);
|
|
35
|
+
const decoded = decodeXmlEntities(normalizeLineEndings(text));
|
|
36
|
+
return decoded !== null && (stack.length > 0 || decoded.trim().length === 0);
|
|
37
|
+
};
|
|
17
38
|
while (cursor < xml.length) {
|
|
18
39
|
const open = xml.indexOf("<", cursor);
|
|
19
40
|
if (open === -1) {
|
|
20
|
-
if (!
|
|
41
|
+
if (!acceptText(xml.slice(cursor), mergeAdjacentText)) return { status: "unsupported" };
|
|
21
42
|
break;
|
|
22
43
|
}
|
|
23
|
-
if (open > cursor && !
|
|
44
|
+
if (open > cursor && !acceptText(xml.slice(cursor, open), mergeAdjacentText)) return { status: "unsupported" };
|
|
24
45
|
if (open > cursor) mergeAdjacentText = true;
|
|
25
46
|
if (xml.startsWith("<!--", open)) {
|
|
26
47
|
const close = xml.indexOf("-->", open + 4);
|
|
@@ -30,7 +51,8 @@ const parseStreamingXmlInternal = (xml, visitOpenTag, inheritedNamespaceScope =
|
|
|
30
51
|
}
|
|
31
52
|
if (xml.startsWith("<![CDATA[", open)) {
|
|
32
53
|
const close = xml.indexOf("]]>", open + 9);
|
|
33
|
-
|
|
54
|
+
const text = normalizeLineEndings(xml.slice(open + 9, close));
|
|
55
|
+
if (close === -1 || !(options.mode === "tree" ? appendRawText(text, stack) : stack.length > 0 || text.trim().length === 0)) return { status: "unsupported" };
|
|
34
56
|
cursor = close + 3;
|
|
35
57
|
mergeAdjacentText = false;
|
|
36
58
|
continue;
|
|
@@ -49,15 +71,29 @@ const parseStreamingXmlInternal = (xml, visitOpenTag, inheritedNamespaceScope =
|
|
|
49
71
|
const name = xml.slice(open + 2, close).trim();
|
|
50
72
|
const frame = stack.pop();
|
|
51
73
|
if (!frame || frame.name !== name) return { status: "unsupported" };
|
|
74
|
+
if (frame.start !== void 0) sourceRanges.set(frame.element, {
|
|
75
|
+
start: frame.start,
|
|
76
|
+
end: close + 1
|
|
77
|
+
});
|
|
52
78
|
cursor = close + 1;
|
|
53
79
|
mergeAdjacentText = false;
|
|
54
80
|
continue;
|
|
55
81
|
}
|
|
56
|
-
|
|
82
|
+
openTagOptions.start = open + 1;
|
|
83
|
+
openTagOptions.close = close;
|
|
84
|
+
const parsedTag = parseOpenTag(openTagOptions);
|
|
57
85
|
if (parsedTag.status === "unsupported") return parsedTag;
|
|
58
86
|
const parent = stack.at(-1)?.element;
|
|
59
|
-
|
|
60
|
-
|
|
87
|
+
const scope = parent?.namespaceScope ?? inheritedNamespaceScope;
|
|
88
|
+
if (options.mode === "attributes" && options.retainAttribute !== void 0 && parsedTag.element.attributes === void 0) parsedTag.element.namespaceScope = scope;
|
|
89
|
+
else attachXmlNamespaceContext(parsedTag.element, scope);
|
|
90
|
+
options.visitElement?.({
|
|
91
|
+
element: parsedTag.element,
|
|
92
|
+
attributeValueSpans: parsedTag.attributeValueSpans ?? /* @__PURE__ */ new Map(),
|
|
93
|
+
nameEnd: parsedTag.nameEnd,
|
|
94
|
+
parent
|
|
95
|
+
});
|
|
96
|
+
const rewritten = (options.mode === "tree" ? parsedTag.attributeValueSpans !== void 0 : options.retainAttribute === void 0 || parsedTag.element.attributes !== void 0) ? visitOpenTag?.(parsedTag.element, parsedTag.attributeValueSpans ?? /* @__PURE__ */ new Map()) : null;
|
|
61
97
|
if (rewritten) for (const [attributeName, value] of rewritten) {
|
|
62
98
|
const span = parsedTag.attributeValueSpans?.get(attributeName);
|
|
63
99
|
if (!span) return { status: "unsupported" };
|
|
@@ -66,14 +102,21 @@ const parseStreamingXmlInternal = (xml, visitOpenTag, inheritedNamespaceScope =
|
|
|
66
102
|
value
|
|
67
103
|
});
|
|
68
104
|
}
|
|
69
|
-
appendElement(parent ?? root, parsedTag.element);
|
|
105
|
+
if (options.mode === "tree") appendElement(parent ?? root, parsedTag.element);
|
|
70
106
|
if (!parsedTag.selfClosing) {
|
|
71
107
|
if (stack.length >= FOLIO_XML_RESOURCE_LIMITS.maxDepth) return { status: "unsupported" };
|
|
72
|
-
stack.push({
|
|
108
|
+
stack.push(options.mode === "tree" && options.sourceRanges === "tracked" ? {
|
|
109
|
+
element: parsedTag.element,
|
|
110
|
+
name: parsedTag.name,
|
|
111
|
+
start: open
|
|
112
|
+
} : {
|
|
73
113
|
element: parsedTag.element,
|
|
74
114
|
name: parsedTag.name
|
|
75
115
|
});
|
|
76
|
-
}
|
|
116
|
+
} else if (options.mode === "tree" && options.sourceRanges === "tracked") sourceRanges.set(parsedTag.element, {
|
|
117
|
+
start: open,
|
|
118
|
+
end: close + 1
|
|
119
|
+
});
|
|
77
120
|
cursor = close + 1;
|
|
78
121
|
mergeAdjacentText = false;
|
|
79
122
|
}
|
|
@@ -89,15 +132,48 @@ const parseStreamingXmlInternal = (xml, visitOpenTag, inheritedNamespaceScope =
|
|
|
89
132
|
* fragment captured out of a part whose root declared them.
|
|
90
133
|
*/
|
|
91
134
|
const parseStreamingXml = (xml, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) => {
|
|
92
|
-
const parsed = parseStreamingXmlInternal(
|
|
135
|
+
const parsed = parseStreamingXmlInternal({
|
|
136
|
+
xml,
|
|
137
|
+
mode: "tree",
|
|
138
|
+
inheritedNamespaceScope
|
|
139
|
+
});
|
|
140
|
+
return parsed.status === "parsed" ? {
|
|
141
|
+
status: "parsed",
|
|
142
|
+
value: parsed.value
|
|
143
|
+
} : { status: "unsupported" };
|
|
144
|
+
};
|
|
145
|
+
/** Retain source spans for every element without changing the parsed namespace tree. */
|
|
146
|
+
const parseStreamingXmlWithSourceRanges = (xml, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) => {
|
|
147
|
+
const parsed = parseStreamingXmlInternal({
|
|
148
|
+
xml,
|
|
149
|
+
mode: "tree",
|
|
150
|
+
inheritedNamespaceScope,
|
|
151
|
+
sourceRanges: "tracked"
|
|
152
|
+
});
|
|
93
153
|
return parsed.status === "parsed" ? {
|
|
94
154
|
status: "parsed",
|
|
95
155
|
value: parsed.value
|
|
96
156
|
} : { status: "unsupported" };
|
|
97
157
|
};
|
|
158
|
+
/** Retain the body tree while collecting identities for import repair. */
|
|
159
|
+
const parseStreamingXmlWithIdentityVisitor = (xml, visitOpenTag) => {
|
|
160
|
+
const parsed = parseStreamingXmlInternal({
|
|
161
|
+
xml,
|
|
162
|
+
mode: "tree",
|
|
163
|
+
visitOpenTag
|
|
164
|
+
});
|
|
165
|
+
return parsed.status === "parsed" ? {
|
|
166
|
+
status: "parsed",
|
|
167
|
+
value: parsed.value
|
|
168
|
+
} : parsed;
|
|
169
|
+
};
|
|
98
170
|
/** Rewrite selected decimal attribute values while preserving every other source byte. */
|
|
99
171
|
const rewriteStreamingXmlDecimalAttributes = (xml, visitOpenTag) => {
|
|
100
|
-
const parsed = parseStreamingXmlInternal(
|
|
172
|
+
const parsed = parseStreamingXmlInternal({
|
|
173
|
+
xml,
|
|
174
|
+
mode: "attributes",
|
|
175
|
+
visitOpenTag
|
|
176
|
+
});
|
|
101
177
|
if (parsed.status === "unsupported") return parsed;
|
|
102
178
|
const chunks = [];
|
|
103
179
|
let cursor = 0;
|
|
@@ -112,15 +188,42 @@ const rewriteStreamingXmlDecimalAttributes = (xml, visitOpenTag) => {
|
|
|
112
188
|
value: chunks.join("")
|
|
113
189
|
};
|
|
114
190
|
};
|
|
115
|
-
|
|
191
|
+
/** Visit identity attributes and namespace bindings without building an XML tree. */
|
|
192
|
+
const scanStreamingXmlNumericIdAttributes = (xml, visitOpenTag) => {
|
|
193
|
+
const parsed = parseStreamingXmlInternal({
|
|
194
|
+
xml,
|
|
195
|
+
mode: "attributes",
|
|
196
|
+
visitOpenTag,
|
|
197
|
+
retainAttribute: ({ attributeName, elementName }) => {
|
|
198
|
+
if (attributeName === "xmlns" || attributeName.startsWith("xmlns:")) return true;
|
|
199
|
+
return isOoxmlNumericIdAttributeName({
|
|
200
|
+
elementName,
|
|
201
|
+
attributeName
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
return parsed.status === "parsed" ? { status: "scanned" } : parsed;
|
|
206
|
+
};
|
|
207
|
+
/** Visit namespace-resolved start tags and source spans without building a tree. */
|
|
208
|
+
const scanStreamingXmlElements = (xml, visitElement) => {
|
|
209
|
+
const parsed = parseStreamingXmlInternal({
|
|
210
|
+
xml,
|
|
211
|
+
mode: "attributes",
|
|
212
|
+
visitOpenTag: () => null,
|
|
213
|
+
visitElement
|
|
214
|
+
});
|
|
215
|
+
return parsed.status === "parsed" ? { status: "scanned" } : parsed;
|
|
216
|
+
};
|
|
217
|
+
const parseOpenTag = ({ xml, start, close, spanMode, retainAttribute }) => {
|
|
116
218
|
let cursor = skipWhitespace(xml, start, close);
|
|
117
219
|
const nameStart = cursor;
|
|
118
220
|
cursor = scanName(xml, cursor, close);
|
|
119
221
|
if (cursor === nameStart) return { status: "unsupported" };
|
|
120
222
|
const name = xml.slice(nameStart, cursor);
|
|
223
|
+
const nameEnd = cursor;
|
|
121
224
|
if (isUnsafePropertyName(name)) return { status: "unsupported" };
|
|
122
225
|
let attributes;
|
|
123
|
-
|
|
226
|
+
let attributeValueSpans;
|
|
124
227
|
let selfClosing = false;
|
|
125
228
|
while (cursor < close) {
|
|
126
229
|
cursor = skipWhitespace(xml, cursor, close);
|
|
@@ -146,12 +249,23 @@ const parseOpenTag = (xml, start, close, captureAttributeSpans) => {
|
|
|
146
249
|
if (cursor === -1 || cursor > close) return { status: "unsupported" };
|
|
147
250
|
const decoded = decodeXmlEntities(normalizeLineEndings(xml.slice(valueStart, cursor)));
|
|
148
251
|
if (decoded === null) return { status: "unsupported" };
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
252
|
+
if (retainAttribute === void 0 || retainAttribute({
|
|
253
|
+
attributeName,
|
|
254
|
+
elementName: name
|
|
255
|
+
})) {
|
|
256
|
+
attributes ??= {};
|
|
257
|
+
attributes[attributeName] = decoded;
|
|
258
|
+
if (spanMode === "all" || spanMode === "identities" && isOoxmlNumericIdAttributeName({
|
|
259
|
+
elementName: name,
|
|
260
|
+
attributeName
|
|
261
|
+
})) {
|
|
262
|
+
attributeValueSpans ??= /* @__PURE__ */ new Map();
|
|
263
|
+
attributeValueSpans.set(attributeName, {
|
|
264
|
+
start: valueStart,
|
|
265
|
+
end: cursor
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
155
269
|
cursor += 1;
|
|
156
270
|
}
|
|
157
271
|
const element = {
|
|
@@ -163,6 +277,7 @@ const parseOpenTag = (xml, start, close, captureAttributeSpans) => {
|
|
|
163
277
|
status: "parsed",
|
|
164
278
|
element,
|
|
165
279
|
name,
|
|
280
|
+
nameEnd,
|
|
166
281
|
selfClosing,
|
|
167
282
|
attributeValueSpans
|
|
168
283
|
};
|
|
@@ -289,4 +404,4 @@ const isUnsafePropertyName = (name) => {
|
|
|
289
404
|
}
|
|
290
405
|
};
|
|
291
406
|
//#endregion
|
|
292
|
-
export { parseStreamingXml, rewriteStreamingXmlDecimalAttributes };
|
|
407
|
+
export { getXmlSourceRange, parseStreamingXml, parseStreamingXmlWithIdentityVisitor, parseStreamingXmlWithSourceRanges, rewriteStreamingXmlDecimalAttributes, scanStreamingXmlElements, scanStreamingXmlNumericIdAttributes };
|