document-cli 1.3.0 → 1.5.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/cli.js +305 -30
- package/dist/index.cjs +378 -21
- package/dist/index.js +385 -28
- package/dist/{odb-structure-CX_0zL8_.js → odb-structure-Du1w_hOU.js} +83 -1
- package/dist/{tui-C8ITn1zQ.js → tui-B-o3iI9a.js} +161 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, hsqldbCellDisplayText, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readPdf, writePdf } from "documents.js";
|
|
2
|
+
import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodeMarkdownText, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readOdfFormulaContent, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, writePdf, xlsxToPdf } from "documents.js";
|
|
3
3
|
import { basename, dirname, extname, join } from "node:path";
|
|
4
4
|
import { Command, InvalidArgumentError } from "commander";
|
|
5
|
-
import { decodePackage, encodePackage as encodePackage$1 } from "odf.js";
|
|
5
|
+
import { decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
//#region src/format.ts
|
|
8
8
|
const EXTENSION_TO_FORMAT = {
|
|
@@ -532,8 +532,158 @@ function registerConversionCommands(program) {
|
|
|
532
532
|
});
|
|
533
533
|
}
|
|
534
534
|
//#endregion
|
|
535
|
+
//#region src/docx-extras-format.ts
|
|
536
|
+
const INDENT$1 = " ";
|
|
537
|
+
function indent$1(depth) {
|
|
538
|
+
return INDENT$1.repeat(depth);
|
|
539
|
+
}
|
|
540
|
+
function commentLine(comment, position) {
|
|
541
|
+
const author = comment.author ?? "(no author)";
|
|
542
|
+
return `${indent$1(1)}[${position}] ${author}: ${comment.text}`;
|
|
543
|
+
}
|
|
544
|
+
function footnoteLine(footnote, position) {
|
|
545
|
+
const typeSuffix = footnote.type === void 0 ? "" : ` (${footnote.type})`;
|
|
546
|
+
return `${indent$1(1)}[${position}]${typeSuffix} ${footnote.text}`;
|
|
547
|
+
}
|
|
548
|
+
function commentsSection(comments) {
|
|
549
|
+
if (comments.length === 0) return [];
|
|
550
|
+
return ["comments", ...comments.map((comment, index) => commentLine(comment, index + 1))];
|
|
551
|
+
}
|
|
552
|
+
function footnotesSection(footnotes) {
|
|
553
|
+
if (footnotes.length === 0) return [];
|
|
554
|
+
return ["footnotes", ...footnotes.map((footnote, index) => footnoteLine(footnote, index + 1))];
|
|
555
|
+
}
|
|
556
|
+
function headerOrFooterSection(label, values) {
|
|
557
|
+
if (values.length === 0) return [];
|
|
558
|
+
return [label, ...values.map((text, index) => `${indent$1(1)}[${index + 1}] ${text}`)];
|
|
559
|
+
}
|
|
560
|
+
function numberingLevelLine(ilvl, level) {
|
|
561
|
+
const restartSuffix = level.restart === void 0 ? "" : `, restarts at level ${level.restart}`;
|
|
562
|
+
return `${indent$1(2)}level ${ilvl}: ${level.format} ${JSON.stringify(level.text)} starting at ${level.startAt}${restartSuffix}`;
|
|
563
|
+
}
|
|
564
|
+
function numberingSection(numbering) {
|
|
565
|
+
const numIds = Object.keys(numbering);
|
|
566
|
+
if (numIds.length === 0) return [];
|
|
567
|
+
const lines = ["numbering"];
|
|
568
|
+
for (const numId of numIds) {
|
|
569
|
+
const definition = numbering[numId];
|
|
570
|
+
if (definition === void 0) continue;
|
|
571
|
+
lines.push(`${indent$1(1)}numId ${numId}`);
|
|
572
|
+
const ilvls = Object.keys(definition.levels).sort((a, b) => Number(a) - Number(b));
|
|
573
|
+
for (const ilvl of ilvls) {
|
|
574
|
+
const level = definition.levels[ilvl];
|
|
575
|
+
if (level === void 0) continue;
|
|
576
|
+
lines.push(numberingLevelLine(ilvl, level));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return lines;
|
|
580
|
+
}
|
|
581
|
+
function formatDocxExtrasLines(extras) {
|
|
582
|
+
const nonEmptySections = [
|
|
583
|
+
commentsSection(extras.comments),
|
|
584
|
+
footnotesSection(extras.footnotes),
|
|
585
|
+
headerOrFooterSection("headers", extras.headers),
|
|
586
|
+
headerOrFooterSection("footers", extras.footers),
|
|
587
|
+
numberingSection(extras.numbering)
|
|
588
|
+
].filter((section) => section.length > 0);
|
|
589
|
+
if (nonEmptySections.length === 0) return ["This document carries no comments, footnotes, headers, footers, or numbering definitions."];
|
|
590
|
+
return nonEmptySections.flatMap((section, index) => index === 0 ? section : ["", ...section]);
|
|
591
|
+
}
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/commands/docx-extras.ts
|
|
594
|
+
async function runDocxExtras(input, options) {
|
|
595
|
+
const command = "docx-extras";
|
|
596
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
597
|
+
try {
|
|
598
|
+
const inputBytes = await readInput(input, { signal });
|
|
599
|
+
const pkg = decodePackage(new Uint8Array(inputBytes));
|
|
600
|
+
const extras = readDocxExtras(pkg);
|
|
601
|
+
if (options.json) {
|
|
602
|
+
process.stdout.write(`${JSON.stringify(extras)}\n`);
|
|
603
|
+
return 0;
|
|
604
|
+
}
|
|
605
|
+
for (const line of formatDocxExtrasLines(extras)) process.stdout.write(`${line}\n`);
|
|
606
|
+
return 0;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
|
|
609
|
+
return mapErrorToExit(error, getAbortReason());
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function registerDocxExtrasCommand(program) {
|
|
613
|
+
program.command("docx-extras <input>").description("print a docx's own comments, footnotes, headers, footers, and numbering definitions -- data readDocxContent's ContentDocument cannot carry").option("--json", "emit the raw DocxExtras object as JSON instead of a human-readable report", false).action(async (input, options) => {
|
|
614
|
+
process.exitCode = await runDocxExtras(input, options);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/commands/fonts.ts
|
|
619
|
+
const FONT_SOURCE_FORMATS = {
|
|
620
|
+
docx: true,
|
|
621
|
+
pptx: true,
|
|
622
|
+
odt: true,
|
|
623
|
+
odp: true,
|
|
624
|
+
ods: true,
|
|
625
|
+
odg: true
|
|
626
|
+
};
|
|
627
|
+
function isFontSourceFormat(format) {
|
|
628
|
+
return format in FONT_SOURCE_FORMATS;
|
|
629
|
+
}
|
|
630
|
+
function resolveFontSourcePackage(format, bytes) {
|
|
631
|
+
if (format === "docx" || format === "pptx") return {
|
|
632
|
+
kind: format,
|
|
633
|
+
package: decodePackage(bytes)
|
|
634
|
+
};
|
|
635
|
+
return {
|
|
636
|
+
kind: "odf",
|
|
637
|
+
package: decodePackage$1(bytes)
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
async function runFonts(input, options) {
|
|
641
|
+
const command = "fonts";
|
|
642
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
643
|
+
try {
|
|
644
|
+
const format = inferFormatFromExtension(input);
|
|
645
|
+
if (format === void 0) {
|
|
646
|
+
process.stderr.write(`[${command}] cannot infer a document format from '${input}'; expected one of docx, pptx, odt, odp, ods, odg\n`);
|
|
647
|
+
return 2;
|
|
648
|
+
}
|
|
649
|
+
if (!isFontSourceFormat(format)) {
|
|
650
|
+
process.stderr.write(`[${command}] '${format}' documents carry no source-embedded font faces this command can extract; expected one of docx, pptx, odt, odp, ods, odg\n`);
|
|
651
|
+
return 2;
|
|
652
|
+
}
|
|
653
|
+
const inputBytes = await readInput(input, { signal });
|
|
654
|
+
const source = resolveFontSourcePackage(format, new Uint8Array(inputBytes));
|
|
655
|
+
const summaries = extractSourceFonts(source).map((face) => ({
|
|
656
|
+
family: face.family,
|
|
657
|
+
bold: face.bold,
|
|
658
|
+
italic: face.italic,
|
|
659
|
+
byteLength: face.bytes.length
|
|
660
|
+
}));
|
|
661
|
+
if (options.json) {
|
|
662
|
+
process.stdout.write(`${JSON.stringify(summaries)}\n`);
|
|
663
|
+
return 0;
|
|
664
|
+
}
|
|
665
|
+
if (summaries.length === 0) {
|
|
666
|
+
process.stdout.write("This document embeds no source fonts.\n");
|
|
667
|
+
return 0;
|
|
668
|
+
}
|
|
669
|
+
for (const face of summaries) {
|
|
670
|
+
const style = [face.bold ? "bold" : void 0, face.italic ? "italic" : void 0].filter((value) => value !== void 0).join(" ");
|
|
671
|
+
process.stdout.write(`${face.family}${style === "" ? "" : ` (${style})`} -- ${face.byteLength} bytes\n`);
|
|
672
|
+
}
|
|
673
|
+
return 0;
|
|
674
|
+
} catch (error) {
|
|
675
|
+
process.stderr.write(`${formatError(error, false)}\n`);
|
|
676
|
+
return mapErrorToExit(error, getAbortReason());
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function registerFontsCommand(program) {
|
|
680
|
+
program.command("fonts <input>").description("list every source-embedded font face a docx/pptx/odt/odp/ods/odg document carries (family, weight/style, byte length)").option("--json", "emit the face list as a JSON array instead of a human-readable report", false).action(async (input, options) => {
|
|
681
|
+
process.exitCode = await runFonts(input, options);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
//#endregion
|
|
535
685
|
//#region src/commands/formats.ts
|
|
536
|
-
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package";
|
|
686
|
+
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras, metadata, set-metadata";
|
|
537
687
|
function registerFormatsCommand(program) {
|
|
538
688
|
program.command("formats").description("list every source -> target conversion this CLI supports via a <source>-to-<target> command").option("--json", "emit the conversion list as a JSON array instead of a human-readable table", false).action((options) => {
|
|
539
689
|
const { conversions } = createLocalDocumentConverter();
|
|
@@ -626,6 +776,78 @@ function registerFromPackageCommand(program) {
|
|
|
626
776
|
});
|
|
627
777
|
}
|
|
628
778
|
//#endregion
|
|
779
|
+
//#region src/runtime/metadata-format.ts
|
|
780
|
+
const METADATA_KEYS = [
|
|
781
|
+
"title",
|
|
782
|
+
"author",
|
|
783
|
+
"subject",
|
|
784
|
+
"keywords",
|
|
785
|
+
"creator",
|
|
786
|
+
"producer",
|
|
787
|
+
"createdIso",
|
|
788
|
+
"modifiedIso"
|
|
789
|
+
];
|
|
790
|
+
function isPresent(entry) {
|
|
791
|
+
return entry[1] !== void 0;
|
|
792
|
+
}
|
|
793
|
+
function formatMetadataValue(value) {
|
|
794
|
+
return typeof value === "string" ? value : value.join(", ");
|
|
795
|
+
}
|
|
796
|
+
function presentMetadataEntries(metadata) {
|
|
797
|
+
return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
|
|
798
|
+
}
|
|
799
|
+
function formatMetadataLines(metadata) {
|
|
800
|
+
return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
|
|
801
|
+
}
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region src/commands/metadata.ts
|
|
804
|
+
function readMetadataForFormat(format, bytes, signal) {
|
|
805
|
+
switch (format) {
|
|
806
|
+
case "docx": return readDocxContent(decodePackage(bytes)).metadata;
|
|
807
|
+
case "pptx": return readPptxContent(decodePackage(bytes)).metadata;
|
|
808
|
+
case "odt": return readOdtContent(decodePackage$1(bytes)).metadata;
|
|
809
|
+
case "odp": return readOdpContent(decodePackage$1(bytes)).metadata;
|
|
810
|
+
case "ods": return readOdsContent(decodePackage$1(bytes)).metadata;
|
|
811
|
+
case "odg": return readOdgContent(decodePackage$1(bytes)).metadata;
|
|
812
|
+
case "odf": return readOdfFormulaContent(decodePackage$1(bytes)).metadata;
|
|
813
|
+
case "markdown": return readMarkdownContent(decodeMarkdownText(bytes)).metadata;
|
|
814
|
+
case "pdf": return readPdf(bytes, { signal }).metadata;
|
|
815
|
+
case "xlsx": return readPdf(xlsxToPdf(bytes, { signal }), { signal }).metadata;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function runMetadata(input, options) {
|
|
819
|
+
const command = "metadata";
|
|
820
|
+
const source = inferFormatFromExtension(input);
|
|
821
|
+
if (source === void 0) {
|
|
822
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
823
|
+
return 2;
|
|
824
|
+
}
|
|
825
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
826
|
+
try {
|
|
827
|
+
const inputBytes = await readInput(input, { signal });
|
|
828
|
+
const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
|
|
829
|
+
if (options.json) {
|
|
830
|
+
process.stdout.write(`${JSON.stringify(metadata)}\n`);
|
|
831
|
+
return 0;
|
|
832
|
+
}
|
|
833
|
+
const lines = formatMetadataLines(metadata);
|
|
834
|
+
if (lines.length === 0) {
|
|
835
|
+
process.stdout.write("This document carries no metadata.\n");
|
|
836
|
+
return 0;
|
|
837
|
+
}
|
|
838
|
+
for (const line of lines) process.stdout.write(`${line}\n`);
|
|
839
|
+
return 0;
|
|
840
|
+
} catch (error) {
|
|
841
|
+
process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
|
|
842
|
+
return mapErrorToExit(error, getAbortReason());
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function registerMetadataCommand(program) {
|
|
846
|
+
program.command("metadata <input>").description(`print a document's own title/author/subject/keywords/creator/producer/created/modified metadata (${KNOWN_DOCUMENT_FORMATS})`).option("--json", "emit the metadata as a JSON object instead of a human-readable report", false).action(async (input, options) => {
|
|
847
|
+
process.exitCode = await runMetadata(input, options);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
//#endregion
|
|
629
851
|
//#region src/odb-structure.ts
|
|
630
852
|
const INDENT = " ";
|
|
631
853
|
function indent(depth) {
|
|
@@ -867,7 +1089,7 @@ async function runOdbTables(input, options) {
|
|
|
867
1089
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
868
1090
|
try {
|
|
869
1091
|
const inputBytes = await readInput(input, { signal });
|
|
870
|
-
const pkg = decodePackage(new Uint8Array(inputBytes));
|
|
1092
|
+
const pkg = decodePackage$1(new Uint8Array(inputBytes));
|
|
871
1093
|
const tables = readOdbTables(pkg);
|
|
872
1094
|
if (options.json) {
|
|
873
1095
|
const summary = tables.map((table) => ({
|
|
@@ -912,7 +1134,7 @@ async function runOdbQuery(input, options) {
|
|
|
912
1134
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
913
1135
|
try {
|
|
914
1136
|
const inputBytes = await readInput(input, { signal });
|
|
915
|
-
const pkg = decodePackage(new Uint8Array(inputBytes));
|
|
1137
|
+
const pkg = decodePackage$1(new Uint8Array(inputBytes));
|
|
916
1138
|
const resolved = resolveQuerySql(pkg, options);
|
|
917
1139
|
if ("errorMessage" in resolved) {
|
|
918
1140
|
process.stderr.write(`[${command}] ${resolved.errorMessage}\n`);
|
|
@@ -934,7 +1156,7 @@ async function runOdbForms(input, options) {
|
|
|
934
1156
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
935
1157
|
try {
|
|
936
1158
|
const inputBytes = await readInput(input, { signal });
|
|
937
|
-
const forms = readOdbForms(decodePackage(new Uint8Array(inputBytes)));
|
|
1159
|
+
const forms = readOdbForms(decodePackage$1(new Uint8Array(inputBytes)));
|
|
938
1160
|
if (options.json) {
|
|
939
1161
|
process.stdout.write(`${JSON.stringify(forms.map((form) => odbFormSummary(form)))}\n`);
|
|
940
1162
|
return 0;
|
|
@@ -957,7 +1179,7 @@ async function runOdbReports(input, options) {
|
|
|
957
1179
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
958
1180
|
try {
|
|
959
1181
|
const inputBytes = await readInput(input, { signal });
|
|
960
|
-
const reports = readOdbReports(decodePackage(new Uint8Array(inputBytes)));
|
|
1182
|
+
const reports = readOdbReports(decodePackage$1(new Uint8Array(inputBytes)));
|
|
961
1183
|
if (options.json) {
|
|
962
1184
|
process.stdout.write(`${JSON.stringify(reports)}\n`);
|
|
963
1185
|
return 0;
|
|
@@ -1041,7 +1263,7 @@ async function runOdbRenderReport(input, output, options) {
|
|
|
1041
1263
|
try {
|
|
1042
1264
|
const inputBytes = await readInput(input, { signal });
|
|
1043
1265
|
const fonts = await loadProvidedFonts(options.fontFile ?? [], { signal });
|
|
1044
|
-
const pkg = decodePackage(new Uint8Array(inputBytes));
|
|
1266
|
+
const pkg = decodePackage$1(new Uint8Array(inputBytes));
|
|
1045
1267
|
const bytes = renderOdbReportBytes(readOdbReportContent(pkg, { report: options.report }), targetFormat, {
|
|
1046
1268
|
fonts,
|
|
1047
1269
|
signal,
|
|
@@ -1229,12 +1451,6 @@ function countImagesByFormat(images) {
|
|
|
1229
1451
|
for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
|
|
1230
1452
|
return counts;
|
|
1231
1453
|
}
|
|
1232
|
-
function isPresent(entry) {
|
|
1233
|
-
return entry[1] !== void 0;
|
|
1234
|
-
}
|
|
1235
|
-
function formatMetadataValue(value) {
|
|
1236
|
-
return typeof value === "string" ? value : value.join(", ");
|
|
1237
|
-
}
|
|
1238
1454
|
async function runPdfInspect(input, options) {
|
|
1239
1455
|
const command = "pdf-inspect";
|
|
1240
1456
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
@@ -1252,7 +1468,7 @@ async function runPdfInspect(input, options) {
|
|
|
1252
1468
|
}
|
|
1253
1469
|
});
|
|
1254
1470
|
if (options.full) {
|
|
1255
|
-
process.stdout.write(`${JSON.stringify(layout, void 0, 2)}\n`);
|
|
1471
|
+
process.stdout.write(`${JSON.stringify(layoutDocumentWithSchema(layout), void 0, 2)}\n`);
|
|
1256
1472
|
return 0;
|
|
1257
1473
|
}
|
|
1258
1474
|
const imagesByFormat = countImagesByFormat(layout.images);
|
|
@@ -1276,19 +1492,9 @@ async function runPdfInspect(input, options) {
|
|
|
1276
1492
|
const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
|
|
1277
1493
|
process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
|
|
1278
1494
|
});
|
|
1279
|
-
|
|
1280
|
-
["title", layout.metadata.title],
|
|
1281
|
-
["author", layout.metadata.author],
|
|
1282
|
-
["subject", layout.metadata.subject],
|
|
1283
|
-
["keywords", layout.metadata.keywords],
|
|
1284
|
-
["creator", layout.metadata.creator],
|
|
1285
|
-
["producer", layout.metadata.producer],
|
|
1286
|
-
["createdIso", layout.metadata.createdIso],
|
|
1287
|
-
["modifiedIso", layout.metadata.modifiedIso]
|
|
1288
|
-
].filter(isPresent);
|
|
1289
|
-
if (presentMetadata.length > 0) {
|
|
1495
|
+
if (presentMetadataEntries(layout.metadata).length > 0) {
|
|
1290
1496
|
process.stdout.write("metadata:\n");
|
|
1291
|
-
for (const
|
|
1497
|
+
for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
|
|
1292
1498
|
}
|
|
1293
1499
|
if (imagesByFormat.size > 0) {
|
|
1294
1500
|
process.stdout.write("images:\n");
|
|
@@ -1306,8 +1512,155 @@ function registerPdfInspectCommand(program) {
|
|
|
1306
1512
|
});
|
|
1307
1513
|
}
|
|
1308
1514
|
//#endregion
|
|
1515
|
+
//#region src/commands/set-metadata.ts
|
|
1516
|
+
const REBUILD_FORMATS = {
|
|
1517
|
+
docx: true,
|
|
1518
|
+
pptx: true,
|
|
1519
|
+
odt: true,
|
|
1520
|
+
odp: true,
|
|
1521
|
+
ods: true,
|
|
1522
|
+
odg: true,
|
|
1523
|
+
markdown: true
|
|
1524
|
+
};
|
|
1525
|
+
function isRebuildFormat(format) {
|
|
1526
|
+
return format in REBUILD_FORMATS;
|
|
1527
|
+
}
|
|
1528
|
+
function readContentForFormat(format, bytes) {
|
|
1529
|
+
switch (format) {
|
|
1530
|
+
case "docx": return readDocxContent(decodePackage(bytes));
|
|
1531
|
+
case "pptx": return readPptxContent(decodePackage(bytes));
|
|
1532
|
+
case "odt": return readOdtContent(decodePackage$1(bytes));
|
|
1533
|
+
case "odp": return readOdpContent(decodePackage$1(bytes));
|
|
1534
|
+
case "ods": return readOdsContent(decodePackage$1(bytes));
|
|
1535
|
+
case "odg": return readOdgContent(decodePackage$1(bytes));
|
|
1536
|
+
case "markdown": return readMarkdownContent(decodeMarkdownText(bytes));
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function buildBytesForRebuildFormat(format, content) {
|
|
1540
|
+
switch (format) {
|
|
1541
|
+
case "docx": return encodePackage(buildDocxPackage(content));
|
|
1542
|
+
case "pptx": return encodePackage(buildPptxPackage(content));
|
|
1543
|
+
case "odt": return encodePackage$1(buildOdtPackage(content));
|
|
1544
|
+
case "odp": return encodePackage$1(buildOdpPackage(content));
|
|
1545
|
+
case "ods": return encodePackage$1(buildOdsPackage(content));
|
|
1546
|
+
case "odg": return encodePackage$1(buildOdgPackage(content));
|
|
1547
|
+
case "markdown": return encodeMarkdownText(buildMarkdownText(content));
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function mergeMetadata(current, overrides) {
|
|
1551
|
+
return {
|
|
1552
|
+
...current,
|
|
1553
|
+
...overrides.title !== void 0 ? { title: overrides.title } : {},
|
|
1554
|
+
...overrides.author !== void 0 ? { author: overrides.author } : {},
|
|
1555
|
+
...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
|
|
1556
|
+
...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
function parseKeywords(csv) {
|
|
1560
|
+
return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1561
|
+
}
|
|
1562
|
+
function classifyWritePath(source, target) {
|
|
1563
|
+
if (source === "pdf" && target === "pdf") return { kind: "pdf" };
|
|
1564
|
+
if (target === "xlsx" || source === "xlsx") return { errorMessage: "'xlsx' is not a supported set-metadata source or target -- documents.js does not re-export a ContentDocument-to-xlsx builder or a readXlsxContent from its own public surface (see that package's own README, Architecture section); convert with 'xlsx-to-ods'/'ods-to-xlsx' first, then set metadata on the ods" };
|
|
1565
|
+
if (target === "odf" || source === "odf") return { errorMessage: "'odf' (a standalone formula document) is not a supported set-metadata source or target -- it has no write path back out at all" };
|
|
1566
|
+
if (!isRebuildFormat(source) || !isRebuildFormat(target)) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format (or both 'pdf'). Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1567
|
+
if (source !== target) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format. Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1568
|
+
return {
|
|
1569
|
+
kind: "rebuild",
|
|
1570
|
+
format: source
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
async function runSetMetadata(input, output, options) {
|
|
1574
|
+
const command = "set-metadata";
|
|
1575
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
1576
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
1577
|
+
return 2;
|
|
1578
|
+
}
|
|
1579
|
+
const target = resolveTargetFormat(output, options.out, options.to);
|
|
1580
|
+
if ("errorMessage" in target) {
|
|
1581
|
+
process.stderr.write(`[${command}] ${target.errorMessage}\n`);
|
|
1582
|
+
return 2;
|
|
1583
|
+
}
|
|
1584
|
+
const source = inferFormatFromExtension(input);
|
|
1585
|
+
if (source === void 0) {
|
|
1586
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
1587
|
+
return 2;
|
|
1588
|
+
}
|
|
1589
|
+
const writePath = classifyWritePath(source, target.format);
|
|
1590
|
+
if ("errorMessage" in writePath) {
|
|
1591
|
+
process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
|
|
1592
|
+
return 2;
|
|
1593
|
+
}
|
|
1594
|
+
const overrides = {
|
|
1595
|
+
title: options.setTitle,
|
|
1596
|
+
author: options.setAuthor,
|
|
1597
|
+
subject: options.setSubject,
|
|
1598
|
+
keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
|
|
1599
|
+
};
|
|
1600
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
|
|
1601
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
1602
|
+
try {
|
|
1603
|
+
const inputBytes = await readInput(input, { signal });
|
|
1604
|
+
const bytes = writePath.kind === "pdf" ? (() => {
|
|
1605
|
+
const layout = readPdf(new Uint8Array(inputBytes), { signal });
|
|
1606
|
+
const patched = {
|
|
1607
|
+
...layout,
|
|
1608
|
+
metadata: mergeMetadata(layout.metadata, overrides)
|
|
1609
|
+
};
|
|
1610
|
+
return writePdf(patched, { signal });
|
|
1611
|
+
})() : (() => {
|
|
1612
|
+
const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
|
|
1613
|
+
const nextContent = {
|
|
1614
|
+
...content,
|
|
1615
|
+
metadata: mergeMetadata(content.metadata, overrides)
|
|
1616
|
+
};
|
|
1617
|
+
return buildBytesForRebuildFormat(writePath.format, nextContent);
|
|
1618
|
+
})();
|
|
1619
|
+
await writeOutput(resolvedOutput, bytes);
|
|
1620
|
+
createDiagnosticReporter({
|
|
1621
|
+
json: options.json,
|
|
1622
|
+
quiet: options.quiet,
|
|
1623
|
+
command
|
|
1624
|
+
}).summarize({
|
|
1625
|
+
output: resolvedOutput,
|
|
1626
|
+
bytes: bytes.byteLength,
|
|
1627
|
+
diagnosticCount: 0
|
|
1628
|
+
});
|
|
1629
|
+
return 0;
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
1632
|
+
return mapErrorToExit(error, getAbortReason());
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
function registerSetMetadataCommand(program) {
|
|
1636
|
+
const command = program.command("set-metadata <input> [output]").description("patch a document's own title/author/subject/keywords, leaving every other field and every other flag as-is").addHelpText("after", [
|
|
1637
|
+
"",
|
|
1638
|
+
"Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
|
|
1639
|
+
"involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
|
|
1640
|
+
"odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
|
|
1641
|
+
"this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
|
|
1642
|
+
"since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
|
|
1643
|
+
"",
|
|
1644
|
+
"set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
|
|
1645
|
+
"set-metadata on the result, if you need a different target format."
|
|
1646
|
+
].join("\n"));
|
|
1647
|
+
addOutOption(command);
|
|
1648
|
+
addTimeoutOption(command);
|
|
1649
|
+
addJsonOption(command);
|
|
1650
|
+
addQuietOption(command);
|
|
1651
|
+
addVerboseOption(command);
|
|
1652
|
+
command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
|
|
1653
|
+
command.option("--set-title <text>", "set the title field");
|
|
1654
|
+
command.option("--set-author <text>", "set the author field");
|
|
1655
|
+
command.option("--set-subject <text>", "set the subject field");
|
|
1656
|
+
command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
|
|
1657
|
+
command.action(async (input, output, options) => {
|
|
1658
|
+
process.exitCode = await runSetMetadata(input, output, options);
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
//#endregion
|
|
1309
1662
|
//#region package.json
|
|
1310
|
-
var version = "1.
|
|
1663
|
+
var version = "1.5.0";
|
|
1311
1664
|
//#endregion
|
|
1312
1665
|
//#region src/program.ts
|
|
1313
1666
|
function createProgram() {
|
|
@@ -1324,6 +1677,10 @@ function createProgram() {
|
|
|
1324
1677
|
registerOdmCommand(program);
|
|
1325
1678
|
registerOdbCommands(program);
|
|
1326
1679
|
registerPdfInspectCommand(program);
|
|
1680
|
+
registerFontsCommand(program);
|
|
1681
|
+
registerDocxExtrasCommand(program);
|
|
1682
|
+
registerMetadataCommand(program);
|
|
1683
|
+
registerSetMetadataCommand(program);
|
|
1327
1684
|
return program;
|
|
1328
1685
|
}
|
|
1329
1686
|
//#endregion
|
|
@@ -247,6 +247,88 @@ function resolveDefaultOutputPath(inputPath, targetFormat) {
|
|
|
247
247
|
return join(directory, `${stem}.${formatToExtension(targetFormat)}`);
|
|
248
248
|
}
|
|
249
249
|
//#endregion
|
|
250
|
+
//#region src/docx-extras-format.ts
|
|
251
|
+
const INDENT$1 = " ";
|
|
252
|
+
function indent$1(depth) {
|
|
253
|
+
return INDENT$1.repeat(depth);
|
|
254
|
+
}
|
|
255
|
+
function commentLine(comment, position) {
|
|
256
|
+
const author = comment.author ?? "(no author)";
|
|
257
|
+
return `${indent$1(1)}[${position}] ${author}: ${comment.text}`;
|
|
258
|
+
}
|
|
259
|
+
function footnoteLine(footnote, position) {
|
|
260
|
+
const typeSuffix = footnote.type === void 0 ? "" : ` (${footnote.type})`;
|
|
261
|
+
return `${indent$1(1)}[${position}]${typeSuffix} ${footnote.text}`;
|
|
262
|
+
}
|
|
263
|
+
function commentsSection(comments) {
|
|
264
|
+
if (comments.length === 0) return [];
|
|
265
|
+
return ["comments", ...comments.map((comment, index) => commentLine(comment, index + 1))];
|
|
266
|
+
}
|
|
267
|
+
function footnotesSection(footnotes) {
|
|
268
|
+
if (footnotes.length === 0) return [];
|
|
269
|
+
return ["footnotes", ...footnotes.map((footnote, index) => footnoteLine(footnote, index + 1))];
|
|
270
|
+
}
|
|
271
|
+
function headerOrFooterSection(label, values) {
|
|
272
|
+
if (values.length === 0) return [];
|
|
273
|
+
return [label, ...values.map((text, index) => `${indent$1(1)}[${index + 1}] ${text}`)];
|
|
274
|
+
}
|
|
275
|
+
function numberingLevelLine(ilvl, level) {
|
|
276
|
+
const restartSuffix = level.restart === void 0 ? "" : `, restarts at level ${level.restart}`;
|
|
277
|
+
return `${indent$1(2)}level ${ilvl}: ${level.format} ${JSON.stringify(level.text)} starting at ${level.startAt}${restartSuffix}`;
|
|
278
|
+
}
|
|
279
|
+
function numberingSection(numbering) {
|
|
280
|
+
const numIds = Object.keys(numbering);
|
|
281
|
+
if (numIds.length === 0) return [];
|
|
282
|
+
const lines = ["numbering"];
|
|
283
|
+
for (const numId of numIds) {
|
|
284
|
+
const definition = numbering[numId];
|
|
285
|
+
if (definition === void 0) continue;
|
|
286
|
+
lines.push(`${indent$1(1)}numId ${numId}`);
|
|
287
|
+
const ilvls = Object.keys(definition.levels).sort((a, b) => Number(a) - Number(b));
|
|
288
|
+
for (const ilvl of ilvls) {
|
|
289
|
+
const level = definition.levels[ilvl];
|
|
290
|
+
if (level === void 0) continue;
|
|
291
|
+
lines.push(numberingLevelLine(ilvl, level));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return lines;
|
|
295
|
+
}
|
|
296
|
+
function formatDocxExtrasLines(extras) {
|
|
297
|
+
const nonEmptySections = [
|
|
298
|
+
commentsSection(extras.comments),
|
|
299
|
+
footnotesSection(extras.footnotes),
|
|
300
|
+
headerOrFooterSection("headers", extras.headers),
|
|
301
|
+
headerOrFooterSection("footers", extras.footers),
|
|
302
|
+
numberingSection(extras.numbering)
|
|
303
|
+
].filter((section) => section.length > 0);
|
|
304
|
+
if (nonEmptySections.length === 0) return ["This document carries no comments, footnotes, headers, footers, or numbering definitions."];
|
|
305
|
+
return nonEmptySections.flatMap((section, index) => index === 0 ? section : ["", ...section]);
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/runtime/metadata-format.ts
|
|
309
|
+
const METADATA_KEYS = [
|
|
310
|
+
"title",
|
|
311
|
+
"author",
|
|
312
|
+
"subject",
|
|
313
|
+
"keywords",
|
|
314
|
+
"creator",
|
|
315
|
+
"producer",
|
|
316
|
+
"createdIso",
|
|
317
|
+
"modifiedIso"
|
|
318
|
+
];
|
|
319
|
+
function isPresent(entry) {
|
|
320
|
+
return entry[1] !== void 0;
|
|
321
|
+
}
|
|
322
|
+
function formatMetadataValue(value) {
|
|
323
|
+
return typeof value === "string" ? value : value.join(", ");
|
|
324
|
+
}
|
|
325
|
+
function presentMetadataEntries(metadata) {
|
|
326
|
+
return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
|
|
327
|
+
}
|
|
328
|
+
function formatMetadataLines(metadata) {
|
|
329
|
+
return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
250
332
|
//#region src/odb-structure.ts
|
|
251
333
|
const INDENT = " ";
|
|
252
334
|
function indent(depth) {
|
|
@@ -379,4 +461,4 @@ function formatOdbReportLines(report) {
|
|
|
379
461
|
return lines;
|
|
380
462
|
}
|
|
381
463
|
//#endregion
|
|
382
|
-
export { odbFormSummary as a,
|
|
464
|
+
export { odbFormSummary as a, formatDocxExtrasLines as c, writeOutput as d, loadProvidedFonts as f, isDocumentFormat as h, formatOdbReportLines as i, readInput as l, inferFormatFromExtension as m, describeOdbReport as n, formatMetadataLines as o, formatToExtension as p, formatOdbFormLines as r, presentMetadataEntries as s, describeOdbForm as t, resolveDefaultOutputPath as u };
|