บทความทางเทคนิค

นิพจน์เส้นทาง XFA SOM ใน Delphi: ข้อมูลฟอร์มแบบไดนามิก

losLab PDF Library อ่านและเขียนโหนดข้อมูลที่ซ้ำกันในฟอร์ม XFA แบบไดนามิกผ่านนิพจน์เส้นทาง XFA 3.3 SOM: ฟังก์ชัน GetXFAFormFieldValue และ SetXFAFormFieldValue รองรับรูท $data, $record และ !data, ไวลด์การ์ดระบุรายการซ้ำ [*], ตัวเลือกโหนดลูกหลาน (descendant) และตัวเลือกโหนดลูกไวลด์การ์ด (child-wildcard), คุณสมบัติ parent, ตัวเลือกคลาส #dataGroup/#dataValue และตัวกรอง (predicates) รูปแบบ FormCalc อย่างง่าย เพื่อให้โปรแกรม Delphi อัปเดตรายการใบแจ้งหนี้ทุกบรรทัดในฟอร์มภาษีของรัฐบาลได้ในการเรียกครั้งเดียว บทความนี้เป็นเอกสารอ้างอิงสำหรับการระบุตำแหน่งข้อมูล สำหรับการย้ายชุดข้อมูลทั้งหมดระหว่างไฟล์ โปรดดูที่บทความคู่กันเกี่ยวกับการแลกเปลี่ยนข้อมูลฟอร์ม FDF, XFDF และ XFA

ปัญญานี้จะเกิดขึ้นทันทีที่ฟอร์มไม่ได้เป็นโครงสร้างแบบแบนราบอีกต่อไป แบบฟอร์มภาษีมูลค่าเพิ่มหรือการสำแดงของศุลกากรที่สร้างขึ้นเป็นฟอร์ม XFA แบบไดนามิกจะไม่มีฟิลด์ 12 ฟิลด์ที่ใช้ชื่อตั้งแต่ Total_Price_1 ถึง Total_Price_12 แต่มันจะมีซับฟอร์ม Detail เพียงหนึ่งอันที่ประกาศไว้ใน Template DOM และจะถูกสร้างขึ้นซ้ำตามความต้องการของข้อมูล ค่าเหล่านี้จะอยู่ในทรีที่สอง คือ Data DOM ภายในแพ็กเก็ตชุดข้อมูล โดยแต่ละรายการแถวข้อมูลจะเป็นองค์ประกอบ <Detail> ที่เกิดขึ้นซ้ำภายใต้ <Receipt> ชื่อฟิลด์แบนๆ ไม่สามารถระบุถึง "ราคาในบรรทัดที่สาม" หรือ "ทุกบรรทัดที่มีมูลค่าเกิน 200" ได้ ซึ่งนั่นคือบทบาทหน้าที่ที่บท Scripting Object Model ของข้อกำหนด Adobe XFA 3.3 มอบให้กับนิพจน์ SOM และเป็นไวยากรณ์ที่ XFA field-value APIs ของ losLab PDF Library ใช้งาน

จะระบุตำแหน่งโหนดข้อมูล XFA จาก Delphi ได้อย่างไร?

ทุกเส้นทางชุดข้อมูลเริ่มต้นจากรูท และ losLab PDF Library ยอมรับการสะกดมาตรฐานสลับกันได้: $data เป็นรูปแบบย่อของ xfa.datasets.data ในข้อกำหนด XFA 3.3, !data เป็นรูปแบบย่อที่มีรูทอยู่ที่ xfa.datasets และในแพ็กเก็ตที่ไม่ได้ใช้การประมวลผลเรกคอร์ด $record จะดึงข้อมูลไปยังเรกคอร์ดข้อมูลชั้นนอกสุด ซึ่งเป็นองค์ประกอบแรกภายใต้โหนดข้อมูล ส่วนฝั่งเทมเพลต APIs เช่น SetXFAFormFieldAccess จะยอมรับ $template และ xfa.template ในลักษณะเดียวกัน เส้นทางย่อยอาจคั่นด้วยเครื่องหมายจุดหรือเครื่องหมายทับ (เช่น $data.Receipt.Tax หรือ $data/Receipt/Tax) ซึ่งมีความสำคัญเนื่องจาก GetXFAFormFieldNames จะแจกแจงชื่อฟิลด์เป็นเส้นทางคั่นด้วยเครื่องหมายทับซึ่งสามารถนำไปใช้ใน APIs จัดการค่าและเทมเพลตได้ทันที ดัชนีระบุรายการซ้ำจะเริ่มจากศูนย์ตามข้อกำหนด ดังนั้น Detail[0] คือแถวแรก กฎการหลีกเลี่ยง (escaping) สองข้อจะช่วยให้ตั้งชื่อพิเศษได้: ในเส้นทางที่คั่นด้วยจุด \. หมายถึงเครื่องหมายจุดปกติ (เช่น Line\.Item) ขณะที่เส้นทางที่คั่นด้วยเครื่องหมายทับจะถือว่าจุดเป็นอักขระทั่วไป ข้อมูลธุรกิจที่มีคำนำหน้าเนมสเปซ XML (XML namespace prefix) ของตัวเองจะถูกค้นหาด้วยชื่อท้องถิ่น ดังนั้น <m:Receipt> จะยังคงถูกพบด้วยคำว่า Receipt

var
  Lib: TPDFlib;
  Tax: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('vat-return.pdf', '');
    // Equivalent addresses for the same data node
    Tax := Lib.GetXFAFormFieldValue('Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('xfa.datasets.data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$record.Tax');
    // Slash form with a zero-based occurrence index
    Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
    Lib.SaveToFile('vat-return-updated.pdf');
  finally
    Lib.Free;
  end;
end;

จะกรอกแถวที่ซ้ำกันในฟอร์ม XFA ผ่านการเขียนโปรแกรมได้อย่างไร?

ไวลด์การ์ด [*] สำหรับรายการซ้ำคือเครื่องมือสำหรับการจัดการแบบชุด ในขณะที่ดัชนีตัวเลขจะเลือกเพียงโหนดเดียว [*] จะเลือกโหนดพี่น้องที่มีชื่อเดียวกันทั้งหมด ดังนั้น $data.Receipt.Detail[*].Total_Price จะระบุตำแหน่งของฟิลด์ราคาในทุกบรรทัดรายการพร้อมกัน ในการอ่าน GetXFAFormFieldValue จะส่งกลับค่าของโหนดที่ตรงกันทั้งหมดโดยเชื่อมด้วยตัวคั่น |; ในการเขียน SetXFAFormFieldValue จะอัปเดตทุกโหนดที่ตรงกันด้วยค่าเดียวกันและส่งกลับค่า 1 เมื่อสำเร็จ เส้นทางที่ไม่ตรงกับสิ่งใดเลยจะอ่านค่ากลับมาเป็นสตริงว่าง ซึ่งเป็นวิธีง่ายๆ ในการตรวจสอบว่าสาขาย่อยมีอยู่หรือไม่ก่อนที่จะเขียนข้อมูลลงไป

var
  Lib: TPDFlib;
  Prices: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('invoice.pdf', '');
    // All Detail rows at once: '250.00|60.00'
    Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
    // Reset one column across every repeated row in a single call
    Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
    Lib.SaveToFile('invoice-updated.pdf');
  finally
    Lib.Free;
  end;
end;

ตัวเลือกโหนดลูกหลาน, ตัวเลือกโหนดลูกไวลด์การ์ด และตัวเลือกโหนดแม่

ตัวเลือกโครงสร้างสามแบบจะครอบคลุมกรณีที่คุณทราบชื่อฟิลด์แต่ไม่ทราบระดับความลึกที่แน่นอน ตัวเลือกโหนดลูกหลาน .. จะจับคู่ที่ระดับความลึกใดก็ได้ใต้โหนดปัจจุบัน: $data..Total_Price จะค้นหา Total_Price แรกในทุกระดับใต้รูทข้อมูล และการเขียนผ่านเส้นทางลูกหลานจะอัปเดตโหนดแรกที่จับคู่ได้นี้ ข้อสังเกตขอบเขต: ดัชนีระบุรายการซ้ำที่อยู่หลังการจับคู่ลูกหลานจะไม่ข้ามไปยังสาขาพี่น้อง ดังนั้นหาก $data..Total_Price[0] สิ้นสุดที่แถว Detail แรก ค่าของ $data..Total_Price[1] จะส่งกลับค่าว่างแทนที่จะข้ามไปยังแถวถัดไป; ให้ใช้ Detail[*] เมื่อคุณต้องการทั้งหมด ไวลด์การ์ดของโหนดลูก .* จะจับคู่กับโหนดลูกโดยตรงทุกโหนดและปล่อยให้เซกเมนต์ที่เหลือกรองข้อมูล: $data.Receipt.*.Total_Price จะเข้าถึง Total_Price ภายในแต่ละสาขาลูกของ Receipt โดยไม่ไปเลือกฟิลด์สรุปที่มีชื่อเดียวกันซึ่งอยู่บน Receipt โดยตรง สุดท้าย คุณสมบัติ parent จะปีนขึ้นไปหนึ่งระดับ ซึ่งข้อกำหนด XFA 3.3 แยกความแตกต่างจาก .. อย่างชัดเจน: $data.Receipt.Detail[1].parent.Tax จะเริ่มที่รายการที่สอง ปีนขึ้นไปที่ Receipt และไปสิ้นสุดที่ค่า Tax ที่เป็นพี่น้องของมัน

ตัวเลือกคลาสและตัวกรอง: #dataGroup, #dataValue, .[expression]

ไวยากรณ์ #class จะระบุตำแหน่งโหนด Data DOM ตามคลาสของวัตถุแทนการระบุตามชื่อ ซึ่งเป็นแนวทางปฏิบัติที่มีประโยชน์เมื่อแท็ก XML มีอักขระที่สะกดยากในฐานะชื่อสคริปต์ losLab PDF Library จะกำหนดให้ #dataGroup สอดคล้องกับองค์ประกอบที่มีโหนดลูก และกำหนดให้ #dataValue สอดคล้องกับองค์ประกอบปลายทางที่ไม่มีโหนดลูก และทั้งสองแบบสามารถใช้งานร่วมกับดัชนีระบุรายการซ้ำแบบตัวเลขหรือ [*]: $data.Receipt.#dataGroup[0].#dataValue[0] จะเลือกค่าแรกภายในกลุ่มแรกภายใต้ Receipt ตัวเลือกตัวกรอง .[expression] จะกรองโหนดพี่น้องที่มีชื่อเดียวกันตามเนื้อหา ตัวกรองที่รองรับคือการเปรียบเทียบค่าของโหนดลูกอย่างง่ายเทียบกับค่าคงที่ ด้วยตัวดำเนินการเปรียบเทียบเช่น >, < และ <=; เมื่อตัวกรองตรงกับหลายแถว การอ่านจะส่งกลับค่าที่คั่นด้วย | และการเขียนจะอัปเดตทุกแถวที่ตรงกัน

// Flag every line whose Total_Price exceeds 200
Lib.SetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');

// Descriptions of all low-value lines, '|'-joined when several match
Desc := Lib.GetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price <= 200].Description');

// Class-based addressing when tag names resist script spelling
Total := Lib.GetXFAFormFieldValue(
  '$data.Receipt.#dataGroup[0].#dataValue[0]');

ฟีเจอร์ XFA SOM ใดบ้างที่ไม่รองรับ?

การรองรับ SOM ใน losLab PDF Library ถูกจำกัดขอบเขตไว้เฉพาะเส้นทางของฟิลด์ชุดข้อมูลและเทมเพลตเท่านั้น และมีขอบเขตที่ชัดเจนมากกว่าการพยายามทำอย่างสุดความสามารถ การรู้ขอบเขตเหล่านี้ล่วงหน้าจะช่วยประหยัดเวลาในการดีบักเส้นทางที่ส่งคืนสตริงว่างอย่างเงียบๆ

  • ตัวกรอง (Predicates) ไม่รันคำสั่ง FormCalc หรือ JavaScript ใดๆ: ไม่มีการเรียกใช้ฟังก์ชัน (ตัวกรอง contains(...) จะส่งคืนค่าว่าง), ไม่มีนิพจน์บูลีนแบบผสม, รองรับเฉพาะการเปรียบเทียบ child op literal เดี่ยวๆ เท่านั้น
  • $record ทำงานในแพ็กเก็ตที่ไม่มีการประมวลผลเรกคอร์ดเท่านั้น; ไม่มีการนำการแบ่งหน้าเรกคอร์ดของ dataWindow และการหมุนเวียนกลุ่มเรกคอร์ดมาใช้งาน
  • การดึงข้อมูลจะกระทำกับ Data DOM และ Template DOM โดยตรง; จะไม่มีการวิเคราะห์เชิงเปรียบเทียบของ Form DOM หรือมุมมองรวมที่โปรแกรมอ่านสร้างขึ้นในเวลาทำงาน (runtime)
  • จะไม่มีการนำการโหลดแอตทริบิวต์ของ Data DOM มาใช้งาน; ไลบรารีจะอ่านแพ็กเก็ตชุดข้อมูลในฐานะ XML ดิบ ดังนั้นเส้นทางจะระบุตำแหน่งไปยังองค์ประกอบต่างๆ ไม่ใช่แอตทริบิวต์ที่ถูกยกฐานะให้เป็นโหนด

ข้อจำกัดเหล่านี้ไม่ค่อยส่งผลกระทบต่องานกรอกข้อมูลแบบกลุ่ม เนื่องจากโปรแกรมกรอกข้อมูลจะระบุข้อมูลตามโครงสร้างมากกว่าที่จะใช้ตรรกะของสคริปต์ เมื่อจำเป็นต้องใช้จริงๆ ทางออกคือการทำงานในระดับแพ็กเก็ต: ให้อ่าน XML ชุดข้อมูลทั้งหมด ดำเนินการแปลงใน Delphi แล้วเขียนข้อมูลกลับลงไป

การเขียนแพ็กเก็ตทั้งหมดด้วย SetXFAFromString

SetXFAFromString คือจุดเข้าใช้งานในระดับแพ็กเก็ต: โดยจะติดตั้งสตริง XDP ที่สมบูรณ์ ทั้งเทมเพลตและชุดข้อมูลพร้อมกัน พร้อมสร้างคอนเทนเนอร์ AcroForm ในเอกสารใหม่เมื่อไม่มีคอนเทนเนอร์อยู่ และยอมรับแพ็กเก็ตที่เข้ารหัส UTF-16 ที่มีเครื่องหมายบอกลำดับไบต์ (byte-order marks) ตลอดจน UTF-8 รูปแบบการใช้งานทั่วไปคือการเก็บรักษา XDP ที่ออกโดยหน่วยงานไว้เป็นหลัก, โหลดมันด้วย SetXFAFromString จากนั้นจึงเรียกใช้ SetXFAFormFieldValue ที่ระบุตำแหน่งด้วย SOM สำหรับค่าในใบแจ้งหนี้ที่เปลี่ยนแปลงได้ก่อนบันทึก เนื่องจาก XFA เป็นหนึ่งในโมเดลฟอร์มสองแบบที่ PDF สามารถเก็บได้ ฝั่ง AcroForm แบบคลาสสิกจึงมีระบบอัตโนมัติของตัวเอง ซึ่งครอบคลุมไว้ในบทความเกี่ยวกับการทำงานของฟอร์มแบบโต้ตอบและ JavaScript

XFA field-value, enumeration และ APIs จัดการแพ็กเก็ตที่แสดงไว้ที่นี่มีจัดส่งมาใน losLab PDF Library สำหรับ Delphi, C# และ VB.NET; โดยในหน้าผลิตภัณฑ์จะมีข้อมูลอ้างอิงการจัดการฟอร์มฉบับเต็มไว้บริการ