Пакетное заполнение PDF-форм из Python или Bash
У меня есть PDF-форма, которую нужно заполнить несколько раз (точнее, расписание). Теперь, поскольку я не хочу делать это вручную, я искал способ заполнить их, используя скрипт на python или инструменты, которые можно было бы использовать в скрипте bash.
У кого-нибудь есть опыт с этим?
4 ответа
Намного более быстрая версия, не требуется ни pdftk, ни fdfgen, чистый Python 3.6+:
# -*- coding: utf-8 -*-
from collections import OrderedDict
from PyPDF2 import PdfFileWriter, PdfFileReader
def _getFields(obj, tree=None, retval=None, fileobj=None):
"""
Extracts field data if this PDF contains interactive form fields.
The *tree* and *retval* parameters are for recursive use.
:param fileobj: A file object (usually a text file) to write
a report to on all interactive form fields found.
:return: A dictionary where each key is a field name, and each
value is a :class:`Field<PyPDF2.generic.Field>` object. By
default, the mapping name is used for keys.
:rtype: dict, or ``None`` if form data could not be located.
"""
fieldAttributes = {'/FT': 'Field Type', '/Parent': 'Parent', '/T': 'Field Name', '/TU': 'Alternate Field Name',
'/TM': 'Mapping Name', '/Ff': 'Field Flags', '/V': 'Value', '/DV': 'Default Value'}
if retval is None:
retval = OrderedDict()
catalog = obj.trailer["/Root"]
# get the AcroForm tree
if "/AcroForm" in catalog:
tree = catalog["/AcroForm"]
else:
return None
if tree is None:
return retval
obj._checkKids(tree, retval, fileobj)
for attr in fieldAttributes:
if attr in tree:
# Tree is a field
obj._buildField(tree, retval, fileobj, fieldAttributes)
break
if "/Fields" in tree:
fields = tree["/Fields"]
for f in fields:
field = f.getObject()
obj._buildField(field, retval, fileobj, fieldAttributes)
return retval
def get_form_fields(infile):
infile = PdfFileReader(open(infile, 'rb'))
fields = _getFields(infile)
return OrderedDict((k, v.get('/V', '')) for k, v in fields.items())
def update_form_values(infile, outfile, newvals=None):
pdf = PdfFileReader(open(infile, 'rb'))
writer = PdfFileWriter()
for i in range(pdf.getNumPages()):
page = pdf.getPage(i)
try:
if newvals:
writer.updatePageFormFieldValues(page, newvals)
else:
writer.updatePageFormFieldValues(page,
{k: f'#{i} {k}={v}'
for i, (k, v) in enumerate(get_form_fields(infile).items())
})
writer.addPage(page)
except Exception as e:
print(repr(e))
writer.addPage(page)
with open(outfile, 'wb') as out:
writer.write(out)
if __name__ == '__main__':
from pprint import pprint
pdf_file_name = '2PagesFormExample.pdf'
pprint(get_form_fields(pdf_file_name))
update_form_values(pdf_file_name, 'out-' + pdf_file_name) # enumerate & fill the fields with their own names
update_form_values(pdf_file_name, 'out2-' + pdf_file_name,
{'my_fieldname_1': 'My Value',
'my_fieldname_2': 'My Another alue'}) # update the form fields
Для Python вам понадобятся библиотеки fdfgen и pdftk
Комментарий @Hugh Bothwell на 100% верен, поэтому я дополню этот ответ работающей реализацией.
Если вы находитесь в Windows, вам также необходимо убедиться, что и python, и pdftk содержатся в системном пути (если вы не хотите использовать длинные имена папок).
Вот код для автоматического пакетного заполнения коллекции форм PDF из файла данных CSV:
import csv
from fdfgen import forge_fdf
import os
import sys
sys.path.insert(0, os.getcwd())
filename_prefix = "NVC"
csv_file = "NVC.csv"
pdf_file = "NVC.pdf"
tmp_file = "tmp.fdf"
output_folder = './output/'
def process_csv(file):
headers = []
data = []
csv_data = csv.reader(open(file))
for i, row in enumerate(csv_data):
if i == 0:
headers = row
continue;
field = []
for i in range(len(headers)):
field.append((headers[i], row[i]))
data.append(field)
return data
def form_fill(fields):
fdf = forge_fdf("",fields,[],[],[])
fdf_file = open(tmp_file,"w")
fdf_file.write(fdf)
fdf_file.close()
output_file = '{0}{1} {2}.pdf'.format(output_folder, filename_prefix, fields[1][1])
cmd = 'pdftk "{0}" fill_form "{1}" output "{2}" dont_ask'.format(pdf_file, tmp_file, output_file)
os.system(cmd)
os.remove(tmp_file)
data = process_csv(csv_file)
print('Generating Forms:')
print('-----------------------')
for i in data:
if i[0][1] == 'Yes':
continue
print('{0} {1} created...'.format(filename_prefix, i[1][1]))
form_fill(i)
Примечание: не нужно делать ракетные операции, чтобы понять, как это настроить. Начальные объявления переменных содержат пользовательскую конфигурацию.
В CSV в первой строке каждого столбца будет указано имя соответствующего поля в файле PDF. Любые столбцы, которые не имеют соответствующих полей в шаблоне, будут игнорироваться.
В шаблоне PDF просто создайте редактируемые поля, где вы хотите, чтобы ваши данные заполнялись, и убедитесь, что имена совпадают с данными CSV.
Для этой конкретной конфигурации просто поместите этот файл в ту же папку, что и ваши NVC.csv, NVC.pdf, и папку с именем "output". Запустите его, и он сделает все остальное автоматически.
Заменить оригинальный файл
os.system('pdftk "original.pdf" fill_form "data.fdf" output "output.pdf"')
os.remove("data.fdf")
os.remove("original.pdf")
os.rename("output.pdf","original.pdf")
Я написал библиотеку, основанную на:'pdfrw', 'pdf2image', 'Pillow', 'PyPDF2', под названием fillpdf (
pip install fillpdf
и зависимость от Поплера
conda install -c conda-forge poppler
)
Основное использование:
from fillpdf import fillpdfs
fillpdfs.get_form_fields("blank.pdf")
# returns a dictionary of fields
# Set the returned dictionary values a save to a variable
# For radio boxes ('Off' = not filled, 'Yes' = filled)
data_dict = {
'Text2': 'Name',
'Text4': 'LastName',
'box': 'Yes',
}
fillpdfs.write_fillable_pdf('blank.pdf', 'new.pdf', data_dict)
# If you want it flattened:
fillpdfs.flatten_pdf('new.pdf', 'newflat.pdf')
Подробнее здесь: https://github.com/t-houssian/fillpdf
Если некоторые поля не заполняются, используйте можно использовать fitz (
pip install PyMuPDF
) и PyPDF2 (
pip install PyPDF2
) как следующее изменение точек по мере необходимости:
import fitz
from PyPDF2 import PdfFileReader
file_handle = fitz.open('blank.pdf')
pdf = PdfFileReader(open('blank.pdf','rb'))
box = pdf.getPage(0).mediaBox
w = box.getWidth()
h = box.getHeight()
# For images
image_rectangle = fitz.Rect((w/2)-200,h-255,(w/2)-100,h-118)
pages = pdf.getNumPages() - 1
last_page = file_handle[pages]
last_page._wrapContents()
last_page.insertImage(image_rectangle, filename=f'image.png')
# For text
last_page.insertText(fitz.Point((w/2)-247 , h-478), 'John Smith', fontsize=14, fontname="times-bold")
file_handle.save(f'newpdf.pdf')