为什么 XmlReader 会跳过元素?

Why is the XmlReader skipping elements?

请注意此问题特定于 XmlReader,而不是使用 XDocument 还是 XmlReader

我有一个 XML 片段:

private string GetXmlFragment()
{
    return @"<bookstore>
          <book genre='novel' ISBN='10-861003-324'>
            <title>The Handmaid's Tale</title>
            <price>19.95</price>
          </book>
          <book genre='novel' ISBN='1-861001-57-5'>
            <title>Pride And Prejudice</title>
            <price>24.95</price>
          </book>
        </bookstore>";
}

我还有一个扩展方法:

public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
    reader.MoveToElement();

    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.Element 
            && reader.Name.Equals(elementName, StringComparison.InvariantCulture))
        {
            yield return XNode.ReadFrom(reader) as XElement;
        }
    }
}

然后我尝试通过以下方式获取两个 book 元素:

var xmlReaderSettings = new XmlReaderSettings
{
    CheckCharacters = false,
    ConformanceLevel = ConformanceLevel.Fragment,
    IgnoreComments = true,
    IgnoreWhitespace = true,
    IgnoreProcessingInstructions = true
};

using (var stringReader = new StringReader(this.GetXmlFragment()))
using (var xmlReader = XmlReader.Create(stringReader, xmlReaderSettings))
{
    xmlReader.GetElement("book").Count().ShouldBe(2);
}

但是我只得到第一个元素,调试显示一旦我得到第一个元素 reader 就会跳转到第二个 book 元素的 title

解决方案的灵感来自HERE

非常感谢任何帮助。

问题在于,如果中间没有空格,对 XNode.ReadFrom() 的调用将使 XML reader 位于下一个元素的右侧。 while 条件然后在我们可以检查它之前立即消耗这个元素。解决方法是不在之后立即调用 XmlReader.Read(),而是继续检查节点(因为读取已隐式完成):

while (reader.Read()) {
    while (reader.NodeType == XmlNodeType.Element 
           && reader.Name.Equals(elementName, StringComparison.InvariantCulture)) {
        yield return XNode.ReadFrom(reader) as XElement;
    }
}

(如果不清楚,循环中的if已更改为while。)

public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
    while (!reader.EOF)
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "book")
            yield return XNode.ReadFrom(reader) as XElement;
        else
            reader.Read();
}

代码会跳过所有其他图书标签,因为图书标签紧跟在一起。 read 方法将 reader 留在下一个 book 标签处,然后 read 方法在读取跳过该元素的第二个 book 标签之前移动。尝试下面我开发的代码并且总是有效。

        public static IEnumerable<XElement> GetElement(XmlReader reader, string elementName)
        {
            List<XElement> books = new List<XElement>();


            while (!reader.EOF)
            {
                if(reader.Name != "book")
                {
                    reader.ReadToFollowing("book");
                }
                if(!reader.EOF)
                {
                    books.Add((XElement)XElement.ReadFrom(reader));
                }
            }
            return books;
        }

正如其他人所说,XNode.ReadFrom 将您的 reader 推进到下一本书的打开标签 (如果它们之间没有空格) 然后 reader.Read 将前进到该标签的内部文本。

查看此处了解更多信息:

修复您的扩展方法:

public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
    reader.MoveToElement();

    reader.Read();
    while (!reader.EOF)
    {
        if (reader.NodeType == XmlNodeType.Element 
            && reader.Name.Equals(elementName, StringComparison.InvariantCulture))
        {
            yield return XNode.ReadFrom(reader) as XElement;
        }
        else
        {
            reader.Read();
        }
    }
}