如果您损坏的 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 中)
...但这种方法仍然有效。你只需要稍微调整一下。