HotXLS evaluerer Microsoft 365 regular expression-funktionerne REGEXTEST, REGEXEXTRACT og REGEXREPLACE i sin egen Delphi-formel-motor, uden Excel-installation og uden system-regex-DLL. HotXLS-kalkulatoren linker en fastlåst 16-bit PCRE2 10.49-build statisk, matcher UTF-16-tekst direkte, broadcaster over arrays og belaster hvert backtracking-trin på formlens ressourcebudget
Netop den del er grunden til, at denne artikel findes. En arbejdsbog, der ankommer fra en kunde, er utroværdigt input, og et regular expression inde i en formel er kode, kunden skrev. Mønster plus tekst er alt, der skal til for at fastlåse en CPU-kerne i minutter med catastrophic backtracking, den klassiske ReDoS-form (a+)+$ mod et langt løb af a-tegn med en afsluttende b. En server, der genberegner uploadede arbejdsbøger, har ikke råd til det, så det interessante arbejde i HotXLS 2.384.112 var ikke at kalde PCRE2. Det var at gøre PCRE2 stoppelig, ansvarliggjort og identisk på alle mål
Hvad returnerer REGEXTEST, REGEXEXTRACT og REGEXREPLACE i HotXLS?
HotXLS formel-motor følger Microsofts publicerede signaturer, og argumenternes semantik er dem, man allerede kender fra Excel. Matching er case-sensitiv som standard. Det sidste case_sensitivity-argument tager 0 for case-sensitiv og 1 for case-insensitiv, og enhver anden værdi returnerer #VALUE!. Inline PCRE2-options som (?i) eller (*UCP) virker også, når man hellere vil holde adfærden inde i mønsteret
REGEXTEST(text, pattern, [case_sensitivity]) returnerer en Boolean, og et fejlet match er blot FALSE, aldrig en fejl. REGEXEXTRACT(text, pattern, [return_mode], [case_sensitivity]) har tre modes: 0 returnerer det første komplette match, 1 returnerer hvert komplette match som et vertikalt array, og 2 returnerer capture groups af det første match som et horisontalt array. Intet match returnerer #N/A, mode 2 på et mønster uden capture groups returnerer #VALUE!, og en valgfri gruppe, der ikke deltog, giver en tom streng i stedet for en fejl
REGEXREPLACE(text, pattern, replacement, [occurrence], [case_sensitivity]) bruger PCRE2 replacement-syntaks, så $1 og ${name} refererer captures. En occurrence på 0 erstatter hvert match, en positiv værdi vælger det n'te match fra starten, og en negativ værdi tæller fra enden. Findes den valgte occurrence ikke, returnerer HotXLS den oprindelige tekst uændret. Numeriske selectorer trunkeres mod nul, samme måde Excel behandler fraktionerede heltals-argumenter på
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, når cellen holder et indkøbsordrenummer
Sheet.Cells[2, 2].Formula := 'REGEXTEST(A2, "PO-\d{4}-\d{5}")';
// Første komplette match: "PO-2026-00417"
Sheet.Cells[2, 3].Formula := 'REGEXEXTRACT(A2, "PO-\d{4}-\d{5}")';
// Byt år og sekvens med nummererede captures: "PO-00417-2026 ..."
Sheet.Cells[2, 4].Formula :=
'REGEXREPLACE(A2, "PO-(\d{4})-(\d{5})", "PO-$2-$1")';
// Case-insensitiv test med sidste argument
Sheet.Cells[2, 5].Formula := 'REGEXTEST(A2, "graz", 1)';
Book.Recalculate;
Book.SaveAs('orders.xlsx');
finally
Book.Free;
end;
end;
Formeltekst i Delphi er en almindelig strengliteral, så \d behøver ingen escaping på Pascal-siden. Kun Excel-dobbelt-anførselstegnene inde i formlen betyder noget, og de følger normale Excel-regler. Ved gem gemmer XLSX-writeren disse navne med _xlfn.-future-function-præfikset, som Excel forventer, og en genåbnet arbejdsbog genberegner dem igen. Den klassiske BIFF8-motor kan evaluere funktionerne i et kontekstuelt kald, men den kan ikke skrive dem som native .xls-formel-tokens, så hold regex-formler i XLSX-output
Hvordan virker array-broadcasting med regex-formler?
REGEXTEST, REGEXREPLACE og mode 0 af REGEXEXTRACT broadcaster over kompatible operander, hvilket er det, der gør dem nyttige på rigtige kolonner. Hvert argument kan være en skalar, en række, en kolonne eller et rektangel, og HotXLS tager det største række- og kolonneantal som resultatform. En operand, hvis dimension hverken er 1 eller det maksimum, får hele kaldet til at returnere #VALUE!, det sædvanlige Excel array mismatch. Så REGEXTEST(A2:A500, "^[A-Z]{2}\d{9}$") giver 499 Booleans, og en kolonne af mønstre mod en enkelt tekstcelle virker på samme måde i den anden retning
Modes 1 og 2 af REGEXEXTRACT nægter at broadcaste. Hvert element ville frembringe en vektor af en anden længde, og et ujævnt sæt vektorer kan ikke optage et rektangulært formelresultat. Frem for stille at trunkere eller pade rapporterer HotXLS kaldet som ikke-understøttet. Har man brug for alle matches fra mange celler, så iterér cellerne og udtræk én ad gangen, eller omform problemet med LET- og LAMBDA-closures i HotXLS formel-motor, så hvert closure-kald ser én tekstværdi
Fejlplacering inde i et broadcast-resultat er bevidst. En Excel-fejl for ét element, sig #N/A fra en udtrækning, der fandt ingenting i række 37, lander i netop den celle af resultatet, mens de andre celler beholder deres værdier. En ressourcegrænse eller en annullering er anderledes: den stopper hele kaldet og publicerer aldrig et delvist fyldt array, for et halvt resultat, der ligner noget komplet, er værre end en eksplicit fejl
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; // begræns det arbejde, denne formel må udføre
// Mode 2: capture groups af første match som et 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 er det rigtige indgangspunkt, når mønsteret kommer fra et sted, man ikke kontrollerer. Den evaluerer formlen, som om den boede på den givne række og kolonne, returnerer hele arrayet i stedet for en enkelt cached celleværdi og tager TXLSFormulaEvaluationOptions med eksplicitte trin-, array-hukommelses- og annulleringskontroller. HotXLS formel-motor- og custom function-guiden dækker resten af den evaluerings-API
Hvordan stopper HotXLS et catastrophic backtracking-mønster?
HotXLS stopper løbske mønstre ved at få PCRE2 til at rapportere tilbage til formel-konteksten ved hvert trin. Backend'en kompilerer hvert mønster med PCRE2_AUTO_CALLOUT, hvilket får PCRE2 til at kalde en Pascal-callback ved hvert mønster-element under matching. Den callback belaster én arbejdsenhed på den aktive evaluerings-tilstand, tjekker annulleringsflagget og tjekker resident hukommelse. Siger en af dem stop, returnerer callbacken PCRE2s callout-abort-kode, og matchet vikles rent op inde i PCRE2. Formlen viser sig som xlfeResourceLimit eller en annulleringsstatus, ikke som en hængende tråd
Hukommelse måles på samme måde. Hver PCRE2-kontekst oprettes med en per-instans general-kontekst, hvis allokator er en Pascal-funktion, så det kompilerede mønster, match-dataene og PCRE2s interne backtracking-frames er alle allokeret gennem HotXLS. Hver blok bærer en lille header og er justeret til 16 bytes, og dens størrelse lægges til en løbende total, som budgettjekket sammenligner med formlens array-hukommelsesgrænse. Output-buffere, ekstraktionsvektorer og erstatningsstrengen lægges til samme total, hvilket betyder, at et mønster, der matcher millioner af små strenge, rammer loftet lige så sikkert som ét, der backtrack'er for evigt
Budget-callbacken kører også under en vagt, der betyder mere, end den ligner. En Delphi-exception rejst inde i en callback, som PCRE2 kaldte, må aldrig propagere opad, for mellem callbacken og ens kode sidder C stack frames kompileret uden nogen kendskab til Delphi exception-tabeller. At vikle op gennem dem er udefineret i bedste fald og en korrupt stack i værste fald. HotXLS-broen fanger hver exception i callbacken, låser instansen i en afbrudt tilstand og returnerer en almindelig fejlkode til PCRE2. Ens kode ser en fejlet formel, aldrig en exception, der krydsede en fremmed frame
Omkostningen er reel og værd at nævne. Auto callouts gør matching langsommere end en bar PCRE2-fortolker, og HotXLS kompilerer mønsteret én gang pr. evalueret element, så et broadcast over 50.000 celler betaler 50.000 kompileringer. Til bulk-tekstarbejde på troværdige data vil en dedikeret Delphi regex-pass uden for arbejdsbogen være hurtigere. Indeni en arbejdsbog, som fremmede sender en, slår afgrænset og forudsigeligt hurtigt
Hvorfor en statisk UTF-16 PCRE2-build uden JIT?
Excel-strenge er UTF-16, og det er Delphi WideString-værdier også, hvilket gjorde 16-bit PCRE2-biblioteket til det eneste fornuftige valg. HotXLS giver formeltekst til pcre2_match_16 med PCRE2_UTF sat, og hver offset, der kommer tilbage, er et UTF-16 code unit-indeks. Ingen UTF-8-rundtur sker, så intet kan drive på surrogate pairs. Hvor et tomt match tvinger scanningen til at rykke frem, følger HotXLS PCRE2s global-match-regel: prøv et ikke-tomt forankret match på samme position, og træd så én Unicode scalar value, hvilket rykker to code units forbi et surrogate pair i stedet for at splitte det
JIT er slået fra med vilje. JIT-kompileringen behøver eksekverbar hukommelse og sin egen JIT-stack, hvilket trækker platformspecifik kode og allokeringer uden for per-formel-allokatoren ind. Fortolkeren holder hver byte under HotXLS-budgettet, holder hvert match under callout'en og opfører sig ens på alle fem mål. Hver TXLSRegex-instans, den interne klasse bag disse formler, ejer sine egne kontekster, så parallelle beregningsarbejdere deler ingen mutabel PCRE2-tilstand
Linking-historien er, hvor buildet blev usædvanligt. Delphi linker C objektfiler med {$L}, og dets linker opløser symboler mellem Pascal-units og C-objekter fint nok, men det opløser ikke vilkårlige cirkulære referencer mellem separate C objektfiler. PCRE2 er to dusin translation units, der kalder hinanden i alle retninger. Fixet er et unity build: hele PCRE2 kompileres som én translation unit til ét pcre2_unity-objekt pr. mål, så hver intern reference opløses inde i et enkelt objekt, og Delphi-linkeren kun ser de eksporterede _16-funktioner
C-runtime'en var det andet problem. PCRE2 kalder malloc, memcpy, memcmp og venner, og andre C-backends linket ind i samme eksekverbare kan allerede have deres egne kopier af de navne. HotXLS præproesserer PCRE2, så de kald går til private navne som hotxls_regex_memcpy og implementerer dem i Pascal. Clang kan stadig syntetisere almindelige memset- og memchr-kald efter præprocessering, så de to findes også, inde i samme unit. Symbol-dekoration skal matche hvert objektformat: Win32 OMF-objektet forventer de leadende-understreg C-navne, mens Pascal-deklarationerne for Win64 ELF-objektet, Win64 COFF-objektet og FPC-builds binder de bare navne
Stack probes var den subtile. En C-funktion med en frame større end en side kalder først en probe-rutine, der rører hver 4 KB-side i rækkefølge, så Windows kan commite stacken gennem sin guard page. En stub, der blot returnerer, linker fint og virker i hver unit test med et lille mønster, og crasher så første gang, en dyb frame hopper forbi guard page'en. HotXLS implementerer ægte probes i assembler for hvert Windows-objektformat: __chkstk_noalloc til Win32-objektet, __chkstk til Win64 ELF-format-objektet og ___chkstk_ms til Win64 COFF-objektet. På Linux x64 og macOS ARM64 linker FPC system-C-biblioteket i stedet, og alle andre FPC-mål rapporterer backend'en som utilgængelig i stedet for stille at falde tilbage på en anden regex-dialekt
Hvor er grænserne?
Regex-funktionerne kører på Delphi- og C++Builder Windows-builds og på FPC Linux x64 og macOS ARM64. På ethvert andet mål rapporterer formlen en ikke-understøttet funktion gennem kontekstuel evaluering i stedet for at frembringe andre svar med en anden motor. Det er den rigtige fejl: en arbejdsbog, der beregner forskelligt på en anden platform, er en bug, ingen finder, før en revision
Excel-ækvivalens har også en angivet grænse. Signaturerne og de dokumenterede eksempler følger Microsofts REGEXTEST-, REGEXEXTRACT- og REGEXREPLACE-sider, men Excel-builden brugt til lokal validering eksponerer ikke disse Microsoft 365-funktioner endnu. Edge cases følger derfor de eksplicitte PCRE2- og array-kontrakter beskrevet ovenfor frem for et krav om at matche hver Excel-build bit for bit. Afhænger ens arbejdsbøger af en obskur hjørne, som en specifik backreference-adfærd eller en Unicode property-klasse, så test det hjørne direkte
FAQ
Behøver HotXLS Excel eller en regex-DLL for at beregne REGEXTEST?
Nej. HotXLS linker et statisk UTF-16 PCRE2-objekt ind i ens eksekverbare og evaluerer REGEXTEST, REGEXEXTRACT og REGEXREPLACE i sin egen Delphi formel-motor, uden Excel, COM eller ekstern DLL
Kan et ondsindet mønster hænge min server op?
Ikke forbi det konfigurerede budget. Hvert matching-trin går gennem en callout, der belaster formel-konteksten, så et ReDoS-mønster ender med xlfeResourceLimit eller en annulleringsstatus og intet delvist resultat
Hvorfor fejler REGEXEXTRACT mode 1 på et range-argument?
Hver celle kunne returnere et forskelligt antal matches, og de ujævne vektorer kan ikke danne et rektangulært resultat. HotXLS rapporterer kaldet som ikke-understøttet i stedet for at pade eller trunkere, så udtræk én celle ad gangen
HotXLS leverer regex-funktionerne ved siden af LAMBDA, GROUPBY, PIVOTBY og resten af sin beregningsmotor til Delphi, C++Builder og Lazarus. Detaljer, udgaver og trial-download finder man på HotXLS Delphi spreadsheet component-siden