Odczyt Xml z XmlReader w C#

Staram się jak najszybciej odczytać następujący dokument Xml i pozwolić dodatkowym klasom zarządzać odczytem każdego bloku podrzędnego.

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

Próbuję jednak użyć obiektu XmlReader do odczytu każdego konta, a następnie "StatementsAvailable". Czy sugerujesz używanie XmlReader.Odczytać i sprawdzić każdy element i obsłużyć go?

Myślałem o rozdzieleniu klas, aby prawidłowo obsługiwać każdy węzeł. Więc istnieje Klasa AccountBase, która akceptuje instancję XmlReader, która odczytuje Nazwa konta i kilka innych właściwości o koncie. Potem chciałem przejrzeć oświadczenia i pozwolić innej klasie wypełnić się o oświadczeniu (a następnie dodać go do IList).

Do tej pory mam część "na klasę" wykonaną przez XmlReader.ReadElementString() ale nie mogę ćwiczyć, jak powiedzieć kursorowi, aby przeniósł się do elementu StatementsAvailable i pozwolić mi iterować przez nie i pozwolić innej klasie przeczytać każdy z tych proeprties.

Dźwięki spokojnie!

Author: mezoid, 2010-03-14

7 answers

Moje doświadczenie XmlReader jest takie, że bardzo łatwo jest przypadkowo przeczytać za dużo. Wiem, że powiedziałeś, że chcesz go przeczytać tak szybko, jak to możliwe, ale czy próbowałeś zamiast tego użyć modelu DOM? Odkryłem, że LINQ to XML sprawia, że XML działa znacznie znacznie łatwiej.

Jeśli twój dokument jest szczególnie ogromny, możesz połączyć XmlReader i LINQ z XML, tworząc XElement z XmlReader dla każdego z twoich "zewnętrznych" elementów w sposób strumieniowy: pozwala to wykonać większość konwersji działa w LINQ do XML, ale wciąż potrzebuje tylko niewielkiej części dokumentu w pamięci w tym samym czasie. Oto przykładowy kod (zaadaptowany nieco z tego posta na blogu):

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

Używałem tego do konwersji danych użytkownika StackOverflow (które są ogromne) do innego formatu - działa to bardzo dobrze.

EDIT from radarbob, sformatowany przez Jona-chociaż nie jest do końca jasne, o którym problemie "Czytaj za daleko" jest mowa...

To powinno uprościć zagnieżdżanie i zajmij się problemem "a read too far".

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

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

    reader.ReadEndElement();
}

To rozwiązuje problem "zbyt dalekiego odczytu", ponieważ implementuje Klasyczny wzór pętli while:

initial read;
(while "we're not at the end") {
    do stuff;
    read;
}
 141
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-23 17:35:18

Trzy lata później, być może z ponownym naciskiem na WebApi i dane xml, natknąłem się na to pytanie. Ponieważ codewise jestem skłonny podążać za Skeetem z samolotu bez spadochronu, i widząc jego początkowy kod podwójnie corraborowany przez artykuł MS XML team, a także przykład w Bol Streaming Transform dużych dokumentów Xml , bardzo szybko przeoczyłem inne komentarze, szczególnie z "pbz", który zwrócił uwagę, że jeśli masz te same elementy po nazwie w sukcesja, co drugi jest pomijany z powodu podwójnego odczytu. W rzeczywistości artykuły BOL I MS blog analizowały dokumenty źródłowe z elementami docelowymi zagnieżdżonymi głębiej niż drugi poziom, maskując ten efekt uboczny.

Inne odpowiedzi rozwiązują ten problem. Chciałem tylko zaoferować nieco prostszą wersję, która wydaje się działać dobrze do tej pory i bierze pod uwagę, że xml może pochodzić z różnych źródeł, a nie tylko uri, a więc rozszerzenie działa na zarządzanym użytkowniku XmlReader. Jednym z założeń jest to, że czytnik jest w stanie początkowym, ponieważ w przeciwnym razie pierwsze 'Read ()' może przejść obok pożądanego węzła:

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();
    }
}
 26
Author: mdisibio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-21 20:45:51

Cały czas wykonujemy tego rodzaju parsowanie XML. Kluczem jest określenie, gdzie metoda parsowania opuści czytnik po zakończeniu. Jeśli zawsze zostawiasz czytnik na następnym elemencie po elemencie, który został najpierw odczytany, możesz bezpiecznie i przewidywalnie czytać w strumieniu XML. Jeśli więc reader indeksuje element <Account>, po przetworzeniu reader indeksuje znacznik zamykający </Accounts>.

Kod parsowania wygląda mniej więcej tak:

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

Klasa Statements właśnie czyta w węźle <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;
            }
        }
    }
}

Klasa Statement wyglądałaby bardzo podobnie

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
Author: Paul Alexander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-10 18:22:29

Dla pod-obiektów, ReadSubtree() daje Ci czytnik XML ograniczony do Pod-obiektów, ale ja naprawdę myślę, że robisz to w trudny sposób. Jeśli nie masz bardzo specyficznych wymagań dotyczących obsługi nietypowego / nieprzewidywalnego xml, użyj XmlSerializer (Być może w połączeniu z sgen.exe, jeśli naprawdę chcesz).

XmlReader jest... trudne. Kontrast do:

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
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-14 09:15:08

Poniższy przykład porusza się po strumieniu w celu określenia bieżącego typu węzła, a następnie używa XmlWriter do wyświetlania zawartości 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();

Poniższy przykład wykorzystuje metody XmlReader do odczytu zawartości elementów i atrybutów.

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
Author: Muhammad Awais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-14 07:49:12

Nie mam doświadczenia .Ale myślę, że XmlReader jest niepotrzebny. Jest bardzo trudny w użyciu.
XElement jest bardzo łatwy w użyciu.
Jeśli potrzebujesz wydajności (szybszej), musisz zmienić format pliku i użyć klas StreamReader i StreamWriter.

 0
Author: Mehmet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-28 09:08:55
    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");

Możesz zapętlić xmlnode i pobrać dane...... C# XML Reader

 -1
Author: Elvarism,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-29 09:38:44