Write to middle of text file - delphi

I am using Delphi 7.0 and need to be able to write to the middle of a text file. Here is an example of the text file my program creates.
~V
VERS. 2.0: CWLS LOG ASCII STANDARD - VERSION 2.0
WRAP. NO : One line per depth step
~W
STRT.Ft 10000 : Start Depth
STOP.Ft 11995 : Stop Depth
STEP.Ft 5 : Step
... A bunch of data follows.
Now, when I initially write the values to the text file I would like to remember the file position of the STOP value of 11995 in the above example. Now, some time later my data will change and I would like to move to the position of 11995 and write the new stop value. That way I don't need to rewrite everything in the file.

With standard Pascal File I/O you can only read, rewrite or append data in the file.
If you want to change data in a certain position of the file you can use TFileStream:
var
f:TFileStream;
PositionStr:String;
PositionValue:Integer;
begin
f := TFileStream.Create('filename.log',fmOpenReadWrite);
PositionValue := 200000; // new STOP Position
PositionStr := IntToStr(PositionValue);
f.Seek(100,soFromBeginning); // Data will be overwritten from position 100
f.WriteBuffer(PositionStr[1], length(PositionStr));
f.free;
end;

Related

Modify values programmatically SPSS

I have a file with more than 250 variables and more than 100 cases. Some of these variables have an error in decimal dot (20445.12 should be 2.044512).
I want to modify programatically these data, I found a possible way in a Visual Basic editor provided by SPSS (I show you a screen shot below), but I have an absolute lack of knowledge.
How can I select a range of cells in this language?
How can I store the cell once modified its data?
--- EDITED NEW DATA ----
Thank you for your fast reply.
The problem now its the number of digits that number has. For example, error data could have the following format:
Case A) 43998 (five digits) ---> 4.3998 as correct value.
Case B) 4399 (four digits) ---> 4.3990 as correct value, but parsed as 0.4399 because 0 has been removed when file was created.
Is there any way, like:
IF (NUM < 10000) THEN NUM = NUM / 1000 ELSE NUM = NUM / 10000
Or something like IF (Number_of_digits(NUM)) THEN ...
Thank you.
there's no need for VB script, go this way:
open a syntax window, paste the following code:
do repeat vr=var1 var2 var3 var4.
compute vr=vr/10000.
end repeat.
save outfile="filepath\My corrected data.sav".
exe.
Replace var1 var2 var3 var4 with the names of the actual variables you need to change. For variables that are contiguous in the file you may use var1 to var4.
Replace vr=vr/10000 with whatever mathematical calculation you would like to use to correct the data.
Replace "filepath\My corrected data.sav" with your path and file name.
WARNING: this syntax will change the data in your file. You should make sure to create a backup of your original in addition to saving the corrected data to a new file.

FindFirst and question mark

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.

Delete line in TSTringlist

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));

How to correctly set NumberFormat property when automating different localized versions of Excel

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);

Correct Media Type settings for a DirectShow filter that delivers Wav audio data?

I am using Delphi 6 Pro with the DSPACK DirectShow component library to create a DirectShow filter that delivers data in Wav format from a custom audio source. Just to be very clear, I am delivering the raw PCM audio samples as Byte data. There are no Wave files involved, but other Filters downstream in my Filter Graph expect the output pin to deliver standard WAV format sample data in Byte form.
Note: When I get the data from the custom audio source, I format it to the desired number of channels, sample rate, and bits per sample and store it in a TWaveFile object I created. This object has a properly formatted TWaveFormatEx data member that is set correctly to reflect the underlying format of the data I stored.
I don't know how to properly set up the MediaType parameter during a GetMediaType() call:
function TBCPushPinPlayAudio.GetMediaType(MediaType: PAMMediaType): HResult;
.......
with FWaveFile.WaveFormatEx do
begin
MediaType.majortype := (1)
MediaType.subtype := (2)
MediaType.formattype := (3)
MediaType.bTemporalCompression := False;
MediaType.bFixedSizeSamples := True;
MediaType.pbFormat := (4)
// Number of bytes per sample is the number of channels in the
// Wave audio data times the number of bytes per sample
// (wBitsPerSample div 8);
MediaType.lSampleSize := nChannels * (wBitsPerSample div 8);
end;
What are the correct values for (1), (2), and (3)? I know about the MEDIATYPE_Audio, MEDIATYPE_Stream, and MEDIASUBTYPE_WAVE GUID constants, but I am not sure what goes where.
Also, I assume that I need to copy the WaveFormatEx stucture/record from the my FWaveFile object over to the pbFormat pointer (4). I have two questions about that:
1) I assume that should use CoTaskMemAlloc() to create a new TWaveFormatEx object and copy my FWaveFile object's TWaveFormatEx object on to it, before assigning the pbFormat pointer to it, correct?
2) Is TWaveFormatEx the correct structure to pass along? Here is how TWaveFormatEx is defined:
tWAVEFORMATEX = packed record
wFormatTag: Word; { format type }
nChannels: Word; { number of channels (i.e. mono, stereo, etc.) }
nSamplesPerSec: DWORD; { sample rate }
nAvgBytesPerSec: DWORD; { for buffer estimation }
nBlockAlign: Word; { block size of data }
wBitsPerSample: Word; { number of bits per sample of mono data }
cbSize: Word; { the count in bytes of the size of }
end;
UPDATE: 11-12-2011
I want to highlight one of the comments by #Roman R attached to his accepted reply where he tells me to use MEDIASUBTYPE_PCM for the sub-type, since it is so important. I lost a significant amount of time chasing down a DirectShow "no intermediate filter combination" error because I had forgotten to use that value for the sub-type and was using (incorrectly) MEDIASUBTYPE_WAVE instead. MEDIASUBTYPE_WAVE is incompatible with many other filters such as system capture filters and that was the root cause of the failure. The bigger lesson here is if you are debugging an inter-Filter media format negotiation error, make sure that the formats between the pins being connected are completely equal. I made the mistake during initial debugging of only comparing the WAV format parameters (format tag, number of channels, bits per sample, sample rate) which were identical between the pins. However, the difference in sub-type due to my improper usage of MEDIASUBTYPE_WAVE caused the pin connection to fail. As soon as I changed the sub-type to MEDIASUBTYPE_PCM as Roman suggested the problem went away.
(1) is MEDIATYPE_Audio.
(2) is typically a mapping from FOURCC code into GUID, see Media Types, Audio Media Types section.
(3) is FORMAT_WaveFormatEx.
(4) is a pointer (typically allocated by COM task memory allocator API) to WAVEFORMATEX structure.
1) - yes you should allocate memory, put valid data there, by copying or initializing directly, and put this pointer to pbFormat and structure size into cbFormat.
2) - yes it looks good, it is defined like this in first place: WAVEFORMATEX structure.

Resources