Membaca Xml dengan XmlReader di C #

97

Saya mencoba membaca dokumen Xml berikut secepat mungkin dan membiarkan kelas tambahan mengatur pembacaan setiap sub blok.

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

Namun, saya mencoba menggunakan objek XmlReader untuk membaca setiap Akun dan selanjutnya "StatementsAvailable". Apakah Anda menyarankan menggunakan XmlReader.Read dan memeriksa setiap elemen dan menanganinya?

Saya telah memikirkan untuk memisahkan kelas saya untuk menangani setiap node dengan benar. Jadi ada kelas AccountBase yang menerima contoh XmlReader yang membaca NameOfKin dan beberapa properti lain tentang akun tersebut. Kemudian saya ingin menafsirkan melalui Pernyataan dan membiarkan kelas lain mengisi sendiri tentang Pernyataan (dan kemudian menambahkannya ke IList).

Sejauh ini saya memiliki bagian "per kelas" yang dilakukan dengan melakukan XmlReader.ReadElementString () tetapi saya tidak dapat mengetahui cara memberi tahu pointer untuk pindah ke elemen StatementsAvailable dan biarkan saya mengulanginya dan membiarkan kelas lain membaca masing-masing properti tersebut .

Kedengarannya mudah!

Gloria Huang
sumber
1
Klik tanda tanya oranye di sudut kanan atas kotak edit untuk mendapatkan bantuan pengeditan. Mungkin Anda ingin membuat blok kode, yang dilakukan dengan baris kosong pertama dan kemudian setiap baris menjorok dengan empat spasi.
Anders Abel
atau cukup pilih baris kode / XML Anda lalu klik tombol "kode" (101 010) di toolbar editor - sesederhana itu!
marc_s

Jawaban:

163

Pengalaman saya XmlReaderadalah sangat mudah untuk tidak sengaja membaca terlalu banyak. Saya tahu Anda pernah mengatakan ingin membacanya secepat mungkin, tetapi apakah Anda sudah mencoba menggunakan model DOM? Saya telah menemukan bahwa LINQ ke XML membuat XML bekerja jauh lebih mudah.

Jika dokumen Anda sangat besar, Anda dapat menggabungkan XmlReaderdan LINQ ke XML dengan membuat XElementdari an XmlReaderuntuk setiap elemen "luar" Anda secara streaming: ini memungkinkan Anda melakukan sebagian besar pekerjaan konversi dalam LINQ ke XML, tetapi tetap hanya perlu sebagian kecil dari dokumen dalam memori pada satu waktu. Berikut beberapa contoh kode (diadaptasi sedikit dari posting blog ini ):

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

Saya telah menggunakan ini untuk mengonversi data pengguna StackOverflow (yang sangat besar) ke dalam format lain sebelumnya - ini bekerja dengan sangat baik.

EDIT dari radarbob, diformat ulang oleh Jon - meskipun tidak begitu jelas masalah "membaca terlalu jauh" mana yang dirujuk ...

Ini akan menyederhanakan pengumpulan dan mengatasi masalah "terlalu jauh".

using (XmlReader reader = XmlReader.Create(inputUrl))
{
    reader.ReadStartElement("theRootElement");

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

    reader.ReadEndElement();
}

Ini menangani masalah "membaca terlalu jauh" karena menerapkan pola loop sementara klasik:

initial read;
(while "we're not at the end") {
    do stuff;
    read;
}
Jon Skeet
sumber
17
Memanggil XNode.ReadFrom akan membaca elemen dan beralih ke elemen berikutnya, lalu reader berikutnya.Read () membaca elemen berikutnya lagi. Anda pada dasarnya akan kehilangan satu elemen jika kebetulan memiliki nama yang sama dan berurutan.
pbz
3
@pbz: Terima kasih. Saya tidak yakin saya percaya diri untuk mengeditnya dengan benar (itulah yang saya tidak suka XmlReader :) Apakah Anda dapat mengeditnya dengan benar?
Jon Skeet
1
@ JonSkeet - Saya mungkin melewatkan sesuatu tetapi tidak akan berubah begitu saja if(reader.Name == elementName)untuk while(reader.Name == elementName)memperbaiki masalah yang ditunjukkan oleh pbz?
David McLean
1
@pbz: Saya mengubah baris: XElement el = XNode.ReadFrom (reader) sebagai XElement; menjadi: XElement el = XElement.Load (reader.ReadSubtree ()); karena ini memperbaiki bug elemen berurutan yang dilewati.
Dylan Hogg
1
Seperti yang disebutkan di komentar lain, versi saat ini dari SimpleStreamAxis()akan melewatkan elemen saat XML tidak menjorok ke dalam, karena Node.ReadFrom()menempatkan pembaca di node berikutnya setelah elemen dimuat - yang akan dilewati oleh tanpa syarat berikutnya Read(). Jika node berikutnya adalah spasi maka semuanya baik-baik saja. Kalau tidak, tidak. Untuk versi tanpa masalah ini lihat di sini , di sini atau di sini .
dbc
29

Tiga tahun kemudian, mungkin dengan penekanan baru pada data WebApi dan xml, saya menemukan pertanyaan ini. Karena dengan kode saya cenderung mengikuti Skeet keluar dari pesawat tanpa parasut, dan melihat kode awalnya dikoraborasi dua kali oleh artikel tim MS Xml serta contoh di BOL Streaming Transform of Large Xml Docs , saya dengan cepat mengabaikan komentar lainnya , paling khusus dari 'pbz', yang menunjukkan bahwa jika Anda memiliki elemen yang sama dengan nama secara berurutan, setiap elemen lainnya akan dilewati karena pembacaan ganda. Dan faktanya, artikel blog BOL dan MS sama-sama mem-parse dokumen sumber dengan elemen target bersarang lebih dalam dari level kedua, menutupi efek samping ini.

Jawaban lain menjawab masalah ini. Saya hanya ingin menawarkan revisi yang sedikit lebih sederhana yang tampaknya bekerja dengan baik sejauh ini, dan memperhitungkan bahwa xml mungkin berasal dari sumber yang berbeda, bukan hanya uri, dan ekstensi berfungsi pada XmlReader yang dikelola pengguna. Asumsinya adalah bahwa pembaca berada dalam keadaan awalnya, karena jika tidak, 'Read ()' pertama mungkin maju melewati node yang diinginkan:

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();
    }
}
mdisibio
sumber
1
Pernyataan "if (reader.Name.Equals (elementName))" Anda tidak memiliki "else reader.Read ();" pernyataan. Jika elemennya bukan yang Anda inginkan, Anda ingin melanjutkan membaca. Itulah yang harus saya tambahkan agar berfungsi untuk saya.
Wes
1
@Wes Memperbaiki masalah dengan menciutkan dua kondisional (NodeType dan Name) sehingga else Read()berlaku untuk keduanya. Terima kasih sudah menangkapnya.
mdisibio
1
Saya memberi suara positif kepada Anda, tetapi saya tidak begitu senang melihat panggilan metode Baca ditulis dua kali. Mungkin Anda bisa menggunakan do while loop di sini? :)
nawfal
Jawaban lain yang memperhatikan dan menyelesaikan masalah yang sama dengan dokumen MSDN: stackoverflow.com/a/18282052/3744182
dbc
17

Kami melakukan penguraian XML semacam ini sepanjang waktu. Kuncinya adalah menentukan di mana metode parsing akan membiarkan pembaca keluar. Jika Anda selalu meninggalkan pembaca pada elemen berikutnya setelah elemen yang pertama kali dibaca, maka Anda dapat membaca dengan aman dan dapat diprediksi di aliran XML. Jadi jika pembaca saat ini mengindeks <Account>elemen tersebut, setelah parsing pembaca akan mengindeks </Accounts>tag penutup.

Kode parsing terlihat seperti ini:

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

The Statementskelas hanya membaca di <StatementsAvailable>simpul

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

The Statementkelas akan terlihat sangat banyak yang sama

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
        }       
    }
}
Paul Alexander
sumber
6

Untuk sub-objek, ReadSubtree()memberi Anda xml-reader terbatas pada sub-objek, tapi saya benar-benar berpikir bahwa Anda melakukan ini dengan cara yang keras. Kecuali Anda memiliki persyaratan yang sangat spesifik untuk menangani xml yang tidak biasa / tidak dapat diprediksi, gunakan XmlSerializer(mungkin digabungkan dengan sgen.exejika Anda benar-benar menginginkannya).

XmlReaderadalah ... rumit. Berbeda dengan:

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 {}}}}
        });
    }
}
Marc Gravell
sumber
3

Contoh berikut menavigasi melalui aliran untuk menentukan jenis node saat ini, dan kemudian menggunakan XmlWriter untuk mengeluarkan konten 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();

Contoh berikut menggunakan metode XmlReader untuk membaca konten elemen dan atribut.

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();
Muhammad Awais
sumber
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");

Anda dapat melakukan loop melalui xmlnode dan mendapatkan data ...... C # XML Reader

Elvarisme
sumber
4
Kelas ini tidak digunakan lagi. Jangan gunakan.
nawfal
@Elvarism Ada banyak cara baca xml lainnya di situs web yang Anda bagikan, dan itu sangat membantu saya. Saya akan memilih Anda. Berikut adalah contoh XmlReader lain yang mudah dipahami .
劉鎮 瑲
0

Saya tidak berpengalaman. Tapi saya pikir XmlReader tidak diperlukan. Sangat sulit digunakan.
XElement sangat mudah digunakan.
Jika Anda membutuhkan kinerja (lebih cepat), Anda harus mengubah format file dan menggunakan kelas StreamReader dan StreamWriter.

Mehmet
sumber