web-dev-qa-db-fra.com

Existe-t-il une déclaration "if -then - else" dans XPath?

Il semble qu'avec toute la richesse de la fonction dans xpath que vous puissiez faire un "si". Cependant, mon moteur insiste toujours sur le fait "il n’existe pas de telle fonction" et je ne trouve pratiquement aucune documentation sur le Web (j’ai trouvé des sources douteuses, mais leur syntaxe n’a pas fonctionné) 

J'ai besoin de supprimer ':' de la fin d'une chaîne (si existante), alors je voulais faire ceci:

if (fn:ends-with(//div [@id='head']/text(),': '))
            then (fn:substring-before(//div [@id='head']/text(),': ') )
            else (//div [@id='head']/text())

Aucun conseil? 

37
Yossale

Oui, il existe un moyen de le faire dans XPath 1.0:

 concat (
 sous-chaîne ($ s1, 1, nombre ($ condition) * longueur de chaîne ($ s1)), 
 sous-chaîne ($ s2, 1, nombre (pas ($ condition))) * chaîne -longueur ($ s2)) 
) 

Cela repose sur la concaténation de deux chaînes mutuellement exclusives, la première étant vide si la condition est fausse (0 * string-length(...)), la seconde étant vide si la condition est vraie. Ceci s'appelle "Méthode de Becker", attribuée à Oliver Becker .

Dans ton cas:

 concat (
 sous-chaîne (
 sous-chaîne-avant (// div [@ id = 'tête']/texte (), ':')), 
 1, 
 nombre (
 ends-avec (// div [@ id = 'tête']/texte (), ':') 
) 
 *. chaîne-longueur (sous-chaîne avant (// div [@ id = 'tête ']/text (),': ')) 
), 
 substring (
 // div [@ id =' head ']/text (), 
 1, 
 nombre (not (
 ends-with (// div [@ id = 'head']/text (), ':') 
)) 
 *. longueur de chaîne (// div [@ id = 'head']/text ()) 
) 
) 

Bien que j'essaie de me débarrasser de tous les "//" avant.

En outre, il est possible que //div[@id='head'] renvoie plusieurs nœuds.
Sachez simplement que l'utilisation de //div[@id='head'][1] est plus défensive.

59
Tomalak

La spécification de langue officielle de XPath 2.0 sur W3.org précise que la langue prend effectivement en charge les instructions if. Voir Section 3.8 Expressions conditionnelles , en particulier. Avec le format de syntaxe et l'explication, il donne l'exemple suivant:

if ($widget1/unit-cost < $widget2/unit-cost) 
  then $widget1
  else $widget2

Cela suggérerait que ne devrait pas avoir de crochets entourant vos expressions (sinon la syntaxe semble correcte). Je ne suis pas totalement confiant, mais ça vaut la peine d'essayer. Donc, vous voudrez changer votre requête pour ressembler à ceci:

if (fn:ends-with(//div [@id='head']/text(),': '))
  then fn:substring-before(//div [@id='head']/text(),': ')
  else //div [@id='head']/text()

Je suspecte fortement que cela puisse résoudre le problème, cependant, car le moteur XPath semble essayer d’interpréter if comme une fonction, c’est en fait une construction spéciale du langage.

Enfin, pour souligner l'évidence, assurez-vous que votre moteur XPath prend en charge XPath 2.0 (par opposition à une version antérieure)! Je ne crois pas que les expressions conditionnelles fassent partie des versions précédentes de XPath.

21
Noldorin

selon la loi de pkarat, vous pouvez atteindre XPath conditionnel dans la version 1.0.

Pour votre cas, suivez le concept:

concat(substring-before(your-xpath[contains(.,':')],':'),your-xpath[not(contains(.,':'))])

Cela fonctionnera certainement. Regarde comment ça marche. Donner deux entrées

praba:
karan

Pour la 1ère entrée: il contient : donc la condition true, la chaîne précédant : sera la sortie, disons que praba est votre sortie. La deuxième condition sera fausse, donc pas de problèmes.

Pour la 2e entrée: elle ne contient pas :, donc la condition échoue, la 2e chaîne ne contient pas :, donc la condition est vraie ... et la sortie karan sera renvoyée.

Enfin, votre sortie serait praba, karan.

3
prabakaran

Personnellement, j'utiliserais XSLT pour transformer le code XML et supprimer les deux-points. Par exemple, supposons que j'ai cette entrée:

<?xml version="1.0" encoding="UTF-8"?>
<Document>
    <Paragraph>This paragraph ends in a period.</Paragraph>
    <Paragraph>This one ends in a colon:</Paragraph>
    <Paragraph>This one has a : in the middle.</Paragraph>
</Document>

Si je voulais supprimer les deux-points suivants dans mes paragraphes, je voudrais utiliser ce XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    version="2.0">
    <!-- identity -->
    <xsl:template match="/|@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <!-- strip out colons at the end of paragraphs -->
    <xsl:template match="Paragraph">
        <xsl:choose>
            <!-- if it ends with a : -->
            <xsl:when test="fn:ends-with(.,':')">
                <xsl:copy>
                    <!-- copy everything but the last character -->
                    <xsl:value-of select="substring(., 1, string-length(.)-1)"></xsl:value-of>
                </xsl:copy>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy>
                    <xsl:apply-templates/>
                </xsl:copy>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet> 
3
james.garriss

Pourquoi ne pas utiliser fn: replace (string, pattern, replace) à la place?

XPATH est très souvent utilisé dans les XSLT et si vous êtes dans cette situation et n’avez pas XPATH 2.0, vous pouvez utiliser:

  <xsl:choose>
    <xsl:when test="condition1">
      condition1-statements
    </xsl:when>
    <xsl:when test="condition2">
      condition2-statements
    </xsl:when>
    <xsl:otherwise>
      otherwise-statements
    </xsl:otherwise>
  </xsl:choose>
1
Jonas Elfström

Malheureusement, les réponses précédentes n'étaient pas une option pour moi, j'ai donc cherché pendant un moment et trouvé la solution suivante:

http://blog.alessio.marchetti.name/post/2011/02/12/the-Oliver-Becker-s-XPath-method

Je l'utilise pour afficher du texte si un certain nœud existe. 4 est la longueur du texte foo. Je suppose donc qu’une solution plus élégante consisterait à utiliser une variable.

substring('foo',number(not(normalize-space(/elements/the/element/)))*4)
1
noreabu

Solution XPath 1.0 un peu plus simple, adaptée de Tomalek (posté ici) et de Dimitre ( ici ):

concat(substring($s1, 1 div number($cond)), substring($s2, 1 div number(not($cond))))

Remarque: j'ai constaté qu'un nombre explicite () était requis pour convertir la valeur bool en un entier. Dans le cas contraire, certains évaluateurs XPath ont généré une erreur d'incompatibilité de type. En fonction du degré de correspondance de votre processeur XPath, il se peut que vous n'en ayez pas besoin.

0
tiritea