1

我正在为此拔头发。我使用 XmlReader 进行了一些手动反序列化 - 没什么大不了的,做了无数次。但这是我想不通的事情。

这是示例 xml 文件

<?xml version="1.0" encoding="utf-8"?>
<Theme name="something" version="1.0.0.0">
  <Thumbnail length="1102">[some base64 encoded data]
</Thumbnail>
  <Backgrounds>
    <string>Themes\something\Backgrounds\file1</string>
    <string>Themes\something\Backgrounds\file2</string>
    <string>Themes\something\Backgrounds\file3</string>
  </Backgrounds>
  <Stickers>
    <string>Themes\something\Stickers\stick1</string>
    <string>Themes\something\Stickers\stick1</string>
    <string>Themes\something\Stickers\stick1</string>
  </Stickers>
  <PreviewImages>
    <string>Themes\something\Preview\rh_01.jpg</string>
    <string>Themes\something\Preview\rh_02.jpg</string>
    <string>Themes\something\Preview\rh_03.jpg</string>
  </PreviewImages>
</Theme>

这是反序列化代码(有点简化):

public void ReadXml(System.Xml.XmlReader reader)
{       
    /* Read attributes - not important here */

    while (reader.Read())
    {
        Console.WriteLine("Main: {0} {1}", reader.NodeType, reader.Name);
        switch (reader.Name)
        {
            case Xml.Elements.Thumbnail:
                this._thumbnail = Xml.DeserializeBitmap(reader);
                Console.WriteLine("Inner: {0} {1}", reader.NodeType, reader.Name);
                break;
            case Xml.Elements.Backgrounds:
                this._backgrounds = Xml.DeserializeListOfStrings(reader);
                break;
            case Xml.Elements.Stickers:
                this._stickers = Xml.DeserializeListOfStrings(reader);
                break;
            case Xml.Elements.PreviewImages:
                this._previewImages = Xml.DeserializeListOfStrings(reader);
                break;
        }

        if (reader.NodeType == System.Xml.XmlNodeType.EndElement
                && reader.Name == Xml.Root)
            break;
    }
}

问题:

this._thumbnail反序列化后,位于缩略图节点reader的关闭元素上。然后在循环的开头被调用......并且定位在字符串节点的起始元素上。背景元素被跳过!为什么?reader.Read()whilereader

readerisXmlTextReader并且它的WhitespaceHandling属性设置为WhitespaceHandling.Noneor时会发生这种情况WhitespaceHandling.Significant

如果它设置为WhitespaceHandling.All一切都按预期工作。调用后reader.Read()定位reader背景节点的起始元素上。


[编辑]我在示例代码中添加了两个调试行。

我得到WhitespaceHandling.All了这个:

Main: Whitespace 
Main: Element Thumbnail
Inner: EndElement Thumbnail
Main: Element Backgrounds
Main: Whitespace 
Main: Element Stickers
Main: Whitespace 
Main: Element PreviewImages
Main: Whitespace 
Main: EndElement Theme

我得到WhitespaceHandling.Significant了这个:

Main: Element Thumbnail
Inner: EndElement Thumbnail
Main: Element string
Main: Text 
Main: EndElement string
Main: Element string
Main: Text 
Main: EndElement string
Main: Element string
Main: Text 
Main: EndElement string
Main: EndElement Backgrounds

[编辑 2]调整了调试输出,使其更具可读性。

如您所见,调试输出WhitespaceHandling.Significant结束于</Backgrounds>. 那是因为我Xml.DeserializeListOfStrings还没有检查它是否定位正确并且“意外”将文档读取到最后。但这不是这个问题的范围。

4

1 回答 1

0

我头痛的原因是XmlReader.ReadElementContentAsBase64我用来反序列化<Thumbnail>节点的方法。我正在循环试验它:

private static byte[] ReadBytes(System.Xml.XmlReader reader)
{
    byte[] buffer = new byte[128];
    int length = XmlConvert.ToInt32(reader[Xml.Attributes.Length]);

    using (MemoryStream ms = new MemoryStream(length))
    {
        int count = 0;

        do
        {
            count = reader.ReadElementContentAsBase64(buffer, 0, buffer.Length);
            ms.Write(buffer, 0, count);

        } while (ms.Length < length);

        return ms.GetBuffer();
    }
}

但是 MSDN 说:

如果计数值高于文档中的字节数,或者等于文档中的字节数,则 XmlNodeReader 读取文档中所有剩余的字节并返回读取的字节数。下一个 ReadElementContentAsBase64 方法调用返回零并将阅读器移动到 EndElement 节点之后的节点。

如果在所有元素内容都被消费之前调用 Read,阅读器的行为可能就像消费了第一个内容然后调用了 Read 方法。这意味着读者将阅读所有文本,直到遇到结束元素。然后它将读取结束标记节点,读取下一个节点,然后将自己定位在下一个后续节点上。

似乎尽管阅读到元素内容的末尾(我知道数据长度,所以理论上我可以做到这一点),但XmlReader并没有考虑到我已经“消耗”了所有元素的内容。这导致了 MSDN 中描述的一些意外行为。

和的XmlReader行为相同。我的代码可以使用,因为在最后一次调用之后,跳过了不重要的空格。如果源 xml 文件不包含换行符和制表符,我的代码也会失败。WhietespaceHandling.AllWhietespaceHandling.SignificantWhietespaceHandling.AllXmlReader.ReadElementContentAsBase64readerWhietespaceHandling.All

解决方案是修改 while 循环以XmlReader.ReadElementContentAsBase64在所有字节为红色之后再进行一次调用。这种方法的缺点是,在附加调用之后,将reader移动到 EndElement 节点之后的节点

do
{
    count = reader.ReadElementContentAsBase64(buffer, 0, buffer.Length);
    if (count > 0)
        ms.Write(buffer, 0, count);

} while (count > 0);

也可以使用XmlTextReader.ReadBase64方法一次读取整个元素内容,但由于我的类实现了 IXmlSerializable,我被迫只使用XmlReaderbase,所以这种方法对我不可用。

于 2012-04-05T23:05:47.897 回答