【问题标题】:Inconsistency between XmlReader.Read() and XmlReader.ReadStartElement()XmlReader.Read() 和 XmlReader.ReadStartElement() 之间的不一致
【发布时间】:2017-02-08 10:11:55
【问题描述】:

试图了解XmlReader.Read()XmlReader.ReadStartElement() 之间的“不一致”。在reader1下面,一切都在意料之中,即读取整个xml需要3次读取;更重要的是,第一次阅读时,即阅读<firstname>reader1.Value 是空的。第二读 reader1.Value 是文本节点值。

但是在reader2 中,我期待相同的阅读顺序,因为据我所知ReadStartElement() 内部调用Read(),它应该只读取一个XmlNodeType,例如这里是<firstname>。就好像我们可以用调用来替换 ReadStartElement("firstname") 以检查它是否是名称为 firstname 的开始元素和对 Read() 的调用。为什么 reader2.ValueReadStartElement("firstname") 之后不为空? 我最初在@lesscode 的一个问题下问这个问题,他的解释是,根据msdnReadStartElement() 会将XmlReader 推进到下一个节点,reader.Value 是当前节点的值。但如果是这样,Read()ReadStartElement() 之间不是不一致吗,因为使用 Read() 之后必须检索值,而使用 ReadStartElement() 必须事先检索 Value

 var simpleElement = "<firstname>Jim</firstname>";
 using (var reader1 = XmlReader.Create(new StringReader(simpleElement)))
 {
    var i = 1;
    while (reader1.Read())
    {
       WriteLine($"i = {i++}; value = {reader1.Value}");
    }           
 }

using (var reader2 = XmlReader.Create(new StringReader(simpleElement)))
{
 // this internally calls Read() which should have ONLY read the 'firstname' start element node. 
    reader2.ReadStartElement("firstname"); 

// prints Jim; but why??? The text node has NOT been read yet!
    WriteLine(reader2.Value); 

    reader2.Read(); //WHY needs this line given text node has been read already?
    reader2.ReadEndElement(); 

}

【问题讨论】:

  • 发布包含重复标签的 xml 示例。
  • @jdweng 没有xml样本; Jim 是整个 xml;我只是在测试XmlReader() 的工作原理并找到这个“问题”。
  • @JonSkeet 有什么想法吗?
  • 在下面的帖子中查看我的解决方案。我使用 xmlreader 和 xml linq 的组合来避免您看到的问题。 stackoverflow.com/questions/34274568/…

标签: c# xml xml-parsing


【解决方案1】:

您可以在 Github 上查看源代码:XmlReader.cs

如下所示,这些方法的行为不同:

// Checks that the current node is an element and advances the reader to the next node.
public virtual void ReadStartElement() {
    if (MoveToContent() != XmlNodeType.Element) {
        throw new XmlException(Res.Xml_InvalidNodeType, this.NodeType.ToString(), this as IXmlLineInfo);
    }
    Read();
}

// Checks whether the current node is a content (non-whitespace text, CDATA, Element, EndElement, EntityReference
// or EndEntity) node. If the node is not a content node, then the method skips ahead to the next content node or 
// end of file. Skips over nodes of type ProcessingInstruction, DocumentType, Comment, Whitespace and SignificantWhitespace.
public virtual  XmlNodeType  MoveToContent() {
    do {
        switch (this.NodeType) {
            case XmlNodeType.Attribute:
                MoveToElement();
                goto case XmlNodeType.Element;
            case XmlNodeType.Element:
            case XmlNodeType.EndElement:
            case XmlNodeType.CDATA:
            case XmlNodeType.Text:
            case XmlNodeType.EntityReference:
            case XmlNodeType.EndEntity:
                return this.NodeType;
        }
    } while (Read());
    return this.NodeType;
}

所以ReadStartElement 方法调用MoveToContent 可以进行多次Read 调用以找到内容节点。之后,ReadStartElement 读取当前的起始元素。

【讨论】:

  • 哇,这完全有道理,因为它完美地解释了为什么在示例 xml 中 ReadStartElement() 调用 Read() 两次;一次在MoveToContent() 和一次在MoveToContent() 之后。非常感谢您在 Github 上查看源代码并提供很好的建议!
  • 您好,对此还有一个问题。我同意reader2.ReadStartElement("firstname") 调用Read() 两次,因此在reader2.ReadStartElement("firstname") 行之后已经读取了文本节点。但是为什么在调用reader2.ReadEndElement() 之前还需要再次调用reader2.Read()?我已更新问题以反映这一点。
  • 因为从一个节点到另一个节点的唯一方法是调用其中一个 Read 方法。访问 Value 属性或 Name 或读取属性将使阅读器保持在当前节点上。因此,在 reader.Value 之后第一次调用 Read() 会将光标从文本节点推进到下一个节点。最后,ReadEndElement 将光标从 EndElement 推进到下一个节点(如果存在)。
  • OK,那么如果一个节点已经被读取并转发到下一个节点,可以再次调用Read()吗?刚刚查看了ReadEndElement() 的源代码,可以理解的是,它在读取节点之前首先检查节点是否是结束元素,即它不像ReadStartElement() 可以读取下一个节点。总的来说,现在这很有意义,因为 xml 读取通常应该是只向前的。再次感谢您!
猜你喜欢
  • 1970-01-01
  • 2018-08-08
  • 2014-04-11
  • 2015-03-10
  • 2013-11-17
  • 2011-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多