Errores de división de TStringList


Recientemente he sido informado por un usuario de SO de buena reputación, que TStringList tiene errores de división que harían que fallara al analizar los datos CSV. No he sido informado sobre la naturaleza de estos errores, y una búsqueda en Internet que incluye Quality Central no produjo ningún resultado, así que estoy preguntando. ¿Qué son errores de división de TStringList ?

Por favor tenga en cuenta que no estoy interesado en respuestas basadas en opiniones infundadas.


Lo que yo saber:

No mucho... Uno es que, estos errores aparecen rara vez con los datos de prueba, pero no tan rara vez en el mundo real.

El otro es, como se ha dicho, que impiden el análisis adecuado de CSV. Pensando que es difícil reproducir los errores con datos de prueba, estoy (probablemente) buscando ayuda de quienes han intentado usar una lista de cadenas como un analizador CSV en el código de producción.

Problemas irrelevantes:

Obtuve la información sobre una pregunta etiquetada 'Delphi-XE', así que falla en el análisis debido a que el carácter de espacio "se considera como un delimitador" característica no aplicar. Debido a la introducción de la StrictDelimiter la propiedad con Delphi 2006 resolvió eso. Yo mismo estoy usando Delphi 2007.

Además, dado que la lista de cadenas solo puede contener cadenas, solo es responsable de dividir los campos. Cualquier dificultad de conversión que involucre valores de campo (f.i. fecha, números de coma flotante..) derivadas de diferencias locales, etc. no en el alcance.

Reglas Básicas:

No hay una especificación estándar para CSV. Pero hay reglas básicas inferidas de varias especificaciones .

A continuación se muestra una demostración de cómo TStringList los maneja. Las reglas y cadenas de ejemplo son de Wikipedia . Corchetes ([ ]) se superponen alrededor de cadenas para poder ver los espacios iniciales o finales (cuando sea relevante) por el código de prueba.


Los espacios se consideran parte de un campo y no debe ser ignorado.

Test string: [1997, Ford , E350]
Items: [1997] [ Ford ] [ E350]


Los campos con comas incrustadas deben estar encerrados entre comillas dobles.

Test string: [1997,Ford,E350,"Super, luxurious truck"]
Items: [1997] [Ford] [E350] [Super, luxurious truck]


Los campos con caracteres de comillas dobles incrustados deben estar encerrados entre caracteres de comillas dobles, y cada uno de los caracteres de comillas dobles incrustados debe estar representado por un par de caracteres de comillas dobles.

Test string: [1997,Ford,E350,"Super, ""luxurious"" truck"]
Items: [1997] [Ford] [E350] [Super, "luxurious" truck]


Los campos con saltos de línea incrustados deben estar encerrados dentro comillas dobles.

Test string: [1997,Ford,E350,"Go get one now
they are going fast"]
Items: [1997] [Ford] [E350] [Go get one now
they are going fast]


En las implementaciones CSV que recortan espacios iniciales o finales, los campos con dichos espacios deben estar encerrados entre comillas dobles.

Test string: [1997,Ford,E350," Super luxurious truck "]
Items: [1997] [Ford] [E350] [ Super luxurious truck ]


Los campos siempre pueden estar encerrados entre comillas dobles, sean necesarios o no.

Test string: ["1997","Ford","E350"]
Items: [1997] [Ford] [E350]



Código de prueba:

var
  SL: TStringList;
  rule: string;

  function GetItemsText: string;
  var
    i: Integer;
  begin
    for i := 0 to SL.Count - 1 do
      Result := Result + '[' + SL[i] + '] ';
  end;

  procedure Test(TestStr: string);
  begin
    SL.DelimitedText := TestStr;
    Writeln(rule + sLineBreak, 'Test string: [', TestStr + ']' + sLineBreak,
            'Items: ' + GetItemsText + sLineBreak);
  end;

begin
  SL := TStringList.Create;
  SL.Delimiter := ',';        // default, but ";" is used with some locales
  SL.QuoteChar := '"';        // default
  SL.StrictDelimiter := True; // required: strings are separated *only* by Delimiter

  rule := 'Spaces are considered part of a field and should not be ignored.';
  Test('1997, Ford , E350');

  rule := 'Fields with embedded commas must be enclosed within double-quote characters.';
  Test('1997,Ford,E350,"Super, luxurious truck"');

  rule := 'Fields with embedded double-quote characters must be enclosed within double-quote characters, and each of the embedded double-quote characters must be represented by a pair of double-quote characters.';
  Test('1997,Ford,E350,"Super, ""luxurious"" truck"');

  rule := 'Fields with embedded line breaks must be enclosed within double-quote characters.';
  Test('1997,Ford,E350,"Go get one now'#10#13'they are going fast"');

  rule := 'In CSV implementations that trim leading or trailing spaces, fields with such spaces must be enclosed within double-quote characters.';
  Test('1997,Ford,E350," Super luxurious truck "');

  rule := 'Fields may always be enclosed within double-quote characters, whether necessary or not.';
  Test('"1997","Ford","E350"');

  SL.Free;
end;



Si lo has leído todo, la pregunta fue :), ¿qué son los errores de división de TStringList?"

Author: Sertac Akyuz, 2011-06-24

4 answers

No mucho... Uno es que, estos errores aparecen rara vez con los datos de prueba, pero no tan rara vez en el mundo real.

Todo lo que se necesita es un caso. Los datos de prueba no son datos aleatorios, un usuario con un caso de falla debe enviar los datos y voilà, tenemos un caso de prueba. Si nadie puede proporcionar datos de prueba, tal vez no hay error/fallo?

No hay una especificación estándar para CSV.

Ese sí que ayuda con la confusión. Sin un estándar especificación, ¿cómo demuestras que algo está mal? Si esto se deja a la propia intuición, usted podría entrar en todo tipo de problemas. Aquí hay algo de mi propia interacción feliz con el software emitido por el gobierno; Se suponía que mi aplicación exportaría datos en formato CSV, y se suponía que la aplicación del gobierno los importaría. Esto es lo que nos metió en un lote de problemas varios años seguidos: {[18]]}

  • ¿Cómo se representan los datos vacíos? Dado que no hay estándar CSV, un año mi friendly gov decidió que todo vale, incluyendo nada (dos comas consecutivas). Luego decidieron solo las comas consecutivas están bien, es decir, Field,"",Field no es válido, debe ser Field,,Field. Me divertí mucho explicando a mis clientes que la aplicación gov cambió las reglas de validación de una semana a la siguiente...
  • ¿Exporta datos enteros CERO? Esto fue probablemente un abuso más grande, pero mi "gov app" decidió validar que también. En un momento era obligatorio incluir el 0, entonces fue obligatorio NO incluir el 0. Es decir, en un momento Field,0,Field era válido, siguiente Field,,Field era la única manera válida...

Y aquí hay otro caso de prueba donde (mi) intuición falló:

1997, Ford, E350,"Super, camión de lujo"

Tenga en cuenta el espacio entre , y "Super, y la coma muy afortunada que sigue "Super. El analizador empleado por TStrings solo ve el carácter de comilla si inmediatamente sigue el delimitador. Esa cadena está analizada as:

[1997]
[ Ford]
[ E350]
[ "Super]
[ luxurious truck"]

Intuitivamente esperaría: {[18]]}

[1997]
[ Ford]
[ E350]
[Super luxurious truck]

Pero adivina qué, Excel lo hace de la misma manera que Delphi lo hace...

Conclusión

  • TStrings.CommaText es bastante bueno y está bien implementado, al menos la versión de Delphi 2010 que miré es bastante efectiva (evita asignaciones de múltiples cadenas, usa un PChar para "recorrer" la cadena analizada) y funciona aproximadamente igual que el analizador de Excel.
  • En el mundo real tendrá que intercambiar datos con otro software, escrito utilizando otras bibliotecas (o ninguna biblioteca en absoluto), donde la gente podría haber malinterpretado algunos de los (falta?) reglas de CSV. Tendrás que adaptarte, y probablemente no será un caso de bien o mal, sino un caso de"mis clientes necesitan importar esta mierda". Si eso sucede, tendrás que escribir tu propio analizador, uno que se adapte a los requisitos de la aplicación de terceros con la que estarías tratando. Hasta que eso suceda, puede usar TStrings de forma segura. Y cuando sucede, puede que no sea TString ' s la culpa!
 13
Author: Cosmin Prund,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-24 08:11:15

Voy a arriesgarme y decir que el caso de falla más común es el salto de línea incrustado. Sé que la mayoría del análisis CSV que hago ignora eso. Usaré 2 TStringLists, 1 para el archivo que estoy analizando, el otro para la línea actual. Así que terminaré con un código similar al siguiente:

procedure Foo;
var
    CSVFile, ALine: TStringList;
    s: string;

begin
    CSVFile := TStringList.Create;
    ALine := TStringList.Create;
    ALine.StrictDelimiter := True;
    CSVFile.LoadFromFile('C:\Path\To\File.csv');
    for s in CSVFile do begin
        ALine.CommaText := s;
        DoSomethingInteresting(ALine);
    end;
end;

Por supuesto, ya que no estoy teniendo cuidado de asegurarme de que cada línea esté "completa", potencialmente puedo encontrarme con casos en los que la entrada contiene un salto de línea citado en un campo y me pierdo se.

Hasta que me encuentre con datos del mundo real donde es un problema, no voy a molestarme en arreglarlo. :- P

 4
Author: afrazier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-24 13:43:15

Otro ejemplo... esta lista.El error CommaText existe en Delphi 2009.

procedure TForm1.Button1Click(Sender: TObject);
var
  list : TStringList;
begin
  list := TStringList.Create();
  try
    list.CommaText := '"a""';
    Assert(list.Count = 1);
    Assert(list[0] = 'a');
    Assert(list.CommaText = 'a'); // FAILS -- actual value is "a""
  finally
    FreeAndNil(list);
  end;
end;

La TStringList.CommaText setter y los métodos relacionados corrompen la memoria de la cadena que contiene el elemento a (su carácter terminador nulo se sobrescribe con un ").

 0
Author: Nathan Schubkegel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-06 21:19:47

¿Ya probaste usar TArray<String> dividir?

var
text: String;
arr: TArray<String>;
begin
text := '1997,Ford,E350';
arr := text.split([',']);

Así que arr sería:

arr[0] = 1997;
arr[1] = Ford;
arr[2] = E350;
 0
Author: PâM Bolsoni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-11 02:47:14