0

これは、フォーマットする必要があるデータの例です。

最初の列は単純で、2 番目の列は問題です。

  1. 複数のデータ フィールドを 1 つの列にフォーマットするには、どのような方法が最適でしょうか?
  2. このデータを解析する方法は?

重要*: 2 番目の列には、以下の例のように複数の値を含める必要があります

Name       Details

Alex       Age:25
           Height:6
           Hair:Brown
           Eyes:Hazel
4

3 に答える 3

1

csv はおそらく次のようになります。

Name,Age,Height,Hair,Eyes
Alex,25,6,Brown,Hazel

各セルは、隣接するセルと正確に 1 つのコンマで区切られている必要があります。

特定の改行および改行以外の空白をコンマに置き換える単純な正規表現を使用して、そのように再フォーマットできます (両方の列に値があるため、各ブロックを簡単に見つけることができます)。

于 2012-05-08T12:07:59.270 に答える
0

これは興味深いものです。特定の形式のファイルを解析するのは非常に難しい場合があるため、それらを処理するために特定のクラスを作成することがよくあります。CSV などの従来のファイル形式やその他の区切り形式は、同様の方法でフォーマットされているため、[より] 読みやすくなっています。

上記のような問題は、次の方法で対処できます。

1) 出力はどのようになりますか?

あなたの例では、これは単なる推測ですが、次のことを目指していると思います。

Name, Age, Height, Hair, Eyes
Alex, 25, 6, Brown, Hazel

その場合、上記の構造に基づいてこの情報を解析する必要があります。上記のようにテキストのブロックが繰り返される場合は、次のように言えます。

a. すべての人は、名前の詳細で始まるブロックに含まれています

b. 名前の値は詳細の後の最初のテキストで、他の列は列:値の形式で区切られています。

ただし、追加の属性を含むセクションや、元の入力がオプションの場合に欠落している属性もある可能性があるため、列と序数を追跡することも役立ちます。

したがって、1 つのアプローチは次のようになります。

public void ParseFile(){

        String currentLine;

        bool newSection = false;

        //Store the column names and ordinal position here.
        List<String> nameOrdinals = new List<String>();
        nameOrdinals.Add("Name"); //IndexOf == 0

        Dictionary<Int32, List<String>> nameValues = new Dictionary<Int32 ,List<string>>(); //Use this to store each person's details

        Int32 rowNumber = 0;

        using (TextReader reader = File.OpenText("D:\\temp\\test.txt"))
        {

            while ((currentLine = reader.ReadLine()) != null) //This will read the file one row at a time until there are no more rows to read
            {

                string[] lineSegments = currentLine.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);

                if (lineSegments.Length == 2 && String.Compare(lineSegments[0], "Name", StringComparison.InvariantCultureIgnoreCase) == 0
                    && String.Compare(lineSegments[1], "Details", StringComparison.InvariantCultureIgnoreCase) == 0) //Looking for a Name  Details Line - Start of a new section
                {
                    rowNumber++;
                    newSection = true;
                    continue;
                }

                if (newSection && lineSegments.Length > 1) //We can start adding a new person's details - we know that 
                {
                    nameValues.Add(rowNumber, new List<String>());
                    nameValues[rowNumber].Insert(nameOrdinals.IndexOf("Name"), lineSegments[0]);

                    //Get the first column:value item
                    ParseColonSeparatedItem(lineSegments[1], nameOrdinals, nameValues, rowNumber);

                    newSection = false;
                    continue;
                }

                if (lineSegments.Length > 0 && lineSegments[0] != String.Empty) //Ignore empty lines
                {
                    ParseColonSeparatedItem(lineSegments[0], nameOrdinals, nameValues, rowNumber);
                }

            }
        }


        //At this point we should have collected a big list of items. We can then write out the CSV. We can use a StringBuilder for now, although your requirements will
        //be dependent upon how big the source files are.

        //Write out the columns

        StringBuilder builder = new StringBuilder();

        for (int i = 0; i < nameOrdinals.Count; i++)
        {
            if(i == nameOrdinals.Count - 1)
            {
                builder.Append(nameOrdinals[i]);
            }
            else
            {
                builder.AppendFormat("{0},", nameOrdinals[i]);
            }
        }

        builder.Append(Environment.NewLine);


        foreach (int key in nameValues.Keys)
        {
            List<String> values = nameValues[key];

            for (int i = 0; i < values.Count; i++)
            {
                if (i == values.Count - 1)
                {
                    builder.Append(values[i]);
                }
                else
                {
                    builder.AppendFormat("{0},", values[i]);
                }
            }

            builder.Append(Environment.NewLine);

        }

        //At this point you now have a StringBuilder containing the CSV data you can write to a file or similar




    }


    private void ParseColonSeparatedItem(string textToSeparate, List<String> columns, Dictionary<Int32, List<String>> outputStorage, int outputKey)
    {

        if (String.IsNullOrWhiteSpace(textToSeparate)) { return; }

        string[] colVals = textToSeparate.Split(new[] { ":" }, StringSplitOptions.RemoveEmptyEntries);

        List<String> outputValues = outputStorage[outputKey];

        if (!columns.Contains(colVals[0]))
        {
            //Add the column to the list of expected columns. The index of the column determines it's index in the output
            columns.Add(colVals[0]);

        }

        if (outputValues.Count < columns.Count)
        {
            outputValues.Add(colVals[1]);
        }
        else
        {
            outputStorage[outputKey].Insert(columns.IndexOf(colVals[0]), colVals[1]); //We append the value to the list at the place where the column index expects it to be. That way we can miss values in certain sections yet still have the expected output
        }
    }

これをファイルに対して実行すると、文字列ビルダーには次が含まれます。

"Name,Age,Height,Hair,Eyes\r\nAlex,25,6,Brown,Hazel\r\n"

上記と一致するもの (\r\n は実質的に Windows の改行マーカーです)

このアプローチは、カスタム パーサーがどのように機能するかを示しています。ここで行われる可能性のあるリファクタリングが多数あるため、意図的に冗長になっています。これは単なる例です。

改善点は次のとおりです。

1) この関数は、実際のテキスト項目自体にスペースがないことを前提としています。これはかなり大きな仮定であり、間違っている場合は、線分を解析するための別のアプローチが必要になります。ただし、これを変更する必要があるのは 1 か所だけです。一度に 1 行ずつ読み取るときに正規表現を適用するか、単に文字を読み取って、最初の「column:」セクションの後のすべてが値であると想定することができます。たとえば、 .

2) 例外処理なし

3) テキスト出力は引用されません。各値をテストして、日付または数値であるかどうかを確認できます。そうでない場合は、他のプログラム (Excel など) が基になるデータ型をより効果的に保持しようとするため、引用符で囲みます。

4) 列名が繰り返されていないと仮定します。そうである場合は、列項目が既に追加されているかどうかを確認し、解析セクションで ColName2 列を作成する必要があります。

于 2012-05-08T12:19:43.480 に答える
0

CSV ファイルは、通常、フィールド区切り記号としてコンマを使用し、行区切り記号として CR を使用して定義されます。2 番目の列で CR を使用しているため、問題が発生します。複数の値の間で別の形式の区切り記号を使用するには、2 番目の列を再フォーマットする必要があります。一般的な代替セパレーターは | です。(パイプ) 文字。

フォーマットは次のようになります: Alex,Age:25|Height:6|Hair:Brown|Eyes:Hazel

解析では、最初にカンマ区切りのフィールド (2 つの値を返す) を解析し、次に 2 番目のフィールドをパイプ区切りとして解析します。

于 2012-05-08T11:38:59.957 に答える