为什么在包含XML头部信息时,C#的XmlDocument.LoadXml(string)会失败?

66

有人知道为什么下面的代码示例会因为XmlException "Data at the root level is invalid. Line 1, position 1."而失败吗?

var body = "<?xml version="1.0" encoding="utf-16"?><Report> ......"
XmlDocument bodyDoc = new XmlDocument();            
bodyDoc.LoadXml(body);

1
丹是正确的 - 代码没问题。 检查 XML 的一种快速简便的方法是在 Internet Explorer 中打开它。 - David Hall
1
你确定你的编码方式是 utf-16 而不是其他什么吗?另外,你的 body 字符串是否被转义了,比如像这样:body = "<?xml version=\"1.0\" encoding=\"utf-16\" ?>\n<Report>This is a Test</Report>"; - Zach Burlingame
9个回答

121

背景

虽然你的问题已将编码设置为UTF-16,但你没有正确转义字符串,所以我不确定你是否确实在问题中准确地转录了该字符串。

我遇到了相同的异常:

System.Xml.XmlException: 数据 在根级别处无效。行 1,位置 1。

但是,我的代码如下所示:

string xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<event>This is a Test</event>";
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);

问题

问题在于,在 .NET 中,字符串内部存储为 UTF-16,但是 XML 文档标头中指定的编码可能不同。例如:

<?xml version="1.0" encoding="utf-8"?>
从String的MSDN文档here中可以得知,字符串中的每个Unicode字符都由Unicode标量值定义,也称为Unicode代码点或Unicode字符的序数(数字)值。每个代码点都使用UTF-16编码,并且编码的每个元素的数字值由一个Char对象表示。
这意味着,当你通过XmlDocument.LoadXml()方法传递带有XML头的字符串时,它必须指定编码为UTF-16。否则,实际的底层编码将不匹配头部报告的编码类型,并导致XmlException异常抛出。
解决方案是确保在向Load或LoadXml方法传递数据时,所用的编码与XML头中声明的编码匹配。例如,在上述示例中,你可以将XML头更改为指定UTF-16编码,或者编码输入为UTF-8并使用一个XmlDocument.Load方法
下面是演示如何使用MemoryStream构建一个XmlDocument的示例代码,该XmlDocument使用定义了UTF-8编码的字符串(但实际存储为UTF-16 .NET字符串)。
string xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<event>This is a Test</event>";

// Encode the XML string in a UTF-8 byte array
byte[] encodedString = Encoding.UTF8.GetBytes(xml);

// Put the byte array into a stream and rewind it to the beginning
MemoryStream ms = new MemoryStream(encodedString);
ms.Flush();
ms.Position = 0;

// Build the XmlDocument from the MemorySteam of UTF-8 encoded bytes
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ms);

9
别忘了调用ms.close()。 - Zé Carlos
12
在MemoryStream上使用语句。 - DFTR
为什么需要 ms.Flush()ms.Position = 0 - Sam Carlson

33

简单而有效的解决方案:不要使用 LoadXml() 方法,而是使用 Load() 方法

例如:

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("sample.xml");

这个操作非常容易完成和理解。我的文档没有任何XML头。 - user2672332

8
我明白了。阅读MSDN文档后发现,需要使用.Load而不是LoadXml从字符串中读取内容。这种方法可以100%正常工作。奇怪的是,使用StringReader会导致问题。我认为主要原因是该字符串采用Unicode编码,而StringReader仅支持UTF-8。
MemoryStream stream = new MemoryStream();
            byte[] data = body.PayloadEncoding.GetBytes(body.Payload);
            stream.Write(data, 0, data.Length);
            stream.Seek(0, SeekOrigin.Begin);

            XmlTextReader reader = new XmlTextReader(stream);

            // MSDN reccomends we use Load instead of LoadXml when using in memory XML payloads
            bodyDoc.Load(reader);

1
阅读MSDN文档中XmlDocument.LoadXml(String)的方法说明:http://msdn.microsoft.com/en-us/library/system.xml.xmldocument.loadxml.aspx,方法摘要中指出:“从指定的字符串加载XML文档。”但是,正如您所说,它确实说:“如果要从流、字符串、TextReader或XmlReader加载,请使用Load方法而不是此方法。”此外,如果您查看XmlDocument.Load(String),它说:“要加载包含XML文档的文件的URL。 “URL可以是本地文件或HTTP URL(Web地址)。”(更多内容在另一个评论中) - Zach Burlingame
4
我认为该行中文本的预期目的是“如果你想从流、文件加载”,而不是“如果你想从流、字符串加载” 。但是出现了“字符串”,因为从文件加载需要一个文件名的字符串参数。我不认为他们的意图是“如果你想从内存中的字符串加载XmlDocument,请使用Load”。毕竟,这就是LoadXml(String)的宣称目的!虽然您的解决方案提供了一种解决方法,但我认为它没有解决实际声明问题(我也遇到了这个问题)与XmlDocument.LoadXml(String)。 - Zach Burlingame

7

试试这个:

XmlDocument bodyDoc = new XmlDocument();
bodyDoc.XMLResolver = null;
bodyDoc.Load(body);

3
这是我的解决方案:

这个对我有用:

var xdoc = new XmlDocument { XmlResolver = null };  
xdoc.LoadXml(xmlFragment);

2

这真的帮了我很大的忙。

我基于Zach的回答编写了一个扩展方法,同时我还将其扩展为使用编码作为参数,允许使用不同于UTF-8的编码,并在一个“using”语句中包装了MemoryStream。

public static class XmlHelperExtentions
{
    /// <summary>
    /// Loads a string through .Load() instead of .LoadXml()
    /// This prevents character encoding problems.
    /// </summary>
    /// <param name="xmlDocument"></param>
    /// <param name="xmlString"></param>
    public static void LoadString(this XmlDocument xmlDocument, string xmlString, Encoding encoding = null) {

        if (encoding == null) {
            encoding = Encoding.UTF8;
        }

        // Encode the XML string in a byte array
        byte[] encodedString = encoding.GetBytes(xmlString);

        // Put the byte array into a stream and rewind it to the beginning
        using (var ms = new MemoryStream(encodedString)) {
            ms.Flush();
            ms.Position = 0;

            // Build the XmlDocument from the MemorySteam of UTF-8 encoded bytes
            xmlDocument.Load(ms);
        }
    }
}

1
当我从绝对路径切换到相对路径时,遇到了同样的问题。以下解决了加载和使用相对源路径问题。使用XmlDataProvider,在xaml中定义(在代码中也应该可以):
    <Window.Resources>
    <XmlDataProvider 
        x:Name="myDP"
        x:Key="MyData"
        Source=""
        XPath="/RootElement/Element"
        IsAsynchronous="False"
        IsInitialLoadEnabled="True"                         
        debug:PresentationTraceSources.TraceLevel="High"  /> </Window.Resources>

数据提供程序在设置源后自动加载文档。以下是代码:
        m_DataProvider = this.FindResource("MyData") as XmlDataProvider;
        FileInfo file = new FileInfo("MyXmlFile.xml");

        m_DataProvider.Document = new XmlDocument();
        m_DataProvider.Source = new Uri(file.FullName);

0

我遇到了同样的问题,因为我上传的XML文件是用UTF-8-BOM(UTF-8字节顺序标记)编码的。

在Notepad++中将编码切换为UTF-8后,可以在代码中加载XML文件。


0

简单的一行代码:

bodyDoc.LoadXml(new MemoryStream(Encoding.Unicode.GetBytes(body)));


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接