165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
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 { cliAlert, cliLog, cliPrompt } from "../cli/prompts.ts";
|
|
import { multiSelectMenuInteractive } from "../cli/selectMenu.ts";
|
|
import type { callback, ITool } from "../types.ts";
|
|
|
|
async function renameFields(
|
|
path: string,
|
|
pattern: string | RegExp,
|
|
change: string,
|
|
) {
|
|
if (typeof pattern === "string") pattern = new RegExp(pattern);
|
|
const form = await loadPdfForm(path);
|
|
const fields = form.getFields();
|
|
let changesMade = false;
|
|
for (const field of fields) {
|
|
const name = field.getName();
|
|
if (pattern.test(name)) {
|
|
console.log(name + " %cfound", "color: red");
|
|
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) {
|
|
changesMade = true;
|
|
cField.dict.set(PDFName.of("T"), PDFString.of(mName));
|
|
}
|
|
}
|
|
cField = cField.getParent();
|
|
}
|
|
}
|
|
}
|
|
if (changesMade) {
|
|
savePdf(form.doc, path);
|
|
}
|
|
}
|
|
|
|
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";
|
|
block: TerminalBlock | undefined;
|
|
|
|
setBlock(block: TerminalBlock) {
|
|
this.block = block;
|
|
}
|
|
|
|
async help(standalone = false) {
|
|
await cliAlert(
|
|
"Usage: rename-fields <pdfPath> <pattern> <change>\n",
|
|
standalone ? undefined : this.block,
|
|
);
|
|
}
|
|
async run(pdfPath: string = "", pattern: string = "", change: string = "") {
|
|
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);
|
|
const preview = name.replace(new RegExp(patternRegex), toChange);
|
|
foundUpdates.push([
|
|
`${colorize(name, "red")} -> ${colorize(preview, "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();
|
|
|
|
if (import.meta.main) {
|
|
// await call(renameFields)
|
|
// while (!path || !path.endsWith('.pdf')) path = prompt("Please provide path to PDF:") || '';
|
|
// while (!pattern) pattern = prompt("Please provide search string:") || '';
|
|
// while (!change) change = prompt("Please provide requested change:") || '';
|
|
await callWithArgPrompt(renameFields, [
|
|
["Please provide path to PDF:", (p) => !!p && p.endsWith(".pdf")],
|
|
"Please provide search string:",
|
|
"Please provide requested change:",
|
|
]);
|
|
}
|