מערכת דיווח עורפית ב-Delphi שפלטה .xlsx במשך שנים מקבלת דרישה חדשה: כללי הרכש של לקוח מהמגזר הציבורי מחייבים פלט OpenDocument Spreadsheet, והאנליסטים באותו חשבון שולחים בחזרה את עריכותיהם כקובצי .ods שנשמרו מ-LibreOffice. אז כעת אותו קוד חייב לכתוב ODS ולקרוא אותו. HotXLS, ספריית הגיליונות הילידית של losLab ב-Object Pascal עבור Delphi ו-C++Builder, מטפלת בשני הכיוונים ללא Excel או LibreOffice מותקנים בשום מקום. מה שהיא אינה עושה הוא להפוך את שני הכיוונים לסימטריים. הייצוא נושא הרבה יותר ממה שהייבוא משחזר, וצוות שמניח אחרת יצפה בנוסחאות ובעיצוב מתאדים אי-שם בין הגרסה של הלקוח לדוח הבא, בלי שגיאה להצביע עליה
תמיכת ODS חיה בחזית ה-XLSX, לא ב-XLS
HotXLS משלחת שתי היררכיות מחלקות עצמאיות בחבילה אחת: TXLSWorkbook ביחידה lxHandle לקובצי BIFF8 בינאריים של .xls, ו-TXLSXWorkbook ביחידה lxHandleX לחבילות OOXML של .xlsx. כל נקודת כניסה של OpenDocument - OpenODS, SaveAsODS, GetODSSheetNames - תלויה ב-TXLSXWorkbook. המיקום אינו שרירותי. חבילת ODS, כפי שהוגדרה ב-OASIS ODF 1.3, היא ארכיון zip הנושא חבר mimetype, מניפסט וגוף content.xml, מה שהופך אותה לבת-דודה מבנית של ה-zip של OOXML; ל-BIFF8, זרם רשומות בינארי משנות ה-90, אין דבר במשותף
למיקום הזה יש קצה מעשי: חוברת עבודה מורשתית של .xls אינה יכולה להפוך ל-.ods בקריאה אחת. אתם מגשרים תחילה את תוכן ה-BIFF אל מודל ה-XLSX, עם SaveXLSWorkbookAsXLSX מהיחידה lxXlsxExport, פותחים מחדש את התוצאה דרך TXLSXWorkbook, ואז מייצאים משם. הגשר אינו חסר-אובדן, וכדאי להכיר את הפערים לפני שבונים עליו. הוא מעתיק ערכים, נוסחאות, תבניות מספרים, גופנים, מילויים ורוחבי עמודות. הוא מוותר על גבולות, טווחים ממוזגים, הערות, תרשימים ועיצוב מותנה. מקור .xls עם עיצוב כבד יגיע ל-ODS פשוט יותר ממה שיצא, וזו תכונה של הגשר, לא של כותב ה-ODS
הזיהוי בצד הייבוא אוטומטי. שיטת ה-Open הפשוטה מזהה חבילת ODS לפי חבר ה-mimetype שלה, ונופלת חזרה לבדיקת content.xml ברמה העליונה כשהחבר הזה נעדר, ולכן מסלול קוד גנרי של "פתח כל מה שהמשתמש העלה" אינו צריך הרחת-סיומת משלו. לאחר הפתיחה, המאפיין SourceFormat מדווח איזה ענף נורה
ייצוא ל-ODS עם TODSExportOptions
קריאת הייצוא עצמה היא שורה אחת; אובייקט האפשרויות סביבה נושא את ההחלטות שמבקר ישאל עליהן בהמשך:
var
Book: TXLSXWorkbook;
Opts: TODSExportOptions;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
Opts := TODSExportOptions.Create; // הקורא מחזיק ומשחרר זאת
try
Opts.Generator := 'ReportService 4.2'; // עקיפת meta:generator
Opts.IncludeCharts := True;
Opts.IncludeImages := True;
Book.SaveAsODS('quarterly-report.ods', Opts);
finally
Opts.Free;
end;
finally
Book.Free;
end;
end;
אובייקט האפשרויות בבעלות הקורא. HotXLS לא תשחרר אותו, ולכן ה-try..finally הפנימי קיים ואינו אופציונלי. שני המאפיינים שמשנים את הפלט, ולא רק מתייגים אותו, ראויים למבט קרוב יותר. הגדרת IncludeCharts := False עושה יותר מהסתרת תרשימים: היא מפשיטה את תת-מסמכי התרשים ואת רשומות המניפסט שלהם מהחבילה, וזה בדיוק מה שאתם רוצים כשהצרכן הוא צינור נתונים שהיה נכשל עליהם. Generator עוקף את מחרוזת ה-meta:generator של ODF, שאחרת קוראת HotXLS/<version>; עקפו אותה כשכלים במורד הזרם מטביעים טביעת-אצבע על יצרני קבצים כדי לנתב תמיכה. אם דבר מכל זה אינו חל, דלגו על אובייקט האפשרויות לחלוטין. קריאה ל-SaveAs(FileName, xlsxOpenDocumentSpreadsheet) זהה ל-SaveAsODS עם ברירות מחדל, והעמסות הזרם בשתיהן מאפשרות לכם לכתוב את החבילה ישירות אל תגובת HTTP ללא קובץ זמני
מה מסלול הייבוא קורא - ומה הוא מדלג עליו בכוונה
קראו את החלק הזה בעיון לפני שתבטיחו למישהו נאמנות הלוך-ושוב. ייבוא ODS ב-HotXLS הוא בכוונה מסלול קליל. הוא משמר ערכי תאים סקלריים ואת התוצאה במטמון שכל נוסחה נשאה בזמן השמירה, והוא מרחיב שורות ועמודות חוזרות אל תוך הרשת. הוא אינו מביא סגנונות, ביטויי נוסחה של ODS או ציורים
בחירת הנוסחה היא זו שסביר ביותר שתנשך, והיא נעשתה בכוונה. תא ODF מאחסן שני דברים זה לצד זה: ביטוי הנוסחה, הכתוב בניב ה-OpenFormula המוגדר ב-ODF 1.3 חלק 4, והערך האחרון שהיישום המפיק חישב עבורו. תרגום OpenFormula לתחביר נוסחה של Excel הוא בעיית המרת-ניב משלו, עם מקרי קצה אמיתיים סביב אוצרות מילות פונקציה, תחביר הפניות ומודלי שגיאה. קריאת הערך שבמטמון במקום עוקפת את כל מחלקת התרגום-השגוי-השקט הזו, ולכן המספרים שאתם מייבאים הם בדיוק המספרים שהשולח ראה לאחרונה. המחיר הוא שהם מגיעים כמספרים, ולא כנוסחאות החיות שהפיקו אותם
מצב הכשל שיש לתכנן סביבו נובע ישירות: גיליון שסיכומיו היו נכונים כש-LibreOffice שמר אותו לאחרונה מיובא עם מספרים נכונים, אך מספרים אלו הם כעת קבועים. ערכו תא קלט, חשבו מחדש, ושום דבר אינו זז - הנוסחה נעלמה, רק תוצאתה הסופית נותרה. אם זרימת העבודה זקוקה לנוסחאות חיות לאחר הייבוא, בססו אותן מחדש תכנותית מתוך כללי העסק שלכם דרך Cell.Formula, שבחזית ה-XLSX נוטל את הביטוי ללא סימן שוויון מקדים
תכנון סביב ההלוך-ושוב הא-סימטרי
הייצוא מרנדר ממודל חוברת העבודה המלא שבזיכרון: ערכים, סגנונות, ואם תבקשו אותם, תרשימים ותמונות. הייבוא מחזיר ערכים בלבד. כך שרגל ה-.xlsx אל ה-.ods היא בנאמנות גבוהה, ורגל ה-.ods אל ה-.xlsx מחזירה ערכים ותוצאות מטמון אך ללא עיצוב וללא נוסחאות חיות. שרשרו את השניים והא-סימטריות מצטברת. מחזור מלא של .xlsx אל .ods אל .xlsx כותב הכל בנאמנות בדרך החוצה ומאבד את הסגנונות והנוסחאות בדרך חזרה פנימה, גם אם דבר לא השתבש בשום שלב
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // פורמט מזוהה אוטומטית
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// ערכים ותוצאות נוסחה במטמון נוכחים לאחר ייבוא
// ODS; סגנונות ונוסחאות חיות אינם. בנו מחדש את כל מה
// שהצינור במורד הזרם תלוי בו לפני השמירה.
Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
Book.SaveAs('vendor-revision.xlsx');
end;
finally
Book.Free;
end;
הדפוס הארכיטקטוני שנושר מכך: התייחסו לקובצי .ods נכנסים כהזנות נתונים, לא כמסמכים לעריכה במקום. שמרו את חוברת העבודה הקנונית ב-.xlsx, קראו ערכים מתוך גרסאות הלקוח, ופלטו ODS טרי לפי דרישה מהעותק הקנוני. האימות שייך לשני המחנות - פתחו קבצים מיוצאים ב-LibreOffice Calc, צרכן ה-ODF הייחוסי, וב-Excel, שקרא ODS במשך שנים אך חולק על LibreOffice בקצוות תמיכת התרשימים והסגנונות. מספר גיליונות, חופן תאים מרכזיים ונוכחות תרשימים מהווים בדיקת עשן מספקת לכל פרופיל ייצוא
מיון ODS לפני התחייבות לייבוא
כשנקודת קצה מקבלת העלאות, רשימת שמות הגיליונות זולה בהרבה מניתוח מלא ותופסת הפתעות מבניות מוקדם:
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
raise Exception.Create('not a readable ODS package');
if Names.IndexOf('Data') < 0 then
raise Exception.Create('revision is missing the Data sheet');
finally
Book.Free;
Names.Free;
end;
מוסכמת ההחזרה מכשילה אנשים: קריאות HotXLS בדרך כלל מחזירות ספירה חיובית או 1 בהצלחה ו-1- בכישלון, ומנקות את הרשימה כשהן נכשלות, ולכן בדקו <= 0 במקום להשוות מול ערך חיובי ספציפי אחד. GetODSSheetNames אינו מאפס ואינו מאכלס את מופע חוברת העבודה, ולכן אובייקט בדיקה יחיד יכול לבדוק תיקייה שלמה של קבצים נכנסים. בדיקות מבניות כאלו תופסות את כשל-המציאות הנפוץ ביותר - אנליסט ששינה שם או מחק גיליון לפני שליחת הגרסה בחזרה - בשער, היכן שהודעת השגיאה עדיין יכולה לנקוב בקובץ ובגיליון החסר במקום לצוף כהפניית nil שלוש שכבות עמוק יותר
אם אתם בונים צינור המרה רחב יותר סביב זה, דפוס ספסל-העבודה לביקורת והמרת חוברות עבודה מראה כיצד למפות את תכונות הקובץ לפני בחירת פורמט יעד, ומדריך ביצועי חוברת העבודה הגדולה שומר ייצואי אצווה בתוך גבולות זיכרון שפויים
HotXLS היא ספריית גיליונות ילידית ל-Delphi ול-C++Builder עם קוד מקור מלא; רשימת התכונות המלאה ופרטי הרישוי נמצאים בעמוד המוצר של HotXLS Component