这是对XPath返回所有兄弟姐妹的常见请求的变体,直到某些条件,Dimitre Novatchev在XPath axis, get all following nodes until使用此模式回答了特有的丰满度:
$x/following-sibling::p
[1 = count(preceding-sibling::node()[name() = name($x)][1] | $x)]
但是这种模式依赖于following-sibling
和preceding-sibling
的对称性,能够沿着轴向两个方向看。
当轴为ancestor-or-self
时,是否存在类似的模式?
例如:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
直截了当
<xsl:template match="img">
<xsl:for-each select="ancestor-or-self::*[@xml:base]">
<xsl:value-of select="@xml:base"/>
</xsl:for-each>
<xsl:value-of select="@url"/>
</xsl:template>
将返回
/news/reports/sports/photos/A1.jpg
/news/reports/sports/photos/A1.jpg
但是如果
<c xml:base="sports/" >
而不是
<c xml:base="/sports/" >
使用前导/
,for-each
需要停止,以便返回
/sports/photos/A1.jpg
/sports/photos/A2.jpg
如何(在XSLT / XPath 1.0中)使其停止?
答案 0 :(得分:2)
此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:param name="pWanted" select="//img"/>
<xsl:param name="pWantedAttr" select="'url'"/>
<xsl:template match="/">
<xsl:apply-templates select="$pWanted"/>
</xsl:template>
<xsl:template match="*[not(starts-with(@xml:base, '/'))]">
<xsl:apply-templates select="ancestor::*[@xml:base][1]"/>
<xsl:value-of select="concat(@xml:base,@*[name()=$pWantedAttr])"/>
<xsl:if test="not(@xml:base)"><xsl:text>
</xsl:text></xsl:if>
</xsl:template>
<xsl:template match="*[starts-with(@xml:base, '/')]">
<xsl:value-of select="@xml:base"/>
</xsl:template>
</xsl:stylesheet>
应用于此XML文档:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
会产生想要的正确结果:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
更新 - 单个XPath 2.0表达式解决方案:
for $target in //img,
$top in $target/ancestor::*[starts-with(@xml:base,'/')][1]
return
string-join(
(
$top/@xml:base
, $top/descendant::*
[@xml:base and . intersect $target/ancestor::*]
/@xml:base
, $target/@url,
'
'
),
''
)
基于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:template match="/">
<xsl:sequence select=
"for $target in //img,
$top in $target/ancestor::*[starts-with(@xml:base,'/')][1]
return
string-join(
(
$top/@xml:base
, $top/descendant::*
[@xml:base and . intersect $target/ancestor::*]
/@xml:base
, $target/@url,
'
'
),
''
)
"/>
</xsl:template>
</xsl:stylesheet>
在提供的XML文档上应用此转换时:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
评估XPath表达式,并将此评估的结果复制到输出中:
/news/reports/sports/photos/A1.jpg
/news/reports/sports/photos/A2.jpg
使用修改后的文档:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
再次生成想要的正确结果:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
<强> UPDATE2 强>:
OP提出了这种简化:
原始海报添加的更新:一旦完整嵌入 应用程序,其中完整的URL替换了相对的,Dimitre的 最终成为这种简单的方法
<xsl:template match="@url">
<xsl:attribute name="url">
<xsl:apply-templates mode="uri" select=".." />
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="*" mode="uri">
<xsl:if test="not(starts-with(@xml:base, '/'))">
<xsl:apply-templates select="ancestor::*[@xml:base][1]" mode="uri"/>
</xsl:if>
<xsl:value-of select="@xml:base"/>
</xsl:template>
答案 1 :(得分:2)
有一种方法可以在单个for-each选择表达式中选择正确的节点:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="img">
<xsl:for-each select="
ancestor-or-self::*[
starts-with(@xml:base, '/')
][1]/descendant-or-self::*[
@xml:base and .//img[generate-id() = generate-id(current())]
]">
<xsl:value-of select="@xml:base"/>
</xsl:for-each>
<xsl:value-of select="@url"/>
</xsl:template>
</xsl:stylesheet>
鉴于此输入XML:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
产生了正确的结果:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
XPath表达式可以读作“从最近的祖先开始,其@xml:base
以斜杠开头,选择那个及其所有后代,其当前<img>
为其中一个他们的后代。“
这有效地精确选择了一条正确的路径到XML树中。
答案 2 :(得分:0)
我找到了它:在进入for-each循环之前计算满足条件的祖先。测试每个候选人以查看其条件匹配祖先的计数是否相同。
此样式表
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="img">
<xsl:variable name="distance" select="count(ancestor-or-self::*[@xml:base][substring(@xml:base,1,1)='/'])" />
<xsl:for-each select="ancestor-or-self::*[@xml:base][
count(ancestor-or-self::*[@xml:base][substring(@xml:base,1,1)='/'])=$distance
]">
<xsl:value-of select="@xml:base"/>
</xsl:for-each>
<xsl:value-of select="@url"/>
</xsl:template>
</xsl:stylesheet>
应用于给定的XML
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
返回所需的结果:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
在/
之前没有sports
,for-each与祖先一直匹配到/news
:
/news/reports/sports/photos/A1.jpg
/news/reports/sports/photos/A2.jpg
答案 3 :(得分:0)
对原始模板进行微小更改:
<xsl:template match="img">
<xsl:for-each select="ancestor-or-self::*[@xml:base][not(.//*[starts-with(@xml:base, '/')])]">
<xsl:value-of select="@xml:base"/>
</xsl:for-each>
<xsl:value-of select="@url"/>
</xsl:template>