在 C# 中使用 XmlReader 读取 Xml
Posted
技术标签:
【中文标题】在 C# 中使用 XmlReader 读取 Xml【英文标题】:Reading Xml with XmlReader in C# 【发布时间】:2011-01-27 08:29:00 【问题描述】:我正在尝试尽可能快地阅读以下 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 元素并让我遍历它们并让另一个类读取每个这些属性。
听起来很简单!
【问题讨论】:
点击编辑框右上角橙色问号获取编辑帮助。可能你想创建一个代码块,首先是一个空行,然后每行缩进四个空格。 或者只选择您的代码行/XML,然后单击编辑器工具栏中的“代码”按钮 (101 010) - 就这么简单! 【参考方案1】:我对@987654325@的体验是很容易不小心读太多。我知道您说过您想尽快阅读它,但是您尝试过 使用 DOM 模型吗?我发现 LINQ to XML 使 XML 的工作变得更加非常容易。
如果您的文档特别大,您可以将 XmlReader
和 LINQ to XML 组合在一起,方法是从 XmlReader
以流的方式为每个“外部”元素创建一个 XElement
:这可以让您完成大部分工作LINQ to XML 的转换工作,但在任何时候仍然只需要内存中的一小部分文档。下面是一些示例代码(改编自this blog post):
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;
我之前用它来将 *** 用户数据(非常庞大)转换为另一种格式 - 它工作得很好。
来自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;
【讨论】:
调用 XNode.ReadFrom 读取元素并转到下一个,然后下面的 reader.Read() 再次读取下一个。如果它们碰巧具有相同的名称并且是连续的,那么您基本上会错过一个元素。 @pbz:谢谢。我不确定我是否相信自己可以正确编辑它(这就是我不喜欢 XmlReader 的程度:)你能正确编辑它吗? @JonSkeet - 我可能遗漏了一些东西,但不会简单地将if(reader.Name == elementName)
更改为 while(reader.Name == elementName)
解决 pbz 指出的问题?
@pbz:我改了行:XElement el = XNode.ReadFrom(reader) as XElement;是:XElement el = XElement.Load(reader.ReadSubtree());因为这修复了跳过连续元素的错误。
正如其他cmets中提到的,当前版本的SimpleStreamAxis()
会在XML不缩进时跳过元素,因为Node.ReadFrom()
将阅读器定位在之后的下一个节点加载的元素 - 将被下一个无条件的 Read()
跳过。如果下一个节点是空白,那么一切都很好。否则,不会。对于没有此问题的版本,请参阅 here、here 或 here。【参考方案2】:
三年后,也许随着对 WebApi 和 xml 数据的重新重视,我遇到了这个问题。由于在代码方面我倾向于在没有降落伞的情况下跟随 Skeet 离开飞机,并且看到 MS Xml 团队文章以及 BOL Streaming Transform of Large Xml Docs 中的示例双重证实了他的初始代码,因此我很快忽略了其他 cmets,尤其是来自 '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();
【讨论】:
您的“if(reader.Name.Equals(elementName))”语句缺少相应的“else reader.Read();”陈述。如果元素不是您想要的,请继续阅读。这就是我必须添加的内容才能让它为我工作。 @Wes 通过折叠两个条件(NodeType 和 Name)修复了该问题,以便else Read()
适用于两者。感谢您了解这一点。
我对你投了赞成票,但我不太高兴看到 Read 方法调用被写了两次。也许你可以在这里使用 do while 循环? :)
另一个注意到并解决了与 MSDN 文档相同的问题的答案:***.com/a/18282052/3744182【参考方案3】:
我们一直在进行这种 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
【讨论】:
【参考方案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 getreturn accounts;
public class Account
public string NameOfKin get;set;
private readonly List<Statement> statements = new List<Statement>();
public List<Statement> StatementsAvailable getreturn 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】:以下示例在流中导航以确定当前节点类型,然后使用 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】: 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
【讨论】:
此类已弃用。不要使用。 @Elvarism 在您分享的网站中还有许多其他读取 xml 的方式,这对我帮助很大。我会投票给你。这是另一个易于理解的XmlReader 示例。【参考方案7】:我没有经验。但我认为 XmlReader 是不必要的。 很难用。 XElement 非常易于使用。 如果您需要性能(更快),您必须更改文件格式并使用 StreamReader 和 StreamWriter 类。
【讨论】:
以上是关于在 C# 中使用 XmlReader 读取 Xml的主要内容,如果未能解决你的问题,请参考以下文章
使用 xmlReader 在 C# 中过滤特定元素值的大型 XML