diff --git a/.gitignore b/.gitignore index adb36c8..13d1799 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -*.exe \ No newline at end of file +*.exe + +.env \ No newline at end of file diff --git a/call.ts b/call.ts new file mode 100644 index 0000000..b89aff3 --- /dev/null +++ b/call.ts @@ -0,0 +1,47 @@ +type transformer = (arg: string) => any; +interface IConfig { + multiTransform?: boolean; +} + +export async function call(tool: Tool, conf?: transformer | IConfig, ...transforms: transformer[]) { + const config: IConfig = {} + + if (typeof conf === 'object') { + Object.assign(config, conf) + } else { + transforms.unshift(conf as transformer) + } + + const args = Deno.args; + const shouldPair = transforms.length === args.length; + const multiTransform = config.multiTransform || !shouldPair && transforms.length > 1; + + const transformedArgs = args.map((arg, i) => { + if (shouldPair) return transforms[i](arg); + if (multiTransform) return transforms.reduce((a, b) => b(a), arg) + return transforms[0] ? transforms[0](arg) : arg + }) + + await tool(...transformedArgs as T) +} + +type prompt = [string, (v?: string) => boolean] | string + +export async function callWithArgPrompt(tool: Tool, prompts: prompt[]) { + function buildPromptTransform(p: prompt): transformer { + let validation = (v?: string) => !!v; + let pText = p as string; + + if (Array.isArray(p)) { + [pText, validation] = p; + } + + return (a: string) => { + while (!validation(a)) { + a = prompt(pText) || '' + } + } + } + + await call(tool, ...prompts.map(buildPromptTransform)) +} \ No newline at end of file diff --git a/deno.json b/deno.json index 1831b86..09e7fc7 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,7 @@ { + "name": "@bearmetal/pdf-tools", "tasks": { - "dev": "deno run -A --watch main.ts", + "dev": "deno run -A --watch dev.ts", "compile": "deno compile -o compare-form-fields.exe --target x86_64-pc-windows-msvc -R ./main.ts", "install": "deno install -fgq --import-map ./deno.json -n checkfields -R ./main.ts" }, diff --git a/fieldRename.ts b/fieldRename.ts new file mode 100644 index 0000000..4d1a031 --- /dev/null +++ b/fieldRename.ts @@ -0,0 +1,82 @@ +import { PDFAcroField, PDFHexString, PDFName, PDFString, toHexString } from "pdf-lib"; +import { loadPdfForm, savePdf } from "./saveLoadPdf.ts"; +import { PDFDocument } from "pdf-lib"; +import { call, callWithArgPrompt } from "./call.ts"; + +// const thing = PDFAcroField.prototype.getFullyQualifiedName; +// PDFAcroField.prototype.getFullyQualifiedName = function () { +// const name = thing.call(this) +// // if (name?.includes('langauge')) +// console.log(name) +// return name; +// } + +// const thing = PDFHexString.prototype.copyBytesInto +// PDFHexString.prototype.copyBytesInto = function (buffer: Uint8Array, offset: number) { +// console.log((this as any).value) + +// const result = thing.call(this, buffer, offset) +// return result; +// } + +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)) + // console.log(cField.getPartialName()) + } + } + cField = cField.getParent(); + // console.log(cField?.getPartialName()) + } + console.log(field.getName()) + // const newName = name.replace(pattern, change); + // console.log("Change to: %c" + newName, "color: yellow"); + // if (confirm('Ok?')) { + // let parent = field.acroField.getParent(); + // field.acroField.setPartialName(segments.pop()) + // while (parent && segments.length) { + // console.log(parent.getPartialName()) + // parent.setPartialName(segments.pop()) + // parent = parent.getParent(); + // } + // changesMade = true; + // console.log(field.getName()) + // // dict.set(PDFName.of("T"), PDFHexString.fromText(newName)) + // console.log("%cDone!", "color: lime") + // } + // break; + } + } + if (changesMade) { + savePdf(form.doc, path) + } +} + +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:" + ]) +} + + diff --git a/main.ts b/main.ts index f4ecc9d..d244193 100644 --- a/main.ts +++ b/main.ts @@ -1,25 +1,22 @@ -import {PDFDocument} from "pdf-lib"; +import { PDFDocument } from "pdf-lib"; +import { loadPdfForm } from "./saveLoadPdf.ts"; let [pdfPath, csPath] = Deno.args; while (!pdfPath || !pdfPath.endsWith('.pdf')) pdfPath = prompt("Please provide path to PDF file:") || ""; while (!csPath || !csPath.endsWith('.cs')) csPath = prompt("Please provide path to CS class file:") || ""; -const pdfBytes = await Deno.readFile(pdfPath); - -const pdfDoc = await PDFDocument.load(pdfBytes); - -const form = pdfDoc.getForm(); +const form = await loadPdfForm(pdfPath); const fields = form.getFields(); const csFiles = await Promise.all(csPath.split(",").map(c => Deno.readTextFile(c.trim()))); -const fieldNames:string[] = fields.map(f => f.getName()) -.filter(f => { - const rx = new RegExp(`(? rx.test(c)) -}) -.filter(f => !f.toLowerCase().includes("signature")); +const fieldNames: string[] = fields.map(f => f.getName()) + .filter(f => { + const rx = new RegExp(`(? rx.test(c)) + }) + .filter(f => !f.toLowerCase().includes("signature")); if (fieldNames.length) { console.log("%cThe following field names are not present in the CS code", "color: red") @@ -29,3 +26,5 @@ if (fieldNames.length) { console.log("%cAll form fields present", 'color: lime') alert("Ok!") } + +/additionalAdviser.personalInfo.npn\[\?\]/ \ No newline at end of file diff --git a/saveLoadPdf.ts b/saveLoadPdf.ts new file mode 100644 index 0000000..176b666 --- /dev/null +++ b/saveLoadPdf.ts @@ -0,0 +1,16 @@ +import { PDFDocument } from "pdf-lib"; + +export async function loadPdfForm(path: string) { + const pdfBytes = await Deno.readFile(path); + + const pdfDoc = await PDFDocument.load(pdfBytes); + + const form = pdfDoc.getForm() + return form; +} + +export async function savePdf(doc: PDFDocument, path: string) { + const pdfBytes = await doc.save(); + if (Deno.env.get("DRYRUN")) return + await Deno.writeFile(path, pdfBytes); +} \ No newline at end of file diff --git a/testing/test.pdf b/testing/test.pdf new file mode 100644 index 0000000..b975d45 Binary files /dev/null and b/testing/test.pdf differ diff --git a/testing/test.ts b/testing/test.ts new file mode 100644 index 0000000..e69de29 diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..e06731d --- /dev/null +++ b/types.ts @@ -0,0 +1,3 @@ +declare global { + type Tool = (...args: T) => Promise +} \ No newline at end of file