给定一个元素作为上下文我想选择前面的兄弟元素并检查它是否有特定的名称。需要注意的是,如果存在具有非空白内容的插入文本节点,我不想选择它。
例如,给定这个XML文档......
<r>
<a>a1</a><a>a2</a>
b
<a>a3</a>
<a>a4</a>
<b/>
<a>a5</a>
</r>
...然后:
<a>
兄弟元素)<a>
)。我可以查看前面的兄弟是<a>
preceding-sibling::*[1][name()="a"]
然而,我无法弄清楚如何说“选择以下兄弟节点,无论元素或文本,看看是不是文本还是normalize-space(.)=""
。我最好的猜测是:
preceding-sibling::*[1][name()="a"][following-sibling::node()[1][not(text()) or normalize-space(.)=""]]
......但似乎没有效果。
这是我的测试Ruby文件:
require 'nokogiri'
xpath = 'preceding-sibling::*[1][name()="a"][following-sibling::node()[1][not(text()) or normalize-space(.)=""]]'
fragment = Nokogiri::XML.fragment '<a>a1</a><a>a2</a> b <a>a3</a> <a>a4</a> <b/> <a>a5</a>'
fragment.css('a').each{ |a| p [a.text,a.xpath(xpath).to_s] }
#=> ["a1", ""]
#=> ["a2", ""]
#=> ["a3", "<a>a2</a>"]
#=> ["a4", "<a>a3</a>"]
#=> ["a5", ""]
“a2”和“a3”的结果是错误的,让我感到困惑。它正确地找到了前面的<a>
,但是没有正确地验证第一个后续兄弟是不是文本(应该允许“a2”找到“a1”)或者它只是空白(哪个)应该防止“a3”找到“a2”。
编辑:这是我写的XPath,以及我打算做的事情:
preceding-sibling::*[1][name()="a"]…
- 找到前面的第一个元素,并确保它是<a>
。 这似乎符合要求。
[following-sibling::node()[1][…]]
- 确保第一个后续节点(找到的前一个<a>
)符合某些条件
not(text()) or normalize-space(.)=""
- 确保此后续节点不是文本节点,或者其规范化空间为空答案 0 :(得分:5)
使用强>:
/*/a/preceding-sibling::node()
[not(self::text()[not(normalize-space())])]
[1]
[self::a]
基于XSLT的验证:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select=
"/*/a
/preceding-sibling::node()
[not(self::text()[not(normalize-space())])]
[1]
[self::a]
"/>
</xsl:template>
</xsl:stylesheet>
在提供的XML文档上应用此转换时:
<r>
<a>a1</a><a>a2</a>
b
<a>a3</a>
<a>a4</a>
<b/>
<a>a5</a>
</r>
评估XPath表达式,并将此评估选择的节点复制到输出:
<a>a1</a>
<a>a3</a>
<强>更新强>:
问题中的XPath表达式有什么问题?
问题在于:
[not(text()) or normalize-space(.)='']
这测试上下文节点是否没有文本节点 child 。
但OP希望测试上下文节点是文本节点。
<强>解决方案强>:
将上述内容替换为:
[not(self::text()) or normalize-space(.)='']
基于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:template match="/*/a">
<xsl:copy-of select=
"preceding-sibling::*[1]
[name()='a']
[following-sibling::node()[1]
[not(self::text()) or normalize-space(.)='']
]"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
现在,此转换产生了完全想要的结果:
<a>a1</a>
<a>a3</a>