Lesen von Xml mit XmlReader in C #

97

Ich versuche, das folgende XML-Dokument so schnell wie möglich zu lesen und zusätzliche Klassen das Lesen jedes Unterblocks verwalten zu lassen.

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

Ich versuche jedoch, das XmlReader-Objekt zum Lesen jedes Kontos und anschließend der "StatementsAvailable" zu verwenden. Schlagen Sie vor, XmlReader.Read zu verwenden, jedes Element zu überprüfen und damit umzugehen?

Ich habe darüber nachgedacht, meine Klassen zu trennen, um jeden Knoten richtig zu behandeln. Es gibt also eine AccountBase-Klasse, die eine XmlReader-Instanz akzeptiert, die den NameOfKin und mehrere andere Eigenschaften des Kontos liest. Dann wollte ich die Anweisungen durchgehen lassen und eine andere Klasse sich über die Anweisung ausfüllen lassen (und sie anschließend einer IList hinzufügen).

Bisher habe ich den Teil "pro Klasse" mit XmlReader.ReadElementString () ausgeführt, aber ich kann nicht trainieren, wie der Zeiger angewiesen wird, zum Element StatementsAvailable zu wechseln, und ich kann sie durchlaufen und jede dieser Eigenschaften von einer anderen Klasse lesen lassen .

Klingt einfach!

Gloria Huang
quelle
1
Klicken Sie auf das orangefarbene Fragezeichen in der oberen rechten Ecke des Bearbeitungsfelds, um Hilfe bei der Bearbeitung zu erhalten. Wahrscheinlich möchten Sie einen Codeblock erstellen, der zuerst aus einer leeren Zeile und dann aus jeder Zeile mit vier Leerzeichen besteht.
Anders Abel
oder wählen Sie einfach Ihre Code- / XML-Zeilen aus und klicken Sie dann in der Editor-Symbolleiste auf die Schaltfläche "Code" (101 010) - so einfach ist das!
marc_s

Antworten:

163

Ich habe die Erfahrung gemacht, XmlReaderdass es sehr leicht ist, versehentlich zu viel zu lesen. Ich weiß, dass Sie gesagt haben, Sie möchten es so schnell wie möglich lesen, aber haben Sie stattdessen versucht , ein DOM-Modell zu verwenden? Ich habe festgestellt, dass LINQ to XML die Arbeit mit XML erheblich vereinfacht.

Wenn Ihr Dokument besonders riesig ist, können Sie kombinieren , XmlReaderum XML und LINQ durch eine Schaffung XElementvon einem XmlReaderfür jeden Ihrer „äußeren“ Elemente in einer Streaming - Weise: auf diese Weise können Sie die meisten der Umbauarbeiten in LINQ to XML tun, aber immer noch nur Notwendigkeit jeweils ein kleiner Teil des Dokuments im Speicher. Hier ist ein Beispielcode (leicht angepasst aus diesem Blog-Beitrag ):

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

Ich habe dies verwendet, um die StackOverflow-Benutzerdaten (die enorm sind) in ein anderes Format zu konvertieren - es funktioniert sehr gut.

EDIT von Radarbob, neu formatiert von Jon - obwohl nicht ganz klar ist, auf welches Problem "zu weit lesen" Bezug genommen wird ...

Dies sollte die Verschachtelung vereinfachen und das Problem "zu weit lesen" beheben.

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

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

    reader.ReadEndElement();
}

Dies behebt das Problem "zu weit lesen", da es das klassische while-Schleifenmuster implementiert:

initial read;
(while "we're not at the end") {
    do stuff;
    read;
}
Jon Skeet
quelle
17
Das Aufrufen von XNode.ReadFrom liest das Element und geht zum nächsten, dann liest der folgende Reader.Read () das nächste erneut. Sie würden im Wesentlichen ein Element vermissen, wenn sie denselben Namen haben und aufeinanderfolgend sind.
pbz
3
@pbz: Danke. Ich bin mir nicht sicher, ob ich mir sicher bin, dass ich es richtig bearbeite (so sehr mag ich XmlReader nicht :) Können Sie es richtig bearbeiten?
Jon Skeet
1
@ JonSkeet - Ich vermisse möglicherweise etwas, ändere es aber nicht einfach if(reader.Name == elementName), while(reader.Name == elementName)um das von pbz aufgezeigte Problem zu beheben?
David McLean
1
@pbz: Ich habe die Zeile geändert: XElement el = XNode.ReadFrom (Leser) als XElement; zu sein: XElement el = XElement.Load (reader.ReadSubtree ()); da dies den Fehler beim Überspringen aufeinanderfolgender Elemente behebt.
Dylan Hogg
1
Wie in anderen Kommentaren erwähnt, SimpleStreamAxis()überspringt die aktuelle Version von Elemente, wenn das XML nicht eingerückt ist, da Node.ReadFrom()der Leser am nächsten Knoten nach dem Laden des Elements positioniert wird - was von der nächsten bedingungslosen Version übersprungen wird Read(). Wenn der nächste Knoten ein Leerzeichen ist, ist alles in Ordnung. Sonst nicht. Versionen ohne dieses Problem finden Sie hier , hier oder hier .
dbc
29

Drei Jahre später, vielleicht mit dem erneuten Schwerpunkt auf WebApi- und XML-Daten, stieß ich auf diese Frage. Da ich codeweise dazu neige, Skeet ohne Fallschirm aus einem Flugzeug zu folgen, und seinen ursprünglichen Code, der durch den Artikel des MS Xml-Teams sowie ein Beispiel in der BOL- Streaming-Transformation großer XML-Dokumente doppelt herausgearbeitet wurde , sehr schnell übersehen habe, habe ich die anderen Kommentare sehr schnell übersehen , insbesondere von 'pbz', der darauf hinwies, dass, wenn Sie die gleichen Elemente nacheinander nach Namen haben, jedes andere wegen des doppelten Lesens übersprungen wird. Tatsächlich analysierten die BOL- und MS-Blog-Artikel Quelldokumente mit Zielelementen, die tiefer als die zweite Ebene verschachtelt waren, und maskierten diesen Nebeneffekt.

Die anderen Antworten befassen sich mit diesem Problem. Ich wollte nur eine etwas einfachere Revision anbieten, die bisher gut zu funktionieren scheint, und berücksichtigt, dass die XML-Datei möglicherweise aus verschiedenen Quellen stammt, nicht nur aus einer Uri. Daher funktioniert die Erweiterung auf dem vom Benutzer verwalteten XmlReader. Die eine Annahme ist, dass sich der Leser in seinem Anfangszustand befindet, da andernfalls das erste 'Read ()' an einem gewünschten Knoten vorbeiziehen könnte:

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
quelle
1
In Ihrer Anweisung "if (reader.Name.Equals (elementName))" fehlt ein entsprechendes "else reader.Read ();" Aussage. Wenn das Element nicht Ihren Wünschen entspricht, möchten Sie weiterlesen. Das musste ich hinzufügen, damit es für mich funktioniert.
Wes
1
@Wes Das Problem wurde behoben, indem die beiden Bedingungen (NodeType und Name) reduziert wurden, sodass die else Read()für beide gilt. Danke, dass du das verstanden hast.
mdisibio
1
Ich habe Sie positiv bewertet, bin aber nicht sehr erfreut darüber, dass der Aufruf der Read-Methode zweimal geschrieben wurde. Vielleicht könnten Sie hier eine do while-Schleife verwenden? :)
Nawfal
Eine andere Antwort, die das gleiche Problem mit den MSDN-Dokumenten bemerkte und löste: stackoverflow.com/a/18282052/3744182
dbc
17

Wir führen diese Art der XML-Analyse ständig durch. Der Schlüssel definiert, wo die Analysemethode den Leser beim Beenden verlässt. Wenn Sie den Reader immer auf dem nächsten Element belassen, das dem zuerst gelesenen Element folgt, können Sie den XML-Stream sicher und vorhersehbar einlesen. Wenn der Reader das <Account>Element gerade indiziert , indiziert der Reader nach dem Parsen das </Accounts>schließende Tag.

Der Parsing-Code sieht ungefähr so ​​aus:

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

Die StatementsKlasse liest nur den <StatementsAvailable>Knoten ein

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

Die StatementKlasse würde sehr ähnlich aussehen

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
quelle
6

Für Unterobjekte, ReadSubtree()gibt Ihnen einen XML-Leser auf die Unterobjekte beschränkt, aber ich wirklich denke , dass man dies auf die harte Art und Weise tun. Verwenden Sie diese Option (möglicherweise in Verbindung mit, wenn Sie wirklich möchten), es sei denn, Sie haben sehr spezielle Anforderungen für den Umgang mit ungewöhnlichen / nicht vorhersehbaren XML-Dateien .XmlSerializersgen.exe

XmlReaderist ... schwierig. Im Gegensatz zu:

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
quelle
3

Das folgende Beispiel navigiert durch den Stream, um den aktuellen Knotentyp zu ermitteln, und verwendet dann XmlWriter, um den XmlReader-Inhalt auszugeben.

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

Im folgenden Beispiel werden die XmlReader-Methoden verwendet, um den Inhalt von Elementen und Attributen zu lesen.

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
quelle
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");

Sie können xmlnode durchlaufen und die Daten abrufen ...... C # XML Reader

Elvarismus
quelle
4
Diese Klasse ist veraltet. Verwende nicht.
Nawfal
@Elvarism Es gibt viele andere Möglichkeiten zum Lesen von XML auf der Website, die Sie teilen, und das hilft mir sehr. Ich werde dich abstimmen. Hier ist ein weiteres leicht verständliches XmlReader- Beispiel.
18 瑲
0

Ich bin nicht erfahren. Aber ich denke, XmlReader ist unnötig. Es ist sehr schwer zu bedienen.
XElement ist sehr einfach zu bedienen.
Wenn Sie Leistung (schneller) benötigen, müssen Sie das Dateiformat ändern und die Klassen StreamReader und StreamWriter verwenden.

Mehmet
quelle