How to switch two nodes in a singly linked list in pascal?
procedure List.switch(var n1,n2 : pNode);
var aux : pNode;
begin
aux := n1;
p1:= n2;
n2 := aux;
end;
(Here pNode is a pointer on a node in the list.)
Nodes are defined like this:
pNode = ^Node;
Node = record
data : data;
next : pNode;
end;
This code doesn't work. It either doesn't compile, saying "Can't take the address of constant expressions", or just doesn't do anything. I guess it has to do with how pointers work...
I found relevant information here, but I don't read C.
Thanks for any advice!
I think something like this would work:
function SwapNodes(first: pNode): pNode;
begin
Result := first.next;
first.next := Result.next;
Result.next := first;
end;
Something like this should work for you. It uses a local variable of type Node (which is presumably what PNode is a pointer to) as a placeholder.
procedure List.Switch(NodeA, NodeB: PNode);
var
Temp: Node;
begin
Temp.Data := NodeB^.Data;
Temp.Next := NodeB^.Next;
NodeB^.Data := NodeA^.Data;
NodeB^.Next := NodeA^.Next;
NodeA^.Data := Temp.Data;
NodeA^.Next := Temp.Next;
end;
Here's a version of it that isn't an object method, with a console app that tests it:
program Project1;
uses
System.SysUtils;
type
PNode = ^Node;
Node = record
Data: Integer;
Next: PNode;
end;
procedure Swap(NodeA, NodeB: PNode);
var
Temp: Node;
begin
Temp.Data := NodeB^.Data;
Temp.Next := NodeB^.Next;
NodeB^.Data := NodeA^.Data;
NodeB^.Next := NodeA^.Next;
NodeA^.Data := Temp.Data;
NodeA^.Next := Temp.Next;
end;
var
A, B: Node;
pA, pB: PNode;
begin
New(pA);
pA^.Data := 1;
pA^.Next := nil;
New(pB);
pB^.Data := 2;
pB^.Next := #A;
WriteLn('Before - pA^.Data: ', pA^.Data, ' pB^.Data: ', pB^.Data);
Swap(pA, pB);
WriteLn('After - pA^.Data: ', pA^.Data, ' pB^.Data: ', pB^.Data); // Outputs 2 and 1
Readln;
Dispose(pA);
Dispose(pB);
end.
Related
I have an assignment in "project management". I have to assign modules which can also be sub-modules, so I want to append recursively sub-modules to modules.
Example:
P(project) Modules(M1,M2,M3,M4). Under M1 Module there will be sub-modules(M1S1,M1S2,M1S3), and under sub-module1 (M1S1) there can be many sub-modules (M1S1S1, M1S1S2, M1S1S3) and so on.
I have done this code using Recursion and TTreeNode but i feel the problem is with condition statement.
procedure TForm2.BitBtn1Click(Sender: TObject);
begin
lGlblProjID := 1;
lGlblProjName := 'Project';
ADOConnectionListner.Connected := true;
try
if ADOConnectionListner.Connected then
begin
RootNode := TreeView2.Items.Add(nil, lGlblProjName);
getSubChild(lGlblProjID, RootNode);
end;
except
on E: Exception do
begin
ShowMessage('Exception Class = ' + E.ClassName);
end;
end;
end;
procedure TForm2.getSubChild(var Pid: Integer; var SubRoot: TTreeNode);
var
lcount, I, lcurrentID: Integer;
lcurrentName: String;
lModuleNode: TTreeNode;
begin
// ShowMessage(IntToStr(Pid)+ ' '+SubRoot.Text);
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SELECT * FROM treetab Where parent_id =:value1');
ADOQuery1.Parameters.ParamByName('value1').Value := Pid;
ADOQuery1.Active := true;
lcount := ADOQuery1.RecordCount;
for I := 0 to lcount - 1 do
begin
lcurrentID := ADOQuery1.FieldByName('id').AsInteger;
lcurrentName := ADOQuery1.FieldByName('name').AsString;
ShowMessage(' id ' + IntToStr(lcurrentID) + ' dd ' + lcurrentName); // print valu of i
if ((lcurrentID <> 0)and (SubRoot.Text <> '') ) then //or
begin
lModuleNode := TreeView1.Items.AddChild(SubRoot, lcurrentName);
getSubChild(lcurrentID, lModuleNode);
end else // if
// lcurrentID = 0
ShowMessage('end reached');
// TreeView1.Items.AddChild(SubRoot, ADOQuery1.FieldByName('name').AsString);
ADOQuery1.Next;
//*********
end;
end;
I want to retrieve all the sub-modules for a particular project like in this case project with id=1 only.
Your problem seems to be the non-local ADOQuery1 which gets cleared at entry on each recursive call. Therefore you loose all remaining records from a previous query. You should arrange a local storage for the query results.
Something like (untested):
procedure GetSubChild()
type
TTempRecord = record
id: integer;
name: string;
end;
TTempArray = array of TTempRecord;
var
lcount, I, lcurrentID: Integer;
lcurrentName: String;
lModuleNode: TTreeNode;
recs: TTempArray
begin
// ...
// query the db
// ...
lcount := ADOQuery1.RecordCount;
SetLength(recs, lcount);
for i := 0 to lcount-1 do
begin
recs[i].id := ADOQuery1.FieldByName('id').AsInteger;
recs[i].name := ADOQuery1.FieldByName('name').AsString;
ADOQuery1.Next;
end;
for i := 0 to lcount-1 do
begin
lcurrentID := recs[i].id;
lcurrentname := recs[i].name;
// ...
// add to treeview
// call recursively GetSubChild()
// ...
end;
end;
I have an app that needs to do heavy text manipulation in a TStringList. Basically i need to split text by a delimiter ; for instance, if i have a singe line with 1000 chars and this delimiter occurs 3 times in this line, then i need to split it in 3 lines. The delimiter can contain more than one char, it can be a tag like '[test]' for example.
I've wrote two functions to do this task with 2 different approaches, but both are slow in big amounts of text (more then 2mbytes usually).
How can i achieve this goal in a faster way ?
Here are both functions, both receive 2 paramaters : 'lines' which is the original tstringlist and 'q' which is the delimiter.
function splitlines(lines : tstringlist; q: string) : integer;
var
s, aux, ant : string;
i,j : integer;
flag : boolean;
m2 : tstringlist;
begin
try
m2 := tstringlist.create;
m2.BeginUpdate;
result := 0;
for i := 0 to lines.count-1 do
begin
s := lines[i];
for j := 1 to length(s) do
begin
flag := lowercase(copy(s,j,length(q))) = lowercase(q);
if flag then
begin
inc(result);
m2.add(aux);
aux := s[j];
end
else
aux := aux + s[j];
end;
m2.add(aux);
aux := '';
end;
m2.EndUpdate;
lines.text := m2.text;
finally
m2.free;
end;
end;
function splitLines2(lines : tstringlist; q: string) : integer;
var
aux, p : string;
i : integer;
flag : boolean;
begin
//maux1 and maux2 are already instanced in the parent class
try
maux2.text := lines.text;
p := '';
i := 0;
flag := false;
maux1.BeginUpdate;
maux2.BeginUpdate;
while (pos(lowercase(q),lowercase(maux2.text)) > 0) and (i < 5000) do
begin
flag := true;
aux := p+copy(maux2.text,1,pos(lowercase(q),lowercase(maux2.text))-1);
maux1.add(aux);
maux2.text := copy(maux2.text,pos(lowercase(q),lowercase(maux2.text)),length(maux2.text));
p := copy(maux2.text,1,1);
maux2.text := copy(maux2.text,2,length(maux2.text));
inc(i);
end;
finally
result := i;
maux1.EndUpdate;
maux2.EndUpdate;
if flag then
begin
maux1.add(p+maux2.text);
lines.text := maux1.text;
end;
end;
end;
I've not tested the speed, but for academic purposes, here's an easy way to split the strings:
myStringList.Text :=
StringReplace(myStringList.Text, myDelimiter, #13#10, [rfReplaceAll]);
// Use [rfReplaceAll, rfIgnoreCase] if you want to ignore case
When you set the Text property of TStringList, it parses on new lines and splits there, so converting to a string, replacing the delimiter with new lines, then assigning it back to the Text property works.
The problems with your code (at least second approach) are
You are constantly using lowecase which is slow if called so many times
If I saw correctly you are copying the whole remaining text back to the original source. This is sure to be extra slow for large strings (eg files)
I have a tokenizer in my library. Its not the fastest or best but it should do (you can get it from Cromis Library, just use the units Cromis.StringUtils and Cromis.Unicode):
type
TTokens = array of ustring;
TTextTokenizer = class
private
FTokens: TTokens;
FDelimiters: array of ustring;
public
constructor Create;
procedure Tokenize(const Text: ustring);
procedure AddDelimiters(const Delimiters: array of ustring);
property Tokens: TTokens read FTokens;
end;
{ TTextTokenizer }
procedure TTextTokenizer.AddDelimiters(const Delimiters: array of ustring);
var
I: Integer;
begin
if Length(Delimiters) > 0 then
begin
SetLength(FDelimiters, Length(Delimiters));
for I := 0 to Length(Delimiters) - 1 do
FDelimiters[I] := Delimiters[I];
end;
end;
constructor TTextTokenizer.Create;
begin
SetLength(FTokens, 0);
SetLength(FDelimiters, 0);
end;
procedure TTextTokenizer.Tokenize(const Text: ustring);
var
I, K: Integer;
Counter: Integer;
NewToken: ustring;
Position: Integer;
CurrToken: ustring;
begin
SetLength(FTokens, 100);
CurrToken := '';
Counter := 0;
for I := 1 to Length(Text) do
begin
CurrToken := CurrToken + Text[I];
for K := 0 to Length(FDelimiters) - 1 do
begin
Position := Pos(FDelimiters[K], CurrToken);
if Position > 0 then
begin
NewToken := Copy(CurrToken, 1, Position - 1);
if NewToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(NewToken);
Inc(Counter)
end;
CurrToken := '';
end;
end;
end;
if CurrToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(CurrToken);
Inc(Counter)
end;
SetLength(FTokens, Counter);
end;
How about just using StrTokens from the JCL library
procedure StrTokens(const S: string; const List: TStrings);
It's open source
http://sourceforge.net/projects/jcl/
As an additional option, you can use regular expressions. Recent versions of Delphi (XE4 and XE5) come with built in regular expression support; older versions can find a free regex library download (zip file) at Regular-Expressions.info.
For the built-in regex support (uses the generic TArray<string>):
var
RegexObj: TRegEx;
SplitArray: TArray<string>;
begin
SplitArray := nil;
try
RegexObj := TRegEx.Create('\[test\]'); // Your sample expression. Replace with q
SplitArray := RegexObj.Split(Lines, 0);
except
on E: ERegularExpressionError do begin
// Syntax error in the regular expression
end;
end;
// Use SplitArray
end;
For using TPerlRegEx in earlier Delphi versions:
var
Regex: TPerlRegEx;
m2: TStringList;
begin
m2 := TStringList.Create;
try
Regex := TPerlRegEx.Create;
try
Regex.RegEx := '\[test\]'; // Using your sample expression - replace with q
Regex.Options := [];
Regex.State := [preNotEmpty];
Regex.Subject := Lines.Text;
Regex.SplitCapture(m2, 0);
finally
Regex.Free;
end;
// Work with m2
finally
m2.Free;
end;
end;
(For those unaware, the \ in the sample expression used are because the [] characters are meaningful in regular expressions and need to be escaped to be used in the regular expression text. Typically, they're not required in the text.)
I have the interface:
TOnIntegerValue: function: integer of object;
ITestInterface = interface(IInvokable)
['{54288E63-E6F8-4439-8466-D3D966455B8C}']
function GetOnIntegerValue: TOnIntegerValue;
procedure SetOnIntegerValue(const Value: TOnIntegerValue);
property OnIntegerValue: TOnIntegerValue read GetOnIntegerValue
write SetOnIntegerValue;
end;
and in my tests i have:
.....
FTestInterface: ITestInterface;
.....
procedure Test_TestInterface.SetUp;
begin
FTestInterface := TVirtualInterface.Create(TypeInfo(ITestInterface)) as ITestInterface;
end;
.....
and get the error : "Range check error"
Any idea? or TVirtualInterface doesnt support "function of object" and "procedure of object" types?
Thanks!!
It seems that TVirtualInterface works fine with method pointers, but doesn't like properties. Here's a quick sample to demonstrate:
{$APPTYPE CONSOLE}
uses
SysUtils, Rtti;
type
TIntegerFunc = function: integer of object;
IMyInterface = interface(IInvokable)
['{8ACA4ABC-90B1-44CA-B25B-34417859D911}']
function GetValue: TIntegerFunc;
// property Value: TIntegerFunc read GetValue; // fails with range error
end;
TMyClass = class
class function GetValue: Integer;
end;
class function TMyClass.GetValue: Integer;
begin
Result := 666;
end;
procedure Invoke(Method: TRttiMethod; const Args: TArray<TValue>; out Result: TValue);
begin
Writeln(Method.ToString);
Result := TValue.From<TIntegerFunc>(TMyClass.GetValue);
end;
var
Intf: IMyInterface;
begin
Intf := TVirtualInterface.Create(TypeInfo(IMyInterface), Invoke) as IMyInterface;
Writeln(Intf.GetValue()); // works fine
// Writeln(Intf.Value()); // fails with range error
Readln;
end.
This programs works as expected. However, uncommenting the property is enough to make it fail. It's clearly an RTTI bug. I see no ready way for anyone other than Embarcadero to fix it.
It seems that the combination of a property whose type is a method pointer is the problem. The workaround is to avoid such properties. I suggest that you submit a QC report. The code from this answer is just what you need.
As David already mentioned the problem is the compiler generating wrong RTTI for properties that return a method type.
So for the property
property OnIntegerValue: TOnIntegerValue;
the compiler generates RTTI for a method that would look like this:
function OnIntegerValue: Integer;
but it does not include the implicit Self parameter for this method. This is the reason why you get the range check error because while reading the RTTI to create a TRttiInterfaceType this line of code gets executed:
SetLength(FParameters, FTail^.ParamCount - 1);
This should never happen as all valid methods have the implicit Self parameter.
There is another problem with that wrong RTTI as it messes up the virtual method indizes because of the invalid methods it generates. If the method type has a parameter you do not get the range check error but a wrong TRttiMethod instance which causes all following methods to have a wrong virtual index which will cause the virtual interface invokation to fail.
Here is a unit I wrote that you can use to fix wrong RTTI.
unit InterfaceRttiPatch;
interface
uses
TypInfo;
procedure PatchInterfaceRtti(ATypeInfo: PTypeInfo);
implementation
uses
Windows;
function SkipShortString(P: Pointer): Pointer;
begin
Result := PByte(P) + PByte(P)^ + 1;
end;
function SkipAttributes(P: Pointer): Pointer;
begin
Result := PByte(P) + PWord(P)^;
end;
procedure PatchInterfaceRtti(ATypeInfo: PTypeInfo);
var
typeData: PTypeData;
table: PIntfMethodTable;
p: PByte;
entry: PIntfMethodEntry;
tail: PIntfMethodEntryTail;
methodIndex: Integer;
paramIndex: Integer;
next: PByte;
n: UINT_PTR;
count: Integer;
doPatch: Boolean;
function IsBrokenMethodEntry(entry: Pointer): Boolean;
var
p: PByte;
tail: PIntfMethodEntryTail;
begin
p := entry;
p := SkipShortString(p);
tail := PIntfMethodEntryTail(p);
// if ParamCount is 0 the compiler has generated
// wrong typeinfo for a property returning a method type
if tail.ParamCount = 0 then
Exit(True)
else
begin
Inc(p, SizeOf(TIntfMethodEntryTail));
Inc(p, SizeOf(TParamFlags));
// if Params[0].ParamName is not 'Self'
// and Params[0].Tail.ParamType is not the same typeinfo as the interface
// it is very likely that the compiler has generated
// wrong type info for a property returning a method type
if PShortString(p)^ <> 'Self' then
begin
p := SkipShortString(p); // ParamName
p := SkipShortString(p); // TypeName
if PIntfMethodParamTail(p).ParamType^ <> ATypeInfo then
Exit(True);
end;
end;
Result := False;
end;
begin
if ATypeInfo.Kind <> tkInterface then Exit;
typeData := GetTypeData(ATypeInfo);
table := SkipShortString(#typeData.IntfUnit);
if table.RttiCount = $FFFF then Exit;
next := nil;
for doPatch in [False, True] do
begin
p := PByte(table);
Inc(p, SizeOf(TIntfMethodTable));
for methodIndex := 0 to table.Count - 1 do
begin
entry := PIntfMethodEntry(p);
p := SkipShortString(p);
tail := PIntfMethodEntryTail(p);
Inc(p, SizeOf(TIntfMethodEntryTail));
for paramIndex := 0 to tail.ParamCount - 1 do
begin
Inc(p, SizeOf(TParamFlags)); // TIntfMethodParam.Flags
p := SkipShortString(p); // TIntfMethodParam.ParamName
p := SkipShortString(p); // TIntfMethodParam.TypeName
Inc(p, SizeOf(PPTypeInfo)); // TIntfMethodParamTail.ParamType
p := SkipAttributes(p); // TIntfMethodParamTail.AttrData
end;
if tail.Kind = 1 then // function
begin
p := SkipShortString(p); // TIntfMethodEntryTail.ResultTypeName
Inc(p, SizeOf(PPTypeInfo)); // TIntfMethodEntryTail.ResultType
end;
p := SkipAttributes(p); // TIntfMethodEntryTail.AttrData
if doPatch and IsBrokenMethodEntry(entry) then
begin
WriteProcessMemory(GetCurrentProcess, entry, p, next - p, n);
count := table.Count - 1;
p := #table.Count;
WriteProcessMemory(GetCurrentProcess, p, #count, SizeOf(Word), n);
count := table.RttiCount;
p := #table.RttiCount;
WriteProcessMemory(GetCurrentProcess, p, #count, SizeOf(Word), n);
p := PByte(entry);
end;
end;
p := SkipAttributes(p); // TIntfMethodTable.AttrData
next := p;
end;
end;
end.
I have a function declare like this :
function execProc(ProcName,InValues:PChar;out OutValues:PChar):integer; //The "OutValues" is a out parameter.
And I call this function like this:
procedure TForm1.Button6Click(Sender: TObject);
var
v:integer;
s:pchar;
begin
Memo1.Clear;
v := execProc(pchar('PROC_TEST'),pchar('aaa'),s);
showmessage(inttostr(v)); //mark line
Memo1.Lines.Add(strpas(s));
end;
when i delete the mark line(showmessage(inttostr(v))),i will have a correct result display in the Memo1,but if i keep use the showmessage(), the memo1 will dispaly an error string : "Messag" ,Why?
Thanks for any help!
function execProc(ProcName,InValues:PChar;out OutValues:PChar):integer;
var
str: TStrings;
InValue,OutValue: string;
i,j,scount: integer;
begin
Result := -100;
i := 0;
j := 0;
str := TStringList.Create;
try
sCount := ExtractStrings(['|'], [], InValues, str);
with kbmMWClientStoredProc1 do
begin
Close;
Params.Clear;
StoredProcName := StrPas(ProcName);
FieldDefs.Updated := False;
FieldDefs.Update;
for i := 0 to Params.Count - 1 do
begin
if (Params[i].ParamType = ptUnknown) or
(Params[i].ParamType = ptInput) or
(Params[i].ParamType = ptInputOutput) then
begin
inc(j);
InValue := str[j-1];
Params[i].Value := InValue;
end;
end;
try
ExecProc;
for i := 0 to Params.Count - 1 do
begin
if (Params[i].ParamType = ptOutput) or
(Params[i].ParamType = ptInputOutput) then
OutValue := OutValue + '|' + Params[i].AsString;
end;
OutValues := PChar(Copy(OutValue,2,Length(OutValue)-1));
Result := 0;
except
on E:Exception do
begin
if E.Message = 'Connection lost.' then Result := -101;//服务器连接失败
if E.Message = 'Authorization failed.' then Result := -102;//身份验证失败
Writelog(E.Message);
end;
end;
end;
finally
str.Free;
end;
end;
The problem is in the design of your interface and the use of PChar.
OutValues := PChar(Copy(OutValue,2,Length(OutValue)-1));
This is implemented by making an implicit, hidden, local string variable which holds the value
Copy(OutValue,2,Length(OutValue)-1)
When the function returns, that string variable is destroyed and so OutValues points at deallocated memory. Sometimes your program appears to work but that's really just down to chance. Any small change can disturb that, as you have observed.
The problem is easy enough to fix. Simply use string parameters rather than PChar. This will make the code easier to read as well as making it work correctly.
function execProc(ProcName, InValues: string; out OutValues: string): integer;
I am trying to manipulate a string and pull only certain data from it. I need to do this on a record pulled from a database that gives me the full name of a person. I need to pull only the last name from the string and store it as a variable. Is there a way that I can do this?
Example: SQL query pulls the full field "Mary Ellen Jones" I need to extract only the Jones from the string so I can store it in a variable for further processing.
I thought maybe AnsiRightStr would work but the problem is needing to give it a set integer to pull from the right. Maybe a way to count the characters after the final space allowing me to use AnsiRightStr(string,int) for this? Any help at all is appreciated.
Additional thought: Would replacing the spaces with a delimiter say :: and then parsing that data into a Stringlist followed by allowing me to pull the last index of the string list be possible?
Several valid options have been presented so far. None of them address the situation if say the name is Something like "John St. James, Jr." Is this impossible?
you can use the LastDelimiter function to get the last space position and then with the copy function extract the substring.
uses
SysUtils;
var
Name : string;
p : Integer;
ShortName : string;
begin
Name:='Mary Ellen Jones';
//You can call trim to avoid problems with ending spaces in this case is not necesary, just is a test
//Name:=Trim(Name);
//get the last space position
p:=LastDelimiter(' ',Name);
//get the name
ShortName:=Copy(Name,p+1,length(Name)-p);
end;
or using a function
function GetLast(const Name:string) : string;
var
p : Integer;
begin
Result:=Trim(Name);
p:=LastDelimiter(' ',Result);
Result:=Copy(Result,p+1,length(Result)-p);
end;
function GetLastWord(const Str: string): string;
var
p: integer;
i: Integer;
const
SPACE = #$20;
begin
p := 1;
for i := length(Str) downto 1 do
if Str[i] = SPACE then
begin
p := i + 1;
break;
end;
result := Copy(Str, p, MaxInt);
end;
This will fail if the string ends with (an accidental) space, as 'Andreas Rejbrand '. This more robust version will handle this case too:
function GetLastWord(const Str: string): string;
var
p: integer;
i: Integer;
FoundNonSpace: boolean;
const
SPACE = #$20;
begin
p := 1;
FoundNonSpace := false;
for i := length(Str) downto 1 do
if (Str[i] = SPACE) and FoundNonSpace then
begin
p := i + 1;
break
end
else if Str[i] <> SPACE then
FoundNonSpace := true;
result := TrimRight(Copy(Str, p, MaxInt));
end;
What if the last name is say "St. James" any way to account for that?
Here's my approach.
Make a list of lastname-markers
Search that list in order of preference
As soon as a match is found, mark that as the start of last name
Return substring starting from that pos.
var
LastNameMarkers: TStringList = nil;
SuffixFix: TStringList = nil;
procedure InitLists;
begin
LastNameMarkers:= TStringList.Create;
//LastNameMarkers.LoadFromFile('c:\markers.txt');
LastNameMarkers.Add(' St.');
LastnameMarkers.Add(' Mc');
LastNameMarkers.Add(' '); //Marker of last resort.
SuffixFix:= TStringList.Create;
SuffixFix.Add(' Jr.');
SuffixFix.Add(' Sr.');
end;
function GetLastName(FullName: string): string;
var
i: integer;
start: integer;
found: boolean;
ReplaceWith: string;
begin
if LastNameMarkers = nil then InitLists;
//Fix suffixes
i:= 0;
found:= false;
while (i < SuffixFix.Count) and not found do begin
start:= pos(lower(LastNameMarkers[i]),lower(Fullname));
found:= Start > 0;
Inc(i);
end; {while}
if Found then begin
Dec(i);
ReplaceWith:= StringReplace(Suffix[i], ' ', '_',[]);
FullName:= StringReplace(FullName, SuffixFix[i], ReplaceWith,[]);
end; {if}
//Look for lastnames
i:= 0;
found:= false;
while (i < LastNameMarkers.Count) and not found do begin
start:= pos(LastNameMarkers[i],Fullname);
found:= Start > 0;
Inc(i);
end; {while}
if found then Result:= RightStr(FullName, Length(FullName)- Start + 2)
else Result:= '';
StringReplace(Result, '_', ' ',[]);
end;
I haven't dealt with upper and lowercase properly, but I hope you get the idea.
function TfrmCal.GetLastName(FullName: string): string;
var
i: integer;
found: boolean;
suffix: string;
marker: string;
begin
// Build the lists for the compare.
InitLists;
// Look at Suffixes and attach them to the LastName
i := 0;
found := False;
while (i < SuffixFix.Count) do
begin
if AnsiContainsStr(FullName, SuffixFix[i]) then
begin
suffix := '::' + trim(SuffixFix[i]);
FullName := ReplaceStr(FullName, SuffixFix[i], suffix);
found := True;
end;
inc(i);
if found then
break;
end;
// Look for LastName Markers
i := 0;
found := False;
while (i < LastNameMarkers.Count) do
begin
if AnsiContainsStr(FullName, LastNameMarkers[i]) then
begin
marker := trimright(LastNameMarkers[i]) + '::';
FullName := ReplaceStr(FullName, LastNameMarkers[i], marker);
found := True;
end;
inc(i);
if found then
break;
end;
FullName := GetLastWord(FullName);
FullName := ReplaceStr(FullName, '::', ' ');
LastNameMarkers.Clear;
SuffixFix.Clear;
Result := FullName;
end;
function TfrmCal.GetLastWord(const Str: string): string;
var
p: integer;
i: integer;
const
SPACE = #$20;
begin
p := 1;
for i := Length(Str) downto 1 do
if Str[i] = SPACE then
begin
p := i + 1;
break;
end;
Result := Copy(Str, p, MaxInt);
end;
These two functions together pull off what I needed to do. There is also the initlists function which is clunky and ugly and I need to work on so I didn't post it here.