XSLT 1.0 - 条件节点赋值

5

使用纯XSLT 1.0,我该如何有条件地分配节点。我尝试了以下内容,但它并不起作用。

<xsl:variable name="topcall" select="//topcall"/>
<xsl:variable name="focusedcall" select="//focusedcall" />

<xsl:variable name="firstcall" select="$topcall | $focusedcall"/>

对于变量firstcall,我正在执行条件节点选择。如果存在topcall,则将其分配给firstcall,否则将firstcall分配给focusedcall

3个回答

5
这应该可以运行:
<xsl:variable name="firstcall" select="$topcall[$topcall] |
                                       $focusedcall[not($topcall)]" />

换句话说,如果$topcall节点集非空,请选择$topcall;如果$topcall节点集为空,请选择$focusedcall
关于“可能有5-6个节点”的重新更新:
考虑到可能有5-6个替代品,即除了$topcall和$focusedcall外,还有3-4个...
最简单的解决方案是使用<xsl:choose>:
<xsl:variable name="firstcall">
  <xsl:choose>
    <xsl:when test="$topcall">    <xsl:copy-of select="$topcall" /></xsl:when>
    <xsl:when test="$focusedcall"><xsl:copy-of select="$focusedcall" /></xsl:when>
    <xsl:when test="$thiscall">   <xsl:copy-of select="$thiscall" /></xsl:when>
    <xsl:otherwise>               <xsl:copy-of select="$thatcall" /></xsl:otherwise>
  </xsl:choose>
</xsl:variable>

然而,在XSLT 1.0中,这将把所选结果的输出转换为结果树片段(RTF:基本上是一个冻结的XML子树)。之后,您将无法在$firstcall上使用任何重要的XPath表达式来选择其中的内容。如果您稍后需要在$firstcall上执行XPath选择,例如select="$firstcall[1]",则有几个选择...
1.将这些选择放入或中,以便它们在数据被转换为RTF之前发生。或者,
2.考虑使用node-set()扩展,将RTF转换为节点集,以便您可以从中进行正常的XPath选择。该扩展程序在大多数XSLT处理器中都可用,但并非所有处理器都支持。或者,
3.考虑使用XSLT 2.0,在那里RTFs根本不是问题。实际上,在XPath 2.0中,如果您想要,您可以在XPath表达式中放置普通的if/then/else条件语句。
4.使用嵌套谓词在XPath 1.0中实现它。
select="$topcall[$topcall] |
        ($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])[not($topcall)]"

并且需要不断地嵌套,深入到必要的程度。换句话说,我在这里使用了XPath表达式中的两个备选项,并用$focusedcall替换了它。
($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])

下一轮迭代中,您需要将"$thiscall"替换为。
($thiscall[$thiscall] | $thatcall[not($thiscall)])

当然,这样会变得难以阅读且容易出错,因此除非其他选项不可行,否则我不会选择此选项。

你能否简单解释一下,因为我对XSLT非常陌生。 - rpg
1
竖线 | 是一个联合运算符(不是逻辑 or)。您之前正在对两个节点集取并集。LarsH 的解决方案将两个互斥集合的并集取出,这总是会得到其中一个。 - Wayne
@rgp:是的,这是针对XSLT 1.0的。 - LarsH
@Larsh - 感谢您的精彩解释。但是我仍然不清楚5-6个节点的示例。通过5-6个节点,我指的是类似于topcall、focusedcall的节点,我可能还有thiscall、thatcall等等。在这种情况下,我该如何找到firstcall呢? - rpg
@rpg - 我更新了我的答案,关于3个或更多的替代方案。 - LarsH
@LarsH:可能会简单得多 :) - Dimitre Novatchev

3

您是否需要使用 <xsl:variable name="firstcall" select="($topcall | $focusedcall)[1]"/> ?这通常是按文档顺序获取不同类型节点的第一个节点的方法。


Martin - 我也试过同样的方法,但似乎不起作用。我们已经为 topcall 和 focusedcall 定义了节点。通过这种条件节点分配,我想让 firstcall 分配 topcall(如果存在)或 focusedcall。顺便说一下,在输入的 XML 中,focusedcall 出现在 topcall 之前。 - rpg
@rpg:需要更明确地说明“它不起作用”。您得到了什么结果,它与您的期望有何不同? - LarsH
还有一点,这个OR不仅仅是针对两个节点,它可以是5-6个节点。此外,顶级调用和聚焦调用的位置可能会发生变化,所以似乎很难依赖于任何与文档顺序相关的东西。 - rpg
2
@rpg - 这是你需要在问题中包含的所有信息。如果有更重要的细节,请编辑以包括你的实际问题。 - Wayne
这是一个XSLT 2.0的习语,如果你将“|”替换为“,”。在一般情况下,它在XSLT 1.0中不起作用,因为“|”按文档顺序返回节点。如果您知道当两个节点都存在时,$topcall将始终在$focusedcall之前,则可以使用它。 - Michael Kay
非常适合我正在做的事情!谢谢 +1 - CharlieTuna

2

I. XSLT 1.0 解决方案 这个简短的(30行),简单且可参数化的转换适用于任何数量的节点类型/名称:

<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="pRatedCalls">
   <call type="topcall"/>
   <call type="focusedcall"/>
   <call type="normalcall"/>
 </xsl:param>

 <xsl:variable name="vRatedCalls" select=
  "document('')/*/xsl:param[@name='pRatedCalls']/*"/>

 <xsl:variable name="vDoc" select="/"/>

 <xsl:variable name="vpresentCallNames">
  <xsl:for-each select="$vRatedCalls">
   <xsl:value-of select=
   "name($vDoc//*[name()=current()/@type][1])"/>
   <xsl:text> </xsl:text>
  </xsl:for-each>
 </xsl:variable>

 <xsl:template match="/">
  <xsl:copy-of select=
   "//*[name()
       =
        substring-before(normalize-space($vpresentCallNames),' ')]"/>
 </xsl:template>
</xsl:stylesheet>

应用于此XML文档(请注意,文档顺序与pRatedCalls参数中指定的优先级不一致):
<t>
 <normalcall/>
 <focusedcall/>
 <topcall/>
</t>

能够产生所需的、正确的结果:

<topcall/>

当同样的转换应用于以下XML文档时:

<t>
 <normalcall/>
 <focusedcall/>
</t>

再次获得所需且正确的结果:

<focusedcall/>

解释:

  1. 要搜索的节点名称(尽可能多且按优先级顺序)由名为$pRatedCalls的全局参数指定(通常是外部指定的)。

  2. 在变量$vpresentCallNames的主体中,我们生成以空格分隔的元素名称列表,这些元素指定为call元素的type属性的值,在$pRatedCalls参数中,并且是XML文档中元素的名称。

  3. 最后,我们确定此空格分隔列表中的第一个名称,并选择具有此名称的文档中的所有元素。

II. 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:param name="pRatedCalls" select=
  "'topcall', 'focusedcall', 'normalcall'"/>

 <xsl:template match="/">
  <xsl:sequence select=
   "//*
     [name()=$pRatedCalls
                [. = current()//*/name()]
                                        [1]
     ]"/>
 </xsl:template>
</xsl:stylesheet>

+1,好的解决方案。这利用了OP的替代方案严格基于元素名称的事实。我正在为任何节点集变量创建更通用的解决方案,而不考虑内容...但是你指出这对于这个问题来说并不必要。 - LarsH
@Dimitre - 感谢您提供的通用解决方案。对于新手,您能否请在第一条语句中添加以下内容:<xsl:param name="pRatedCalls"> <call type="topcall"/> <call type="focusedcall"/> <call type="normalcall"/> </xsl:param> - rpg
@rpg:我已经添加了XSLT 1.0解决方案的详细说明。如果您有任何其他问题,请随时提出。 - Dimitre Novatchev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接