Pregunta Lectura de Xml con XmlReader en C #


Intento leer el siguiente documento Xml lo más rápido que puedo y permitir que otras clases administren la lectura de cada subbloque.

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

Sin embargo, estoy tratando de usar el objeto XmlReader para leer cada cuenta y, posteriormente, el "StatementsAvailable". ¿Sugiere usar XmlReader.Read y comprobar cada elemento y manejarlo?

He pensado en separar mis clases para manejar cada nodo correctamente. Entonces hay una clase AccountBase que acepta una instancia de XmlReader que lee el NameOfKin y varias otras propiedades sobre la cuenta. Luego quería intervenir a través de los enunciados y dejar que otra clase se completara con la Declaración (y luego la agregue a un IList).

Hasta ahora tengo la parte "por clase" hecha al hacer XmlReader.ReadElementString (), pero no puedo entrenar cómo decirle al puntero que se mueva al elemento StatementsAvailable y permitirme iterar a través de ellos y dejar que otra clase lea cada una de esas proeprties .

¡Suena fácil!


76
2018-03-14 09:06


origen


Respuestas:


Mi experiencia de XmlReader es que es muy fácil leer demasiado accidentalmente. Sé que has dicho que quieres leerlo lo más rápido posible, pero tienes que intentó usando un modelo DOM en su lugar? Descubrí que LINQ to XML hace que XML funcione mucho mucho más fácil.

Si su documento es particularmente grande, puede combinar XmlReader y LINQ a XML creando un XElement desde un XmlReader para cada uno de sus elementos "externos" de forma continua: esto le permite hacer la mayor parte del trabajo de conversión en LINQ a XML, pero solo necesita una pequeña porción del documento en la memoria en cualquier momento. Aquí hay un código de muestra (adaptado ligeramente de esta publicación en el 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;
          }
        }
      }
    }
  }
}

Lo he usado para convertir los datos de usuario de StackOverflow (que es enorme) en otro formato anterior, funciona muy bien.

EDIT desde radarbob, reformateado por Jon - aunque no está del todo claro a qué problema de "leer demasiado" se está haciendo referencia ...

Esto debería simplificar el anidamiento y resolver el problema de "leer demasiado".

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

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

    reader.ReadEndElement();
}

Esto soluciona el problema de "leer demasiado" porque implementa el patrón de ciclo while clásico:

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

138
2018-03-14 09:17



Tres años después, tal vez con el énfasis renovado en los datos de WebApi y xml, me encontré con esta pregunta. Desde el punto de vista del código, me inclino a seguir a Skeet desde un avión sin paracaídas, y ver su código inicial doblemente corroborado por el artículo del equipo MS Xml, así como un ejemplo en BOL Transmisión en tiempo real de grandes documentos Xml, Rápidamente pasé por alto los otros comentarios, más específicamente de 'pbz', quien señaló que si tiene los mismos elementos por nombre en sucesión, todos los demás se omiten debido a la doble lectura. Y, de hecho, los artículos de blog de BOL y MS estaban analizando documentos de origen con elementos de destino anidados más allá del segundo nivel, enmascarando este efecto secundario.

Las otras respuestas abordan este problema. Solo quería ofrecer una revisión un poco más simple que parece funcionar bien hasta el momento, y toma en cuenta que el xml puede provenir de diferentes fuentes, no solo de un uri, y entonces la extensión funciona en el XmlReader administrado por el usuario. La única suposición es que el lector se encuentra en su estado inicial, ya que de lo contrario la primera 'Lectura ()' podría avanzar más allá de un nodo deseado:

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

25
2017-10-03 17:38



Hacemos este tipo de análisis XML todo el tiempo. La clave es definir dónde el método de análisis dejará al lector al salir. Si siempre deja el lector en el siguiente elemento que sigue al elemento que se leyó por primera vez, puede leer de manera segura y predecible en la secuencia XML. Entonces, si el lector está indexando actualmente <Account> elemento, después de analizar el lector indexará el </Accounts> etiqueta de cierre

El código de análisis se ve así:

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

los Statements la clase solo lee en el <StatementsAvailable> nodo

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

los Statement clase se vería muy parecida

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

15
2018-03-14 09:41



Para subobjetos, ReadSubtree() le da un lector xml limitado a los sub-objetos, pero yo De Verdad piensa que estás haciendo esto de la manera difícil. A menos que tengas muy especifico requisitos para manejar xml inusual / impredecible, uso XmlSerializer (tal vez junto con sgen.exe si realmente quieres).

XmlReader es ... complicado. Contraste a:

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

5
2018-03-14 09:15



El siguiente ejemplo navega por la ruta para determinar el tipo de nodo actual y luego usa XmlWriter para generar el contenido de 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();

El siguiente ejemplo usa los métodos XmlReader para leer el contenido de los elementos y atributos.

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

1
2018-04-14 07:49



No soy experimentado. Pero creo que XmlReader es innecesario. Es muy difícil de usar.
XElement es muy fácil de usar.
Si necesita rendimiento (más rápido), debe cambiar el formato de archivo y usar las clases StreamReader y StreamWriter.


0
2017-12-28 09:08



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

Puede recorrer el xmlnodo y obtener los datos ...... Lector de C # XML


-1
2018-04-03 05:52