يقرأ HotXLS، مكتبة Excel الأصلية لـDelphi وC++Builder، القيمة التي خزّنها Excel بالفعل بجانب الصيغة عبر TryGetCachedFormulaValue وIXLSFormulaCacheReader. لا أي من نقطتي الدخول يستدعي الحاسبة، أو يفك رموز الصيغة، أو يحدّث الحالة المتسخة، أو يكتب أي شيء إلى النموذج، لذا يبقى المصنف الذي تقرؤه فقط كما فتحته تماما
السيناريو الذي يدفع إلى هذا مملّ وشائع للغاية. مهمة ليلية تفتح بضع مئات من المصنفات أنتجها غيرك، وتستخرج عمودا واحدا من الإجماليات من كل منها، وتدفع الأرقام إلى مستودع بيانات. الإجماليات موجودة بالفعل في الملفات — حسبها Excel وحفظها. لكن لحظة تسأل المهمة خلية صيغة عن قيمتها، فإن مكتبة ليس لديها سوى إجابة واحدة لهذا السؤال تبني رسما بيانيا للتبعيات وتقيّم الورقة كلها، وتتحول مهمة كان ينبغي أن تكون مقيدة بالإدخال والإخراج إلى معيار قياس للحساب
لماذا تكلّف قراءة خلية صيغة إعادة حساب كاملة؟
لأن جالب القيمة على خلية صيغة هو طلب لإنتاج قيمة، والطريقة الصحيحة عالميا الوحيدة لإنتاج واحدة هي تقييم الصيغة. هذا هو الافتراضي الصحيح لتطبيق يحرّر المصنفات، والافتراضي الخاطئ لخط معالجة يستخرجها. والأسوأ أن التقييم ليس خاليا من الآثار الجانبية: فهو يكتب النتائج مرة أخرى في الخلايا، ويقلّب أعلام الاتساخ، ويمكن أن تُحَل الأمور بشكل مختلف عن التطبيق المنتج حين تكون دالة غير مدعومة أو مرجع خارجي معطوبا. مهمة وصفتها لفريق العمليات بأنها للقراءة فقط تنتج بهدوء مصنفا لم يعد يطابق الموجود على القرص، وإن حفظه شيء لاحقا يتغير الملف على القرص أيضا
قراءة القيم المخزنة مؤقتا هي النصف الآخر من العقد. إنها تجيب عن سؤال أضيق — ماذا خزّن التطبيق المنتج هنا؟ — وترفض الإجابة عن أي شيء آخر. وحين تريد أرقاما طازجة فعلا، ما زال HotXLS يمنحك إعادة حساب تزايدية مدفوعة برسم بياني للتبعيات؛ بيت القصيد هو أن الاستخراج والتقييم يجب أن يكونا استدعاءين مختلفين، لا استدعاء واحدا بمزاجين
ثلاث حقائق متعامدة عن خلية واحدة
الخلاصة أولا: القيمة المخزنة مؤقتا لصيغة تحمل ثلاث حقائق مستقلة، وطيّها في Variant واحدة يضيّع معلومات تحتاجها. يبقيها TXLSFormulaCacheInfo منفصلة بوصفها State وKind وValue. يسجل TXLSFormulaCacheState الأصل عبر خمس حالات — xlfcsNotFormula وxlfcsMissing وxlfcsLoaded وxlfcsCalculated وxlfcsInvalidated — بينما يصنف TXLSFormulaCacheValueKind الحمولة كـxlfcvBlank أو xlfcvNumber أو xlfcvDateTime أو xlfcvString أو xlfcvBoolean أو xlfcvError. هذا الفصل هو ما يسمح بالإبلاغ عن الوجود بصدق: الفراغ المخزّن مؤقتا، والسلسلة الفارغة المخزنة مؤقتا، وFalse المخزنة مؤقتا، والصفر المخزّن مؤقتا، والخطأ المخزَّن مؤقتا، كلها قيم حقيقية، لذا لا يمكن أبدا استنتاج الوجود من VarIsEmpty أو VarIsNull. يُرجع TryGetCachedFormulaValue True فقط مع xlfcsLoaded وxlfcsCalculated، ويملأ مع ذلك حالة قابلة للتشخيص حين يُرجع False
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex وRow وCol كلها ذات أساس أحادي هنا
if Book.TryGetCachedFormulaValue(1, 12, 5, Info) then
Writeln('cached value: ', VarToStr(Info.Value))
else
Writeln('no usable cache, state ordinal ', Ord(Info.State));
finally
Book.Free;
end;
end;
لماذا القيمة المخزنة مؤقتا مفقودة؟
هناك أربعة أسباب بالضبط يُعيد فيها TryGetCachedFormulaValue False، والحالة تخبرك أيها ينطبق. xlfcsNotFormula تعني أن الخلية تحمل قيمة حرفية أو لا شيء إطلاقا، وكون الإحداثيات خارج المدى ينهار إلى الإجابة نفسها. xlfcsMissing تعني أن الخلية صيغة فعلا لكن المنتج لم يخزّن لها حمولة قيمة — وهو ناتج شائع حين يكتب مولّد صيغا ويترك Excel يملأ النتائج عند أول فتح. xlfcsInvalidated تعني أن نص الصيغة استُبدل بعد التحميل، فالقيمة التي كانت هناك تصف تعبيرا لم يعد موجودا. أما xlfcsCalculated فعلى النقيض حالة نجاح: فهي تميّز قيمة أنتجها كودك أنت أو مقيّم HotXLS خلال هذه الجلسة، خلافا لـxlfcsLoaded التي أتت من الملف
الصدق بشأن ذاكرة التخزين المفقودة أهم من التغطية عليها. يرفض HotXLS اختراع قيمة، وهو عند الحفظ صارم بالقدر نفسه — فقط xlfcsLoaded وxlfcsCalculated تُصدران قيمة مخزنة مؤقتا، بينما xlfcsMissing وxlfcsInvalidated تكتبان الصيغة وحدها بدلا من تجميد رقم بالٍ في الملف. يترك لك ذلك ثلاث استجابات عاقلة في خط معالجة: تخطّي الصف وتسجيل الفجوة، أو إعادة حساب ذلك المصنف عمدا وقبول التكلفة، أو التقييم والمطابقة. وإن اختلف الرقم المقيَّم عما كان التطبيق المنتج سيكتبه، فإن متعقّب تقييم الصيغ هو الأداة لمعرفة أين تتباعد الحسابان، بدلا من التخمين من النتيجة
قارئ واحد عبر محركات BIFF الكلاسيكية وOOXML وODF
لا ينبغي لخط معالجة أن يهتم بما إذا كان الملف الذي فتحه للتو BIFF أو OOXML أو ODF. IXLSFormulaCacheReader هي نقطة الدخول الوحيدة للقراءة فقط للثلاثة: كل من TXLSWorkbook.CreateFormulaCacheReader وTXLSXWorkbook.CreateFormulaCacheReader يُرجع مهايئا خفيفا فوق البحث المتفرّق عن الخلايا الذي يستخدمه كل محرك أصلا، بإحداثيات ورقة وصف وعمود متطابقة ذات أساس أحادي. فئات المصنفات تتعمّد ألا تطبّق الواجهة بنفسها — فمرجع واجهة إلى المصنف سيغير دلالات ملكيته ويسمح للمستدعين بالتسلل عبر عقد مدة الحياة. بدلا من ذلك، إتلاف المصنف يمسح المؤشر الخام داخل ذلك العقد، وأي قارئ ما زال كودك يحتفظ به يثير EXLSFormulaCacheReaderInvalidated عند استعلامه التالي بدلا من إزاحة مرجع إلى ذاكرة محرَّرة. إنه فحص مدة حياة سريع الفشل، لا ضمان تزامن
var
Reader: IXLSFormulaCacheReader;
Info: TXLSFormulaCacheInfo;
Row, Missing, Errors: Integer;
Total: Double;
begin
Reader := Book.CreateFormulaCacheReader;
Total := 0;
Missing := 0;
Errors := 0;
for Row := 2 to LastRow do
if Reader.TryGetCachedFormulaValue(1, Row, 7, Info) then
begin
case Info.Kind of
xlfcvNumber: Total := Total + Double(Info.Value);
xlfcvError: Inc(Errors);
end;
end
else if Info.State = xlfcsMissing then
Inc(Missing);
// لم تعمل أي حاسبة، ولم يتحرك أي علم اتساخ، وBook بلا تغيير
end;
أين تقطن البايتات المخزنة مؤقتا فعلا
في ملفات .xls الكلاسيكية تكون الذاكرة المؤقتة هي الحقل FormulaValue لسجل Formula، ثمانية بايتات يصفها [MS-XLS] §2.5.133. حين تساوي الكلمة العليا $FFFF لا تكون الحمولة مزدوج IEEE 754 بل متغيرا موسوما، والتخطيط سهل أن يُخطأ فيه بمهارة: نوع المتغير يقع في val[0] وحمولة البولياني أو BErr تقع في val[2]، بينما val[1] غير معرّف. كان HotXLS سابقا يقرأ الحمولة من val[1]، وهو نوع خطأ الفرق بواحد الذي يظهر فقط على ملفات بعينها تخزّن بوليانيًا أو خطأ بدلا من رقم. يتفق القارئ وكاتب الصيغ المشتركة الآن على الإزاحات نفسها، فتنجو TRUE المخزنة مؤقتا من التحميل والحفظ سليمة بدلا من أن تتحلل إلى ضجيج
أمانة النوع في صيغ الحزم مشكلة منفصلة بفخها الخاص. في OOXML تتدلى القيمة المخزنة مؤقتا من العنصر c بصيغة <v>، مع السمة t التي تسمّي النوع حسب ECMA-376 Part 1 §18.3.1.4. يقرأ HotXLS t="e" مباشرة في متغير varError ويعيد ربطه بنص الخطأ القياسي عند الحفظ، فلا تتنكر الأخطاء أبدا كأعداد صحيحة عادية — لكن RTL الخاصة بـDelphi لن تساعدك هنا، لأن VarAsType(Integer, varError) تثير استثناء تحويل. البناء العامل يضبط TVarData.VType وTVarData.VError مباشرة. التواريخ تتبع الانضباط نفسه في الاتجاه المعاكس: t="d" ونوع قيمة التاريخ في ODF تصريحان نوعيان صريحان ويصبحان varDate، بينما ذاكرة BIFF الرقمية لا تحمل أي علم تاريخ ولذلك تبقى Double. لا يخمّن HotXLS تاريخا أبدا من تنسيق رقم الخلية، لأن تنسيق الرقم عرض والذاكرة المؤقتة بيانات. يضيف ODF حالة أخرى جديرة بالمعرفة — office:value-type="void" تعبّر عن ذاكرة مؤقتة موجودة لكنها لا تحمل قيمة، وبما أن ODF بلا نوع قيمة خطأ، فإن النص الذي يشبه الخطأ يُحفَظ كنص بدلا من ترقيته إلى خطأ
function DescribeCache(const Info: TXLSFormulaCacheInfo): string;
begin
case Info.State of
xlfcsNotFormula: Result := 'not a formula cell';
xlfcsMissing: Result := 'formula stored with no cached value';
xlfcsInvalidated: Result := 'formula replaced since load';
else
case Info.Kind of
xlfcvError: Result := 'error code ' + IntToStr(TVarData(Info.Value).VError);
xlfcvDateTime: Result := DateTimeToStr(VarToDateTime(Info.Value));
xlfcvBoolean: Result := BoolToStr(Info.Value, True);
xlfcvNumber: Result := FloatToStr(Double(Info.Value));
xlfcvString: Result := VarToStr(Info.Value);
else
Result := 'present but blank';
end;
end;
end;
هل تشارك الصيغ المشتركة قيمها المخزنة مؤقتا؟
لا، وافتراض العكس هو كيف ينتهي مسح بالإبلاغ عن الرقم نفسه لعمود كامل. الصيغة المشتركة في OOXML تشارك تعبير الصيغة وتحسين التخزين فقط؛ كل خلية عضو ما زالت تملك <v> خاصة بها. لذلك لا ينشر HotXLS أبدا ذاكرة العضو الجذر إلى تابع وصل بلا قيمة، والتابع الذي حُمِّل كـxlfcsMissing ما زال يُبلغ عن xlfcsMissing بعد الحفظ وإعادة الفتح. وإن كنت تدرس كيف تُخزَّن المجموعة وتُوسَّع أصلا، فإن آليات سمة si للصيغة المشتركة وتوسيعها مشروحة في موضع منفصل؛ أما لقراءة الذاكرة المؤقتة، فالقاعدة تختصر في سطر واحد — اسأل كل خلية، ولا تثق بشيء لم تسأل عنه
قراءة القيم المخزنة مؤقتا، والقارئ الموحّد عبر المحركات، ومحرك إعادة الحساب الذي يمكنك أن تختار ألا تستدعيه، كلها تشحن في مكوّن HotXLS القياسي لجداول بيانات Delphi لـDelphi وC++Builder، دون أي اعتماد على Excel أو على أي خادم أتمتة OLE؛ صفحة المنتج تحمل مرجع API الكامل لنقاط دخول المصنفات والقارئ الموضحة هنا