Solved

problem with manipulation of strings

Posted on 1998-12-19
19
362 Views
Last Modified: 2008-03-10

Hi experts,

Imagine the following situation: With the help of a entry form
I give the user the possibility to input some data. After editing is
finished, the  user is prompted for a filename where the input data will
be stored. After this the user is asked, if he wants to create another input file.
If he answers with yes, the entry form is displayed again on the screen, and
the input szenario starts again. As soon as the question is answered with
"no", the specified filenames are passed to an external executable as
command line parameters.
The big problem lies in the handling of the specified filenames.
This is very complicated, because you don't know, how many inputfiles
the user will create during one session at runtime, so the concrete number
of filenames the user will specify is unknown at program start.
You could set a limit within the source code to let us say a maximum of
100 allowed filenames. In this case you could easily handle the specified
filenames with the help of an static array of string, where you know the
exact number of elements. But as I prefer a dynamic solution and try to write
the specified filenames to a dynamic buffer. In C, you would use malloc.
Are there functions like "malloc" available in Delphi, too?
The next problem is that you have to find out the single elements (command
line parameters) of this composed dynamic buffer, containing the filenames.
In C you would call the function "strtok". Is there such a function in Delphi
available, too?

To illustrate what I want to achieve, I added a pseudo code.
Any suggestions, how this could be done with Delphi 3.0 ?

filename = Save_strings_from_Entry_Form_to_textfile();
      length = strlen (filename);
      if (buffer == NULL)
      {                  
        buffer = malloc (length + 1);
        if (buffer == NULL) /* no succesful memory allocation */
          {
            errormessage();
            exit (1);
          }
        strcpy (buffer, filename);
      }
      else /*memory allocation was succesful */
      {                  
        char *newbuf;
        newbuf = malloc (strlen (buffer) + 1 + strlen (filename) + 1);
        
        if (newbuf == NULL)
          {
            errormessage();
            exit (1);
          }
        strcpy (newbuf, buffer);
        strcat (newbuf, " ");
        strcat (newbuf, filename);
        buffer = newbuf;
      }
      Message("Create one more input file (y/n)" ?);
      } while (chr == 'Y');
    part = (char *) strtok (buffer, " ");
    while (part != NULL)
    {
      if ((fp = fopen (part, "r")) != NULL)  /* send the specified */
      {                                    /* filenames to mathprogram.exe*/  
        p = searchpath ("mathprogram.EXE");  
        exec(p, part, &Ret, "file.TMP");
        fclose (fp);
      }
      else
      {
        counter = counter + 1;
        
      }
      part = (char *) strtok (NULL, " ");
    }
message ("all inputfiles were examined by mathprogram.exe");
message ("end of all calculations");  
free(buffer);
}

0
Comment
Question by:mathes
  • 7
  • 5
  • 4
  • +1
19 Comments
 
LVL 4

Accepted Solution

by:
dwwang earned 100 total points
Comment Utility
Of course you can translate above code to Delphi with no problem:

 C -->        Delphi
malloc --> getmem
strlen-->strlen
char * --> PChar
strcat --> strcat
Strcpy --> strCopy

and I don't see the declaration of part, hence not sure about strtok.
If you can give exact description of these two things, I can give the answer too.

Regards,
Wang
0
 
LVL 4

Expert Comment

by:dwwang
Comment Utility
Bye the way, check the help of those functions/procedures whose name begin with 'str', and you can get more infomation.
0
 
LVL 3

Expert Comment

by:Matvey
Comment Utility
I hope I understood you right... Dealing with lists of strings is very simple in Delphi:

Example 1 (TStringList):
_________________________________________________________________________
var
  list1: TStringList;
begin
  list1 := TStringList.Create;
  list1.Add('c:\filename1');
  list1.Add('c:\filename2');
  list1.Add('c:\filename3');
  .....................
  list1.Free;
end;
_________________________________________________________________________


example 2 (variant arrays)
_________________________________________________________________________
var
 A: Variant;
 I: Integer;
begin
 A := VarArrayCreate([0, 4], varOleStr);
 for I := 0 to 4 do A[I] := 'Initial';
 ...........................................
 VarArrayRedim(A, 9);
 for I := 5 to 9 do A[I] := 'Additional';
 ...
end;
_________________________________________________________________________



example 3 (PChar. This is just an endless string.):
_________________________________________________________________________
var
  p: PChar;
begin
 p := 'filename1';
 p := StrCat(p, ' ' + 'filename2');
 p := StrCat(p, ' ' + 'filename3');
 ..................................
end;
_________________________________________________________________________

--Matvey
0
 

Author Comment

by:mathes
Comment Utility

Dear experts,

thank you all for your hints.

I like especially example 3 from Matvey:

 example 3 (PChar. This is just an endless string.):
         _________________________________________________________________________
         var
           p: PChar;
         begin
          p := 'filename1';
          p := StrCat(p, ' ' + 'filename2');
          p := StrCat(p, ' ' + 'filename3');
          ..................................
         end;
         _________________________________________________________________________

     
This comment is very helpful for me, however so far it is still only the first step
but not an complete answer to my question.

Let us assume I created a total string p:= "file1.txt file2.txt file3.txt"
(The single filenamesfiles are seprated by 1 blank!)

As I later want to pass the single files as command line parameters to an external *.exe file,
I now need must split this total string into its single sub strings "file1.txt","file2.txt"
and file3.txt.

In C I would use the strtok function to achieve this goal.
How can this be done in Delphi?

It is something like this:

for all tokens in p do:

myexefile.exe file1to3.txt

Note that you don't know the exact number of files which you find in p before runtime!!!

Any sugestions for a solution?
 
With kind regards

Mathes


 


0
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
Mathes,

hmmm. First of all you should think about the paremeter stuff, because AFAIK, the parameter length in windows is limited somehow. Don't know the exact length. Perhaps you should put the strings in the clipboard instead or pass them via messages or write them to registry or create a file mapping object or ...

However, why using PChar? Now that you work with Delphi, you should use Delphi's fantastic strings. I think, they're much better to handle than C's 0-terminated strings.

var s1 : string;
begin
  s1:='file1.txt'; s1:=s1+'|file2.txt'; s1:=s1+'|file3.txt';
end;

Perhaps my string functions will help you.
Examples:
SubStrCount("file1.txt|file2.txt|file3.txt") -> 3
RetSubStr("file1.txt|file2.txt|file3.txt",2) -> file2.txt

function FindStr(const subStr,str: string; fromPos,toPos: cardinal) : cardinal; assembler
asm      //            EAX    EDX          ECX     [ESP+8]              EAX
        TEST    EAX,EAX                  // subStr empty ?
        JE      @@noWork
        TEST    EDX,EDX                  // str empty ?
        JE      @@fail4
        TEST    ECX,ECX                  // fromPos = 0 ?
        JE      @@fail4
        PUSH    EBX
        MOV     EBX,ECX                  // EBX = fromPos
        MOV     ECX,[ESP+12]             // ECX = toPos  (+4 w/ PUSH EBX)
        TEST    ECX,ECX                  // toPos = 0 ?
        JE      @@fail3
        PUSH    ESI
        PUSH    EDI
        MOV     ESI,EAX                  // ESI = substr
        MOV     EDI,EDX                  // EDI = str
        CMP     EBX,ECX                  // fromPos > toPos ?
        JA      @@backwards
@@forwards:
        MOV     EDX,[EDI-4]              // EDX = Length(substr)
        CMP     EBX,EDX                  // fromPos > Length(str) ?
        JA      @@fail2
        CMP     ECX,EDX                  // toPos <= Length(str) ?
        JNA     @@toPosOk
        MOV     ECX,EDX                  // toPos = Length(str)
@@toPosOk:
        MOV     EDX,[ESI-4]              // EDX = Length(substr)
        DEC     EDX                      // EDX = Length(substr) - 1
        JS      @@fail2                  // EDX < 0 ?
        PUSH    EDI                      // remember str position to calculate index
        DEC     EBX                      // dec(fromPos)
        ADD     EDI,EBX                  // "Delete (str, 1, fromPos - 1)"
        SUB     ECX,EBX                  // toPos := toPos - fromPos + 1
        SUB     ECX,EDX                  // #positions in str to look at = Length(str) - Length(substr) + 1
        JBE     @@fail1                  // #positions <= 0 ?
        MOV     AL,[ESI]                 // AL = first char of substr
        INC     ESI                      // Point ESI to 2'nd char of substr
@@fwLoop:
        REPNE   SCASB
        JNE     @@fail1
        MOV     EBX,ECX                  // save outer loop counter
        PUSH    ESI                      // save outer loop substr pointer
        PUSH    EDI                      // save outer loop str pointer
        MOV     ECX,EDX
        REPE    CMPSB
        POP     EDI                      // restore outer loop str pointer
        POP     ESI                      // restore outer loop substr pointer
        JE      @@fwFound
        MOV     ECX,EBX                  // restore outer loop counter
        JMP     @@fwLoop
@@fwFound:
        POP     EDX                      // restore pointer to first char of str
        MOV     EAX,EDI                  // EDI points of char after match
        SUB     EAX,EDX                  // the difference is the correct index
        POP     EDI
        POP     ESI
        POP     EBX
        JMP     @@noWork
@@backwards:
        MOV     EDX,[EDI-4]              // EDX = Length(substr)
        CMP     ECX,EDX                  // toPos > Length(str) ?
        JA      @@fail2
        CMP     EBX,EDX                  // fromPos <= Length(str) ?
        JNA     @@fromPosOk
        MOV     EBX,EDX                  // fromPos = Length(str)
@@fromPosOk:
        MOV     EDX,[ESI-4]              // EDX = Length(substr)
        DEC     EDX                      // EDX = Length(substr) - 1
        JS      @@fail2                  // EDX < 0 ?
        MOV     EAX,EDI                  // remember str position to calculate index
        ADD     EAX,EDX                  // add backwards calculation
        SUB     EAX,2
        PUSH    EAX
        DEC     ECX                      // dec(toPos)
        ADD     EDI,ECX                  // "Delete (str, 1, toPos - 1)"
        SUB     EBX,ECX                  // fromPos := fromPos - toPos + 1
        MOV     ECX,EBX                  // swap (fromPos, lastPos)
        ADD     EDI,ECX
        DEC     EDI
        ADD     ESI,EDX
        SUB     ECX,EDX                  // #positions in str to look at = Length(str) - Length(substr) + 1
        JBE     @@fail1                  // #positions <= 0 ?
        MOV     AL,[ESI]                 // AL = first char of substr
        DEC     ESI                      // Point ESI to 2'nd char of substr
        STD
@@bwLoop:
        REPNE   SCASB
        JNE     @@fail0
        MOV     EBX,ECX                  // save outer loop counter
        PUSH    ESI                      // save outer loop substr pointer
        PUSH    EDI                      // save outer loop str pointer
        MOV     ECX,EDX
        REPE    CMPSB
        POP     EDI                      // restore outer loop str pointer
        POP     ESI                      // restore outer loop substr pointer
        JE      @@bwFound
        MOV     ECX,EBX                  // restore outer loop counter
        JMP     @@bwLoop
@@bwFound:
        POP     EDX                      // restore pointer to first char of str + backwards calculation
        MOV     EAX,EDI                  // EDI points of char after match
        SUB     EAX,EDX                  // the difference is the correct index
        CLD
        POP     EDI
        POP     ESI
        POP     EBX
        JMP     @@noWork
@@fail0:
        CLD
@@fail1:
        POP     EDX                      // get rid of saved str pointer
@@fail2:
        POP     EDI
        POP     ESI
@@fail3:
        POP     EBX
@@fail4:
        XOR     EAX,EAX
@@noWork:
end;

function SubStrCount(str: string) : cardinal;
var c1 : cardinal;
begin
  result:=0;
  if str='' then exit;
  c1:=0; repeat inc(result); c1:=findStr('|',str,c1+1,maxCard) until c1=0;
end;

function RetSubStr(str: string; index: cardinal) : string;
var c1,c2 : cardinal;
begin
  result:='';
  if (str='') or (index<1) then exit;
  c2:=0;
  repeat
    dec(index);
    c1:=c2+1; c2:=findStr('|',str,c1,maxCard);
  until index=0;
  if c2=0 then result:=copy(str,c1,maxInt) else result:=copy(str,c1,c2-c1);
end;

Regards, Madshi.
0
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
Perhaps the functions look a little bit overdressed (especially the "FindStr" function). I'm using the "FindStr" function in a lot of other string manipulation functions, so I decided to build it in assembler.

Examples for FindStr:
FindStr('test','blabla test blabla test blabla',1,maxInt) -> 8
FindStr('test','blabla test blabla test blabla',9,maxInt) -> 20
FindStr('test','blabla test blabla test blabla',maxInt,1) -> 20
FindStr('test','blabla test blabla test blabla',19,1) -> 8

Perhaps you'll find this function useful, too...  :-)

Regards... Madshi.
0
 

Author Comment

by:mathes
Comment Utility


Dear Madshi,

thank you for providing me with your string routines. I am not sure if they are helpful for me. As far as I have understood your routines they work under the precondition that you already know the exact number of substrings. But this exact number is unknow when in my case the external executable is launched.
I give a concrete example: Let us say we have a total string total:= "Text1.txt text2.txt text3.txt".
How would you launch three seesions of notepad  [shellexecute (notepad.exe text1.txt);
shellexecute (notepad.exe text2.txt); shellexecute (notepad.exe text3.txt);] by using your string routines?

With kind regards

Mathes


0
 
LVL 20

Expert Comment

by:Madshi
Comment Utility
Hi Mathes,

my function SubStrCount gives you the number of substrings. So you could do something like this:

procedure StartSubStrs(s1: string);
var i1,i2 : integer;
    s1    : string;
begin
  i2:=SubStrCount(s1);
  // You should ask the number of the subStrs only once (=faster)
  for i1:=1 to i2 do
    ShellExecute(...,s1,...);  
  // You can start the file directly instead of starting notepad with the file as a parameter
end;

Please note, that my functions need a '|' instead of a space between the file names. BTW, this is much better, because there could be spaces in a filename, but there can't be a '|' in a filename.

Regards... Madshi.

P.S: Of course, if you like my answer the most, I would like to get the points...  :-)))
0
 
LVL 4

Expert Comment

by:dwwang
Comment Utility
Why not use stringlist, just as madshi suggested?

You know, when you want to translated your programm from C to Delphi, you just think of manipulate those PChar's and #0's. However, in Delphi there are much more powerful structures/types such as stringlist, etc. If you use them, things could be easy enough. In adition for madshi's example1, you can do like that:

var
        list1: TStringList;
        i:Integer;
      begin
        list1 := TStringList.Create;
        list1.Add('c:\filename1');
        list1.Add('c:\filename2');
        list1.Add('c:\filename3');
        ...
        for i:= 0 to list1.count-1 do
             winexec(pchar('notepad.exe '+list1.strings[i],SW_SHOW);
       .....................
        list1.Free;
      end;

I would rather you adopt this, also you can reject my answer, since Madshi first give this suggestion.

The only idea I have is: Make Delphi your power tool!

Regards,
Wang
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 3

Expert Comment

by:Matvey
Comment Utility
OK, the easiest way to get the tokens of a string is to use TParser from classes.PAS:

_________________________________________________________________________
var
  p: PChar;
  AP: TParser;
  SS: TStringStream;
begin
  p := PChar('file1 file2 file3');
  SS := TStringStream.Create(p);
  AP := TParser.Create(SS);
  with AP do
    repeat
      showMessage(TokenString)
    until NextToken = #0;
  AP.Free;
  SS.Free;
end;
_________________________________________________________________________

This shows consequently messages of "file1", file2" and "file3".

--Matvey
0
 
LVL 3

Expert Comment

by:Matvey
Comment Utility
0
 
LVL 4

Expert Comment

by:dwwang
Comment Utility
Hi, Matvey
Sorry for mix-up your name with Madshi :-)
0
 

Author Comment

by:mathes
Comment Utility
Hi experts,

I meanwhile carefully studied all your proposal. I think that the idea to use stringlists works best in my specific case. However I have big problems to implement this idea.
In my sourcecode which you can see below is an error.
In line
 
for i := 0 to list1.count - 1 do

Delphi expects a decleration, but instead of this, Delphi finds a FOR.
So Delphi says thzere is a syntax error in my code.

Can you please tell me what I am doing wrong here?

As soon as this problem is fixed, I will rate your comments.

With kind regards

Mathes


procedure TForm1.Button1Click(Sender: TObject);
var
  filename: string;
  list1: TStringList;
  i: Integer;
  f: textfile;
begin
  if Application.MessageBox('Save one more file ?', 'Test', 36) = 6 then
  begin
    if savedialog1.execute then
      filename := savedialog1.filename
    else
      filename := '';
    assignfile(f, filename);
{$I-}
    rewrite(f);
{$I+}
    if ioresult <> 0 then halt;
    writeln(f, 'this is a test');
    closefile(f);
  end;
  list1 := TStringList.Create;
  list1.Add(filename);
end;
for i := 0 to list1.count - 1 do
  winexec(pchar('notepad.exe ' + list1.strings[i]), SW_SHOW);
list1.Free;
end.

0
 
LVL 3

Expert Comment

by:Matvey
Comment Utility
Oh come on... you just putted the FOR loop after the procedure's END :) The loop itself should be OK - Delphi expects a new procedure where you putted it...
0
 
LVL 4

Expert Comment

by:dwwang
Comment Utility
I think the program flow is: when the user press button1, you ask him whether to save a file, and finally you do something to save all the files the user commited. So there should be some chages:

type TForm1=class(TForm)
        ....
        public
           list1: TStringList;  //must declare it here so that it exists during for1's life time.
        private
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
        list1 := TStringList.Create;  // Create it here so that you can use it anywhre in the form.
end;
procedure TForm1.Button1Click(Sender: TObject);
      var
        filename: string;
        i: Integer;
        f: textfile;
      begin
        if Application.MessageBox('Save one more file ?', 'Test', 36) = 6 then
        begin
          if savedialog1.execute then
            filename := savedialog1.filename
          else
            filename := '';
          assignfile(f, filename);
      {$I-}
          rewrite(f);
      {$I+}
          if ioresult <> 0 then halt;
          writeln(f, 'this is a test');
          closefile(f);
        end;
        list1.Add(filename);
      end;

procedure TForm1.Button2Click(Sender: TObject);  // or other events of the form as you like
begin
      for i := 0 to list1.count - 1 do
        winexec(pchar('notepad.exe ' + list1.strings[i]), SW_SHOW);
      list1.Free;
end;

end.

In above code, when user press Button1, you can save a file, and when user press Buttons2, all files are opend by notepad.

Regards,
Wang
0
 
LVL 4

Expert Comment

by:dwwang
Comment Utility
Sorry, in the first sentence of last comment, I mean "open", rather than "save".
0
 

Author Comment

by:mathes
Comment Utility
Dear Matvey and Wang,

thank you for your hints.

I know that the for loop must be before the procedures's end.

But if I change the code like this...

...
  list1 := TStringList.Create;
  list1.Add(filename);
  for i := 0 to list1.count - 1 do
  winexec(pchar('notepad.exe ' + list1.strings[i]), SW_SHOW);
list1.Free;
end;
end.


I have a program flow like:

save file
display file
save file
display file
save file
display file


However I prefer a program flow
like

save file
save file
save file
display file
display file
display file

Wang showed me how this program flow could be accomplished, but he needs a second
command button.
Is it possible to assign saving and displaying the files to only one command button?

With kind regards

Mathes
0
 
LVL 4

Expert Comment

by:dwwang
Comment Utility
Yes, you can, in last example I didn't realized that you use a message box to judge when to end the input. So the complete example is below, translated from your above C code:

procedure TForm1.Button1Click(Sender: TObject);
var
   filename: string;
   i: Integer;
   f: textfile;
   list1: TStringList;   //now put the declaration back here :-)
begin
     list1 := TStringList.Create;
     while Application.MessageBox('Save one more file ?', 'Test', 36) = 6 do
        begin
        if savedialog1.execute then
           begin
           filename := savedialog1.filename;
           assignfile(f, filename);
           {$I-}
           rewrite(f);
           {$I+}
           if ioresult <> 0 then halt;
           writeln(f, 'this is a test');
           closefile(f);
           list1.Add(filename);
           end;
        end;
     for i := 0 to list1.count - 1 do
         winexec(pchar('notepad.exe ' + list1.strings[i]), SW_SHOW);
     list1.Free;
end;

This example tested, hope it accomplish what you expected.

Wang
0
 

Author Comment

by:mathes
Comment Utility
i dwwang,

thank you for your help. This is now exactly what I was looking for

With kind regards

mathes

P.S.: I wish you and all the the other experts merry Christmas and a happy new year.

0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now