2025, Oct 17 14:32
Adobe XFA PDFs में गायब फ़ील्ड निर्देशांक कैसे निकालें
Adobe XFA PDF में छूटे फ़ील्ड पकड़ें: PyMuPDF से सफेद-भरे आयत पहचानें, निर्देशांक निकालें, CSV लिखें और पेज मार्क करें; pikepdf आधारित खुला स्रोत समाधान.
Adobe XFA PDFs से फॉर्म फ़ील्ड के निर्देशांक निकालना अक्सर सीधा-सा लगता है, जब तक कि कुछ फ़ील्ड आपके डेटा में दिखाई ही न दें। आम तौर पर /PieceInfo के तहत /PageItemUIDToLocationDataMap पढ़ा जाता है, लेकिन कुछ फ़ाइलों में केवल कुछ ही फ़ील्ड मिलते हैं। ऐसे में काम यह है कि किसी व्यावसायिक टूल पर निर्भर हुए बिना गायब निर्देशांक वापस हासिल किए जाएँ।
समस्या की रूपरेखा
नीचे दिया गया तरीका पेज-दर-पेज चलता है, /PieceInfo[/InDesign] से /PageItemUIDToLocationDataMap निकालता है, नतीजों को CSV में लिखता है और PDF पर निशान लगाता है। यह समस्या को उजागर करता है: कुछ पेजों पर मैप उन फ़ील्ड्स को छोड़ देता है जो दस्तावेज़ को रीडर में खोलने पर साफ़ दिखाई देते हैं।
import pikepdf
import fitz  # PyMuPDF
import csv
SRC_PDF = "input.pdf"
CSV_OUT = "points.csv"
PDF_OUT = "output.pdf"
MAP_KEY = "/PageItemUIDToLocationDataMap"
def pull_datamap_points(pdf_file, target_key=MAP_KEY):
    rows_out = []
    with pikepdf.open(pdf_file) as pdf:
        for idx, pg in enumerate(pdf.pages):
            piece_meta = pg.get('/PieceInfo', None)
            if piece_meta and '/InDesign' in piece_meta:
                idn = piece_meta['/InDesign']
                if target_key in idn:
                    for key, val in idn[target_key].items():
                        try:
                            uid = int(str(key).lstrip('/'))
                            kind_val = float(val[2])
                            crds = [float(n) for n in list(val)[3:7]]
                            rows_out.append([idx + 1, uid, kind_val] + crds)
                        except Exception as err:
                            print(f"Error parsing {key}:{val} ({err})")
    return rows_out
def count_pages(pdf_file):
    with pikepdf.open(pdf_file) as pdf:
        return len(pdf.pages)
def normalize_rows(data_rows, max_pages):
    Y_BASE = 420.945  # y-निर्देशांक रूपांतरण के लिए स्थानीय स्थिरांक
    pg_total = count_pages(SRC_PDF)
    map_page = lambda p: 2 if (p >= max_pages) else (p + 1 if p > 1 else p)
    norm_rows = []
    for rec in data_rows:
        pg, uid, kind, x0, y0, x1_, y1_ = rec
        pg_fix = map_page(pg)
        y0n = round(Y_BASE - y0, 3)
        y1n = round(Y_BASE - y1_, 3)
        x0n = round(x0, 3)
        x1n = round(x1_, 3)
        height = round(abs(y1n - y0n), 1)
        norm_rows.append([pg_fix, uid, kind, x0n, y0n, x1n, y1n, height])
    return norm_rows
def order_and_pick(data_rows):
    rows_sorted = sorted(data_rows, key=lambda r: (r[0], -r[6], r[3], r[1]))
    picked = []
    for rec in rows_sorted:
        if (rec[2] == 4 and rec[7] == 17):
            picked.append(rec)
    return picked
def export_csv(csv_path, data_rows):
    with open(csv_path, 'w', newline='', encoding='utf-8') as f:
        w = csv.writer(f)
        w.writerow(['page', 'id', 'type', 'x1', 'y1', 'x2', 'y2', 'h'])
        w.writerows(data_rows)
def paint_points(src_pdf, out_pdf, data_rows):
    doc = fitz.open(src_pdf)
    for rec in data_rows:
        pno = int(rec[0])
        cx = rec[3]
        cy = rec[6]
        page = doc[pno - 1]
        y_mupdf = page.rect.height - cy
        page.draw_circle((cx, y_mupdf), radius=2, color=(0, 0, 0), fill=(0, 0, 0))
    doc.save(out_pdf)
if __name__ == "__main__":
    raw_points = pull_datamap_points(SRC_PDF)
    pg_count = count_pages(SRC_PDF)
    mapped_points = normalize_rows(raw_points, pg_count)
    final_points = order_and_pick(mapped_points)
    export_csv(CSV_OUT, final_points)
    paint_points(SRC_PDF, PDF_OUT, final_points)
    print(f"Done. Points: {len(final_points)}; Wrote {CSV_OUT} and {PDF_OUT}")
अगर आप पेज पर मिल रही हर चीज़—किसी भी फ़िल्टर से पहले—देखना चाहते हैं, तो type/height फ़िल्टर चरण छोड़ दें और सभी पंक्तियाँ CSV में एक्सपोर्ट करें।
असल में क्या हो रहा है
/PageItemUIDToLocationDataMap उपयोगी है, लेकिन ऐसे दस्तावेज़ों में हर पेज के हर दृश्य इनपुट एरिया को शामिल करने की गारंटी नहीं देता। दिए गए उदाहरण में, कुछ पेज रीडर में स्पष्ट रूप से भरने योग्य हैं, फिर भी केवल उसी मैप पर निर्भर रहने से अपेक्षित सभी प्रविष्टियाँ नहीं मिलतीं। वहीं, वे गायब फ़ील्ड पेज पर आकृतियों के रूप में दिखाई देते हैं—जो उन्हें ढूँढ़ने के लिए एक अलग, भरोसेमंद रास्ता खोलता है।
उद्देश्य को “मैप से हर XFA फ़ील्ड पढ़ो” से बदलकर “पेज पर मौजूद सफेद इनपुट बॉक्स खोजो” करने पर यह चूक नहीं होती। इन्हें पेज की ड्रॉइंग निर्देशों से सीधे पहचान सकते हैं—सफेद रंग से भरे आयतों को ढूँढ़कर।
समाधान: पेज ड्रॉइंग से सफेद बॉक्स पहचानें
पेज की ड्रॉइंग्स को स्कैन करके और सफेद-भरे आयतों पर फ़िल्टर लगाकर, गायब निर्देशांक वापस मिल जाते हैं—वह भी बिना स्वामित्व वाले टूल के। नीचे दिया कोड आयतों के निर्देशांक CSV में लिखता है और पेज पर हर बॉक्स पर एक छोटा-सा गोला बनाकर चिन्हित करता है।
import fitz  # PyMuPDF
import csv
SRC_DOC = "input.pdf"
MARKED_PDF = "output.pdf"
BOXES_CSV = "output.csv"
def is_color(color, target=(1, 1, 1)):
    return color == target
pdf = fitz.open(SRC_DOC)
# ज़ीरो-आधारित पेज इंडेक्स; आवश्यकता अनुसार समायोजित करें, या सभी पेज प्रोसेस करने के लिए range(len(pdf)) का उपयोग करें
pages_idx = [1]
with open(BOXES_CSV, mode="w", newline="", encoding="utf-8") as fh:
    w = csv.writer(fh)
    w.writerow(["page_num", "x0", "y0", "x1", "y1"])
    for idx in pages_idx:
        pg = pdf[idx]
        vector_ops = pg.get_drawings()
        canvas = pg.new_shape()
        for op in vector_ops:
            rc = op.get("rect")
            fc = op.get("fill")
            if rc and is_color(fc, target=(1, 1, 1)):
                x0, y0, x1, y1 = rc
                cx, cy = x0, y1
                canvas.draw_circle((cx, cy), 2)
                w.writerow([idx, x0, y0, x1, y1])
        canvas.finish(color=(0, 0, 1), fill=None)
        canvas.commit()
pdf.save(MARKED_PDF)
pdf.close()
यह तरीका प्रभावी है, क्योंकि जिन हिस्सों की तलाश है वे पेज कंटेंट स्ट्रीम में सफेद आयतों के रूप में मौजूद होते हैं। ड्रॉइंग ऑपरेशंस इकट्ठे करके और fill color (1, 1, 1) की जाँच करके, उन क्षेत्रों के निर्देशांक आगे की ऑटोमेशन के लिए उपलब्ध हो जाते हैं।
यह समझना क्यों महत्वपूर्ण है
जटिल PDFs में फ़ील्ड खोजने के कई रास्ते हो सकते हैं। अगर कोई मेटाडेटा-आधारित तरीका अधूरे नतीजे देता है, तो सीधे पेज की ड्रॉइंग निर्देशों का विश्लेषण एक व्यावहारिक बैकअप बन जाता है। जैसे ही बॉन्डिंग बॉक्स मिल जाते हैं, आप अपने फ़ॉर्म फ़ील्ड रख सकते हैं और डेटा एंट्री को स्वचालित कर सकते हैं—और यह सब खुले स्रोत के टूल्स के भीतर रहकर।
मुख्य निष्कर्ष
जब /PageItemUIDToLocationDataMap सभी फ़ील्ड सूचीबद्ध न करे, तो काम को XFA मैप्स खंगालने की बजाय दृश्य इनपुट बॉक्स ढूँढ़ने के रूप में लें। PyMuPDF से ड्रॉइंग प्रिमिटिव निकालें, सफेद-भरे आयतों को फ़िल्टर करें और उनके निर्देशांक रिकॉर्ड करें। शुरुआती जाँच के दौरान CSV एक्सपोर्ट को बिना फ़िल्टर रखें, और उसके बाद ही अपने पेज लेआउट के अनुरूप प्रकार या आकार के फ़िल्टर जोड़ें। इस संयोजन से आपको एक मज़बूत, गैर-स्वामित्व वाला पाइपलाइन मिलता है जो ज़रूरी पोज़िशंस वापस दिलाता है और ऑटो-फिलिंग की ओर बढ़ने देता है।