我正在尝试将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的书......
答案 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'的类属性命中一个元素,不要检查它的后代。”这使得变换甚至无法检查无趣的元素。
最后,最后一个模板定义了当您点击一个有趣的元素时要做什么 - 在这种情况下,它会将其完整地复制到输出中。