1

次の形式のCSV値を1行ずつ読みました。

30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y"

これらの項目をさらに解析するには、これを次のようなものに分解する必要があります。

30: "NY"

41: "JOHN S."

36: "HAMPTON"

42: "123 Road Street, NY"(コンマに注意してください)

...

私はFileHelper,ライブラリを使用していますが、区切り文字で区切られたコンマで分割したいのですが、行ごとに読むのが好きなようです。

私はレコードクラスを持っています:

[DelimitedRecord(",")]
class BoxRecord
{
    public String record;
}

そして、私は以下を介して配列内のいくつかのオブジェクトになることを望んでいたものを取得しますが、それは私に元の行を返すだけです:

DelimitedFileEngine engine = new DelimitedFileEngine(typeof(BoxRecord));
BoxRecord[] boxes = (BoxRecord[])engine.ReadString(boxLine);

私が含めたいboxes[].recordもの:

30: "NY"

41: "JOHN S."

36: "HAMPTON"

42: "123 Road Street, NY"

...

実際に含まれているもの:

30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y"

4

3 に答える 3

2

行を取得したら、以下の linq に基づいて行を分割して、必要なものを取得できます。

string input = "30: \"NY\", 41: \"JOHN S.\", " +
   "36: \"HAMPTON\", 42: \"123 Road Street, NY\", 68: \"Y\"";

var tempList = input.Split('\"').ToList();

var result = Enumerable.Range(0, tempList.Count/2)
    .Select(i => string.Join(": "
        , tempList[2*i].Split(new[] { ',', ':' })
           .Single(ss => !string.IsNullOrWhiteSpace(ss))

        , tempList[2*i + 1]));

更新:私にとって興味深いようです。このコードは、ケースをコメントとして処理することです:

var tempList1 = input.Split(':').ToList();

var tempList2 = tempList1.SelectMany((s, index) =>
 {
     if (index == 0 || index == tempList1.Count - 1)
         return new List<string>() { s };

     var subList = s.Split(',');
     return new List<string>
           { 
                string.Concat(subList.Take(subList.Length - 1)),
                subList.Last()
           };
 }).ToList();

var result = Enumerable.Range(0, tempList2.Count / 2)
         .Select(i => string.Join(": ", tempList2[2 * i], tempList2[2 * i + 1]));
于 2012-08-15T18:35:18.200 に答える
1

あなたが見ているサンプルは、技術的に言えば、有効な CSV 形式のファイルではありません。基本的に、ファイルを提供した人は誰でも、テキスト修飾子記号 - 二重引用符 " - を非標準的な方法で使用しました。従来の使用方法は次のとおりです。

123,"Sue said, ""Hi, this is a test!""",2012-08-15

このステートメントは次のように解析する必要があります。

Assert.AreEqual(line.Length, 3);
Assert.AreEqual(line[0], @"123");
Assert.AreEqual(line[1], @"Sue said, ""Hi, this is a test!""");
Assert.AreEqual(line[2], @"2012-08-15");

あなたの質問で提供されたサンプルCSVから、私が見た基準によると、正しい処理は基本的に引用符をテキスト修飾子ではなく文字列内の通常の文字として扱う必要があります。これが私があなたのセリフをどのように解釈するかです - もし私が間違っていたら教えてください!

Assert.AreEqual(line.Length, 6);
Assert.AreEqual(line[0], @"30: ""NY""");
Assert.AreEqual(line[1], @" 41: ""JOHN S.""");
Assert.AreEqual(line[2], @" 36: ""HAMPTON""");
Assert.AreEqual(line[3], @" 42: ""123 Road Street");
Assert.AreEqual(line[4], @" NY""");
Assert.AreEqual(line[5], @" 68: ""Y""");

テキストがテキスト修飾されているか、適切に区切られているかを判断できないため、FileHelper が壊れていると思います。これを処理するには、カスタム コードを使用することをお勧めします。Cuong Le が提供するソリューションは、あなたのソリューションに適しているようです。

参考までに、私の C# CSV ライブラリはこちらです: https://code.google.com/p/csharp-csv-reader/

編集:楽しみのために、正規表現を使用してこれをデコードできるかどうか疑問に思いました。厳密に CSV でなくても、データは一貫してフォーマットされているため、ツールボックスには別のものがある可能性があります。

String mystring = @"30: ""NY"", 41: ""JOHN S."", 36: ""HAMPTON"", 42: ""123 Road Street, NY"", 68: ""Y""
    20: ""STEVE"", 12: ""JONES"", 96: ""1600 PENNSYLVANIA AVE, NW""
    30: ""NY"", 41: ""JOHN S."", 36: ""HAMPTON"", 42: ""123 Road Street, NY"", 68: ""Y"", 40: 12345";
Regex r = new Regex(@"(?<id>\d*): (""(?<field>[^""]*)""|(?<field>[\d]*))");
MatchCollection mc = r.Matches(mystring);
foreach (Match m in mc) {
    Console.WriteLine("{0}: {1}", m.Groups["id"], m.Groups["field"]);
}

基本的に、正規表現は 2 桁の 10 進数を探し、その後にコロン - スペース - 二重引用符が続きます。次に、別の二重引用符に到達するまで、すべてのテキストを検索します。私のテストから、これは質問で説明した両方のテスト行に対しても正しい一致を生成します。

私の正規表現が正しくない場合は、ここで利用できる気の利いたオンライン正規表現テスターがあります: http://gskinner.com/RegExr/ - データをコピーして検索領域に貼り付け、この正規表現文字列を出発点として使用してみてください:

(?<id>\d*): ("(?<field>[^"]*)"|(?<field>[\d]*))

EDIT2:以下のコメントで引用した「40:12345」の値も考慮するように正規表現を修正しました。すべての例ですべてのフィールドが正しく検出されるようになりました。

EDIT3: 別のリクエストから、この正規表現はコロンの前に無制限の長さの数字をサポートするようになりました。正規表現がどのように機能するかの簡単な説明は次のとおりです。

  • (?<id>\d*)- この最初のチャンクはキャプチャ グループと呼ばれます。キャプチャ グループは括弧で囲まれています。*10 進数 ( ) の繰り返し文字列 ( ) をキャプチャし、\d「id」( ?<id>) という名前を付けようとします。
  • :- レコード間のコロン スペースに一致します。
  • "(?<field>[^"]*)"- 開始の引用符を検索し、次に引用符 ( ) 以外の多数の文字を検索し、[^"]別の引用符で終了します。結果を「フィールド」に保存します。
  • (?<field>[\d]*)- 任意の数の 10 進数を検索し、結果を「フィールド」に保存します。一部の正規表現エンジンは、同じ名前の 2 つのキャプチャ グループを持つことをサポートしていないことに注意してください。一方を「field1」、もう一方を「field2」と呼ぶ必要があるかもしれません。
于 2012-08-15T22:29:23.763 に答える
0

私が遭遇したすべての「車輪を再発明しないでください」という投稿(すべて)にもかかわらず、それは私にとって最良の解決策であり、機能することがわかった唯一の解決策です.

FileHelper フレームワーク、Cuong Le の回答、および VB を使用してみましたTextFieldParser。それぞれが異なる方法で機能し、機能しませんでした。

これを解析できるようにする必要がありました (「非標準」の CSV 形式)。これらの行はファイルからの入力ですが、CSV ファイルではありません。それらはより大きな構造の一部です:

30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y", 40: 12345


FileHelper は、引用符で囲まれたカンマで分割します。たとえば、次のようになります。

123 Road Street, NY

になる

213 Road Street

NY

Cuong Le's answer はケースを処理しませんでした: 40: 12345(引用符なしのデータ値)

TextFieldParser も、FileHelper のように、引用符で囲まれたコンマで分割されます。


私の簡単で汚れた、独自のソリューション(そしてそれは機能します!):

    private List<KeyValuePair<string, string>> SplitBoxLine(String input)
    {
        //SAMPLE input:
        //30: "NY", 41: "JOHN S.", 36: "HAMPTON", 42: "123 Road Street, NY", 68: "Y", 40: 12345

        List<KeyValuePair<string, string>> boxes = new List<KeyValuePair<string, string>>();

        int quoteCount = 0;
        String buffer = "";
        String boxNum = "";
        String boxValue = "";

        for (int i = 0; i < input.Length; i++)
        {
            if (i == input.Length - 1)
            {
                //if the input character at the end ISN'T a quote or comma, add it to the buffer
                //supports the case where the last item is 40: 12345
                if (input[i] != ',' && input[i] != '\"')
                {
                    buffer += input[i];
                }
                boxValue = String.Copy(buffer.Trim());

                //once we have the value, we can create the pair
                KeyValuePair<string, string> pair = new KeyValuePair<string, string>(boxNum, boxValue);
                boxes.Add(pair);

                Console.WriteLine("BOX VALUE [LAST ITEM]: " + boxValue);
            }

            if (input[i] == ':')
            {
                boxNum = String.Copy(buffer.Trim());
                buffer = "";
                Console.WriteLine("BOX NUM: " + boxNum);
            }
            else if (input[i] == '\"')
            {
                quoteCount++;
            }
            else if (input[i] == ',')
            {
                if (quoteCount % 2 == 0) //comma occurs outside of quotes
                {
                    boxValue = String.Copy(buffer.Trim());
                    buffer = "";

                    //once we have the value, we can create the pair
                    KeyValuePair<string, string> pair = new KeyValuePair<string, string>(boxNum, boxValue);
                    boxes.Add(pair);

                    Console.WriteLine("BOX VALUE: " + boxValue);
                }
                else //the comma occurs in some quotes
                {
                    buffer += input[i]; //add the comma, it's just part of the boxValue
                }
            }
            //nothing special about this chacter, add it to the buffer and continue
            else
            {
                buffer += input[i];
            }
        }

        return boxes;
    }
于 2012-08-15T22:09:15.613 に答える