【问题标题】:XPath to select the nodes that matchesXPath 选择匹配的节点
【发布时间】:2013-07-18 11:54:37
【问题描述】:

我有一个如下所示的 XML:

<?xml version="1.0"?>
<RootName>
  <RandomNode v="someValue"/>
  <Series>
    <Idendity v="C16"/>
    <CodeOut v="C41073"/>
    <Period>
      <TimePeriod v="2013-07-18T22:00Z/2013-07-19T22:00Z"/>
      <Resolution v="PT60M"/>
      <Interval>
        <Pos v="1"/>
        <Qty v="14.1"/>
      </Interval>
      <Interval>
        <Pos v="2"/>
        <Qty v="20.7"/>
      </Interval>

我需要一个 xPath 来返回所有符合这些条件的 Period 节点:

  • 节点CodeOut/CodeIn 具有我在数组中的任何值的值
  • 此节点CodeOut 可以命名为CodeOutCodeIn,但只能是其中之一
  • TimePeriod 上的日期必须匹配

在 xml 中重复的唯一节点是 Series 节点。也就是说,每个Series只有一个Period,但有很多不同的Series

例如,获取所有Period 节点,其CodeoutCodeIn 值为C41073B85028,日期为2013-07-18

我尝试匹配多个名称,使用类似:

//*[@v="C41073"] | //*[@v="B85028"] | ...

但我认为如果只匹配正确的节点会更好,以防其他节点具有相同的值,不是吗?

我正在寻找使用“包含”之类的东西,但它以不同的方式工作。

我正在使用 .Net,如果这很重要,我将在 .SelectNodes() 函数上使用这个 xPath。


编辑:

发生了一些奇怪的事情。也许语法不正确。看看这个测试:

这个:doc.SelectNodes("/*")(0).Name 正在返回 RootName
这:doc.SelectNodes("/*/*").Count 正在返回 912
这:doc.SelectNodes("/*/*")(11).Name 正在返回 Series

但是这个:doc.SelectNodes("/RootName").Count 正在返回 0
这:doc.SelectNodes("/*/Series").Count 正在返回 0
而这个:doc.SelectNodes("/*/RootName").Count 正在返回 0

使答案中建议的所有其他 xPath 序列不起作用。

编辑:

好的,这是命名空间,我这样做了:

Dim xmlnsManager As Xml.XmlNamespaceManager = New System.Xml.XmlNamespaceManager(doc.NameTable)
xmlnsManager.AddNamespace("ns", "http://example")

并在 xPath 序列中的每个元素节点名称之前添加 ns:。 (有关它的更多信息,请参阅此:Is it possible to specify the namespace prefix just once in a xpath expression?

【问题讨论】:

  • TimePeriod 元素上的 v 属性的值似乎不是标准的 xml 数据类型值。我认为这是用斜线分隔的两个日期/时间值(开始时间和结束时间)?
  • 是的。我所做的是使用/ 进行拆分。我不知道 xPath 是否可行,我现在正在寻找它。
  • 你必须使用 XPath 吗?为什么不使用 Linq to XML?
  • 我使用的是 Net Framework 2.0

标签: c# .net xml vb.net xpath


【解决方案1】:

要选择仅受CodeIn/CodeOut 列表限制的所有Period 元素,您可以执行以下操作:

/RootName/Series[(CodeOut/@v = 'C41073') or (CodeOut/@v = 'B85028') or (CodeIn/@v = 'C41073') or (CodeIn/@v = 'B85028')]/Period

如果您不想将列表中的每个项目作为单独的条件列出,您可以将它们全部连接到一个分隔列表中,然后使用contains 函数,如下所示:

/RootName/Series[(CodeOut/@v and contains('|C41073|B85028|', concat('|', CodeOut/@v, '|'))) or (CodeIn/@v and contains('|C41073|B85028|', concat('|', CodeIn/@v, '|')))]/Period

注意,为了避免像C4 这样的子字符串匹配完整值的问题,比如C41073,您需要在属性值之前和之后连接分隔符。此外,您需要确保分隔符存在于分隔值列表的开头和结尾。此外,您选择的任何分隔符都必须是一个无效字符,该字符永远不会出现在列表中的任何值中。

但是,也通过TimePeriod 限制它会有点问题,因为它似乎是一个非标准的时间范围值。如果将开始时间和结束时间存储在不同的节点中,那就更容易了。

如果您需要做的只是匹配一个精确的 TimePeriod 值,例如,您可以这样做:

/RootName/Series[(CodeOut/@v = 'C41073') or (CodeOut/@v = 'B85028') or (CodeIn/@v = 'C41073') or (CodeIn/@v = 'B85028')]/Period[TimePeriod/@v = '2013-07-18T22:00Z/2013-07-19T22:00Z']

您可以使用substring-before(TimePeriod, '/')substring-after(TimePeriod, '/') 分割/ 字符上的字符串,但除非您使用XPath 2.0,否则您无法比较字符串以查看它们是大于还是小于。如果您使用的是 2.0,则可以使用 compare 函数将这些子字符串中的每一个与搜索值进行比较,但这仍然很混乱。最好在 .NET 代码中处理时间范围比较。

【讨论】:

  • 那么,实际上,最好的选择是将每个代码放置两次? (对于CodeOutCodeIn)?而且我想没有像contains 这样的东西来检查很多值,而不是用OR 检查每个值。
  • 我更新了我的答案以演示如何使用 contains 函数。不过,老实说,它有点 hacky,而且没有更优雅,所以,就我个人而言,我还是坚持第一个例子。
  • 由于您正在搜索两个不同的元素名称,我想不出避免两次列出这些项目的方法。也许比我有更多 XPath 专业知识的人会想出一个好方法来做到这一点。如果是这样,我很想看到它:)
  • 您知道是否有任何方法可以将命名空间设为默认命名空间,而不是将ns(参见示例)放在每个名称上?不,成立:stackoverflow.com/questions/7241931/…
  • 是的,我实际上也已经多次回答过这个命名空间问题,比如这里:stackoverflow.com/a/11348458/1359668
猜你喜欢
  • 2015-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多