无需商业软件,如何提取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
相关产品推荐
相关产品推荐

