Есть ли оператор if-then-else в XPath?

Кажется, что со всем богатым количеством функций в xpath вы можете сделать «если». Однако мой движок продолжает настаивать на том, что «такой функции нет», и я почти не нахожу никакой документации в Интернете (я нашел несколько сомнительных источников, но синтаксис у них не работал)

Мне нужно удалить ':' с конца строки (если она существует), поэтому я хотел сделать это:

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

Любой совет?


person Yossale    schedule 09.06.2009    source источник
comment
Есть ли больше контекста проблемы? XPath обычно используется для запроса информации, а не как средство манипулирования. Манипуляции обычно оставляют для чего-то вроде шаблона XSL или языка более высокого уровня.   -  person James Conigliaro    schedule 09.06.2009
comment
Я считаю, что с учебными пособиями w3schools легко работать. Вы могли бы начать там. w3schools.com/Xpath   -  person Michael Petrotta    schedule 09.06.2009
comment
@James: на самом деле это запрос. Не позволяйте синтаксису if-then-else одурачить вас — технически это условное выражение, а не оператор if — больше похоже на тернарный условный оператор во многих языках программирования.   -  person Noldorin    schedule 09.06.2009
comment
Аналогичный вопрос: stackoverflow.com/ вопросы/8833225/   -  person Vadzim    schedule 11.09.2014


Ответы (7)


Да, в XPath 1.0 это можно сделать:

concat(
  substring($s1, 1, number($condition)      * string-length($s1)),
  substring($s2, 1, number(not($condition)) * string-length($s2))
)

Это основано на объединении двух взаимоисключающих строк, первая из которых пуста, если условие ложно (0 * string-length(...)), а вторая пуста, если условие истинно. Это называется "методом Беккера" и приписывается Оливеру Беккеру. .

В твоем случае:

concat(
  substring(
    substring-before(//div[@id='head']/text(), ': '),
    1, 
    number(
      ends-with(//div[@id='head']/text(), ': ')
    )
    * string-length(substring-before(//div [@id='head']/text(), ': '))
  ),
  substring(
    //div[@id='head']/text(), 
    1, 
    number(not(
      ends-with(//div[@id='head']/text(), ': ')
    ))
    * string-length(//div[@id='head']/text())
  )
)

Хотя я бы постарался избавиться от всех "//" раньше.

Кроме того, существует вероятность того, что //div[@id='head'] вернет более одного узла.
Просто имейте в виду, что использование //div[@id='head'][1] является более защитным.

person Tomalak    schedule 09.06.2009
comment
Я, вероятно, не буду использовать это (я просто обновлю движок xpath), но это отличный трюк и отличный совет :) - person Yossale; 10.06.2009
comment
возможно, в этих выражениях нужен +1, мне пришлось добавить его по адресу: ‹xsl:template match=i:item› ‹xsl:param name=selection/› ‹xsl:имя переменной=selected select=@id=$selection /› ‹li class=menuItem id={substring('current', number(not($selected)) * (string-length('current')+1))}› ... - person George Birbilis; 26.09.2014
comment
метод Беккера. Я искал оператор ? С#. - person LuckyLikey; 03.02.2016
comment
@LuckyLikey Ну, мы не можем все получить то, что хотим, не так ли? Есть более приятный способ сделать это в XPath 2.0+. - person Tomalak; 03.02.2016
comment
@Tomalak Конечно, я рад, что нашел это решение. Спасибо за подсказку с XPath 2.0+ - person LuckyLikey; 03.02.2016
comment
@Tomalak Ссылка не работает, вот еще одна ссылка на страницу с обсуждением метода: blog.alessio.marchetti.name/post/2011/02/12/ - person ADJenks; 29.03.2019

Официальная спецификация языка для XPath 2.0 на W3.org подробно описывает, что язык действительно поддержка операторов if. См., в частности, Раздел 3.8 Условные выражения. Наряду с форматом синтаксиса и объяснением он дает следующий пример:

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

Это предполагает, что вы не должны заключать выражения в скобки (иначе синтаксис будет выглядеть правильно). Я не совсем уверен, но попробовать определенно стоит. Итак, вы захотите изменить свой запрос, чтобы он выглядел так:

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

Однако я сильно подозреваю, что это может исправить это, поскольку ваш движок XPath, кажется, пытается интерпретировать if как функцию, где на самом деле это специальная конструкция языка.

Наконец, чтобы указать на очевидное, убедитесь, что ваш движок XPath действительно поддерживает XPath 2.0 (в отличие от более ранней версии)! Я не верю, что условные выражения являются частью предыдущих версий XPath.

person Noldorin    schedule 09.06.2009
comment
Я не думаю, что это говорит о том, что вы не должны использовать круглые скобки, а только о том, что вам необходимо их использовать. Если он жалуется на отсутствие такой функции, то я подозреваю, что он не использует XPath 2. Спецификация требует круглых скобок вокруг условного выражения. - person Rob Kennedy; 09.06.2009
comment
@Rob: Это вполне возможно, но тогда я заявляю, что это не обязательно может быть исправлением. Однако всегда стоит следовать примеру, если вы пытаетесь заставить что-то работать. :) - person Noldorin; 09.06.2009
comment
Но да, я как-то пропустил скобки вокруг условия. Исправлено сейчас. - person Noldorin; 09.06.2009

Лично я бы использовал XSLT для преобразования XML и удаления двоеточий в конце. Например, предположим, что у меня есть этот ввод:

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

Если бы я хотел удалить завершающие двоеточия в своих абзацах, я бы использовал этот 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> 
person james.garriss    schedule 18.02.2011

согласно закону пкарата, вы можете получить условный XPath в версии 1.0.

Для вашего случая следуйте концепции:

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

Это определенно сработает. Посмотри, как это работает. Дайте два входа

praba:
karan

Для 1-го ввода: он содержит :, поэтому условие истинно, строка перед : будет выводом, скажем, praba - ваш вывод. 2-е условие будет ложным, так что никаких проблем.

Для 2-го ввода: он не содержит :, поэтому условие не выполнено, при переходе ко 2-му условию строка не содержит :, поэтому условие истинно... поэтому вывод karan будет сгенерирован.

Наконец, ваш вывод будет praba,karan.

person prabakaran    schedule 16.11.2010

К сожалению, предыдущие ответы не были для меня вариантом, поэтому я некоторое время исследовал и нашел это решение:

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

Я использую его для вывода текста, если существует определенный узел. 4 — длина текста foo. Поэтому я думаю, что более элегантным решением было бы использование переменной.

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

Как насчет использования fn:replace(string,pattern,replace) вместо этого?

XPATH очень часто используется в XSLT, и если вы находитесь в такой ситуации и у вас нет XPATH 2.0, вы можете использовать:

  <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>
person Jonas Elfström    schedule 09.06.2009

Несколько более простое решение XPath 1.0, адаптированное из Томалека (опубликовано здесь) и Димитры (здесь):

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

Примечание. Я обнаружил, что для преобразования bool в int требуется явное число(), иначе некоторые оценщики XPath выдавали ошибку несоответствия типов. В зависимости от того, насколько строг ваш процессор XPath к сопоставлению типов, он может вам не понадобиться.

person tiritea    schedule 06.10.2017