บทความเทคนิค

เทียบข้อความใน HotXLS: ลำดับ word sort แบบ Excel ใน Delphi

HotXLS Delphi Component เทียบค่าข้อความสองค่าแบบเดียวกับ Excel 16 ตั้งแต่ v2.384.67: ไม่สนตัวพิมพ์ ด้วยลำดับ “word sort” ของ Windows user locale ซึ่งก็คือผลที่ CompareStringW คืนเมื่อใส่ flag NORM_IGNORECASE ขีดกับ apostrophe ถูกข้ามในรอบแรกและแค่ตัดสินแพ้ชนะตอนเสมอ ="a-b">"ab" จึงเป็น TRUE ขณะที่เครื่องหมายวรรคตอนอื่นเรียงอยู่หน้าตัวเลขกับตัวอักษร ="a~b"<"ab" จึงเป็น TRUE เช่นกัน ลำดับเดียวกันนี้ตอนนี้ขับเคลื่อน operator เปรียบเทียบ เกณฑ์ > / < การเรียงช่วง และ VLOOKUP

ไม่มีใครเปิดบั๊กชื่อว่า “collation ไม่ตรงกัน” รายงานจริงพูดว่า COUNTIF(A:A,">M") นับได้มากกว่าบนเซิร์ฟเวอร์สองแถว, ราคาสินค้าที่เรียงโดย reporting service วาง X-100 ไว้ตรงที่ Excel ไม่วาง หรือ VLOOKUP("ABC",...) คืน #N/A ทั้งที่คอลัมน์มี abc ตรง ๆ ทั้งสามเรื่องมาจากคำถามเดียวกัน: เมื่อ operand ทั้งสองเป็นข้อความ ตัวไหนเล็กกว่า Excel มีคำตอบที่เป๊ะ ซึ่งไม่ใช่คำตอบที่โค้ด Delphi ส่วนใหญ่ให้ และก่อน v2.384.67 HotXLS เองก็ให้สามคำตอบต่างกันตามเส้นทางโค้ดที่ถูกถาม

Excel ใช้กฎอะไรเทียบข้อความสองสาย

Excel เทียบข้อความด้วย word sort ของ locale ผู้ใช้ โดยไม่สนตัวพิมพ์ word sort คือ collation เริ่มต้นของฟังก์ชันเปรียบเทียบ Windows NLS: ตัวอักษรเทียบกันตามลำดับทางภาษาศาสตร์ ไม่ใช่ตาม code point ตัวอักษรมีวรรณยุกต์นั่งติดกับตัวฐานของมัน และมีสองอักขระที่ได้รับการปฏิบัติพิเศษ ขีด - กับ apostrophe ' ถูกเมินในรอบแรก co-op กับ coop จึงตกลงข้าง ๆ กัน และเมื่อส่วนที่เหลือของสองสายเสมอกันเท่านั้นที่การมีอยู่ของมันตัดสินลำดับ เครื่องหมายวรรคตอนอื่นทุกตัวมีน้ำหนักและเรียงอยู่หน้าตัวเลข ส่วนตัวเลขเรียงอยู่หน้าตัวอักษร

ตารางแสดงว่ามันแปลว่าอะไรในทางปฏิบัติ คู่กับการเปรียบเทียบสองแบบที่นักพัฒนา Delphi มักหยิบมาใช้ คอลัมน์ Excel เก็บคำตัดสินที่ Excel 16 คืนให้กับ IF(A<B,...) ซึ่ง HotXLS ทำให้ตรงกันตั้งแต่ v2.384.67

A vs BExcel 16 / HotXLSCompareStr (ordinal)CompareText
"a-b" vs "ab"มากกว่าน้อยกว่าน้อยกว่า
"a'b" vs "ab"มากกว่าน้อยกว่าน้อยกว่า
"a~b" vs "ab"น้อยกว่ามากกว่ามากกว่า
"a_b" vs "ab"น้อยกว่าน้อยกว่ามากกว่า
"ab" vs "AB"เท่ากันมากกว่าเท่ากัน
"é" vs "f"น้อยกว่ามากกว่ามากกว่า
"Z" vs "f"มากกว่าน้อยกว่ามากกว่า

สองผลลัพธ์ที่พลาดง่าย หนึ่ง บทบาทตัดสินแพ้ชนะของขีดแปลว่า ="a-b"="ab" เป็น FALSE: สองสายเป็นเพื่อนบ้านสนิทในการเรียง แต่ไม่เท่ากัน สอง ความเท่ากันเมินตัวพิมพ์สนิท ab, AB กับ Ab เป็น key เดียวกันในสายตาการเปรียบเทียบทุกรูปแบบ เรียงคำทดสอบ 20 คำด้วย Range.Sort ของ Excel จะได้ a b, a.b, a_b, a~b, a0, a1b, ab / AB / Ab, ab-, a'b, a-b, -ab, ab1, abc, b, e, é, f, Z ภายในกลุ่ม ab ตำแหน่งของอักขระที่ถูกเมินตัดสินลำดับ

แผนภาพ word sort ของ HotXLS จัดอันดับคำทดสอบทั้ง 20 คำ จาก a b, a.b, a_b กับ a~b ผ่าน a0 กับ a1b ต่อด้วยกลุ่ม ab ที่มี AB กับ Ab รูปแบบขีดกับ apostrophe อย่าง a-b กับ a'b ไปจนถึง abc, b, e, e ล้อ, f กับ Z แสดงเครื่องหมายวรรคตอนมาก่อนตัวเลข ตัวเลขมาก่อนตัวอักษร โดยเมินตัวพิมพ์
เครื่องหมายวรรคตอนกับช่องว่างเรียงก่อนตัวเลข ตัวเลขก่อนตัวอักษร ตัวพิมพ์ถูกพับทิ้ง และขีดกับ apostrophe แค่ตัดสินแพ้ชนะเท่านั้น นั่นคือเหตุที่ a-b ตกข้าง ab แต่ยังเทียบได้มากกว่า

ลำดับข้อความของ Excel ถูกค้นพบอย่างไร

ลำดับข้อความของ Excel ถูกค้นด้วยการวัด ไม่ใช่ด้วยเอกสาร เพราะเอกสารของ Excel ไม่เคยเอ่ยชื่อ collation การทดสอบสุ่มคู่สตริงมา 4,000 คู่จากเครื่องหมายวรรคตอน ASCII ตัวเลข ทั้งตัวพิมพ์ใหญ่เล็ก ช่องว่าง é, ß, ä อักษรจีน full-width form และ non-breaking space ความยาว 0 ถึง 4 โดยครึ่งหนึ่งของคู่ถูกประกอบให้ต่างกันนิดเดียว Excel 16 ประเมิน IF(A<B,-1,IF(A=B,0,1)) ให้ทุกคู่ แล้วเอาคำตัดสินไปเทียบกับ Windows comparison API ด้วยชุด flag ต่าง ๆ

  • NORM_IGNORECASE เดี่ยว ๆ (word sort เริ่มต้น locale ผู้ใช้): ไม่มี mismatch แท้เลย 7 ต่างที่เจอคือเซลล์ที่เนื้อหาทั้งเซลล์เป็น ' ซึ่ง Excel กลืนไปเป็น text prefix character เป็นของเสียจากการสุ่ม ไม่ใช่ความต่างของ collation
  • NORM_IGNORECASE คู่กับ SORT_STRINGSORT: mismatch 41 จุด string sort ถือขีดกับ apostrophe เป็นสัญลักษณ์ธรรมดา ซึ่งเป็นพฤติกรรมที่ Excel ไม่มีเป๊ะ ๆ
  • เพิ่ม NORM_IGNOREWIDTH: ผิดอีกแบบ เพราะมันทำให้รูป full-width กับ half-width ของตัวอักษรเดียวกันเทียบเท่ากัน ขณะที่ Excel แยกพวกมันออกจากกัน

การเช็กชุดที่สองคัดมือ เทียบคู่ทั้ง 190 คู่ที่หยิบจากคำเข้ายาก 20 คำกับผล Range.Sort ของ Excel บนคอลัมน์เดียวกัน สองอย่างลงรอยกับ word sort แบบ NORM_IGNORECASE ธรรมดา และคำตัดสิน 190 คำพวกนี้บวกลำดับที่เรียงแล้วตอนนี้อยู่ในชุด regression ของ HotXLS รันผ่านทั้งเอนจินคลาสสิก TXLSWorkbook และเอนจินแท้ XLSX อย่าง TXLSXWorkbook

ทำไม CompareText กับการเทียบแบบ ordinal ถึงตอบผิด

CompareText กับการเทียบแบบ ordinal ตอบลำดับของ Excel ผิดเพราะพวกมันเทียบ UTF-16 code unit และลำดับ code point โยนเครื่องหมายวรรคตอนไปวางที่อับ ๆ ตามดวงเมื่อเทียบกับตัวอักษร ขีดคือ U+002D apostrophe คือ U+0027 ทั้งคู่ต่ำกว่าตัวอักษรทุกตัว การเทียบแบบ ordinal จึงบอกว่า "a-b" เล็กกว่า "ab" แทนที่จะถือขีดเป็นตัวตัดสินแพ้ชนะ ส่วน tilde U+007E สูงกว่าตัวอักษรทุกตัว "a~b" จึงออกมาใหญ่กว่า ตรงข้ามกับ Excel ทั้งหมด CompareText ใน Delphi RTL พับแค่ a..z ขึ้นเป็นตัวพิมพ์ใหญ่แล้วเทียบ code unit ซึ่งเติมความบิดเบี้ยวรอบที่สอง: ขีดล่าง U+005F นั่งอยู่ระหว่างตัวพิมพ์ใหญ่กับตัวพิมพ์เล็ก การพับเป็นตัวพิมพ์ใหญ่จึงเลื่อน "a_b" จากใต้ "ab" ขึ้นไปอยู่เหนือมัน และไม่มีฟังก์ชันไหนรู้ว่า é ควรอยู่ระหว่าง e กับ f

แผนภาพการเปรียบเทียบของ HotXLS คู่ลำดับ code point กับ word sort ของ Excel: การเทียบแบบ ordinal วาง apostrophe ขีด กับขีดล่างที่ 0x27, 0x2D กับ 0x5F รอบกลุ่มตัวอักษร a-b เทียบ ab จึงออกมาเป็นน้อยกว่า ขณะที่ word sort ผลักเครื่องหมายวรรคตอนไว้หน้าตัวเลขกับตัวอักษร และถือแค่ขีดกับ apostrophe เป็นตัวตัดสินแพ้ชนะ
code point โปรยเครื่องหมายวรรคตอนไปรอบตัวอักษร การเทียบแบบ ordinal กับแบบพับ ASCII จึงพลิกคำตัดสิน word sort เลื่อนเครื่องหมายวรรคตอนไปหน้าตัวเลข แล้วลดขีดกับ apostrophe ลงเป็นแค่ตัวตัดสินแพ้ชนะ

เครื่องมือ Delphi ทั่วไปตกอยู่สองฟากฝั่ง:

  • CompareStr, operator < ของ string กับ TComparer<string>.Default (ที่เรียก CompareStr) เป็นแบบ ordinal สนตัวพิมพ์ TArray.Sort<string> ที่ไม่ส่ง comparer จึงวาง Z ไว้หน้า f
  • CompareText กับ SameText เป็น ordinal หลังพับตัวพิมพ์แค่ช่วง ASCII
  • AnsiCompareText กับ WideCompareText ใน Delphi RTL บน Windows เรียก CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, ...) ซึ่งเป็น call เดียวกับที่ตรงกับ Excel TStringList ที่เรียงด้วยค่าเริ่มต้น (UseLocale True, CaseSensitive False) วิ่งผ่าน AnsiCompareText จึงลงรอยกับ Excel ด้วย
  • บนเป้าหมาย POSIX Delphi RTL ส่ง AnsiCompareText ไปผ่าน ICU collator ซึ่งเป็นอัลกอริทึมคนละแบบที่กฎเครื่องหมายวรรคตอนต่างกัน และ AnsiCompareText ของ Free Pascal บน Windows เรียก CompareStringA หลังแปลงเป็น ANSI code page ซึ่งทำอักขระที่ code page นั้นแทนไม่ได้หายไปทั้งตัว

ฟังก์ชัน RTL ที่รู้เรื่อง locale จึงถูกบน Windows ด้วย implementation ไม่ใช่ด้วยสัญญา และโค้ดที่ต้องการลำดับของ Excel ควรเรียก API นั้นเองชัด ๆ HotXLS เองก็เคยมีส่วนผสมแบบเดียวกันภายใน operator เปรียบเทียบยกตัวพิมพ์ใหญ่ทั้งคู่แล้วเทียบ code point กิ่ง > / < ของฟังก์ชันเกณฑ์ใช้การเทียบ Variant แบบสนตัวพิมพ์ของ Delphi และ VLOOKUP / HLOOKUP จับคู่ข้อความด้วยการเทียบ Variant สนตัวพิมพ์แบบเดียวกัน VLOOKUP("ABC",A1:A20,1,FALSE) จึงหา abc ไม่เจอ ส่วนการเรียงช่วงใช้ WideCompareText อยู่แล้ว สามเส้นทาง สามลำดับ

HotXLS v2.384.67 เปลี่ยนอะไรไป

ตั้งแต่ v2.384.67 การเทียบข้อความกับข้อความในเส้นทางคำนวณและเรียงลำดับของ HotXLS วิ่งผ่านฟังก์ชันเดียว คือ XlsCompareText ใน lxStandard.pas ซึ่งเรียก CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, ...) แล้วลบ CSTR_EQUAL ออก ผู้เรียกได้แก่ operator เปรียบเทียบทั้งหก การเทียบทีละ element ใน array formula กิ่ง >, <, >= กับ <= ของเกณฑ์สไตล์ COUNTIF และฟังก์ชันฐานข้อมูล, VLOOKUP กับ HLOOKUP (ทั้งตรงเป๊ะและประมาณ), ตัวช่วยจัดลำดับหลังฟังก์ชัน dynamic-array กับ XLOOKUP / XMATCH และการเรียงช่วงของทั้งสองเอนจิน การผลักการเรียงช่วงผ่านฟังก์ชันเดียวกันรับประกันว่าลำดับการเรียงกับลำดับการเปรียบเทียบจะไม่แยกทางกันอีก ซึ่งสำคัญเพราะ VLOOKUP แบบประมาณบนข้อความมีความหมายก็ต่อเมื่อคอลัมน์ถูกเรียงไว้ในลำดับเดียวกับที่ lookup เทียบ

แผนภาพเส้นทางของ HotXLS แสดงการเปรียบเทียบข้อความทุกเส้นทาง จาก operator เปรียบเทียบหกตัวกับเกณฑ์สไตล์ COUNTIF ผ่าน VLOOKUP, HLOOKUP, XLOOKUP กับการเรียงช่วงของทั้งสองเอนจิน ลู่เข้า XlsCompareText ที่เรียก CompareStringW ด้วย LOCALE_USER_DEFAULT กับ NORM_IGNORECASE แล้วแมป 1, 2, 3 เป็น -1, 0, 1
operator เกณฑ์ lookup กับการเรียงแชร์ฟังก์ชันเดียว ลำดับที่ Excel เห็นกับลำดับที่ HotXLS เรียงจึงแยกทางกันไม่ได้ API คืน 1, 2 หรือ 3 และศูนย์แปลว่าล้มเหลว ไม่ใช่น้อยกว่า
uses
  System.Variants, lxHandleX;

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Sheets.Add('Data');  // Calculate ประเมินบน active sheet
    Writeln(VarToStr(Book.Calculate('="a-b">"ab"')));   // True: ขีดแค่ตัดสินแพ้ชนะ
    Writeln(VarToStr(Book.Calculate('="a-b"="ab"')));   // False: เสมอกันแต่ไม่เท่ากัน
    Writeln(VarToStr(Book.Calculate('="a~b"<"ab"')));   // True: เครื่องหมายวรรคตอนมาก่อน
    Writeln(VarToStr(Book.Calculate('="ABC"="abc"')));  // True: ไม่สนตัวพิมพ์
  finally
    Book.Free;
  end;
end;

การเทียบคนละชนิดเป็นกฎแยกและไม่เปลี่ยน: ตัวเลขทุกตัวต่ำกว่าข้อความทุกค่า และข้อความทุกค่าต่ำกว่าค่า boolean ทุกค่า ตามที่เล่าไว้ในบทความเรื่องลูกโซ่การเปรียบเทียบ operand ว่าง และ SUMIF word sort ทำงานก็ต่อเมื่อ operand ทั้งสองเป็นข้อความเท่านั้น การจับคู่ wildcard ก็แยกต่างหาก เกณฑ์อย่าง "a*" หรือ "=ab" เป็นการทดสอบรูปแบบหรือความเท่ากัน เล่าไว้ในคู่มือ wildcard ของ Excel ใน COUNTIF, MATCH และ DSUM ส่วน collation ที่พูดถึงในนี้ตัดสินแค่ operator เรียงลำดับเท่านั้น

ตัวอย่างถัดไปโหลดคำทดสอบ 20 คำลงคอลัมน์ เรียงด้วย TXLSXWorksheet.SortRange แล้วเช็กจำนวนจากเกณฑ์กับ lookup หนึ่งครั้ง จำนวนที่ได้คือค่าที่ Excel 16 คืนสำหรับคอลัมน์เดียวกัน

const
  Words: array [0..19] of string = ('ab', 'a-b', 'a~b', 'a_b', 'AB', 'a b',
    'ab1', 'ab-', '-ab', 'abc', 'a''b', 'Ab', 'b', 'a.b', 'a1b', 'a0',
    #$00E9, 'e', 'f', 'Z');
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Words');
    for i := 0 to High(Words) do
      Sheet.Cells[i + 1, 1].Value := WideString(Words[i]);

    // Excel 16 บนคอลัมน์เดียวกัน: 11, 11, 14
    Writeln(VarToStr(Book.Calculate('=COUNTIF(A1:A20,">ab")')));
    Writeln(VarToStr(Book.Calculate('=COUNTIF(A1:A20,"<a-b")')));
    Writeln(VarToStr(Book.Calculate('=COUNTIF(A1:A20,">=AB")')));

    // เดิมเป็น #N/A ก่อน v2.384.67: lookup เทียบแบบสนตัวพิมพ์
    Sheet.Cells[1, 3].Formula := '=VLOOKUP("ABC",A1:A20,1,FALSE)';
    Book.Recalculate;
    Writeln(VarToStr(Sheet.Cells[1, 3].Value));                // abc

    // คอลัมน์ key เดียว เรียงขึ้น: a b, a.b, a_b, a~b, a0, a1b, ab, AB, Ab, ...
    Sheet.SortRange(1, 1, 20, 1, [1], [False]);
    for i := 1 to 20 do
      Writeln(VarToStr(Sheet.Cells[i, 1].Value));
  finally
    Book.Free;
  end;
end;

TXLSXWorksheet.SortRange ใช้ merge sort แบบเสถียร ab, AB กับ Ab ที่เทียบเท่ากันจึงคงลำดับสัมพัทธ์เดิมก่อนเรียง เซลล์ว่างไปท้ายทั้งสองทิศทาง เหมือนใน Excel

จะให้โค้ด Delphi ของตัวเองเรียงตาม Excel ทำอย่างไร

ให้โค้ด Delphi ของคุณได้ลำดับข้อความแบบ Excel เรียก CompareStringW ด้วย LOCALE_USER_DEFAULT กับ NORM_IGNORECASE และอย่าเติม SORT_STRINGSORT หรือ NORM_IGNOREWIDTH ค่าที่คืนไม่ใช่ผลเปรียบเทียบแบบมีเครื่องหมาย: API คืน CSTR_LESS_THAN (1), CSTR_EQUAL (2) หรือ CSTR_GREATER_THAN (3) และคืน 0 เมื่อ call ล้ม ลบ 2 เพื่อได้ธรรมเนียมติดลบ / ศูนย์ / บวกตามปกติ และเช็ก 0 ก่อน เพราะความล้มเหลวที่ถูกเข้าใจผิดเป็นผลลัพธ์จะกลายเป็น -2 ซึ่งคือ “น้อยกว่า” อย่างเงียบ ๆ

uses
  Winapi.Windows, System.SysUtils, System.Generics.Defaults,
  System.Generics.Collections;

// ลำดับข้อความแบบ Excel: word sort ตาม locale ผู้ใช้ ไม่สนตัวพิมพ์
function ExcelCompareText(const A, B: string): Integer;
var
  R: Integer;
begin
  R := CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
    PWideChar(A), Length(A), PWideChar(B), Length(B));
  if R = 0 then
    RaiseLastOSError;          // 0 คือความล้มเหลว ไม่ใช่ผลการเปรียบเทียบ
  Result := R - CSTR_EQUAL;    // 1/2/3 กลายเป็น -1/0/1
end;

var
  Keys: TArray<string>;
begin
  Keys := ['abc', 'a-b', 'AB', 'a~b', '-ab', 'ab'];
  TArray.Sort<string>(Keys, TComparer<string>.Construct(
    function(const L, R: string): Integer
    begin
      Result := ExcelCompareText(L, R);
    end));
  // a~b, ab / AB (เท่ากัน เรียงแบบไหนก็ได้), a-b, -ab, abc
end;

TArray.Sort ไม่เสถียร key ที่เทียบเท่ากันอย่าง ab กับ AB อาจออกมาสลับกันก็ได้ ถ้าลำดับเดิมของ key ที่เท่ากันสำคัญ เรียงผ่าน array ของ index โดยใช้ตำแหน่งเดิมเป็น key รอง กรณีตรงข้ามก็มีขึ้นเหมือนกัน บางทีคอลัมน์ต้องไม่เดินตามลำดับของ Excel เช่น part number ที่ X-100 กับ X100 เป็นรหัสต่างกันและควรเรียงตาม code point TXLSXWorksheet.SortRange มี overload ที่รับ TXLSSortCompareEvent เมธอดที่ลายเซ็นเป็น function(const Left, Right: Variant): Integer of object แล้วใช้มันแทนการเปรียบเทียบในตัว

uses
  System.SysUtils, System.Variants, lxStandard, lxHandleX;

type
  TPartNumberOrder = class
    function Compare(const Left, Right: Variant): Integer;
  end;

function TPartNumberOrder.Compare(const Left, Right: Variant): Integer;
begin
  // comparer แบบกำหนดเองก็ได้รับเซลล์ว่าง (เป็น Null) ด้วย: จัดที่เอง
  if VarIsNull(Left) or VarIsNull(Right) then
    Exit(Ord(VarIsNull(Left)) - Ord(VarIsNull(Right)));
  Result := CompareStr(VarToStr(Left), VarToStr(Right));   // ตาม code point สนตัวพิมพ์
end;

var
  Sheet: TXLSXWorksheet;   // sheet ที่มีข้อมูลแล้ว แถว 2..501 คอลัมน์ A..D
  Order: TPartNumberOrder;
begin
  // ...
  Order := TPartNumberOrder.Create;
  try
    // ใช้คอลัมน์ A เป็น key เรียงขึ้น
    Sheet.SortRange(2, 1, 501, 4, [1], [False], xlsSortByRows,
      xlsSortExcelLike, Order.Compare);
  finally
    Order.Free;
  end;
end;

เมื่อส่ง comparer แบบกำหนดเองเข้าไป HotXLS จะข้ามการจัดการเซลล์ว่างของตัวเองแล้วส่งค่า key ดิบให้ comparer ต้องจัดการ Null เอง key ที่เรียงลง HotXLS จะกลับเครื่องหมายทุกอย่างที่ comparer คืน ซึ่งพาเซลล์ว่างขึ้นหัวไปด้วย ถ้า comparer ไม่จัดการเรื่องนี้เอง เตือนตัวเองไว้ว่าคอลัมน์ที่เรียงแบบนี้ไม่ได้อยู่ในลำดับที่ VLOOKUP ประมาณของ Excel หรือ XLOOKUP แบบ binary search คาดไว้อีกต่อไป กับดักของสองโหมดนี้กับข้อมูลที่เรียงคนละลำดับเล่าไว้ในคู่มือโหมด binary search ของ XLOOKUP และ XMATCH

ทำไม workbook เดียวกันถึงเรียงไม่เหมือนกันบนอีกเครื่อง

workbook เดียวกันเรียงไม่เหมือนกันบนอีกเครื่องได้ เพราะลำดับข้อความของ Excel ผูกกับ Windows user locale และ HotXLS ตั้งใจตามความผูกนี้ word sort ขึ้นกับภาษา: collation ของสวีเดน เช่น วาง ä ไว้หลัง z ขณะที่อังกฤษกับเยอรมันคงมันไว้ข้าง a Excel สืบทอดสิ่งนี้จาก locale ที่ตัวมันรันอยู่ workbook ที่เพื่อนนักที่สตอก์ฮ์ล์มคำนวณใหม่จึงคืน COUNTIF(...,">y") ต่างจากไฟล์เดียวกันบนเดสก์ท็อปที่ชิคาโกได้ HotXLS ส่ง LOCALE_USER_DEFAULT เพื่อให้ผลของมันเท่ากับของ Excel บนเครื่องเดียวกัน ถ้าตรึง locale ตายตัว HotXLS จะขัดกับ Excel บนทุกเครื่องที่ตั้งค่าต่างออกไป

ผลที่ตามมาทางปฏิบัติสามข้อสำหรับการผลิตฝั่งเซิร์ฟเวอร์:

  • locale ที่นับคือของบัญชีที่ process รันอยู่ Windows service หรือ application pool ของ IIS อาจใช้รูปแบบภูมิภาคต่างจากเดสก์ท็อปของนักพัฒนา สิ่งที่เห็นใน IDE จึงไม่ใช่สิ่งที่ production คำนวณโดยอัตโนมัติ
  • ผลคำนวณที่แคชไว้ในไฟล์สะท้อน locale ของเครื่องที่ผลิต Excel คำนวณใหม่ด้วย locale ของตัวเอง ค่าหนึ่งจึงเปลี่ยนได้เมื่อไฟล์ถูกเปิดที่อื่นแล้วคำนวณใหม่ นั่นคือพฤติกรรมของ Excel ไม่ใช่ของเสียจาก HotXLS
  • locale ขัดกันเยอะที่ตัวอักษรมีวรรณยุกต์ กับกลุ่มอักษรที่บางภาษาถือเป็นตัวอักษรเดียว และสคริปต์ที่ไม่ใช่ละติน ข้อมูลทดสอบที่มีแต่คำอังกฤษธรรมดาจะไม่เผยปัญหานี้

เส้นแบ่งแพลตฟอร์มเรียบง่าย HotXLS เป็นไลบรารี Windows บิลด์สำหรับ Win32 กับ Win64 ด้วย Delphi กับ C++Builder และสำหรับเป้าหมาย win32 / win64 ด้วย Lazarus กับ Free Pascal ทุกบิลด์เรียก CompareStringW ตัวเดียวกัน ไม่มีเส้นทาง collation แยกสำหรับ non-Windows ของเดียวที่เหลือคือการ fallback เมื่อ API call ล้ม: ถ้า CompareStringW คืน 0 XlsCompareText จะเทียบสตริงที่ยกตัวพิมพ์ใหญ่แล้วตาม code unit แทนการ throw exception กลางการคำนวณใหม่ การคำนวณยังเดินต่อได้แต่ไม่ได้การันตีลำดับของ Excel อีก

สรุป: การเปรียบเทียบข้อความแบบ Excel ใน HotXLS

  • กฎ: word sort ตาม locale ผู้ใช้พร้อม NORM_IGNORECASE ไม่มี SORT_STRINGSORT ไม่มี NORM_IGNOREWIDTH ใน HotXLS ตั้งแต่ v2.384.67
  • - กับ ' แค่ตัดสินแพ้ชนะ: ="a-b">"ab" เป็น TRUE และ ="a-b"="ab" เป็น FALSE
  • เครื่องหมายวรรคตอนอื่นเรียงก่อนตัวเลข ตัวเลขก่อนตัวอักษร: ="a~b"<"ab" กับ ="a0"<"ab" เป็น TRUE
  • ตัวพิมพ์ไม่มีวันมีผล: ="ABC"="abc" เป็น TRUE และ VLOOKUP("ABC",...) หา abc เจอ
  • เส้นทางที่ครอบคลุม: operator เปรียบเทียบ การเทียบ array เกณฑ์ > / <, VLOOKUP / HLOOKUP, การจัดลำดับ dynamic-array, SortRange ในทั้งสองเอนจิน
  • ไม่อยู่ใต้กฎนี้: ชนิดผสม (ตัวเลข < ข้อความ < boolean) กับเกณฑ์ wildcard ซึ่งมีกฎของตัวเอง
  • ในโค้ด Delphi: CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, ...) เช็ก 0 ก่อน แล้วลบ CSTR_EQUAL เลี่ยง CompareText, CompareStr กับ TComparer<string>.Default เมื่อผลต้องตรงกับ Excel
  • ผลลัพธ์ขึ้นกับ locale ของบัญชีที่รันโค้ด ทั้งใน Excel และ HotXLS เหมือนกัน

คำธรรมดาเรียงเหมือนกันใต้ทุกกฎ มีแต่รหัสที่มีขีด เครื่องหมายวรรคตอน และชื่อที่มีวรรณยุกต์เท่านั้นที่เผย collation ที่ผิด HotXLS ตอนนี้ให้คำตอบแบบ Excel กับทุกกรณีพวกนั้นทั้งเอนจิน XLS และ XLSX รายละเอียดลิขสิทธิ์ เวอร์ชัน Delphi กับ C++Builder ที่รองรับ และดาวน์โหลดรุ่นทดลองอยู่ที่หน้า HotXLS Delphi Excel component