选择拥有列表中所有/任何值的节点

时间:2010-08-13 07:48:28

标签: xml xslt xpath

我希望在一个元素中提供分类ID的分隔列表...

<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>中所示的ID)。

此外,ID不一定按照它们出现在CategoryIds属性中的顺序给出,并且该属性中的字符串不应包含确切的<Categories>字符串。

使用XSLT / XPath 1.0是否可以这样?我知道2.0有一个令人满意的标记功能,但不幸的是我正在使用的CMS还不支持2.0。

非常感谢任何帮助!!

2 个答案:

答案 0 :(得分:1)

此样式表:

<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”模式中首次失败时失败“。

编辑:只是为了好玩,使用Dimitre的输入:

<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表达式,它会更简洁......

答案 1 :(得分:0)

虽然从这个问题中根本不清楚你想做什么,但这里是答案的一部分:

  

这样的事情可以使用   XSLT / XPath 1.0?我知道2.0有一个   令牌化功能   完美的,但不幸的是   我正在使用的CMS还没有   支持2.0。

多年来,在XSLT 1.0中完成了标记化。虽然可以编写自己的递归teplate来标记字符串,但最好记住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的参数,其中可以指定多个分隔符。

更新:我终于明白OP想要解决这个问题了。这是我的解决方案,它再次使用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>
相关问题