مقال تقني

HotXLS Delphi Component: formula calculation and custom functions في Delphi

توضح النسخة المحدثة أن الصيغ تخزّن ولا تُقيّم فوراً، وأن OnUserFunction يضيف دوال أعمال ذات سلوك مُصدَر. وتغطي نسخة Ex للدوال المعتمدة على الموضع، وحدود انتقال الدوال إلى Excel، وأوضاع الحساب وR1C1 وarray formulas في XLSX

مكتبة جداول بيانات تخزّن نصوص الصيغ فقط، ومكتبة بها محرك صيغ يعمل فعلا، منتجان مختلفان يبدوان متطابقين حتى اللحظة التي تطلب فيها من أحدهما رقما معظم كود جداول البيانات في Delphi لا يلاحظ الفجوة أبدا، لأن Excel يغطيها: اكتب SUM(B2:B501) في خلية، احفظ، ويعيد Excel حساب المجموع في اللحظة التي يفتح فيها إنسان الملف أزل الإنسان من الحلقة، وشغّل المصنف نفسه عبر خط أنابيب على الخادم يصدّر مباشرة إلى CSV، ويتوقف الفارق عن كونه نظريا يحمل CSV النص الحرفي =SUM(B2:B501) في مكان كان يجب أن يحمل رقما، لأنه لم يحدث في أي لحظة أن قام أي شيء فعليا بحساب الصيغة

هذا هو الخط الذي تقف HotXLS على الجانب الصحيح منه تعامل الصيغة كما تعاملها صيغ الملفات، نصا مخزنا مع نتيجة مخبأة اختيارية، بحيث ينتج تصدير CSV مجرد الوصفة لا الطبق لكنها تحمل أيضا محرك حساب يمكنك استدعاؤه مباشرة، المحرك نفسه في واجهتي XLS وXLSX، إضافة إلى نقطة ربط لحل أسماء دوال لم يسمع بها المحرك من قبل HotXLS مكتبة Object Pascal أصلية تقرأ وتكتب XLS وXLSX من Delphi وC++Builder دون أتمتة Excel، ونصف الحساب فيها هو ما يحوّل الصيغ المخزنة إلى قيم عند الطلب

مخطط لاستدعاء Calculate في HotXLS يحول نص صيغة Excel المخزن إلى قيمة Variant قبل تصدير CSV في Delphi
يصدّر مخزون الصيغة وصفتها ما لم يقيّمها شيء. ويعيد Calculate قيمة Variant يمكنك إبقاؤها فتحمل CSV أعداداً

الصيغ تُخزَّن، لا تُحسَب فورا

كتابة صيغة في خلية لا تحسب شيئا عند الحفظ يسجّل المصنف نص الصيغة في جانب XLS يسجّل أيضا أعلاما يحكمها RecalcOnSave، التي تكون True افتراضيا وتخبر Excel بإعادة الحساب عند الفتح هذا النموذج صحيح للملفات الموجهة إلى Excel وخاطئ لخطوط الأنابيب التي تستهلك قيم الخلايا مباشرة، سواء كان ذلك تصدير CSV أو تصدير HTML أو كود خاص بك يقرأ الخلايا مرة أخرى بالنسبة إلى هذه الحالات، احسب صراحة باستخدام Calculate توجد في أربع نقاط دخول: TXLSWorkbook وIXLSWorksheet وTXLSXWorkbook وTXLSXWorksheet كلها تعرض function Calculate(const Formula: WideString): Variant

// احسب داخل العملية، ثم أرسل القيمة لا الوصفة
Total := Book.Calculate('SUM(Sales!B2:B501)');
Sheet.Cells[502, 2].Value := Total;
Book.SaveAsCSV('sales.csv', 0, ',');   // ملف CSV يحمل الآن الرقم

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

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

إضافة دوال تجارية باستخدام OnUserFunction

حين يواجه المحرك اسم دالة لا يعرفه، يُطلق حدثا بدل أن يفشل مباشرة عيّن OnUserFunction على أي من صنفي المصنف وتستطيع حل الاستدعاء بنفسك

مخطط لحدث OnUserFunction في HotXLS يحل دالة DISCOUNT غير معروفة داخل صيغة في Delphi
ترفع الأسماء المجهولة OnUserFunction بدل الإخفاق. ويطابق المعالج بلا حساسية لحالة الأحرف، ويتلقى وسائط مقيَّمة مسبقاً، ويطالب بالاستدعاء عبر Handled
procedure TReportBuilder.HandleUserFunction(Sender: TObject;
  const FunctionName: WideString; const Args: Variant;
  var Value: Variant; var Handled: Boolean);
begin
  if SameText(FunctionName, 'DISCOUNT') then
  begin
    Value := Args[0] * 0.9;   // تصل Args كمصفوفة Variant
    Handled := True;
  end;
end;

// الربط والاستخدام
Book.OnUserFunction := HandleUserFunction;
Sheet.Cells[1, 1].Value := 200;
Sheet.Cells[1, 2].Formula := 'DISCOUNT(A1)';
Net := Book.Calculate('DISCOUNT(A1) + SUM(A1:A1)');

ثلاثة تفاصيل تستحق الانتباه أولا، اضبط Handled := True فقط حين تتعرف فعلا على الاسم تركها False يسمح للمحرك بمتابعة معالجته المعتادة للدالة غير المعروفة، بحيث يمكن لمعالج واحد أن يخدم عدة مصنفات دون أن يدّعي كل ما يمر عبره ثانيا، قارن الأسماء بغض النظر عن حالة الأحرف باستخدام SameText، لأن كاتبي الصيغ يكتبون discount( وDISCOUNT( بالتبادل ثالثا، تصل الوسائط مُحسَبة مسبقا: DISCOUNT(A1) يسلّمك قيمة A1، لا المرجع، لذا لا يمكن للدالة أن تعرف من أين أتت مدخلاتها هذه النقطة الأخيرة تمهّد للقيد الذي يتناوله القسم التالي

عامل جسم المعالج بالحذر نفسه الذي تعامل به أي نقطة دخول خارجية مصفوفة Args تعكس ما كتبه كاتب الصيغة، لذا تحقق من عدد الوسائط وأنواعها قبل الفهرسة فيها، وقرر مسبقا ماذا يعيد الاستدعاء غير الصالح: قيمة خطأ من نوع Variant، أو استثناء يُطلَق الاختيار مهم لأن استثناء يُطلَق داخل المعالج ينتشر خارجا عبر استدعاء Calculate الذي أطلق الحساب هذا مقبول في مولّد محكم التحكم وغير لائق في خدمة تحسب مصنفات كتبها مستخدمون، حيث ستُسقط صيغة واحدة سيئة الطلب بأكمله في ذلك السياق، التقط الاستثناء داخل المعالج وأعد قيمة حارسة يستطيع سير العمل المحيط التعرف عليها وتسجيلها

الدوال المعتمدة على الموضع تحتاج نسخة Ex

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

procedure TReportBuilder.HandleUserFunctionEx(Sender: TObject;
  const FunctionName: WideString; const Args: Variant;
  const Context: TXLSUserFunctionContext;
  var Value: Variant; var Handled: Boolean);
begin
  if SameText(FunctionName, 'REGIONRATE') then
  begin
    // الصيغة نفسها تعطي معدلا مختلفا في كل ورقة إقليمية
    Value := RateForSheet(Context.SheetIndex) * Args[0];
    Handled := True;
  end;
end;

TXLSUserFunctionContext يحمل SheetIndex وRow وCol للخلية قيد الحساب إذا كانت نتيجة دالة تعتمد على موقعها ولو قليلا، اربط حدث Ex منذ البداية إضافة السياق لاحقا إلى معالج تستدعيه ثلاثون صيغة بالفعل أكثر فوضى بكثير من اختيار التوقيع الصحيح منذ اليوم الأول، والحدثان متشابهان جدا فيما عدا ذلك بحيث لا يوجد سبب وجيه للبدء بالأضيق منهما

الدوال المخصصة لا تنتقل إلى Excel

الدالة المخصصة تعيش بالكامل داخل عمليتك الاسم DISCOUNT له معنى فقط أثناء تشغيل كود Delphi ومعالج الحدث الخاص به افتح الملف المحفوظ في Excel وستكون DISCOUNT مجرد اسم غير معروف؛ تُظهر الخلية #NAME? ما لم تصادف وجود دالة VBA مطابقة أو إضافة على جهاز المستخدم هذه حقيقة تصميمية تفصل بين عرض توضيحي ومنتج قابل للشحن، وتفرض عليك خيارا يجب اتخاذه بوعي بدل اكتشافه لاحقا

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

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

مخطط للعقدَين لدوال HotXLS المخصصة في Delphi وخطر ‎#NAME?‎ عندما تسافر الصيغ المخصصة إلى Excel
لا تعني الدالة المخصصة شيئاً إلا ما دامت عمليتك تجري. وتستخدم الخلايا المواجهة لـ Excel مفردات Excel نفسها، بينما تُقيَّم القواعد الخاصة داخل العملية وتُحفظ قيماً

أوضاع الحساب، والتكرار، وR1C1: أزرار ضبط واجهة XLS

واجهة XLS تعرض إعدادات الحساب على مستوى BIFF التي يقرأها Excel من الملف CalculationMode تقبل xlCalcManual أو xlCalcAutomatic (الافتراضي) أو xlCalcAutomaticExceptTables، وهي تحدد سلوك Excel بمجرد فتح الملف مصنف نموذجي بآلاف الصيغ غالبا ما يكون أكثر ودّية إذا سُلِّم في الوضع اليدوي، بحيث يقرر المستلم متى تحدث عاصفة إعادة الحساب EnableIteration (افتراضيا False)، مع MaxIterations (افتراضيا 100) وMaxIterationChange (افتراضيا 0.001)، يفتح المراجع الدائرية المتعمدة من نوع التقارب التكراري التي تظهر في بعض النماذج المالية ReferenceStyle يبدّل بين عرض A1 وR1C1، وUseFullPrecision يعكس خيار Excel للدقة كما تُعرَض

تعيش هذه الخصائص في واجهة XLS لأنها تقابل سجلات BIFF؛ عند توليد .xlsx، خطط للصيغ بحيث لا تعتمد على إعدادات التكرار، أو احسب القيم المتقاربة في Delphi واكتب النتائج

صيغ المصفوفات: نقطة الدخول العامة هي XLSX

صيغ المصفوفات القديمة من نوع CSE تُنشأ عبر TXLSXRange.SetArrayFormula

// صيغة مصفوفة واحدة تمتد عبر A2:A4
Sheet.RCRange[2, 1, 4, 1].SetArrayFormula('A1*{1;2;3}');

الطريقة المكافئة موجودة في التسلسل الهرمي لصنف XLS لكنها تقع في قسم خاص، لذا لا توجد طريقة مدعومة لتأليف صيغ مصفوفات جديدة في ملفات .xls الصيغ الموجودة في ملفات مفتوحة تنتقل ذهابا وإيابا سليمة؛ ما لا يمكنك فعله هو إنشاؤها القاعدة التي تترتب على ذلك بسيطة بما يكفي: حين تكون دلالات المصفوفة جزءا من المتطلبات، استهدف .xlsx إذا كان تسليم .xls قديم يحتاج فعلا إلى سلوك المصفوفة، فالمسار العملي هو حساب نتيجة المصفوفة في Delphi وكتابة القيم الفردية في الخلايا

قراءتان مرتبطتان على هذا الموقع: الأسماء المعرَّفة والصيغ عبر الأوراق تغطي حل الأسماء الذي يجريه المحرك، ومقالة تصدير CSV وTSV تفصّل سلوك التصدير الذي يجعل الحساب الصريح ضروريا مرجع المحرك الكامل، بما في ذلك مجموعة الدوال المدعومة، يأتي مع HotXLS Delphi Component