מאמר טכני

הגדרת מאפייני מסמך Excel ונתוני מטא של חוברת עבודה ב-Delphi עם HotXLS

גיליון אלקטרוני נושא שתי שכבות זהות. יש את רשת התאים, ויש את נתוני המטא של המסמך הרוכבים לצידה: כותרת, מחבר, חברה, מילות מפתח, חותמות הזמן. Excel לעולם אינו מציג את השכבה השנייה הזו ברשת, אך זו השכבה ש-Windows Search מאנדקס, זו ש-SharePoint קורא כדי לכותרת מסמך, וזו שמערכת ניהול רשומות מתייקת לפיה. כאשר חוברת עבודה מחוללת יורשת את ה-Author וה-Title שלה מהתבנית שממנה נבנתה, כל מערכת במורד הזרם רושמת את מעצב התבנית כמחבר של ארבעת אלפים דוחות לקוח. נתוני המטא אינם נכונים בשום מקום ומיועצים בכל מקום

HotXLS חושפת את השכבה הזו כמאפיינים רגילים ברמת חוברת העבודה בשני המנועים שלה: מעטפת ה-BIFF ל-.xls ומעטפת ה-OOXML ל-.xlsx. אתם קוראים שדה אחרי פתיחת קובץ ואתם כותבים שדה לפני שמירתו. הספרייה מחליטה לאיזה מכל פיזי הערך נוחת. מה שכדאי להבין לפני שאתם כותבים מחולל הוא אילו שדות כל פורמט באמת תומך בהם, היכן השדות האלה חיים פיזית, וכלל הגישה היחיד השולט בשאלה אם .xlsx רושם נתוני מטא כלשהם בכלל

שני פורמטים, שני מודלי אחסון

הסיבה שספריית גיליונות אלקטרוניים זקוקה לשני מימושי נתוני מטא, והסיבה שכלים מוגמרים-למחצה מטביעים פורמט אחד נכון ושוכחים את האחר, היא ש-.xls ו-.xlsx שומרים את מאפייניהם במקומות לא קשורים. חוברת עבודה BIFF כותבת אותם לתוך זרמי קובץ-מורכב OLE, בעיקר מערך המאפיינים SummaryInformation שקדם ל-Excel עצמו, לצד רשומת ה-WRITEACCESS שבזרם המנקבת בשם מי ששמר את הקובץ לאחרונה. חוברת עבודה OOXML שומרת אותם כחלקי XML בתוך חבילת ה-zip, מפוצלים לפי מטרה: docProps/core.xml מחזיק את שדות Dublin Core (כותרת, יוצר, נושא, מילות מפתח, תאריכים) ו-docProps/app.xml מחזיק את השדות ברמת היישום כגון חברה ויישום מחולל, לפי ECMA-376 חלק 1

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

הטבעת חוברות עבודה מחוללות מתוך הרשומה העסקית

בצד ה-XLSX, TXLSXWorkbook חושפת את Title, Subject, Author, Keywords, Description, Category, LastModifiedBy, Company, Application ו-AppVersion כמחרוזות, בתוספת Created ו-Modified כערכי TDateTime שבהם אפס פירושו לא-מוגדר. הכלל שסוגר את חור הירושה הוא משפט אחד: הקצו כל שדה בכל ריצה, תוך לקיחת הערכים מהרשומה העסקית במקום לסמוך על מה שהתבנית במקרה נשאה

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('statement-template.xlsx') <> 1 then
      raise Exception.Create('Template not available');

    // Overwrite every field: anything left untouched is
    // inherited from whoever designed the template.
    Book.Title := 'Account Statement 2026-06 / ACME Corp';
    Book.Subject := 'Monthly account statement';
    Book.Author := 'Billing Service 4.2';
    Book.LastModifiedBy := 'Billing Service 4.2';
    Book.Company := 'Northwind Financial';
    Book.Category := 'Customer Delivery';
    Book.Keywords := 'statement;billing;2026-06;acct-10024';
    Book.Description := 'Generated document - manual edits are not retained';
    Book.Created := Now;
    Book.Modified := Now;

    Book.SaveAs('statement-10024.xlsx');
  finally
    Book.Free;
  end;
end;

שדה ה-Keywords משתלם במחשבה יותר ממה שהוא בדרך כלל מקבל. תשתית חיפוש מאנדקסת אותו מילה במילה, Windows Search, SharePoint, ורוב מוצרי ה-DMS כאחד, ולכן מוסכמה מופרדת-נקודה-פסיק הנושאת את מספר החשבון והתקופה הופכת כל חוברת עבודה שנשלחה לרשומה ניתנת-לאיתור ללא סבב מסד נתונים. אותו הישג הוא המלכוד. מאפיינים נוסעים עם כל עותק של הקובץ, הרבה מעבר לבקרות הגישה של המערכת שכתבה אותם, ולכן נתונים אישיים אינם שייכים שם

זוג חותמות הזמן נושא סמנטיקה שכדאי לקבע במדיניות ולא להשאיר להרגל. Created צריך לסמן את הרגע שבו הצינור שלכם חולל את המסמך ואז להישאר קפוא. Modified הוא השדה ש-Excel מעדכן בכל פעם שנמען שומר את הקובץ, ולכן הסטה בין השניים אחרי המסירה היא ראיה חיובית שמישהו ערך את חוברת העבודה במורד הזרם, מה שמכריע יותר ממחלוקת אחת על שאלת מי הם המספרים שגיליון שהועבר באמת מחזיק. מלכודת אחת מסתתרת במצב הלא-מוגדר: זהו הערך המילולי אפס, לא חריגה ולא null, ולכן קוד ביקורת חייב לבדוק אפס במפורש. עצבו TDateTime לא-מוגדר ללא אותה הגנה והיומנים שלכם יתמלאו בתאריך דצמבר 1899 שגוי בביטחון

DocPropsTouched: חוברת העבודה הנשלחת ללא docProps

דגל לקריאה-בלבד, DocPropsTouched, מגדר את כותב מאפייני ה-XLSX. חוברת עבודה שבה אף מאפיין לא הוקצה אי-פעם אינה מפיקה חלקי docProps כלל; HotXLS נמנעת מלכתוב שלד נתוני מטא ריק. ההתנהגות מסודרת, ויש לה שתי השלכות שכדאי לתכנן סביבן

קוד קליטה בצד הצורך אסור שיניח ש-core.xml קיים בכל חבילה. כלי הדורש אותו קשיחות ידחה קבצים מינימליים תקינים לחלוטין. ואם עמדת הציות שלכם דורשת שכל מסמך יוצא יישא לפחות זהות מחולל, אותה דרישה הופכת לקוד ולא למאפיין של הפורמט: הקצו Application ו-Author ללא תנאי במסלול השמירה, מכיוון שחוברת עבודה שלא נגעו בה חוקית לחלוטין לפי המפרט בעודה מפרה בשקט את המדיניות שלכם

משטח ה-XLS הישן ומלכודת ה-Comments

מעטפת ה-BIFF נושאת את מערך השדות הישן והקטן יותר: Title, Subject, Author, Keywords, Comments, Company ו-Manager, בתוספת LastSavedBy, כינוי של UserName, שכותב את רשומת ה-WRITEACCESS ש-Excel מציג כשמשתמש אחר נעל את הקובץ

var
  Legacy: IXLSWorkbook;     // reference-counted interface: no manual Free
begin
  Legacy := TXLSWorkbook.Create;
  if Legacy.Open('archive-1999.xls') <= 0 then
    raise Exception.Create('Cannot open archive file');

  Legacy.Title := 'FY1999 ledger (migrated copy)';
  Legacy.Author := 'Archive Migration Batch';
  Legacy.Company := 'Northwind Financial';
  Legacy.Comments := 'Migrated 2026-06-11; source retained in cold storage';
  Legacy.LastSavedBy := 'migration-svc';   // BIFF WRITEACCESS record

  Legacy.SaveAs('archive-1999-stamped.xls');
end;

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

קריאת נתוני מטא בקליטה, ופער הבדיקה

הקריאה סימטרית. אחרי Open, אותם מאפיינים חוזרים מאוכלסים מהקובץ, מה שהופך ביקורת נתוני מטא של חוברות עבודה נכנסות ללולאה קצרה

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open(FileName) = 1 then
    begin
      Writeln(Format('%s | title="%s" author="%s" created=%s',
        [ExtractFileName(FileName), Book.Title, Book.Author,
         FormatDateTime('yyyy-mm-dd', Book.Created)]));
      if Book.Created = 0 then
        Writeln('  no creation date recorded');
    end;
  finally
    Book.Free;
  end;
end;

תכננו סביב מגבלה אחת תוך כדי. אין בדיקת מאפיינים-בלבד. GetSheetNames יכולה לרשום גיליונות מבלי לטעון חוברת עבודה, אך קריאת Title או Author פירושה Open מלא, ולכן מיון נתוני מטא על פני ארכיון גדול משלם את עלות הניתוח המלאה בכל קובץ. בצד ה-BIFF תוכלו לקצץ את העלות הזו עבור ביקורות לקריאה-בלבד על-ידי הגדרת _DisableGraphics ל-true לפני הפתיחה, מה שמדלג על שכבת הציור מכל וכל. זה מתאים ללולאה שקוראת רק מאפיינים וסטטיסטיקת תאים, וזה שגוי בדיוק ברגע שאותו מופע עשוי לשמור, מפני שתוכן הציור שדולג עליו יושמט. כשמבנה גיליון לבדו יכול לסנן-מראש את הקבוצה, כשייצוא חד-גיליוני הוא הדבר הברור לדלג עליו, הטכניקות הזולות במאמר שלנו על רשימת גיליונות ובדיקה קלת-משקל חותכות כמה קבצים מגיעים אל המעבר היקר. ובעבודות הטבעה המוניות, שבהן אלפי פלטים נכתבים ולא נבדקים, תבניות קצב-ההעברה בצד-הכתיבה במאמר שלנו על כתיבות הזרמה לעבודות אצווה עוברות ללא שינוי, מכיוון שהקצאת מאפיינים אינה מוסיפה דבר מדיד לזמן השמירה

חציית פורמטים והכלת הדליפה

מאפיינים עוברים סבב בנקיות בתוך מעטפת יחידה: פתחו .xlsx, ערכו אותו, שמרו אותו, והמערך חוזר ללא פגיעה. חציית פורמטים היא המקום שבו ההנחה של שוויון נשברת, מפני שמערכי השדות של BIFF ושל OOXML אינם מתיישרים אחד-לאחד. ל-BIFF יש Manager ואין חותמות זמן; ל-OOXML יש Category, Description ואת זוג ה-Created/Modified. ממיר שמעתיק בעיוורון מאבד כל מה שפורמט היעד אינו יכול להחזיק, ולכן מפו את השדות במפורש והכניסו את המיפוי לרשימת בדיקת ההמרה שלכם לצד כל דבר אחר שאינו שורד את המעבר

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

הנראות הזו במורד הזרם היא גם הסיבה שכמה שדות מרוויחים יותר טיפול מהשאר. Title, Author, Keywords (המופיעים כ-Tags), ו-Comments או Description נושאים את רוב משקל האינדוקס ב-SharePoint וב-Windows Search. כותרת שהיא באמת ייחודית לכל מסמך, הנושאת את התקופה ואת החשבון, עושה יותר לאיתור מכל שיטת תיוק-תיקיות שמעורמת מעליה, והיא עולה הקצאה אחת לכל שמירה

מאפייני מסמך הם הליטוש המקצועי הזול ביותר שחוברת עבודה מחוללת יכולה לשאת, והפגם הנשלח הנפוץ ביותר כשאיש אינו אחראי עליהם. שני משטחי המאפיינים המתוארים כאן שייכים ל-HotXLS Component, שכותב אותם באופן מקורי עבור XLS ו-XLSX ללא אוטומציית Excel