HotPDF מעביר ערכי list box רבי-בחירה סבב שלם דרך FDF ו-XFDF על ידי שמירה על ערך השדה כמערך מקצה לקצה. מאז גרסה 2.755.0, ExportLoadedFormToFDF, ExportLoadedInterchangeToFDF ו-ExportLoadedFormToXFDF כותבים כל אפשרות נבחרת כמחרוזת FDF משלה או כאלמנט <value> משלה של XFDF, ומתודות הייבוא המתאימות בודקות כל ערך מול אפשרויות השדה ובונות מחדש את אינדקסי הבחירה של ה-/I לפני שהן משנות משהו. שום דבר לא מודבק למחרוזת אחת בדרך
הכשל שהזה מתקן קל לשחזור. לוקחים טופס הזמנה עם list box רב-בחירה של אפשרויות מוצר, נותנים למשתמש לבחור שתיים מהן, מייצאים את נתוני הטופס עבור מערכת משרדית, ואז מייבאים את הקובץ הערוך בחזרה אל ה-PDF. לפני השינוי הזה ה-list box חזר ריק או שגוי. הסיבה היא שאחד מערכי הייצוא הכיל מעבר שורה, והנתיב הישן שטח את הבחירות למחרוזת אחת מופרדת-שורות. לחלץ בחירות מרובות בחזרה מהמחרוזת הזאת מעולם לא היה אמין, ועם ערך ייצוא שמכיל בעצמו מעבר שורה זה לא יכול לעבוד בכלל
למה חיבור ערכי רב-בחירה עם מעברי שורה שובר את ה-round trip?
חיבור הבחירות למחרוזת אחת זורק את הגבולות בין הערכים, וערך יכול להכיל את המפריד, ולכן אף מייבא לא יכול לפצל את המחרוזת חזרה נכון. ISO 32000-1 §12.7.4.4 מתיר לרשומת ה-/V של שדה בחירה להיות או מחרוזת טקסט יחידה או מערך של מחרוזות טקסט, ו-list box עם דגל MultiSelect (ביט 22 של /Ff) משתמש בצורת המערך ברגע שנבחרה יותר מאפשרות אחת. אותו סעיף מגדיר את ה-/I כמערך של אינדקסי אפשרויות מבוססי-0 בסדר עולה, שמציגים משתמשים בו כדי להבחין בין שתי אפשרויות שקרה להן לחלוק ערך export. ב-HotPDF ה-getter הסקלרי GetFormFieldValue קורא רק את צורת המחרוזת, ולכן הרצת מערך דרכו שדרגה את הייצוא למחרוזת ריקה, והייבוא הישן של XFDF חיבר אלמנטי <value> חוזרים עם LF. תארו לכם אפשרות שיוצאה כ-Deep, הזנת שורה, Blue: אחרי החיבור, Deep\nBlue\nRed יכולה להיות שתי בחירות או שלוש, והקובץ לא נותן דרך לדעת איזה. התיקון היה להפסיק להשתמש בסקלר באמצע ה-round trip לגמרי
מה קבצי ה-FDF וה-XFDF המיוצאים מכילים?
HotPDF כותב ערך רב-בחירה כמערך מוטיפוס ב-FDF וכאלמנט <value> אחד לכל בחירה ב-XFDF, כך שהגבולות נשארים גלויים על הדיסק. ב-FDF כל פריט שומר על הכתיב שהיה לו ב-PDF המקור: מחרוזות הקסדצימליות יוצאות כ-hex, ומחרוזות literal מוברחות על ידי helper יחיד שהופך CR ו-LF אל \r ו-\n. ב-XFDF השורש נושא xml:space="preserve" כפי ש-ISO 19444-1 דורש, מה שאומר שכל רווח בתוך אלמנט טקסט נספר כנתון. HotPDF לכן כותב את תג הפתיחה, את הטקסט המוברח ואת תג הסיום של כל <value> בחתיכה אחת, משאיר הזחה מחוץ לאלמנט, ומקודד CR, LF ו-TAB כהפניות תווים כך ש-parser של XML שמחיל נרמול סיומי-שורה לא יוכל לשנות את הבתים המקוריים
<!-- FDF: מערך מוטיפוס אחד לכל שדה -->
<< /T (options) /V [(Deep\nBlue) (Red)] >>
<< /T (region) /V [<45553132>] >>
<!-- XFDF: <value> אחד לכל בחירה -->
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="options">
<value>Deep
Blue</value>
<value>Red</value>
</field>
</fields>
</xfdf>
שני מקרי קצה של ייצוא שווים להכיר לפני שכותבים את קוד הקריאה. ראשית, ExportLoadedFormToFDF בונה את גוף ה-FDF המלא בזיכרון לפני שהוא יוצר את קובץ היעד (תוקן ב-2.755.1), כך שערך שלא ניתן לייצוא, כמו מערך שמחזיק משהו שאינו מחרוזות, מעלה חריגה בלי לקצץ קובץ קיים. שנית, בחירה ריקה ב-list box שמציע גם ערך export של מחרוזת ריקה עמומה ב-XFDF, כי <value/> יכול לפרוש שלא נבחר כלום או שהאפשרות הריקה נבחרה. ExportLoadedFormToXFDF מעלה חריגה במקרה כזה במקום לנחש, והוא מעלה לפני שקובץ היעד נפתח. ל-FDF אין עמימות כזאת, כי /V [] ו-/V [()] נבדלים. שני מייצאי ה-FDF גם מדלגים על קצוות widget בלבד שאין להם שם /T, בהתאמה למייצא ה-XFDF, כי אף מייבא לעולם לא יכול להתאים את הרשומות האלה בחזרה אל שדה
var
Pdf: THotPDF;
Written: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
begin
// list box רבי-בחירה נכתבים כ-/V [(...) (...)]
Written := Pdf.ExportLoadedFormToFDF('order-form.fdf');
try
Pdf.ExportLoadedFormToXFDF('order-form.xfdf');
except
on E: Exception do
// בחירה ריקה בתוספת אפשרות export ריקה: XFDF לא יכול להבחין
// ביניהם, והקובץ ה-.xfdf הקיים נשאר ללא מגע
ShowMessage('XFDF export refused: ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
end;
איך HotPDF מאמת ערך רב-בחירה בייבוא?
HotPDF מקבל מערך מיובא רק כשהיעד הוא שדה בחירה עם דגל MultiSelect מוגדר וכל ערך במערך תואם ערך export במערך ה-/Opt של השדה. כל משבצת אפשרות יכולה להיכבש פעם אחת, ולכן רשימה עם שתי אפשרויות שחולקות את ערך ה-export b מקבלת [<62> <62>] כשתי בחירות נבדלות ודוחה b שלישי. ה-/I הנבנה מחדש עוקב אחר סדר ה-/Opt ולא אחר סדר הערכים הנכנסים, כפי ש-§12.7.4.4 דורש אינדקסים עולים. HotPDF בונה את ה-/V וה-/I החדשים כאובייקטים מנותקים ומקצה אותם רק אחרי שכל ערך עבר אימות, כך שערך נדחה לעולם לא משאיר מאחור חצי מערך או אינדקסים מיושנים. העותק נכתב אל השדה שמיובא ולא אל מערך אב משותף, כתיבי hex שמגיעים מ-FDF נשארים hex דרך השמירה, ושדות שהחישובים שלהם תלויים ב-list box מסומנים לחישוב מחדש. אם צריך רק להגדיר ערך יחיד, קביעת ערך שדה טופס אחד ב-PDF טעון עוברת דרך הנתיב הסקלרי, שעל פי עיצובו אינו מטפל בבחירות מרובות
כמה כלים אחרים כותבים ערכי export של ASCII פשוט כמחרוזות hex בלי סימן סדר בתים, למשל <416272>, ואז מייצאים XFDF על ידי כתיבת הספרות ההקסדצימליות האלה כטקסט. השוואת literal קפדנית בדרך חזרה נכשלת, והייבוא נפסק. גרסה 2.755.1 מוסיפה ניסיון חוזר אחד: כשערך לא תואם אף אפשרות, HPDFHexSpellingText מפענח את הטקסט כמטען hex ומשווה את התוצאה שוב. הניסיון החוזר חל רק על קלט שאחרת היה מעלה חריגה, ולכן הוא לעולם לא משנה ערך שכבר נתאם. אותה מהדורה גם הפכה את הנתיב הסקלרי והמערכי להשתמש באותו מפענח Unicode, שמבין PDFDocEncoding, UTF-16 עם כל סימן סדר בתים ו-UTF-8. לפני זה, ערך לוגי אחד יכול היה להתאים בנתיב אחד ולהיכשל באחר במסמכים שערבבו קידודים
למה קובץ FDF תקין עדיין יכול לאבד שדות בפענוח?
סורק FDF שאינו עוקב אחר מחרוזות הקסדצימליות יכול לחתוך מילון שדה לשניים כשערך hex נגמר בדיוק ליד מסיים המילון. ב-<< /T (region) /V <416273>> ה-> הראשון סוגר את המחרוזת ההקסדצימלית, אבל סורק נאיבי קורא אותו יחד עם ה-> הבא כסוף המילון ושומט בשקט את השדה. מייבא ה-FDF ברמת הקובץ כבר עקב אחרי השאלה האם הוא בתוך מחרוזת הקסדצימלית, וב-2.755.1 הסורקים של המערך והמילון מאחורי ImportLoadedInterchangeFromFDF עושים זאת גם כן. נושא שני נוגע להפניות עקיפות. קובץ FDF הוא מסמך תחביר-PDF קטן עם מספור אובייקטים משלו (ISO 32000-1 §12.7.7), ולכן ערך כמו /V [11 0 R] מתייחס לאובייקט 11 של קובץ ה-FDF, לא לאובייקט 11 של ה-PDF שאתם ממלאים. ה-parser המפושט של FDF ב-HotPDF לא פותר הפניות בתוך הקובץ, ולכן הוא דוחה מערך כזה במקום לקרוא את אובייקט 11 שקרה להיות במסמך היעד
ייבוא קובץ, זרם ו-XFDF מדווחים על שגיאות אחרת
שלושת נתיבי הייבוא מאמתים באותה צורה אבל מדווחים על כשלים אחרת, ושווה לבחור אחד במכוון. ImportLoadedFormFromFDF מדלג על כל שדה שנכשל באימות ומחזיר את מספר השדות שכן החיל, כך שמספר קטן מהצפוי הוא הסימן היחיד לבעיה. ImportLoadedInterchangeFromFDF ו-ImportLoadedFormFromXFDF מעלים חריגה בשדה הנדחה הראשון. כל שדה נכבש בפני עצמו, ולכן שדות שעובדו לפני החריגה שומרים על הערכים החדשים שלהם. אל תתייחסו לאף אחד מהם כאל טרנזקציה על קובץ החילוף כולו: אם צריכים התנהגות של הכול-או-כלום, משליכים את המסמך הטעון כשחריגה מתרחשת במקום לשמור אותו
var
Pdf: THotPDF;
Source: TMemoryStream;
Status: AnsiString;
Info: THPDFFDFInterchangeInfo;
begin
Pdf := THotPDF.Create(nil);
Source := TMemoryStream.Create;
try
Source.LoadFromFile('order-form-reviewed.fdf');
if Pdf.LoadFromFile('order-form.pdf', '') > 0 then
try
// שדות בלבד; ערך מחוץ ל-/Opt או יעד שאינו רב-בחירה מעלה חריגה
if Pdf.ImportLoadedInterchangeFromFDF(Source, True, False, Status, Info) then
Pdf.SaveLoadedDocument('order-form-filled.pdf');
except
on E: Exception do
ShowMessage('Import rejected, nothing saved: ' + E.Message);
end;
finally
Source.Free;
Pdf.Free;
end;
end;
הרחבת ה-callbacks של XFDF בלי לשבור קוראים קיימים
התמיכה במערכים ביחידת ה-XFDF הנמוכה יותר חיה ברשומה נפרדת, THPDFXFDFArrayAccess, וב-overloads חדשים של HPDFXFDFExportFields ו-HPDFXFDFImportFields, לא בשדות נוספים שהתווספו לסוף הרשומה הקיימת THPDFXFDFAccess. הסיבה היא תאימות בינארית. קוד שממלא THPDFXFDFAccess כמשתנה מקומי לעיתים קרובות מגדיר רק את המשבצות שהוא מכיר ולעולם לא מנקה את השאר, ולכן מצביע פונקציה חדש שנוסף לרשומה הזאת יכיל זבל מחמסנית, והספרייה הייתה לוקחת אותו כ-callback אמיתי. עם רשומה נפרדת, קוראים ישנים שומרים על הפריסה הישנה ועל ה-overloads הישנים, וה-overloads האלה מעבירים רשומת מערך all-nil בפנים. overload הייבוא הסקלרי המקורי עדיין מחבר ערכים חוזרים עם LF עבור תאימות, ורק ה-overload מודע-המערך מפריד ביניהם. כשקושרים חנות נתונים משלכם, מתחילים מ-Default(THPDFXFDFArrayAccess). מחזירים True מה-GetFormFieldValueArray עבור כל שדה בעל ערך-רשימה, כולל כזה שבו לא נבחר כלום, ו-False כדי לחזור אל ה-callback הסקלרי
uses HPDFXFDF;
// מצביע פונקציה פשוט, לא "of object": ה-Context נושא את החנות שלכם
function StoreGetSelections(Context: Pointer; FieldIndex: Integer;
out Values: THPDFXFDFValueArray): Boolean;
begin
Result := TFormStore(Context).IsListField(FieldIndex);
if Result then
Values := TFormStore(Context).Selections(FieldIndex);
end;
procedure ExportStore(Store: TFormStore; out Bytes: TBytes);
var
Access: THPDFXFDFAccess;
ArrayAccess: THPDFXFDFArrayAccess;
begin
Access := MakeStoreAccess(Store); // הקשרות הסקלריות הקיימות שלכם
ArrayAccess := Default(THPDFXFDFArrayAccess); // כל משבצת לא בשימוש היא nil
ArrayAccess.GetFormFieldValueArray := StoreGetSelections;
HPDFXFDFExportFields(Access, ArrayAccess, Bytes);
end;
חילוף רב-בחירה עובד על list box-ים שכבר קיימים ויש להם את ביט ה-MultiSelect מוגדר ב-/Ff. לגבי איך שדות בחירה וביטי הדגל שלהם נוצרים מלכתחילה, ראו הוספת ListBox ושדות AcroForm אחרים ל-PDF טעון. לגבי סימון הערות שעובר דרך עץ ה-<annots> של ה-XFDF, ראו ייבוא וייצוא הערות XFDF ב-HotPDF. ההפניה המלאה ל-API וההורדה לניסיון נמצאים בעמוד רכיב HotPDF ל-PDF בדלפי