使用C#,如何关闭格式错误的XML标记?

时间:2012-04-06 03:15:29

标签: c# xml regex

背景

我继承了一大堆XML文件,这些文件始终包含一个带有两个开口的标签,而不是一个开口和一个闭包。我需要遍历所有这些文件并纠正格式错误的XML。

以下是错误XML的简化示例,它是每个文件中完全相同的标记:

<meals>
    <breakfast>
         Eggs and Toast
    </breakfast>
    <lunch>
         Salad and soup
    <lunch>
    <supper>
         Roast beef and potatoes
    </supper>
</meals>

请注意<lunch>标记没有关闭。这在所有文件中都是一致的。

问题

最好使用regex来修复此问题,如果是这样,我该怎么做?

我已经知道如何迭代文件系统并将文档读入XML或字符串对象,因此您无需回答该部分。

谢谢!

4 个答案:

答案 0 :(得分:3)

如果你破碎的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>表示由封闭的parens捕获的组可以通过名称“open”访问。该分组捕获开始标记,包括尖括号。它假设开始标记上没有xml属性。在该分组中,还有另一个命名组 - 这个名称使用名称“tag”并捕获标记名称本身,不带尖括号。

然后正则表达式懒洋洋地捕获一堆介入文本((.+?)),然后另一个“开放”标记,用反向引用指定。懒惰捕获就在那里,因此它不会在文本中篡改任何可能的插入开放标记。

由于XML可能跨越多个换行符,因此您需要RegexOptions.Singleline

逻辑然后在循环中应用此正则表达式,将任何匹配的文本替换为固定版本 - 带有结束标记的有效xml。使用简单的字符串切片生成固定的XML。

如果符合以下条件,此正则表达式将无效:

  • 开始标记
  • 上有XML属性
  • 有奇怪的间距 - 包围标签名称
  • 的尖括号之间的空格
  • 标签名称使用短划线或数字或任何非小写ASCII字符
  • 包含尖括号(在CDATA中)
  • 之间的字符串

......但这种方法仍然有效。你只需稍微调整一下。

答案 1 :(得分:2)

我认为如果情况真的像你描述的那样简单,那么正则表达式会有点过分(即,它总是相同的标签,并且总是只有其中一个)。如果您的XML文件相对较小(千字节,而不是兆字节),您可以将整个内容加载到内存中,使用字符串操作插入缺少的斜杠,并将其称为一天。这比尝试使用正则表达式更有效(更快)。如果您的文件非常大,您可以修改它以逐行读取文件,直到它找到第一个<lunch>标记,然后查找下一个并相应地修改它。这里有一些代码供您开始使用:

var xml = File.ReadAllText( @"C:\Path\To\NaughtyXml.xml" );

var firstLunchIdx = xml.IndexOf( "<lunch>" );
var secondLunchIdx = xml.IndexOf( "<lunch>", firstLunchIdx+1 );

var correctedXml = xml.Substring( 0, secondLunchIdx + 1 ) + "/" +
xml.Substring( secondLunchIdx + 1 );

File.WriteAllText( @"C:\Path\To\CorrectedXml.xml", correctedXml );

答案 2 :(得分:0)

如果您的xml文件中唯一的问题是您所显示的内容,那么Chesso的答案就足够了。事实上,即使它完全满足我80-90%的需求,我也会走这条路 - 其余的情况下,我可以选择手动处理或编写特定的处理代码。

说,如果文件结构很复杂并且不像你描述的那么简单,那么你应该看看一些文本词法分析器,它允许你将文件内容分解为标记。用于检查和纠正不规则性的令牌的语义分析必须由您完成,但至少解析文本会更简单。请参阅以下几个链接到C#中的lexing的资源:

  1. http://blogs.msdn.com/b/drew/archive/2009/12/31/a-simple-lexer-in-c-that-uses-regular-expressions.aspx
  2. Poor man's "lexer" for C#
  3. http://www.seclab.tuwien.ac.at/projects/cuplex/lex.htm

答案 3 :(得分:-1)

最好不要将这些视为XML文件:它们是非XML文件。这会立即告诉您,为处理XML而设计的工具将毫无用处,因为输入不是XML。您需要使用基于文本的工具。在UNIX上,这将是sed / awk / perl;我不知道Windows上的等价物是什么。