@stll/folio-core 0.33.1 → 0.33.2
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 +15 -0
- package/dist/ai-edits/apply.js +134 -25
- package/dist/ai-edits/headless.d.ts +47 -3
- package/dist/ai-edits/headless.js +53 -3
- package/dist/ai-edits/index.d.ts +1 -1
- package/dist/ai-edits/snapshot.d.ts +19 -1
- package/dist/ai-edits/snapshot.js +25 -3
- package/dist/ai-edits/table-geometry.d.ts +56 -0
- package/dist/ai-edits/table-geometry.js +217 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
- package/dist/ai-edits/table-row-column-mutations.js +12 -4
- package/dist/ai-edits/table-template.d.ts +60 -0
- package/dist/ai-edits/table-template.js +176 -0
- package/dist/ai-edits/types.d.ts +7 -0
- package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
- package/dist/compare/__fixtures__/body-sequence.js +94 -6
- package/dist/compare/compare.d.ts +14 -7
- package/dist/compare/compare.js +136 -38
- package/dist/compare/plan.d.ts +31 -1
- package/dist/compare/plan.js +233 -66
- package/dist/compare/types.d.ts +9 -7
- package/dist/compare/types.js +7 -5
- package/dist/compare/verification.d.ts +31 -12
- package/dist/compare/verification.js +88 -17
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +2 -2
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +10 -1
- package/dist/document-operations.js +3 -2
- package/dist/docx/appVersionNormalization.d.ts +49 -0
- package/dist/docx/appVersionNormalization.js +74 -0
- package/dist/docx/blockContentParser.js +3 -2
- package/dist/docx/drawingUtils.js +4 -3
- package/dist/docx/paragraphParser.js +3 -2
- package/dist/docx/rezip.d.ts +10 -4
- package/dist/docx/rezip.js +54 -18
- package/dist/docx/runParser.js +9 -8
- package/dist/docx/sdtProperties.js +4 -3
- package/dist/docx/selectiveSave.js +7 -6
- package/dist/docx/serializer/commentSerializer.d.ts +1 -1
- package/dist/docx/serializer/commentSerializer.js +43 -19
- package/dist/docx/serializer/documentSerializer.d.ts +3 -1
- package/dist/docx/serializer/documentSerializer.js +42 -78
- package/dist/docx/serializer/fontTableSerializer.js +8 -5
- package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
- package/dist/docx/serializer/headerFooterSerializer.js +34 -29
- package/dist/docx/serializer/noteSerializer.js +34 -30
- package/dist/docx/serializer/numberingSerializer.js +8 -4
- package/dist/docx/serializer/paragraphSerializer.js +3 -3
- package/dist/docx/serializer/partNamespaces.d.ts +78 -0
- package/dist/docx/serializer/partNamespaces.js +324 -0
- package/dist/docx/serializer/runSerializer.js +1 -1
- package/dist/docx/serializer/settingsSerializer.js +8 -2
- package/dist/docx/serializer/stylesSerializer.js +8 -5
- package/dist/docx/serializer/tableSerializer.js +83 -18
- package/dist/docx/serializer/themeSerializer.js +10 -2
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
- package/dist/docx/strictValueEncodings.gen.js +275 -0
- package/dist/docx/tableParser.d.ts +0 -6
- package/dist/docx/tableParser.js +52 -7
- package/dist/docx/transitionalSpelling.d.ts +23 -0
- package/dist/docx/transitionalSpelling.js +36 -0
- package/dist/docx/universalMeasure.d.ts +20 -0
- package/dist/docx/universalMeasure.js +32 -0
- package/dist/docx/verbatimCapture.d.ts +20 -0
- package/dist/docx/verbatimCapture.js +131 -0
- package/dist/docx/vmlImageParser.js +4 -3
- package/dist/docx/watermarkParser.js +4 -3
- package/dist/docx/xmlParser.d.ts +23 -2
- package/dist/docx/xmlParser.js +43 -11
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/prosemirror/commands/comments.js +2 -1
- package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
- package/dist/prosemirror/containerFinalParagraph.js +92 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +15 -4
- package/dist/prosemirror/conversion/toProseDoc.js +12 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
- package/dist/prosemirror/schema/marks.d.ts +1 -1
- package/dist/prosemirror/schema/nodes.d.ts +22 -0
- package/dist/server.d.ts +1 -1
- package/dist/types/block-id.d.ts +12 -1
- package/dist/types/block-id.js +17 -1
- package/dist/utils/canonicalJson.d.ts +14 -0
- package/dist/utils/canonicalJson.js +19 -0
- package/dist/utils/formatToStyle.d.ts +1 -1
- package/package.json +2 -2
package/dist/docx/rezip.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
|
|
2
|
+
import { normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
|
|
2
3
|
import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
|
|
3
4
|
import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
|
|
4
5
|
import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
@@ -16,6 +17,7 @@ import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
|
16
17
|
import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
17
18
|
import { serializeEndnotes, serializeFootnotes, serializeNewEndnotesPart, serializeNewFootnotesPart } from "./serializer/noteSerializer.js";
|
|
18
19
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
20
|
+
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
19
21
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
20
22
|
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
21
23
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
@@ -122,13 +124,12 @@ function assertDocumentPackageFidelity(originalDocumentXml, serializedDocumentXm
|
|
|
122
124
|
async function serializeCommentsToZip(doc, zip, compressionLevel) {
|
|
123
125
|
const comments = doc.package.document.comments ?? [];
|
|
124
126
|
const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
|
|
127
|
+
const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
|
|
125
128
|
if (comments.length === 0) {
|
|
126
|
-
if (!
|
|
127
|
-
const sourceCommentsXml = await sourceCommentsFile.async("text");
|
|
128
|
-
if (!hasCommentEntries(sourceCommentsXml)) return;
|
|
129
|
+
if (sourceCommentsXml === void 0 || !hasCommentEntries(sourceCommentsXml)) return;
|
|
129
130
|
}
|
|
130
131
|
ensureThreadedCommentParaIds(comments);
|
|
131
|
-
const commentsXml = serializeComments(comments);
|
|
132
|
+
const commentsXml = serializeComments(comments, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
132
133
|
zip.file(sourceCommentsFile?.name ?? "word/comments.xml", commentsXml, {
|
|
133
134
|
compression: "DEFLATE",
|
|
134
135
|
compressionOptions: { level: compressionLevel }
|
|
@@ -428,6 +429,30 @@ const normalizePackageIdsInZip = async (zip, compressionLevel) => {
|
|
|
428
429
|
compressionOptions: { level: compressionLevel }
|
|
429
430
|
});
|
|
430
431
|
};
|
|
432
|
+
const EXTENDED_PROPERTIES_PATH = "docProps/app.xml";
|
|
433
|
+
/**
|
|
434
|
+
* Bring the application version the package states about itself into the form
|
|
435
|
+
* the schema gives it. A package that holds no extended properties keeps
|
|
436
|
+
* holding none.
|
|
437
|
+
*/
|
|
438
|
+
const normalizeAppVersionInZip = async (zip, compressionLevel) => {
|
|
439
|
+
const extendedProperties = zip.file(EXTENDED_PROPERTIES_PATH);
|
|
440
|
+
if (!extendedProperties) return;
|
|
441
|
+
const xml = await extendedProperties.async("text");
|
|
442
|
+
const normalized = normalizeAppVersionInExtendedProperties(xml);
|
|
443
|
+
if (normalized !== xml) zip.file(EXTENDED_PROPERTIES_PATH, normalized, {
|
|
444
|
+
compression: "DEFLATE",
|
|
445
|
+
compressionOptions: { level: compressionLevel }
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Every pass a save owes the package as a whole, in one function so that the
|
|
450
|
+
* full repack and the selective save cannot drift into running different ones.
|
|
451
|
+
*/
|
|
452
|
+
const normalizePackageOnSave = async (zip, compressionLevel) => {
|
|
453
|
+
await normalizePackageIdsInZip(zip, compressionLevel);
|
|
454
|
+
await normalizeAppVersionInZip(zip, compressionLevel);
|
|
455
|
+
};
|
|
431
456
|
/**
|
|
432
457
|
* The single exit for a repacked package. Reconciliation runs here rather than
|
|
433
458
|
* at each caller so no save path can emit a package whose relationships or
|
|
@@ -435,7 +460,7 @@ const normalizePackageIdsInZip = async (zip, compressionLevel) => {
|
|
|
435
460
|
*/
|
|
436
461
|
const generateDocxZip = async (zip, compressionLevel) => {
|
|
437
462
|
await reconcilePackageReferences(zip, compressionLevel);
|
|
438
|
-
await
|
|
463
|
+
await normalizePackageOnSave(zip, compressionLevel);
|
|
439
464
|
return zip.generateAsync({
|
|
440
465
|
type: "arraybuffer",
|
|
441
466
|
compression: "DEFLATE",
|
|
@@ -470,14 +495,14 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
470
495
|
await processNewHyperlinks(parts, outputZip, compressionLevel);
|
|
471
496
|
assertValidFolioDocumentModel(document, "Cannot repack invalid DOCX document model");
|
|
472
497
|
applyReplyThreadMarkers(document);
|
|
473
|
-
const documentXml = serializeDocument(document);
|
|
498
|
+
const documentXml = serializeDocument(document, originalDocumentXml === void 0 ? void 0 : readRootNamespaceBindings(originalDocumentXml));
|
|
474
499
|
if (originalDocumentXml) assertDocumentPackageFidelity(originalDocumentXml, documentXml, document);
|
|
475
500
|
outputZip.file("word/document.xml", documentXml, {
|
|
476
501
|
compression: "DEFLATE",
|
|
477
502
|
compressionOptions: { level: compressionLevel }
|
|
478
503
|
});
|
|
479
504
|
await rebindWatermarkRelIds(document, outputZip, compressionLevel);
|
|
480
|
-
serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
505
|
+
await serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
481
506
|
await serializeNotesToZip({
|
|
482
507
|
doc: document,
|
|
483
508
|
originalZip,
|
|
@@ -559,14 +584,14 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
559
584
|
await processNewHyperlinks(parts, newZip, compressionLevel);
|
|
560
585
|
assertValidFolioDocumentModel(exportDocument, "Cannot repack invalid DOCX document model");
|
|
561
586
|
applyReplyThreadMarkers(exportDocument);
|
|
562
|
-
const documentXml = serializeDocument(exportDocument);
|
|
587
|
+
const documentXml = serializeDocument(exportDocument, rawContent.documentXml ? readRootNamespaceBindings(rawContent.documentXml) : void 0);
|
|
563
588
|
if (rawContent.documentXml) assertDocumentPackageFidelity(rawContent.documentXml, documentXml, exportDocument);
|
|
564
589
|
newZip.file("word/document.xml", documentXml, {
|
|
565
590
|
compression: "DEFLATE",
|
|
566
591
|
compressionOptions: { level: compressionLevel }
|
|
567
592
|
});
|
|
568
593
|
await rebindWatermarkRelIds(exportDocument, newZip, compressionLevel);
|
|
569
|
-
serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
594
|
+
await serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
570
595
|
await serializeNotesToZip({
|
|
571
596
|
doc: exportDocument,
|
|
572
597
|
originalZip: rawContent.originalZip,
|
|
@@ -775,11 +800,11 @@ async function updateMultipleFiles(originalBuffer, updates, options = {}) {
|
|
|
775
800
|
* Apply file updates to an already-loaded JSZip instance and generate the output.
|
|
776
801
|
* Use this when the zip is already loaded to avoid a redundant decompression pass.
|
|
777
802
|
*
|
|
778
|
-
* This is the selective save's exit, so it owes the package the same
|
|
803
|
+
* This is the selective save's exit, so it owes the package the same passes
|
|
779
804
|
* {@link generateDocxZip} runs: a save that rewrites only the changed
|
|
780
805
|
* paragraphs still has to see the parts it left alone, both to know which
|
|
781
|
-
* revision ids are free and because an out-of-range paragraph id
|
|
782
|
-
* part it never touched.
|
|
806
|
+
* revision ids are free and because an out-of-range paragraph id, or a
|
|
807
|
+
* malformed application version, can sit in a part it never touched.
|
|
783
808
|
*/
|
|
784
809
|
async function applyUpdatesToZip(zip, updates, options = {}) {
|
|
785
810
|
const { compressionLevel = 6 } = options;
|
|
@@ -787,7 +812,7 @@ async function applyUpdatesToZip(zip, updates, options = {}) {
|
|
|
787
812
|
compression: "DEFLATE",
|
|
788
813
|
compressionOptions: { level: compressionLevel }
|
|
789
814
|
});
|
|
790
|
-
await
|
|
815
|
+
await normalizePackageOnSave(zip, compressionLevel);
|
|
791
816
|
return await zip.generateAsync({
|
|
792
817
|
type: "arraybuffer",
|
|
793
818
|
compression: "DEFLATE",
|
|
@@ -1090,7 +1115,13 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
|
|
|
1090
1115
|
});
|
|
1091
1116
|
}
|
|
1092
1117
|
}
|
|
1093
|
-
|
|
1118
|
+
/**
|
|
1119
|
+
* Re-serialize every header/footer the model still owns, keyed by part path.
|
|
1120
|
+
*
|
|
1121
|
+
* `sourceZip` supplies each part as it stands before the save so the rebuilt
|
|
1122
|
+
* root can keep any prefix binding only the source document declared.
|
|
1123
|
+
*/
|
|
1124
|
+
async function collectHeaderFooterUpdates(doc, sourceZip) {
|
|
1094
1125
|
const updates = /* @__PURE__ */ new Map();
|
|
1095
1126
|
const rels = doc.package.relationships;
|
|
1096
1127
|
if (!rels) return updates;
|
|
@@ -1107,8 +1138,13 @@ function collectHeaderFooterUpdates(doc) {
|
|
|
1107
1138
|
for (const [rId, headerFooter] of map.entries()) {
|
|
1108
1139
|
const rel = rels.get(rId);
|
|
1109
1140
|
if (rel && rel.type === type && rel.target) {
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1141
|
+
const path = resolveRelativePath(documentRelsPath, rel.target);
|
|
1142
|
+
const sourceFile = findZipEntryCaseInsensitive(sourceZip, path.toLowerCase());
|
|
1143
|
+
const bindings = sourceFile ? readRootNamespaceBindings(await sourceFile.async("text")) : /* @__PURE__ */ new Map();
|
|
1144
|
+
updates.set(path, serializeHeaderFooter(headerFooter, {
|
|
1145
|
+
path,
|
|
1146
|
+
bindings
|
|
1147
|
+
}));
|
|
1112
1148
|
}
|
|
1113
1149
|
}
|
|
1114
1150
|
}
|
|
@@ -1117,9 +1153,9 @@ function collectHeaderFooterUpdates(doc) {
|
|
|
1117
1153
|
/**
|
|
1118
1154
|
* Serialize modified headers and footers into the ZIP
|
|
1119
1155
|
*/
|
|
1120
|
-
function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1156
|
+
async function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1121
1157
|
const compressionOptions = { level: compressionLevel };
|
|
1122
|
-
for (const [filename, xml] of collectHeaderFooterUpdates(doc)) zip.file(filename, xml, {
|
|
1158
|
+
for (const [filename, xml] of await collectHeaderFooterUpdates(doc, zip)) zip.file(filename, xml, {
|
|
1123
1159
|
compression: "DEFLATE",
|
|
1124
1160
|
compressionOptions
|
|
1125
1161
|
});
|
package/dist/docx/runParser.js
CHANGED
|
@@ -7,8 +7,9 @@ import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapePar
|
|
|
7
7
|
import { isTextBoxDrawing } from "./textBoxParser.js";
|
|
8
8
|
import { requiresXmlSpacePreserve } from "./textWhitespace.js";
|
|
9
9
|
import { resolveThemeFontRef } from "./themeParser.js";
|
|
10
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
10
11
|
import { parseVmlImageContent } from "./vmlImageParser.js";
|
|
11
|
-
import { cloneWithXmlnsDeclarations,
|
|
12
|
+
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
12
13
|
import { DRAWING_RAW_XML_MODES, normalizeRevisionId } from "@stll/docx-core/model";
|
|
13
14
|
//#region src/docx/runParser.ts
|
|
14
15
|
/**
|
|
@@ -489,7 +490,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
489
490
|
if (groupImage) return {
|
|
490
491
|
type: "drawing",
|
|
491
492
|
image: groupImage,
|
|
492
|
-
rawXml:
|
|
493
|
+
rawXml: captureVerbatimXml(element)
|
|
493
494
|
};
|
|
494
495
|
if (shouldPreserveRawShapeDrawing(element)) return {
|
|
495
496
|
type: "drawing",
|
|
@@ -502,7 +503,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
502
503
|
},
|
|
503
504
|
wrap: { type: "inline" }
|
|
504
505
|
},
|
|
505
|
-
rawXml:
|
|
506
|
+
rawXml: captureVerbatimXml(element),
|
|
506
507
|
rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
|
|
507
508
|
};
|
|
508
509
|
const shape = parseShapeFromDrawing(element);
|
|
@@ -516,7 +517,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
516
517
|
type: "drawing",
|
|
517
518
|
image
|
|
518
519
|
};
|
|
519
|
-
if (!image.src) drawing.rawXml =
|
|
520
|
+
if (!image.src) drawing.rawXml = captureVerbatimXml(element);
|
|
520
521
|
return drawing;
|
|
521
522
|
}
|
|
522
523
|
/**
|
|
@@ -593,7 +594,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
593
594
|
if (groupedChoiceDrawing) {
|
|
594
595
|
const groupedDrawing = parseDrawingContent(groupedChoiceDrawing, rels, media);
|
|
595
596
|
if (groupedDrawing?.type === "drawing" && groupedDrawing.image.src) {
|
|
596
|
-
groupedDrawing.rawXml =
|
|
597
|
+
groupedDrawing.rawXml = captureVerbatimXml(child);
|
|
597
598
|
contents.push(groupedDrawing);
|
|
598
599
|
break;
|
|
599
600
|
}
|
|
@@ -601,7 +602,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
601
602
|
const fallbackPict = fallbackEl ? getChildElements(fallbackEl).find((el) => getLocalName(el.name) === "pict") : void 0;
|
|
602
603
|
const fallbackVml = fallbackPict && !choiceTextBoxDrawing ? parseVmlImageContent(fallbackPict, rels, media, rootXmlns) : null;
|
|
603
604
|
if (fallbackVml?.image.src) {
|
|
604
|
-
fallbackVml.rawXml =
|
|
605
|
+
fallbackVml.rawXml = captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns));
|
|
605
606
|
contents.push(fallbackVml);
|
|
606
607
|
break;
|
|
607
608
|
}
|
|
@@ -611,13 +612,13 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
611
612
|
if (innerName === "drawing") {
|
|
612
613
|
const innerDrawing = parseDrawingContent(innerChild, rels, media);
|
|
613
614
|
if (innerDrawing) {
|
|
614
|
-
if (innerDrawing.type === "drawing" && (innerDrawing.rawXml !== void 0 || !innerDrawing.image.src)) innerDrawing.rawXml =
|
|
615
|
+
if (innerDrawing.type === "drawing" && (innerDrawing.rawXml !== void 0 || !innerDrawing.image.src)) innerDrawing.rawXml = captureVerbatimXml(child);
|
|
615
616
|
contents.push(innerDrawing);
|
|
616
617
|
}
|
|
617
618
|
} else if (innerName === "pict") {
|
|
618
619
|
const innerVml = parseVmlImageContent(innerChild, rels, media, rootXmlns);
|
|
619
620
|
if (innerVml) {
|
|
620
|
-
innerVml.rawXml =
|
|
621
|
+
innerVml.rawXml = captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns));
|
|
621
622
|
contents.push(innerVml);
|
|
622
623
|
}
|
|
623
624
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SdtLockSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
-
import {
|
|
2
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
3
|
+
import { findChild, getAttributeAnyPrefix, getLocalName, parseBooleanElement } from "./xmlParser.js";
|
|
3
4
|
//#region src/docx/sdtProperties.ts
|
|
4
5
|
function parseListItems(el) {
|
|
5
6
|
const items = [];
|
|
@@ -201,7 +202,7 @@ function rewriteOpenTagAttrPrefixes(raw, canonical, local) {
|
|
|
201
202
|
function parseSdtProperties(sdtPr, sdtEndPr) {
|
|
202
203
|
const props = { sdtType: "richText" };
|
|
203
204
|
if (sdtPr) {
|
|
204
|
-
props.rawPropertiesXml = normalizeWordPrefix(
|
|
205
|
+
props.rawPropertiesXml = normalizeWordPrefix(captureVerbatimXml(sdtPr), sdtPr);
|
|
205
206
|
for (const el of sdtPr.elements ?? []) {
|
|
206
207
|
if (el.type !== "element") continue;
|
|
207
208
|
const name = el.name ? getLocalName(el.name) : "";
|
|
@@ -283,7 +284,7 @@ function parseSdtProperties(sdtPr, sdtEndPr) {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
|
-
if (sdtEndPr) props.rawEndPropertiesXml = normalizeWordPrefix(
|
|
287
|
+
if (sdtEndPr) props.rawEndPropertiesXml = normalizeWordPrefix(captureVerbatimXml(sdtEndPr), sdtEndPr);
|
|
287
288
|
return props;
|
|
288
289
|
}
|
|
289
290
|
//#endregion
|
|
@@ -13,6 +13,7 @@ import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExten
|
|
|
13
13
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
14
14
|
import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
|
|
15
15
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
16
|
+
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
16
17
|
//#region src/docx/selectiveSave.ts
|
|
17
18
|
/**
|
|
18
19
|
* Check if document content has new images (data: URL without rId) or
|
|
@@ -209,7 +210,6 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
209
210
|
if (!validateFolioDocumentModel(doc).valid) return null;
|
|
210
211
|
const comments = doc.package.document.comments ?? [];
|
|
211
212
|
const hasComments = comments.length > 0;
|
|
212
|
-
const headerFooterUpdates = collectHeaderFooterUpdates(doc);
|
|
213
213
|
try {
|
|
214
214
|
const zip = await (await import("jszip")).default.loadAsync(originalBuffer);
|
|
215
215
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && isUnsafePackagePath(path)) return null;
|
|
@@ -228,15 +228,16 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
228
228
|
if (unrouted === null || unrouted.size > 0) return null;
|
|
229
229
|
}
|
|
230
230
|
if (bodyChangedIds.size > 0) {
|
|
231
|
-
const patchedDocXml = buildPatchedDocumentXml(originalDocXml, serializeDocument(doc), bodyChangedIds);
|
|
231
|
+
const patchedDocXml = buildPatchedDocumentXml(originalDocXml, serializeDocument(doc, readRootNamespaceBindings(originalDocXml)), bodyChangedIds);
|
|
232
232
|
if (!patchedDocXml) return null;
|
|
233
233
|
updates.set("word/document.xml", patchedDocXml);
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
-
const
|
|
237
|
-
if (hasComments ||
|
|
236
|
+
const sourceCommentsFile = zip.file("word/comments.xml");
|
|
237
|
+
if (hasComments || sourceCommentsFile) {
|
|
238
238
|
ensureThreadedCommentParaIds(comments);
|
|
239
|
-
|
|
239
|
+
const sourceBindings = sourceCommentsFile ? readRootNamespaceBindings(await sourceCommentsFile.async("text")) : void 0;
|
|
240
|
+
updates.set("word/comments.xml", serializeComments(comments, sourceBindings));
|
|
240
241
|
}
|
|
241
242
|
if (hasComments) {
|
|
242
243
|
const ctFile = zip.file("[Content_Types].xml");
|
|
@@ -256,7 +257,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
256
257
|
}
|
|
257
258
|
if (!await patchCommentsExtended(zip, comments, updates)) return null;
|
|
258
259
|
await patchNumberingPart(zip, doc, updates);
|
|
259
|
-
for (const [path, xml] of
|
|
260
|
+
for (const [path, xml] of await collectHeaderFooterUpdates(doc, zip)) updates.set(path, xml);
|
|
260
261
|
await queueSettingsUpdates(zip, updates);
|
|
261
262
|
const corePropsFile = zip.file("docProps/core.xml");
|
|
262
263
|
if (corePropsFile) {
|
|
@@ -7,7 +7,7 @@ import { Comment } from "../../types/content.js";
|
|
|
7
7
|
* comment — leaving the previous file in place would otherwise re-emit
|
|
8
8
|
* the orphaned comment threads on every save.
|
|
9
9
|
*/
|
|
10
|
-
declare function serializeComments(comments: Comment[]): string;
|
|
10
|
+
declare function serializeComments(comments: Comment[], sourceBindings?: ReadonlyMap<string, string>): string;
|
|
11
11
|
/**
|
|
12
12
|
* Assign a deterministic `w14:paraId` to the LAST paragraph of every comment
|
|
13
13
|
* that needs a commentsExtended entry (a reply, a reply's parent, or a resolved
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { deterministicHexId } from "../../utils/hexId.js";
|
|
2
2
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
3
|
+
import { serializePartElement } from "./partNamespaces.js";
|
|
3
4
|
import { serializeTextFormatting } from "./runSerializer.js";
|
|
4
5
|
import { escapeXml } from "./xmlUtils.js";
|
|
5
6
|
//#region src/docx/serializer/commentSerializer.ts
|
|
@@ -36,12 +37,29 @@ function serializeComment(comment) {
|
|
|
36
37
|
xml += "</w:comment>";
|
|
37
38
|
return xml;
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const COMMENTS_BASELINE_PREFIXES = [
|
|
41
|
+
"wpc",
|
|
42
|
+
"mc",
|
|
43
|
+
"o",
|
|
44
|
+
"r",
|
|
45
|
+
"m",
|
|
46
|
+
"v",
|
|
47
|
+
"wp",
|
|
48
|
+
"w10",
|
|
49
|
+
"w",
|
|
50
|
+
"w14",
|
|
51
|
+
"wp14",
|
|
52
|
+
"wpg",
|
|
53
|
+
"wpi",
|
|
54
|
+
"wne",
|
|
55
|
+
"wps"
|
|
56
|
+
];
|
|
57
|
+
const COMMENTS_EXTENDED_BASELINE_PREFIXES = [
|
|
58
|
+
"mc",
|
|
59
|
+
"w",
|
|
60
|
+
"w15"
|
|
61
|
+
];
|
|
62
|
+
const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
|
|
45
63
|
/**
|
|
46
64
|
* Serialize comments array to comments.xml content. Returns a valid empty
|
|
47
65
|
* `<w:comments/>` document for an empty array so callers can overwrite an
|
|
@@ -49,18 +67,21 @@ const COMMENTS_HEADER = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
49
67
|
* comment — leaving the previous file in place would otherwise re-emit
|
|
50
68
|
* the orphaned comment threads on every save.
|
|
51
69
|
*/
|
|
52
|
-
function serializeComments(comments) {
|
|
70
|
+
function serializeComments(comments, sourceBindings) {
|
|
53
71
|
const topLevel = [];
|
|
54
72
|
const replies = [];
|
|
55
73
|
for (const c of comments) {
|
|
56
74
|
const { parentId } = c;
|
|
57
75
|
(parentId === null || parentId === void 0 ? topLevel : replies).push(c);
|
|
58
76
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
const body = topLevel.map((comment) => serializeComment(comment)).join("") + replies.map((reply) => serializeComment(reply)).join("");
|
|
78
|
+
return XML_DECLARATION + serializePartElement({
|
|
79
|
+
partPath: "word/comments.xml",
|
|
80
|
+
rootName: "w:comments",
|
|
81
|
+
baselinePrefixes: COMMENTS_BASELINE_PREFIXES,
|
|
82
|
+
sourceBindings,
|
|
83
|
+
body
|
|
84
|
+
});
|
|
64
85
|
}
|
|
65
86
|
/** The `w14:paraId` Word threads a comment by: its LAST paragraph's paraId. */
|
|
66
87
|
function commentThreadParaId(comment) {
|
|
@@ -145,7 +166,6 @@ function buildCommentExtendedEntries(comments) {
|
|
|
145
166
|
}
|
|
146
167
|
return entries.length > 0 ? entries : null;
|
|
147
168
|
}
|
|
148
|
-
const COMMENTS_EXTENDED_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w15:commentsEx xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:w15=\"http://schemas.microsoft.com/office/word/2012/wordml\" mc:Ignorable=\"w15\">";
|
|
149
169
|
/**
|
|
150
170
|
* Serialize `commentsExtended.xml` (`w15:commentsEx`) for reply threading and
|
|
151
171
|
* resolved state, or `null` when no comment needs an entry (see
|
|
@@ -155,13 +175,17 @@ const COMMENTS_EXTENDED_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" stand
|
|
|
155
175
|
function serializeCommentsExtended(comments) {
|
|
156
176
|
const entries = buildCommentExtendedEntries(comments);
|
|
157
177
|
if (!entries) return null;
|
|
158
|
-
|
|
159
|
-
for (const entry of entries) {
|
|
178
|
+
const body = entries.map((entry) => {
|
|
160
179
|
const parentAttr = entry.paraIdParent !== void 0 ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parentAttr} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
181
|
+
}).join("");
|
|
182
|
+
return XML_DECLARATION + serializePartElement({
|
|
183
|
+
partPath: "word/commentsExtended.xml",
|
|
184
|
+
rootName: "w15:commentsEx",
|
|
185
|
+
baselinePrefixes: COMMENTS_EXTENDED_BASELINE_PREFIXES,
|
|
186
|
+
sourceBindings: void 0,
|
|
187
|
+
body
|
|
188
|
+
});
|
|
165
189
|
}
|
|
166
190
|
//#endregion
|
|
167
191
|
export { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended };
|
|
@@ -11,9 +11,11 @@ declare function serializeDocumentBody(body: document_d_exports.DocumentBody): s
|
|
|
11
11
|
* Serialize a complete Document to valid document.xml
|
|
12
12
|
*
|
|
13
13
|
* @param doc - The document to serialize
|
|
14
|
+
* @param sourceBindings - Root `xmlns:*` of the part being replaced, so a
|
|
15
|
+
* prefix only the source document bound keeps its URI
|
|
14
16
|
* @returns Complete XML string for document.xml
|
|
15
17
|
*/
|
|
16
|
-
declare function serializeDocument(doc: document_d_exports.Document): string;
|
|
18
|
+
declare function serializeDocument(doc: document_d_exports.Document, sourceBindings?: ReadonlyMap<string, string>): string;
|
|
17
19
|
/**
|
|
18
20
|
* Serialize just the document body (useful for partial updates)
|
|
19
21
|
*
|
|
@@ -1,78 +1,41 @@
|
|
|
1
1
|
import { serializeBlockSdt } from "./blockSdtSerializer.js";
|
|
2
2
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
3
|
+
import { serializePartElement } from "./partNamespaces.js";
|
|
3
4
|
import { resetAutoIdCounter } from "./runSerializer.js";
|
|
4
5
|
import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
|
|
5
6
|
import { serializeTable } from "./tableSerializer.js";
|
|
6
7
|
//#region src/docx/serializer/documentSerializer.ts
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
|
|
39
|
-
w16: "http://schemas.microsoft.com/office/word/2018/wordml",
|
|
40
|
-
w16sdtdh: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
|
|
41
|
-
w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
|
|
42
|
-
wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
|
|
43
|
-
wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
|
|
44
|
-
wne: "http://schemas.microsoft.com/office/word/2006/wordml",
|
|
45
|
-
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Build namespace declaration string for document element
|
|
49
|
-
*/
|
|
50
|
-
function buildNamespaceDeclarations() {
|
|
51
|
-
const declared = {
|
|
52
|
-
a: NAMESPACES.a,
|
|
53
|
-
wpc: NAMESPACES.wpc,
|
|
54
|
-
mc: NAMESPACES.mc,
|
|
55
|
-
o: NAMESPACES.o,
|
|
56
|
-
pic: NAMESPACES.pic,
|
|
57
|
-
r: NAMESPACES.r,
|
|
58
|
-
m: NAMESPACES.m,
|
|
59
|
-
v: NAMESPACES.v,
|
|
60
|
-
wp14: NAMESPACES.wp14,
|
|
61
|
-
wp: NAMESPACES.wp,
|
|
62
|
-
w10: NAMESPACES.w10,
|
|
63
|
-
w: NAMESPACES.w,
|
|
64
|
-
w14: NAMESPACES.w14,
|
|
65
|
-
w15: NAMESPACES.w15,
|
|
66
|
-
w16: NAMESPACES.w16,
|
|
67
|
-
w16cex: NAMESPACES.w16cex,
|
|
68
|
-
w16cid: NAMESPACES.w16cid,
|
|
69
|
-
w16sdtdh: NAMESPACES.w16sdtdh,
|
|
70
|
-
w16se: NAMESPACES.w16se,
|
|
71
|
-
wpg: NAMESPACES.wpg,
|
|
72
|
-
wps: NAMESPACES.wps
|
|
73
|
-
};
|
|
74
|
-
return Object.entries(declared).map(([prefix, uri]) => `xmlns:${prefix}="${uri}"`).join(" ");
|
|
75
|
-
}
|
|
9
|
+
* Prefixes document.xml declares whether or not the body uses them.
|
|
10
|
+
*
|
|
11
|
+
* A canonical `<w:sdtPr>` legitimately carries `<w16sdtdh:dataHash>` /
|
|
12
|
+
* `<w16cex:*>` / `<w16cid:*>` children the parser stores opaquely, and the
|
|
13
|
+
* drawing prefixes host raw-replayed shapes, so the part keeps the shape Word
|
|
14
|
+
* writes even when this particular save emits none of them.
|
|
15
|
+
*/
|
|
16
|
+
const DOCUMENT_BASELINE_PREFIXES = [
|
|
17
|
+
"a",
|
|
18
|
+
"wpc",
|
|
19
|
+
"mc",
|
|
20
|
+
"o",
|
|
21
|
+
"pic",
|
|
22
|
+
"r",
|
|
23
|
+
"m",
|
|
24
|
+
"v",
|
|
25
|
+
"wp14",
|
|
26
|
+
"wp",
|
|
27
|
+
"w10",
|
|
28
|
+
"w",
|
|
29
|
+
"w14",
|
|
30
|
+
"w15",
|
|
31
|
+
"w16",
|
|
32
|
+
"w16cex",
|
|
33
|
+
"w16cid",
|
|
34
|
+
"w16sdtdh",
|
|
35
|
+
"w16se",
|
|
36
|
+
"wpg",
|
|
37
|
+
"wps"
|
|
38
|
+
];
|
|
76
39
|
/**
|
|
77
40
|
* Serialize a single block content item (paragraph, table, or block-level SDT).
|
|
78
41
|
*/
|
|
@@ -103,19 +66,20 @@ function serializeDocumentBody(body) {
|
|
|
103
66
|
* Serialize a complete Document to valid document.xml
|
|
104
67
|
*
|
|
105
68
|
* @param doc - The document to serialize
|
|
69
|
+
* @param sourceBindings - Root `xmlns:*` of the part being replaced, so a
|
|
70
|
+
* prefix only the source document bound keeps its URI
|
|
106
71
|
* @returns Complete XML string for document.xml
|
|
107
72
|
*/
|
|
108
|
-
function serializeDocument(doc) {
|
|
73
|
+
function serializeDocument(doc, sourceBindings) {
|
|
109
74
|
resetAutoIdCounter();
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return parts.join("");
|
|
75
|
+
const body = `<w:body>${serializeDocumentBody(doc.package.document)}</w:body>`;
|
|
76
|
+
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + serializePartElement({
|
|
77
|
+
partPath: "word/document.xml",
|
|
78
|
+
rootName: "w:document",
|
|
79
|
+
baselinePrefixes: DOCUMENT_BASELINE_PREFIXES,
|
|
80
|
+
sourceBindings,
|
|
81
|
+
body
|
|
82
|
+
});
|
|
119
83
|
}
|
|
120
84
|
/**
|
|
121
85
|
* Serialize just the document body (useful for partial updates)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { serializePartElement } from "./partNamespaces.js";
|
|
1
2
|
import { escapeXml } from "./xmlUtils.js";
|
|
2
3
|
//#region src/docx/serializer/fontTableSerializer.ts
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const serializeFontTableXml = (fontTable) => "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
5
|
+
partPath: "word/fontTable.xml",
|
|
6
|
+
rootName: "w:fonts",
|
|
7
|
+
baselinePrefixes: ["w"],
|
|
8
|
+
sourceBindings: void 0,
|
|
9
|
+
body: fontTable.fonts.map(serializeFont).join("")
|
|
10
|
+
});
|
|
8
11
|
const serializeFont = (font) => {
|
|
9
12
|
const parts = [];
|
|
10
13
|
if (font.altName) parts.push(`<w:altName w:val="${escapeXml(font.altName)}"/>`);
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
import { SourcePart } from "./partNamespaces.js";
|
|
2
3
|
//#region src/docx/serializer/headerFooterSerializer.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Serialize a HeaderFooter object to valid OOXML XML
|
|
5
6
|
*
|
|
6
7
|
* @param hf - HeaderFooter object to serialize
|
|
8
|
+
* @param source - The part being replaced, so a prefix only the source
|
|
9
|
+
* document bound keeps its URI
|
|
7
10
|
* @returns Complete XML string for header*.xml or footer*.xml
|
|
8
11
|
*/
|
|
9
|
-
declare function serializeHeaderFooter(hf: document_d_exports.HeaderFooter): string;
|
|
12
|
+
declare function serializeHeaderFooter(hf: document_d_exports.HeaderFooter, source?: SourcePart): string;
|
|
10
13
|
//#endregion
|
|
11
14
|
export { serializeHeaderFooter };
|