在C#中使用XmlReader读取Xml
我试图尽可能快地读取下面的XML文档,并让其他类pipe理每个子块的读取。
<ApplicationPool> <Accounts> <Account> <NameOfKin></NameOfKin> <StatementsAvailable> <Statement></Statement> </StatementsAvailable> </Account> </Accounts> </ApplicationPool>
但是,我试图使用XmlReader对象来读取每个帐户,随后“StatementsAvailable”。 你build议使用XmlReader.Read并检查每个元素,并处理它?
我想过分开我的类来正确处理每个节点。 因此,这是一个AccountBase类,它接受一个读取NameOfKin的XmlReader实例以及有关该帐户的其他几个属性。 然后,我想要通过陈述进行交stream,让另一个class级充分了解陈述(并随后将其添加到IList中)。
到目前为止,我已经通过XmlReader.ReadElementString()完成了“每类”部分,但我无法弄清楚如何告诉指针移动到StatementsAvailable元素,让我遍历它们,并让其他类读取每个这些proeprties 。
听起来很简单!
我对XmlReader
经验是,意外阅读太容易了。 我知道你已经说过你想尽快阅读,但你有尝试过使用DOM模型吗? 我发现LINQ to XML使得XML的工作更容易。
如果您的文档特别庞大,您可以将XmlReader
和LINQ合并为XML,方法是以stream方式为每个“外部”元素创buildXmlReader
的XElement
:这样可以完成大部分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; }
三年后,也许再次强调WebApi和XML数据,我遇到了这个问题。 由于代码方面,我倾向于在没有降落伞的情况下跟踪Skeet,并且看到他的初始代码被MS Xml团队文章加倍篡改,以及BOL Streaming Transform of Large Xml Docs中的一个例子,我很快就忽略了其他评论,尤其是来自“pbz”的人,他指出如果你连续有相同的名称,那么因为双重读取,所有其他的都会被跳过。 而实际上,BOL和MS博客文章都是对源文件进行parsing,目标元素嵌套深于第二层,掩盖了这种副作用。
其他答案解决了这个问题。 我只是想提供一个稍微简单一点的修订版本,目前看起来工作得很好,并考虑到xml可能来自不同的来源,而不仅仅是一个uri,所以扩展工作在用户pipe理的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(); } }
我们一直在做这种XMLparsing。 关键在于定义parsing方法将退出读者的位置。 如果您始终将阅读器放在第一个阅读元素之后的下一个元素上,则可以安全且可预测地读取XMLstream。 因此,如果读者正在对<Account>
元素进行索引,parsing后,读者将索引</Accounts>
结束标记。
parsing代码如下所示:
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>
节点
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 } } }
对于子对象, ReadSubtree()
为您提供了一个限于子对象的xml读取器,但是我真的认为您正在以这种方式做到这一点。 除非你对处理exception/不可预知的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 {}}}} }); } }
以下示例浏览stream以确定当前节点types,然后使用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