Чтение Xml с помощью XmlReader в C#

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

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

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

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

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

звуки легко!

7 ответов


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

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

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

это должно упростить вложенность и позаботиться о проблеме "чтение слишком далеко".

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

три года спустя, возможно, с новым акцентом на WebApi и xml-данных, я столкнулся с этим вопросом. Поскольку codewise я склонен следовать за скитом из самолета без парашюта, и видя его исходный код дважды corraborated статьей команды 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();
    }
}

мы делаем этот вид синтаксического анализа 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;
            }
        }
    }
}

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

для подобъектов,ReadSubtree() дает вам xml-reader, ограниченный подобъектами, но I действительно думаю, что вы делаете это трудным путем. Если только у вас нет очень специфический требования для обработки необычного / непредсказуемого 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 {}}}}
        });
    }
}

в следующем примере выполняется переход по потоку для определения текущего типа узла, а затем используется 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();

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


    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