XPath - 搜索a)匹配过滤器的后代元素,b)没有特定的祖先

时间:2009-12-18 01:39:05

标签: html xml xpath

我正在尝试将XPath表达式放在一起,它将为我提供与过滤器匹配的节点的所有后代元素(例如[contains(@class,“interesting”)]但是没有特定的祖先例如[contains(@class,“frame”)]。可能最好用例子解释:

    <div class="frame">
        <p class="interesting">alice</p>
        <p class="interesting">bob</p>
        <p class="interesting">carol>/p>

        <div> 
            <div>
                <h3 class="interesting">david</h3>
            </div>
        </div>

        <div class="frame">
            <p class="interesting">drevil</p>
        </div>
    </div>

所以在这个例子中,我希望能够匹配所有“有趣”元素,即第一个div的后代with class =“frame”。但我想要嵌套的“frame”div下面的“有趣”元素。

理想情况下,我会有一个XPath表达式,它会给我带有alice,bob,carol和david内容的元素。但不是drevil。

就像嵌套框架的存在会遮挡搜索树的那个分支。

有什么想法吗?所有回复都非常感谢。


为了回应罗伯特,我有这个Python代码(虽然我会完全按照浏览器方式):

from lxml import etree

from StringIO import StringIO

testxml = """
<div>
    <div class="frame">
        <p class="interesting">alice</p>
        <p class="interesting">bob</p>
        <p class="interesting">carol</p>

        <div> 
            <div>
                <h3 class="interesting">david</h3>
            </div>
        </div>

        <div class="frame">
            <p class="interesting">drevil</p>
        </div>
    </div>    
</div>
"""

xsl = """
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <output>
           <xsl:apply-templates select="//div[@class='frame'][1]/*"/>
        </output>
    </xsl:template>

    <xsl:template match="*">
       <xsl:apply-templates select="*"/>
    </xsl:template>

    <xsl:template match="*[@class='frame']"/>

    <xsl:template match="*[@class='interesting']">
       <xsl:copy-of select="."/>
    </xsl:template>

</xsl:stylesheet>
"""


def test_xsl():
    xslt_doc = etree.parse(StringIO(xsl))
    transform = etree.XSLT(xslt_doc)
    doc = etree.parse(StringIO(testxml))
    result = transform(doc)
    print result

if __name__=="__main__":
    test_xsl()

这给出了以下结果:

<?xml version="1.0"?>
<output>
    <p class="interesting">alice</p>
    <p class="interesting">bob</p>
    <p class="interesting">carol</p>
    <h3 class="interesting">david</h3>
    <p class="interesting">drevil</p>
</output>

正如你所看到的,drevil潜伏着。

注意,Tomalak是正确的,因为*上的第二个匹配没有效果(除了从输出中删除有点奇怪的空格!)。

虽然我可能无法使用XSLT方法,但最初做XPath查询的重点是获取对原始HTML文档中节点的引用。如果我进行转换, new 结果文档中包含的节点将是副本而不是我正在寻找的原始节点,因此没有用!

这可能是有史以来最愚蠢的问题,但有没有办法维护转换文档中节点的引用到原始节点?

感谢Tomalak,Robert和mykhal到目前为止的帮助。我想我只需要买一本关于XSLT的书......

2 个答案:

答案 0 :(得分:2)

你可以使用选择器限制祖先div [@ class =“frame”]元素到1

//div[@class="frame"][1]//*[@class="interesting" and count(ancestor::div[@class="frame"])=1]

它起作用了:

>>> import lxml.html
>>> data = """
        <div class="frame">
            <p class="interesting">alice</p>
            <p class="interesting">bob</p>
            <p class="interesting">carol</p>

            <div> 
                <div>
                    <h3 class="interesting">david</h3>
                </div>
            </div>

            <div class="frame">
                <p class="interesting">drevil</p>
            </div>
        </div>
    """
>>> tree = lxml.html.fromstring(data)
>>> tree.xpath('//div[@class="frame"][1]//*[@class="interesting" and count(ancestor::div[@class="frame"])=1]/text()')
['alice', 'bob', 'carol', 'david']

答案 1 :(得分:0)

mykhal的回答可能是您在XPath中可以做的最好的,至少在您定义问题时是这样。

它的问题在于,当在具有许多潜在有趣元素的大型文档上使用时,它可能会非常低效。对于它找到的每个可能有趣的元素,它必须检查其祖先轴中的每个节点。

在XSLT中,您可以实现一系列模板,这些模板只查找您正在查找的元素,并且不仅访问每个元素一次,也不访问他们不需要的任何元素: / p>

<xsl:template match="/">
    <output>
       <xsl:apply-templates select="/descendant::*[@class='frame'][1]/*"/>
    </output>
</xsl:template>

<xsl:template match="*[@class='frame']"/>

<xsl:template match="*[@class='interesting']">
   <xsl:copy-of select="."/>
</xsl:template>

元素的内置模板行为(无论何时将模板应用于元素并且未找到更高级别的模板时使用)是将模板应用于其子元素。

第一个模板找到您感兴趣的祖先元素,并将模板应用于其子元素。

第二个模板基本上说,“如果你向下递归元素并使用'frame'的类属性命中一个元素,不要检查它的后代。”这使得变换甚至无法检查无趣的元素。

最后,最后一个模板定义了当您点击一个有趣的元素时要做什么 - 在这种情况下,它会将其完整地复制到输出中。