מאמר טכני

רישום שמות גיליונות במהירות ב-Delphi עם HotXLS GetSheetNames

לעתים השאלה היחידה שעל שגרת קליטה לקבל עליה תשובה היא מבנית: האם לחוברת העבודה הזו יש גיליון בשם "Mapping", או כמה לשוניות היא נושאת. מענה על כך באמצעות קריאה ל-Open הוא הדרך היקרה לעשות זאת. פתיחה מלאה מנפחת את טבלת המחרוזות המשותפות, מפענחת כל רשומת סגנון, ומהלכת על תאי כל גיליון עבודה, משום שאין לה דרך לדעת שרק רציתם את תוכן העניינים. בקובץ גדול זה מאות מגה-בתים של הקצאות וכמה שניות של CPU שמושקעים בקריאת רשימה התופסת כמה קילו-בתים. HotXLS, ספריית הגיליונות הילידית של losLab ל-Delphi, נותנת לכם את הרשימה הזו בפני עצמה: GetSheetNames מחזיר את שמות גיליונות העבודה, בסדר חוברת העבודה, מבלי לממש אף תא יחיד

מדוע הקטלוג זול לקריאה

שני פורמטי הגיליונות שמים את תוכן העניינים שלהם בקדמת הקובץ, וזה מה שהופך קריאת רישום למהירה ולא לחכמה. חבילת OOXML שומרת את קטלוג הגיליונות ב-xl/workbook.xml, חלק שנשאר קטן בין אם חוברת העבודה מחזיקה עשר שורות או עשרה מיליון. קובץ BIFF8 .xls מאחסן את רשומות ה-BoundSheet שלו בתחילת זרם הגלובלים של חוברת העבודה, לפני כל נתוני תאים. כך שהעבודה שקריאת רישום נמנעת ממנה אינה שגיאת עיגול מול פתיחה מלאה. היא רוב הקובץ. קריאת הקטלוג עולה את אותו חופן קילו-בתים ללא קשר לספירת השורות, בעוד פתיחה מלאה מתרחבת עם הנתונים, ובחוברת עבודה רבת-מגה-בתים הפער הזה מגיע לכמה סדרי גודל הן בבתים שנגעו בהם והן בזיכרון שהוקצה

העלות השטוחה הזו היא התכונה ששווה לתכנן סביבה. שער קליטה הבנוי על GetSheetNames מתנהג זהה בקובץ של 200 שורות ובקובץ של 200 מגה-בתים, כך שהקובץ האיטי ביותר באצווה כבר אינו קובע את הקצב להכרעה אם קובץ בכלל שווה עיבוד

קריאה אחת על פני .xls, .xlsx ופורמטי התבנית

בחזית ה-XLS, TXLSWorkbook.GetSheetNames קורא יותר מ-.xls. הוא גם מקבל את .xlsx, .xlsm, .xltx ו-.xltm מבוססי-ה-zip, ומושך רק את workbook.xml מתוך הארכיון. עבור קלט .xls אמיתי הוא סורק רשומות BoundSheet ועוצר ברשומת ה-EOF הראשונה של תת-זרם הגלובלים, כך שקובץ בינארי גדול עדיין עולה רק את הקילו-בתים הפותחים שלו. חזית ה-XLSX נושאת ערובה שחשובה לקוד שירות ארוך-ריצה יותר מכפי שהיא נראית בתחילה: TXLSXWorkbook.GetSheetNames משאיר את מופע חוברת העבודה לא-מאופס ולא-מאוכלס, כך שמופע שכבר מחזיק מסמך פתוח יכול לבדוק קבצים אחרים מבלי להפריע לזה שביד. GetODSSheetNames מיישם את אותה גישה לחבילות OpenDocument, ולכל אחת מהקריאות האלו יש העמסת זרם, מה שמאפשר לכם לבחון העלאה שלעולם אינה נוחתת על דיסק

var
  Book: TXLSXWorkbook;
  Names: TStringList;
  I: Integer;
begin
  Names := TStringList.Create;
  Book := TXLSXWorkbook.Create;
  try
    if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
      raise Exception.Create('unreadable workbook package');
    if Names.IndexOf('Mapping') < 0 then
      raise Exception.Create('required Mapping sheet is missing');
    for I := 0 to Names.Count - 1 do
      Writeln(Format('sheet %d: %s', [I, Names[I]]));
  finally
    Book.Free;
    Names.Free;
  end;
end;

אותה קריאה מהווה דו-שיח ייבוא שולחני טוב. רשמו את הגיליונות, אפשרו למשתמש לבחור אחד, ושלמו על הפתיחה המלאה רק לאחר שהבחירה נעשתה. עם חוברת עבודה של חמישים גיליונות ההבדל נראה: בורר שמופיע מיד מול אחד שנתקע בעוד כל הקובץ נטען מאחוריו

קובצי .xlsm מאופשרי-מאקרו ופורמטי התבנית מתרשמים בדיוק כמו .xlsx פשוט, כיוון שהקטלוג יושב באותו workbook.xml בין אם vbaProject.bin נוסע יחד בחבילה ובין אם לא. צינור קליטה יכול לכן למנות את גיליונות חוברת עבודת מאקרו לצורך ניתוב, מבלי לגעת אי-פעם במטען המאקרו ומבלי לעשות אי-פעם דבר שיריץ אותו, ולהשאיר את הכרעת מדיניות המאקרו לשלב שבאמת פותח את הקובץ

קריאת ערך ההחזרה בלי להטעות את עצמכם

מוסכמות ההחזרה אינן אחידות על פני HotXLS. חלק מהקריאות מחזירות 1 בהצלחה, אחרות מחזירות ספירה, ולכן עבור פונקציות הרישום הבדיקה היחידה שמחזיקה מעמד היא להתייחס לכל ערך של אפס או נמוך ממנו ככישלון, עם רשימת המחרוזות מנוקה. עמדו בפיתוי לקרוא רשימה ריקה כ"חוברת עבודה ללא גיליונות". הן ECMA-376 והן מפרט BIFF8 דורשים לפחות גיליון אחד בחוברת עבודה תקפה, ולכן אפס שמות תמיד אומר שהקריאה נכשלה, לעולם לא שהקובץ ריק כדין

רישום שנכשל הוא בעצמו אות ששווה לשמור. קובץ .xlsx שנכשל בקריאה הוא אחד מכמה דברים ספציפיים: קטוע, לא ממש חבילת OOXML כלל (ייצואי CSV שתויגו שגוי ממערכות אחרות צצים כאן ללא הרף), או מכל מוצפן. ההבחנה ביניהם היא תפקיד הבדיקה הבאה. רישום הבתים הראשונים של הקובץ שנדחה לצד הכישלון בדרך כלל הופך שרשור תמיכה להודעה יחידה

זיהוי מכלים מוצפנים לפני שאתם מנתבים

קובץ .xlsx מוצפן אינו zip. הוא קובץ מורכב OLE העוטף זרמי EncryptionInfo ו-EncryptedPackage, ולכן GetSheetNames אינו יכול לראות לתוכו ומחזיר כישלון כמו כל קובץ אחר שאינו ניתן לקריאה. CanReadEncrypted בודק את צורת המכל הזו, מה שמאפשר לקליטה לנתב קובץ מוצפן בכוונה במקום לבלוע שגיאת קריאה גנרית ממקום עמוק כלשהו בעובד:

type
  TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);

function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // OOXML מוצפן הוא מכל OLE, לא zip: בדקו תחילה,
    // משום שקריאות הרישום אינן יכולות להביט לתוכו.
    if Book.CanReadEncrypted(FileName) then
      Exit(irNeedsPassword);
    if SameText(ExtractFileExt(FileName), '.ods') then
    begin
      if Book.GetODSSheetNames(FileName, Names) <= 0 then
        Exit(irUnreadable);
    end
    else if Book.GetSheetNames(FileName, Names) <= 0 then
      Exit(irUnreadable);
    Result := irNormal;
  finally
    Book.Free;
  end;
end;

ההצפנה היא המקום שבו HotXLS א-סימטרית בכוונה, ולכן הניתוב חייב לכבד זאת. הצפנת .xls מורשתית (RC4, RC4 CryptoAPI, XOR) ניתנת לקריאה: TXLSWorkbook.Open(FileName, Password) מפענח עם סיסמה מאוחסנת, וקבצים אלו יכולים להישאר במסלול האוטומטי. חבילות OOXML מוצפנות הולכות בכיוון ההפוך. HotXLS יכולה לכתוב אחת עם SaveAsEncrypted, אך אינה יכולה לקרוא אחת בחזרה. OpenEncrypted מעלה EXlsxEncryptionNotImplemented כשמוסרים לו חבילה מוצפנת, וזו הסיבה שתכנון קליטה כן שולח .xlsx מוצפן לאדם עם Excel ושומר את ה-.xls נושא-הסיסמה בקוד

עבור עבודת אצווה, מסווג זה מצדיק את מקומו בכך שהוא רץ על פני תיקייה נכנסת שלמה לפני שעובד כלשהו מתחיל עיבוד אמיתי, כיוון שכל בדיקה עולה בערך פתיחת קובץ אחת וכמה קילו-בתים של קריאות. הקדמתו משנה את מצב הכשל שתפעול באמת אכפת לו ממנו. במקום עבודה ב-3 לפנות בוקר שמתה על קובץ 412 מתוך 600, אתם מקבלים 412 קבצים בתור ו-5 נדחים בקליטה עם סיבה מצורפת לכל אחד. אותן קריאות ספרייה, סיפור תפעולי טוב בהרבה

השאלות שקריאת רישום אינה יכולה לענות עליהן

שמות וסדר הם כל מה שאתם מקבלים. קריאות הרישום אינן אומרות דבר על נראות, ולכן גיליונות מוסתרים ומוסתרים-מאוד מגיעים ברשימה ונראים כמו כל אחד אחר. הן אינן מדווחות על ממדי טווח-בשימוש, ספירות תאים או מאפייני מסמך. החלק docProps/core.xml קטן אף הוא, אך אין כיום בדיקת מאפיינים-בלבד, ולכן מטא-נתוני מחבר וכותרת עדיין עולים Open מלא. הדרך הנקייה לחיות עם זה היא לתת לעובדות הזולות לנתב כל קובץ ולשמור את היקרות לקבצים ששורדים את הניתוב. עבור הקבצים שכן ממשיכים לקריאה עמוקה, סריקת קריאה-בלבד של .xls גדול רצה מהר בצורה ניכרת עם _DisableGraphics := True, שמדלג על ניתוח OfficeArt. רק לעולם אל תשמרו מאותו מופע: שכבת הציור שהוא דילג עליה נעלמה מהמודל, והשמירה הייתה משמיטה אותה מהקובץ

קבצים שעוברים את המיון בדרך כלל פונים לניתוח עמוק יותר. ספסל העבודה לביקורת והמרת חוברות עבודה מכסה את מוני כל-גיליון ששווה לאסוף ברגע שפתיחה מלאה מוצדקת, ומדריך ביצועי חוברת העבודה הגדולה מכסה את שמירת אותה פתיחה מלאה מהירה

HotXLS היא ספריית גיליונות ילידית ב-Object Pascal ל-Delphi ול-C++Builder; משטח ה-API המלא, כולל קריאות הבחינה המוצגות כאן, מתועד בעמוד המוצר של HotXLS Component