在 C# 中使用 XmlReader 读取 Xml

Posted

技术标签:

【中文标题】在 C# 中使用 XmlReader 读取 Xml【英文标题】:Reading Xml with XmlReader in C# 【发布时间】:2011-01-27 08:29:00 【问题描述】:

我正在尝试尽可能快地阅读以下 Xml 文档,并让其他类管理每个子块的阅读。

<ApplicationPool>
    <Accounts>
        <Account>
            <NameOfKin></NameOfKin>
            <StatementsAvailable>
                <Statement></Statement>
            </StatementsAvailable>
        </Account>
    </Accounts>
</ApplicationPool>

但是,我正在尝试使用 XmlReader 对象来读取每个帐户以及随后的“StatementsAvailable”。您是否建议使用 XmlReader.Read 并检查每个元素并进行处理?

我考虑过分离我的类以正确处理每个节点。因此,有一个 AccountBase 类接受一个 XmlReader 实例,该实例读取 NameOfKin 和有关帐户的其他几个属性。然后我想通过 Statements 进行交互,让另一个类自己填写 Statement(然后将其添加到 IList)。

到目前为止,我已经通过执行 XmlReader.ReadElementString() 完成了“每个类”部分,但我无法锻炼如何告诉指针移动到 StatementsAvailable 元素并让我遍历它们并让另一个类读取每个这些属性。

听起来很简单!

【问题讨论】:

点击编辑框右上角橙色问号获取编辑帮助。可能你想创建一个代码块,首先是一个空行,然后每行缩进四个空格。 或者只选择您的代码行/XML,然后单击编辑器工具栏中的“代码”按钮 (101 010) - 就这么简单! 【参考方案1】:

我对@9​​87654325@的体验是很容易不小心读太多。我知道您说过您想尽快阅读它,但是您尝试过 使用 DOM 模型吗?我发现 LINQ to XML 使 XML 的工作变得更加非常容易。

如果您的文档特别大,您可以将 XmlReader 和 LINQ to XML 组合在一起,方法是从 XmlReader 以流的方式为每个“外部”元素创建一个 XElement:这可以让您完成大部分工作LINQ to XML 的转换工作,但在任何时候仍然只需要内存中的一小部分文档。下面是一些示例代码(改编自this blog post):

static IEnumerable<XElement> SimpleStreamAxis(string inputUrl,
                                              string elementName)

  using (XmlReader reader = XmlReader.Create(inputUrl))
  
    reader.MoveToContent();
    while (reader.Read())
    
      if (reader.NodeType == XmlNodeType.Element)
      
        if (reader.Name == elementName)
        
          XElement el = XNode.ReadFrom(reader) as XElement;
          if (el != null)
          
            yield return el;
          
        
      
    
  

我之前用它来将 *** 用户数据(非常庞大)转换为另一种格式 - 它工作得很好。

来自radarbob 的编辑,由Jon 重新格式化——虽然目前还不清楚是指哪个“读得太远”问题...

这应该会简化嵌套并解决“读取太远”的问题。

using (XmlReader reader = XmlReader.Create(inputUrl))

    reader.ReadStartElement("theRootElement");

    while (reader.Name == "TheNodeIWant")
    
        XElement el = (XElement) XNode.ReadFrom(reader);
    

    reader.ReadEndElement();

这解决了“读取太远”的问题,因为它实现了经典的 while 循环模式:

initial read;
(while "we're not at the end") 
    do stuff;
    read;

【讨论】:

调用 XNode.ReadFrom 读取元素并转到下一个,然后下面的 reader.Read() 再次读取下一个。如果它们碰巧具有相同的名称并且是连续的,那么您基本上会错过一个元素。 @pbz:谢谢。我不确定我是否相信自己可以正确编辑它(这就是我不喜欢 XmlReader 的程度:)你能正确编辑它吗? @JonSkeet - 我可能遗漏了一些东西,但不会简单地将 if(reader.Name == elementName) 更改为 while(reader.Name == elementName) 解决 pbz 指出的问题? @pbz:我改了行:XElement el = XNode.ReadFrom(reader) as XElement;是:XElement el = XElement.Load(reader.ReadSubtree());因为这修复了跳过连续元素的错误。 正如其他cmets中提到的,当前版本的SimpleStreamAxis()会在XML不缩进时跳过元素,因为Node.ReadFrom()将阅读器定位在之后的下一个节点加载的元素 - 将被下一个无条件的 Read() 跳过。如果下一个节点是空白,那么一切都很好。否则,不会。对于没有此问题的版本,请参阅 here、here 或 here。【参考方案2】:

三年后,也许随着对 WebApi 和 xml 数据的重新重视,我遇到了这个问题。由于在代码方面我倾向于在没有降落伞的情况下跟随 Skeet 离开飞机,并且看到 MS Xml 团队文章以及 BOL Streaming Transform of Large Xml Docs 中的示例双重证实了他的初始代码,因此我很快忽略了其他 cmets,尤其是来自 'pbz' 的人指出,如果您连续有相同的元素名称,则会因为重复读取而跳过所有其他元素。事实上,BOL 和 MS 博客文章都在解析源文档,其中目标元素的嵌套深度超过了二级,从而掩盖了这种副作用。

其他答案解决了这个问题。我只是想提供一个稍微简单的修订版,到目前为止似乎运行良好,并考虑到 xml 可能来自不同的来源,而不仅仅是 uri,因此扩展适用于用户管理的 XmlReader。一个假设是阅读器处于其初始状态,否则第一个“Read()”可能会超过所需的节点:

public static IEnumerable<XElement> ElementsNamed(this XmlReader reader, string elementName)

    reader.MoveToContent(); // will not advance reader if already on a content node; if successful, ReadState is Interactive
    reader.Read();          // this is needed, even with MoveToContent and ReadState.Interactive
    while(!reader.EOF && reader.ReadState == ReadState.Interactive)
    
        // corrected for bug noted by Wes below...
        if(reader.NodeType == XmlNodeType.Element && reader.Name.Equals(elementName))
        
             // this advances the reader...so it's either XNode.ReadFrom() or reader.Read(), but not both
             var matchedElement = XNode.ReadFrom(reader) as XElement;
             if(matchedElement != null)
                 yield return matchedElement;
        
        else
            reader.Read();
    

【讨论】:

您的“if(reader.Name.Equals(elementName))”语句缺少相应的“else reader.Read();”陈述。如果元素不是您想要的,请继续阅读。这就是我必须添加的内容才能让它为我工作。 @Wes 通过折叠两个条件(NodeType 和 Name)修复了该问题,以便 else Read() 适用于两者。感谢您了解这一点。 我对你投了赞成票,但我不太高兴看到 Read 方法调用被写了两次。也许你可以在这里使用 do while 循环? :) 另一个注意到并解决了与 MSDN 文档相同的问题的答案:***.com/a/18282052/3744182【参考方案3】:

我们一直在进行这种 XML 解析。关键是定义解析方法将使读者退出的位置。如果您始终将阅读器留在第一次读取的元素之后的下一个元素上,那么您可以安全且可预测地读取 XML 流。因此,如果阅读器当前正在索引&lt;Account&gt; 元素,则在解析后阅读器将索引&lt;/Accounts&gt; 结束标记。

解析代码如下所示:

public class Account

    string _accountId;
    string _nameOfKin;
    Statements _statmentsAvailable;

    public void ReadFromXml( XmlReader reader )
    
        reader.MoveToContent();

        // Read node attributes
        _accountId = reader.GetAttribute( "accountId" );
        ...

        if( reader.IsEmptyElement )  reader.Read(); return; 

        reader.Read();
        while( ! reader.EOF )
        
            if( reader.IsStartElement() )
            
                switch( reader.Name )
                
                    // Read element for a property of this class
                    case "NameOfKin":
                        _nameOfKin = reader.ReadElementContentAsString();
                        break;

                    // Starting sub-list
                case "StatementsAvailable":
                    _statementsAvailable = new Statements();
                    _statementsAvailable.Read( reader );
                    break;

                    default:
                        reader.Skip();
                
            
            else
            
                reader.Read();
                break;
            
               
    

Statements 类只读取&lt;StatementsAvailable&gt; 节点

public class Statements

    List<Statement> _statements = new List<Statement>();

    public void ReadFromXml( XmlReader reader )
    
        reader.MoveToContent();
        if( reader.IsEmptyElement )  reader.Read(); return; 

        reader.Read();
        while( ! reader.EOF )
        
            if( reader.IsStartElement() )
            
                if( reader.Name == "Statement" )
                
                    var statement = new Statement();
                    statement.ReadFromXml( reader );
                    _statements.Add( statement );               
                
                else
                
                    reader.Skip();
                
            
            else
            
                reader.Read();
                break;
            
        
    

Statement 类看起来非常相似

public class Statement

    string _satementId;

    public void ReadFromXml( XmlReader reader )
    
        reader.MoveToContent();

        // Read noe attributes
        _statementId = reader.GetAttribute( "statementId" );
        ...

        if( reader.IsEmptyElement )  reader.Read(); return; 

        reader.Read();
        while( ! reader.EOF )
                   
            ....same basic loop
               
    

【讨论】:

【参考方案4】:

对于子对象,ReadSubtree() 为您提供了一个仅限于子对象的 xml 阅读器,但我真的认为您这样做很难。除非您对处理异常/不可预测的 xml 有非常具体的要求,否则请使用 XmlSerializer(如果您真的需要,可以加上 sgen.exe)。

XmlReader 是……很棘手。对比:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
public class ApplicationPool 
    private readonly List<Account> accounts = new List<Account>();
    public List<Account> Accounts getreturn accounts;

public class Account 
    public string NameOfKin get;set;
    private readonly List<Statement> statements = new List<Statement>();
    public List<Statement> StatementsAvailable getreturn statements;

public class Statement 
static class Program 
    static void Main() 
        XmlSerializer ser = new XmlSerializer(typeof(ApplicationPool));
        ser.Serialize(Console.Out, new ApplicationPool 
            Accounts =  new Account  NameOfKin = "Fred",
                StatementsAvailable =  new Statement , new Statement 
        );
    

【讨论】:

【参考方案5】:

以下示例在流中导航以确定当前节点类型,然后使用 XmlWriter 输出 XmlReader 内容。

    StringBuilder output = new StringBuilder();

    String xmlString =
            @"<?xml version='1.0'?>
            <!-- This is a sample XML document -->
            <Items>
              <Item>test with a child element <more/> stuff</Item>
            </Items>";
    // Create an XmlReader
    using (XmlReader reader = XmlReader.Create(new StringReader(xmlString)))
    
        XmlWriterSettings ws = new XmlWriterSettings();
        ws.Indent = true;
        using (XmlWriter writer = XmlWriter.Create(output, ws))
        

            // Parse the file and display each of the nodes.
            while (reader.Read())
            
                switch (reader.NodeType)
                
                    case XmlNodeType.Element:
                        writer.WriteStartElement(reader.Name);
                        break;
                    case XmlNodeType.Text:
                        writer.WriteString(reader.Value);
                        break;
                    case XmlNodeType.XmlDeclaration:
                    case XmlNodeType.ProcessingInstruction:
                        writer.WriteProcessingInstruction(reader.Name, reader.Value);
                        break;
                    case XmlNodeType.Comment:
                        writer.WriteComment(reader.Value);
                        break;
                    case XmlNodeType.EndElement:
                        writer.WriteFullEndElement();
                        break;
                
            

        
    
    OutputTextBlock.Text = output.ToString();

以下示例使用 XmlReader 方法读取元素和属性的内容。

StringBuilder output = new StringBuilder();

String xmlString =
    @"<bookstore>
        <book genre='autobiography' publicationdate='1981-03-22' ISBN='1-861003-11-0'>
            <title>The Autobiography of Benjamin Franklin</title>
            <author>
                <first-name>Benjamin</first-name>
                <last-name>Franklin</last-name>
            </author>
            <price>8.99</price>
        </book>
    </bookstore>";

// Create an XmlReader
using (XmlReader reader = XmlReader.Create(new StringReader(xmlString)))

    reader.ReadToFollowing("book");
    reader.MoveToFirstAttribute();
    string genre = reader.Value;
    output.AppendLine("The genre value: " + genre);

    reader.ReadToFollowing("title");
    output.AppendLine("Content of the title element: " + reader.ReadElementContentAsString());


OutputTextBlock.Text = output.ToString();

【讨论】:

【参考方案6】:
    XmlDataDocument xmldoc = new XmlDataDocument();
    XmlNodeList xmlnode ;
    int i = 0;
    string str = null;
    FileStream fs = new FileStream("product.xml", FileMode.Open, FileAccess.Read);
    xmldoc.Load(fs);
    xmlnode = xmldoc.GetElementsByTagName("Product");

可以通过xmlnode循环获取数据......C# XML Reader

【讨论】:

此类已弃用。不要使用。 @Elvarism 在您分享的网站中还有许多其他读取 xml 的方式,这对我帮助很大。我会投票给你。这是另一个易于理解的XmlReader 示例。【参考方案7】:

我没有经验。但我认为 XmlReader 是不必要的。 很难用。 XElement 非常易于使用。 如果您需要性能(更快),您必须更改文件格式并使用 StreamReader 和 StreamWriter 类。

【讨论】:

以上是关于在 C# 中使用 XmlReader 读取 Xml的主要内容,如果未能解决你的问题,请参考以下文章

C# 使用 XMLReader(?) 读取子元素

XmlReader 创建空字符串 C#

c# 操作xml之xmlReader

使用 xmlReader 在 C# 中过滤特定元素值的大型 XML

C# 使用 XmlReader 但不使用 XmlDocument 获取额外的空白值

将创建 xml 文件更改为:使用 xmlreader 加载到数据表