I need to delete all files, which names are started with "a", then three arbitrary letters and ".txt" extension like "a123.txt". Here is the code:
var
sFileMask: string;
tsrMessage: TSearchRec;
begin
sFileMask := 'c:/a???.txt';
if SysUtils.FindFirst(sFileMask, 0, tsrMessage) = 0 then
begin
repeat
ShowMessage(tsrMessage.Name);
until FindNext(tsrMessage) <> 0;
SysUtils.FindClose(tsrMessage);
end;
end;
I always thought that the question mark means one and only one character, but to my surprise found that this code returns "a.txt", "a1.txt" and "a123.txt" file names. Is there a simple way to modify the code for it to seek only files like "a123.txt"?
The simplest solution for your specific need is to replace this:
ShowMessage(tsrMessage.Name);
with this
if length(tsrMessage.Name)=8 then ShowMessage(tsrMessage.Name);
this will ensure that the length of the file name is exactly four characters + the period + the extension. Like David says, there's no way to have the API do this kind of filtering, so you'll have to do it yourself, but in your particular case, there's no need to enumerate the entire directory. You may at least let the API do the filtering it can do, and then do your own filtering on top of it.
EDIT: If you need to ensure that the three characters following the "a" are digits, you can do it this way:
if (length(tsrMessage.Name)=8) and tsrMessage[2].IsDigit and tsrMessage[3].IsDigit and tsrMessage[4].IsDigit then ShowMessage(tsrMessage.Name);
provided you are using a modern compiler (you'll need to include the "Characters" unit). Also take note that if you are compiling a mobile version, you'll need to use index [1], [2] and [3] instead, as they start index at 0 for strings.
If you are using an older version, you can do it like this:
function IsDigit(c : char) : boolean;
begin
Result:=(c>='0') and (c<='9')
end;
if (length(tsrMessage.Name)=8) and IsDigit(tsrMessage[2]) and IsDigit(tsrMessage[3]) and IsDigit(tsrMessage[4]) then ShowMessage(tsrMessage.Name);
This behaviour is as designed. It is explained by Raymond Chen here: How did wildcards work in MS-DOS?
You will see the exact same behaviour from the command interpreter.
C:\Desktop>dir a???.txt
Volume in drive C has no label.
Volume Serial Number is 20DA-7FEB
Directory of C:\Desktop
26/06/2016 14:03 6 a.txt
26/06/2016 14:03 6 a1.txt
26/06/2016 14:03 6 a12.txt
26/06/2016 14:03 6 a123.txt
4 File(s) 24 bytes
0 Dir(s) 286,381,445,120 bytes free
There is no way to persuade FindFirstFile (the API that is behind the RTL's FindFirst on Windows) to behave the way you wish. Your best option is to enumerate the entire directory, and perform your own filtering using your chosen pattern matching algorithm.
Related
Kenneth is a string. Let's say it contains 'justabcsome123texthaha'.
I know this already:
To find text:
if(pos('bcsome12',Kenneth) > 0) then
To check length:
if(Length('Kenneth') > 10) then
Question 1:
I want to find 'texthaha', but only if it is at the end of the string.
if(pos('texthaha',Kenneth) > 0) then
Sadly this will find it anywhere, even if it is in the middle. Is there a simple way?
Question 2:
Is there a simple way to do a search, but with a * (any character in between)?
For example, if I want to search for bcsome1*3text and I don't care what character the * is. I think it's called a wildcard, isn't it?
if(pos('bcsome1'*'3text',Kenneth) > 0) then
I know the above doesn't work. but is there a similar way?
Edit: Might be of importance: **Delphi version used is very old, not sure of the version, but it's from year 2006.
There are functions EndsStr() and EndsText() (the last is case-insensitive) in the StrUtils unit
But, you easily could provide the needed functionality with known functions (Pos also has overloaded version with the third parameter in fresh Delphi):
NPos = Length(S) - Length(Sub) + 1;
if PosEx(Sub, S, NPos) = NPos then...
or variant proposed by #Sertac Akyuz:
if Copy(S, NPos, Length(Sub)) = Sub ...
The second problem might be solved with function like MatchesMask()
if MatchesMask(Kenneth, '*bcsome1*3text*')...
To get the last occurrence, try LastDelimiter (see help). For wildcards, see this post.
Hello i am writing some values to a stringlist. And would like to delete a value from the string list.
Currently I write to the string list like this.
FGamePlay.Locations.strings[0] := ('NumberOfLocations='+inttostr(NOL+1)); //add one to total
FGameplay.Locations.Add(inttostr(Position.x)+inttostr(Position.Y)+'=pos'); //add the location to list
This will return me a list like so
INDEX VALUE
[0] NumberOfLocations=4
[1] 23=pos
[2] 34=pos
[3] 24=pos
[4] 52=pos
Now i try to delete it like this
FGamePlay.Locations.Delete(FGamePlay.Locations.IndexOf(inttostr(ePosition.x)+inttostr(ePosition.Y)));
were ePosition.x + ePosition.Y will equal 23, 34,24,or 52. Thus it should delete the that line but instead when i add this delete line i get index out of bounds -1. I did stop the code just before this line and looked at Locations() and it had all these numbers in there. Also looked at epostion and the X,Y values were 34, thus correct too. Any idea?
thanks
Glen
When you uses the IndexOf function you must pass the exact string to find, in this case since you are adding the strings in this way
FGameplay.Locations.Add(inttostr(Position.x)+inttostr(Position.Y)+'=pos');
You must add the =pos to the string to search, something like
LIndex:=FGamePlay.Locations.IndexOf(inttostr(ePosition.x)+inttostr(ePosition.Y)+'=pos');
If LIndex>=0 then
FGamePlay.Locations.Delete(LIndex);
As RRUZ says, the string you are looking for to delete is missing the "=pos" suffix.
In order to debug this more effectively, you should break up the code a bit more. If you had this equivalent code:
str := inttostr(ePosition.x)+inttostr(ePosition.Y);
pos := FGamePlay.Locations.IndexOf(str);
FGamePlay.Locations.Delete(pos);
You would get an error on the pos := line, which would allow to to see the source of the error much more easily.
You could also consider making a function like:
function MakePosString(Position : Point);
begin
Result := inttostr(ePosition.x)+inttostr(ePosition.Y)+'=pos';
end;
Then you can call that function instead of reimplementing that code and you are guaranteed that your strings will be consistent.
Whilst I agree with everything everyone else has said about considering using a better data structure for the job at hand, I think for the sake of anyone with a similar problem in the future it is worth mentioning something that nobody else yet identified.
Your expression:
IntToStr(ePosition.x) + IntToStr(ePosition.y)
identifies the NAME of an entry in your string list, when considered as a name/value list. That is, a TStringList where each item is of the form "name=value". Whilst one way to fix your code is to append the rest of the string ('=pos') this of course only works when the "value" part of every named value is always "pos".
If there is the possibility that the "pos" value could be different or unknown for a given named value, then you can still find it by looking up the index of the item using just the name part:
itemName := IntToStr(ePosition.x) + IntToStr(ePosition.y);
itemIndex := fGamePlay.Locations.IndexOfName(itemName);
if itemIndex > -1 then
fGamePlay.Locations.Delete(IndexOfName(itemName));
I've ran into the following problem:
When automating Excel via OLE from my Delphi program and trying to set a cell's NumberFormat property, Excel is expecting the format string in a localized format.
Normally, when checking the formatting by recording a macro in Excel, Excel is expecting it like this:
Cells(1, 2).NumberFormat = "#,##0.00"
That means the thousands separator is "," and the decimal separator is ".".
In reality, I'm using a localized version of Excel. In my locale, the thousands separator is " " and the decimal separator is ",".
So whenever setting the NumberFormat from my Delphi program I need specify it like "# ##0,00".
My question is: Obviously, if I hardcode these values in my program there is going to be an exception when my program is used with an English or another differently localized version of Excel. Is there a "universal" way to set the NumberFormat property? (using the default English locale?)
Thanks!
Update: I've found a more elegant way to do it on this page:
http://www.delphikingdom.com/asp/viewitem.asp?catalogid=920&mode=print
It's in Russian (which I don't speak too) but you can easily understand the code.
In Excel you have two Fields:
NumberFormat
NumberFormatLocal
NumberFormat takes the format always locale invariant in the american standard and NumberFormatLocal expects the format with the set locale.
For example
Sub test()
Dim r As Range
Set r = ActiveWorkbook.ActiveSheet.Range("$A$1")
r.NumberFormat = "#,##0.00"
Set r = ActiveWorkbook.ActiveSheet.Range("$A$2")
r.NumberFormat = "#.##0,00"
Set r = ActiveWorkbook.ActiveSheet.Range("$A$3")
r.NumberFormatLocal = "#,##0.00"
Set r = ActiveWorkbook.ActiveSheet.Range("$A$4")
r.NumberFormatLocal = "#.##0,00"
End Sub
With german settings (decimal sep: , and thousand sep: .) gives you correct formatted numbers for $A$1 and $A$4. You can test it, if you change your regional settings in windows to anything you like and try, if your formatting is working.
Assuming you use Delphi 5 and have code to start Excel like this (and have access to ComObj.pas):
var
oXL, oWB, oSheet : Variant;
LocaleId : Integer;
begin
oXL := CreateOleObject('Excel.Application');
oXL.Visible := True;
oWB := oXL.Workbooks.Add;
oSheet := oWB.ActiveSheet;
oSheet.Range['$A$1'].NumberFormatLocal := '#.##0,00';
oSheet.Range['$A$2'].NumberFormatLocal := '#,##0.00';
LocaleID:= DispCallLocaleID($0409);
try
oSheet.Range['$A$3'].NumberFormat := '#.##0,00';
oSheet.Range['$A$4'].NumberFormat := '#,##0.00';
finally
DispCallLocaleId( LocaleId);
end;
end;
then by default every call goes through ComObj.VarDispInvoke which calls ComObj.DispatchInvoke. There you find the call to Dispatch.Invoke which gets as third parameter the lcid. This is set to 0. You can use the technique shown in the first link in the comment to this, to create your own unit and copy all code from ComObj to your own unit (or modify ComObj directly). Just don't forget to set the VarDispProc variable in the initialization of the unit. The last part seems not work in all cases (probably depends on the order of the modules), but you can set the variable in your code:
VarDispProc := #VarDispInvoke;
where you must place VarDispInvoke into the interface section of your ComObj copy module.
The code of the first link does not work directly as it modifies a different method which is not called in the above Delphi sample.
And it is enough to change the locale for the numberformat call (to avoid side effects).
The above example together with the described modifications works for my german excel correct. Without the modification or the call to DispCallLocaleId I see the same problem as you describe.
You can let excel manage this option To avoid the difference in other system :
.....NumberFormat :='#'+Excel.ThousandsSeparator+'##0'+Excel.DecimalSeparator+'00';
You can also direct set a property value
SetDispatchPropValue(oSheet,
'Range['$A$1'].NumberFormatLocal',$0409);
When searching for files with FindFirst() I get an attribute value in the TSearchRec.Attr field of 2080. It is not specified in the help as there are only these values available and no combination of them yields 2080:
1 faReadOnly
2 faHidden
4 faSysFile
8 faVolumeID
16 faDirectory
32 faArchive
64 faSymLink
71 faAnyFile
Does anyone know what 2080 means and why I get that attribute value? The OS is XP embedded.
It turns out that the file found by FindFirst() was compressed and thus had the compressed bit set. Took me a while to figure out and I could not find a reference on the web that stated the actual value of TSearchRec.Attr when the compressed bit is set. Unclicking "Compress file" in the files advanced properties did the trick.
Attributes in TSearchRec map directly to the Windows file attributes used with the TWin32FindData record from FindFirstFile.
In hex (always render bit fields in hex, not decimal), 2080 is $0820, where it's clear there are two bits set. The lower bit corresponds to File_Attribute_Archive, or Delphi's faArchive, and the upper bit corresponds to File_Attribute_Compressed. It has no equivalent in the units that come with Delphi, but you can use the JclFileUtils.faCompressed symbol from the JCL.
In JclFileUtils unit from Jedi Code Library I found:
faNormalFile = $00000080;
...
faNotContentIndexed = $00002000;
If 2080 is in hex then this is it.
Look also at: http://www.tek-tips.com/viewthread.cfm?qid=1543818&page=9
EDIT:
While 2080 id decimal, and 2080 dec = 820 hex then attributes are combination of:
faArchive = $00000020;
faCompressed = $00000800;
This will extract the faDirectory bit and you dont have to worry about the compression bit set or not.
if ((sr.Attr AND faDirectory) <> 0) then
begin
.......
end;
I've been working on Issue 14 on the PascalScript scripting engine, in which using a Goto command to jump out of a Case block produces a compiler error, even though this is perfectly valid (if ugly) Object Pascal code.
Turns out the ProcessCase routine in the compiler calls HasInvalidJumps, which scans for any Gotos that lead outside of the Case block, and gives a compiler error if it finds one. If I comment that check out, it compiles just fine, but ends up crashing at runtime. A disassembly of the bytecode shows why. I've annotated it with the original script code:
[TYPES]
<SNIPPED>
[VARS]
Var [0]: 27 Class TFORM
Var [1]: 28 Class TAPPLICATION
Var [2]: 11 S32 //i: integer
[PROCS]
Proc [0] Export: !MAIN -1
{begin}
[0] ASSIGN GlobalVar[2], [1]
{ i := 1;}
[15] PUSHTYPE 11(S32) // 1
[20] ASSIGN Base[1], GlobalVar[2]
{ case i of}
[31] PUSHTYPE 25(U8) // 2
{ 0:}
[36] COMPARE into Base[2]: [0] = Base[1]
[57] COND_NOT_GOTO currpos + 5 Base[2] [72]
{ end;}
[67] GOTO currpos + 41 [113]
{ 1:}
[72] COMPARE into Base[2]: [1] = Base[1]
[93] COND_NOT_GOTO currpos + 10 Base[2] [113]
{ goto L1;}
[103] GOTO currpos + 8 [116]
{ end;}
[108] GOTO currpos + 0 [113]
{ end; //<-- case}
[113] POP // 1
[114] POP // 0
{ Exit;}
[115] RET
{L1:
Writeln('Label L1');}
[116] PUSHTYPE 17(WideString) // 1
[121] ASSIGN Base[1], ['????????']
[144] CALL 1
{end.}
[149] POP // 0
[150] RET
Proc [1]: External Decl: \00\00 WRITELN
The "goto L1;" statement at 103 skips the cleanup pops at 113 and 114, which leaves the stack in an invalid state.
Delphi doesn't have any trouble with this, because it doesn't use a calculation stack. PascalScript, though, is not as fortunate. I need some way to make this work, as this pattern is very common in some legacy scripts from a much simpler system with little in the way of control structures that I've translated to PascalScript and need to be able to support.
Anyone have any ideas how to patch the codegen so it'll clean up the stack properly?
IIRC the goto rules in classic pascals were:
jumps are only allowed out of a block (iow from a higher to a lower nesting level on the "same" branch of the tree)
from local procedures to their parents.
The later was afaik never supported by Borland derived Pascals, but the first still holds.
So you need to generate exiting code like Martin says, but possibly it can be for multiple block levels, so you can't have a could codegeneration for each goto, but must generate code (to exit the precise number of needed blocks).
A typical test pattern is to exit from multiple nested ifs (possibly within a loop) using a goto, since that was a classic microoptimization that was faster at least up to D7.
Keep in mind that the if evaluation(s) and the begin..end blocks of their branches might have generated temps that need cleanup.
---------- added later
I think the codegenerator needs a way to walk the scopes between the goto and its endpoint, generating the relevant exit code for blocks along the way. That way a fix works for the general case and not just this example.
Since you can only jump out of scopes, and not into it that might not that be that hard.
IOW generate something that is equivalent to (for a hypothetical double case block)
Lgoto1gluecode:
// exit code first block
pop x
pop y
// exit code first block
pop A
pop B
goto real_goto_destination
Additional analysis can be done. E.g. if there is only one scope, and it has already a cleanup exit label, you can jump directly. If you know for certain that the above pop's are only discarded values (and not saves of registers) you can do them at once with add $16,%esp (4*4 byte values) etc.
The straightforward solution would be:
When generating a GOTO for goto statement, prefix the GOTO with the same cleanup code that comes before RET.
It looks to me like the calculation of how far to jump forward is the problem. I would have to spend some time looking at the implementation of the parser to help further, but my guess would be that additional handling must be performed when using a goto and there are values on the stack AND the goto would be placed after those values would be removed from the stack. Of course to determine this you would need to save the current location being parsed (the goto) and the forward parse to the target location watching for stack changes, and if so then to either adjust the goto location backwards, or inject the code as Martin suggested.