@stll/folio-core 0.47.4 → 0.47.5
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/compare/inline-atoms.js +218 -31
- package/dist/compare/section-boundary-properties.js +1 -0
- package/dist/content-controls/mutateContentControls.js +1 -3
- package/dist/docx/commentRangeIntegrity.js +7 -0
- package/dist/docx/documentParser.js +55 -3
- package/dist/docx/inlineWrapperContent.d.ts +18 -16
- package/dist/docx/inlineWrapperContent.js +18 -16
- package/dist/docx/normalizeBaseDirection.js +4 -0
- package/dist/docx/rezip.js +1 -0
- package/dist/docx/serializer/documentSerializer.js +16 -1
- package/dist/docx/serializer/paragraphSerializer.js +8 -0
- package/dist/docx/serializer/tableSerializer.js +5 -2
- package/dist/docx/server/createBilingualDocument.js +23 -14
- package/dist/docx/tableParser.js +101 -75
- package/dist/markdown/renderRuns.js +4 -0
- package/dist/markdown/renderTable.js +46 -12
- package/dist/prosemirror/attrs/index.js +67 -3
- package/dist/prosemirror/commentReferenceAttrs.js +2 -2
- package/dist/prosemirror/commentReferenceIntegrity.d.ts +7 -3
- package/dist/prosemirror/commentReferenceIntegrity.js +9 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +74 -18
- package/dist/prosemirror/conversion/toProseDoc.js +75 -42
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +13 -6
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +24 -6
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -2
- package/dist/prosemirror/schema/marks.d.ts +11 -0
- package/dist/prosemirror/schema/nodes.d.ts +6 -4
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/prosemirror/validation.js +48 -7
- package/dist/types/content.d.ts +2 -2
- package/dist/types/index.d.ts +3 -1
- package/dist/utils/mergeDocumentContent.js +1 -7
- package/package.json +2 -2
|
@@ -52,6 +52,10 @@ const normalizeCellBlock = (block, styles) => {
|
|
|
52
52
|
switch (block.type) {
|
|
53
53
|
case "paragraph": return normalizeParagraph(block, styles);
|
|
54
54
|
case "table": return normalizeTable(block, styles);
|
|
55
|
+
case "blockSdt": return {
|
|
56
|
+
...block,
|
|
57
|
+
content: block.content.map((child) => normalizeCellBlock(child, styles))
|
|
58
|
+
};
|
|
55
59
|
case "preservedBlock":
|
|
56
60
|
case "bookmarkStart":
|
|
57
61
|
case "bookmarkEnd": return block;
|
package/dist/docx/rezip.js
CHANGED
|
@@ -404,6 +404,7 @@ function collectNewImages(blocks) {
|
|
|
404
404
|
for (const sub of item.content) if (sub.type === "run") collectFromRun(sub);
|
|
405
405
|
}
|
|
406
406
|
} else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) images.push(...collectNewImages(cell.content));
|
|
407
|
+
else if (block.type === "blockSdt") images.push(...collectNewImages(block.content));
|
|
407
408
|
return images;
|
|
408
409
|
}
|
|
409
410
|
/** Map MIME type to file extension (inverse of getContentTypeForExtension) */
|
|
@@ -5,6 +5,8 @@ import { serializePartElement } from "./partNamespaces.js";
|
|
|
5
5
|
import { resetAutoIdCounter } from "./runSerializer.js";
|
|
6
6
|
import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
|
|
7
7
|
import { serializeTable } from "./tableSerializer.js";
|
|
8
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
9
|
+
import { themeColorToken } from "@stll/docx-core/model";
|
|
8
10
|
//#region src/docx/serializer/documentSerializer.ts
|
|
9
11
|
/**
|
|
10
12
|
* Prefixes document.xml declares whether or not the body uses them.
|
|
@@ -55,6 +57,19 @@ function serializeBlockContent(block) {
|
|
|
55
57
|
function serializeBodyContent(content) {
|
|
56
58
|
return content.map((block) => serializeBlockContent(block)).join("");
|
|
57
59
|
}
|
|
60
|
+
/** Serialize the optional page background that precedes `w:body`. */
|
|
61
|
+
function serializeDocumentBackground(background) {
|
|
62
|
+
if (background === void 0) return "";
|
|
63
|
+
const attributes = [];
|
|
64
|
+
if (background.color?.auto) attributes.push("w:color=\"auto\"");
|
|
65
|
+
else if (background.color?.rgb !== void 0) attributes.push(`w:color="${escapeXmlAttribute(background.color.rgb)}"`);
|
|
66
|
+
if (background.themeColor !== void 0) attributes.push(`w:themeColor="${escapeXmlAttribute(themeColorToken(background.themeColor))}"`);
|
|
67
|
+
if (background.themeTint !== void 0) attributes.push(`w:themeTint="${escapeXmlAttribute(background.themeTint)}"`);
|
|
68
|
+
if (background.themeShade !== void 0) attributes.push(`w:themeShade="${escapeXmlAttribute(background.themeShade)}"`);
|
|
69
|
+
const attributeText = attributes.length === 0 ? "" : ` ${attributes.join(" ")}`;
|
|
70
|
+
const drawing = background.drawing?.rawXml;
|
|
71
|
+
return drawing === void 0 ? `<w:background${attributeText}/>` : `<w:background${attributeText}>${drawing}</w:background>`;
|
|
72
|
+
}
|
|
58
73
|
/**
|
|
59
74
|
* Serialize a DocumentBody to document.xml body content
|
|
60
75
|
*
|
|
@@ -77,7 +92,7 @@ function serializeDocumentBody(body) {
|
|
|
77
92
|
*/
|
|
78
93
|
function serializeDocument(doc, sourceBindings) {
|
|
79
94
|
resetAutoIdCounter();
|
|
80
|
-
const body = `<w:body>${serializeDocumentBody(doc.package.document)}</w:body>`;
|
|
95
|
+
const body = serializeDocumentBackground(doc.package.document.background) + `<w:body>${serializeDocumentBody(doc.package.document)}</w:body>`;
|
|
81
96
|
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + serializePartElement({
|
|
82
97
|
partPath: "word/document.xml",
|
|
83
98
|
rootName: "w:document",
|
|
@@ -358,6 +358,10 @@ function serializeInlineSdt(sdt, disposition = "kept") {
|
|
|
358
358
|
case "inlineWrapper": return serializeParagraphContent(item, disposition);
|
|
359
359
|
case "bookmarkStart": return serializeBookmarkMarker(item);
|
|
360
360
|
case "bookmarkEnd": return serializeBookmarkMarker(item);
|
|
361
|
+
case "moveFromRangeStart": return serializeMoveRangeStart("moveFromRangeStart", item);
|
|
362
|
+
case "moveFromRangeEnd": return `<w:moveFromRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
363
|
+
case "moveToRangeStart": return serializeMoveRangeStart("moveToRangeStart", item);
|
|
364
|
+
case "moveToRangeEnd": return `<w:moveToRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
361
365
|
case "insertion": return serializeTrackedChange("ins", item);
|
|
362
366
|
case "deletion": return serializeTrackedChange("del", item);
|
|
363
367
|
case "moveFrom": return serializeTrackedChange("moveFrom", item);
|
|
@@ -420,6 +424,10 @@ function serializeTrackedChange(tag, change) {
|
|
|
420
424
|
case "moveTo": return serializeTrackedChange(trackedChangeTag(item), item);
|
|
421
425
|
case "bookmarkStart":
|
|
422
426
|
case "bookmarkEnd": return serializeBookmarkMarker(item);
|
|
427
|
+
case "moveFromRangeStart": return serializeMoveRangeStart("moveFromRangeStart", item);
|
|
428
|
+
case "moveFromRangeEnd": return `<w:moveFromRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
429
|
+
case "moveToRangeStart": return serializeMoveRangeStart("moveToRangeStart", item);
|
|
430
|
+
case "moveToRangeEnd": return `<w:moveToRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
423
431
|
case "preservedInline": return item.xml;
|
|
424
432
|
case "inlineWrapper":
|
|
425
433
|
case "inlineSdt": return serializeParagraphContent(item, disposition);
|
|
@@ -6,6 +6,7 @@ import { TRANSITIONAL_NAME_BY_STRICT_NAME } from "../strictNames.gen.js";
|
|
|
6
6
|
import { TABLE_LOOK_FLAGS } from "../tableLook.js";
|
|
7
7
|
import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTablePropertyExceptions, parseTableRowProperties } from "../tableParser.js";
|
|
8
8
|
import { OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
|
|
9
|
+
import { serializeBlockSdt } from "./blockSdtSerializer.js";
|
|
9
10
|
import { serializeBorder } from "./borderSerializer.js";
|
|
10
11
|
import { serializeBookmarkMarker } from "./markupRangeAttributes.js";
|
|
11
12
|
import { serializeSdtWrapper } from "./sdtPropertiesSerializer.js";
|
|
@@ -511,16 +512,18 @@ const preservedChild = (xml, child) => ({
|
|
|
511
512
|
* Serialize cell content (paragraphs, nested tables)
|
|
512
513
|
*/
|
|
513
514
|
function serializeCellContent(content, serializeParagraph) {
|
|
514
|
-
const
|
|
515
|
+
const serializeCellBlock = (block) => {
|
|
515
516
|
switch (block.type) {
|
|
516
517
|
case "paragraph": return serializeParagraph(block);
|
|
517
518
|
case "table": return serializeTable(block, serializeParagraph);
|
|
519
|
+
case "blockSdt": return serializeBlockSdt(block, serializeCellBlock);
|
|
518
520
|
case "preservedBlock": return block.xml;
|
|
519
521
|
case "bookmarkStart":
|
|
520
522
|
case "bookmarkEnd": return serializeBookmarkMarker(block);
|
|
521
523
|
default: return block;
|
|
522
524
|
}
|
|
523
|
-
}
|
|
525
|
+
};
|
|
526
|
+
const parts = content.map(serializeCellBlock);
|
|
524
527
|
if (parts.length === 0) parts.push("<w:p/>");
|
|
525
528
|
return parts.join("");
|
|
526
529
|
}
|
|
@@ -464,30 +464,39 @@ const remapListRendering = (rendering, cloner) => {
|
|
|
464
464
|
};
|
|
465
465
|
const collectTableParagraphs = (table) => {
|
|
466
466
|
const out = [];
|
|
467
|
-
|
|
468
|
-
|
|
467
|
+
const collectBlocks = (blocks) => {
|
|
468
|
+
for (const item of blocks) if (item.type === "paragraph") out.push(item);
|
|
469
|
+
else if (item.type === "table") out.push(...collectTableParagraphs(item));
|
|
470
|
+
else if (item.type === "blockSdt") collectBlocks(item.content);
|
|
471
|
+
};
|
|
472
|
+
for (const row of table.rows) for (const cell of row.cells) collectBlocks(cell.content);
|
|
469
473
|
return out;
|
|
470
474
|
};
|
|
471
475
|
const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner, bookmarkIds }) => {
|
|
472
476
|
const paragraphs = [];
|
|
477
|
+
const cloneBlocks = (blocks) => blocks.map((item) => {
|
|
478
|
+
if (item.type === "table") return cloneTable(item);
|
|
479
|
+
if (item.type === "blockSdt") return {
|
|
480
|
+
...structuredClone(item),
|
|
481
|
+
content: cloneBlocks(item.content)
|
|
482
|
+
};
|
|
483
|
+
if (item.type !== "paragraph") return structuredClone(item);
|
|
484
|
+
const targetParaId = paraIds.mint(item.paraId);
|
|
485
|
+
const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
|
|
486
|
+
if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
|
|
487
|
+
sourceParaId: item.paraId,
|
|
488
|
+
targetParaId,
|
|
489
|
+
sourceText: getParagraphText(item)
|
|
490
|
+
});
|
|
491
|
+
return copy;
|
|
492
|
+
});
|
|
473
493
|
const cloneTable = (source) => ({
|
|
474
494
|
...structuredClone(source),
|
|
475
495
|
rows: source.rows.map((row) => ({
|
|
476
496
|
...structuredClone(row),
|
|
477
497
|
cells: row.cells.map((cell) => ({
|
|
478
498
|
...structuredClone(cell),
|
|
479
|
-
content: cell.content
|
|
480
|
-
if (item.type === "table") return cloneTable(item);
|
|
481
|
-
if (item.type !== "paragraph") return structuredClone(item);
|
|
482
|
-
const targetParaId = paraIds.mint(item.paraId);
|
|
483
|
-
const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
|
|
484
|
-
if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
|
|
485
|
-
sourceParaId: item.paraId,
|
|
486
|
-
targetParaId,
|
|
487
|
-
sourceText: getParagraphText(item)
|
|
488
|
-
});
|
|
489
|
-
return copy;
|
|
490
|
-
})
|
|
499
|
+
content: cloneBlocks(cell.content)
|
|
491
500
|
}))
|
|
492
501
|
}))
|
|
493
502
|
});
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -766,81 +766,104 @@ function withContainerXmlns(options, element) {
|
|
|
766
766
|
};
|
|
767
767
|
}
|
|
768
768
|
function parseCellContent(tcElement, styles, theme, numbering, rels, media, options) {
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
undeclared: { AlternateContent: (child) => {
|
|
777
|
-
const selectedBranch = selectAlternateContentBranch(child);
|
|
778
|
-
if (!selectedBranch) return;
|
|
779
|
-
dispatchCellChildren(selectedBranch, withContainerXmlns(withContainerXmlns(childOptions, child), selectedBranch));
|
|
780
|
-
} },
|
|
781
|
-
handlers: {
|
|
782
|
-
p: (child) => {
|
|
783
|
-
const para = parseParagraph(child, styles, theme, numbering, rels, media, {
|
|
784
|
-
...childOptions,
|
|
785
|
-
runConsolidation: "deferred"
|
|
786
|
-
});
|
|
787
|
-
enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media, parseTable);
|
|
788
|
-
modelled.push(para);
|
|
789
|
-
},
|
|
790
|
-
tbl: (child) => {
|
|
791
|
-
const table = parseTable(child, styles, theme, numbering, rels, media, childOptions);
|
|
792
|
-
if (!table) return;
|
|
793
|
-
modelled.push(table);
|
|
794
|
-
},
|
|
795
|
-
sdt: (child) => {
|
|
796
|
-
const sdtContent = findWordprocessingChild(child, "sdtContent");
|
|
797
|
-
if (!sdtContent) return;
|
|
798
|
-
dispatchCellChildren(sdtContent, withContainerXmlns(withContainerXmlns(childOptions, child), sdtContent));
|
|
799
|
-
},
|
|
800
|
-
bookmarkStart: (child) => {
|
|
801
|
-
modelled.push(parseBookmarkStart(child));
|
|
802
|
-
},
|
|
803
|
-
bookmarkEnd: (child) => {
|
|
804
|
-
modelled.push(parseBookmarkEnd(child));
|
|
805
|
-
},
|
|
806
|
-
tcPr: CELL_PROPERTIES_OWNER,
|
|
807
|
-
sectPr: CAPTURE,
|
|
808
|
-
altChunk: CAPTURE,
|
|
809
|
-
commentRangeEnd: CAPTURE,
|
|
810
|
-
commentRangeStart: CAPTURE,
|
|
811
|
-
customXml: CAPTURE,
|
|
812
|
-
customXmlDelRangeEnd: CAPTURE,
|
|
813
|
-
customXmlDelRangeStart: CAPTURE,
|
|
814
|
-
customXmlInsRangeEnd: CAPTURE,
|
|
815
|
-
customXmlInsRangeStart: CAPTURE,
|
|
816
|
-
customXmlMoveFromRangeEnd: CAPTURE,
|
|
817
|
-
customXmlMoveFromRangeStart: CAPTURE,
|
|
818
|
-
customXmlMoveToRangeEnd: CAPTURE,
|
|
819
|
-
customXmlMoveToRangeStart: CAPTURE,
|
|
820
|
-
del: CAPTURE,
|
|
821
|
-
ins: CAPTURE,
|
|
822
|
-
moveFrom: CAPTURE,
|
|
823
|
-
moveFromRangeEnd: CAPTURE,
|
|
824
|
-
moveFromRangeStart: CAPTURE,
|
|
825
|
-
moveTo: CAPTURE,
|
|
826
|
-
moveToRangeEnd: CAPTURE,
|
|
827
|
-
moveToRangeStart: CAPTURE,
|
|
828
|
-
permEnd: CAPTURE,
|
|
829
|
-
permStart: CAPTURE,
|
|
830
|
-
proofErr: CAPTURE
|
|
769
|
+
const findLastFlowBlock = (blocks) => {
|
|
770
|
+
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
771
|
+
const block = blocks[index];
|
|
772
|
+
if (block?.type === "paragraph" || block?.type === "table") return block;
|
|
773
|
+
if (block?.type === "blockSdt") {
|
|
774
|
+
const nested = findLastFlowBlock(block.content);
|
|
775
|
+
if (nested) return nested;
|
|
831
776
|
}
|
|
832
|
-
}
|
|
833
|
-
if (preserved?.children) captured.push(...preserved.children);
|
|
777
|
+
}
|
|
834
778
|
};
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
779
|
+
const parseCellChildren = (element, childOptions, requireTrailingParagraph) => {
|
|
780
|
+
const modelled = [];
|
|
781
|
+
const captured = [];
|
|
782
|
+
const dispatchCellChildren = (childContainer, nestedOptions) => {
|
|
783
|
+
const preserved = dispatchChildren({
|
|
784
|
+
element: childContainer,
|
|
785
|
+
container: "block-content",
|
|
786
|
+
capturePosition: () => modelled.length,
|
|
787
|
+
undeclared: { AlternateContent: (child) => {
|
|
788
|
+
const selectedBranch = selectAlternateContentBranch(child);
|
|
789
|
+
if (!selectedBranch) return;
|
|
790
|
+
dispatchCellChildren(selectedBranch, withContainerXmlns(withContainerXmlns(nestedOptions, child), selectedBranch));
|
|
791
|
+
} },
|
|
792
|
+
handlers: {
|
|
793
|
+
p: (child) => {
|
|
794
|
+
const para = parseParagraph(child, styles, theme, numbering, rels, media, {
|
|
795
|
+
...nestedOptions,
|
|
796
|
+
runConsolidation: "deferred"
|
|
797
|
+
});
|
|
798
|
+
enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media, parseTable);
|
|
799
|
+
modelled.push(para);
|
|
800
|
+
},
|
|
801
|
+
tbl: (child) => {
|
|
802
|
+
const table = parseTable(child, styles, theme, numbering, rels, media, nestedOptions);
|
|
803
|
+
if (!table) return;
|
|
804
|
+
modelled.push(table);
|
|
805
|
+
},
|
|
806
|
+
sdt: (child) => {
|
|
807
|
+
const properties = parseSdtProperties(findWordprocessingChild(child, "sdtPr"), findWordprocessingChild(child, "sdtEndPr"));
|
|
808
|
+
const siblings = captureSdtSiblingMarkers(child);
|
|
809
|
+
if (siblings.before.length > 0) properties.rawSdtChildrenBeforeContent = siblings.before;
|
|
810
|
+
if (siblings.after.length > 0) properties.rawSdtChildrenAfterContent = siblings.after;
|
|
811
|
+
const sdtContent = findWordprocessingChild(child, "sdtContent");
|
|
812
|
+
if (!sdtContent) return CAPTURE;
|
|
813
|
+
modelled.push({
|
|
814
|
+
type: "blockSdt",
|
|
815
|
+
properties,
|
|
816
|
+
content: parseCellChildren(sdtContent, withContainerXmlns(withContainerXmlns(nestedOptions, child), sdtContent), false)
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
bookmarkStart: (child) => {
|
|
820
|
+
modelled.push(parseBookmarkStart(child));
|
|
821
|
+
},
|
|
822
|
+
bookmarkEnd: (child) => {
|
|
823
|
+
modelled.push(parseBookmarkEnd(child));
|
|
824
|
+
},
|
|
825
|
+
tcPr: CELL_PROPERTIES_OWNER,
|
|
826
|
+
sectPr: CAPTURE,
|
|
827
|
+
altChunk: CAPTURE,
|
|
828
|
+
commentRangeEnd: CAPTURE,
|
|
829
|
+
commentRangeStart: CAPTURE,
|
|
830
|
+
customXml: CAPTURE,
|
|
831
|
+
customXmlDelRangeEnd: CAPTURE,
|
|
832
|
+
customXmlDelRangeStart: CAPTURE,
|
|
833
|
+
customXmlInsRangeEnd: CAPTURE,
|
|
834
|
+
customXmlInsRangeStart: CAPTURE,
|
|
835
|
+
customXmlMoveFromRangeEnd: CAPTURE,
|
|
836
|
+
customXmlMoveFromRangeStart: CAPTURE,
|
|
837
|
+
customXmlMoveToRangeEnd: CAPTURE,
|
|
838
|
+
customXmlMoveToRangeStart: CAPTURE,
|
|
839
|
+
del: CAPTURE,
|
|
840
|
+
ins: CAPTURE,
|
|
841
|
+
moveFrom: CAPTURE,
|
|
842
|
+
moveFromRangeEnd: CAPTURE,
|
|
843
|
+
moveFromRangeStart: CAPTURE,
|
|
844
|
+
moveTo: CAPTURE,
|
|
845
|
+
moveToRangeEnd: CAPTURE,
|
|
846
|
+
moveToRangeStart: CAPTURE,
|
|
847
|
+
permEnd: CAPTURE,
|
|
848
|
+
permStart: CAPTURE,
|
|
849
|
+
proofErr: CAPTURE
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
if (preserved?.children) captured.push(...preserved.children);
|
|
853
|
+
};
|
|
854
|
+
dispatchCellChildren(element, childOptions);
|
|
855
|
+
if (requireTrailingParagraph) {
|
|
856
|
+
if (findLastFlowBlock(modelled)?.type !== "paragraph") modelled.push({
|
|
857
|
+
type: "paragraph",
|
|
858
|
+
content: []
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
return withPreservedChildren(modelled, { children: captured }, (xml) => ({
|
|
862
|
+
type: "preservedBlock",
|
|
863
|
+
xml
|
|
864
|
+
}));
|
|
865
|
+
};
|
|
866
|
+
return parseCellChildren(tcElement, options, true);
|
|
844
867
|
}
|
|
845
868
|
/**
|
|
846
869
|
* Parse a table cell (w:tc)
|
|
@@ -993,12 +1016,15 @@ function parseTableRow(trElement, styles, theme, numbering, rels, media, options
|
|
|
993
1016
|
function parseTableGrid(tblGridElement) {
|
|
994
1017
|
if (!tblGridElement) return;
|
|
995
1018
|
const widths = [];
|
|
1019
|
+
let hasExplicitZero = false;
|
|
996
1020
|
const gridCols = findChildren(tblGridElement, "w", "gridCol");
|
|
997
1021
|
for (const col of gridCols) {
|
|
998
|
-
const
|
|
1022
|
+
const statedWidth = parseNumericAttribute(col, "w", "w");
|
|
1023
|
+
hasExplicitZero ||= statedWidth === 0;
|
|
1024
|
+
const width = statedWidth ?? 0;
|
|
999
1025
|
widths.push(width);
|
|
1000
1026
|
}
|
|
1001
|
-
if (widths.length > 0 && widths.every((width) => width <= 0)) return;
|
|
1027
|
+
if (widths.length > 0 && widths.every((width) => width <= 0) && !hasExplicitZero) return;
|
|
1002
1028
|
return widths.length > 0 ? widths : void 0;
|
|
1003
1029
|
}
|
|
1004
1030
|
/**
|
|
@@ -165,6 +165,10 @@ function renderTrackedWrapper(ctx, pkg, wrapper, paraId) {
|
|
|
165
165
|
case "complexField": return renderParagraphInline(ctx, pkg, [child], paraId);
|
|
166
166
|
case "bookmarkStart":
|
|
167
167
|
case "bookmarkEnd":
|
|
168
|
+
case "moveFromRangeStart":
|
|
169
|
+
case "moveFromRangeEnd":
|
|
170
|
+
case "moveToRangeStart":
|
|
171
|
+
case "moveToRangeEnd":
|
|
168
172
|
case "preservedInline": return "";
|
|
169
173
|
default: return child;
|
|
170
174
|
}
|
|
@@ -17,10 +17,11 @@ function needsHtmlFallback(rows) {
|
|
|
17
17
|
for (const row of rows) for (const cell of row.cells) {
|
|
18
18
|
if ((cell.formatting?.gridSpan ?? 1) > 1) return true;
|
|
19
19
|
if (cell.formatting?.vMerge) return true;
|
|
20
|
-
if (cell.content
|
|
20
|
+
if (containsTable(cell.content)) return true;
|
|
21
21
|
}
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
|
+
const containsTable = (blocks) => blocks.some((block) => block.type === "table" || block.type === "blockSdt" && containsTable(block.content));
|
|
24
25
|
function renderGfmTable(ctx, pkg, rows, firstRowIsHeader) {
|
|
25
26
|
const cellTexts = rows.map((row) => renderGfmRow(ctx, pkg, row));
|
|
26
27
|
let maxCols = 0;
|
|
@@ -55,10 +56,24 @@ function renderGfmRow(ctx, pkg, row) {
|
|
|
55
56
|
}
|
|
56
57
|
function renderGfmCell(ctx, pkg, cell) {
|
|
57
58
|
const blocks = [];
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const renderBlocks = (content) => {
|
|
60
|
+
for (const item of content) switch (item.type) {
|
|
61
|
+
case "paragraph": {
|
|
62
|
+
const md = renderParagraph(ctx, pkg, item);
|
|
63
|
+
if (md.trim()) blocks.push(md);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "blockSdt":
|
|
67
|
+
renderBlocks(item.content);
|
|
68
|
+
break;
|
|
69
|
+
case "table":
|
|
70
|
+
case "preservedBlock":
|
|
71
|
+
case "bookmarkStart":
|
|
72
|
+
case "bookmarkEnd": break;
|
|
73
|
+
default: return item;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
renderBlocks(cell.content);
|
|
62
77
|
return escapeTableCell(blocks.join("\n"));
|
|
63
78
|
}
|
|
64
79
|
function renderHtmlTable(ctx, pkg, rows, firstRowIsHeader) {
|
|
@@ -111,13 +126,28 @@ function cellAtGridColumn(row, gridCol) {
|
|
|
111
126
|
}
|
|
112
127
|
function renderHtmlCell(ctx, pkg, cell) {
|
|
113
128
|
const parts = [];
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
const renderBlocks = (content) => {
|
|
130
|
+
for (const item of content) switch (item.type) {
|
|
131
|
+
case "paragraph": {
|
|
132
|
+
const inner = renderHtmlInline(ctx, pkg, item.content, item.paraId);
|
|
133
|
+
if (inner) parts.push(inner);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "table": {
|
|
137
|
+
const nested = renderHtmlTable(ctx, pkg, item.rows, true);
|
|
138
|
+
if (nested) parts.push(nested);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "blockSdt":
|
|
142
|
+
renderBlocks(item.content);
|
|
143
|
+
break;
|
|
144
|
+
case "preservedBlock":
|
|
145
|
+
case "bookmarkStart":
|
|
146
|
+
case "bookmarkEnd": break;
|
|
147
|
+
default: return item;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
renderBlocks(cell.content);
|
|
121
151
|
return parts.join("<br>");
|
|
122
152
|
}
|
|
123
153
|
function escapeHtml(text) {
|
|
@@ -175,6 +205,10 @@ function renderHtmlChildren(ctx, pkg, children, paraId) {
|
|
|
175
205
|
case "moveTo": return renderHtmlInline(ctx, pkg, [child], paraId);
|
|
176
206
|
case "bookmarkStart":
|
|
177
207
|
case "bookmarkEnd":
|
|
208
|
+
case "moveFromRangeStart":
|
|
209
|
+
case "moveFromRangeEnd":
|
|
210
|
+
case "moveToRangeStart":
|
|
211
|
+
case "moveToRangeEnd":
|
|
178
212
|
case "preservedInline": return "";
|
|
179
213
|
default: return child;
|
|
180
214
|
}
|
|
@@ -868,6 +868,11 @@ const taggedLayerValidator = (label) => (layer, path, issues) => {
|
|
|
868
868
|
path: `${path}.${key}`,
|
|
869
869
|
message: "Expected a string."
|
|
870
870
|
});
|
|
871
|
+
const propertiesXml = layer["propertiesXml"];
|
|
872
|
+
if (typeof propertiesXml === "string" && !isSafePreservedChildXml(propertiesXml)) issues.push({
|
|
873
|
+
path: `${path}.propertiesXml`,
|
|
874
|
+
message: "Expected one bounded, well-formed XML element."
|
|
875
|
+
});
|
|
871
876
|
for (const key of Object.keys(layer)) if (!TAGGED_LAYER_KEYS.has(key)) issues.push({
|
|
872
877
|
path: `${path}.${key}`,
|
|
873
878
|
message: `Unexpected ${label} wrapper property.`
|
|
@@ -894,6 +899,19 @@ const INLINE_WRAPPER_LAYER_VALIDATORS = {
|
|
|
894
899
|
const inlineWrapperLayerValidator = (kind) => {
|
|
895
900
|
return typeof kind === "string" ? INLINE_WRAPPER_LAYER_VALIDATORS[kind] : void 0;
|
|
896
901
|
};
|
|
902
|
+
const inlineWrapperProperties = function* (layers) {
|
|
903
|
+
for (let index = 0; index < layers.length; index += 1) {
|
|
904
|
+
const layer = layers[index];
|
|
905
|
+
yield { xml: isRecord(layer) && typeof layer["propertiesXml"] === "string" ? layer["propertiesXml"] : "" };
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
const emptyHyperlinkWrapperProperties = function* (stacks) {
|
|
909
|
+
for (let stackIndex = 0; stackIndex < stacks.length; stackIndex += 1) {
|
|
910
|
+
const stack = stacks[stackIndex];
|
|
911
|
+
yield {};
|
|
912
|
+
if (Array.isArray(stack)) yield* inlineWrapperProperties(stack);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
897
915
|
const validateInlineWrapperStack = (value, path, issues) => {
|
|
898
916
|
if (!Array.isArray(value)) {
|
|
899
917
|
issues.push({
|
|
@@ -909,6 +927,13 @@ const validateInlineWrapperStack = (value, path, issues) => {
|
|
|
909
927
|
});
|
|
910
928
|
return;
|
|
911
929
|
}
|
|
930
|
+
if (!isWithinPreservedMarkupBudget(inlineWrapperProperties(value))) {
|
|
931
|
+
issues.push({
|
|
932
|
+
path,
|
|
933
|
+
message: "Inline wrapper stack exceeds its resource budget."
|
|
934
|
+
});
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
912
937
|
for (const [index, layer] of value.entries()) {
|
|
913
938
|
const layerPath = `${path}[${index}]`;
|
|
914
939
|
if (!isRecord(layer)) {
|
|
@@ -950,6 +975,22 @@ const readInlineWrapperMarkAttrs = (mark) => {
|
|
|
950
975
|
const issues = [];
|
|
951
976
|
expectMarkType(mark, "inlineWrapper", issues);
|
|
952
977
|
validateInlineWrapperStack(attrs["stack"], "inlineWrapper.attrs.stack", issues);
|
|
978
|
+
const hyperlinkIndex = attrs["_docxHyperlinkIndex"];
|
|
979
|
+
const stackStart = attrs["_docxInsideHyperlinkStackStart"];
|
|
980
|
+
const hasHyperlinkIndex = hyperlinkIndex !== void 0 && hyperlinkIndex !== null;
|
|
981
|
+
const hasStackStart = stackStart !== void 0 && stackStart !== null;
|
|
982
|
+
if (hasHyperlinkIndex !== hasStackStart) issues.push({
|
|
983
|
+
path: "inlineWrapper.attrs",
|
|
984
|
+
message: "Hyperlink wrapper provenance requires both an index and stack start."
|
|
985
|
+
});
|
|
986
|
+
else if (hasHyperlinkIndex && hasStackStart) {
|
|
987
|
+
validateNonNegativeInteger(hyperlinkIndex, "inlineWrapper.attrs._docxHyperlinkIndex", issues);
|
|
988
|
+
validateNonNegativeInteger(stackStart, "inlineWrapper.attrs._docxInsideHyperlinkStackStart", issues);
|
|
989
|
+
if (typeof stackStart === "number" && Array.isArray(attrs["stack"]) && stackStart >= attrs["stack"].length) issues.push({
|
|
990
|
+
path: "inlineWrapper.attrs._docxInsideHyperlinkStackStart",
|
|
991
|
+
message: "Expected a stack position inside the wrapper stack."
|
|
992
|
+
});
|
|
993
|
+
}
|
|
953
994
|
return attrsResult(attrs, issues);
|
|
954
995
|
};
|
|
955
996
|
const expectInlineWrapperMarkAttrs = (mark) => expectCachedMarkAttrs(mark, inlineWrapperAttrsCache, readInlineWrapperMarkAttrs, "inline wrapper attrs");
|
|
@@ -1745,7 +1786,7 @@ const optionalPositionedBookmarks = (attrs, path, issues) => {
|
|
|
1745
1786
|
});
|
|
1746
1787
|
continue;
|
|
1747
1788
|
}
|
|
1748
|
-
|
|
1789
|
+
validateNonNegativeSafeInteger(entry["index"], `${entryPath}.index`, issues);
|
|
1749
1790
|
const marker = entry["marker"];
|
|
1750
1791
|
if (!isRecord(marker)) {
|
|
1751
1792
|
issues.push({
|
|
@@ -1762,7 +1803,7 @@ const optionalPositionedBookmarks = (attrs, path, issues) => {
|
|
|
1762
1803
|
});
|
|
1763
1804
|
continue;
|
|
1764
1805
|
}
|
|
1765
|
-
|
|
1806
|
+
validateNonNegativeSafeInteger(marker["id"], `${entryPath}.marker.id`, issues);
|
|
1766
1807
|
if (type === "bookmarkStart") requiredString(marker, "name", `${entryPath}.marker.name`, issues);
|
|
1767
1808
|
optionalContentControls(entry, `${entryPath}.contentControls`, issues);
|
|
1768
1809
|
}
|
|
@@ -2574,11 +2615,17 @@ const validateNonNegativeInteger = (value, path, issues) => {
|
|
|
2574
2615
|
});
|
|
2575
2616
|
return;
|
|
2576
2617
|
}
|
|
2577
|
-
if (!Number.
|
|
2618
|
+
if (!Number.isSafeInteger(value) || value < 0) issues.push({
|
|
2578
2619
|
path,
|
|
2579
2620
|
message: "Expected a non-negative integer."
|
|
2580
2621
|
});
|
|
2581
2622
|
};
|
|
2623
|
+
const validateNonNegativeSafeInteger = (value, path, issues) => {
|
|
2624
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) issues.push({
|
|
2625
|
+
path,
|
|
2626
|
+
message: "Expected a non-negative safe integer."
|
|
2627
|
+
});
|
|
2628
|
+
};
|
|
2582
2629
|
const optionalBookmarkArray = (value, issues) => {
|
|
2583
2630
|
if (value === void 0 || value === null) return;
|
|
2584
2631
|
if (!Array.isArray(value)) {
|
|
@@ -2633,6 +2680,23 @@ const optionalEmptyHyperlinkArray = (value, issues) => {
|
|
|
2633
2680
|
optionalString(item, "target", `${itemPath}.target`, issues);
|
|
2634
2681
|
optionalBoolean(item, "history", `${itemPath}.history`, issues);
|
|
2635
2682
|
optionalString(item, "docLocation", `${itemPath}.docLocation`, issues);
|
|
2683
|
+
const wrapperStacks = item["_docxEmptyWrapperStacks"];
|
|
2684
|
+
if (wrapperStacks === void 0 || wrapperStacks === null) continue;
|
|
2685
|
+
if (!Array.isArray(wrapperStacks)) {
|
|
2686
|
+
issues.push({
|
|
2687
|
+
path: `${itemPath}._docxEmptyWrapperStacks`,
|
|
2688
|
+
message: "Expected an array of inline wrapper stacks."
|
|
2689
|
+
});
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
if (!isWithinPreservedMarkupBudget(emptyHyperlinkWrapperProperties(wrapperStacks))) {
|
|
2693
|
+
issues.push({
|
|
2694
|
+
path: `${itemPath}._docxEmptyWrapperStacks`,
|
|
2695
|
+
message: "Empty inline wrapper stacks exceed their aggregate resource budget."
|
|
2696
|
+
});
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
for (const [stackIndex, stack] of wrapperStacks.entries()) validateInlineWrapperStack(stack, `${itemPath}._docxEmptyWrapperStacks[${stackIndex}]`, issues);
|
|
2636
2700
|
}
|
|
2637
2701
|
};
|
|
2638
2702
|
//#endregion
|
|
@@ -8,9 +8,9 @@ const readCommentReferenceAttrs = (node) => {
|
|
|
8
8
|
message: `Expected commentReference, got ${node.type.name}.`
|
|
9
9
|
});
|
|
10
10
|
const commentId = node.attrs["commentId"];
|
|
11
|
-
if (typeof commentId !== "number" || !Number.
|
|
11
|
+
if (typeof commentId !== "number" || !Number.isSafeInteger(commentId)) issues.push({
|
|
12
12
|
path: "commentReference.attrs.commentId",
|
|
13
|
-
message: "Expected a
|
|
13
|
+
message: "Expected a safe integer."
|
|
14
14
|
});
|
|
15
15
|
if (issues.length > 0 || typeof commentId !== "number") return {
|
|
16
16
|
ok: false,
|
|
@@ -10,13 +10,17 @@
|
|
|
10
10
|
* a comment mark creates none — so the repair is computed here and applied by
|
|
11
11
|
* the extension's `appendTransaction`, rather than left to each call site.
|
|
12
12
|
*/
|
|
13
|
-
type
|
|
14
|
-
commentId: number;
|
|
13
|
+
type PositionedReference = {
|
|
15
14
|
position: number;
|
|
16
15
|
/** Node size, so a caller can delete the node without re-reading it. */
|
|
17
16
|
size: number;
|
|
18
|
-
malformed: boolean;
|
|
19
17
|
};
|
|
18
|
+
type CommentReferenceOccurrence = (PositionedReference & {
|
|
19
|
+
status: "valid";
|
|
20
|
+
commentId: number;
|
|
21
|
+
}) | (PositionedReference & {
|
|
22
|
+
status: "malformed";
|
|
23
|
+
});
|
|
20
24
|
/** Where a comment's mark last covers content, and how far that content runs. */
|
|
21
25
|
type CommentMarkExtent = {
|
|
22
26
|
commentId: number;
|
|
@@ -10,7 +10,15 @@ const planCommentReferenceRepairs = ({ references, markExtents }) => {
|
|
|
10
10
|
const deletions = [];
|
|
11
11
|
const keptIds = /* @__PURE__ */ new Set();
|
|
12
12
|
for (const reference of references) {
|
|
13
|
-
if (reference.
|
|
13
|
+
if (reference.status === "malformed") {
|
|
14
|
+
deletions.push({
|
|
15
|
+
type: "delete",
|
|
16
|
+
position: reference.position,
|
|
17
|
+
size: reference.size
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!markedIds.has(reference.commentId)) {
|
|
14
22
|
deletions.push({
|
|
15
23
|
type: "delete",
|
|
16
24
|
position: reference.position,
|