git-svn-id: https://192.168.0.254/svn/Componentes.Terceros.jcl@20 c37d764d-f447-7644-a108-883140d013fb
753 lines
24 KiB
ObjectPascal
753 lines
24 KiB
ObjectPascal
{**************************************************************************************************}
|
|
{ }
|
|
{ Project JEDI Code Library (JCL) }
|
|
{ }
|
|
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
|
|
{ you may not use this file except in compliance with the License. You may obtain a copy of the }
|
|
{ License at http://www.mozilla.org/MPL/ }
|
|
{ }
|
|
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF }
|
|
{ ANY KIND, either express or implied. See the License for the specific language governing rights }
|
|
{ and limitations under the License. }
|
|
{ }
|
|
{ The Original Code is JclPCRE.pas. }
|
|
{ }
|
|
{ The Initial Developer of the Original Code is Peter Thornqvist. }
|
|
{ Portions created by Peter Thornqvist are Copyright (C) of Peter Thornqvist. All rights reserved. }
|
|
{ }
|
|
{ Contributor(s): }
|
|
{ Robert Rossmair (rrossmair) }
|
|
{ Mario R. Carro }
|
|
{ Florent Ouchet (outchy) }
|
|
{ }
|
|
{**************************************************************************************************}
|
|
{ }
|
|
{ Class wrapper for PCRE (PERL Compatible Regular Expression) }
|
|
{ }
|
|
{**************************************************************************************************}
|
|
{ }
|
|
{ Last modified: $Date:: 2009-08-09 15:08:29 +0200 (dim., 09 août 2009) $ }
|
|
{ Revision: $Rev:: 2921 $ }
|
|
{ Author: $Author:: outchy $ }
|
|
{ }
|
|
{**************************************************************************************************}
|
|
|
|
unit JclPCRE;
|
|
|
|
{$I jcl.inc}
|
|
|
|
{$RANGECHECKS OFF}
|
|
|
|
interface
|
|
|
|
uses
|
|
pcre,
|
|
{$IFDEF UNITVERSIONING}
|
|
JclUnitVersioning,
|
|
{$ENDIF UNITVERSIONING}
|
|
{$IFDEF HAS_UNIT_LIBC}
|
|
Libc,
|
|
{$ENDIF HAS_UNIT_LIBC}
|
|
Classes, SysUtils,
|
|
JclBase, JclStringConversions;
|
|
|
|
const
|
|
JCL_PCRE_CALLOUT_NOERROR = 0;
|
|
JCL_PCRE_CALLOUT_FAILCONTINUE = 1;
|
|
|
|
JCL_PCRE_ERROR_CALLOUTERROR = -998;
|
|
JCL_PCRE_ERROR_STUDYFAILED = -999;
|
|
|
|
type
|
|
TJclRegEx = class;
|
|
|
|
EPCREError = class(EJclError)
|
|
private
|
|
FErrorCode: Integer;
|
|
public
|
|
constructor CreateRes(ResStringRec: PResStringRec; ErrorCode: Integer);
|
|
property ErrorCode: Integer read FErrorCode;
|
|
end;
|
|
|
|
TPCREIntArray = array [0 .. 0] of Integer;
|
|
PPCREIntArray = ^TPCREIntArray;
|
|
|
|
TJclRegExOption = (roIgnoreCase, roMultiLine, roDotAll, roExtended,
|
|
roAnchored, roDollarEndOnly, roExtra, roNotBOL, roNotEOL, roUnGreedy,
|
|
roNotEmpty, roUTF8, roNoAutoCapture, roNoUTF8Check, roAutoCallout,
|
|
roPartial, roDfaShortest, roDfaRestart, roDfaFirstLine, roDupNames,
|
|
roNewLineCR, roNewLineLF, roNewLineCRLF, roNewLineAny, roBSRAnyCRLF,
|
|
roBSRUnicode, roJavascriptCompat, roNoStartOptimize);
|
|
TJclRegExOptions = set of TJclRegExOption;
|
|
TJclCaptureRange = record
|
|
FirstPos: Integer;
|
|
LastPos: Integer;
|
|
end;
|
|
|
|
TJclRegExCallout = procedure (Sender: TJclRegEx;
|
|
Index, MatchStart, SubjectPos, LastCapture, PatternPos, NextItemLength: Integer;
|
|
var ErrorCode: Integer) of object;
|
|
TPCRECalloutIndex = 0 .. 255;
|
|
|
|
TJclRegEx = class(TObject)
|
|
private
|
|
FCode: PPCRE;
|
|
FExtra: PPCREExtra;
|
|
FOptions: TJclRegExOptions;
|
|
FPattern: string;
|
|
FDfaMode: Boolean;
|
|
FSubject: string;
|
|
|
|
FViewChanges: Boolean;
|
|
FChangedCaptures: TList;
|
|
FResultValues: array of string;
|
|
|
|
FErrorCode: Integer;
|
|
FErrorMessage: string;
|
|
FErrorOffset: Integer;
|
|
|
|
FVector: PPCREIntArray;
|
|
FVectorSize: Integer;
|
|
FCaptureCount: Integer;
|
|
|
|
FOnCallout: TJclRegExCallout;
|
|
|
|
function GetResult: string;
|
|
function GetCapture(Index: Integer): string;
|
|
procedure SetCapture(Index: Integer; const Value: string);
|
|
function GetCaptureRange(Index: Integer): TJclCaptureRange;
|
|
function GetNamedCapture(const Name: string): string;
|
|
procedure SetNamedCapture(const Name, Value: string);
|
|
function GetCaptureNameCount: Integer;
|
|
function GetCaptureName(Index: Integer): string;
|
|
function GetAPIOptions(RunTime: Boolean): Integer;
|
|
function CalloutHandler(var CalloutBlock: pcre_callout_block): Integer;
|
|
|
|
public
|
|
destructor Destroy; override;
|
|
|
|
property Options: TJclRegExOptions read FOptions write FOptions;
|
|
function Compile(const Pattern: string; Study: Boolean;
|
|
UserLocale: Boolean = False): Boolean;
|
|
property Pattern: string read FPattern;
|
|
property DfaMode: Boolean read FDfaMode write FDfaMode;
|
|
function Match(const Subject: string; StartOffset: Cardinal = 1): Boolean;
|
|
property Subject: string read FSubject;
|
|
property Result: string read GetResult;
|
|
|
|
property ViewChanges: Boolean read FViewChanges write FViewChanges;
|
|
property CaptureCount: Integer read FCaptureCount write FCaptureCount;
|
|
property Captures[Index: Integer]: string read GetCapture write SetCapture;
|
|
property CaptureRanges[Index: Integer]: TJclCaptureRange read GetCaptureRange;
|
|
|
|
property NamedCaptures[const Name: string]: string
|
|
read GetNamedCapture write SetNamedCapture;
|
|
property CaptureNameCount: Integer read GetCaptureNameCount;
|
|
property CaptureNames[Index: Integer]: string read GetCaptureName;
|
|
function IndexOfName(const Name: string): Integer;
|
|
function IsNameValid(const Name: string): Boolean;
|
|
|
|
property ErrorCode: Integer read FErrorCode;
|
|
property ErrorMessage: string read FErrorMessage;
|
|
property ErrorOffset: Integer read FErrorOffset;
|
|
|
|
property OnCallout: TJclRegExCallout read FOnCallout write FOnCallout;
|
|
end;
|
|
|
|
TJclAnsiRegEx = TJclRegEx;
|
|
TJclAnsiRegExOption = TJclRegExOption;
|
|
TJclAnsiRegExOptions = TJclRegExOptions;
|
|
TJclAnsiCaptureRange = TJclCaptureRange;
|
|
TJclAnsiRegExCallout = TJclRegExCallout;
|
|
|
|
|
|
procedure InitializeLocaleSupport;
|
|
procedure TerminateLocaleSupport;
|
|
|
|
// Args is an array of pairs (CaptureIndex, Value) or (CaptureName, Value).
|
|
// For example: NewIp := StrReplaceRegEx(DirIP, '(\d+)\.(\d+)\.(\d+)\.(\d+)', [3, '128', 4, '254']);
|
|
function StrReplaceRegEx(const Subject, Pattern: string; Args: array of const): string;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
const
|
|
UnitVersioning: TUnitVersionInfo = (
|
|
RCSfile: '$URL: https://jcl.svn.sourceforge.net/svnroot/jcl/tags/JCL-2.1-Build3536/jcl/source/common/JclPCRE.pas $';
|
|
Revision: '$Revision: 2921 $';
|
|
Date: '$Date: 2009-08-09 15:08:29 +0200 (dim., 09 août 2009) $';
|
|
LogPath: 'JCL\source\common';
|
|
Extra: '';
|
|
Data: nil
|
|
);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysConst,
|
|
JclResources;
|
|
|
|
function EncodeString(const S: string; ToUTF8: Boolean): AnsiString; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF SUPPORTS_INLINE}
|
|
begin
|
|
if ToUTF8 then
|
|
Result := StringToUTF8(S)
|
|
else
|
|
Result := AnsiString(S);
|
|
end;
|
|
|
|
function DecodeString(const S: AnsiString; IsUTF8: Boolean): string; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF SUPPORTS_INLINE}
|
|
begin
|
|
if IsUTF8 then
|
|
Result := UTF8ToString(S)
|
|
else
|
|
Result := string(S);
|
|
end;
|
|
|
|
function TranslateIndex(const S: string; ToUTF8: Boolean; Index: SizeInt): SizeInt;
|
|
var
|
|
UTF8Buffer: TUTF8String;
|
|
UTF8Pos, StrPos, StrLen: SizeInt;
|
|
Ch: UCS4;
|
|
begin
|
|
if ToUTF8 then
|
|
begin
|
|
SetLength(UTF8Buffer, 6);
|
|
StrPos := 1;
|
|
StrLen := Length(S);
|
|
while (StrPos > 0) and (StrPos <= StrLen) and (Index > 1) do
|
|
begin
|
|
UTF8Pos := 1;
|
|
Ch := StringGetNextChar(S, StrPos);
|
|
if (StrPos > 0) and UTF8SetNextChar(UTF8Buffer, UTF8Pos, Ch) and (UTF8Pos > 0) then
|
|
Dec(Index, UTF8Pos - 1);
|
|
end;
|
|
if StrPos <= 0 then
|
|
raise EJclUnexpectedEOSequenceError.Create
|
|
else
|
|
if StrPos > StrLen then
|
|
Result := StrLen + 1
|
|
else
|
|
Result := StrPos;
|
|
end
|
|
else
|
|
Result := Index;
|
|
end;
|
|
|
|
var
|
|
GTables: PAnsiChar;
|
|
|
|
function JclPCREGetMem(Size: SizeInt): Pointer; {$IFDEF PCRE_EXPORT_CDECL} cdecl; {$ENDIF PCRE_EXPORT_CDECL}
|
|
begin
|
|
GetMem(Result, Size);
|
|
end;
|
|
|
|
procedure JclPCREFreeMem(P: Pointer); {$IFDEF PCRE_EXPORT_CDECL} cdecl; {$ENDIF PCRE_EXPORT_CDECL}
|
|
begin
|
|
FreeMem(P);
|
|
end;
|
|
|
|
function JclPCRECallout(var callout_block: pcre_callout_block): Integer; {$IFDEF PCRE_EXPORT_CDECL} cdecl; {$ENDIF PCRE_EXPORT_CDECL}
|
|
begin
|
|
Result := TJclRegEx(callout_block.callout_data).CalloutHandler(callout_block);
|
|
end;
|
|
|
|
function PCRECheck(Value: Integer): Boolean;
|
|
var
|
|
PErr: PResStringRec;
|
|
begin
|
|
Result := Value >= 0;
|
|
if Result then Exit;
|
|
|
|
case Value of
|
|
PCRE_ERROR_NOMATCH:
|
|
PErr := @RsErrNoMatch;
|
|
PCRE_ERROR_NULL:
|
|
PErr := @RsErrNull;
|
|
PCRE_ERROR_BADOPTION:
|
|
PErr := @RsErrBadOption;
|
|
PCRE_ERROR_BADMAGIC:
|
|
PErr := @RsErrBadMagic;
|
|
PCRE_ERROR_UNKNOWN_NODE:
|
|
PErr := @RsErrUnknownNode;
|
|
PCRE_ERROR_NOMEMORY:
|
|
PErr := @RsErrNoMemory;
|
|
PCRE_ERROR_NOSUBSTRING:
|
|
PErr := @RsErrNoSubString;
|
|
PCRE_ERROR_MATCHLIMIT:
|
|
PErr := @RsErrMatchLimit;
|
|
PCRE_ERROR_CALLOUT:
|
|
PErr := @RsErrCallout;
|
|
PCRE_ERROR_BADUTF8:
|
|
PErr := @RsErrBadUTF8;
|
|
PCRE_ERROR_BADUTF8_OFFSET:
|
|
PErr := @RsErrBadUTF8Offset;
|
|
PCRE_ERROR_PARTIAL:
|
|
PErr := @RsErrPartial;
|
|
PCRE_ERROR_BADPARTIAL:
|
|
PErr := @RsErrBadPartial;
|
|
PCRE_ERROR_INTERNAL:
|
|
PErr := @RsErrInternal;
|
|
PCRE_ERROR_BADCOUNT:
|
|
PErr := @RsErrBadCount;
|
|
PCRE_ERROR_DFA_UITEM:
|
|
PErr := @RsErrDfaUItem;
|
|
PCRE_ERROR_DFA_UCOND:
|
|
PErr := @RsErrDfaUCond;
|
|
PCRE_ERROR_DFA_UMLIMIT:
|
|
PErr := @RsErrDfaUMLimit;
|
|
PCRE_ERROR_DFA_WSSIZE:
|
|
PErr := @RsErrDfaWSSize;
|
|
PCRE_ERROR_DFA_RECURSE:
|
|
PErr := @RsErrDfaRecurse;
|
|
PCRE_ERROR_RECURSIONLIMIT:
|
|
PErr := @RsErrRecursionLimit;
|
|
PCRE_ERROR_NULLWSLIMIT:
|
|
PErr := @RsErrNullWsLimit;
|
|
PCRE_ERROR_BADNEWLINE:
|
|
PErr := @RsErrBadNewLine;
|
|
JCL_PCRE_ERROR_STUDYFAILED:
|
|
PErr := @RsErrStudyFailed;
|
|
JCL_PCRE_ERROR_CALLOUTERROR:
|
|
PErr := @RsErrCalloutError;
|
|
else
|
|
PErr := @RsErrUnknownError;
|
|
end;
|
|
|
|
raise EPCREError.CreateRes(PErr, Value);
|
|
end;
|
|
|
|
//=== { TJclRegEx } ===========================================================
|
|
|
|
destructor TJclRegEx.Destroy;
|
|
begin
|
|
if Assigned(FCode) then
|
|
CallPCREFree(FCode);
|
|
if Assigned(FExtra) then
|
|
CallPCREFree(FExtra);
|
|
if Assigned(FVector) then
|
|
FreeMem(FVector);
|
|
if Assigned(FChangedCaptures) then
|
|
FChangedCaptures.Free;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TJclRegEx.Compile(const Pattern: string; Study: Boolean;
|
|
UserLocale: Boolean = False): Boolean;
|
|
var
|
|
ErrMsgPtr: PAnsiChar;
|
|
Tables: PAnsiChar;
|
|
begin
|
|
if UserLocale then
|
|
begin
|
|
InitializeLocaleSupport;
|
|
Tables := GTables;
|
|
end
|
|
else
|
|
Tables := nil;
|
|
|
|
FPattern := Pattern;
|
|
if FPattern = '' then
|
|
raise EPCREError.CreateRes(@RsErrNull, PCRE_ERROR_NULL);
|
|
|
|
if Assigned(FCode) then
|
|
begin
|
|
CallPCREFree(FCode);
|
|
FCode := nil;
|
|
end;
|
|
FCode := pcre_compile2(PAnsiChar(EncodeString(FPattern, roUTF8 in Options)), GetAPIOptions(False),
|
|
@FErrorCode, @ErrMsgPtr, @FErrorOffset, Tables);
|
|
Inc(FErrorOffset);
|
|
FErrorMessage := string(AnsiString(ErrMsgPtr));
|
|
Result := Assigned(FCode);
|
|
if Result then
|
|
begin
|
|
if Study then
|
|
begin
|
|
if Assigned(FExtra) then CallPCREFree(FExtra);
|
|
FExtra := pcre_study(FCode, 0, @ErrMsgPtr);
|
|
Result := Assigned(FExtra) or (not Assigned(ErrMsgPtr));
|
|
if not Result then
|
|
begin
|
|
FErrorCode := JCL_PCRE_ERROR_STUDYFAILED;
|
|
FErrorMessage := string(AnsiString(ErrMsgPtr));
|
|
end;
|
|
end;
|
|
|
|
if FDfaMode then
|
|
FVectorSize := FCaptureCount
|
|
else
|
|
begin
|
|
PCRECheck(pcre_fullinfo(FCode, FExtra, PCRE_INFO_CAPTURECOUNT, @FCaptureCount));
|
|
FVectorSize := (FCaptureCount + 1) * 3;
|
|
end;
|
|
ReAllocMem(FVector, FVectorSize * SizeOf(FVector[0]));
|
|
end;
|
|
end;
|
|
|
|
function TJclRegEx.GetAPIOptions(RunTime: Boolean): Integer;
|
|
const
|
|
{ roIgnoreCase, roMultiLine, roDotAll, roExtended,
|
|
roAnchored, roDollarEndOnly, roExtra, roNotBOL, roNotEOL, roUnGreedy,
|
|
roNotEmpty, roUTF8, roNoAutoCapture, roNoUTF8Check, roAutoCallout,
|
|
roPartial, roDfaShortest, roDfaRestart, roDfaFirstLine, roDupNames,
|
|
roNewLineCR, roNewLineLF, roNewLineCRLF, roNewLineAny }
|
|
cDesignOptions: array [TJclRegExOption] of Integer =
|
|
(PCRE_CASELESS, PCRE_MULTILINE, PCRE_DOTALL, PCRE_EXTENDED, PCRE_ANCHORED,
|
|
PCRE_DOLLAR_ENDONLY, PCRE_EXTRA, 0, 0, PCRE_UNGREEDY, 0, PCRE_UTF8,
|
|
PCRE_NO_AUTO_CAPTURE, PCRE_NO_UTF8_CHECK, PCRE_AUTO_CALLOUT, 0, 0, 0, 0,
|
|
PCRE_DUPNAMES, PCRE_NEWLINE_CR, PCRE_NEWLINE_LF, PCRE_NEWLINE_CRLF,
|
|
PCRE_NEWLINE_ANY, PCRE_BSR_ANYCRLF, PCRE_BSR_UNICODE,
|
|
PCRE_JAVASCRIPT_COMPAT, PCRE_NO_START_OPTIMIZE);
|
|
cRunOptions: array [TJclRegExOption] of Integer =
|
|
(0, 0, 0, 0, 0, 0, 0, PCRE_NOTBOL, PCRE_NOTEOL, 0, PCRE_NOTEMPTY, 0, 0,
|
|
PCRE_NO_UTF8_CHECK, 0, PCRE_PARTIAL, 0, 0, 0, 0, PCRE_NEWLINE_CR,
|
|
PCRE_NEWLINE_LF, PCRE_NEWLINE_CRLF, PCRE_NEWLINE_ANY, PCRE_BSR_ANYCRLF,
|
|
PCRE_BSR_UNICODE, PCRE_JAVASCRIPT_COMPAT, PCRE_NO_START_OPTIMIZE);
|
|
var
|
|
I: TJclRegExOption;
|
|
SUPPORT_UTF8: Integer;
|
|
begin
|
|
PCRECheck(pcre_config(PCRE_CONFIG_UTF8, @SUPPORT_UTF8));
|
|
if (roUTF8 in Options) and (SUPPORT_UTF8 = 0) then
|
|
raise EPCREError.CreateRes(@RsErrNoUTF8Support, 0);
|
|
|
|
Result := 0;
|
|
if RunTime then
|
|
begin
|
|
for I := Low(TJclRegExOption) to High(TJclRegExOption) do
|
|
if I in Options then
|
|
Result := Result or cRunOptions[I];
|
|
end
|
|
else
|
|
begin
|
|
for I := Low(TJclRegExOption) to High(TJclRegExOption) do
|
|
if I in Options then
|
|
Result := Result or cDesignOptions[I];
|
|
end;
|
|
end;
|
|
|
|
function TJclRegEx.GetResult: string;
|
|
var
|
|
Index, CaptureIndex: Integer;
|
|
Pos: Integer;
|
|
Range: TJclCaptureRange;
|
|
begin
|
|
if Assigned(FChangedCaptures) and (FChangedCaptures.Count > 0) then
|
|
begin
|
|
Pos := 1;
|
|
Result := '';
|
|
for Index := 0 to FChangedCaptures.Count - 1 do
|
|
begin
|
|
CaptureIndex := SizeInt(FChangedCaptures[Index]);
|
|
Range := GetCaptureRange(CaptureIndex);
|
|
|
|
Result := Result +
|
|
Copy(FSubject, Pos, Range.FirstPos - Pos) +
|
|
FResultValues[CaptureIndex];
|
|
|
|
Pos := Range.LastPos + 1;
|
|
end;
|
|
if Pos <= Length(FSubject) then
|
|
Result := Result + Copy(FSubject, Pos, Length(FSubject) - Pos + 1);
|
|
end
|
|
else
|
|
Result := FSubject;
|
|
end;
|
|
|
|
function TJclRegEx.GetCapture(Index: Integer): string;
|
|
var
|
|
FromPos, ToPos: SizeInt;
|
|
begin
|
|
if (Index < 0) or (Index >= FCaptureCount) then
|
|
PCRECheck(PCRE_ERROR_NOSUBSTRING)
|
|
else
|
|
begin
|
|
if FViewChanges and (FChangedCaptures.IndexOf(Pointer(SizeInt(Index))) >= 0) then
|
|
begin
|
|
Result := FResultValues[Index];
|
|
Exit;
|
|
end;
|
|
|
|
Index := Index * 2;
|
|
FromPos := TranslateIndex(FSubject, roUTF8 in Options, FVector^[Index] + 1);
|
|
ToPos := TranslateIndex(FSubject, roUTF8 in Options, FVector^[Index + 1] + 1) - 1;
|
|
Result := Copy(FSubject, FromPos, ToPos - FromPos + 1);
|
|
end;
|
|
end;
|
|
|
|
procedure TJclRegEx.SetCapture(Index: Integer; const Value: string);
|
|
begin
|
|
if (Index < 0) or (Index >= FCaptureCount) then
|
|
PCRECheck(PCRE_ERROR_NOSUBSTRING)
|
|
else
|
|
begin
|
|
if (not Assigned(FChangedCaptures)) or (FChangedCaptures.Count = 0) then
|
|
begin
|
|
if not Assigned(FChangedCaptures) then
|
|
FChangedCaptures := TList.Create;
|
|
|
|
// Always resize to the max length to avoid repeated allocations.
|
|
FChangedCaptures.Capacity := FCaptureCount;
|
|
SetLength(FResultValues, FCaptureCount);
|
|
end;
|
|
|
|
if FChangedCaptures.IndexOf(Pointer(SizeInt(Index))) < 0 then
|
|
FChangedCaptures.Add(Pointer(SizeInt(Index)));
|
|
FResultValues[Index] := Value;
|
|
end;
|
|
end;
|
|
|
|
function TJclRegEx.GetCaptureRange(Index: Integer): TJclCaptureRange;
|
|
begin
|
|
if (Index < 0) or (Index >= FCaptureCount) then
|
|
PCRECheck(PCRE_ERROR_NOSUBSTRING)
|
|
else
|
|
begin
|
|
Index := Index * 2;
|
|
Result.FirstPos := TranslateIndex(FSubject, roUTF8 in Options, FVector^[Index] + 1);
|
|
Result.LastPos := TranslateIndex(FSubject, roUTF8 in Options, FVector^[Index + 1] + 1) - 1;
|
|
end;
|
|
end;
|
|
|
|
function TJclRegEx.GetNamedCapture(const Name: string): string;
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
Index := pcre_get_stringnumber(FCode, PAnsiChar(EncodeString(Name, roUTF8 in Options)));
|
|
PCRECheck(Index);
|
|
|
|
Result := GetCapture(Index);
|
|
end;
|
|
|
|
procedure TJclRegEx.SetNamedCapture(const Name, Value: string);
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
Index := pcre_get_stringnumber(FCode, PAnsiChar(EncodeString(Name, roUTF8 in Options)));
|
|
PCRECheck(Index);
|
|
|
|
SetCapture(Index, Value);
|
|
end;
|
|
|
|
function TJclRegEx.GetCaptureNameCount: Integer;
|
|
begin
|
|
PCRECheck(pcre_fullinfo(FCode, FExtra, PCRE_INFO_NAMECOUNT, @Result));
|
|
end;
|
|
|
|
function TJclRegEx.GetCaptureName(Index: Integer): string;
|
|
var
|
|
NameTable: PAnsiChar;
|
|
EntrySize: Integer;
|
|
begin
|
|
PCRECheck(pcre_fullinfo(FCode, FExtra, PCRE_INFO_NAMETABLE, @NameTable));
|
|
PCRECheck(pcre_fullinfo(FCode, FExtra, PCRE_INFO_NAMEENTRYSIZE, @EntrySize));
|
|
|
|
NameTable := NameTable + EntrySize * Index + 2;
|
|
Result := DecodeString(AnsiString(NameTable), roUTF8 in Options);
|
|
end;
|
|
|
|
function TJclRegEx.CalloutHandler(var CalloutBlock: pcre_callout_block): Integer;
|
|
begin
|
|
try
|
|
Result := JCL_PCRE_CALLOUT_NOERROR;
|
|
if Assigned(FOnCallout) then
|
|
begin
|
|
with CalloutBlock do
|
|
begin
|
|
FCaptureCount := capture_top;
|
|
FOnCallout(Self, callout_number, start_match + 1, current_position + 1,
|
|
capture_last, pattern_position + 1, next_item_length, Result);
|
|
end;
|
|
end;
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
FErrorMessage := E.Message;
|
|
Result := JCL_PCRE_ERROR_CALLOUTERROR;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJclRegEx.Match(const Subject: string; StartOffset: Cardinal = 1): Boolean;
|
|
var
|
|
LocalExtra: real_pcre_extra;
|
|
Extra: Pointer;
|
|
WorkSpace: array [0 .. 19] of Integer;
|
|
ExecRslt: Integer;
|
|
EncodedSubject: AnsiString;
|
|
begin
|
|
if Assigned(FOnCallout) then
|
|
begin
|
|
if Assigned(FExtra) then
|
|
begin
|
|
LocalExtra.flags := PCRE_EXTRA_STUDY_DATA or PCRE_EXTRA_CALLOUT_DATA;
|
|
LocalExtra.study_data := FExtra;
|
|
end
|
|
else
|
|
LocalExtra.flags := PCRE_EXTRA_CALLOUT_DATA;
|
|
LocalExtra.callout_data := Self;
|
|
Extra := @LocalExtra;
|
|
SetPCRECalloutCallback(JclPCRECallout);
|
|
end
|
|
else
|
|
begin
|
|
Extra := FExtra;
|
|
SetPCRECalloutCallback(nil);
|
|
end;
|
|
|
|
FSubject := Subject;
|
|
if Assigned(FChangedCaptures) then
|
|
FChangedCaptures.Clear;
|
|
EncodedSubject := EncodeString(FSubject, roUTF8 in Options);
|
|
|
|
// convert index
|
|
if roUTF8 in Options then
|
|
StartOffset := Length(EncodeString(Copy(FSubject, 1, StartOffset - 1), True)) + 1;
|
|
|
|
if FDfaMode then
|
|
begin
|
|
ExecRslt := pcre_dfa_exec(FCode, Extra, PAnsiChar(EncodedSubject), Length(EncodedSubject),
|
|
StartOffset - 1, GetAPIOptions(True), PInteger(FVector), FVectorSize, @Workspace, 20);
|
|
end
|
|
else
|
|
begin
|
|
ExecRslt := pcre_exec(FCode, Extra, PAnsiChar(EncodedSubject), Length(EncodedSubject),
|
|
StartOffset - 1, GetAPIOptions(True), PInteger(FVector), FVectorSize);
|
|
end;
|
|
Result := ExecRslt >= 0;
|
|
if Result then
|
|
begin
|
|
FCaptureCount := ExecRslt;
|
|
FErrorCode := 0;
|
|
end
|
|
else
|
|
begin
|
|
FErrorCode := ExecRslt;
|
|
if FErrorCode <> PCRE_ERROR_NOMATCH then
|
|
PCRECheck(FErrorCode);
|
|
end;
|
|
end;
|
|
|
|
function TJclRegEx.IndexOfName(const Name: string): Integer;
|
|
begin
|
|
Result := pcre_get_stringnumber(FCode, PAnsiChar(EncodeString(Name, roUTF8 in Options)));
|
|
end;
|
|
|
|
function TJclRegEx.IsNameValid(const Name: string): Boolean;
|
|
begin
|
|
Result := pcre_get_stringnumber(FCode, PAnsiChar(EncodeString(Name, roUTF8 in Options))) >= 0;
|
|
end;
|
|
|
|
procedure InitializeLocaleSupport;
|
|
begin
|
|
if not Assigned(GTables) then
|
|
GTables := pcre_maketables;
|
|
end;
|
|
|
|
procedure TerminateLocaleSupport;
|
|
begin
|
|
if Assigned(GTables) then
|
|
begin
|
|
CallPCREFree(GTables);
|
|
GTables := nil;
|
|
end;
|
|
end;
|
|
|
|
// TODO: Better/specific error messages, show index when available.
|
|
function StrReplaceRegEx(const Subject, Pattern: string; Args: array of const): string;
|
|
|
|
function ArgToString(Index: Integer): string;
|
|
begin
|
|
// TODO: Any other type?
|
|
case TVarRec(Args[Index]).VType of
|
|
vtPChar:
|
|
Result := string(AnsiString(TVarRec(Args[Index]).VPChar));
|
|
vtPWideChar:
|
|
Result := string(WideString(TVarRec(Args[Index]).VPWideChar));
|
|
vtString:
|
|
Result := string(TVarRec(Args[Index]).VString^);
|
|
vtAnsiString:
|
|
Result := string(AnsiString(TVarRec(Args[Index]).VAnsiString));
|
|
vtWideString:
|
|
Result := string(WideString(TVarRec(Args[Index]).VWideString));
|
|
{$IFDEF SUPPORTS_UNICODE_STRING}
|
|
vtUnicodeString:
|
|
Result := string(UnicodeString(TVarRec(Args[Index]).VUnicodeString));
|
|
{$ENDIF SUPPORTS_UNICODE_STRING}
|
|
vtChar:
|
|
Result := string(AnsiString(TVarRec(Args[Index]).VChar));
|
|
vtWideChar:
|
|
Result := string(WideString(TVarRec(Args[Index]).VWideChar));
|
|
else
|
|
raise EConvertError.Create(SInvalidFormat);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Re: TJclRegEx;
|
|
Index, ArgIndex: Integer;
|
|
Value: string;
|
|
begin
|
|
if Odd(Length(Args)) then
|
|
raise EConvertError.Create(SArgumentMissing)
|
|
else
|
|
begin
|
|
Re := TJclRegEx.Create;
|
|
try
|
|
if Re.Compile(Pattern, False) and Re.Match(Subject) then
|
|
begin
|
|
for Index := 0 to Length(Args) div 2 - 1 do
|
|
begin
|
|
ArgIndex := Index * 2;
|
|
Value := ArgToString(ArgIndex + 1);
|
|
|
|
if TVarRec(Args[ArgIndex]).VType = vtInteger then
|
|
Re.Captures[TVarRec(Args[ArgIndex]).VInteger] := Value
|
|
else
|
|
Re.NamedCaptures[ArgToString(ArgIndex)] := Value;
|
|
end;
|
|
|
|
Result := Re.Result;
|
|
end
|
|
else
|
|
raise EConvertError.Create(SInvalidFormat);
|
|
finally
|
|
Re.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//=== { EPCREError } =========================================================
|
|
|
|
constructor EPCREError.CreateRes(ResStringRec: PResStringRec; ErrorCode: Integer);
|
|
begin
|
|
FErrorCode := ErrorCode;
|
|
inherited CreateRes(ResStringRec);
|
|
end;
|
|
|
|
procedure LibNotLoadedHandler; {$IFDEF PCRE_EXPORT_CDECL} cdecl; {$ENDIF PCRE_EXPORT_CDECL}
|
|
begin
|
|
raise EPCREError.CreateRes(@RsErrLibNotLoaded, 0);
|
|
end;
|
|
|
|
initialization
|
|
pcre.LibNotLoadedHandler := LibNotLoadedHandler;
|
|
if LoadPCRE then
|
|
begin
|
|
SetPCREMallocCallback(JclPCREGetMem);
|
|
SetPCREFreeCallback(JclPCREFreeMem);
|
|
end;
|
|
{$IFDEF UNITVERSIONING}
|
|
RegisterUnitVersion(HInstance, UnitVersioning);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
finalization
|
|
TerminateLocaleSupport;
|
|
{$IFDEF UNITVERSIONING}
|
|
UnregisterUnitVersion(HInstance);
|
|
{$ENDIF UNITVERSIONING}
|
|
UnloadPCRE;
|
|
|
|
end.
|
|
|