مقال تقني

تقييم التنسيق الشرطي لإكسل في Delphi عبر HotXLS

HotXLS مكوّن جداول بيانات أصلي لـ Delphi وC++Builder، ومنذ الإصدار 2.209.0 يستطيع الإجابة عن السؤال الذي يحتفظ به Excel لنفسه عادةً: لهذه الخلية بالضبط، أي قواعد التنسيق الشرطي تُفعَّل، وإلى أي تعبئة أو خط أو شريط بيانات أو أيقونة تُحلّ؟ تلك الإجابة هي ما تحتاجه في اللحظة التي يكون فيها إخراجك تقرير HTML، أو PDF، أو شبكة ترسمها بنفسك

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

لماذا لا يخبرك تنسيق الملف بأي الخلايا تُضيء

الإجابة القصيرة أن ECMA-376 وISO 29500-1 يُعرّفان التخزين، لا التقييم. عنصر conditionalFormatting (§18.3.1.18) يحمل sqref وقائمة أبناء cfRule (§18.3.1.10)، وكل قاعدة تحمل type، وعامل تشغيل اختياري operator، وpriority، وعلامة stopIfTrue، وابنًا أو ابنَي formula، وللعائلات المرئية مجموعة عتبات cfvo. كل واحد من هذه يصف بأمانة ما هيّأه المستخدم، ولا أحد منها خوارزمية. لنصف أنواع القواعد لا تهم تلك الفجوة: cellIs مع operator="greaterThan" يعني أكبر من، وcontainsText يعني وجود السلسلة الفرعية. الفجوة تنفتح على العائلات المجمِّعة. قاعدة top10 بـ rank="10" وpercent="1" على 27 خلية رقمية مُعبَّأة تُظلّل كم خلية؟ اثنتان وسبعة أعشار ليست رقمًا. تقريب، أو تدوير للأسفل، أو للأعلى — المواصفة صامتة، واختيار خاطئ يعني أن ملف PDF لديك يختلف عن المصنّف الذي يفتحه العميل بجانبه

قواعد الخلية المفردة وأين تتوقف TCondFormatRule.Evaluate

أخذ HotXLS النصف الأرخص أولًا. تُجيب TCondFormatRule.Evaluate في lxCondFormat.pas، المُضافة في 2.199.0، عن ما إذا كانت قاعدة واحدة تُفعَّل لخلية واحدة دون معرفة أي شيء عن بقية النطاق. تتعامل مع عوامل المقارنة الثمانية من BIFF خلف cellIs (بين، ليس بين، يساوي، لا يساوي، أكبر، أصغر، أكبر أو يساوي، أصغر أو يساوي)، وقواعد expression الحرة المُقيَّمة عند الخلية بحيث تُعاد أسس المراجع النسبية بشكل صحيح، والمُسندات النصية الأربعة، ومُسندات الفراغ والخطأ. العتبات تأتي من FFormula1 وFFormula2 المُحلَّلتين عبر TXLSCalculator.GetRangeValue عند موضع الخلية، والحدود المعكوسة تُبدَّل بدلًا من رفضها

var
  I: Integer;
  Rule: TCondFormatRule;
  Value: Variant;
begin
  Value := Sheet.Cells[Row, Col].Value;
  for I := 0 to CondFormat.RuleCount - 1 do
  begin
    Rule := CondFormat.Rule(I);
    // Single-cell verdict only. Aggregate and visual kinds answer False.
    if Rule.Evaluate(Calculator, SheetIndex, Row, Col, Value) then
      ApplyHighlight(Row, Col, Rule.Style);
  end;
end;

الجزء الصادق من تلك الطريقة هو ما ترفض تخمينه. تعيد top10 وaboveAverage وbelowAverage وduplicateValues وuniqueValues القيمة False، ليس لأنها صعبة بل لأنها غير قابلة للحسم من خلية واحدة — كل واحدة منها تحتاج إحصائية على النطاق بأكمله. العائلات المرئية الأربع، dataBar وcolorScale2 وcolorScale3 وiconSet، تُعيد False لسبب مختلف: لا تُنتج قيمة منطقية إطلاقًا، بل تُنتج حمولة عرض، ونوع إرجاع منطقي هو الشكل الخاطئ لها

كيف يتجنب مُقيِّم على مستوى ورقة العمل إعادة مسح الورقة؟

بحساب كل كمية مشتركة مرة واحدة، عند البناء، ولا مرة أخرى أبدًا. TXLSXConditionalFormatEvaluator في lxHandleX.pas لقطة غير قابلة للتغيير لورقة عمل واحدة، مبنية عبر TXLSXWorksheet.CreateConditionalFormatEvaluator، وتصميمها بأكمله دفاع ضد التنفيذ الساذج حيث كل خلية مطلية تُطلق مسحًا كاملًا للنطاق

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

الرابع هو الأهم على النطاق الواسع. صيغة قاعدة نسبية مثل =A1>AVERAGE($A$1:$A$100) تعني شيئًا مختلفًا في كل خلية من النطاق، والتنفيذ الواضح يُصرِّف شجرة نحوية جديدة لكل خلية. تُصرِّف TXlsxCfRulePlan واحدة مرة واحدة وتُعيد تقييم نفس الشجرة عبر إزاحات إحداثيات قابلة للعكس، مما يحافظ على سلوك ثبات Excel دون تخصيص شجرة نحوية لكل خلية. تُرتَّب القواعد بعد ذلك بـ priority، وتطابق قاعدة يُعيَّن فيها StopIfTrue يكسر الحلقة، تمامًا كما يقطع Excel التقييم مبكرًا

var
  Evaluator: TXLSXConditionalFormatEvaluator;
  Res: TXLSXCfCellResult;
begin
  Evaluator := Sheet.CreateConditionalFormatEvaluator;
  try
    if Evaluator.EvaluateCell(Row, Col, Res) then
    begin
      if Res.HasFillColor then
        Canvas.Brush.Color := TColor(Res.FillColor);
      if Res.HasIcon then
        // IconIndex is zero-based inside Res.IconSetType
        DrawIcon(Res.IconSetType, Res.IconIndex, Res.IconCount);
      if Res.HasDataBar then
        // DataBarAxis and DataBarEnd are normalised to 0..1
        DrawBar(Res.DataBarAxis, Res.DataBarEnd, Res.DataBarColor);
      if not Res.ShowCellValue then
        Exit;  // showValue="0" on the rule hides the number
    end;
  finally
    Evaluator.Free;
  end;
end;

كيف يُقرِّب Excel فعليًا قاعدة أعلى 10 بالمئة؟

يُقرِّب للأسفل، بحد أدنى واحد، ويشمل التعادلات عند نقطة القطع. هذا غير مكتوب في أي مكان في ISO 29500-1 — جرى تثبيته باختبار Excel 16 بمصنّفات مبنية يدويًا وقراءة أي الخلايا ظلّلها التطبيق. يُنفِّذ HotXLS ذلك بالضبط: عدد الرتبة هو Floor(Count * Min(Rank, 100) / 100)، يُرفَع إلى 1 عندما يقع عند صفر، يُقيَّد بالعدد المُعبَّأ، وتُقارَن قيمة القطع بعدها بـ >= بحيث تُظلَّل كل خلية تساوي الحد حتى عندما يتجاوز ذلك العدد المطلوب. سبعة وعشرون قيمة وقاعدة 10 بالمئة تُظلِّل خليتين، بالإضافة إلى أي خلايا أخرى متعادلة مع الثانية

قواعد فوق المتوسط أخفت غموضًا ثانيًا: aboveAverage مع stdDev="1" يختار خلايا أعلى بانحراف معياري واحد فوق المتوسط، لكن انحراف العينة والانحراف المجتمعي يختلفان بتصحيح بيسيل ويتعارضان بشكل مرئي على نطاقات صغيرة، وهو بالضبط حيث يُستخدم التنسيق الشرطي. يستخدم Excel 16 الانحراف المجتمعي، ويطابقه HotXLS، مع علامة equalAverage التي تجعل المقارنة الصارمة شاملة فقط عندما لا يكون نطاق انحراف قيد اللعب. قواعد التكرار والفرادة اعتمدت على هوية المفتاح بدلًا من ذلك. إذا حملت خلية الرقم 100 وحملت أخرى النص "100"، يعامل Excel كليهما كمفتاح تكرار واحد، لذا يُطبِّع HotXLS النص الرقمي إلى مساحة المفتاح الرقمي بدلًا من مقارنة السلاسل الخام. الخلايا الفارغة الحالة المرآة: خلية فارغة حقيقية تشارك في حصر النطاق لكنها ليست مُنسَّقة بنفسها، لذا الخلايا الفارغة في عمود لا تُضيء كلها كتكرارات لبعضها البعض

مقاييس الألوان ومجموعات الأيقونات: الاستيفاء وقواعد الحدود

العائلات المرئية تُحلّ إلى أرقام جاهزة للعرض بدلًا من قيم منطقية، وسلوكها الحدي ثُبّت بنفس الطريقة. لمقياس ألوان بعتبات رقمية صريحة، يُقيّد HotXLS كسر الموضع إلى الفترة المغلقة صفر إلى واحد، ثم يستوفي لكل قناة بالاقتطاع لا بالتقريب — قيمة أسفل الحد الأدنى تحصل على اللون الأدنى بدلًا من لون مُستقرأ، ومقياس بثلاث نقاط توقف يختار زوجه بالمقارنة مع نقطة التوقف الوسطى، ومقياس متدنٍّ طرفاه يحملان نفس العتبة ينهار إلى اللون الأعلى بدلًا من القسمة على صفر. مجموعات الأيقونات احتاجت نوعًا معاكسًا من العناية، لأن كل cfvo بعد الأول يحمل صرامة مقارنته الخاصة: يقرأ HotXLS ThresholdEqualsInclude لكل عتبة ويُطبِّق >= أو > تبعًا لذلك، متجولًا صعودًا بحيث تفوز أعلى عتبة مُستوفاة بفهرس الأيقونة. مجموعة معكوسة تعكس الفهرس المُحلَّل لا العتبات، وتجاوزات لكل أيقونة يمكن أن تسحب رمزًا من عائلة مختلفة، وأي عتبة غير صالحة تُلغي القاعدة بدلًا من إنتاج أيقونة خاطئة تبدو معقولة

تغذية شبكة، وتصدير HTML، وPDF من نتيجة واحدة

لأن EvaluateCell تُعيد TXLSXCfCellResult مُحلَّلة بالكامل — تعبئة تفاضلية ولون خط بتلوين مظهر مُطبَّق بالفعل، وسميك، ومائل، وتسطير، ومعرّف تنسيق رقم، وامتدادات شريط اتجاهية موجبة وسالبة، وموضع محور، وعائلة أيقونة وفهرسها — كل مستهلك يقرأ نفس السجل ولا يحتاج أي منها فهم داخليات القاعدة. يستخدم HotXLS ذلك المسار الواحد لتصدير HTML، وتصدير PDF، والعارض التفاعلي، وهي الطريقة العملية الوحيدة لمنع ثلاثة عارضات من الانحراف عن بعضها. وصل الإصدار 2.210.0 ذلك بـ TXLSWorkbookViewer، الذي يُخزّن مؤقتًا مُقيِّمًا واحدًا جاهزًا لكل ورقة عمل نشطة ويُعيد استخدامه عبر التمرير والتحديد وإعادة الرسم، ويُحرِّره عند تغيّر المصنّف أو ورقة العمل — إعادة بناء اللقطة عند كل Paint ستُبطل التصميم بأكمله في زمن البناء. تلك الذاكرة المؤقتة هي أيضًا سبب وجود TXLSWorkbookViewer.RefreshConditionalFormats: اللقطة غير قابلة للتغيير، لذا إذا عدّلت المصنّف المُرفَق في مكانه تكون الإحصائيات المجمَّعة والعتبات المُحلَّلة قديمة حتى تستدعيها

// Editing behind a live viewer: the cached snapshot must be invalidated.
Sheet := Viewer.XlsxWorkbook.Sheets[1];
Sheet.Cells[5, 2].Value := 4200;         // changes mean, min, max, ranking
Viewer.RefreshConditionalFormats;        // drop evaluator, repaint

ما لن يفعله المُقيِّم لك

ثلاثة حدود تستحق الذكر بوضوح. TCondFormatRule.Evaluate الكلاسيكية لخلية مفردة وTXLSXConditionalFormatEvaluator على مستوى ورقة العمل سطحان مختلفان بقدرات مختلفة، والأول عمدًا يرفض العائلات المجمِّعة والمرئية بدلًا من تقريبها — إن احتجت Top/Bottom أو مقياس ألوان، ابنِ المُقيِّم. فترات التاريخ النسبية تعتمد على ساعة الجهاز عند وقت التقييم، لذا تُعرض قاعدة timePeriod بشكل مختلف في ملف PDF مُولَّد اليوم وآخر مُولَّد الأسبوع القادم، وهو سلوك صحيح ولا يزال تذكرة دعم منتظرة إن كان يُفترض بأرشيفك أن يكون ثابت البايتات. الثالث نحوي لا تقني: قواعد نحو صيغة التنسيق الشرطي تحظر مراجع الجدول المُنظَّمة، لذا لا تستطيع قاعدة عنونة عمود جدول بالاسم كما تفعل صيغة ورقة العمل، وهذا قيد من التنسيق لا من التنفيذ

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