مقال تقني

تقسيم التنسيقات الشرطية المرتبطة بمرساة في HotXLS

يقسّم HotXLS، مكوّن Excel لـDelphi وC++Builder، تلقائيًا قاعدة تنسيق شرطي أو تحقق من صحة بيانات إلى كائني قاعدة منفصلين أو أكثر كلما قطع إدراج أو حذف صف أو عمود نطاق تغطية القاعدة إلى قطع تحتاج مراسي صيغة نسبية مختلفة، ثم يعيد تعيين رقم أولوية جديد وفريد لكل قاعدة تنسيق شرطي. شُحن هذا السلوك في الإصدار 2.196 من محرك XLSX ويعمل تلقائيًا، دون إعداد لإلغاء تفعيله. المُحفِّز ضيق النطاق لكن شائع: قاعدة cellIs أو تعبير تقرأ صيغتها خلية بالنسبة لنطاقها الخاص، تعيش في ورقة عمل يُدرج فيها لاحقًا أو يُحذف منها صف في مكان ما وسط ذلك النطاق بالضبط

تتوقف معظم المقالات حول أتمتة Excel عند مشكلة نص الصيغة: إزاحة أرقام الصفوف والأعمدة داخل كل SUM() وكل VLOOKUP() بحيث تظل المراجع تشير إلى الخلايا الصحيحة. ذلك النصف من القصة حقيقي، ومشروح في المقالة المرافقة حول كيفية إعادة كتابة HotXLS لمراجع الصيغ عند تحرك الصفوف والأعمدة، لكن التنسيق الشرطي أو قاعدة التحقق من صحة البيانات ليست مجرد صيغة تجلس في خلية. فهي تقرن صيغة بنطاق، sqref بمصطلحات ECMA-376، ويجب أن يتحرك الاثنان معًا. عندما يقطع تعديل بنيوي ذلك النطاق إلى قطعتين ستحتاجان إزاحتين نسبيتين مختلفتين للبقاء صحيحتين، يتوقف الاحتفاظ بكائن قاعدة واحد بسلسلة صيغة واحدة عن كونه خيارًا، والتظاهر بخلاف ذلك هو كيف تبدأ قاعدة تظليل بصمت في مقارنة الصفوف الخاطئة

لماذا يقسّم إدراج صف قاعدة تنسيق شرطي بدلًا من مجرد نقلها؟

تحتفظ قاعدة تنسيق شرطي أو تحقق من صحة بيانات بصيغة واحدة بالضبط لنطاقها بأكمله، تُقيَّم بالنسبة لخلية مرساة واحدة، بحيث بمجرد أن يُجبر تعديل جزأين من ذلك النطاق على الحاجة إلى إزاحتين نسبيتين مختلفتين، لم تعد صيغة واحدة قادرة على وصف كلا الجزأين بشكل صحيح. يعبّر ECMA-376 عن تغطية قاعدة كخاصية sqref على عنصر conditionalFormatting أو dataValidation، ويُقيّم Excel Formula1 وFormula2 كما لو كُتب النص في الخلية العلوية اليسرى لذلك sqref وعُبّئ عبر بقيته، بالطريقة نفسها التي تُعبّئ بها صيغة نسبية عادية عمودًا. تخيّل تظليل انحراف فوق B2:B50 يُشير إلى أي رقم فعلي يتجاوز ميزانيته، مبني كقاعدة cellIs حيث Formula1 هي النص الحرفي C2، أي قارن خلية B للصف الحالي مقابل خلية C للصف نفسه

Idx := Sheet.AddConditionalFormat('B2:B50', xlsxCfOpGreaterThan, 'C2');
Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);

Sheet.InsertRows(25, 1);   // one blank separator row, starting at old row 25

أدرج صف الفاصل الواحد ذلك عند الصف القديم 25 ولن تتحرك الصفوف فوق نقطة الإدراج، لذا فإن حصتها من القاعدة لا تزال تقرأ Formula1 كـC2 بشكل صحيح. الصفوف التي كانت من 25 إلى 50 تنزلق إلى 26 حتى 51، وبالنسبة لها أصبحت C2 الآن الخلية الخاطئة كليًا، بما أن الصف 26 يحتاج للمقارنة مقابل C26، لا مقابل رقم ميزانية يبعد اثنتي عشرة صفًا فوقه

كيف يقرر HotXLS ما إذا كانت القاعدة تحتاج للتقسيم

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

إعادة تأسيس صيغة قطعة هي حركة من خطوتين تعيد استخدام آلية يحملها HotXLS بالفعل لمجموعات الصيغ المشتركة في OOXML: أولًا تُترجَم الصيغة كما لو كانت في الأصل مرتكزة عند خلية تلك القطعة العلوية اليسرى، باستخدام حسابات الإزاحة النسبية نفسها التي توسّع صيغة مشتركة عبر نطاقها، ثم تمر النتيجة عبر ماسح إزاحة الصفوف والأعمدة نفسه الذي يعيد كتابة صيغ ورقة العمل العادية. هذه هي الطريقة التي تنتقل بها Formula1 من C2 إلى C26 في حركتين بدلًا من حالة خاصة مكتوبة يدويًا واحدة: تُرجم C2 إلى الأمام بـ23 صفًا للحصول على C25، كما لو كانت القاعدة قد بدأت هناك دائمًا، ثم تدع الإزاحة العادية عند الصف 25 تدفعها إلى C26. كل خاصية أخرى، لون التعبئة، وإيقاف-عند-صحيح، والعامل نفسه، تنتقل دون تغيير إلى كائن القاعدة الجديد، بحيث يستمر كلا النصفين في تلوين الخلايا باللون الذي كانا يلونانه دائمًا

// ConditionalFormats now holds two rules instead of one:
//   B2:B25    Formula1 = 'C2'    (rows above the insert)
//   B26:B51   Formula1 = 'C26'   (rows that shifted down)

هل تنقسم أشرطة البيانات ومجموعات الأيقونات بالطريقة نفسها التي تنقسم بها قواعد cellIs؟

لا: يقسّم HotXLS فقط أنواع القواعد التي تعتمد صحتها فعليًا على صيغة نسبية لكل منطقة، أي مقارنات cellIs وقواعد التعبير، ويترك كل نوع تنسيق شرطي آخر ككائن قاعدة واحد تنمو sqref الخاصة به ببساطة ليغطي القطع المُزاحة كاتحاد متعدد المناطق. داخليًا الفرع هو مجرد فحص Kind، cf.Kind in [cfkCellIs, cfkExpression]، لا شيء أكثر غرابة من ذلك. تحمل أشرطة البيانات، ومقاييس اللونين والثلاثة ألوان، ومجموعات الأيقونات، وتصنيفات القمة والقاع، وكاشفات التكرار والفراغ والخطأ حمولة، لون شريط، مجموعة توقفات مقياس، عائلة أيقونات، تصف النطاق المُغطى بأكمله دفعة واحدة بدلًا من مقارنة نسبية لكل خلية، بحيث إن تقسيمها إلى عدة كائنات قواعد ذات أولوية لن يشتري أي صحة إضافية وسيضيف فقط قواعد يجب إدارتها. عندما يقسّم تعديل نطاقها، يعيد HotXLS دمج القطع في قاعدة واحدة بـsqref متعدد المناطق ويعيد ترسية الحمولة كوحدة واحدة بدلًا من استنساخ كائن قاعدة جديد لكل قطعة. يتماشى هذا التمييز مع تصنيف أنواع القواعد في مقالة أساسيات التنسيق الشرطي والنص المنسّق: تقف أشرطة البيانات ومقاييس الألوان ومجموعات الأيقونات بالفعل بمعزل عن قواعد cellIs بتجاهلها خاصية Style كليًا، ويتضح الآن أنها تقف بمعزل عن إعادة الترسية لكل منطقة للسبب الكامن نفسه

لماذا تتغير أولويات القواعد بعد تعديل بنيوي؟

تتغير الأولويات لأن كل نسخة مستنسخة تبدأ بحمل قيمة الأولوية نفسها بالضبط للقاعدة التي انقسمت عنها، ويشغّل HotXLS تمريرة تطبيع بعد ذلك تحل التكرارات الناتجة إلى ترتيب نظيف بلا فجوات بدلًا من ترك قاعدتين متعادلتين بالرتبة نفسها. روتين داخلي ثانٍ، XlsxNormalizeConditionalFormatPriorities، يأخذ الأولوية الحالية لكل تنسيق شرطي، ويعود إلى موضع تلك القاعدة في المجموعة لأي قاعدة لم يُضبط لها قط قيمة صراحة، ويفرز القائمة بأكملها بثبات بحيث تحتفظ حالات التعادل بترتيبها النسبي الأصلي، ويعيد ترقيم النتيجة المُرتَّبة إلى تسلسل كثيف 1، 2، 3 بلا فجوات وبلا تكرار. يشغّله HotXLS مرة قبل بدء الإزاحة، بحيث يبدأ الاستنساخ من خط أساس نظيف، ومرة أخرى بعد كل انقسام وبعد إزالة كل قاعدة أصبحت فارغة، بحيث لا يحتوي الملف المحفوظ أبدًا على إدخالي قاعدة يدّعيان الأولوية نفسها. يهم هذا إذا اتبعت النصيحة في مقالة أساسيات التنسيق الشرطي بترك فجوات بين قيم الأولوية بحيث يمكن لقاعدة لاحقة أن تندرج دون إعادة ترقيم البقية: تنجو الفجوات حتى يلمس تعديل صف أو عمود لاحق ورقة العمل تلك، ثم تنهار، لأن التطبيع لا يضمن سوى التفرد والترتيب الثابت، لا عودة نظام الترقيم الأصلي دون تغيير

قواعد التحقق من صحة البيانات تنقسم أيضًا، دون أولوية يجب إعادة ترقيمها

تمر قواعد التحقق من صحة البيانات عبر منطق تقسيم النطاق نفسه الذي تمر به قواعد cellIs والتعبير، وخلافًا للتنسيق الشرطي، يسلك كل نوع تحقق ذلك المسار بشكل موحّد: لا يملك HotXLS عائلة منفصلة غير قائمة على الصيغة للتحقق من صحة البيانات بالطريقة التي تُعامَل بها أشرطة البيانات ومجموعات الأيقونات في التنسيق الشرطي، لذا فإن قاعدة قائمة بسيطة أو رقم صحيح كامل تُقسَّم بالروتين نفسه بالضبط الذي يعالج صيغة مخصصة نسبية. ما يختلف هو الأولوية: لا يمنح ECMA-376 عنصر dataValidation أي خاصية priority على الإطلاق، لذا لا توجد خطوة إعادة ترقيم للتحقق بالطريقة الموجودة للتنسيقات الشرطية. تخيّل تحقق صيغة مخصصة يمنع المبلغ الفعلي لكل صف من تجاوز ميزانيته الخاصة في العمود المجاور له

Sheet.AddCustomValidation('D2:D400', 'D2<=C2');
Sheet.DeleteRows(150, 5);   // remove five rows out of the validated range
// DataValidations now holds two rules instead of one:
//   D2:D149    Formula1 = 'D2<=C2'      (rows above the deletion)
//   D150:D395  Formula1 = 'D150<=C150'  (rows that shifted up)

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

ما الذي يجب فحصه بعد إدراج أو حذف جماعي

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

يُشحن تقسيم القواعد وتطبيع الأولوية كسلوك قياسي لمحرك XLSX في مكوّن HotXLS لـExcel في Delphi لـDelphi وC++Builder؛ تحمل صفحة المنتج مرجع واجهة برمجة تحرير ورقة العمل الكامل، بما في ذلك طرائق التنسيق الشرطي والتحقق من صحة البيانات الموصوفة هنا