يقيّم HotXLS دوال التعبيرات النمطية من Microsoft 365 وهي REGEXTEST و REGEXEXTRACT و REGEXREPLACE داخل محرك معادلاته الخاص بـ Delphi، دون تثبيت Excel ودون DLL تعبيرات نمطية من النظام. يربط حاسبة HotXLS بناء PCRE2 10.49 سداسي عشري البتات مثبَّتاً ساكنة، تطابق نص UTF-16 مباشرة، وتبثّ عبر المصفوفات، وتحسب كل خطوة تتبع خلفي ضمن ميزانية الموارد للمعادلة
هذه الجزئية الأخيرة سبب وجود هذه المقالة. المصنف الذي يصل من عميل هو إدخال غير موثوق، والتعبير النمطي داخل معادلة هو كود كتبه العميل. نمط زائد نص كل ما يلزم لتثبيت نواة معالج لدقائق بالتتبع الخلفي الكارثي، الشكل الكلاسيكي لـ ReDoS وهو (a+)+$ مقابل جرية طويلة من محارف a تنتهي بـ b. خادم يعيد حساب مصنفات مرفوعة لا يحتمل ذلك، فالعمل المثير في HotXLS 2.384.112 لم يكن استدعاء PCRE2. كان جعل PCRE2 قابلة للإيقاف وخاضعة للمسومة ومتطابقة على كل هدف
ماذا تعيد REGEXTEST و REGEXEXTRACT و REGEXREPLACE في HotXLS؟
يتبع محرك معادلات HotXLS التوقيعات المنشورة من Microsoft، ودلالات المعاملات هي التي تعرفها من Excel أصلاً. المطابقة حساسة لحالة الأحرف افتراضياً. المعامل الأخير case_sensitivity يأخذ 0 للحساسية لحالة الأحرف و 1 لعدمها، وأي قيمة أخرى تعيد #VALUE!. خيارات PCRE2 الداخلية مثل (?i) أو (*UCP) تعمل أيضاً حين تفضّل إبقاء السلوك داخل النمط
REGEXTEST(text, pattern, [case_sensitivity]) يعيد قيمة منطقية، والمطابقة الفاشلة مجرد FALSE، لا خطأ أبداً. و REGEXEXTRACT(text, pattern, [return_mode], [case_sensitivity]) له ثلاثة أوضاع: 0 يعيد أول مطابقة كاملة، و 1 يعيد كل مطابقة كاملة بوصفها مصفوفة رأسية، و 2 يعيد مجموعات الالتقاط لأول مطابقة بوصفها مصفوفة أفقية. المطابقة الغائبة تعيد #N/A، والوضع 2 على نمط بلا مجموعات التقاط يعيد #VALUE!، والمجموعة الاختيارية التي لم تشارك تعطي سلسلة فارغة بدلاً من خطأ
REGEXREPLACE(text, pattern, replacement, [occurrence], [case_sensitivity]) يستخدم صيغة الاستبدال في PCRE2، فيشير $1 و ${name} إلى الالتقاطات. وقيمة occurrence تساوي 0 تستبدل كل مطابقة، والقيمة الموجبة تختار المطابقة رقم n من البداية والسالبة تعد من النهاية. عندما لا توجد المطابقة المختارة، يعيد HotXLS النص الأصلي دون تغيير. وتُبتَر المحددات الرقمية نحو الصفر، بالطريقة نفسها التي يعامل بها Excel معاملات الأعداد الصحيحة الكسرية
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Orders');
Sheet.Cells[2, 1].Value := 'PO-2026-00417 shipped to Graz';
// TRUE عندما تحوي الخلية رقم أمر شراء
Sheet.Cells[2, 2].Formula := 'REGEXTEST(A2, "PO-\d{4}-\d{5}")';
// أول مطابقة كاملة: "PO-2026-00417"
Sheet.Cells[2, 3].Formula := 'REGEXEXTRACT(A2, "PO-\d{4}-\d{5}")';
// بدّل السنة والتسلسل بالالتقاطات المرقمة: "PO-00417-2026 ..."
Sheet.Cells[2, 4].Formula :=
'REGEXREPLACE(A2, "PO-(\d{4})-(\d{5})", "PO-$2-$1")';
// اختبار غير حساس لحالة الأحرف بالمعامل الأخير
Sheet.Cells[2, 5].Formula := 'REGEXTEST(A2, "graz", 1)';
Book.Recalculate;
Book.SaveAs('orders.xlsx');
finally
Book.Free;
end;
end;
نص المعادلة في Delphi حرفية سلسلة عادية، فلا يحتاج \d إلى تهريب على جانب Pascal. المهم فقط علامتا الاقتباس المزدوجتان في Excel داخل المعادلة، وهما يتبعان قواعد Excel العادية. وعند الحفظ يخزن كاتب XLSX تلك الأسماء بالبادئة _xlfn. للدوال المستقبلية التي يتوقعها Excel، ويعيد المصنف المفتوح من جديد حسابها مرة أخرى. يمكن لمحرك BIFF8 الكلاسيكي أن يقيّم الدوال في استدعاء سياقي، لكنه لا يستطيع كتابتها بوصفها رموز معادلات .xls أصلية، فأبقِ معادلات التعبيرات النمطية في مخرجات XLSX
كيف يعمل بثّ المصفوفات مع معادلات Regex؟
تتبثّ REGEXTEST و REGEXREPLACE والوضع 0 من REGEXEXTRACT عبر معاملات متوافقة، وهذا ما يجعلها مفيدة على أعمدة حقيقية. قد يكون كل معامل عدداً قياسياً أو صفاً أو عموداً أو مستطيلاً، وياخذ HotXLS أكبر عدد صف وأكبر عدد عمود بوصفهما شكل النتيجة. المعامل الذي لا يكون بُعده 1 ولا ذلك الحد الأقصى يجعل الاستدعاء كله يعيد #VALUE!، وهو عدم تطابق المصفوفات المعتاد في Excel. هكذا يعطي REGEXTEST(A2:A500, "^[A-Z]{2}\d{9}$") قيماً منطقية بعددها 499، وعمود أنماط مقابل خلية نص واحدة يعمل بالطريقة نفسها في الاتجاه الآخر
الوضعان 1 و 2 من REGEXEXTRACT يرفضان البثّ. كل عنصر كان سينتج متجهاً بطول مختلف، ومجموعة متجهات متموجة لا يمكنها أن تشغل نتيجة معادلة مستطيلة. بدلاً من البتر أو الحشو بصمت، يبلّغ HotXLS عن الاستدعاء بوصفه غير مدعوم. وإذا احتجت كل المطابقات من خلايا كثيرة، فكرّر على الخلايا واستخرج واحدة كل مرة، أو أعد تشكيل المسألة عبر إغلاقات LET و LAMBDA في محرك معادلات HotXLS بحيث يرى كل استدعاء إغلاق قيمة نص واحدة
موضع الخطأ داخل نتيجة مُبثَّة مقصود. خطأ Excel لعنصر واحد، كـ #N/A من استخراج لم يجد شيئاً في الصف 37، يهبط في تلك الخلية واحدة من النتيجة بينما تحفظ الخلايا الأخرى قيمها. حد الموارد أو الإلغاء شيء آخر: يوقف الاستدعاء كله ولا ينشر أبداً مصفوفة مملوءة جزئياً، لأن نتيجة نصفها تبدو كاملة أسوأ من فشل صريح
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Opts: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
I: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Log');
Sheet.Cells[1, 1].Value := 'GET /api/v2/orders 200 18ms';
Opts := XLSDefaultFormulaEvaluationOptions;
Opts.MaxEvaluationSteps := 200000; // حدّد سقف العمل الذي يُسمح لهذه المعادلة به
// الوضع 2: مجموعات الالتقاط لأول مطابقة بوصفها مصفوفة 1 x 3
Eval := Sheet.EvaluateFormulaAt(1, 2,
'REGEXEXTRACT(A1, "(GET|POST) (\S+) (\d{3})", 2)', xlfrsA1, Opts);
case Eval.Status of
xlfeOk:
if VarIsArray(Eval.Value) then
for I := VarArrayLowBound(Eval.Value, 2) to
VarArrayHighBound(Eval.Value, 2) do
Log(VarToStr(Eval.Value[0, I]));
xlfeResourceLimit:
Log('Pattern exceeded its budget: ' + Eval.Issue.Message);
else
Log('Evaluation failed: ' + Eval.Issue.Message);
end;
finally
Book.Free;
end;
end;
TXLSXWorksheet.EvaluateFormulaAt هو نقطة الدخول الصائبة عندما يأتي النمط من مكان لا تتحكم فيه. يقيّم المعادلة وكأنها تسكن في الصف والعمود المعطيَين، ويعيد المصفوفة الكاملة بدلاً من قيمة خلية مخزنة واحدة، وياخذ TXLSFormulaEvaluationOptions بضوابط صريحة للخطوات وذاكرة المصفوفات والإلغاء. تغطي دليل محرك معادلات HotXLS والدوال المخصصة بقية تلك الواجهة للتقييم
كيف يوقف HotXLS نمط تتبع خلفي كارثي؟
يوقف HotXLS الأنماط الجارفة بجعل PCRE2 تبلّغ عن نفسها لسياق المعادلة عند كل خطوة. يجمّع الواجهة الخلفية كل نمط مع PCRE2_AUTO_CALLOUT، وهو ما يجعل PCRE2 تستدعي دالة رد نداء من Pascal عند كل عنصر نمط أثناء المطابقة. تحسب تلك الدالة وحدة عمل واحدة على حالة التقييم النشطة، وتفحص علم الإلغاء وتفحص الذاكرة المقيمة. عندما يقول أي منها توقف، تعيد الدالة كود إجهاض النداء في PCRE2 وتُفك المطابقة بنظافة داخل PCRE2. تظهر المعادلة بوصفها xlfeResourceLimit أو حالة إلغاء، لا بوصفها خيطاً معلقاً
الذاكرة تُقاس بالطريقة نفسها. كل سياق PCRE2 يُنشأ بسياق عام لكل نسخة مُخصِّصه دالة من Pascal، فالنمط المجمَّع وبيانات المطابقة وإطارات التتبع الخلفي الداخلية في PCRE2 كلها تُخصَّص عبر HotXLS. كل كتلة تحمل ترويسة صغيرة ومحاذاة إلى 16 بايتاً، ويُضاف حجمها إلى إجمالي جاري يقارنه فحص الميزانية بحد ذاكرة المصفوفات للمعادلة. تُضاف مخازن الإخراج ومتجهات الاستخراج وسلسلة الاستبدال إلى الإجمالي نفسه، ما يعني أن نمطاً يطابق ملايين السلاسل الصغيرة يصطدم بالسقف بقوة النمط الذي يتتبع خلفياً إلى الأبد
دالة رد نداء الميزانية تعمل أيضاً تحت واقٍ أهم مما يبدو. استثناء Delphi يُرفع داخل دالة رد نداء استدعتها PCRE2 يجب ألا ينتشر صعوداً أبداً، لأن بين دالة النداء وكودك إطارات مكدس C جمّعت دون أي معرفة بجداول استثناءات Delphi. الفك عبرها غير معرّف في أحسن الحالات ومكدس تالف في أسوأها. تمسك جسر HotXLS كل استثناء داخل دالة النداء، وتقفل النسخة في حالة إجهاض، وتعيد كود خطأ عادياً إلى PCRE2. يرى كودك معادلة فاشلة، ولا يرى أبداً استثناء عبر إطار أجنبي
التكلفة حقيقية وتستحق الذكر. النداءات التلقائية تجعل المطابقة أبطأ من مفسر PCRE2 عارياً، ويجمّع HotXLS النمط مرة لكل عنصر يُقيَّم، فبثّ فوق 50,000 خلية يدفع 50,000 تجميع. لعمل النصوص السائب على بيانات موثوقة سيكون ممر regex مخصص في Delphi خارج المصنف أسرع. وداخل مصنف يرسله لك غرباء، المحدود والمتوقَّع يتقدم على السريع
لماذا بناء PCRE2 ساكن بـ UTF-16 ودون JIT؟
سلاسل Excel بترميز UTF-16، وقيم WideString في Delphi كذلك، وهذا جعل مكتبة PCRE2 بـ 16 بت الخيار العقلاني الوحيد. يسلّم HotXLS نص المعادلة إلى pcre2_match_16 مع ضبط PCRE2_UTF، وكل إزاحة تعود فهي فهرس وحدة كود UTF-16. لا جولة ذهاب وعودة عبر UTF-8 تحدث، فلا شيء يمكن أن ينحرف على أزواج البديلة. وحيثما تجبر مطابقة فارغة المسحَ على التقدم، يتبع HotXLS قاعدة المطابقة الشاملة في PCRE2: أعد محاولة مطابقة غير فارغة مثبتة في الموضع نفسه، ثم تقدم قيمة عدلية Unicode واحدة، وهي تتحرك وحدتَي كود تتجاوزان زوج البديلة بدلاً من شطره
الـ JIT مطفأ عمداً. يحتاج مجمّع JIT ذاكرة قابلة للتنفيذ ومكدس JIT خاصاً به، وهذا يسحب كوداً خاصاً بالمنصة وتخصيصات خارج المُخصِّص لكل معادلة. يبقي المفسر كل بايت تحت ميزانية HotXLS، ويبقي كل مطابقة تحت دالة النداء، ويسلك السلوك نفسه على الأهداف الخمسة كلها. كل نسخة TXLSRegex، الفئة الداخلية خلف هذه الدوال، تملك سياقاتها الخاصة، فلا يتشارك عمّال الحساب المتوازيون أي حالة PCRE2 قابلة للتغيير
قصة الربط هي موضع شذوذ البناء. يربط Delphi ملفات كائن C عبر {$L}، ويلبي الرابط لديه الرموز بين وحدات Pascal وكائنات C بما يكفي، لكنه لا يلبي الإشارات المرجعية الدائرية التعسفية بين ملفات كائن C منفصلة. PCRE2 نحو عشرين وحدة ترجمة تستدعي بعضها في كل اتجاه. الحل هو بناء وحدة موحدة: كل PCRE2 تُجمَّع بوصفها وحدة ترجمة واحدة في كائن pcre2_unity واحد لكل هدف، فتُلبي كل إشارة داخلية داخل كائن واحد، ولا يرى رابط Delphi سوى دوال _16 المصدَّرة
كانت بيئة تشغيل C المشكلة الثانية. تستدعي PCRE2 malloc و memcpy و memcmp وأخواتهما، وقد تكون واجهات C خلفية أخرى مربوطة في التنفيذي نفسه قد أحضرت نسخها الخاصة من تلك الأسماء. يعالج HotXLS PCRE2 مسبقاً بحيث تذهب تلك الاستدعاءات إلى أسماء خاصة مثل hotxls_regex_memcpy وينفذها في Pascal. لا يزال بإمكان Clang توليد استدعاءات memset و memchr عادية بعد المعالجة المسبقة، فتوجد هاتان أيضاً داخل الوحدة نفسها. ولا بد أن تطابق زخرفة الرموز كل صيغة كائن: كائن OMF الخاص بـ Win32 يتوقع أسماء C المسبوقة بخط سفلي، بينما تربط التعريفات من Pascal لكائن Win64 بصيغة ELF وكائن Win64 بصيغة COFF وبنيات FPC الأسماء المجردة
مجسات المكدس كانت الدقيقة. دالة C بإطار أكبر من صفحة تستدعي روتين مسح أولاً، يلمس كل صفحة بحجم 4 KB بالترتيب حتى يستطيع Windows تخصيص المكدس عبر صفحة الحارس الخاصة به. ستيب يعيد ببساطة يُربط جيداً ويعمل في كل اختبار وحدة بنمط صغير، ثم ينهار أول مرة يقفز فيها إطار عميق فوق صفحة الحارس. ينفذ HotXLS مجسات حقيقية بلغة التجميع لكل صيغة كائنات Windows: __chkstk_noalloc لكائن Win32، و __chkstk لكائن Win64 بصيغة ELF، و ___chkstk_ms لكائن Win64 بصيغة COFF. وعلى Linux x64 و macOS ARM64 يربط FPC مكتبة C النظام بدلاً من ذلك، ويبلّغ كل هدف FPC آخر عن الواجهة الخلفية بوصفها غير متاحة بدلاً من التراجع بصمت إلى لهجة regex مختلفة
أين الحدود؟
تعمل دوال regex على بنيات Delphi و C++Builder لـ Windows وعلى FPC لـ Linux x64 و macOS ARM64. وعلى أي هدف آخر تبلّغ المعادلة عن دالة غير مدعومة عبر التقييم السياقي بدلاً من إنتاج إجابات مختلفة بمحرك آخر. هذا هو الفشل الصائب: مصنف يحسب بشكل مختلف على منصة مختلفة خلل لا يجده أحد حتى يأتي تدقيق
وللتكافؤ مع Excel حد معلن أيضاً. التوقيعات والأمثلة الموثقة تتبع صفحات REGEXTEST و REGEXEXTRACT و REGEXREPLACE من Microsoft، لكن بنية Excel المستخدمة للتحقق المحلي لا تكشف دوال Microsoft 365 هذه بعد. لذا تتبع الحالات الطرفية عقودَي PCRE2 والمصفوفات الصريحة الموصوفة أعلاه، لا ادعاء مطابقة كل بنية Excel بت-ببت. وإذا اعتمدت مصنفاتك على زاوية خفية، كسلوك مرجع خلفي محدد أو فئة خاصية Unicode، فاختبر تلك الزاوية مباشرة
الأسئلة الشائعة
هل يحتاج HotXLS إلى Excel أو DLL regex لحساب REGEXTEST؟
لا. يربط HotXLS كائن PCRE2 ساكناً بترميز UTF-16 داخل تنفيذيك ويقيّم REGEXTEST و REGEXEXTRACT و REGEXREPLACE في محرك معادلاته الخاص بـ Delphi، دون Excel أو COM أو DLL خارجي
هل يمكن لنمط خبيث أن يعلّق خادمي؟
ليس بعد الميزانية المضبوطة. كل خطوة مطابقة تمر عبر نداء يحسبها على سياق المعادلة، فينتهي نمط ReDoS بحالة xlfeResourceLimit أو حالة إلغاء ودون نتيجة جزئية
لماذا يفشل الوضع 1 من REGEXEXTRACT على معامل نطاق؟
يمكن لكل خلية أن تعيد عدداً مختلفاً من المطابقات، وتلك المتجهات المتموجة لا تستطيع أن تشكل نتيجة مستطيلة. يبلّغ HotXLS عن الاستدعاء بوصفه غير مدعوم بدلاً من الحشو أو البتر، فاستخرج خلية واحدة كل مرة
يشحن HotXLS دوال regex إلى جانب LAMBDA و GROUPBY و PIVOTBY وبقية محرك الحساب الخاص به لـ Delphi و C++Builder و Lazarus. التفاصيل والإصدارات وتحميل النسخة التجريبية على صفحة مكوّن جداول HotXLS لـ Delphi