Mutley2003
asked on
Turn a string with ( multiple) \n embedded into a Pascal string declaration
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
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
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
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
Thanks for the quick response, and I will start testing your code in a few minutes.
Thanks again
ASKER
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.
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.
Doesn't StringReplace do the job? (I think that function was implemented on D7)
ShowMessage(StringReplace( 'My multiline\ntestmessage\n\n by luis','\n',#13+#10,[rfRepl aceAll]))
Also I beleive you can turn back the parameters to convert #13#10 to \n
Also I beleive you can turn back the parameters to convert #13#10 to \n
> 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
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
ASKER
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.ConvertCStringToDel phiString( 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.T ext, #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(outst rings[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.btnProcessHeredocCl ick(Sender : TObject);
begin
dsmemo2.lines.text := ConvertCStringToDelphiStri ng(dsmemo1 .lines.Tex t);
end;
-------------------------- ---- test string input--------------------- ---------- ---------- -------
s <<< this
is
gggggggggggggggggggggggggg gggggggggg ggggg***** ********** ********** ********** ********** ********** ********** ********** ********** ********** ********** ********** ********** *******ggg ggg dduh
gggggggggggggggggggggggggg gggggggggg gggggg yyyyyyyyyyyyyyyyyyyyyyyy
rrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrr rrrrrrrrrr rrrrrrrr 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 gggggggggggggggggggggggggg gggggggggg ggggg***** ********** ****'
+'************************ ********** ********** ********** ********** ***'
+'************************ ********** ********** **gggggg dduh'
+' gggggggggggggggggggggggggg gggggggggg gggggg yyyyyyyyyyyyyyyyyyyyyyyy'
+' rrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrr rrrrrrrrrr rrrrrrrr'
+' 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';
function TForm1.ConvertCStringToDel
var
sl3 : Tstringlist;
function SplitLongStringAtPos(const
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.T
// 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(outst
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.btnProcessHeredocCl
begin
dsmemo2.lines.text := ConvertCStringToDelphiStri
end;
--------------------------
s <<< this
is
gggggggggggggggggggggggggg
gggggggggggggggggggggggggg
rrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrrrrrr
of some stuff
and this is some \n\n\nmore
testing of the same \nstuff
ok
--------------------------
s := 'this is gggggggggggggggggggggggggg
+'************************
+'************************
+' gggggggggggggggggggggggggg
+' rrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrr
+' 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';
your code seems to work pretty well, I like the "try to find the best place to split" way you are doing it :-)
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 :-)