簡體   English   中英

在 C# 中使用 XmlReader 讀取 Xml

[英]Reading Xml with XmlReader in C#

我正在嘗試盡可能快地閱讀以下 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 元素,讓我遍歷它們並讓另一個類讀取每個屬性.

聽起來很簡單!

我對XmlReader體驗是很容易不小心讀多了。 我知道你說過你想盡快閱讀它,但你是否嘗試過使用 DOM 模型? 我發現 LINQ to XML 使 XML 的工作變得更加容易。

如果您的文檔特別大,您可以通過以流式方式從XmlReader為每個“外部”元素創建XElement來組合XmlReader和 LINQ to XML:這使您可以在 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 用戶數據(這是巨大的)轉換為另一種格式 - 它工作得很好。

來自radarbob的編輯,由喬恩重新格式化 - 雖然不太清楚哪個“讀得太遠”問題正在被提及......

這應該會簡化嵌套並解決“讀得太遠”的問題。

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

三年后,也許隨着重新強調 WebApi 和 xml 數據,我遇到了這個問題。 由於代碼方面,我傾向於在沒有降落傘的情況下跟隨 Skeet 離開飛機,並且看到他的初始代碼得到了 MS Xml 團隊文章以及 BOL Streaming Transform of Large Xml Docs 中的一個示例的雙重證實,我很快就忽略了其他評論,最特別的是來自'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();
    }
}

我們一直在做這種 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
        }       
    }
}

對於子對象, 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 {}}}}
        });
    }
}

下面的示例在流中導航以確定當前節點類型,然后使用 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();
    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

我沒有經驗。但我認為 XmlReader 是不必要的。 使用起來非常困難。
XElement 非常易於使用。
如果您需要性能(更快),您必須更改文件格式並使用 StreamReader 和 StreamWriter 類。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM