@stll/folio-core 0.44.0 → 0.46.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/__fixtures__/paragraphs.js +2 -2
- package/dist/ai-edits/apply.js +59 -19
- package/dist/ai-edits/headless.js +2 -1
- package/dist/compare/compare.js +2 -5
- package/dist/compare/content-alignment.js +78 -53
- package/dist/compare/inline-atoms.js +34 -20
- package/dist/compare/scenario.js +2 -5
- package/dist/compare/types.d.ts +9 -1
- package/dist/compare/types.js +12 -1
- package/dist/compat/eigenpal.d.ts +2 -1
- package/dist/compat/eigenpal.js +2 -1
- package/dist/content-controls/checkboxDisplay.d.ts +11 -0
- package/dist/content-controls/checkboxDisplay.js +40 -0
- package/dist/content-controls/findContentControls.js +3 -1
- package/dist/content-controls/mutateContentControls.js +21 -4
- package/dist/controller/contentControlWidgetController.d.ts +1 -0
- package/dist/controller/contentControlWidgetController.js +3 -0
- package/dist/controller/hiddenEditorApi.js +3 -1
- package/dist/controller/hiddenEditorManager.d.ts +1 -1
- package/dist/controller/hiddenEditorManager.js +6 -3
- package/dist/display-list/build/buildDisplayList.js +20 -1
- package/dist/display-list/build/imagePrimitives.d.ts +1 -1
- package/dist/display-list/build/imagePrimitives.js +3 -1
- package/dist/display-list/build/paragraphPrimitives.js +21 -2
- package/dist/display-list/dom/renderDisplayListToDom.js +18 -10
- package/dist/display-list/types.d.ts +17 -0
- package/dist/document-operations.js +14 -3
- package/dist/docx/attributeRemainder.d.ts +43 -0
- package/dist/docx/attributeRemainder.js +65 -0
- package/dist/docx/blockContentParser.d.ts +1 -1
- package/dist/docx/blockContentParser.js +87 -63
- package/dist/docx/blockPlainText.d.ts +3 -4
- package/dist/docx/blockPlainText.js +1 -0
- package/dist/docx/bookmarkPlacement.js +2 -0
- package/dist/docx/borderParser.js +5 -5
- package/dist/docx/commentAnchorIndex.d.ts +30 -0
- package/dist/docx/commentAnchorIndex.js +50 -0
- package/dist/docx/commentParser.d.ts +1 -1
- package/dist/docx/commentParser.js +78 -50
- package/dist/docx/commentRangeIntegrity.d.ts +16 -1
- package/dist/docx/commentRangeIntegrity.js +42 -1
- package/dist/docx/commentRangeJoin.d.ts +9 -0
- package/dist/docx/commentRangeJoin.js +33 -0
- package/dist/docx/commentReferenceCompletion.d.ts +9 -0
- package/dist/docx/commentReferenceCompletion.js +47 -0
- package/dist/docx/commentReferenceNormalization.js +1 -0
- package/dist/docx/commentReplyMarkers.js +11 -19
- package/dist/docx/commentThreadKey.d.ts +18 -0
- package/dist/docx/commentThreadKey.js +22 -0
- package/dist/docx/compatibility.js +1 -0
- package/dist/docx/containerChildren.d.ts +111 -0
- package/dist/docx/containerChildren.gen.d.ts +28 -0
- package/dist/docx/containerChildren.gen.js +246 -0
- package/dist/docx/containerChildren.js +103 -0
- package/dist/docx/diagramPreview.js +87 -27
- package/dist/docx/documentParser.d.ts +1 -1
- package/dist/docx/documentParser.js +3 -3
- package/dist/docx/ensureParaIds.js +17 -7
- package/dist/docx/fieldParser.d.ts +1 -1
- package/dist/docx/fieldParser.js +4 -4
- package/dist/docx/fieldState.d.ts +26 -0
- package/dist/docx/fieldState.js +53 -0
- package/dist/docx/footnoteParser.d.ts +1 -1
- package/dist/docx/graphicFrameLocks.d.ts +16 -4
- package/dist/docx/graphicFrameLocks.js +19 -6
- package/dist/docx/groupDrawingParser.js +3 -3
- package/dist/docx/headerFooterParser.js +1 -1
- package/dist/docx/headerFooterReferenceNormalization.js +1 -0
- package/dist/docx/hyperlinkParser.d.ts +27 -8
- package/dist/docx/hyperlinkParser.js +78 -20
- package/dist/docx/imageParser.d.ts +9 -1
- package/dist/docx/imageParser.js +91 -20
- package/dist/docx/imageRawXml.d.ts +14 -1
- package/dist/docx/imageRawXml.js +30 -6
- package/dist/docx/inlineWrapperContent.d.ts +85 -0
- package/dist/docx/inlineWrapperContent.js +84 -0
- package/dist/docx/mathToMathml.js +12 -14
- package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
- package/dist/docx/nonVisualDrawingProps.js +46 -0
- package/dist/docx/normalizeBaseDirection.js +10 -1
- package/dist/docx/paraIdAttribute.d.ts +21 -0
- package/dist/docx/paraIdAttribute.js +64 -0
- package/dist/docx/paragraphParser.d.ts +1 -1
- package/dist/docx/paragraphParser.js +277 -168
- package/dist/docx/paragraphPropertySource.js +5 -1
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +1 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +9 -52
- package/dist/docx/paragraphTraversal.js +7 -4
- package/dist/docx/parseWarningMessage.js +2 -1
- package/dist/docx/parser.js +2 -2
- package/dist/docx/preservedRunContent.d.ts +32 -0
- package/dist/docx/preservedRunContent.js +86 -0
- package/dist/docx/previewBudget.d.ts +64 -0
- package/dist/docx/previewBudget.js +88 -0
- package/dist/docx/renderedPageBreakNormalization.js +2 -4
- package/dist/docx/revisionIdNormalization.js +17 -5
- package/dist/docx/rezip.js +91 -36
- package/dist/docx/runParser.d.ts +1 -1
- package/dist/docx/runParser.js +16 -12
- package/dist/docx/sdtPropertiesPatch.js +24 -18
- package/dist/docx/sectionParser.js +8 -1
- package/dist/docx/sectionReferenceHistory.js +2 -2
- package/dist/docx/selectiveSave.js +6 -6
- package/dist/docx/selectiveXmlPatch.d.ts +46 -2
- package/dist/docx/selectiveXmlPatch.js +86 -39
- package/dist/docx/serializer/blockSdtSerializer.js +38 -26
- package/dist/docx/serializer/borderSerializer.d.ts +1 -1
- package/dist/docx/serializer/borderSerializer.js +15 -14
- package/dist/docx/serializer/commentSerializer.d.ts +41 -16
- package/dist/docx/serializer/commentSerializer.js +87 -92
- package/dist/docx/serializer/documentSerializer.js +8 -11
- package/dist/docx/serializer/fontTableSerializer.js +6 -6
- package/dist/docx/serializer/headerFooterSerializer.js +12 -14
- package/dist/docx/serializer/markupRangeAttributes.js +2 -2
- package/dist/docx/serializer/noteSerializer.js +8 -8
- package/dist/docx/serializer/numberingSerializer.js +7 -6
- package/dist/docx/serializer/paragraphSerializer.js +93 -72
- package/dist/docx/serializer/partNamespaces.js +2 -2
- package/dist/docx/serializer/runSerializer.js +73 -44
- package/dist/docx/serializer/sectionPropertiesSerializer.js +19 -14
- package/dist/docx/serializer/settingsSerializer.js +4 -3
- package/dist/docx/serializer/stylesSerializer.js +6 -6
- package/dist/docx/serializer/tableSerializer.js +28 -17
- package/dist/docx/serializer/textFormattingSerializer.js +29 -28
- package/dist/docx/serializer/themeSerializer.js +6 -6
- package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
- package/dist/docx/serializer/xmlUtils.d.ts +1 -2
- package/dist/docx/serializer/xmlUtils.js +1 -13
- package/dist/docx/server/boundedArchive.d.ts +12 -0
- package/dist/docx/server/boundedArchive.js +20 -1
- package/dist/docx/server/createBilingualDocument.js +3 -1
- package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
- package/dist/docx/server/materializeYjsDocx.js +9 -1
- package/dist/docx/server/migrateYjsAttrSchema.d.ts +55 -0
- package/dist/docx/server/migrateYjsAttrSchema.js +95 -0
- package/dist/docx/server/validateDocxConformance.js +22 -1
- package/dist/docx/shapeParser.js +12 -10
- package/dist/docx/tableParser.d.ts +1 -1
- package/dist/docx/tableParser.js +206 -85
- package/dist/docx/textBoxParser.d.ts +25 -2
- package/dist/docx/textBoxParser.js +59 -4
- package/dist/docx/unzip.d.ts +23 -0
- package/dist/docx/unzip.js +37 -26
- package/dist/docx/verbatimCapture.js +1 -1
- package/dist/docx/vmlImageParser.d.ts +17 -1
- package/dist/docx/vmlImageParser.js +20 -3
- package/dist/docx/vmlPreview.d.ts +1 -3
- package/dist/docx/vmlPreview.js +2 -30
- package/dist/docx/xmlEncoding.d.ts +5 -0
- package/dist/docx/xmlEncoding.js +12 -0
- package/dist/docx/xmlParser.d.ts +26 -2
- package/dist/docx/xmlParser.js +70 -27
- package/dist/docx/xmlResourceLimits.d.ts +89 -9
- package/dist/docx/xmlResourceLimits.js +105 -24
- package/dist/headless-layout.js +10 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +5 -2
- package/dist/internal/paragraphFormattingSerialization.js +3 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +102 -12
- package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
- package/dist/layout-engine/measure/tableCellFloating.js +2 -0
- package/dist/layout-engine/types.d.ts +23 -1
- package/dist/layout-painter/renderImage.d.ts +5 -1
- package/dist/layout-painter/renderImage.js +14 -6
- package/dist/layout-painter/renderPage.d.ts +2 -0
- package/dist/layout-painter/renderPage.js +4 -0
- package/dist/layout-painter/renderParagraph.d.ts +13 -1
- package/dist/layout-painter/renderParagraph.js +26 -6
- package/dist/managers/autoSaveCodec.d.ts +9 -1
- package/dist/managers/autoSaveCodec.js +14 -12
- package/dist/markdown/images.js +1 -4
- package/dist/markdown/renderBlock.js +1 -0
- package/dist/markdown/renderRuns.d.ts +1 -1
- package/dist/markdown/renderRuns.js +28 -5
- package/dist/markdown/renderTable.js +22 -5
- package/dist/markdown/trailers.js +4 -1
- package/dist/pdf/images.js +142 -6
- package/dist/prosemirror/attrs/index.d.ts +14 -3
- package/dist/prosemirror/attrs/index.js +224 -2
- package/dist/prosemirror/authoredTransformAttrs.d.ts +28 -0
- package/dist/prosemirror/authoredTransformAttrs.js +64 -0
- package/dist/prosemirror/commands/contentControls.js +10 -2
- package/dist/prosemirror/commands/image.d.ts +9 -2
- package/dist/prosemirror/commands/image.js +43 -28
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commentReferenceAttrs.d.ts +8 -0
- package/dist/prosemirror/commentReferenceAttrs.js +33 -0
- package/dist/prosemirror/commentReferenceIntegrity.d.ts +47 -0
- package/dist/prosemirror/commentReferenceIntegrity.js +43 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
- package/dist/prosemirror/conversion/fromProseDoc.js +338 -99
- package/dist/prosemirror/conversion/index.d.ts +2 -2
- package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -9
- package/dist/prosemirror/conversion/toProseDoc.js +551 -327
- package/dist/prosemirror/extensions/StarterKit.js +8 -58
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +45 -27
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -1
- package/dist/prosemirror/extensions/features/ImagePasteExtension.js +5 -3
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -0
- package/dist/prosemirror/extensions/markRegistry.d.ts +41 -0
- package/dist/prosemirror/extensions/markRegistry.js +75 -0
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.d.ts +18 -0
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +63 -0
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +47 -4
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.d.ts +11 -0
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +87 -0
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +9 -7
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +20 -0
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.d.ts +32 -0
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +67 -0
- package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.d.ts +16 -0
- package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +60 -0
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +10 -2
- package/dist/prosemirror/extensions/nodes/TableExtension.js +3 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +13 -5
- package/dist/prosemirror/inlineWrapperStack.d.ts +39 -0
- package/dist/prosemirror/inlineWrapperStack.js +75 -0
- package/dist/prosemirror/pageBreakRunProjection.d.ts +17 -7
- package/dist/prosemirror/pageBreakRunProjection.js +19 -9
- package/dist/prosemirror/paragraphFormattingProvenance.d.ts +6 -3
- package/dist/prosemirror/paragraphFormattingProvenance.js +12 -3
- package/dist/prosemirror/replacedAnnotations.d.ts +59 -0
- package/dist/prosemirror/replacedAnnotations.js +165 -0
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +3 -1
- package/dist/prosemirror/runFormattingInlineCarriers.js +5 -1
- package/dist/prosemirror/schema/index.d.ts +3 -3
- package/dist/prosemirror/schema/marks.d.ts +25 -1
- package/dist/prosemirror/schema/nodes.d.ts +139 -3
- package/dist/prosemirror/schema/nodes.js +16 -1
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +2 -0
- package/dist/prosemirror/trackedRunInlineAtoms.js +2 -0
- package/dist/prosemirror/validation.js +16 -2
- package/dist/prosemirror/yjsDocumentMetadata.d.ts +73 -0
- package/dist/prosemirror/yjsDocumentMetadata.js +171 -0
- package/dist/prosemirror/zeroWidthAnchors.js +2 -0
- package/dist/render-dom/commentAnchorAttributes.d.ts +37 -0
- package/dist/render-dom/commentAnchorAttributes.js +54 -0
- package/dist/server.d.ts +3 -1
- package/dist/server.js +3 -1
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/base64.d.ts +36 -0
- package/dist/utils/base64.js +40 -0
- package/dist/utils/clipboard.d.ts +9 -1
- package/dist/utils/clipboard.js +29 -2
- package/dist/utils/findReplace.js +1 -1
- package/dist/utils/imageLuminance.d.ts +15 -0
- package/dist/utils/imageLuminance.js +32 -0
- package/dist/utils/mergeDocumentContent.js +7 -17
- package/dist/utils/replaceText.js +1 -0
- package/dist/utils/units.d.ts +10 -1
- package/dist/utils/units.js +12 -1
- package/dist/utils/urlSecurity.d.ts +8 -2
- package/dist/utils/urlSecurity.js +21 -3
- package/package.json +2 -2
- package/dist/docx/blockRangeMarkers.d.ts +0 -36
- package/dist/docx/blockRangeMarkers.js +0 -59
- package/dist/prosemirror/yjsParagraphSourceContract.d.ts +0 -9
- package/dist/prosemirror/yjsParagraphSourceContract.js +0 -26
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { serializePreservedAttributes } from "../attributeRemainder.js";
|
|
1
2
|
import { getUnserializedSectionPropertyChildNames } from "../sectionParser.js";
|
|
2
3
|
import { serializeSectionReferenceHistory } from "../sectionReferenceHistory.js";
|
|
3
4
|
import { serializeBorder } from "./borderSerializer.js";
|
|
4
5
|
import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
|
|
5
|
-
import {
|
|
6
|
+
import { intAttr } from "./xmlUtils.js";
|
|
7
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
6
8
|
//#region src/docx/serializer/sectionPropertiesSerializer.ts
|
|
7
|
-
const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${
|
|
8
|
-
const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${
|
|
9
|
+
const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${escapeXmlAttribute(ref.rId)}"/>`;
|
|
10
|
+
const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${escapeXmlAttribute(ref.rId)}"/>`;
|
|
9
11
|
function serializeFootnoteProperties(props) {
|
|
10
12
|
if (!props) return "";
|
|
11
13
|
const parts = [];
|
|
@@ -52,12 +54,12 @@ function serializePaperSource(props) {
|
|
|
52
54
|
return attrs.length > 0 ? `<w:paperSrc ${attrs.join(" ")}/>` : "";
|
|
53
55
|
}
|
|
54
56
|
function serializeColumns(props) {
|
|
55
|
-
if (props.columnCount === void 0 && !props.columns?.length && props.columnSpace === void 0 && props.equalWidth === void 0) return "";
|
|
57
|
+
if (props.columnCount === void 0 && !props.columns?.length && props.columnSpace === void 0 && props.equalWidth === void 0 && props.separator === void 0) return "";
|
|
56
58
|
const attrs = [];
|
|
57
59
|
if (props.columnCount !== void 0 && props.columnCount >= 1) attrs.push(`w:num="${intAttr(props.columnCount)}"`);
|
|
58
60
|
if (props.columnSpace !== void 0) attrs.push(`w:space="${intAttr(props.columnSpace)}"`);
|
|
59
61
|
if (props.equalWidth !== void 0) attrs.push(`w:equalWidth="${props.equalWidth ? "1" : "0"}"`);
|
|
60
|
-
if (props.separator) attrs.push(
|
|
62
|
+
if (props.separator !== void 0) attrs.push(`w:sep="${props.separator ? "1" : "0"}"`);
|
|
61
63
|
const colElements = (props.columns ?? []).map((col) => {
|
|
62
64
|
const colAttrs = [];
|
|
63
65
|
if (col.width !== void 0) colAttrs.push(`w:w="${intAttr(col.width)}"`);
|
|
@@ -84,7 +86,7 @@ function serializePageNumbering(props) {
|
|
|
84
86
|
if (pageNumbering.format) attrs.push(`w:fmt="${pageNumbering.format}"`);
|
|
85
87
|
if (pageNumbering.start !== void 0) attrs.push(`w:start="${intAttr(pageNumbering.start)}"`);
|
|
86
88
|
if (pageNumbering.chapterStyle !== void 0) attrs.push(`w:chapStyle="${intAttr(pageNumbering.chapterStyle)}"`);
|
|
87
|
-
if (pageNumbering.chapterSeparator) attrs.push(`w:chapSep="${
|
|
89
|
+
if (pageNumbering.chapterSeparator) attrs.push(`w:chapSep="${escapeXmlAttribute(pageNumbering.chapterSeparator)}"`);
|
|
88
90
|
return attrs.length > 0 ? `<w:pgNumType ${attrs.join(" ")}/>` : "";
|
|
89
91
|
}
|
|
90
92
|
function serializePageBorders(props) {
|
|
@@ -114,10 +116,10 @@ function serializeBackground(props) {
|
|
|
114
116
|
const attrs = [];
|
|
115
117
|
const { background } = props;
|
|
116
118
|
if (background.color?.auto) attrs.push("w:color=\"auto\"");
|
|
117
|
-
else if (background.color?.rgb) attrs.push(`w:color="${
|
|
118
|
-
if (background.themeColor ?? background.color?.themeColor) attrs.push(`w:themeColor="${
|
|
119
|
-
if (background.themeTint ?? background.color?.themeTint) attrs.push(`w:themeTint="${
|
|
120
|
-
if (background.themeShade ?? background.color?.themeShade) attrs.push(`w:themeShade="${
|
|
119
|
+
else if (background.color?.rgb) attrs.push(`w:color="${escapeXmlAttribute(background.color.rgb)}"`);
|
|
120
|
+
if (background.themeColor ?? background.color?.themeColor) attrs.push(`w:themeColor="${escapeXmlAttribute(background.themeColor ?? background.color?.themeColor ?? "")}"`);
|
|
121
|
+
if (background.themeTint ?? background.color?.themeTint) attrs.push(`w:themeTint="${escapeXmlAttribute(background.themeTint ?? background.color?.themeTint ?? "")}"`);
|
|
122
|
+
if (background.themeShade ?? background.color?.themeShade) attrs.push(`w:themeShade="${escapeXmlAttribute(background.themeShade ?? background.color?.themeShade ?? "")}"`);
|
|
121
123
|
return attrs.length > 0 ? `<w:background ${attrs.join(" ")}/>` : "";
|
|
122
124
|
}
|
|
123
125
|
function serializeDocGrid(props) {
|
|
@@ -127,7 +129,7 @@ function serializeDocGrid(props) {
|
|
|
127
129
|
if (dg.type) attrs.push(`w:type="${dg.type}"`);
|
|
128
130
|
if (dg.linePitch !== void 0) attrs.push(`w:linePitch="${intAttr(dg.linePitch)}"`);
|
|
129
131
|
if (dg.charSpace !== void 0) attrs.push(`w:charSpace="${intAttr(dg.charSpace)}"`);
|
|
130
|
-
return attrs.length > 0 ? `<w:docGrid ${attrs.join(" ")}/>` : "";
|
|
132
|
+
return attrs.length > 0 ? `<w:docGrid ${attrs.join(" ")}/>` : "<w:docGrid/>";
|
|
131
133
|
}
|
|
132
134
|
function serializeOnOffElement(value, name) {
|
|
133
135
|
if (value === void 0) return "";
|
|
@@ -173,12 +175,15 @@ function serializeSectionProperties(props) {
|
|
|
173
175
|
serializeOnOffElement(props.noEndnote, "noEndnote"),
|
|
174
176
|
serializeOnOffElement(props.rtlGutter, "rtlGutter")
|
|
175
177
|
]) if (xml) parts.push(xml);
|
|
176
|
-
if (props.printerSettingsRelationshipId) parts.push(`<w:printerSettings r:id="${
|
|
178
|
+
if (props.printerSettingsRelationshipId) parts.push(`<w:printerSettings r:id="${escapeXmlAttribute(props.printerSettingsRelationshipId)}"/>`);
|
|
177
179
|
for (const change of props.propertyChanges ?? []) parts.push(serializeSectionPropertyChange(change));
|
|
178
180
|
const unserializedChildNames = getUnserializedSectionPropertyChildNames(props);
|
|
179
181
|
if (unserializedChildNames && unserializedChildNames.length > 0) return "";
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
const attributes = serializePreservedAttributes([], props.preservedAttributes);
|
|
183
|
+
const startTag = attributes.length > 0 ? `<w:sectPr ${attributes.join(" ")}` : "<w:sectPr";
|
|
184
|
+
if (parts.length > 0) return `${startTag}>${parts.join("")}</w:sectPr>`;
|
|
185
|
+
if (Object.keys(props).some((key) => key !== "preservedAttributes")) return "";
|
|
186
|
+
if (attributes.length > 0) return `${startTag}/>`;
|
|
182
187
|
return "<w:sectPr/>";
|
|
183
188
|
}
|
|
184
189
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { serializePartElement } from "./partNamespaces.js";
|
|
2
|
-
import {
|
|
2
|
+
import { intAttr } from "./xmlUtils.js";
|
|
3
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
3
4
|
//#region src/docx/serializer/settingsSerializer.ts
|
|
4
5
|
const serializeSettingsXml = (settings) => {
|
|
5
6
|
const parts = [`<w:defaultTabStop w:val="${intAttr(settings.defaultTabStop)}"/>`];
|
|
@@ -7,8 +8,8 @@ const serializeSettingsXml = (settings) => {
|
|
|
7
8
|
if (settings.updateFields) parts.push("<w:updateFields w:val=\"true\"/>");
|
|
8
9
|
if (settings.themeFontLang) {
|
|
9
10
|
const attrs = [];
|
|
10
|
-
if (settings.themeFontLang.eastAsia) attrs.push(`w:eastAsia="${
|
|
11
|
-
if (settings.themeFontLang.bidi) attrs.push(`w:bidi="${
|
|
11
|
+
if (settings.themeFontLang.eastAsia) attrs.push(`w:eastAsia="${escapeXmlAttribute(settings.themeFontLang.eastAsia)}"`);
|
|
12
|
+
if (settings.themeFontLang.bidi) attrs.push(`w:bidi="${escapeXmlAttribute(settings.themeFontLang.bidi)}"`);
|
|
12
13
|
if (attrs.length > 0) parts.push(`<w:themeFontLang ${attrs.join(" ")}/>`);
|
|
13
14
|
}
|
|
14
15
|
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
@@ -2,7 +2,7 @@ import { serializeParagraphFormatting } from "./paragraphSerializer.js";
|
|
|
2
2
|
import { serializePartElement } from "./partNamespaces.js";
|
|
3
3
|
import { serializeTableCellFormatting, serializeTableFormatting, serializeTableRowFormatting } from "./tableSerializer.js";
|
|
4
4
|
import { serializeTextFormatting } from "./textFormattingSerializer.js";
|
|
5
|
-
import {
|
|
5
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
6
6
|
//#region src/docx/serializer/stylesSerializer.ts
|
|
7
7
|
const serializeStylesXml = (definitions) => {
|
|
8
8
|
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
@@ -28,12 +28,12 @@ const serializeLatentStyles = (definitions) => {
|
|
|
28
28
|
return `<w:latentStyles${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}/>`;
|
|
29
29
|
};
|
|
30
30
|
const serializeStyle = (style) => {
|
|
31
|
-
const attrs = [`w:type="${style.type}"`, `w:styleId="${
|
|
31
|
+
const attrs = [`w:type="${style.type}"`, `w:styleId="${escapeXmlAttribute(style.styleId)}"`];
|
|
32
32
|
if (style.default) attrs.push("w:default=\"1\"");
|
|
33
|
-
const parts = [`<w:name w:val="${
|
|
34
|
-
if (style.basedOn) parts.push(`<w:basedOn w:val="${
|
|
35
|
-
if (style.next) parts.push(`<w:next w:val="${
|
|
36
|
-
if (style.link) parts.push(`<w:link w:val="${
|
|
33
|
+
const parts = [`<w:name w:val="${escapeXmlAttribute(style.name ?? style.styleId)}"/>`];
|
|
34
|
+
if (style.basedOn) parts.push(`<w:basedOn w:val="${escapeXmlAttribute(style.basedOn)}"/>`);
|
|
35
|
+
if (style.next) parts.push(`<w:next w:val="${escapeXmlAttribute(style.next)}"/>`);
|
|
36
|
+
if (style.link) parts.push(`<w:link w:val="${escapeXmlAttribute(style.link)}"/>`);
|
|
37
37
|
if (style.uiPriority !== void 0) parts.push(`<w:uiPriority w:val="${style.uiPriority}"/>`);
|
|
38
38
|
pushBooleanElement(parts, "hidden", style.hidden);
|
|
39
39
|
pushBooleanElement(parts, "semiHidden", style.semiHidden);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { canonicalJson } from "../../utils/canonicalJson.js";
|
|
2
2
|
import { isValidHexColor } from "../../utils/colorResolver.js";
|
|
3
|
-
import {
|
|
3
|
+
import { serializePreservedAttributes } from "../attributeRemainder.js";
|
|
4
|
+
import { serializeWithPreservedChildren } from "../containerChildren.js";
|
|
4
5
|
import { TABLE_LOOK_FLAGS } from "../tableLook.js";
|
|
5
6
|
import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTableRowProperties } from "../tableParser.js";
|
|
6
7
|
import { sanitizeCapturedXmlElement } from "../verbatimCapture.js";
|
|
7
8
|
import { NAMESPACES, OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
|
|
8
9
|
import { serializeBorder } from "./borderSerializer.js";
|
|
9
10
|
import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
|
|
10
|
-
import {
|
|
11
|
+
import { intAttr } from "./xmlUtils.js";
|
|
12
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
11
13
|
//#region src/docx/serializer/tableSerializer.ts
|
|
12
14
|
/**
|
|
13
15
|
* The captured element with the revision children written back into it.
|
|
@@ -130,15 +132,15 @@ function serializeCellMargins(margins, elementName) {
|
|
|
130
132
|
function serializeShading(shading) {
|
|
131
133
|
if (!shading) return "";
|
|
132
134
|
const attrs = [];
|
|
133
|
-
if (shading.pattern) attrs.push(`w:val="${
|
|
135
|
+
if (shading.pattern) attrs.push(`w:val="${escapeXmlAttribute(shading.pattern)}"`);
|
|
134
136
|
else attrs.push("w:val=\"clear\"");
|
|
135
|
-
if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${
|
|
137
|
+
if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXmlAttribute(shading.color.rgb)}"`);
|
|
136
138
|
else if (shading.color?.auto) attrs.push("w:color=\"auto\"");
|
|
137
|
-
if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${
|
|
139
|
+
if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXmlAttribute(shading.fill.rgb)}"`);
|
|
138
140
|
else if (shading.fill?.auto) attrs.push("w:fill=\"auto\"");
|
|
139
|
-
if (shading.fill?.themeColor) attrs.push(`w:themeFill="${
|
|
140
|
-
if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${
|
|
141
|
-
if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${
|
|
141
|
+
if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXmlAttribute(shading.fill.themeColor)}"`);
|
|
142
|
+
if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXmlAttribute(shading.fill.themeTint)}"`);
|
|
143
|
+
if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXmlAttribute(shading.fill.themeShade)}"`);
|
|
142
144
|
if (attrs.length === 0) return "";
|
|
143
145
|
return `<w:shd ${attrs.join(" ")}/>`;
|
|
144
146
|
}
|
|
@@ -154,7 +156,7 @@ function serializeShading(shading) {
|
|
|
154
156
|
function serializeTableLook(look) {
|
|
155
157
|
if (!look) return "";
|
|
156
158
|
const attrs = [];
|
|
157
|
-
if (look.val !== void 0) attrs.push(`w:val="${
|
|
159
|
+
if (look.val !== void 0) attrs.push(`w:val="${escapeXmlAttribute(look.val)}"`);
|
|
158
160
|
for (const flag of TABLE_LOOK_FLAGS) {
|
|
159
161
|
const stated = look[flag];
|
|
160
162
|
if (stated !== void 0) attrs.push(`w:${flag}="${stated ? "1" : "0"}"`);
|
|
@@ -189,7 +191,7 @@ function serializeTableFormatting(formatting, propertyChanges) {
|
|
|
189
191
|
if (tableSource !== null) return withRevisionChildren(tableSource, (propertyChanges ?? []).map((change) => serializeTablePropertyChange(change)));
|
|
190
192
|
const parts = [];
|
|
191
193
|
if (formatting) {
|
|
192
|
-
if (formatting.styleId) parts.push(`<w:tblStyle w:val="${
|
|
194
|
+
if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXmlAttribute(formatting.styleId)}"/>`);
|
|
193
195
|
const floatingXml = serializeFloatingTableProperties(formatting.floating);
|
|
194
196
|
if (floatingXml) parts.push(floatingXml);
|
|
195
197
|
if (formatting.overlap) parts.push(`<w:tblOverlap w:val="${formatting.overlap}"/>`);
|
|
@@ -368,8 +370,14 @@ const replayableGridChangeXml = (gridChangeXml) => sanitizeCapturedXmlElement(gr
|
|
|
368
370
|
* Serialize cell content (paragraphs, nested tables)
|
|
369
371
|
*/
|
|
370
372
|
function serializeCellContent(content, serializeParagraph) {
|
|
371
|
-
const parts =
|
|
372
|
-
|
|
373
|
+
const parts = content.map((block) => {
|
|
374
|
+
switch (block.type) {
|
|
375
|
+
case "paragraph": return serializeParagraph(block);
|
|
376
|
+
case "table": return serializeTable(block, serializeParagraph);
|
|
377
|
+
case "preservedBlock": return block.xml;
|
|
378
|
+
default: return block;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
373
381
|
if (parts.length === 0) parts.push("<w:p/>");
|
|
374
382
|
return parts.join("");
|
|
375
383
|
}
|
|
@@ -390,8 +398,9 @@ function serializeTableRow(row, serializeParagraph) {
|
|
|
390
398
|
const parts = [];
|
|
391
399
|
const trPrXml = serializeTableRowFormatting(row.formatting, row.propertyChanges, row.structuralChange);
|
|
392
400
|
if (trPrXml) parts.push(trPrXml);
|
|
393
|
-
|
|
394
|
-
|
|
401
|
+
parts.push(serializeWithPreservedChildren(row.cells.map((cell) => serializeTableCell(cell, serializeParagraph)), row.preserved));
|
|
402
|
+
const attrs = serializePreservedAttributes([], row.preservedAttributes);
|
|
403
|
+
return `<w:tr${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${parts.join("")}</w:tr>`;
|
|
395
404
|
}
|
|
396
405
|
/**
|
|
397
406
|
* Serialize a table to OOXML XML (w:tbl)
|
|
@@ -400,9 +409,11 @@ function serializeTableRow(row, serializeParagraph) {
|
|
|
400
409
|
* @returns XML string for the table
|
|
401
410
|
*/
|
|
402
411
|
function serializeTable(table, serializeParagraph) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
412
|
+
return `<w:tbl>${[
|
|
413
|
+
serializeTableFormatting(table.formatting, table.propertyChanges) || "<w:tblPr/>",
|
|
414
|
+
serializeTableGrid(table),
|
|
415
|
+
serializeWithPreservedChildren(table.rows.map((row) => serializeTableRow(row, serializeParagraph)), table.preserved)
|
|
416
|
+
].join("")}</w:tbl>`;
|
|
406
417
|
}
|
|
407
418
|
/**
|
|
408
419
|
* Serialize multiple tables to OOXML XML
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { HIGHLIGHT_COLOR_VALUES } from "../../types/documentEnumValues.js";
|
|
2
2
|
import { isValidHexColor } from "../../utils/colorResolver.js";
|
|
3
3
|
import { roundHorizontalScalePercentForSerialization } from "../../utils/horizontalScale.js";
|
|
4
|
-
import {
|
|
4
|
+
import { intAttr } from "./xmlUtils.js";
|
|
5
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
5
6
|
//#region src/docx/serializer/textFormattingSerializer.ts
|
|
6
7
|
const VALID_HIGHLIGHT_COLORS = new Set(HIGHLIGHT_COLOR_VALUES);
|
|
7
8
|
/**
|
|
@@ -12,10 +13,10 @@ function serializeColorElement(color) {
|
|
|
12
13
|
const { auto, rgb, themeColor, themeTint, themeShade } = color;
|
|
13
14
|
const attrs = [];
|
|
14
15
|
if (auto) attrs.push("w:val=\"auto\"");
|
|
15
|
-
else if (rgb && isValidHexColor(rgb)) attrs.push(`w:val="${
|
|
16
|
-
if (themeColor) attrs.push(`w:themeColor="${
|
|
17
|
-
if (themeTint) attrs.push(`w:themeTint="${
|
|
18
|
-
if (themeShade) attrs.push(`w:themeShade="${
|
|
16
|
+
else if (rgb && isValidHexColor(rgb)) attrs.push(`w:val="${escapeXmlAttribute(rgb)}"`);
|
|
17
|
+
if (themeColor) attrs.push(`w:themeColor="${escapeXmlAttribute(themeColor)}"`);
|
|
18
|
+
if (themeTint) attrs.push(`w:themeTint="${escapeXmlAttribute(themeTint)}"`);
|
|
19
|
+
if (themeShade) attrs.push(`w:themeShade="${escapeXmlAttribute(themeShade)}"`);
|
|
19
20
|
return attrs.length === 0 ? "" : `<w:color ${attrs.join(" ")}/>`;
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
@@ -25,20 +26,20 @@ function serializeShading(shading) {
|
|
|
25
26
|
if (!shading) return "";
|
|
26
27
|
const { pattern, color, fill } = shading;
|
|
27
28
|
const attrs = [];
|
|
28
|
-
if (pattern) attrs.push(`w:val="${
|
|
29
|
+
if (pattern) attrs.push(`w:val="${escapeXmlAttribute(pattern)}"`);
|
|
29
30
|
else attrs.push("w:val=\"clear\"");
|
|
30
31
|
if (color) {
|
|
31
32
|
const { rgb, auto, themeColor: _themeColor, themeTint: _themeTint, themeShade: _themeShade } = color;
|
|
32
|
-
if (rgb && isValidHexColor(rgb)) attrs.push(`w:color="${
|
|
33
|
+
if (rgb && isValidHexColor(rgb)) attrs.push(`w:color="${escapeXmlAttribute(rgb)}"`);
|
|
33
34
|
else if (auto) attrs.push("w:color=\"auto\"");
|
|
34
35
|
}
|
|
35
36
|
if (fill) {
|
|
36
37
|
const { rgb, auto, themeColor, themeTint, themeShade } = fill;
|
|
37
|
-
if (rgb && isValidHexColor(rgb)) attrs.push(`w:fill="${
|
|
38
|
+
if (rgb && isValidHexColor(rgb)) attrs.push(`w:fill="${escapeXmlAttribute(rgb)}"`);
|
|
38
39
|
else if (auto) attrs.push("w:fill=\"auto\"");
|
|
39
|
-
if (themeColor) attrs.push(`w:themeFill="${
|
|
40
|
-
if (themeTint) attrs.push(`w:themeFillTint="${
|
|
41
|
-
if (themeShade) attrs.push(`w:themeFillShade="${
|
|
40
|
+
if (themeColor) attrs.push(`w:themeFill="${escapeXmlAttribute(themeColor)}"`);
|
|
41
|
+
if (themeTint) attrs.push(`w:themeFillTint="${escapeXmlAttribute(themeTint)}"`);
|
|
42
|
+
if (themeShade) attrs.push(`w:themeFillShade="${escapeXmlAttribute(themeShade)}"`);
|
|
42
43
|
}
|
|
43
44
|
return attrs.length === 0 ? "" : `<w:shd ${attrs.join(" ")}/>`;
|
|
44
45
|
}
|
|
@@ -49,19 +50,19 @@ function serializeTextFormatting(input) {
|
|
|
49
50
|
if (!input) return "";
|
|
50
51
|
const { bold, boldCs, italic, italicCs, underline, strike, doubleStrike, vertAlign, smallCaps, allCaps, hidden, noProof, color, highlight, shading, fontSize, fontSizeCs, fontFamily, language, spacing, position, scale, kerning, effect, emphasisMark, emboss, imprint, outline, shadow, rtl, cs, styleId } = input;
|
|
51
52
|
const parts = [];
|
|
52
|
-
if (styleId) parts.push(`<w:rStyle w:val="${
|
|
53
|
+
if (styleId) parts.push(`<w:rStyle w:val="${escapeXmlAttribute(styleId)}"/>`);
|
|
53
54
|
if (fontFamily) {
|
|
54
55
|
const { ascii, hAnsi, eastAsia, cs: complexScript, hint, asciiTheme, hAnsiTheme, eastAsiaTheme, csTheme } = fontFamily;
|
|
55
56
|
const fontAttrs = [];
|
|
56
|
-
if (ascii) fontAttrs.push(`w:ascii="${
|
|
57
|
-
if (hAnsi) fontAttrs.push(`w:hAnsi="${
|
|
58
|
-
if (eastAsia) fontAttrs.push(`w:eastAsia="${
|
|
59
|
-
if (complexScript) fontAttrs.push(`w:cs="${
|
|
60
|
-
if (hint) fontAttrs.push(`w:hint="${
|
|
61
|
-
if (asciiTheme) fontAttrs.push(`w:asciiTheme="${
|
|
62
|
-
if (hAnsiTheme) fontAttrs.push(`w:hAnsiTheme="${
|
|
63
|
-
if (eastAsiaTheme) fontAttrs.push(`w:eastAsiaTheme="${
|
|
64
|
-
if (csTheme) fontAttrs.push(`w:cstheme="${
|
|
57
|
+
if (ascii) fontAttrs.push(`w:ascii="${escapeXmlAttribute(ascii)}"`);
|
|
58
|
+
if (hAnsi) fontAttrs.push(`w:hAnsi="${escapeXmlAttribute(hAnsi)}"`);
|
|
59
|
+
if (eastAsia) fontAttrs.push(`w:eastAsia="${escapeXmlAttribute(eastAsia)}"`);
|
|
60
|
+
if (complexScript) fontAttrs.push(`w:cs="${escapeXmlAttribute(complexScript)}"`);
|
|
61
|
+
if (hint) fontAttrs.push(`w:hint="${escapeXmlAttribute(hint)}"`);
|
|
62
|
+
if (asciiTheme) fontAttrs.push(`w:asciiTheme="${escapeXmlAttribute(asciiTheme)}"`);
|
|
63
|
+
if (hAnsiTheme) fontAttrs.push(`w:hAnsiTheme="${escapeXmlAttribute(hAnsiTheme)}"`);
|
|
64
|
+
if (eastAsiaTheme) fontAttrs.push(`w:eastAsiaTheme="${escapeXmlAttribute(eastAsiaTheme)}"`);
|
|
65
|
+
if (csTheme) fontAttrs.push(`w:cstheme="${escapeXmlAttribute(csTheme)}"`);
|
|
65
66
|
if (fontAttrs.length > 0) parts.push(`<w:rFonts ${fontAttrs.join(" ")}/>`);
|
|
66
67
|
}
|
|
67
68
|
if (bold === true) parts.push("<w:b/>");
|
|
@@ -114,10 +115,10 @@ function serializeTextFormatting(input) {
|
|
|
114
115
|
const uAttrs = [`w:val="${style}"`];
|
|
115
116
|
if (underlineColor) {
|
|
116
117
|
const { rgb, themeColor, themeTint, themeShade, auto: _auto } = underlineColor;
|
|
117
|
-
if (rgb && isValidHexColor(rgb)) uAttrs.push(`w:color="${
|
|
118
|
-
if (themeColor) uAttrs.push(`w:themeColor="${
|
|
119
|
-
if (themeTint) uAttrs.push(`w:themeTint="${
|
|
120
|
-
if (themeShade) uAttrs.push(`w:themeShade="${
|
|
118
|
+
if (rgb && isValidHexColor(rgb)) uAttrs.push(`w:color="${escapeXmlAttribute(rgb)}"`);
|
|
119
|
+
if (themeColor) uAttrs.push(`w:themeColor="${escapeXmlAttribute(themeColor)}"`);
|
|
120
|
+
if (themeTint) uAttrs.push(`w:themeTint="${escapeXmlAttribute(themeTint)}"`);
|
|
121
|
+
if (themeShade) uAttrs.push(`w:themeShade="${escapeXmlAttribute(themeShade)}"`);
|
|
121
122
|
}
|
|
122
123
|
parts.push(`<w:u ${uAttrs.join(" ")}/>`);
|
|
123
124
|
}
|
|
@@ -133,9 +134,9 @@ function serializeTextFormatting(input) {
|
|
|
133
134
|
if (language) {
|
|
134
135
|
const { val, eastAsia, bidi } = language;
|
|
135
136
|
const languageAttrs = [];
|
|
136
|
-
if (val) languageAttrs.push(`w:val="${
|
|
137
|
-
if (eastAsia) languageAttrs.push(`w:eastAsia="${
|
|
138
|
-
if (bidi) languageAttrs.push(`w:bidi="${
|
|
137
|
+
if (val) languageAttrs.push(`w:val="${escapeXmlAttribute(val)}"`);
|
|
138
|
+
if (eastAsia) languageAttrs.push(`w:eastAsia="${escapeXmlAttribute(eastAsia)}"`);
|
|
139
|
+
if (bidi) languageAttrs.push(`w:bidi="${escapeXmlAttribute(bidi)}"`);
|
|
139
140
|
if (languageAttrs.length > 0) parts.push(`<w:lang ${languageAttrs.join(" ")}/>`);
|
|
140
141
|
}
|
|
141
142
|
return parts.length === 0 ? "" : `<w:rPr>${parts.join("")}</w:rPr>`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { serializePartElement } from "./partNamespaces.js";
|
|
2
|
-
import {
|
|
2
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
3
3
|
//#region src/docx/serializer/themeSerializer.ts
|
|
4
4
|
const serializeThemeXml = (theme) => {
|
|
5
|
-
const name =
|
|
5
|
+
const name = escapeXmlAttribute(theme.name ?? "Folio Theme");
|
|
6
6
|
const elements = `${serializeColorScheme(theme.colorScheme)}${serializeFontScheme(theme)}${serializeFormatScheme(theme)}`;
|
|
7
7
|
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
|
|
8
8
|
partPath: "word/theme/theme1.xml",
|
|
@@ -28,17 +28,17 @@ const serializeColorScheme = (colors) => {
|
|
|
28
28
|
hlink: colors?.hlink ?? "0563C1",
|
|
29
29
|
folHlink: colors?.folHlink ?? "954F72"
|
|
30
30
|
};
|
|
31
|
-
return `<a:clrScheme name="Folio">${Object.entries(values).map(([slot, value]) => `<a:${slot}><a:srgbClr val="${
|
|
31
|
+
return `<a:clrScheme name="Folio">${Object.entries(values).map(([slot, value]) => `<a:${slot}><a:srgbClr val="${escapeXmlAttribute(value)}"/></a:${slot}>`).join("")}</a:clrScheme>`;
|
|
32
32
|
};
|
|
33
33
|
const serializeFontScheme = (theme) => {
|
|
34
34
|
return `<a:fontScheme name="Folio">${serializeThemeFont(theme.fontScheme?.majorFont, "majorFont", "Arial")}${serializeThemeFont(theme.fontScheme?.minorFont, "minorFont", "Arial")}</a:fontScheme>`;
|
|
35
35
|
};
|
|
36
36
|
const serializeThemeFont = (font, element, fallback) => {
|
|
37
|
-
const scriptFonts = Object.entries(font?.fonts ?? {}).map(([script, typeface]) => `<a:font script="${
|
|
38
|
-
return `<a:${element}><a:latin typeface="${
|
|
37
|
+
const scriptFonts = Object.entries(font?.fonts ?? {}).map(([script, typeface]) => `<a:font script="${escapeXmlAttribute(script)}" typeface="${escapeXmlAttribute(typeface)}"/>`).join("");
|
|
38
|
+
return `<a:${element}><a:latin typeface="${escapeXmlAttribute(font?.latin ?? fallback)}"/><a:ea typeface="${escapeXmlAttribute(font?.ea ?? "")}"/><a:cs typeface="${escapeXmlAttribute(font?.cs ?? "")}"/>${scriptFonts}</a:${element}>`;
|
|
39
39
|
};
|
|
40
40
|
const serializeFormatScheme = (theme) => {
|
|
41
|
-
return `<a:fmtScheme name="${
|
|
41
|
+
return `<a:fmtScheme name="${escapeXmlAttribute(theme.formatScheme?.name ?? "Folio")}"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst></a:fmtScheme>`;
|
|
42
42
|
};
|
|
43
43
|
//#endregion
|
|
44
44
|
export { serializeThemeXml };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DATE_UTC_ATTRIBUTE } from "../trackedChangeInfo.js";
|
|
2
|
-
import { escapeXml } from "./xmlUtils.js";
|
|
3
2
|
import { panic } from "better-result";
|
|
3
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
4
4
|
import { normalizeRevisionId } from "@stll/docx-core/model";
|
|
5
5
|
//#region src/docx/serializer/trackedChangeAttributes.ts
|
|
6
6
|
/** Enforces the singular `w:rPrChange` child in a run-property container. */
|
|
@@ -26,6 +26,6 @@ const trackedChangeAttributeEntries = (info) => {
|
|
|
26
26
|
return entries;
|
|
27
27
|
};
|
|
28
28
|
const trackedChangeAttributeRecord = (info) => Object.fromEntries(trackedChangeAttributeEntries(info));
|
|
29
|
-
const serializeTrackedChangeAttributes = (info) => trackedChangeAttributeEntries(info).map(([name, value]) => `${name}="${
|
|
29
|
+
const serializeTrackedChangeAttributes = (info) => trackedChangeAttributeEntries(info).map(([name, value]) => `${name}="${escapeXmlAttribute(value)}"`).join(" ");
|
|
30
30
|
//#endregion
|
|
31
31
|
export { getSingularRunPropertyChange, serializeTrackedChangeAttributes, trackedChangeAttributeEntries, trackedChangeAttributeRecord };
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Shared XML utility functions for serializers.
|
|
4
4
|
*/
|
|
5
|
-
declare function escapeXml(text: string): string;
|
|
6
5
|
/**
|
|
7
6
|
* Format a numeric value as an integer XML attribute.
|
|
8
7
|
*
|
|
@@ -29,4 +28,4 @@ declare function intAttr(value: number | undefined | null): string;
|
|
|
29
28
|
*/
|
|
30
29
|
declare function isSingleWellFormedElement(xml: string, expectedLocalName: string): boolean;
|
|
31
30
|
//#endregion
|
|
32
|
-
export {
|
|
31
|
+
export { intAttr, isSingleWellFormedElement };
|
|
@@ -3,18 +3,6 @@ import { getLocalName, parseXml } from "../xmlParser.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Shared XML utility functions for serializers.
|
|
5
5
|
*/
|
|
6
|
-
const XML_SPECIAL_CHARACTER_PATTERN = /[&<>"']/u;
|
|
7
|
-
const XML_SPECIAL_CHARACTER_GLOBAL_PATTERN = /[&<>"']/gu;
|
|
8
|
-
function escapeXml(text) {
|
|
9
|
-
if (!XML_SPECIAL_CHARACTER_PATTERN.test(text)) return text;
|
|
10
|
-
return text.replace(XML_SPECIAL_CHARACTER_GLOBAL_PATTERN, (character) => {
|
|
11
|
-
if (character === "&") return "&";
|
|
12
|
-
if (character === "<") return "<";
|
|
13
|
-
if (character === ">") return ">";
|
|
14
|
-
if (character === "\"") return """;
|
|
15
|
-
return "'";
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
6
|
/**
|
|
19
7
|
* Format a numeric value as an integer XML attribute.
|
|
20
8
|
*
|
|
@@ -57,4 +45,4 @@ function isSingleWellFormedElement(xml, expectedLocalName) {
|
|
|
57
45
|
return root?.name !== void 0 && getLocalName(root.name) === expectedLocalName;
|
|
58
46
|
}
|
|
59
47
|
//#endregion
|
|
60
|
-
export {
|
|
48
|
+
export { intAttr, isSingleWellFormedElement };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { XmlResourceLimits } from "../xmlResourceLimits.js";
|
|
1
2
|
import JSZip from "jszip";
|
|
2
3
|
//#region src/docx/server/boundedArchive.d.ts
|
|
3
4
|
declare module "jszip" {
|
|
@@ -27,6 +28,17 @@ type DocxArchiveOptions = {
|
|
|
27
28
|
maxEntryBytes?: number;
|
|
28
29
|
maxTotalBytes?: number;
|
|
29
30
|
maxEntries?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Bounds on parsed XML structure, applied to every XML part this archive
|
|
33
|
+
* hands out as a string.
|
|
34
|
+
*
|
|
35
|
+
* Every consumer of `readEntryString` parses the result into an object tree,
|
|
36
|
+
* and the byte caps above bound the markup, not the tree. Enforcing the
|
|
37
|
+
* element and attribute bounds here rather than at each parse site means a
|
|
38
|
+
* new consumer is bounded by construction: there is no way to obtain a part
|
|
39
|
+
* string from this archive that has not been counted.
|
|
40
|
+
*/
|
|
41
|
+
xmlLimits?: Partial<XmlResourceLimits>;
|
|
30
42
|
};
|
|
31
43
|
type DocxArchiveEntry = {
|
|
32
44
|
readonly path: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FOLIO_XML_RESOURCE_LIMITS, assertXmlResourceLimits, createXmlPackageBudget } from "../xmlResourceLimits.js";
|
|
1
2
|
import { TaggedError } from "better-result";
|
|
2
3
|
import JSZip from "jszip";
|
|
3
4
|
//#region src/docx/server/boundedArchive.ts
|
|
@@ -117,6 +118,12 @@ const loadDocxArchive = async (bytes, options = {}) => {
|
|
|
117
118
|
message: `DOCX archive declares ${declaredTotalBytes} cumulative bytes (max ${maxTotalBytes})`,
|
|
118
119
|
reason: "total-too-large"
|
|
119
120
|
});
|
|
121
|
+
const xmlLimits = {
|
|
122
|
+
...FOLIO_XML_RESOURCE_LIMITS,
|
|
123
|
+
...options.xmlLimits
|
|
124
|
+
};
|
|
125
|
+
const xmlBudget = createXmlPackageBudget();
|
|
126
|
+
const countedParts = /* @__PURE__ */ new Set();
|
|
120
127
|
let totalBytesRead = 0;
|
|
121
128
|
let readChain = Promise.resolve();
|
|
122
129
|
const readEntry = async (path, readOptions = {}) => {
|
|
@@ -151,7 +158,19 @@ const loadDocxArchive = async (bytes, options = {}) => {
|
|
|
151
158
|
}))),
|
|
152
159
|
async readEntryString(path) {
|
|
153
160
|
const content = await readEntry(path);
|
|
154
|
-
|
|
161
|
+
if (content === null) return null;
|
|
162
|
+
const xml = new TextDecoder("utf-8", { ignoreBOM: true }).decode(content);
|
|
163
|
+
const lower = path.toLowerCase();
|
|
164
|
+
if (!lower.endsWith(".xml") && !lower.endsWith(".rels")) return xml;
|
|
165
|
+
const charged = countedParts.has(path);
|
|
166
|
+
countedParts.add(path);
|
|
167
|
+
assertXmlResourceLimits({
|
|
168
|
+
xml,
|
|
169
|
+
limits: xmlLimits,
|
|
170
|
+
partPath: path,
|
|
171
|
+
...charged ? {} : { budget: xmlBudget }
|
|
172
|
+
});
|
|
173
|
+
return xml;
|
|
155
174
|
},
|
|
156
175
|
readEntryUint8: readEntry
|
|
157
176
|
};
|
|
@@ -203,6 +203,7 @@ const flattenBlocks = (content) => {
|
|
|
203
203
|
out.push(block);
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
|
+
if (block.type === "preservedBlock") return;
|
|
206
207
|
for (const child of block.content) visit(child);
|
|
207
208
|
};
|
|
208
209
|
for (const block of content) visit(block);
|
|
@@ -462,7 +463,7 @@ const remapListRendering = (rendering, cloner) => {
|
|
|
462
463
|
const collectTableParagraphs = (table) => {
|
|
463
464
|
const out = [];
|
|
464
465
|
for (const row of table.rows) for (const cell of row.cells) for (const item of cell.content) if (item.type === "paragraph") out.push(item);
|
|
465
|
-
else out.push(...collectTableParagraphs(item));
|
|
466
|
+
else if (item.type === "table") out.push(...collectTableParagraphs(item));
|
|
466
467
|
return out;
|
|
467
468
|
};
|
|
468
469
|
const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner, bookmarkIds }) => {
|
|
@@ -475,6 +476,7 @@ const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner
|
|
|
475
476
|
...structuredClone(cell),
|
|
476
477
|
content: cell.content.map((item) => {
|
|
477
478
|
if (item.type === "table") return cloneTable(item);
|
|
479
|
+
if (item.type !== "paragraph") return structuredClone(item);
|
|
478
480
|
const targetParaId = paraIds.mint(item.paraId);
|
|
479
481
|
const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
|
|
480
482
|
if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
|
|
@@ -4,7 +4,7 @@ declare const FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME = "prosemirror";
|
|
|
4
4
|
/** Maximum accepted size of a complete Yjs collaboration state update. */
|
|
5
5
|
declare const FOLIO_YJS_UPDATE_MAX_BYTES: number;
|
|
6
6
|
/** Stable failure codes returned by server-side Yjs-to-DOCX materialization. */
|
|
7
|
-
declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "source_mismatch", "update_too_large"];
|
|
7
|
+
declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "source_mismatch", "stale_attr_schema", "update_too_large"];
|
|
8
8
|
/** Failure code for a rejected Yjs-to-DOCX materialization request. */
|
|
9
9
|
type FolioYjsDocxMaterializationErrorCode = (typeof FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES)[number];
|
|
10
10
|
declare const FolioYjsDocxMaterializationError_base: import("better-result").TaggedErrorClass<"FolioYjsDocxMaterializationError">;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fromProseDoc } from "../../prosemirror/conversion/fromProseDoc.js";
|
|
2
2
|
import { schema } from "../../prosemirror/schema/index.js";
|
|
3
|
-
import { readYjsParagraphSourceContract, withParagraphSourceContract } from "../../prosemirror/
|
|
3
|
+
import { applyAttrSchemaMigrations, readYjsAttrSchemaVersion, readYjsParagraphSourceContract, withParagraphSourceContract } from "../../prosemirror/yjsDocumentMetadata.js";
|
|
4
4
|
import { ParagraphPropertySourceValidationError } from "../paragraphPropertySource.js";
|
|
5
5
|
import { parseDocx } from "../parser.js";
|
|
6
6
|
import { repackDocx } from "../rezip.js";
|
|
@@ -18,6 +18,7 @@ const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES = [
|
|
|
18
18
|
"invalid_update",
|
|
19
19
|
"missing_document",
|
|
20
20
|
"source_mismatch",
|
|
21
|
+
"stale_attr_schema",
|
|
21
22
|
"update_too_large"
|
|
22
23
|
];
|
|
23
24
|
/** Typed failure raised when a collaboration snapshot cannot be materialized. */
|
|
@@ -40,6 +41,13 @@ const readProseMirrorDocument = (yjsUpdate) => {
|
|
|
40
41
|
code: "missing_document",
|
|
41
42
|
message: "Yjs update does not contain a Folio document."
|
|
42
43
|
});
|
|
44
|
+
const attrSchemaVersion = readYjsAttrSchemaVersion(ydoc);
|
|
45
|
+
if (attrSchemaVersion.isErr()) throw new FolioYjsDocxMaterializationError({
|
|
46
|
+
cause: attrSchemaVersion.error,
|
|
47
|
+
code: "stale_attr_schema",
|
|
48
|
+
message: attrSchemaVersion.error.message
|
|
49
|
+
});
|
|
50
|
+
applyAttrSchemaMigrations(ydoc, fragment, attrSchemaVersion.value);
|
|
43
51
|
const contract = readYjsParagraphSourceContract(ydoc);
|
|
44
52
|
if (!contract) throw new FolioYjsDocxMaterializationError({
|
|
45
53
|
code: "source_mismatch",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { FolioYjsAttrSchemaVersion } from "../../prosemirror/yjsDocumentMetadata.js";
|
|
2
|
+
import { Result } from "better-result";
|
|
3
|
+
//#region src/docx/server/migrateYjsAttrSchema.d.ts
|
|
4
|
+
/** Stable failure codes returned by an offline snapshot migration. */
|
|
5
|
+
declare const FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES: readonly ["invalid_update", "unsupported_version", "update_too_large"];
|
|
6
|
+
/** Failure code for a snapshot that could not be migrated. */
|
|
7
|
+
type FolioYjsSnapshotMigrationErrorCode = (typeof FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES)[number];
|
|
8
|
+
declare const FolioYjsSnapshotMigrationError_base: import("better-result").TaggedErrorClass<"FolioYjsSnapshotMigrationError">;
|
|
9
|
+
/** Typed failure raised when a collaboration snapshot cannot be migrated. */
|
|
10
|
+
declare class FolioYjsSnapshotMigrationError extends FolioYjsSnapshotMigrationError_base<{
|
|
11
|
+
code: FolioYjsSnapshotMigrationErrorCode;
|
|
12
|
+
message: string;
|
|
13
|
+
cause?: unknown;
|
|
14
|
+
}> {}
|
|
15
|
+
/** What an offline migration did to one snapshot. */
|
|
16
|
+
type FolioYjsSnapshotMigrationResult = {
|
|
17
|
+
/** The complete state update to store in place of the input. */
|
|
18
|
+
update: Uint8Array;
|
|
19
|
+
/** The attr-schema version the input was written under. */
|
|
20
|
+
fromVersion: FolioYjsAttrSchemaVersion;
|
|
21
|
+
/** The attr-schema version the output carries. */
|
|
22
|
+
toVersion: FolioYjsAttrSchemaVersion;
|
|
23
|
+
/** Paragraphs whose attrs the migration rewrote. */
|
|
24
|
+
paragraphsRewritten: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Carry a stored collaboration snapshot forward to the attr schema this build
|
|
28
|
+
* writes, without a ProseMirror schema and without rebuilding a single node.
|
|
29
|
+
*
|
|
30
|
+
* A Yjs XML element's attributes are ordinary CRDT map entries holding JSON, so
|
|
31
|
+
* {@link applyAttrSchemaMigrations} rewrites values in the fragment directly.
|
|
32
|
+
* Nothing here can trip y-prosemirror's node rebuild, which deletes an element
|
|
33
|
+
* it fails to construct. The function is pure and offline: no network, no
|
|
34
|
+
* DOCX, no editor.
|
|
35
|
+
*
|
|
36
|
+
* A snapshot already at the current version is returned byte for byte, so a
|
|
37
|
+
* host may run this over its whole corpus repeatedly.
|
|
38
|
+
*
|
|
39
|
+
* **The contract a reseed must respect.** This function never reseeds and never
|
|
40
|
+
* touches the paragraph-source contract: it preserves whichever source binding
|
|
41
|
+
* the snapshot already carries. A reseed does the opposite, and Folio holds it
|
|
42
|
+
* to one rule: *the DOCX handed to the seeding editor must be the same bytes
|
|
43
|
+
* the host will later pass to `materializeYjsDocx` as that room's source*.
|
|
44
|
+
* The contract is a digest of those bytes, so seeding from one document while
|
|
45
|
+
* recording another as the room's source makes every later save fail with
|
|
46
|
+
* `source_mismatch`. The reverse mistake is worse and Folio cannot see it: a
|
|
47
|
+
* host that reseeds from the document a room was *created* from, rather than
|
|
48
|
+
* from the latest content it published, produces a perfectly well-formed
|
|
49
|
+
* snapshot whose collaborative history has been replaced by an older document.
|
|
50
|
+
* Rebind the recorded source in the same statement that clears the seed, or do
|
|
51
|
+
* not clear it.
|
|
52
|
+
*/
|
|
53
|
+
declare const migrateFolioYjsSnapshot: (update: Uint8Array) => Result<FolioYjsSnapshotMigrationResult, FolioYjsSnapshotMigrationError>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES, FolioYjsSnapshotMigrationError, FolioYjsSnapshotMigrationErrorCode, FolioYjsSnapshotMigrationResult, migrateFolioYjsSnapshot };
|