home *** CD-ROM | disk | FTP | other *** search
/ PC-X 1997 October / pcx14_9710.iso / swag / delphi.swg / 0159_String Parsing.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-08-30  |  7.0 KB  |  188 lines

  1. String manipulation and parsing is often a place where we wish that we had
  2. more functions and methods to do our work for us.  There are many existing
  3. methods that cover our work-a-day needs, but every now and then we need
  4. something more.
  5.  
  6. In Paradox for Windows, there is an immensely useful function called
  7. BreakApart().  It can do so many things that I have written one (or two)
  8. for Delphi.
  9.  
  10. Here is how it works:
  11. You pass the base string that you want to parse and the string that you
  12. want to use for the BreakApart().  The function then separates the string
  13. into several pieces and fills a TStringList with their values.
  14.  
  15. Example:
  16.  
  17. Base String:  "Here we go."
  18. Break String: " "
  19.  
  20. Resulting array:
  21. TStringList[0]: "Here"
  22.  
  23. TStringList[1]: "we"
  24. TStringList[2]: "go."
  25.  
  26. Note that the break string, in this case spaces, is not to be seen
  27. anywhere in the output.
  28.  
  29. Now that we know what is done, lets take a look at how to do it.
  30.  
  31. First, lets look at the string version.
  32.  
  33. function sBreakApart(BaseString, BreakString: string; StringList: TStringList): TStringList;
  34. var
  35.   EndOfCurrentString: byte;
  36.   TempStr: string;
  37. begin
  38.   repeat
  39.     EndOfCurrentString := Pos(BreakString, BaseString);
  40.     if EndOfCurrentString = 0 then
  41.       StringList.add(BaseString)
  42.     else
  43.       StringList.add(Copy(BaseString, 1, EndOfCurrentString - 1));
  44.     BaseString := Copy(BaseString, EndOfCurrentString + length(BreakString), length(BaseString) - EndOfCurrentString);
  45.  
  46.   until EndOfCurrentString = 0;
  47.   result := StringList;
  48. end;
  49.  
  50. This is a fairly straight forward version (as compared with the PChar
  51. version).  We look for the break string.  If we donÆt find it at all,
  52. then we just assign it to the TStringList and go on our merry way.
  53. (Note:  The TStringList must be created outside the function or there
  54. will be problems when the variable goes out of scope.)  If we do find it,
  55. then we want to extract that portion of the string and assign it to the
  56. next available position in the TStringList.  Note that the string is
  57. updated each step of the way.  This is done just because it simplifies
  58. the code.  There are several possible ways of doing this.  I just picked
  59. one.
  60.  
  61. Let me answer some questions that I hear you asking.
  62.  
  63. 1.  Why didnÆt I make this a procedure?  It is true that the TStringList
  64. would reflect all changes without a value returned, but I left it as a
  65. function because it allows for use in another function that uses the
  66. TStringList.  I use the function in this way later in the article.
  67.  
  68. E.g.  Listbox1.items.assign(sBreakApart(...));
  69.  
  70. 2.  It doesnÆt interfere with calling it as if it was a procedure.
  71. (I.e. You donÆt need to catch the result if you donÆt want to code it that
  72. way.)
  73.  
  74. Now that we have seen that it does something, how about if we have it do
  75. something useful.
  76.  
  77. Here is a search and replace that uses the string version of the
  78. BreakApart() function.
  79.  
  80. function ReplaceStr(BaseString, ReplaceThis, WithThis: string): string;
  81. var
  82.   t: TStringList;
  83.  
  84.   i: integer;
  85. begin
  86.   t := TStringList.create;
  87.   sBreakApart(BaseString, ReplaceThis, t);
  88.   if t.count > 1 then
  89.   begin
  90.     result := '';
  91.     for i := 0 to t.count - 2 do
  92.       result := result + t[i] + WithThis;
  93.     result := result + t[i + 1];
  94.   end
  95.   else result := BaseString;
  96.   t.free;
  97. end;
  98.  
  99. This example requires a form with a pushbutton and 3 edit boxes.
  100. You can call this function like this:
  101.  
  102. edit1.text := ReplaceStr(edit1.text, edit2.text, edit3.text);
  103.  
  104. It replaces all occurrences of edit2.text in edit1.text with edit3.text.
  105.  
  106. I told you that this was simple and useful!
  107.  
  108. Now lets take a look at the PChar version.  Since we have an idea of how
  109. this works now, IÆll show the code first.
  110.  
  111. function pBreakApart(BaseString, BreakString: PChar; StringList: TStringList): TStringList;
  112. var
  113.   BreakStringLength: word;
  114.   pEndOfCurrentString, pEndOfBaseString: PChar;
  115. {Automatically gets memory allocated for it.}
  116.   temp: array[0..255] of char;
  117. begin
  118. {Initialize the pointers.}
  119.   BreakStringLength := StrLen(BreakString);
  120.   pEndOfBaseString := BaseString;
  121.   inc(pEndOfBaseString, StrLen(BaseString));
  122.   repeat
  123.     pEndOfCurrentString := StrPos(BaseString, BreakString);
  124.     StringList.add(StrPas(StrLCopy(temp, BaseString, pEndOfCurrentString - BaseString)));
  125.  
  126.     inc(BaseString, pEndOfCurrentString - BaseString + BreakStringLength);
  127.   until BaseString >= pEndOfBaseString - BreakStringLength;
  128.   result := StringList;
  129. end;
  130.  
  131. This takes a different approach to solving the same problem.  Since this
  132. is done with a PChar that can be of greatly varying size, it was best to
  133. do it with pointers and pointer arithmetic.  Since we are not changing the
  134. value passed in (which would be bad as it is passed by reference and that
  135. would change the values in their original memory locations) we need a way
  136. to keep track of just where we are in the current part of the process.
  137.  
  138. A word about pointer arithmetic...  The Inc() and Dec() functions have an
  139. undocumented feature that allows for pointer arithmetic.  The relevant
  140. feature is that the function "knows" the size of the object pointed to
  141. and increments the pointer the correct number of bytes.
  142.  
  143. Here is an example that uses the PChar version:
  144.  
  145. procedure TForm1.Button1Click(Sender: TObject);
  146. var
  147.   f: file;
  148.   pStr: PChar;
  149.   LengthOfFile: integer;
  150.   t: TStringList;
  151. begin
  152.   {Get the information.}
  153.   AssignFile(f, 'c:\autoexec.bat');
  154.   {Because this is not a text file type, the record size is 1 (char)}
  155.   Reset(f, 1);
  156.   LengthOfFile := FileSize(f) + 1; {Add one for the null terminator.}
  157.   pStr := AllocMem(LengthOfFile); {Zeros the memory also.}
  158.   BlockRead(f, pStr^, LengthOfFile - 1);
  159.   CloseFile(f);
  160.   t := TStringList.create;
  161.   listBox1.items.assign(pBreakApart(pStr, #13#10, t));
  162.   t.free;
  163.   FreeMem(pStr, LengthOfFile);
  164. end;
  165.  
  166. This example requires a form that has a listbox and a pushbutton. It reads
  167. the autoexec.bat file into memory in a single gulp using Blockread().
  168. There is just enough memory allocated to get the job done.  This is done
  169. by using the fileÆs size as the basis.  The PChar version is called and
  170. assigned directly, and the file is "broken apart" by carriage return/line
  171. feed.  (Note:  I know that a LoadFromFile() will do this also, but this is
  172. an exercise in memory juggling.)  Then memory clean up is performed.
  173. The contents of the autoexec.bat file are then displayed in the listbox
  174. line by line.
  175.  
  176. The uses for this are many and varied.  If you used this on a filename with
  177. the full path and did a BreakApart() on "\", you would have element 0 of
  178. the list as the drive, and the last element would be the file name.  You
  179. could break that apart on the ".", and get the separated file name and
  180. extension.
  181.  
  182. I did not include error checking here.  A string can only hold 255
  183. characters.  It is possible that your users might try to put more than
  184. that in there.  If you want to do the error checking for them, then I
  185. wish you well.  I thought that it would be beyond the scope of this short
  186. article and left it to the reader.
  187.  
  188.