I use the following function to search a TStringList I am reading from a file.
I know that when I search a value, then the return value I need is always on the line after the one with the item I search.
It has always worked using POS to search, but now the file has been expanded and I have to look for 2 items 'Adresse' and 'Adresse 2'
That gives me an issue since pos finds 'Adresse' in both cases and my data is then wrong.
Is there another method of searching a string for a substring that I don't know of or do I have to make my own.
function FindValue(const aFilename, aSearch: string): string;
var
InfoList: TStringList;
Counter: integer;
begin
InfoList := TStringList.Create;
try
InfoList.LoadFromFile(aFilename);
if InfoList.Count > 0 then
for Counter := 0 to InfoList.Count - 1 do
begin
if Pos(aSearch, Infolist.Strings[Counter]) > 0 then
Result := Infolist.Strings[Counter + 1]
end
else
Result := '';
finally
InfoList.Free;
end;
end;
For info: the input to the TStringList comes from a textfile extracted from a HTML file.
A sample of a file could be:
OZ8HP
Hugo Pedersen
Radioamatør
Nykøbing M
Sendeposition:
Adresse:
Prinsensvej 18
Postnummer:
7900
Bynavn:
Nykøbing M
Antenne højde (m):
Kote (m):?Kote (m):Brugerens/tilladelsesindehaverens øvrige adresseoplysninger så som Stednavn og/eller Postboks. Hjælpetegnet * kan anvendes, som beskrevet i hjælp.
Koordinater:
Geografisk anvendelse:
Frekvensmaske:
Tekniske specifikationer:
Sendeeffekt basisstation:
Sendeeffekt mobile anlæg:
Båndbredde (MHz):
Antal anlæg:
MMSI:
Kaldesignaltype:
Personlig
Frekvenskategori:
Udstedelses-metode:
Intention om overdragelse:
Nej
Udløbsdato:
Brugerdata:
Brugernummer:
956078
Adresse:
Prinsensvej 18
Adresse 2:
Sejerslev
Postnr.:
7900
Bynavn:
Nykøbing M
Kaldesignal-kategori:
Bestået A
It looks to me as though the real mistake is being too lax in your search. Why accept partial matches? It would seem more robust to look for complete matches
if SameText(aSearch, Infolist[Counter]) then
or perhaps to account for leading and trailing whitespace:
if SameText(aSearch, Trim(Infolist[Counter])) then
You'd need to pass 'Adresse:' or 'Adresse 2:' as the search string, or add the colon in the search function.
Use AnsiSameText if you want locale sensitive comparison. Use = if you want case sensitive comparison, etc.
You might pass multiple search strings and be able to loop only once over the file. As it stands you read it twice which seems wasteful. Indeed surely better to operate on a string list and not be coupled to file storage.
You return the last match in the data rather than the first, for instance. What if there are multiple matches? Does your code behave as intended?
You should also be aware that if no match is found your function does not assign to the Result variable which means it is undefined.
Related
I am trying to create a Complex Number Calculator using Delphi Pascal. The first part is to extract a string entry, separate the real & imaginary part by inserting a delimiter on 'i'. For Example: A number entry can be: 7+2i or any other combination. This was my approach:
TForm2.btnExtractClick(Sender: TObject);
var sCode, pic: string;
sConst: integer;
im,re: integer;
iConst: string;
j,k, delimiterPos: integer;
begin
memDisplay.Clear;
sCode := Edit1.Text;
sConst := Pos (sCode, 'i');
im := StrToInt(Copy(sCode, sConst - 1));
Delete(sCode, sConstant - 1, im);
re := StrToInt(sCode);
But I am getting the following error:
The substring which you search for should be the first argument to Pos.
Replace
Pos(sCode, 'i')
with
Pos('i', sCode)
This function is documented here: http://docwiki.embarcadero.com/Libraries/en/System.Pos
There are many further issues with your code, but this answers the primary question posed here, namely why Pos is returning 0. I won't attempt to debug the rest of your code, not least because this isn't your real code because it contains compile errors.
To resolve the subsequent issues in your code you must learn to use the debugger. Step through the code and inspect the value of each of the local variables after each line of code has executed. Compare the values you see under the debugger with the values that you expect to see based on your paper based static analysis.
Long time reader, first time poster, I'm turning to you because I so many times found answers to my questions here, that I'm sure this one will be just a formality for this great community :)
My question might seem odd, even newbish, but I'm building an application for parsing text lines with urls.
A the beginning of the code, the first step is to determine how many urls there are in the text block. I do it by using the "copy" function from the beginning till the end of the text block, looking for the tag "a href=" tag.
This works fine.
Here is the code :
Tag := '<a href="';
Longueur := Length(ArtistNBSource);
Result := 0;
For i := 1 to Longueur do
begin
Copied := Copy(ArtistNBSource,i,Length(Tag));
if Copied = Tag then inc(Result)
end;
ARTIST_COUNT := Result;
Now, depending on the number of urls found, I'm going to loop through the text block.
What I would like to avoid is things like this...
if Result : 1 do
begin
some instruction
end
else if Result = 2
begin
other instruction
end
else if Result = 3....
...because with a maximum of 5 url possible in the text block, that would give me a veryyyyy long code.
What I imagined was this :
First of all, I declare variables up to the maximum known possible.
var
AUPOS1, AUPOS2, AUPOS3, AUPOS4, AUPOS5, ANPOS1, ANPOS2, ANPOS3, ANPOS4, ANPOS5, ia : Integer;
As the parsing patern is fixed, I imagined this :
For ia := 1 to ARTIST_COUNT do
begin
(AUPOS+IntToStr(ia)):= Pos('">', ArtistNBSource);
(AURL+IntToStr(ia)) := Copy(ArtistNBSource,11,(AUPOS+IntToStr(ia))-11);
Delete(ArtistNBSource,1,(AUPOS+IntToStr(ia))+1);
(ANPOS+IntTostr(ia)) := Pos('</a>', ArtistNBSource);
(ANAME+IntToStr(ia)) := Copy(ArtistNBSource,1,(ANPOS+IntToStr(ia))-1);
Delete(ArtistNBSource, 1,(ANPOS+IntToStr(ia))+4);
end;
The ia variable matching the number of loops AND the variables names for each loop, I thought I could auto increment the variables names and assign their values to the previously declared variables.
But of course this does not work :)
My question :
Do any of you see a solution out of this ?
Am I condemned to writing the 'if then' long sequence, or can I dynamically adjust variable names through the loop ?
Thank you all in advance for any comment that might give me a clue of what direction to follow.
Cheers
Mathmathou.
I would recommend having just one variable - a dictionary/hash table and then have the 'dynamic variable names' be keys in that dictionary and the values be what you would store in those 'dynamically named' variables.
Here is a tutorial about dictionaries:
http://beensoft.blogspot.se/2008/09/simple-generic-dictionary-tdictionary.html
procedure TForm1.Button1Click(Sender: TObject);
var
xlap,xlbook,xlsheet:variant;
y:integer;
begin
xlap:=createoleobject('excel.application');
xlbook:=xlap.workbooks.add;
xlap.visible:=true;
xlsheet:=xlbook.worksheets.add;
for y:=1 to 50 do
begin
xlsheet.cells[y,2].value:= concat('=IF(A',y,'>6,"Good","Bad")')
inc(y);
end;
end;
That's my code to make an Excel through Delphi. I want to input the IF formula into column B of the Excel, but there is error when I use the concat('=IF(A',y,'>6,"Good","Bad")').
May be I need another way to include y between those strings.
Any suggestion? Thanks before
Delphi has a format statement bit like sprintf printf in c, well nearly
xlsheet.cells[y,2].value:= format('=IF(A%d>6,"Good", "Bad")',[y])
%d is a place holder for an int. Look it up for loads of other stuff.
NB the variables you want to interpolate are assed in an array.
In addition to Tony's answer about Format, here are a couple of other approaches. (Format is great if you have mixed types or many values, but it carries some overhead that might not be needed.)
You can concatenate strings with a simple + - in fact, the Concat documentation says it does the same thing but is faster:
Temp := 'ing';
Str := 'Test' + Temp; // Str = 'Testing'
As your y variable is an integer, you'll need to convert it to a string first (note you don't need to Inc(y);, as the loop will do that already - it's automatically incremented from the starting value to the ending value on each pass through the loop):
for y:=1 to 50 do
begin
xlsheet.cells[y,2].value:= '=IF(A' + IntToStr(y) + '>6,"Good","Bad")';
end;
I am using (in a Delphi win32 application) OLE to perform search and replace in Word Documents.
THe user prepares a file with some textual tags enclosing them in "{" and "}" and saves the file.
Something like
Dear {NAME},
I want to tell you {WHAT_I_DID_LAST_WEEK}
Of course NAME and WHAT_I_DID_LAST_WEEK are DB fields that can be longer than 255.
So now by using Search and replace with OLE i get a STRING PARAMETER TOO LONG error (it seems 255 is the longest string usable there).
Is there an easy way to get rid of the problem?
Some home made solutons I thought of are:
1) truncate to 255 (good one ;) ) may be appending "..." at the end
2) for every "tag" that requires a replace of more than 255 chars I could first insert more tags like {WHAT_I_DID_LAST_WEEK_1}{WHAT_I_DID_LAST_WEEK_2}{WHAT_I_DID_LAST_WEEK_N} and then replace 255 chars at a time
(1) is a quick solution, at least user doesn't recieve the error, but of course it is not very good
(2) would probably work but it is a workaround, I would prefer another solution.
May be another solution is not use OLE Serach&Replace but use another function.
we use AWordApp.Selection.TypeText(strValue) and loop for replacing tags that have value string longer then 255 chars ...
var
AWordApp: OLEVariant;
...
AWordApp := CreateOleObject('Word.Application');
...
if (Length(strValue) > 255) then
begin
bFound := AWordApp.Selection.Find.Execute(params...);
while bFound do
begin
AWordApp.Selection.TypeText(strValue);
bFound := AWordApp.Selection.Find.Execute(params...);
end;
end;
regards
I am having a problem getting a list of fields from a query defined at run time by the users of my program. I let my users enter a SQL query into a memo control and then I want to let them go through the fields that will return and do such things as format the output, sum column values and so forth. So, I have to get the column names so they have a place to enter the additional information.
I would do fine if there were no parameters, but I also have to let them define filter parameters for the query. So, if I want to set the parameters to null, I have to know what the parameter's datatype is.
I am using Delphi 2006. I connect to a Firebird 2.1 database using the DBExpress component TSQLConnection and TSQLQuery. Previously, I was successful using:
for i := 0 to Qry.Params.Count - 1 do Qry.Params[i].value := varNull;
I discovered I had a problem when I tried to use a date parameter. It was just a coincidence that all my parameters up until then had been integers (record IDs). It turns out that varNull is just an enumerated constant with a value of 1 so I was getting acceptable results (no records) was working okay.
I only need a list of the fields. Maybe I should just parse the SELECT clause of the SQL statement. I thought setting Qry.Prepared to True would get me a list of the fields but no such luck. It wants values for the parameters.
If you have an idea, I would sure like to hear it. Thanks for any help.
Replied again 'coz I'm interested. My methods works (with my queries) because they have been pre-defined with the params' datatypes preset to the correct type:)
I'm not sure how you are expecting the query to know or derive the datatype of the param given that you are not even selecting the field that it operates against.
So I think your query setup and user input method will need more attention. I've just looked up how I did this a while ago. I do not use a parameterised query - I just get the "parameter values" from the user and put them directly into the SQL. So your sql would then read:
SELECT s.hEmployee, e.sLastName
FROM PR_Paystub s
INNER JOIN PR_Employee e ON e.hKey = s.hEmployee
WHERE s.dtPaydate > '01/01/2008'
therefore no parameter type knowledge is necessary. Does not stop your users entering garbage but that goes back to input control :)
Although a slightly different dataset type this is what I use with TClientDataset simple and effective :)
for i := 0 to FilterDataSet.Params.Count -1 do
begin
Case FilterDataSet.Params.Items[i].Datatype of
ftString:
ftSmallint, ftInteger, ftWord:
ftFloat, ftCurrency, ftBCD:
ftDate:
ftTime:
ftDateTime:
.
.
.
end;
end;
can you not do something similar with the query?
You guys are making this way too hard:
for i := 0 to Qry.Params.Count - 1 do begin
Qry.Params[i].Clear;
Qry.Params[i].Bound := True;
end;
I'm not sure what version of Delphi you are using. In the Delphi 2006 help under Variant Types, it says:
Special conversion rules apply to the
Borland.Delphi.System.TDateTime type
declared in the System unit. When a
Borland.Delphi.System.TDateTime is
converted to any other type, it
treated as a normal Double. When an
integer, real, or Boolean is converted
to a Borland.Delphi.System.TDateTime,
it is first converted to a Double,
then read as a date-time value. When a
string is converted to a
Borland.Delphi.System.TDateTime, it is
interpreted as a date-time value using
the regional settings. When an
Unassigned value is converted to
Borland.Delphi.System.TDateTime, it is
treated like the real or integer value
0. Converting a Null value to Borland.Delphi.System.TDateTime raises
an exception.
The last sentence seems important to me. I would read that as varNull cannot be converted to a TDateTime to put into the field, and hence you get the exception that you're experiencing.
It also implies that this is the only special case.
Couldn't you do something like:
for i := 0 to Qry.Params.Count - 1 do
begin
if VarType(Qry.Params[i].value) and varTypeMask = varDate then
begin
Qry.Params[i].value := Now; //or whatever you choose as your default
end
else
begin
Qry.Params[i].value := varNull;
end;
end;
What I ended up doing was this:
sNull := 'NULL';
Qry.SQL.Add(sSQL);
for i := 0 to Qry.Params.Count - 1 do begin
sParamName := Qry.Params[i].Name;
sSQL := SearchAndReplace (sSQL, ':' + sParamName, sNull, DELIMITERS);
end;
I had to write SearchAndReplace but that was easy. Delimiters are just the characters that signal the end of a word.
TmpQuery.ParamByName('MyDateTimeParam').DataType := ftDate;
TmpQuery.ParamByName('MyDateTimeParam').Clear;
TmpQuery.ParamByName('MyDateTimeParam').Bound := True;