لا يثق Excel بمخزن #SPILL! أو #CALC! إلا عندما يحمل المصنف عقد الخطأ الحديث كاملاً: بيانات Rich Data وصفية يكون فيها URI امتداد XLRICHVALUE هو الـ GUID بأحرف صغيرة، و fileVersion بقيمة 7، و calcId لا يقل عن 191029، وميزة الحساب microsoft.com:RD. يُصدر HotXLS، مكوّن XLSX الأصلي لـ Delphi، الأربعة كلها كلما حملت خلية محفوظة أحد تلك الأخطاء
فوّت أي قطعة والفشل نادراً ما يكون صاخباً. أحياناً يرفض Excel 16 فتح الملف إطلاقاً. وأكثر غالباً يفتح جيداً ويعرض #VALUE! حيث أنتج محرك الحساب لديك #SPILL!، وهو ما يبدو خلل حساب فيقودك إلى التفتيش في كود معادلات سليم تماماً. اضغط F9 فيظهر الخطأ الصحيح، فيميل الأمر إلى إغلاق المشكلة بوصفها "تعمل على جهازي" حتى يفتح عميل التقرير بإيقاف الحساب التلقائي. تستعرض هذه المقالة ماذا تفعل كل قطعة، وفخ حالة أحرف الـ GUID الذي يبدو مستحيلاً أن يهم، وقاعدة مخزن نص المصفوفة التي تفشل بالطريقة الهادئة نفسها
لماذا يعرض Excel قيمة #VALUE! بدلاً من مخزن #SPILL!؟
يعرض Excel قيمة #VALUE! لأن هذا حرفياً ما تخزنه الخلية. نوع الخطأ القديم في SpreadsheetML من ECMA-376 له قائمة ثابتة من نصوص الأخطاء، و #SPILL! و #CALC! ليستا عليها. خلية بـ t="e" لا تستطيع أن تقول سوى #NULL! أو #DIV/0! أو #VALUE! أو #REF! أو #NAME? أو #NUM! أو #N/A، فتكتب إصدارات Excel الأحدث #VALUE! بوصفها تراجعاً متوافقاً مع الإصدارات السابقة وتعلّق نوع الخطأ الحقيقي على فهرس بيانات وصفية للقيمة
ذلك الفهرس، خاصية vm، يشير إلى xl/metadata.xml، التي تشير إلى قيمة Rich Data تصف الخطأ الفعلي. القارئات الأقدم ترى #VALUE! وتتجاوز. وExcel الحالي يتبع السلسلة ويعرض الخطأ الحديث، لكن فقط إذا قرر أن السلسلة موثوقة. هكذا تبدو معادلة فيضت ثم انحُجبت على القرص:
<!-- sheet1.xml: literal قديم مع مؤشر بيانات وصفية للقيمة -->
<c r="A1" t="e" cm="1" vm="1">
<f t="array" ref="A1">_xlfn.SEQUENCE(3)</f>
<v>#VALUE!</v>
</c>
<!-- xl/richData/rdrichvaluestructure.xml -->
<s t="_error">
<k n="errorType" t="i"/>
<k n="colOffset" t="i"/>
<k n="rwOffset" t="i"/>
</s>
<!-- xl/richData/rdrichvalue.xml: errorType 8 يعني #SPILL! -->
<rv s="0"><v>8</v><v>0</v><v>2</v></rv>
يكتب HotXLS نوع الخطأ 8 مع إزاحتَي عمود وصف لـ #SPILL!، ونوع الخطأ 13 دون إزاحات لـ #CALC!. وتبقى القيمة القديمة <v> على #VALUE! في الحالتين عمداً، لأن تلك هي القيمة التي يُفترض أن يراها أي قارئ يتجاهل Rich Data
الـ GUID بأحرف صغيرة الذي يفحصه Excel 16 فعلاً
يجب أن تستخدم كتلة البيانات الوصفية المستقبلية XLRICHVALUE الـ URI الخاص بالامتداد {3e2802c4-a4d2-4d8b-9148-e3be6c30e623} بأحرف صغيرة، محرفاً بمحرف. كبّر حروف الـ hex ورفضت بنية Excel 16 المحلية فتح المصنف بشكل طبيعي، مع أن كل جزء XML حلَّل بنجاح، وكل علاقة استُوفيت، وكل عدّاد وفهرس اصطفّا
<futureMetadata name="XLRICHVALUE" count="1">
<bk>
<extLst>
<ext uri="{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}">
<xlrd:rvb i="0"/>
</ext>
</extLst>
</bk>
</futureMetadata>
<valueMetadata count="1">
<bk><rc t="2" v="0"/></bk>
</valueMetadata>
كل من عمل مع COM أو السجل يعرف أن GUIDs تُقارن بلا حساسية لحالة الأحرف، وهذه العادة هي بالضبط ما تعضّ هنا. عزلنا الأمر بالطريقة البطيئة: بنينا حزمة دنيا رفضها Excel، ثم بدّلنا شيئاً واحداً في كل مرة. استبدال أنواع المحتوى لم يصلحها. إضافة جزء أنواع قيم غنية لم تصلحها. تغيير بادئات النطاقات أو إعادة كتابة بيانات القيمة الغنية لم تصلحها. جعل خاصية uri الواحدة تلك بأحرف صغيرة أصلحها، وفتحت الحزمة نفسها بعدها وعرضت كل من #CALC! و #SPILL!. عامِل URI الامتداد بوصفه حرفية سلسلة معتمة، وانسخه من تعريف الصيغة، ولا تمرره أبداً عبر مُنسِّق GUID يختار حالته الخاصة
وهناك نتيجة عملية عملية لمن يكتب حزم تشخيص يدوياً. نفّذ التعديلات بمحلل XML، لا باستبدال نصي شامل فوق XML مُسلسَل. استبدال شامل موجه إلى رمز واحد يمكن أن يفسد كيانات مثل > في مكان آخر من الجزء، وستكون حينها تنقّح مسباراً معطوباً بدلاً من السؤال الأصلي
ماذا يحتاج Excel آخر الأمر لقراءة خطأ حديث مخزَّن؟
أجزاء البيانات الوصفية وعلاقاتها ضرورية لكنها لا تكفي. مع سلسلة Rich Data صالحة تماماً في مكانها، تجاهل Excel الخطأ المخزَّن مع ذلك حتى أعلن جزء المصنف عن نفسه بوصفه قادماً من محرك حساب حديث. يكتب HotXLS ثلاثة إعلانات في workbook.xml كلما حملت خلية محفوظة #SPILL! أو #CALC!:
<fileVersion appName="xl" lastEdited="7" lowestEdited="7"/>
...
<calcPr calcId="191029"/>
<extLst>
<ext uri="...">
<calcFeatures>
<feature name="microsoft.com:RD"/>
</calcFeatures>
</ext>
</extLst>
عنصر fileVersion بيانات وصفية مصنف عادية في ECMA-376 الجزء 1، وقيمتا lastEdited و lowestEdited البالغة 7 تخبران Excel أن الملف حُفظ آخر مرة بتوليد تطبيق يفهم Rich Data. الـ calcId على calcPr يعرّف إصدار محرك الحساب، و191029 هو الحد الأدنى وجدناه يقبل به Excel لهذه المخازن. ومدخل microsoft.com:RD في امتداد ميزات الحساب، في نطاق http://schemas.microsoft.com/office/spreadsheetml/2018/calcfeatures، يعلن أن المحرك الذي أنتج القيم المخزَّنة قيّم بدلالات Rich Data. توفير النوع الفرعي وحده، أو جزء الأنواع وحده، أو بادئات ومسارات علاقات مختلفة وحدها لم يغيّر النتيجة. كان لا بد أن تحضر الإعلانات الثلاثة معاً
ثلاث تفاصيل في كيفية تطبيق HotXLS لها تستحق المعرفة إذا ضبطت هذه القيم بنفسك:
TXLSXWorkbook.CalcIdلا يُستبدل. يرفع الكاتب القيمة المُصدَرة إلى 191029 في الإخراج فقط، فيحفظ المستدعي الذي كوّنCalcIdأعلى قيمته، وتبقى الخاصية في الذاكرة بالضبط ما أسندتَه- ميزات الحساب الموجودة وامتدادات المصنف غير ذات الصلة التي وردت مع ملف مفتوح تُحفظ لا تُستبدل
- يفحص الكاتب وجود ميزة
microsoft.com:RDسابقة قبل إضافة واحدة، فحفظ المصنف نفسه مراراً لا يكدّس إعلانات مكررة أبداً
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('forecast.xlsx');
Sheet := Book.Sheets[1];
// A1 يفيض ثلاثة صفوف؛ A2 مشغولة، فالنتيجة #SPILL!
Sheet.Range['A1'].SetDynamicArrayFormula('=SEQUENCE(3)');
Sheet.Cells[2, 1].Value := 'blocker';
Book.Recalculate;
// CalcId أعلى ينجو؛ الكاتب يرفعه إلى 191029 فقط إذا كان أدنى
Book.CalcId := 191029;
Book.SaveAs('forecast-out.xlsx'); // تُكتب أجزاء الأخطاء الغنية تلقائياً
finally
Book.Free;
end;
end;
لا تبني أجزاء Rich Data بنفسك أبداً. قبل الكتابة، تمسح TXLSXWorkbook كل خلية بحثاً عن قيم #SPILL! أو #CALC!، وتنشئ xl/metadata.xml وجزئي القيمة الغنية أو توسّعهما، وتُسقط روابط البيانات الوصفية للقيمة من الخلايا التي تغيّر خطؤها أو صُفّر منذ ذلك الحين. الإسناد الصريح لـ CalcId أعلاه اختياري ولم يُعرض إلا لتوضيح أنه محترم
مخازن نص المصفوفات مكانها t="str" لا السلاسل المشتركة
يظهر الفشل الهادئ نفسه أيضاً للنتائج النصية داخل المصفوفات. يحفظ HotXLS النص المخزَّن لخلية معادلة، ولكل خلية تابعة في نطاق CSE أو مصفوفة ديناميكية، بوصفه t="str" مع السلسلة الحرفية في <v>. كتابة تلك المخازن سلاسلاً مشتركة بـ t="s" تجعل Excel يعرض #VALUE! في خلايا المصفوفة عندما يقرأ الملف المخزَّن
<!-- صحيح: مخزن نص حرفي على تابع مصفوفة -->
<c r="B3" t="str"><v>Northwind</v></c>
<!-- خطأ: فهرس سلسلة مشتركة، يعرض Excel #VALUE! من المخزن -->
<c r="B3" t="s"><v>17</v></c>
هذه سهلة الخطأ لأن t="s" هو الخيار الصائب لخلايا القيم العادية، وأغلب الكتّاب يوجهون كل سلسلة عبر جدول السلاسل المشتركة افتراضياً. الفخ في التحقق. أعد الحساب في Excel فتعرض خلايا المصفوفة النص الصحيح، وهذا يثبت أن المعادلة سليمة ولا يثبت شيئاً عن المخزن الذي كتبته. وإذا كان مستخدموك يفتحون التقارير بحساب يدوي، أو تقرأ أداة لاحقة القيم المخزنة دون حساب، فنسخة السلاسل المشتركة معطوبة ببساطة
صيغة تخزين المعادلات: يساوي مسبقة وفواصل و _xlws
ينزل تنظيف ذو صلة في الكاتب نفسه: المعادلات الداخلة إلى OOXML تُخزَّن بصيغة الملف، لا بالصيغة التي كتبها المستخدم. يحفظ HotXLS أي نص أسندته إلى Formula لعرضه ذهاباً وإياباً، لكن المعادلة المُسلسَلة تُسقط علامة = المسبقة، وتستخدم الفواصل بين معاملات الدوال، وتترك فواصل صفوف الثوابت المصفوفية والنص المقتبس كما هي. يُخزَّن FILTER و SORT بالبادئة _xlfn._xlws. التي يتوقعها Excel لدالتي ورقة العمل هاتين، بينما تحصل دوال مستقبلية أخرى مثل SEQUENCE على البادئة _xlfn. العادية
// كُتب بفواصل منقوطة، وخُزّن بوصفه
// _xlfn._xlws.FILTER(A2:A50,C2:C50>1000)
Sheet.Range['E2'].SetDynamicArrayFormula('=FILTER(A2:A50;C2:C50>1000)');
Book.Recalculate; // لا صف فوق 1000 يعطي #CALC!، ويُحفظ مع Rich Data
الترتيب مهم داخل الكاتب. يجرى تهريب XML بعد تسوية الصياغة، لا قبلها أبداً، للسبب نفسه الذي لأجله تحتاج حزم التشخيص إلى محلل: إعادة كتابة فاصل مطبقة على نص مُهرَّب أصلاً كانت ستشوه كيانات مثل > في معاملات المقارنة
كيف تتحقق من خطأ مخزَّن دون أن تخدع نفسك
الاختبار الموثوق وحده هو فتح الملف في Excel والحساب مضبوط أصلاً على يدوي، وقراءة الخلايا، وفقط بعدها إعادة الحساب. يرث Excel وضع الحساب من أول مصنف يفتح في جلسة، فيُنشئ هيكل قبولنا أول مقياس مصنفاً خاصاً به، ويحوّله إلى يدوي، ثم يفتح الملف قيد الاختبار. المسبارات التي تتخطت خطوة المقياس أثبتت فقط أن الملف يفتح ويحسب بشكل صحيح. وأي شيء سمّته مخزَّناً كان قد أُعيد حسابه أصلاً
ذلك التمييز هو المقالة كلها مصغَّرة. مخزن خطأ حديث صحيح عندما يعرضه Excel قبل أن يعمل أي حساب، وكل متطلب أعلاه، من الـ GUID بأحرف صغيرة إلى التوابع النصية t="str"، وُجد بالفحص لتلك الحالة لا للحالة المعاد حسابها. وللخلفية عن كيفية تحجيم HotXLS لنطاقات الفيض وامتلاكها في المقام الأول، انظر معادلات فيض المصفوفات الديناميكية في Delphi عبر HotXLS. وإذا كان عرَضك نافذة إصلاح لا خطأ خاطئاً، فقواعد مستوى الحزمة في لماذا يصلح Excel ملف XLSX صالحاً هي نقطة البداية الأصلح
الأسئلة الشائعة
هل يحتاج HotXLS أي ضبط لحفظ #SPILL! و #CALC! بشكل صحيح؟
لا. يكشف HotXLS قيم #SPILL! و #CALC! وقت الحفظ ويكتب أجزاء Rich Data و fileVersion والحد الأدنى calcId وميزة microsoft.com:RD تلقائياً
لماذا ما يزال XML الخلية يحوي #VALUE!؟
نوع الخطأ القديم في SpreadsheetML لا يملك نصاً لـ #SPILL! أو #CALC!، فـ #VALUE! هو التراجع المتوافق مع الإصدارات السابقة. يقرأ Excel الحالي الخطأ الحقيقي عبر رابط البيانات الوصفية للقيمة vm
هل سيخفض HotXLS قيمة CalcId أسندتها بنفسي؟
لا. لا يُرفع TXLSXWorkbook.CalcId إلى 191029 في الملف المكتوب إلا عند وجود خطأ حديث، والقيمة الأعلى التي كوّنتها تبقى كما هي
يقرأ HotXLS ويكتب XLSX و XLSM و XLS القديم من Delphi و C++Builder بمحرك حسابه الخاص، والمصفوفات الديناميكية شمولاً. التفاصيل وتحميل النسخة التجريبية على صفحة مكوّن جداول HotXLS لـ Delphi