مقال تقني

قراءة وكتابة مصنفات XLSB الثنائية في Delphi

يقرأ HotXLS ويكتب ملفات Excel Binary Workbook (XLSB) من Delphi عبر واجهة TXLSXWorkbook نفسها المستخدمة لـ XLSX: يكشف Open مصنفاً ثنائياً من محتوى الحزمة، ويكتب SaveAs أو SaveAsChecked مع xlsxExcelBinaryWorkbook ملفاً ثنائياً. تغطي الواجهة الخلفية مجموعة فرعية متحقق منها وترفض أي شيء لا تستطيع تمثيله قبل أن تلمس الوجهة

هذه العبارة الأخيرة هي الجزء الذي يستحق القراءة ببطء. تبدو XLSB تنويعاً بسيطاً على XLSX، وفي مصطلحات التغليف هي كذلك فعلاً. تعيش المشكلة في التفاصيل التي تُرمِّزها الصيغتان بشكل مختلف، وفي ما تفترضه المكتبة بصمت عندما تجسر إحداهما إلى الأخرى. أكثر خلل في هذه الواجهة الخلفية إفادةً لم يكن سجلاً مشوهاً إطلاقاً. كان خطاً افتراضياً اختفى، وعمود تواريخ تحوّل إلى #### في Excel مع أن القيمة وكود التنسيق وعرض العمود كلها كانت صحيحة

ما ملف XLSB وكيف يختلف عن XLSX؟

ملف XLSB حزمة OPC ببنية أجزاء مطابقة تماماً لبنية مصنف XLSX، لكن أجزاء المصنف وورقة العمل والأنماط والسلاسل المشتركة تخزن سجلات ثنائية بدلاً من XML. الصيغة موصوفة في [MS-XLSB]، وكثيراً ما يُسمى ترميز السجلات BIFF12. لا يزال تحصل على [Content_Types].xml وأجزاء العلاقات وحاوية ZIP؛ ما يتغير هو أن xl/workbook.bin يحل محل xl/workbook.xml، و xl/worksheets/sheet1.bin يحل محل XML ورقة العمل، وهكذا

كل سجل BIFF12 يبدأ بترويسة مضغوطة متغيرة الطول. نوع السجل يأخذ بايتاً أو بايتَين، سبع بتات لكل بايت مع البت العالي بوصفه علماً استمرارية، فيتسع نوع السجل في 14 بتة. يليه حجم الحمولة في بايت إلى أربعة بالمخطط السباعي نفسه. السلاسل عدّاد محارف من 32 بتة يليه وحدات كود UTF-16. مقارنة بـ BIFF8 هذا تخطيط أنظف بكثير: لا شطر لسجل CONTINUE عند 8224 بايتاً، ولا أعلام سلاسل مضغوطة-أم-عريضة في منتصف السجل. ومقارنة بـ XLSX يتخطى تماماً ترميز XML، وهذا سبب ميل ملفات XLSB الكبيرة إلى أن تكون أصغر على القرص وأسرع فتحاً لدى Excel

تفصيلة تغليف واحدة تعثر كود الكشف. يسجل Excel جزء مصنف XLSB بنوع المحتوى application/vnd.ms-excel.sheet.binary.macroEnabled.main، حتى للمصنفات التي لا تحوي ماكرو. يفحص HotXLS ذلك التسجيل في [Content_Types].xml أولاً ولا يتراجع إلى امتداد الجزء .bin إلا حين يكون نوع المحتوى غائباً، وكلا المسارَين لا بد أن يجتازا مدقق السجلات الصارم. القواعد العامة حول أسماء الأجزاء والعلاقات وأنواع المحتوى هي ذاتها المشمولة في لماذا يصلح Excel ملف XLSX صالحاً وقواعد حزمة OPC خلفه

متى تختار XLSB بدلاً من XLSX؟

اختر XLSB عندما يشترطها نظام لاحق أو عندما تتبادل مصنفات كبيرة غنية بالقيم مع مستخدمي Excel يهتمون بزمن الفتح؛ وإلا فتبقى XLSX الافتراضي الأأمن. الـ XLSX معيار ISO (ISO/IEC 29500) بدعم أدوات واسع، وهو قابل للفحص بأداة ZIP ومحرر نصوص، وكل منتج جداول يقرأه. أما XLSB فصيغة Microsoft بمواصفة منشورة لكنها بنظام بيئي أضيق

عملياً تأتي الطلبات من اتجاهين. بعض خطوط التقارير والأرشيفات استقرت على .xlsb منذ سنوات وترفض غيرها. وفريق آخر يولّد مصنفات بمئات آلاف الخلايا الرقمية ووجد أن المستخدمين يفتحون النسخة الثنائية أسرع بوضوح. كلاهما سبب مشروع. أما "تبدو أكثر كفاءة" فليس سبباً، لأن الواجهة الخلفية الثنائية في HotXLS تجهّز الحزمة الكاملة في الذاكرة، فليست الأداة الصائبة لصادرات خادم ذات ذاكرة محدودة. وتلك الحالات طريقها كاتب XLSX المتدفق

فتح XLSB وحفظه عبر TXLSXWorkbook

لا يحتاج TXLSXWorkbook.Open أي تلميح صيغة: الاستدعاء نفسه يحمل XLSX أو XLSM أو XLSB، وبعد تحميل ثنائي ناجح يبلّغ TXLSXWorkbook.SourceFormat عن xlsxExcelBinaryWorkbook. الكتابة صريحة. الـ SaveAs(FileName) دون صيغة ينتج دائماً مصنف Open XML، حتى عندما ينتهي اسم الملف بـ .xlsb، فالإخراج الثنائي دائماً يسمّي الصيغة

uses
  SysUtils, lxHandleX;

procedure ConvertToXlsb(const Source, Target: string);
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // يكشف Open صيغة XLSX أو XLSM أو XLSB من الحزمة نفسها
    if Book.Open(Source) <> 1 then
      raise Exception.Create('Input is unsupported or invalid');

    // الامتداد وحده لا يختار إخراجاً ثنائياً أبداً
    if Book.SaveAsChecked(Target, xlsxExcelBinaryWorkbook) <> 1 then
      raise Exception.Create('XLSB output is unsupported or failed');
  finally
    Book.Free;
  end;
end;

مخرجات التدفق تمر عبر SaveAs(Stream, xlsxExcelBinaryWorkbook) أو التحميل الزائد التدفقي لـ SaveAsChecked. داخلياً يوجَّه كلاهما إلى الطريقتين الخاصتَين SaveAsXlsbStream و SaveAsXlsbFile؛ ولا تستدعيهما مباشرة أبداً. يُجهَّز إخراج الملف في ملف مؤقت شقيق ويُودَع باستبدال نهائي واحد. ويجب أن تكون وجهة التدفق قابلة للقراءة والتموضع، لأن الكاتب يلتقط لقطة لبايتاتها وحجمها وموضعها ويعيدها إذا فشلت النسخة النهائية. يبلّغ TXLSXWorkbook.LastSaveUsedStreaming عن False بعد حفظ ثنائي، وهو بلاغ دقيق: الحزمة كاملة توجد في الذاكرة قبل أن تصل بايتة واحدة إلى وجهتك

لماذا مجموعة فرعية متحقق منها بدلاً من وفاء كامل؟

لا يحتفظ HotXLS بنموذج كائنات ثانٍ لـ XLSB. عند الفتح، يحول XlsbToOpenXml الحزمة الثنائية إلى حزمة Open XML في الذاكرة ويحملها محلل XML العادي؛ وعند الحفظ يبني كاتب XML حزمة ويشفّرها OpenXmlToXlsb بوصفها سجلات ثنائية. كل ميزة إذن لا بد أن تنجو من ترجمتين، والميزة التي لا تستطيع الجسر ترجمتها بدقة تُرفض بدلاً من أن تُقرَّب

المجموعة المدعومة كما في HotXLS 2.384.113 واسعة بما يكفي لأغلب مصنفات البيانات: القيم المطبوعة بالأنواع، والسلاسل المشتركة والمضمنة، والخطوط الشائعة، والتعبئات والحدود وتنسيقات الأعداد، وارتفاعات الصفوف وعرض الأعمدة، ومعادلات الخلايا العادية بنتائج مخزنة، والأسماء المعرفة، والخلايا المدمجة، والروابط التشعبية، والألواح المجمدة والمقسومة، ونطاقات AutoFilter بمعايير مخصصة بسيطة، وجداول أوراق العمل العادية. وخارجها تجلس المعادلات المشتركة والمصفوفية، والدوال المستقبلية والمعرفة من المستخدم، ومراجع المصنفات الخارجية، والمخططات والرسومات، و PivotTables و slicers، و VBA، والتنسيق الشرطي، والتحقق من صحة البيانات، وإعدادات الحساب غير الافتراضية كالحساب اليدوي أو التكراري

الرفض إلزامي لا استشاري. يسرد TXLSXWorkbook.GetConversionReport(xlsConversionExcelBinaryWorkbook) الميزات غير المدعومة قبل أن تحفظ، وتفشل تلك المدخلات الحفظ حتى مع AAllowLossy = True أو SaveAs دون فحص. ذلك مقصود. إسقاط PivotTable بصمت من نسخة ثنائية هو نوع الخسارة التي لا يلاحظها أحد حتى يجلس المصنف أمام عميل

uses
  Classes, lxHandleX, lxDiagnostics;

function CanSaveAsXlsb(Book: TXLSXWorkbook; Log: TStrings): Boolean;
var
  Report: TXLSConversionReport;
  I: Integer;
begin
  Report := Book.GetConversionReport(xlsConversionExcelBinaryWorkbook);
  try
    Result := True;
    for I := 0 to Report.Count - 1 do
      if Report[I].Fidelity = xlsConversionUnsupported then
      begin
        Result := False;
        Log.Add(Report[I].SheetName + ' ' + Report[I].Location + ': ' +
          Report[I].Feature + ' - ' + Report[I].Message);
      end;
  finally
    Report.Free;   // المستدعي يملك التقرير
  end;
end;

يغطي التقرير النموذج الحي. وتبقى تحقق الحزمة النهائي البوابة الأخيرة للأشياء التي لا يراها النموذج قط، كسجلات مشوهة أو خصائص غير مألوفة في الملف المصدر، فتقرير نظيف يليه حفظ فاشل ممكن، ونتيجة الحفظ هي ما تتصرف على أساسه

لماذا اختفى الخط الافتراضي وتنسيق 0.00؟

اختفت قراءة أنماط XML اختفت لأن قارئ أنماط XML وضع افتراضَين صحيحَين لملفات يكتبها HotXLS بنفسه لكنهما لا يصحان لمصنفات يكتبها Excel. حفظ الجسر الموردَين كليهما بشكل مثالي؛ وحدثت الخسارة طبقةً واحدة فوقه، في مسار Open العام. تلك الفجوة هي الدرس الرئيسي لهذه الواجهة الخلفية: جسر صيغة صحيح لا يضمن رحلة ذهاب وعودة عامة وفيّة

الافتراض الأول يخص fonts[0]. عندما يحفظ HotXLS ملف XLSX، يكتب خطاً افتراضياً ضمنياً في الخانة صفر، ويتخطى قارئ XML لديه تلك الخانة حتى تبدأ مجموعة Fonts المواجهة للمستخدم عند الفهرس صفر. أما ملف XLSB مؤلف بـ Excel فيحمل افتراضه الصريح في تلك الخانة، وتخطيه ألقى ذلك الاختيار بعيداً. أما الافتراض الثاني فيخص تنسيقات الأعداد. كان القارئ يحوّل قيم numFmtId المخصصة فقط (164 وما فوق) إلى NumberFormats؛ وبقيت المعرفات المدمجة صفراً. خلية فارغة منسوبة تحمل التنسيق المدمج 2، وهو 0.00، كانت تعود General

يعيش الإصلاح كلياً في مسار الاستيراد الثنائي. بعد أن تجتاز الحزمة الكاملة الفحص المسبق، يضبط فتح XLSB نمط أنماط خاصاً للتحليل XML المتزامن فقط. في ذلك النمط يحمل القارئ كل خط شاملاً الخانة صفر ويزيح كل fontId واحداً، ويحوّل كل معرف مدمج مدعوم إلى كود تنسيق صريح في النموذج، ولا يزال يستخدم numFmtId الأصلي لكشف التواريخ، فيحتفظ تنسيق تاريخ مدمج بدلالته التاريخية. ويمسح كتلة finally ذلك النمط دائماً، ويُمسح حالة الخط الافتراضي المحفوظة أيضاً عند Open لاحق عادي لـ XLSX أو ODS

كيف يؤثر fonts[0] على عرض أعمدة Excel؟

يحسب Excel عرض الأعمدة من الخط الافتراضي للمصنف في fonts[0]، لا من الخطوط المطبقة على خلايا ذلك العمود. تخزن خاصية width للعمود بوحدات محارف من أقصى عرض رقم للخط الافتراضي، فتغيير fonts[0] يغيّر عرض البكسل لكل عمود، شاملاً الأعمدة التي تستخدم خلاياها خطاً مختلفاً كلها

أنتج ذلك أكثر عرَض مربكاً في التحقيق كله. كان المصنف المعاد حفظه بقيم خلايا متطابقة، وأكواد تنسيق متطابقة، وخصائص width متطابقة، وخط الخلية ونمط Normal كلاهما يشيران إلى الخط المحفوظ، ومع ذلك ما يزال Excel يعرض عمود تواريخ على هيئة ####. كان fonts[0] المكتوب قد رجع إلى Calibri 11، فتقلص أقصى عرض رقم، ولم تعد التواريخ تلائم. يحفظ HotXLS الآن خط المصدر الافتراضي ويكتبه عائداً بوصفه fonts[0]، متراجعاً إلى Calibri 11 فقط للمصنفات التي لم تكن ثنائية المصدر قط. رياضيات العرض الكاملة في عرض عمود Excel وأقصى عرض رقم (MDW) في Delphi

التنسيقات المدمجة المعتمدة على الإعدادات المحلية تُرفض لا تُخمَّن

معرف تنسيق عدد مدمج بلا كود تنسيق صريح يُقبل فقط عندما يكون معناه ثابتاً؛ أما المعرفات المعتمدة على الإعدادات المحلية فتُرفض أثناء الفحص المسبق للحزمة. يحوّل HotXLS المعرفات من 0 إلى 22 ومن 37 إلى 40 ومن 45 إلى 49 إلى أكواد معروفة مثل 0.00 و 0% و mm:ss و @. والمعرفات المتبقية دون 164 تنتمي إلى تنسيقات يتوقف عرضها على الإعدادات الإقليمية لتثبيت Excel، ويرفضها الاستيراد برسالة "A locale-dependent number format requires an explicit format code"

ابتداع كود لتلك المعرفات أسوأ من الفشل. نمط تاريخ شرق آسيوي مُخمَّن يبدو معقولاً، ويعود ذهاباً وإياباً دون خطأ، ويعرض الشيء الخطأ لنصف مستخدميك. والتوقيت مهم أيضاً: يعمل الفحص في الفحص المسبق للحزمة الكاملة، قبل أن تُمسح حالة المصنف الحالي، فيترك الملف المرفوض النموذجَ المحمَّل سابقاً واسم الملف وصيغة المصدر دون مساس. تغطي مقالة أرقام تسلسل التواريخ و numFmt في Excel كيفية تفاعل معرفات التواريخ المدمجة مع نظامَي التاريخ 1900 و 1904

procedure LoadBinaryWorkbook(Book: TXLSXWorkbook; const FileName: string);
var
  I: Integer;
begin
  // الإدخال الثنائي يحتاج تحميلاً كاملاً متحمساً؛ الأنماط الانتقائية ترفض مبكراً
  Book.MetadataOnly := False;
  Book.SelectedSheets.Clear;

  if Book.Open(FileName) <> 1 then
  begin
    for I := 0 to Book.Diagnostics.Count - 1 do
      Writeln(Book.Diagnostics[I].Message);
    Exit;   // المصنف المحمّل سابقاً ما يزال سليماً
  end;

  // إخراج المعادلات يحتاج مخزناً ما لم يعيد الحفظ الحساب
  Book.RecalcBeforeSave := xrmAlways;
end;

قواعد عملية لـ XLSB في كود الإنتاج

الواجهة الخلفية الثنائية آمنة الاعتماد عندما تعامل رفضها بوصفها بيانات لا بوصفها أخطاء تُكتم. قلّة من القواعد تنبع من السلوك أعلاه

  • سمِّ الصيغة في كل حفظ ثنائي؛ امتداد .xlsb وحده ما يزال ينتج XLSX
  • شغّل GetConversionReport(xlsConversionExcelBinaryWorkbook) قبل حفظ مصنفات حررها المستخدمون واعرض الميزات غير المدعومة عليهم
  • أبقِ MetadataOnly مطفأً و SelectedSheets فارغة للإدخال الثنائي، لأن التحميل الكسول كان سيتطلب الاحتفاظ بأرشيف محوَّل
  • امنح المعادلات نتيجة مخزنة أو أعد الحساب قبل الحفظ، لأن مخزناً مفقوداً يُرفض تحت xrmManual وتحت xrmIfDirty نظيف
  • احسب الذاكرة للحزمة المحوَّلة كاملة زائد الإخراج الثنائي؛ استخدم XLSX المتدفق عندما يجب أن تبقى الذاكرة محدودة

عند الاختبار، قارن مع Excel عبر الواجهة البرمجية العامة لا عند مستوى الجسر. كان انحدارا الخط الافتراضي و 0.00 كلاهما يجتاز اختبارات الجسر، وكلاهما ظهر فوراً في مصنف مؤلف بـ Excel فُتح وأعيد حفظه عبر Open و SaveAsChecked

الأسئلة الشائعة

هل يكشف HotXLS ملفات XLSB تلقائياً؟

نعم. يفحص TXLSXWorkbook.Open نوع المحتوى المسجل لجزء المصنف ويحقق من السجلات الثنائية، فالاستدعاء نفسه يحمل XLSX و XLSB، ويبلّغ SourceFormat عن أيهما قُرئ

لماذا ما يزال SaveAs باسم ملف .xlsb يكتب XLSX؟

لا يستنتج HotXLS صيغة الإخراج من الامتداد قط. مرّر xlsxExcelBinaryWorkbook إلى SaveAs أو SaveAsChecked لكتابة مصنف ثنائي

ماذا يحدث عندما يستخدم مصنف ميزات خارج مجموعة XLSB الفرعية؟

يفشل الحفظ قبل أن تتغير الوجهة، حتى مع السماح بالحفظ الضار. يسرد GetConversionReport الميزات المانعة مسبقاً، ويشرح Diagnostics الفشل بعدها

دعم XLSB وتقرير التحويل وبقية واجهة المصنف تأتي ضمن HotXLS، مكوّن الجداول الأصلي لـ Delphi و C++Builder