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

การเขียนไฟล์ XLSX แบบ ISO 29500 Strict จาก Delphi

HotXLS เขียนสมุดงาน Open XML แบบ ISO/IEC 29500 Strict จาก Delphi และ C++Builder ด้วยการตั้งค่าพร็อพเพอร์ตี้เพียงตัวเดียวคือ StrictOOXML ก่อนบันทึก ทุกส่วนในแพ็กเกจ ตั้งแต่ xl/workbook.xml ไปจนถึงไฟล์ความสัมพันธ์และประเภทเนื้อหา จะถูกเขียนด้วยคำศัพท์ purl.oclc.org แบบ strict แทนที่จะเป็น schemas.openxmlformats.org แบบ transitional และคุณสมบัติที่ Strict ไม่อนุญาตจะถูกปฏิเสธด้วยข้อยกเว้นที่ชัดเจน แทนที่จะถูกเขียนออกไปอยู่ดี

นักพัฒนาส่วนใหญ่พบข้อกำหนดนี้ผ่านเอกสารการจัดซื้อจัดจ้าง การประมูลของภาครัฐในหลายเขตอำนาจศาลเรียกร้องรูปแบบ Open XML ที่ผ่านการกำหนดมาตรฐาน ISO ไม่ใช่รูปแบบ transitional ที่ Office เขียนเป็นค่าเริ่มต้น และคลังเก็บที่บังคับใช้ ISO 29500 Strict จะปฏิเสธ .xlsx ธรรมดา แม้ว่า Excel จะเปิดไฟล์นั้นได้อย่างสมบูรณ์แบบก็ตาม เนมสเปซ transitional มีไว้เพื่อรองรับพฤติกรรมไฟล์ไบนารีดั้งเดิม ส่วนเนมสเปซ strict คือมาตรฐานที่แท้จริง

อะไรคือความแตกต่างที่แท้จริงระหว่าง Strict กับ Transitional

ความแตกต่างที่มองเห็นได้คือคำศัพท์ ส่วนสมุดงานแบบ strict ประกาศ http://purl.oclc.org/ooxml/spreadsheetml/main เป็นเนมสเปซรากของมัน และ http://purl.oclc.org/ooxml/officeDocument/relationships สำหรับการอ้างอิงความสัมพันธ์ และไม่มีเนมสเปซ transitional ใดสามารถหลงเหลืออยู่ที่ใดในแพ็กเกจได้เลย ประเภทความสัมพันธ์ก็เปลี่ยนตามไปด้วย ดังนั้นส่วนความสัมพันธ์ระดับรากจึงใช้ชื่อ .../ooxml/officeDocument/relationships/officeDocument แทนที่ค่าเทียบเท่าแบบ openxmlformats ที่คุ้นเคย และประเภทของคุณสมบัติขยายก็ใช้รูปแบบ camelCase เป็น extendedProperties

ความแตกต่างที่มองไม่เห็นคือขอบเขต Strict ตัดทอนส่วนของสคีมา transitional ที่มีอยู่เพียงเพื่อ round-trip ไฟล์ไบนารีดั้งเดิมออกไปอย่างจงใจ พร้อมกับส่วนขยายของผู้ผลิตที่ Office เพิ่มเข้ามาภายหลัง นี่คือเหตุผลที่การแปลงไม่ใช่แค่การค้นหาและแทนที่สตริง คุณสมบัติบางอย่างไม่มีการสะกดแบบ strict อยู่เลย และต้องไม่ถูกเขียนออกมาเลยด้วยซ้ำ

การเปิดใช้งาน

โค้ดการสร้างเอกสารตามปกติไม่ต้องเปลี่ยนแปลง สร้างสมุดงานตามวิธีที่คุณทำอยู่เสมอ ตั้งค่าแฟล็ก แล้วบันทึก:

var
  Wb: TXLSXWorkbook;
  Sh: TXLSXWorksheet;
begin
  Wb := TXLSXWorkbook.Create;
  try
    Sh := Wb.Sheets.Add('Data');
    Sh.Cells[1, 1].Value := 'Product';
    Sh.Cells[1, 2].Value := 'Amount';
    Sh.Cells[2, 1].Value := 'Widget';
    Sh.Cells[2, 2].Value := 17;
    Sh.Cells[3, 2].Formula := '=SUM(B2:B2)';

    Wb.StrictOOXML := True;          // ผลลัพธ์แบบ ISO/IEC 29500 Strict
    if Wb.SaveAs('archive-copy.xlsx') <> 1 then
      raise Exception.Create('strict save failed');
  finally
    Wb.Free;
  end;
end;

แฟล็กนี้จะถูกรีเซ็ตทุกครั้งเมื่อเริ่มการบันทึก และถูกกำหนดค่าใหม่จากพร็อพเพอร์ตี้ของสมุดงาน ดังนั้นข้อยกเว้นระหว่างการบันทึกครั้งหนึ่งจึงไม่สามารถรั่วไหลโหมด strict เข้าไปในการบันทึกครั้งถัดไปได้ รายละเอียดนี้สำคัญในกระบวนการเซิร์ฟเวอร์ที่อ็อบเจกต์สมุดงานตัวเดียวให้บริการคำขอส่งออกหลายรายการ

เหตุใดการบันทึกแบบ strict จึงปฏิเสธการทำงานได้

คุณสมบัติสี่กลุ่มเป็นส่วนขยายของ Microsoft ที่ไม่มีค่าเทียบเท่าใน ISO 29500 Strict และ HotXLS จะยกข้อยกเว้นขึ้นในเวลาที่บันทึก แทนที่จะสร้างแพ็กเกจที่อ้างว่าสอดคล้องกับ strict ทั้งที่ไม่เป็นเช่นนั้น:

// ผลลัพธ์แบบ Strict ไม่สามารถฝังโปรเจกต์ VBA ได้
//   -> ให้บันทึกสมุดงานที่มีมาโครเป็น .xlsm แบบ transitional แทน
// ผลลัพธ์แบบ Strict ไม่สามารถพกตัวควบคุมฟอร์มได้
//   -> ปุ่ม เช็กบ็อกซ์ คอมโบบ็อกซ์ และ ctrlProps ของมัน
// ผลลัพธ์แบบ Strict ไม่สามารถพกความคิดเห็นแบบธรีดได้
//   -> โมเดล persons/threads สมัยใหม่ ไม่ใช่โน้ตแบบคลาสสิก
// ผลลัพธ์แบบ Strict ไม่สามารถพกเมทาดาทาอาร์เรย์ไดนามิกได้
//   -> ช่วง spill ที่บันทึกผ่านส่วนเมทาดาทา

การล้มเหลวอย่างชัดเจนคือทางเลือกที่ถูกต้องในกรณีนี้ โปรเจกต์ VBA ที่ถูกทิ้งไปอย่างเงียบๆ จะเปลี่ยนสมุดงานที่ใช้งานได้ให้กลายเป็นสมุดงานที่เสียหายแต่ยังคงเปิดได้ และรายงานความล้มเหลวจะมาถึงจากผู้ใช้อีกหลายสัปดาห์ให้หลัง ข้อยกเว้นระบุชื่อคุณสมบัติและพร็อพเพอร์ตี้ที่ต้องเปลี่ยน ในขณะที่โค้ดที่เรียกใช้งานยังคงรู้ว่ากำลังส่งออกอะไรอยู่ การเก็บรักษามาโครและลิงก์ภายนอกในเส้นทาง transitional อธิบายไว้ในการเก็บรักษาโปรเจกต์ VBA และลิงก์ภายนอก

ส่วนขยายสองกลุ่มถูกจัดการแตกต่างออกไป และควรรู้เหตุผลไว้ Data bars, sparklines และคุณสมบัติที่คล้ายกันอยู่ในคำศัพท์ x14 และ xm ส่วนภาพแบบ SVG อยู่ใน c15 สิ่งเหล่านี้เป็นเนื้อหารายการส่วนขยายที่เนมสเปซของมันบอกความหมายในตัวเอง ตัวแยกวิเคราะห์สเปรดชีตทั่วไปยอมรับได้ และไม่มีค่าเทียบเท่า ISO ให้แปลงไปหา HotXLS จึงเก็บสิ่งเหล่านี้ไว้แทนที่จะทิ้งเนื้อหาของผู้ใช้ไปเฉยๆ หากตัววาลิเดตในไปป์ไลน์ของคุณเข้มงวดกับส่วนขยายเช่นเดียวกับเนมสเปซ ให้ลบคุณสมบัติเหล่านั้นออกจากสมุดงานต้นทางก่อนส่งออก

การแปลต้องเข้าถึงส่วนที่ปกติแล้วไม่มีใครเขียนใหม่

ปัญหาทางวิศวกรรมที่น่าสนใจในผลลัพธ์แบบ strict ไม่ใช่ XML ของเวิร์กชีต แต่เป็นส่วนที่ตัวเขียนแบบเร็วอยากจะคัดลอกตรงๆ มากกว่า HotXLS เก็บรักษาธีม การเชื่อมต่อ ลิงก์ภายนอก แผนภูมิ และก้อนข้อมูล pivot ด้วยการคัดลอกไบต์ที่บีบอัดแล้วดั้งเดิมผ่านตรงๆ ซึ่งเป็นสิ่งที่ถูกต้องอย่างยิ่งสำหรับความเที่ยงตรง แต่ผิดอย่างยิ่งสำหรับผลลัพธ์แบบ strict เพราะไบต์ที่คัดลอกมานั้นพกเนมสเปซ transitional มาด้วย

ภายใต้ StrictOOXML เส้นทางการเก็บรักษาทั้งห้าจะเปลี่ยนไปใช้การสร้างขึ้นใหม่หรือการเล่นซ้ำแบบแปลความหมาย โดยข้ามเส้นทางเร็วแบบคัดลอกไบต์ไป XML ทั้งหมดผ่านรูทีนแปลความหมายเดียว ซึ่งยึดกับค่าแอตทริบิวต์ที่อยู่ในเครื่องหมายคำพูดคู่ เพื่อไม่ให้สตริงที่ดูเหมือน URI ภายในเซลล์ถูกเขียนทับโดยไม่ตั้งใจ ข้อความในเซลล์ที่มี URI เดียวกันจะถูกเอสเคปเป็นเอนทิตีใน XML ดังนั้นการแทนที่ที่ยึดไว้จึงมองไม่เห็นมัน ตัวเขียนแบบสตรีมมิงแปลโครงร่างของมันก่อน แล้วจึงแบ่งที่ sheetData เพราะบล็อกแถวไม่มี URI คำศัพท์ใดๆ เลย กลไกที่เกี่ยวข้องสำหรับเส้นทางการเก็บรักษาอธิบายไว้ในการ round-trip แบบไม่สูญเสียข้อมูลของธีม รายการส่วนขยาย และ calcChain

การอ่านไฟล์ที่ Excel บันทึกแบบ strict

ผลลัพธ์เป็นเพียงครึ่งหนึ่งของเรื่องราวเท่านั้น Excel มีตัวเลือกการบันทึกชื่อ "Strict Open XML Spreadsheet" และไฟล์ที่สร้างขึ้นด้วยวิธีนั้นต้องเปิดได้อย่างถูกต้อง HotXLS ทำให้ประเภทความสัมพันธ์เป็นมาตรฐานเดียวกันที่ทุกจุดที่แยกวิเคราะห์ความสัมพันธ์ในแพ็กเกจ ได้แก่ ราก ลิงก์ภายนอก เวิร์กชีต ภาพวาด และตาราง pivot ดังนั้นประเภทความสัมพันธ์แบบ strict จึงตรงกับค่าคงที่ภายในเดียวกับคู่ของมันแบบ transitional

คู่ตรงข้ามในฝั่งตัวอ่านคือการทำให้พรีฟิกซ์เนมสเปซเป็นมาตรฐานเดียวกัน ซึ่งยอมให้พรีฟิกซ์ตามอำเภอใจและคำศัพท์ทั้งสองแบบคลี่คลายไปเป็นตารางชื่อมาตรฐานเดียว งานนี้เป็นประโยชน์ทั้งกับไฟล์ธรรมดาและไฟล์แบบ strict เพราะตัวสร้างของบุคคลที่สามผูกพรีฟิกซ์ได้อย่างอิสระ และนี่คือกลไกเดียวกับที่อธิบายไว้ในการคลี่คลายความสัมพันธ์ OPC ในแพ็กเกจ XLSX

รายการตรวจสอบสั้นๆ ก่อนส่งมอบผลลัพธ์แบบ strict

ตรวจสอบด้วยแพ็กเกจ ไม่ใช่ด้วย Excel Excel เปิดได้ทั้งสองรูปแบบอย่างสบายใจ ดังนั้นการเปิดสำเร็จจึงไม่พิสูจน์อะไรเลยเกี่ยวกับความสอดคล้องตามมาตรฐาน แตกไฟล์ zip ของผลลัพธ์แล้วยืนยันว่า xl/workbook.xml ประกาศเนมสเปซ purl ไม่มีส่วนใดมี schemas.openxmlformats.org/spreadsheetml และประเภทความสัมพันธ์ใน _rels/.rels กับ xl/_rels/workbook.xml.rels ใช้รูปแบบ strict

จากนั้นเปิดไฟล์ใหม่ผ่าน HotXLS และเปรียบเทียบค่า สูตร รูปแบบ และไฮเปอร์ลิงก์กับต้นฉบับ การทดสอบอ่านย้อนกลับเป็นวิธีเดียวที่ประหยัดในการพิสูจน์ว่าการแปลไม่ได้ทำลายเนื้อหา และในขณะเดียวกันก็ทดสอบการทำให้เป็นมาตรฐานในฝั่งตัวอ่านไปด้วย หากสมุดงานของคุณมีแผนภูมิ ให้ตรวจสอบสิ่งเหล่านั้นด้วย เพราะส่วนแผนภูมิเป็นหนึ่งในส่วนที่เก็บรักษาไว้ซึ่งเปลี่ยนไปใช้เส้นทางสร้างขึ้นใหม่ภายใต้โหมด strict

ผลลัพธ์แบบ strict การอ่านแบบยืดหยุ่น และการเก็บรักษาแบบไม่สูญเสียข้อมูล ล้วนเป็นส่วนหนึ่งของเอนจิน OOXML เดียวกันสำหรับ Delphi และ C++Builder รายการคุณสมบัติทั้งหมดอยู่ที่หน้าคอมโพเนนต์สเปรดชีต HotXLS สำหรับ Delphi