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; โดยในหน้าผลิตภัณฑ์จะมีข้อมูลอ้างอิงการจัดการฟอร์มฉบับเต็มไว้บริการ