web-dev-qa-db-ja.com

C#でXmlReaderを使用してXmlを読み取る

次のXMLドキュメントをできるだけ早く読み、追加のクラスに各サブブロックの読み取りを管理させようとしています。

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

ただし、XmlReaderオブジェクトを使用して各Accountを読み取り、その後で "StatementsAvailable"を読み取ろうとしています。 XmlReader.Readを使用して各要素を確認し、処理することをお勧めしますか?

各ノードを適切に処理するためにクラスを分離することを考えました。そのため、NameOfKinおよびアカウントに関する他のいくつかのプロパティを読み取るXmlReaderインスタンスを受け入れるAccountBaseクラスがあります。次に、Statementsを操作して、Statementについて別のクラスに記入してもらいたい(その後、IListに追加したい)。

これまで、XmlReader.ReadElementString()を実行して「クラスごと」の部分を実行しましたが、StatementsAvailable要素に移動するようにポインターに指示し、それらを反復して別のクラスにそれらのプロパティを読み取らせる方法はありません。

簡単ですね!

87
Gloria Huang

XmlReaderの私の経験では、誤って読みすぎてしまうのは非常に簡単です。できるだけ早く読みたいと言ったことは知っていますが、代わりにDOMモデルを使用してtriedしましたか? 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ユーザーデータ(これは非常に大きい)を別の形式に変換するためにこれを使用したことがあります-非常にうまく機能します。

ジョンによって再フォーマットされたレーダーボブからの編集-どの「遠くまで読みすぎる」問題が言及されているかは明確ではありませんが...

これにより、ネストが簡素化され、「読みすぎ」の問題に対処する必要があります。

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;
}
152
Jon Skeet

3年後、おそらくWebApiとxmlデータに新たな重点を置いて、この質問に出会いました。コードに関しては、パラシュートなしで飛行機からスキートを追いかけ、MS Xmlチームの記事とBOLの例で二重に修正された彼の初期コードを見る傾向があります Streaming Transform of Large Xml Docs 、他のコメント、特に「pbz」からのコメントをすぐに見落としました。彼は、同じ名前の要素が連続してある場合、二重読み取りのために他のコメントはすべてスキップされると指摘しました。実際、BOLとMSのブログ記事は両方とも、この副作用を隠して、ターゲット要素が第2レベルよりも深くネストされたソースドキュメントを解析していました。

他の回答はこの問題に対処します。私はこれまでのところうまくいくように見える少しシンプルなリビジョンを提供したかっただけで、xmlはuriだけでなく異なるソースから来る可能性があることを考慮しているため、拡張機能はユーザー管理のXmlReaderで動作します。 1つの前提は、リーダーが初期状態にあることです。そうしないと、最初の「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();
    }
}
26
mdisibio

この種の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
        }       
    }
}
17
Paul Alexander

サブオブジェクトの場合、ReadSubtree()はサブオブジェクトに限定されたxml-readerを提供しますが、私は実際にこれを難しい方法で行っていると思います。 非常に具体的異常な/予測不可能な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 {}}}}
        });
    }
}
6
Marc Gravell

次の例では、ストリームをナビゲートして現在のノードタイプを特定し、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();
2
Muhammad Awais
    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

0
Elvarism

経験はありませんが、XmlReaderは不要だと思います。とても使いにくいです。
XElementは非常に使いやすいです。
パフォーマンス(高速)が必要な場合は、ファイル形式を変更し、StreamReaderクラスとStreamWriterクラスを使用する必要があります。

0
Mehmet