مقال تقني

HotXLS Delphi Component: CSV, TSV, HTML, and RTF export في Delphi

تشرح النسخة المحدثة لماذا قد يحتوي CSV على صيغ بدل الأرقام، وما يضمنه الكاتب المحدد، ولماذا يمثل HTML لقطة لا صيغة تبادل. كما توضح موضع RTF، وأن استيراد CSV يهدم معلومات مقصودة، وكيفية الكتابة مباشرة إلى stream

تخيّل مهمة ليلية تبني مصنف فواتير في الكود وتكتبه كملف CSV ليستورده نظام لاحق. الأرقام تبدو صحيحة في Excel. ملف CSV يُفتح بنظافة في محرر نصوص. ثم يختنق المستورد عند عمود المجاميع، لأن حقل المبلغ للصف 42 يقرأ =SUM(D2:D41)، أي نص الصيغة الحرفي، لا الرقم الذي يُفترض أن تحسبه. لا شيء معطّل هنا. هذا سلوك موثّق، وهو أول ما ينبغي فهمه عن التصدير من HotXLS: الكاتب يسلسل نموذج الخلية تمامًا كما هو، وخلية صيغة لم تُحسَب قيمتها قط ليس لديها لتسليمه سوى نص صيغتها

مخطط يوضح خلية مصنف HotXLS داخل Delphi تحتفظ بنص الصيغة فقط حتى يحسب Book.Calculate القيمة، فيُصدر تصدير CSV عددًا بدلاً من نص =SUM
يسلسل SaveAsCSV نموذج الخلية كما هو — فدون Calculate يحمل حقل المبلغ نص الصيغة الحرفي ويرفضه المستورد

لماذا يحتوي CSV الخاص بك على صيغ بدلًا من أرقام

يخزّن HotXLS نص الصيغة والقيمة المحسوبة كشيئين منفصلين. SaveAsCSV لا تشغّل محرك الحساب في طريقها إلى الخرج، وهذا مقصود: فالتصدير لا ينبغي أن يغيّر المصنف، ولا ينبغي أن يخاطر بالتعليق على سلسلة صيغ مَرَضية. الملفات التي حفظها Excel نفسه تحمل نتائج مخبأة إلى جانب الصيغ، لذا فإن إعادة تصديرها يتصرف كما تتوقع. الفخ خاص بالمصنفات التي أنشأها كودك أنت، حيث كُتبت الصيغ لكنها لم تُقيَّم قط. الحل هو جعل القيم موجودة قبل التصدير، باستخدام محرك Calculate نفسه الذي يحل المراجع بين الأوراق والدوال المخصصة:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('invoice-run.xlsx');
    Sheet := Book.Sheets[0];

    // حوّل نتائج الصيغ إلى قيم فعلية حتى يحمل CSV أرقامًا لا نص '=...'
    for R := 2 to 41 do
      if Sheet.Cells[R, 4].Formula <> '' then
        Sheet.Cells[R, 4].Value := Book.Calculate(Sheet.Cells[R, 4].Formula);

    Book.SaveAsCSV('feed.csv', 0, ',');    // الورقة 0، فاصلة
    Book.SaveAsCSV('feed.tsv', 0, #9);     // نفس الورقة كـ TSV
  finally
    Book.Free;
  end;
end;

راقب ما تفعله الحلقة فعليًا: إنها تكتب فوق خلايا الصيغ بقيمها المحسوبة. هذا صحيح تمامًا لتمريرة تصدير تُستخدم مرة واحدة، وخاطئ إذا كنت تنوي حفظ المصنف مجددًا كـ ‎.xlsx بعد ذلك، لأنك استبدلت للتو صيغًا حية بأرقام مجمّدة. صدّر من نسخة، أو احصر الكتابة العكسية لتلمس فقط تشغيلة التصدير. المحرك خلف Calculate يذهب أبعد من هذا، بما يشمل تسجيل دوالك الخاصة، وهو موضوع محرك الصيغ في HotXLS والدوال المخصصة

ما الذي يضمنه الكاتب المحدَّد بفاصل

مسار CSV ينتج UTF-8 مع علامة ترتيب البايت (BOM)، ونهايات أسطر CRLF، واقتباس وفق RFC 4180. أي حقل يحتوي على الفاصل أو علامة اقتباس أو فاصل سطر يُلَفّ، وعلامات الاقتباس المضمّنة تُضاعف. التواريخ تُعرَض كـ yyyy-mm-dd hh:nn:ss بصرف النظر عن تنسيق عرض الخلية. هذا هو القرار الصحيح لمستهلك آلي، رغم أنه يفاجئ من توقع أن ينتقل التنسيق المرئي كما هو. خلايا النص المنسّق تُسطَّح بدمج أجزائها معًا

مخطط لكاتب HotXLS المحدد الواحد داخل Delphi ينتج CSV بفاصلة و TSV بـ #9 بينما تتشارك كلتا المخرجتين BOM لـ UTF-8 ونهايات CRLF وتنصيص RFC 4180
يأتي CSV وTSV من الكاتب نفسه، فتنطبق BOM لـ UTF-8 ونهايات CRLF وتأطير RFC 4180 على كليهما دون تغيير

هذه الإعدادات الافتراضية تحسم معظم الخلافات مع المستورد قبل أن تبدأ، لكن اثنين منها يستحقان مكانًا في عقد الواجهة الخاص بك بأي حال. الأول هو BOM. إنه ما يتيح لـExcel فتح الملف مع بقاء الأحرف المنقّطة سليمة، لكن حفنة من المحللات الصارمة تعامل تلك البايتات الثلاث كبيانات؛ فإن كان محللك واحدًا منها، فأزلها عند التسليم. الثاني هو TSV. إنه ليس ميزة منفصلة على الإطلاق، بل نفس الكاتب مستدعًى بـ#9 فاصلًا، فكل ما سبق ينطبق عليه دون تغيير. الورقة المراد تصديرها تُختار بفهرس يبدأ من صفر في الحمولة الزائدة متعددة المعاملات، بينما اختصار المعامل الواحد SaveAsCSV(FileName) يأخذ الورقة النشطة

تصدير HTML لقطة، لا صيغة تبادل

حيث يتخلص CSV من كل شيء ما عدا القيم، تحاول SaveAsHTML الحفاظ على المظهر: <table> واحد لكل ورقة، مناطق مدمجة تُعبَّر عنها بـcolspan وrowspan، وتنسيق خلايا أساسي مضمَّن كـCSS. الألوان النسبية للسمة تُتخطى بدلًا من أن تُحل، لذا فإن قالبًا يعتمد على فتحات السمة يخرج أبسط مما يبدو في Excel. اضبط ألوان RGB صريحة على أي شيء يجب أن ينجو من الرحلة. كائن الخيارات يتحكم في الغلاف:

var
  Opts: TXLSXHtmlExportOptions;
begin
  Opts := TXLSXHtmlExportOptions.Create;
  try
    Opts.Title := 'Weekly settlement';
    Opts.TableClass := 'report-grid';     // نقطة ربط لورقة أنماط الصفحة المضيفة
    Opts.WriteDocument := True;           // صفحة كاملة، لا جزء منها
    if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
      raise Exception.Create('Sheet index out of range');
  finally
    Opts.Free;
  end;
end;

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

خرج RTF، وأين لا يزال يستحق مكانه

الهدف الرابع يكتب جداول RTF 1.6، ورقة واحدة لكل استدعاء عبر SaveAsRTF. عروض الأعمدة تُقرَّب إلى نحو 96 twip لكل حرف من عرض العمود. القيد البنيوي الذي ينبغي معرفته هو أن الخلايا المدمجة لا تمتد في الخرج: فقط خلية المرساة تحمل محتواها، والخلايا المغطاة تخرج فارغة. هذا يستبعد RTF من القوالب الثقيلة في التخطيط. ومع ذلك لا يزال يستحق مكانه كمسار أقل مقاومة لإسقاط نتائج جدولية في معالج نصوص أو في نظام إدارة مستندات قديم يسبق استيعاب HTML

الرحلة العكسية: استيراد CSV مدمِّر بالتصميم

قراءة CSV مجددًا لها عقدها الخاص. OpenCSV تمسح المصنف بأكمله وتعيد بناءه كورقة واحدة باسم Sheet1. إنها بانٍ من حيث الروح، لا دمج، لذا لا تستدعها أبدًا على مصنف لا يزال يحمل محتوى غير محفوظ. تمرير #0 كفاصل يُشغّل الكشف التلقائي عن الفاصل. علامة ADetectTypes تتحكم في ترقية النوع: عند تفعيلها، تصبح السلاسل الرقمية أرقامًا، وسلاسل ISO-8601 تصبح تواريخ، وtrue/false تصبح قيمًا منطقية. أطفئها عندما تحمل التغذية معرّفات بأصفار بادئة، أو رموز بريدية، أو رموز منتجات، فكل هذه تُشوَّه بصمت إلى أرقام عبر الترقية (الصفر البادئ يختفي ببساطة بمجرد أن تصبح 00123 هي 123). كلا الواجهتين تكشفان الاستيراد نفسه. اقرنه باستدعاءات التصدير أعلاه ويكون لديك جسر صيغ لا يحتاج أي تثبيت لـExcel في أي مكان من خط الأنابيب، وهو السيناريو الذي تغطيه مقالة توليد تقارير Excel من قاعدة بيانات باستخدام HotXLS

التصدير مباشرة إلى تدفق

كل كاتب هنا لديه حمولة زائدة من نوع تدفق تجلس إلى جانب نسخة اسم الملف: CSV وHTML وRTF وصيغ المصنف نفسها. في كود الخادم، تلك الحمولات الزائدة هي ما ينبغي اللجوء إليه. نقطة نهاية ويب تخدم تنزيل CSV يمكنها الكتابة إلى TMemoryStream وتسليمه مباشرة إلى كائن الاستجابة، دون ملف مؤقت، ودون مهمة تنظيف، ودون تصادم بين طلبين اختارا مصادفة الاسم المولَّد نفسه. الأمر نفسه ينطبق على دفع التصديرات إلى تخزين كائنات ثنائية كبيرة أو إرفاقها ببريد صادر. نظام الملفات يخرج من الصورة تمامًا

هذا النمط يتضاعف مع طريقة نشر المكتبة. كلا الواجهتين قارئان وكاتبان أصليان بلغة Object Pascal، فلا يوجد تثبيت لـExcel، ولا أتمتة COM، ولا عنق زجاجة لكل عملية يسلسل الطلبات على الخادم. كل طلب يمكن أن يملك كائن مصنفه الخاص، ويشغّل الكتابة العكسية للحساب من القسم الأول، ويبث تصديره بالتوازي مع جيرانه. الذاكرة هي المورد الوحيد الذي يجب مراقبته. نموذج المصنف يعيش في الذاكرة العشوائية طوال مدة التصدير، لذا فإن خدمة تفتح ملفات كبيرة جدًا فقط لإعادة إصدارها كـCSV ينبغي أن تحدّ من المهام المتزامنة، أو تصفّ الملفات الضخمة في قائمة انتظار، بدلًا من ترك ارتفاع مفاجئ في الحركة يقرر مجموعة العمل

مقبض أصغر: اضبط IncludeBOM في خيارات HTML عندما سيُحفظ الجزء كملف مستقل يستنشقه أداة لاحقة بحثًا عن الترميز. عندما تخدم HTML مباشرة عبر HTTP، اترك إعلان مجموعة الأحرف لترويسات الاستجابة بدلًا من ذلك

عندما تخرج البايتات خاطئة رغم ذلك

أكثر أسئلة الدعم شيوعًا حول تصدير CSV هي نفس مشكلة الافتتاح بزي مختلف: Excel يعرض رموزًا مشوّهة بدلًا من الأحرف المنقّطة. الغريزة الأولى هي لوم الكاتب، لكنه يصدر BOM بصيغة UTF-8 لهذا السبب بالضبط، والملف يكاد يكون دائمًا صحيحًا حين يغادر كودك. شيء ما بين تلك النقطة وExcel التهم BOM. نقل FTP في وضع نصي، أو نسخ تدفق يتخطى البايتات الثلاث الأولى، أو وسيط يعيد الترميز في الطريق: أي من هذه سيزيل العلامة ويترك Excel يخمّن الترميز، وهو يفعل ذلك بشكل سيئ. شخّص هذا عند الحدود، لا في استدعاء التصدير. افتح الملف المُسلَّم في عارض سداسي عشري وتأكد أن EF BB BF لا يزال أول ما فيه

مخطط يتتبع كيف تُجرَّد BOM لـ UTF-8 الصحيحة التي يكتبها تصدير CSV في HotXLS داخل Delphi بواسطة نقل FTP بوضع النص أو وسيط يعيد الترميز، تاركًا Excel يعرض رموزًا مشوهة
يُصدر الكاتب EF BB BF صحيحاً — ولا يظهر mojibake إلا بعد أن تنزع وسيلة نقل الوسم، فشخّص البايتات المسلَّمة في عارض سداسي عشري

هذا هو الخيط الناظم لكل الصيغ الأربع. استدعاء التصدير هو الجزء السهل، وHotXLS يتخذ خيارًا يمكن الدفاع عنه عند كل قرار يواجهه الكاتب. الإخفاقات تعيش عند الوصلات، حيث يلتقي نص الصيغة بمحلل أراد رقمًا، وحيث تلتقي BOM بنقل لا يحافظ عليها، وحيث تلتقي خلية مدمجة بنموذج جدول RTF المسطّح. كل واحدة من هذه حقيقة ينبغي كتابتها في العقد بين مُصدِّرك وأي جهة تستهلكه، لأن المستهلك لا يستطيع قراءة نواياك من البايتات. للحصول على قائمة الطرق الكاملة عبر واجهتَي المصنف كلتيهما، تحمل صفحة منتج HotXLS Delphi Component المرجع الكامل