web-dev-qa-db-fra.com

Quelle est la meilleure façon d'analyser (gros) XML dans du code C #?

J'écris un outil client SIG en C # pour récupérer des "fonctionnalités" dans un schéma XML basé sur GML (exemple ci-dessous) à partir d'un serveur. Les extraits sont limités à 100 000 fonctionnalités.

J'imagine que le plus grand extract.xml pourrait atteindre environ 150 mégaoctets, donc évidemment les analyseurs DOM sont sortis, j'ai essayé de choisir entre XmlSerializer et XSD.EXE liaisons générées --OR-- XmlReader et un graphique d'objet fabriqué à la main.

Ou peut-être y a-t-il une meilleure façon que je n'ai pas encore envisagée? Comme XLINQ, ou ????

S'il vous plaît, quelqu'un peut-il me guider? Surtout en ce qui concerne l'efficacité de la mémoire d'une approche donnée. Sinon, je devrai "prototyper" les deux solutions et les profiler côte à côte.

Je suis un peu une crevette crue dans .NET. Tout conseil serait grandement apprécié.

Je vous remercie. Keith.


Échantillon XML - jusqu'à 100 000 d'entre eux, jusqu'à 234 600 accords par fonctionnalité.

<feature featId="27168306" fType="vegetation" fTypeId="1129" fClass="vegetation" gType="Polygon" ID="0" cLockNr="51598" metadataId="51599" mdFileId="NRM/TIS/VEGETATION/9543_22_v3" dataScale="25000">
  <MultiGeometry>
    <geometryMember>
      <Polygon>
        <outerBoundaryIs>
          <LinearRing>
            <coordinates>153.505004,-27.42196 153.505044,-27.422015 153.503992 .... 172 coordinates omitted to save space ... 153.505004,-27.42196</coordinates>
          </LinearRing>
        </outerBoundaryIs>
      </Polygon>
    </geometryMember>
  </MultiGeometry>
</feature>
59
corlettk

Utilisez XmlReader pour analyser de gros documents XML. XmlReader fournit un accès rapide, en avant uniquement, sans mise en cache aux données XML. (Avant uniquement signifie que vous pouvez lire le fichier XML du début à la fin mais ne pouvez pas reculer dans le fichier.) XmlReader utilise de petites quantités de mémoire et équivaut à utiliser un simple lecteur SAX.

    using (XmlReader myReader = XmlReader.Create(@"c:\data\coords.xml"))
    {
        while (myReader.Read())
        {
           // Process each node (myReader.Value) here
           // ...
        }
    }

Vous pouvez utiliser XmlReader pour traiter des fichiers d'une taille maximale de 2 gigaoctets (Go).

Ref: Comment lire XML à partir d'un fichier en utilisant Visual C #

62
Mitch Wheat

Asat 14 mai 2009: Je suis passé à l'utilisation d'une approche hybride ... voir le code ci-dessous.

Cette version présente la plupart des avantages des deux:
* Le XmlReader/XmlTextReader (efficacité de la mémoire -> vitesse); et
* Le XmlSerializer (code-gen -> rapidité et flexibilité de développement).

Il utilise le XmlTextReader pour parcourir le document et crée des "doclets" qu'il désérialise à l'aide des classes XmlSerializer et "XML binding" générées avec XSD.EXE.

Je suppose que cette recette est universellement applicable, et elle est rapide ... J'analyse un document XML de 201 Mo contenant 56 000 fonctionnalités GML en environ 7 secondes ... l'ancienne implémentation VB6 de cette application prenait des minutes (voire des heures) à analyser grands extraits ... donc je suis prêt à partir.

Encore une fois, un GRAND Merci aux forumites d'avoir donné votre temps précieux. J'apprécie vraiment cela.

Bravo à tous. Keith.

using System;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Generic;

using nrw_rime_extract.utils;
using nrw_rime_extract.xml.generated_bindings;

namespace nrw_rime_extract.xml
{
    internal interface ExtractXmlReader
    {
        rimeType read(string xmlFilename);
    }

    /// <summary>
    /// RimeExtractXml provides bindings to the RIME Extract XML as defined by
    /// $/Release 2.7/Documentation/Technical/SCHEMA and DTDs/nrw-rime-extract.xsd
    /// </summary>
    internal class ExtractXmlReader_XmlSerializerImpl : ExtractXmlReader
    {
        private Log log = Log.getInstance();

        public rimeType read(string xmlFilename)
        {
            log.write(
                string.Format(
                    "DEBUG: ExtractXmlReader_XmlSerializerImpl.read({0})",
                    xmlFilename));
            using (Stream stream = new FileStream(xmlFilename, FileMode.Open))
            {
                return read(stream);
            }
        }

        internal rimeType read(Stream xmlInputStream)
        {
            // create an instance of the XmlSerializer class, 
            // specifying the type of object to be deserialized.
            XmlSerializer serializer = new XmlSerializer(typeof(rimeType));
            serializer.UnknownNode += new XmlNodeEventHandler(handleUnknownNode);
            serializer.UnknownAttribute += 
                new XmlAttributeEventHandler(handleUnknownAttribute);
            // use the Deserialize method to restore the object's state
            // with data from the XML document.
            return (rimeType)serializer.Deserialize(xmlInputStream);
        }

        protected void handleUnknownNode(object sender, XmlNodeEventArgs e)
        {
            log.write(
                string.Format(
                    "XML_ERROR: Unknown Node at line {0} position {1} : {2}\t{3}",
                    e.LineNumber, e.LinePosition, e.Name, e.Text));
        }

        protected void handleUnknownAttribute(object sender, XmlAttributeEventArgs e)
        {
            log.write(
                string.Format(
                    "XML_ERROR: Unknown Attribute at line {0} position {1} : {2}='{3}'",
                    e.LineNumber, e.LinePosition, e.Attr.Name, e.Attr.Value));
        }

    }

    /// <summary>
    /// xtractXmlReader provides bindings to the extract.xml 
    /// returned by the RIME server; as defined by:
    ///   $/Release X/Documentation/Technical/SCHEMA and 
    /// DTDs/nrw-rime-extract.xsd
    /// </summary>
    internal class ExtractXmlReader_XmlTextReaderXmlSerializerHybridImpl :
        ExtractXmlReader
    {
        private Log log = Log.getInstance();

        public rimeType read(string xmlFilename)
        {
            log.write(
                string.Format(
                    "DEBUG: ExtractXmlReader_XmlTextReaderXmlSerializerHybridImpl." +
                    "read({0})",
                    xmlFilename));

            using (XmlReader reader = XmlReader.Create(xmlFilename))
            {
                return read(reader);
            }

        }

        public rimeType read(XmlReader reader)
        {
            rimeType result = new rimeType();
            // a deserializer for featureClass, feature, etc, "doclets"
            Dictionary<Type, XmlSerializer> serializers = 
                new Dictionary<Type, XmlSerializer>();
            serializers.Add(typeof(featureClassType), 
                newSerializer(typeof(featureClassType)));
            serializers.Add(typeof(featureType), 
                newSerializer(typeof(featureType)));

            List<featureClassType> featureClasses = new List<featureClassType>();
            List<featureType> features = new List<featureType>();
            while (!reader.EOF)
            {
                if (reader.MoveToContent() != XmlNodeType.Element)
                {
                    reader.Read(); // skip non-element-nodes and unknown-elements.
                    continue;
                }

                // skip junk nodes.
                if (reader.Name.Equals("featureClass"))
                {
                    using (
                        StringReader elementReader =
                            new StringReader(reader.ReadOuterXml()))
                    {
                        XmlSerializer deserializer =
                            serializers[typeof (featureClassType)];
                        featureClasses.Add(
                            (featureClassType)
                            deserializer.Deserialize(elementReader));
                    }
                    continue;
                    // ReadOuterXml advances the reader, so don't read again.
                }

                if (reader.Name.Equals("feature"))
                {
                    using (
                        StringReader elementReader =
                            new StringReader(reader.ReadOuterXml()))
                    {
                        XmlSerializer deserializer =
                            serializers[typeof (featureType)];
                        features.Add(
                            (featureType)
                            deserializer.Deserialize(elementReader));
                    }
                    continue;
                    // ReadOuterXml advances the reader, so don't read again.
                }

                log.write(
                    "WARNING: unknown element '" + reader.Name +
                    "' was skipped during parsing.");
                reader.Read(); // skip non-element-nodes and unknown-elements.
            }
            result.featureClasses = featureClasses.ToArray();
            result.features = features.ToArray();
            return result;
        }

        private XmlSerializer newSerializer(Type elementType)
        {
            XmlSerializer serializer = new XmlSerializer(elementType);
            serializer.UnknownNode += new XmlNodeEventHandler(handleUnknownNode);
            serializer.UnknownAttribute += 
                new XmlAttributeEventHandler(handleUnknownAttribute);
            return serializer;
        }

        protected void handleUnknownNode(object sender, XmlNodeEventArgs e)
        {
            log.write(
                string.Format(
                    "XML_ERROR: Unknown Node at line {0} position {1} : {2}\t{3}",
                    e.LineNumber, e.LinePosition, e.Name, e.Text));
        }

        protected void handleUnknownAttribute(object sender, XmlAttributeEventArgs e)
        {
            log.write(
                string.Format(
                    "XML_ERROR: Unknown Attribute at line {0} position {1} : {2}='{3}'",
                    e.LineNumber, e.LinePosition, e.Attr.Name, e.Attr.Value));
        }
    }
}
16
corlettk

Juste pour résumer et rendre la réponse un peu plus évidente pour quiconque trouve ce fil dans google.

Avant .NET 2, le XmlTextReader était l'analyseur XML le plus efficace en mémoire disponible dans l'API standard (merci Mitch ;-)

.NET 2 a introduit la classe XmlReader qui est encore meilleure C'est un itérateur d'élément en avant seulement (un peu comme un analyseur StAX). (merci Cerebrus ;-)

Et souvenez-vous des enfants, de toute instance XML a le potentiel d'être plus grande qu'environ 500k, N'UTILISEZ PAS DOM!

Bravo à tous. Keith.

12
corlettk

Un SAX analyseur pourrait être ce que vous cherchez. SAX ne vous oblige pas à lire l'intégralité du document en mémoire - il le parcourt progressivement et vous permet de traiter les éléments au fur et à mesure. Je ne sais pas si un analyseur SAX est fourni dans .NET, mais il existe quelques options open source que vous pouvez consulter:

Voici un article connexe:

6
Andy White

Je voulais juste ajouter cette méthode d'extension simple comme exemple d'utilisation de XmlReader (comme Mitch l'a répondu):

public static bool SkipToElement (this XmlReader xmlReader, string elementName)
{
    if (!xmlReader.Read ())
        return false;

    while (!xmlReader.EOF)
    {
        if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == elementName)
            return true;

        xmlReader.Skip ();
    }

    return false;
}

Et l'utilisation:

using (var xml_reader = XmlReader.Create (this.source.Url))
{
    if (!SkipToElement (xml_reader, "Root"))
        throw new InvalidOperationException ("XML element \"Root\" was not found.");

    if (!SkipToElement (xml_reader, "Users"))
        throw new InvalidOperationException ("XML element \"Root/Users\" was not found.");

    ...
}
1
Michael Logutov