为什么 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();
}
}
}
请注意此问题特定于 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();
}
}
}