Bläddra i källkod

* fix for Delphi incompatible DelimitedText, Mantis #19610, big patch from Reinier Olislagers.

git-svn-id: trunk@22549 -
marco 13 år sedan
förälder
incheckning
a48aba60fb
3 ändrade filer med 761 tillägg och 18 borttagningar
  1. 1 0
      .gitattributes
  2. 47 18
      rtl/objpas/classes/stringl.inc
  3. 713 0
      tests/webtbs/tw19610.pp

+ 1 - 0
.gitattributes

@@ -12709,6 +12709,7 @@ tests/webtbs/tw19511.pp svneol=native#text/pascal
 tests/webtbs/tw19548.pp svneol=native#text/pascal
 tests/webtbs/tw19555.pp svneol=native#text/pascal
 tests/webtbs/tw19581.pp svneol=native#text/plain
+tests/webtbs/tw19610.pp svneol=native#text/plain
 tests/webtbs/tw19622.pp -text svneol=native#text/plain
 tests/webtbs/tw1964.pp svneol=native#text/plain
 tests/webtbs/tw19651.pp svneol=native#text/plain

+ 47 - 18
rtl/objpas/classes/stringl.inc

@@ -140,30 +140,33 @@ Function TStrings.GetDelimitedText: string;
 Var
   I : integer;
   p : pchar;
-  c : set of char;
+  BreakChars : set of char;
   S : String;
   
 begin
   CheckSpecialChars;
   result:='';
   if StrictDelimiter then
-    c:=[#0,Delimiter]
+    BreakChars:=[#0,QuoteChar,Delimiter]
   else  
-    c:=[#0..' ',QuoteChar,Delimiter];
+    BreakChars:=[#0..' ',QuoteChar,Delimiter];
+
+  // Check for break characters and quote if required.
   For i:=0 to count-1 do
     begin
     S:=Strings[i];
     p:=pchar(S);
-    while not(p^ in c) do
+    //Quote strings that include BreakChars:
+    while not(p^ in BreakChars) do
      inc(p);
-// strings in list may contain #0
-    if (p<>pchar(S)+length(S)) and not StrictDelimiter then
+    if (p<>pchar(S)+length(S)) then
       Result:=Result+QuoteString(S,QuoteChar)
     else
       Result:=Result+S;
     if I<Count-1 then 
       Result:=Result+Delimiter;
     end;
+  // Quote empty string:
   If (Length(Result)=0) and (Count=1) then
     Result:=QuoteChar+QuoteChar;
 end;
@@ -268,22 +271,48 @@ begin
  j:=1;
  aNotFirst:=false;
 
+ { Paraphrased from Delphi XE2 help:
+ Strings must be separated by Delimiter characters or spaces.
+ They may be enclosed in QuoteChars.
+ QuoteChars in the string must be repeated to distinguish them from the QuoteChars enclosing the string.
+ }
  try
   Clear;
   If StrictDelimiter then
     begin
-    // Easier, faster loop.
-    While I<=Length(AValue) do
-      begin
-      If (AValue[I] in [FDelimiter,#0]) then
-        begin
-        Add(Copy(AValue,J,I-J));
-        J:=I+1;
-        end;
-      Inc(i);
+    while i<=length(AValue) do begin
+     // skip delimiter
+     if aNotFirst and (i<=length(AValue)) and (AValue[i]=FDelimiter) then inc(i);
+
+     // read next string
+     if i<=length(AValue) then begin
+      if AValue[i]=FQuoteChar then begin
+       // next string is quoted
+       j:=i+1;
+       while (j<=length(AValue)) and
+             ( (AValue[j]<>FQuoteChar) or
+               ( (j+1<=length(AValue)) and (AValue[j+1]=FQuoteChar) ) ) do begin
+        if (j<=length(AValue)) and (AValue[j]=FQuoteChar) then inc(j,2)
+                                                          else inc(j);
+       end;
+       // j is position of closing quote
+       Add( StringReplace (Copy(AValue,i+1,j-i-1),
+                           FQuoteChar+FQuoteChar,FQuoteChar, [rfReplaceAll]));
+       i:=j+1;
+      end else begin
+       // next string is not quoted; read until delimiter
+       j:=i;
+       while (j<=length(AValue)) and
+             (AValue[j]<>FDelimiter) do inc(j);
+       Add( Copy(AValue,i,j-i));
+       i:=j;
       end;
-    If (Length(AValue)>0) then
-      Add(Copy(AValue,J,I-J));  
+     end else begin
+      if aNotFirst then Add('');
+     end;
+
+     aNotFirst:=true;
+    end;
     end
   else 
     begin
@@ -310,7 +339,7 @@ begin
                            FQuoteChar+FQuoteChar,FQuoteChar, [rfReplaceAll]));
        i:=j+1;
       end else begin
-       // next string is not quoted
+       // next string is not quoted; read until control character/space/delimiter
        j:=i;
        while (j<=length(AValue)) and
              (Ord(AValue[j])>Ord(' ')) and

+ 713 - 0
tests/webtbs/tw19610.pp

@@ -0,0 +1,713 @@
+{ Source provided for Free Pascal bug report 19610 }
+{ Submitted by Reinier Olislagers on 20120920 }
+{
+Note: the tests here are somewhat more extensive than the original bug report.
+They are aimed at confirming interoperability between Delphi and FPC sdf formats
+The basis for the tests is therefore Delphi's handling.
+
+The only exception are the Put tests, which also accept results that are always
+quoted. As Get_StrictDelimTrueSafeQuote and Get_StrictDelimFalseSafeQuote prove,
+always quoting output leads to correct/the same input.
+The advantage of this is that having strictdelimiter on or off does not matter
+and the output format is more unambiguous (i.e. more compatible with RFC4180
+for CSV).
+
+On Delphi, rename to .dpr.
+
+Tests successfully completed on:
+Turbo Delphi 2006 (Reinier Olislagers)
+Delphi 2007 (OBones)
+Delphi XE (Marco van de Voort, OBones)
+Delphi XE2 Win32 (OBones)
+Delphi XE2 Win64 (OBones)
+}
+{
+    This file is part of the Free Pascal packages.
+    Copyright (c) 1999-2012 by the Free Pascal development team
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+{$apptype console}
+{$ifdef fpc}
+  {$mode objfpc}{$H+}
+{$endif fpc}
+program tw19610;
+
+uses Classes, SysUtils;
+
+{$ifndef fpc}
+//Delphi
+const
+  LineEnding=#13+#10;
+{$endif}
+
+function Get_StrictDelimFalse:boolean;
+// Test if input works with Delphi-compatible sdf output
+// Strictdelimiter:=false (default) when processing the delimitedtext
+//
+// Mainly check if reading quotes is according to Delphi sdf specs and works.
+// Based on del4.zip in bug 19610
+const
+  // Matches del4.zip in bug 19610:
+  DelimText='normal_string;"quoted_string";"quoted;delimiter";"quoted and space";"""quoted_and_starting_quote";"""quoted, starting quote, and space";"quoted_with_tab'+#9+'character";"quoted_multi'+LineEnding+
+    'line";  UnquotedSpacesInfront;UnquotedSpacesAtTheEnd   ;  "Spaces before quoted string"';
+  TestName='tw19610.Get_StrictDelimFalse';
+var
+  TestSL: TStringList;
+  Expected: TStringList;
+  i: integer;
+begin
+  result:=true;
+  //Expected values:
+  Expected:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    Expected.Add('normal_string');
+    Expected.Add('quoted_string');
+    Expected.Add('quoted;delimiter');
+    Expected.Add('quoted and space');
+    Expected.Add('"quoted_and_starting_quote');
+    Expected.Add('"quoted, starting quote, and space');
+    Expected.Add('quoted_with_tab'+#9+'character');
+    Expected.Add('quoted_multi'+LineEnding+
+      'line');
+    Expected.Add('UnquotedSpacesInfront');
+    Expected.Add('UnquotedSpacesAtTheEnd');
+    Expected.Add('Spaces before quoted string');
+
+    TestSL.Delimiter:=';'; //Match example in bug 19610, del4.zip
+    TestSL.StrictDelimiter:=false;
+    TestSL.DelimitedText:=DelimText;
+    //Test:
+    if Expected.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(Expected.Count)+' expected strings.');
+    end;
+
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (Expected.Count>i) and (TestSL[i]<>Expected[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+Expected[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    Expected.Free;
+    TestSL.Free;
+  end;
+end;
+
+function Get_StrictDelimTrue:boolean;
+// Test if input works with Delphi-compatible sdf output
+// Strictdelimiter:=true when processing the delimitedtext
+//
+// Mainly check if reading quotes is according to Delphi sdf specs and works.
+// Based on del4.zip in bug 19610
+const
+  // Matches del4.zip in bug 19610:
+  DelimText='normal_string;"quoted_string";"quoted;delimiter";"quoted and space";"""quoted_and_starting_quote";"""quoted, starting quote, and space";"quoted_with_tab'+#9+'character";"quoted_multi'+LineEnding+
+    'line";  UnquotedSpacesInfront;UnquotedSpacesAtTheEnd   ;  "Spaces before quoted string"';
+  TestName='tw19610.Get_StrictDelimTrue';
+var
+  TestSL: TStringList;
+  Expected: TStringList;
+  i: integer;
+begin
+  result:=true;
+  //Expected values:
+  Expected:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    Expected.Add('normal_string');
+    Expected.Add('quoted_string');
+    Expected.Add('quoted;delimiter');
+    Expected.Add('quoted and space');
+    Expected.Add('"quoted_and_starting_quote');
+    Expected.Add('"quoted, starting quote, and space');
+    Expected.Add('quoted_with_tab'+#9+'character');
+    Expected.Add('quoted_multi'+LineEnding+
+      'line');
+    Expected.Add('  UnquotedSpacesInfront');
+    Expected.Add('UnquotedSpacesAtTheEnd   ');
+    Expected.Add('  "Spaces before quoted string"');
+
+    TestSL.Delimiter:=';'; //Match example in bug 19610, del4.zip
+    TestSL.StrictDelimiter:=true;
+    TestSL.DelimitedText:=DelimText;
+    //Test:
+    if Expected.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(Expected.Count)+' expected strings.');
+    end;
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (Expected.Count>i) and (TestSL[i]<>Expected[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+Expected[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    Expected.Free;
+    TestSL.Free;
+  end;
+end;
+
+function Get_StrictDelimFalseCornerCases:boolean;
+// Test if input works with Delphi-compatible sdf output
+// Strictdelimiter:=false (default) when processing the delimitedtext
+//
+// Has some corner cases that Delphi produces but are not evident from their
+// documentation
+// Based on del4.zip in bug 19610
+const
+  // Matches del4.zip in bug 19610:
+  DelimText='"Spaces after quoted string"   ;';
+  TestName='tw19610.Get_StrictDelimFalseCornerCases';
+var
+  TestSL: TStringList;
+  Expected: TStringList;
+  i: integer;
+begin
+  result:=true;
+  //Expected values:
+  Expected:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    Expected.Add('Spaces after quoted string');
+    Expected.Add('');
+
+    TestSL.Delimiter:=';'; //Match example in bug 19610, del4.zip
+    TestSL.StrictDelimiter:=false;
+    TestSL.DelimitedText:=DelimText;
+    //Test:
+    if Expected.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(Expected.Count)+' expected strings.');
+    end;
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (Expected.Count>i) and (TestSL[i]<>Expected[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+Expected[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    Expected.Free;
+    TestSL.Free;
+  end;
+end;
+
+function Get_StrictDelimTrueCornerCases:boolean;
+// Test if input works with Delphi-compatible sdf output
+// Strictdelimiter:=true when processing the delimitedtext
+//
+// Has some corner cases that Delphi produces but are not evident from their
+// documentation
+// Based on del4.zip in bug 19610
+const
+  // Matches del4.zip in bug 19610:
+  DelimText='"Spaces after quoted string"   ;';
+  TestName='tw19610.Get_StrictDelimTrueCornerCases';
+var
+  TestSL: TStringList;
+  Expected: TStringList;
+  i: integer;
+begin
+  result:=true;
+  //Expected values:
+  Expected:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    // With delimiter true, we get 2 extra empty lines, also some spaces
+    Expected.Add('Spaces after quoted string');
+    Expected.Add('   ');
+    Expected.Add('');
+
+    TestSL.Delimiter:=';'; //Match example in bug 19610, del4.zip
+    TestSL.StrictDelimiter:=true;
+    TestSL.DelimitedText:=DelimText;
+    //Test:
+    if Expected.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(Expected.Count)+' expected strings.');
+    end;
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (Expected.Count>i) and (TestSL[i]<>Expected[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+Expected[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    Expected.Free;
+    TestSL.Free;
+  end;
+end;
+
+function Get_StrictDelimTrueSafeQuote:boolean;
+// Test if input works with sdf output that has always been quoted
+// Delphi accepts this input even though it does not write it by default
+// This is a more unambiguous format than unquoted
+// Strictdelimiter:=true when processing the delimitedtext
+//
+const
+  DelimText='"normal_string";"""quoted_string""";"""quoted;delimiter""";"""quoted and space""";"""starting_quote";"string_with_tab'+#9+'character";"multi'+LineEnding+
+    'line";"  SpacesInfront";"SpacesAtTheEnd   ";"  ""Spaces before quoted string"""';
+  TestName='tw19610.Get_StrictDelimTrueSafeQuote';
+var
+  TestSL: TStringList;
+  Expected: TStringList;
+  i: integer;
+begin
+  result:=true;
+  //Expected values:
+  Expected:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    Expected.Add('normal_string');
+    Expected.Add('"quoted_string"');
+    Expected.Add('"quoted;delimiter"');
+    Expected.Add('"quoted and space"');
+    Expected.Add('"starting_quote');
+    Expected.Add('string_with_tab'+#9+'character');
+    Expected.Add('multi'+LineEnding+
+      'line');
+    Expected.Add('  SpacesInfront');
+    Expected.Add('SpacesAtTheEnd   ');
+    Expected.Add('  "Spaces before quoted string"');
+
+    TestSL.Delimiter:=';'; //Match example in bug 19610, del4.zip
+    TestSL.StrictDelimiter:=true;
+    TestSL.DelimitedText:=DelimText;
+    //Test:
+    if Expected.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(Expected.Count)+' expected strings.');
+    end;
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (Expected.Count>i) and (TestSL[i]<>Expected[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+Expected[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    Expected.Free;
+    TestSL.Free;
+  end;
+end;
+
+function Get_StrictDelimFalseSafeQuote:boolean;
+// Test if input works with sdf output that has always been quoted
+// Delphi accepts this input even though it does not write it by default
+// This is a more unambiguous format than unquoted
+// Strictdelimiter:=false when processing the delimitedtext
+//
+const
+  DelimText='"normal_string";"""quoted_string""";"""quoted;delimiter""";"""quoted and space""";"""starting_quote";"string_with_tab'+#9+'character";"multi'+LineEnding+
+    'line";"  SpacesInfront";"SpacesAtTheEnd   ";"  ""Spaces before quoted string"""';
+  TestName='tw19610.Get_StrictDelimTrueSafeQuote';
+var
+  TestSL: TStringList;
+  Expected: TStringList;
+  i: integer;
+begin
+  result:=true;
+  //Expected values:
+  Expected:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    Expected.Add('normal_string');
+    Expected.Add('"quoted_string"');
+    Expected.Add('"quoted;delimiter"');
+    Expected.Add('"quoted and space"');
+    Expected.Add('"starting_quote');
+    Expected.Add('string_with_tab'+#9+'character');
+    Expected.Add('multi'+LineEnding+
+      'line');
+    Expected.Add('  SpacesInfront');
+    Expected.Add('SpacesAtTheEnd   ');
+    Expected.Add('  "Spaces before quoted string"');
+
+    TestSL.Delimiter:=';'; //Match example in bug 19610, del4.zip
+    TestSL.StrictDelimiter:=false;
+    TestSL.DelimitedText:=DelimText;
+    //Test:
+    if Expected.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(Expected.Count)+' expected strings.');
+    end;
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (Expected.Count>i) and (TestSL[i]<>Expected[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+Expected[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    Expected.Free;
+    TestSL.Free;
+  end;
+end;
+
+function Get_Commatext:boolean;
+// Test if input works with Delphi-compatible commatext
+const
+  CommaText='normal_string,"quoted_string","quoted,delimiter","quoted and space","""quoted_and_starting_quote","""quoted, starting quote, and space","quoted_with_tab'+#9+'character","quoted_multi'+LineEnding+
+    'line","  UnquotedSpacesInfront","UnquotedSpacesAtTheEnd   ","  ""Spaces before quoted string"""';
+  TestName='tw19610.Get_Commatext';
+var
+  TestSL: TStringList;
+  Expected: TStringList;
+  i: integer;
+begin
+  result:=true;
+  //Expected values:
+  Expected:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    Expected.Add('normal_string');
+    Expected.Add('quoted_string');
+    Expected.Add('quoted,delimiter');
+    Expected.Add('quoted and space');
+    Expected.Add('"quoted_and_starting_quote');
+    Expected.Add('"quoted, starting quote, and space');
+    Expected.Add('quoted_with_tab'+#9+'character');
+    Expected.Add('quoted_multi'+LineEnding+
+      'line');
+    Expected.Add('  UnquotedSpacesInfront');
+    Expected.Add('UnquotedSpacesAtTheEnd   ');
+    Expected.Add('  "Spaces before quoted string"');
+
+    TestSL.CommaText:=CommaText;
+    //Test:
+    if Expected.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(Expected.Count)+' expected strings.');
+    end;
+
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (Expected.Count>i) and (TestSL[i]<>Expected[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+Expected[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    Expected.Free;
+    TestSL.Free;
+  end;
+end;
+
+
+function Put_StrictDelimFalse:boolean;
+// Test if conversion stringlist=>delimitedtext gives the right data
+// (right in this case: what Delphi outputs)
+// Strictdelimiter:=false when processing the delimitedtext
+const
+  Expected='normal_string;"""quoted_string""";"just;delimiter";"""quoted;delimiter""";"""quoted and space""";"""starting_quote";"single""quote";"""""quoted starting quote and space""";"with_tab'+#9+'character";"multi'+LineEnding+
+    'line";"   UnquotedSpacesInfront";"UnquotedSpacesAtTheEnd  ";"  ""Spaces before quoted string"""';
+  //If we choose to output the "safely quoted" version, we need to test for it:
+  //Though this version is not the same output as Delphi, it leads to the
+  //same input if imported again (see Get_StrictDelimFalseSafeQuote for corresponding tests)
+  ExpectedSafeQuote='"normal_string";"""quoted_string""";"just;delimiter";"""quoted;delimiter""";"""quoted and space""";"""starting_quote";"single""quote";"""""quoted starting quote and space""";"with_tab'+#9+'character";"multi'+LineEnding+
+    'line";"   UnquotedSpacesInfront";"UnquotedSpacesAtTheEnd  ";"  ""Spaces before quoted string"""';
+  TestName='tw19610.Put_StrictDelimFalse';
+var
+  TestSL: TStringList;
+begin
+  result:=true;
+  TestSL:=TStringList.Create;
+  try
+    TestSL.Add('normal_string');
+    TestSL.Add('"quoted_string"');
+    TestSL.Add('just;delimiter');
+    TestSL.Add('"quoted;delimiter"');
+    TestSL.Add('"quoted and space"');
+    TestSL.Add('"starting_quote');
+    TestSL.Add('single"quote');
+    TestSL.Add('""quoted starting quote and space"');
+    TestSL.Add('with_tab'+#9+'character');
+    TestSL.Add('multi'+LineEnding+
+      'line');
+    TestSL.Add('   UnquotedSpacesInfront');
+    TestSL.Add('UnquotedSpacesAtTheEnd  ');
+    TestSL.Add('  "Spaces before quoted string"');
+
+    TestSL.Delimiter:=';';
+    TestSL.StrictDelimiter:=false;
+    if (TestSL.DelimitedText<>Expected) and (TestSL.DelimitedText<>ExpectedSafeQuote) then
+    begin
+      writeln('');
+      writeln(TestName+': failed: result:');
+      writeln('*'+TestSL.DelimitedText+'*');
+      writeln('while expected was:');
+      writeln('*'+Expected+'*');
+      writeln('- or, with safe quote output:');
+      writeln('*'+ExpectedSafeQuote+'*');
+      result:=false
+    end;
+  finally
+    TestSL.Free;
+  end;
+end;
+
+function Put_StrictDelimTrue:boolean;
+// Test if conversion stringlist=>delimitedtext gives the right data
+// (right in this case: what Delphi outputs)
+// Strictdelimiter:=true when processing the delimitedtext
+const
+  Expected='normal_string;"""quoted_string""";"just;delimiter";"""quoted;delimiter""";"""quoted and space""";"""starting_quote";"single""quote";"""""quoted starting quote and space""";with_tab'+#9+'character;multi'+LineEnding+
+    'line;   UnquotedSpacesInfront;UnquotedSpacesAtTheEnd  ;"  ""Spaces before quoted string"""';
+  //If we choose to output the "safely quoted" version, we need to test for it:
+  //Though this version is not the same output as Delphi, it leads to the
+  //same input if imported again (see Get_StrictDelimTrueSafeQuote for corresponding tests)
+  ExpectedSafeQuote='"normal_string";"""quoted_string""";"just;delimiter";"""quoted;delimiter""";"""quoted and space""";"""starting_quote";"single""quote";"""""quoted starting quote and space""";"with_tab'+#9+'character";"multi'+LineEnding+
+    'line";"   UnquotedSpacesInfront";"UnquotedSpacesAtTheEnd  ";"  ""Spaces before quoted string"""';
+  TestName='tw19610.Put_StrictDelimTrue';
+var
+  TestSL: TStringList;
+begin
+  result:=true;
+  TestSL:=TStringList.Create;
+  try
+    TestSL.Add('normal_string');
+    TestSL.Add('"quoted_string"');
+    TestSL.Add('just;delimiter');
+    TestSL.Add('"quoted;delimiter"');
+    TestSL.Add('"quoted and space"');
+    TestSL.Add('"starting_quote');
+    TestSL.Add('single"quote');
+    TestSL.Add('""quoted starting quote and space"');
+    TestSL.Add('with_tab'+#9+'character');
+    TestSL.Add('multi'+LineEnding+
+      'line');
+    TestSL.Add('   UnquotedSpacesInfront');
+    TestSL.Add('UnquotedSpacesAtTheEnd  ');
+    TestSL.Add('  "Spaces before quoted string"');
+
+    TestSL.Delimiter:=';';
+    TestSL.StrictDelimiter:=true;
+    if (TestSL.DelimitedText<>Expected) and (TestSL.DelimitedText<>ExpectedSafeQuote) then
+    begin
+      writeln('');
+      writeln(TestName+': failed: result:');
+      writeln('*'+TestSL.DelimitedText+'*');
+      writeln('while expected was:');
+      writeln('*'+Expected+'*');
+      writeln('- or, with safe quote output:');
+      writeln('*'+ExpectedSafeQuote+'*');
+      result:=false
+    end;
+  finally
+    TestSL.Free;
+  end;
+end;
+
+function GetPut_StrictDelimFalse:boolean;
+// Test if conversion stringlist=>delimitedtext=>stringlist gives identical data
+// Strictdelimiter:=false (default) when processing the delimitedtext
+const
+  TestName='tw19610.GetPut_StrictDelimFalse';
+var
+  TestSL: TStringList;
+  ResultSL: TStringList;
+  i: integer;
+begin
+  result:=true;
+  ResultSL:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    TestSL.Add('normal_string');
+    TestSL.Add('"quoted_string"');
+    TestSL.Add('"quoted;delimiter"');
+    TestSL.Add('"quoted and space"');
+    TestSL.Add('"starting_quote');
+    TestSL.Add('""quoted, starting quote, and space"');
+    TestSL.Add('with_tab'+#9+'character');
+    TestSL.Add('multi'+LineEnding+
+      'line');
+    TestSL.Add('   UnquotedSpacesInfront');
+    TestSL.Add('UnquotedSpacesAtTheEnd  ');
+    TestSL.Add('  "Spaces before quoted string"');
+
+    TestSL.Delimiter:=';';
+    TestSL.StrictDelimiter:=false;
+    ResultSL.Delimiter:=';';
+    ResultSL.StrictDelimiter:=false;
+    ResultSL.DelimitedText:=TestSL.DelimitedText;
+    //Test:
+    if ResultSL.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(ResultSL.Count)+' expected strings.');
+    end;
+
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (ResultSL.Count>i) and (TestSL[i]<>ResultSL[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+ResultSL[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    ResultSL.Free;
+    TestSL.Free;
+  end;
+end;
+
+function GetPut_StrictDelimTrue:boolean;
+// Test if conversion stringlist=>delimitedtext=>stringlist gives identical data
+// Strictdelimiter:=true when processing the delimitedtext
+const
+  TestName='tw19610.GetPut_StrictDelimTrue';
+var
+  TestSL: TStringList;
+  ResultSL: TStringList;
+  i: integer;
+begin
+  result:=true;
+  ResultSL:=TStringList.Create;
+  TestSL:=TStringList.Create;
+  try
+    TestSL.Add('normal_string');
+    TestSL.Add('"quoted_string"');
+    TestSL.Add('"quoted;delimiter"');
+    TestSL.Add('"quoted and space"');
+    TestSL.Add('"starting_quote');
+    TestSL.Add('""quoted, starting quote, and space"');
+    TestSL.Add('with_tab'+#9+'character');
+    TestSL.Add('multi'+LineEnding+
+      'line');
+    TestSL.Add('   UnquotedSpacesInfront');
+    TestSL.Add('UnquotedSpacesAtTheEnd  ');
+    TestSL.Add('  "Spaces before quoted string"');
+
+    TestSL.Delimiter:=';';
+    TestSL.StrictDelimiter:=false;
+    ResultSL.Delimiter:=';';
+    ResultSL.StrictDelimiter:=true;
+    ResultSL.DelimitedText:=TestSL.DelimitedText;
+    //Test:
+    if ResultSL.Count<>TestSL.Count then
+    begin
+      writeln('');
+      writeln(TestName+': failed: count mismatch: '+
+      inttostr(TestSL.Count)+' test strings; '+inttostr(ResultSL.Count)+' expected strings.');
+    end;
+
+    for i:=0 to TestSL.Count-1 do
+    begin
+      if (ResultSL.Count>i) and (TestSL[i]<>ResultSL[i]) then
+      begin
+        writeln('');
+        writeln(TestName+': failed: result:');
+        writeln('*'+TestSL[i]+'*');
+        writeln('while expected was:');
+        writeln('*'+ResultSL[i]+'*');
+        result:=false;
+      end;
+    end;
+  finally
+    ResultSL.Free;
+    TestSL.Free;
+  end;
+end;
+
+var
+  FailCount: integer;
+begin
+  FailCount:=0;
+  // The Get_... tests load in delimitedtext and test the resulting stringlist:
+  if not(Get_StrictDelimFalse) then FailCount:=FailCount+1;
+  if not(Get_StrictDelimTrue) then FailCount:=FailCount+1;
+  if not(Get_StrictDelimFalseCornerCases) then FailCount:=FailCount+1;
+  if not(Get_StrictDelimTrueCornerCases) then FailCount:=FailCount+1;
+  if not(Get_StrictDelimTrueSafeQuote) then FailCount:=FailCount+1;
+  if not(Get_StrictDelimFalseSafeQuote) then FailCount:=FailCount+1;
+
+  if not(Get_CommaText) then FailCount:=FailCount+1;
+
+  // The Put_... tests load strings and test the resulting delimitedtext:
+  if not(Put_StrictDelimFalse) then FailCount:=FailCount+1;
+  if not(Put_StrictDelimTrue) then FailCount:=FailCount+1;
+
+  // Test writing to delimitedtext and reading from delimitedtext:
+  if not(GetPut_StrictDelimFalse) then FailCount:=FailCount+1;
+  if not(GetPut_StrictDelimTrue) then FailCount:=FailCount+1;
+
+  // Indicate success or failure to test framework:
+  if FailCount=0 then
+  begin
+    writeln('');
+    writeln('tw19610: sdf tests succeeded.');
+  end
+  else
+  begin
+    writeln('');
+    writeln('tw19610: sdf test(s) failed. Number of failed test group(s): '+inttostr(FailCount));
+  end;
+
+  halt(FailCount);
+end.