Bogues de fractionnement de TStringList

Récemment, j'ai été informé par un utilisateur de bonne réputation SO, que TStringList a des bogues de fractionnement qui le feraient échouer l'analyse des données CSV. Je n'ai pas été informé de la nature de ces bugs, et une recherche sur internet incluant Quality Central n'a pas produit de résultats, donc je demande. Quels sont les bogues de fractionnement TStringList ?

Veuillez noter que je ne suis pas intéressé par des réponses fondées sur des opinions non fondées.


Ce Que Je savoir:

Pas grand-chose... L'un est que, ces bugs apparaissent rarement avec des données de test, mais pas si rarement dans le monde réel.

L'autre est, comme indiqué, qu'ils empêchent l'analyse correcte de CSV. Pensant qu'il est difficile de reproduire les bogues avec des données de test, je cherche (probablement) de l'aide de qui ont essayé d'utiliser une liste de chaînes comme analyseur CSV dans le code de production.

Problèmes non pertinents:

J'ai obtenu les informations sur une question étiquetée' Delphi-XE', donc échec de l'analyse en raison du caractère d'Espace "considéré comme un délimiteur" caractéristique ne pas appliquer. Parce que l'introduction de la StrictDelimiter propriété avec Delphi 2006 résolu que. Moi-même, j'utilise Delphi 2007.

De plus, comme la liste de chaînes ne peut contenir que des chaînes, elle n'est responsable que du fractionnement des champs. Toute difficulté de conversion impliquant des valeurs de champ (F. I. date, nombres à virgule flottante..) découlant des différences locales, etc. ne sont pas dans la portée.

Règles de Base:

Il n'y a pas de spécification standard pour CSV. Mais il existe des règles de base déduites de diverses spécifications .

Ci-dessous est une démonstration de la façon dont TStringList les gère. Les règles et les chaînes d'exemple proviennent de Wikipedia . Supports ([ ]) sont superposés autour des chaînes pour pouvoir voir les espaces de début ou de fin (le cas échéant) par le code de test.


les Espaces sont considérés comme faisant partie d'un champ et ne doit pas être ignoré.

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


les champs avec des virgules intégrées doivent être entourés de guillemets doubles.

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


les champs comportant des guillemets doubles intégrés doivent être entourés de guillemets doubles, et chacun des guillemets doubles intégrés doit être représenté par une paire de guillemets doubles.

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


les champs avec des sauts de ligne intégrés doivent être entourés les guillemets doubles.

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]


dans les implémentations CSV qui découpent les espaces de début ou de fin, les champs contenant de tels espaces doivent être entourés de guillemets doubles.

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


les champs peuvent toujours être entourés de guillemets doubles, qu'ils soient nécessaires ou non.

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



Code de test:

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 vous avez tout lu, la question était :), quels sont les bugs de fractionnement de " TStringList?"

30
demandé sur Sertac Akyuz 2011-06-24 02:05:09

4 réponses

Pas grand-chose... L'un est que, ces bugs apparaissent rarement avec des données de test, mais pas si rarement dans le monde réel.

Il suffit d'un cas. Les données de Test ne sont pas des données aléatoires, un utilisateur avec un cas d'échec devrait soumettre les données et voilà, nous avons un cas de test. Si personne ne peut fournir des données de test, peut-être qu'il n'y a pas de bug / échec?

Il n'y a pas de spécification standard pour CSV.

Celui-là aide à la confusion. Sans un standard spécification, comment prouvez-vous que quelque chose ne va pas? Si cela est laissé à sa propre intuition, vous pourriez avoir toutes sortes de problèmes. Voici quelques-uns de ma propre interaction heureuse avec le logiciel émis par le gouvernement; mon application était censée exporter des données au format CSV, et l'application gouvernementale était censée l'importer. Voici ce qui nous a mis dans un lot de trouble plusieurs années de suite:

  • Comment représentez-vous des données vides? Comme il n'y a pas de norme CSV, un an mon friendly gov a décidé tout va, y compris rien (deux virgules consécutives). Ensuite, ils ont décidé seules les virgules consécutives sont correctes, c'est-à-dire que Field,"",Field n'est pas valide, devrait être Field,,Field. J'ai eu beaucoup de plaisir à expliquer à mes clients que l'application gov a changé les règles de validation d'une semaine à l'autre...
  • exportez-vous des données entières nulles? C'était probablement un abus plus important, mais mon "application gov" a décidé de valider cela aussi. À un moment donné, il était obligatoire d'inclure le 0, puis il était obligatoire de ne pas inclure le 0. C'est-à-dire qu'à un moment donné Field,0,Field était valide, next Field,,Field était le seul moyen valide...

Et voici un autre cas de test où (mon) intuition a échoué:

En 1997, Ford, E350, "super, luxueux camion"

Veuillez noter l'espace entre , et "Super, et la virgule très chanceuse qui suit "Super. L'analyseur utilisé par TStrings ne voit le caractère de citation que s'ilsuit immédiatement le délimiteur. Cette chaîne est analysée comme:

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

Intuitivement, Je m'attends à:

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

Mais devinez quoi, Excel le fait de la même manière que Delphi...

Conclusion

  • TStrings.CommaText est assez bon et bien implémenté, au moins la version Delphi 2010 que j'ai regardée est assez efficace (évite plusieurs allocations de chaînes, utilise un PChar pour "marcher" la chaîne analysée) et fonctionne à peu près comme L'analyseur D'Excel.
  • Dans le monde réel, vous aurez besoin d'échanger des données avec d'autres logiciels, écrit en utilisant d'autres bibliothèques (ou pas de bibliothèques du tout), où les gens pourraient avoir manqué-interprété certains des (manquants?) les règles de CSV. Vous devrez vous adapter, et ce ne sera probablement pas un cas de bien ou de mal, mais un cas de "mes clients ont besoin d'importer cette merde". Si cela se produit, vous devrez écrire votre propre analyseur, qui s'adapte aux exigences de l'application tierce que vous traiterez. Jusqu'à ce que cela se produise, vous pouvez utiliser en toute sécurité TStrings. Et quand cela arrive, il se peut que ce ne soit pas TString ' s faute!
13
répondu Cosmin Prund 2011-06-24 08:11:15

Je vais aller sur un membre et dire que le cas d'échec le plus commun est le saut de ligne intégré. Je sais que la plupart de l'analyse CSV que je fais ignore cela. Je vais utiliser 2 TStringLists, 1 pour le fichier que j'analyse, l'autre pour la ligne actuelle. Donc, je vais me retrouver avec un code similaire à ce qui suit:

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;

Bien sûr, puisque je ne prends pas soin de m'assurer que chaque ligne est "complète" , je peux potentiellement rencontrer des cas où l'entrée contient un saut de ligne entre guillemets dans un champ et je manque il.

Jusqu'à ce que je rencontre des données réelles où c'est un problème, Je ne vais pas prendre la peine de le réparer. :- P

4
répondu afrazier 2011-06-24 13:43:15

Un autre exemple... cette TStringList.CommaText bug existe dans 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 et les méthodes associées corrompent la mémoire de la chaîne qui contient l'élément a (son caractère de terminaison null est remplacé par un ").

0
répondu Nathan Schubkegel 2013-08-06 21:19:47

Déjà essayé d'utiliser TArray<String> split?

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

, Donc arr serait:

arr[0] = 1997;
arr[1] = Ford;
arr[2] = E350;
0
répondu PâM Bolsoni 2018-08-11 02:47:14