2013-01-15 10 views
6

Jestem całkiem nowy w XSLT i dlatego chcę wiedzieć, jaka jest najlepsza praktyka sprawdzania istnienia atrybutu. Mój XML wygląda mniej więcej tak:XSLT, jeśli atrybut istnieje/else

<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> 

Należy zauważyć, że atrybut "lang" dla elementu "element" jest opcjonalny.

Teraz chcę przechodzić przez elementy używając -loop, sprawdzając czy ma "lang" -atrybut. Jeśli tak, chcę pobrać cały ciąg za pomocą identyfikatora (np. EN -> "English"). Jeśli atrybut nie jest ustawiony, chcę napisać "Brak zestawu językowego" lub coś podobnego.

Teraz używam następującego kodu, ale pytam siebie, czy nie można tego zrobić w bardziej efektywny sposób.

<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> 

Wszelkie sugestie/porady?

+2

można dodać przykład jakie dokładnie dane wyjściowe powinny otrzymać dane wejściowe dostarczone? –

+0

Carlo, zapomniałeś podać żądane wyjście - Myślałem, że chcesz, aby rozszerzony ciąg znaków językowych zastąpił bieżącą wartość atrybutu "lang". Proszę * edytować * pytanie i podać pożądany wynik, gdy transformacja zostanie zastosowana w dostarczonym dokumencie XML. –

Odpowiedz

7

Bardziej wydajne może być użycie klucza. Zdefiniowanie klucza za pomocą elementu najwyższego poziomu poza szablonów

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

Następnie w pętli można po prostu powiedzieć

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

ale ogólnie rzecz biorąc bardziej naturalne podejście XSLT do całej sprawy byłoby użyj szablonu dopasowanie zamiast for-each i 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> 

z tych szablonów na miejscu można wtedy zrobić <xsl:apply-templates select="/root/items/item" /> i automatycznie wybierze odpowiedni szablon dla każdego elementu. Zasadą jest, że użyje najbardziej szczegółowego szablonu, a więc item[@lang] dla tych produktów, które mają atrybut lang i zwykłego item dla tych, którzy tego nie robią.

Trzecią możliwością jest mały trick Dowiedziałem na SO umieścić całość if/else sprawdzić w jednym wyrażeniu XPath

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

Sztuką jest to, że boolean(@lang) gdy traktowane jako liczba jest 1 jeśli atrybut lang istnieje i 0, jeśli nie. Jeśli istnieje lang="EN", powiedzmy, skonstruujemy ciąg "No language setEnglish", a następnie pobierzmy podciąg rozpoczynający się na 16. znaku, który jest "English". Jeśli istnieje atrybut no, konstruujemy ciąg znaków "No language set" (ponieważ wartość ciągu pustego zestawu węzłów jest pustym łańcuchem) i bierzemy podciąg rozpoczynający się od pierwszego znaku (to jest całego ciągu znaków).

Możesz użyć tej samej sztuczki z innymi atrybutami, np. Załóżmy, że miał opcjonalny atrybut koloru i chciał powiedzieć "No color specified" jeśli jest nieobecny, można to zrobić z

<xsl:value-of select="substring(
    concat('No color specified', @color), 
    1 + (18 * boolean(@color)) 
)" /> 
+0

Dzięki, próbuję użyć , ale kiedy to zrobię, strona się nie załaduje. Co mogłem robić źle? – carlo

+0

Rozwiązał problem, przesuwając z carlo

+0

Co mogę zrobić, jeśli będzie dodatkowy opcjonalny atrybut dla , powiedzmy "kolor"? – carlo

0

Innym alternatywnym, jeśli jesteś w stanie wykorzystać XSLT 3.0, jest map (kolejne pomocne łącza: map).

XML wejściowe (stały się dobrze uformowane)

<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> 

Wyjście

<root> 
    <items> 
     <item>English</item> 
     <item>No language found.</item> 
     <item>French</item> 
    </items> 
</root>