问题描述
根据标题,从包含 CDATA
元素的 XML 文件获取数据到数组中时遇到问题。
基于我目前对如何做的有限了解,我想出了这个基本的工作方法
CDATA
很奇怪,所以我的常规方法不起作用。我找到节点的正常路线并没有停在它们上面,然后是整个 CDATA
问题。
XmlTextReader xmlReader = new XmlTextReader(FilePath);
while (xmlReader.Read())
{
// Position the reader on the OrderNumber node
xmlReader.ReadToFollowing("quoteNumber");
XmlReader inner = xmlReader.ReadSubtree();
while (inner.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.CDATA:
Globals.COData[0] = inner.Value;
break;
}
}
xmlReader.ReadToFollowing("orderNumber");
inner = xmlReader.ReadSubtree();
while (inner.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.CDATA:
Globals.COData[1] = inner.Value;
break;
}
}
但是我有很多数据元素要获取并假设有更好的方法。文件看起来像:
以及相关部分:
<quoteNumber>
<![CDATA[ John Test 123]]>
</quoteNumber>
<orderNumber>
<![CDATA[ 1352738]]>
</orderNumber>
包含的项目在文件末尾有一个结束元素。整个 XML 太大,无法发布。
XML 格式不在我的控制范围内。
我的最终目标是将 OrderNumber
及其值放入一个数组中。还有 Quote number
及其值。我已经习惯看到 <OrderNumber>123</OrderNumber>
,所以 CDATA
节点对我来说是新的。
解决方法
由于您没有共享完整的 XML,所以并不完全清楚您哪里出错了,但是您没有从 Read()
循环内部检查 XmlReader.ReadToFollowing(string)
的返回值。因此,一旦你读过了最后一个 <orderNumber>
,当没有找到另一个 <quoteNumber>
时,你会得到一个异常。
我建议按如下方式重构您的代码:
var ns = ""; // Replace with @"http://intelliquip.com/integrationS..." can't see the full namespace from the XML image.
var list = new List<Tuple<string,string>>(); // List of (quoteNumber,orderNumber) values.
var xmlReader = XmlReader.Create(FilePath);
while (xmlReader.ReadToFollowing("quoteNumber",ns))
{
string quoteNumber = null;
string orderNumber = null;
using (var inner = xmlReader.ReadSubtree())
{
// We need to skip the insignificant whitespace around the CDATA nodes which ReadElementContentAsString() will not do.
while (inner.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Text:
case XmlNodeType.CDATA:
quoteNumber += inner.Value;
break;
}
}
// After ReadSubtree() the reader is positioned on the </quoteNumber> element end.
}
// If the next orderNumber node is nmissing,ReadToFollowing() will read all the way past the next quoteNumber node.
// Use ReadToNextSibling() instead.
if (xmlReader.ReadToNextSibling("orderNumber",ns))
{
using (var inner = xmlReader.ReadSubtree())
{
while (inner.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Text:
case XmlNodeType.CDATA:
orderNumber += inner.Value;
break;
}
}
}
}
if (quoteNumber != null && orderNumber != null)
list.Add(Tuple.Create(quoteNumber,orderNumber));
else
{
// Add error handling here
}
}
注意事项:
-
CDATA
只是对 XML 字符数据节点进行编码的另一种方式,有关详细信息,请参阅 What does <![CDATA[]]> in XML mean?。XmlReader.Value
将包含 XML 字符数据节点的未转义值,无论它是编码为常规文本节点还是CDATA
节点。 -
从您的问题中不清楚 XML 文件中是否必须只有一个
<quoteNumber>
节点。因此,我将报价和订单号对读入List<Tuple<string,string>>
。阅读完成后,您可以检查阅读了多少,然后根据需要添加到Globals.COData
。 -
XmlReader.ReadToFollowing()
返回true
如果找到匹配的元素;否则false
和XmlReader
处于文件结束状态。因此需要检查它的返回值以确保您不会尝试读取文件末尾。
-
您的代码不会尝试处理缺少
<orderNumber>
的情况。如果是,则代码可能会跳过下一个<quoteNumber>
以读取其订单号。为了避免这种可能性,我使用XmlReader.ReadToNextSibling()
将搜索范围限制为属于同一父节点的<orderNumber>
节点。 -
通过使用
XmlReader.ReadToFollowing("orderNumber")
,您可以对代码进行硬编码,以假设orderNumber
节点没有命名空间前缀。与其这样做,不如明确指出它们所在的命名空间,它似乎类似于http://intelliquip.com/integrationS...
,其中未显示...
部分。我建议使用
XmlReader.ReadToFollowing("orderNumber",ns)
,其中ns
是订单和报价节点实际所在的命名空间。 -
XmlTextReader
自 .Net 2.0 起已弃用。改用XmlReader.Create()
。 -
XmlReader
API 使用起来相当繁琐。如果您的 XML 文件不大,您可以考虑将它们加载到XDocument
中并使用 LINQ to XML 进行查询。例如,您的
XmlReader
代码可以重写如下:var doc = XDocument.Load(FilePath); XNamespace ns = ""; // Replace with @"http://intelliquip.com/integrationS..." can't see the full namespace from the XML image. var query = from quote in doc.Descendants(ns + "quoteNumber") let order = quote.ElementsAfterSelf(ns + "orderNumber").FirstOrDefault() where order != null select Tuple.Create(quote.Value,order.Value); var list = query.ToList();
看起来简单多了。
-
您也可以考虑用适当的数据模型替换
Tuple<string,string>
,例如public class Order { public string QuoteNumber { get; set; } public string OrderNumber { get; set; } }