Teknik Makale

Delphi'de REGEXTEST ve REGEXEXTRACT: Statik PCRE2 Üzerinden

HotXLS, Microsoft 365 düzenli ifade fonksiyonları olan REGEXTEST, REGEXEXTRACT ve REGEXREPLACE'i kendi Delphi formül motorunun içinde, Excel kurulumu olmadan ve sistem regex DLL'i olmadan değerlendirir. HotXLS hesaplayıcısı, sabitlenmiş 16 bitlik bir PCRE2 10.49 derlemesini statik bağlar, UTF-16 metni doğrudan eşleştirir, diziler üzerinde yayın yapar ve her geri izleme adımını formülün kaynak bütçesinden tahakkuk ettirir

Son kısım, bu yazının var olma nedenidir. Müşteriden gelen bir workbook güvenilmeyen girdidir ve formül içindeki bir düzenli ifade, müşterinin yazdığı koddur. Bir CPU çekirdeğini dakikalarca meşgul etmek için desen artı metin yeter: uzun bir a dizisine, sonunda bir b ile gelen klasik ReDoS deseni (a+)+$. Yüklenen workbookları yeniden hesaplayan bir sunucu bunu kaldıramaz; bu yüzden HotXLS 2.384.112'deki ilginç iş PCRE2 çağırmak değildi. PCRE2'yi durdurulabilir, hesap verebilir ve her hedefte özdeş yapmak oldu

REGEXTEST, REGEXEXTRACT ve REGEXREPLACE HotXLS'te ne döndürür?

HotXLS formül motoru Microsoft'un yayımladığı imzaları izler ve argüman semantiği, Excel'den zaten bildiğiniz semantiktir. Eşleştirme varsayılan olarak büyük/küçük harfe duyarlıdır. Sondaki case_sensitivity argümanı büyük/küçük harfe duyarlı için 0, duyarsız için 1 alır; diğer her değer #VALUE! döndürür. (?i) ya da (*UCP) gibi satır içi PCRE2 seçenekleri de çalışır; davranışı desenin içinde tutmayı tercih ediyorsanız

REGEXTEST(text, pattern, [case_sensitivity]) bir Boolean döndürür ve başarısız bir eşleşme düpedüz FALSEdur, asla hata değil. REGEXEXTRACT(text, pattern, [return_mode], [case_sensitivity]) üç moda sahiptir: 0 ilk eksiksiz eşleşmeyi döndürür, 1 her eksiksiz eşleşmeyi dikey bir dizi olarak döndürür ve 2 ilk eşleşmenin capture gruplarını yatay bir dizi olarak döndürür. Eşleşme yokluğu #N/A döndürür; capture grubu olmayan bir desende 2 modu #VALUE! döndürür ve katılmamış isteğe bağlı bir grup, hata yerine boş bir metin verir

REGEXREPLACE(text, pattern, replacement, [occurrence], [case_sensitivity]), PCRE2 replacement sözdizimini kullanır; $1 ile ${name} capture'lara referans verir. 0 olan bir occurrence her eşleşmeyi değiştirir, pozitif bir değer baştan n'inci eşleşmeyi seçer ve negatif bir değer sondan sayar. Seçilen occurrence var olmadığında HotXLS özgün metni değiştirmeden döndürür. Sayısal seçiciler sıfıra doğru yuvarlanır; kesirli tamsayı argümanlarını Excel'in ele aldığı gibi

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';

    // Hücre bir satın alma siparişi numarası tuttuğunda TRUE
    Sheet.Cells[2, 2].Formula := 'REGEXTEST(A2, "PO-\d{4}-\d{5}")';

    // İlk eksiksiz eşleşme: "PO-2026-00417"
    Sheet.Cells[2, 3].Formula := 'REGEXEXTRACT(A2, "PO-\d{4}-\d{5}")';

    // Numaralı capture'larla yıl ve sıra takası: "PO-00417-2026 ..."
    Sheet.Cells[2, 4].Formula :=
      'REGEXREPLACE(A2, "PO-(\d{4})-(\d{5})", "PO-$2-$1")';

    // Son argümanla büyük/küçük harfe duyarsız test
    Sheet.Cells[2, 5].Formula := 'REGEXTEST(A2, "graz", 1)';

    Book.Recalculate;
    Book.SaveAs('orders.xlsx');
  finally
    Book.Free;
  end;
end;

Delphi'de formül metni düz bir string literaldir; bu yüzden \d'nin Pascal tarafında kaçışa ihtiyacı yoktur. Önemli olan tek şey formülün içindeki Excel çift tırnaklarıdır ve onlar normal Excel kurallarını izler. Kaydederken XLSX yazıcısı bu adları Excel'in beklediği _xlfn. future-function önekiyle saklar ve yeniden açılan bir workbook onları yeniden hesaplar. Klasik BIFF8 motoru fonksiyonları bağlamsal bir çağrıda değerlendirebilir ama onları yerel .xls formül token'ları olarak yazamaz; bu yüzden regex formüllerini XLSX çıktısında tutun

Dizi yayını regex formülleriyle nasıl çalışır?

REGEXTEST, REGEXREPLACE ve REGEXEXTRACT'in 0 modu uyumlu operandlar arasında yayın yapar; onları gerçek sütunlarda işe yarar kılan da budur. Her argüman bir skaler, bir satır, bir sütun ya da bir dikdörtgen olabilir ve HotXLS sonuç şekli olarak en büyük satır ve sütun sayısını alır. Boyutu ne 1 ne o azami olan bir operand, bütün çağrıyı #VALUE! döndürür; alışılmış Excel dizi uyuşmazlığı. Böylece REGEXTEST(A2:A500, "^[A-Z]{2}\d{9}$") 499 Boolean üretir ve tek bir metin hücresine karşı desen sütunu da öbür yönde aynı şekilde çalışır

REGEXEXTRACT'in 1 ile 2 modları yayına izin vermez. Her eleman farklı uzunlukta bir vektör üretirdi ve pürüzlü bir vektör kümesi dikdörtgen bir formül sonucuna sığamaz. HotXLS sessizce kısaltmak ya da doldurmak yerine çağrıyı desteklenmiyor olarak raporlar. Birçok hücreden bütün eşleşmelere ihtiyacınız varsa hücreleri tek tek gezin ve bir seferde birini çıkarın ya da problemi HotXLS formül motorunda LET ve LAMBDA closure'ları ile yeniden şekillendirin; böylece her closure çağrısı tek bir metin değeri görür

Bir yayın sonucunun içindeki hata yerleşimi bilinçlidir. Tek bir eleman için bir Excel hatası, diyelim ki 37. satırda hiçbir şey bulamayan bir çıkarmadan gelen #N/A, sonucun yalnızca o hücresine düşer; diğer hücreler değerlerini korur. Bir kaynak sınırı ya da iptal farklıdır: bütün çağrıyı durdurur ve asla yarım dolumlu bir dizi yayımlamaz; çünkü eksiksiz görünen yarım sonuç, açık bir başarısızlıktan daha kötüdür

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;   // bu formülün yapabileceği işi sınırla

    // Mod 2: ilk eşleşmenin capture grupları 1 x 3 dizi olarak
    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, desen denetlemediğiniz bir yerden geldiğinde doğru giriş noktasıdır. Formülü, verilen satır ve sütunda yaşıyormuş gibi değerlendirir, tek bir önbelleklenmiş hücre değeri yerine bütün diziyi döndürür ve açık adım, dizi-bellek ile iptal denetimleri içeren TXLSFormulaEvaluationOptions alır. O değerlendirme API'sinin gerisini HotXLS formül motoru ve custom function rehberi anlatır

HotXLS felaket geri izlemeli bir deseni nasıl durdurur?

HotXLS kontrolden çıkan desenleri, PCRE2'yi her adımda formül bağlamına rapor verdirerek durdurur. Backend her deseni PCRE2_AUTO_CALLOUT ile derler; bu, PCRE2'nin eşleştirme sırasında her desen öğesinde bir Pascal callback'i çağırmasını sağlar. O callback, etkin değerlendirme durumuna bir iş birimi tahakkuk ettirir, iptal bayrağını denetler ve ikamet belleğini denetler. Bunlardan herhangi biri dur dediğinde callback, PCRE2'nin callout-abort kodunu döndürür ve eşleşme PCRE2'nin içinde tertemiz geri sarılır. Formül, asılı bir iş parçacığı olarak değil xlfeResourceLimit ya da bir iptal durumu olarak ortaya çıkar

Bellek de aynı şekilde ölçülür. Her PCRE2 context'i, ayırıcısı bir Pascal fonksiyonu olan örnek başına genel bir context ile kurulur; böylece derlenmiş desen, eşleşme verisi ve PCRE2'nin iç geri izleme çerçevelerinin hepsi HotXLS üzerinden ayrılır. Her blok küçük bir başlık taşır ve 16 bayta hizalanır; boyutu, bütçe denetiminin formülün dizi-bellek sınırıyla karşılaştırdığı yürüyen toplama eklenir. Çıktı tamponları, çıkarma vektörleri ve replacement metni de aynı toplama eklenir; bu, sonsuza dek geri izleyen bir desen kadar, milyonlarca minik metne eşleşen bir desenin de tavana çarptığı anlamına gelir

Bütçe callback'i ayrıca göründüğünden daha önemli bir koruma altında çalışır. PCRE2'nin çağırdığı bir callback'in içinde yükselen bir Delphi istisnası asla yukarı yayılmamalıdır; çünkü callback ile sizin kodunuz arasında, Delphi istisna tablolarından hiç haberi olmayan C yığın çerçeveleri durur. Onların üzerinden geri sarmak en iyisiyle tanımsız, en kötüsüyle bozuk bir yığındır. HotXLS köprüsü callback içindeki her istisnayı yakalar, örneği iptal edilmiş duruma kilitler ve PCRE2'ye sıradan bir hata kodu döndürür. Sizin kodunuz başarısız bir formül görür; yabancı bir çerçeveden geçmiş bir istisna asla

Maliyet gerçektir ve söylemeye değer. Auto callout'lar eşleştirmeyi çıplak bir PCRE2 yorumlayıcısından yavaşlatır ve HotXLS deseni, değerlendirilen eleman başına bir kez derler; 50.000 hücre üzerine bir yayın 50.000 derleme öder. Güvenilir veri üzerinde toplu metin işi için workbook dışında özel bir Delphi regex geçişi daha hızlı olacaktır. Yabancıların size gönderdiği bir workbook'un içinde ise sınırlı ve öngörülebilir olmak, hızlı olmaktan iyidir

Neden JIT'siz statik bir UTF-16 PCRE2 derlemesi?

Excel metinleri UTF-16'dır ve Delphi WideString değerleri de öyle; bu da 16 bitlik PCRE2 kütüphanesini tek makul seçim yaptı. HotXLS formül metnini PCRE2_UTF ayarlı olarak pcre2_match_16ya verir ve geri dönen her kayma bir UTF-16 code unit indeksidir. UTF-8 gidiş dönüşü yaşanmaz; bu yüzden surrogate çiftlerinde hiçbir şey kayamaz. Boş bir eşleşmenin taramayı ilerletmesi gerektiğinde HotXLS, PCRE2'nin global-match kuralını izler: aynı konumda boş olmayan sabitlenmiş bir eşleşmeyi yeniden dene, sonra bir Unicode scalar değer ilerle; bu, bir surrogate çiftini bölmek yerine iki code unit atlar

JIT bilerek kapalıdır. JIT derleyicisi çalıştırılabilir bellek ve kendi JIT yığınını ister; bu da formül başına ayırıcının dışında platforma özgü kod ve ayırmalar çeker. Yorumlayıcı her baytı HotXLS bütçesi altında tutar, her eşleşmeyi callout altında tutar ve beş hedefin hepsinde aynı davranır. Bu formüllerin arkasındaki iç sınıf olan her TXLSRegex örneği kendi context'lerine sahiptir; böylece paralel hesaplama işçileri değiştirilebilir hiçbir PCRE2 durumunu paylaşmaz

Bağlama hikâyesi, derlemenin sıra dışılaştığı yerdir. Delphi C object dosyalarını {$L} ile bağlar ve linker'ı Pascal unit'leri ile C object'leri arasındaki sembolleri yeterince çözer; ama ayrı C object dosyaları arasındaki keyfî dairesel referansları çözmez. PCRE2, her yönden birbirini çağıran iki düzine translation unit'tir. Çözüm bir unity build'dir: PCRE2'nin tamamı tek bir translation unit olarak, hedef başına tek bir pcre2_unity object'ine derlenir; böylece her iç referans tek bir object içinde çözülür ve Delphi linker'ı yalnızca dışa açılan _16 fonksiyonlarını görür

C çalışma zamanı ikinci sorundu. PCRE2, malloc, memcpy, memcmp ve arkadaşlarını çağırır; aynı çalıştırılabilirin içine bağlanan diğer C backendleri o adların kendi kopyalarını çoktan getirmiş olabilir. HotXLS PCRE2'yi önişler; böylece o çağrılar hotxls_regex_memcpy gibi özel adlara gider ve Pascal'da uygulanır. Clang, ön işlemeden sonra yine de düz memset ve memchr çağrıları sentezleyebilir; o yüzden o ikisi de aynı unit'in içinde vardır. Sembol süslemesi her object biçimiyle eşleşmek zorundadır: Win32 OMF object'i alt çizgi önekli C adlarını bekler; Win64 ELF object'i, Win64 COFF object'i ve FPC derlemeleri için Pascal bildirimleri ise çıplak adları bağlar

Yığın probe'ları ince olandır. Çerçevesi bir sayfadan büyük olan bir C fonksiyonu önce bir probe rutini çağırır; o rutin her 4 KB sayfaya sırayla dokunur ki Windows yığını guard page'i üzerinden taahhüt edebilsin. Düpedüz geri dönen bir stub sorunsuz bağlanır ve küçük bir desenle her unit testinde çalışır; sonra derin bir çerçeve ilk kez guard page'i atladığında çöker. HotXLS her Windows object biçimi için gerçek probe'ları assembly'de uygular: Win32 object'i için __chkstk_noalloc, Win64 ELF biçimli object için __chkstk ve Win64 COFF object'i için ___chkstk_ms. Linux x64 ve macOS ARM64'te FPC bunun yerine sistem C kütüphanesini bağlar ve diğer bütün FPC hedefleri, sessizce başka bir regex lehçesine düşmek yerine backend'i kullanılamaz olarak raporlar

Sınırlar nerede?

Regex fonksiyonları Delphi ve C++Builder Windows derlemelerinde ve FPC Linux x64 ile macOS ARM64'te çalışır. Diğer her hedefte formül, başka bir motorla farklı cevaplar üretmek yerine bağlamsal değerlendirme üzerinden desteklenmeyen fonksiyon bildirir. Doğru başarısızlık budur: farklı bir platformda farklı hesaplayan bir workbook, denetim gelene kadar kimsenin bulamadığı bir hatadır

Excel eşdeğerliğinin de bildirilmiş bir sınırı vardır. İmzalar ve belgelenmiş örnekler Microsoft'un REGEXTEST, REGEXEXTRACT ve REGEXREPLACE sayfalarını izler; ama yerel doğrulamada kullanılan Excel derlemesi bu Microsoft 365 fonksiyonlarını henüz açmıyor. Bu yüzden uç durumlar, her Excel derlemesiyle bit bit eşleşme iddiası yerine yukarıda anlatılan açık PCRE2 ve dizi sözleşmelerini izler. Workbooklarınız görece bilinmeyen bir köşeye, örneğin belirli bir backreference davranışına ya da bir Unicode property sınıfına dayanıyorsa o köşeyi doğrudan test edin

FAQ

HotXLS, REGEXTEST hesaplamak için Excel'e ya da bir regex DLL'ine ihtiyaç duyar mı?

Hayır. HotXLS, çalıştırılabilirin içine statik bir UTF-16 PCRE2 object'i bağlar ve REGEXTEST, REGEXEXTRACT ile REGEXREPLACE'i kendi Delphi formül motorunda, Excel olmadan, COM olmadan ve harici DLL olmadan değerlendirir

Kötü niyetli bir desen sunucumu asılı tutabilir mi?

Yapılandırılmış bütçenin ötesine geçemez. Her eşleştirme adımı, formül bağlamına tahakkuk ettiren bir callout'tan geçer; bu yüzden bir ReDoS deseni xlfeResourceLimit ya da bir iptal durumuyla biter ve kısmi sonuç vermez

REGEXEXTRACT'in 1 modu bir aralık argümanında neden başarısız olur?

Her hücre farklı sayıda eşleşme döndürebilir ve o pürüzlü vektörler dikdörtgen bir sonuç oluşturamaz. HotXLS doldurmak ya da kısaltmak yerine çağrıyı desteklenmiyor olarak raporlar; bu yüzden bir seferde bir hücreden çıkarın

HotXLS, regex fonksiyonlarını LAMBDA, GROUPBY, PIVOTBY ve hesaplama motorunun geri kalanıyla birlikte, Delphi, C++Builder ve Lazarus için gönderir. Ayrıntılar, sürümler ve deneme indirmesi HotXLS Delphi spreadsheet component sayfasında