Чтение Xml с помощью XmlReader в С#
Я пытаюсь прочитать следующий Xml-документ так быстро, как могу, и позволить дополнительным классам управлять чтением каждого дополнительного блока.
<ApplicationPool>
<Accounts>
<Account>
<NameOfKin></NameOfKin>
<StatementsAvailable>
<Statement></Statement>
</StatementsAvailable>
</Account>
</Accounts>
</ApplicationPool>
Тем не менее, я пытаюсь использовать объект XmlReader для чтения каждой учетной записи, а затем "StatementsAvailable". Вы предлагаете использовать XmlReader.Read и проверять каждый элемент и обрабатывать его?
Я думал о том, чтобы разделять мои классы, чтобы обрабатывать каждый node правильно. Таким образом, класс AccountBase принимает экземпляр XmlReader, который читает NameOfKin и несколько других свойств об учетной записи. Затем я хотел поступить через заявления и позволить другому классу заполнить выражение (и впоследствии добавить его в IList).
До сих пор у меня есть часть "для каждого класса", выполняемая с помощью XmlReader.ReadElementString(), но я не могу тренироваться, как указать указателю перейти к элементу StatementsAvailable и позволить мне проходить через них и позволить другому классу читать каждый из тех proeprties.
Звучит просто!
Ответы
Ответ 1
Мой опыт 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();
}
Это касается проблемы с чтением слишком далеко, поскольку она реализует классический шаблон цикла:
initial read;
(while "we're not at the end") {
do stuff;
read;
}
Ответ 2
Спустя три года, возможно, с новым акцентом на данные WebApi и xml, я столкнулся с этим вопросом. Так как кодекс я склонен последовать за Skeet из самолета без парашюта и видеть его начальный код, который был вдвойне похож на статью команды MS Xml, а также на пример в BOL Streaming Transform of Large Xml Docs, я очень быстро упустил другие комментарии, в частности, от "pbz", который указал, что если у вас есть одинаковые элементы по имени подряд, каждый другой пропущен из-за двойного чтения. Фактически, статьи блога BOL и MS анализировали исходные документы с целевыми элементами, вложенными глубже второго уровня, маскируя этот побочный эффект.
Другие ответы касаются этой проблемы. Я просто хотел предложить немного более простой пересмотр, который, кажется, хорошо работает до сих пор, и учитывает, что xml может происходить из разных источников, а не только с помощью uri, и поэтому расширение работает с управляемым пользователем XmlReader. Одно из предположений состоит в том, что читатель находится в исходном состоянии, так как в противном случае первая "Read()" может продвигаться мимо желаемого node:
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 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();
}
}
Ответ 3
Мы все время разбираем XML-парсинг. Ключ определяет, где метод синтаксического анализа оставит читателя при выходе. Если вы всегда оставляете читателя на следующем элементе после элемента, который был сначала прочитан, тогда вы можете безопасно и предсказуемо читать в потоке XML. Поэтому, если читатель в настоящее время индексирует элемент <Account>
, после разбора читатель проиндексирует тег закрытия </Accounts>
.
Код анализа выглядит примерно так:
public class Account
{
string _accountId;
string _nameOfKin;
Statements _statmentsAvailable;
public void ReadFromXml( XmlReader reader )
{
reader.MovToContent();
// 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>
node
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.MovToContent();
// Read noe attributes
_statementId = reader.GetAttribute( "statementId" );
...
if( reader.IsEmptyElement ) { reader.Read(); return; }
reader.Read();
while( ! reader.EOF )
{
....same basic loop
}
}
}
Ответ 4
Для под-объектов 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 {}}}}
});
}
}
Ответ 5
Следующий пример перемещается по потоку для определения текущего типа node, а затем использует 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();
Ответ 6
Я не испытываю опыта. Но я думаю, что XmlReader не нужен.
Это очень сложно использовать.
XElement очень прост в использовании.
Если вам нужна производительность (быстрее), вы должны изменить формат файла и использовать классы StreamReader и StreamWriter.
Ответ 7
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 и получить данные... С# XML Reader