草庐IT

xml - 用于在具有不同父元素的元素之间选择元素的 XPath

coder 2024-07-04 原文

给定一个这样的 XML 文档:

<r>
  <a/><b/><c/>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>

并且给定条件 “从 b 开始,在 d2b 停止” 是否有一个 XPath 表达式可以选择:

理想情况下:

<c/><d><d1/><d2><d2a/></d2></d>

合理:

<c/>

我知道根据“从‘a’开始到‘e’结束”的条件,我可以使用表达式 //*[preceding-sibling::a][following-sibling::e];我想知道在不能保证开始元素和结束元素共享同一父元素时,是否有办法对祖先轴和前面的兄弟进行一些奇怪的交集以找到共同的祖先。

最佳答案

XPath(1.0 和 2.0)是一种用于 XML 文档的查询语言。因此它不能改变任何 XML 文档的节点和结构

可以通过 XSLT 转换获得想要的结果(I. 下面使用 XSLT 1.0):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*">
  <xsl:choose>
      <xsl:when test=
      "count(.|$vFollowingStart) = count($vFollowingStart)
      and
       count(.|$vPrecedingEnd) = count($vPrecedingEnd)
      ">
       <xsl:call-template name="identity"/>
      </xsl:when>
      <xsl:otherwise>
       <xsl:apply-templates/>
      </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档时:

<r>
  <a/><b/><c/>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>

产生了想要的、正确的结果:

<c/>
<d>
   <d1/>
   <d2>
      <d2a/>
   </d2>
</d>

解释:

  1. 身份规则“按原样”复制每个匹配的节点。

  2. 有一个覆盖任何元素的覆盖模板。

  3. 在这个模板中做了两个测试:当前节点是否属于所有元素的集合“following the start”当前节点是否属于所有元素的集合“在结束之前”。如果是,则将当前节点传递给标识模板(复制),否则忽略(删除)。


二。 XSLT 2.0 解决方案

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

 <xsl:variable name="vWanted" select=
  "$vFollowingStart intersect $vPrecedingEnd"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*[not(. intersect $vWanted)]">
  <xsl:apply-templates/>
 </xsl:template>
</xsl:stylesheet>

当对上面的 XML 文件应用此转换时,再次产生相同的正确结果。

说明:使用XPath 2.0操作符intersect


III. XPath 1.0 解决方案,只选择节点而不改变文档:

为了可读性,我提供了一个 XSLT 转换,它输出选择所需节点的结果。同样的目的,子表达式被定义为变量:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

 <xsl:template match="node()|@*">
  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

  <xsl:copy-of select=
   "$vFollowingStart
      [count(.|$vPrecedingEnd)
      =
       count($vPrecedingEnd)
      ]
   "/>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于所提供的 XML 文档(如上)时,将输出所需的选定节点:

<c/>
<d>

   <d1/>

   <d2>

      <d2a/>

      <d2b/>

      <d2c/>

   </d2>

</d>
<d1/>
<d2>

   <d2a/>

   <d2b/>

   <d2c/>

</d2>
<d2a/>

说明:这里我对两个节点集 $ns1$ns2 的交集使用 Kayessian(@Michael Kay 的)公式:

$ns1[count(.|$ns2) = count($ns2)]

IV.最后是Xpath 2.0方案(对应XPath 1.0方案):

我再次使用 XSLT (2.0) 转换将结果复制到输出:

<xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <xsl:param name="pStart" select="/*/b"/>
     <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

     <xsl:template match="node()|@*">
      <xsl:variable name="vFollowingStart" select=
      "$pStart/following::* | $pStart/descendant::*"/>

     <xsl:variable name="vPrecedingEnd" select=
      "$pEnd/preceding::* | $pEnd/ancestor::*"/>

      <xsl:sequence select=
       "$vFollowingStart intersect $vPrecedingEnd"/>
     </xsl:template>
</xsl:stylesheet>

产生了与 XPath 1.0 解决方案相同的结果(正是想要的节点):

<c/>
<d>
        <d1/>
        <d2>
               <d2a/>
               <d2b/>
               <d2c/>
        </d2>
    </d>
<d1/>
<d2>
            <d2a/>
            <d2b/>
            <d2c/>
</d2>
<d2a/>

更新:这是针对“合理”问题的 XPath 1.0 解决方案。它再次表示为 XSLT 样式表模块,其中,为了更好的可读性,子表达式被定义为单独的变量:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pStart" select="/*/*/b"/>
 <xsl:param name="pEnd" select="/*/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

  <xsl:variable name="vcommonAncestor" select=
  "$pStart/ancestor::*
    [count(.|$pEnd/ancestor::*)
    =
     count($pEnd/ancestor::*)
    ][1]
    "/>
 <xsl:variable name="vEndHighestAncestor" select=
  "$vcommonAncestor/*
       [count($pEnd | descendant::*)
       =
        count(descendant::*)
       ]"/>

  <xsl:variable name="vPrecedingEnd" select=
  "$vEndHighestAncestor/preceding::*
  |
   $vEndHighestAncestor/ancestor::*"/>


 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/">
  <xsl:copy-of select=
  "//*[count(.|$vFollowingStart) = count($vFollowingStart)
      and
       count(.|$vPrecedingEnd) = count($vPrecedingEnd)
      ]
  "/>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时(与提供的相同,但包装到另一个顶级元素中,并且两个子元素(gh)添加到 c - 使更有趣:

<t>
<r>
  <a/><b/><c><g/><h/></c>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>
</t>

选择所需的正确节点集并将其复制到输出:

<c>
   <g/>
   <h/>
</c>
<g/>
<h/>

解释:这与之前几乎相同,但我们将其最高祖先视为 $pEnd -- 这是 $pStart$pEnd 的共同祖先的直接子代。

关于xml - 用于在具有不同父元素的元素之间选择元素的 XPath,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7816219/

有关xml - 用于在具有不同父元素的元素之间选择元素的 XPath的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  4. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  5. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  6. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  7. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  8. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

    我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

  9. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  10. ruby-on-rails - Rails 3.1 中具有相同形式的多个模型? - 2

    我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#

随机推荐