Выбор узлов, имеющих все / любые значения в списке - PullRequest
1 голос
/ 13 августа 2010

Я хочу взять разделенный список идентификаторов категорий, представленных в одном элементе ...

<Categories>851|849</Categories>
<MatchType>any</MatchType>

... и использовать их для оформления других элементов ...

<Page CategoryIds="848|849|850|851">Page 1</Page>
<Page CategoryIds="849|850|">Page 2</Page>
<Page CategoryIds="848|850|">Page 3</Page>
<Page CategoryIds="848|849|850|851">Page 4</Page>
<Page CategoryIds="848|850|851">Page 5</Page>
<Page CategoryIds="848|849|850">Page 6</Page>

... в зависимости от того, обладают ли они любым (или всеми ... в зависимости от того, что указано в <MatchType>) данных идентификаторов.

Кроме того, идентификаторы не обязательно будут указываться в том порядке, в котором они указаны в атрибуте CategoryIds, а строка внутри атрибута не должна содержать точную строку <Categories>.

Возможно ли что-то подобное с использованием XSLT / XPath 1.0?Я знаю, что 2.0 имеет функцию токенизации, которая идеально подходит для этого, но, к сожалению, CMS, с которой я работаю, пока не поддерживает 2.0.

Любая помощь будет принята с благодарностью !!

Ответы [ 2 ]

1 голос
/ 13 августа 2010

Эта таблица стилей:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="vMatch">
        <Categories>851|849</Categories>
        <MatchType>any</MatchType>
    </xsl:variable>
    <xsl:param name="pMatch" select="document('')/*/xsl:variable[@name='vMatch']"/>
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Page" name="page">
        <xsl:param name="pCategories" select="$pMatch/Categories"/>
        <xsl:if test="$pCategories != ''">
            <xsl:variable name="vTest" select="contains(concat('|',
                                                                   @CategoryIds,
                                                                   '|'),
                                                            concat('|',
                                                                   substring-before(concat($pCategories,
                                                                                           '|'),
                                                                                    '|'),
                                                                   '|'))"/>
            <xsl:choose>
                <xsl:when test="$vTest and ($pMatch/MatchType = 'any' or
                                            substring-after($pCategories,
                                                            '|')
                                            = '')">
                    <xsl:call-template name="identity"/>
                </xsl:when>
                <xsl:when test="($vTest and $pMatch/MatchType = 'all') or
                                $pMatch/MatchType = 'any' ">
                    <xsl:call-template name="page">
                        <xsl:with-param name="pCategories" select="substring-after($pCategories,'|')"/>
                    </xsl:call-template>
                </xsl:when>
            </xsl:choose>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

С этим входом:

<Pages>
    <Page CategoryIds="848|849|850|851">Page 1</Page>
    <Page CategoryIds="849|850|">Page 2</Page>
    <Page CategoryIds="848|850|">Page 3</Page>
    <Page CategoryIds="848|849|850|851">Page 4</Page>
    <Page CategoryIds="848|850|851">Page 5</Page>
    <Page CategoryIds="848|849|850">Page 6</Page>
</Pages>

Выход:

<Pages>
    <Page CategoryIds="848|849|850|851">Page 1</Page>
    <Page CategoryIds="849|850|">Page 2</Page>
    <Page CategoryIds="848|849|850|851">Page 4</Page>
    <Page CategoryIds="848|850|851">Page 5</Page>
    <Page CategoryIds="848|849|850">Page 6</Page>
</Pages>

Примечание: Поскольку я не знаю, где вы можете протестировать Categories, я поместил их в таблицу стилей. Это имеет некоторую оптимизацию: после тестирования первой категории, успех (шаблон вызова identity), если категория найдена и тип соответствия равен any, или это последняя категория для тестирования, в противном случае выполняется рекурсивный вызов, только если категория найдена и соответствует тип all или категория не найдена, а тип соответствия any. Таким образом, он успешен при первом совпадении в any «режиме» и завершается неудачей при первом сбое в all «режиме».

Редактировать : просто для удовольствия, с вводом Димитра:

<t>
 <select-criteria>
  <Categories>851|849</Categories>
  <MatchType>all</MatchType>
 </select-criteria>
 <pages>
  <Page CategoryIds="848|849|850|851">Page 1</Page>
  <Page CategoryIds="849|850">Page 2</Page>
  <Page CategoryIds="848|850">Page 3</Page>
  <Page CategoryIds="848|849|850|851">Page 4</Page>
  <Page CategoryIds="848|850|851">Page 5</Page>
  <Page CategoryIds="848|849|850">Page 6</Page>
 </pages>
</t>

Одна строка XPath 2.0 :

/t/*/Page[(
           /t/*/MatchType = 'any' 
                   and 
           tokenize(/t/*/Categories,'\|') = tokenize(@CategoryIds,'\|')
          ) or (
           /t/*/MatchType = 'all' 
                   and 
           (every $x in tokenize(/t/*/Categories,'\|') 
            satisfies $x = tokenize(@CategoryIds,'\|'))
          )]

С XPath 2.1 let выражение будет менее многословным ...

0 голосов
/ 13 августа 2010

Хотя из этого вопроса совершенно не ясно, что вы хотите сделать, вот ответ на часть этого вопроса:

Возможно ли что-то подобное, используя XSLT / XPath 1.0? Я знаю, что 2.0 имеет функция токенизации, которая будет идеально подходит для этого, но, к сожалению, CMS, с которой я работаю, еще не поддержка 2.0.

Токенизация была сделана в XSLT 1.0 в течение многих лет. Несмотря на то, что можно написать собственный рекурсивный шаблон для токенизации строки, хорошо помнить, что такое решение уже доступно в библиотеке FXSL и гарантированно работает, оно более мощное, чем типичная реализованная токенизация, и не имеет известных ошибки - просто готов к использованию.

Это шаблон str-split-to-words, и вот один типичный пример его использования :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common">

   <xsl:import href="strSplit-to-Words.xsl"/>

   <xsl:output indent="yes" omit-xml-declaration="yes"/>

    <xsl:template match="/">
      <xsl:variable name="vwordNodes">
        <xsl:call-template name="str-split-to-words">
          <xsl:with-param name="pStr" select="/"/>
          <xsl:with-param name="pDelimiters" 
                          select="', &#9;&#10;&#13;'"/>
        </xsl:call-template>
      </xsl:variable>

      <xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
    </xsl:template>

    <xsl:template match="word">
      <xsl:value-of select="concat(position(), ' ', ., '&#10;')"/>
    </xsl:template>
</xsl:stylesheet>

когда это преобразование применяется к следующему документу XML :

<t>Sorry, kid, first-borns really are smarter.
First-borns are typically smarter, while
younger siblings get better grades and
are more outgoing, the researchers say</t>

желаемый, правильный результат получается :

1 Sorry
2 kid
3 first-borns
4 really
5 are
6 smarter.
7 First-borns
8 are
9 typically
10 smarter
11 while
12 younger
13 siblings
14 get
15 better
16 grades
17 and
18 are
19 more
20 outgoing
21 the
22 researchers
23 say

Обратите внимание , что шаблон принимает параметр с именем pDelimiters, в котором можно указать несколько разделителей.

Обновление : Я наконец понял, что ОП хочет с этой проблемой. Вот мое решение, которое снова использует шаблон str-split-to-words для токенизации:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
>

   <xsl:import href="strSplit-to-Words.xsl"/>

   <!-- to be applied upon: test-strSplit-to-Words2.xml -->

   <xsl:output indent="yes" omit-xml-declaration="yes"/>

    <xsl:template match="/">
      <xsl:variable name="vCategories">
        <xsl:call-template name="str-split-to-words">
          <xsl:with-param name="pStr" select=
          "/*/select-criteria/Categories"/>
          <xsl:with-param name="pDelimiters" 
                          select="'|'"/>
        </xsl:call-template>
      </xsl:variable>

      <xsl:apply-templates select="*/pages/Page">
        <xsl:with-param name="pCategories" select=
         "ext:node-set($vCategories)"/>
        <xsl:with-param name="pMatchType" select=
        "*/select-criteria/MatchType"/>
      </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="Page">
     <xsl:param name="pCategories"/>
     <xsl:param name="pMatchType" select="any"/>

     <xsl:variable name="vDecoratedCurrent"
          select="concat('|', @CategoryIds, '|')"/>

     <xsl:variable name="vSelected" select=
      "$pCategories/*
                [$pMatchType = 'any']
                   [contains($vDecoratedCurrent,
                             concat('|', ., '|')
                              )
                   ][1]

       or
        not($pCategories/*[not(contains($vDecoratedCurrent,
                                        concat('|', ., '|')
                                        )
                               )
                          ][1]
            )
       "/>

       <xsl:copy-of select="self::node()[$vSelected]"/>
    </xsl:template>
</xsl:stylesheet>

когда это преобразование применяется к этому документу XML:

<t>
 <select-criteria>
  <Categories>851|849</Categories>
  <MatchType>any</MatchType>
 </select-criteria>
 <pages>
  <Page CategoryIds="848|849|850|851">Page 1</Page>
  <Page CategoryIds="849|850|">Page 2</Page>
  <Page CategoryIds="848|850|">Page 3</Page>
  <Page CategoryIds="848|849|850|851">Page 4</Page>
  <Page CategoryIds="848|850|851">Page 5</Page>
  <Page CategoryIds="848|849|850">Page 6</Page>
 </pages>
</t>

желаемый, правильный результат получается :

<Page CategoryIds="848|849|850|851">Page 1</Page>
<Page CategoryIds="849|850|">Page 2</Page>
<Page CategoryIds="848|849|850|851">Page 4</Page>
<Page CategoryIds="848|850|851">Page 5</Page>
<Page CategoryIds="848|849|850">Page 6</Page>

Когда в документе XML мы указываем :

  <MatchType>all</MatchType>

мы снова получаем желаемый, правильный результат :

<Page CategoryIds="848|849|850|851">Page 1</Page>
<Page CategoryIds="848|849|850|851">Page 4</Page>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...