これは興味深いものです。特定の形式のファイルを解析するのは非常に難しい場合があるため、それらを処理するために特定のクラスを作成することがよくあります。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 列を作成する必要があります。