質問で示したように、壊れたXMLが比較的単純な場合は、いくつかの単純なロジックと基本的な正規表現を使用することができます。
public static void Main(string[] args)
{
string broken = @"
<meals>
<breakfast>
Eggs and Toast
</breakfast>
<lunch>
Salad and soup
<lunch>
<supper>
Roast beef and potatoes
</supper>
</meals>";
var pattern1 = "(?<open><(?<tag>[a-z]+)>)([^<]+?)(\\k<open>)";
var re1 = new Regex(pattern1, RegexOptions.Singleline);
String work = broken;
Match match = null;
do
{
match = re1.Match(work);
if (match.Success)
{
Console.WriteLine("Match at position {0}.", match.Index);
var tag = match.Groups["tag"].ToString();
Console.WriteLine("tag: {0}", tag.ToString());
work = work.Substring(0, match.Index) +
match.Value.Substring(0, match.Value.Length - tag.Length -1) +
"/" +
work.Substring(match.Index + match.Value.Length - tag.Length -1);
Console.WriteLine("fixed: {0}", work);
}
} while (match.Success);
}
その正規表現は、.NET正規表現の「名前付き」キャプチャグループ機能を使用します。は?<open>
、囲んでいる親によってキャプチャされたグループに「open」という名前でアクセスできることを示します。そのグループ化は、山形鋼を含む開始タグをキャプチャします。開始タグにxml属性がないことを前提としています。そのグループ内には、別の名前付きグループがあります。これは、「タグ」という名前を使用し、山かっこなしでタグ名自体をキャプチャします。
次に、正規表現は、間にある一連のテキスト()を遅延キャプチャし、(.+?)
次に、後方参照で指定された別の「オープン」タグをキャプチャします。怠惰なキャプチャがそこにあるので、テキストに介在する可能性のあるオープンタグを丸呑みにすることはありません。
XMLは複数の改行にまたがる可能性があるため、が必要ですRegexOptions.Singleline
。
次に、ロジックはこの正規表現をループで適用し、一致したテキストを固定バージョン(有効なxmlと終了タグ)に置き換えます。固定XMLは、単純な文字列スライスで生成されます。
この正規表現は、次の場合には機能しません。
- 開始タグにはXML属性があります
- 奇妙な間隔があります-タグ名を囲む山かっこの間の空白
- タグ名には、ダッシュや数字など、小文字のASCII文字以外のものが使用されます
- 間にある文字列には山かっこが含まれます(CDATA内)
...しかし、このアプローチは引き続き機能します。少し調整する必要があります。