31

最近、評判の良い SO ユーザーから、TStringListCSV データの解析に失敗する分割バグがあるとの連絡を受けました。これらのバグの性質について知らされておらず、Quality Centralを含むインターネットで検索しても結果が得られなかったので、お尋ねします。TStringList 分割バグとは何ですか?

根拠のない意見に基づく回答には興味がないことに注意してください。


私が知っていること:

それほど多くはありません... 1つは、これらのバグがテストデータで表示されることはめったにありませんが、実際にはめったに表示されないということです。

もう 1 つは、前述のように、CSV の適切な解析を妨げることです。テスト データでバグを再現するのは難しいと考えて、(おそらく) 製品コードで文字列リストを CSV パーサーとして使用しようとした人に助けを求めています。

無関係の問題:

「Delphi-XE」タグ付きの質問の情報を入手したので、「スペース文字を区切り文字と見なす」 機能による解析の失敗は当てはまりません。StrictDelimiterDelphi 2006 でのプロパティの導入により、それが解決されたためです。私自身、Delphi 2007 を使用しています。

また、文字列リストは文字列しか保持できないため、フィールドの分割のみを担当します。ロケールの違いなどに起因するフィールド値 (fi 日付、浮動小数点数など) に関連する変換の問題は対象外です。

基本的なルール:

CSV の標準仕様はありません。しかし、さまざまな仕様から推測される基本的なルールがあります。

以下は、TStringList がこれらを処理する方法のデモです。ルールと文字列の例はWikipediaからのものです。角かっこ ( [ ]) は、テスト コードによって先頭または末尾のスペース (該当する場合) を確認できるように、文字列の周りに重ねられます。


スペースはフィールドの一部と見なされるため、無視しないでください。

テスト文字列: [1997、フォード、E350]
アイテム: [1997] [フォード] [E350]


コンマが埋め込まれたフィールドは、二重引用符で囲む必要があります。

テスト文字列: [1997年、フォード、E350、「スーパー、ラグジュアリー トラック」]
アイテム: [1997] [フォード] [E350] [超高級トラック]


二重引用符文字が埋め込まれたフィールドは二重引用符文字で囲む必要があり、埋め込まれた二重引用符文字はそれぞれ二重引用符文字のペアで表す必要があります。

テスト文字列: [1997,Ford,E350,"Super, ""luxurious"" truck"]
アイテム: [1997] [フォード] [E350] [スーパー、「豪華な」トラック]


改行が埋め込まれたフィールドは、二重引用符で囲む必要があります。

テスト文字列: [1997,Ford,E350,"Go get one now.
彼らは速く進んでいます」]
アイテム: [1997] [フォード] [E350] [今すぐ入手してください。
彼らは速く進んでいます]


先頭または末尾のスペースを削除する CSV 実装では、そのようなスペースを含むフィールドを二重引用符で囲む必要があります。

テスト文字列: [1997年、フォード、E350、「スーパー ラグジュアリー トラック」]
アイテム: [1997] [フォード] [E350] [超高級トラック]


フィールドは、必要かどうかに関係なく、常に二重引用符で囲むことができます。

テスト文字列: ["1997","Ford","E350"]
アイテム: [1997] [フォード] [E350]



テスト コード:

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;



すべて読んだ場合、質問は :)、「TStringList 分割バグ」とは何ですか?

4

4 に答える 4

13

それほど多くはありません... 1つは、これらのバグがテストデータで表示されることはめったにありませんが、実際にはめったに表示されないということです。

必要なのは1つのケースだけです。テスト データはランダム データではありません。1 つの失敗ケースを持つ 1 人のユーザーがデータを送信する必要があり、ほら、テスト ケースができました。誰もテストデータを提供できない場合、バグ/障害はありませんか?

CSV の標準仕様はありません。

それは確かに混乱に役立ちます。標準仕様がない場合、何かが間違っていることをどのように証明しますか? これを自分の勘に任せると、いろいろなトラブルに巻き込まれる可能性があります。これは、政府発行のソフトウェアとの私自身の楽しいやり取りの一部です。私のアプリケーションはデータを CSV 形式でエクスポートし、政府のアプリケーションはそれをインポートすることになっていました。これが、数年連続で私たちを多くの問題に陥らせた理由です。

  • 空のデータをどのように表現しますか? CSV 標準がないため、ある年、私の友好的な政府は、何もしない (2 つの連続するコンマ) を含めて、何でもよいと決定しました。次に、連続するコンマのみField,"",Fieldが OK である、つまり無効であると判断しField,,Fieldました。政府アプリが検証ルールをある週から次の週に変更したことを顧客に説明するのはとても楽しかったです...
  • ゼロ整数データをエクスポートしますか? これはおそらくより大きな悪用でしたが、私の「政府アプリ」はそれも検証することにしました。かつては を含めることが義務付けられていましたが0、その後は を含めることは必須ではありませんでした0。つまり、かつてField,0,Fieldは有効でしたが、次Field,,Fieldは唯一の有効な方法でした...

そして、ここに(私の)直感が失敗した別のテストケースがあります:

1997年、フォード、E350、「超豪華トラック」

,と の間のスペースと"Super、それに続く非常に幸運なコンマに注意してください"Super。で使用されるパーサーは、区切り文字の直後にTStringsある場合にのみ引用文字を認識します。その文字列は次のように解析されます。

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

直感的に私は期待します:

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

しかし、Excel は Delphi が行うのと同じ方法でそれを行います...

結論

  • TStrings.CommaText少なくとも私が見た Delphi 2010 バージョンは非常に効果的で (複数の文字列の割り当てを回避しPChar、解析された文字列を "ウォーク" するために a を使用します)、Excel のパーサーとほぼ同じように動作します。
  • 現実の世界では、他のライブラリを使用して (またはライブラリをまったく使用せずに) 作成された他のソフトウェアとデータを交換する必要があります。この場合、人々は CSV の (欠落している?) ルールの一部を誤解している可能性があります。あなたは適応しなければならず、それはおそらく正しいか間違っているかではなく、「私のクライアントはこのがらくたをインポートする必要がある」というケースになるでしょう. その場合は、処理するサードパーティ アプリの要件に適合する独自のパーサーを作成する必要があります。それまでは安心してご利用いただけますTStrings。そして、それが起こったとき、それはTStringのせいではないかもしれません!
于 2011-06-24T08:11:15.270 に答える
4

端的に言って、最も一般的な失敗のケースは改行が埋め込まれていることです。私が行うCSV解析のほとんどがそれを無視していることを私は知っています。2 つの TStringList を使用します。1 つは解析中のファイル用、もう 1 つは現在の行用です。したがって、次のようなコードになります。

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;

もちろん、各行が「完全」であることを確認していないため、入力のフィールドに引用符付きの改行が含まれていて、それを見逃す可能性があります。

それが問題となる実世界のデータに出くわすまで、わざわざ修正するつもりはありません。:-P

于 2011-06-24T12:58:41.693 に答える