@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,12 @@
|
|
|
1
|
+
//#region src/docx/numericIdAllocator.d.ts
|
|
2
|
+
type NumericIdAllocatorOptions = {
|
|
3
|
+
space: string;
|
|
4
|
+
firstId: 0 | 1;
|
|
5
|
+
};
|
|
6
|
+
/** Each OOXML id space owns one instance, retaining loaded and minted ids through rollover. */
|
|
7
|
+
declare const createNumericIdAllocator: ({ space, firstId }: NumericIdAllocatorOptions) => {
|
|
8
|
+
reserve: (ids: Iterable<number>) => void;
|
|
9
|
+
next: () => number;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createNumericIdAllocator };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TaggedError } from "better-result";
|
|
2
|
+
import { MAX_REVISION_ID } from "@stll/docx-core/model";
|
|
3
|
+
//#region src/docx/numericIdAllocator.ts
|
|
4
|
+
var OoxmlIdSpaceExhaustedError = class extends TaggedError("OoxmlIdSpaceExhaustedError") {};
|
|
5
|
+
/** Each OOXML id space owns one instance, retaining loaded and minted ids through rollover. */
|
|
6
|
+
const createNumericIdAllocator = ({ space, firstId }) => {
|
|
7
|
+
const reserved = /* @__PURE__ */ new Set();
|
|
8
|
+
let nextId = firstId;
|
|
9
|
+
return {
|
|
10
|
+
reserve: (ids) => {
|
|
11
|
+
let max = -1;
|
|
12
|
+
for (const id of ids) {
|
|
13
|
+
if (!Number.isInteger(id) || id < firstId || id > MAX_REVISION_ID) continue;
|
|
14
|
+
reserved.add(id);
|
|
15
|
+
max = Math.max(max, id);
|
|
16
|
+
}
|
|
17
|
+
if (max >= nextId) nextId = max === MAX_REVISION_ID ? firstId : max + 1;
|
|
18
|
+
},
|
|
19
|
+
next: () => {
|
|
20
|
+
if (reserved.size >= MAX_REVISION_ID - firstId + 1) throw new OoxmlIdSpaceExhaustedError({
|
|
21
|
+
message: `OOXML ${space} id space is exhausted`,
|
|
22
|
+
space
|
|
23
|
+
});
|
|
24
|
+
while (reserved.has(nextId)) nextId = nextId === MAX_REVISION_ID ? firstId : nextId + 1;
|
|
25
|
+
const id = nextId;
|
|
26
|
+
reserved.add(id);
|
|
27
|
+
nextId = id === MAX_REVISION_ID ? firstId : id + 1;
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createNumericIdAllocator };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RawDocxContent } from "./unzip.js";
|
|
2
|
+
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/numericIdNormalization.d.ts
|
|
4
|
+
type NumericIdNormalizationOptions = {
|
|
5
|
+
onParsedDocument?: (document: XmlElement) => void;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Repair out-of-range imported integers before any model or opaque XML is captured.
|
|
9
|
+
* Reserve the complete package first, then remap each old value once per space.
|
|
10
|
+
* Source splices preserve unrelated XML bytes; a valid package takes the fast path.
|
|
11
|
+
*/
|
|
12
|
+
declare const normalizeImportedNumericIds: (parts: ReadonlyMap<string, string>, options?: NumericIdNormalizationOptions) => ReadonlyMap<string, string>;
|
|
13
|
+
/** Parser boundary: all captures and selective-save bytes see the same identities. */
|
|
14
|
+
declare const normalizeRawDocxNumericIds: (raw: RawDocxContent) => Promise<XmlElement | undefined>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { normalizeImportedNumericIds, normalizeRawDocxNumericIds };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { REVISION_ELEMENT_NAMES } from "./revisionIdNormalization.js";
|
|
2
|
+
import { parseStreamingXmlWithIdentityVisitor, scanStreamingXmlNumericIdAttributes } from "./streamingXmlParser.js";
|
|
3
|
+
import { replaceRawDocxXmlParts } from "./unzip.js";
|
|
4
|
+
import { getLocalName, getNamespaceUri, resolveAttributeNamespaceUri } from "./xmlParser.js";
|
|
5
|
+
import { XmlResourceLimitError } from "./xmlResourceLimits.js";
|
|
6
|
+
import { panic } from "better-result";
|
|
7
|
+
import { assertValidOoxmlNumericId, isOoxmlNumericIdAttributeName, isValidOoxmlNumericId, mayContainInvalidOoxmlNumericIds, mayContainOoxmlNumericIds, ooxmlNumericIdDomain } from "@stll/docx-core";
|
|
8
|
+
//#region src/docx/numericIdNormalization.ts
|
|
9
|
+
const identityKey = (value) => {
|
|
10
|
+
const spelling = String(value).trim();
|
|
11
|
+
if (!/^[+-]?\d+$/u.test(spelling)) return spelling;
|
|
12
|
+
const digits = spelling.replace(/^[+-]?0*/u, "");
|
|
13
|
+
return digits === "" ? "0" : `${spelling.startsWith("-") ? "-" : ""}${digits}`;
|
|
14
|
+
};
|
|
15
|
+
const identitySpace = ({ elementName, attributeName, domain }) => {
|
|
16
|
+
if (domain === "unsigned32") return "drawing";
|
|
17
|
+
const element = getLocalName(elementName);
|
|
18
|
+
const attribute = getLocalName(attributeName);
|
|
19
|
+
switch (attribute === "val" ? element : attribute) {
|
|
20
|
+
case "numId": return "numbering-instance";
|
|
21
|
+
case "abstractNumId": return "numbering-abstract";
|
|
22
|
+
case "numPicBulletId":
|
|
23
|
+
case "lvlPicBulletId": return "numbering-picture";
|
|
24
|
+
default: break;
|
|
25
|
+
}
|
|
26
|
+
switch (element) {
|
|
27
|
+
case "footnote":
|
|
28
|
+
case "footnoteReference": return "footnote";
|
|
29
|
+
case "endnote":
|
|
30
|
+
case "endnoteReference": return "endnote";
|
|
31
|
+
case "id": return "content-control";
|
|
32
|
+
default: return `annotation:${REVISION_ELEMENT_NAMES.has(element) ? "revision" : element.replace(/(?:Range)?(?:Start|End)$/u, "").replace(/^comment.*$/u, "comment")}`;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const identifiedAttributes = (element) => {
|
|
36
|
+
const attributes = [];
|
|
37
|
+
for (const [name, value] of Object.entries(element.attributes ?? {})) {
|
|
38
|
+
if (!isOoxmlNumericIdAttributeName({
|
|
39
|
+
elementName: element.name ?? "",
|
|
40
|
+
attributeName: name
|
|
41
|
+
})) continue;
|
|
42
|
+
if (value === void 0 || !/^[+-]?\d+$/u.test(String(value).trim())) continue;
|
|
43
|
+
const domain = ooxmlNumericIdDomain({
|
|
44
|
+
elementName: element.name ?? "",
|
|
45
|
+
elementNamespace: getNamespaceUri(element),
|
|
46
|
+
attributeName: name,
|
|
47
|
+
attributeNamespace: resolveAttributeNamespaceUri(element, name)
|
|
48
|
+
});
|
|
49
|
+
if (domain === void 0) continue;
|
|
50
|
+
attributes.push({
|
|
51
|
+
name,
|
|
52
|
+
value,
|
|
53
|
+
domain,
|
|
54
|
+
space: identitySpace({
|
|
55
|
+
elementName: element.name ?? "",
|
|
56
|
+
attributeName: name,
|
|
57
|
+
domain
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return attributes;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Repair out-of-range imported integers before any model or opaque XML is captured.
|
|
65
|
+
* Reserve the complete package first, then remap each old value once per space.
|
|
66
|
+
* Source splices preserve unrelated XML bytes; a valid package takes the fast path.
|
|
67
|
+
*/
|
|
68
|
+
const normalizeImportedNumericIds = (parts, options = {}) => {
|
|
69
|
+
if (![...parts.values()].some((xml) => mayContainInvalidOoxmlNumericIds(xml, "range"))) return parts;
|
|
70
|
+
const normalized = new Map(parts);
|
|
71
|
+
const candidates = [...parts].filter(([, xml]) => mayContainOoxmlNumericIds(xml)).toSorted(([left], [right]) => left.localeCompare(right));
|
|
72
|
+
const spaces = /* @__PURE__ */ new Map();
|
|
73
|
+
const reservedPools = /* @__PURE__ */ new Map();
|
|
74
|
+
const changedSpans = /* @__PURE__ */ new Map();
|
|
75
|
+
let parsedDocument;
|
|
76
|
+
const scan = ({ path, xml, visitor }) => {
|
|
77
|
+
const scanned = path.toLowerCase() === "word/document.xml" && options.onParsedDocument !== void 0 ? parseStreamingXmlWithIdentityVisitor(xml, visitor) : scanStreamingXmlNumericIdAttributes(xml, visitor);
|
|
78
|
+
if (scanned.status === "unsupported") throw new XmlResourceLimitError({
|
|
79
|
+
message: `Numeric-id normalization could not safely scan ${path}`,
|
|
80
|
+
limit: "syntax",
|
|
81
|
+
observed: 0,
|
|
82
|
+
allowed: 0
|
|
83
|
+
});
|
|
84
|
+
if (scanned.status === "parsed") parsedDocument = scanned.value;
|
|
85
|
+
};
|
|
86
|
+
for (const [path, xml] of candidates) scan({
|
|
87
|
+
path,
|
|
88
|
+
xml,
|
|
89
|
+
visitor: (element, attributeValueSpans) => {
|
|
90
|
+
for (const attribute of identifiedAttributes(element)) {
|
|
91
|
+
let space = spaces.get(attribute.space);
|
|
92
|
+
if (space === void 0) {
|
|
93
|
+
const poolName = attribute.space.startsWith("annotation:") ? "annotation" : attribute.space;
|
|
94
|
+
let reserved = reservedPools.get(poolName);
|
|
95
|
+
if (reserved === void 0) {
|
|
96
|
+
reserved = /* @__PURE__ */ new Set();
|
|
97
|
+
reservedPools.set(poolName, reserved);
|
|
98
|
+
}
|
|
99
|
+
space = {
|
|
100
|
+
reserved,
|
|
101
|
+
replacements: /* @__PURE__ */ new Map(),
|
|
102
|
+
next: 1
|
|
103
|
+
};
|
|
104
|
+
spaces.set(attribute.space, space);
|
|
105
|
+
}
|
|
106
|
+
if (isValidOoxmlNumericId(attribute.value, attribute.domain)) {
|
|
107
|
+
space.reserved.add(Number(attribute.value));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const key = identityKey(attribute.value);
|
|
111
|
+
space.replacements.set(key, "");
|
|
112
|
+
const span = attributeValueSpans.get(attribute.name);
|
|
113
|
+
if (span === void 0) panic("Missing imported numeric identity source span");
|
|
114
|
+
let spans = changedSpans.get(path);
|
|
115
|
+
if (spans === void 0) {
|
|
116
|
+
spans = [];
|
|
117
|
+
changedSpans.set(path, spans);
|
|
118
|
+
}
|
|
119
|
+
spans.push({
|
|
120
|
+
start: span.start,
|
|
121
|
+
end: span.end,
|
|
122
|
+
space: attribute.space,
|
|
123
|
+
key,
|
|
124
|
+
element,
|
|
125
|
+
attributeName: attribute.name
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
for (const space of spaces.values()) for (const key of space.replacements.keys()) {
|
|
132
|
+
while (space.reserved.has(space.next)) space.next += 1;
|
|
133
|
+
assertValidOoxmlNumericId({
|
|
134
|
+
value: space.next,
|
|
135
|
+
partPath: "import",
|
|
136
|
+
elementName: "allocated identity",
|
|
137
|
+
attributeName: "id"
|
|
138
|
+
});
|
|
139
|
+
const replacement = String(space.next);
|
|
140
|
+
space.replacements.set(key, replacement);
|
|
141
|
+
space.reserved.add(space.next);
|
|
142
|
+
space.next += 1;
|
|
143
|
+
}
|
|
144
|
+
for (const [path, spans] of changedSpans) {
|
|
145
|
+
const xml = parts.get(path);
|
|
146
|
+
if (xml === void 0) panic(`Missing imported numeric identity part: ${path}`);
|
|
147
|
+
const chunks = [];
|
|
148
|
+
let cursor = 0;
|
|
149
|
+
for (const span of spans.toSorted((left, right) => left.start - right.start)) {
|
|
150
|
+
const replacement = spaces.get(span.space)?.replacements.get(span.key);
|
|
151
|
+
if (replacement === void 0 || replacement === "") panic("Missing imported numeric identity replacement");
|
|
152
|
+
if (span.element.attributes === void 0) panic("Missing imported numeric identity attributes");
|
|
153
|
+
span.element.attributes[span.attributeName] = replacement;
|
|
154
|
+
chunks.push(xml.slice(cursor, span.start), replacement);
|
|
155
|
+
cursor = span.end;
|
|
156
|
+
}
|
|
157
|
+
chunks.push(xml.slice(cursor));
|
|
158
|
+
normalized.set(path, chunks.join(""));
|
|
159
|
+
}
|
|
160
|
+
if (parsedDocument !== void 0) options.onParsedDocument?.(parsedDocument);
|
|
161
|
+
return normalized;
|
|
162
|
+
};
|
|
163
|
+
/** Parser boundary: all captures and selective-save bytes see the same identities. */
|
|
164
|
+
const normalizeRawDocxNumericIds = async (raw) => {
|
|
165
|
+
let documentTree;
|
|
166
|
+
const normalized = normalizeImportedNumericIds(raw.allXml, { onParsedDocument: (tree) => {
|
|
167
|
+
documentTree = tree;
|
|
168
|
+
} });
|
|
169
|
+
if (normalized === raw.allXml) return documentTree;
|
|
170
|
+
await replaceRawDocxXmlParts(raw, normalized);
|
|
171
|
+
return documentTree;
|
|
172
|
+
};
|
|
173
|
+
//#endregion
|
|
174
|
+
export { normalizeImportedNumericIds, normalizeRawDocxNumericIds };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import JSZip from "jszip";
|
|
2
2
|
//#region src/docx/packageParts.d.ts
|
|
3
|
+
/** Resolve an internal URI reference once, before comparing it with ZIP entries. */
|
|
4
|
+
declare const resolvePackageRelationshipTarget: (target: string, relsPath: string) => string | undefined;
|
|
3
5
|
/**
|
|
4
6
|
* Whether an entry path would escape the package when a host writes the archive
|
|
5
7
|
* to disk: absolute, backslash-separated, or climbing out through `..`. This is
|
|
@@ -76,4 +78,4 @@ type PackageIntegrityReport = {
|
|
|
76
78
|
*/
|
|
77
79
|
declare const checkPackageIntegrity: (zip: JSZip) => Promise<PackageIntegrityReport>;
|
|
78
80
|
//#endregion
|
|
79
|
-
export { PackageIntegrityReport, PackageReferenceRepair, checkPackageIntegrity, isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries };
|
|
81
|
+
export { PackageIntegrityReport, PackageReferenceRepair, checkPackageIntegrity, isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries, resolvePackageRelationshipTarget };
|
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Result } from "better-result";
|
|
2
2
|
//#region src/docx/packageParts.ts
|
|
3
|
+
/**
|
|
4
|
+
* What a repack carries out of the source package, and the guarantee that
|
|
5
|
+
* nothing in the output points at a part the output does not hold.
|
|
6
|
+
*
|
|
7
|
+
* A `.docx` is an OPC package: `[Content_Types].xml` declares every part in it
|
|
8
|
+
* and the `.rels` parts wire them together. Folio models a handful of those
|
|
9
|
+
* parts (the document body, headers, footers, notes, numbering, styles,
|
|
10
|
+
* comments) and rewrites them on save. Everything else — embedded objects,
|
|
11
|
+
* media, custom XML, fonts, charts, macro projects, parts folio has never heard
|
|
12
|
+
* of — belongs to the author, and a repack hands it back byte for byte.
|
|
13
|
+
*
|
|
14
|
+
* Folio never executes document content and never removes it. The document is
|
|
15
|
+
* the user's; the only thing a save refuses is a package path that would let
|
|
16
|
+
* the archive escape its own directory when a host writes it out, which is a
|
|
17
|
+
* property of the package structure rather than of the content. A macro-enabled
|
|
18
|
+
* document therefore stays macro-enabled, main-part content type included:
|
|
19
|
+
* silently handing back a `.docm` stripped of its project is data loss dressed
|
|
20
|
+
* up as safety.
|
|
21
|
+
*
|
|
22
|
+
* A refused path loses its relationships and its content-type override with it.
|
|
23
|
+
* That is the invariant this module exists for: Word and LibreOffice both
|
|
24
|
+
* refuse a package whose `document.xml.rels` points at nothing, so a repack
|
|
25
|
+
* that removes a part without removing its references produces a file that
|
|
26
|
+
* does not open.
|
|
27
|
+
*/
|
|
3
28
|
const CONTENT_TYPE_ELEMENT = /<(?:Default|Override)\b[^>]*?\/?>/giu;
|
|
4
29
|
const PART_NAME_ATTRIBUTE = /\bPartName\s*=\s*(?<quote>["'])(?<value>[^"']*)\k<quote>/u;
|
|
5
30
|
const RELATIONSHIP_ELEMENT = /<Relationship\b[^>]*?(?:\/>|>\s*<\/Relationship>)/giu;
|
|
@@ -24,12 +49,38 @@ const owningPartPath = (relsPath) => {
|
|
|
24
49
|
};
|
|
25
50
|
/** Normalize a package path for comparison: lower case, no leading slash. */
|
|
26
51
|
const normalizePartPath = (path) => (path.startsWith("/") ? path.slice(1) : path).toLowerCase();
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
/** Decode URI escapes as UTF-8; malformed references cannot name a part. */
|
|
53
|
+
const decodePartName = (name) => {
|
|
54
|
+
const decoded = Result.try({
|
|
55
|
+
try: () => decodeURIComponent(name),
|
|
56
|
+
catch: () => void 0
|
|
57
|
+
});
|
|
58
|
+
return decoded.isOk() ? decoded.value : void 0;
|
|
59
|
+
};
|
|
60
|
+
/** Resolve an internal URI reference once, before comparing it with ZIP entries. */
|
|
61
|
+
const resolvePackageRelationshipTarget = (target, relsPath) => {
|
|
62
|
+
const fragment = target.indexOf("#");
|
|
63
|
+
if (fragment === 0) {
|
|
64
|
+
const owner = owningPartPath(relsPath);
|
|
65
|
+
return owner === void 0 ? void 0 : normalizePartPath(owner);
|
|
66
|
+
}
|
|
67
|
+
const reference = fragment > 0 ? target.slice(0, fragment) : target;
|
|
68
|
+
if (!reference || reference.startsWith("//") || /[\\?:]/u.test(reference)) return void 0;
|
|
69
|
+
const owner = owningPartPath(relsPath);
|
|
70
|
+
const segments = reference.startsWith("/") || owner === void 0 ? [] : owner.split("/").slice(0, -1);
|
|
71
|
+
for (const encoded of reference.split("/")) {
|
|
72
|
+
const segment = decodePartName(encoded);
|
|
73
|
+
if (segment === void 0 || /[/\\]/u.test(segment) || segment.includes(String.fromCharCode(0))) return void 0;
|
|
74
|
+
if (segment === "" || segment === ".") continue;
|
|
75
|
+
if (segment === "..") {
|
|
76
|
+
if (segments.pop() === void 0) return void 0;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
segments.push(segment);
|
|
80
|
+
}
|
|
81
|
+
const path = normalizePartPath(segments.join("/"));
|
|
82
|
+
return isUnsafePackagePath(path) ? void 0 : path;
|
|
83
|
+
};
|
|
33
84
|
/**
|
|
34
85
|
* Whether an entry path would escape the package when a host writes the archive
|
|
35
86
|
* to disk: absolute, backslash-separated, or climbing out through `..`. This is
|
|
@@ -53,15 +104,13 @@ const presentPartPaths = (zip) => {
|
|
|
53
104
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir) present.add(normalizePartPath(path));
|
|
54
105
|
return present;
|
|
55
106
|
};
|
|
56
|
-
const isPresent = (present, partPath) => present.has(normalizePartPath(partPath)) || present.has(normalizePartPath(decodePartName(partPath)));
|
|
107
|
+
const isPresent = (present, partPath) => present.has(normalizePartPath(partPath)) || present.has(normalizePartPath(decodePartName(partPath) ?? ""));
|
|
57
108
|
/** The part a relationship names, or null when it points outside the package. */
|
|
58
109
|
const relationshipTarget = (element, relsPath) => {
|
|
59
110
|
if (TARGET_MODE_ATTRIBUTE.exec(element)?.groups?.["value"] === "External") return null;
|
|
60
111
|
const target = TARGET_ATTRIBUTE.exec(element)?.groups?.["value"];
|
|
61
112
|
if (target === void 0) return null;
|
|
62
|
-
|
|
63
|
-
if (fragment === 0) return owningPartPath(relsPath) ?? "";
|
|
64
|
-
return resolveRelativePath(relsPath, fragment > 0 ? target.slice(0, fragment) : target);
|
|
113
|
+
return resolvePackageRelationshipTarget(target, relsPath) ?? "";
|
|
65
114
|
};
|
|
66
115
|
/**
|
|
67
116
|
* Every `.rels` part with its text. Read one at a time rather than through
|
|
@@ -107,8 +156,8 @@ const reconcilePackageReferences = async (zip, compressionLevel) => {
|
|
|
107
156
|
let dropped = false;
|
|
108
157
|
const pruned = xml.replaceAll(RELATIONSHIP_ELEMENT, (element) => {
|
|
109
158
|
const target = relationshipTarget(element, path);
|
|
110
|
-
if (target === null ||
|
|
111
|
-
repair.danglingRelationships.push(target);
|
|
159
|
+
if (target === null || present.has(target)) return element;
|
|
160
|
+
repair.danglingRelationships.push(target || TARGET_ATTRIBUTE.exec(element)?.groups?.["value"] || "");
|
|
112
161
|
dropped = true;
|
|
113
162
|
const id = RELATIONSHIP_ID_ATTRIBUTE.exec(element)?.groups?.["value"];
|
|
114
163
|
const owningPart = owningPartPath(path);
|
|
@@ -154,7 +203,7 @@ const reconcilePackageReferences = async (zip, compressionLevel) => {
|
|
|
154
203
|
const prunedContentTypes = (await contentTypes.async("text")).replaceAll(CONTENT_TYPE_ELEMENT, (element) => {
|
|
155
204
|
const partName = PART_NAME_ATTRIBUTE.exec(element)?.groups?.["value"];
|
|
156
205
|
if (partName === void 0 || isPresent(present, partName)) return element;
|
|
157
|
-
repair.danglingOverrides.push(normalizePartPath(decodePartName(partName)));
|
|
206
|
+
repair.danglingOverrides.push(normalizePartPath(decodePartName(partName) ?? partName));
|
|
158
207
|
droppedOverride = true;
|
|
159
208
|
return "";
|
|
160
209
|
});
|
|
@@ -195,7 +244,7 @@ const checkPackageIntegrity = async (zip) => {
|
|
|
195
244
|
const id = RELATIONSHIP_ID_ATTRIBUTE.exec(element)?.groups?.["value"];
|
|
196
245
|
if (id !== void 0) ids.add(id);
|
|
197
246
|
const target = relationshipTarget(element, path);
|
|
198
|
-
if (target !== null && !
|
|
247
|
+
if (target !== null && !present.has(target)) report.danglingRelationshipTargets.push(`${path}|${target || TARGET_ATTRIBUTE.exec(element)?.groups?.["value"] || ""}`);
|
|
199
248
|
}
|
|
200
249
|
if (owningPart !== void 0) idsByOwningPart.set(owningPart, ids);
|
|
201
250
|
}
|
|
@@ -211,4 +260,4 @@ const checkPackageIntegrity = async (zip) => {
|
|
|
211
260
|
return report;
|
|
212
261
|
};
|
|
213
262
|
//#endregion
|
|
214
|
-
export { checkPackageIntegrity, isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries };
|
|
263
|
+
export { checkPackageIntegrity, isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries, resolvePackageRelationshipTarget };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/paragraphMarkPropertyPresence.d.ts
|
|
4
|
+
declare const EMPTY_MARK_PROPERTIES_ATTRIBUTE = "emptyMarkProperties";
|
|
5
|
+
/** A paragraph-owned rPr carrier cannot distinguish absent and explicitly empty formatting. */
|
|
6
|
+
declare const serializeEmptyMarkProperties: (paragraph: document_d_exports.Paragraph) => string | undefined;
|
|
7
|
+
declare const restoreEmptyMarkProperties: (paragraph: document_d_exports.Paragraph, element: XmlElement) => void;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { EMPTY_MARK_PROPERTIES_ATTRIBUTE, restoreEmptyMarkProperties, serializeEmptyMarkProperties };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { OOXML_NAMESPACES } from "./serializer/partNamespaces.js";
|
|
2
|
+
import { getAttributeByNamespaceUri } from "./xmlParser.js";
|
|
3
|
+
import { EMPTY_PROPERTY_SETS } from "@stll/docx-core/ops";
|
|
4
|
+
//#region src/docx/paragraphMarkPropertyPresence.ts
|
|
5
|
+
const EMPTY_MARK_PROPERTIES_ATTRIBUTE = "emptyMarkProperties";
|
|
6
|
+
const REVIEW_HISTORY_NAMESPACES = /* @__PURE__ */ new Set([OOXML_NAMESPACES.folio.uri]);
|
|
7
|
+
/** A paragraph-owned rPr carrier cannot distinguish absent and explicitly empty formatting. */
|
|
8
|
+
const serializeEmptyMarkProperties = (paragraph) => {
|
|
9
|
+
const properties = paragraph.formatting?.runProperties;
|
|
10
|
+
if (!(paragraph.pPrMark !== void 0 || paragraph.formatting?.runInWithNext !== void 0) || properties === void 0 || Object.keys(properties).length !== 0) return void 0;
|
|
11
|
+
return `folio:${EMPTY_MARK_PROPERTIES_ATTRIBUTE}="${EMPTY_PROPERTY_SETS.KEEP}"`;
|
|
12
|
+
};
|
|
13
|
+
const restoreEmptyMarkProperties = (paragraph, element) => {
|
|
14
|
+
if (paragraph.pPrMark === void 0 && paragraph.formatting?.runInWithNext === void 0 || getAttributeByNamespaceUri(element, REVIEW_HISTORY_NAMESPACES, "emptyMarkProperties") !== EMPTY_PROPERTY_SETS.KEEP || paragraph.formatting?.runProperties !== void 0) return;
|
|
15
|
+
paragraph.formatting = {
|
|
16
|
+
...paragraph.formatting,
|
|
17
|
+
runProperties: {}
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { EMPTY_MARK_PROPERTIES_ATTRIBUTE, restoreEmptyMarkProperties, serializeEmptyMarkProperties };
|