web-dev-qa-db-fra.com

Lecture de XML avec XmlReader en C #

J'essaie de lire aussi rapidement que possible le document XML suivant et de laisser des classes supplémentaires gérer la lecture de chaque sous-bloc.

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

Cependant, j'essaie d'utiliser l'objet XmlReader pour lire chaque compte, puis le "StatementsAvailable". Suggérez-vous d’utiliser XmlReader.Read, vérifiez chaque élément et gérez-le?

J'ai pensé à séparer mes classes pour gérer chaque nœud correctement. Il existe donc une classe AccountBase qui accepte une instance de XmlReader qui lit NameOfKin et plusieurs autres propriétés du compte. Ensuite, je voulais interagir à travers les déclarations et laisser une autre classe se renseigner sur la déclaration (et l'ajouter par la suite à une liste IList).

Jusqu'ici, j'ai terminé la partie "par classe" en faisant XmlReader.ReadElementString () mais je ne peux pas expliquer comment indiquer au pointeur de se déplacer vers l'élément StatementsAvailable et de me laisser le parcourir et laisser une autre classe lire chacune de ces propriétés. .

Cela semble facile!

89
Gloria Huang

Mon expérience de XmlReader est qu’il est très facile de lire trop accidentellement. Je sais que vous avez dit que vous souhaitiez le lire le plus rapidement possible, mais avez-vous essayé d'utiliser un modèle DOM à la place? J'ai constaté que LINQ to XML facilite grandement le travail XML .

Si votre document est particulièrement volumineux, vous pouvez combiner XmlReader et LINQ to XML en créant un XElement à partir de XmlReader pour chacun de vos éléments "extérieurs" de manière continue: cela vous permet de: La plupart des travaux de conversion fonctionnent dans LINQ to XML, mais ils n’ont toujours besoin que d’une petite partie du document en mémoire à la fois. Voici un exemple de code (adapté légèrement de cet article de 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 déjà utilisé cela pour convertir les données utilisateur StackOverflow (qui est énorme) dans un autre format auparavant - cela fonctionne très bien.

EDIT de radarbob, reformaté par Jon - bien que l’on ne sache pas exactement à quel problème "lire trop loin" on parle ...

Cela devrait simplifier l’emboîtement et résoudre le problème "une lecture trop loin".

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

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

    reader.ReadEndElement();
}

Cela résout le problème "une lecture trop loin" car il implémente le motif classique de la boucle while:

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

Trois ans plus tard, avec peut-être l’importance renouvelée accordée à WebApi et aux données XML, je suis tombé sur cette question. Depuis que je suis codément, je suis enclin à suivre Skeet dans un avion sans parachute, et à voir son code initial doublement corrodé par l'article de l'équipe MS Xml ainsi qu'un exemple dans BOL Transformation en streaming de documents grand format XML , J'ai très vite négligé les autres commentaires, plus particulièrement ceux de "pbz", qui ont souligné que si vous avez les mêmes éléments nommément l'un après l'autre, tous les autres sont ignorés à cause de la double lecture. Et en fait, les articles de blog BOL et MS analysaient les documents source avec des éléments cibles imbriqués plus profondément que le deuxième niveau, masquant ainsi cet effet secondaire.

Les autres réponses abordent ce problème. Je voulais juste proposer une révision un peu plus simple qui semble bien fonctionner jusqu'à présent et qui tient compte du fait que le xml peut provenir de différentes sources, pas seulement d'un uri. L'extension fonctionne donc sur XmlReader géré par l'utilisateur. La première hypothèse est que le lecteur est dans son état initial, sinon le premier 'Read ()' pourrait dépasser un nœud souhaité:

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

Nous faisons ce genre d'analyse XML tout le temps. La clé est de définir où la méthode d'analyse laissera le lecteur à la sortie. Si vous laissez toujours le lecteur sur l'élément suivant après l'élément qui a été lu en premier, vous pouvez alors lire de manière sûre et prévisible dans le flux XML. Donc, si le lecteur est en train d’indexer l’élément <Account>, après l’analyse, le lecteur indexera la balise de fermeture </Accounts>.

Le code d'analyse ressemble à ceci:

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

La classe Statements ne lit que dans le noeud <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;
            }
        }
    }
}

La classe Statement se ressemblerait beaucoup

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

Pour les sous-objets, ReadSubtree() vous donne un lecteur xml limité aux sous-objets, mais je vraiment pense que vous le faites à la dure. Sauf si vous avez très spécifique exigences relatives à la gestion des fichiers XML inhabituels/imprévisibles, utilisez XmlSerializer (éventuellement associé à sgen.exe si vous le souhaitez vraiment).

XmlReader est ... difficile. Contraste à:

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

L'exemple suivant navigue dans le flux pour déterminer le type de nœud actuel, puis utilise XmlWriter pour générer le contenu 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();

L'exemple suivant utilise les méthodes XmlReader pour lire le contenu des éléments et des attributs.

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

Vous pouvez parcourir xmlnode et obtenir les données ...... Lecteur C # XML

0
Elvarism

Je ne suis pas expérimenté. Mais je pense que XmlReader est inutile. C'est très difficile à utiliser.
XElement est très facile à utiliser.
Si vous avez besoin de performances (plus rapide), vous devez changer le format de fichier et utiliser les classes StreamReader et StreamWriter.

0
Mehmet