3

考虑以下 C# 代码:

using System.Xml.Linq;

namespace TestXmlParse
{
    class Program
    {
        static void Main(string[] args)
        {
            var testxml = 
            @"<base>
                <elem1 number='1'>
                    <elem2>yyy</elem2>
                    <elem3>xxx   <yyy zzz aaa</elem3>
                </elem1>
            </base>";
            XDocument.Parse(testxml);
        }
    }
}

我在解析时得到一个 System.Xml.XmlException,当然,抱怨 elem3。错误信息是这样的:

System.Xml.XmlException was unhandled
  Message='aaa' is an unexpected token. The expected token is '='. Line 4, position 59.
  Source=System.Xml
  LineNumber=4
  LinePosition=59

显然这不是真正的 XML(我们从第三方获取 xml),虽然最好的答案是第三方在将其发送给我们之前清理他们的 xml,但有没有其他方法可以修复这个 xml在我把它交给解析器之前?我设计了一种破解方法来解决这个问题;捕获异常并使用它来告诉我需要在哪里查找应该转义的字符。我希望有一些更优雅和全面的东西。

欢迎任何建议。

如果这是一个骗局,请指出其他问题;我会自己关闭这个。我对答案比任何业力增益更感兴趣。

编辑:

我想我的问题没有像我希望的那样清楚。我知道 elem3 中的“<”不正确;在尝试解析之前,我试图找到一种优雅的方法来检测(并纠正)任何格式错误的 xml 。正如我所说,我从第三方获得了这个 xml,我无法控制他们给我的东西。

4

1 回答 1

2

我建议您不要操纵收到的数据。如果它无效,那是您的客户的问题。

编辑输入使其成为有效的 xml 可能会导致严重的问题,例如,您最终可能会处理错误的数据,而不是抛出错误(因为您已尽力使 xml 有效,但这可能会导致不同的数据)。


[编辑] 我仍然认为这不是一个好主意,但有时你必须做你必须做的事情。

这是一个非常简单的类,它解析输入并替换无效的开始标签。您可以使用正则表达式(我不擅长)来执行此操作,但此解决方案并不完整,例如,根据您的要求(或者说您得到的坏 xml)您将不得不采用它(例如扫描完整的 xml 元素而不是只有“<”和“>”括号,将 CDATA 放在节点的内部文本周围等等)。

我只是想说明你是如何做到的,所以如果它很慢/有错误,请不要抱怨(正如我所提到的,我不会这样做)。

class XmlCleaner
    {

        public void Clean(Stream sourceStream, Stream targetStream)
        {
            const char openingIndicator = '<';
            const char closingIndicator = '>';
            const int bufferSize = 1024;
            long length = sourceStream.Length;
            char[] buffer = new char[bufferSize];
            bool startTagFound = false;
            StringBuilder writeBuffer = new StringBuilder();

            using(var reader = new StreamReader(sourceStream))            
            {
                var writer = new StreamWriter(targetStream);

                try
                {
                    while (reader.Read(buffer, 0, bufferSize) > 0)
                    {
                        foreach (var c in buffer)
                        {
                            if (c == openingIndicator)
                            {
                                if (startTagFound)
                                {
                                    // we have 2 following opening tags without a closing one                                
                                    // just replace the first one
                                    writeBuffer = writeBuffer.Replace("<", "&lt;");

                                    // append the new one
                                    writeBuffer.Append(c);
                                }
                                else
                                {
                                    startTagFound = true;
                                    writeBuffer.Append(c);
                                }
                            }
                            else if (c == closingIndicator)
                            {
                                startTagFound = false;
                                // write writebuffer...
                                writeBuffer.Append(c);
                                writer.Write(writeBuffer.ToString());
                                writeBuffer.Clear();
                            }
                            else
                            {
                                writeBuffer.Append(c);
                            }
                        }
                    }
                }
                finally
                {
                    // unfortunately the streamwriter's dispose method closes the underlying stream, so e just flush it
                    writer.Flush();
                }                
            }
        }

要测试它:

var testxml =
            @"<base>
                <elem1 number='1'>
                    <elem2>yyy</elem2>
                    <elem3>xxx   <yyy zzz aaa</elem3>
                </elem1>
            </base>";

            string result;

            using (var source = new MemoryStream(Encoding.ASCII.GetBytes(testxml)))
            using(var target = new MemoryStream()) {

                XmlCleaner cleaner = new XmlCleaner();
                cleaner.Clean(source, target);

                target.Position = 0;
                using (var reader = new StreamReader(target))
                {
                    result = reader.ReadToEnd();
                }
            }

            XDocument.Parse(result);

            var expectedResult = 
                @"<base>
                <elem1 number='1'>
                    <elem2>yyy</elem2>
                    <elem3>xxx   &lt;yyy zzz aaa</elem3>
                </elem1>
            </base>";
            Debug.Assert(result == expectedResult);
于 2012-07-06T13:15:37.700 回答