7

我对 XSLT 很陌生,因此我想知道检查属性是否存在的最佳实践是什么。我的 XML 看起来像这样:

<root>
    <languages>
        <lang id="EN">English<lang>
        <lang id="FR">French<lang>
        <lang id="DE">German</lang>
    </languages>
    <items>
        <item lang="EN">test 1</item>
        <item>test 2</item>
        <item lang="FR">item 3</item>
    </items>
</root>

请注意,“item”元素的“lang”属性是可选的。

现在我想使用 -loop 遍历这些项目,同时检查它是否具有“lang”属性。如果是这样,我想使用 ID 获取整个字符串(例如 EN -> 'English')。如果未设置属性,我希望它写“无语言集”或类似的东西。

现在我使用以下代码,但我在质疑自己是否不能以更有效的方式完成。

<xsl:for-each select="//root/items/item">
    <xsl:variable name="cur_lang" select="@lang" /> <!-- first I store the attr lang in a variable -->
    <xsl:choose>
        <xsl:when test="@lang"> <!-- then i test if the attr exists -->
            <xsl:value-of select="//root/languages/lang[@id=$cur_lang]" /> <!-- if so, parse the element value -->
        </xsl:when>
        <xsl:otherwise>
            No language set <!-- else -->
        </xsl:otherwise>
    </xsl:choose>
</xsl:for-each>

有什么建议/提示吗?

4

2 回答 2

7

使用密钥可能更有效。您使用模板外部的顶级元素定义键

<xsl:key name="langByCode" match="lang" use="@id" />

然后在循环中你可以简单地说

<xsl:when test="@lang"> <!-- then i test if the attr exists -->
   <xsl:value-of select="key('langByCode', @lang)" />
</xsl:when>

但一般来说,对整个事情更自然的 XSLT 方法是使用模板匹配而不是for-eachand if

<xsl:template match="item[@lang]">
  <xsl:value-of select="key('langByCode', @lang)" />
</xsl:template>

<xsl:template match="item">
  <xsl:text>No language set</xsl:text>
</xsl:template>

有了这些模板,您就可以这样做<xsl:apply-templates select="/root/items/item" />,它会自动为每个项目选择适当的模板。规则是它将使用最具体的模板,因此item[@lang]对于那些具有属性的项目使用一个模板,而对于那些没有lang属性的项目则使用普通模板item

第三种可能性是我在 SO 上学到的一个小技巧,将整个 if/else 检查放入单个 XPath 表达式

<xsl:value-of select="
  substring(
    concat('No language set', key('langByCode', @lang)),
    1 + (15 * boolean(@lang))
  )" />

这里的技巧是,boolean(@lang)当被视为一个数字1时,是否存在 lang 属性,0如果不存在。如果有一个lang="EN",比如说,那么我们构造一个字符串"No language setEnglish",然后取从第 16 个字符开始的子字符串,即"English"。如果没有lang 属性,我们构造字符串"No language set"(因为空节点集的字符串值是空字符串)并取从第一个字符开始的子字符串(即整个字符串)。

您可以对其他属性使用相同的技巧,例如,假设我们有一个可选的颜色属性并且想要说明"No color specified"它是否不存在,您可以使用

<xsl:value-of select="substring(
   concat('No color specified', @color),
   1 + (18 * boolean(@color))
 )" />
于 2013-01-15T21:21:37.240 回答
0

如果您能够使用 XSLT 3.0,另一种选择是地图(另一个有用的链接:地图)。

XML 输入(固定为格式正确)

<root>
    <languages>
        <lang id="EN">English</lang>
        <lang id="FR">French</lang>
        <lang id="DE">German</lang>
    </languages>
    <items>
        <item lang="EN">test 1</item>
        <item>test 2</item>
        <item lang="FR">item 3</item>
    </items>
</root>

XSLT 3.0

<xsl:stylesheet version="3.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" extension-element-prefixes="xs map">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:variable name="lang-map" as="map(xs:string, xs:string)" 
        select="map:new(
        for $lang in /*/languages/lang 
        return 
            map{$lang/@id := $lang/string()}
        )"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="languages"/>

    <xsl:template match="item[@lang and map:contains($lang-map,@lang)]">
        <item><xsl:value-of select="$lang-map(current()/@lang)"/></item>
    </xsl:template>

    <xsl:template match="item">
        <item>No language found.</item>
    </xsl:template>

</xsl:stylesheet>

输出

<root>
   <items>
      <item>English</item>
      <item>No language found.</item>
      <item>French</item>
   </items>
</root>
于 2013-01-15T22:07:52.617 回答