在C#中使用XmlReader读取Xml


97

我试图尽快读取以下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元素,并让我对其进行迭代,并让另一个类读取每个属性。

听起来很简单!


1
单击编辑框右上角的橙色问号以获取编辑帮助。可能您想创建一个代码块,该代码块首先是空白行,然后每行缩进四个空格。
安德斯·亚伯2010年

或只是选择您的代码行/ XML,然后单击编辑器工具栏中的“代码”按钮(101 010)-就这么简单!
marc_s

Answers:


163

我的经验XmlReader是,偶然阅读过多很容易。我知道您已经说过要尽快阅读,但是您是否尝试过使用DOM模型呢?我发现LINQ to XML使XML的工作容易得多

如果您的文档特别大,则可以XmlReader通过流方式为每个“外部”元素创建一个XElementfrom 来合并和LINQ to XML XmlReader:这使您可以完成LINQ to XML的大部分转换工作,但仍然只需要随时可以将一小部分文档存储在内存中。这是一些示例代码(从本博客文章中略作修改):

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;
          }
        }
      }
    }
  }
}

我曾经用它来将StackOverflow用户数据(巨大)转换成另一种格式-效果很好。

由乔恩(Jon)重新格式化的Radarbob的EDIT-尽管目前尚不清楚是指哪个“读得太远”的问题...

这应该简化嵌套并解决“读取过多”问题。

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;
}

17
调用XNode.ReadFrom会读取该元素并转到下一个元素,然后调用下一个reader.Read()会再次读取下一个元素。如果它们恰好具有相同的名称并且是连续的,那么您实际上会错过一个元素。
pbz 2011年

3
@pbz:谢谢。我不确定我是否相信自己可以正确地编辑它(这就是我不喜欢XmlReader的程度:)您能够正确地编辑它吗?
乔恩·斯基特

1
@JonSkeet-我可能会丢失一些东西,但不会简单地更改if(reader.Name == elementName)while(reader.Name == elementName)解决pbz指出的问题?
David McLean 2014年

1
@pbz:我更改了这一行:XElement el = XNode.ReadFrom(reader)as XElement; 为:XElement el = XElement.Load(reader.ReadSubtree()); 因为这解决了跳过连续元素的错误。
Dylan Hogg

1
如其他注释所述,当前版本的SimpleStreamAxis()会在不缩进XML时跳过元素,因为Node.ReadFrom()将读取器放置在元素加载的下一个节点上-下一个无条件会跳过该节点Read()。如果下一个节点是空白,那么一切都很好。否则,不会。对于没有此问题的版本,请参见此处此处此处
dbc

29

三年后,也许随着对WebApi和xml数据的重新强调,我遇到了这个问题。由于代码方面的问题,我倾向于在没有降落伞的情况下跟随Skeet离开飞机,并且看到他的初始代码被MS Xml团队文章以及大型Xml Docs的 BOL Streaming Transform中的示例双重修饰,因此我很快忽略了其他评论,特别是来自“ 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();
    }
}

1
您的“ if(reader.Name.Equals(elementName))”语句缺少相应的“ else reader.Read();” 声明。如果该元素不是您想要的元素,则您要继续阅读。这就是我必须添加的内容,以使其对我有效。
2014年

1
@Wes通过折叠两个条件(NodeType和Name)使此条件同时else Read()适用于此问题。感谢您抓住这一点。
mdisibio 2014年

1
我支持您,但是我对看到两次编写Read方法调用并不满意。也许您可以在此处使用do while循环?:)
nawfal

另一个注意到并解决了MSDN文档相同问题的答案:stackoverflow.com/a/18282052/3744182
dbc

17

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

解析代码如下所示:

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班只是读取的<StatementsAvailable>节点

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
        }       
    }
}

6

对于子对象,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 {get{return accounts;}}
}
public class Account {
    public string NameOfKin {get;set;}
    private readonly List<Statement> statements = new List<Statement>();
    public List<Statement> StatementsAvailable {get{return 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 {}}}}
        });
    }
}

3

下面的示例在流中导航以确定当前节点类型,然后使用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();

0
    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


4
此类已弃用。不使用。
nawfal

@Elvarism您共享的网站中还有许多其他阅读xml的方法,这些方法对我有很大帮助。我会投票给你。这是另一个易于理解的XmlReader示例。
刘镇玱

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.