@stll/folio-core 0.21.0 → 0.22.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/docx/rezip.js +48 -4
- package/dist/docx/selectiveXmlPatch.d.ts +19 -1
- package/dist/docx/selectiveXmlPatch.js +72 -1
- package/dist/docx/serializer/stylesSerializer.d.ts +2 -1
- package/dist/docx/serializer/stylesSerializer.js +1 -1
- package/dist/docx/server/createBilingualDocument.d.ts +62 -0
- package/dist/docx/server/createBilingualDocument.js +511 -0
- package/dist/docx/server/createBilingualDocx.d.ts +19 -0
- package/dist/docx/server/createBilingualDocx.js +26 -0
- package/dist/server.d.ts +3 -1
- package/dist/server.js +3 -1
- package/package.json +1 -1
package/dist/docx/rezip.js
CHANGED
|
@@ -6,7 +6,7 @@ import { assertValidFolioDocumentModel } from "./modelValidation.js";
|
|
|
6
6
|
import { isNewDataUrlDrawing } from "./newImage.js";
|
|
7
7
|
import { parseNumbering } from "./numberingParser.js";
|
|
8
8
|
import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
|
|
9
|
-
import { buildParagraphOffsetIndex, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds } from "./selectiveXmlPatch.js";
|
|
9
|
+
import { appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNumberingDefs, collectParaIds } from "./selectiveXmlPatch.js";
|
|
10
10
|
import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
11
11
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
12
12
|
import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
@@ -14,7 +14,7 @@ import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
|
14
14
|
import { serializeEndnotes, serializeFootnotes, serializeNewEndnotesPart, serializeNewFootnotesPart } from "./serializer/noteSerializer.js";
|
|
15
15
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
16
16
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
17
|
-
import { serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
17
|
+
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
18
18
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
19
19
|
import { escapeXml } from "./serializer/xmlUtils.js";
|
|
20
20
|
import { isPreservableDocxEntry } from "./unzip.js";
|
|
@@ -450,6 +450,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
450
450
|
serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
451
451
|
await serializeNotesToZip(document, originalZip, outputZip, compressionLevel);
|
|
452
452
|
await serializeNumberingIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
453
|
+
await serializeAddedStylesIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
453
454
|
await serializeCommentsToZip(document, outputZip, compressionLevel);
|
|
454
455
|
if (updateModifiedDate && originalCorePropertiesXml) {
|
|
455
456
|
const updatedCoreProperties = updateCoreProperties(originalCorePropertiesXml, {
|
|
@@ -530,6 +531,7 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
530
531
|
serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
531
532
|
await serializeNotesToZip(exportDocument, rawContent.originalZip, newZip, compressionLevel);
|
|
532
533
|
await serializeNumberingIntoZip(exportDocument, rawContent.originalZip, newZip, compressionLevel);
|
|
534
|
+
await serializeAddedStylesIntoZip(exportDocument, rawContent.originalZip, newZip, compressionLevel);
|
|
533
535
|
await serializeCommentsToZip(exportDocument, newZip, compressionLevel);
|
|
534
536
|
if (updateModifiedDate && rawContent.corePropsXml) {
|
|
535
537
|
const updatedCoreProps = updateCoreProperties(rawContent.corePropsXml, {
|
|
@@ -1081,14 +1083,56 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
|
|
|
1081
1083
|
}
|
|
1082
1084
|
const currentXml = serializeNumberingXml(numbering);
|
|
1083
1085
|
const changed = collectChangedNumberingDefs(baseline.serializedXml, currentXml);
|
|
1084
|
-
|
|
1085
|
-
const
|
|
1086
|
+
const added = collectAddedNumberingDefs(baseline.serializedXml, currentXml);
|
|
1087
|
+
const hasChanged = changed.abstractNums.size > 0 || changed.nums.size > 0;
|
|
1088
|
+
const hasAdded = added.abstractNums.size > 0 || added.nums.size > 0;
|
|
1089
|
+
if (!hasChanged && !hasAdded) return;
|
|
1090
|
+
const spliced = buildPatchedNumberingXml(baseline.originalXml, currentXml, changed);
|
|
1091
|
+
if (spliced === null) return;
|
|
1092
|
+
const patched = appendNumberingDefs(spliced, currentXml, added);
|
|
1086
1093
|
if (patched === null) return;
|
|
1087
1094
|
newZip.file(file.name, patched, {
|
|
1088
1095
|
compression: "DEFLATE",
|
|
1089
1096
|
compressionOptions: { level: compressionLevel }
|
|
1090
1097
|
});
|
|
1091
1098
|
}
|
|
1099
|
+
const STYLES_PART_PATH = "word/styles.xml";
|
|
1100
|
+
const STYLES_CLOSE_ROOT = "</w:styles>";
|
|
1101
|
+
const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
|
|
1102
|
+
/**
|
|
1103
|
+
* Append styles the model defines but the original `word/styles.xml` lacks.
|
|
1104
|
+
* Existing styles stay byte-exact (edits to them are not written here, the
|
|
1105
|
+
* part is otherwise preserved verbatim); only minted definitions, such as the
|
|
1106
|
+
* per-language clones a bilingual transform adds, are emitted before the root
|
|
1107
|
+
* close so paragraphs referencing them resolve on reopen.
|
|
1108
|
+
*/
|
|
1109
|
+
async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compressionLevel) {
|
|
1110
|
+
const styles = doc.package.styles;
|
|
1111
|
+
if (!styles || styles.styles.length === 0) return;
|
|
1112
|
+
const file = findNotePartEntry(originalZip, STYLES_PART_PATH);
|
|
1113
|
+
const originalXml = file ? await file.async("text") : null;
|
|
1114
|
+
const rootClose = originalXml?.lastIndexOf(STYLES_CLOSE_ROOT) ?? -1;
|
|
1115
|
+
if (file === null || originalXml === null || rootClose < 0) {
|
|
1116
|
+
await materializeNewNotePart({
|
|
1117
|
+
contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
|
|
1118
|
+
newZip,
|
|
1119
|
+
partPath: STYLES_PART_PATH,
|
|
1120
|
+
relationshipType: RELATIONSHIP_TYPES.styles,
|
|
1121
|
+
serializedPart: serializeStylesXml(styles),
|
|
1122
|
+
compressionLevel
|
|
1123
|
+
});
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
const existing = /* @__PURE__ */ new Set();
|
|
1127
|
+
for (const match of originalXml.matchAll(STYLE_ID_PATTERN)) existing.add(match.groups["id"]);
|
|
1128
|
+
const added = styles.styles.filter((style) => !existing.has(style.styleId));
|
|
1129
|
+
if (added.length === 0) return;
|
|
1130
|
+
const patched = originalXml.slice(0, rootClose) + added.map(serializeStyle).join("") + originalXml.slice(rootClose);
|
|
1131
|
+
newZip.file(file.name, patched, {
|
|
1132
|
+
compression: "DEFLATE",
|
|
1133
|
+
compressionOptions: { level: compressionLevel }
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1092
1136
|
async function patchNotePartIntoZip(conventionalLowerPath, currentXml, baselineFrom, originalZip, newZip, compressionLevel) {
|
|
1093
1137
|
const file = findNotePartEntry(originalZip, conventionalLowerPath);
|
|
1094
1138
|
if (!file) return;
|
|
@@ -129,5 +129,23 @@ declare function collectChangedNumberingDefs(baselineXml: string, currentXml: st
|
|
|
129
129
|
* in either input (so the caller preserves the original part verbatim).
|
|
130
130
|
*/
|
|
131
131
|
declare function buildPatchedNumberingXml(originalXml: string, currentXml: string, changed: ChangedNumberingDefs): string | null;
|
|
132
|
+
/**
|
|
133
|
+
* Numbering definitions present in `currentXml` (the model's serialization)
|
|
134
|
+
* but absent from `baselineXml` (the re-parsed original): definitions a
|
|
135
|
+
* transform minted. `collectChangedNumberingDefs` deliberately skips these
|
|
136
|
+
* because they cannot be spliced by id; they are appended instead.
|
|
137
|
+
*/
|
|
138
|
+
declare function collectAddedNumberingDefs(baselineXml: string, currentXml: string): ChangedNumberingDefs;
|
|
139
|
+
/**
|
|
140
|
+
* Append added `w:abstractNum` / `w:num` definitions from `currentXml` to
|
|
141
|
+
* `xml`. ECMA-376 §17.9 orders every `w:abstractNum` before the first `w:num`,
|
|
142
|
+
* so abstract definitions go right before the first `<w:num ` (or the root
|
|
143
|
+
* close when the part has none) and instances before the root close. A
|
|
144
|
+
* synthetic numFmt in an added abstract is restored from the original
|
|
145
|
+
* definition it was cloned from (the one with an identical body), so a custom
|
|
146
|
+
* format survives cloning. Returns null when an added id cannot be extracted
|
|
147
|
+
* or the part has no `</w:numbering>`.
|
|
148
|
+
*/
|
|
149
|
+
declare function appendNumberingDefs(xml: string, currentXml: string, added: ChangedNumberingDefs): string | null;
|
|
132
150
|
//#endregion
|
|
133
|
-
export { ChangedNumberingDefs, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
|
|
151
|
+
export { ChangedNumberingDefs, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
|
|
@@ -552,8 +552,79 @@ function buildPatchedNumberingXml(originalXml, currentXml, changed) {
|
|
|
552
552
|
for (const { start, end, newXml } of replacements) result = result.slice(0, start) + newXml + result.slice(end);
|
|
553
553
|
return result;
|
|
554
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Numbering definitions present in `currentXml` (the model's serialization)
|
|
557
|
+
* but absent from `baselineXml` (the re-parsed original): definitions a
|
|
558
|
+
* transform minted. `collectChangedNumberingDefs` deliberately skips these
|
|
559
|
+
* because they cannot be spliced by id; they are appended instead.
|
|
560
|
+
*/
|
|
561
|
+
function collectAddedNumberingDefs(baselineXml, currentXml) {
|
|
562
|
+
const addedForKind = (kind) => {
|
|
563
|
+
const added = /* @__PURE__ */ new Set();
|
|
564
|
+
const baselineIndex = buildNumberingElementOffsetIndex(baselineXml, kind);
|
|
565
|
+
const currentIndex = buildNumberingElementOffsetIndex(currentXml, kind);
|
|
566
|
+
for (const [id, range] of currentIndex) if (range && !baselineIndex.has(id)) added.add(id);
|
|
567
|
+
return added;
|
|
568
|
+
};
|
|
569
|
+
return {
|
|
570
|
+
abstractNums: addedForKind("abstractNum"),
|
|
571
|
+
nums: addedForKind("num")
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
const NUMBERING_CLOSE_ROOT = "</w:numbering>";
|
|
575
|
+
/**
|
|
576
|
+
* Append added `w:abstractNum` / `w:num` definitions from `currentXml` to
|
|
577
|
+
* `xml`. ECMA-376 §17.9 orders every `w:abstractNum` before the first `w:num`,
|
|
578
|
+
* so abstract definitions go right before the first `<w:num ` (or the root
|
|
579
|
+
* close when the part has none) and instances before the root close. A
|
|
580
|
+
* synthetic numFmt in an added abstract is restored from the original
|
|
581
|
+
* definition it was cloned from (the one with an identical body), so a custom
|
|
582
|
+
* format survives cloning. Returns null when an added id cannot be extracted
|
|
583
|
+
* or the part has no `</w:numbering>`.
|
|
584
|
+
*/
|
|
585
|
+
function appendNumberingDefs(xml, currentXml, added) {
|
|
586
|
+
if (added.abstractNums.size === 0 && added.nums.size === 0) return xml;
|
|
587
|
+
const rootClose = xml.lastIndexOf(NUMBERING_CLOSE_ROOT);
|
|
588
|
+
if (rootClose < 0) return null;
|
|
589
|
+
const abstractXmls = [];
|
|
590
|
+
for (const id of added.abstractNums) {
|
|
591
|
+
const def = extractNumberingElementXml(currentXml, "abstractNum", id);
|
|
592
|
+
if (def === null) return null;
|
|
593
|
+
abstractXmls.push(restoreClonedLevelNumFmts(xml, currentXml, def, id));
|
|
594
|
+
}
|
|
595
|
+
const numXmls = [];
|
|
596
|
+
for (const id of added.nums) {
|
|
597
|
+
const def = extractNumberingElementXml(currentXml, "num", id);
|
|
598
|
+
if (def === null) return null;
|
|
599
|
+
numXmls.push(def);
|
|
600
|
+
}
|
|
601
|
+
const firstNum = findFirstElement(xml, NUMBERING_OPEN_LITERAL.num, NUMBERING_CLOSE_TAG.num);
|
|
602
|
+
const abstractInsertAt = firstNum ? firstNum.start : rootClose;
|
|
603
|
+
const head = xml.slice(0, abstractInsertAt);
|
|
604
|
+
const middle = xml.slice(abstractInsertAt, rootClose);
|
|
605
|
+
const tail = xml.slice(rootClose);
|
|
606
|
+
return head + abstractXmls.join("") + middle + numXmls.join("") + tail;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* For an added abstract definition that still carries a synthetic numFmt, find
|
|
610
|
+
* the original abstract it was cloned from (same serialized body apart from the
|
|
611
|
+
* id) and restore the level formats from it.
|
|
612
|
+
*/
|
|
613
|
+
function restoreClonedLevelNumFmts(originalXml, currentXml, addedDefXml, addedId) {
|
|
614
|
+
if (!SYNTHETIC_NUM_FMT_PATTERN.test(addedDefXml)) return addedDefXml;
|
|
615
|
+
const addedBody = stripAbstractNumId(addedDefXml, addedId);
|
|
616
|
+
const originalIds = collectElementIds(originalXml, NUMBERING_OPEN_LITERAL.abstractNum, NUMBERING_ID_ATTR.abstractNum);
|
|
617
|
+
for (const [id] of originalIds) {
|
|
618
|
+
const candidate = extractNumberingElementXml(currentXml, "abstractNum", id);
|
|
619
|
+
if (candidate === null || stripAbstractNumId(candidate, id) !== addedBody) continue;
|
|
620
|
+
const originalDef = extractNumberingElementXml(originalXml, "abstractNum", id);
|
|
621
|
+
if (originalDef !== null) return restoreLevelNumFmts(originalDef, addedDefXml);
|
|
622
|
+
}
|
|
623
|
+
return restoreLevelNumFmts("", addedDefXml);
|
|
624
|
+
}
|
|
625
|
+
const stripAbstractNumId = (defXml, id) => defXml.replace(`${NUMBERING_ID_ATTR.abstractNum}="${id}"`, "");
|
|
555
626
|
function escapeRegExp(str) {
|
|
556
627
|
return str.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
557
628
|
}
|
|
558
629
|
//#endregion
|
|
559
|
-
export { buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
|
|
630
|
+
export { appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
2
|
//#region src/docx/serializer/stylesSerializer.d.ts
|
|
3
3
|
declare const serializeStylesXml: (definitions: document_d_exports.StyleDefinitions) => string;
|
|
4
|
+
declare const serializeStyle: (style: document_d_exports.Style) => string;
|
|
4
5
|
//#endregion
|
|
5
|
-
export { serializeStylesXml };
|
|
6
|
+
export { serializeStyle, serializeStylesXml };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
//#region src/docx/server/createBilingualDocument.d.ts
|
|
3
|
+
type BilingualRowKind = "paragraph" | "heading" | "listItem";
|
|
4
|
+
/** One translatable unit: a source paragraph and its right-column copy. */
|
|
5
|
+
type BilingualParagraphRef = {
|
|
6
|
+
/** `paraId` of the untouched source paragraph (left column). */
|
|
7
|
+
sourceParaId: string | undefined;
|
|
8
|
+
/** `paraId` minted for the right-column copy; stable across re-runs. */
|
|
9
|
+
targetParaId: string;
|
|
10
|
+
/** Plain text of the source paragraph. */
|
|
11
|
+
sourceText: string;
|
|
12
|
+
};
|
|
13
|
+
type BilingualRow = ({
|
|
14
|
+
kind: BilingualRowKind;
|
|
15
|
+
/** Equals `targetParaId`; the handle callers use to address the row. */
|
|
16
|
+
rowId: string;
|
|
17
|
+
} & BilingualParagraphRef) | {
|
|
18
|
+
kind: "table";
|
|
19
|
+
rowId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Every paragraph inside the table, in document order. The table is not
|
|
22
|
+
* copied, so these are the paragraphs to translate in place.
|
|
23
|
+
*/
|
|
24
|
+
paragraphs: BilingualTableParagraphRef[];
|
|
25
|
+
};
|
|
26
|
+
type BilingualTableParagraphRef = {
|
|
27
|
+
paraId: string | undefined;
|
|
28
|
+
sourceText: string;
|
|
29
|
+
};
|
|
30
|
+
type BilingualBorders = "none" | "grid";
|
|
31
|
+
type CreateBilingualDocumentOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* Suffix for cloned style ids and names (for example `"en"` turns
|
|
34
|
+
* `Heading1` into `Heading1-en`). Must be a non-empty token of letters,
|
|
35
|
+
* digits, or `-`.
|
|
36
|
+
*/
|
|
37
|
+
targetStyleSuffix: string;
|
|
38
|
+
/** Table borders; legal practice is usually `"none"`. Default `"none"`. */
|
|
39
|
+
borders?: BilingualBorders;
|
|
40
|
+
};
|
|
41
|
+
type CreateBilingualDocumentResult = {
|
|
42
|
+
document: document_d_exports.Document;
|
|
43
|
+
rows: BilingualRow[];
|
|
44
|
+
/** Non-fatal fidelity notes (for example an unresolvable numbering style link). */
|
|
45
|
+
warnings: string[];
|
|
46
|
+
};
|
|
47
|
+
declare const InvalidBilingualDocumentOptionsError_base: import("better-result").TaggedErrorClass<"InvalidBilingualDocumentOptionsError", {
|
|
48
|
+
message: string;
|
|
49
|
+
option: "targetStyleSuffix";
|
|
50
|
+
}>;
|
|
51
|
+
declare class InvalidBilingualDocumentOptionsError extends InvalidBilingualDocumentOptionsError_base {}
|
|
52
|
+
declare function createBilingualDocument(source: document_d_exports.Document, options: CreateBilingualDocumentOptions): CreateBilingualDocumentResult;
|
|
53
|
+
/**
|
|
54
|
+
* Re-derive the row manifest from a document produced by
|
|
55
|
+
* {@link createBilingualDocument}. Detection is structural: a top-level table
|
|
56
|
+
* whose rows are all either a left | right pair of single-paragraph cells or
|
|
57
|
+
* one cell spanning both columns. Rows are returned in document order; the
|
|
58
|
+
* right paragraph's `paraId` is the row handle, as at creation.
|
|
59
|
+
*/
|
|
60
|
+
declare function readBilingualDocument(document: document_d_exports.Document): BilingualRow[];
|
|
61
|
+
//#endregion
|
|
62
|
+
export { BilingualBorders, BilingualParagraphRef, BilingualRow, BilingualRowKind, BilingualTableParagraphRef, CreateBilingualDocumentOptions, CreateBilingualDocumentResult, InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument };
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import { deterministicHexId } from "../../utils/hexId.js";
|
|
2
|
+
import { getParagraphText } from "../paragraphParser.js";
|
|
3
|
+
import { TaggedError } from "better-result";
|
|
4
|
+
//#region src/docx/server/createBilingualDocument.ts
|
|
5
|
+
/**
|
|
6
|
+
* Bilingual document transform: body -> two-column table, one row per block.
|
|
7
|
+
*
|
|
8
|
+
* The left column keeps every source block untouched; the right column holds a
|
|
9
|
+
* copy of the same block whose numbering and numbered paragraph styles are
|
|
10
|
+
* cloned per language, so both columns count independently (1. / 1. instead of
|
|
11
|
+
* 1. / 2.) and stay live in Word. Right-column paragraphs receive fresh
|
|
12
|
+
* `paraId`s so callers can address each row later (for example to replace the
|
|
13
|
+
* placeholder copy with a translation by block id).
|
|
14
|
+
*
|
|
15
|
+
* Section breaks cannot live inside a table cell, so the body is split at
|
|
16
|
+
* paragraphs carrying `sectionProperties`: each section becomes its own table
|
|
17
|
+
* and the break paragraph stays between the tables. A source table (parties,
|
|
18
|
+
* signature block) is kept once, in a row spanning both columns: it is signed
|
|
19
|
+
* and read once, and its labels are translated inline rather than duplicated.
|
|
20
|
+
*/
|
|
21
|
+
const STYLE_SUFFIX_PATTERN = /^[A-Za-z0-9-]+$/u;
|
|
22
|
+
var InvalidBilingualDocumentOptionsError = class extends TaggedError("InvalidBilingualDocumentOptionsError")() {};
|
|
23
|
+
const FULL_WIDTH_PCT = 5e3;
|
|
24
|
+
const HALF_WIDTH_PCT = 2500;
|
|
25
|
+
const A4_TEXT_WIDTH_TWIPS = 9072;
|
|
26
|
+
const ROW_ID_NAMESPACE = "folio-bilingual";
|
|
27
|
+
const GRID_BORDER = {
|
|
28
|
+
style: "single",
|
|
29
|
+
size: 4,
|
|
30
|
+
space: 0
|
|
31
|
+
};
|
|
32
|
+
const TABLE_BORDERS = {
|
|
33
|
+
none: {
|
|
34
|
+
top: { style: "nil" },
|
|
35
|
+
bottom: { style: "nil" },
|
|
36
|
+
left: { style: "nil" },
|
|
37
|
+
right: { style: "nil" },
|
|
38
|
+
insideH: { style: "nil" },
|
|
39
|
+
insideV: { style: "nil" }
|
|
40
|
+
},
|
|
41
|
+
grid: {
|
|
42
|
+
top: GRID_BORDER,
|
|
43
|
+
bottom: GRID_BORDER,
|
|
44
|
+
left: GRID_BORDER,
|
|
45
|
+
right: GRID_BORDER,
|
|
46
|
+
insideH: GRID_BORDER,
|
|
47
|
+
insideV: GRID_BORDER
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function createBilingualDocument(source, options) {
|
|
51
|
+
if (!STYLE_SUFFIX_PATTERN.test(options.targetStyleSuffix)) throw new InvalidBilingualDocumentOptionsError({
|
|
52
|
+
message: `targetStyleSuffix must match ${STYLE_SUFFIX_PATTERN}; received ${JSON.stringify(options.targetStyleSuffix)}`,
|
|
53
|
+
option: "targetStyleSuffix"
|
|
54
|
+
});
|
|
55
|
+
const borders = options.borders ?? "none";
|
|
56
|
+
const warnings = [];
|
|
57
|
+
const styles = source.package.styles;
|
|
58
|
+
const numbering = source.package.numbering;
|
|
59
|
+
const styleById = new Map((styles?.styles ?? []).map((style) => [style.styleId, style]));
|
|
60
|
+
const blocks = flattenBlocks(source.package.document.content);
|
|
61
|
+
const cloner = createNumberingCloner({
|
|
62
|
+
numbering,
|
|
63
|
+
styleById,
|
|
64
|
+
warnings
|
|
65
|
+
});
|
|
66
|
+
const styleCloner = createStyleCloner({
|
|
67
|
+
styleById,
|
|
68
|
+
suffix: options.targetStyleSuffix,
|
|
69
|
+
cloner
|
|
70
|
+
});
|
|
71
|
+
const paraIds = createParaIdMinter(collectPackageParaIds(source.package));
|
|
72
|
+
const rows = [];
|
|
73
|
+
const content = [];
|
|
74
|
+
let sectionRows = [];
|
|
75
|
+
const textWidth = resolveTextWidthTwips(source);
|
|
76
|
+
const flushSection = () => {
|
|
77
|
+
if (sectionRows.length > 0) content.push(buildTable(sectionRows, borders, textWidth));
|
|
78
|
+
sectionRows = [];
|
|
79
|
+
};
|
|
80
|
+
const copyParagraph = (paragraph) => {
|
|
81
|
+
const targetParaId = paraIds.mint(paragraph.paraId);
|
|
82
|
+
return {
|
|
83
|
+
copy: cloneParagraphForTarget(paragraph, targetParaId, styleCloner, cloner),
|
|
84
|
+
ref: {
|
|
85
|
+
sourceParaId: paragraph.paraId,
|
|
86
|
+
targetParaId,
|
|
87
|
+
sourceText: getParagraphText(paragraph)
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
for (const block of blocks) {
|
|
92
|
+
if (block.type === "paragraph" && block.sectionProperties) {
|
|
93
|
+
flushSection();
|
|
94
|
+
content.push(block);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (block.type === "paragraph") {
|
|
98
|
+
if (isEmptyParagraph(block)) continue;
|
|
99
|
+
const { copy, ref } = copyParagraph(block);
|
|
100
|
+
rows.push({
|
|
101
|
+
kind: classifyParagraph(block, styleById),
|
|
102
|
+
rowId: ref.targetParaId,
|
|
103
|
+
...ref
|
|
104
|
+
});
|
|
105
|
+
sectionRows.push(buildRow(block, copy));
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const paragraphs = collectTableParagraphs(block).map((paragraph) => ({
|
|
109
|
+
paraId: paragraph.paraId,
|
|
110
|
+
sourceText: getParagraphText(paragraph)
|
|
111
|
+
}));
|
|
112
|
+
rows.push({
|
|
113
|
+
kind: "table",
|
|
114
|
+
rowId: paragraphs.at(0)?.paraId ?? tableRowHandle(rows.length),
|
|
115
|
+
paragraphs
|
|
116
|
+
});
|
|
117
|
+
sectionRows.push(buildSpanningRow(block));
|
|
118
|
+
}
|
|
119
|
+
flushSection();
|
|
120
|
+
return {
|
|
121
|
+
document: {
|
|
122
|
+
...source,
|
|
123
|
+
package: {
|
|
124
|
+
...source.package,
|
|
125
|
+
document: {
|
|
126
|
+
...source.package.document,
|
|
127
|
+
content
|
|
128
|
+
},
|
|
129
|
+
...cloner.hasClones() && { numbering: cloner.toDefinitions() },
|
|
130
|
+
...styleCloner.hasClones() && styles && { styles: styleCloner.toDefinitions(styles) }
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
rows,
|
|
134
|
+
warnings
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Handle for a table row whose paragraphs carry no `paraId`: its position in
|
|
138
|
+
* the manifest, which creation and reading derive identically. */
|
|
139
|
+
const tableRowHandle = (index) => `table-${index}`;
|
|
140
|
+
/** Top-level body blocks with content controls flattened to their children. */
|
|
141
|
+
const flattenBlocks = (content) => {
|
|
142
|
+
const out = [];
|
|
143
|
+
const visit = (block) => {
|
|
144
|
+
if (block.type === "paragraph" || block.type === "table") {
|
|
145
|
+
out.push(block);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const child of block.content) visit(child);
|
|
149
|
+
};
|
|
150
|
+
for (const block of content) visit(block);
|
|
151
|
+
return out;
|
|
152
|
+
};
|
|
153
|
+
const isEmptyParagraph = (paragraph) => {
|
|
154
|
+
if (getParagraphText(paragraph).trim().length > 0) return false;
|
|
155
|
+
return paragraph.content.every((item) => item.type === "run" && item.content.every((part) => part.type === "text"));
|
|
156
|
+
};
|
|
157
|
+
/** Heading style families across Word UI languages (en, cs/sk, de, fr, pl). */
|
|
158
|
+
const HEADING_STYLE_PATTERN = /heading|nadpis|berschrift|titre|nag[łl]/iu;
|
|
159
|
+
const classifyParagraph = (paragraph, styleById) => {
|
|
160
|
+
const formatting = paragraph.formatting;
|
|
161
|
+
const style = formatting?.styleId ? styleById.get(formatting.styleId) : void 0;
|
|
162
|
+
const outlineLevel = formatting?.outlineLevel ?? resolveInheritedOutlineLevel(style, styleById);
|
|
163
|
+
if (outlineLevel !== void 0 && outlineLevel < 9) return "heading";
|
|
164
|
+
if (style && (HEADING_STYLE_PATTERN.test(style.styleId) || HEADING_STYLE_PATTERN.test(style.name ?? ""))) return "heading";
|
|
165
|
+
if (effectiveNumPr(paragraph, styleById) !== void 0) return "listItem";
|
|
166
|
+
return "paragraph";
|
|
167
|
+
};
|
|
168
|
+
const resolveInheritedOutlineLevel = (style, styleById) => {
|
|
169
|
+
const seen = /* @__PURE__ */ new Set();
|
|
170
|
+
let current = style;
|
|
171
|
+
while (current && !seen.has(current.styleId)) {
|
|
172
|
+
seen.add(current.styleId);
|
|
173
|
+
if (current.pPr?.outlineLevel !== void 0) return current.pPr.outlineLevel;
|
|
174
|
+
current = current.basedOn ? styleById.get(current.basedOn) : void 0;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
/** The numbering a paragraph renders with: direct `numPr`, else the style chain's. */
|
|
178
|
+
const effectiveNumPr = (paragraph, styleById) => {
|
|
179
|
+
const direct = paragraph.formatting?.numPr;
|
|
180
|
+
if (direct?.numId !== void 0) return direct.numId === 0 ? void 0 : direct;
|
|
181
|
+
const styleId = paragraph.formatting?.styleId;
|
|
182
|
+
return styleId ? styleNumPr(styleById.get(styleId), styleById) : void 0;
|
|
183
|
+
};
|
|
184
|
+
const styleNumPr = (style, styleById) => {
|
|
185
|
+
const seen = /* @__PURE__ */ new Set();
|
|
186
|
+
let current = style;
|
|
187
|
+
while (current && !seen.has(current.styleId)) {
|
|
188
|
+
seen.add(current.styleId);
|
|
189
|
+
const numPr = current.pPr?.numPr;
|
|
190
|
+
if (numPr?.numId !== void 0) return numPr.numId === 0 ? void 0 : numPr;
|
|
191
|
+
current = current.basedOn ? styleById.get(current.basedOn) : void 0;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const createNumberingCloner = ({ numbering, styleById, warnings }) => {
|
|
195
|
+
const abstractNums = numbering?.abstractNums ?? [];
|
|
196
|
+
const nums = numbering?.nums ?? [];
|
|
197
|
+
const abstractById = new Map(abstractNums.map((item) => [item.abstractNumId, item]));
|
|
198
|
+
const numById = new Map(nums.map((item) => [item.numId, item]));
|
|
199
|
+
let nextAbstractNumId = Math.max(0, ...abstractNums.map((item) => item.abstractNumId)) + 1;
|
|
200
|
+
let nextNumId = Math.max(0, ...nums.map((item) => item.numId)) + 1;
|
|
201
|
+
const clonedAbstract = /* @__PURE__ */ new Map();
|
|
202
|
+
const clonedNum = /* @__PURE__ */ new Map();
|
|
203
|
+
/**
|
|
204
|
+
* Word keys list counters by the abstract definition a `w:num` points at;
|
|
205
|
+
* two instances sharing one abstract continue the same sequence. A clone
|
|
206
|
+
* therefore needs its own abstract, and an abstract that only links to a
|
|
207
|
+
* numbering style must be materialized from that style's levels, otherwise
|
|
208
|
+
* both clones resolve to the same linked definition and share counters.
|
|
209
|
+
*/
|
|
210
|
+
const cloneAbstract = (sourceId) => {
|
|
211
|
+
const existing = clonedAbstract.get(sourceId);
|
|
212
|
+
if (existing) return existing;
|
|
213
|
+
const source = abstractById.get(sourceId);
|
|
214
|
+
if (!source) return;
|
|
215
|
+
const resolved = resolveLinkedAbstract(source);
|
|
216
|
+
const { numStyleLink: _numStyleLink, styleLink: _styleLink, ...rest } = resolved;
|
|
217
|
+
const clone = {
|
|
218
|
+
...rest,
|
|
219
|
+
abstractNumId: nextAbstractNumId,
|
|
220
|
+
levels: structuredClone(resolved.levels)
|
|
221
|
+
};
|
|
222
|
+
nextAbstractNumId += 1;
|
|
223
|
+
clonedAbstract.set(sourceId, clone);
|
|
224
|
+
return clone;
|
|
225
|
+
};
|
|
226
|
+
const resolveLinkedAbstract = (abstract) => {
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
let current = abstract;
|
|
229
|
+
while (current.numStyleLink && !seen.has(current.abstractNumId)) {
|
|
230
|
+
seen.add(current.abstractNumId);
|
|
231
|
+
const linkedNumId = styleById.get(current.numStyleLink)?.pPr?.numPr?.numId;
|
|
232
|
+
const linkedNum = linkedNumId === void 0 ? void 0 : numById.get(linkedNumId);
|
|
233
|
+
const linkedAbstract = linkedNum ? abstractById.get(linkedNum.abstractNumId) : void 0;
|
|
234
|
+
if (!linkedAbstract) {
|
|
235
|
+
warnings.push(`Numbering style link "${current.numStyleLink}" on abstractNum ${current.abstractNumId} could not be resolved; the clone keeps the link.`);
|
|
236
|
+
return current;
|
|
237
|
+
}
|
|
238
|
+
current = linkedAbstract;
|
|
239
|
+
}
|
|
240
|
+
return current;
|
|
241
|
+
};
|
|
242
|
+
const cloneNumId = (numId) => {
|
|
243
|
+
const existing = clonedNum.get(numId);
|
|
244
|
+
if (existing) return existing.numId;
|
|
245
|
+
const source = numById.get(numId);
|
|
246
|
+
if (!source) {
|
|
247
|
+
warnings.push(`Numbering instance ${numId} is not defined; paragraphs using it keep the source instance.`);
|
|
248
|
+
return numId;
|
|
249
|
+
}
|
|
250
|
+
const abstract = cloneAbstract(source.abstractNumId);
|
|
251
|
+
if (!abstract) warnings.push(`Numbering instance ${numId} references abstractNum ${source.abstractNumId}, which is not defined; its copy shares the source counters.`);
|
|
252
|
+
const clone = {
|
|
253
|
+
...source,
|
|
254
|
+
numId: nextNumId,
|
|
255
|
+
abstractNumId: abstract ? abstract.abstractNumId : source.abstractNumId
|
|
256
|
+
};
|
|
257
|
+
nextNumId += 1;
|
|
258
|
+
clonedNum.set(numId, clone);
|
|
259
|
+
return clone.numId;
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
cloneNumId,
|
|
263
|
+
clonedAbstractNumId: (abstractNumId) => clonedAbstract.get(abstractNumId)?.abstractNumId,
|
|
264
|
+
hasClones: () => clonedNum.size > 0,
|
|
265
|
+
toDefinitions: () => ({
|
|
266
|
+
abstractNums: [...abstractNums, ...clonedAbstract.values()],
|
|
267
|
+
nums: [...nums, ...clonedNum.values()]
|
|
268
|
+
})
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* A paragraph style is cloned only when its chain carries numbering. The clone
|
|
273
|
+
* keeps `basedOn` and every other property; only `pPr.numPr` is rewritten to
|
|
274
|
+
* the cloned instance, so indent precedence stays "style-sourced" exactly as
|
|
275
|
+
* in the source (see `ParagraphFormatting.numPrFromStyle`).
|
|
276
|
+
*/
|
|
277
|
+
const createStyleCloner = ({ styleById, suffix, cloner }) => {
|
|
278
|
+
const clones = /* @__PURE__ */ new Map();
|
|
279
|
+
const styleIdFor = (styleId) => {
|
|
280
|
+
const existing = clones.get(styleId);
|
|
281
|
+
if (existing) return existing.styleId;
|
|
282
|
+
const style = styleById.get(styleId);
|
|
283
|
+
if (!style || style.type !== "paragraph") return styleId;
|
|
284
|
+
const numPr = styleNumPr(style, styleById);
|
|
285
|
+
if (numPr?.numId === void 0) return styleId;
|
|
286
|
+
const cloneId = `${styleId}-${suffix}`;
|
|
287
|
+
if (styleById.has(cloneId)) return cloneId;
|
|
288
|
+
const clone = {
|
|
289
|
+
...style,
|
|
290
|
+
styleId: cloneId,
|
|
291
|
+
name: `${style.name ?? style.styleId} (${suffix})`,
|
|
292
|
+
...style.next !== void 0 && { next: style.next === styleId ? cloneId : style.next },
|
|
293
|
+
default: false,
|
|
294
|
+
pPr: {
|
|
295
|
+
...style.pPr,
|
|
296
|
+
numPr: {
|
|
297
|
+
...numPr,
|
|
298
|
+
numId: cloner.cloneNumId(numPr.numId)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
clones.set(styleId, clone);
|
|
303
|
+
return cloneId;
|
|
304
|
+
};
|
|
305
|
+
return {
|
|
306
|
+
styleIdFor,
|
|
307
|
+
hasClones: () => clones.size > 0,
|
|
308
|
+
toDefinitions: (styles) => ({
|
|
309
|
+
...styles,
|
|
310
|
+
styles: [...styles.styles, ...clones.values()]
|
|
311
|
+
})
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
const cloneParagraphForTarget = (paragraph, targetParaId, styleCloner, cloner) => {
|
|
315
|
+
const { textId: _textId, sectionProperties: _sectionProperties, ...rest } = paragraph;
|
|
316
|
+
const formatting = paragraph.formatting;
|
|
317
|
+
const nextFormatting = formatting && {
|
|
318
|
+
...formatting,
|
|
319
|
+
...formatting.styleId !== void 0 && { styleId: styleCloner.styleIdFor(formatting.styleId) },
|
|
320
|
+
...formatting.numPr?.numId !== void 0 && formatting.numPr.numId !== 0 && { numPr: {
|
|
321
|
+
...formatting.numPr,
|
|
322
|
+
numId: cloner.cloneNumId(formatting.numPr.numId)
|
|
323
|
+
} },
|
|
324
|
+
...formatting.numPrFromStyle?.numId !== void 0 && formatting.numPrFromStyle.numId !== 0 && { numPrFromStyle: {
|
|
325
|
+
...formatting.numPrFromStyle,
|
|
326
|
+
numId: cloner.cloneNumId(formatting.numPrFromStyle.numId)
|
|
327
|
+
} }
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
...rest,
|
|
331
|
+
content: structuredClone(paragraph.content),
|
|
332
|
+
paraId: targetParaId,
|
|
333
|
+
...nextFormatting && { formatting: nextFormatting },
|
|
334
|
+
...paragraph.listRendering && { listRendering: remapListRendering(paragraph.listRendering, cloner) }
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
const remapListRendering = (rendering, cloner) => {
|
|
338
|
+
const clonedAbstract = rendering.abstractNumId === void 0 ? void 0 : cloner.clonedAbstractNumId(rendering.abstractNumId);
|
|
339
|
+
return {
|
|
340
|
+
...rendering,
|
|
341
|
+
numId: cloner.cloneNumId(rendering.numId),
|
|
342
|
+
...clonedAbstract !== void 0 && { abstractNumId: clonedAbstract }
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
const collectTableParagraphs = (table) => {
|
|
346
|
+
const out = [];
|
|
347
|
+
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);
|
|
348
|
+
else out.push(...collectTableParagraphs(item));
|
|
349
|
+
return out;
|
|
350
|
+
};
|
|
351
|
+
const buildRow = (left, right) => ({
|
|
352
|
+
type: "tableRow",
|
|
353
|
+
formatting: { cantSplit: true },
|
|
354
|
+
cells: [buildCell(left), buildCell(right)]
|
|
355
|
+
});
|
|
356
|
+
const buildCell = (paragraph) => ({
|
|
357
|
+
type: "tableCell",
|
|
358
|
+
formatting: {
|
|
359
|
+
width: {
|
|
360
|
+
value: HALF_WIDTH_PCT,
|
|
361
|
+
type: "pct"
|
|
362
|
+
},
|
|
363
|
+
verticalAlign: "top"
|
|
364
|
+
},
|
|
365
|
+
content: [paragraph]
|
|
366
|
+
});
|
|
367
|
+
/** A source table kept once, across both columns. */
|
|
368
|
+
const buildSpanningRow = (table) => ({
|
|
369
|
+
type: "tableRow",
|
|
370
|
+
formatting: { cantSplit: true },
|
|
371
|
+
cells: [{
|
|
372
|
+
type: "tableCell",
|
|
373
|
+
formatting: {
|
|
374
|
+
width: {
|
|
375
|
+
value: FULL_WIDTH_PCT,
|
|
376
|
+
type: "pct"
|
|
377
|
+
},
|
|
378
|
+
gridSpan: 2,
|
|
379
|
+
verticalAlign: "top"
|
|
380
|
+
},
|
|
381
|
+
content: [table, {
|
|
382
|
+
type: "paragraph",
|
|
383
|
+
content: []
|
|
384
|
+
}]
|
|
385
|
+
}]
|
|
386
|
+
});
|
|
387
|
+
const buildTable = (rows, borders, textWidth) => ({
|
|
388
|
+
type: "table",
|
|
389
|
+
formatting: {
|
|
390
|
+
width: {
|
|
391
|
+
value: FULL_WIDTH_PCT,
|
|
392
|
+
type: "pct"
|
|
393
|
+
},
|
|
394
|
+
layout: "fixed",
|
|
395
|
+
borders: TABLE_BORDERS[borders],
|
|
396
|
+
look: {
|
|
397
|
+
firstRow: false,
|
|
398
|
+
firstColumn: false,
|
|
399
|
+
noHBand: true,
|
|
400
|
+
noVBand: true
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
columnWidths: [Math.floor(textWidth / 2), Math.ceil(textWidth / 2)],
|
|
404
|
+
rows
|
|
405
|
+
});
|
|
406
|
+
const resolveTextWidthTwips = (doc) => {
|
|
407
|
+
const section = doc.package.document.finalSectionProperties ?? doc.package.document.sections?.at(0)?.properties;
|
|
408
|
+
if (!section?.pageWidth) return A4_TEXT_WIDTH_TWIPS;
|
|
409
|
+
const width = section.pageWidth - (section.marginLeft ?? 0) - (section.marginRight ?? 0);
|
|
410
|
+
return width > 0 ? width : A4_TEXT_WIDTH_TWIPS;
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Every `paraId` anywhere in the package (body, headers, footers, notes,
|
|
414
|
+
* comments), so a minted id cannot collide with a part the body never sees.
|
|
415
|
+
* Walks the model generically: any object with `type: "paragraph"` and a
|
|
416
|
+
* string `paraId` counts.
|
|
417
|
+
*/
|
|
418
|
+
const collectPackageParaIds = (pkg) => {
|
|
419
|
+
const ids = /* @__PURE__ */ new Set();
|
|
420
|
+
const seen = /* @__PURE__ */ new Set();
|
|
421
|
+
const visit = (value) => {
|
|
422
|
+
if (typeof value !== "object" || value === null || seen.has(value)) return;
|
|
423
|
+
seen.add(value);
|
|
424
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return;
|
|
425
|
+
if (Array.isArray(value)) {
|
|
426
|
+
for (const item of value) visit(item);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (value instanceof Map) {
|
|
430
|
+
for (const item of value.values()) visit(item);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (isParagraphWithId(value)) ids.add(value.paraId);
|
|
434
|
+
for (const child of Object.values(value)) visit(child);
|
|
435
|
+
};
|
|
436
|
+
visit(pkg);
|
|
437
|
+
return ids;
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* Fresh ids derived from the source id (so re-running on the same source
|
|
441
|
+
* yields the same handles), salted past any id already in the document.
|
|
442
|
+
*/
|
|
443
|
+
const createParaIdMinter = (taken) => {
|
|
444
|
+
let ordinal = 0;
|
|
445
|
+
return { mint: (sourceParaId) => {
|
|
446
|
+
ordinal += 1;
|
|
447
|
+
const seed = `${ROW_ID_NAMESPACE}:${sourceParaId ?? `ordinal-${ordinal}`}`;
|
|
448
|
+
let id = deterministicHexId(seed);
|
|
449
|
+
for (let salt = 1; taken.has(id); salt += 1) id = deterministicHexId(`${seed}:${salt}`);
|
|
450
|
+
taken.add(id);
|
|
451
|
+
return id;
|
|
452
|
+
} };
|
|
453
|
+
};
|
|
454
|
+
/**
|
|
455
|
+
* Re-derive the row manifest from a document produced by
|
|
456
|
+
* {@link createBilingualDocument}. Detection is structural: a top-level table
|
|
457
|
+
* whose rows are all either a left | right pair of single-paragraph cells or
|
|
458
|
+
* one cell spanning both columns. Rows are returned in document order; the
|
|
459
|
+
* right paragraph's `paraId` is the row handle, as at creation.
|
|
460
|
+
*/
|
|
461
|
+
function readBilingualDocument(document) {
|
|
462
|
+
const styleById = new Map((document.package.styles?.styles ?? []).map((style) => [style.styleId, style]));
|
|
463
|
+
const rows = [];
|
|
464
|
+
for (const block of flattenBlocks(document.package.document.content)) {
|
|
465
|
+
if (block.type !== "table" || !isBilingualTable(block)) continue;
|
|
466
|
+
for (const row of block.rows) {
|
|
467
|
+
const [left, right] = row.cells;
|
|
468
|
+
if (!left) continue;
|
|
469
|
+
if (!right) {
|
|
470
|
+
const paragraphs = left.content.filter((item) => item.type === "table").flatMap(collectTableParagraphs).map((paragraph) => ({
|
|
471
|
+
paraId: paragraph.paraId,
|
|
472
|
+
sourceText: getParagraphText(paragraph)
|
|
473
|
+
}));
|
|
474
|
+
rows.push({
|
|
475
|
+
kind: "table",
|
|
476
|
+
rowId: paragraphs.at(0)?.paraId ?? tableRowHandle(rows.length),
|
|
477
|
+
paragraphs
|
|
478
|
+
});
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const source = left.content.at(0);
|
|
482
|
+
const target = right.content.at(0);
|
|
483
|
+
if (source?.type !== "paragraph" || target?.type !== "paragraph" || !target.paraId) continue;
|
|
484
|
+
rows.push({
|
|
485
|
+
kind: classifyParagraph(source, styleById),
|
|
486
|
+
rowId: target.paraId,
|
|
487
|
+
sourceParaId: source.paraId,
|
|
488
|
+
targetParaId: target.paraId,
|
|
489
|
+
sourceText: getParagraphText(source)
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return rows;
|
|
494
|
+
}
|
|
495
|
+
const isBilingualTable = (table) => {
|
|
496
|
+
let pairs = 0;
|
|
497
|
+
for (const row of table.rows) {
|
|
498
|
+
const cells = row.cells;
|
|
499
|
+
if (cells.length === 2) {
|
|
500
|
+
if (!cells.every((cell) => cell.content.length === 1 && cell.content[0]?.type === "paragraph")) return false;
|
|
501
|
+
pairs += 1;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (cells.length === 1 && cells[0]?.formatting?.gridSpan === 2) continue;
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
return pairs > 0;
|
|
508
|
+
};
|
|
509
|
+
const isParagraphWithId = (value) => "type" in value && value.type === "paragraph" && "paraId" in value && typeof value.paraId === "string";
|
|
510
|
+
//#endregion
|
|
511
|
+
export { InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BilingualRow, CreateBilingualDocumentOptions } from "./createBilingualDocument.js";
|
|
2
|
+
//#region src/docx/server/createBilingualDocx.d.ts
|
|
3
|
+
type CreateBilingualDocxResult = {
|
|
4
|
+
buffer: ArrayBuffer;
|
|
5
|
+
rows: BilingualRow[];
|
|
6
|
+
warnings: string[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Bytes-in / bytes-out form of {@link createBilingualDocument}: stamp every
|
|
10
|
+
* paragraph with a `paraId` (so left-column rows are addressable later), parse
|
|
11
|
+
* the DOCX (no font preloading, so it never touches the DOM), lay the body out
|
|
12
|
+
* as a two-column table, and repack onto the original package so theme, fonts,
|
|
13
|
+
* media, headers and footers carry over untouched.
|
|
14
|
+
*/
|
|
15
|
+
declare function createBilingualDocx(input: ArrayBuffer | Uint8Array, options: CreateBilingualDocumentOptions): Promise<CreateBilingualDocxResult>;
|
|
16
|
+
/** Bytes-in form of {@link readBilingualDocument}. */
|
|
17
|
+
declare function readBilingualDocx(input: ArrayBuffer | Uint8Array): Promise<BilingualRow[]>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { CreateBilingualDocxResult, createBilingualDocx, readBilingualDocx };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ensureParaIds } from "../ensureParaIds.js";
|
|
2
|
+
import { parseDocx } from "../parser.js";
|
|
3
|
+
import { createDocx } from "../rezip.js";
|
|
4
|
+
import { createBilingualDocument, readBilingualDocument } from "./createBilingualDocument.js";
|
|
5
|
+
//#region src/docx/server/createBilingualDocx.ts
|
|
6
|
+
/**
|
|
7
|
+
* Bytes-in / bytes-out form of {@link createBilingualDocument}: stamp every
|
|
8
|
+
* paragraph with a `paraId` (so left-column rows are addressable later), parse
|
|
9
|
+
* the DOCX (no font preloading, so it never touches the DOM), lay the body out
|
|
10
|
+
* as a two-column table, and repack onto the original package so theme, fonts,
|
|
11
|
+
* media, headers and footers carry over untouched.
|
|
12
|
+
*/
|
|
13
|
+
async function createBilingualDocx(input, options) {
|
|
14
|
+
const { document, rows, warnings } = createBilingualDocument(await parseDocx((await ensureParaIds(input)).docx, { preloadFonts: false }), options);
|
|
15
|
+
return {
|
|
16
|
+
buffer: await createDocx(document),
|
|
17
|
+
rows,
|
|
18
|
+
warnings
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Bytes-in form of {@link readBilingualDocument}. */
|
|
22
|
+
async function readBilingualDocx(input) {
|
|
23
|
+
return readBilingualDocument(await parseDocx(input, { preloadFonts: false }));
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { createBilingualDocx, readBilingualDocx };
|
package/dist/server.d.ts
CHANGED
|
@@ -19,9 +19,11 @@ import { EvaluateDocxXmlPatchProposalArgs, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULT
|
|
|
19
19
|
import { FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FolioDocxConformanceCheck, FolioDocxConformanceCheckId, FolioDocxConformanceCheckStatus, FolioDocxConformanceIssue, FolioDocxConformanceIssueCode, FolioDocxConformanceReport, FolioDocxConformanceStatus, ValidateDocxConformanceOptions, validateDocxConformance } from "./docx/server/validateDocxConformance.js";
|
|
20
20
|
import { ApplyDocxXmlPatchProposalArgs, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FolioDocxXmlPatchApplication, FolioDocxXmlPatchApplicationError, FolioDocxXmlPatchApplicationReceipt, UnsupportedFolioDocxXmlPatchApplicationProfileError, applyDocxXmlPatchProposal } from "./docx/server/applyDocxXmlPatchProposal.js";
|
|
21
21
|
import { HEADING_LEVELS, HeadingLevel, InvalidFolioReportBuilderOptionsError, TableCellSpec, bookmark, createTableOfContentsField, endnote, heading, hyperlink, pageBreak, paragraph, run, table } from "./docx/server/build.js";
|
|
22
|
+
import { BilingualBorders, BilingualParagraphRef, BilingualRow, BilingualRowKind, BilingualTableParagraphRef, CreateBilingualDocumentOptions, CreateBilingualDocumentResult, InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument } from "./docx/server/createBilingualDocument.js";
|
|
23
|
+
import { CreateBilingualDocxResult, createBilingualDocx, readBilingualDocx } from "./docx/server/createBilingualDocx.js";
|
|
22
24
|
import { docxToMarkdown } from "./docx/server/docxToMarkdown.js";
|
|
23
25
|
import { DocxParagraphSource, DocxTableRowKind, DocxTableRowPosition, ExtractedDocxParagraph, ExtractedDocxTableCell, ExtractedDocxTableCellParagraph, ExtractedDocxText, extractDocxText } from "./docx/server/extractDocxText.js";
|
|
24
26
|
import { FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FolioDocxInspectedXmlPart, FolioDocxPackageInspection, FolioDocxPackageInspectionError, FolioDocxPackageInspectionErrorCode, FolioDocxPackageInspectionLimits, FolioDocxPackagePart, FolioDocxPackagePartKind, InspectDocxPackageOptions, inspectDocxPackage } from "./docx/server/inspectDocxPackage.js";
|
|
25
27
|
import { GenerateRedlineDocxOptions, GenerateRedlineDocxResult, GenerateRedlineUnprocessedStory, InvalidGenerateRedlineDocxOptionsError, generateRedlineDocx } from "./redline.js";
|
|
26
28
|
import { FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FolioBlockDiff, FolioCompareDocxVersionsOptions, FolioDocumentMetadataValue, FolioFormatProperty, FolioMetadataDiff, FolioStoryDiff, FolioVersionBlockHandle, FolioVersionComparisonPrivacyTransform, FolioVersionComparisonScope, FolioVersionDiff, FolioVersionDiffPrivacyOptions, FolioVersionDiffPrivacyReport, FolioVersionDiffSegment, FolioVersionDiffSummaryCounts, InvalidFolioVersionComparisonOptionsError, applyFolioVersionDiffPrivacy, compareDocxVersions, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope } from "./version-comparison.js";
|
|
27
|
-
export { type ApplyDocxXmlPatchProposalArgs, type ApplyFolioAIEditsToBufferOptions, type ApplyFolioAIEditsToBufferResult, type CreateCommentReplyInput, type CreateEmptyDocumentOptions, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, type DeriveBlockIdInput, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, DocxArchiveError, type DocxArchiveOptions, type DocxParagraphSource, type DocxTableRowKind, type DocxTableRowPosition, EnsureParaIdsError, type EnsureParaIdsOptions, type EnsureParaIdsResult, type EvaluateDocxXmlPatchProposalArgs, type ExtractDocumentStyleSetOptions, type ExtractedDocxParagraph, type ExtractedDocxTableCell, type ExtractedDocxTableCellParagraph, type ExtractedDocxText, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditSnapshot, type FolioAIInlineFormatting, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsOptions, type FolioApplyDocumentOperationsToStoryOptions, type FolioApplyOperationsOptions, type FolioBlockDiff, type FolioBlockId, type FolioCompareDocxVersionsOptions, type FolioDocumentMetadataProperty, type FolioDocumentMetadataValue, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioDocxConformanceCheck, type FolioDocxConformanceCheckId, type FolioDocxConformanceCheckStatus, type FolioDocxConformanceIssue, type FolioDocxConformanceIssueCode, type FolioDocxConformanceReport, type FolioDocxConformanceStatus, type FolioDocxInspectedXmlPart, type FolioDocxPackageInspection, FolioDocxPackageInspectionError, type FolioDocxPackageInspectionErrorCode, type FolioDocxPackageInspectionLimits, type FolioDocxPackagePart, type FolioDocxPackagePartKind, type FolioDocxPreparedXmlReplacement, FolioDocxReviewer, type FolioDocxReviewerOptions, type FolioDocxXmlPatchApplication, FolioDocxXmlPatchApplicationError, type FolioDocxXmlPatchApplicationReceipt, type FolioDocxXmlPatchProposal, type FolioDocxXmlPatchProposalEvaluation, type FolioDocxXmlPatchProposalIssue, type FolioDocxXmlPatchProposalIssueCode, type FolioDocxXmlPatchProposalLimits, type FolioDocxXmlReplacement, type FolioEditableDocumentStoryHandle, type FolioFormatProperty, type FolioMetadataDiff, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeFilter, type FolioReviewChangeKind, type FolioReviewComment, type FolioReviewCommentFilter, type FolioReviewCommentReply, type FolioReviewReplyInput, type FolioReviewedStory, type FolioReviewedView, type FolioStoryDiff, type FolioVersionBlockHandle, type FolioVersionComparisonPrivacyTransform, type FolioVersionComparisonScope, type FolioVersionDiff, type FolioVersionDiffPrivacyOptions, type FolioVersionDiffPrivacyReport, type FolioVersionDiffSegment, type FolioVersionDiffSummaryCounts, type GenerateRedlineDocxOptions, type GenerateRedlineDocxResult, type GenerateRedlineUnprocessedStory, HEADING_LEVELS, type HeadingLevel, type InspectDocxPackageOptions, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, type ParseOptions, type RewriteDocxMetadataPrivacyResult, STELLA_STYLE_SET_NAME, type TableCellSpec, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, type ValidateDocxConformanceOptions, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
|
|
29
|
+
export { type ApplyDocxXmlPatchProposalArgs, type ApplyFolioAIEditsToBufferOptions, type ApplyFolioAIEditsToBufferResult, type BilingualBorders, type BilingualParagraphRef, type BilingualRow, type BilingualRowKind, type BilingualTableParagraphRef, type CreateBilingualDocumentOptions, type CreateBilingualDocumentResult, type CreateBilingualDocxResult, type CreateCommentReplyInput, type CreateEmptyDocumentOptions, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, type DeriveBlockIdInput, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, DocxArchiveError, type DocxArchiveOptions, type DocxParagraphSource, type DocxTableRowKind, type DocxTableRowPosition, EnsureParaIdsError, type EnsureParaIdsOptions, type EnsureParaIdsResult, type EvaluateDocxXmlPatchProposalArgs, type ExtractDocumentStyleSetOptions, type ExtractedDocxParagraph, type ExtractedDocxTableCell, type ExtractedDocxTableCellParagraph, type ExtractedDocxText, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditSnapshot, type FolioAIInlineFormatting, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsOptions, type FolioApplyDocumentOperationsToStoryOptions, type FolioApplyOperationsOptions, type FolioBlockDiff, type FolioBlockId, type FolioCompareDocxVersionsOptions, type FolioDocumentMetadataProperty, type FolioDocumentMetadataValue, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioDocxConformanceCheck, type FolioDocxConformanceCheckId, type FolioDocxConformanceCheckStatus, type FolioDocxConformanceIssue, type FolioDocxConformanceIssueCode, type FolioDocxConformanceReport, type FolioDocxConformanceStatus, type FolioDocxInspectedXmlPart, type FolioDocxPackageInspection, FolioDocxPackageInspectionError, type FolioDocxPackageInspectionErrorCode, type FolioDocxPackageInspectionLimits, type FolioDocxPackagePart, type FolioDocxPackagePartKind, type FolioDocxPreparedXmlReplacement, FolioDocxReviewer, type FolioDocxReviewerOptions, type FolioDocxXmlPatchApplication, FolioDocxXmlPatchApplicationError, type FolioDocxXmlPatchApplicationReceipt, type FolioDocxXmlPatchProposal, type FolioDocxXmlPatchProposalEvaluation, type FolioDocxXmlPatchProposalIssue, type FolioDocxXmlPatchProposalIssueCode, type FolioDocxXmlPatchProposalLimits, type FolioDocxXmlReplacement, type FolioEditableDocumentStoryHandle, type FolioFormatProperty, type FolioMetadataDiff, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeFilter, type FolioReviewChangeKind, type FolioReviewComment, type FolioReviewCommentFilter, type FolioReviewCommentReply, type FolioReviewReplyInput, type FolioReviewedStory, type FolioReviewedView, type FolioStoryDiff, type FolioVersionBlockHandle, type FolioVersionComparisonPrivacyTransform, type FolioVersionComparisonScope, type FolioVersionDiff, type FolioVersionDiffPrivacyOptions, type FolioVersionDiffPrivacyReport, type FolioVersionDiffSegment, type FolioVersionDiffSummaryCounts, type GenerateRedlineDocxOptions, type GenerateRedlineDocxResult, type GenerateRedlineUnprocessedStory, HEADING_LEVELS, type HeadingLevel, type InspectDocxPackageOptions, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, type ParseOptions, type RewriteDocxMetadataPrivacyResult, STELLA_STYLE_SET_NAME, type TableCellSpec, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, type ValidateDocxConformanceOptions, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
|
package/dist/server.js
CHANGED
|
@@ -10,6 +10,8 @@ import { createDocx } from "./docx/rezip.js";
|
|
|
10
10
|
import { FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FolioDocxXmlPatchApplicationError, UnsupportedFolioDocxXmlPatchApplicationProfileError, applyDocxXmlPatchProposal } from "./docx/server/applyDocxXmlPatchProposal.js";
|
|
11
11
|
import { DocxArchiveError } from "./docx/server/boundedArchive.js";
|
|
12
12
|
import { HEADING_LEVELS, InvalidFolioReportBuilderOptionsError, bookmark, createTableOfContentsField, endnote, heading, hyperlink, pageBreak, paragraph, run, table } from "./docx/server/build.js";
|
|
13
|
+
import { InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument } from "./docx/server/createBilingualDocument.js";
|
|
14
|
+
import { createBilingualDocx, readBilingualDocx } from "./docx/server/createBilingualDocx.js";
|
|
13
15
|
import { docxToMarkdown } from "./docx/server/docxToMarkdown.js";
|
|
14
16
|
import { FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, evaluateDocxXmlPatchProposal, parseFolioDocxXmlPatchProposal } from "./docx/server/evaluateDocxXmlPatchProposal.js";
|
|
15
17
|
import { extractDocxText } from "./docx/server/extractDocxText.js";
|
|
@@ -22,4 +24,4 @@ import { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION } from "./style-set
|
|
|
22
24
|
import { deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
|
|
23
25
|
import { createEmptyDocument } from "./utils/createDocument.js";
|
|
24
26
|
import { FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, InvalidFolioVersionComparisonOptionsError, applyFolioVersionDiffPrivacy, compareDocxVersions, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope } from "./version-comparison.js";
|
|
25
|
-
export { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocxArchiveError, EnsureParaIdsError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FolioDocumentPrivacyArchiveError, FolioDocumentStoryNotFoundError, FolioDocxPackageInspectionError, FolioDocxReviewer, FolioDocxXmlPatchApplicationError, HEADING_LEVELS, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
|
|
27
|
+
export { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocxArchiveError, EnsureParaIdsError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FolioDocumentPrivacyArchiveError, FolioDocumentStoryNotFoundError, FolioDocxPackageInspectionError, FolioDocxReviewer, FolioDocxXmlPatchApplicationError, HEADING_LEVELS, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|