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.cjs
CHANGED
|
@@ -533,8 +533,158 @@ function registerConversionCommands(program) {
|
|
|
533
533
|
});
|
|
534
534
|
}
|
|
535
535
|
//#endregion
|
|
536
|
+
//#region src/docx-extras-format.ts
|
|
537
|
+
const INDENT$1 = " ";
|
|
538
|
+
function indent$1(depth) {
|
|
539
|
+
return INDENT$1.repeat(depth);
|
|
540
|
+
}
|
|
541
|
+
function commentLine(comment, position) {
|
|
542
|
+
const author = comment.author ?? "(no author)";
|
|
543
|
+
return `${indent$1(1)}[${position}] ${author}: ${comment.text}`;
|
|
544
|
+
}
|
|
545
|
+
function footnoteLine(footnote, position) {
|
|
546
|
+
const typeSuffix = footnote.type === void 0 ? "" : ` (${footnote.type})`;
|
|
547
|
+
return `${indent$1(1)}[${position}]${typeSuffix} ${footnote.text}`;
|
|
548
|
+
}
|
|
549
|
+
function commentsSection(comments) {
|
|
550
|
+
if (comments.length === 0) return [];
|
|
551
|
+
return ["comments", ...comments.map((comment, index) => commentLine(comment, index + 1))];
|
|
552
|
+
}
|
|
553
|
+
function footnotesSection(footnotes) {
|
|
554
|
+
if (footnotes.length === 0) return [];
|
|
555
|
+
return ["footnotes", ...footnotes.map((footnote, index) => footnoteLine(footnote, index + 1))];
|
|
556
|
+
}
|
|
557
|
+
function headerOrFooterSection(label, values) {
|
|
558
|
+
if (values.length === 0) return [];
|
|
559
|
+
return [label, ...values.map((text, index) => `${indent$1(1)}[${index + 1}] ${text}`)];
|
|
560
|
+
}
|
|
561
|
+
function numberingLevelLine(ilvl, level) {
|
|
562
|
+
const restartSuffix = level.restart === void 0 ? "" : `, restarts at level ${level.restart}`;
|
|
563
|
+
return `${indent$1(2)}level ${ilvl}: ${level.format} ${JSON.stringify(level.text)} starting at ${level.startAt}${restartSuffix}`;
|
|
564
|
+
}
|
|
565
|
+
function numberingSection(numbering) {
|
|
566
|
+
const numIds = Object.keys(numbering);
|
|
567
|
+
if (numIds.length === 0) return [];
|
|
568
|
+
const lines = ["numbering"];
|
|
569
|
+
for (const numId of numIds) {
|
|
570
|
+
const definition = numbering[numId];
|
|
571
|
+
if (definition === void 0) continue;
|
|
572
|
+
lines.push(`${indent$1(1)}numId ${numId}`);
|
|
573
|
+
const ilvls = Object.keys(definition.levels).sort((a, b) => Number(a) - Number(b));
|
|
574
|
+
for (const ilvl of ilvls) {
|
|
575
|
+
const level = definition.levels[ilvl];
|
|
576
|
+
if (level === void 0) continue;
|
|
577
|
+
lines.push(numberingLevelLine(ilvl, level));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return lines;
|
|
581
|
+
}
|
|
582
|
+
function formatDocxExtrasLines(extras) {
|
|
583
|
+
const nonEmptySections = [
|
|
584
|
+
commentsSection(extras.comments),
|
|
585
|
+
footnotesSection(extras.footnotes),
|
|
586
|
+
headerOrFooterSection("headers", extras.headers),
|
|
587
|
+
headerOrFooterSection("footers", extras.footers),
|
|
588
|
+
numberingSection(extras.numbering)
|
|
589
|
+
].filter((section) => section.length > 0);
|
|
590
|
+
if (nonEmptySections.length === 0) return ["This document carries no comments, footnotes, headers, footers, or numbering definitions."];
|
|
591
|
+
return nonEmptySections.flatMap((section, index) => index === 0 ? section : ["", ...section]);
|
|
592
|
+
}
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/commands/docx-extras.ts
|
|
595
|
+
async function runDocxExtras(input, options) {
|
|
596
|
+
const command = "docx-extras";
|
|
597
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
598
|
+
try {
|
|
599
|
+
const inputBytes = await readInput(input, { signal });
|
|
600
|
+
const pkg = (0, documents_js.decodePackage)(new Uint8Array(inputBytes));
|
|
601
|
+
const extras = (0, documents_js.readDocxExtras)(pkg);
|
|
602
|
+
if (options.json) {
|
|
603
|
+
process.stdout.write(`${JSON.stringify(extras)}\n`);
|
|
604
|
+
return 0;
|
|
605
|
+
}
|
|
606
|
+
for (const line of formatDocxExtrasLines(extras)) process.stdout.write(`${line}\n`);
|
|
607
|
+
return 0;
|
|
608
|
+
} catch (error) {
|
|
609
|
+
process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
|
|
610
|
+
return mapErrorToExit(error, getAbortReason());
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function registerDocxExtrasCommand(program) {
|
|
614
|
+
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) => {
|
|
615
|
+
process.exitCode = await runDocxExtras(input, options);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/commands/fonts.ts
|
|
620
|
+
const FONT_SOURCE_FORMATS = {
|
|
621
|
+
docx: true,
|
|
622
|
+
pptx: true,
|
|
623
|
+
odt: true,
|
|
624
|
+
odp: true,
|
|
625
|
+
ods: true,
|
|
626
|
+
odg: true
|
|
627
|
+
};
|
|
628
|
+
function isFontSourceFormat(format) {
|
|
629
|
+
return format in FONT_SOURCE_FORMATS;
|
|
630
|
+
}
|
|
631
|
+
function resolveFontSourcePackage(format, bytes) {
|
|
632
|
+
if (format === "docx" || format === "pptx") return {
|
|
633
|
+
kind: format,
|
|
634
|
+
package: (0, documents_js.decodePackage)(bytes)
|
|
635
|
+
};
|
|
636
|
+
return {
|
|
637
|
+
kind: "odf",
|
|
638
|
+
package: (0, odf_js.decodePackage)(bytes)
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
async function runFonts(input, options) {
|
|
642
|
+
const command = "fonts";
|
|
643
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
644
|
+
try {
|
|
645
|
+
const format = inferFormatFromExtension(input);
|
|
646
|
+
if (format === void 0) {
|
|
647
|
+
process.stderr.write(`[${command}] cannot infer a document format from '${input}'; expected one of docx, pptx, odt, odp, ods, odg\n`);
|
|
648
|
+
return 2;
|
|
649
|
+
}
|
|
650
|
+
if (!isFontSourceFormat(format)) {
|
|
651
|
+
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`);
|
|
652
|
+
return 2;
|
|
653
|
+
}
|
|
654
|
+
const inputBytes = await readInput(input, { signal });
|
|
655
|
+
const source = resolveFontSourcePackage(format, new Uint8Array(inputBytes));
|
|
656
|
+
const summaries = (0, documents_js.extractSourceFonts)(source).map((face) => ({
|
|
657
|
+
family: face.family,
|
|
658
|
+
bold: face.bold,
|
|
659
|
+
italic: face.italic,
|
|
660
|
+
byteLength: face.bytes.length
|
|
661
|
+
}));
|
|
662
|
+
if (options.json) {
|
|
663
|
+
process.stdout.write(`${JSON.stringify(summaries)}\n`);
|
|
664
|
+
return 0;
|
|
665
|
+
}
|
|
666
|
+
if (summaries.length === 0) {
|
|
667
|
+
process.stdout.write("This document embeds no source fonts.\n");
|
|
668
|
+
return 0;
|
|
669
|
+
}
|
|
670
|
+
for (const face of summaries) {
|
|
671
|
+
const style = [face.bold ? "bold" : void 0, face.italic ? "italic" : void 0].filter((value) => value !== void 0).join(" ");
|
|
672
|
+
process.stdout.write(`${face.family}${style === "" ? "" : ` (${style})`} -- ${face.byteLength} bytes\n`);
|
|
673
|
+
}
|
|
674
|
+
return 0;
|
|
675
|
+
} catch (error) {
|
|
676
|
+
process.stderr.write(`${formatError(error, false)}\n`);
|
|
677
|
+
return mapErrorToExit(error, getAbortReason());
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function registerFontsCommand(program) {
|
|
681
|
+
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) => {
|
|
682
|
+
process.exitCode = await runFonts(input, options);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
//#endregion
|
|
536
686
|
//#region src/commands/formats.ts
|
|
537
|
-
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package";
|
|
687
|
+
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";
|
|
538
688
|
function registerFormatsCommand(program) {
|
|
539
689
|
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) => {
|
|
540
690
|
const { conversions } = (0, documents_js.createLocalDocumentConverter)();
|
|
@@ -627,6 +777,78 @@ function registerFromPackageCommand(program) {
|
|
|
627
777
|
});
|
|
628
778
|
}
|
|
629
779
|
//#endregion
|
|
780
|
+
//#region src/runtime/metadata-format.ts
|
|
781
|
+
const METADATA_KEYS = [
|
|
782
|
+
"title",
|
|
783
|
+
"author",
|
|
784
|
+
"subject",
|
|
785
|
+
"keywords",
|
|
786
|
+
"creator",
|
|
787
|
+
"producer",
|
|
788
|
+
"createdIso",
|
|
789
|
+
"modifiedIso"
|
|
790
|
+
];
|
|
791
|
+
function isPresent(entry) {
|
|
792
|
+
return entry[1] !== void 0;
|
|
793
|
+
}
|
|
794
|
+
function formatMetadataValue(value) {
|
|
795
|
+
return typeof value === "string" ? value : value.join(", ");
|
|
796
|
+
}
|
|
797
|
+
function presentMetadataEntries(metadata) {
|
|
798
|
+
return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
|
|
799
|
+
}
|
|
800
|
+
function formatMetadataLines(metadata) {
|
|
801
|
+
return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
|
|
802
|
+
}
|
|
803
|
+
//#endregion
|
|
804
|
+
//#region src/commands/metadata.ts
|
|
805
|
+
function readMetadataForFormat(format, bytes, signal) {
|
|
806
|
+
switch (format) {
|
|
807
|
+
case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes)).metadata;
|
|
808
|
+
case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes)).metadata;
|
|
809
|
+
case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
810
|
+
case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
811
|
+
case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
812
|
+
case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
813
|
+
case "odf": return (0, documents_js.readOdfFormulaContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
814
|
+
case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes)).metadata;
|
|
815
|
+
case "pdf": return (0, documents_js.readPdf)(bytes, { signal }).metadata;
|
|
816
|
+
case "xlsx": return (0, documents_js.readPdf)((0, documents_js.xlsxToPdf)(bytes, { signal }), { signal }).metadata;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async function runMetadata(input, options) {
|
|
820
|
+
const command = "metadata";
|
|
821
|
+
const source = inferFormatFromExtension(input);
|
|
822
|
+
if (source === void 0) {
|
|
823
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
824
|
+
return 2;
|
|
825
|
+
}
|
|
826
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
827
|
+
try {
|
|
828
|
+
const inputBytes = await readInput(input, { signal });
|
|
829
|
+
const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
|
|
830
|
+
if (options.json) {
|
|
831
|
+
process.stdout.write(`${JSON.stringify(metadata)}\n`);
|
|
832
|
+
return 0;
|
|
833
|
+
}
|
|
834
|
+
const lines = formatMetadataLines(metadata);
|
|
835
|
+
if (lines.length === 0) {
|
|
836
|
+
process.stdout.write("This document carries no metadata.\n");
|
|
837
|
+
return 0;
|
|
838
|
+
}
|
|
839
|
+
for (const line of lines) process.stdout.write(`${line}\n`);
|
|
840
|
+
return 0;
|
|
841
|
+
} catch (error) {
|
|
842
|
+
process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
|
|
843
|
+
return mapErrorToExit(error, getAbortReason());
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function registerMetadataCommand(program) {
|
|
847
|
+
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) => {
|
|
848
|
+
process.exitCode = await runMetadata(input, options);
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
//#endregion
|
|
630
852
|
//#region src/odb-structure.ts
|
|
631
853
|
const INDENT = " ";
|
|
632
854
|
function indent(depth) {
|
|
@@ -1230,12 +1452,6 @@ function countImagesByFormat(images) {
|
|
|
1230
1452
|
for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
|
|
1231
1453
|
return counts;
|
|
1232
1454
|
}
|
|
1233
|
-
function isPresent(entry) {
|
|
1234
|
-
return entry[1] !== void 0;
|
|
1235
|
-
}
|
|
1236
|
-
function formatMetadataValue(value) {
|
|
1237
|
-
return typeof value === "string" ? value : value.join(", ");
|
|
1238
|
-
}
|
|
1239
1455
|
async function runPdfInspect(input, options) {
|
|
1240
1456
|
const command = "pdf-inspect";
|
|
1241
1457
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
@@ -1253,7 +1469,7 @@ async function runPdfInspect(input, options) {
|
|
|
1253
1469
|
}
|
|
1254
1470
|
});
|
|
1255
1471
|
if (options.full) {
|
|
1256
|
-
process.stdout.write(`${JSON.stringify(layout, void 0, 2)}\n`);
|
|
1472
|
+
process.stdout.write(`${JSON.stringify((0, documents_js.layoutDocumentWithSchema)(layout), void 0, 2)}\n`);
|
|
1257
1473
|
return 0;
|
|
1258
1474
|
}
|
|
1259
1475
|
const imagesByFormat = countImagesByFormat(layout.images);
|
|
@@ -1277,19 +1493,9 @@ async function runPdfInspect(input, options) {
|
|
|
1277
1493
|
const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
|
|
1278
1494
|
process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
|
|
1279
1495
|
});
|
|
1280
|
-
|
|
1281
|
-
["title", layout.metadata.title],
|
|
1282
|
-
["author", layout.metadata.author],
|
|
1283
|
-
["subject", layout.metadata.subject],
|
|
1284
|
-
["keywords", layout.metadata.keywords],
|
|
1285
|
-
["creator", layout.metadata.creator],
|
|
1286
|
-
["producer", layout.metadata.producer],
|
|
1287
|
-
["createdIso", layout.metadata.createdIso],
|
|
1288
|
-
["modifiedIso", layout.metadata.modifiedIso]
|
|
1289
|
-
].filter(isPresent);
|
|
1290
|
-
if (presentMetadata.length > 0) {
|
|
1496
|
+
if (presentMetadataEntries(layout.metadata).length > 0) {
|
|
1291
1497
|
process.stdout.write("metadata:\n");
|
|
1292
|
-
for (const
|
|
1498
|
+
for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
|
|
1293
1499
|
}
|
|
1294
1500
|
if (imagesByFormat.size > 0) {
|
|
1295
1501
|
process.stdout.write("images:\n");
|
|
@@ -1307,8 +1513,155 @@ function registerPdfInspectCommand(program) {
|
|
|
1307
1513
|
});
|
|
1308
1514
|
}
|
|
1309
1515
|
//#endregion
|
|
1516
|
+
//#region src/commands/set-metadata.ts
|
|
1517
|
+
const REBUILD_FORMATS = {
|
|
1518
|
+
docx: true,
|
|
1519
|
+
pptx: true,
|
|
1520
|
+
odt: true,
|
|
1521
|
+
odp: true,
|
|
1522
|
+
ods: true,
|
|
1523
|
+
odg: true,
|
|
1524
|
+
markdown: true
|
|
1525
|
+
};
|
|
1526
|
+
function isRebuildFormat(format) {
|
|
1527
|
+
return format in REBUILD_FORMATS;
|
|
1528
|
+
}
|
|
1529
|
+
function readContentForFormat(format, bytes) {
|
|
1530
|
+
switch (format) {
|
|
1531
|
+
case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes));
|
|
1532
|
+
case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes));
|
|
1533
|
+
case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes));
|
|
1534
|
+
case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes));
|
|
1535
|
+
case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes));
|
|
1536
|
+
case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes));
|
|
1537
|
+
case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes));
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function buildBytesForRebuildFormat(format, content) {
|
|
1541
|
+
switch (format) {
|
|
1542
|
+
case "docx": return (0, documents_js.encodePackage)((0, documents_js.buildDocxPackage)(content));
|
|
1543
|
+
case "pptx": return (0, documents_js.encodePackage)((0, documents_js.buildPptxPackage)(content));
|
|
1544
|
+
case "odt": return (0, odf_js.encodePackage)((0, documents_js.buildOdtPackage)(content));
|
|
1545
|
+
case "odp": return (0, odf_js.encodePackage)((0, documents_js.buildOdpPackage)(content));
|
|
1546
|
+
case "ods": return (0, odf_js.encodePackage)((0, documents_js.buildOdsPackage)(content));
|
|
1547
|
+
case "odg": return (0, odf_js.encodePackage)((0, documents_js.buildOdgPackage)(content));
|
|
1548
|
+
case "markdown": return (0, documents_js.encodeMarkdownText)((0, documents_js.buildMarkdownText)(content));
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function mergeMetadata(current, overrides) {
|
|
1552
|
+
return {
|
|
1553
|
+
...current,
|
|
1554
|
+
...overrides.title !== void 0 ? { title: overrides.title } : {},
|
|
1555
|
+
...overrides.author !== void 0 ? { author: overrides.author } : {},
|
|
1556
|
+
...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
|
|
1557
|
+
...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function parseKeywords(csv) {
|
|
1561
|
+
return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1562
|
+
}
|
|
1563
|
+
function classifyWritePath(source, target) {
|
|
1564
|
+
if (source === "pdf" && target === "pdf") return { kind: "pdf" };
|
|
1565
|
+
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" };
|
|
1566
|
+
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" };
|
|
1567
|
+
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.` };
|
|
1568
|
+
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.` };
|
|
1569
|
+
return {
|
|
1570
|
+
kind: "rebuild",
|
|
1571
|
+
format: source
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
async function runSetMetadata(input, output, options) {
|
|
1575
|
+
const command = "set-metadata";
|
|
1576
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
1577
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
1578
|
+
return 2;
|
|
1579
|
+
}
|
|
1580
|
+
const target = resolveTargetFormat(output, options.out, options.to);
|
|
1581
|
+
if ("errorMessage" in target) {
|
|
1582
|
+
process.stderr.write(`[${command}] ${target.errorMessage}\n`);
|
|
1583
|
+
return 2;
|
|
1584
|
+
}
|
|
1585
|
+
const source = inferFormatFromExtension(input);
|
|
1586
|
+
if (source === void 0) {
|
|
1587
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
1588
|
+
return 2;
|
|
1589
|
+
}
|
|
1590
|
+
const writePath = classifyWritePath(source, target.format);
|
|
1591
|
+
if ("errorMessage" in writePath) {
|
|
1592
|
+
process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
|
|
1593
|
+
return 2;
|
|
1594
|
+
}
|
|
1595
|
+
const overrides = {
|
|
1596
|
+
title: options.setTitle,
|
|
1597
|
+
author: options.setAuthor,
|
|
1598
|
+
subject: options.setSubject,
|
|
1599
|
+
keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
|
|
1600
|
+
};
|
|
1601
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
|
|
1602
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
1603
|
+
try {
|
|
1604
|
+
const inputBytes = await readInput(input, { signal });
|
|
1605
|
+
const bytes = writePath.kind === "pdf" ? (() => {
|
|
1606
|
+
const layout = (0, documents_js.readPdf)(new Uint8Array(inputBytes), { signal });
|
|
1607
|
+
const patched = {
|
|
1608
|
+
...layout,
|
|
1609
|
+
metadata: mergeMetadata(layout.metadata, overrides)
|
|
1610
|
+
};
|
|
1611
|
+
return (0, documents_js.writePdf)(patched, { signal });
|
|
1612
|
+
})() : (() => {
|
|
1613
|
+
const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
|
|
1614
|
+
const nextContent = {
|
|
1615
|
+
...content,
|
|
1616
|
+
metadata: mergeMetadata(content.metadata, overrides)
|
|
1617
|
+
};
|
|
1618
|
+
return buildBytesForRebuildFormat(writePath.format, nextContent);
|
|
1619
|
+
})();
|
|
1620
|
+
await writeOutput(resolvedOutput, bytes);
|
|
1621
|
+
createDiagnosticReporter({
|
|
1622
|
+
json: options.json,
|
|
1623
|
+
quiet: options.quiet,
|
|
1624
|
+
command
|
|
1625
|
+
}).summarize({
|
|
1626
|
+
output: resolvedOutput,
|
|
1627
|
+
bytes: bytes.byteLength,
|
|
1628
|
+
diagnosticCount: 0
|
|
1629
|
+
});
|
|
1630
|
+
return 0;
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
1633
|
+
return mapErrorToExit(error, getAbortReason());
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function registerSetMetadataCommand(program) {
|
|
1637
|
+
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", [
|
|
1638
|
+
"",
|
|
1639
|
+
"Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
|
|
1640
|
+
"involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
|
|
1641
|
+
"odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
|
|
1642
|
+
"this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
|
|
1643
|
+
"since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
|
|
1644
|
+
"",
|
|
1645
|
+
"set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
|
|
1646
|
+
"set-metadata on the result, if you need a different target format."
|
|
1647
|
+
].join("\n"));
|
|
1648
|
+
addOutOption(command);
|
|
1649
|
+
addTimeoutOption(command);
|
|
1650
|
+
addJsonOption(command);
|
|
1651
|
+
addQuietOption(command);
|
|
1652
|
+
addVerboseOption(command);
|
|
1653
|
+
command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
|
|
1654
|
+
command.option("--set-title <text>", "set the title field");
|
|
1655
|
+
command.option("--set-author <text>", "set the author field");
|
|
1656
|
+
command.option("--set-subject <text>", "set the subject field");
|
|
1657
|
+
command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
|
|
1658
|
+
command.action(async (input, output, options) => {
|
|
1659
|
+
process.exitCode = await runSetMetadata(input, output, options);
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
//#endregion
|
|
1310
1663
|
//#region package.json
|
|
1311
|
-
var version = "1.
|
|
1664
|
+
var version = "1.5.0";
|
|
1312
1665
|
//#endregion
|
|
1313
1666
|
//#region src/program.ts
|
|
1314
1667
|
function createProgram() {
|
|
@@ -1325,6 +1678,10 @@ function createProgram() {
|
|
|
1325
1678
|
registerOdmCommand(program);
|
|
1326
1679
|
registerOdbCommands(program);
|
|
1327
1680
|
registerPdfInspectCommand(program);
|
|
1681
|
+
registerFontsCommand(program);
|
|
1682
|
+
registerDocxExtrasCommand(program);
|
|
1683
|
+
registerMetadataCommand(program);
|
|
1684
|
+
registerSetMetadataCommand(program);
|
|
1328
1685
|
return program;
|
|
1329
1686
|
}
|
|
1330
1687
|
//#endregion
|