improves block functionality
adds cli compatible prompts/logs adds logfile function for debug adds multiselect support new fieldRename adds listFieldNames
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
import {
|
||||
PDFAcroField,
|
||||
PDFHexString,
|
||||
PDFName,
|
||||
PDFString,
|
||||
toHexString,
|
||||
} from "pdf-lib";
|
||||
import { loadPdfForm, savePdf } from "util/saveLoadPdf.ts";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { call, callWithArgPrompt } from "util/call.ts";
|
||||
import { type PDFAcroField, type PDFField, PDFName, PDFString } from "pdf-lib";
|
||||
import { loadPdf, loadPdfForm, savePdf } from "util/saveLoadPdf.ts";
|
||||
import { callWithArgPrompt } from "util/call.ts";
|
||||
import { TerminalBlock } from "../cli/TerminalLayout.ts";
|
||||
import { forceArgs } from "../cli/forceArgs.ts";
|
||||
import { colorize } from "../cli/style.ts";
|
||||
import { cliLog, cliPrompt } from "../cli/prompts.ts";
|
||||
import { multiSelectMenuInteractive } from "../cli/selectMenu.ts";
|
||||
|
||||
// const thing = PDFAcroField.prototype.getFullyQualifiedName;
|
||||
// PDFAcroField.prototype.getFullyQualifiedName = function () {
|
||||
@@ -80,18 +78,108 @@ async function renameFields(
|
||||
}
|
||||
}
|
||||
|
||||
function applyRename(
|
||||
field: PDFField,
|
||||
name: string,
|
||||
pattern: RegExp,
|
||||
change: string,
|
||||
) {
|
||||
const segments = name.split(".");
|
||||
const matchingSegments = segments.filter((s) => pattern.test(s));
|
||||
let cField: PDFAcroField | undefined = field.acroField;
|
||||
while (cField) {
|
||||
if (
|
||||
cField.getPartialName() &&
|
||||
matchingSegments.includes(cField.getPartialName()!)
|
||||
) {
|
||||
const mName = cField.getPartialName()?.replace(pattern, change);
|
||||
if (mName) {
|
||||
cField.dict.set(PDFName.of("T"), PDFString.of(mName));
|
||||
// console.log(cField.getPartialName())
|
||||
}
|
||||
}
|
||||
cField = cField.getParent();
|
||||
// console.log(cField?.getPartialName())
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateChange(change: string, match: RegExpExecArray) {
|
||||
return change.replace(
|
||||
/\$(\d+)(i?)/g,
|
||||
(_, i, indexed) =>
|
||||
indexed
|
||||
? (parseInt(match[i]) ? (parseInt(match[i]) - 1).toString() : match[i])
|
||||
: match[i],
|
||||
);
|
||||
}
|
||||
|
||||
class RenameFields implements ITool {
|
||||
name = "renamefields";
|
||||
description = "Renames fields in a PDF form";
|
||||
help() {
|
||||
console.log("Usage: renamefields <pdfPath> <pattern> <change>");
|
||||
block: TerminalBlock | undefined;
|
||||
|
||||
setBlock(block: TerminalBlock) {
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
help(standalone = false) {
|
||||
cliLog(
|
||||
"Usage: renamefields <pdfPath> <pattern> <change>",
|
||||
standalone ? undefined : this.block,
|
||||
);
|
||||
}
|
||||
async run(pdfPath: string = "", pattern: string = "", change: string = "") {
|
||||
await callWithArgPrompt(renameFields, [
|
||||
["Please provide path to PDF:", (p) => !!p && p.endsWith(".pdf")],
|
||||
"Please provide search string:",
|
||||
"Please provide requested change:",
|
||||
], [pdfPath, pattern, change]);
|
||||
if (!this.block) {
|
||||
this.block = new TerminalBlock();
|
||||
}
|
||||
this.block.setPreserveHistory(true);
|
||||
|
||||
[pdfPath, pattern, change] = await forceArgs(
|
||||
[pdfPath, pattern, change],
|
||||
[
|
||||
["Please provide path to PDF:", (p) => !!p && p.endsWith(".pdf")],
|
||||
"Please provide search string:",
|
||||
"Please provide requested change:",
|
||||
],
|
||||
this.block,
|
||||
);
|
||||
|
||||
const patternRegex = new RegExp(pattern);
|
||||
|
||||
const pdf = await loadPdf(pdfPath);
|
||||
const form = pdf.getForm();
|
||||
const fields = form.getFields();
|
||||
|
||||
const foundUpdates: [string, callback][] = [];
|
||||
|
||||
for (const field of fields) {
|
||||
const name = field.getName();
|
||||
const match = patternRegex.exec(name);
|
||||
if (match) {
|
||||
const toChange = evaluateChange(change, match);
|
||||
foundUpdates.push([
|
||||
`${colorize(name, "red")} -> ${colorize(toChange, "green")}`,
|
||||
() => {
|
||||
applyRename(field, name, patternRegex, toChange);
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundUpdates.length) {
|
||||
cliLog("Found updates:", this.block);
|
||||
await multiSelectMenuInteractive(
|
||||
"Please select an option to apply",
|
||||
foundUpdates,
|
||||
{ terminalBlock: this.block },
|
||||
);
|
||||
}
|
||||
|
||||
const path = await cliPrompt(
|
||||
"Save to path (or hit enter to keep current):",
|
||||
this.block,
|
||||
);
|
||||
await savePdf(pdf, path || pdfPath);
|
||||
}
|
||||
}
|
||||
export default new RenameFields();
|
||||
|
Reference in New Issue
Block a user