HotXLS wertet die Microsoft-365-Regular-Expression-Funktionen REGEXTEST, REGEXEXTRACT und REGEXREPLACE innerhalb seiner eigenen Delphi-Formel-Engine aus, ohne Excel-Installation und ohne System-Regex-DLL. Der HotXLS-Calculator linkt einen gepinnten 16-Bit-PCRE2-10.49-Build statisch, matcht UTF-16-Text direkt, broadcastet über Arrays und belastet jeden Backtracking-Schritt gegen das Ressourcenbudget der Formel
Genau dieser Teil ist der Grund, dass dieser Artikel existiert. Eine Mappe, die von einem Kunden ankommt, ist nicht vertrauenswürdige Eingabe, und eine Regular Expression in einer Formel ist Code, den der Kunde geschrieben hat. Pattern plus Text genügt, um mit katastrophalem Backtracking einen CPU-Kern minutenlang festzunageln – die klassische ReDoS-Form (a+)+$ gegen eine lange Folge von a-Zeichen mit einem angehängten b. Ein Server, der hochgeladene Mappen neu berechnet, kann sich das nicht leisten; die interessante Arbeit in HotXLS 2.384.112 war also nicht, PCRE2 aufzurufen. Sie war, PCRE2 stoppbar, abrechenbar und auf jedem Target identisch zu machen
Was geben REGEXTEST, REGEXEXTRACT und REGEXREPLACE in HotXLS zurück?
Die HotXLS-Formel-Engine folgt den veröffentlichten Signaturen von Microsoft, und die Argumentsemantik ist die, die Sie von Excel kennen. Gematcht wird standardmäßig case-sensitiv. Das letzte Argument case_sensitivity nimmt 0 für case-sensitiv und 1 für case-insensitiv, jeder andere Wert gibt #VALUE! zurück. Inline-PCRE2-Optionen wie (?i) oder (*UCP) funktionieren ebenfalls, wenn Sie das Verhalten lieber im Pattern halten wollen
REGEXTEST(text, pattern, [case_sensitivity]) gibt einen Boolean zurück, ein fehlgeschlagener Match ist schlicht FALSE, nie ein Fehler. REGEXEXTRACT(text, pattern, [return_mode], [case_sensitivity]) hat drei Modi: 0 gibt den ersten vollständigen Match zurück, 1 jeden vollständigen Match als vertikales Array, und 2 die Capture-Groups des ersten Matchs als horizontales Array. Kein Match gibt #N/A zurück, Modus 2 auf einem Pattern ohne Capture-Groups gibt #VALUE!, und eine optionale Group, die nicht teilgenommen hat, ergibt einen leeren String statt eines Fehlers
REGEXREPLACE(text, pattern, replacement, [occurrence], [case_sensitivity]) nutzt die PCRE2-Replacement-Syntax, $1 und ${name} referenzieren also Captures. Ein occurrence von 0 ersetzt jeden Match, ein positiver Wert pickt den n-ten Match vom Anfang, ein negativer zählt vom Ende. Existiert die gewählte Occurrence nicht, gibt HotXLS den Originaltext unverändert zurück. Numerische Selektoren werden Richtung null abgeschnitten, so wie Excel gebrochene Integer-Argumente behandelt
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, wenn die Zelle eine Bestellnummer hält
Sheet.Cells[2, 2].Formula := 'REGEXTEST(A2, "PO-\d{4}-\d{5}")';
// Erster vollständiger Match: "PO-2026-00417"
Sheet.Cells[2, 3].Formula := 'REGEXEXTRACT(A2, "PO-\d{4}-\d{5}")';
// Jahr und Sequenz mit nummerierten Captures tauschen: "PO-00417-2026 ..."
Sheet.Cells[2, 4].Formula :=
'REGEXREPLACE(A2, "PO-(\d{4})-(\d{5})", "PO-$2-$1")';
// Case-insensitiver Test mit dem letzten Argument
Sheet.Cells[2, 5].Formula := 'REGEXTEST(A2, "graz", 1)';
Book.Recalculate;
Book.SaveAs('orders.xlsx');
finally
Book.Free;
end;
end;
Formeltext ist in Delphi ein schlichtes String-Literal, \d braucht auf der Pascal-Seite also kein Escaping. Nur die Excel-Doppelten Anführungszeichen innerhalb der Formel zählen, und sie folgen den normalen Excel-Regeln. Beim Speichern legt der XLSX-Writer diese Namen mit dem _xlfn.-Future-Function-Präfix ab, das Excel erwartet, und eine wieder geöffnete Mappe berechnet sie erneut. Die klassische BIFF8-Engine kann die Funktionen in einem kontextuellen Aufruf auswerten, aber sie kann sie nicht als native .xls-Formel-Tokens schreiben, halten Sie Regex-Formeln also in XLSX-Ausgabe
Wie funktioniert Array-Broadcasting bei Regex-Formeln?
REGEXTEST, REGEXREPLACE und Modus 0 von REGEXEXTRACT broadcasten über kompatible Operanden, was sie auf echten Spalten nützlich macht. Jedes Argument darf ein Skalar, eine Zeile, eine Spalte oder ein Rechteck sein, und HotXLS nimmt die größte Zeilen- und Spaltenzahl als Ergebnisform. Ein Operand, dessen Dimension weder 1 noch dieses Maximum ist, lässt den ganzen Aufruf #VALUE! zurückgeben, den üblichen Excel-Array-Mismatch. REGEXTEST(A2:A500, "^[A-Z]{2}\d{9}$") liefert also 499 Booleans, und eine Spalte von Patterns gegen eine einzelne Textzelle funktioniert umgekehrt genauso
Die Modi 1 und 2 von REGEXEXTRACT verweigern das Broadcasting. Jedes Element würde einen Vektor anderer Länge produzieren, und ein zerrissener Satz von Vektoren kann kein rechteckiges Formelergebnis füllen. Statt stillschweigend zu beschneiden oder aufzufüllen, meldet HotXLS den Aufruf als nicht unterstützt. Brauchen Sie alle Matches aus vielen Zellen, iterieren Sie die Zellen und extrahieren Sie einzeln, oder formen Sie das Problem mit LET- und LAMBDA-Closures in der HotXLS-Formel-Engine um, sodass jeder Closure-Aufruf einen Textwert sieht
Die Fehlerplatzierung innerhalb eines Broadcast-Ergebnisses ist bewusst. Ein Excel-Fehler für ein Element, etwa #N/A von einer Extraktion, die in Zeile 37 nichts fand, landet in genau dieser einen Zelle des Ergebnisses, während die anderen Zellen ihre Werte behalten. Ein Ressourcenlimit oder eine Abbruchanforderung ist anders: Sie stoppen den ganzen Aufruf und publizieren nie ein teils gefülltes Array, denn ein halbes Ergebnis, das komplett aussieht, ist schlimmer als ein explizites Versagen
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; // deckelt die Arbeit, die diese Formel tun darf
// Mode 2: Capture-Groups des ersten Matchs als 1 x 3 Array
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 ist der richtige Einstiegspunkt, wenn das Pattern von irgendwoher kommt, das Sie nicht kontrollieren. Er wertet die Formel aus, als säße sie an der gegebenen Zeile und Spalte, liefert das volle Array statt eines einzelnen gecachten Zellwerts und nimmt TXLSFormulaEvaluationOptions mit expliziten Step-, Array-Memory- und Cancellation-Kontrollen. Der Rest dieser Evaluations-API steht im HotXLS-Formel-Engine- und Custom-Function-Guide
Wie stoppt HotXLS ein katastrophal backtrackendes Pattern?
HotXLS stoppt ausufernde Patterns, indem es PCRE2 bei jedem Schritt an den Formel-Kontext zurückmelden lässt. Das Backend kompiliert jedes Pattern mit PCRE2_AUTO_CALLOUT, PCRE2 ruft also bei jedem Pattern-Item während des Matchings einen Pascal-Callback auf. Dieser Callback belastet den aktiven Evaluationszustand mit einer Arbeitseinheit, prüft das Cancellation-Flag und prüft den Resident-Memory. Sagt eines davon Stopp, gibt der Callback PCRE2s Callout-Abort-Code zurück, und der Match windet sich sauber in PCRE2 ab. Die Formel taucht als xlfeResourceLimit oder ein Cancellation-Status auf, nicht als hängender Thread
Der Speicher wird genauso abgerechnet. Jeder PCRE2-Kontext wird mit einem pro Instanz erzeugten General Context angelegt, dessen Allokator eine Pascal-Funktion ist; das kompilierte Pattern, die Match-Daten und PCRE2s interne Backtracking-Frames werden also alle durch HotXLS allokiert. Jeder Block trägt einen kleinen Header, ist auf 16 Bytes ausgerichtet, und seine Größe läuft in einen fortlaufenden Total, den die Budgetprüfung gegen das Array-Memory-Limit der Formel hält. Output-Buffer, Extraktionsvektoren und der Replacement-String laufen in denselben Total, ein Pattern, das Millionen winziger Strings matcht, erreicht die Decke also genauso sicher wie eines, das ewig backtracked
Der Budget-Callback läuft außerdem unter einem Guard, der mehr zählt, als er aussieht. Eine Delphi-Exception, die innerhalb eines Callbacks geworfen wird, den PCRE2 gerufen hat, darf nie nach oben propagieren, denn zwischen dem Callback und Ihrem Code sitzen C-Stack-Frames, kompiliert ohne jede Kenntnis Delphischer Exception-Tabellen. Durch sie hindurch zu unwinden ist im besten Fall undefiniert und im schlimmsten ein korrupter Stack. Die HotXLS-Bridge fängt jede Exception im Callback, verriegelt die Instanz in einen Abbruchzustand und gibt einen gewöhnlichen Fehlercode an PCRE2 zurück. Ihr Code sieht eine fehlgeschlagene Formel, nie eine Exception, die einen fremden Frame gekreuzt hat
Der Preis ist real und erwähnenswert. Auto Callouts machen das Matching langsamer als einen nackten PCRE2-Interpreter, und HotXLS kompiliert das Pattern einmal pro ausgewertetem Element, ein Broadcast über 50.000 Zellen zahlt also 50.000 Kompilierungen. Für Bulk-Textarbeit auf vertrauenswürdigen Daten wird ein dedizierter Delphi-Regex-Durchlauf außerhalb der Mappe schneller sein. Innerhalb einer Mappe, die Ihnen Fremde schicken, schlägt begrenzt und vorhersagbar schnell
Warum ein statischer UTF-16-PCRE2-Build ohne JIT?
Excel-Strings sind UTF-16, und Delphi-WideString-Werte auch, das machte die 16-Bit-PCRE2-Bibliothek zur einzig sinnvollen Wahl. HotXLS übergibt Formeltext an pcre2_match_16 mit gesetztem PCRE2_UTF, und jeder zurückkommende Offset ist ein UTF-16-Code-Unit-Index. Kein UTF-8-Rundtrip passiert, nichts kann also auf Surrogate-Paaren abdriften. Wo ein leerer Match den Scan zum Weiterschalten zwingt, folgt HotXLS PCRE2s Global-Match-Regel: einen nichtleeren verankerten Match an derselben Position wiederholen, dann um einen Unicode-Skalarwert weiterschalten, der zwei Code Units an einem Surrogate-Paar vorübergeht, statt es zu zerschneiden
JIT ist absichtlich aus. Der JIT-Compiler braucht ausführbaren Speicher und seinen eigenen JIT-Stack, was plattformspezifischen Code und Allokationen außerhalb des Pro-Formula-Allokators hereinzieht. Der Interpreter hält jedes Byte unter dem HotXLS-Budget, jeden Match unter dem Callout und verhält sich auf allen fünf Targets gleich. Jede TXLSRegex-Instanz, die interne Klasse hinter diesen Formeln, besitzt ihre eigenen Kontexte, parallele Berechnungs-Worker teilen sich also keinen veränderlichen PCRE2-Zustand
Die Link-Geschichte ist der Punkt, an dem der Build ungewöhnlich wurde. Delphi linkt C-Objektdateien mit {$L}, und sein Linker löst Symbole zwischen Pascal-Units und C-Objekten gut genug auf, aber er löst keine beliebigen Zirkelbezüge zwischen separaten C-Objektdateien auf. PCRE2 sind zwei Dutzend Translation Units, die sich in alle Richtungen gegenseitig aufrufen. Der Fix ist ein Unity Build: ganz PCRE2 wird als eine Translation Unit in ein pcre2_unity-Objekt je Target kompiliert, jede interne Referenz löst sich also innerhalb eines einzigen Objekts auf, und der Delphi-Linker sieht nur die exportierten _16-Funktionen
Die C-Runtime war das zweite Problem. PCRE2 ruft malloc, memcpy, memcmp und Gefährten, und andere C-Backends, die in dieselbe Executable gelinkt werden, bringen ihre eigenen Kopien dieser Namen vielleicht bereits mit. HotXLS präprozessiert PCRE2 so, dass diese Aufrufe zu privaten Namen wie hotxls_regex_memcpy gehen und in Pascal implementiert werden. Clang kann nach dem Präprozessieren weiterhin schlichte memset- und memchr-Aufrufe synthetisieren, also existieren auch diese beiden, innerhalb derselben Unit. Die Symbol-Dekoration muss zum jeweiligen Objektformat passen: Das Win32-OMF-Objekt erwartet die C-Namen mit führendem Unterstrich, während die Pascal-Deklarationen für das Win64-ELF-Objekt, das Win64-COFF-Objekt und die FPC-Builds die nackten Namen binden
Die Stack-Probes waren der subtile Teil. Eine C-Funktion mit einem größeren als seitengroßen Frame ruft zuerst eine Probe-Routine auf, die jede 4-KB-Seite der Reihe nach anfasst, damit Windows den Stack über seine Guard-Page committen kann. Ein Stub, der schlicht zurückgibt, linkt fehlerfrei und funktioniert in jedem Unit-Test mit kleinem Pattern, crasht dann aber beim ersten tiefen Frame, der über die Guard-Page springt. HotXLS implementiert echte Probes in Assembler für jedes Windows-Objektformat: __chkstk_noalloc für das Win32-Objekt, __chkstk für das Win64-ELF-Format-Objekt und ___chkstk_ms für das Win64-COFF-Objekt. Unter Linux x64 und macOS ARM64 linkt FPC stattdessen die System-C-Bibliothek, und jedes weitere FPC-Target meldet das Backend als nicht verfügbar, statt stillschweigend auf einen anderen Regex-Dialekt zurückzufallen
Wo liegen die Grenzen?
Die Regex-Funktionen laufen auf Delphi- und C++Builder-Windows-Builds sowie auf FPC Linux x64 und macOS ARM64. Auf jedem anderen Target meldet die Formel über kontextuelle Auswertung eine nicht unterstützte Funktion, statt mit einer anderen Engine andere Antworten zu produzieren. Das ist das richtige Versagen: eine Mappe, die auf einer anderen Plattform anders rechnet, ist ein Bug, den niemand findet, bis eine Audit kommt
Die Excel-Äquivalenz hat ebenfalls eine deklarierte Grenze. Signaturen und dokumentierte Beispiele folgen Microsofts REGEXTEST-, REGEXEXTRACT- und REGEXREPLACE-Seiten, aber der Excel-Build, der für die lokale Validierung benutzt wurde, exponiert diese Microsoft-365-Funktionen noch nicht. Randfälle folgen deshalb den expliziten PCRE2- und Array-Verträgen von oben statt dem Anspruch, jeden Excel-Build bitweise zu treffen. Hängen Ihre Mappen an einer obskuren Ecke – ein bestimmtes Backreference-Verhalten oder eine Unicode-Property-Class –, testen Sie diese Ecke direkt
FAQ
Braucht HotXLS Excel oder eine Regex-DLL, um REGEXTEST zu berechnen?
Nein. HotXLS linkt ein statisches UTF-16-PCRE2-Objekt in Ihre Executable und wertet REGEXTEST, REGEXEXTRACT und REGEXREPLACE in seiner eigenen Delphi-Formel-Engine aus, ohne Excel, COM oder externe DLL
Kann ein bösartiges Pattern meinen Server aufhängen?
Nicht über das konfigurierte Budget hinaus. Jeder Matching-Schritt läuft durch einen Callout, der den Formel-Kontext belastet; ein ReDoS-Pattern endet also mit xlfeResourceLimit oder einem Cancellation-Status und ohne Teilergebnis
Warum failt REGEXEXTRACT-Modus 1 bei einem Range-Argument?
Jede Zelle könnte eine unterschiedliche Anzahl von Matches liefern, und diese zerrissenen Vektoren können kein rechteckiges Ergebnis bilden. HotXLS meldet den Aufruf als nicht unterstützt, statt aufzufüllen oder zu beschneiden; extrahieren Sie also Zelle für Zelle
HotXLS liefert die Regex-Funktionen neben LAMBDA, GROUPBY, PIVOTBY und dem Rest seiner Berechnungs-Engine für Delphi, C++Builder und Lazarus aus. Details, Editionen und den Trial-Download finden Sie auf der HotXLS Delphi spreadsheet component page