101 votes

Lire le Xml avec XmlReader en C#

Je suis en train de lire le document Xml suivant aussi vite que je peux et de laisser d'autres catégories de gérer la lecture de chaque sous bloc.

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

Cependant, je suis en train d'utiliser l'objet XmlReader à lire chaque Compte et par la suite le "StatementsAvailable". Avez-vous suggérons d'utiliser XmlReader.Lire et vérifier chaque élément et de le manipuler?

J'ai pensé à séparant mes classes pour gérer chaque nœud correctement. Si theres une AccountBase classe qui accepte un XmlReader instance qui lit le NameOfKin et plusieurs autres propriétés sur le compte. Alors je voulais interate à travers les Déclarations et laisser une autre classe de remplissage de lui-même à propos de la Déclaration (et par la suite l'ajouter à un IList).

Jusqu'à présent j'ai l' "par la classe" partie fait en faisant XmlReader.ReadElementString() mais je ne peut pas séance d'entraînement comment dire pour déplacer le pointeur à l'StatementsAvailable élément et permettez-moi de parcourir et d'en faire une autre classe de lire chacun de ces propriétés.

Semble facile!

169voto

Jon Skeet Points 692016

Mon expérience de l' XmlReader , c'est qu'il est très facile à lire accidentellement trop. Je sais que vous avez dit que vous voulez le lire le plus rapidement possible, mais avez-vous essayé à l'aide d'un modèle DOM à la place? J'ai trouvé que LINQ to XML, XML travail beaucoup beaucoup plus facile.

Si votre document est particulièrement énorme, vous pouvez combiner XmlReader et LINQ to XML en créant un XElement d'un XmlReader pour chacun de vos "externe" des éléments dans un flux de manière: cela vous permet de faire la plupart des travaux de transformation dans LINQ to XML, mais encore besoin seulement d'une petite partie du document en mémoire à tout moment. Voici un exemple de code (adapté légèrement de ce blog):

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

J'ai utilisé ce pour convertir le StackOverflow les données de l'utilisateur (ce qui est énorme) dans un autre format avant - ça fonctionne très bien.

EDIT de radarbob, reformaté par Jon - bien que ce n'est pas tout à fait clair "lire trop loin", problème qui est mentionné...

Cela devrait tout simplement la nidification et de prendre soin de la "une lecture trop loin" problème.

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

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

    reader.ReadEndElement();
}

Cela prend en charge "d'une lecture trop loin" problème parce qu'il met en œuvre la classique boucle while modèle:

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

30voto

mdisibio Points 1061

Trois ans plus tard, peut-être avec l'accent mis sur WebApi et des données xml, je suis tombé sur cette question. Depuis codewise je suis enclin à suivre Skeet d'un avion sans parachute, et de voir son code initial doublement corraborated par le MS Xml de l'équipe de l'article, ainsi qu'un exemple de BOL Streaming Transformation de Xml Volumineux Docs, j'ai très rapidement négligé les autres commentaires, plus précisément de "pbz', qui a souligné que si vous avez les mêmes éléments par nom dans la succession, toutes les autres sont ignorées en raison de la double lecture. Et en fait, le BOL et MS articles de blog à la fois ont été l'analyse des documents source à la cible éléments imbriqués plus profond de deuxième niveau, de masquage, de ce côté-effet.

Les autres réponses face à ce problème. J'ai juste voulu donner un peu plus simple révision qui semble bien fonctionner jusqu'à présent, et ne prend en compte que le xml peut provenir de différentes sources, et pas seulement une uri, et donc l'extension fonctionne sur l'utilisateur géré XmlReader. Une hypothèse est que le lecteur est dans son état initial, car sinon le premier "Read ()" pourrait avancer au-delà d'un nœud désiré:

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

17voto

Paul Alexander Points 17611

Nous faire ce genre d'analyse XML tout le temps. La clé est de définir où l'analyse de la méthode laissera le lecteur sur la sortie. Si vous avez toujours laisser le lecteur sur le prochain élément suivant l'élément qui a d'abord été lu, alors vous pouvez en toute sécurité et de façon prévisible lire dans le flux de données XML. Si le lecteur est actuellement l'indexation de l' <Account> élément, après l'analyse, le lecteur à l'indice de l' </Accounts> balise de fermeture.

Le code d'analyse ressemble à quelque chose comme ceci:

public class Account
{
    string _accountId;
    string _nameOfKin;
    Statements _statmentsAvailable;

    public void ReadFromXml( XmlReader reader )
    {
        reader.MovToContent();

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

L' Statements classe se lit dans l' <StatementsAvailable> noeud

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

L' Statement de la classe ont les mêmes

public class Statement
{
    string _satementId;

    public void ReadFromXml( XmlReader reader )
    {
        reader.MovToContent();

        // Read noe attributes
        _statementId = reader.GetAttribute( "statementId" );
        ...

        if( reader.IsEmptyElement ) { reader.Read(); return; }

        reader.Read();
        while( ! reader.EOF )
        {           
            ....same basic loop
        }       
    }
}

7voto

Marc Gravell Points 482669

Pour les sous-objets, ReadSubtree() vous donne un xml-lecteur limitée à la sous-objets, mais j'ai vraiment pense que vous faites cela à la dure. Sauf si vous avez très spécifiques exigences relatives à la manipulation inhabituelle / unpredicatable xml, utilisez XmlSerializer (peut-être couplé avec sgen.exe si vous voulez vraiment).

XmlReader , c'est... compliqué. Contrairement à:

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

0voto

Elvarism Points 1
    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");

Vous pouvez faire une boucle par xmlnode et d'obtenir les données...... C# Lecteur XML

Elvaris

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X