You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需商业软件,如何提取PDF中缺失的Adobe XFA表单域?

提取缺失的Adobe XFA表单域(非商业软件方案)

问题背景

从部分PDF文件中提取/PageItemUIDToLocationDataMap时,部分Adobe XFA表单域会缺失,仅能显示第1页和第3页中带黑点标记的域。需要找到无需商业软件的提取方案。

原提取代码(存在缺失问题)

以下代码用于提取input.pdf的数据映射,保存到CSV并在output.pdf中标记点。禁用sort_and_filter可查看CSV中的原始数据:

import pikepdf
import fitz  # PyMuPDF
import csv

INPUT_PDF = "input.pdf"
OUTPUT_CSV = "points.csv"
OUTPUT_PDF = "output.pdf"
TARGET_KEY = "/PageItemUIDToLocationDataMap"

def extract_datamap_points(pdf_path, target_key=TARGET_KEY):
    out_rows = []
    with pikepdf.open(pdf_path) as pdf:
        for i, page in enumerate(pdf.pages):
            piece_info = page.get('/PieceInfo', None)
            if piece_info and '/InDesign' in piece_info:
                indesign = piece_info['/InDesign']
                if target_key in indesign:
                    for k, v in indesign[target_key].items():
                        try:
                            id_ = int(str(k).lstrip('/'))
                            type_val = float(v[2])
                            coords = [float(val) for val in list(v)[3:7]]
                            out_rows.append([i+1, id_, type_val] + coords)
                        except Exception as e:
                            print(f"解析 {k}:{v} 出错 ({e})")
    return out_rows

def get_pdf_page_count(pdf_path):
    with pikepdf.open(pdf_path) as pdf:
        return len(pdf.pages)

def process_rows(rows, max_pdf_pages):
    Y_TRANSFORM_BASE = 420.945  # 用于Y坐标转换的本地常量

    # 数据映射按顺序读取,此处为页面映射的临时处理逻辑
    total_pages = get_pdf_page_count(INPUT_PDF)
    hack_page = lambda page: 2 if (page >= max_pdf_pages) else (page + 1 if page > 1 else page)
    processed_rows = []
    for row in rows:
        page, id_, type_val, x1, y1, x2, y2 = row
        hacked_page = hack_page(page)
        new_y1 = round(Y_TRANSFORM_BASE - y1, 3)
        new_y2 = round(Y_TRANSFORM_BASE - y2, 3)
        new_x1 = round(x1, 3)
        new_x2 = round(x2, 3)
        h = round(abs(new_y2 - new_y1), 1)
        processed_rows.append([hacked_page, id_, type_val, new_x1, new_y1, new_x2, new_y2, h])
    return processed_rows

def sort_and_filter(rows):
    # 按页码升序、-y2降序、x1升序、ID升序排序
    rows_sorted = sorted(rows, key=lambda r: (r[0], -r[6], r[3], r[1]))
    # 过滤行
    filtered = []
    for row in rows_sorted:
        if (row[2] == 4                 # 类型
            and row[7] == 17):          # 高度
            filtered.append(row)
    return filtered

def write_csv(csv_filename, rows):
    with open(csv_filename, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(['page', 'id', 'type', 'x1', 'y1', 'x2', 'y2', 'h'])
        writer.writerows(rows)

def mark_points_on_pdf(input_pdf, output_pdf, rows):
    doc = fitz.open(input_pdf)
    for row in rows:
        page_num = int(row[0])
        cx = row[3]
        cy = row[6]
        page = doc[page_num - 1]
        pymupdf_y = page.rect.height - cy
        page.draw_circle((cx, pymupdf_y), radius=2, color=(0, 0, 0), fill=(0, 0, 0))
    doc.save(output_pdf)

if __name__ == "__main__":
    points = extract_datamap_points(INPUT_PDF)
    processed_points = process_rows(points, total_pages)
    filtered_points = sort_and_filter(processed_points)
    write_csv(OUTPUT_CSV, filtered_points)
    mark_points_on_pdf(INPUT_PDF, OUTPUT_PDF, filtered_points)
    print(f"完成。标记点数量:{len(filtered_points)};已生成 {OUTPUT_CSV} 和 {OUTPUT_PDF}")

方案1:解压PDF页面流(PDFtk)

使用PDFtk解压PDF页面流,可直接在文本编辑器中查看原始内容,从中定位XFA表单域:

pdftk doc.pdf output doc.unc.pdf uncompress

解压后得到input_uncompressed.pdf,可直接查看其中的XFA相关数据。


方案2:提取文本块并标记(PyMuPDF)

以下代码可提取PDF中的所有文本块,并在output_bb.pdf中标记位置,覆盖更多原本缺失的表单域:

import fitz  # PyMuPDF

INPUT_PDF = "input.pdf"
OUTPUT_PDF = "output_bb.pdf"

doc = fitz.open(INPUT_PDF)

for page_num in range(len(doc)):
    page = doc[page_num]
    blocks = page.get_text("blocks")
    for block in blocks:
        x0, y0, x1, y1, text, block_no = block[:6]
        # 标记文本块左下角(x0, y1)
        cx, cy = x0, y1
        shape = page.new_shape()
        # 绘制蓝色实心圆(RGB:(0, 0, 1)),半径2
        shape.draw_circle((cx, cy), 2)
        shape.finish(color=(0, 0, 1), fill=(0, 0, 1))
        shape.commit()
        # 添加块编号标签(无需可移除)
        page.insert_text((cx + 5, cy - 5), str(block_no), fontname="helv", fontsize=8, color=(0, 0, 1))
        
doc.save(OUTPUT_PDF)
print(f"完成。已保存 {OUTPUT_PDF}")

生成的PDF中,蓝色标记点对应所有文本块位置,可从中识别出原本缺失的XFA表单域。


内容的提问来源于stack exchange,提问作者flywire

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 13:44:57