מאמר טכני

השוואת שתי חוברות עבודה של Excel בדלפי עם HotXLS

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

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

מה נחשב הבדל?

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

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

ערך או נוסחה, ואיך כל אחד מושווה

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

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

הרצת השוואה

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

uses
  lxHandleX, lxCompare;

var
  Left, Right: TXLSXWorkbook;
  Cmp: TXLSXWorkbookCompare;
  Lines: TStringList;
begin
  Left := TXLSXWorkbook.Create;
  Right := TXLSXWorkbook.Create;
  Cmp := TXLSXWorkbookCompare.Create;
  Lines := TStringList.Create;
  try
    if (Left.Open('baseline.xlsx') <> 1) or
       (Right.Open('reviewed.xlsx') <> 1) then
      Exit;

    if Cmp.Compare(Left, Right) = 0 then
      Writeln('workbooks are equivalent')
    else
    begin
      Cmp.Report(Lines);              // שורה קריאה אחת לכל הבדל
      Lines.SaveToFile('workbook-diff.txt');
      Writeln(Format('%d difference(s) written', [Cmp.Count]));
    end;
  finally
    Lines.Free;
    Cmp.Free;
    Right.Free;
    Left.Free;
  end;
end;

שורה שהופקה על ידי Report נקראת כמו value changed: Data!A2: 10 -> 99, שזה מספיק לסוקר ומספיק להודעת commit. זה הפן הפונה לאדם. הפן התכנותי הוא רשומת ההבדל עצמה, וזה מה שכדאי להשתמש בו כשההשוואה מזינה החלטה ולא מסמך

הנעת לוגיקה מההבדלים המובנים

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

var
  I: Integer;
  D: TlxCompareDiff;
  FormulaEdits: Integer;
begin
  FormulaEdits := 0;
  for I := 0 to Cmp.Count - 1 do
  begin
    D := Cmp.Diff(I);
    case D.Kind of
      lckFormulaChanged:
        begin
          Inc(FormulaEdits);
          Writeln(Format('%s R%dC%d: %s => %s',
            [D.Sheet, D.Row, D.Col, D.LeftText, D.RightText]));
        end;
      lckSheetAdded, lckSheetRemoved:
        Writeln(Format('structure: %s', [D.Describe]));
      lckMergeAdded, lckMergeRemoved:
        Writeln(Format('layout: %s at %s', [D.Describe, D.Ref]));
    end;
  end;

  // מדיניות סקירה שחוסמת רק על עריכות נוסחה
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

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

איפה השוואת חוברות עבודה משתלמת

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

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

המגבלות, נאמרות בפירוש

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

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

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