مقال تقني

تأطير سجلات PivotCache في BIFF مع HotXLS في Delphi

يخزن التدفق الفرعي PivotCache في BIFF مجموعة بيانات PivotTable المخبأة منفصلة عن العرض الذي يعرضها، ويقرأ HotXLS ذلك التدفق ويكتبه بفحص متن السجلات لا بالوثوق بأرقام السجلات. وهذا التمييز هو القصة كلها: رقم السجل نفسه يحمل تخطيطَي متن غير متوافقين بحسب أي كاتب أنتج الملف، فيقرر القارئ التأطير من أول متن سجل يراه

وتقابل هذه الطبقة اللحظة التي يجب فيها أن ينجو PivotTable من دورة كاملة. فعرض pivot بلا ذاكرته قشرة، وسيعيد Excel بناء الذاكرة من المجال المصدر حين يفتح الملف، وهذا لا بأس به حتى النقطة التي يكون فيها المجال المصدر قد زال، أو لُصقت البيانات من استعلام، أو كان المصنف إقفالًا مؤرشفًا يجب ألا يتغير حين يفتحه أحد

بنيتان وموضعان في الملف

البيانات المخبأة وتعريف الذاكرة يسكنان أجزاء مختلفة من المصنف، وخلطهما أول ما يجب أن تصيب صوابه. فالسجلات المخبأة تشكل تدفقًا فرعيًا خاصًا بها، معطى في [MS-XLS] §2.1.7.12 بوصفه PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. ولاحظ ما هو غائب: لا BOF في رأس ذلك الإنتاج

ويجلس التعريف بدلًا من ذلك في عالميات المصنف، بوصفه PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3)، موضوعًا بعد سجلات التنسيق وقبل سجلَّي BoundSheet وCountry. فذاكرة واحدة توصف في موضعين يفصلهما مئات السجلات، والرابط بينهما معرف تدفق يجب أن يوافق في ثلاثة مواضع مرة واحدة

تأطير PivotCache في HotXLS داخل BIFF8: يجلس PIVOTCACHEDEFINITION بمعرفه SXStreamID في عالميات المصنف بعد التنسيق وقبل BoundSheet، بينما تسكن السجلات المخبأة تدفقًا تحت مخزن _SX_DB_CUR المسمى بسداسي عشري بأربعة أرقام كبيرة الحالة يحمل سجلات SXDB وSXDBEx وSXFORMULA وFDB وDBB بلا BOF، وعلى SXStreamID.idStm وحقل idstm في SXDB واسم التدفق أن يوافقوا
ذاكرة pivot واحدة توصف في موضعين يفصلهما مئات السجلات، تربطهما معرف تدفق عليه أن يوافق في العالميات وترويسة SXDB واسم التدفق الفرعي مرة واحدة

كل ذاكرة تعود إلى تدفق تحت _SX_DB_CUR اسمه تهجئة معرفها سداسيًا عشريًا بأربعة أرقام كبيرة الحالة. وعلى SXStreamID.idStm وحقل idstm المكرر في ترويسة SXDB واسم التدفق ذاك أن يطابقوا جميعًا. وحين تخصص معرفًا جديدًا، احجز كل رقم قُرئ من الملف أولًا، وإلا يستطيع جد ذاكرة جديد أن يطالب برقم يعود لذاكرة أقدم لم يمشِ القارئ إليها بعد

ومعرف آخر يصطاد الناس. فقيمة iCache في عرض pivot هي موضع SXStreamID الموافق في التسلسل العالمي بترقيم يبدأ من صفر، لا معرف ذاكرة تختاره أنت. وعند الكتابة عليه أن يُحوَّل من كائن الذاكرة إلى موضع إخراجها الفعلي، وتعيد العروض القائمة ترقيمها معه، وإلا أشارت ترقية ذاكرة واحدة بصمت عرضًا إلى ذاكرة أخرى

var
  Book: TXLSWorkbook;
  Cache: TXLSPivotCache;
  Field: TXLSPivotCacheField;
  V: TXLSPivotCacheValue;
begin
  Book := TXLSWorkbook.Create(nil);
  try
    Book.LoadFromFile('sales.xls');
    Cache := Book.PivotCaches.Add;
    Cache.SourceRangeSheet := 'Data';
    Cache.SourceFirstRow := 1;  Cache.SourceFirstCol := 1;
    Cache.SourceLastRow := 500; Cache.SourceLastCol := 6;
    Cache.SourceDataType := 1;        // SXVS SHEET، MS-XLS 2.4.317
    Cache.RefreshOnLoad := False;     // ثق بالسجلات المخبأة
    Cache.SaveData := True;

    Field := Cache.AddField('Region', xlpcftString);
    V.ValueType := xlpcftString;
    V.StrValue := 'North';
    Field.FindOrAddItem(V);

    Cache.SetRecordCount(0);          // صفّر، ثم حدد حجم شبكة السجلات
    Cache.SetRecordCount(500);
    Book.StorePivotCaches;
  finally
    Book.Free;
  end;
end;

وSetRecordCount المكررة ليست خرافة. فـ RecordCount كتابة خاصية عادية لا تخصص، ومسار النمو الداخلي يهيئ الصفوف المضافة حديثًا فقط، فذاكرة ضُبط عدّها عبر مسار الترويسة قد تنتهي بشبكة فهارس طولها صفر. وتُرمى كتابات RecordIndices حينها دون خطأ. وضبط العدد صفرًا ثم رجوعه يعيد تأسيس الشبكة، وعليه أن يحدث بعد إضافة كل حقل، لأن عرض الصف يأتي من عدد الحقول

لماذا لا يستطيع رقم سجل أن يخبرك بتخطيط المتن

لأن أرقام السجلات وتخطيطات المتن تغيرت في أوقات مختلفة، فالتحويل بينهما ليس دالة. فرقم واحد في الطقم الإرثي لا يظهر إلا في ملفات من كتّاب أقدم، وهذا يجعله إشارة موثوقة في اتجاه واحد. ورقم آخر غامض فعلًا: يظهر في الملفات الصحيحة وفي مدى من الإصدارات الوسيطة التي استخدمت الرقم الجديد مع تخطيط المتن القديم

عليه يجب أن يُقرر التأطير من المتن، ومرة لكل تدفق فرعي ذاكرة لا لكل سجل. يثبّت HotXLS اللهجة من طول أول سجل SXDBB في كل تدفق فرعي. وفي تأطير المواصفة، يحمل SXDBB واحد سجل ذاكرة واحدًا بالضبط، فيساوي طوله عرض صف واحد. وفي التأطير المعبأ الأقدم، يحمل السجل الأول ما يتسع من صفوف، فأي ذاكرة بأكثر من صف يبلغ طوله عرضَي صفين على الأقل. والمقارنة حاسمة كلما اختلف التنبآن

مثبت تأطير SXDBB في HotXLS: رقم سجل واحد يحمل تخطيطَي متن غير متوافقين، فيقارن القارئ طول أول سجل SXDBB بعرض الصف، ويثبت عرض صف واحد اللهجة وفق المواصفة بينما يثبت عرضان من الصفوف أو أكثر التأطير المعبأ الإرثي، وتأخذ التعادلات قراءة المواصفة، وتثبت اللهجة مرة لكل تدفق فرعي ذاكرة لا لكل سجل
لا تستطيع أرقام السجلات أن تقرر تخطيط المتن لأن الاثنين تغيرا في أوقات مختلفة، لذا يثبت HotXLS اللهجة مرة لكل تدفق فرعي من طول أول SXDBB ويأخذ قراءة المواصفة عند التعادل

وحين لا يختلفان، تأخذ القراءة قراءةَ المواصفة، على المبدأ أن الملفات التي كتبها Excel تعدو عدد الملفات التي كتبها بناء وسيط. وتلك النقطة العمياء ضيقة بالبناء، وحين تحدث فعلًا، ما زال الملف نفسه يعاد تشغيله بايتًا بايتًا. والفهارس المكتوبة الأنواع المعرضة للمستدعين وحدها هي المتأثرة

عرض الفهرس يسكن سجلًا مختلفًا

يحمل SXDBB (§2.4.276) فهرسًا واحدًا لكل حقل ذاكرة وعلم قيمه المميزة مضبوط، بترتيب الحقول، وعرض كل فهرس يُقرر في مكان آخر: يعلن سجل الحقل SXFDB الموافق (§2.4.283) علم بنود قصيرة، وذلك العلم يقول هل يشغل الفهرس بايتين أم واحدًا. سجلان، وعقد ضمني واحد، وجملة واحدة في المواصفة تربطهما

وهذا الاقتران بالضبط موضع أخطاء الترميز ذاتي الصنع. فكاتب HotXLS سابق عبّأ كل حقل في أدنى عدد من البتات، مضمرًا إلى حد بايت بين الصفوف، وهو أمر يمكن الدفاع عنه بمعزل ومتناقض مباشرة مع العرض الذي أعلنه الكاتب نفسه قبل قليل في SXFDB. فحقل بثلاث قيم مميزة وُصف بعرض بايت واحد في سجل وشغل بتين في الآخر. ولم يكن الإصلاح تصحيح الحساب بل استخراج قرار العرض إلى دالة واحدة يستدعيها كلا المُصدِرَين، فلا يستطيع السجلان أن يفترقا بعد الآن. وذلك الصنف نفسه من العيوب الذي تصفه مقالة انحراف إعلان طول سجل BIFF، حيث ينفصل حجم معلن عن متن فعلي

ونتيجة عدم قراءة هذه السجلات إطلاقًا تستحق التوضيح، لأنه من السهل التقليل منها. حين تخطى القارئ فهارس السجلات، بلّغت كل ذاكرة محملة من ملف فهرسًا صفريًا لكل حقل من كل صف، أي أن كل صف أشار إلى أول قيمة لكل حقل. وهذا ليس مجرد انكماش في الفحص الداخلي: مسار تقييم pivot ومسار الملء من الذاكرة إلى الخلايا كلاهما يستهلك تلك الشبكة. واختبار دورة كاملة لا يستطيع كشفه، لأن الذاكرة التي ما زالت على إعادة التشغيل الخام تُكتب ردًا من بايتاتها الأصلية

// أعلام المصدر تخبرك بما تحمله وما يجوز إعادة كتابته
if Cache.FromRawBlobs then
begin
  Writeln('stream id        : ', IntToHex(Cache.StreamId, 4));
  Writeln('legacy framing   : ', Cache.RawFramingIsLegacy);
  Writeln('own storage      : ', Cache.RawHasStorageStream);
  Writeln('model complete   : ', Cache.RawModelIsComplete);
  // إعادة الإصدار بلا خسارة فقط عندما يكون لكل سجل نموذج هنا
  if Cache.CanUpgradeFraming then
    Writeln('safe to rewrite with the current emitters');
end;

متى تكون إعادة كتابة ذاكرة بلا خسارة

فقط حين تسري ثلاثة شروط معًا، وCanUpgradeFraming هي الخاصية المنفردة التي تجيب عن السؤال. يجب أن تكون الذاكرة ما زالت على إعادة التشغيل الخام، وأن يكون التدفق الفرعي في أحد التأطيرات التي كتبتها هذه المكتبة خطأً سابقًا، وأن يكون القارئ قد بنى نموذجًا مكتوب الأنواع كاملًا لكل سجل فيها. وذاكرة كتبها Excel لا تتأهل قط، لأن تدفقها الفرعي يحمل سجلات لا نموذج لـ HotXLS عنها، وإعادة إصدارها من النموذج ستُسقطها

واختبار الاكتمال أشد صرامة مما يبدو أول الأمر. فالسجل الذي أبقى القارئ بايتات معتمة فقط يوسم النموذج ناقصًا. وكذلك عدد معلن من سجلات الصيغ لا يستطيع المُصدِر إعادة إنتاجه، لأن إعادة الإصدار ستعيد كتابة إعلان لعدة سجلات صيغ بوصفه إعلانًا ولا شيء، وقيمة في الملف لا يمكن إعادة إنتاجها تعادل سجلًا لا يمكن إعادة إنتاجه

والتحفظ المتعمد يجري في الكاتب أيضًا. فالفهارس تُثبَّت داخل المدى المشروع بدل ترميزها حارسًا خارج النطاق، لأن المواصفة تعرّف فهرسًا في تسلسل القيم المميزة ولا شيء غير ذلك، والخلية الفارغة هي نفسها قيمة في ذلك التسلسل. ومتن سجل ذاكرة يتجاوز سقف سجل BIFF لا يُكتب إطلاقًا، وهو أمر يستلزم آلاف حقول الذاكرة ولا يمكن بلوغه ضمن حد أعمدة BIFF8 على أي حال؛ والبديل أن Excel يحدّث من المجال المصدر، وهو سلوك معرف لا ملف تالف

والتواريخ تحمل آخر اعتمادية عابرة للسجلات. فتحويل الرقم التسلسلي إلى تاريخ يعتمد على نظام تواريخ المصنف، ولا يستطيع مُصدِر السجلات رؤية المصنف، فيُمرر اختيار التاريخ الأساس وسيطًا افتراضه نظام 1900 ويزوده مسار الحفظ على مستوى المصنف. وتحت نظام 1900 يكون الرقم التسلسلي هو القيمة مباشرة؛ ونظام 1904 يختلف بـ 1462 يومًا. والمعاملة الأوسع للأرقام التسلسلية للتواريخ في الأرقام التسلسلية للتواريخ ونظام 1904 وصيغ الأعداد

وإن كنت تعمل عند طبقة العرض لا طبقة الذاكرة، فالسجلات التي تصف pivot المرئي مغطاة في طقم سجلات PivotTable في BIFF8، والسلوك من جهة الحساب في الحقول المحسوبة والبنود المحسوبة والتحديث. والطبقات الثلاث كلها ترد في مكوّن جداول HotXLS Delphi، وهو ما يجعل ممكنًا تحميل مصنف إرثي وفحص ما تحمله ذاكرته فعلًا وقرر ما إذا كانت إعادة كتابته آمنة قبل أن تفعل