如 What is the best way to parse (big) XML in C# Code? 中所述,您可以使用XmlReader
来流传输具有有限内存消耗的巨大XML文件。但是,XmlReader
的使用有些棘手,因为如果XML并非如预期那样准确,那么读取太少或太多就很容易。 (即使微不足道的空白也可以引发XmlReader
算法。)
为减少发生此类错误的机会,首先引入以下扩展方法,该方法迭代当前元素的所有直接子元素:
public static partial class XmlReaderExtensions
{
/// <summary>
/// Read all immediate child elements of the current element,and yield return a reader for those matching the incoming name & namespace.
/// Leave the reader positioned after the end of the current element
/// </summary>
public static IEnumerable<XmlReader> ReadElements(this XmlReader inReader,string localName,string namespaceURI)
{
inReader.MoveToContent();
if (inReader.NodeType != XmlNodeType.Element)
throw new InvalidOperationException("The reader is not positioned on an element.");
var isEmpty = inReader.IsEmptyElement;
inReader.Read();
if (isEmpty)
yield break;
while (!inReader.EOF)
{
switch (inReader.NodeType)
{
case XmlNodeType.EndElement:
// Move the reader AFTER the end of the element
inReader.Read();
yield break;
case XmlNodeType.Element:
{
if (inReader.LocalName == localName && inReader.NamespaceURI == namespaceURI)
{
using (var subReader = inReader.ReadSubtree())
{
subReader.MoveToContent();
yield return subReader;
}
// ReadSubtree() leaves the reader positioned ON the end of the element,so read that also.
inReader.Read();
}
else
{
// Skip() leaves the reader positioned AFTER the end of the element.
inReader.Skip();
}
}
break;
default:
// Not an element: Text value,whitespace,comment. Read it and move on.
inReader.Read();
break;
}
}
}
/// <summary>
/// Read all immediate descendant elements of the current element,and yield return a reader for those matching the incoming name & namespace.
/// Leave the reader positioned after the end of the current element
/// </summary>
public static IEnumerable<XmlReader> ReadDescendants(this XmlReader inReader,string namespaceURI)
{
inReader.MoveToContent();
if (inReader.NodeType != XmlNodeType.Element)
throw new InvalidOperationException("The reader is not positioned on an element.");
using (var reader = inReader.ReadSubtree())
{
while (reader.ReadToFollowing(localName,namespaceURI))
{
using (var subReader = inReader.ReadSubtree())
{
subReader.MoveToContent();
yield return subReader;
}
}
}
// Move the reader AFTER the end of the element
inReader.Read();
}
}
有了它,您的python算法可以被复制如下:
var zipListBox = new List<string>();
using (var archive = ZipFile.Open(fullFileName,ZipArchiveMode.Read))
{
foreach (var entry in archive.Entries)
{
if (Path.GetExtension(entry.Name).Equals(".xml",StringComparison.OrdinalIgnoreCase))
{
using (var zipEntryStream = entry.Open())
using (var reader = XmlReader.Create(zipEntryStream))
{
// Move to the root element
reader.MoveToContent();
var query = reader
// Read all child elements <Widget>
.ReadElements("Widget","")
// And extract the text content of their first child element <Description>
.SelectMany(r => r.ReadElements("Description","").Select(i => i.ReadElementContentAsString()).Take(1));
zipListBox.AddRange(query);
}
}
}
}
注意:
-
您的c#XPath查询与您原来的python查询不匹配。您原始的python代码执行以下操作:
zfft = et.parse(zff).getroot()
这无条件获得了根元素(docs)。
zffts = zfft.findall('Widget')
这将找到所有名为“ Widget”的直接子元素(未使用递归下降运算符//
)(docs)。
wgt.find('Description').text for wgt in zffts
这会循环遍历各个小部件,并为每个小部件找到名为“ Description”的第一个子元素并获取其文本(docs)。
为了进行比较,xmlDoc.SelectNodes("//Root/Widget")
递归地降低了整个XML元素层次结构,以查找嵌套在名为<Widget>
的节点内的名为<Root>
的节点-可能不是您想要的。同样,tmp.SelectSingleNode("//Description")
递归地下降到<Widget>
下的XML层次结构中以找到描述节点。递归下降可能在这里起作用,但是如果存在多个嵌套的<Description>
节点,则可能返回不同的结果。
-
使用XmlReader.ReadSubtree()
可确保整个元素都被消耗掉-不多也不少。
-
ReadElements()
与LINQ to XML很好地配合。例如。如果您想流化XML并获取每个小部件的ID,描述和名称,而又不将它们全部加载到内存中,则可以执行以下操作:
var query = reader
.ReadElements("Widget","")
.Select(r => XElement.Load(r))
.Select(e => new { Description = e.Element("Description")?.Value,Id = e.Attribute("id")?.Value,Name = e.Element("Name")?.Value });
foreach (var widget in query)
{
Console.WriteLine("Id = {0},Name = {1},Description = {2}",widget.Id,widget.Name,widget.Description);
}
这里再次限制了内存的使用,因为在任何时候都只引用一个XElement
对应的一个<Widget>
。
演示小提琴here。
更新
如果<Widget>
标记的集合实际上不包含在XML根目录中,而是实际上包含在根目录的单个<Widgets>
子树中,那么您的代码将如何更改? / em>
您在这里有几个选择。首先,您可以通过将LINQ语句链接到一起来对ReadElements
进行嵌套调用,这些语句用SelectMany
使元素层次结构扁平化:
var query = reader
// Read all child elements <Widgets>
.ReadElements("Widgets","")
// Read all child elements <Widget>
.SelectMany(r => r.ReadElements("Widget",""))
// And extract the text content of their first child element <Description>
.SelectMany(r => r.ReadElements("Description","").Select(i => i.ReadElementContentAsString()).Take(1));
如果您仅对仅在某些特定XPath上读取<Widget>
节点感兴趣,请使用此选项。
或者,您可以简单地阅读所有名为<Widget>
的后代,如下所示:
var query = reader
// Read all descendant elements <Widget>
.ReadDescendants("Widget","")
// And extract the text content of their first child element <Description>
.SelectMany(r => r.ReadElements("Description","").Select(i => i.ReadElementContentAsString()).Take(1));
如果有兴趣读取XML中出现的<Widget>
个节点,请使用此选项。
演示小提琴#2 here。
本文链接:https://www.f2er.com/3167242.html