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

สูตร array ใน HotXLS: ทำไม Excel ถึงใส่ @ กับ #VALUE!

Excel 365 แทรก @ ลงในสูตรอย่าง =SUM(A1:B1*{10,100}) แล้วโชว์ #VALUE! เมื่อไฟล์เก็บสูตรนั้นเป็นสูตรธรรมดา เพราะ Excel จะเอา implicit intersection แบบเก่าไปคาบทุก operand ของ operator ตั้งแต่ v2.384.68 HotXLS Delphi Component เก็บสูตรพวก array-operator แบบเดียวกับที่ Excel 365 ทำ คือเป็นสูตร dynamic-array ในเซลล์เดียวใน XLSX และเป็นสูตร array หนึ่งเซลล์ใน XLS

อาการนี้รอดจาก code review มาได้สบาย service Delphi ของคุณเขียน workbook เสร็จ HotXLS คำนวณใหม่แล้วแคช 210 ไว้ให้ =SUM(A1:B1*{10,100}) แต่ลูกค้าเปิดใน Excel 16 กลับเห็น =SUM(@A1:B1*@{10,100}) ใน formula bar กับ #VALUE! ในเซลล์ ในไฟล์ไม่มีอะไรเสียหายเลย สิ่งที่ขาดไปคือ metadata ที่บอก Excel ว่าสูตรนี้เขียนอยู่ใต้กฎของ dynamic array และถ้าไม่มีมัน Excel จะกลับไปใช้โมเดลประเมินผลยุคก่อน dynamic array

ทำไม Excel 365 ถึงใส่ @ ให้สูตรที่ HotXLS คำนวณถูกแล้ว

Excel 365 ใส่ @ เพราะสูตรที่ไม่มีเครื่องหมาย dynamic array นิยามแล้วคือสูตร legacy และสูตร legacy จะยุบช่วงหลายเซลล์เหลือเซลล์เดียวทุกที่ที่ operator ต้องการค่าเดียว การยุบนั่นแหละคือ implicit intersection: Excel เลือกเซลล์ของช่วงที่อยู่แถวเดียวกับสูตร (ถ้าช่วงแนวตั้ง) หรือคอลัมน์เดียวกัน (ถ้าช่วงแนวนอน) และถ้าไม่มีเซลล์แบบนั้นเลย ผลลัพธ์คือ #VALUE! Excel 365 คงความหมายนี้ไว้กับสูตรสไตล์เก่า แล้วแสดง @ ให้เห็นการยุบนั้นชัด ๆ

ลองวาง =SUM(A1:B1*{10,100}) ไว้ที่ E5 แล้วการอ่านแบบ legacy จะเห็นชัดทันที A1:B1 เป็นช่วงแนวนอน สูตรอยู่คอลัมน์ E ช่วงนี้ไม่มีเซลล์ในคอลัมน์ E @A1:B1 จึงเป็น #VALUE! และ SUM ทั้งอันสืบทอดค่านั้นไป ส่วนใต้กฎ dynamic array ข้อความเดียวกันจะคูณกันทีละตัว 1 × 10 + 2 × 100 ได้ 210 เอนจินสูตรของ HotXLS ประเมินผลแบบ dynamic array มาตั้งแต่รีลีส v2.384.61 กับ v2.384.63 แค่รูปแบบไฟล์ไม่เคยบอกเรื่องนี้เท่านั้นเอง เมื่อ A1:B2 มี 1, 2, 3 กับ 4 นี่คือสูตรทดสอบกับสิ่งที่ Excel 16 แสดง:

แผนภาพ HotXLS เทียบการประเมินผลแบบ implicit intersection กับ dynamic array ของ SUM(A1:B1*{10,100}) ในเซลล์ E5: โมเดล legacy หาเซลล์ของช่วงแนวนอน A1:B1 ในคอลัมน์ E ไม่เจอจึงคืน #VALUE! ส่วนโมเดล dynamic array คูณ 1 ด้วย 10 และ 2 ด้วย 100 ได้ 210
Excel แทรก @ ในสูตรธรรมดาแล้วโชว์ #VALUE! เพราะ implicit intersection หาอะไรในคอลัมน์ E ไม่เจอ พอมีเครื่องหมาย dynamic array ของ HotXLS สูตรเดียวกันจะคูณกันทีละตัวและลงที่ 210
สูตรผลจาก HotXLSExcel 16 เมื่อเก็บเป็นสูตรธรรมดาที่เก็บตั้งแต่ v2.384.68
=SUM(A1:B1*{10,100})210#VALUE!Dynamic array, Excel แสดง 210
=SUM((A1:B2>2)*1)2Implicit intersection, ผิดหรือ errorDynamic array, Excel แสดง 2
=SUMPRODUCT((A1:B2>2)*1)2Implicit intersection, ผิดหรือ errorDynamic array, Excel แสดง 2
=MAX(A1:B2-1)3Implicit intersection, ผิดหรือ errorDynamic array, Excel แสดง 3
=SUM(A1:B2)1010สูตรธรรมดา ไม่เปลี่ยน

แถวสุดท้ายสำคัญพอ ๆ กับสี่แถวแรก SUM(A1:B2) ส่งช่วงเข้าพารามิเตอร์ฟังก์ชันที่รับ reference ตรง ๆ ไม่มี operator เห็นช่วงหลายเซลล์เลย intersection จึงเกิดขึ้นไม่ได้ Excel 365 เองก็เซฟสูตรนี้เป็นสูตรธรรมดา และ HotXLS ทำเช่นเดียวกัน

HotXLS เก็บสูตร array-operator ลง XLSX กับ XLS อย่างไร

HotXLS เขียนสูตร array-operator ใน XLSX เป็น dynamic array หนึ่งเซลล์ คือ element <c> แบก cm="1" สูตรเป็น <f t="array" ref="E5"> และแพ็กเกจได้ xl/metadata.xml เพิ่มมาพร้อม metadata type แบบ XLDAPR ที่ extension ของมันถือ dynamicArrayProperties fDynamic="1" attribute cm เป็นดัชนีเริ่มที่ 1 ชี้เข้าบล็อก cellMetadata ของ part นั้น และ record XLDAPR ที่อยู่หลังมันต่างหากที่บอก Excel ว่า “ประเมินตัวนี้ใต้กฎ dynamic array” โครงสร้างนี้ก็คือแบบเดียวกับที่ Excel 16 เขียนเมื่อคุณพิมพ์สูตรเดียวกันแล้วเซฟ ซึ่งเป็นวิธีที่ layout เป้าหมายถูกกำหนดขึ้นมาตั้งแรก

ใน XLS ไม่มี part สำหรับ metadata จึงเหลือ construct เดียวของ BIFF8 สำหรับการประเมิน array คือสูตร array หนึ่งเซลล์ เซลล์จะได้ record FORMULA ที่ token stream เป็น PtgExp ตัวเดียวชี้หาตัวเอง ตามด้วย record ARRAY ($0221) ที่แบกสูตรที่ parse จริงครอบช่วงหนึ่งเซลล์ Excel 365 เขียนสูตร dynamic array ลง XLS ด้วยวิธีเดียวกัน และ Excel รุ่นเก่าที่เปิดไฟล์จะเห็นเป็นสูตร array แบบ Ctrl+Shift+Enter คลาสสิก

แผนภาพการเก็บของ HotXLS สำหรับสูตร array-operator SUM(A1:B1*{10,100}): เอนจิน XLSX เขียน dynamic array หนึ่งเซลล์ที่ cm เท่ากับ 1 มี element f แบบ array กับ record XLDAPR ใน xl/metadata.xml ที่ต้องใช้ GUID ตัวเล็กทั้งหมด ส่วนเอนจิน XLS เขียน record FORMULA ที่มี PtgExp คู่กับ record ARRAY 0221
เอนจิน XLSX ทำเครื่องหมายเซลล์ด้วย cm=1 บวก record metadata XLDAPR ส่วนเอนจินคลาสสิกจับคู่ FORMULA ที่มี PtgExp กับ record ARRAY ครอบหนึ่งเซลล์ และ Excel 365 เซฟ dynamic array ลง XLS ด้วยวิธีเดียวกัน

ไม่มี API ใหม่เกี่ยวข้อง การทำเครื่องหมายเกิดตอนคุณ assign สูตรผ่าน cell API ปกติ ในทั้งสองเอนจิน ฝั่ง XLSX คือ TXLSXCell.Formula:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Data');
    Sheet.Cells[1, 1].Value := 1;
    Sheet.Cells[1, 2].Value := 2;
    Sheet.Cells[2, 1].Value := 3;
    Sheet.Cells[2, 2].Value := 4;

    // Operator บนช่วงหรือ inline array: เก็บเป็น dynamic array
    Sheet.Cells[5, 5].Formula := '=SUM(A1:B1*{10,100})';
    Sheet.Cells[6, 5].Formula := '=SUM((A1:B2>2)*1)';
    // ช่วงส่งเข้าฟังก์ชันตรง ๆ: คงเป็น <f> ธรรมดา
    Sheet.Cells[7, 5].Formula := '=SUM(A1:B2)';

    if Book.Recalculate = lxOk then
      Writeln(VarToStr(Sheet.Cells[5, 5].Value));   // 210

    // root ของ array คงข้อความไว้โดยไม่มี '=' นำหน้า
    Writeln(Sheet.Cells[5, 5].Formula);              // SUM(A1:B1*{10,100})

    Book.SaveAs('probe.xlsx');   // E5 กับ E6 ได้ cm="1" + t="array"
  finally
    Book.Free;
  end;
end;

หลังแปลงแล้ว TXLSXCell.Formula คืนข้อความโดยไม่มี = รูปแบบเดียวกับที่ TXLSXRange.SetDynamicArrayFormula เก็บ โค้ดที่เทียบสตริงสูตรหลัง assign จึงควร normalize ตัว = นำหน้าเสียก่อน

เอนจินคลาสสิกทำตามกฎเดียวกันผ่าน IXLSRange.Formula บนเซลล์เดียว การ assign สูตรจะถูกย้ายเส้นทางไป path ของ array หนึ่งเซลล์เบื้องหลัง XLS ที่เซฟออกมาจึงมีคู่ FORMULA กับ ARRAY:

var
  Wb: IXLSWorkbook;
  Sh: TXLSWorksheet;
begin
  Wb := TXLSWorkbook.Create;
  Sh := Wb.Sheets.Add;
  Sh.Range['A1', 'A1'].Value := 1;
  Sh.Range['B1', 'B1'].Value := 2;
  Sh.Range['A2', 'A2'].Value := 3;
  Sh.Range['B2', 'B2'].Value := 4;

  Sh.Range['E5', 'E5'].Formula := '=SUM(A1:B1*{10,100})';  // record ARRAY
  Sh.Range['E6', 'E6'].Formula := '=MAX(A1:B2-1)';         // record ARRAY
  Sh.Range['E7', 'E7'].Formula := '=SUM(A1:B2)';           // FORMULA ธรรมดา

  Writeln(VarToStr(Sh.Range['E5', 'E5'].Value));   // 210
  Writeln(VarToStr(Sh.Range['E6', 'E6'].Value));   // 3
  Wb.SaveAs('probe.xls');
end;

ถ้าสิ่งที่ anchor คือผลลัพธ์หลายเซลล์ ไม่ใช่ค่ารวมสเกลาร์ API แบบระบุชัดยังเป็นเครื่องมือที่ถูกทาง คือ SetArrayFormula สำหรับสี่เหลี่ยมที่กำหนดขนาดไว้แล้ว ตามที่เล่าไว้ในสูตร dynamic array spill ด้วย HotXLS หรือ TXLSXRange.SetDynamicArrayFormula เมื่อคุณอยากได้เครื่องหมาย dynamic array ของ XLSX บนช่วงที่กำหนดขนาดเอง เส้นทางอัตโนมัติในบทความนี้ครอบคลุมแค่สูตรที่พิมพ์ลงเซลล์เดียวเท่านั้น

สูตรแบบไหนที่ HotXLS ทำเครื่องหมายเป็น dynamic array

HotXLS ทำเครื่องหมายสูตรเฉพาะเมื่อ operator มี operand subtree ที่ผลิต array ออกมา การตรวจรันบน syntax tree ที่คอมไพล์แล้ว และ operand จะผลิต array ได้ถ้ามันเป็นช่วงหลายเซลล์ array constant แบบ inline หรือนิพจน์ operator อื่นที่ตัวมันเองมี operand แบบนั้น วงเล็บโปร่งใส operator ที่นับคือตัวคณิต (+ - * / ^) การต่อสตริง (&) การเปรียบเทียบหกแบบ unary plus กับ unary minus และ percent:

  • A1:B1*{10,100}, (A1:B2>2)*1, --(B1:B2>0) กับ A1:B2-1 ถูกทำเครื่องหมาย ไม่ว่าจะโผล่ตรงไหนในสูตร รวมถึงข้างใน SUMPRODUCT
  • SUM(A1:B2) กับ SUMPRODUCT(A1:A2,{1;10}) ไม่ถูกทำเครื่องหมาย เพราะช่วงกับ array เดินเข้าอาร์กิวเมนต์ฟังก์ชันตรง ๆ ไม่มี operator แตะต้องเลย
  • A1*2 หรือ SUM(A1,B1)*2 ไม่ถูกทำเครื่องหมาย: reference หนึ่งเซลล์กับผลลัพธ์ฟังก์ชันเป็นสเกลาร์ในสายตาการตรวจนี้

ขอบเขตสามข้อนี้ตั้งใจไว้ หนึ่ง การทำเครื่องหมายเกิดเฉพาะเมื่อสูตรถูกป้อนผ่าน API หมายความว่า TXLSXCell.Formula ในเอนจิน XLSX และการ assign Formula หรือ Value บนเซลล์เดียวในเอนจินคลาสสิก สูตรที่โหลดจากไฟล์จะเขียนกลับตรงตามที่เจอ เพราะสูตร legacy จาก producer อื่นอาจพึ่ง implicit intersection ตั้งใจไว้แต่แรก สอง ข้อความที่ไม่มีทั้ง : และ { จะถูกข้ามโดยไม่คอมไพล์รอบสอง สาม สูตรที่จะ spill อย่าง =A1:B1*2 ลอย ๆ จะถูกทำเครื่องหมายเป็น dynamic array หนึ่งเซลล์ที่ anchor ไว้ตรงที่คุณวาง HotXLS ไม่ spill ให้ และ Excel จะกางผลลัพธ์ไปเซลล์ข้างเคียงตอนคำนวณใหม่รอบหน้า

กฎเรื่อง operand นี้เป็นราวพี่น้องของกฎ argument class ที่เล่าไว้ในimplicit intersection ของ defined names ใน HotXLS บทความนั้นพูดถึงพารามิเตอร์ฟังก์ชันที่ประกาศเป็น value class ส่วนบทความนี้พูดถึง operator ซึ่งในโมเดล legacy ต้องการค่าเสมอ

เอนจินคำนวณเปลี่ยนอะไรไปเพื่อให้ผลลัพธ์ตรงกัน

การแก้ฝั่งจัดเก็บใน v2.384.68 ยืนอยู่บนความจริงที่เอนจินสูตรของ HotXLS คืนค่าแบบ Excel 365 มาก่อนแล้ว ซึ่งผ่านการแก้หลายรอบในทั้งสองเอนจิน จุดที่เห็นชัดสุดคือ SUMPRODUCT: จนถึง v2.384.61 มันรับแต่ช่วงธรรมดาตั้งแต่สองช่วงขึ้นไป SUMPRODUCT((B1:B2>0)*1), SUMPRODUCT(--(B1:B2>0)) กระทั่ง SUMPRODUCT(B1:B2) ที่ให้อาร์กิวเมนต์เดียวเลยคืน #N/A ตอนนี้ HotXLS ประเมินอาร์กิวเมนต์แบบนิพจน์ทีละ element ตามกฎของ Excel:

  • ทุกอาร์กิวเมนต์ต้องมีรูปร่างเท่ากันเป๊ะ สเกลาร์นับเป็น 1 × 1 ไม่งั้นผลลัพธ์คือ #VALUE!
  • ค่า error ที่อยู่ข้างในอาร์กิวเมนต์ใด ๆ จะถูกคืนเป็นผลลัพธ์
  • element แบบข้อความกับตรรกะนับเป็น 0 ยังต้องใช้ (B1:B2>0)*1 หรือ -- ครอบอยู่ดีเพื่อเปลี่ยน TRUE เป็น 1
  • อาร์กิวเมนต์ที่เป็นช่วงธรรมดาล้วนยังใช้ streaming loop เดิม ช่วงใหญ่ ๆ จึงไม่ถูกแปลงเป็น array เต็มตัว

ตระกูล SUM (SUM, COUNT, AVERAGE, MIN, MAX, COUNTA) ใช้ evaluator แบบทีละ element ตัวเดียวกันเมื่ออาร์กิวเมนต์เป็นนิพจน์ operator บนช่วง =SUM((B1:B2>0)*1) จึงนับทั้งสองแถว ไม่ใช่ก้มมองแต่เซลล์แรก v2.384.62 ทำให้ space intersection operator คืนสี่เหลี่ยมร่วมของ reference สองตัว โดยคืน #NULL! เมื่อไม่ทับกัน =SUM(A1:B2 B1:B2) จึงได้ 6 ไม่ใช่ 2 และผลลัพธ์ป้อนเข้าพารามิเตอร์แบบ reference อย่าง ROWS กับ INDEX ได้ v2.384.63 เพิ่ม array constant แบบ inline อย่าง {1,2;3,4} (คอมมาคั่นคอลัมน์ เซมิโคลอนคั่นแถว) กับ reference union อย่าง (A1:B2,D4) เข้า parser การเปรียบเทียบแบบทีละ element ยังให้ element ว่างใช้ชนิดของอีกฝั่ง เป็น FALSE เมื่อเทียบกับค่าตรรกะ ตรงกับกฎสเกลาร์จาก v2.384.53 ที่เล่าไว้ในลูกโซ่การเปรียบเทียบกับเซลล์ว่างใน HotXLS

var
  V: Variant;
begin
  // Book คือ TXLSXWorkbook จากตัวอย่างแรก;
  // active sheet ของมันมี A1:B2 = 1, 2, 3, 4
  V := Book.Calculate('=SUMPRODUCT((A1:B2>2)*1)');   // 2
  V := Book.Calculate('=SUMPRODUCT(A1:B2)');          // 10, อาร์กิวเมนต์เดียว
  V := Book.Calculate('=SUMPRODUCT(A1:A2,{1;10})');   // 31 = 1*1 + 3*10
  V := Book.Calculate('=SUM(A1:B2 B1:B2)');           // 6, ช่วงร่วม B1:B2
  V := Book.Calculate('=SUM((A1:B2,B1:B2))');         // 16, ส่วนทับกันนับสองครั้ง
  V := Book.Calculate('=ROWS({1,2,3;4,5,6})');        // 2
  V := Book.Calculate('=TRUE*1');                     // 1, เดิมเป็น -1 ก่อน v2.384.61
end;

TXLSXWorkbook.Calculate ประเมินสตริงสูตรบน active sheet โดยไม่เก็บลงไฟล์ เป็นวิธีเช็กพฤติกรรมเอนจินที่ไวมาก แต่มีข้อควรระวังกับตัว @ เอง: HotXLS รับ @ คั่นกลาง reference สองตัวเป็น binary intersection มาแต่ไหนแต่ไร และตอนนี้ประเมินรูปแบบนั้นด้วยความหมาย intersection จริง ๆ ขณะที่ใน Excel 365 @ เป็น prefix บอก implicit intersection แบบ unary อย่าเขียน @ ลงในข้อความสูตรแล้วหวังความหมายแบบ Excel ใช้ช่องว่างสำหรับ intersection แล้วปล่อยกฎการจัดเก็บด้านบนจัดการความหมาย dynamic array เอาเอง

ทำไม Excel ถึงปฏิเสธเปิดไฟล์หรือคำนวณค่าผิด

การที่ Excel ยอมรับเครื่องหมาย dynamic array ต้องผ่านการแก้สามจุดที่เทสต์ round-trip กับตัวเองไม่มีทางจับเจอ เพราะ HotXLS อ่าน output ของตัวเองถูกต้องในทุกกรณี ทุกจุดถูกค้นพบด้วยการเปิด output ของ HotXLS ใน Excel 16 แล้วเปลี่ยนตัวแปรทีละหนึ่ง:

  1. GUID ของ extension ต้องเป็นตัวเล็กทั้งหมด ext uri ใน xl/metadata.xml ต้องเป๊ะเป็น {bdbb8cdc-fa1e-496e-a857-3c3f30c029c3} เทมเพลต HotXLS รุ่นเก่าสะกดปนตัวใหญ่ Excel 16 จึงปฏิเสธเปิดทั้งแพ็กเกจ ไม่ใช่แค่เซลล์ workbook ที่สร้างด้วย TXLSXRange.SetDynamicArrayFormula ก่อน v2.384.68 เจอปัญหาเดียวกัน
  2. ข้อความของ array root ไม่แบก = นำหน้า ตัวเขียน XLSX ปล่อยข้อความที่เก็บไว้ของ array root ลง <f> ตรง ๆ ถ้าเซลล์ที่แปลงแล้วยังติด = element จะอ่านได้ <f t="array" ref="E5">=SUM(...)</f> ซึ่ง Excel ก็ปฏิเสธตอนเปิดเหมือนกัน HotXLS ตัดมันทิ้งตอนแปลง TXLSXCell.Formula จึงอ่านกลับมาไม่มีตัวนั้น
  3. Double(True) ใน Delphi เป็น -1 การแปลง Variant เดินตามธรรมเนียม COM ที่ TRUE คือบิตเปิดทุกบิต และ VarIsNumeric(True) ก็คืน True เหมือนกัน ก่อน v2.384.61 ทำให้ =TRUE*1 คืน -1 กับให้ element array แบบตรรกะถูกจัดประเภทเป็นตัวเลข การเปรียบเทียบอย่าง (B1:B2>0)=TRUE จึงเพี้ยน ตอนนี้ HotXLS เช็ก varBoolean ก่อนถือ Variant เป็นตัวเลขทั้งในเลขคณิตสเกลาร์ เลขคณิต array และการจัดประเภท element ของ array และ TRUE นับเป็น 1

operand class ของ BIFF8: รายละเอียดระดับไบต์สำหรับคนเขียน format

ใน BIFF8 token operand ทุกตัวแบก operand class ของมันไว้ในไบต์ token เอง และ Excel เชื่อ class นั้นมากกว่าโครงสร้างของสูตร [MS-XLS] นิยาม class เป็นฟิลด์ PtgDataType สองบิตในบิตที่ 5 กับ 6 ของ token: 1 สำหรับ reference 2 สำหรับ value 3 สำหรับ array ห้าบิตต่ำตั้งชื่อ token ดังนั้น area reference ตัวเดียวกันมีสามการสะกด:

Tokenclass แบบ referenceclass แบบ valueclass แบบ array
PtgRef$24$44$64
PtgArea$25$45$65
PtgArray$20$40$60

HotXLS เคยพลาดสามจุดในนี้ในที่ต่าง ๆ กัน แต่ละจุดก็ให้อาการต่างกันใน Excel ทั้งที่อ่านกลับใน HotXLS ราบรื่น:

  • array constant class แบบ reference ตัว encoder เลือก class จาก context พารามิเตอร์ของ SUM กับ ROWS เป็น reference class =SUM({1,2}) จึงถูกเขียนด้วย PtgArray เป็น $20 แล้ว Excel แสดงสูตรทั้งอันเป็น =#N/A array constant เป็น reference ไม่ได้เด็ดขาด ตั้งแต่ v2.384.63 HotXLS จึงเขียน class แบบ array $60 ทุกที่ที่ context เรียกหา reference
  • operand ของ PtgIsect กับ PtgUnion เป็น class แบบ value operator ทวิภาครับ operand แบบ value ซึ่งถูกกับ * แต่ผิดกับ reference operator พอเป็น area $45 นำหน้า PtgIsect ($0F) Excel จะอ่าน =SUM(A1:B2 B1:B2) เป็น =SUM(@A1:B2 @B1:B2) แล้วคืน #VALUE! ตั้งแต่ v2.384.62 operand ของ PtgIsect กับ PtgUnion ($10) ถูกเขียนเป็น reference class คือ $25
  • operand แบบ value ข้างใน record ARRAY Excel ใช้ implicit intersection แม้ข้างในสูตร array เมื่อ operand เป็น value class HotXLS เขียน $45 ตรงนั้น สูตร array หนึ่งเซลล์ของ =SUM(A1:B1*{10,100}) จึงประเมินได้ 10 ใน Excel ตั้งแต่ v2.384.68 token stream ของ record ARRAY จะเลื่อน reference ทุกตัวที่เป็น value class กับ array constant ขึ้นเป็น array class คือ $65 กับ $60 ซึ่งตรงกับที่ Excel เขียน
แผนภาพ BIFF8 ของ HotXLS: บิตที่ 5 กับ 6 ของไบต์ token แต่ละตัวเลือก class แบบ reference value หรือ array PtgArea จึงสะกดเป็น 25, 45 กับ 65 พร้อมสามข้อบกพร่องที่แก้แล้ว: array constant เป็น 20 โชว์ #N/A, operand ของ PtgIsect เป็น 45 คืน #VALUE!, และ operand ใน record ARRAY เป็น 45 ทำให้ SUM(A1:B1*{10,100}) คืน 10
token operand ของ BIFF8 ทุกตัวแบก class ไว้ในบิตที่ 5 กับ 6 และ Excel เชื่อบิตพวกนี้มากกว่าโครงสร้าง ส่วน HotXLS เขียน array constant เป็น 60, operand ของ PtgIsect เป็น 25 และเลื่อน token ใน record ARRAY ขึ้นเป็น array class

reader ที่เมิน class bits จะ round-trip ทั้งสามกรณีได้สบาย ถ้าคุณดูแล BIFF8 writer ของตัวเองอยู่ เทียบ class bits ของ token operand ทุกตัวกับไฟล์ที่ Excel เซฟจากสูตรเดียวกัน อย่าเทียบแค่หมายเลข token

สรุปไว้เช็กเร็ว ๆ

  • Excel 365 แสดง @ เมื่อ operator ในสูตรธรรมดาที่ไม่มีเครื่องหมายได้รับช่วงหลายเซลล์หรือ inline array
  • HotXLS v2.384.68 ขึ้นไปเก็บสูตรแบบนี้เป็น dynamic array หนึ่งเซลล์ใน XLSX (cm="1", t="array", metadata XLDAPR) และเป็นสูตร array หนึ่งเซลล์ใน XLS (FORMULA ที่มี PtgExp คู่กับ ARRAY $0221)
  • นับเฉพาะ operand ของ operator ช่วงที่ส่งตรงเข้าอาร์กิวเมนต์ฟังก์ชันยังเป็นสูตรธรรมดา
  • ทำเครื่องหมายเฉพาะสูตรที่ป้อนผ่าน TXLSXCell.Formula หรือ Formula / Value แบบเซลล์เดียวฝั่งคลาสสิก สูตรที่โหลดมาไม่ถูกแตะ
  • เซลล์ root ที่แปลงแล้วอ่านกลับมาไม่มี = นำหน้า
  • GUID ของ ext uri สำหรับ dynamic array ต้องเป็นตัวเล็ก ไม่งั้น Excel ปฏิเสธทั้งแพ็กเกจ
  • ใน Delphi Double(True) เป็น -1 เช็ก varBoolean ก่อนแปลงเป็นตัวเลข
  • BIFF8: array constant ไม่มีวันเป็น reference class, operand ของ PtgIsect / PtgUnion เป็น reference class, operand ใน record ARRAY เป็น array class

HotXLS อ่าน เขียน และคำนวณ workbook XLS กับ XLSX จาก Delphi กับ C++Builder โดยตรง และเก็บสูตร array-operator ไว้ให้ Excel 365 เปิดแล้วเจอค่าเดียวกับที่ HotXLS คำนวณ ดูHotXLS Delphi spreadsheet component สำหรับรุ่น เอกสาร และดาวน์โหลดรุ่นทดลอง