C# XmlReader 根据我调用阅读器方法的方式读取 XML 错误且不同
Posted
技术标签:
【中文标题】C# XmlReader 根据我调用阅读器方法的方式读取 XML 错误且不同【英文标题】:C# XmlReader reads XML wrong and different based on how I invoke the reader's methods 【发布时间】:2020-02-23 23:37:40 【问题描述】:所以我目前对 C# XmlReader
工作原理的理解是,当我将它包装在以下构造中时,它需要一个给定的 XML 文件并逐个节点地读取它:
using System.Xml;
using System;
using System.Diagnostics;
...
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
settings.IgnoreProcessingInstructions = true;
using (XmlReader reader = XmlReader.Create(path, settings))
while (reader.Read())
// All reader methods I call here will reference the current node
// until I move the pointer to some further node by calling methods like
// reader.Read(), reader.MoveToContent(), reader.MoveToElement() etc
为什么以下两个 sn-ps(在上述构造中)会产生两个截然不同的结果,即使它们都调用相同的方法?
I used this example file for testing.
Debug.WriteLine(new string(' ', reader.Depth * 2) + "<" + reader.NodeType.ToString() + "|" + reader.Name + ">" + reader.ReadString() + "</>");
(片段 1) 对比 (片段 2)
string xmlcontent = reader.ReadString();
string xmlname = reader.Name.ToString();
string xmltype = reader.NodeType.ToString();
int xmldepth = reader.Depth;
Debug.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");
片段 1 的输出:
<XmlDeclaration|xml></>
<Element|rss></>
<Element|head></>
<Text|>Test Xml File</>
<Element|description>This will test my xml reader</>
<EndElement|head></>
<Element|body></>
<Element|g:id>1QBX23</>
<Element|g:title>Example Title</>
<Element|g:description>Example Description</>
<EndElement|item></>
<Element|item></>
<Text|>2QXB32</>
<Element|g:title>Example Title</>
<Element|g:description>Example Description</>
<EndElement|item></>
<EndElement|body></>
<EndElement|xml></>
<EndElement|rss></>
是的,这是在我的输出窗口中格式化的。可以看出,它跳过了某些元素并为其他一些元素输出了错误的深度。因此,NodeTypes 是正确的,不像 Snippet Number 2,它输出:
<XmlDeclaration|xml></>
<Element|xml></>
<Element|title></>
<EndElement|title>Test Xml File</>
<EndElement|description>This will test my xml reader</>
<EndElement|head></>
<Element|item></>
<EndElement|g:id>1QBX23</>
<EndElement|g:title>Example Title</>
<EndElement|g:description>Example Description</>
<EndElement|item></>
<Element|g:id></>
<EndElement|g:id>2QXB32</>
<EndElement|g:title>Example Title</>
<EndElement|g:description>Example Description</>
<EndElement|item></>
<EndElement|body></>
<EndElement|xml></>
<EndElement|rss></>
再一次,深度搞砸了,但它不像片段编号 1 那样重要。它还跳过了一些元素并分配了错误的节点类型。
为什么不能输出预期的结果?为什么这两个 sn-ps 会产生两个完全不同的输出,具有不同的深度、NodeTypes 和跳过的节点? 我很感激这方面的任何帮助。我为此搜索了很多答案,但似乎我是唯一遇到这些问题的人。我在 Visual Studio 2017 中使用 .NET Framework 4.6.2 和 Asp.net Web 窗体。
【问题讨论】:
顺序很重要,因为 ReadString 将推进当前位置。但是,文档建议无论如何不要使用该方法。 @DaveM 我知道文档建议使用ReadContentAsString()
或ReadElementContentAsString()
,但目前我想遍历所有节点,这两种方法在遇到没有任何内容的节点。为什么 ReadString() 会推进位置?有什么我可以使用的替代品吗?
XmlReader 是在 .net 中解析 xml 的“硬方法”。但它可能更有效。它本质上是基于流的,因此您可以读取/解析非常大的 xml 文档,而无需将它们完全放在内存中。为了实现这一点,许多读取操作会推进位置(因为必须推进底层流,并且“返回”并不容易或不可能,具体取决于流的类型)。
如果你对性能要求不高,也不想解析巨大的 xml 文件,我建议改用 XmlDocument。
如果你事先知道xml格式,使用XmlSerializer(或等效的)就更容易了。
【参考方案1】:
首先,您使用的是XmlReader.ReadString()
已弃用的方法:
XmlReader.ReadString 方法
... 将元素或文本节点的内容作为字符串读取。但是,我们建议您改用
ReadElementContentAsString
方法,因为它提供了一种更直接的方式来处理此操作。
但是,除了警告我们不要使用该方法之外,文档并没有准确说明它的实际作用。要确定这一点,我们需要转到reference source:
public virtual string ReadString()
if (this.ReadState != ReadState.Interactive)
return string.Empty;
this.MoveToElement();
if (this.NodeType == XmlNodeType.Element)
if (this.IsEmptyElement)
return string.Empty;
else if (!this.Read())
throw new InvalidOperationException(Res.GetString(Res.Xml_InvalidOperation));
if (this.NodeType == XmlNodeType.EndElement)
return string.Empty;
string result = string.Empty;
while (IsTextualNode(this.NodeType))
result += this.Value;
if (!this.Read())
break;
return result;
此方法执行以下操作:
如果当前节点为空元素节点,则返回空字符串。
如果当前节点是一个非空元素,提前阅读器。
如果当前节点是元素的末尾,则返回一个空字符串。
虽然当前节点是文本节点,但将文本添加到字符串中并提前阅读。只要当前节点不是文本节点,就返回累积的字符串。
由此我们可以看出,这种方法是为了提高读者的阅读能力而设计的。我们还可以看到,给定混合内容 XML,如 <head>text <b>BOLD</b> more text</head>
,ReadString()
只会部分读取 <head>
元素,而将阅读器定位在 <b>
。这种奇怪之处可能是微软弃用该方法的原因。
我们还可以看到为什么您的两个 sn-ps 功能不同。首先,在调用ReadString()
并推进读者之前,您会收到reader.Depth
和reader.NodeType
。在推进阅读器之后,您将获得这些属性。
由于您的意图是遍历节点并获取每个节点的值,而不是 ReadString()
或 ReadElementContentAsString()
,您应该只使用 XmlReader.Value
:
获取当前节点的文本值。
因此,您更正后的代码应如下所示:
string xmlcontent = reader.Value;
string xmlname = reader.Name.ToString();
string xmltype = reader.NodeType.ToString();
int xmldepth = reader.Depth;
Console.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");
XmlReader
很难使用。您总是需要检查文档以确定给定方法将读者定位的确切位置。例如,XmlReader.ReadElementContentAsString()
将阅读器移动到元素的末尾过去,而XmlReader.ReadSubtree()
将阅读器移动到元素的末尾到。但作为一般规则,任何名为 Read
的方法都会使读者进步,因此您需要小心在外部 while (reader.Read())
循环内使用 Read
方法。
演示小提琴here.
【讨论】:
非常感谢!明天我会在工作中测试你的答案。这似乎是一个合乎逻辑的结论。 +1 附带说明,在循环中处理元素本身时,如何修改固定的 sn-p 以读取元素文本内容(如果它只有一个文本节点)?跨度> @SearchingSolutions - 你真的不能,因为XmlReader
是只进的。问题如前所述,该元素可能具有mixed content,例如<body>text <b>BOLD</b> more text</body>
。您可以做的是推迟发出元素节点并开始读取其子节点。如果只遇到文字,就累加;如果遇到其他问题,则发出延迟节点 + 累积文本。
如果您事先知道 XML 架构,您有更多选择。
我找到了一个很好的解决方案,遗憾的是我不能确定输入文件会有什么方案。无论如何,感谢您抽出宝贵的时间!以上是关于C# XmlReader 根据我调用阅读器方法的方式读取 XML 错误且不同的主要内容,如果未能解决你的问题,请参考以下文章