خلفية تقارير Delphi أصدرت ملفات .xlsx لسنوات تواجه متطلبًا جديدًا: قواعد المشتريات لدى عميل من القطاع العام تفرض خرج OpenDocument Spreadsheet، والمحللون في ذلك الحساب يعيدون تعديلاتهم كملفات .ods محفوظة من LibreOffice. فالآن يجب على الكود نفسه أن يكتب ODS وأن يقرأه أيضًا. HotXLS، مكتبة جداول بيانات Object Pascal الأصلية من losLab لـDelphi وC++Builder، تتولى الاتجاهين دون أي تثبيت لـExcel أو LibreOffice في أي مكان. ما لا تفعله هو جعل الاتجاهين متماثلين. التصدير يحمل أكثر بكثير مما يستعيده الاستيراد، والفريق الذي يفترض غير ذلك سيرى الصيغ والتنسيق تتبخر في مكان ما بين مراجعة العميل والتقرير التالي، دون أي خطأ يشير إليه
دعم 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
الكشف في جهة الاستيراد تلقائي. طريقة 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 مسار خفيف الوزن عمدًا. إنه يحافظ على قيم الخلايا العددية والنتيجة المخبأة التي حملتها كل صيغة وقت الحفظ، ويوسّع الصفوف والأعمدة المتكررة إلى الشبكة. لكنه لا ينقل الأنماط، ولا تعبيرات صيغ ODS، ولا الرسوم
خيار الصيغ هو الأرجح أن يعضّك، وقد اتُّخذ عمدًا. خلية ODF تخزن شيئين جنبًا إلى جنب: تعبير الصيغة، مكتوبًا بلهجة OpenFormula المعرَّفة في ODF 1.3 الجزء 4، وآخر قيمة حسبها التطبيق المُنتِج لها. ترجمة OpenFormula إلى صياغة صيغ Excel مشكلة تحويل لهجة قائمة بذاتها، بحالات حدّية حقيقية حول مفردات الدوال وصياغة المراجع ونماذج الأخطاء. قراءة القيمة المخبأة بدلًا من ذلك تتجاوز تلك الفئة الكاملة من سوء الترجمة الصامت، لذا فإن الأرقام التي تستوردها هي بالضبط الأرقام التي رآها المرسل آخر مرة. الثمن أنها تصل كأرقام، لا كصيغ حية أنتجتها
نمط الفشل الذي يجب التصميم حوله يتبع ذلك مباشرة: جدول بيانات كانت مجاميعه صحيحة عندما حفظه LibreOffice آخر مرة يُستورد بأرقام صحيحة، لكن تلك الأرقام أصبحت الآن ثوابت. حرّر خلية إدخال، وأعد الحساب، ولن يتحرك شيء - الصيغة اختفت، ولم يبقَ سوى نتيجتها النهائية. إذا احتاج سير العمل صيغًا حية بعد الاستيراد، أعد إنشاءها برمجيًا من قواعد عملك الخاصة عبر Cell.Formula، التي تأخذ في واجهة XLSX التعبير دون علامة يساوي بادئة
التصميم حول الرحلة غير المتماثلة
التصدير يُرسم من نموذج المصنف الكامل في الذاكرة: القيم والأنماط، وإن طلبتها، المخططات والصور. الاستيراد يعيد القيم فقط. لذا فإن مسار .xlsx إلى .ods عالي الدقة، ومسار .ods إلى .xlsx يعيد القيم والنتائج المخبأة لكن دون تنسيق ودون صيغ حية. سلسل الاثنين وسيتضاعف عدم التماثل. دورة كاملة من .xlsx إلى .ods إلى .xlsx تكتب كل شيء بأمانة في الطريق للخارج وتفقد الأنماط والصيغ في طريق العودة، رغم أن لا شيء أخطأ في أي من الخطوتين
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // الصيغة تُكتشف تلقائيًا
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// القيم ونتائج الصيغ المخبأة موجودة بعد استيراد ODS؛
// الأنماط والصيغ الحية ليست موجودة. أعد بناء كل ما
// يعتمد عليه خط الأنابيب اللاحق قبل الحفظ.
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 قبل الالتزام باستيراده
عندما تقبل نقطة نهاية رفع الملفات، فإن سرد أسماء الأوراق أرخص بكثير من تحليل كامل ويكشف المفاجآت البنيوية مبكرًا:
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