delphi 7 tstringlist
TStringList dividiendo errores (3)
Recientemente un usuario de SO de buena reputación me ha informado que TStringList
tiene errores que causan que falle el análisis de datos CSV. No me han informado sobre la naturaleza de estos errores, y una búsqueda en Internet que incluye Quality Central no arrojó ningún resultado, por lo que estoy preguntando. ¿Qué son los errores de división de TStringList ?
Tenga en cuenta que no estoy interesado en respuestas basadas en opiniones infundadas.
Lo que yo sé:
No mucho ... Uno es que estos errores aparecen raramente con datos de prueba, pero no tan raramente en el mundo real.
El otro es, como se dijo, que impiden el análisis adecuado de CSV. Al pensar que es difícil reproducir los errores con los datos de prueba, estoy (probablemente) buscando ayuda de quienes hayan intentado usar una lista de cadenas como analizador de CSV en el código de producción.
Problemas irrelevantes:
Obtuve la información en una pregunta etiquetada ''Delphi-XE'', por lo que no se aplica el análisis fallido debido a la característica "carácter de espacio considerado como un delimitador" . Debido a que la introducción de la propiedad StrictDelimiter
con Delphi 2006 resolvió eso. Yo, 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 (fecha, números de coma flotante ...) que surjan de las diferencias regionales, etc., no están dentro del alcance.
Reglas básicas:
No hay especificaciones estándar para CSV. Pero hay reglas básicas deducidas de varias especificaciones .
A continuación se muestra cómo TStringList maneja estos. Las reglas y cadenas de ejemplo son de Wikipedia . Los corchetes ( [
]
) se superponen alrededor de las 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 deben ignorarse.
Test string: [1997, Ford , E350] Items: [1997] [ Ford ] [ E350]
Los campos con comas incrustadas deben estar entre caracteres de 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 entre caracteres de comillas dobles, y cada uno de los caracteres de comillas dobles debe representarse mediante 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 entre caracteres de 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 los espacios iniciales o finales, los campos con dichos espacios deben estar entre caracteres de comillas dobles.
Test string: [1997,Ford,E350," Super luxurious truck "] Items: [1997] [Ford] [E350] [ Super luxurious truck ]
Los campos siempre pueden estar dentro de caracteres de 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 era :), ¿qué son los "errores de división de TStringList"?
No mucho ... Uno es que estos errores aparecen raramente con datos de prueba, pero no tan raramente en el mundo real.
Todo lo que 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 errores / fallas?
No hay especificaciones estándar para CSV.
Ese seguro ayuda con la confusión. Sin una especificación estándar, ¿cómo demuestra que algo está mal? Si esto se deja a la propia intuición, podrías tener todo tipo de problemas. Aquí hay algunos de mi propia interacción feliz con el software emitido por el gobierno; Se suponía que mi aplicación debía exportar datos en formato CSV, y se suponía que la aplicación del gobierno la importaría. Esto es lo que nos metió en muchos problemas durante varios años seguidos:
- ¿Cómo se representan los datos vacíos? Como no hay un estándar de CSV, un año mi amigo amigo decidió que todo funcionara, incluso nada (dos comas consecutivas). Luego decidieron que solo las comas consecutivas son correctas, es decir,
Field,"",Field
no es válido, debe serField,,Field
. Me divertí mucho explicando a mis clientes que la aplicación del gobierno cambió las reglas de validación de una semana a otra ... - ¿Exporta datos enteros ZERO? Este fue probablemente un abuso mayor, pero mi "aplicación de gobierno" decidió validar eso también. Hubo un tiempo en que era obligatorio incluir el
0
, entonces era obligatorio NO incluir el0
. Es decir, al mismo tiempoField,0,Field
era válido, nextField,,Field
era la única forma válida ...
Y aquí hay otro caso de prueba en el que (mi) intuición falló:
1997, Ford, E350, "Super, camión de lujo"
Tenga en cuenta el espacio entre ,
y "Super
, y la muy afortunada coma que sigue "Super
. El analizador empleado por TStrings
solo ve la cita char si sigue inmediatamente al delimitador. Esa cadena se analiza como:
[1997]
[ Ford]
[ E350]
[ "Super]
[ luxurious truck"]
Intuitivamente esperaba:
[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 he analizado es bastante efectiva (evita múltiples asignaciones de cadenas, utilizaPChar
para "recorrer" la cadena analizada) y funciona de la misma manera que el analizador de Excel. - En el mundo real, tendrá que intercambiar datos con otro software, escrito utilizando otras bibliotecas (o ninguna biblioteca), donde las personas podrían haber interpretado mal algunas de las reglas (que faltan) de CSV. Tendrá que adaptarse, y probablemente no será un caso de correcto o incorrecto sino un caso de "mis clientes necesitan importar esta porquería". 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
maneraTStrings
. Y cuando sucede, ¡puede que no seaTString
deTString
!
Otro ejemplo ... este error TStringList.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;
El setter de texto TStringList.CommaText y los métodos relacionados corrompen la memoria de la cadena que contiene el elemento a (un carácter de terminación nulo se sobrescribe con un "
).
Voy a dar un paso en falso y decir que el caso de falla más común es el linebreak incrustado. Sé que la mayoría del análisis de CSV que hago ignora eso. Usaré 2 TStringLists, 1 para el archivo que estoy analizando y 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, dado que no me estoy ocupando de asegurarme de que cada línea esté "completa", puedo encontrar casos en los que la entrada contenga un salto de línea citado en un campo y lo echo de menos.
Hasta que no encuentre datos del mundo real en los que sea un problema, no me molestaré en solucionarlo. :-PAG