xslt2+ 如何将组与任何匹配的元素组合并删除元素的重复项

Posted

技术标签:

【中文标题】xslt2+ 如何将组与任何匹配的元素组合并删除元素的重复项【英文标题】:xslt2+ How to combine groups with any matching elements and remove duplicates of elements 【发布时间】:2022-01-09 15:59:26 【问题描述】:

这是我将组与任何匹配元素组合并删除重复元素的解决方案。 例如,我画了一个简单的输入和输出应该是什么。如果两个组具有相同的元素,则将这些组合并为一个具有除重复之外的所有元素的组。 有其他方法吗?

<xsl:variable name="in">
   <g>
      <i>8</i>
      <i>2</i>
   </g>
   <g>
      <i>2</i>
      <i>4</i>
   </g>
   <g>
      <i>4</i>
      <i>5</i>
   </g>
   <g>
      <i>6</i>
      <i>7</i>
   </g>
</xsl:variable>

<xsl:template match="/">
 <out>
  <xsl:for-each-group select="$in/g/i" group-by="k2:iin(.,$in)[1]">
    <g>
    <xsl:for-each-group select="current-group()" group-by=".">
      <xsl:copy-of select="current-group()[1]"/>
    </xsl:for-each-group>
    </g>
  </xsl:for-each-group>
 </out>
</xsl:template>

<xsl:function name="k2:iin">
 <xsl:param name="i"/> <!-- current catch -->
 <xsl:param name="in"/> <!-- const catch scope -->
 <xsl:sequence select="
let $xi:=$in/g[i = $i]/i return
if($xi[not(. = $i)])then
 k2:iin($xi,$in) else
$xi
 "/>
</xsl:function>


<out>
   <g>
      <i>8</i>
      <i>2</i>
      <i>4</i>
      <i>5</i>
   </g>
   <g>
      <i>6</i>
      <i>7</i>
   </g>
</out>

【问题讨论】:

$in/g[i = $i] 这样的比较通常建议您可以使用密钥&lt;xsl:key name="by-i" match="g" use="i"/&gt; 谢谢。是否需要编辑表达式以调用 key() 以使用 你可以用key函数来代替比较,也就是说,你可以用key('by-id', $i, $in)/i代替$in/g[i = $i]/i &lt;xsl:copy-of select="current-group()[1]"/&gt;可以简单地写成&lt;xsl:copy-of select="."/&gt;,因为当前组中的第一项是for-each-group中的上下文项。 【参考方案1】:

除了 cmets 中的建议外,您可以将内部 xsl:for-each-group 替换为

<xsl:for-each select="distinct-values(current-group())">
   <i><xsl:value-of select="."/></i>
</xsl:for-each>

虽然distinct-values 不保证保留订单,但xsl:for-each-group 可以。所以你的方法没有真正的好处(但你确实要求替代方案......)

【讨论】:

这里不仅顺序受到影响,还必须手动重新创建元素【参考方案2】:

正如问题所说的xslt2+ 我想到了一种紧凑或优雅的 XSLT 方法,看来您实际上并不需要使用分组,但可以只存储整数数组的序列。然而,不知何故,尝试使用fold-left 编写递归处理方法并没有真正给出紧凑或优雅的方法,我发布它只是为了展示尝试:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all"
  expand-text="yes">

  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="data">
    <xsl:copy>
      <xsl:variable name="i-value-groups" 
        select="fold-left(g, (), function($value, $g) 
                  let $i-values := distinct-values($g/i/xs:integer(.)),
                      $group := $value[?* = $i-values],
                      $group-pos := for $pos in 1 to count($value) return $pos[exists($value[?* = $i-values])]
                  return 
                    if (exists($group))
                    then (subsequence($value, 1, $group-pos - 1), array  distinct-values(($group?*, $i-values)) , subsequence($value, $group-pos + 1))
                    else ($value, array  $i-values )                  
                 
                )"/>
      <xsl:for-each select="$i-value-groups">
        <g>
          <xsl:for-each select="?*">
            <i>.</i>
          </xsl:for-each>
        </g>
      </xsl:for-each>      
    </xsl:copy>
  </xsl:template>
  
  <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>

这假设输入样本像

<data>
   <g>
      <i>8</i>
      <i>2</i>
   </g>
   <g>
      <i>2</i>
      <i>4</i>
   </g>
   <g>
      <i>4</i>
      <i>5</i>
   </g>
   <g>
      <i>6</i>
      <i>7</i>
   </g>
</data>

当然,如果需要“分组”g 元素而不是普通整数,您可以使用相同的 fold-left 方法返回数组序列,或者在以下示例中,返回一个序列地图数量(在 XSLT/XPath 4 包裹或记录中):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="#all"
  expand-text="yes">

  <xsl:output method="xml" indent="yes"/>
  
  <xsl:function name="mf:collect-parcels" as="map(xs:string, element(g)*)*">
    <xsl:param name="input-sequence" as="element(g)*"/>
    <xsl:sequence
      select="fold-left(
                $input-sequence,
                (), 
                function($parcel-ac, $g) 
                  let $i-elements := $g/i,
                      $matching-parcel-pos := 
                        for $pos in 1 to count($parcel-ac)
                        return $pos[exists($parcel-ac[$pos][?value/i = $i-elements])],
                      $matching-parcel := $parcel-ac[$matching-parcel-pos]
                  return 
                    if (exists($matching-parcel))
                    then (subsequence($parcel-ac, 1, $matching-parcel-pos - 1), map:entry('value', ($matching-parcel?value, $g)), subsequence($parcel-ac, $matching-parcel-pos + 1))
                    else ($parcel-ac, map:entry('value', $g))                  
                
              )"/>
  </xsl:function>
  
  <xsl:template match="data">
    <xsl:copy>
      <xsl:for-each select="mf:collect-parcels(g)">
        <g>
          <xsl:for-each-group select="?value/i" group-by=".">
            <xsl:copy-of select="."/>
          </xsl:for-each-group>
        </g>
      </xsl:for-each>      
    </xsl:copy>
  </xsl:template>
  
  <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>

【讨论】:

【参考方案3】:

我意识到可以上升到Groups级别(不需要Items的序列)

<xsl:template match="/">
 <out>
  <xsl:for-each-group
  select="$in/g"
  group-by="k2:g(.)[1]"
  >
    <g>
    <xsl:for-each-group
    select="current-group()/i"
    group-by="."
    >
      <xsl:copy-of select="."/>
    </xsl:for-each-group>
    </g>
  </xsl:for-each-group>
 </out>
</xsl:template>

<xsl:function name="k2:g">
 <xsl:param name="g"/>
 <xsl:sequence select="
let $xg:=$g[1]/../g[i[. = $g/i]] return
if(count($xg) gt count($g))then k2:g($xg) else
$xg
 "/>
</xsl:function>

为了尽量避免分组的开销,您可以先收集所有消失的组。 (当然,要为解决方案控制的这种复杂性买单)

<xsl:template match="/">
 <xsl:variable name="gmap" select="k2:gmap(1,$in/g[1],map)"/>
 <xsl:message select="$gmap"/>
 <out>
  <xsl:comment select="'+gmap()'"/>
  <xsl:for-each-group
  select="$in/g"
  group-by="
let $x:=(1 + count(preceding-sibling::g)) return
($gmap($x),$x)[1]
  "
  >
    <g>
    <xsl:for-each-group
    select="current-group()/i"
    group-by="."
    >
      <xsl:copy-of select="."/>
    </xsl:for-each-group>
    </g>
  </xsl:for-each-group>
 </out>
</xsl:template>

<xsl:function name="k2:g">
 <xsl:param name="g"/>
 <xsl:sequence select="
let $xg:=$g[1]/../g[i[. = $g/i]] return
if(count($xg) gt count($g))then k2:g($xg) else
$xg
 "/>
</xsl:function>

<xsl:function name="k2:gmap">
 <xsl:param name="gpos"/>
 <xsl:param name="g"/>
 <xsl:param name="gmap"/>
 <xsl:sequence select="
if(empty($g))then $gmap else
let $xg:=if($gmap($gpos))then () else k2:g($g) return
k2:gmap(
 $gpos + 1
,$g/following-sibling::g[1]
,if(empty($xg[2]))then $gmap else
 map:merge(
  ($gmap
  ,for $x in subsequence($xg,2) return
   map:entry(1 + count($x/preceding-sibling::g),$gpos)
  )
 )
)
 "/>
</xsl:function>

【讨论】:

以上是关于xslt2+ 如何将组与任何匹配的元素组合并删除元素的重复项的主要内容,如果未能解决你的问题,请参考以下文章

将每个元组与元组列表中的下一个合并

元组tuple

元组类型

Python语法学习第三天--元组

Python3 元组

python