2009-03-24 18 views
48

Ich schreibe ein GIS-Client-Tool in C#, um "Features" in einem GML-basierten XML-Schema (Beispiel unten) von einem Server abzurufen. Extrakte sind auf 100.000 Funktionen beschränkt.Was ist der beste Weg, um (Big) XML in C# -Code zu analysieren?

ich guestimate, dass der größte extract.xml könnte bis etwa 150 Megabyte bekommen, so offensichtlich DOM-Parser sind aus Ich habe versucht, zwischen XmlSerializer und XSD.EXE erzeugt Bindungen --ODER-- XmlReader und eine Hand zu entscheiden, gestalteter Objektgraph.

Oder vielleicht gibt es einen besseren Weg, den ich noch nicht berücksichtigt habe? Wie XLINQ oder ????

Bitte kann mich jemand leiten? Vor allem im Hinblick auf die Speichereffizienz eines gegebenen Ansatzes. Wenn nicht, muss ich beide Lösungen "prototypisieren" und sie nebeneinander darstellen.

Ich bin ein bisschen eine rohe Garnele in .NET. Jede Anleitung würde sehr geschätzt werden.

Vielen Dank. Keith.


XML-Beispiel - bis zu 100.000 von ihnen, von bis zu 234.600 coords pro Funktion.

<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> 
+0

eine etwas späte Antwort: SAX/Stax Streaming Parsing ist in der Nützlichkeit eingeschränkt, versuchen Sie vtd-xml, die 1/5 von DOM –

Antwort

53

Verwenden XmlReader große XML-Dokumente zu analysieren. XmlReader bietet schnellen, nur Vorwärts- und nicht im Cache gespeicherten Zugriff auf XML-Daten. (Nur vorwärts bedeutet, dass Sie die XML-Datei von Anfang bis Ende lesen können, aber nicht rückwärts in der Datei verschieben können.) XmlReader verwendet kleine Speichermengen und entspricht der Verwendung eines einfachen SAX-Lesegeräts.

Sie können XmlReader verwenden, um Dateien mit einer Größe von bis zu 2 Gigabyte (GB) zu verarbeiten.

Ref: How to read XML from a file by using Visual C#

+4

IIRC verwendet, .NET 2.0 ab, MS empfiehlt die Verwendung der XmlReader-Klasse direkt anstelle des XmlTextReader. – Cerebrus

+0

@Cerebrus und Mitch: Danke, meine Herren. Das ist ziemlich genau das, was ich dachte, aber es ist wirklich sehr nett, eine zweite (informierte) Meinung zu bekommen, bevor man Tage verschwendet, um den falschen Weg einzuschlagen. Sehr geschätzt! – corlettk

+2

In CF den speziellen ctor auch benutzen. XmlReader.Create, da es ein wenig optimiert ist und denken Sie daran, .Skip() zu verwenden, um Elemente zu überspringen, die uninteressant sind! – Quibblesome

6

Ein SAX Parser könnte das sein, was Sie suchen. SAX erfordert nicht, dass Sie das gesamte Dokument in den Speicher einlesen - es analysiert es inkrementell und ermöglicht es Ihnen, die Elemente zu bearbeiten, während Sie gehen. Ich weiß nicht, ob es ein SAX-Parser in .NET zur Verfügung gestellt, aber es gibt ein paar Open-Source-Optionen, die Sie betrachten könnten:

hier ein Zusammenhang mit Post :

+1

Es wäre interessant, die Leistung von Sax v XmlTextReader zu vergleichen - hat jemand versucht dies – MrTelly

+0

Ich würde auch interessiert sein, habe ich nicht verglichen –

+0

.NET bietet keinen nativen Sax-Parser, aber ich lese ein Article (in slashdot, denke ich) was gezeigt hat, wie einfach es war, einen eigenen SAX-Parser mit den "primitiven" XmlReader zu erstellen. – corlettk

12

Nur um zusammenzufassen, und machen Sie die Antwort ein bisschen offensichtlicher für jeden, der diesen Thread in Google findet.

Vor .NET 2 die XmlTextReader war der Speicher effizient XML-Parser in dem Standard-API zur Verfügung (thanx Mitch ;-)

.NET 2 die XmlReader Klasse eingeführt, die besser ist, wieder ist es ein Vorwärts-Element Iterator (ein bisschen wie ein StAX-Parser).(danke Cerebrus ;-)

Und erinnern Sie sich Kiddies, von jeder XML-Instanz hat das Potenzial, größer als etwa 500k zu sein, verwenden Sie nicht DOM!

Prost alle. Keith.

15

Asat 14 May 2009: Ich habe auf einen Hybrid-Ansatz umgestellt ... siehe Code unten.

Diese Version die meisten der Vorteile von beiden hat:
    * die XmlReader/XmlTextReader (Speichereffizienz -> Geschwindigkeit); und
    * der XmlSerializer (Code-Gen -> Entwicklungsgeschwindigkeit und Flexibilität).

Es verwendet den XmlTextReader, um durch das Dokument zu iterieren, und erstellt "doclets", die es deserialisiert, indem die Klassen XmlSerializer und "XML binding" verwendet werden, die mit XSD.EXE generiert werden.

Ich denke, dieses Rezept ist universell anwendbar, und es ist schnell ... Ich analysiere ein XML-Dokument 201 MB mit 56.000 GML-Funktionen in etwa 7 Sekunden ... die alte VB6-Implementierung dieser Anwendung dauerte Minuten (oder sogar Stunden), um große Extrakte zu analysieren ... also sehe ich gut aus.

Noch einmal, ein BIG Vielen Dank an die forumites für die Spende Ihrer wertvollen Zeit. Ich schätze es sehr.

Prost alle. 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)); 
     } 
    } 
} 
1

Ich wollte nur diese einfache Erweiterung Methode als Beispiel hinzuzufügen XmlReader der Verwendung (als Mitch beantwortet):

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

Und Nutzung:

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

    ... 
} 
+0

Nice ... Eine vorgeschlagene Verbesserung: die Abwesenheit, wenn das gesuchte Element immer terminal zu der aktuellen Operation ist (es hat unseren Leser zu EOF übersprungen haben) also einfach Exception direkt in SkipTo werfen, statt falsch zurück zu geben ...Sie haben den gesuchten Elementnamen zu melden, also verwenden Sie ihn, anstatt sich in Fehlermeldungen zu wiederholen. – corlettk

+0

Ja, hast du recht. Nur in meinem speziellen Fall musste ich den vollständigen Pfad zu dem verpassten Element angeben und nicht nur den Namen. –