מאמר טכני

שרשורי תגובות בקובצי Excel ב-Delphi עם HotXLS

צרו חוברת עבודה מ-Delphi, הוסיפו הערה לתא באמצעות API ההערות הקלאסי, וקובץ ה-Excel 365 יציג את הטקסט שלכם תחת Notes (פתקים): תיבה צהובה בסגנון ישן ללא שדה תגובה (Reply), ללא תמונת פרופיל של המחבר וללא אפשרות סימון כפתור. התוכנה HotXLS כותבת שרשורי תגובות (threaded comments) אמיתיים של Excel 365 באמצעות AddThreadedComment ו-AddThreadedReply במנוע ה-XLSX, כאשר היא מפיקה חלק ייעודי xl/threadedComments/threadedCommentN.xml לכל גיליון ורושמת כל מחבר בחלק ה-person ברמת חוברת העבודה, כך שהשיחה שהקוד שלכם כותב היא אותו אובייקט שמבקר מגיב אליו ב-Excel

ההבחנה חשובה מכיוון ששתי התכונות רק נראות דומות. פתק (note) הוא תיבת טקסט צפה; שרשור תגובות הוא רשומת שיחה עם מזהים יציבים, עץ תגובות, מרשם מחברים ודגל פתור — הדברים שעליהם מבוסס תהליך עבודה של בדיקה. מאמר זה עובר תחילה על מבנה ה-OOXML, מכיוון שברגע שתראו את שני מודלי האחסון זה לצד זה, כל הבדל בהתנהגות ב-Excel יפסיק להיות מסתורי

מדוע התגובות שלי מוצגות כפתקים ב-Excel 365?

התשובה הקצרה: שני דורות של תגובות חולקים רשת אחת, והם חיים בחלקים שונים לחלוטין של המכל. המודל הישן — זה ש-AddComment פונה אליו — שומר טקסט ב-xl/comments1.xml (סוג תוכן application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml) וממקם את הבלון באמצעות חלק ציור VML נלווה, xl/drawings/vmlDrawing1.vml. צמד זה קדם לסרגל הכלים; Excel 365 עדיין קוראת אותו בנאמנות, אך היא מרנדרת את התוצאה כ"פתק" ואינה מציעה ממשק משתמש לשרשור, מכיוון שאין באחסון דבר המאפשר שרשור. המחבר הוא מחרוזת תצוגה, המיקום הוא צורת VML, וזהו המודל כולו

מה HotXLS כותבת לתוך המכל

התוכנה HotXLS ממדלת כל תגובה כאובייקט TXLSXThreadedComment הנושא עוגן תא (Ref), מזהה בסגנון GUID‏ (Id), מזהה הורה אופציונלי (ParentId), מזהה מחבר AuthorId המפוענח דרך רשימת האנשים, מראה DisplayName, גוף הטקסט, דגל Done וחותמת זמן DateTime בתקן ISO-8601. בעת השמירה, כל רשומה הופכת לאלמנט <threadedComment> שתכונת ה-t שלו מחזיקה את המזהה, תכונת ה-dT שלו מחזיקה את חותמת הזמן ותכונת ה-parentT שלו — הקיימת רק בתגובות ילדים — מציינת את מזהה ההורה. התוכנה Excel משחזרת את עץ השיחה על ידי התאמת parentT ל-t; אין קינון ב-XML, אלא רק רשימה שטוחה בתוספת הפניות

חלק ה-person הוא החלק שרוב המימושים הידניים שוכחים. כל authorId בשרשור תגובות חייב להיות מפוענח לרשומת <person> ב-xl/person/person.xml, המקושרת ליחסי חוברת העבודה, עם דריסת סוג התוכן שלה — אחרת ל-Excel אין שם תצוגה להראות. HotXLS מטפלת ברישום באופן אוטומטי: AddThreadedComment מחפשת את המחבר ברשימת ה-Persons של חוברת העבודה לפי שם תצוגה ויוצרת רשומה עם GUID חדש כאשר אין כזו, כך שתגובות רצופות של אותו מחבר משתפות זהות אחת. יחסי גיליון העבודה מקבלים את קשר ה-threadedComments, יחסי חוברת העבודה מקבלים את קשר ה-person, ומניפסט סוגי התוכן מקבל את שתי הדריסות, מבלי שדבר מזה יופיע בקוד שלכם

בניית שרשרת תגובות עם AddThreadedComment ו-AddThreadedReply

הפונקציה AddThreadedComment יוצרת את שורש השיחה: ה-ParentId שלו נשאר ריק, שזה מה שמסמן אותו כראש השרשור. הפונקציה AddThreadedReply מקבלת פרמטר ParentRef — הפניה לתא שבו השורש חי — מוצאת את השורש באותו עוגן ורושמת את ה-ParentId של התגובה החדשה עם ה-Id של השורש. שתיהן מחזירות את ה-TXLSXThreadedComment החדש, כך שתוכלו להגדיר את חותמת הזמן או את מצב הפתרון על האובייקט שיצרתם כעת

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Root: TXLSXThreadedComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('forecast.xlsx');
    Sheet := Book.Sheets[0];

    // Conversation root; ParentId stays empty
    Root := Sheet.AddThreadedComment(8, 3,
      'Q3 figure looks low against the pipeline export', 'Maria Ortiz');
    Root.DateTime := '2026-07-06T09:12:00Z';

    // Replies anchor at the same cell; ParentRef names the root cell
    Sheet.AddThreadedReply(8, 3, Root.Ref,
      'Pipeline export missed the EMEA renewals, re-running', 'Jan Kowalski');
    Sheet.AddThreadedReply(8, 3, Root.Ref,
      'Confirmed, refreshed figure lands tomorrow', 'Maria Ortiz');

    Book.SaveAs('forecast-reviewed.xlsx');
  finally
    Book.Free;
  end;
end;

שני פרטים ברשימה זו דורשים תשומת לב. ראשית, Root.Ref מועבר במקום מחרוזת שנבנתה ידנית: ההפניה שהספרייה ייצרה מובטחת להתאים למה ש-AddThreadedReply תחפש, מה שמונע סוג שלם של באגים של עוגנים מוסטים. שנית, שני המחברים הועברו כשמות תצוגה פשוטים — מרשם האנשים, זהויות ה-GUID וחיבורי ה-authorId התרחשו מאחורי הקלעים. פתחו את הקובץ השמור ב-Excel 365 ועיגון התאים, סדר התגובות ושני המשתתפים השונים כולם פעילים: לחצו על Reply ו-Excel תצרף את התגובה לאותו שרשור שהקוד שלכם התחיל

קריאת שיחה חזרה: התנהגות סבב (round-trip)

התוכנה HotXLS תומכת בסבב של שרשורי תגובות החל מגרסה 2.122.0: פתיחה מחדש של מכל שמור בונה מחדש את רשימת השיחות של כל גיליון ואת רשימת המשתתפים של חוברת העבודה מתוך החלקים, כך שהמזהים, קישורי התגובות ושמות התצוגה שורדים מחזור מלא של קריאה←כתיבה. המנתח קורא את person.xml לפני כל תוכן גיליון, מה שמאפשר לכל authorId לפענח שם תצוגה בזמן הטעינה; מזהי האנשים המנותחים נשמרים כפי שהם במקום להיווצר מחדש, כך שחוברת עבודה הנעה בין הקוד שלכם ל-Excel שומרת על זהויות מחברים יציבות על פני מספר סבבים שרירותי

var
  i: Integer;
  Cmt: TXLSXThreadedComment;
begin
  Book.Open('forecast-reviewed.xlsx');
  Sheet := Book.Sheets[0];
  for i := 0 to Sheet.ThreadedComments.Count - 1 do
  begin
    Cmt := Sheet.ThreadedComments[i];
    if Cmt.ParentId = '' then
      Writeln('Root at ', Cmt.Ref, ' by ', Cmt.DisplayName, ': ', Cmt.Text)
    else
      Writeln('  reply by ', Cmt.DisplayName, ': ', Cmt.Text);
  end;
  Writeln('Participants: ', Book.Persons.Count);
end;

אחסון הרשימה השטוחה בתוספת הפניות מוצג כאן במכוון: ThreadedComments מונה תגובות בסדר החלקים, ו-ParentId ריק מזהה כל שורש. אם אתם צריכים את מבנה העץ — למשל, כדי לייצא לוג בדיקה — קבצו לפי Ref תחילה ואז שרשרו את ParentId ל-Id בתוך הקבוצה של כל תא. התנהגות זו של שמירה על מה שנותח היא דוגמה לעיקרון רחב יותר בספרייה; המאמר על סבב ללא אובדן נתונים של ערכות עיצוב, רשימות הרחבה ושרשרת החישובים מכסה כיצד אותו עיקרון חל על שאר המכל

מצב פתור, חותמות זמן וזהות מחבר

שלוש תכונות נושאות את הסמנטיקה של הבדיקה, ושלושתן ניתנות לכתיבה באובייקטים המוחזרים. המאפיין Done ממופה לתכונת ה-done — התוכנה Excel מציגה שרשור פתור מכווץ עם קישור Reopen. המאפיין DateTime הוא חותמת זמן היצירה UTC בתקן ISO-8601 (תכונת dT); HotXLS מציבה סימן מקום קבוע כאשר אתם משאירים אותו ריק כדי שהחלק תמיד יעבור אימות, אך חותמת זמן אמיתית היא מה שהופך את היסטוריית השרשור לקריאה, לכן הגדירו אותה. בצד הזהות, כל TXLSXPerson חושף את ProviderId — בדרך כלל None עבור רשומות שנכתבו מקומית — ו-UserId שתוכלו למלא ב-UPN או באימייל כאשר האפליקציה שלכם יודעת אותו

var
  Root: TXLSXThreadedComment;
  Person: TXLSXPerson;
begin
  Root := Sheet.ThreadedComments.FindAt('D9');
  if Root <> nil then
    Root.Done := True;   // saved as done="1"; Excel shows the thread resolved

  Person := Book.Persons.FindByDisplayName('Maria Ortiz');
  if Person <> nil then
    Person.UserId := 'maria.ortiz@example.com';
end;

גבול אחד שיש לציין בבירור: HotXLS כותבת את האלמנט <mentions> כשהוא ריק. רשומות אזכור (@-mention) — המנגנון המדגיש את שמו של עמית בתוך טקסט התגובה ומפעיל התראה ב-Microsoft 365 — אינן מיוצגות, ולכן טקסט המכיל סימן @ נשמר כטקסט פשוט. עבור חוברת עבודה של בדיקה מיוצרת זהו לעיתים נדירות חיסרון, אך אם תהליך העבודה שלכם תלוי בהתראות אזכור, תכננו שאדם יוסיף אותן בתוך Excel. זהות המחבר בחלק ה-person משתלבת באופן טבעי עם שדות המקור ברמת חוברת העבודה המכוסים במטא-נתונים של חוברת עבודה ומאפייני מסמך, שזה המקום שבו שאר נתיבי הביקורת של קובץ מיוצר חיים

מה גרסאות Excel ישנות יותר עושות עם שרשורי תגובות?

גרסאות Excel 2016 ומטה קדמו למודל השרשורי, והן פשוט מתעלמות מחלקים שאינן מבינות. כאשר Excel 365 עצמה שומרת שיחה משורשרת, היא כותבת גם צל של תגובה ישנה — פתק סימן מקום הקורא "[Threaded comment]…" — בדיוק כדי שגרסאות ישנות יותר יציגו משהו בתא העוגן. התוכנה HotXLS מפיקה את החלקים המשורשרים בלבד, ללא צל ישן, כך שקובץ שנכתב על ידי הקוד שלכם אינו מציג שום חיווי תגובה כלל כאשר הוא נפתח ב-Excel 2016. אם הקהל שלכם כולל התקנות שלפני 365 וההערה חייבת להיות גלויה שם, ה-API הישן AddComment נותר הערוץ התואם; פשוט הימנעו מערבוב שני המודלים באותו תא ללא בדיקה של כל גרסת Excel שאתם שולחים אליה, מכיוון שאופן היישוב של פתק ושרשור בתא אחד הוא החלטה של הקורא, לא שלכם

הגבול הקשיח השני הוא פורמט הקובץ עצמו. שרשורי תגובות הם מבנה OOXML ללא מקביל ב-BIFF8, ולכן לצד ה-.xls של HotXLS אין API משורשר — חוברת עבודה שחייבת להישאר בפורמט הבינארי הישן מוגבלת לפתקים קלאסיים. בפועל טבלת ההחלטות קצרה: כתיבת .xlsx עבור Excel 365 או קוראי Excel באינטרנט/בנייד, השתמשו ב-AddThreadedComment וקבלו שיחות אמיתיות; מיקוד ב-Excel 2016 או .xls, השתמשו ב-AddComment וקבלו את מודל הפתקים; ביקורת קובץ שקיבלתם, בדקו הן את ThreadedComments והן את Comments, מכיוון שחוברת עבודה שחייתה בשני העולמות יכולה לשאת את שניהם באופן חוקי. ממשק ה-API המלא עבור שני הדורות מתועד בדף המוצר של רכיב HotXLS Delphi Excel