@stll/folio-core 0.64.1 → 0.66.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 +31 -9
- package/dist/ai-edits/headless.js +3 -3
- package/dist/compare/reproducible-package.d.ts +5 -0
- package/dist/compare/reproducible-package.js +10 -23
- package/dist/docx/archiveInflation.d.ts +8 -103
- package/dist/docx/archiveInflation.js +8 -145
- package/dist/docx/attributeRemainder.js +1 -1
- package/dist/docx/canonicalStoryRepack.js +2 -2
- package/dist/docx/documentSectionFacts.js +1 -1
- package/dist/docx/ensureParaIds.js +9 -24
- package/dist/docx/importedIdCollisions.js +1 -1
- package/dist/docx/mathToMathml.js +1 -1
- package/dist/docx/metadataPrivacy.js +38 -48
- package/dist/docx/numericIdNormalization.js +2 -2
- package/dist/docx/packageParts.d.ts +4 -4
- package/dist/docx/packageParts.js +17 -24
- package/dist/docx/parser.js +5 -6
- package/dist/docx/removeHeaderFooterParts.d.ts +2 -2
- package/dist/docx/removeHeaderFooterParts.js +19 -36
- package/dist/docx/revisionIdNormalization.js +2 -2
- package/dist/docx/rezip.d.ts +7 -7
- package/dist/docx/rezip.js +144 -532
- package/dist/docx/sdtProperties.js +1 -2
- package/dist/docx/selectedXmlIdentityScan.d.ts +1 -1
- package/dist/docx/selectedXmlIdentityScan.js +1 -1
- package/dist/docx/selectiveSave.js +37 -41
- package/dist/docx/selectiveSaveFlags.d.ts +1 -1
- package/dist/docx/selectiveSaveTripwire.js +5 -11
- package/dist/docx/selectiveXmlPatch.js +2 -2
- package/dist/docx/server/applyDocxXmlPatchProposal.js +20 -41
- package/dist/docx/server/boundedArchive.d.ts +2 -3
- package/dist/docx/server/boundedArchive.js +78 -99
- package/dist/docx/server/inspectDocxPackage.d.ts +1 -1
- package/dist/docx/server/inspectDocxPackage.js +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -1
- package/dist/docx/shapeAlternateContent.js +1 -1
- package/dist/docx/storyBlockReplay.js +1 -1
- package/dist/docx/unzip.d.ts +2 -3
- package/dist/docx/unzip.js +127 -155
- package/dist/docx/verbatimCapture.js +1 -1
- package/dist/docx/xmlParser.d.ts +5 -36
- package/dist/docx/xmlParser.js +14 -78
- package/dist/docx/xmlSafety.js +5 -5
- package/dist/layout-painter/renderPage.js +4 -2
- package/dist/markdown/countParagraphList.d.ts +18 -0
- package/dist/markdown/countParagraphList.js +90 -0
- package/dist/markdown/renderParagraph.js +2 -62
- package/dist/markdown/renderRuns.js +4 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +5 -3
- package/dist/prosemirror/conversion/toProseDoc.js +4 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +6 -0
- package/dist/prosemirror/replacedAnnotations.js +30 -8
- package/dist/prosemirror/schema/nodes.d.ts +7 -0
- package/dist/prosemirror/textBoxAnchorAttrs.js +9 -1
- package/package.json +3 -5
- package/dist/docx/streamingXmlParser.d.ts +0 -60
- package/dist/docx/streamingXmlParser.js +0 -406
- package/dist/docx/xmlNamespaceContext.d.ts +0 -6
- package/dist/docx/xmlNamespaceContext.js +0 -53
- package/dist/docx/xmlResourceLimits.d.ts +0 -107
- package/dist/docx/xmlResourceLimits.js +0 -194
package/dist/ai-edits/apply.js
CHANGED
|
@@ -26,7 +26,7 @@ import { getDocumentNumbering, getDocumentNumberingInstanceIds } from "../prosem
|
|
|
26
26
|
import { getDocumentStyleDefinitions, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
|
|
27
27
|
import { claimRevisionIds, nextRevisionId, nextRevisionIdRange, reserveRevisionIds, seedRevisionIdsFromDoc } from "../prosemirror/plugins/revisionIds.js";
|
|
28
28
|
import { rebaseParagraphRuns } from "../prosemirror/rebaseParagraphRuns.js";
|
|
29
|
-
import { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentsAcrossBlockBoundary, commentsForTrackedInsertion, hasReplacedAnnotations, inheritedReplacementMarks, joinCommentStretches, placeTrackedInsertion, surveyReplacedAnnotations } from "../prosemirror/replacedAnnotations.js";
|
|
29
|
+
import { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentsAcrossBlockBoundary, commentsForTrackedInsertion, hasReplacedAnnotations, inheritedReplacementMarks, joinCommentStretches, placeTrackedInsertion, surveyAcceptedReplacedAnnotations, surveyReplacedAnnotations } from "../prosemirror/replacedAnnotations.js";
|
|
30
30
|
import { applyMarksToRunFormattingRepresentation, runFormattingInlineControlNodeName, selectRunFormattingCarrierRepresentations } from "../prosemirror/runFormattingInlineCarriers.js";
|
|
31
31
|
import { RUN_FORMATTING_MARK_NAMES } from "../prosemirror/runFormattingMarkNames.js";
|
|
32
32
|
import { readAuthoredRunFormatting, readAuthoredRunPropertySnapshot, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
|
|
@@ -1395,6 +1395,17 @@ const holdsOnlyInsertedContent = (paragraph, revisions) => {
|
|
|
1395
1395
|
});
|
|
1396
1396
|
return whollyInserted;
|
|
1397
1397
|
};
|
|
1398
|
+
/** Whether every inline of `paragraph` is an ordinary insertion one of `revisionIds` made. */
|
|
1399
|
+
const holdsOnlyContentInsertedBy = (paragraph, revisionIds) => {
|
|
1400
|
+
let whollyInserted = true;
|
|
1401
|
+
paragraph.descendants((node) => {
|
|
1402
|
+
if (!whollyInserted) return false;
|
|
1403
|
+
if (!node.isInline || isZeroWidthAnchor(node)) return true;
|
|
1404
|
+
whollyInserted = node.marks.some((mark) => mark.type.name === "insertion" && mark.attrs["moveKind"] !== "moveTo" && revisionIds.has(mark.attrs["revisionId"]));
|
|
1405
|
+
return false;
|
|
1406
|
+
});
|
|
1407
|
+
return whollyInserted;
|
|
1408
|
+
};
|
|
1398
1409
|
/**
|
|
1399
1410
|
* Remove the paragraph between `from` and `to`, its content with it but not
|
|
1400
1411
|
* the range boundaries it holds: those stay where it was, as rejecting a
|
|
@@ -2072,7 +2083,7 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
2072
2083
|
const trackedInsertion = insertedRevisionId !== null && batchRevisionIds.has(insertedRevisionId);
|
|
2073
2084
|
const suggestedInsertion = isSuggestedParagraphInsert(node);
|
|
2074
2085
|
const deletedBreakRevisionId = addedOrDeletedBreakRevisionId(deletedMark);
|
|
2075
|
-
if (isPlainDeletedPPrMark(deletedMark) && deletedBreakRevisionId !== null && !batchRevisionIds.has(deletedBreakRevisionId) && !holdsOnlyDeletedContent(previous.node) && (trackedInsertion || suggestedInsertion
|
|
2086
|
+
if (isPlainDeletedPPrMark(deletedMark) && deletedBreakRevisionId !== null && !batchRevisionIds.has(deletedBreakRevisionId) && !holdsOnlyDeletedContent(previous.node) && (trackedInsertion && holdsOnlyContentInsertedBy(node, batchRevisionIds) || suggestedInsertion && holdsOnlyInsertedContent(node, "ordinary"))) {
|
|
2076
2087
|
supersededDeletedBreaks.push({
|
|
2077
2088
|
preceding: previous.position,
|
|
2078
2089
|
inserted: position,
|
|
@@ -2094,6 +2105,7 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
2094
2105
|
});
|
|
2095
2106
|
};
|
|
2096
2107
|
collectSupersededDeletedBreaks(tr.doc, 0);
|
|
2108
|
+
const passedThrough = new Set(supersededDeletedBreaks.map(({ preceding }) => preceding));
|
|
2097
2109
|
for (const { preceding, inserted, deletedMark, precedingMark, precedingFormatting } of supersededDeletedBreaks) {
|
|
2098
2110
|
const insertionRevisionId = addedBreakRevisionId(precedingMark);
|
|
2099
2111
|
if (insertionRevisionId !== null) {
|
|
@@ -2118,6 +2130,7 @@ const withRotatedAddedFinalBreaks = ({ tr, batchRevisionIds, revisionSeed, autho
|
|
|
2118
2130
|
tr.setNodeAttribute(preceding, "pPrMark", precedingMark);
|
|
2119
2131
|
tr.setNodeAttribute(inserted, "pPrMark", deletedMark);
|
|
2120
2132
|
if (insertionRevisionId === null) continue;
|
|
2133
|
+
if (passedThrough.has(inserted)) continue;
|
|
2121
2134
|
const source = tr.doc.nodeAt(inserted);
|
|
2122
2135
|
const survivor = paragraphLeftAfter({
|
|
2123
2136
|
doc: tr.doc,
|
|
@@ -2903,7 +2916,12 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
|
|
|
2903
2916
|
}
|
|
2904
2917
|
const nodes = [];
|
|
2905
2918
|
for (const group of nodeGroups.toReversed()) for (const node of group) nodes.push(node);
|
|
2906
|
-
tr =
|
|
2919
|
+
tr = insertBlocksInsideComments({
|
|
2920
|
+
tr,
|
|
2921
|
+
at: item.from,
|
|
2922
|
+
blocks: nodes,
|
|
2923
|
+
...item.boundaryComments !== void 0 && { carriedComments: item.boundaryComments }
|
|
2924
|
+
});
|
|
2907
2925
|
applied.push(...runApplied);
|
|
2908
2926
|
continue;
|
|
2909
2927
|
}
|
|
@@ -3288,7 +3306,8 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
|
|
|
3288
3306
|
tr = insertBlocksInsideComments({
|
|
3289
3307
|
tr,
|
|
3290
3308
|
at: item.from,
|
|
3291
|
-
blocks: built.nodes
|
|
3309
|
+
blocks: built.nodes,
|
|
3310
|
+
...item.boundaryComments !== void 0 && { carriedComments: item.boundaryComments }
|
|
3292
3311
|
});
|
|
3293
3312
|
break;
|
|
3294
3313
|
}
|
|
@@ -3713,7 +3732,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
|
|
|
3713
3732
|
const carriedComments = tableAfterDeletedSplit.get(item.blockFrom);
|
|
3714
3733
|
const usesRetractedSplit = carriedComments !== void 0 && item.from === item.blockTo;
|
|
3715
3734
|
let at = usesRetractedSplit ? tr.mapping.map(item.blockFrom, -1) : item.from;
|
|
3716
|
-
let insertionComments = carriedComments;
|
|
3735
|
+
let insertionComments = carriedComments ?? item.boundaryComments;
|
|
3717
3736
|
if (!usesRetractedSplit) {
|
|
3718
3737
|
const boundary = tr.doc.resolve(at);
|
|
3719
3738
|
const before = boundary.nodeBefore;
|
|
@@ -3903,6 +3922,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations: reques
|
|
|
3903
3922
|
if (mode === "direct") {
|
|
3904
3923
|
if (item.to > item.from) tr = tr.delete(item.from, item.to);
|
|
3905
3924
|
tr = tr.split(item.from);
|
|
3925
|
+
tr = tr.setNodeAttribute(item.blockFrom, "pPrMark", null);
|
|
3906
3926
|
if (splitsFromItsTextBoxes) gatherTextBoxesAfterParagraph(tr, item.blockFrom);
|
|
3907
3927
|
} else {
|
|
3908
3928
|
const revisionIdMark = operationRevisionSeed++;
|
|
@@ -4754,7 +4774,7 @@ const directReplacementMarks = ({ doc, plan, replacementBackground }) => {
|
|
|
4754
4774
|
const first = pieces.at(0);
|
|
4755
4775
|
const last = pieces.at(-1);
|
|
4756
4776
|
if (first === void 0 || last === void 0) panic("A planned replacement lost the characters it removes");
|
|
4757
|
-
marks = addCarriedMarks(inheritedReplacementMarks(doc, first.from, first.to),
|
|
4777
|
+
marks = addCarriedMarks(inheritedReplacementMarks(doc, first.from, first.to), surveyAcceptedReplacedAnnotations(doc, first.from, last.to).carried);
|
|
4758
4778
|
}
|
|
4759
4779
|
const written = clearsBackground ? withoutBackground(marks) : marks;
|
|
4760
4780
|
for (let offset = 0; offset < change.text.length; offset++) allocated.push(written);
|
|
@@ -5020,7 +5040,7 @@ const applyMinimalTrackedReplacement = ({ tr, item, noteReferences, replacement,
|
|
|
5020
5040
|
if (first === void 0 || last === void 0 || styleSource === null) panic("A planned replacement lost the characters it removes");
|
|
5021
5041
|
at = last.to;
|
|
5022
5042
|
removedFrom = first.from;
|
|
5023
|
-
marks = addCarriedMarks(inheritedReplacementMarks(doc, styleSource.from, styleSource.to),
|
|
5043
|
+
marks = addCarriedMarks(inheritedReplacementMarks(doc, styleSource.from, styleSource.to), surveyAcceptedReplacedAnnotations(doc, first.from, last.to).carried);
|
|
5024
5044
|
for (const piece of change.pieces) {
|
|
5025
5045
|
addTrackedDeletionMark({
|
|
5026
5046
|
insertionPolicy: "preserve-pending",
|
|
@@ -5285,7 +5305,8 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
|
|
|
5285
5305
|
blockFrom,
|
|
5286
5306
|
blockTo,
|
|
5287
5307
|
blockNode,
|
|
5288
|
-
insertTexts
|
|
5308
|
+
insertTexts,
|
|
5309
|
+
boundaryComments: commentsAcrossBlockBoundary(doc, insertFrom)
|
|
5289
5310
|
}
|
|
5290
5311
|
};
|
|
5291
5312
|
}
|
|
@@ -5541,7 +5562,8 @@ const resolveOperationTarget = ({ snapshot, operation, liveBlocks, liveBlocksByP
|
|
|
5541
5562
|
to: insertFrom,
|
|
5542
5563
|
blockFrom,
|
|
5543
5564
|
blockTo,
|
|
5544
|
-
blockNode
|
|
5565
|
+
blockNode,
|
|
5566
|
+
boundaryComments: commentsAcrossBlockBoundary(doc, insertFrom)
|
|
5545
5567
|
}
|
|
5546
5568
|
};
|
|
5547
5569
|
}
|
|
@@ -56,7 +56,7 @@ import { TaggedError, panic } from "better-result";
|
|
|
56
56
|
import { REVIEW_CARRIERS, finalizeParagraphFormatting, numberingLevelEntries, setDocumentBodyComments } from "@stll/docx-core/model";
|
|
57
57
|
import { reconcileDocumentPartRelationships } from "@stll/docx-core/ops";
|
|
58
58
|
import { EditorState } from "prosemirror-state";
|
|
59
|
-
import
|
|
59
|
+
import { ZipArchive } from "@stll/docx-core/zip";
|
|
60
60
|
// src/ai-edits/headless.ts
|
|
61
61
|
/**
|
|
62
62
|
* Headless `.docx` review path: buffer -> apply AI edits -> buffer, with
|
|
@@ -1990,8 +1990,8 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
1990
1990
|
if (xml === null) return null;
|
|
1991
1991
|
const extension = media.path.split(".").at(-1)?.toLowerCase();
|
|
1992
1992
|
if (!extension || !/^[a-z0-9]+$/u.test(extension)) return null;
|
|
1993
|
-
const originalZip = await
|
|
1994
|
-
const existingPaths = new Set(
|
|
1993
|
+
const originalZip = await ZipArchive.read(this.originalBuffer);
|
|
1994
|
+
const existingPaths = new Set(originalZip.paths().map((path) => path.toLowerCase()));
|
|
1995
1995
|
let suffix = 1;
|
|
1996
1996
|
let path = `word/media/folio-import-${suffix}.${extension}`;
|
|
1997
1997
|
while (existingPaths.has(path.toLowerCase()) || this.baseDocument.package.media?.has(path) || this.importedMedia.has(path)) path = `word/media/folio-import-${++suffix}.${extension}`;
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date comparison output from the comparison's timestamp. Imported entries
|
|
3
|
+
* retain source dates, and saves can stamp `dcterms:modified` from the wall
|
|
4
|
+
* clock; restamping both makes repeated comparisons reproducible.
|
|
5
|
+
*/
|
|
1
6
|
export declare const withFixedPackageDates: (buffer: ArrayBuffer, date: Date) => Promise<ArrayBuffer>;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { decodeText
|
|
2
|
-
import
|
|
3
|
-
import { writeZipPart } from "@stll/docx-core/zip";
|
|
1
|
+
import { decodeText } from "../utils/textEncoding.js";
|
|
2
|
+
import { ZipArchive } from "@stll/docx-core/zip";
|
|
4
3
|
// src/compare/reproducible-package.ts
|
|
5
4
|
/**
|
|
6
5
|
* Date comparison output from the comparison's timestamp. Imported entries
|
|
7
6
|
* retain source dates, and saves can stamp `dcterms:modified` from the wall
|
|
8
7
|
* clock; restamping both makes repeated comparisons reproducible.
|
|
9
8
|
*/
|
|
10
|
-
/**
|
|
9
|
+
/** ZipArchive deflate level `repackDocx` writes DOCX parts at. */
|
|
11
10
|
const DOCX_COMPRESSION_LEVEL = 6;
|
|
12
11
|
const CORE_PROPERTIES_PATH = "docProps/core.xml";
|
|
13
12
|
const MODIFIED_ELEMENT = /<dcterms:modified[^<>]*>[^<]*<\/dcterms:modified>/u;
|
|
@@ -18,24 +17,12 @@ const MODIFIED_ELEMENT = /<dcterms:modified[^<>]*>[^<]*<\/dcterms:modified>/u;
|
|
|
18
17
|
*/
|
|
19
18
|
const withFixedModifiedDate = (corePropsXml, date) => corePropsXml.replace(MODIFIED_ELEMENT, `<dcterms:modified xsi:type="dcterms:W3CDTF">${date.toISOString()}</dcterms:modified>`);
|
|
20
19
|
const withFixedPackageDates = async (buffer, date) => {
|
|
21
|
-
const zip = await
|
|
22
|
-
const coreProps = zip.
|
|
23
|
-
if (coreProps)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
compression: "DEFLATE",
|
|
29
|
-
compressionOptions: { level: DOCX_COMPRESSION_LEVEL }
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
zip.forEach((_path, file) => {
|
|
33
|
-
file.date = date;
|
|
34
|
-
});
|
|
35
|
-
return await zip.generateAsync({
|
|
36
|
-
type: "arraybuffer",
|
|
37
|
-
compression: "DEFLATE",
|
|
38
|
-
compressionOptions: { level: DOCX_COMPRESSION_LEVEL }
|
|
39
|
-
});
|
|
20
|
+
const zip = await ZipArchive.read(buffer);
|
|
21
|
+
const coreProps = zip.entry(CORE_PROPERTIES_PATH);
|
|
22
|
+
if (coreProps) zip.set(CORE_PROPERTIES_PATH, withFixedModifiedDate(decodeText(await coreProps.bytes(), { ignoreBOM: true }), date));
|
|
23
|
+
return (await zip.generate({
|
|
24
|
+
level: DOCX_COMPRESSION_LEVEL,
|
|
25
|
+
modified: date
|
|
26
|
+
})).buffer;
|
|
40
27
|
};
|
|
41
28
|
export { withFixedPackageDates };
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* platform-neutral.
|
|
9
|
-
*/
|
|
10
|
-
internalStream(type: "uint8array"): JSZip.JSZipStreamHelper<Uint8Array>;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Compression bounds for DOCX packages.
|
|
3
|
+
*
|
|
4
|
+
* The archive reader (`@stll/docx-core/zip`) checks every size an archive
|
|
5
|
+
* declares before anything inflates, and stops each inflation at its entry's
|
|
6
|
+
* declared size. The values here are the DOCX policy it is given.
|
|
7
|
+
*/
|
|
13
8
|
/**
|
|
14
9
|
* Default ceiling on inflated bytes per compressed byte.
|
|
15
10
|
*
|
|
@@ -26,100 +21,10 @@ export declare const DOCX_MAX_COMPRESSION_RATIO = 200;
|
|
|
26
21
|
* matter. The byte caps still apply below this size.
|
|
27
22
|
*/
|
|
28
23
|
export declare const DOCX_COMPRESSION_RATIO_GRACE_BYTES: number;
|
|
29
|
-
/**
|
|
30
|
-
* Upper bound on the central-directory records in `bytes`, counted without
|
|
31
|
-
* parsing the archive and stopping once the count passes `stopAfter`.
|
|
32
|
-
*
|
|
33
|
-
* Every record JSZip reads starts with the record signature, so the number of
|
|
34
|
-
* signature occurrences cannot be lower than the number of entries JSZip would
|
|
35
|
-
* build, whatever the end-of-central-directory record claims.
|
|
36
|
-
*/
|
|
37
|
-
export declare const countCentralDirectoryRecords: (bytes: Uint8Array, stopAfter: number) => number;
|
|
38
|
-
/** Sizes a loaded entry declares in the central directory. */
|
|
39
|
-
export type ZipEntrySizes = {
|
|
40
|
-
readonly compressedBytes: number | null;
|
|
41
|
-
readonly uncompressedBytes: number | null;
|
|
42
|
-
};
|
|
43
|
-
/** Declared sizes of an entry JSZip loaded from an archive. */
|
|
44
|
-
export declare const getZipEntrySizes: (entry: JSZip.JSZipObject) => ZipEntrySizes;
|
|
45
|
-
/**
|
|
46
|
-
* Whether a loaded entry is stored rather than compressed. A stored entry
|
|
47
|
-
* reads back exactly the bytes the archive carries, so it cannot expand.
|
|
48
|
-
*/
|
|
49
|
-
export declare const isStoredZipEntry: (entry: JSZip.JSZipObject) => boolean;
|
|
50
24
|
/**
|
|
51
25
|
* Whether the per-entry ratio cap applies to a part. Markup and text parts
|
|
52
26
|
* never compress anywhere near the cap. Binary parts can: an uncompressed
|
|
53
27
|
* bitmap of one colour compresses like a run of one byte. Those stay bounded
|
|
54
28
|
* by the byte caps and the package-wide ratio instead.
|
|
55
29
|
*/
|
|
56
|
-
export declare const isRatioBoundedPart: (path: string) => boolean;
|
|
57
|
-
/** The per-entry ratio cap for `path`: `maxRatio`, or none for a binary part. */
|
|
58
|
-
export declare const compressionRatioLimitFor: (path: string, maxRatio: number) => number;
|
|
59
|
-
type CompressionRatioCheck = {
|
|
60
|
-
inflatedBytes: number;
|
|
61
|
-
compressedBytes: number | null;
|
|
62
|
-
maxRatio: number;
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* Whether `inflatedBytes` produced from `compressedBytes` passes the ratio cap.
|
|
66
|
-
* Sizes under {@link DOCX_COMPRESSION_RATIO_GRACE_BYTES} never do, and an
|
|
67
|
-
* unknown compressed size is left to the byte caps.
|
|
68
|
-
*/
|
|
69
|
-
export declare const exceedsCompressionRatio: ({ inflatedBytes, compressedBytes, maxRatio }: CompressionRatioCheck) => boolean;
|
|
70
|
-
/**
|
|
71
|
-
* Inflated bytes charged across one package. `aborted` stops every inflation
|
|
72
|
-
* sharing the budget at its next chunk, once one of them has failed the
|
|
73
|
-
* package.
|
|
74
|
-
*/
|
|
75
|
-
export type InflationBudget = {
|
|
76
|
-
readonly maxTotalBytes: number;
|
|
77
|
-
inflatedBytes: number;
|
|
78
|
-
aborted: boolean;
|
|
79
|
-
};
|
|
80
|
-
export declare const createInflationBudget: (maxTotalBytes: number) => InflationBudget;
|
|
81
|
-
/**
|
|
82
|
-
* The bound an inflation stopped at.
|
|
83
|
-
*
|
|
84
|
-
* - `declared-size` — the entry produced more than its declared size.
|
|
85
|
-
* - `compression-ratio` — the entry passed the ratio cap for its compressed size.
|
|
86
|
-
* - `entry` — the entry passed the per-entry cap the caller set.
|
|
87
|
-
* - `total` — the package passed its cumulative budget.
|
|
88
|
-
* - `aborted` — another inflation sharing the budget already failed.
|
|
89
|
-
*/
|
|
90
|
-
export type InflationLimit = "declared-size" | "compression-ratio" | "entry" | "total" | "aborted";
|
|
91
|
-
export type InflateEntryResult = {
|
|
92
|
-
readonly ok: true;
|
|
93
|
-
readonly bytes: Uint8Array<ArrayBuffer>;
|
|
94
|
-
} | {
|
|
95
|
-
readonly ok: false;
|
|
96
|
-
readonly limit: InflationLimit;
|
|
97
|
-
};
|
|
98
|
-
export type InflateEntryOptions = {
|
|
99
|
-
entry: JSZip.JSZipObject;
|
|
100
|
-
/** Most bytes this entry may inflate to. */
|
|
101
|
-
maxEntryBytes: number;
|
|
102
|
-
/** Ratio cap while streaming; see {@link compressionRatioLimitFor}. */
|
|
103
|
-
maxCompressionRatio: number;
|
|
104
|
-
budget: InflationBudget;
|
|
105
|
-
/**
|
|
106
|
-
* `false` inflates the entry only to prove it stays within its bounds; the
|
|
107
|
-
* chunks are dropped and the result carries no bytes. A package part that
|
|
108
|
-
* is never read here is still inflated by a later save, so the bound has
|
|
109
|
-
* to be established for it too.
|
|
110
|
-
*/
|
|
111
|
-
retain?: boolean;
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* Inflate one entry chunk by chunk, checking every bound before a chunk is
|
|
115
|
-
* kept. Pausing the stream abandons the rest of the entry, so a limit bounds
|
|
116
|
-
* memory rather than reporting an overrun afterwards.
|
|
117
|
-
*
|
|
118
|
-
* The budget is charged as chunks arrive, which keeps concurrent inflations
|
|
119
|
-
* of one package honest, and refunded when this entry stops at a limit or
|
|
120
|
-
* fails, since none of its bytes are kept. Stream errors (a corrupt entry)
|
|
121
|
-
* reject; limits resolve with `ok: false` for the caller to map onto its own
|
|
122
|
-
* error or skip policy.
|
|
123
|
-
*/
|
|
124
|
-
export declare const inflateEntryWithinLimits: ({ entry, maxEntryBytes, maxCompressionRatio, budget, retain }: InflateEntryOptions) => Promise<InflateEntryResult>;
|
|
125
|
-
export {};
|
|
30
|
+
export declare const isRatioBoundedPart: (path: string) => boolean;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// src/docx/archiveInflation.ts
|
|
2
2
|
/**
|
|
3
|
+
* Compression bounds for DOCX packages.
|
|
4
|
+
*
|
|
5
|
+
* The archive reader (`@stll/docx-core/zip`) checks every size an archive
|
|
6
|
+
* declares before anything inflates, and stops each inflation at its entry's
|
|
7
|
+
* declared size. The values here are the DOCX policy it is given.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
3
10
|
* Default ceiling on inflated bytes per compressed byte.
|
|
4
11
|
*
|
|
5
12
|
* Document parts in the repository's fixtures stay under 75:1, and parts over a
|
|
@@ -15,57 +22,6 @@ const DOCX_MAX_COMPRESSION_RATIO = 200;
|
|
|
15
22
|
* matter. The byte caps still apply below this size.
|
|
16
23
|
*/
|
|
17
24
|
const DOCX_COMPRESSION_RATIO_GRACE_BYTES = 4194304;
|
|
18
|
-
const CENTRAL_DIRECTORY_RECORD_SIGNATURE = [
|
|
19
|
-
80,
|
|
20
|
-
75,
|
|
21
|
-
1,
|
|
22
|
-
2
|
|
23
|
-
];
|
|
24
|
-
/**
|
|
25
|
-
* Upper bound on the central-directory records in `bytes`, counted without
|
|
26
|
-
* parsing the archive and stopping once the count passes `stopAfter`.
|
|
27
|
-
*
|
|
28
|
-
* Every record JSZip reads starts with the record signature, so the number of
|
|
29
|
-
* signature occurrences cannot be lower than the number of entries JSZip would
|
|
30
|
-
* build, whatever the end-of-central-directory record claims.
|
|
31
|
-
*/
|
|
32
|
-
const countCentralDirectoryRecords = (bytes, stopAfter) => {
|
|
33
|
-
const [first, second, third, fourth] = CENTRAL_DIRECTORY_RECORD_SIGNATURE;
|
|
34
|
-
let count = 0;
|
|
35
|
-
let offset = bytes.indexOf(first);
|
|
36
|
-
while (offset !== -1 && offset + 3 < bytes.length) {
|
|
37
|
-
if (bytes[offset + 1] === second && bytes[offset + 2] === third && bytes[offset + 3] === fourth) {
|
|
38
|
-
count += 1;
|
|
39
|
-
if (count > stopAfter) return count;
|
|
40
|
-
}
|
|
41
|
-
offset = bytes.indexOf(first, offset + 1);
|
|
42
|
-
}
|
|
43
|
-
return count;
|
|
44
|
-
};
|
|
45
|
-
const finiteSize = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
46
|
-
/** Declared sizes of an entry JSZip loaded from an archive. */
|
|
47
|
-
const getZipEntrySizes = (entry) => {
|
|
48
|
-
const data = "_data" in entry ? entry._data : void 0;
|
|
49
|
-
if (typeof data !== "object" || data === null) return {
|
|
50
|
-
compressedBytes: null,
|
|
51
|
-
uncompressedBytes: null
|
|
52
|
-
};
|
|
53
|
-
return {
|
|
54
|
-
compressedBytes: "compressedSize" in data ? finiteSize(data.compressedSize) : null,
|
|
55
|
-
uncompressedBytes: "uncompressedSize" in data ? finiteSize(data.uncompressedSize) : null
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
const STORED_COMPRESSION_MAGIC = "\0\0";
|
|
59
|
-
/**
|
|
60
|
-
* Whether a loaded entry is stored rather than compressed. A stored entry
|
|
61
|
-
* reads back exactly the bytes the archive carries, so it cannot expand.
|
|
62
|
-
*/
|
|
63
|
-
const isStoredZipEntry = (entry) => {
|
|
64
|
-
const data = "_data" in entry ? entry._data : void 0;
|
|
65
|
-
if (typeof data !== "object" || data === null || !("compression" in data)) return false;
|
|
66
|
-
const { compression } = data;
|
|
67
|
-
return typeof compression === "object" && compression !== null && "magic" in compression && compression.magic === STORED_COMPRESSION_MAGIC;
|
|
68
|
-
};
|
|
69
25
|
const RATIO_BOUNDED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
70
26
|
"xml",
|
|
71
27
|
"rels",
|
|
@@ -84,97 +40,4 @@ const RATIO_BOUNDED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
84
40
|
* by the byte caps and the package-wide ratio instead.
|
|
85
41
|
*/
|
|
86
42
|
const isRatioBoundedPart = (path) => RATIO_BOUNDED_EXTENSIONS.has(path.slice(path.lastIndexOf(".") + 1).toLowerCase());
|
|
87
|
-
|
|
88
|
-
const compressionRatioLimitFor = (path, maxRatio) => isRatioBoundedPart(path) ? maxRatio : Number.POSITIVE_INFINITY;
|
|
89
|
-
/**
|
|
90
|
-
* Whether `inflatedBytes` produced from `compressedBytes` passes the ratio cap.
|
|
91
|
-
* Sizes under {@link DOCX_COMPRESSION_RATIO_GRACE_BYTES} never do, and an
|
|
92
|
-
* unknown compressed size is left to the byte caps.
|
|
93
|
-
*/
|
|
94
|
-
const exceedsCompressionRatio = ({ inflatedBytes, compressedBytes, maxRatio }) => compressedBytes !== null && inflatedBytes > 4194304 && inflatedBytes > compressedBytes * maxRatio;
|
|
95
|
-
const createInflationBudget = (maxTotalBytes) => ({
|
|
96
|
-
maxTotalBytes,
|
|
97
|
-
inflatedBytes: 0,
|
|
98
|
-
aborted: false
|
|
99
|
-
});
|
|
100
|
-
const EMPTY_BYTES = /* @__PURE__ */ new Uint8Array(0);
|
|
101
|
-
const concatChunks = (chunks, totalBytes) => {
|
|
102
|
-
const merged = new Uint8Array(totalBytes);
|
|
103
|
-
let offset = 0;
|
|
104
|
-
for (const chunk of chunks) {
|
|
105
|
-
merged.set(chunk, offset);
|
|
106
|
-
offset += chunk.length;
|
|
107
|
-
}
|
|
108
|
-
return merged;
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* Inflate one entry chunk by chunk, checking every bound before a chunk is
|
|
112
|
-
* kept. Pausing the stream abandons the rest of the entry, so a limit bounds
|
|
113
|
-
* memory rather than reporting an overrun afterwards.
|
|
114
|
-
*
|
|
115
|
-
* The budget is charged as chunks arrive, which keeps concurrent inflations
|
|
116
|
-
* of one package honest, and refunded when this entry stops at a limit or
|
|
117
|
-
* fails, since none of its bytes are kept. Stream errors (a corrupt entry)
|
|
118
|
-
* reject; limits resolve with `ok: false` for the caller to map onto its own
|
|
119
|
-
* error or skip policy.
|
|
120
|
-
*/
|
|
121
|
-
const inflateEntryWithinLimits = async ({ entry, maxEntryBytes, maxCompressionRatio, budget, retain = true }) => {
|
|
122
|
-
const { compressedBytes, uncompressedBytes } = getZipEntrySizes(entry);
|
|
123
|
-
return await new Promise((resolve, reject) => {
|
|
124
|
-
const stream = entry.internalStream("uint8array");
|
|
125
|
-
const chunks = [];
|
|
126
|
-
let entryBytes = 0;
|
|
127
|
-
let settled = false;
|
|
128
|
-
const settle = () => {
|
|
129
|
-
if (settled) return false;
|
|
130
|
-
settled = true;
|
|
131
|
-
return true;
|
|
132
|
-
};
|
|
133
|
-
const refund = () => {
|
|
134
|
-
budget.inflatedBytes -= entryBytes;
|
|
135
|
-
};
|
|
136
|
-
const stop = (limit) => {
|
|
137
|
-
if (!settle()) return;
|
|
138
|
-
stream.pause();
|
|
139
|
-
refund();
|
|
140
|
-
resolve({
|
|
141
|
-
ok: false,
|
|
142
|
-
limit
|
|
143
|
-
});
|
|
144
|
-
};
|
|
145
|
-
const limitFor = () => {
|
|
146
|
-
if (budget.aborted) return "aborted";
|
|
147
|
-
if (uncompressedBytes !== null && entryBytes > uncompressedBytes) return "declared-size";
|
|
148
|
-
if (exceedsCompressionRatio({
|
|
149
|
-
inflatedBytes: entryBytes,
|
|
150
|
-
compressedBytes,
|
|
151
|
-
maxRatio: maxCompressionRatio
|
|
152
|
-
})) return "compression-ratio";
|
|
153
|
-
if (entryBytes > maxEntryBytes) return "entry";
|
|
154
|
-
if (budget.inflatedBytes > budget.maxTotalBytes) return "total";
|
|
155
|
-
return null;
|
|
156
|
-
};
|
|
157
|
-
stream.on("data", (chunk) => {
|
|
158
|
-
if (settled) return;
|
|
159
|
-
entryBytes += chunk.length;
|
|
160
|
-
budget.inflatedBytes += chunk.length;
|
|
161
|
-
const limit = limitFor();
|
|
162
|
-
if (limit !== null) {
|
|
163
|
-
stop(limit);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (retain) chunks.push(chunk);
|
|
167
|
-
}).on("end", () => {
|
|
168
|
-
if (!settle()) return;
|
|
169
|
-
resolve({
|
|
170
|
-
ok: true,
|
|
171
|
-
bytes: retain ? concatChunks(chunks, entryBytes) : EMPTY_BYTES
|
|
172
|
-
});
|
|
173
|
-
}).on("error", (error) => {
|
|
174
|
-
if (!settle()) return;
|
|
175
|
-
refund();
|
|
176
|
-
reject(error);
|
|
177
|
-
}).resume();
|
|
178
|
-
});
|
|
179
|
-
};
|
|
180
|
-
export { DOCX_COMPRESSION_RATIO_GRACE_BYTES, DOCX_MAX_COMPRESSION_RATIO, compressionRatioLimitFor, countCentralDirectoryRecords, createInflationBudget, exceedsCompressionRatio, getZipEntrySizes, inflateEntryWithinLimits, isRatioBoundedPart, isStoredZipEntry };
|
|
43
|
+
export { DOCX_COMPRESSION_RATIO_GRACE_BYTES, DOCX_MAX_COMPRESSION_RATIO, isRatioBoundedPart };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { modelledAttributeNames } from "./propertyElementAttributes.js";
|
|
2
2
|
import { OOXML_NAMESPACES } from "./serializer/partNamespaces.js";
|
|
3
|
-
import { XML_NAMESPACE_URI } from "./xmlNamespaceContext.js";
|
|
4
3
|
import { getLocalName, resolveAttributeNamespaceUri } from "./xmlParser.js";
|
|
5
4
|
import { TaggedError } from "better-result";
|
|
6
5
|
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
6
|
+
import { XML_NAMESPACE_URI } from "@stll/docx-core/xml/namespaces";
|
|
7
7
|
// src/docx/attributeRemainder.ts
|
|
8
8
|
/**
|
|
9
9
|
* The attribute remainder: what an element carried and its parser has no field for.
|
|
@@ -5,7 +5,7 @@ import { readDocumentSectionFacts } from "./documentSectionFacts.js";
|
|
|
5
5
|
import { parseDocx } from "./parser.js";
|
|
6
6
|
import { assertSectionCarriersMatchModel } from "./rezip.js";
|
|
7
7
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
8
|
-
import
|
|
8
|
+
import { ZipArchive } from "@stll/docx-core/zip";
|
|
9
9
|
// src/docx/canonicalStoryRepack.ts
|
|
10
10
|
/** Canonical snapshots own explicit section references; the PM projection does not. */
|
|
11
11
|
const referenceKey = ({ element, type, rId }) => `${element}:${type}:${rId}`;
|
|
@@ -19,7 +19,7 @@ const xmlFingerprint = async (xml) => {
|
|
|
19
19
|
const repackWithCanonicalStoryRemovals = async ({ document, repack }) => {
|
|
20
20
|
const originalBuffer = document.originalBuffer;
|
|
21
21
|
if (!originalBuffer) return repack();
|
|
22
|
-
const xml = await (await
|
|
22
|
+
const xml = await (await ZipArchive.read(originalBuffer)).entry("word/document.xml")?.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }));
|
|
23
23
|
if (!xml) return repack();
|
|
24
24
|
const before = readDocumentSectionFacts(xml);
|
|
25
25
|
const serializedXml = serializeDocument(document);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseHeaderFooterType } from "./headerFooterRefParser.js";
|
|
2
2
|
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, parseXml } from "./xmlParser.js";
|
|
3
|
-
import { assertXmlResourceLimits } from "
|
|
3
|
+
import { assertXmlResourceLimits } from "@stll/docx-core/xml/limits";
|
|
4
4
|
// src/docx/documentSectionFacts.ts
|
|
5
5
|
const isWordprocessingElement = (element, localName) => getLocalName(element.name) === localName && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "");
|
|
6
6
|
/** Both facts from one walk of a parsed document tree. */
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { deterministicHexId } from "../utils/hexId.js";
|
|
2
|
-
import { encodeUtf8 } from "../utils/textEncoding.js";
|
|
3
2
|
import { resolvePackageRelationshipTarget } from "./packageParts.js";
|
|
4
3
|
import { spliceXml } from "./selectiveXmlPatch.js";
|
|
5
4
|
import { loadDocxArchive } from "./server/boundedArchive.js";
|
|
6
|
-
import { scanStreamingXmlElements } from "./streamingXmlParser.js";
|
|
7
|
-
import { resolveNamespaceUri } from "./xmlNamespaceContext.js";
|
|
8
5
|
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, getAttribute, getChildElements, getLocalName, getNamespaceUri, parseXmlDocument, resolveAttributeNamespaceUri } from "./xmlParser.js";
|
|
9
6
|
import { TaggedError } from "better-result";
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
7
|
+
import { scanStreamingXmlElements } from "@stll/docx-core/xml";
|
|
8
|
+
import { resolveNamespaceUri } from "@stll/docx-core/xml/namespaces";
|
|
9
|
+
import { ZipArchive } from "@stll/docx-core/zip";
|
|
12
10
|
// src/docx/ensureParaIds.ts
|
|
13
11
|
/**
|
|
14
12
|
* Headless `w14:paraId` normalization for a `.docx` buffer.
|
|
@@ -321,7 +319,7 @@ const mainDocumentPart = (rels, entries) => {
|
|
|
321
319
|
/** OPC part names are case-insensitive; compare lowercased. */
|
|
322
320
|
const isTargetPart = (path) => TARGET_PART_PATTERNS.some((pattern) => pattern.test(path.toLowerCase()));
|
|
323
321
|
const toUint8Array = (docx) => docx instanceof Uint8Array ? docx : new Uint8Array(docx);
|
|
324
|
-
const hasDigitalSignatureParts = (zip) =>
|
|
322
|
+
const hasDigitalSignatureParts = (zip) => zip.entries().some(({ path }) => path.toLowerCase().startsWith(DIGITAL_SIGNATURE_PART_PREFIX));
|
|
325
323
|
/**
|
|
326
324
|
* Backfill `w14:paraId` on every paragraph of a `.docx` buffer. See the
|
|
327
325
|
* module doc for the exact contract. Throws {@link EnsureParaIdsError} when
|
|
@@ -381,31 +379,18 @@ const ensureParaIdsInternal = async (docx, options) => {
|
|
|
381
379
|
alreadyComplete: true,
|
|
382
380
|
mintedParaIds: []
|
|
383
381
|
};
|
|
384
|
-
const zip = await
|
|
382
|
+
const zip = await ZipArchive.read(docx);
|
|
385
383
|
if (hasDigitalSignatureParts(zip) && options.allowSignedPackageMutation !== true) throw new EnsureParaIdsError({
|
|
386
384
|
reason: ENSURE_PARA_IDS_REASONS.SIGNED_PACKAGE,
|
|
387
385
|
message: "Refusing to normalize a digitally signed package because rewriting OOXML invalidates its signatures. Warn the user and pass allowSignedPackageMutation only if invalidation is acceptable."
|
|
388
386
|
});
|
|
389
387
|
for (const [partPath, content] of updates) {
|
|
390
|
-
const sourceEntry = zip.
|
|
391
|
-
if (sourceEntry ===
|
|
392
|
-
|
|
393
|
-
zip,
|
|
394
|
-
path: partPath,
|
|
395
|
-
data: encodeUtf8(content),
|
|
396
|
-
options: {
|
|
397
|
-
compression: "DEFLATE",
|
|
398
|
-
compressionOptions: { level: 6 },
|
|
399
|
-
date: sourceEntry.date
|
|
400
|
-
}
|
|
401
|
-
});
|
|
388
|
+
const sourceEntry = zip.entry(partPath);
|
|
389
|
+
if (sourceEntry === void 0) throw createEnsureParaIdsError(`Package part disappeared during normalization: ${partPath}`);
|
|
390
|
+
zip.set(partPath, content, { modified: sourceEntry.modified });
|
|
402
391
|
}
|
|
403
392
|
return {
|
|
404
|
-
docx: await zip.
|
|
405
|
-
type: "uint8array",
|
|
406
|
-
compression: "DEFLATE",
|
|
407
|
-
compressionOptions: { level: 6 }
|
|
408
|
-
}),
|
|
393
|
+
docx: await zip.generate({ level: 6 }),
|
|
409
394
|
assigned,
|
|
410
395
|
deduplicated,
|
|
411
396
|
alreadyComplete: false,
|
|
@@ -3,10 +3,10 @@ import { REVISION_ELEMENT_NAMES } from "./revisionIdNormalization.js";
|
|
|
3
3
|
import { scanSelectedStreamingXmlElements } from "./selectedXmlIdentityScan.js";
|
|
4
4
|
import { OOXML_NAMESPACES } from "./serializer/partNamespaces.js";
|
|
5
5
|
import { toTransitionalNamespaceUri } from "./transitionalSpelling.js";
|
|
6
|
-
import { resolveNamespaceUri } from "./xmlNamespaceContext.js";
|
|
7
6
|
import { NAMESPACES, getLocalName, getNamespaceUri } from "./xmlParser.js";
|
|
8
7
|
import { panic } from "better-result";
|
|
9
8
|
import { IMPORTED_ID_COLLISION_KINDS, PARSE_WARNING_CODES } from "@stll/docx-core/model";
|
|
9
|
+
import { resolveNamespaceUri } from "@stll/docx-core/xml/namespaces";
|
|
10
10
|
// src/docx/importedIdCollisions.ts
|
|
11
11
|
const WORD_2010_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
12
12
|
const PACKAGE_RELATIONSHIP_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WORDPROCESSINGML_NAMESPACE_URIS, findChildByLocalName, findChildByNamespaceUri, getAttributeByNamespaceUri, getLocalName, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
|
|
2
|
-
import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
|
|
3
2
|
import { escapeXmlText } from "@stll/docx-core";
|
|
3
|
+
import { assertXmlResourceLimits } from "@stll/docx-core/xml/limits";
|
|
4
4
|
// src/docx/mathToMathml.ts
|
|
5
5
|
/**
|
|
6
6
|
* OMML → MathML conversion.
|