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」属性はオプションであることに注意してください。

「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です。

3番目の可能性は、if/elseチェック全体を単一のXPath式に入れるためにSOで学んだ小さなトリックです。

<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 を使用できる場合の別の代替手段は、マップです(別の役立つリンク: map )。

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 に答える