لإنتاج ملف ODS يقرؤه Excel و LibreOffice كلاهما قراءةً صحيحة، يكتب HotXLS كل صيغة بصياغة OpenFormula تحت نطاق of: مصرَّح، ويكتب كل تنسيق شرطي من نوع قيمة أو صيغة مرتين: <style:map> على نمط كل خلية مغطاة، وهو ما لا يقرؤه Excel 16 سواه، وكتلة calcext:conditional-formats، وهي الصورة التي يثق بها LibreOffice. ويتجاهل كل تطبيق النصف الموجه لآخره، فملف يظهر صحيحاً في أحدهما لا يثبت شيئاً عن الآخر
تلك الجملة الأخيرة هي الدرس خلف ستة إصدارات من HotXLS بين v2.384.55 و v2.384.72. بدأ كل إصلاح بملف كتبه HotXLS وقرأه عائداً بإتقان وأخطأ فيه أحد التطبيقين الهدفين. وما يلي هو ما يقبله كل تطبيق فعلاً، والوسم الذي يرضيهما معاً، واستدعاءات HotXLS التي تنتجه من Delphi
لماذا يبدو ملف ODS سليماً في تطبيق ومكسوراً في الآخر؟
يبدو ملف ODS سليماً في تطبيق ومكسوراً في الآخر لأن Excel و LibreOffice يقرآن أجزاءً مختلفة من الحزمة نفسها. يمنح OpenDocument الصيغ والتنسيقات الشرطية أكثر من تهجئة مشروعة، ويضيف LibreOffice نطاق امتداداته الخاص فوق ذلك، وينتقي كل مستهلك المجموعة الجزئية التي ينفذها. والكاتب المفحوص على مستهلك واحد وحده سيتقارب بسعادة إلى وسم يقرؤه الآخر قراءة خاطئة بصمت
لا يبلّغ أي من التطبيقين عن خطأ. يعرض LibreOffice #VALUE! في الخلايا التي لم يستطع تفسير صيغها؛ ويفتح Excel المصنف دون التنسيقات الشرطية ببساطة، أو بصيغة أُعيدت كتابتها إلى ما يقيّم #NAME? أو الثابت 0. والكاتب الذي يعيد دورة ناتجه الخاص لا يرى شيئاً من هذا. وقع HotXLS في ذلك الفخ تماماً مع نطاق الصيغ: طابق قارؤه سابقة of: نصاً صريحاً، فنجحت كل دورة ذاتية بينما عرض LibreOffice #VALUE! في كل خلية صيغة
| الميزة | يقرؤها Excel 16 | يقرؤها LibreOffice 26.2 |
|---|---|---|
عمود كامل مكتوب A:A | تُقرأ خطأً A:(A) | تُحتمل |
عمود كامل مكتوب [.A:.A] | نعم | نعم |
تنسيقات شرطية في <style:map> | نعم، الصورة الوحيدة التي يقرؤها | تُتجاهل حين يوجد calcext |
تنسيقات شرطية في calcext:conditional-formats | تُتجاهل | نعم، المفضلة |
قاعدة قيمة calcext بخاصية calcext:operator | تُتجاهل | تُستورد "يساوي 0" |
قاعدة صيغة calcext مهجأة is-true-formula(...) | تُتجاهل | تُستورد مقارنة قيمة مع 0 |
OpenFormula في ODS: صرّح النطاق ثم أحسِن الصياغة
لا تكون خلية الصيغة في ODS مقروءةً لـ LibreOffice إلا إذا تحولت سابقة of: في table:formula إلى نطاق XML مصرَّح. والسابقة ليست زخرفة. of: يقابل urn:oasis:names:tc:opendocument:xmlns:of:1.2، و msoxl:، السابقة التي يستخدمها HotXLS للصيغ التي لا يصوغها مترجم OpenFormula عنده، تقابل http://schemas.microsoft.com/office/excel/formula. قبل v2.384.56 استخدم جذر content.xml السابقتين دون التصريح بهما، ولم يستطع LibreOffice تمييز نحو الصيغ أصلاً
<!-- قبل v2.384.56: سابقة مستخدمة ولم تُصرَّح قط؛ يعرض LibreOffice #VALUE! -->
<office:document-content xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" ...>
<table:table-cell table:formula="of:=SUM([.A1:.A3])" office:value-type="float" office:value="245"/>
<!-- منذ v2.384.56: كلا نطاقي الصيغ مصرَّحان على الجذر -->
<office:document-content
xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"
xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" ...>
مع النطاق مصححاً يبقى على التعبير نفسه أن يكون OpenFormula صالحاً كما يعرّفه OpenDocument 1.3 الجزء 4. والفخاخ في المواضع التي تتشابه فيها صياغة Excel مع OpenFormula ظاهرياً دون أن تكونا شيئاً واحداً:
- مراجع الخلايا بين قوسين وبسابقة نقطة، وعلامتا
$جزء من المرجع: [.$A$1]و[.A$1:.$B2]صالحتان في OpenFormula. قبل v2.384.55 أسقط كاتب HotXLS كل$، فعادت المراجع المطلقة نسبيةً ولم تخطئ إلا حين ينسخ أحد الخلية - الأعمدة والصفوف الكاملة يجب أن تستخدم الصورة المقوسة
[.A:.A]و[.$A:.$B]و[.1:.1]و[.$1:.$2]. يحتمل LibreOffice of:=SUM(A:A)العارية، لكن Excel 16 يفتحها=SUM(A:(A))مع#NAME?ويحول مراجع الصفوف و$A:$Bإلى الثابت 0. يكتب HotXLS الصورة المقوسة منذ v2.384.65 - وسائط الدوال تفصل بـ
;لا, - اتحادات المراجع تستخدم عامل
~: تصيرAREAS((A1,B2))في Excel AREAS(([.A1]~[.B2])). وترجمة تلك الفاصلة إلى;بدلاً من ذلك تحوّل وسيط اتحاد واحد إلى وسيطين - المصفوفات المضمّنة تفصل الأعمدة بـ
;والصفوف بـ|: تصير{1,2;3,4}في Excel {1;2|3;4}. أنتج HotXLS قبل v2.384.55 {1;2;3;4}، صفّاً واحداً من أربع قيم
الفاصلة هي الجزء الصعب، لأن محرف Excel الواحد يحمل ثلاثة معانٍ. منذ v2.384.55 يتتبع كاتب HotXLS كومة أقواس أثناء الترجمة: القوس ( الملاصق لاسمٍ يفتح استدعاء دالة تصير فواصله ;؛ وأي قوس آخر قوسُ تجميعٍ تصير فواصله ~؛ والفواصل داخل {} فواصل أعمدة مصفوفة. ومع ذلك وإصلاح النطاق قيّم LibreOffice 26.2 صيغ البروب الثماني كلها للمصفوفات والاتحادات قراءةً صحيحة، ومن ضمنها INDEX و AREAS فوق اتحادات
uses
lxHandleX;
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Orders');
Sheet.Cells[1, 1].Value := 120;
Sheet.Cells[2, 1].Value := 80;
Sheet.Cells[3, 1].Value := 45;
Sheet.Cells[1, 2].Value := 0.2;
// تكتب of:=SUM([.A:.A]) منذ v2.384.65
Sheet.Cells[1, 4].Formula := 'SUM(A:A)';
// تكتب of:=[.A1]*[.$B$1]؛ وعلامتا $ تبقيان منذ v2.384.55
Sheet.Cells[2, 4].Formula := 'A1*$B$1';
Book.SaveAsODS('orders.ods');
finally
Book.Free;
end;
end;
الصيغ التي لا يصوغها المترجم تتراجع إلى msoxl:= بنص Excel بلا تغيير، ولهذا يهم التصريح بـ msoxl أيضاً. في الكاتب الحالي يشمل ذلك المسار مراجع مؤهلة بورقةٍ مثل Sheet2!A1 ومراجع الجداول المهيكلة. يقرأ HotXLS صيغ msoxl: عائدةً عند الاستيراد، فتبقى دورته الذاتية تحفظ التعبير سليماً، لكن معاملة تطبيق آخر لها خارج سيطرة الكاتب. فإن خرجت صيغة يعتمد عليها مستهلكوك بسابقة msoxl:، افتح الملف في التطبيقين قبل أن تشحنه
لماذا لا يرى Excel تنسيقات شرطية كُتبت بصيغة calcext وحدها؟
لا يرى Excel 16 التنسيقات الشرطية بصيغة calcext لأنه يقرأ تنسيقات ODS الشرطية حصرياً من أبناء <style:map> لأنماط الخلايا ويتجاهل كتلة calcext:conditional-formats كلياً. والتجربة التي تحسمها قصيرة: خذ ملف ODS حفظه LibreOffice واحذف عناصر style:map، فيقرأ Excel صفر قواعد؛ واحذف كتلة calcext بدلاً منها، فيقرأ Excel القواعد كلها ما زالت. ويتصرف LibreOffice بالعكس. calcext نطاق امتدادات LibreOffice، وليس جزءاً من معيار ODF، وحين توجد قاعدة calcext يأخذها LibreOffice ويتجاهل style:map
قبل v2.384.69 كتب HotXLS calcext وحده، فُتح ملف ODS بتظليل سليم تماماً في Excel دون قواعد قيمة ودون قواعد صيغ على الإطلاق. يكتب HotXLS الآن الصورتين معاً. يستخدم نصف style:map نحوَ الشروط في مخطط OpenDocument (ODF 1.3 الجزء 3)، بالتهجئات الدقيقة التي ينتجها Excel 16 و LibreOffice 26.2 كلاهما حين يحفظان ODS:
<!-- مبسط. النمط الحامل لكل خلية في A1:A50 (قاعدتا قيمة) -->
<style:style style:name="ce3" style:family="table-cell">
<style:map style:condition="cell-content()>100"
style:apply-style-name="CF_Hit"
style:base-cell-address="Orders.A1"/>
<style:map style:condition="cell-content-is-between(1,10)"
style:apply-style-name="CF_Low"
style:base-cell-address="Orders.A1"/>
</style:style>
<!-- النمط الحامل لكل خلية في C1:C50 (قاعدة صيغة واحدة) -->
<style:style style:name="ce4" style:family="table-cell">
<style:map style:condition="is-true-formula(COUNTIF([.$C:.$C];[.C1])>1)"
style:apply-style-name="CF_Dup"
style:base-cell-address="Orders.C1"/>
</style:style>
المصيدة مع style:map أنه يسكن أنماط الخلايا، فهو لكل خلية. كل خلية في نطاق القاعدة يجب أن تحمل نمطاً يحوي الخريطة، والخلايا الفارغة ضمنها، وإلا فالقاعدة ببساطة لا تغطي تلك الخلية في Excel. ينسخ HotXLS نمط التنسيق القائم لكل خلية، ويُلحق الخرائط، ويُنقّي الأنماط الحاملة تكراراً بزوج النمط الأصلي ونص الخريطة، فيظل نطاق من 500 خلية بتنسيق متطابق ينتج نمطاً واحداً. ويمد الكاتب الجدول المكتوب إلى نطاق القاعدة أيضاً، أي تصدر صفوف الذيل الفارغة داخل قاعدةٍ بدل إسقاطها. ومنذ v2.384.69 يحمل styles.xml أيضاً نمط خلية Default فارغاً، فتجد style:apply-style-name="Default" هدفاً دوماً
تهجئة calcext التي يقبلها LibreOffice فعلاً
لا يقبل LibreOffice قاعدة قيمة calcext إلا إذا كان عامل المقارنة جزءاً من نص القيمة، مثل >3 أو between(1,10)، وقاعدة صيغة إلا إذا هُجئت formula-is(...). وكلتا النقطتين كلفتا HotXLS إصداراً، لأن التهجئات الخاطئة تنتج قاعدة تستورد بلا خطأ ثم تطابق الخلايا الخطأ
كان الخطأ الأول خاصية calcext:operator بجوار calcext:value. تُقرأ طبيعياً، لكنها مخترعة: لا يعرف LibreOffice تلك الخاصية، فاستورد كل قاعدة قيمة بوصفها «يساوي 0». والخطأ الثاني وضع is-true-formula(...)، تهجئة style:map، في شرط calcext، فاستورده LibreOffice مقارنةَ قيمة خلية مع 0 كذلك. شُحن إصلاح الصيغ في v2.384.66 وإصلاح القيمة في v2.384.69:
<!-- خطأ: يتجاهل LibreOffice calcext:operator ويستورد «يساوي 0» -->
<calcext:condition calcext:apply-style-name="CF_Hit"
calcext:operator="greater-than" calcext:value="100"/>
<!-- صواب: يسافر العامل داخل القيمة -->
<calcext:condition calcext:apply-style-name="CF_Hit"
calcext:value=">100" calcext:base-cell-address=".A1"/>
<calcext:condition calcext:apply-style-name="CF_Low"
calcext:value="between(1,10)" calcext:base-cell-address=".A1"/>
<!-- صواب: قواعد الصيغ تستخدم formula-is ومراجع نسبية ترسو في الخلية الأساس -->
<calcext:condition calcext:apply-style-name="CF_Dup"
calcext:value="formula-is(COUNTIF([.$C:.$C];[.C1])>1)"
calcext:base-cell-address=".C1"/>
الخلية الأساس هي التي تعطي المراجع النسبية معناها. يرسّي HotXLS كل قاعدة عند الخلية العلوية اليسرى من أول مساحة نطاق لها، فتُقيَّم صيغة كُتبت لـ C1 كأنها C2 و C3 وهكذا نزولاً في النطاق، تماماً كما يفعل التنسيق الشرطي الخاص بـ Excel. ويمر تعبير القاعدة عبر المترجم نفسه الذي تمر به صيغ الخلايا، فتخرج المصفوفات والاتحادات والأعمدة الكاملة وعلامات $ بالصور الموصوفة أعلاه. وعلى جانب Delphi تضيف القواعد تماماً كما تضيفها لملف .xlsx
uses
lxHandleX;
procedure AddOrderHighlights(Book: TXLSXWorkbook; Sheet: TXLSXWorksheet);
var
Idx: Integer;
Opts: TODSExportOptions;
begin
// قواعد القيمة: style:map cell-content()>100 مع قيمة calcext ">100"
Idx := Sheet.AddConditionalFormat('A1:A50', xlsxCfOpGreaterThan, '100');
Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($00C0C0FF); // BGR: أحمر فاتح
Idx := Sheet.AddConditionalFormat('A1:A50', xlsxCfOpBetween, '1', '10');
Sheet.ConditionalFormats[Idx].Style.SetFontBold(True);
// قاعدة صيغة بصياغة Excel (فواصل بفاصلة، نسبية إلى C1):
// style:map is-true-formula(...) مع calcext formula-is(...)
Idx := Sheet.AddCondFormatExpression('C1:C50', 'COUNTIF($C:$C,C1)>1');
Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($00CCFFFF); // BGR: أصفر فاتح
Opts := TODSExportOptions.Create;
try
Opts.Generator := 'OrderExport 3.1';
Book.SaveAsODS('orders.ods', Opts);
finally
Opts.Free;
end;
end;
قراءة ODS من Excel و LibreOffice عائدةً إلى Delphi
حين يفتح HotXLS ملف ODS يقبل قارؤه لهجتَي التنسيقات الشرطية وتهجئتي calcext معاً، ولا يعدّ قاعدةً مرتين حين يحملها الملف بالصورتين. الملفات الحقيقية تأتي من ثلاثة كاتبين، لكل منهم عاداته:
- calcext القديم والجديد. الملفات ذات خاصية
calcext:operator، ومنها ODS كتبه HotXLS قبل v2.384.69، تمر عبر التحليل القديم ما زالت. وتُعرف شروط الصيغ بـformula-is(...)أوis-true-formula(...)كليهما - تهجئة
style:mapالخاصة بـ Excel. يسوق Excel الشروط بسابقةof:، كما فيof:cell-content-is-between(1,10)، ويحذف الخلية الأساس في قواعد القيمة. وكلاهما مقبول - الخلايا الفارغة. يضع Excel و LibreOffice كلاهما خريطة الخلايا الفارغة على النمط الافتراضي للعمود لا على خلية، فيحل القارئ الأنماط الافتراضية للأعمدة للخلايا المتكررة قبل جمع الخرائط
- إعادة بناء النطاقات. تُجمع الخرائط لكل خلية، فبعد قراءة ورقةٍ يدمج القارئ الخلايا المتشاركة في الشرط والخلية الأساس نفسيهما نطاقاتٍ، أولاً عبر كل صفٍ ثم نزولاً في امتدادات الأعمدة المتطابقة، ويسقط أي قاعدة قُرئت سلفاً من calcext
إصلاح v2.384.72 يخص أنماط الأرقام لا القواعد. يكتب Excel 16 و LibreOffice 26.2 كلاهما تنسيق General نمطَ أرقامٍ عنصرُ number:number فيه بلا number:decimal-places، عادةً <number:number number:min-integer-digits="1"/>. عامل قارئ HotXLS العددَ المفقود عشريتين ثابتتين، فاستوردت كل قيمة في نمط Default بـ 0.00 وعُرض 1.5 بـ 1.50. ومنذ v2.384.72 يقابل عنصر العدد العاري بلا منازل عشرية ولا حد أدنى للعشريات ولا تجميع وبمحرف صحيح واحد على الأكثر تنسيقَ General، و General المنفرد يترك الخلية بلا تنسيق أرقام أصلاً. ويُحفظ النص حوله، كما في General" kg"، والأرقام المجمعة تبقي المطابقة السابقة لأن Excel لا يملك تنسيق General مجمّعاً
uses
SysUtils, lxCondFormat, lxHandleX;
procedure DumpOdsRules(const FileName: string);
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Rule: TXLSXConditionalFormat;
I: Integer;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open(FileName) <= 0 then
raise Exception.Create('cannot open ' + FileName);
if Book.SourceFormat <> xlsxOpenDocumentSpreadsheet then
raise Exception.Create('not an ODS package');
Sheet := Book.Sheets[1]; // فهرس Sheets يبدأ من 1
for I := 0 to Sheet.ConditionalFormats.Count - 1 do
begin
Rule := Sheet.ConditionalFormats[I];
case Rule.Kind of
cfkCellIs:
Writeln(Rule.Range, ' value rule ', Ord(Rule.Op), ' ',
Rule.Formula1, ' ', Rule.Formula2);
cfkExpression:
Writeln(Rule.Range, ' formula rule ', Rule.Formula1);
end;
end;
// خلية بنمط Excel العام تعود للقراءة بلا تنسيق أرقام
// منذ v2.384.72، بدل '0.00'
Writeln('A2 format: "', Sheet.Cells[2, 1].NumberFormat, '"');
finally
Book.Free;
end;
end;
تعود صيغ القواعد بصياغة Excel بفواصل الفاصلة، الصورة نفسها التي تمررها إلى AddCondFormatExpression، فالقاعدة التي كتبها HotXLS تعود السلسلةَ المتطابقة نفسها. وللصورة الأوسع عما يحفظه مسار استيراد ODS وما يُسقطه، راجع دليل دورة فتح وحفظ ODS في HotXLS؛ ولمعرفة كيف تُمدَّد الصفوف المتكررة من Excel و LibreOffice عند الاستيراد، راجع الصفوف المتكررة في ODS بوصفها مدّات ارتفاع صفوف
ما حدود توافق التنسيقات الشرطية في ODS مع HotXLS؟
منهج الوسم المزدوج يغطي قواعد مقارنة القيم وقواعد الصيغ، ويتوقف هناك. كل ما عداه أحادي الجانب أو لا يُكتب أصلاً:
- سلالم الألوان وأشرطة البيانات تكتب عناصر calcext وحدها، فيعرضها LibreOffice ولا يعرضها Excel
- أنواع القواعد الأخرى، كمجموعات الأيقونات وقواعد النص و top-N وفوق المتوسط والتكرار، ليس لها ناتج ODS في الكاتب الحالي. يمكن عادة إعادة صياغة قاعدة نص قاعدةَ صيغة، مثل
ISNUMBER(SEARCH("late",B2))فوقB2:B200، فتصل إلى التطبيقين - قواعد العمود الكامل والصف الكامل مثل
C:Cتُبسط فوق مساحة الجدول المكتوبة فعلاً فقط، لا فوق 1,048,576 صفاً كلها، فرى Excel تلك القواعد على الخلايا الموجودة في الملف وحدها - الملفات ذات style:map وحده. حين لا يحوي الملف كتلة calcext يفسّر HotXLS المراجع النسبية في قواعد الصيغ من الزاوية العلوية اليسرى للنطاق المعاد بناؤه، لا بالإزاحة من الخلية الأساس المذكورة
- القواعد المتداخلة من LibreOffice. حين تغطي عدة قواعد خليةً واحدة يكتب LibreOffice خريطة القاعدة الأولى وحدها عليها. ومثل تلك الملفات لا يمكن قراءته كاملاً من
style:mapوحده، وهذا سبب إضافي ليفضّل القارئ calcext حين يوجد كلاهما
حد العملية أهم من كل هذه. تجاوزت العيوب خلف هذه الإصدارات دوراتٍ كتبت ODS وقرأتها عائدةً بـ HotXLS، وكان بعضها سيمر أيضاً فحصاً يدوياً في التطبيق الخطأ: صيغ الأعمدة الكاملة عملت في LibreOffice بينما عرض Excel #NAME?، ومنذ v2.384.66 عملت قواعد الصيغ في LibreOffice بينما لم يعرض Excel أي قواعد حتى v2.384.69. إن كان توافق ODS متطلباً، فاختبار القبول هو فتح الملف في Excel وفي LibreOffice ومقارنة ما يعرضه كل منهما. ويسري الانضباط نفسه على الأنماط التي تشير إليها القواعد؛ تغطي مقالة التنسيق الشرطي والأنماط في HotXLS كيفية تعريف أنماط التظليل على جانب المصنف
مرجع سريع: ODS يقرؤه التطبيقان معاً
- صرّح
xmlns:ofوxmlns:msoxlعلى جذرcontent.xml، وإلا عرض LibreOffice #VALUE!لكل صيغة (HotXLS منذ v2.384.56) - اكتب المراجع
[.A1]، واحفظ كل$، واكتب الأعمدة والصفوف الكاملة[.A:.A]و[.1:.1](منذ v2.384.55 و v2.384.65) - استخدم
;للوسائط، و~لاتحادات المراجع، و|بين صفوف المصفوفة المضمّنة - اكتب كل قاعدة قيمة أو صيغة
<style:map>على نمط كل خلية مغطاة لأجل Excel، وشرط calcext لأجل LibreOffice (منذ v2.384.69) - في calcext ضع العامل داخل القيمة (
>3،between(1,10)) وهجئ قواعد الصيغformula-is(...)مع خلية أساس (منذ v2.384.66 و v2.384.69) - توقع نمط أرقام General بلا
number:decimal-placesعند الاستيراد؛ يقرؤه HotXLS بوصفه General منذ v2.384.72 - تحقق من كل ملف تصدير جديد بفتح الملف في Excel و LibreOffice معاً، لا في أحدهما قط
HotXLS مكتبة جداول أصلية لـ Delphi و C++Builder تقرأ XLS و XLSX و ODS وتكتبها دون Excel أو LibreOffice مثبتين؛ والمصدر الكامل وقائمة المزايا والترخيص في صفحة مكوّن HotXLS Delphi للجداول الحسابية