C # 'da XmlReader ile Xml Okuma


98

Aşağıdaki Xml belgesini olabildiğince hızlı okumaya çalışıyorum ve ek sınıfların her bir alt bloğun okumasını yönetmesine izin veriyorum.

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

Ancak, her Hesabı ve ardından "StatementsAvailable" öğesini okumak için XmlReader nesnesini kullanmaya çalışıyorum. XmlReader kullanmayı öneriyor musunuz, her öğeyi okuyun, kontrol edin ve işleyin

Her düğümü düzgün bir şekilde ele almak için sınıflarımı ayırmayı düşündüm. Dolayısıyla, NameOfKin'i ve hesapla ilgili diğer birkaç özelliği okuyan bir XmlReader örneğini kabul eden bir AccountBase sınıfı vardır. Sonra İfadeler aracılığıyla araya girmek ve başka bir sınıfın İfade hakkında kendisini doldurmasına izin vermek (ve ardından bunu bir IList'e eklemek) istiyordum.

Şimdiye kadar XmlReader.ReadElementString () yaparak yapılan "sınıf başına" kısmına sahibim, ancak işaretçiye StatementsAvailable öğesine gitmesini nasıl söyleyeceğimi ve bunları yinelememe ve başka bir sınıfın bu proeprty'lerin her birini okumasına izin veremem .

Kolay geliyor!


1
Düzenleme yardımı almak için düzenleme kutusunun sağ üst köşesindeki turuncu soru işaretini tıklayın. Muhtemelen, önce boş bir satır ve ardından her satırın dört boşlukla girintilenmesiyle yapılan bir kod bloğu oluşturmak istiyorsunuz.
Anders Abel

veya sadece kod / XML satırlarınızı seçin ve ardından düzenleyici araç çubuğundaki "kod" düğmesine (101 010) tıklayın - bu kadar basit!
marc_s

Yanıtlar:


164

Benim deneyimim, XmlReaderyanlışlıkla çok fazla okumak çok kolay olmasıdır. Mümkün olduğunca çabuk okumak istediğini söylediğini biliyorum, ama bunun yerine bir DOM modeli kullanmayı denedin mi? LINQ to XML'in XML'in çok daha kolay çalışmasını sağladığını buldum .

Belgeniz özellikle büyükse, akış şeklinde "dış" öğelerinizin her biri için bir " dan " XmlReaderoluşturarak LINQ to XML birleştirebilir ve LINQ to XML yapabilirsiniz : bu, LINQ'ten XML'e dönüştürme çalışmalarının çoğunu yapmanızı sağlar, ancak yine de yalnızca herhangi bir zamanda hafızada belgenin küçük bir kısmı. İşte bazı örnek kodlar ( bu blog gönderisinden biraz uyarlanmıştır ):XElementXmlReader

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

Bunu daha önce StackOverflow kullanıcı verilerini (çok büyük olan) başka bir formata dönüştürmek için kullandım - çok iyi çalışıyor.

Jon tarafından yeniden biçimlendirilen radarbob'dan EDIT - hangi "çok fazla okuma" sorunundan bahsedildiği tam olarak net olmasa da ...

Bu, yerleştirmeyi basitleştirmeli ve "çok fazla okuma" sorunuyla ilgilenmelidir.

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

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

    reader.ReadEndElement();
}

Bu, klasik while döngüsü modelini uyguladığı için "çok fazla okuma" sorunuyla ilgilenir:

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

17
XNode.ReadFrom çağrısı, öğeyi okur ve bir sonrakine gider, ardından aşağıdaki reader.Read () bir sonrakini tekrar okur. Aynı ada sahip ve birbirini takip eden bir öğe olursa, aslında bir öğeyi özlersiniz.
pbz

3
@pbz: Teşekkürler. Doğru düzenleme konusunda kendime güvenmediğimden emin değilim (XmlReader'ı bu kadar sevmiyorum :) Doğru şekilde düzenleyebiliyor musunuz?
Jon Skeet

1
@JonSkeet - Bir şeyler eksik ama alışkanlık basitçe değişen olabilir if(reader.Name == elementName)için while(reader.Name == elementName)sorun PBZ tarafından işaret düzeltmek?
David McLean

1
@pbz: Satırı değiştirdim: XElement el = XNode.ReadFrom (okuyucu) XElement olarak; : XElement el = XElement.Load (reader.ReadSubtree ()); çünkü bu, ardışık öğeleri atlama hatasını düzeltir.
Dylan Hogg

1
Diğer açıklamalarda belirtildiği gibi, geçerli sürümü SimpleStreamAxis()XML girintili değilken, elementler atlar çünkü Node.ReadFrom()bir sonraki düğümde okuyucu pozisyonları sonrasında elemanı yüklenen - Bir sonraki koşulsuz tarafından atlanır alacak Read(). Bir sonraki düğüm boşluksa, o zaman her şey yolunda demektir. Aksi takdirde hayır. Bu sorunun olmadığı sürümler için buraya , buraya veya buraya bakın .
dbc

29

Üç yıl sonra, belki WebApi ve xml verilerine yenilenen vurguyla, bu soruyla karşılaştım. Kod olarak Skeet'i paraşütsüz bir uçaktan takip etmeye meyilli olduğumdan ve onun ilk kodunun MS Xml ekibi makalesinin yanı sıra BOL Streaming Transform of Large Xml Docs'daki bir örnekle iki kat daha fazla düzeltildiğini görünce , diğer yorumları çok hızlı bir şekilde gözden kaçırdım , en özel olarak 'pbz'den, eğer aynı öğelere arka arkaya isimleriyle sahipseniz, çift okuma nedeniyle diğerlerinin atlandığını belirten' pbz '. Ve aslında, BOL ve MS blog makalelerinin her ikisi de, kaynak belgeleri ikinci seviyeden daha derine yerleştirilmiş hedef öğelerle ayrıştırıyor ve bu yan etkiyi maskeliyordu.

Diğer cevaplar bu sorunu ele almaktadır. Sadece şu ana kadar iyi çalışan ve xml'nin sadece bir uri'den değil farklı kaynaklardan gelebileceğini ve bu nedenle uzantının kullanıcı tarafından yönetilen XmlReader üzerinde çalıştığını hesaba katan biraz daha basit bir revizyon sunmak istedim. Bir varsayım, okuyucunun başlangıç ​​durumunda olduğudur, çünkü aksi takdirde ilk 'Oku ()' istenen bir düğümü geçebilir:

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
"İf (reader.Name.Equals (elementName))" ifadenizde karşılık gelen "else reader.Read ();" Beyan. Öğe istediğiniz şey değilse okumaya devam etmek istersiniz. Benim için çalışmasını sağlamak için eklemem gereken şey buydu.
Wes

1
@Wes else Read()Her ikisi için de geçerli olacak şekilde iki koşulu (NodeType ve Name) daraltarak sorunu çözdü . Yakaladığınız için teşekkürler.
mdisibio

1
Size oy verdim, ancak iki kez yazılan Okuma yöntemi çağrısını görmekten çok mutlu değilim. Burada bir do while döngüsü kullanabilir misin? :)
nawfal

MSDN belgeleriyle aynı sorunu fark eden ve çözen başka bir yanıt: stackoverflow.com/a/18282052/3744182
dbc

17

Bu tür XML çözümlemelerini her zaman yapıyoruz. Anahtar, ayrıştırma yönteminin okuyucuyu çıkışta nerede bırakacağını tanımlamaktır. Okuyucuyu her zaman ilk okunan öğeden sonraki öğede bırakırsanız, XML akışında güvenli ve tahmin edilebilir şekilde okuyabilirsiniz. Dolayısıyla, okuyucu şu anda <Account>öğeyi indeksliyorsa, ayrıştırdıktan sonra okuyucu </Accounts>kapanış etiketini indeksleyecektir .

Ayrıştırma kodu şuna benzer:

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

StatementsSınıf sadece okur <StatementsAvailable>düğüm

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

StatementSınıf çok aynı görünecektir

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

7

Alt nesneler için, ReadSubtree()size alt nesnelerle sınırlı bir xml okuyucu verir, ancak ben gerçekten zor yoldan yaptığınızı düşünüyorum. Eğer yoksa çok özel sıradışı / unpredicatable xml işlemek için gereksinimleri, kullanım XmlSerializer(belki birleştiğinde sgen.exegerçekten istiyorsanız).

XmlReader... aldatıcı. Kontrast:

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

4

Aşağıdaki örnek, geçerli düğüm türünü belirlemek için akışta gezinir ve ardından XmlReader içeriğini çıkarmak için XmlWriter kullanır.

    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();

Aşağıdaki örnek, öğelerin ve özniteliklerin içeriğini okumak için XmlReader yöntemlerini kullanır.

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 üzerinden döngü yapabilir ve verileri alabilirsiniz ...... C # XML Okuyucu


4
Bu sınıf kullanımdan kaldırıldı. Kullanmayın.
nawfal

@Elvarism Paylaştığınız web sitesinde başka birçok xml okuma yolu var ve bu bana çok yardımcı oluyor. Sana oy vereceğim. İşte başka bir kolay anlaşılır XmlReader örneği.
劉鎮 瑲

0

Deneyimli değilim. Ama XmlReader'ın gereksiz olduğunu düşünüyorum. Kullanması çok zor.
XElement'in kullanımı çok kolaydır.
Performansa (daha hızlı) ihtiyacınız varsa, dosya formatını değiştirmeli ve StreamReader ve StreamWriter sınıflarını kullanmalısınız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.