مقال تقني

HotXLS Delphi Component: ODS open, save, and round-trip في Delphi

توضح المقالة أن دعم OpenDocument موجود على واجهة XLSX لا واجهة XLS، وأن التصدير يستخدم TODSExportOptions. كما تسجل ما يقرأه الاستيراد وما يتجاوزه عمداً، وتتعامل مع round trip غير المتناظر عبر فحص ملف ODS قبل الالتزام بالاستيراد

خلفية تقارير Delphi أصدرت ملفات ‎.xlsx‎ لسنوات تواجه متطلبًا جديدًا: قواعد المشتريات لدى عميل من القطاع العام تفرض خرج OpenDocument Spreadsheet، والمحللون في ذلك الحساب يعيدون تعديلاتهم كملفات ‎.ods‎ محفوظة من LibreOffice. فالآن يجب على الكود نفسه أن يكتب ODS وأن يقرأه أيضًا. HotXLS، مكتبة جداول بيانات Object Pascal الأصلية من losLab لـDelphi وC++Builder، تتولى الاتجاهين دون أي تثبيت لـExcel أو LibreOffice في أي مكان. الاتجاهان متقاربان لكنهما ليسا مطابقين. فمنذ HotXLS 2.88.229 يعيد مسار الاستيراد الصيغ كصيغ حية جنبًا إلى جنب مع نتائجها المخبأة، كما تقرأ الإصدارات الحالية الأنماط وتنسيقات الأرقام والنطاقات المدمجة والتعليقات والصور والمخططات والتنسيقات الشرطية. وما زال لا يصمد هو دلالات OpenFormula الكاملة وحفنة من تفاصيل الحزمة، لذا فالفريق الذي يفترض مرآة كاملة سيظل يصطاد حالات حدية في مكان ما بين مراجعة العميل والتقرير التالي، غالبًا من دون أي خطأ يشير إليه

دعم ODS يعيش في واجهة XLSX، لا في واجهة XLS

يشحن HotXLS تسلسلين هرميين مستقلين للأصناف في حزمة واحدة: TXLSWorkbook في وحدة lxHandle لملفات ‎.xls‎ الثنائية بصيغة BIFF8، وTXLSXWorkbook في وحدة lxHandleX لحزم OOXML بصيغة ‎.xlsx‎. كل نقطة دخول لـOpenDocument - OpenODS وSaveAsODS وGetODSSheetNames - تتعلق بـTXLSXWorkbook. هذا الموضع ليس اعتباطيًا. حزمة ODS، كما تحددها مواصفة OASIS ODF 1.3، هي أرشيف مضغوط يحمل عضو mimetype وبيانًا وجسم content.xml، وهذا يجعلها ابنة عم بنيوية لأرشيف OOXML؛ أما BIFF8 فهو تدفق سجلات ثنائي من تسعينيات القرن الماضي لا يشترك في شيء معها

لذلك الموضع حافة عملية: مصنف ‎.xls‎ قديم لا يمكن أن يصبح ‎.ods‎ باستدعاء واحد. تبني جسرًا لمحتوى BIFF إلى نموذج XLSX أولًا، عبر SaveXLSWorkbookAsXLSX من وحدة lxXlsxExport، ثم تعيد فتح الناتج عبر TXLSXWorkbook، ثم تصدّر من هناك. الجسر ليس عديم الفقد، ويستحق معرفة الثغرات قبل البناء عليه. إنه ينسخ القيم والصيغ وتنسيقات الأرقام والخطوط والتعبئات والحدود والنطاقات المدمجة وعروض الأعمدة وارتفاعات الصفوف. ويُسقط التعليقات والمخططات والتنسيق الشرطي. مصدر ‎.xls‎ ذو تنسيق ثقيل سيصل إلى ODS أبسط مظهرًا مما غادر، وهذه خاصية للجسر، لا لكاتب ODS

مخطط لتخطيط أصناف HotXLS داخل Delphi حيث تسكن كل نقاط دخول ODS على TXLSXWorkbook ويحمل جسر SaveXLSWorkbookAsXLSX محتوى .xls بـ BIFF8 عابرًا
تتعلى كل نقطة دخول OpenDocument على TXLSXWorkbook، ولا يبلغ ملف .xls القديم ODS إلا عبر جسر BIFF إلى XLSX الفاقد

الكشف في جهة الاستيراد تلقائي. طريقة Open العادية تتعرف على حزمة ODS من عضوها mimetype، وتلجأ إلى فحص content.xml في المستوى الأعلى عندما يكون ذلك العضو غائبًا، لذا فإن مسار كود عام من نوع "افتح أيًا كان ما رفعه المستخدم" لا يحتاج أي استنشاق امتداد خاص به. بعد الفتح، خاصية SourceFormat تُبلغ أي فرع تم تفعيله

التصدير إلى ODS باستخدام TODSExportOptions

استدعاء التصدير نفسه سطر واحد؛ أما كائن الخيارات المحيط به فيحمل القرارات التي سيسأل عنها مراجع لاحقًا:

var
  Book: TXLSXWorkbook;
  Opts: TODSExportOptions;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarterly-report.xlsx');
    Opts := TODSExportOptions.Create;        // المستدعي يملك هذا الكائن ويحرره
    try
      Opts.Generator := 'ReportService 4.2'; // تجاوز meta:generator
      Opts.IncludeCharts := True;
      Opts.IncludeImages := True;
      Book.SaveAsODS('quarterly-report.ods', Opts);
    finally
      Opts.Free;
    end;
  finally
    Book.Free;
  end;
end;

المستدعي يملك كائن الخيارات. لن يحرره HotXLS، وهذا سبب وجود try..finally الداخلي وعدم كونه اختياريًا. الخاصيتان اللتان تغيّران الخرج فعليًا، لا مجرد وسمه، تستحقان نظرة أقرب. ضبط IncludeCharts := False يفعل أكثر من إخفاء المخططات: إنه يزيل المستندات الفرعية للمخططات وإدخالات البيان الخاصة بها من الحزمة تمامًا، وهذا بالضبط ما تريده عندما يكون المستهلك خط أنابيب بيانات سيتعثر بها. Generator يتجاوز سلسلة meta:generator الخاصة بـODF، التي تقرأ خلاف ذلك HotXLS/<version>؛ تجاوزها عندما تبصم أدوات لاحقة منتجي الملفات لتوجيه الدعم. إذا لم ينطبق شيء من ذلك، تخطَّ كائن الخيارات كليًا. استدعاء SaveAs(FileName, xlsxOpenDocumentSpreadsheet) مطابق لـSaveAsODS بالإعدادات الافتراضية، والحمولات الزائدة من نوع تدفق في كليهما تتيح لك كتابة الحزمة مباشرة إلى استجابة HTTP دون ملف مؤقت

ما الذي يقرؤه مسار الاستيراد - وما الذي لا يزال يتخطاه

اقرأ هذا الجزء بعناية قبل أن تعِد أي أحد بدقة رحلة ذهاب وإياب. بدأ استيراد ODS في HotXLS مسارًا خفيفًا يحتفظ بالقيم العددية ونتائج الصيغ المخبأة فقط، وقد نما منذ ذلك أبعد بكثير. الإصدارات الحالية تقرأ content.xml مع styles.xml وsettings.xml: القيم العددية ونتائج الصيغ المخبأة، والصفوف والأعمدة المتكررة موسّعة إلى الشبكة، والنطاقات المدمجة، والنص الغني، والروابط التشعبية، والتعليقات، والصور والمخططات المضمّنة، وأنماط الخلايا الموروثة والافتراضية، وتنسيقات الأرقام والتواريخ، وارتفاعات الصفوف وعروض الأعمدة، والنطاقات المسماة، وتثبيت الأجزاء، ونطاقات AutoFilter، والتحقق من صحة البيانات، والتنسيقات الشرطية. وما OpenODS لا يزال لا يحوّله هو دلالات OpenFormula الكاملة، ومفاتيح حماية ODS، وهندسة التعليقات، والتنسيق المتقدم للتعليقات، وميزات الحزمة الواقعة خارج محتوى جدول البيانات

الصيغ هي الجزء الأرجح أن يعضّك. خلية ODF تخزن شيئين جنبًا إلى جنب: تعبير الصيغة، مكتوبًا بلهجة OpenFormula المعرَّفة في ODF 1.3 الجزء 4، وآخر قيمة حسبها التطبيق المُنتِج لها. الإصدارات المبكرة كانت تقرأ القيمة المخبأة فقط. ومنذ HotXLS 2.88.229 يستورد OpenODS التعبير أيضًا: الخلية المكتوبة بالبادئة of:= أو oooc:= تحتفظ بقيمتها المخبأة وتحصل على صيغة Excel مطبَّعة، مع تحويل المراجع المعقوصة مثل [.A1:.B1] وفواصل الوسائط المنقوطة إلى صياغة Excel. ومنذ 2.384.32 يحوّل الاستيراد أيضًا صفوف المصفوفات المضمّنة ({1;2|3;4} تصبح {1,2;3,4}) وعامل اتحاد المراجع ~، ومنذ 2.384.55 يُقرأ ذلك الاتحاد من دون زوج أقواس ثانٍ. تبقى ترجمة OpenFormula إلى صياغة Excel مشكلة تحويل لهجة بحالات حدّية حقيقية حول مفردات الدوال وصياغة المراجع ونماذج الأخطاء، ولهذا تأتي القيمة المخبأة على الدوام: فالأرقام التي تستوردها هي بالضبط الأرقام التي رآها المرسل آخر مرة

ضاق نمط الفشل الذي يجب التصميم حوله تبعًا لذلك. قبل 2.88.229 كان جدول بيانات كانت مجاميعه صحيحة عندما حفظه LibreOffice آخر مرة يُستورد بأرقام صحيحة أصبحت ثوابت: حرّر خلية إدخال، وأعد الحساب، ولن يتحرك شيء. أما في الإصدارات الحالية فتعود الصيغ حية، فيبقى الخطر المتبقي بناءً لا يُنمذجه المترجم، وقد يترك صيغة تعيد الحساب بشكل مختلف عن التطبيق المُنتِج بينما تُظهر القيمة المخبأة نتيجة المرسل. قارن نتائج إعادة الحساب بالقيم المخبأة المستوردة للصيغ التي يعتمد عليها خط أنابيبك، وأعد إنشاء أي صيغة تختلف من قواعد عملك الخاصة عبر Cell.Formula، التي تأخذ في واجهة XLSX التعبير دون علامة يساوي بادئة

التصميم حول فجوات الرحلة المتبقية

التصدير يُرسم من نموذج المصنف الكامل في الذاكرة: القيم والصيغ والأنماط، وإن طلبتها، المخططات والصور. وتحسّن تصدير الصيغ إصدارًا بعد إصدار. فمنذ 2.384.55 تحافظ المصفوفات المضمّنة واتحادات المراجع والمراجع المطلقة على معناها في OpenFormula؛ ومنذ 2.384.56 يصرّح HotXLS بنطاقات أسماء بادئات صيغه، فيقيّم LibreOffice الصيغ بدل أن يُظهر #VALUE! في كل خلية صيغة؛ ومنذ 2.384.65 تُكتب مراجع العمود الكامل والصف الكامل بصيغة OpenFormula ‏[.A:.A]، فلا يفتحها Excel بعد اليوم كـ#NAME?. وتبعتها التنسيقات الشرطية: فمنذ 2.384.66 تُكتب القواعد المبنية على صيغ كـformula-is(...) مع خليتها الأساسية، وهو ما يعرفه LibreOffice، ومنذ 2.384.69 تُكتب قواعد القيم والصيغ أيضًا كشروط style:map قياسية، وهي الصيغة الوحيدة التي يقرؤها Excel، بينما يقرأ طرف الاستيراد الشكلين معًا. لذا فإن دورة كاملة من ‎.xlsx‎ إلى ‎.ods‎ إلى ‎.xlsx‎ على إصدار حالي تحافظ على القيم والصيغ والأنماط والتنسيقات الشرطية؛ وما زال بإمكانها فقدانه يقع في الثغرات المذكورة في القسم السابق

مخطط لرحلة ODS ذهابًا وعودةً في HotXLS من Delphi: تصدير من نموذج المصنف في الذاكرة واستيراد حصرته الإصدارات المبكرة في القيم، تاركًا الصيغ ثوابت قبل HotXLS 2.88.229
استوردت الإصدارات المبكرة القيم والنتائج المخبأة فقط، فألغت دورة .xlsx إلى .ods إلى .xlsx كاملةً الأنماط والصيغ الحية؛ أما الإصدارات الحالية فتقرأ الاثنين معًا
Book := TXLSXWorkbook.Create;
try
  Book.Open('vendor-revision.ods');          // الصيغة تُكتشف تلقائيًا
  if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
  begin
    // منذ HotXLS 2.88.229 تعود الصيغ حية جنبًا إلى جنب مع نتائجها
    // المخبأة؛ أعد إنشاء أي صيغة لا يستطيع خط الأنابيب اللاحق
    // الوثوق بترجمتها قبل الحفظ.
    Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
    Book.SaveAs('vendor-revision.xlsx');
  end;
finally
  Book.Free;
end;

النمط المعماري الذي ينتج عن هذا: عامل ملفات ‎.ods‎ الواردة كتغذيات بيانات، لا كمستندات تُحرَّر في مكانها. أبقِ المصنف المرجعي بصيغة ‎.xlsx‎، واستخرج القيم من مراجعات العملاء، وأصدر ODS جديدًا عند الطلب من النسخة المرجعية. التحقق ينتمي إلى المعسكرين كليهما - افتح الملفات المصدَّرة في LibreOffice Calc، المستهلك المرجعي لـODF، وفي Excel، الذي يقرأ ODS منذ سنوات لكنه يختلف مع LibreOffice عند أطراف دعم المخططات والأنماط. عدد الأوراق، وحفنة من الخلايا الأساسية، ووجود المخططات تشكّل فحص تدخين كافيًا لكل ملف تصدير

فرز ملف ODS قبل الالتزام باستيراده

عندما تقبل نقطة نهاية رفع الملفات، فإن سرد أسماء الأوراق أرخص بكثير من تحليل كامل ويكشف المفاجآت البنيوية مبكرًا:

مخطط لببوابة فرز الرفع في HotXLS داخل Delphi حيث يرفض GetODSSheetNames حزم ODS غير القابلة للقراءة والأوراق الناقصة قبل تشغيل استيراد كامل
فحص GetODSSheetNames يكلف أقل بكثير من تحليل كامل ويلتقط إخفاق الورقة المعاد تسميتها ما دام الخطأ قادراً على تسمية الملف
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
  if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
    raise Exception.Create('not a readable ODS package');
  if Names.IndexOf('Data') < 0 then
    raise Exception.Create('revision is missing the Data sheet');
finally
  Book.Free;
  Names.Free;
end;

اصطلاح القيمة المُعادة يوقع الناس: استدعاءات HotXLS تعيد عمومًا عددًا موجبًا أو 1 عند النجاح و-1 عند الفشل، وتفرغ القائمة عند الفشل، لذا اختبر <= 0 بدلًا من المقارنة بقيمة موجبة محددة واحدة. GetODSSheetNames لا تعيد ضبط كائن المصنف ولا تملؤه، لذا يمكن لكائن فحص واحد أن يفحص مجلدًا كاملًا من الملفات الواردة. فحوصات بنيوية كهذه تلتقط أكثر إخفاقات العالم الحقيقي شيوعًا - محلل يعيد تسمية ورقة أو يحذفها قبل إرسال المراجعة - عند البوابة، حيث لا تزال رسالة الخطأ قادرة على تسمية الملف والورقة المفقودة بدلًا من الظهور كمرجع nil على بُعد ثلاث طبقات أعمق

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

HotXLS مكتبة جداول بيانات أصلية لـDelphi وC++Builder بشيفرة مصدرية كاملة؛ قائمة الميزات الكاملة وتفاصيل الترخيص موجودة على صفحة منتج HotXLS Delphi Component