مقال تقني

جولة ذهاب وعودة لـ XLSX دون فقدان للبيانات في دلفي: المظهر (Theme)، و extLst، و calcChain

تم بناء HotXLS، وهي مكتبة Excel الأصلية لدلفي و C++Builder، لجولات ذهاب وعودة لـ XLSX دون فقدان للبيانات: افتح مصنفًا، وغير خلية واحدة، واحفظ، وسيصمد كل من مظهر (theme) العميل المخصص، وكتل الامتداد extLst الخارجية، وسلسلة الحساب. وتجعل ثلاث آليات ذلك يعمل — التخزين المؤقت الحرفي لملف xl/theme/theme1.xml، وإعادة التسلسل القائم على الأحداث لكتل <ext> غير المعروفة، وملف xl/calcChain.xml الجديد والصالح للمواصفات عند كل حفظ لمصنف يحتوي على صيغ

السيناريو الذي يدفع بهذه الآليات الثلاث شائع بشكل محبط. تقوم خدمة الفوترة بتحميل قالب صممه العميل في Excel — مظهر ألوان الشركة، وخطوط المؤشرات (sparklines) في عمود KPI، وقاعدة تنسيق شرطي تمت إضافتها بواسطة إصدار Excel أحدث — وتكتب إجمالي فاتورة واحدة في الخلية B3، وتحفظ. ويفتح العميل النتيجة فيجد ألوان العلامة التجارية قد عادت إلى أزرق Office الافتراضي، واختفت خطوط المؤشرات، ويعرض Excel "إصلاح" الملف. ولم يلمس أي شيء في الكود أيًا من تلك الميزات. ولكن المكتبة فعلت ذلك، ببساطة عن طريق الحفظ

لماذا تفقد ملفات Excel تنسيقها بعد تعديلات المكتبة؟

تفقد ملفات Excel تنسيقها بعد تعديلات المكتبة لأن معظم المكتبات لا تعدل الملف — بل تعيد بناءه. وحزمة .xlsx هي ملف ZIP لأجزاء XML: هي xl/workbook.xml، وملف xl/worksheets/sheetN.xml واحد لكل ورقة، و xl/styles.xml، و xl/theme/theme1.xml، و xl/calcChain.xml، وغيرها. وتحلل المكتبة النموذجية تلك الأجزاء إلى نموذج كائن عند الفتح وتجدد كل جزء من هذا النموذج عند الحفظ. وأي ميزة لا يمثلها النموذج — كمظهر لم يحلله أبدًا، أو كتلة امتداد من إصدار Excel أحدث — ليس لها مكان لتعيش فيه في الذاكرة، وبالتالي فإن الجزء المعاد بناؤه يسقطها صمتًا

كيف يحافظ HotXLS على المظهر المخصص بايت ببايت؟

يحافظ HotXLS على مظهر المصنف عن طريق تخزين البايتات الأصلية لملف xl/theme/theme1.xml مؤقتًا عند وقت الفتح وكتابتها حرفيًا عند وقت الحفظ. وجزء المظهر (theme) (مواصفة ECMA-376 الجزء 1، الفقرة §14.2.7) هو لغة DrawingML، وليس SpreadsheetML — مخططات الألوان، مخططات الخطوط، مخططات التنسيق — ولا يوجد سبب يجعل محرك جداول البيانات يمثلها بعمق. وكانت إصدارات HotXLS السابقة تجدد مظهر Office ثابتًا عند كل حفظ، وهو بالضبط فشل "ألوان العلامة التجارية عادت للوراء" المذكور أعلاه؛ ومنذ الإصدار v2.89.46 يتم تخزين مظهر الحزمة المفتوحة خامًا وإعادة إرساله دون مساس، ويتم إنشاء مظهر Office المدمج فقط للمصنفات التي تم إنشاؤها من الصفر. والبايتات الخام هي أقوى ضمان ممكن للدقة: لا تحليل، لا إعادة تسلسل، لا فرصة للانحراف

تفوز النسخة الحرفية عمدًا على الوصول البرمجي للمظهر. وتعرض TXLSXWorkbook الخصائص ThemeMajorFont و ThemeMinorFont بحيث يمكنك اختيار خطوط العناوين والجسم للمصنفات الجديدة، ولكن عندما تم التقاط مظهر حرفي عند الفتح، فلن يكون لواضعي تلك الخصائص أي تأثير على الملف المحفوظ — فلجولة الذهاب والعودة الأولوية. وإذا كنت بحاجة فعليًا إلى تغيير مظهر مصنف موجود، فهذه إشارة لتعديل القالب في برنامج Excel نفسه بدلاً من واجهة برمجة تطبيقات موجهة للبيانات. ولا تحتاج الحالة اليومية إلى أي واجهة برمجة تطبيقات على الإطلاق:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // the one edit
    Book.SaveAs('branded-invoice-out.xlsx');
    // theme1.xml in the output is byte-identical to the input
  finally
    Book.Free;
  end;
end;

ماذا يحدث لكتل extLst غير المعروفة عند الحفظ؟

يلتقط HotXLS كل كتلة <ext> على مستوى ورقة العمل لا يمثلها داخليًا ويعيد تشغيلها في extLst لورقة العمل المحفوظة، بحيث تصمد الميزات المكتوبة بواسطة إصدارات Excel الأحدث خلال جولة الذهاب والعودة سليمة. ومنذ الإصدار v2.131.0، تكون الأجزاء الملتقطة مرئية من خلال خاصية للقراءة فقط RawWorksheetExts، وهي TStringList في كل ورقة عمل XLSX، مما يجعل الضمان قابلاً للتدقيق من كود الاختبار بدلاً من كونه مجرد اعتقاد:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('from-newer-excel.xlsx');
    Sheet := Book.Sheets[0];
    WriteLn(Format('%d foreign ext block(s) captured',
      [Sheet.RawWorksheetExts.Count]));
    for i := 0 to Sheet.RawWorksheetExts.Count - 1 do
      WriteLn(Copy(Sheet.RawWorksheetExts[i], 1, 100)); // peek at each uri
  finally
    Book.Free;
  end;
end;

تفصيل التنفيذ الجدير بالمعرفة هو أن الالتقاط هو إعادة تسلسل على مستوى الأحداث، وليس نسخة بايتات خام. ولا يكشف قارئ XML المتدفق لـ HotXLS عن إزاحات المصدر، وبالتالي يتم إعادة بناء الشجرة الفرعية غير المعروفة من أحداث Element و Text و EndElement أثناء تدفقها. ويخفي هذا النهج فخًا كلاسيكيًا واحدًا: فالعنصر ذاتي الإغلاق مثل <a/> يطلق فقط حدث Element محددًا كفارغ وليس حدث EndElement أبدًا، وبالتالي فإن أي عداد عمق يتناقص فقط عند EndElement لن يرى الشجرة الفرعية تغلق أبدًا. وإذا قمت بمعالجته، فسيكون الجزء المعاد بناؤه مكافئًا دلاليًا للأصل — حيث يتم تطبيع اقتباسات السمات والنماذج ذاتية الإغلاق، لذا فهو ليس متطابقًا في البايتات، ولكن Excel يقرأ المعنى وليس البايتات. وتجعل خاصيتان لمخرجات Excel نفسها إعادة التشغيل آمنة: يعلن Excel عن سمات xmlns اللازمة على عنصر <ext> أو داخله، بحيث يكون كل جزء ملتقط مكتفيًا ذاتيًا من حيث مساحة الأسماء، وهذا الاكتفاء الذاتي نفسه هو السبب في أن نسخ ورقة عمل داخل أو عبر المصنفات يمكن أن ينقل الكتل الخارجية مع تعيين بسيط لقائمة السلاسل

كتابة calcChain.xml بحيث يثق Excel في صيغك

يكتب HotXLS ملف xl/calcChain.xml (جزء سلسلة الحساب، مواصفة ECMA-376 الجزء 1، الفقرة §12.3.1) عندما يحتوي المصنف المحفوظ على صيغ، ويختار بين ترتيبين. فإذا تم بناء مخطط تبعية الصيغ بالفعل وكان حديثًا — كاستدعائك لـ Recalculate بعد تعديلك الأخير — يتم إرسال السلسلة بالترتيب الطوبولوجي الكامل، التابعين قبل المتبوعين، مع إلحاق أي أعضاء مراجع دائرية في النهاية. وإلا يتم إدراج الخلايا بترتيب المستند. وكلاهما صحيح: تعامل ملاحظات تنفيذ مايكروسوفت للتنسيق، [MS-XLSX]، سلسلة الحساب كتلميح يتحقق منه Excel ويعيد ترتيبه أثناء التحميل، وبالتالي فإن أي قائمة كاملة هي قانونية، ويرفض HotXLS عمدًا فرض بناء مخطط داخل SaveAs — فبناء الحواف هو عملية تربيعية في عدد الخلايا، وهي تكلفة خفية غير مقبولة في حفظ يحتوي على مليون خلية

Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Saved now, calcChain.xml lists formula cells in document order.
// After Recalculate the dependency graph exists, so the same save
// emits a full topological order instead:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

أين تنتهي جولة الذهاب والعودة دون فقدان للبيانات

الصداقة تهم أكثر من مجرد علامة تسويقية هنا، لذا تستحق الحدود نفس الأهمية. لا يقوم HotXLS بنسخ الحزمة بأكملها بايت ببايت: فيتم تجديد ملفات XML لورقة العمل، والأنماط، والسلاسل المشتركة، وأجزاء المصنف من النموذج المحلل، وبالتالي تكون المخرجات أمينة دلاليًا ولكنها ليست متطابقة ثنائيًا — فتحمل ترويسات ZIP المحلية وحدها طوابع زمنية جديدة لـ DOS. وتعود أجزاء <ext> الملتقطة مطبعة، كما هو موضح أعلاه. ويتم تجاهل التجاوزات البرمجية لخطوط المظهر عند وجود مظهر حرفي. وشبكة الحفظ لها شبكة محددة: ميزات يمثلها HotXLS داخليًا (خطوط المؤشرات، على سبيل المثال، يتم تحليلها وإعادة كتابتها بدلاً من نسخها بشكل أعمى) بالإضافة إلى محتوى extLst الخارجي بالإضافة إلى الأجزاء المخزنة مؤقتًا حرفيًا. والجزء الذي لم يتم تمثيله ولا يقع داخل نقطة امتداد — كجزء مخصص لوظيفة إضافية غريبة، مثلاً — يقع خارج الآليات الثلاث التي يغطيها هذا المقال، لذا اختبر قوالبك الفعلية بدلاً من الافتراض

يكتمل المشهد بأعمال الحفظ المجاورة. فتمر مشاريع VBA ومراجع المصنفات الخارجية عبر الحفظ بنفس فلسفة "احتفظ بما لا تمثله"، والمغطاة في المقال المصاحب حول حفظ مشاريع VBA والروابط الخارجية، وخصائص المستند في docProps لها واجهة برمجة تطبيقات القراءة والكتابة الخاصة بها بدلاً من إسقاطها صمتًا. وعندما تقيم أي مكتبة جداول بيانات، أجرِ اختبار الخلية الواحدة: افتح مصنف إنتاج غني بالميزات، وغير قيمة واحدة، واحفظ، وقارن الأجزاء غير المضغوطة بالأصل. وما تغير خارج الورقة التي لمستها يخبرك عن المكتبة أكثر من أي مصفوفة ميزات

تشحن آليات جولة الذهاب والعودة الموضحة هنا — الاحتفاظ بالمظهر منذ الإصدار v2.89.46، والتقاط extLst الخارجي وإرسال calcChain.xml منذ الإصدار v2.131.0 — في مكون HotXLS لدلفي و Excel الحالي، وتوثق صفحة منتجه مجموعة ميزات قراءة وكتابة XLSX الكاملة لدلفي و C++Builder