如何在XSLT中测试空格分隔值的所有排列?

最后发布: 2016-02-16 23:20:56


问题

在测试xsl:when的变量的可能值(用空格分隔)时,我需要合并所有排列。

例如:

<xsl:when test="$var='A B C' 
             or $var='B A C' 
             or $var='...' 
             or ...>
    <xsl:value-of select="X+Z"/>

有一种聪明而简单的方法吗?

xml xslt permutation
回答

我将测试源的所有值是否存在于目标中,并且源和目标包含相同数量的值,而不是尝试生成所有排列。

这在XSLT 1.0中有点冗长,但仍然:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="delimiter" select="' '"/>

<xsl:variable name="source" select="'A B C'"/>
<xsl:variable name="target" select="'B A C'"/>

<xsl:variable name="every-source-in-target">
    <xsl:call-template name="every-source-in-target">
        <xsl:with-param name="source" select="$source"/>
        <xsl:with-param name="target" select="$target"/>
    </xsl:call-template>        
</xsl:variable>

<xsl:variable name="count-source" select="string-length(translate($source, translate($source, $delimiter, ''), ''))" />
<xsl:variable name="count-target" select="string-length(translate($target, translate($target, $delimiter, ''), ''))" /> 

<xsl:template match="/">
    <result>
        <xsl:if test="$every-source-in-target='true' and $count-source=$count-target ">MATCH</xsl:if>
    </result>
</xsl:template>

<xsl:template name="every-source-in-target">
    <xsl:param name="source"/>
    <xsl:param name="target"/>
    <xsl:param name="delimiter" select="' '"/>
    <xsl:variable name="token" select="substring-before(concat($source, $delimiter), $delimiter)" />
    <xsl:choose>
        <xsl:when test="not(contains(concat($delimiter, $target, $delimiter), concat($delimiter, $token, $delimiter)))">
            <xsl:value-of select="false()"/>
        </xsl:when>
        <xsl:when test="contains($source, $delimiter)">
            <!-- recursive call -->
            <xsl:call-template name="every-source-in-target">
                <xsl:with-param name="source" select="substring-after($source, $delimiter)"/>
                <xsl:with-param name="target" select="$target"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="true()"/>
        </xsl:otherwise>
   </xsl:choose>
</xsl:template>

</xsl:stylesheet>

请注意,这里有一些假设:例如, "ABBC""BAAC"将返回匹配。 如果那是不可接受的,那么下一个最好的东西,恕我直言,就是在比较集合之前对值进行排序:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="delimiter" select="' '"/>

<xsl:variable name="source" select="'A B C'"/>
<xsl:variable name="target" select="'B A C'"/>

<xsl:variable name="sorted-source">
    <xsl:call-template name="sort-list">
        <xsl:with-param name="list" select="$source"/>
    </xsl:call-template>        
</xsl:variable>

<xsl:variable name="sorted-target">
    <xsl:call-template name="sort-list">
        <xsl:with-param name="list" select="$target"/>
    </xsl:call-template>        
</xsl:variable>

<xsl:template match="/">
    <result>
        <xsl:if test="$sorted-source=$sorted-target">MATCH</xsl:if>
    </result>
</xsl:template>

<xsl:template name="sort-list">
    <xsl:param name="list"/>
    <!-- tokenize the list -->
    <xsl:variable name="tokens">
        <xsl:call-template name="tokenize">
            <xsl:with-param name="text" select="$list"/>
        </xsl:call-template>        
    </xsl:variable>
    <!-- re-assemble the list in alphabetic order -->
    <xsl:for-each select="exsl:node-set($tokens)/token">
        <xsl:sort select="." data-type="text" order="ascending"/>
        <xsl:value-of select="."/>
        <xsl:if test="position()!=last">
            <xsl:value-of select="$delimiter"/>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

<xsl:template name="tokenize">
    <xsl:param name="text"/>
    <xsl:param name="delimiter" select="' '"/>
        <xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" />
        <xsl:if test="$token">
            <token>
                <xsl:value-of select="$token"/>
            </token>
        </xsl:if>
        <xsl:if test="contains($text, $delimiter)">
            <!-- recursive call -->
            <xsl:call-template name="tokenize">
                <xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
            </xsl:call-template>
        </xsl:if>
</xsl:template>

</xsl:stylesheet>


回答

验证单个字符串是给定字符串集的精确排列的有效解决方案如下所示: https//stackoverflow.com/a/35497256/36305

以下内容验证源XML文档的top元素的子元素是给定字符串集的所有可能排列

规则:

  1. 必须使用单个空格作为分隔符。
  2. 给定字符串集中的任何字符串都不包含空格。
  3. XML文档顶部元素的任何子元素的值必须是规范化的字符串 - 应该只包含内部单个空格,每个空格分隔两个相邻的不包含空格的子字符串。

如果/*/*的字符串值(XML文档的顶部元素的子元素)表示给定字符串集的项的每个可能的排列 - 并且恰好一次,则转换产生字符串“ 有效输入。 ”。

如果不是这样,则转换将终止,并显示诊断消息,说明找到的确切违规。

字符串集的项表示为XML元素的子元素,该元素是全局参数的值,名为prtfData

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="prtfData">
   <v>A</v>
   <v>B</v>
   <v>C</v>
 </xsl:param>

 <xsl:variable name="vData" select="document('')/*/xsl:param[@name = 'prtfData']/*"/>
 <xsl:variable name="vnumItems" select="count($vData)"/>
 <xsl:variable name="vTotalLength" select="string-length($prtfData) + $vnumItems -1"/>

 <xsl:variable name="vnumPermutations">
   <xsl:call-template name="factorial">
     <xsl:with-param name="pN" select="$vnumItems"/>
   </xsl:call-template>
 </xsl:variable>

  <xsl:template match="/*">
    <xsl:if test="not(count(*) = $vnumPermutations)">
      <xsl:message terminate="yes">
         Error: The count of /*/* is not <xsl:value-of select="$vnumPermutations"/>
      </xsl:message>
    </xsl:if>

    <xsl:if test="v[not(string-length() = $vTotalLength)]">
      <xsl:message terminate="yes">
         The input item "<xsl:value-of select="v[not(string-length() = $vTotalLength)]"/>" <xsl:text/>
         <xsl:text/>has string-length not-equal to <xsl:text/>
         <xsl:value-of select="$vTotalLength"/>
      </xsl:message>
    </xsl:if>

    <xsl:variable name="vInput" select="/*/*"/>

    <xsl:for-each select="$vData">
      <xsl:variable name="vPaddedItem" select="concat(' ', ., ' ')"/>
      <xsl:if test="$vInput[not(contains(concat(' ', ., ' '), $vPaddedItem))]">
          <xsl:message terminate="yes">
             Error: The data item "<xsl:value-of select="."/>" isn't contained in <xsl:text/>
             <xsl:value-of select="$vInput[not(contains(concat(' ', ., ' '), $vPaddedItem))]"/>.
          </xsl:message>
      </xsl:if>
    </xsl:for-each>

    <xsl:if test="$vInput[. = preceding-sibling::* or . = following-sibling::*]">
          <xsl:message terminate="yes">
             Error: Some data items are equal. Not all permutations represented.
          </xsl:message>
    </xsl:if>

    Valid input.
  </xsl:template>

  <xsl:template name="factorial">
    <xsl:param name="pN" select="1"/>
    <xsl:param name="pResult" select="1"/>

    <xsl:choose>
        <xsl:when test="not($pN > 0)">
          <xsl:value-of select="$pResult"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="factorial">
            <xsl:with-param name="pN" select="$pN -1"/>
            <xsl:with-param name="pResult" select="$pN * $pResult"/>
          </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

将此转换应用于以下XML文档时:

<t>
  <v>C B A</v>
  <v>C A B</v>
  <v>B A C</v>
  <v>B C A</v>
  <v>A C B</v>
  <v>A B C</v>
</t>

结果是

有效输入。

应用于此XML文档时

<t>
  <v>C B A</v>
  <v> C A B </v>
  <v>B A C</v>
  <v>B C A</v>
  <v>A C B</v>
  <v>A B C</v>
</t>

处理以此消息终止

输入项“CAB”的字符串长度不等于5

应用于此XML文档时

<t>
  <v>C B A</v>
  <v>C C B</v>
  <v>B A C</v>
  <v>B C A</v>
  <v>A C B</v>
  <v>A B C</v>
</t>

处理以此消息终止

错误:CC B中不包含数据项“A”。

应用于此XML文档时

<t>
  <v>C B A</v>
  <v>C C B</v>
  <v>B A C</v>
</t>

处理以此消息终止

错误: /*/*的计数不是6

最后,当对此XML文档应用转换时

<t>
  <v>C B A</v>
  <v>C A B</v>
  <v>C A B</v>
  <v>B A C</v>
  <v>A C B</v>
  <v>A B C</v>
</t>

处理以此消息终止

错误:某些数据项相同。 并非所有排列都代表。


回答

如果所有字符串都是有效名称,那么一个简洁的2.0解决方案是将字符串转换为属性并使用deep-equals():

deep-equal(f:to-atts(source), f:to-atts(target))

<xsl:function f:to-atts as="xs:boolean">
  <xsl:param name="in" as="xs:string">
  <e>
   <xsl:for-each select="tokenize($in, ' ')">
     <xsl:attribute name="." select="0"/>
   </xsl:for-each>
  </e>
</xsl:function>

注意这消除了重复:“AA”将等于“A”。 你还没有说这是否可取。


回答

快速简便的方法是首先定义一个变量,将分隔符放在开头和结尾,如下所示:

<xsl:variable name="tempVar" select="concat(' ',$var,' ')"/>

然后简单地使用

<xsl:when test="contains($tempVar,' A ') and
                contains($tempVar, ' B ') and
                contains($tempVar, ' C ')">
  <xsl:value-of select="X+Z"/>
</xsl:when>

tempVar的开头和结尾放置一个空格只意味着所有值都被空格包围,无论是在开头还是结尾,这样就可以只检查每个值的存在,每边都有一个空格。

你当然可以不使用变量,你只需要在test属性中重复三次concat表达式。


回答

XSLT 2.0解决方案,避免排序,效率更高: O(N)O(N*log(N))

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my">
 <xsl:output method="text"/>
 <xsl:key name="kAllItems" match="x" use="."/>

  <xsl:param name="pData">
   <x>A</x>
   <x>B</x>
   <x>C</x>
 </xsl:param>

  <xsl:template match="v">
    (<xsl:value-of select="."/>) ==> <xsl:sequence 
                                          select="my:exactPremutation(., $pData)"/>
  </xsl:template>

  <xsl:function name="my:exactPremutation" as="xs:boolean">
    <xsl:param name="pInput" as="xs:string"/>
    <xsl:param name="pDataItems" as="document-node()"/>

    <xsl:variable name="vNumDataItems" select="count($pDataItems/*)"/>

    <xsl:variable name="vMatches" as="xs:integer*">
      <xsl:for-each-group select="tokenize($pInput, '\s+')" group-by=".">
        <xsl:variable name="vMatchedDataItem" 
                      select="key('kAllItems', current-grouping-key(), $pDataItems)"/>

          <xsl:sequence select="1[not(current-group()[2]) and $vMatchedDataItem]"/>
          <xsl:sequence select="($vNumDataItems +1)[not($vMatchedDataItem)]"/>
      </xsl:for-each-group>
    </xsl:variable>

    <xsl:sequence select="sum($vMatches) eq $vNumDataItems"/>
  </xsl:function>
</xsl:stylesheet>

将此转换应用于以下XML文档时

<t>
  <!-- Correct -->
   <v>A B C</v>
   <v>A C B</v>
   <v>B A C</v>
   <v>B C A</v>
   <v>C A B</v>
   <v>C B A</v>
  <!-- Incorrect -->
   <v>C A C</v>
   <v>A B</v>
   <v></v>
   <v>A B C D</v>   
</t>

产生了想要的正确结果

 (ABC) ==> true (ACB) ==> true (BAC) ==> true (BCA) ==> true (CAB) ==> true (CBA) ==> true (CAC) ==> false (AB) ==> false () ==> false (ABCD) ==> false 

注意 :线性效率估计假定XSLT处理器使用有效(例如基于散列表)的分组实现。


回答

这是一个简单的XPath 1.0解决方案:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:variable name="x" select="'B A C'"/>
  <xsl:variable name="y" select="'A B C'"/>

  <xsl:template match="/">
    <xsl:choose>
      <xsl:when test="string-length($x) = string-length($y)
                      and translate($x, $y, '') = ''">
        <xsl:message>Same</xsl:message>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message>Different</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

请注意,这假设置换了唯一的单字符值。