Читання Xml за допомогою XmlReader на C #


97

Я намагаюся прочитати наступний документ Xml якомога швидше і дозволити додатковим класам керувати читанням кожного підблоку.

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

Однак я намагаюся використовувати об'єкт XmlReader для читання кожного облікового запису, а потім "StatementsAvailable". Чи пропонуєте Ви використовувати XmlReader.Read та перевіряти кожен елемент та обробляти його?

Я думав розділити свої класи, щоб правильно обробляти кожен вузол. Отже, є клас AccountBase, який приймає екземпляр XmlReader, який читає NameOfKin та кілька інших властивостей облікового запису. Тоді я хотів поглянути на Statements і дозволити іншому класу заповнити Statement (і згодом додати його до IList).

На даний момент у мене є частина "за клас", виконана за допомогою XmlReader.ReadElementString (), але я не можу потренуватися, як сказати вказівнику перейти до елемента StatementsAvailable і дозволити мені перебирати їх, і дозволити іншому класу прочитати кожну з цих властивостей .

Звучить просто!


1
Клацніть на помаранчевий знак питання у верхньому правому куті поля редагування, щоб отримати допомогу щодо редагування. Ймовірно, ви хочете створити блок коду, який виконується спочатку порожнім рядком, а потім кожним рядком з відступами з чотирма пробілами.
Андерс Абель

або просто виберіть свої рядки коду / XML, а потім натисніть кнопку "код" (101 010) на панелі інструментів редактора - так просто!
marc_s

Відповіді:


163

Мій досвід XmlReaderполягає в тому, що дуже легко випадково прочитати занадто багато. Я знаю, що ви сказали, що хочете прочитати його якомога швидше, але чи намагалися ви замість цього використовувати модель DOM? Я виявив , що LINQ до XML робить XML роботу набагато набагато простіше.

Якщо документ, в зокрема , величезний, ви можете комбінувати XmlReaderі LINQ до XML шляхом створення XElementз XmlReaderкожного з ваших «зовнішніх» елементів в прямому порядку: це дозволяє робити більшу частину роботи перетворення в LINQ до XML, але все - таки потрібно тільки невелика частина документа в пам’яті в будь-який час. Ось зразок коду (злегка адаптований із цього допису в блозі ):

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

Раніше я використовував це для перетворення даних користувача StackOverflow (що є величезними) в інший формат - він працює дуже добре.

EDIT від radarbob, переформатований Джоном - хоча не зовсім зрозуміло, на яку проблему йдеться "занадто далеко" ...

Це має спростити вкладеність та подбати про проблему "занадто далеко читання".

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

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

    reader.ReadEndElement();
}

Це опікується проблемою "занадто далекого читання", оскільки реалізує класичний шаблон циклу while:

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

17
Виклик XNode.ReadFrom зчитує елемент і переходить до наступного, а потім до наступного зчитувача. Read () знову читає наступний. Ви б по суті пропустили елемент, якщо вони мають однакові назви та будуть послідовними.
pbz

3
@pbz: Дякую. Я не впевнений, що довіряю собі його правильне редагування (саме так мені не подобається XmlReader :) Чи можете ви його правильно редагувати?
Джон Скіт,

1
@JonSkeet - Можливо, я чогось пропускаю, але не буду просто змінюватись, if(reader.Name == elementName)щоб while(reader.Name == elementName)виправити проблему, на яку вказав pbz?
Девід Маклін

1
@pbz: я змінив рядок: XElement el = XNode.ReadFrom (рідер) як XElement; бути: XElement el = XElement.Load (reader.ReadSubtree ()); оскільки це виправляє пропуск помилок послідовних елементів.
Ділан Хогг,

1
Як згадувалося в інших коментарях, поточна версія програми SimpleStreamAxis()буде пропускати елементи, коли XML не буде відступати, оскільки розміщує Node.ReadFrom()зчитувач на наступному вузлі після завантаженого елемента - який буде пропущений наступним безумовним Read(). Якщо наступний вузол - пробіл, тоді все добре. Інакше ні. Версії без цього випуску дивіться тут , тут або тут .
dbc

29

Через три роки, можливо, з новим акцентом на даних WebApi та xml, я зіткнувся з цим питанням. Оскільки codewise, я схильний слідувати за Скітом з літака без парашута, і бачачи його початковий код подвійно підтвердженим статтею команди MS Xml, а також прикладом у BOL Streaming Transform of Large Xml Docs , я дуже швидко пропустив інші коментарі , конкретніше від "pbz", який зазначив, що якщо у вас є однакові елементи по імені по черзі, кожен другий пропускається через подвійне читання. Насправді, статті BOL та MS в блозі аналізували вихідні документи з цільовими елементами, вкладеними глибше, ніж другий рівень, маскуючи цей побічний ефект.

Інші відповіді вирішують цю проблему. Я просто хотів запропонувати дещо простішу версію, яка, здається, працює на сьогоднішній день, і враховує, що xml може надходити з різних джерел, а не лише з uri, і тому розширення працює на керованому користувачем XmlReader. Єдиним припущенням є те, що зчитувач перебуває у початковому стані, оскільки в протилежному випадку перший 'Read ()' може пройти повз бажаний вузол:

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

1
У вашому операторі "if (reader.Name.Equals (elementName))" відсутній відповідний "else reader.Read ();" заява. Якщо елемент не такий, як ви хочете, ви хочете продовжувати читати. Це те, що я повинен був додати, щоб це працювало на мене.
Вес,

1
@Wes Виправлено проблему, згорнувши два умови (NodeType та Name), щоб else Read()застосувати їх до обох. Дякуємо, що зловили це.
mdisibio

1
Я підтримав вас, але я не дуже радий бачити, як двічі писався виклик методу Read. Може, тут ви можете використати цикл do while? :)
nawfal

Ще одна відповідь, яка помітила та вирішила ту саму проблему з документами MSDN: stackoverflow.com/a/18282052/3744182
dbc

17

Ми постійно робимо подібний аналіз XML. Ключ визначає, де метод синтаксичного аналізу залишить зчитувач на виході. Якщо ви завжди залишаєте зчитувач на наступному елементі, що слідує за елементом, який був вперше прочитаний, тоді ви можете безпечно і передбачувано читати в потоці XML. Отже, якщо в даний час зчитувач індексує <Account>елемент, після аналізу зчитувач проіндексує </Accounts>закривальний тег.

Код розбору виглядає приблизно так:

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

StatementsКлас просто читає в <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;
            }
        }
    }
}

StatementКлас буде виглядати дуже те ж саме

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

6

Щодо суб-об’єктів, ReadSubtree()ви отримуєте xml-рідер, обмежений під-об’єктами, але я справді думаю, що ви робите це важким способом. Якщо у вас немає дуже конкретних вимог до роботи з незвичним / непередбачуваним xml, використовуйте XmlSerializer(можливо, у поєднанні з, sgen.exeякщо ви дійсно хочете).

XmlReaderце ... хитро. Контраст:

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

3

У наступному прикладі здійснюється навігація по потоку для визначення поточного типу вузла, а потім за допомогою XmlWriter виводиться вміст 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();

У наступному прикладі використовуються методи XmlReader для читання вмісту елементів та атрибутів.

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

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

Ви можете прокрутити xmlnode і отримати дані ...... C # XML Reader


4
Цей клас застарілий. Не використовувати.
nawfal

@Elvarism На веб-сайті, яким ви ділитесь, є багато інших способів читання xml, і це мені дуже допомагає. Я проголосую вас. Ось ще один легко зрозумілий приклад XmlReader .
劉鎮 瑲

0

У мене немає досвіду. Але я думаю, що XmlReader непотрібний. Це дуже важко використовувати.
XElement дуже простий у використанні.
Якщо вам потрібна продуктивність (швидша), ви повинні змінити формат файлу та використовувати класи StreamReader та StreamWriter.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.