Работа со строками
FastStrings.pas
Code: |
//================================================== //All code herein is copyrighted by //Peter Morris //----- //Do not alter / remove this copyright notice //Email me at : Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра. // //The homepage for this library is https://www.droopyeyes.com // // CURRENT VERSION V3.2 // //(Check out www.HowToDoThings.com for Delphi articles !) //(Check out www.stuckindoors.com if you need a free events page on your site !) //==================================================
unit FastStrings;
interface
uses {$IFNDEF LINUX} Windows, {$ENDIF} SysUtils;
//This TYPE declaration will become apparent later type TBMJumpTable = array[0..255] of Integer; TFastPosProc = function (const aSource, aFind: Pointer; const aSourceLen, aFindLen: Integer; var JumpTable: TBMJumpTable): Pointer; TFastPosIndexProc = function (const aSourceString, aFindString: string; const aSourceLen, aFindLen, StartPos: Integer; var JumpTable: TBMJumpTable): Integer; TFastTagReplaceProc = procedure (var Tag: string; const UserData: Integer);
//Boyer-Moore routines procedure MakeBMTable(Buffer: PChar; BufferLen: Integer; var JumpTable: TBMJumpTable); procedure MakeBMTableNoCase(Buffer: PChar; BufferLen: Integer; var JumpTable: TBMJumpTable); function BMPos(const aSource, aFind: Pointer; const aSourceLen, aFindLen: Integer; var JumpTable: TBMJumpTable): Pointer; function BMPosNoCase(const aSource, aFind: Pointer; const aSourceLen, aFindLen: Integer; var JumpTable: TBMJumpTable): Pointer;
function FastAnsiReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string; procedure FastCharMove(const Source; var Dest; Count : Integer); function FastCharPos(const aSource : string; const C: Char; StartPos : Integer): Integer; function FastCharPosNoCase(const aSource : string; C: Char; StartPos : Integer): Integer; function FastPos(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; function FastPosNoCase(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; function FastPosBack(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; function FastPosBackNoCase(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; function FastReplace(const aSourceString : string; const aFindString, aReplaceString : string; CaseSensitive : Boolean = False) : string; function FastTagReplace(const SourceString, TagStart, TagEnd: string; FastTagReplaceProc: TFastTagReplaceProc; const UserData: Integer): string; function SmartPos(const SearchStr,SourceStr : string; const CaseSensitive : Boolean = TRUE; const StartPos : Integer = 1; const ForwardSearch : Boolean = TRUE) : Integer;
implementation
const cDeltaSize = 1.5;
var GUpcaseTable : array[0..255] of char; GUpcaseLUT: Pointer;
//MakeBMJumpTable takes a FindString and makes a JumpTable procedure MakeBMTable(Buffer: PChar; BufferLen: Integer; var JumpTable: TBMJumpTable); begin if BufferLen = 0thenraise Exception.Create('BufferLen is 0'); asm push EDI push ESI mov EDI, JumpTable mov EAX, BufferLen mov ECX, $100 REPNE STOSD mov ECX, BufferLen mov EDI, JumpTable mov ESI, Buffer dec ECX xor EAX, EAX @@loop: mov AL, [ESI] lea ESI, ESI + 1 mov [EDI + EAX * 4], ECX dec ECX jg @@loop
pop ESI pop EDI end; end;
procedure MakeBMTableNoCase(Buffer: PChar; BufferLen: Integer; var JumpTable: TBMJumpTable); begin if BufferLen = 0thenraise Exception.Create('BufferLen is 0'); asm push EDI push ESI
mov EDI, JumpTable mov EAX, BufferLen mov ECX, $100 REPNE STOSD
mov EDX, GUpcaseLUT mov ECX, BufferLen mov EDI, JumpTable mov ESI, Buffer dec ECX xor EAX, EAX @@loop: mov AL, [ESI] lea ESI, ESI + 1 mov AL, [EDX + EAX] mov [EDI + EAX * 4], ECX dec ECX jg @@loop pop ESI pop EDI end; end;
function BMPos(const aSource, aFind: Pointer; const aSourceLen, aFindLen: Integer; var JumpTable: TBMJumpTable): Pointer; var LastPos: Pointer; begin LastPos := Pointer(Integer(aSource) + aSourceLen - 1); asm push ESI push EDI push EBX
mov EAX, aFindLen mov ESI, aSource lea ESI, ESI + EAX - 1 std mov EBX, JumpTable
@@comparetext: cmp ESI, LastPos jg @@NotFound mov EAX, aFindLen mov EDI, aFind mov ECX, EAX push ESI //Remember where we are lea EDI, EDI + EAX - 1 xor EAX, EAX @@CompareNext: mov al, [ESI] cmp al, [EDI] jne @@LookAhead lea ESI, ESI - 1 lea EDI, EDI - 1 dec ECX jz @@Found jmp @@CompareNext
@@LookAhead: //Look up the char in our Jump Table pop ESI mov al, [ESI] mov EAX, [EBX + EAX * 4] lea ESI, ESI + EAX jmp @@CompareText
@@NotFound: mov Result, 0 jmp @@TheEnd @@Found: pop EDI //We are just popping, we don't need the value inc ESI mov Result, ESI @@TheEnd: cld pop EBX pop EDI pop ESI end; end;
function BMPosNoCase(const aSource, aFind: Pointer; const aSourceLen, aFindLen: Integer; var JumpTable: TBMJumpTable): Pointer; var LastPos: Pointer; begin LastPos := Pointer(Integer(aSource) + aSourceLen - 1); asm push ESI push EDI push EBX
mov EAX, aFindLen mov ESI, aSource lea ESI, ESI + EAX - 1 std mov EDX, GUpcaseLUT
@@comparetext: cmp ESI, LastPos jg @@NotFound mov EAX, aFindLen mov EDI, aFind push ESI //Remember where we are mov ECX, EAX lea EDI, EDI + EAX - 1 xor EAX, EAX @@CompareNext: mov al, [ESI] mov bl, [EDX + EAX] mov al, [EDI] cmp bl, [EDX + EAX] jne @@LookAhead lea ESI, ESI - 1 lea EDI, EDI - 1 dec ECX jz @@Found jmp @@CompareNext
@@LookAhead: //Look up the char in our Jump Table pop ESI mov EBX, JumpTable mov al, [ESI] mov al, [EDX + EAX] mov EAX, [EBX + EAX * 4] lea ESI, ESI + EAX jmp @@CompareText
@@NotFound: mov Result, 0 jmp @@TheEnd @@Found: pop EDI //We are just popping, we don't need the value inc ESI mov Result, ESI @@TheEnd: cld pop EBX pop EDI pop ESI end; end;
//NOTE : FastCharPos and FastCharPosNoCase do not require you to pass the length // of the string, this was only done in FastPos and FastPosNoCase because // they are used by FastReplace many times over, thus saving a LENGTH() // operation each time. I can't see you using these two routines for the // same purposes so I didn't do that this time ! function FastCharPos(const aSource : string; const C: Char; StartPos : Integer) : Integer; var L : Integer; begin //If this assert failed, it is because you passed 0 for StartPos, lowest value is 1 !! Assert(StartPos > 0);
Result := 0; L := Length(aSource); if L = 0then exit; if StartPos > L then exit; Dec(StartPos); asm PUSH EDI //Preserve this register
mov EDI, aSource //Point EDI at aSource add EDI, StartPos mov ECX, L //Make a note of how many chars to search through sub ECX, StartPos mov AL, C //and which char we want @Loop: cmp Al, [EDI] //compare it against the SourceString jz @Found inc EDI dec ECX jnz @Loop jmp @NotFound @Found: sub EDI, aSource //EDI has been incremented, so EDI-OrigAdress = Char pos ! inc EDI mov Result, EDI @NotFound:
POP EDI end; end;
function FastCharPosNoCase(const aSource : string; C: Char; StartPos : Integer) : Integer; var L : Integer; begin Result := 0; L := Length(aSource); if L = 0then exit; if StartPos > L then exit; Dec(StartPos); if StartPos < 0then StartPos := 0;
asm PUSH EDI //Preserve this register PUSH EBX mov EDX, GUpcaseLUT
mov EDI, aSource //Point EDI at aSource add EDI, StartPos mov ECX, L //Make a note of how many chars to search through sub ECX, StartPos
xor EBX, EBX mov BL, C mov AL, [EDX+EBX] @Loop: mov BL, [EDI] inc EDI cmp Al, [EDX+EBX] jz @Found dec ECX jnz @Loop jmp @NotFound @Found: sub EDI, aSource //EDI has been incremented, so EDI-OrigAdress = Char pos ! mov Result, EDI @NotFound:
POP EBX POP EDI end; end;
//The first thing to note here is that I am passing the SourceLength and FindLength //As neither Source or Find will alter at any point during FastReplace there is //no need to call the LENGTH subroutine each time ! function FastPos(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; var JumpTable: TBMJumpTable; begin //If this assert failed, it is because you passed 0 for StartPos, lowest value is 1 !! Assert(StartPos > 0); if aFindLen < 1thenbegin Result := 0; exit; end; if aFindLen > aSourceLen thenbegin Result := 0; exit; end;
MakeBMTable(PChar(aFindString), aFindLen, JumpTable); Result := Integer(BMPos(PChar(aSourceString) + (StartPos - 1), PChar(aFindString),aSourceLen - (StartPos-1), aFindLen, JumpTable)); if Result > 0then Result := Result - Integer(@aSourceString[1]) +1; end;
function FastPosNoCase(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; var JumpTable: TBMJumpTable; begin //If this assert failed, it is because you passed 0 for StartPos, lowest value is 1 !! Assert(StartPos > 0); if aFindLen < 1thenbegin Result := 0; exit; end; if aFindLen > aSourceLen thenbegin Result := 0; exit; end;
MakeBMTableNoCase(PChar(AFindString), aFindLen, JumpTable); Result := Integer(BMPosNoCase(PChar(aSourceString) + (StartPos - 1), PChar(aFindString),aSourceLen - (StartPos-1), aFindLen, JumpTable)); if Result > 0then Result := Result - Integer(@aSourceString[1]) +1; end;
function FastPosBack(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; var SourceLen : Integer; begin if aFindLen < 1thenbegin Result := 0; exit; end; if aFindLen > aSourceLen thenbegin Result := 0; exit; end;
if (StartPos = 0) or (StartPos + aFindLen > aSourceLen) then SourceLen := aSourceLen - (aFindLen-1) else SourceLen := StartPos;
asm push ESI push EDI push EBX
mov EDI, aSourceString add EDI, SourceLen Dec EDI
mov ESI, aFindString mov ECX, SourceLen Mov Al, [ESI]
@ScaSB: cmp Al, [EDI] jne @NextChar
@CompareStrings: mov EBX, aFindLen dec EBX jz @FullMatch
@CompareNext: mov Ah, [ESI+EBX] cmp Ah, [EDI+EBX] Jnz @NextChar
@Matches: Dec EBX Jnz @CompareNext
@FullMatch: mov EAX, EDI sub EAX, aSourceString inc EAX mov Result, EAX jmp @TheEnd @NextChar: dec EDI dec ECX jnz @ScaSB
mov Result,0
@TheEnd: pop EBX pop EDI pop ESI end; end;
function FastPosBackNoCase(const aSourceString, aFindString : string; const aSourceLen, aFindLen, StartPos : Integer) : Integer; var SourceLen : Integer; begin if aFindLen < 1thenbegin Result := 0; exit; end; if aFindLen > aSourceLen thenbegin Result := 0; exit; end;
if (StartPos = 0) or (StartPos + aFindLen > aSourceLen) then SourceLen := aSourceLen - (aFindLen-1) else SourceLen := StartPos;
asm push ESI push EDI push EBX
mov EDI, aSourceString add EDI, SourceLen Dec EDI
mov ESI, aFindString mov ECX, SourceLen
mov EDX, GUpcaseLUT xor EBX, EBX
mov Bl, [ESI] mov Al, [EDX+EBX]
@ScaSB: mov Bl, [EDI] cmp Al, [EDX+EBX] jne @NextChar
@CompareStrings: PUSH ECX mov ECX, aFindLen dec ECX jz @FullMatch
@CompareNext: mov Bl, [ESI+ECX] mov Ah, [EDX+EBX] mov Bl, [EDI+ECX] cmp Ah, [EDX+EBX] Jz @Matches
//Go back to findind the first char POP ECX Jmp @NextChar
@Matches: Dec ECX Jnz @CompareNext
@FullMatch: POP ECX
mov EAX, EDI sub EAX, aSourceString inc EAX mov Result, EAX jmp @TheEnd @NextChar: dec EDI dec ECX jnz @ScaSB
mov Result,0
@TheEnd: pop EBX pop EDI pop ESI end; end;
//My move is not as fast as MOVE when source and destination are both //DWord aligned, but certainly faster when they are not. //As we are moving characters in a string, it is not very likely at all that //both source and destination are DWord aligned, so moving bytes avoids the //cycle penality of reading/writing DWords across physical boundaries procedure FastCharMove(const Source; var Dest; Count : Integer); asm //Note: When this function is called, delphi passes the parameters as follows //ECX = Count //EAX = Const Source //EDX = Var Dest
//If no bytes to copy, just quit altogether, no point pushing registers cmp ECX,0 Je @JustQuit
//Preserve the critical delphi registers push ESI push EDI
//move Source into ESI (generally the SOURCE register) //move Dest into EDI (generally the DEST register for string commands) //This may not actually be neccessary, as I am not using MOVsb etc //I may be able just to use EAX and EDX, there may be a penalty for //not using ESI, EDI but I doubt it, this is another thing worth trying ! mov ESI, EAX mov EDI, EDX
//The following loop is the same as repNZ MovSB, but oddly quicker ! @Loop: //Get the source byte Mov AL, [ESI] //Point to next byte Inc ESI //Put it into the Dest mov [EDI], AL //Point dest to next position Inc EDI //Dec ECX to note how many we have left to copy Dec ECX //If ECX <> 0 then loop Jnz @Loop
//Another optimization note. //Many people like to do this
//Mov AL, [ESI] //Mov [EDI], Al //Inc ESI //Inc ESI
//There is a hidden problem here, I wont go into too much detail, but //the pentium can continue processing instructions while it is still //working out the result of INC ESI or INC EDI //(almost like a multithreaded CPU) //if, however, you go to use them while they are still being calculated //the processor will stop until they are calculated (a penalty) //Therefore I alter ESI and EDI as far in advance as possible of using them
//Pop the critical Delphi registers that we have altered pop EDI pop ESI @JustQuit: end;
function FastAnsiReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string; var BufferSize, BytesWritten: Integer; SourceString, FindString: string; ResultPChar: PChar; FindPChar, ReplacePChar: PChar; SPChar, SourceStringPChar, PrevSourceStringPChar: PChar; FinalSourceMarker: PChar; SourceLength, FindLength, ReplaceLength, CopySize: Integer; FinalSourcePosition: Integer; begin //Set up string lengths BytesWritten := 0; SourceLength := Length(S); FindLength := Length(OldPattern); ReplaceLength := Length(NewPattern); //Quick exit if (SourceLength = 0) or (FindLength = 0) or (FindLength > SourceLength) then begin Result := S; Exit; end;
//Set up the source string and find string if rfIgnoreCase in Flags then begin SourceString := AnsiUpperCase(S); FindString := AnsiUpperCase(OldPattern); endelse begin SourceString := S; FindString := OldPattern; end;
//Set up the result buffer size and pointers try if ReplaceLength <= FindLength then //Result cannot be larger, only same size or smaller BufferSize := SourceLength else //Assume a source string made entired of the sub string BufferSize := (SourceLength * ReplaceLength) div FindLength;
//10 times is okay for starters. We don't want to //go allocating much more than we need. if BufferSize > (SourceLength * 10) then BufferSize := SourceLength * 10; except //Oops, integer overflow! Better start with a string //of the same size as the source. BufferSize := SourceLength; end; SetLength(Result, BufferSize); ResultPChar := @Result[1];
//Set up the pointers to S and SourceString SPChar := @S[1]; SourceStringPChar := @SourceString[1]; PrevSourceStringPChar := SourceStringPChar; FinalSourceMarker := @SourceString[SourceLength - (FindLength - 1)];
//Set up the pointer to FindString FindPChar := @FindString[1];
//Set the pointer to ReplaceString if ReplaceLength > 0then ReplacePChar := @NewPattern[1] else ReplacePChar := nil;
//Replace routine repeat //Find the sub string SourceStringPChar := AnsiStrPos(PrevSourceStringPChar, FindPChar); if SourceStringPChar = nilthen Break; //How many characters do we need to copy before //the string occurs CopySize := SourceStringPChar - PrevSourceStringPChar;
//Check we have enough space in our Result buffer if CopySize + ReplaceLength > BufferSize - BytesWritten then begin BufferSize := Trunc((BytesWritten + CopySize + ReplaceLength) * cDeltaSize); SetLength(Result, BufferSize); ResultPChar := @Result[BytesWritten + 1]; end;
//Copy the preceeding characters to our result buffer Move(SPChar^, ResultPChar^, CopySize); Inc(BytesWritten, CopySize); //Advance the copy position of S Inc(SPChar, CopySize + FindLength); //Advance the Result pointer Inc(ResultPChar, CopySize); //Copy the replace string into the Result buffer if Assigned(ReplacePChar) then begin Move(ReplacePChar^, ResultPChar^, ReplaceLength); Inc(ResultPChar, ReplaceLength); Inc(BytesWritten, ReplaceLength); end;
//Fake delete the start of the source string PrevSourceStringPChar := SourceStringPChar + FindLength; until (PrevSourceStringPChar > FinalSourceMarker) or not (rfReplaceAll in Flags);
FinalSourcePosition := Integer(SPChar - @S[1]); CopySize := SourceLength - FinalSourcePosition; SetLength(Result, BytesWritten + CopySize); if CopySize > 0then Move(SPChar^, Result[BytesWritten + 1], CopySize); end;
function FastReplace(const aSourceString : string; const aFindString, aReplaceString : string; CaseSensitive : Boolean = False) : string; var PResult : PChar; PReplace : PChar; PSource : PChar; PFind : PChar; PPosition : PChar; CurrentPos, BytesUsed, lResult, lReplace, lSource, lFind : Integer; Find : TFastPosProc; CopySize : Integer; JumpTable : TBMJumpTable; begin LSource := Length(aSourceString); if LSource = 0thenbegin Result := aSourceString; exit; end; PSource := @aSourceString[1];
LFind := Length(aFindString); if LFind = 0then exit; PFind := @aFindString[1];
LReplace := Length(aReplaceString);
//Here we may get an Integer Overflow, or OutOfMemory, if so, we use a Delta try if LReplace <= LFind then SetLength(Result,lSource) else SetLength(Result, (LSource *LReplace) div LFind); except SetLength(Result,0); end;
LResult := Length(Result); if LResult = 0thenbegin LResult := Trunc((LSource + LReplace) * cDeltaSize); SetLength(Result, LResult); end;
PResult := @Result[1];
if CaseSensitive then begin MakeBMTable(PChar(AFindString), lFind, JumpTable); Find := BMPos; endelse begin MakeBMTableNoCase(PChar(AFindString), lFind, JumpTable); Find := BMPosNoCase; end;
BytesUsed := 0; if LReplace > 0thenbegin PReplace := @aReplaceString[1]; repeat PPosition := Find(PSource,PFind,lSource, lFind, JumpTable); if PPosition = nilthen break;
CopySize := PPosition - PSource; Inc(BytesUsed, CopySize + LReplace);
if BytesUsed >= LResult thenbegin //We have run out of space CurrentPos := Integer(PResult) - Integer(@Result[1]) +1; LResult := Trunc(LResult * cDeltaSize); SetLength(Result,LResult); PResult := @Result[CurrentPos]; end;
FastCharMove(PSource^,PResult^,CopySize); Dec(lSource,CopySize + LFind); Inc(PSource,CopySize + LFind); Inc(PResult,CopySize);
FastCharMove(PReplace^,PResult^,LReplace); Inc(PResult,LReplace);
until lSource < lFind; endelsebegin repeat PPosition := Find(PSource,PFind,lSource, lFind, JumpTable); if PPosition = nilthen break;
CopySize := PPosition - PSource; FastCharMove(PSource^,PResult^,CopySize); Dec(lSource,CopySize + LFind); Inc(PSource,CopySize + LFind); Inc(PResult,CopySize); Inc(BytesUsed, CopySize); until lSource < lFind; end;
SetLength(Result, (PResult+LSource) - @Result[1]); if LSource > 0then FastCharMove(PSource^, Result[BytesUsed + 1], LSource); end;
function FastTagReplace(const SourceString, TagStart, TagEnd: string; FastTagReplaceProc: TFastTagReplaceProc; const UserData: Integer): string; var TagStartPChar: PChar; TagEndPChar: PChar; SourceStringPChar: PChar; TagStartFindPos: PChar; TagEndFindPos: PChar; TagStartLength: Integer; TagEndLength: Integer; DestPChar: PChar; FinalSourceMarkerStart: PChar; FinalSourceMarkerEnd: PChar; BytesWritten: Integer; BufferSize: Integer; CopySize: Integer; ReplaceString: string;
procedure AddBuffer(const Buffer: Pointer; Size: Integer); begin if BytesWritten + Size > BufferSize then begin BufferSize := Trunc(BufferSize * cDeltaSize); if BufferSize <= (BytesWritten + Size) then BufferSize := Trunc((BytesWritten + Size) * cDeltaSize); SetLength(Result, BufferSize); DestPChar := @Result[BytesWritten + 1]; end; Inc(BytesWritten, Size); FastCharMove(Buffer^, DestPChar^, Size); DestPChar := DestPChar + Size; end;
begin Assert(Assigned(@FastTagReplaceProc)); TagStartPChar := PChar(TagStart); TagEndPChar := PChar(TagEnd); if (SourceString = '') or (TagStart = '') or (TagEnd = '') then begin Result := SourceString; Exit; end;
SourceStringPChar := PChar(SourceString); TagStartLength := Length(TagStart); TagEndLength := Length(TagEnd); FinalSourceMarkerEnd := SourceStringPChar + Length(SourceString) - TagEndLength; FinalSourceMarkerStart := FinalSourceMarkerEnd - TagStartLength;
BytesWritten := 0; BufferSize := Length(SourceString); SetLength(Result, BufferSize); DestPChar := @Result[1];
repeat TagStartFindPos := AnsiStrPos(SourceStringPChar, TagStartPChar); if (TagStartFindPos = nil) or (TagStartFindPos > FinalSourceMarkerStart) then Break; TagEndFindPos := AnsiStrPos(TagStartFindPos + TagStartLength, TagEndPChar); if (TagEndFindPos = nil) or (TagEndFindPos > FinalSourceMarkerEnd) then Break; CopySize := TagStartFindPos - SourceStringPChar; AddBuffer(SourceStringPChar, CopySize); CopySize := TagEndFindPos - (TagStartFindPos + TagStartLength); SetLength(ReplaceString, CopySize); if CopySize > 0then Move((TagStartFindPos + TagStartLength)^, ReplaceString[1], CopySize); FastTagReplaceProc(ReplaceString, UserData); if Length(ReplaceString) > 0then AddBuffer(@ReplaceString[1], Length(ReplaceString)); SourceStringPChar := TagEndFindPos + TagEndLength; until SourceStringPChar > FinalSourceMarkerStart; CopySize := PChar(@SourceString[Length(SourceString)]) - (SourceStringPChar - 1); if CopySize > 0then AddBuffer(SourceStringPChar, CopySize); SetLength(Result, BytesWritten); end;
function SmartPos(const SearchStr,SourceStr : string; const CaseSensitive : Boolean = TRUE; const StartPos : Integer = 1; const ForwardSearch : Boolean = TRUE) : Integer; begin // NOTE: When using StartPos, the returned value is absolute! if (CaseSensitive) then if (ForwardSearch) then Result:= FastPos(SourceStr,SearchStr,Length(SourceStr),Length(SearchStr),StartPos) else Result:= FastPosBack(SourceStr,SearchStr,Length(SourceStr),Length(SearchStr),StartPos) else if (ForwardSearch) then Result:= FastPosNoCase(SourceStr,SearchStr,Length(SourceStr),Length(SearchStr),StartPos) else Result:= FastPosBackNoCase(SourceStr,SearchStr,Length(SourceStr),Length(SearchStr),StartPos) end;
var I: Integer; initialization {$IFNDEF LINUX} for I:=0to255do GUpcaseTable[I] := Chr(I); CharUpperBuff(@GUpcaseTable[0], 256); {$ELSE} for I:=0to255do GUpcaseTable[I] := UpCase(Chr(I)); {$ENDIF} GUpcaseLUT := @GUpcaseTable[0]; end.
FastStringFuncs.pas
//================================================== //All code herein is copyrighted by //Peter Morris //----- //Do not alter / remove this copyright notice //Email me at : Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра. // //The homepage for this library is https://www.droopyeyes.com // //(Check out www.HowToDoThings.com for Delphi articles !) //(Check out www.stuckindoors.com if you need a free events page on your site !)
unit FastStringFuncs;
interface
uses {$IFDEF LINUX} QGraphics, {$ELSE} Graphics, {$ENDIF} FastStrings, Sysutils, Classes;
const cHexChars = '0123456789ABCDEF'; cSoundexTable: array[65..122] of Byte = ({A}0, {B}1, {C}2, {D}3, {E}0, {F}1, {G}2, {H}0, {I}0, {J}2, {K}2, {L}4, {M}5, {N}5, {O}0, {P}1, {Q}2, {R}6, {S}2, {T}3, {U}0, {V}1, {W}0, {X}2, {Y}0, {Z}2, 0, 0, 0, 0, 0, 0, {a}0, {b}1, {c}2, {d}3, {e}0, {f}1, {g}2, {h}0, {i}0, {j}2, {k}2, {l}4, {m}5, {n}5, {o}0, {p}1, {q}2, {r}6, {s}2, {t}3, {u}0, {v}1, {w}0, {x}2, {y}0, {z}2);
function Base64Encode(const Source: AnsiString): AnsiString; function Base64Decode(const Source: string): string; function CopyStr(const aSourceString : string; aStart, aLength : Integer) : string; function Decrypt(const S: string; Key: Word): string; function Encrypt(const S: string; Key: Word): string; function ExtractHTML(S : string) : string; function ExtractNonHTML(S : string) : string; function HexToInt(aHex : string) : int64; function LeftStr(const aSourceString : string; Size : Integer) : string; function StringMatches(Value, Pattern : string) : Boolean; function MissingText(Pattern, Source : string; SearchText : string = '?') : string; function RandomFileName(aFilename : string) : string; function RandomStr(aLength : Longint) : string; function ReverseStr(const aSourceString: string): string; function RightStr(const aSourceString : string; Size : Integer) : string; function RGBToColor(aRGB : string) : TColor; function StringCount(const aSourceString, aFindString : string; Const CaseSensitive : Boolean = TRUE) : Integer; function SoundEx(const aSourceString: string): Integer; function UniqueFilename(aFilename : string) : string; function URLToText(aValue : string) : string; function WordAt(Text : string; Position : Integer) : string;
procedure Split(aValue : string; aDelimiter : Char; var Result : TStrings);
implementation const cKey1 = 52845; cKey2 = 22719; Base64_Table : shortstring = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function StripHTMLorNonHTML(const S : string; WantHTML : Boolean) : string; forward;
//Encode to Base64 function Base64Encode(const Source: AnsiString): AnsiString; var NewLength: Integer; begin NewLength := ((2 + Length(Source)) div3) * 4; SetLength( Result, NewLength);
asm Push ESI Push EDI Push EBX Lea EBX, Base64_Table Inc EBX // Move past String Size (ShortString) Mov EDI, Result Mov EDI, [EDI] Mov ESI, Source Mov EDX, [ESI-4] //Length of Input String @WriteFirst2: CMP EDX, 0 JLE @Done MOV AL, [ESI] SHR AL, 2 {$IFDEF VER140}// Changes to BASM in D6 XLATB {$ELSE} XLAT {$ENDIF} MOV [EDI], AL INC EDI MOV AL, [ESI + 1] MOV AH, [ESI] SHR AX, 4 AND AL, 63 {$IFDEF VER140}// Changes to BASM in D6 XLATB {$ELSE} XLAT {$ENDIF} MOV [EDI], AL INC EDI CMP EDX, 1 JNE @Write3 MOV AL, 61// Add == MOV [EDI], AL INC EDI MOV [EDI], AL INC EDI JMP @Done @Write3: MOV AL, [ESI + 2] MOV AH, [ESI + 1] SHR AX, 6 AND AL, 63 {$IFDEF VER140}// Changes to BASM in D6 XLATB {$ELSE} XLAT {$ENDIF} MOV [EDI], AL INC EDI CMP EDX, 2 JNE @Write4 MOV AL, 61// Add = MOV [EDI], AL INC EDI JMP @Done @Write4: MOV AL, [ESI + 2] AND AL, 63 {$IFDEF VER140}// Changes to BASM in D6 XLATB {$ELSE} XLAT {$ENDIF} MOV [EDI], AL INC EDI ADD ESI, 3 SUB EDX, 3 JMP @WriteFirst2 @done: Pop EBX Pop EDI Pop ESI end; end;
//Decode Base64 function Base64Decode(const Source: string): string; var NewLength: Integer; begin { NB: On invalid input this routine will simply skip the bad data, a better solution would probably report the error
ESI -> Source String EDI -> Result String
ECX -> length of Source (number of DWords) EAX -> 32 Bits from Source EDX -> 24 Bits Decoded
BL -> Current number of bytes decoded }
SetLength( Result, (Length(Source) div4) * 3); NewLength := 0; asm Push ESI Push EDI Push EBX
Mov ESI, Source
Mov EDI, Result //Result address Mov EDI, [EDI]
Or ESI,ESI // Nil Strings Jz @Done
Mov ECX, [ESI-4] Shr ECX,2// DWord Count
JeCxZ @Error // Empty String
Cld
jmp @Read4
@Next: Dec ECX Jz @Done
@Read4: lodsd
Xor BL, BL Xor EDX, EDX
Call @DecodeTo6Bits Shl EDX, 6 Shr EAX,8 Call @DecodeTo6Bits Shl EDX, 6 Shr EAX,8 Call @DecodeTo6Bits Shl EDX, 6 Shr EAX,8 Call @DecodeTo6Bits
// Write Word
Or BL, BL JZ @Next // No Data
Dec BL Or BL, BL JZ @Next // Minimum of 2 decode values to translate to 1 byte
Mov EAX, EDX
Cmp BL, 2 JL @WriteByte
Rol EAX, 8
BSWAP EAX
StoSW
Add NewLength, 2
@WriteByte: Cmp BL, 2 JE @Next SHR EAX, 16 StoSB
Inc NewLength jmp @Next
@Error: jmp @Done
@DecodeTo6Bits:
@TestLower: Cmp AL, 'a' Jl @TestCaps Cmp AL, 'z' Jg @Skip Sub AL, 71 Jmp @Finish
@TestCaps: Cmp AL, 'A' Jl @TestEqual Cmp AL, 'Z' Jg @Skip Sub AL, 65 Jmp @Finish
@TestEqual: Cmp AL, '=' Jne @TestNum // Skip byte ret
@TestNum: Cmp AL, '9' Jg @Skip Cmp AL, '0' JL @TestSlash Add AL, 4 Jmp @Finish
@TestSlash: Cmp AL, '/' Jne @TestPlus Mov AL, 63 Jmp @Finish
@TestPlus: Cmp AL, '+' Jne @Skip Mov AL, 62
@Finish: Or DL, AL Inc BL
@Skip: Ret
@Done: Pop EBX Pop EDI Pop ESI
end;
SetLength( Result, NewLength); // Trim off the excess end;
//Encrypt a string function Encrypt(const S: string; Key: Word): string; var I: byte; begin SetLength(result,length(s)); for I := 1to Length(S) do begin Result[I] := char(byte(S[I]) xor (Key shr8)); Key := (byte(Result[I]) + Key) * cKey1 + cKey2; end; end;
//Return only the HTML of a string function ExtractHTML(S : string) : string; begin Result := StripHTMLorNonHTML(S, True); end;
function CopyStr(const aSourceString : string; aStart, aLength : Integer) : string; var L : Integer; begin L := Length(aSourceString); if L=0then Exit; if (aStart < 1) or (aLength < 1) then Exit;
if aStart + (aLength-1) > L then aLength := L - (aStart-1);
if (aStart <1) then exit;
SetLength(Result,aLength); FastCharMove(aSourceString[aStart], Result[1], aLength); end;
//Take all HTML out of a string function ExtractNonHTML(S : string) : string; begin Result := StripHTMLorNonHTML(S,False); end;
//Decrypt a string encoded with Encrypt function Decrypt(const S: string; Key: Word): string; var I: byte; begin SetLength(result,length(s)); for I := 1to Length(S) do begin Result[I] := char(byte(S[I]) xor (Key shr8)); Key := (byte(S[I]) + Key) * cKey1 + cKey2; end; end;
//Convert a text-HEX value (FF0088 for example) to an integer function HexToInt(aHex : string) : int64; var Multiplier : Int64; Position : Byte; Value : Integer; begin Result := 0; Multiplier := 1; Position := Length(aHex); while Position >0dobegin Value := FastCharPosNoCase(cHexChars, aHex[Position], 1)-1; if Value = -1then raise Exception.Create('Invalid hex character ' + aHex[Position]);
Result := Result + (Value * Multiplier); Multiplier := Multiplier * 16; Dec(Position); end; end;
//Get the left X amount of chars function LeftStr(const aSourceString : string; Size : Integer) : string; begin if Size > Length(aSourceString) then Result := aSourceString elsebegin SetLength(Result, Size); Move(aSourceString[1],Result[1],Size); end; end;
//Do strings match with wildcards, eg //StringMatches('The cat sat on the mat', 'The * sat * the *') = True function StringMatches(Value, Pattern : string) : Boolean; var NextPos, Star1, Star2 : Integer; NextPattern : string; begin Star1 := FastCharPos(Pattern,'*',1); if Star1 = 0then Result := (Value = Pattern) else begin Result := (Copy(Value,1,Star1-1) = Copy(Pattern,1,Star1-1)); if Result then begin if Star1 > 1then Value := Copy(Value,Star1,Length(Value)); Pattern := Copy(Pattern,Star1+1,Length(Pattern));
NextPattern := Pattern; Star2 := FastCharPos(NextPattern, '*',1); if Star2 > 0then NextPattern := Copy(NextPattern,1,Star2-1);
//pos(NextPattern,Value); NextPos := FastPos(Value, NextPattern, Length(Value), Length(NextPattern), 1); if (NextPos = 0) andnot (NextPattern = '') then Result := False else begin Value := Copy(Value,NextPos,Length(Value)); if Pattern = ''then Result := True else Result := Result and StringMatches(Value,Pattern); end; end; end; end;
//Missing text will tell you what text is missing, eg //MissingText('the ? sat on the mat','the cat sat on the mat','?') = 'cat' function MissingText(Pattern, Source : string; SearchText : string = '?') : string; var Position : Longint; BeforeText, AfterText : string; BeforePos, AfterPos : Integer; lSearchText, lBeforeText, lAfterText, lSource : Longint; begin Result := ''; Position := Pos(SearchText,Pattern); if Position = 0then exit;
lSearchText := Length(SearchText); lSource := Length(Source); BeforeText := Copy(Pattern,1,Position-1); AfterText := Copy(Pattern,Position+lSearchText,lSource);
lBeforeText := Length(BeforeText); lAfterText := Length(AfterText);
AfterPos := lBeforeText; repeat AfterPos := FastPosNoCase(Source,AfterText,lSource,lAfterText,AfterPos+lSearchText); if AfterPos > 0thenbegin BeforePos := FastPosBackNoCase(Source,BeforeText,AfterPos-1,lBeforeText,AfterPos - (lBeforeText-1)); if (BeforePos > 0) thenbegin Result := Copy(Source,BeforePos + lBeforeText, AfterPos - (BeforePos + lBeforeText)); Break; end; end; until AfterPos = 0; end;
//Generates a random filename but preserves the original path + extension function RandomFilename(aFilename : string) : string; var Path, Filename, Ext : string; begin Result := aFilename; Path := ExtractFilepath(aFilename); Ext := ExtractFileExt(aFilename); Filename := ExtractFilename(aFilename); if Length(Ext) > 0then Filename := Copy(Filename,1,Length(Filename)-Length(Ext)); repeat Result := Path + RandomStr(32) + Ext; untilnot FileExists(Result); end;
//Makes a string of aLength filled with random characters function RandomStr(aLength : Longint) : string; var X : Longint; begin if aLength <= 0then exit; SetLength(Result, aLength); for X:=1to aLength do Result[X] := Chr(Random(26) + 65); end;
function ReverseStr(const aSourceString: string): string; var L : Integer; S, D : Pointer; begin L := Length(aSourceString); SetLength(Result,L); if L = 0then exit;
S := @aSourceString[1]; D := @Result[L];
asm push ESI push EDI
mov ECX, L mov ESI, S mov EDI, D
@Loop: mov Al, [ESI] inc ESI mov [EDI], Al dec EDI dec ECX jnz @Loop
pop EDI pop ESI end; end;
//Returns X amount of chars from the right of a string function RightStr(const aSourceString : string; Size : Integer) : string; begin if Size > Length(aSourceString) then Result := aSourceString elsebegin SetLength(Result, Size); FastCharMove(aSourceString[Length(aSourceString)-(Size-1)],Result[1],Size); end; end;
//Converts a typical HTML RRGGBB color to a TColor function RGBToColor(aRGB : string) : TColor; begin if Length(aRGB) < 6thenraise EConvertError.Create('Not a valid RGB value'); if aRGB[1] = '#'then aRGB := Copy(aRGB,2,Length(aRGB)); if Length(aRGB) <> 6thenraise EConvertError.Create('Not a valid RGB value');
Result := HexToInt(aRGB); asm mov EAX, Result BSwap EAX shr EAX, 8 mov Result, EAX end; end;
//Splits a delimited text line into TStrings (does not account for stuff in quotes but it should) procedure Split(aValue : string; aDelimiter : Char; var Result : TStrings); var X : Integer; S : string; begin if Result = nilthen Result := TStringList.Create; Result.Clear; S := ''; for X:=1to Length(aValue) dobegin if aValue[X] <> aDelimiter then S:=S + aValue[X] elsebegin Result.Add(S); S := ''; end; end; if S <> ''then Result.Add(S); end;
//counts how many times a substring exists within a string //StringCount('XXXXX','XX') would return 2 function StringCount(const aSourceString, aFindString : string; Const CaseSensitive : Boolean = TRUE) : Integer; var Find, Source, NextPos : PChar; LSource, LFind : Integer; Next : TFastPosProc; JumpTable : TBMJumpTable; begin Result := 0; LSource := Length(aSourceString); if LSource = 0then exit;
LFind := Length(aFindString); if LFind = 0then exit;
if CaseSensitive then begin Next := BMPos; MakeBMTable(PChar(aFindString), Length(aFindString), JumpTable); endelse begin Next := BMPosNoCase; MakeBMTableNoCase(PChar(aFindString), Length(aFindString), JumpTable); end;
Source := @aSourceString[1]; Find := @aFindString[1];
repeat NextPos := Next(Source, Find, LSource, LFind, JumpTable); if NextPos <> nilthen begin Dec(LSource, (NextPos - Source) + LFind); Inc(Result); Source := NextPos + LFind; end; until NextPos = nil; end;
function SoundEx(const aSourceString: string): Integer; var CurrentChar: PChar; I, S, LastChar, SoundexGroup: Byte; Multiple: Word; begin if aSourceString = ''then Result := 0 else begin //Store first letter immediately Result := Ord(Upcase(aSourceString[1]));
//Last character found = 0 LastChar := 0; Multiple := 26;
//Point to first character CurrentChar := @aSourceString[1];
for I := 1to Length(aSourceString) do begin Inc(CurrentChar);
S := Ord(CurrentChar^); if (S > 64) and (S < 123) then begin SoundexGroup := cSoundexTable[S]; if (SoundexGroup <> LastChar) and (SoundexGroup > 0) then begin Inc(Result, SoundexGroup * Multiple); if Multiple = 936then Break; {26 * 6 * 6} Multiple := Multiple * 6; LastChar := SoundexGroup; end; end; end; end; end;
//Used by ExtractHTML and ExtractNonHTML function StripHTMLorNonHTML(const S : string; WantHTML : Boolean) : string; var X: Integer; TagCnt: Integer; ResChar: PChar; SrcChar: PChar; begin TagCnt := 0; SetLength(Result, Length(S)); if Length(S) = 0then Exit;
ResChar := @Result[1]; SrcChar := @S[1]; for X:=1to Length(S) do begin case SrcChar^ of '<': begin Inc(TagCnt); if WantHTML and (TagCnt = 1) then begin ResChar^ := '<'; Inc(ResChar); end; end; '>': begin Dec(TagCnt); if WantHTML and (TagCnt = 0) then begin ResChar^ := '>'; Inc(ResChar); end; end; else case WantHTML of False: if TagCnt <= 0then begin ResChar^ := SrcChar^; Inc(ResChar); TagCnt := 0; end; True: if TagCnt >= 1then begin ResChar^ := SrcChar^; Inc(ResChar); endelse if TagCnt < 0then TagCnt := 0; end; end; Inc(SrcChar); end; SetLength(Result, ResChar - PChar(@Result[1])); Result := FastReplace(Result, ' ', ' ', False); Result := FastReplace(Result,'&','&', False); Result := FastReplace(Result,'<','<', False); Result := FastReplace(Result,'>','>', False); Result := FastReplace(Result,'"','"', False); end;
//Generates a UniqueFilename, makes sure the file does not exist before returning a result function UniqueFilename(aFilename : string) : string; var Path, Filename, Ext : string; Index : Integer; begin Result := aFilename; if FileExists(aFilename) thenbegin Path := ExtractFilepath(aFilename); Ext := ExtractFileExt(aFilename); Filename := ExtractFilename(aFilename); if Length(Ext) > 0then Filename := Copy(Filename,1,Length(Filename)-Length(Ext)); Index := 2; repeat Result := Path + Filename + IntToStr(Index) + Ext; Inc(Index); untilnot FileExists(Result); end; end;
//Decodes all that %3c stuff you get in a URL function URLToText(aValue : string) : string; var X : Integer; begin Result := ''; X := 1; while X <= Length(aValue) dobegin if aValue[X] <> '%'then Result := Result + aValue[X] elsebegin Result := Result + Chr( HexToInt( Copy(aValue,X+1,2) ) ); Inc(X,2); end; Inc(X); end; end;
//Returns the whole word at a position function WordAt(Text : string; Position : Integer) : string; var L, X : Integer; begin Result := ''; L := Length(Text);
if (Position > L) or (Position < 1) then Exit; for X:=Position to L dobegin if Upcase(Text[X]) in ['A'..'Z','0'..'9'] then Result := Result + Text[X] else Break; end;
for X:=Position-1downto1dobegin if Upcase(Text[X]) in ['A'..'Z','0'..'9'] then Result := Text[X] + Result else Break; end; end;
end.
|
- Подробности
- Родительская категория: Работа со строками
- Категория: Справочные материалы, общие вопросы
Предположим, что в вашу задачу, как разработчика программного обеспечения, входит создание некоторого специализированного текстового процессора. Не вдаваясь в рассуждения о необходимости создания еще одного приложения подобного рода, мы просто рассмотрим один прием, который придаст вашей разработке весьма ощутимое преимущество по сравнению с аналогами. К примеру, вам необходимо создать некий HTML-редактор. Как и в случае с любым другим приложением такого типа, ваша программа должна будет обладать функциями орфографической проверки текста. Естественно, можно потратить много времени на создание своего собственного шедевра в данной области, но почему бы нам не воспользоваться уже готовыми решениями? В рамках данной статьи я бы хотел поговорить о технологии использования в ваших приложениях механизмов проверки орфографии, входящих в состав всем известного приложения - Microsoft Word с использованием автоматизации (OLE Automation).
OLE Automation
Идея, заложенная в автоматизацию, включает разработку приложений, функциональность которых может быть доступна и другим программам, а также создание приложений, которые "знают", как использовать функциональность, предоставляемую вам другими программными продуктами. Если говорить техническим языком, приложение, которое предоставляет некоторую повторно используемую функциональность, называется сервером автоматизации (automation server) (также часто называемым сервером COM). Приложение же, использующее функциональность, предоставляемую сервером автоматизации, называется клиентом автоматизации (automation client), также часто называемым контроллером автоматизации. Важно подчеркнуть, что сервер автоматизации может не быть "чистым" сервером автоматизации, так же как и клиент автоматизации может не быть "чистым" клиентом автоматизации. В действительности сервер автоматизации может использовать сервисы другого приложения, которое также является сервером автоматизации. Клиент автоматизации, предоставляющий свои сервисы другому клиенту, также может являться как клиентом, так и сервером автоматизации. Глубинные механизмы (сетевые и транспортные протоколы), с помощью которых клиент автоматизации взаимодействует с сервером, уже являются частью собственно COM.
Сервер автоматизации - это просто двоичный исполняемый модуль, который может состоять из нескольких объектов автоматизации. Объект автоматизации (также называемый объектом COM, хотя технически объект автоматизации является объектом COM особого сорта) - это отдельный, самодостаточный объект, спроектированный для выполнения специфической задачи или функции. В общем, все объекты автоматизации, собранные в одном сервере, предназначены для осуществления каких-то функциональных возможностей. Например, Microsoft Excel является сервером автоматизации, состоящим из нескольких меньших серверов автоматизации (Workbook - книга, Chart - диаграмма, Worksheet - лист, Range - диапазон и т.д.), каждый из которых определяет часть функций, предоставляемых пользователю Microsoft Excel. Идея заключается в том, что сервер автоматизации "позволяет" своим клиентам получать доступ и использовать свои объекты так же легко и просто, как будто это его внутренние объекты.
Для решения задачи, поставленной перед нами в начале данной статьи, мы можем воспользоваться теми возможностями, которые предоставляет нам сервер автоматизации Microsoft Word. C помощью приложения, разработанного в Borland Delphi (программа будет выступать в качестве клиента автоматизации), мы сможем динамически создать новый документ и поместить в него некоторый текст (который и будем проверять). После этого нам останется лишь с помощью MS Word осуществить эту проверку. Если приложение Word будет минимизировано, то пользователи могут и не почувствовать, что выполнение части функций нашего приложения берет на себя другая программа. Обращаю внимание, что для полноценного использования OLE-автоматизации вам надо будет знать как можно больше о возможностях и интерфейсах того приложения, функциональностью которого вы решили воспользоваться. Кроме того, для корректного выполнения всех функций разрабатываемого приложения необходимо, чтобы на компьютере пользователя было установлено соответствующее приложение. В нашем случае - Microsoft Word.
Основные принципы работы
Существует три основных метода использования OLE-автоматизации в Borland Delphi в зависимости от того, какую версию этой среды разработки вы используете.
Delphi 5. Закладка Servers на палитре компонентов.
Если вы являетесь счастливым обладателем этой версии Delphi, то для работы с Microsoft Word можно воспользоваться компонентами, расположенными на закладке Ser-vers (рис. 1). Такие компоненты, как TWord-Application и TWordDocument, предоставляют все необходимые для работы интерфейсы.
Delphi 3, 4. Раннее связывание.
Используя термины автоматизации, для обеспечения в Delphi доступа к методам и свойствам, предоставляемым MS Word, необходимо установить соответствующую библиотеку типов. Библиотека типов предоставляет информацию обо всех свойствах и методах, которые разработчик может использовать при работе с сервером автоматизации. Для использования библиотеки типов Microsoft Word в Delphi (3 или 4 версии) необходимо произвести следующие несложные действия:
выбрать пункт меню Project|Import Type Library;
в открывшемся диалоге найти файл msword8.olb (для Microsoft Office'2000 этот файл будет иметь название msword9.olb), расположенный в подкаталоге "Office" того каталога, в который был установлен Microsoft Office.
После этого будет создан файл с именем word_TLB.pas, в котором в синтаксисе object pascal будут описаны константы, типы, свойства и методы для доступа к серверу автоматизации Microsoft Word. Файл word_TLB.pas должен быть включен в список uses всех модулей, в которых вы планируете использовать функции Microsoft Word. Такая технология работы с серверами автоматизации называется ранним связыванием. Одним из преимуществ раннего связывания является осуществление контроля вызовов и передаваемых параметров на этапе компиляции.
Delphi 2. Позднее связывание.
Для доступа к объектам MS Word без применения библиотеки типов можно использовать так называемое позднее связывание. В данном случае доступ к Word осуществляется так же, как к переменной типа Variant, следствием чего является необходимость знания вами всех предоставляемых сервером автоматизации интерфейсов. Позднего связывания следует по возможности избегать, поскольку при этом отсутствует возможность контроля корректности вызовов процедур и функций со стороны компилятора, и если вы неправильно написали имя того или иного метода, то узнаете об этом, только, когда программа "вывалится" по ошибке в процессе выполнения.
Начнем!
Итак, вернемся к теме статьи. Для демонстрации принципов работы с MS Word я буду использовать механизмы, предоставляемые пятой версией Delphi (т.е. компоненты TWordApplication, TWordDocument). Ниже я приведу код, обеспечивающий соединение и работу с MS Word в случае использования библиотеки типов и позднего связывания и больше не буду касаться этой темы.
Для доступа к объектам Word при работе в Delphi 3, 4 (запуск приложения и создание нового документа) используйте следующий код:
Code: |
uses Word_TLB; ... var WordApp: _Application; WordDoc: _Document; VarFalse: OleVariant; begin WordApp := CoApplication.Create; WordDoc := WordApp.Documents.Add(EmptyParam, EmptyParam); { код для проверки орфографии, описываемы далее в данной статье } VarFalse:=False; WordApp.Quit(VarFalse, EmptyParam, EmptyParam); end; |
Обращаю внимание, что в методах MS Word множество параметров описаны как необязательные (optional). При использовании интерфейсов (библиотек типов), Delphi не позволит вам опускать те или иные параметры, даже если в контексте разрабатываемого вами кода они не нужны. В четвертой версии Delphi в модуле system.pas описана переменная EmptyParam, которую можно использовать в качестве "заглушки" для неиспользуемых переменных в вызываемом методе.
Для автоматизации MS Word с использованием переменной Variant (позднее связывание) используйте следующий код:
Code: |
uses ComObj; ... var WordApp, WordDoc: Variant; begin WordApp := CreateOleObject('Word.Application'); WordDoc := WordApp.Documents.Add; { код для проверки орфографии, описываемы далее в данной статье } WordApp.Quit(False) end; |
При использовании позднего связывания компилятор Delphi позволяет вам опускать те или иные параметры при вызове методов сервера автоматизации.
Как уже упоминалось, Delphi 5 упрощает программисту использование функциональности MS Word в своих приложениях путем предоставления его методов и свойств в виде компонентов. Так как множество параметров, определенных в методах Word'а, описаны как необязательные, то в Delphi данные процедуры и функции переопределены и представляют собой набор из нескольких методов с различным количеством параметров. Таким образом, разработчику предоставляется возможность при вызове метода не указывать последние n параметров, необходимость в которых отсутствует.
Шаг за шагом
Для создания своего редактора с возможностью проверки орфографии в минимальном варианте нам понадобится две формы: одна будет использоваться для редактирования текста, а вторая - для отображения диалога правки найденных ошибок. Однако предлагаю начать с самого начала.
Если у вас не запущен Delphi - запустите его. Создайте новый проект (если он не был создан при открытии приложения). По умолчанию проект будет содержать одну форму. Данная форма будет главной в нашем проекте. Поместите на форму один компонент типа TMemo и две кнопки (TButton). Заполните свойство Lines компонента Memo1 каким-нибудь текстом (содержащим ошибки). Заголовок одной кнопки определите как "Орфография", а второй - "Тезаурус". Затем перейдите на закладку Servers палитры компонентов и поместите на форму по одному компоненту типа TWordApplication и TWordDocument (рис. 2). Установите значения свойства Name первого компонента в Word-App, а второго - WordDoc.
TWordApplication, TWordDocument
При автоматизации MS Word для управления приложением, отображения его рабочего окна, получения доступа к атрибутам и объектной модели MS Word мы используем объект Application. Для того чтобы указать приложению, запускать ли новую копию процесса Word или использовать уже запущенный, применяется свойство Applicati-on.ConnectKind. В нашем случае мы устанавливаем данное свойство в значение ckRunningInstance. Другие возможные значения этого свойства вы сможете узнать, воспользовавшись справочной системой Delphi.
Когда мы открываем в MS Word существующий файл или создаем новый, мы тем самым создаем объект Document. Типичной задачей при использовании автоматизации Word является работа с некоторой областью документа: добавление текста, выделение некоторой области, проверка орфографии и т.д. Объект, определяющий некоторую область в документе, называется Range.
Естественно, в рамках статьи я не смогу подробно рассказать обо всех нюансах работы с компонентами, расположенными на закладке Servers палитры компонентов (кстати, с любой другой закладкой ситуация состоит ничуть не лучше). Для более детального их изучения предлагаю воспользоваться справочной системой Borland Delphi. В нашем же сегодняшнем разговоре я буду упоминать только те свойства и методы, которые будут необходимы.
Как это все будет работать
Алгоритм работы нашего приложения будет достаточно прост. Каждое слово, входящее в состав проверяемого нами текста, будет передаваться в MS Word для проверки. Сервер автоматизации Word содержит метод SpellingErrors, который позволяет вам осуществлять проверку текста, входящего в состав некоторой области Range. Мы же будем каждый раз определять эту область таким образом, чтобы она содержала только переданное нами в Word слово. Метод SpellingErrors в качестве результата своей работы возвращает коллекцию слов, написание которых признано ошибочным. Если эта коллекция пуста, то мы переходим к рассмотрению следующего слова. Иначе - переходим к процедуре замены неправильно напечатанного слова. Путем вызова метода GetSpellingSuggestions можно получить список слов, предлагаемых в качестве замены. Эти слова помещаются в коллекцию SpellingSuggestions. Данную коллекцию мы помещаем в качестве списка (компонент типа TListBox), расположенного во второй форме нашего проекта. Думаю, самое время немного поговорить о ней.
Для того чтобы добавить новую форму в проект, следует выбрать пункт меню File|New Form. Назовем эту форму frSpellCheck. На форму поместим три кнопки типа TBitBtn, два элемента редактирования (TEdit) и один список (TListBox). На форму также следует поместить три метки (см. рис. 3). Компонент edNID (editNotInDictionary) служит для отображения заменяемого слова. edReplaceWith содержит выделенный в данный момент вариант для замены, а список lbSuggestions - список предлагаемых вариантов (заполняемый на основании данных, содержащихся в коллекции SpellingSuggestions). Три кнопки выполняют именно те функции, которым соответствуют их заголовки - не больше и не меньше. Каждой из кнопок соответствует свое значение, возвращаемое функцией frSpellCheck.ModalResult. В зависимости от этого значения в основной обрабатывающей процедуре осуществляется то или иное действие - игнорирование, замена или отмена дальнейшей проверки. Форма frSpellCheck содержит одно общедоступное свойство:
sReplacedWord :String
Оно служит для передачи в основную форму слова для замены в случае нажатия пользователем кнопки "Заменить".
Пишем код!
Ниже приводится код основной процедуры приложения.
Code: |
procedure TForm1.btnSpellCheckClick(Sender: TObject); var colSpellErrors : ProofreadingErrors; colSuggestions : SpellingSuggestions; i : Integer; StopLoop : Boolean; itxtLen, itxtStart : Integer; varFalse : OleVariant; begin WordApp.Connect; WordDoc.ConnectTo(WordApp.Docum-ents.Add(EmptyParam, EmptyParam));
StopLoop:=False; itxtStart:=0; Memo.SelStart:=0; itxtlen:=0; whilenot StopLoop do begin itxtStart := itxtLen + itxtStart; itxtLen := Pos(' ', Copy(Memo.Text,itxtStart+1,MaxInt)); if itxtLen = 0then StopLoop := True; Memo.SelStart := itxtStart; Memo.SelLength := -1 + itxtLen;
if Memo.SelText = ''then Continue;
Caption:=Memo.SelText;
WordDoc.Range.Delete(EmptyParam,Emp-tyParam); WordDoc.Range.Set_Text(Memo.SelText); colSpellErrors := WordDoc.SpellingErrors; if colSpellErrors.Count <> 0then begin colSuggestions := WordApp.GetSpellingSuggestions (colSpellErrors.Item(1).Get_Text); with frSpellCheck do begin edNID.text := colSpellErrors.Item(1).Get_Text; lbSuggestions.Items.Clear; for i:= 1to colSuggestions.Count do lbSuggestions.Items.Add(VarToStr-(colSuggestions.Item(i))); lbSuggestions.ItemIndex := 0; lbSuggestionsClick(Sender); ShowModal; case frSpellCheck.ModalResult of mrAbort: Break; mrIgnore: Continue; mrOK: if sReplacedWord <> ''then begin Memo.SelText := sReplacedWord; itxtLen := Length(sReplacedWord); end; end; end; end; end; WordDoc.Disconnect; varFalse:=False; WordApp.Quit(varFalse); Memo.SelStart := 0; Memo.SelLength := 0; end; |
Обработчики событий нажатий на кнопки формы frSpellCheck и список слов, предлагаемых для замены:
Code: |
procedure TfrSpellCheck.lbSuggestionsClick(Sen-der: TObject); begin if lbSuggestions.ItemIndex <> -1then edReplaceWith.Text := lbSuggestions.Items[lbSuggestio-ns.ItemIndex] else edReplaceWith.Text := ''; end;
procedure TfrSpellCheck.btnChangeClick(Sender: TObject); begin sReplacedWord := edReplaceWith.Text; end;
procedure TfrSpellCheck.btnIgnoreClick(Sender: TObject); begin sReplacedWord := ''; end; |
Тезаурус
Теперь рассмотрим вопрос добавления в нашу программу функций тезауруса. Делается это достаточно просто:
Code: |
procedure TForm1.btnThesaurusClick(Sender: TObject); var varFalse : OleVariant; begin if Memo.SelText <> ''then begin WordApp.Connect; WordDoc.ConnectTo(WordApp.Documen-ts.Add(EmptyParam, EmptyParam));
WordDoc.Range.Delete(EmptyParam,Empty-Param); WordDoc.Range.Set_Text(Memo.SelText);
WordDoc.Range.CheckSynonyms;
Memo.SelText := WordDoc.Range.Get_Text;
WordDoc.Disconnect; varFalse:=False; WordApp.Quit(varFalse); end; end; |
Тестирование
В тексте, помещенном в компонент Memo, мною было сознательно сделано несколько ошибок, которые вы сможете увидеть, приглядевшись к изображению, представленному на рисунке 1. В частности, вместо слова "своих" я написал "свиох", вместо "путем" - "пуетм", а вместо "виде" - "виед". Как же повела себя программа? На следующих рисунках (рисунки 4-6) можно видеть, что проверка текста действительно работает.
Надеюсь, вы понимаете, что в рамках одной статьи невозможно описать все те возможности, которые открываются перед разработчиком программного обеспечения в случае использования серверов автоматизации. И речь идет не только о Microsoft Word, но и о других приложениях (к примеру, широко распространено применение MS Excel в качестве базы для построения отчетов). Все разнообразие данного направления программирования можно познать, на мой взгляд, только через собственный опыт. Так что удачного вам кода!
https://delphiworld.narod
DelphiWorld 6.0
- Подробности
- Родительская категория: Работа со строками
- Категория: Справочные материалы, общие вопросы
Code: |
{ **** UBPFD *********** by delphibase.endimus.com **** >> Небольшой модуль для работы со строками
function CompMask(S, Mask: string):string; //выбор строки по маске // удаление из строки count символов начиная с posit function deleteStr(s:string;posit,count:integer):string; //Удаление из строки s сначала first и с конца last символов function deleteFaskaStr(s:string; first,last:integer):string; Запись в стринлист strg всех вхождений по маске mask из строки source procedure getStrings(var strg: TStringList; mask,source: string);
Зависимости: classes,sysutils Автор: SuMaga, Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра., ICQ:721602488, Махачкала Copyright: Сам состряпал :) Дата: 24 января 2003 г. ***************************************************** }
unit StrMask; interface uses classes, sysutils; function CompMask(S, Mask: string): string; function deleteStr(s: string; posit, count: integer): string; function deleteFaskaStr(s: string; first, last: integer): string; procedure getStrings(var strg: TStringList; mask, source: string);
implementation
type TmaskObj = class constructor open; public Maschr: tstringlist; Masposish: TStringList; destructor close; end;
procedure getStrings(var strg: TStringList; mask, source: string); var s, s2: string; begin s2 := source; s := CompMask(s2, mask); while s <> ''do begin strg.Add(s); s2 := StringReplace(s2, s, '', []); s := CompMask(s2, mask); if pos(s, s2) = 0then break; end;
end;
function eraseMask(inpstr: TStrings): TStrings; var i: integer; e: boolean; begin e := false; for i := 0to inpstr.Count - 1do if (i <> inpstr.Count - 1) and (i < inpstr.Count - 1) then if ((inpstr[i] = '`') and (inpstr[i + 1] = '|')) or ((inpstr[i] = '|') and (inpstr[i + 1] = '`')) or ((inpstr[i] = '`') and (inpstr[i + 1] = '`')) then begin e := true; end;
if (e = false) or (i <= inpstr.Count - 1) then begin Result := inpstr; exit; end;
for i := 0to inpstr.Count - 1do if (i <> inpstr.Count - 1) and (i < inpstr.Count - 1) then if ((inpstr[i] = '`') and (inpstr[i + 1] = '|')) or ((inpstr[i] = '`') and (inpstr[i + 1] = '`')) or ((inpstr[i] = '|') and (inpstr[i + 1] = '`')) then begin inpstr.Delete(i + 1); inpstr[i] := '`'; end; Result := eraseMask(inpstr); end;
{ `<---- Эквивалентна----->* |<---- Эквивалентна----->? }
function SplitMask(mask: string; MaskList: TStringList): TStringList; var i, j, k: integer; s1: string; mch: TmaskObj; begin mch := TmaskObj.open; for i := 1to length(Mask) do begin if Mask[i] = '`'then begin mch.Maschr.Add('`'); mch.Masposish.Add(inttostr(i)) end;
if Mask[i] = '|'then begin mch.Maschr.Add('|'); mch.Masposish.Add(inttostr(i)) end; end; k := 0; for i := 0to mch.Maschr.Count - 1do begin j := strtoint(mch.Masposish.Strings[i]) - k; if j - 1 <> 0then s1 := copy(Mask, 1, j - 1) else s1 := ''; delete(Mask, 1, j); k := length(s1) + 1 + k; if (s1 <> mch.Maschr.Strings[i]) and (length(s1) <> 0) then MaskList.Add(s1); MaskList.Add(mch.Maschr.Strings[i]); end; if Mask <> ''then MaskList.Add(Mask); mch.close; Result := TStringList(eraseMask(MaskList)); end;
function deleteStr(s: string; posit, count: integer): string; begin Delete(s, posit, count); Result := s; end;
function deleteFaskaStr(s: string; first, last: integer): string; begin result := deleteStr(s, 1, first); result := deleteStr(Result, length(Result) - last + 1, length(Result) - (length(Result) - last)); end;
function CompMask(S, Mask: string): string; var i, j, k, y: integer; s1, s2, s3, s4, s5: string; MaskList: TStringList; PrPos: integer; var fm: boolean; label 1, 2, 3;
begin 2: if length(s) = 0then exit; if length(Mask) = 0then exit; if length(s) < length(Mask) then exit; //if Assigned(MaskList) then begin MaskList := TStringList.Create; MaskList := SplitMask(Mask, MaskList); end; PrPos := 0; s4 := s; fm := false; s3 := ''; i := 0; result := ''; if MaskList.Count - 1 = 0then begin if (MaskList[0] = '`') then begin
s3 := s; fm := true; end; if (MaskList[0] = '|') then begin s3 := s[1]; fm := true; result := s3; exit; end; if (MaskList[0] <> '`') and (MaskList[0] <> '|') then begin if pos(MaskList[0], s) = 0then exit; s3 := copy(s, pos(MaskList[0], s), length(MaskList[0])); fm := true; end; i := MaskList.Count + 1; end;
//Начало цикла while i <= MaskList.Count - 1do begin if (MaskList[i] = '`') and (PrPos = 0) and (i + 1 <= MaskList.Count - 1) then begin if pos(MaskList[i + 1], s) = 0then goto2; j := pos(MaskList[i + 1], s) + length(MaskList[i + 1]) - 1; s3 := copy(s, 1, j); delete(s, 1, j); fm := true; PrPos := j; i := i + 1; goto1; end;
if (MaskList[i] = '|') and (PrPos = 0) and (i + 1 <= MaskList.Count - 1) then begin k := i; y := 0; if i + 1 <= MaskList.Count - 1then while (MaskList[k] = '|') do begin k := k + 1; y := y + 1; if k >= MaskList.Count - 1then break; end; if pos(MaskList[k], s) = 0then goto2; j := pos(MaskList[k], s); s3 := copy(s, j - y, length(MaskList[k]) + y); delete(s, 1, j + length(MaskList[k]) - 1); fm := true; PrPos := j - 1; i := k; goto1; end; if (PrPos = 0) and (MaskList[i] <> '|') and (MaskList[i] <> '`') then begin if pos(MaskList[i], s) = 0then break; j := pos(MaskList[i], s); s3 := copy(s, j, length(MaskList[i])); delete(s, 1, j + length(MaskList[i]) - 1); fm := true; PrPos := length(MaskList[i]); goto1; end;
fm := false; if (PrPos <> 0) and (i < MaskList.Count - 1) then begin if (MaskList[i] = '`') then begin if pos(MaskList[i + 1], s) = 0then goto2; j := pos(MaskList[i + 1], s); s3 := s3 + copy(s, 1, j + length(MaskList[i + 1]) - 1); fm := true;
delete(s, 1, j + length(MaskList[i + 1]) - 1);
PrPos := j + length(MaskList[i + 1]); i := i + 1; goto1;
end; if (MaskList[i] = '|') then begin if i + 1 <= MaskList.Count - 1then if MaskList[i + 1] <> '|'then begin if pos(MaskList[i + 1], s) > 2then begin //break; goto2; end; s3 := s3 + copy(s, 1, length(MaskList[i + 1]) + 1); delete(s, 1, length(MaskList[i + 1]) + 1); fm := true; i := i + 1; goto1; end; s3 := s3 + copy(s, 1, 1); delete(s, 1, 1); fm := true; PrPos := 1; end;
if (MaskList[i] <> '`') and (MaskList[i] <> '|') then begin if pos(MaskList[i], s) = 0then goto2; j := pos(MaskList[i], s); s3 := s3 + copy(s, j, length(MaskList[i])); delete(s, 1, j + length(MaskList[i]) - 1); fm := true; PrPos := length(MaskList[i]); fm := true end; end;
if (PrPos <> 0) and (i = MaskList.Count - 1) then begin if (MaskList[i] = '`') then begin s3 := s3 + s; s := ''; fm := true; PrPos := j; end; if (MaskList[i] = '|') then begin s3 := s3 + copy(s, 1, 1); delete(s, 1, 1); fm := true; PrPos := 1; end; if (MaskList[i] <> '`') and (MaskList[i] <> '|') then begin if pos(MaskList[i], s) <> 0then j := pos(MaskList[i], s) + length(MaskList[i]) - 1 else goto2; s3 := s3 + copy(s, 1, j); delete(s, 1, j); fm := true; PrPos := j + length(s3); end; end; 1: inc(i); end; s5 := s3; if s3 <> ''then for i := 0to MaskList.Count - 1do if (MaskList[i] <> '`') and (MaskList[i] <> '|') then begin if pos(MaskList[i], s3) = 0then goto2; s3 := StringReplace(s3, MaskList[i], '', []); end;
s3 := s5; MaskList.Free;
if fm then begin result := s3; end { {result:=''; else if length(s)>=length(Mask) then result:=CompMask(s,Mask) else Result:='';} end;
destructor TmaskObj.close; begin Maschr.free; Masposish.free; end;
constructor TmaskObj.open; begin Maschr := TStringList.Create; Masposish := TStringList.Create; end; end. Пример использования:
s := 'asd r'; s := CompMask(s, 'd |'); //в результате s='d r'; |
- Подробности
- Родительская категория: Работа со строками
- Категория: Справочные материалы, общие вопросы
Тип String:
по смещению -4 храниться длина строки
по смещению -8 храниться счётчик ссылок на строку (когда он обнуляется строка уничтожается)
Сама строка располагается в памяти как есть - каждая буква занимает 1 байт.
При копировании строки:
s1:=s2 - реального копирования не происходит, увеличивается только счётчик ссылок, но если после этого изменить одну из строк:
s1:=s1+'a';
то произойдёт физическое копирование содержимого строк, и теперь s1 и s2 будут показывать на разные адреса памяти.
PChar - длина строки определяется от начала до #0 байта, по сути это чистой воды pointer, так что все действия по отслеживанию распределения памяти лежат на программисте - сами заботьтесь о том чтобы хватило места для распределения памяти и освобождении после использования. Тоже одна буква = 1 байт Для хранения unicode (т.е. 2х байтовых символов) используйте соответствующие символы с приставкой Wide...
Автор:Vit
Примечание Fantasist'a:
Это верно только если s1 - локальная переменная, или s1 и s2 - обе не локальные. Если s1 не локальная(глобальная или член класса), а s2 - локальная происходит копирование.
Взято с Vingrad.ruhttps://forum.vingrad
- Подробности
- Родительская категория: Работа со строками
- Категория: Справочные материалы, общие вопросы
LTrim() - Удаляем все пробелы в левой части строки
RTrim() - Удаляем все пробелы в правой части строки
Trim() - Удаляем все пробелы по краям строки
RightStr() - Возвращаем правую часть стоки заданной длины
LeftStr() - Возвращаем левую часть стоки заданной длины
MidStr() - Возвращаем центральную часть строки
squish() - возвращает строку со всеми белыми пробелами и с удаленными повторяющимися апострофами.
before() - возвращает часть стоки, находящейся перед первой найденной подстроки Find в строке Search. Если Find не найдена, функция возвращает Search.
after() - возвращает часть строки, находящейся после первой найденной подстроки Find в строке Search. Если Find не найдена, функция возвращает NULL.
RPos() - возвращает первый символ последней найденной подстроки Find в строке Search. Если Find не найдена, функция возвращает 0. Подобна реверсированной Pos().
inside() - возвращает подстроку, вложенную между парой подстрок Front ... Back.
leftside() - возвращает левую часть "отстатка" inside() или Search.
rightside() - возвращает правую часть "остатка" inside() или Null.
trim() - возвращает строку со всеми удаленными по краям белыми пробелами.
Code: |
unit TrimStr; {$B-} { Файл: TrimStr Автор: Bob Swart [100434,2072] Описание: программы для удаления конечных/начальных пробелов и левых/правых частей строк (аналог Basic-функций). Версия: 2.0
LTrim() - Удаляем все пробелы в левой части строки RTrim() - Удаляем все пробелы в правой части строки Trim() - Удаляем все пробелы по краям строки RightStr() - Возвращаем правую часть стоки заданной длины LeftStr() - Возвращаем левую часть стоки заданной длины MidStr() - Возвращаем центральную часть строки
} interface const Space = #$20;
function LTrim(const Str: string): string; function RTrim(Str: string): string; function Trim(Str: string): string; function RightStr(const Str: string; Size: Word): string; function LeftStr(const Str: string; Size: Word): string; function MidStr(const Str: string; Size: Word): string;
implementation
function LTrim(const Str: string): string; var len: Byte absolute Str; i: Integer; begin i := 1; while (i <= len) and (Str[i] = Space) do Inc(i); LTrim := Copy(Str, i, len) end{LTrim};
function RTrim(Str: string): string; var len: Byte absolute Str; begin while (Str[len] = Space) do Dec(len); RTrim := Str end{RTrim};
function Trim(Str: string): string; begin Trim := LTrim(RTrim(Str)) end{Trim};
function RightStr(const Str: string; Size: Word): string; var len: Byte absolute Str; begin if Size > len then Size := len; RightStr := Copy(Str, len - Size + 1, Size) end{RightStr};
function LeftStr(const Str: string; Size: Word): string; begin LeftStr := Copy(Str, 1, Size) end{LeftStr};
function MidStr(const Str: string; Size: Word): string; var len: Byte absolute Str; begin if Size > len then Size := len; MidStr := Copy(Str, ((len - Size) div2) + 1, Size) end{MidStr};
end.
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
const BlackSpace = [#33..#126];
{ squish() возвращает строку со всеми белыми пробелами и с удаленными повторяющимися апострофами. }
function squish(const Search: string): string; var
Index: byte; InString: boolean; begin
InString := False; Result := ''; forIndex := 1to Length(Search) do begin if InString or (Search[Index] in BlackSpace) then AppendStr(Result, Search[Index]); InString := ((Search[Index] = '''') and (Search[Index - 1] <> '\')) xor InString; end; end;
{
before() возвращает часть стоки, находящейся перед первой найденной подстроки Find в строке Search. Если Find не найдена, функция возвращает Search. }
function before(const Search, Find: string): string; var
index: byte; begin
index := Pos(Find, Search); ifindex = 0then Result := Search else Result := Copy(Search, 1, index - 1); end;
{
after() возвращает часть строки, находящейся после первой найденной подстроки Find в строке Search. Если Find не найдена, функция возвращает NULL. }
function after(const Search, Find: string): string; var
index: byte; begin
index := Pos(Find, Search); ifindex = 0then Result := '' else Result := Copy(Search, index + Length(Find), 255); end;
{
RPos() возвращает первый символ последней найденной подстроки Find в строке Search. Если Find не найдена, функция возвращает 0. Подобна реверсированной Pos(). }
function RPos(const Find, Search: string): byte; var
FindPtr, SearchPtr, TempPtr: PChar; begin
FindPtr := StrAlloc(Length(Find) + 1); SearchPtr := StrAlloc(Length(Search) + 1); StrPCopy(FindPtr, Find); StrPCopy(SearchPtr, Search); Result := 0; repeat TempPtr := StrRScan(SearchPtr, FindPtr^); if TempPtr <> nilthen if (StrLComp(TempPtr, FindPtr, Length(Find)) = 0) then begin Result := TempPtr - SearchPtr + 1; TempPtr := nil; end else TempPtr := #0; until TempPtr = nil; end;
{
inside() возвращает подстроку, вложенную между парой подстрок Front ... Back. }
function inside(const Search, Front, Back: string): string; var
Index, Len: byte; begin
Index := RPos(Front, before(Search, Back)); Len := Pos(Back, Search); if (Index > 0) and (Len > 0) then Result := Copy(Search, Index + 1, Len - (Index + 1)) else Result := ''; end;
{
leftside() возвращает левую часть "отстатка" inside() или Search. }
function leftside(const Search, Front, Back: string): string; begin
Result := before(Search, Front + inside(Search, Front, Back) + Back); end;
{
rightside() возвращает правую часть "остатка" inside() или Null. }
function rightside(const Search, Front, Back: string): string; begin
Result := after(Search, Front + inside(Search, Front, Back) + Back); end;
{
trim() возвращает строку со всеми удаленными по краям белыми пробелами. }
function trim(const Search: string): string; var
Index: byte; begin
Index := 1; while (Index <= Length(Search)) andnot (Search[Index] in BlackSpace) do Index := Index + 1; Result := Copy(Search, Index, 255); Index := Length(Result); while (Index > 0) andnot (Result[Index] in BlackSpace) do Index := Index - 1; Result := Copy(Result, 1, Index); end;
|
https://delphiworld.narod
DelphiWorld 6.0
- Подробности
- Родительская категория: Работа со строками
- Категория: Справочные материалы, общие вопросы
Страница 20 из 21