Solved

Turn a string with ( multiple)  \n  embedded into a Pascal string declaration

Posted on 2007-12-02
9
227 Views
Last Modified: 2010-04-05
I am looking for an elegant and simple solution to the following problem .. it is not something I think I can do easily with StringReplace or grep, t my attempts at it have ended up with nasty code that fails to take account of special cases.

Essentially I want to enclose some source strings in quotation marks, while taking account of \n, to form a valid delphi string assignment statement. (it is sort of a heredoc front end to Delphi, invoked from the tools menu). Line breaks in the source strings are not significant, nor are leading and trailing spaces, except that test lines within a paragraph are assumed to have a single space separating words on consecutive lines..

So if I start out with

fred <<< a frog he would
       a wooing go,\n\n whether his mother would
let him or no \n
\n
            hey ho

I want to generate the delphi code

fred := 'a frog he would a wooing go,'+#13+#10+#13+#10+
'whether his mother would let him or no'+#13+#10+#13+#10+'hey ho';

Anyone care to code up something elegant to handle this?  Maybe it is some sort of state machine?

thanks

0
Comment
Question by:Mutley2003
  • 4
  • 3
  • 2
9 Comments
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 20393470
couple of questions...
1) how do you know when you have reached the end of the string?  eg. " hey ho"
2) would it have to handle "commented code"
eg. ignore "bob" in the following example

fred <<< a frog said hi
// bob <<< a cow replaied hi yourself
bill <<< a chicken ignored them both...
 
my c skills are pretty rusty so I don't remember all the syntax :-)
0
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 500 total points
ID: 20393617
here's a quick one I whipped up
It assumes the 2 questions above do not apply and you are dealing with an entire "string" only

procedure TForm1.Button1Click(Sender: TObject);

begin

  memo2.lines.text := ConvertCStringToDelphiString(memo1.lines.Text);

end;
 

function TForm1.ConvertCStringToDelphiString(InCString: string): string;

const

  vardeclare = '<<<';

  newline = '\n';

  replaceCRLF = #39 + ' + #13#10 +' + #13#10 + #39;

  emptyCRLFline = #39#39 + ' + #13#10 +';

var

  i: integer;

  s: string;

  FullString: string;

  VarPart: string;

  indent: string; // for nice formatting only

  OutStrings: TStrings;

begin

  result := '';

  OutStrings := TStringList.Create;

  try

// firstly, remove any white space

    OutStrings.Text := InCString;

    for i := pred(OutStrings.Count) downto 0 do

    begin

      s := TRIM(OutStrings[i]);

      if (s = '') then OutStrings.Delete(i) else OutStrings[i] := s;

    end;

// now concatenate the lines as 1 line with actual cr/lfs replaced as single spaces

    FullString := StringReplace(OutStrings.Text, #13#10, ' ', [rfReplaceAll]);

  // gives us

  // mystring <<< this is a test \nof some stuff and this is some \n\n\nmore testing of the same \nstuff ok

// Determine the variable

    i := pos(vardeclare, FullString);

    if i = 0 then Raise Exception.Create('no variable declaration');
 

    VarPart := Trim(copy(FullString, 1, i-1));

    delete(FullString, 1, i + length(vardeclare)-1);

    FullString := #39 + trim(FullString) + #39; // remove spaces between variable and text

    // gives us 'this is a test of \n some stuff and this is some \n\nmore testing of the same \nstuff ok'

    FullString := StringReplace(FullString, newline, replaceCRLF, [rfReplaceAll, rfIgnoreCase]);

{ gives us

  'this is a test of ' + #13#10 +

  'some stuff and this is some ' + #13#10 +

  '' + #13#10 +

  '' + #13#10 +

  'more testing of the same ' + #13#10 +

  'stuff ok'

  }

// concatenate multiple CRLFs

    OutStrings.Text := FullString;

    i := 2; // start at the 3rd line.. no point starting earlier

    while (i < OutStrings.Count) do

    begin

      if (OutStrings[i] = emptyCRLFline) then

      begin

        s := OutStrings[i-1];

        delete(s, length(s)-1, 2); // remove the " +" portion

        s := s + '#13#10 +';

        OutStrings[i-1] := s;

        Outstrings.Delete(i);

      end

      else

        inc(i);

    end;
 

// nicely indent each line

    Varpart := VarPart + ' := ';

    SetLength(indent, length(VarPart));

    FillChar(indent[1], length(VarPart), ord(' '));

    for i := pred(OutStrings.Count) downto 1 do OutStrings[i] := indent + OutStrings[i];

    result := VarPart + OutStrings.Text;

// remove final CRLF if applicable

    if copy(result, length(result)-1, 2) = #13#10 then

      delete(result, length(result)-1, 2);

    result := result + ';';

  finally

    OutStrings.Clear;

    OutStrings.Free;

  end;

end;
 

end.
 

*** FORM FOLLOWS ***
 

object Form1: TForm1

  Left = 249

  Top = 107

  Width = 528

  Height = 553

  Caption = 'Form1'

  Color = clBtnFace

  Font.Charset = DEFAULT_CHARSET

  Font.Color = clWindowText

  Font.Height = -11

  Font.Name = 'MS Sans Serif'

  Font.Style = []

  OldCreateOrder = False

  PixelsPerInch = 96

  TextHeight = 13

  object Memo1: TMemo

    Left = 12

    Top = 8

    Width = 481

    Height = 169

    Font.Charset = DEFAULT_CHARSET

    Font.Color = clWindowText

    Font.Height = -11

    Font.Name = 'Courier New'

    Font.Style = []

    Lines.Strings = (

      '   s <<< this is a test \nof some stuff'

      '       and this is some \n\n\nmore '

      'testing of the same \nstuff'

      '  ok'

      '')

    ParentFont = False

    TabOrder = 0

  end

  object Memo2: TMemo

    Left = 8

    Top = 228

    Width = 493

    Height = 281

    Font.Charset = DEFAULT_CHARSET

    Font.Color = clWindowText

    Font.Height = -11

    Font.Name = 'Courier New'

    Font.Style = []

    Lines.Strings = (

      'Memo2')

    ParentFont = False

    TabOrder = 1

  end

  object Button1: TButton

    Left = 12

    Top = 192

    Width = 75

    Height = 25

    Caption = 'Button1'

    TabOrder = 2

    OnClick = Button1Click

  end

end

Open in new window

0
 

Author Comment

by:Mutley2003
ID: 20393761
loki .. as you assume, no comments and a full string supplied.

Thanks for the quick response, and I will start testing your code in a few minutes.

Thanks again

0
 

Author Comment

by:Mutley2003
ID: 20393956
yes, it works beautifully, except that if there is a lot of text with no \n anywhere the Delphi string generated
will be too long (the compiler does not  like strings that go past col 127 or something)

About the only solution I can see s to add another stringlist in there, and check for a space inside a quoted string near some preferred column boundary

I am having a go at that now.


0
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 
LVL 2

Expert Comment

by:lortega
ID: 20394101
Doesn't StringReplace do the job? (I think that function was implemented on D7)
0
 
LVL 2

Expert Comment

by:lortega
ID: 20394117
ShowMessage(StringReplace('My multiline\ntestmessage\n\nby luis','\n',#13+#10,[rfReplaceAll]))

Also I beleive you can turn back the parameters to convert #13#10 to \n
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 20394300
> will be too long (the compiler does not  like strings that go past col 127 or something)

ah, personally, I'd do a final loop through the stringlist, and split lines > 127 into 2 lines.
I'll take a look at it tomorrow and see if I can make it look nice
0
 

Author Comment

by:Mutley2003
ID: 20394357
well, this is my approach to the long lines problem .. it is not very pretty, but it seems to work Loki, unless you think you have a much nicer way of doing that reformatting, I don't wan you to spend any more time on it .. I am happy to give you the points, thanks for getting me most of the way.


function TForm1.ConvertCStringToDelphiString(InCString: string): string;

var
  sl3          : Tstringlist;

  function SplitLongStringAtPos(const s: string; const StartFrom: integer; const Prefer: integer): Boolean;
  var
    numspaces, len: integer;
    spacesAt   : array of Boolean;
    PlusesAt   : array of Boolean;
    InsideQuotes: array of Boolean;
    inQuote    : Boolean;
    s1, s2     : string;
    k, nearest, nearestpos: integer;
  begin
    result := false;
    len := length(s);
    setlength(spacesAt, len + 1);
    setlength(PlusesAt, len + 1);
    setLength(InsideQuotes, len + 1);
    numspaces := 0;
    inquote := false;
    for k := StartFrom to len do begin
      spacesAt[k] := false;
      PlusesAt[k] := false;
      insideQuotes[k] := false;
      if inquote then insideQuotes[k] := true;
      if s[k] = '+' then PlusesAt[k] := true;
      if s[k] = '''' then inquote := not (inquote);
      if s[k] = ' ' then begin
        spacesAt[k] := true;
        inc(numspaces);
      end;
    end;                                // end for

    if numspaces = 0 then exit;         // cannot do it

    // ------------------------------------------------------------------------------
   // now look for a space near the preferred boundary,  outside the quotes
    nearest := maxInt; nearestPos := -1;
    for k := 1 to len do begin
      if not spacesAt[k] then continue;
      if insideQuotes[k] then continue;
      if abs(k - Prefer) < nearest then begin
        nearestpos := k;
        nearest := abs(k - Prefer);
      end;
    end;                                // end for

    // check if within tolerance (say 10 characters)
    if nearest < 10 then begin
      s1 := copy(s, 1, nearestpos);
      sl3.add(s1);
      s2 := copy(s, nearestpos + 1, len);
      if length(s2) < Prefer then begin
        sl3.add(s2);
        result := true;
        exit;
      end;
      // second bit of string is still too long .. recurse down
      result := SplitLongStringAtPos(s2, 1, prefer);
      exit;
    end;


    // ------------------------------------------------------------------------------
   // now look for a  plus near the preferred boundary, outside the quotes
    nearest := maxInt; nearestPos := -1;
    for k := 1 to len do begin
      if not PlusesAt[k] then continue;
      if insideQuotes[k] then continue;
      if abs(k - Prefer) < nearest then begin
        nearestpos := k;
        nearest := abs(k - Prefer);
      end;
    end;                                // end for

    // check if within tolerance (say 10 characters)
    if nearest < 10 then begin
      s1 := copy(s, 1, nearestpos);
      sl3.add(s1);
      s2 := copy(s, nearestpos + 1, len);
      if length(s2) < Prefer then begin
        sl3.add(s2);
        result := true;
        exit;
      end;
      // second bit of string is still too long .. recurse down
      result := SplitLongStringAtPos(s2, 1, prefer);
      exit;
    end;


    // ------------------------------------------------------------------------------
   // now look for a space near the preferred boundary inside a quoted string

    nearest := maxInt; nearestPos := -1;
    for k := 1 to len do begin
      if not spacesAt[k] then continue;
      if insideQuotes[k] = false then continue;
      if abs(k - Prefer) < nearest then begin
        nearestpos := k;
        nearest := abs(k - Prefer);
      end;
    end;                                // end for

    // check if within tolerance, again
    if nearest < 10 then begin
      s1 := copy(s, 1, nearestpos - 1) + '''';
      sl3.add(s1);
      s2 := '+''' + copy(s, nearestpos, len);
      if length(s2) < Prefer then begin
        sl3.add(s2);
        result := true;
        exit;
      end;
      // second bit of string is still too long .. recurse down
      result := SplitLongStringAtPos(s2, 1, prefer);
      exit;
    end;




    // ------------------------------------------------------------------------------
   //  we could not find any space near the preferred boundary (within tolerance)
   //  so we do a brute force split at position Prefer

    inquote := false;
    for k := StartFrom to Prefer do begin
      if s[k] = '''' then inquote := not (inquote);
    end;                                // end for
    nearestpos := Prefer;
    case inquote of
      true: begin
          s1 := copy(s, 1, nearestpos - 1) + '''';
          sl3.add(s1);
          s2 := '+''' + copy(s, nearestpos, len);
        end;
      false: begin
          s1 := copy(s, 1, nearestpos);
          sl3.add(s1);
          s2 := copy(s, nearestpos + 1, len);
        end;
    end;                                // end case
    if length(s2) < Prefer then begin
      sl3.add(s2);
      result := true;
      exit;
    end;
    // second bit of string is still too long .. recurse down
    result := SplitLongStringAtPos(s2, 1, prefer);
  end;

const
  vardeclare   = '<<<';
  newline      = '\n';
  replaceCRLF  = #39 + ' + #13#10 +' + #13#10 + #39;
  emptyCRLFline = #39#39 + ' + #13#10 +';
var
  i, crit      : integer;
  s            : string;
  FullString   : string;
  VarPart      : string;
  indent       : string;                // for nice formatting only
  OutStrings   : TStrings;
  sl           : Tstringlist;
  need2Split   : Boolean;

begin
  result := '';
  OutStrings := TStringList.Create;
  try
    // firstly, remove any white space
    OutStrings.Text := InCString;
    for i := pred(OutStrings.Count) downto 0 do begin
      s := TRIM(OutStrings[i]);
      if (s = '') then OutStrings.Delete(i) else OutStrings[i] := s;
    end;
    // now concatenate the lines as 1 line with actual cr/lfs replaced as single spaces
    FullString := StringReplace(OutStrings.Text, #13#10, ' ', [rfReplaceAll]);
    // gives us
    // mystring <<< this is a test \nof some stuff and this is some \n\n\nmore testing of the same \nstuff ok

    // Determine the variable
    i := pos(vardeclare, FullString);
    if i = 0 then raise Exception.Create('no variable declaration');

    VarPart := Trim(copy(FullString, 1, i - 1));
    delete(FullString, 1, i + length(vardeclare) - 1);
    FullString := #39 + trim(FullString) + #39; // remove spaces between variable and text
    // gives us 'this is a test of \n some stuff and this is some \n\nmore testing of the same \nstuff ok'
    FullString := StringReplace(FullString, newline, replaceCRLF, [rfReplaceAll, rfIgnoreCase]);
    { gives us
      'this is a test of ' + #13#10 +
      'some stuff and this is some ' + #13#10 +
      '' + #13#10 +
      '' + #13#10 +
      'more testing of the same ' + #13#10 +
      'stuff ok'
      }

    // concatenate multiple CRLFs
    OutStrings.Text := FullString;
    i := 2;                             // start at the 3rd line.. no point starting earlier
    while (i < OutStrings.Count) do begin
      if (OutStrings[i] = emptyCRLFline) then begin
        s := OutStrings[i - 1];
        delete(s, length(s) - 1, 2);    // remove the " +" portion
        s := s + '#13#10 +';
        OutStrings[i - 1] := s;
        Outstrings.Delete(i);
      end
      else
        inc(i);
    end;

    // nicely indent each line
    Varpart := VarPart + ' := ';
    SetLength(indent, length(VarPart));
    FillChar(indent[1], length(VarPart), ord(' '));

    crit := 70;
    need2split := false;
    for i := 0 to Outstrings.count - 1 do begin
      if length(outstrings[i]) > crit then need2Split := true;
    end;




    if need2Split = false then begin
      for i := pred(OutStrings.Count) downto 0 do begin
        if i <> 0 then OutStrings[i] := indent + OutStrings[i];
      end;
      result := VarPart + OutStrings.Text;
      // remove final CRLF if applicable
      if copy(result, length(result) - 1, 2) = #13#10 then
        delete(result, length(result) - 1, 2);
      result := result + ';';
    end else begin


      // -- now split long lines, as needed,  at some notional boundary

      sl := Tstringlist.create;
      for i := 0 to outstrings.count - 1 do begin
        if length(outstrings[i]) <= crit then begin
          sl.add(outstrings[i]);
        end else begin
          sl3 := Tstringlist.create;
          if SplitLongStringAtPos(outstrings[i], 1, crit) then begin
            sl.addstrings(sl3);
          end
          else begin
            sl.add('// warning .. could not split this string');
            sl.add(outstrings[i]);
          end;
          sl3.free;
        end;
      end;
      for i := pred(sl.Count) downto 0 do begin
        if i <> 0 then sl[i] := indent + sl[i];
      end;

      result := VarPart + sl.Text;
      // remove final CRLF if applicable
      if copy(result, length(result) - 1, 2) = #13#10 then
        delete(result, length(result) - 1, 2);
      result := result + ';';
      sl.free;
    end;

  finally
    OutStrings.Clear;
    OutStrings.Free;
  end;
end;




procedure TForm1.btnProcessHeredocClick(Sender: TObject);
begin
  dsmemo2.lines.text := ConvertCStringToDelphiString(dsmemo1.lines.Text);
end;

------------------------------ test string input------------------------------------------------
s <<< this
is
ggggggggggggggggggggggggggggggggggggggggg************************************************************************************************************************************gggggg    dduh
    gggggggggggggggggggggggggggggggggggggggggg       yyyyyyyyyyyyyyyyyyyyyyyy

  rrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr  555555555555555555555             66666666666666666           999999999999   a        test\n
of some stuff
       and this is some \n\n\nmore
 testing of the same \nstuff
   ok

---------------------------------  output ---------------------------------------

s := 'this is ggggggggggggggggggggggggggggggggggggggggg*******************'
     +'*******************************************************************'
     +'**********************************************gggggg    dduh'
     +' gggggggggggggggggggggggggggggggggggggggggg       yyyyyyyyyyyyyyyyyyyyyyyy'
     +' rrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr'
     +'  555555555555555555555             66666666666666666          '
     +' 999999999999   a        test' + #13#10 +
     ' of some stuff and this is some ' + #13#10#13#10#13#10 +
     'more testing of the same ' + #13#10 +
     'stuff ok';






0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 20399640
your code seems to work pretty well, I like the "try to find the best place to split" way you are doing it :-)
0

Featured Post

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
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…
Windows 10 is mostly good. However the one thing that annoys me is how many clicks you have to do to dial a VPN connection. You have to go to settings from the start menu, (2 clicks), Network and Internet (1 click), Click VPN (another click) then fi…
Sending a Secure fax is easy with eFax Corporate (http://www.enterprise.efax.com). First, just open a new email message. In the To field, type your recipient's fax number @efaxsend.com. You can even send a secure international fax — just include t…

920 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

16 Experts available now in Live!

Get 1:1 Help Now