HotXLS Delphi Component קורא את אותה מחרוזת תבנית בארבע דרכים שונות, כי Excel 16 עושה זאת. ב-COUNTIF וב-SUMIF הטקסט a~b הוא ליטרלי אלא אם הקריטריון מכיל גם * או ?; ב-MATCH וב-XLOOKUP במצב wildcard הטילדה היא תמיד escape, ולכן a~b מוצא את ab; ב-DSUM ובשאר פונקציות המסד טקסט רגיל פירושו "מתחיל ב"; ו-Find של תא שלם חייב לחזור אחורה אל ה-* האחרון. HotXLS הולך אחרי הכללים הנמדדים האלה מאז v2.384.52, v2.384.60 ו-v2.384.64
דיווחי הבאגים בתחום הזה לא מזכירים wildcards. הם אומרים שדוח שנוצר בשרת סופר כמה שורות פחות מאשר אותו קובץ שחושב מחדש ב-Excel, או שמספר חלק שמכיל טילדה נמצא על ידי נוסחה אחת ומתעלם ממנו הנוסחה הבאה. הסיבה היא matcher שמניח שתבנית פירושה דבר אחד בכל מקום. Excel לא עובד כך, ולכן גם מנוע שהתוצאות השמורות במטמון שלו חייבות להסכים עם Excel לא יכול לעבוד כך. לפני v2.384.52 HotXLS הזרים כל קריטריון דרך מסכת קבצים בסגנון DOS, שקיבלה תבניות יומיומיות נכון ואת מקרי הקצה שגויים בשקט
מדוע מחרוזת תבנית אחת פירושה ארבעה דברים שונים ב-Excel?
מחרוזת תבנית אחת פירושה ארבעה דברים שונים כי Excel ירש ארבעה כללי התאמה מארבע תכונות ומעולם לא איחד אותם. פונקציות הקריטריונים (COUNTIF, SUMIF, AVERAGEIF ומשפחת ה-*IFS) מחליטות לכל קריטריון אם wildcards חלים בכלל. פונקציות החיפוש (MATCH עם טיפוס התאמה 0, XLOOKUP עם match_mode 2) תמיד מיישמות אותם. פונקציות המסד (DSUM, DCOUNTA וחברותיהן) הולכות אחרי ה-Advanced Filter, שבו מילה רגילה היא קידומת. לתיבת הדו-שיח של Find יש מצבי תא-שלם וחלקי משלה. הטבלה להלן מפרטת אילו תאים תואמים כל תבנית מול עמודה אחת שמחזיקה a~b, ab, AB, abc, abcb, a*b ו-axb, כשכל פונקציה במצב ברירת המחדל שלה ללא רגישות לרישיות
| תבנית | COUNTIF / SUMIF | MATCH(…,0) / XLOOKUP מצב 2 | קריטריון DSUM | Find, תא שלם, wildcards פעילים |
|---|---|---|---|---|
ab | ab, AB | ab, AB | ab, AB, abc, abcb | ab, AB |
a*b | a~b, ab, AB, abcb, a*b, axb | כמו COUNTIF | כל ערך, כולל abc | כמו COUNTIF |
a~b | רק a~b | ab, AB | ab, AB, abc, abcb | ab, AB |
a~*b | רק a*b | רק a*b | רק a*b | רק a*b |
=ab | ab, AB | לא רלוונטי | ab, AB | לא רלוונטי |
השורה של a~b היא המקום שבו COUNTIF ו-MATCH חולקים, ומספרי חלקים וקודים שהוקלדו ידנית מכילים טילדות יותר מכפי שסביר לצפות. השורה של a*b מציגה את המוקש השני: abc תואם עבור DSUM ולא עבור COUNTIF, כי פונקציית המסד מוסיפה * בשקט. ערכי ה-DSUM עבור ab, a*b ו-=ab הגיעו ישירות מהרצות של Excel 16; הערך של DSUM עבור a~b נובע מאותו כלל קידומת, מכיוון שה-* המתווסף הופך את הקריטריון לתבנית wildcard שבה ~b הוא b מהונק
מתי COUNTIF עובר למצב wildcard?
COUNTIF עובר למצב wildcard רק כשטקסט הקריטריון מכיל * או ?, מהונקים או לא. בלי אף אחד מהתווים, Excel משווה את הקריטריון מול כל תא בתור מחרוזת שלמה, ללא רגישות לרישיות, וטילדה היא סתם טילדה, ולכן COUNTIF(A1:A7,"a~b") סופר את התא שמחזיק באמת את a~b. מוסיפים כוכבית אחת והמשמעות מתהפכת: ב-"a~b*" הטילדה עכשיו מהנקה את ה-b, התבנית נקראת "ab ואחריו כל דבר", והתא a~b כבר לא נספר. HotXLS מיישם את הכלל הזה בשני המנועים מאז v2.384.52, דרך matcher קריטריונים אחד ב-lxCalc המשותף ל-COUNTIF, SUMIF, AVERAGEIF, COUNTIFS, SUMIFS, AVERAGEIFS ולפונקציות המסד
בתוך מצב ה-wildcard כללי ההנקה זהים לכל מקום אחר ב-Excel: ~ הופך את התו הבא לליטרלי מה שלא יהיה, ולכן ~b פירושו b ו-~~ פירושו טילדה אחת, וטילדה בסוף התבנית מושמטת, ולכן "a*~" מתנהג כמו "a*". סוגריים מרובעים לעולם אינם מיוחדים. קריטריון של "[x]" סופר תאים שמחזיקים את שלושת התווים [x], ו-"[a-z]" לא סופר דבר על נתונים רגילים. TXLSXWorkbook.Calculate מחשב מחרוזת נוסחה מול הגיליון הפעיל ומחזיר Variant, הדרך המהירה ביותר לבדוק את הכללים האלה מול הנתונים שלכם
uses
System.Variants, lxHandleX;
const
Names: array [1..7] of string = ('a~b', 'ab', 'AB', 'abc', 'abcb', 'a*b', 'axb');
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
i: Integer;
procedure Show(const Formula: string);
begin
Writeln(Formula, ' = ', VarToStr(Book.Calculate(Formula)));
end;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Data');
for i := 1 to High(Names) do
begin
Sheet.Cells[i, 1].Value := Names[i];
Sheet.Cells[i, 2].Value := 1 shl (i - 1); // 1, 2, 4 ... כך סכום SUMIF מזהה את שורותיו
end;
Sheet.Cells[8, 1].Value := 5; // מספר; A9 נשאר ריק
Show('=COUNTIF(A1:A7,"a~b")'); // 1 בלי * או ?: טקסט רגיל, התא a~b
Show('=COUNTIF(A1:A7,"a~b*")'); // 4 מצב wildcard: ab, AB, abc, abcb
Show('=COUNTIF(A1:A7,"a*b")'); // 6 wildcard על המחרוזת כולה, abc נשלל
Show('=SUMIF(A1:A7,"a*b",B1:B7)'); // 119 כל שורה חוץ מ-abc (8)
Show('=COUNTIF(A1:A7,"a~*b")'); // 1 ה-a*b הליטרלי
Show('=COUNTIF(A1:A9,"<>ab")'); // 7 המספר 5 וה-A9 הריק נספרים
Show('=COUNTIF(A1:A9,"<>")'); // 8 תאים לא ריקים
finally
Book.Free;
end;
end.
מה "<>text" סופר?
קריטריון "<>text" סופר כל תא שאינו הטקסט הזה, וב-Excel 16 זה כולל מספרים, booleans, ערכי שגיאה ותאים ריקים. "<>" ריק הוא שאלה אחרת לגמרי: הפירוש הוא "לא תא ריק", ולכן הוא מדלג על תאים ריקים אבל סופר כל ערך, כולל הטקסט הריק שנוסחה כמו ="" מחזירה. הקוד הישן של HotXLS קיבל נכון תאי טקסט אבל לא מספרים: אי-שוויון של Variant גרם ל-Delphi להמיר את 'ab' למספר, ההמרה העלתה חריגה, handler בלע אותה בתור "אין התאמה", ותאים מספריים נשרו מהספירה בשקט. הצד של התאים הריקים בסיפור הזה, כולל למה שווה אופרנד ריק בהשוואה רגילה, מכוסה ב-איך HotXLS מטפל בשרשראות השוואה, בתאים ריקים וב-SUMIF
מדוע MATCH מוצא את ab כשמחפשים a~b?
MATCH מוצא את ab כשמחפשים a~b כי MATCH עם טיפוס התאמה 0 ו-XLOOKUP עם match_mode 2 תמיד במצב wildcard, ולכן הטילדה היא escape גם כשהתבנית לא מכילה * או ?. Excel 16 מאשר זאת על טווח של שני תאים שמחזיק a~b ו-ab: MATCH("a~b",D1:D2,0) מחזיר 2, ועל טווח שמחזיק רק a~b אותה קריאה מחזירה #N/A. כדי לחפש את הטקסט הליטרלי a~b כותבים "a~~b". במקביל, COUNTIF(D1:D2,"a~b") על אותם שני תאים מחזיר 1, וסופר את התא השני. אותה מחרוזת, אותו טווח, תא הפוך
זו הסיבה ש-HotXLS שומר על שתי ההחלטות בנפרד במקום מאחורי נקודת כניסה אחת של "התאם תבנית". ה-matcher עצמו משותף: מאז v2.384.52, MATCH, XLOOKUP ופונקציות הקריטריונים מריצות את אותו matcher עם backtracking, עם אותו טיפול בהנקות ועם אותו כלל טילדה סופית. מה ששונה הוא השער שלפניו. נתיב הקריטריונים שואל קודם "האם הטקסט הזה מכיל * או ??"; נתיב ה-lookup לעולם לא שואל. איחוד השניים היה מתקן משפחה אחת ושובר את השנייה, ושני הכיוונים נבדקים מול ערכי Excel 16 בשני המנועים. ל-lookups עם wildcard יש גם תנאי מוקדם משלהם: XLOOKUP דוחה התאמת wildcard משולבת עם מצב חיפוש בינארי, כלל שמתואר ב-המדריך של HotXLS למצבי החיפוש של XLOOKUP ו-XMATCH
איך DSUM ופונקציות המסד קוראות קריטריון טקסט רגיל?
DSUM ושאר פונקציות המסד קוראות קריטריון טקסט בלי =, < או > פותחים בתור "מתחיל ב", כשה-wildcards עדיין פעילים. זהו כלל ה-Advanced Filter, והוא שונה מ-COUNTIF בכוונה. Excel 16 נמדד על עמודת Name שמחזיקה abc, ab, xab, AB, a~b ו-a*b: הקריטריון ab תואם את abc, ab ו-AB; =ab תואם רק את ab ו-AB; <>ab הוא אי-שוויון של ערך שלם; a*b ו-a? הן תבניות קידומת גם כן; >ab הוא השוואה רגילה. לפני v2.384.64 HotXLS התאים את ab במדויק, ולכן DSUM על נתוני הבדיקה האלה החזיר 10 במקום 11 ש-Excel מחזיר
התיקון היה צריך לעקוף את מפרסר התנאים, שמקפל גם את ab וגם את =ab לאותו תנאי שוויון. HotXLS לכן בוחן את טקסט הקריטריון הגולמי לפני שהוא סומך על התנאי המפורסר: קריטריון טקסט שהתו הראשון שלו אינו =, < או > מקבל * מוסף ועובר דרך matcher ה-wildcards, וכל השאר שומר על השוואת הערך השלם שלו. הערה מעשית אחת כשבונים טווחי קריטריונים בקוד: במנוע ה-XLSX, שיוך המחרוזת '=ab' אל TXLSXCell.Value שומר טקסט, בזמן שהמנוע הקלאסי TXLSWorkbook מקמפל ערך שמתחיל ב-= בתור נוסחה אלא אם מקדימים אותו בגרש
const
Names: array [1..7] of string = ('a~b', 'ab', 'AB', 'abc', 'abcb', 'a*b', 'axb');
Criteria: array [0..4] of string = ('ab', '=ab', '<>ab', 'a*b', 'a~*');
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
i: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Db');
Sheet.Cells[1, 1].Value := 'Name';
Sheet.Cells[1, 2].Value := 'Val';
for i := 1 to High(Names) do
begin
Sheet.Cells[i + 1, 1].Value := Names[i];
Sheet.Cells[i + 1, 2].Value := 1 shl (i - 1);
end;
Sheet.Cells[1, 4].Value := 'Name'; // כותרת הקריטריון ב-D1
for i := 0 to High(Criteria) do
begin
Sheet.Cells[2, 4].Value := Criteria[i]; // נשאר טקסט במנוע ה-XLSX
Writeln(Criteria[i], ' -> ',
VarToStr(Book.Calculate('=DSUM(A1:B8,"Val",D1:D2)')));
end;
// ab -> 30 ab, AB, abc, abcb (מתחיל ב)
// =ab -> 6 ab, AB (הערך השלם)
// <>ab -> 121 הכול חוץ מ-ab ו-AB
// a*b -> 127 a*b* תואם את כל השבעה, abc נכלל
// a~* -> 32 רק ה-a*b הליטרלי
finally
Book.Free;
end;
end;
הבדל אחד קשור שרד את תיקון הקידומת וחשוב ב-builds ישנים. השוואות טקסט כמו >ab השתמשו בסדר code point, בזמן ש-Excel שם פיסוק לפני אותיות, ולכן "a~b">"ab" הוא FALSE ב-Excel והיה TRUE ב-HotXLS. מאז v2.384.67 הקריטריונים > ו-<, יחד עם השוואת טקסט רגילה ומיון, משתמשים ב-collation ה-word sort של Excel תחת ה-locale של המשתמש הנוכחי, והשניים מסכימים שוב
מדוע Find של תא שלם פספס את abcb?
Find של תא שלם פספס את abcb כי ה-matcher עצר בנקודה הראשונה שבה התבנית אזלה במקום לחזור אחורה אל ה-* האחרון. ה-matcher של ההתאמה החלקית שמאחורי Replace מחזיר ברגע שהתבנית מתרוקנת; Find של תא שלם השתמש בו ואז דרש שההתאמה תכסה את כל התא: a*b מול abcb עצר אחרי ab, צרך 2 תווים מתוך 4, ונדחה. מאז v2.384.60 ה-matcher של תא שלם הוא מימוש נפרד שמתייחס ל-"התבנית נגמרה, הטקסט לא" בתור אי-התאמה נוספת ומנסה שוב מהכוכבית האחרונה, ולכן a*b תואם את abcb ו-a?b*b תואם את axbyb, כפי ש-Find של Excel 16 עושה עם "Match entire cell contents" מסומן
אותה מהדורה שינתה גם את הטילדה. Find של Excel 16, גם במצב תא-שלם וגם בחלקי, מתייחס ל-~ בתור escape לכל תו שאחריו: a~b מוצא את ab, a~~b מוצא את a~b, וטילדה סופית מתעלמת, ולכן q~ מתנהג כמו q. ה-matcher הישן של HotXLS זיהה רק את ~*, ~? ו-~~ כהנקות, ולכן a~b מצא את הטקסט a~b. תבנית Find של ~ בודדת אינה יציבה ב-Excel עצמו, מתאימה כל תא כמו תבנית ריקה, ו-HotXLS לא מחקה זאת
במנוע ה-XLSX החיפוש הוא TXLSXWorksheet.FindText עם סט TXLSXFindOptions: lxfUseWildcards מדליק את *, ? ו-~, lxfWholeCell דורש שהתא כולו יתאים, ו-lxfMatchCase הופך את ההשוואה לרגישה לרישיות. בלי lxfUseWildcards כל תו, כוכבית כוללת, הוא ליטרלי. Find מביט בערכי טקסט בלבד; תאים מספריים מדולגים, ותאי נוסחה מדולגים אלא אם מוגדר lxfSearchFormulas, ואז טקסט הנוסחה נחפש. העוגן שנקבע על ידי StartRow ו-StartCol הוא כולל, ולכן לולאת Find All צועדת עמודה אחת אחרי כל פגיעה
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Row, Col, NextRow, NextCol, Changed: Integer;
Opts: TXLSXFindOptions;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Parts');
Sheet.Cells[1, 1].Value := WideString('abc');
Sheet.Cells[2, 1].Value := WideString('abcb');
Sheet.Cells[3, 1].Value := WideString('a~b');
Sheet.Cells[4, 1].Value := WideString('ab');
Opts := [lxfUseWildcards, lxfWholeCell];
if Sheet.FindText('a*b', Row, Col, Opts, 1, 1) then
Writeln('a*b whole cell -> row ', Row); // 2: abc נדחה, abcb חוזר אחורה
if Sheet.FindText('a~b', Row, Col, Opts, 1, 1) then
Writeln('a~b whole cell -> row ', Row); // 4: ~b הוא b מהונק
if Sheet.FindText('a~~b', Row, Col, Opts, 1, 1) then
Writeln('a~~b whole cell -> row ', Row); // 3: ~~ הוא טילדה ליטרלית אחת
// התאמה חלקית, Find All: תא העוגן נכלל, ולכן מדלגים אחרי כל פגיעה
NextRow := 1;
NextCol := 1;
while Sheet.FindText('a*b', Row, Col, [lxfUseWildcards], NextRow, NextCol) do
begin
Writeln('a*b contained in row ', Row); // שורות 1, 2, 3 ו-4
NextRow := Row;
NextCol := Col + 1;
end;
// החלפה עם wildcard של תא שלם משכתבת רק את ה-a~b הליטרלי
Changed := Sheet.ReplaceText('a~~b', 'a-b', Opts);
Writeln(Changed, ' cell(s) replaced'); // 1
finally
Book.Free;
end;
end;
הלולאה החלקית מוצאת את כל ארבע השורות, כולל abc, כי במצב חלקי ל-a*b מספיק להופיע איפשהו בתוך התא. FindTextIn ו-ReplaceTextIn מקבלים את אותן אפשרויות בתוספת חלון FirstRow, FirstCol, LastRow, LastCol, המקבילה התוכנתית של חיפוש בתוך בחירה. המנוע הקלאסי חושף את אותם כללים דרך overload עם שלושה booleans, TXLSWorksheet.FindText(SearchText, Row, Col, MatchCase, UseWildcards, WholeCell), בתוספת overload מתאים של ReplaceText, עם תוצאות שורה ועמודה שמתחילות מ-1:
var
Classic: IXLSWorkbook;
Sheet: TXLSWorksheet;
Row, Col: Integer;
begin
Classic := TXLSWorkbook.Create;
Sheet := Classic.Sheets.Add;
Sheet.Range['A1', 'A1'].Value := 'abcb';
// MatchCase = False, UseWildcards = True, WholeCell = True
if Sheet.FindText('a*b', Row, Col, False, True, True) then
Writeln('found at ', Row, ',', Col); // 1,1
if not Sheet.FindText('a*c', Row, Col, False, True, True) then
Writeln('a*c does not cover abcb');
end;
מה matcher מסכת ה-DOS הישן קיבל שגוי?
ה-matcher הישן קיבל שגוי את התווים המיוחדים, כי מסכת קבצים של DOS היא שפה אחרת מ-wildcard של Excel. לפני v2.384.52 פונקציות הקריטריונים ופונקציות המסד העבירו כל תבנית אל MatchesMask, matcher של מסכות קבצים ביחידת lxMasks. התחביר שלו חופף את זה של Excel במקרים נפוצים, מה שהסתיר את הבעיה, אבל הוא סוטה בדיוק במקומות שבהם נתונים אמיתיים מתעניינים:
[x]נקרא בתור קבוצת תווים, ולכןCOUNTIF(A1:A10,"[x]")ספר תאים שמחזיקיםxבמקום את הטקסט המסוגר, ו-"[a-z]"התאים כל תא בן אות אחת- לא הייתה הנקת טילדה, ולכן
"a~*b"לא יכל להתאים כוכבית ליטרלית - מסכה משובשת, כמו סוגריים שלא נסגרו, העלתה חריגה שהקורא בלע בתור "אין התאמה", והפכה טעות הקלדה בקריטריון לסכום שגוי בשקט
- בצד החיפוש,
MATCHו-XLOOKUPהתייחסו רק ל-~*,~?ו-~~בתור הנקות, ולכןMATCH("a~b",…,0)מצא את ה-a~bהליטרלי במקום אתab
אם חוברות העבודה שלכם השתמשו רק ב-* וב-? על נתונים אלפאנומריים רגילים, התוצאות כבר היו נכונות ולא ישתנו. אם הן מכילות סוגריים, טילדות, עמודות מטיפוסים מעורבים תחת "<>text", או קריטריוני DSUM שנכתבו כמילים רגילות, חישוב מחדש עם v2.384.64 ומעלה עשוי לשנות סכומים, והסכומים החדשים הם אלה ש-Excel מציג. אותה הבחנה בין איך Excel שומר קריטריון ואיך הוא משווה אותו צצה גם עבור מסננים שמורים, שנדונו ב-המאמר של HotXLS על קריטריוני DOPER של AutoFilter ב-BIFF8
עזר זריז: כללי ה-wildcard של Excel ב-HotXLS
COUNTIF,SUMIF,AVERAGEIFומשפחת ה-*IFSמשתמשים ב-wildcards רק כשהקריטריון מכיל*או?; אחרת הם משווים מחרוזות שלמות ללא רגישות לרישיות וה-~הוא ליטרלי (מאז v2.384.52)MATCHעם טיפוס התאמה 0 ו-XLOOKUPעם match_mode 2 תמיד משתמשים ב-wildcards, ולכןa~bמוצא אתabוהליטרלי דורשa~~b(מאז v2.384.52)- במצב wildcard ה-
~מהנק כל תו בא וה-~הסופית מושמטת;[ו-]הם תווים רגילים "<>text"סופר מספרים, booleans, שגיאות ותאים ריקים;"<>"ריק סופר תאים לא ריקים, כולל תוצאות=""DSUMושאר פונקציות המסד מתייחסות לטקסט רגיל בתור "מתחיל ב";=textו-<>textמשווים את הערך השלם (מאז v2.384.64)- Find של תא שלם עם
lxfUseWildcardsו-lxfWholeCellחוזר אחורה, ולכןa*bתואם אתabcb; Find ו-Replace מתייחסים ל-~בתור escape לכל תו (מאז v2.384.60) - סדר הטקסט בקריטריונים
>ו-<הולך אחרי ה-collation של word sort של Excel, פיסוק לפני אותיות (מאז v2.384.67)
תאימות ל-Excel במנוע נוסחאות היא ברובה מקרי קצה כאלה, נמדדים מול Excel ולא ננחשים מהתיעוד. HotXLS מחשב את COUNTIF, MATCH, XLOOKUP, DSUM ושאר ספריית הפונקציות שלו באופן טבעי ב-Delphi וב-C++Builder, גם במנוע הקלאסי וגם במנוע ה-XLSX, בלי Excel מותקן. פרטים, מהדורות והורדת ניסיון נמצאים ב-דף רכיב הגיליונות HotXLS ל-Delphi