Чтение Xml с XmlReader в C # - PullRequest
87 голосов
/ 14 марта 2010

Я пытаюсь читать следующий XML-документ так быстро, как могу, и позволяю дополнительным классам управлять чтением каждого субблока.

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

Однако я пытаюсь использовать объект XmlReader для чтения каждой учетной записи, а затем «StatementsAvailable». Вы предлагаете использовать XmlReader.Read, проверять каждый элемент и обрабатывать его?

Я думал о разделении моих классов для правильной обработки каждого узла. Таким образом, существует класс AccountBase, который принимает экземпляр XmlReader, который читает NameOfKin и ряд других свойств учетной записи. Затем я хотел изучить предложения и позволить другому классу заполнить себя в заявлении (и впоследствии добавить его в IList).

До сих пор я выполнял часть «на класс», выполняя XmlReader.ReadElementString (), но я не могу понять, как сказать указателю перейти к элементу StatementsAvailable, и я позволю мне пройти через них и позволить другому классу читать каждый из этих свойств.

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

Ответы [ 7 ]

152 голосов
/ 14 марта 2010

По моему опыту XmlReader очень легко случайно прочитать слишком много. Я знаю, что вы сказали, что хотите прочитать это как можно быстрее, но пытались ли вы использовать модель DOM? Я обнаружил, что LINQ to XML упрощает работу XML на намного .

Если ваш документ особенно большой, вы можете объединить XmlReader и LINQ to XML, создав XElement из XmlReader для каждого из ваших «внешних» элементов в потоковом режиме: это позволяет вам выполнять преобразование работает в LINQ to 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 (которые огромны) в другой формат раньше - это работает очень хорошо.

РЕДАКТИРОВАТЬ из радарбоба, переформатированный Джоном - хотя не совсем понятно, к какой проблеме «читать слишком далеко» относится ...

Это должно упростить вложение и решить проблему «слишком большое чтение».

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;
}
26 голосов
/ 03 октября 2013

Три года спустя, возможно, с новым акцентом на данные WebApi и xml, я столкнулся с этим вопросом. Поскольку в коде я склонен следовать за Скитом из самолета без парашюта, и вижу его первоначальный код, дважды подтвержденный статьей команды MS Xml, а также примером в BOL Потоковое преобразование больших документов Xml , я очень быстро пропустил другие комментарии, в частности, из '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();
    }
}
17 голосов
/ 14 марта 2010

Мы выполняем такой анализ 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 голосов
/ 14 марта 2010

Для подобъектов 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 {}}}}
        });
    }
}
2 голосов
/ 14 апреля 2016

Следующий пример перемещается по потоку, чтобы определить текущий тип узла, а затем использует 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 голосов
/ 28 декабря 2016

У меня нет опыта. Но я думаю, что XmlReader не нужен. Это очень трудно использовать.
XElement очень прост в использовании.
Если вам нужна производительность (быстрее), вы должны изменить формат файла и использовать классы StreamReader и StreamWriter.

0 голосов
/ 03 апреля 2014
    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

...