HotXLS utvärderar reguljära uttrycksfunktionerna i Microsoft 365, REGEXTEST, REGEXEXTRACT och REGEXREPLACE, inuti sin egen Delphi-formelmotor, utan Excel-installation och utan systemets regex-DLL. HotXLS-kalkylatorn länkar en pinnad 16-bitars PCRE2 10.49-bygge statiskt, matchar UTF-16-text direkt, broadcastar över arrayer och belastar varje backtracking-steg mot formulans resursbudget
Den sista delen är skälet till att den här artikeln finns. En arbetsbok som anländer från en kund är opålitlig indata, och ett reguljärt uttryck i en formel är kod kunden skrev. Mönster plus text är allt som krävs för att fästa en CPU-kärna i minuter med katastrofal backtracking, den klassiska ReDoS-formen (a+)+$ mot en lång rad av a-tecken med en avslutande b. En server som räknar om uppladdade arbetsböcker har inte råd med det, så det intressanta arbetet i HotXLS 2.384.112 var inte att anropa PCRE2. Det var att göra PCRE2 stoppbar, redovisad och identisk på varje målplattform
Vad returnerar REGEXTEST, REGEXEXTRACT och REGEXREPLACE i HotXLS?
HotXLS-formelmotorn följer Microsoft:s publicerade signaturer, och argumentsemantiken är den du redan känner till från Excel. Matchningen är skiftlägeskänslig som standard. Det sista case_sensitivity-argumentet tar 0 för skiftlägeskänsligt och 1 för skiftlägesokänsligt, och varje annat värde returnerar #VALUE!. Inline-PCRE2-alternativ som (?i) eller (*UCP) fungerar också när du hellre håller beteendet inne i mönstret
REGEXTEST(text, pattern, [case_sensitivity]) returnerar en Boolean, och en misslyckad matchning är helt enkelt FALSE, aldrig ett fel. REGEXEXTRACT(text, pattern, [return_mode], [case_sensitivity]) har tre lägen: 0 returnerar första fullständiga matchningen, 1 returnerar varje fullständig matchning som en vertikal array, och 2 returnerar capture-grupperna av första matchningen som en horisontell array. Ingen matchning returnerar #N/A, läge 2 på ett mönster utan capture-grupper returnerar #VALUE!, och en valfri grupp som inte deltog ger en tom sträng snarare än ett fel
REGEXREPLACE(text, pattern, replacement, [occurrence], [case_sensitivity]) använder PCRE2-ersättningssyntax, så $1 och ${name} refererar capture-grupper. En occurrence på 0 ersätter varje matchning, ett positivt värde plockar den n:te matchningen från början och ett negativt värde räknar från slutet. Finns den valda förekomsten inte returnerar HotXLS originaltexten oförändrad. Numeriska väljare trunkeras mot noll, på samma sätt som Excel behandlar fractionella heltalsargument
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 håller ett inköpsordernummer
Sheet.Cells[2, 2].Formula := 'REGEXTEST(A2, "PO-\d{4}-\d{5}")';
// Första fullständiga matchningen: "PO-2026-00417"
Sheet.Cells[2, 3].Formula := 'REGEXEXTRACT(A2, "PO-\d{4}-\d{5}")';
// Byt år och sekvens med numrerade captures: "PO-00417-2026 ..."
Sheet.Cells[2, 4].Formula :=
'REGEXREPLACE(A2, "PO-(\d{4})-(\d{5})", "PO-$2-$1")';
// Skiftlägesokänsligt test med sista argumentet
Sheet.Cells[2, 5].Formula := 'REGEXTEST(A2, "graz", 1)';
Book.Recalculate;
Book.SaveAs('orders.xlsx');
finally
Book.Free;
end;
end;
Formeltext i Delphi är en vanlig strängliteral, så \d behöver ingen escaping på Pascal-sidan. Bara Excel:s dubbla citattecken inne i formeln spelar roll, och de följer vanliga Excel-regler. Vid sparning lagrar XLSX-skrivaren dessa namn med prefixet _xlfn. för framtidsfunktioner som Excel förväntar sig, och en återöppnad arbetsbok räknar om dem igen. Den klassiska BIFF8-motorn kan utvärdera funktionerna i ett kontextuellt anrop, men den kan inte skriva dem som inbyggda .xls-formeltoken, så håll regex-formler i XLSX-utdata
Hur fungerar array-broadcasting med regex-formler?
REGEXTEST, REGEXREPLACE och läge 0 av REGEXEXTRACT broadcastar över kompatibla operander, vilket är det som gör dem användbara på riktiga kolumner. Varje argument får vara en skalär, en rad, en kolumn eller en rektangel, och HotXLS tar största rad- och kolumnantal som resultatets form. En operand vars dimension varken är 1 eller det maximumet får hela anropet att returnera #VALUE!, den vanliga Excel-array-mismatchen. Så REGEXTEST(A2:A500, "^[A-Z]{2}\d{9}$") ger 499 Booleaner, och en kolumn av mönster mot en enda textcell fungerar på samma sätt åt andra hållet
Lägena 1 och 2 av REGEXEXTRACT vägrar broadcasta. Varje element skulle producera en vektor av annan längd, och en ojämn uppsättning vektorer kan inte uppta ett rektangulärt formelresultat. I stället för att tyst avkorta eller fylla ut rapporterar HotXLS anropet som ostött. Behöver du alla matchningar från många celler, iterera cellerna och extrahera en i taget, eller forma om problemet med LET- och LAMBDA-closures i HotXLS-formelmotorn så att varje closure-anrop ser ett textvärde
Felplaceringen inne i ett broadcast-resultat är medveten. Ett Excel-fel för ett element, säg #N/A från en extraktion som fann ingenting i rad 37, landar i den ena cellen av resultatet medan de andra cellerna behåller sina värden. En resursgräns eller en annullering är annorlunda: den stoppar hela anropet och publicerar aldrig en delvis fylld array, eftersom en halv resultat som ser fullständigt ut är värre än ett explicit misslyckande
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; // tak för det arbete formeln får göra
// Läge 2: capture-grupperna av första matchningen som en 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 är rätt ingångspunkt när mönstret kommer från någonstans du inte styr. Den utvärderar formeln som om den bodde på given rad och kolumn, returnerar hela arrayen i stället för ett enda cachat cellvärde, och tar TXLSFormulaEvaluationOptions med explicita steg-, arrayminnes- och annulleringskontroller. Guiden för HotXLS-formelmotorn och anpassade funktioner täcker resten av det utvärderings-API:et
Hur stoppar HotXLS ett katastrofalt backtracking-mönster?
HotXLS stoppar rasande mönster genom att låta PCRE2 rapportera tillbaka till formelkontexten vid varje steg. Backend:en kompilerar varje mönster med PCRE2_AUTO_CALLOUT, vilket gör att PCRE2 anropar en Pascal-callback vid varje mönsterobjekt under matchningen. Den callbacken belastar en enhet arbete på det aktiva utvärderingstillståndet, kontrollerar annulleringsflaggan och kontrollerar resident minne. Säger någon av de stopp returnerar callbacken PCRE2:s callout-abortkod och matchningen viks upp rent inuti PCRE2. Formeln syns som xlfeResourceLimit eller en annulleringsstatus, inte som en hängande tråd
Minnet mäts på samma sätt. Varje PCRE2-kontext skapas med en per-instans allmän kontext vars allokator är en Pascal-funktion, så det kompilerade mönstret, matchdatan och PCRE2:s interna backtracking-ramar allokeras alla genom HotXLS. Varje block bär ett litet huvud och är alinjerat till 16 bytes, och dess storlek läggs till en löpande total som budgetkontrollen jämför mot formulans arrayminnesgräns. Utgångsbuffers, extraktionsvektorer och ersättningssträngen läggs till i samma total, vilket betyder att ett mönster som matchar miljontals små strängar träffar taket lika säkert som ett som backtrackar i evighet
Budget-callbacken körs också under en vakt som betyder mer än den ser ut. Ett Delphi-undantag uppkastat inne i en callback som PCRE2 anropade får aldrig sprida sig uppåt, för mellan callbacken och din kod sitter C-stackramar kompilerade utan varje kännedom om Delphi-undantagstabeller. Att vika upp genom dem är i bästa fall odefinierat och i värsta fall en korrupt stack. HotXLS-bryggan fångar varje undantag i callbacken, låser instansen i ett avbrutet tillstånd och returnerar en vanlig felkod till PCRE2. Din kod ser en fallerad formel, aldrig ett undantag som korsade en främmande ram
Kostnaden är verklig och värd att säga. Auto callouts gör matchningen långsammare än en naken PCRE2-tolk, och HotXLS kompilerar mönstret en gång per utvärderat element, så en broadcast över 50 000 celler betalar 50 000 kompileringar. För bulkarbete på betrodd data blir en dedikerad Delphi-regexomgång utanför arbetsboken snabbare. Inne i en arbetsbok som främlingar skickar dig slår avgränsat och förutsägbart snabbt
Varför en statisk UTF-16 PCRE2-bygge utan JIT?
Excel-strängar är UTF-16, och det är Delphi:s WideString-värden också, vilket gjorde 16-bitars PCRE2-biblioteket till det enda förnuftiga valet. HotXLS lämnar formeltext till pcre2_match_16 med PCRE2_UTF satt, och varje offset som kommer tillbaka är ett UTF-16-kodenhetsindex. Ingen UTF-8-rundtur sker, så ingenting kan driva på surrogatpar. Där en tom matchning tvingar skanningen att avancera följer HotXLS PCRE2:s globala matchregel: försök igen med en icke-tom förankrad matchning på samma position, och stega sedan ett Unicode-scalarvärde, som flyttar två kodenheter förbi ett surrogatpar i stället för att klyva det
JIT är av med flit. JIT-kompilatorn behöver exekverbart minne och sin egen JIT-stack, vilket drar in plattformsspecifik kod och allokeringar utanför per-formel-allokatorn. Tolkaren håller varje byte under HotXLS-budgeten, håller varje matchning under callouten och beter sig likadant på alla fem mål. Varje TXLSRegex-instans, den interna klassen bakom de här formulerna, äger sina egna kontexter, så parallella kalkylarbetare delar inget föränderligt PCRE2-tillstånd
Länkningshistorien är där bygget blev ovanligt. Delphi länkar C-objektfiler med {$L}, och dess länkare löser symboler mellan Pascal-units och C-objekt tillräckligt bra, men den löser inte godtyckliga cirkulära referenser mellan separata C-objektfiler. PCRE2 är ett par dussin översättningsenheter som anropar varandra i alla riktningar. Fixen är en unity build: hela PCRE2 kompileras som en översättningsenhet till ett enda pcre2_unity-objekt per mål, så varje intern referens löses inuti ett enda objekt och Delphi-länkaren ser bara de exporterade _16-funktionerna
C-runtimen var det andra problemet. PCRE2 anropar malloc, memcpy, memcmp och vänner, och andra C-backends länkade in i samma körbara fil kan redan ha med sig egna kopior av de namnen. HotXLS förbehandlar PCRE2 så att de anropen går till privata namn som hotxls_regex_memcpy och implementerar dem i Pascal. Clang kan fortfarande syntetisera enkla memset- och memchr-anrop efter förbehandlingen, så de två finns också, inuti samma unit. Symboldekorationen måste matcha varje objektformat: Win32-OMF-objektet förväntar sig C-namnen med inledande understreck, medan Pascal-deklarationerna för Win64-ELF-objektet, Win64-COFF-objektet och FPC-byggena binder de nakna namnen
Stackprobarna var det subtila. En C-funktion med en ram större än en sida anropar först en prob-rutin, som vidrör varje 4 KB-sida i ordning så att Windows kan committa stacken genom dess guard-sida. En stubb som bara returnerar länkar fint och fungerar i varje unittest med ett litet mönster, för att sedan krascha första gången en djup ram hoppar förbi guard-sidan. HotXLS implementerar riktiga probbar i assembler för varje Windows-objektformat: __chkstk_noalloc för Win32-objektet, __chkstk för Win64-ELF-formatobjektet och ___chkstk_ms för Win64-COFF-objektet. På Linux x64 och macOS ARM64 länkar FPC systemets C-bibliotek i stället, och varje annat FPC-mål rapporterar backend:en som otillgänglig i stället för att tyst falla tillbaka på en annan regexdialekt
Var går gränserna?
Regex-funktionerna körs på Delphi- och C++Builder-Windows-byggen och på FPC Linux x64 och macOS ARM64. På vilket annat mål som helst rapporterar formeln en ostödd funktion genom kontextuell utvärdering i stället för att producera andra svar med en annan motor. Det är rätt fel: en arbetsbok som räknar olika på olika plattformar är en bugg ingen hittar förrän en revision
Excel-ekvivalensen har en angiven gräns också. Signaturerna och de dokumenterade exemplen följer Microsoft:s sidor för REGEXTEST, REGEXEXTRACT och REGEXREPLACE, men Excel-bygget som använts för lokal validering exponerar inte dessa Microsoft 365-funktioner ännu. Kantfall följer därför de explicita PCRE2- och arraykontrakten som beskrivits ovan snarare än ett påstående om att matcha varje Excel-bygge bit för bit. Beror dina arbetsböcker på ett obskyrt hörn, som en specifik backreference-beteende eller en Unicode-egensklass, testa det hörnet direkt
FAQ
Behöver HotXLS Excel eller en regex-DLL för att beräkna REGEXTEST?
Nej. HotXLS länkar ett statiskt UTF-16 PCRE2-objekt in i din körbara fil och utvärderar REGEXTEST, REGEXEXTRACT och REGEXREPLACE i sin egen Delphi-formelmotor, utan Excel, COM eller extern DLL
Kan ett elakt mönster låsa min server?
Inte bortom den konfigurerade budgeten. Varje matchningssteg går genom en callout som belastar formelkontexten, så ett ReDoS-mönster slutar med xlfeResourceLimit eller en annulleringsstatus och inget partiellt resultat
Varför fallerar REGEXEXTRACT läge 1 på ett intervallargument?
Varje cell kunde returnera ett annat antal matchningar, och de ojämna vektorerna kan inte bilda ett rektangulärt resultat. HotXLS rapporterar anropet som ostött i stället för att fylla ut eller avkorta, så extrahera en cell i taget
HotXLS levererar regex-funktionerna bredvid LAMBDA, GROUPBY, PIVOTBY och resten av sin kalkylmotor för Delphi, C++Builder och Lazarus. Detaljer, utgåvor och nedladdningen av provversionen finns på sidan för HotXLS Delphi spreadsheet component