@stll/folio-core 0.20.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/paragraphParser.js +6 -4
- package/dist/docx/parser.js +6 -2
- package/dist/docx/rezip.d.ts +9 -1
- package/dist/docx/rezip.js +108 -41
- package/dist/docx/selectiveSave.js +6 -0
- package/dist/docx/selectiveXmlPatch.d.ts +19 -1
- package/dist/docx/selectiveXmlPatch.js +72 -1
- package/dist/docx/serializer/paragraphSerializer.js +1 -0
- package/dist/docx/serializer/settingsSerializer.js +1 -0
- package/dist/docx/serializer/stylesSerializer.d.ts +2 -1
- package/dist/docx/serializer/stylesSerializer.js +1 -1
- package/dist/docx/server/build.d.ts +103 -0
- package/dist/docx/server/build.js +265 -0
- 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/docx/settingsParser.js +8 -5
- package/dist/docx/xmlParser.d.ts +9 -1
- package/dist/docx/xmlParser.js +13 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.js +5 -1
- package/dist/style-sets/stellaStyle.js +146 -0
- package/dist/utils/createDocument.js +1 -0
- package/package.json +2 -2
|
@@ -712,14 +712,16 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
712
712
|
const run = parseRun(runElement, styles, theme, rels, media, inScopeXmlns);
|
|
713
713
|
const commentReferenceId = getCommentReferenceId(runElement);
|
|
714
714
|
let hasFieldBegin = false;
|
|
715
|
+
let beginFldLock = false;
|
|
716
|
+
let beginDirty = false;
|
|
715
717
|
let hasFieldSeparate = false;
|
|
716
718
|
let hasFieldEnd = false;
|
|
717
719
|
let endOriginalValue;
|
|
718
720
|
let instrText = "";
|
|
719
721
|
for (const content of run.content) if (content.type === "fieldChar") if (content.charType === "begin") {
|
|
720
722
|
hasFieldBegin = true;
|
|
721
|
-
|
|
722
|
-
|
|
723
|
+
beginFldLock = content.fldLock === true;
|
|
724
|
+
beginDirty = content.dirty === true;
|
|
723
725
|
} else if (content.charType === "separate") hasFieldSeparate = true;
|
|
724
726
|
else {
|
|
725
727
|
hasFieldEnd = true;
|
|
@@ -736,8 +738,8 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
736
738
|
complexFieldInstr = "";
|
|
737
739
|
complexFieldCodeRuns = [];
|
|
738
740
|
complexFieldResultRuns = [];
|
|
739
|
-
complexFieldLock =
|
|
740
|
-
complexFieldDirty =
|
|
741
|
+
complexFieldLock = beginFldLock;
|
|
742
|
+
complexFieldDirty = beginDirty;
|
|
741
743
|
complexFieldFormatting = run.formatting;
|
|
742
744
|
}
|
|
743
745
|
if (inComplexField) {
|
package/dist/docx/parser.js
CHANGED
|
@@ -371,8 +371,12 @@ function parseHeadersAndFooters(raw, styles, theme, numbering, rels, media) {
|
|
|
371
371
|
* Parse footnotes and endnotes from raw content
|
|
372
372
|
*/
|
|
373
373
|
function parseNotesContent(raw, styles, theme, numbering, rels, media) {
|
|
374
|
-
const
|
|
375
|
-
|
|
374
|
+
const relsForNotePart = (partPath) => {
|
|
375
|
+
const xml = getMapCaseInsensitive(raw.allXml, getRelationshipsPathForPart(partPath));
|
|
376
|
+
return xml ? parseRelationships(xml) : rels;
|
|
377
|
+
};
|
|
378
|
+
const footnoteMap = parseFootnotes(raw.footnotesXml, styles, theme, numbering, relsForNotePart("word/footnotes.xml"), media);
|
|
379
|
+
const endnoteMap = parseEndnotes(raw.endnotesXml, styles, theme, numbering, relsForNotePart("word/endnotes.xml"), media);
|
|
376
380
|
return {
|
|
377
381
|
footnotes: footnoteMap.getNormalFootnotes(),
|
|
378
382
|
endnotes: endnoteMap.getNormalEndnotes()
|
package/dist/docx/rezip.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { BlockContent, Hyperlink } from "../types/content.js";
|
|
2
3
|
import { RawDocxContent } from "./unzip.js";
|
|
3
4
|
import JSZip from "jszip";
|
|
4
5
|
//#region src/docx/rezip.d.ts
|
|
@@ -9,6 +10,11 @@ declare class DocxPackageFidelityError extends Error {
|
|
|
9
10
|
* Find the highest rId number in a relationships XML string.
|
|
10
11
|
*/
|
|
11
12
|
declare function findMaxRId(relsXml: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* Collect all hyperlinks that have an href but no rId from block content.
|
|
15
|
+
* These are newly created hyperlinks that need relationship entries.
|
|
16
|
+
*/
|
|
17
|
+
declare function collectHyperlinksWithoutRId(blocks: BlockContent[]): Hyperlink[];
|
|
12
18
|
/**
|
|
13
19
|
* Options for repacking DOCX
|
|
14
20
|
*/
|
|
@@ -126,6 +132,8 @@ declare function hasUnmaterializedHeaderFooter(doc: document_d_exports.Document)
|
|
|
126
132
|
*/
|
|
127
133
|
declare function hasModelDrivenPictureWatermark(doc: document_d_exports.Document): boolean;
|
|
128
134
|
declare function collectHeaderFooterUpdates(doc: document_d_exports.Document): Map<string, string>;
|
|
135
|
+
/** `word/Footnotes.xml` -> `word/_rels/Footnotes.xml.rels` (casing preserved). */
|
|
136
|
+
declare function notePartRelsPath(partPath: string): string;
|
|
129
137
|
/** Update existing core-property values without synthesizing absent metadata. */
|
|
130
138
|
declare function updateCoreProperties(corePropsXml: string, { updateModifiedDate, modifiedBy }: {
|
|
131
139
|
updateModifiedDate?: boolean;
|
|
@@ -163,4 +171,4 @@ declare function createEmptyDocx(): Promise<ArrayBuffer>;
|
|
|
163
171
|
*/
|
|
164
172
|
declare function createDocx(doc: document_d_exports.Document): Promise<ArrayBuffer>;
|
|
165
173
|
//#endregion
|
|
166
|
-
export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, RepackOptions, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, isDocxBuffer, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx };
|
|
174
|
+
export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, RepackOptions, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, collectHyperlinksWithoutRId, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, isDocxBuffer, notePartRelsPath, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx };
|
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,11 +14,11 @@ 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";
|
|
21
|
-
import { findChild, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml } from "./xmlParser.js";
|
|
21
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml } from "./xmlParser.js";
|
|
22
22
|
import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
|
|
23
23
|
import { panic } from "better-result";
|
|
24
24
|
import JSZip from "jszip";
|
|
@@ -70,8 +70,7 @@ function findMaxRId(relsXml) {
|
|
|
70
70
|
}
|
|
71
71
|
return maxId;
|
|
72
72
|
}
|
|
73
|
-
const
|
|
74
|
-
const isWordprocessingElement = (element, localName) => getLocalName(element.name) === localName && WORDPROCESSINGML_NAMESPACES.has(getNamespaceUri(element) ?? "");
|
|
73
|
+
const isWordprocessingElement = (element, localName) => getLocalName(element.name) === localName && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "");
|
|
75
74
|
const countDocumentSections = (xml) => {
|
|
76
75
|
assertXmlResourceLimits(xml);
|
|
77
76
|
let count = 0;
|
|
@@ -212,15 +211,22 @@ function relativeTargetForPart(partPath, absoluteTarget) {
|
|
|
212
211
|
while (shared < fromDir.length && shared < to.length - 1 && fromDir[shared] === to[shared]) shared += 1;
|
|
213
212
|
return `${"../".repeat(fromDir.length - shared)}${to.slice(shared).join("/")}`;
|
|
214
213
|
}
|
|
215
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Every package part whose block content may mint relationships (images,
|
|
216
|
+
* external hyperlinks), paired with the rels part those relationships belong
|
|
217
|
+
* to. Header/footer parts own `word/_rels/<part>.rels`; footnotes and endnotes
|
|
218
|
+
* own `word/_rels/<notes part>.rels`, derived from the ZIP entry the note
|
|
219
|
+
* serializer resolves (case-insensitively) so a producer's casing is reused
|
|
220
|
+
* instead of minting a second, lowercase rels part.
|
|
221
|
+
*/
|
|
222
|
+
function collectDocxParts(doc, zip) {
|
|
216
223
|
const parts = [{
|
|
217
224
|
relsPath: "word/_rels/document.xml.rels",
|
|
218
225
|
blocks: doc.package.document.content
|
|
219
226
|
}];
|
|
220
227
|
const rels = doc.package.relationships;
|
|
221
|
-
if (!rels) return parts;
|
|
222
228
|
const addHeaderFooterParts = (map, type) => {
|
|
223
|
-
if (!map) return;
|
|
229
|
+
if (!map || !rels) return;
|
|
224
230
|
for (const [rId, headerFooter] of map.entries()) {
|
|
225
231
|
const rel = rels.get(rId);
|
|
226
232
|
if (!rel || rel.type !== type || !rel.target) continue;
|
|
@@ -232,6 +238,16 @@ function collectImageParts(doc) {
|
|
|
232
238
|
};
|
|
233
239
|
addHeaderFooterParts(doc.package.headers, RELATIONSHIP_TYPES.header);
|
|
234
240
|
addHeaderFooterParts(doc.package.footers, RELATIONSHIP_TYPES.footer);
|
|
241
|
+
const addNoteParts = (notes, conventionalLowerPath) => {
|
|
242
|
+
if (!notes || notes.length === 0) return;
|
|
243
|
+
const partPath = findNotePartEntry(zip, conventionalLowerPath)?.name ?? conventionalLowerPath;
|
|
244
|
+
parts.push({
|
|
245
|
+
relsPath: notePartRelsPath(partPath),
|
|
246
|
+
blocks: notes.flatMap((note) => note.content)
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
addNoteParts(doc.package.footnotes, "word/footnotes.xml");
|
|
250
|
+
addNoteParts(doc.package.endnotes, "word/endnotes.xml");
|
|
235
251
|
return parts;
|
|
236
252
|
}
|
|
237
253
|
async function readRelsOrStub(zip, relsPath) {
|
|
@@ -360,30 +376,29 @@ function collectHyperlinksWithoutRId(blocks) {
|
|
|
360
376
|
for (const block of blocks) if (block.type === "paragraph") {
|
|
361
377
|
for (const item of block.content) if (item.type === "hyperlink" && item.href && !item.rId && !item.anchor) hyperlinks.push(item);
|
|
362
378
|
} else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
|
|
379
|
+
else hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
|
|
363
380
|
return hyperlinks;
|
|
364
381
|
}
|
|
365
382
|
/**
|
|
366
|
-
* Process newly created hyperlinks: assign rIds and add
|
|
367
|
-
* Mutates the hyperlinks' rId
|
|
383
|
+
* Process newly created hyperlinks in every part: assign rIds and add
|
|
384
|
+
* relationship entries to the owning part's rels. Mutates the hyperlinks' rId
|
|
385
|
+
* fields in-place.
|
|
368
386
|
*/
|
|
369
|
-
async function processNewHyperlinks(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (relEntries.length > 0) {
|
|
385
|
-
relsXml = relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`);
|
|
386
|
-
zip.file(relsPath, relsXml, {
|
|
387
|
+
async function processNewHyperlinks(parts, zip, compressionLevel) {
|
|
388
|
+
for (const { relsPath, blocks } of parts) {
|
|
389
|
+
const newHyperlinks = collectHyperlinksWithoutRId(blocks);
|
|
390
|
+
if (newHyperlinks.length === 0) continue;
|
|
391
|
+
const relsXml = await readRelsOrStub(zip, relsPath);
|
|
392
|
+
let maxId = findMaxRId(relsXml);
|
|
393
|
+
const relEntries = [];
|
|
394
|
+
for (const hyperlink of newHyperlinks) {
|
|
395
|
+
if (!hyperlink.href) continue;
|
|
396
|
+
maxId++;
|
|
397
|
+
const newRId = `rId${maxId}`;
|
|
398
|
+
relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.hyperlink}" Target="${escapeXml(hyperlink.href)}" TargetMode="External"/>`);
|
|
399
|
+
hyperlink.rId = newRId;
|
|
400
|
+
}
|
|
401
|
+
zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
387
402
|
compression: "DEFLATE",
|
|
388
403
|
compressionOptions: { level: compressionLevel }
|
|
389
404
|
});
|
|
@@ -420,8 +435,9 @@ const cloneDocxZip = (source) => {
|
|
|
420
435
|
};
|
|
421
436
|
const finishRepack = async ({ document, originalZip, outputZip, originalDocumentXml, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy }) => {
|
|
422
437
|
await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
|
|
423
|
-
|
|
424
|
-
await
|
|
438
|
+
const parts = collectDocxParts(document, outputZip);
|
|
439
|
+
await processNewImages(parts, outputZip, compressionLevel);
|
|
440
|
+
await processNewHyperlinks(parts, outputZip, compressionLevel);
|
|
425
441
|
assertValidFolioDocumentModel(document, "Cannot repack invalid DOCX document model");
|
|
426
442
|
applyReplyThreadMarkers(document);
|
|
427
443
|
const documentXml = serializeDocument(document);
|
|
@@ -434,6 +450,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
434
450
|
serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
435
451
|
await serializeNotesToZip(document, originalZip, outputZip, compressionLevel);
|
|
436
452
|
await serializeNumberingIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
453
|
+
await serializeAddedStylesIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
437
454
|
await serializeCommentsToZip(document, outputZip, compressionLevel);
|
|
438
455
|
if (updateModifiedDate && originalCorePropertiesXml) {
|
|
439
456
|
const updatedCoreProperties = updateCoreProperties(originalCorePropertiesXml, {
|
|
@@ -499,8 +516,9 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
499
516
|
});
|
|
500
517
|
}
|
|
501
518
|
await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
|
|
502
|
-
|
|
503
|
-
await
|
|
519
|
+
const parts = collectDocxParts(exportDocument, newZip);
|
|
520
|
+
await processNewImages(parts, newZip, compressionLevel);
|
|
521
|
+
await processNewHyperlinks(parts, newZip, compressionLevel);
|
|
504
522
|
assertValidFolioDocumentModel(exportDocument, "Cannot repack invalid DOCX document model");
|
|
505
523
|
applyReplyThreadMarkers(exportDocument);
|
|
506
524
|
const documentXml = serializeDocument(exportDocument);
|
|
@@ -513,6 +531,7 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
513
531
|
serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
514
532
|
await serializeNotesToZip(exportDocument, rawContent.originalZip, newZip, compressionLevel);
|
|
515
533
|
await serializeNumberingIntoZip(exportDocument, rawContent.originalZip, newZip, compressionLevel);
|
|
534
|
+
await serializeAddedStylesIntoZip(exportDocument, rawContent.originalZip, newZip, compressionLevel);
|
|
516
535
|
await serializeCommentsToZip(exportDocument, newZip, compressionLevel);
|
|
517
536
|
if (updateModifiedDate && rawContent.corePropsXml) {
|
|
518
537
|
const updatedCoreProps = updateCoreProperties(rawContent.corePropsXml, {
|
|
@@ -768,14 +787,17 @@ function findMaxHeaderFooterNum(zip, prefix) {
|
|
|
768
787
|
* relationship.
|
|
769
788
|
*/
|
|
770
789
|
async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
771
|
-
const rels = doc.package.relationships;
|
|
772
|
-
if (!rels) return;
|
|
773
790
|
if (!hasUnmaterializedHeaderFooter(doc)) return;
|
|
791
|
+
doc.package.relationships ??= /* @__PURE__ */ new Map();
|
|
792
|
+
const rels = doc.package.relationships;
|
|
774
793
|
const relEntries = [];
|
|
775
794
|
const overrides = [];
|
|
776
795
|
let maxHeaderNum = findMaxHeaderFooterNum(zip, "header");
|
|
777
796
|
let maxFooterNum = findMaxHeaderFooterNum(zip, "footer");
|
|
778
|
-
|
|
797
|
+
const relsPath = "word/_rels/document.xml.rels";
|
|
798
|
+
const relsXml = await readRelsOrStub(zip, relsPath);
|
|
799
|
+
const zipRels = parseRelationships(relsXml);
|
|
800
|
+
let maxRId = findMaxRId(relsXml);
|
|
779
801
|
const considerNumericRId = (id) => {
|
|
780
802
|
const match = /^rId(?<num>\d+)$/u.exec(id);
|
|
781
803
|
if (match) {
|
|
@@ -792,7 +814,7 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
792
814
|
const materialize = (map, relType, prefix, contentType, isHeader) => {
|
|
793
815
|
if (!map) return;
|
|
794
816
|
for (const rId of [...map.keys()]) {
|
|
795
|
-
const existing = rels.get(rId);
|
|
817
|
+
const existing = rels.get(rId) ?? zipRels.get(rId);
|
|
796
818
|
if (existing && existing.type === relType && existing.target) continue;
|
|
797
819
|
let effectiveRId = rId;
|
|
798
820
|
if (existing) {
|
|
@@ -820,8 +842,6 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
820
842
|
materialize(doc.package.footers, RELATIONSHIP_TYPES.footer, "footer", FOOTER_CONTENT_TYPE, false);
|
|
821
843
|
if (relEntries.length === 0) return;
|
|
822
844
|
const compressionOptions = { level: compressionLevel };
|
|
823
|
-
const relsPath = "word/_rels/document.xml.rels";
|
|
824
|
-
const relsXml = await readRelsOrStub(zip, relsPath);
|
|
825
845
|
zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
826
846
|
compression: "DEFLATE",
|
|
827
847
|
compressionOptions
|
|
@@ -1063,14 +1083,56 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
|
|
|
1063
1083
|
}
|
|
1064
1084
|
const currentXml = serializeNumberingXml(numbering);
|
|
1065
1085
|
const changed = collectChangedNumberingDefs(baseline.serializedXml, currentXml);
|
|
1066
|
-
|
|
1067
|
-
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);
|
|
1068
1093
|
if (patched === null) return;
|
|
1069
1094
|
newZip.file(file.name, patched, {
|
|
1070
1095
|
compression: "DEFLATE",
|
|
1071
1096
|
compressionOptions: { level: compressionLevel }
|
|
1072
1097
|
});
|
|
1073
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
|
+
}
|
|
1074
1136
|
async function patchNotePartIntoZip(conventionalLowerPath, currentXml, baselineFrom, originalZip, newZip, compressionLevel) {
|
|
1075
1137
|
const file = findNotePartEntry(originalZip, conventionalLowerPath);
|
|
1076
1138
|
if (!file) return;
|
|
@@ -1109,6 +1171,11 @@ function collectChangedNoteParaIds(baselineXml, currentXml) {
|
|
|
1109
1171
|
}
|
|
1110
1172
|
return changed;
|
|
1111
1173
|
}
|
|
1174
|
+
/** `word/Footnotes.xml` -> `word/_rels/Footnotes.xml.rels` (casing preserved). */
|
|
1175
|
+
function notePartRelsPath(partPath) {
|
|
1176
|
+
const lastSlash = partPath.lastIndexOf("/");
|
|
1177
|
+
return `${partPath.slice(0, lastSlash + 1)}_rels/${partPath.slice(lastSlash + 1)}.rels`;
|
|
1178
|
+
}
|
|
1112
1179
|
/**
|
|
1113
1180
|
* Locate a note part in the ZIP, matching case-insensitively so a producer that
|
|
1114
1181
|
* cased the entry differently still resolves to the existing part.
|
|
@@ -1362,4 +1429,4 @@ const assertStyleNumberingReferences = (doc) => {
|
|
|
1362
1429
|
for (const numbering of doc.package.numbering?.nums ?? []) if (!availableAbstract.has(numbering.abstractNumId)) panic(`Numbering definition ${numbering.numId} references missing abstract numbering`);
|
|
1363
1430
|
};
|
|
1364
1431
|
//#endregion
|
|
1365
|
-
export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, isDocxBuffer, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx };
|
|
1432
|
+
export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, collectHyperlinksWithoutRId, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, isDocxBuffer, notePartRelsPath, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx };
|
|
@@ -182,6 +182,12 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
182
182
|
if (originalBuffer.byteLength > maxBytes) return null;
|
|
183
183
|
const content = doc.package.document.content;
|
|
184
184
|
if (hasNewImagesOrHyperlinks(content)) return null;
|
|
185
|
+
for (const part of [
|
|
186
|
+
...doc.package.headers?.values() ?? [],
|
|
187
|
+
...doc.package.footers?.values() ?? [],
|
|
188
|
+
...doc.package.footnotes ?? [],
|
|
189
|
+
...doc.package.endnotes ?? []
|
|
190
|
+
]) if (hasNewImagesOrHyperlinks(part.content)) return null;
|
|
185
191
|
if (hasUnmaterializedHeaderFooter(doc)) return null;
|
|
186
192
|
if (hasModelDrivenPictureWatermark(doc)) return null;
|
|
187
193
|
if (withoutOrphanCommentRanges(doc) !== doc) return null;
|
|
@@ -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 };
|
|
@@ -263,6 +263,7 @@ function serializeComplexField(field) {
|
|
|
263
263
|
const rPrXml = structuralFormatting ? serializeTextFormatting(structuralFormatting) : "";
|
|
264
264
|
const beginAttrs = ["w:fldCharType=\"begin\""];
|
|
265
265
|
if (field.fldLock) beginAttrs.push("w:fldLock=\"true\"");
|
|
266
|
+
if (field.dirty) beginAttrs.push("w:dirty=\"true\"");
|
|
266
267
|
parts.push(`<w:r>${rPrXml}<w:fldChar ${beginAttrs.join(" ")}/></w:r>`);
|
|
267
268
|
if (field.fieldCode.length > 0) parts.push(...field.fieldCode.map((run) => serializeRun(run)));
|
|
268
269
|
else if (field.instruction.length > 0) {
|
|
@@ -4,6 +4,7 @@ const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
|
4
4
|
const serializeSettingsXml = (settings) => {
|
|
5
5
|
const parts = [`<w:defaultTabStop w:val="${intAttr(settings.defaultTabStop)}"/>`];
|
|
6
6
|
if (settings.evenAndOddHeaders) parts.push("<w:evenAndOddHeaders/>");
|
|
7
|
+
if (settings.updateFields) parts.push("<w:updateFields w:val=\"true\"/>");
|
|
7
8
|
if (settings.themeFontLang) {
|
|
8
9
|
const attrs = [];
|
|
9
10
|
if (settings.themeFontLang.eastAsia) attrs.push(`w:eastAsia="${escapeXml(settings.themeFontLang.eastAsia)}"`);
|
|
@@ -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,103 @@
|
|
|
1
|
+
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
import { ShadingProperties } from "../../types/colors.js";
|
|
3
|
+
import { Hyperlink, Paragraph, ParagraphContent, Run, Table } from "../../types/content.js";
|
|
4
|
+
import { ParagraphFormatting, TextFormatting } from "../../types/formatting.js";
|
|
5
|
+
//#region src/docx/server/build.d.ts
|
|
6
|
+
declare const HEADING_LEVELS: readonly [1, 2, 3, 4, 5, 6];
|
|
7
|
+
type HeadingLevel = (typeof HEADING_LEVELS)[number];
|
|
8
|
+
declare const InvalidFolioReportBuilderOptionsError_base: import("better-result").TaggedErrorClass<"InvalidFolioReportBuilderOptionsError", {
|
|
9
|
+
message: string;
|
|
10
|
+
path: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* A builder received a value outside the model's domain (a zero `gridSpan`,
|
|
14
|
+
* a negative column width, a reversed TOC range). Thrown at the boundary so
|
|
15
|
+
* the invalid value never reaches the serializer.
|
|
16
|
+
*/
|
|
17
|
+
declare class InvalidFolioReportBuilderOptionsError extends InvalidFolioReportBuilderOptionsError_base {}
|
|
18
|
+
declare const run: (text: string, formatting?: TextFormatting) => Run;
|
|
19
|
+
declare const paragraph: (content: string | ParagraphContent[], formatting?: ParagraphFormatting) => Paragraph;
|
|
20
|
+
type HeadingOptions = {
|
|
21
|
+
text: string;
|
|
22
|
+
level: HeadingLevel;
|
|
23
|
+
};
|
|
24
|
+
/** A paragraph in the `Heading<level>` style. */
|
|
25
|
+
declare const heading: ({ text, level }: HeadingOptions) => Paragraph;
|
|
26
|
+
/** An empty paragraph carrying a hard page break. */
|
|
27
|
+
declare const pageBreak: () => Paragraph;
|
|
28
|
+
type HyperlinkTarget = {
|
|
29
|
+
href: string;
|
|
30
|
+
anchor?: never;
|
|
31
|
+
} | {
|
|
32
|
+
anchor: string;
|
|
33
|
+
href?: never;
|
|
34
|
+
};
|
|
35
|
+
type HyperlinkOptions = HyperlinkTarget & {
|
|
36
|
+
text: string;
|
|
37
|
+
formatting?: TextFormatting;
|
|
38
|
+
tooltip?: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* An external (`href`) or in-document (`anchor`, a bookmark name) link. The
|
|
42
|
+
* relationship for an external link is minted when the document is written.
|
|
43
|
+
*/
|
|
44
|
+
declare const hyperlink: ({ text, formatting, tooltip, href, anchor }: HyperlinkOptions) => Hyperlink;
|
|
45
|
+
type BookmarkOptions = {
|
|
46
|
+
name: string;
|
|
47
|
+
content: ParagraphContent[];
|
|
48
|
+
/**
|
|
49
|
+
* Bookmark id, unique per document. Defaults to a process-wide counter,
|
|
50
|
+
* which is unique for documents built from scratch; pass an explicit id
|
|
51
|
+
* when adding bookmarks to a parsed document.
|
|
52
|
+
*/
|
|
53
|
+
id?: number;
|
|
54
|
+
};
|
|
55
|
+
/** `content` wrapped in a named bookmark, the target of `hyperlink({ anchor })`. */
|
|
56
|
+
declare const bookmark: ({ name, content, id }: BookmarkOptions) => ParagraphContent[];
|
|
57
|
+
type TableCellSpec = string | {
|
|
58
|
+
content: Paragraph[];
|
|
59
|
+
shading?: ShadingProperties;
|
|
60
|
+
gridSpan?: number;
|
|
61
|
+
vMerge?: "restart" | "continue";
|
|
62
|
+
};
|
|
63
|
+
type TableOptions = {
|
|
64
|
+
/** Header row labels; rendered bold, optionally shaded and repeated per page. */
|
|
65
|
+
header?: string[];
|
|
66
|
+
rows: TableCellSpec[][];
|
|
67
|
+
/** Grid column widths in twips. Omitted: the consumer autofits. */
|
|
68
|
+
columnWidths?: number[];
|
|
69
|
+
headerShading?: ShadingProperties;
|
|
70
|
+
/** Repeat the header row at the top of every page (default true). */
|
|
71
|
+
repeatHeader?: boolean;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* A full-width grid table in the `TableGrid` style. A string cell becomes one
|
|
75
|
+
* plain paragraph; an object cell supplies its own paragraphs plus optional
|
|
76
|
+
* shading and horizontal (`gridSpan`) or vertical (`vMerge`) merge.
|
|
77
|
+
*/
|
|
78
|
+
declare const table: ({ header, rows, columnWidths, headerShading, repeatHeader }: TableOptions) => Table;
|
|
79
|
+
/**
|
|
80
|
+
* Register an endnote on `doc` and return the reference run to place in body
|
|
81
|
+
* text. Allocates the next free endnote id (Word reserves 0 and -1 for the
|
|
82
|
+
* separator notes) and pushes the note into `doc.package.endnotes`.
|
|
83
|
+
*/
|
|
84
|
+
declare const endnote: (doc: document_d_exports.Document, content: string | Paragraph[]) => Run;
|
|
85
|
+
type TableOfContentsOptions = {
|
|
86
|
+
/** Heading levels to include, `1 <= from <= to <= 9` (default 1-3). */
|
|
87
|
+
levels?: {
|
|
88
|
+
from: number;
|
|
89
|
+
to: number;
|
|
90
|
+
};
|
|
91
|
+
/** Make entries hyperlinks (`\h`, default true). */
|
|
92
|
+
hyperlinks?: boolean;
|
|
93
|
+
/** Result text shown until the consumer recomputes the field. */
|
|
94
|
+
placeholderText?: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* A paragraph holding a dirty `TOC` field, so the consumer recomputes the
|
|
98
|
+
* table on open. Set `package.settings.updateFields` as well to have Word
|
|
99
|
+
* recompute without prompting for each field.
|
|
100
|
+
*/
|
|
101
|
+
declare const createTableOfContentsField: ({ levels, hyperlinks, placeholderText }?: TableOfContentsOptions) => Paragraph;
|
|
102
|
+
//#endregion
|
|
103
|
+
export { HEADING_LEVELS, HeadingLevel, InvalidFolioReportBuilderOptionsError, TableCellSpec, bookmark, createTableOfContentsField, endnote, heading, hyperlink, pageBreak, paragraph, run, table };
|