给定一个元素作为上下文,我想选择前面的兄弟元素并检查它是否具有特定的名称。需要注意的是,如果有一个中间文本节点具有非空白内容,我不希望选择它。
例如,给定这个XML文档…
<r>
<a>a1</a><a>a2</a>
b
<a>a3</a>
<a>a4</a>
<b/>
<a>a5</a>
</r>
…然后:
- 对于"a1"不应该有匹配(在它之前没有
<a>
的兄弟元素) - 对于"a2",则应该匹配"a1"(没有中间的文本节点)
- 对于"a3"不应该有匹配(有一个中间的文本节点与非空白内容)
- 对于"a4",则应匹配"a3"(中间的文本节点仅为空白)
- 对于"a5"不应该有匹配(前面的兄弟元素不是
<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"one_answers"a3"的结果是什么是错误的,让我困惑。它可以正确地找到前面的<a>
,但是不能正确地验证它的第一个兄弟不是文本(这应该允许"a2"找到"a1")或者它只是空白(这应该阻止"a3"找到"a2"。
Edit:这是我正在编写的XPath,以及我想要它做的事情:
preceding-sibling::*[1][name()="a"]…
-找到前面的第一个元素,并确保它是<a>
。[following-sibling::node()[1][…]]
-确保以下第一个节点(在找到的<a>
之前)匹配某些条件-
not(text()) or normalize-space(.)=""
-确保以下节点不是文本节点,或者它的规范化空间为空
-
使用:
/*/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(.)='']
测试上下文节点是否没有文本节点子节点。
但是OP想要测试上下文节点是否是文本节点。
<<p> 解决方案/strong>:用:
代替[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>