简体   繁体   English

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

[英]How to test all permutations of space-separated values in XSLT?

I need to incorporate all permutations when testing the possible values (separated by a space) for a variable in xsl:when . 在测试xsl:when的变量的可能值(用空格分隔)时,我需要合并所有排列。

For example: 例如:

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

Is there a smart and simple way of doing it? 有一种聪明而简单的方法吗?

Instead of trying to generate all permutations, I would test if all values of the source are present in the target, and that both source and target contain the same number of values. 我将测试源的所有值是否存在于目标中,并且源和目标包含相同数量的值,而不是尝试生成所有排列。

This is a bit verbose in XSLT 1.0, but still: 这在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>

Note that some assumptions are being made here: for example, "ABBC" and "BAAC" will return a match. 请注意,这里有一些假设:例如, "ABBC""BAAC"将返回匹配。 If that's not acceptable, then the next best thing, IMHO, would be to sort the values before comparing the sets: 如果那是不可接受的,那么下一个最好的东西,恕我直言,就是在比较集合之前对值进行排序:

XSLT 1.0 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>

An efficient solution for verifying that a single string is an exact permutation of a given set of strings is presented here: https://stackoverflow.com/a/35497256/36305 验证单个字符串是给定字符串集的精确排列的有效解决方案如下所示: https//stackoverflow.com/a/35497256/36305

The following verifies that the children of the top element of the source XML document are all possible permutations of a given set of strings . 以下内容验证源XML文档的top元素的子元素是给定字符串集的所有可能排列

Rules: 规则:

  1. A single space must be used as delimiter. 必须使用单个空格作为分隔符。
  2. Any string in the given string-set doesn't contain a space. 给定字符串集中的任何字符串都不包含空格。
  3. The value of any child of the top element of the XML document must be a normalized string -- should only contain inner single spaces, each delimiting two adjacent non-space-containing substrings. XML文档顶部元素的任何子元素的值必须是规范化的字符串 - 应该只包含内部单个空格,每个空格分隔两个相邻的不包含空格的子字符串。

The transformation produces the string " Valid input. " if the string values of /*/* (the children of the top element of the XML document) represent every possible permutation of the items of the given string-set -- and exactly once. 如果/*/*的字符串值(XML文档的顶部元素的子元素)表示给定字符串集的项的每个可能的排列 - 并且恰好一次,则转换产生字符串“ 有效输入。 ”。

If this is not so, the transformation terminates with diagnostic messages explaining the exact violation found. 如果不是这样,则转换将终止,并显示诊断消息,说明找到的确切违规。

The items of the string-set are represented as children of an XML element that is the value of the global parameter, named prtfData 字符串集的项表示为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>

When this transformation is applied on the following XML document: 将此转换应用于以下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>

the result is : 结果是

Valid input. 有效输入。

When applied on this XML document : 应用于此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>

the processing is terminated with this message : 处理以此消息终止

The input item " CAB " has string-length not-equal to 5 输入项“CAB”的字符串长度不等于5

When applied on this XML document : 应用于此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>

the processing is terminated with this message : 处理以此消息终止

Error: The data item "A" isn't contained in CC B. 错误:CC B中不包含数据项“A”。

When applied on this XML document : 应用于此XML文档时

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

the processing is terminated with this message : 处理以此消息终止

Error: The count of /*/* is not 6 错误: /*/*的计数不是6

Finally, when the transformation is applied on this XML document : 最后,当对此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>

the processing is terminated with this message : 处理以此消息终止

Error: Some data items are equal. 错误:某些数据项相同。 Not all permutations represented. 并非所有排列都代表。

If all the strings are valid names, then a neat 2.0 solution would be to turn the strings into attributes and use deep-equals(): 如果所有字符串都是有效名称,那么一个简洁的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>

Note this eliminates duplicates: "AA" will be equal to "A". 注意这消除了重复:“AA”将等于“A”。 You haven't said whether this is desirable. 你还没有说这是否可取。

The quick & easy way of doing this is to first define a variable that puts your delimiter at the beginning and end like this: 快速简便的方法是首先定义一个变量,将分隔符放在开头和结尾,如下所示:

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

then simply use 然后简单地使用

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

Putting a space at the beginning & end of tempVar just means that all values are surrounded by spaces whether at the beginning/end or not, making it possible to just check for the presence of each value with a space at each side. tempVar的开头和结尾放置一个空格只意味着所有值都被空格包围,无论是在开头还是结尾,这样就可以只检查每个值的存在,每边都有一个空格。

You could do without the variable of course, you'd just need to repeat the concat expression three times in your test attribute. 你当然可以不使用变量,你只需要在test属性中重复三次concat表达式。

An XSLT 2.0 solution that avoids sorting and is more efficient: O(N) vs. O(N*log(N)) 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>

When this transformation is applied on the following XML document : 将此转换应用于以下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>

the wanted, correct result is produced : 产生了想要的正确结果

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

Note : The linear efficiency estimation assumes that an efficient (such as hash-table based) implementation of grouping is used by the XSLT processor. 注意 :线性效率估计假定XSLT处理器使用有效(例如基于散列表)的分组实现。

Here's a simple XPath 1.0 solution: 这是一个简单的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>

Note that this assumes unique, single-char values being permuted. 请注意,这假设置换了唯一的单字符值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM