HotXLS Delphi Component یک رشته pattern را چهار جور متفاوت میخواند، چون Excel 16 همین کار را میکند. در COUNTIF و SUMIF متن a~b لفظی است مگر اینکه معیار هم * داشته باشد هم ?؛ در MATCH و حالت wildcard مربوط به XLOOKUP علامت مد همیشه یک escape است، پس a~b مقدار ab را پیدا میکند؛ در DSUM و بقیه توابع دیتابیس متن ساده یعنی «شروع میشود با»؛ و Find کل-سلولی باید به آخرین * برگردد و دوباره امتحان کند. HotXLS از v2.384.52 و v2.384.60 و v2.384.64 این قواعد اندازهگیریشده را دنبال میکند
باگریپورتهای این حوزه هرگز اسمی از wildcard نمیبرند. میگویند گزارشی که سرور تولید کرده چند سطر کمتر از همان فایلی میشمارد که در Excel دوباره محاسبه شده، یا شماره قطعهای که داخلش علامت مد هست با یک فرمول پیدا میشود و با فرمول بعدی نه. علتش matcher ای است که فرض میکند یک pattern همهجا یک معنا دارد. Excel اینطور کار نمیکند، پس موتوری که نتایج cacheشدهاش باید با Excel جور باشد هم نمیتواند. قبل از v2.384.52 HotXLS هر معیاری را از یک فایلماسک سبک DOS میگذراند، که pattern های روزمره را درست درمیآورد و edge case ها را بیسروصدا غلط
چرا یک رشته pattern در Excel چهار معنای متفاوت دارد؟
یک رشته pattern چهار معنای متفاوت دارد چون Excel چهار قاعده مچ کردن را از چهار فیچر به ارث برده و هرگز یکدستشان نکرده. توابع معیار (COUNTIF و SUMIF و AVERAGEIF و خانواده *IFS) بهازای هر معیار تصمیم میگیرند که اصلاً wildcard اعمال شود یا نه. توابع lookup (MATCH با match type صفر، XLOOKUP با match_mode برابر 2) همیشه اعمالشان میکنند. توابع دیتابیس (DSUM و DCOUNTA و رفقا) از Advanced Filter پیروی میکنند که در آن یک کلمه لخت یعنی پیشوند. دیالوگ Find هم حالتهای کل-سلول و جزئی خودش را دارد. جدول پایین نشان میدهد کدام سلولها هر pattern را مچ میکنند وقتی ستون مقادیر a~b و ab و AB و abc و abcb و a*b و axb را دارد و همه توابع در حالت پیشفرض بیحساسیت-به-حروفاند
| Pattern | COUNTIF / SUMIF | MATCH(…,0) / حالت 2 مربوط به XLOOKUP | معیار DSUM | Find، کل سلول، wildcard روشن |
|---|---|---|---|---|
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 از همان قاعده پیشوند دنبال میشود، چون همان * اضافهشده معیار را به یک pattern wildcard میبرد که در آن ~b یک b با escape است
COUNTIF کی وارد حالت wildcard میشود؟
COUNTIF فقط وقتی وارد حالت wildcard میشود که متن معیار * یا ? داشته باشد، با escape یا بدون آن. بدون هیچکدام از این دو کاراکتر، Excel معیار را با هر سلول بهعنوان یک رشته کامل مقایسه میکند، بیحساسیت به بزرگی حروف، و یک علامت مد فقط همان علامت مد است، پس COUNTIF(A1:A7,"a~b") سلولی را میشمارد که لفظی a~b دارد. یک ستاره اضافه کنید و معنا برمیگردد: در "a~b*" حالا علامت مد آن b را escape میکند، pattern خوانده میشود «ab و بعدش هر چیزی»، و سلول a~b دیگر شمرده نمیشود. HotXLS این قاعده را از v2.384.52 در هر دو موتور اعمال میکند، از طریق یک matcher معیار واحد در lxCalc که بین COUNTIF و SUMIF و AVERAGEIF و COUNTIFS و SUMIFS و AVERAGEIFS و توابع دیتابیس مشترک است
داخل حالت wildcard قواعد escape همان بقیه جاهای Excel است: ~ کاراکتر بعدی را لفظی میکند هر چه باشد، پس ~b یعنی b و ~~ یعنی یک علامت مد، و علامت مد در انتهای pattern حذف میشود، پس "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 این شامل اعداد و boolean ها و مقادیر خطا و سلولهای خالی هم میشود. یک "<>" لخت سؤال دیگری است کلاً: یعنی «سلول خالی نباشد»، پس سلولهای خالی را رد میکند اما هر مقداری را میشمارد، از جمله متن خالیای که فرمولی مثل ="" برمیگرداند. کد قدیمی HotXLS سلولهای متنی را درست درمیآورد اما اعداد را نه: یک نابرابری Variant باعث میشد Delphi مقدار 'ab' را به عدد تبدیل کند، تبدیل exception میداد، یک handler آن را بهعنوان «مچ نشد» قورت میداد، و سلولهای عددی بیسروصدا از شمارش میافتادند. سمت سلولهای خالی این ماجرا، از جمله اینکه یک عملوند خالی در مقایسه معمولی با چه چیزی برابر است، در نحوه مدیریت زنجیرههای مقایسه، سلولهای خالی و SUMIF در HotXLS پوشش داده شده
چرا وقتی دنبال a~b هستید MATCH مقدار ab را پیدا میکند؟
MATCH وقتی دنبال a~b هستید مقدار ab را پیدا میکند چون MATCH با match type صفر و XLOOKUP با match_mode برابر 2 همیشه در حالت wildcard هستند، پس علامت مد حتی وقتی pattern هیچ * یا ? ندارد یک escape است. Excel 16 روی بازه دو سلولی که a~b و ab دارد همین را تأیید میکند: MATCH("a~b",D1:D2,0) برمیگرداند 2، و روی بازهای که فقط a~b دارد همان فراخوانی برمیگرداند #N/A. برای lookup کردن متن لفظی a~b باید بنویسید "a~~b". در همین حال COUNTIF(D1:D2,"a~b") روی همان دو سلول برمیگرداند 1 و سلول دیگر را میشمارد. همان رشته، همان بازه، سلول مخالف
برای همین HotXLS این دو تصمیم را جدا نگه میدارد نه پشت یک نقطه ورود واحد به نام «pattern را مچ کن». خود matcher مشترک است: از v2.384.52 به بعد MATCH و XLOOKUP و توابع معیار همان matcher بازگشتی را اجرا میکنند، با همان مدیریت escape و همان قاعده علامت-مد-انتهایی. چیزی که فرق دارد دروازه جلوی آن است. مسیر معیارها اول میپرسد «آیا این متن * یا ? دارد؟»؛ مسیر lookup هیچوقت نمیپرسد. ادغام این دو یکی از دو خانواده را فیکس میکرد و دیگری را خراب میکرد، و هر دو جهت در هر دو موتور مقابل مقادیر Excel 16 چک میشوند. lookup های wildcard هم پیششرط خودشان را دارند: XLOOKUP مچ کردن wildcard را با حالت جستوجوی دودویی نمیپذیرد، قاعدهای که در راهنمای HotXLS برای مدهای جستوجوی XLOOKUP و XMATCH توضیح داده شده
DSUM و توابع دیتابیس یک معیار متنی ساده را چطور میخوانند؟
DSUM و بقیه توابع دیتابیس معیار متنی را که با = یا < یا > شروع نمیشود بهعنوان «شروع میشود با» میخوانند، با wildcard های همچنان فعال. همان قاعده 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? هم pattern های پیشوندیاند؛ >ab یک مقایسه معمولی است. قبل از v2.384.64 HotXLS مقدار ab را دقیق مچ میکرد، پس یک DSUM روی همان داده تست برمیگرداند 10 در جایی که Excel برمیگرداند 11
فیکس باید دور parser شرط میخورد، که هم ab و هم =ab را در همان شرط تساوی میتالد. HotXLS پس قبل از اینکه به شرط parseشده اعتماد کند متن خام معیار را بازرسی میکند: معیار متنی که اولین کاراکترش = یا < یا > نباشد یک * بهش میخورد و از matcher wildcard میگذرد، و همه چیز دیگر مقایسه کل-درایه خودش را نگه میدارد. یک نکته عملی وقتی بازه معیارها را در کد میسازید: در موتور XLSX assign کردن رشته '=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;
یک تفاوت مرتبط از فیکس پیشوند عمر بیشتری داشت و روی buildهای قدیمی مهم میشود. مقایسههای متنی مثل >ab ترتیب کدپوینت را استفاده میکردند، در حالی که Excel علائم نگارشی را قبل از حروف میگذارد، پس "a~b">"ab" در Excel برابر FALSE است و در HotXLS برابر TRUE بود. از v2.384.67 معیارهای > و < همراه با مقایسه متنی معمولی و مرتبسازی از collation ای به سبک word sort خود Excel زیر locale فعلی کاربر استفاده میکنند، و دوباره با هم یکیاند
چرا Find کل-سلولی مقدار abcb را از دست میداد؟
Find کل-سلولی مقدار abcb را از دست میداد چون matcher در اولین نقطهای که pattern تمام میشد میایستاد بهجای اینکه به آخرین * برگردد و دوباره امتحان کند. matcher مچ-جزئی پشت Replace هر وقت pattern تمام میشد برمیگشت؛ Find کل-سلولی همان را reuse میکرد و بعدش شرط میگذاشت که مچ کل سلول را بپوشاند: مقدار a*b مقابل abcb بعد از ab میایستاد، 2 کاراکتر از 4 را مصرف کرده بود، و رد میشد. از v2.384.60 matcher کل-سلولی یک پیادهسازی جداگانه است که «pattern تمام شد، متن نه» را یک مچ-نشد دیگر حساب میکند و از آخرین ستاره دوباره تلاش میکند، پس 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 فقط ~* و ~? و ~~ را escape میشناخت، پس a~b همان متن a~b را پیدا میکرد. یک pattern مربوط به Find که فقط ~ باشد خود در Excel ناپایدار است و هر سلولی را مثل pattern خالی مچ میکند، و HotXLS آن را تقلید نمیکند
در موتور XLSX جستوجو با TXLSXWorksheet.FindText و یک مجموعه TXLSXFindOptions انجام میشود: lxfUseWildcards مقدارهای * و ? و ~ را روشن میکند، lxfWholeCell شرط میگذارد کل سلول مچ شود، و lxfMatchCase مقایسه را حساس به بزرگی حروف میکند. بدون lxfUseWildcards هر کاراکتری، ستاره هم شامل، لفظی است. Find فقط مقادیر متنی را نگاه میکند؛ سلولهای عددی رد میشوند و سلولهای فرمول هم رد میشوند مگر اینکه lxfSearchFormulas ست شده باشد که در آن صورت متن فرمول جستوجو میشود. لنگری که StartRow و StartCol میدهند inclusive است، پس حلقه Find All باید بعد از هر hit یک ستون جلوتر قدم بگذارد
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 با escape است
if Sheet.FindText('a~~b', Row, Col, Opts, 1, 1) then
Writeln('a~~b whole cell -> row ', Row); // 3: مقدار ~~ یک علامت ~ لفظی است
// مچ جزئی و Find All: سلول لنگر هم داخل است، پس از هر hit رد شوید
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 با سه boolean بیرون میدهد، یعنی TXLSWorksheet.FindText(SearchText, Row, Col, MatchCase, UseWildcards, WholeCell)، بهعلاوه یک overload متناظر برای ReplaceText، با نتایج سطر و ستون یک-مبنا:
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 اکسل. قبل از v2.384.52 توابع معیار و توابع دیتابیس هر pattern را به MatchesMask میدادند، یک matcher فایلماسک در یونیت lxMasks. سینتکسش برای حالتهای رایج با Excel همپوشانی دارد، و برای همین مشکل پنهان میماند، اما هر جا که داده واقعی جالب میشود واگرا میشود:
- مقدار
[x]بهعنوان یک مجموعه کاراکتر خوانده میشد، پسCOUNTIF(A1:A10,"[x]")سلولهایی را میشمارد کهxدارند بهجای متن براکتدار، و"[a-z]"هر سلول یکحرفی را مچ میکرد - هیچ escape ای برای علامت مد نبود، پس
"a~*b"نمیتوانست یک ستاره لفظی را مچ کند - یک ماسک بدفرم، مثل براکت بستهنشده، exception میداد که فراخواننده آن را بهعنوان «مچ نشد» قورت میداد، و یک غلط تایپی در معیار به یک جمع غلط بیصدا تبدیل میشد
- در سمت lookup،
MATCHوXLOOKUPفقط~*و~?و~~را escape میگرفتند، پسMATCH("a~b",…,0)همانa~bلفظی را پیدا میکرد بهجایab
اگر workbookهایتان همیشه فقط روی داده الفبایی ساده از * و ? استفاده میکردند، نتایج از قبل درست بود و عوض نمیشود. اگر براکت دارند، علامت مد دارند، ستونهای نوع-مخلوط زیر "<>text" دارند، یا معیارهای DSUM بهشکل کلمه لخت نوشته شدهاند، دوباره محاسبه کردنشان با v2.384.64 یا بعدتر میتواند جمعها را عوض کند، و جمعهای جدید همانهایی است که Excel نشان میدهد. همان تمایز بین اینکه Excel معیار را چطور ذخیره میکند و چطور مقایسهاش میکند سراغ filterهای ذخیرهشده هم میآید، که در مقاله HotXLS درباره معیارهای DOPER مربوط به AutoFilter در BIFF8 بحث شده
مرجع سریع: قواعد wildcard اکسل در HotXLS
COUNTIFوSUMIFوAVERAGEIFو خانواده*IFSفقط وقتی wildcard استفاده میکنند که معیار*یا?داشته باشد؛ وگرنه رشتهها را کامل و بیحساسیت به بزرگی حروف مقایسه میکنند و~لفظی است (از v2.384.52)MATCHبا match type صفر وXLOOKUPبا match_mode برابر 2 همیشه wildcard دارند، پسa~bمقدارabرا پیدا میکند و برای متن لفظی بایدa~~bنوشت (از v2.384.52)- در حالت wildcard مقدار
~هر کاراکتر بعدی را escape میکند و~انتهایی حذف میشود؛ [و]کاراکترهای معمولیاند -
"<>text"اعداد و boolean ها و خطاها و سلولهای خالی را میشمارد؛ یک"<>"لخت سلولهای ناخالی را میشمارد، نتیجههای=""هم شامل 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 در یک موتور فرمول بیشتر همین edge case ها است، که مقابل خود Excel اندازهگیری میشوند نه از مستندات حدس زده میشوند. HotXLS مقدارهای COUNTIF و MATCH و XLOOKUP و DSUM و بقیه کتابخانه توابعش را بهصورت بومی در Delphi و C++Builder ارزیابی میکند، در موتور کلاسیک و موتور XLSX، بدون نصب بودن Excel. جزئیات و نسخهها و دانلود آزمایشی در صفحه کامپوننت صفحهگسترده HotXLS در Delphi است