מאמר טכני

הערות והיפר-קישורים ב-Excel ב-Delphi עם HotXLS

שנה שם גיליון מ-"Summary" ל-"Overview" בחוברת עבודה מחוללת, וכל היפר-קישור פנימי שהצביע על Summary!A1 מפסיק להגיע לשום מקום. אין חריג בשמירה, אין בפתיחה. הקישור עדיין מרונדר, עדיין נראה לחיץ, ובשקט מפורק לכלום. אותו סוג של שבירה עולה לאחר המרת שמירה-בשם או round-trip של ‎.xls/‎.xlsx‎, כאשר הערה נוחתת עמודה אחת הצידה או קישור יחסי משיל את היעד שלו. שתי התכונות נושאות מצב סקירה שאנשים אמיתיים פועלים לפיו, כך שכשהן נשברות הכשל בלתי-נראה עד שסוקר לוחץ ושום דבר לא קורה

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

הערות תאים כרשומות סקירה שנכתבות-במכונה

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

תרשים של ניסיון חוזר של הערה ב-HotXLS ב-Delphi שבו חישה של FindAt מעדכנת את הערת התא הקיימת בעוד ניסיון חוזר עיוור של AddComment מערים כפילות
ניסיון חוזר שקורא ל-AddComment בעיוורון עורם הערה שנייה על אותו תא, בזמן שה-probe של FindAt עורך את ההערה שכבר שוכנת שם
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // הערה שנכתבה על הנתון המותאם
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // עדכון הערה קיימת במקום להערים הערה שנייה
    Note := Sheet.Comments.FindAt(14, 4);
    if Note <> nil then
      Note.Text := Note.Text + ' [verified 2026-06-11]';

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

הבדיקה FindAt נושאת משקל רב מכפי שנראה. עבודת batch שמנסה מחדש לאחר כשל חולף תקרא בשמחה ל-AddComment פעם שנייה על תא שכבר הוסרה בו הערה, והתא מסתיים עם שתי הערות מוערמות שאף אחד לא ביקש. בדוק עם FindAt תחילה, ועדכן את האובייקט שהוא מחזיר. אוסף Comments גם חושף DeleteAt ו-DeleteInRange. וריאנט הטווח הוא זה שיש להושיט יד אליו כאשר מחטאים חוברת עבודה לפני שהיא עוזבת את הבניין: ניקוי הערות QA פנימיות מאזור שלם הוא קריאה יחידה במקום לולאה כתובת-יד על תאים

URL חיצוניים וקפיצות בתוך-חוברת הם ממשקי API שונים

OOXML שומר את שני סוגי הקישור במקומות שונים. URL חיצוני הופך לרשומת relationship בחלק ה-‎.rels‎ של הגיליון, כאשר התא מצביע על ה-relationship לפי id. קפיצה פנימית לעולם לא נוגעת בשכבת ה-relationship כלל; היא מחרוזת מיקום רגילה כגון Summary!A1 המאוחסנת ישירות על הקישור. HotXLS שומר על אותה הבחנה גלויה ב-API במקום לערוך-יתר מתודה יחידה, מה שאומר שאתה בוחר את הקריאה הנכונה על ידי ידיעה היכן היעד חי:

תרשים המנגד כיצד HotXLS מאחסן URL חיצוני כקשר relationship בחלק ה-rels וקפיצה פנימית כמחרוזת מיקום פשוטה בחוברות שנוצרו ב-Delphi
URL חיצוני נוסע דרך שכבת ה-relationship בזמן שקפיצה פנימית היא טקסט פשוט, ולכן כל סוג נכשל בדרכו שלו וזקוק לכלל ביקורת משלו
Sheet.Cells[2, 1].Value := 'Source record';
Sheet.AddHyperlink(2, 1, 'https://intranet.example.com/records/2214',
  'Open record 2214', 'ERP source entry');

Sheet.Cells[3, 1].Value := 'Totals';
Sheet.AddHyperlinkToCell(3, 1, 'Overview!B12', 'Jump to totals');

על אובייקט TXLSXHyperlink המתקבל, Url ו-Location הם בלעדיים-הדדית, ו-IsInternal אומר לך איזה מהשניים מאוכלס. הדגל הזה הוא מה שבודקים כאשר מלאי הקישורים בחוברת פתוחה נעשה וצריך לטפל ב"עוזב את הקובץ" ו"נשאר בקובץ" תחת כללים שונים: host חיצוני עשוי להתמודד מול allowlist בעוד שיעד פנימי רק צריך לנקוב בגיליון שקיים. קישורים פנימיים לא נושאים חלקי relationship מאחוריהם, מה שגם הופך אותם זולים יותר לכתיבה-מחדש בכמות

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

הצד ה-XLS: אותם מושגים, צנרת ישנה יותר

facade ה-BIFF8 תולה הערות מטווחים במקום מאוסף ברמת-גיליון. קוראים ל-AddComment על IXLSRange ומקבלים בחזרה TXLSComment; מאפיין Comment של הטווח קורא הערה קיימת, ו-ClearComments מוחק אותן. הקצה החד כאן הוא מיקומי. TXLSComment לא חושף בפומבי את השורה והעמודה של עצמו, כך שהלולאה הטבעית, "עבור על כל הערה ודווח היכן היא יושבת," רצה אחורנית כנגד ה-API. צריך להתחיל מהתאים. או להניע את האודיט מרשימת הכתובות שהערת, או לשמור לוג מיקום משלך תוך כדי כתיבה, כי אובייקט ההערה לא יגיד לך לאחר מכן היכן הוא חי

var
  Book: IXLSWorkbook;
  Sheet: IXLSWorksheet;
  Remark: TXLSComment;
begin
  Book := TXLSWorkbook.Create;
  Sheet := Book.Sheets.Add;
  Sheet.Name := 'Review';
  Sheet.Cells.Item[5, 2].Value := 4821.50;

  Remark := Sheet.Cells.Item[5, 2].AddComment('Awaiting sign-off from controller');
  Remark.Visible := True;   // פתיחת ההערה אוטומטית בצפייה הראשונה

  Sheet.AddHyperlink(7, 2, 'https://intranet.example.com/signoff/4821',
    'Sign-off form', 'Opens the controller queue');
  Book.SaveAs('review.xls');
end;

הגדרת Visible ל-True היא הדרך המורשתת להפוך הערה לבלתי-אפשרית-להתעלם: התיבה הצהובה נשארת פתוחה על הגיליון במקום לחכות לריחוף. TXLSComment הולך צעד אחד מעבר למקבילו ה-XLSX על ידי חשיפת TextRuns, כך שהערה יחידה יכולה לשאת אזהרה מודגשת לצד הסבר רגיל, עיצוב ש-API הערות ה-XLSX לא חושף באותה דרך. היפר-קישורים בצד הזה מגיעים דרך שלושה עומסים מתקדמים (כתובת בלבד, ואז עם טקסט תצוגה, ואז עם screen tip) ונקראים בחזרה דרך אוסף HyperLinks של הגיליון, שבו כל קישור חושף Address, SubAddress, DisplayText, ו-ScreenTip

גיליון אינדקס סקירה עדיף על הערות פזורות

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

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

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

מעבר אודיט טרום-משלוח שבאמת תופס את השבירה

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

תרשים של מעבר הביקורת לפני מסירה ב-HotXLS הבודק יעדים פנימיים, רשימות היתר של URL, מספרי הערות וניקוי נמענים לפני SaveAs ב-Delphi
ארבע בדיקות רצות רגע לפני SaveAs וכל אחת תופסת כשל שהספרייה עצמה לעולם לא תעלה עליו חריגה
  • אסוף כל מיקום פנימי שנכתב במהלך יצירה ואשר ששם הגיליון לפני סימן הקריאה עדיין קיים באוסף הגיליונות של חוברת העבודה
  • בדוק URL חיצוניים כנגד allowlist של סכמות ו-hostים. file:// חשופים ונתיבי UNC חושפים פרטי סביבה ונשברים ברגע שהקובץ עוזב את הרשת שלך
  • ספור הערות לכל גיליון והשווה למה שהמחולל שלך התכוון לכתוב. ניסיון-חוזר שהכפיל הערות עולה כאן במקום בתיבת הדואר של הסוקר
  • הסר הערות פנימיות-בלבד עם DeleteInRange בכל פעם שהנמען יושב מחוץ לארגון

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

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

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