如何将 CSV 或 FDF 数据解析为 Python 字典并注入到模板 PDF 表单中?

问题描述 投票:0回答:1

我在 CRM 中有客户数据,需要使用它来自动完成 PDF 表单。

我已经弄清楚了 CRM 提取到 CSV、提取后 CSV 操作、将 CSV 正确解析为 FDF 格式以及创建和保存新的 PDF 文件,该文件应该填充来自 FDF 的数据。该代码应该打开一个模板表单,其中的字段名称与 CSV 和 FDF 文件匹配,注入字段的值并将其保存为新文件。

我似乎遇到的问题是我无法将 FDF 数据放入模板中。当我打开新保存的 PDF 时,字段为空白。我尝试过几种不同的方法,但现在我已经陷入了困境,我认为我已经失去了客观的能力。

我正在使用 pypdf 4.1.0 库,因为我看到开发人员已将 PyPDF2 重新纳入其中。

这是我当前正在编写的代码。我尝试了许多不同的方法,但这是代表当前状态的最干净的版本:

import csv
import os
from pypdf import PdfWriter, PdfReader

# Read the CSV file
with open('acrobat_import.csv', 'r') as file:
    reader = csv.DictReader(file)
    data = list(reader)

# Function to create the FDF file content
def create_fdf_content(row):
    fdf_content = """
    
%FDF-1.2
%����
1 0 obj
<</FDF<</F(template_form.pdf)/Fields[
    <</T(courseType)/V/{courseType}>>
    <</T(gender)/V/{gender}>>
    <</T(dateofbirth)/V({dateofbirth})>>
    <</T(preferredLanguage)/V/{preferredLanguage}>>
    <</T(LastName)/V({lastName})>>
    <</T(firstName)/V({firstName})>>
    <</T(middleName)/V({middleName})>>
    <</T(addressStreet)/V({addressStreet})>>
    <</T(addressCity)/V({addressCity})>>
    <</T(addressState)/V({addressState})>>
    <</T(addressCountry)/V({addressCountry})>>
    <</T(addressPostalCode)/V({addressPostalCode})>>
    <</T(phoneNumber)/V({phoneNumber})>>
    <</T(emailAddress)/V({emailAddress})>>
    <</T(studentSigDate)/V({studentSigDate})>>
    <</T(courseStartDate)/V({courseStartDate})>>
    <</T(courseEndDate)/V({courseEndDate})>>
    <</T(classHours)/V({classHours})>>
    <</T(numberOfStudents)/V({numberOfStudents})>>
    <</T(courseState)/V({courseState})>>
    <</T(courseCity)/V({courseCity})>>
    <</T(courseLanguage)/V/{courseLanguage}>>
    <</T(instructorNumber)/V({instructorNumber})>>
    <</T(instructorLastName)/V({instructorLastName})>>
    <</T(instructorFirstName)/V({instructorFirstName})>>
    <</T(instructorSigDate)/V({instructorSigDate})>>
    <</T(courseFee)/V({courseFee})>>
    <</T(examinerNumber)/V({examinerNumber})>>
    <</T(examinerLastName)/V({examinerLastName})>>
    <</T(examinerFirstName)/V({examinerFirstName})>>
    <</T(examinerSigDate)/V({examinerSigDate})>>]
    >> >>
endobj
trailer
<</Root 1 0 R>>
%%EOF
""".format(**row)
    return fdf_content    
    
# Loop through each row, create an FDF file
for row in data:
    if row['lastName'] and row['firstName']:
        # Create the FDF file
        fdf_filename = f"{row['lastName']}_{row['firstName']} - {row['courseType']}.fdf"
        with open(fdf_filename, 'w', encoding='utf-8') as file:
            file.write(create_fdf_content(row))
        print(f"Created FDF file: {fdf_filename}")

# Open the template PDF
template_pdf = PdfReader("template_form.pdf", "rb")

# Create a dictionary from the fdf file
def parse_fdf_file(fdf_filename):
    fields = []
    with open(fdf_filename, 'r', encoding='utf-8') as fdf_file:
        lines = fdf_file.readlines()
        for line in lines:
            if line.startswith('<</T(') and '/V(' in line: # Does not account for radio button fields which have /V/ not /V()
                field_name = line.split('<</T(')[1].split(')')[0]
                field_value = line.split('/V(')[1].split(')')[0]
                fields.append({'field_name': field_name, 'field_value': field_value})
    return fields

# Import the FDF data into the template PDF
pdf_writer = PdfWriter()
page = template_pdf.pages[0]
fields = template_pdf.get_fields()
pdf_writer.add_page(page)
for field in fields:
    pdf_writer.update_page_form_field_values(0, field['field_name'], field['field_value'])

# Save the resulting PDF with the same name as the FDF file
pdf_filename = f"{row['lastName']}_{row['firstName']} - {row['courseType']}.pdf"
with open(pdf_filename, "wb") as pdf_file:
    pdf_writer.write(pdf_file)
print(f"Created PDF file: {pdf_filename}")

验证我这里的代码确实以正确的格式创建了一个 FDF 文件,并且我可以手动将该 FDF 导入到模板表单中。这告诉我 FDF 已正确创建,因此数据、字段名称等正确且有效。

我想我可能仍然处于无效的轨道上。我不需要需要和FDF文件,我只需要CSV文件中的数据进入模板表单并保存。 CSV 有多行,每行有 1 个客户数据。在阅读 pypdf 文档时,我发现 FDF 步骤是浪费的,而且很可能有问题,而且 FDF 形式与 python 字典不是同一实体。

所以,我来这里寻求意见和帮助。解析 CSV 并注入模板表单的最有效或至少最有效的方法是什么?

我应该补充一点,模板表单主要包含文本表单字段,但也有一些单选按钮组。据我所知,FDF 仅以 /V( 前面的文本字段的值和 /V/ 前面的单选按钮值来不同地处理这些。我还没有修复第 75:77 行,因为我想得到首先从这里反馈。

我确实阅读了这篇文章和很多其他文章,但我不想创建整个PDF,我需要使用带有必须填充的字段的模板表单。

python-3.x csv pdf adobe fdf
1个回答
0
投票

这可能是打开 CSV、迭代每一行并使用 PyPDF 填充新 PDF 的相当简单的操作...但它可能会出现某种问题...因为 PDF。

根据关于填写表单的最新 PyPDF 文档,我制作了一个脚本,它将读取输入 CSV,如下所示:

| First_name | Last_name | Home_address   | County     | Birthday  |
|------------|-----------|----------------|------------|-----------|
| Foo        | Bar       | 123 Main St    | Multnomah  | 1/1/2000  |
| Baz        | Baker     | 456 Second Ave | Washington | 6/21/1999 |

然后迭代每一行并调用一个函数来打开原始 PDF,复制它(基于 First_name)并填写。我使用 csv 模块的 DictReader 因为我可以将 dict 行直接传递给 update_page_form_field_values 方法:

import csv
from pypdf import PdfReader, PdfWriter


def new_pdf_from(row: dict[str, str]):
    reader = PdfReader("form.pdf")
    writer = PdfWriter()
    writer.append(reader)

    writer.update_page_form_field_values(
        writer.pages[0],
        row,
        auto_regenerate=False,
    )

    with open(f"output-{row['First_name']}.pdf", "wb") as output_stream:
        writer.write(output_stream)


with open("data.csv", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        new_pdf_from(row)

form.pdf 是我直接在 Acrobat 中创建的一个非常简单的表单。即便如此,原始版本还是有一些我在 Acrobat 中无法看到或修复的问题,但却导致 PyPDF 出错:

Traceback (most recent call last):
  File "main.py", line 9, in <module>
    writer.update_page_form_field_values(
  File ".venv/lib/python3.12/site-packages/pypdf/_writer.py", line 955, in update_page_form_field_values
    value if value in k[AA.AP]["/N"] else "/Off"
                      ~^^^^^^^
  File ".venv/lib/python3.12/site-packages/pypdf/generic/_data_structures.py", line 319, in __getitem__
    return dict.__getitem__(self, key).get_object()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: '/AP'

我可以看到姓氏字段看起来与其他文本字段不同:

reader = PdfReader("form.pdf")
print(reader.get_fields())
...
{
    "First_name": {
        "/T": "First_name",
        "/FT": "/Tx",
    },
    "Last_name": {
        "/T": "Last_name",
        "/FT": "/Tx",
        "/Ff": 0,
        "/Kids": [IndirectObject(10, 0, 4353225744)],
    ...

所以我只是删除了 Acrobat 中的字段并重新创建它,然后它就起作用了。

© www.soinside.com 2019 - 2024. All rights reserved.