XSL:根据属性计数和匹配创建组

Posted

技术标签:

【中文标题】XSL:根据属性计数和匹配创建组【英文标题】:XSL: Create group based on attribute count and match 【发布时间】:2019-08-31 09:06:36 【问题描述】:

当属性的计数 = X 和基于属性值的第二组时,我试图插入一个容器。这两个属性不相关。

使用 XSLT-V1

我想首先根据属性的值进行分组。 IE。任何时候 ID=01 都会创建一个组。然后我想在 count = X 时插入一个新的属性/容器。

我可以根据属性值进行分组,但不确定如何确定计数和添加新容器。

我的 XML 如下所示:

<Items>
  <Details>
    <ID>01</ID>
    <Name>Name for 01</Name>
    <Owner>User1</Owner>
    <Rev>01-A</Rev>
    <Rev_Owner>User2</Rev_Owner>
    <Rev_Code>US</Rev_Code>
  </Details>
  <Details>
    <ID>01</ID>
    <Name>Name for 01</Name>
    <Owner>User1</Owner>
    <Rev>01-B</Rev>
    <Rev_Owner>User3</Rev_Owner>
    <Rev_Code>CN</Rev_Code>
  </Details>
  <Details>
    <ID>02</ID>
    <Name>Name for 02</Name>
    <Owner>User1</Owner>
    <Rev>02-A</Rev>
    <Rev_Owner>User4</Rev_Owner>
    <Rev_Code>MX</Rev_Code>
  </Details>
  <Details>
    <ID>03</ID>
    <Name>Name for 03</Name>
    <Owner>User1</Owner>
    <Rev>03-A</Rev>
    <Rev_Owner>User5</Rev_Owner>
    <Rev_Code>CA</Rev_Code>
  </Details>
  <Details>
    <ID>02</ID>
    <Name>Name for 02</Name>
    <Owner>User1</Owner>
    <Rev>02-B</Rev>
    <Rev_Owner>User5</Rev_Owner>
    <Rev_Code>AU</Rev_Code>
  </Details>
  <Details>
    <ID>01</ID>
    <Name>Name for 01</Name>
    <Owner>User1</Owner>
    <Rev>02-C</Rev>
    <Rev_Owner>User5</Rev_Owner>
    <Rev_Code>JP</Rev_Code>
  </Details>
</Items>

我有以下 XSL,它为项目 ID 创建预期组


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

  <xsl:key name="ItemGroup" match="Details" use="ID"/>

  <xsl:template match="/*">
    <Items>
      <xsl:apply-templates/>
    </Items>
  </xsl:template>

  <xsl:template match="Details[generate-id()=generate-id(key('ItemGroup',ID)[1])]">
    <ItemID name="ID">
      <xsl:copy-of select="key('ItemGroup',ID)"/>
    </ItemID>
  </xsl:template>
  <xsl:template match="Details[not(generate-id()=generate-id(key('ItemGroup',ID)[1]))]"/>
</xsl:stylesheet>

aboe XSL 的输出:

<Items>
  <ItemID name="01">
      <Details>
         <ID>01</ID>
         <Name>Name for 01</Name>
         <Owner>User1</Owner>
         <Rev>01-A</Rev>
         <Rev_Owner>User2</Rev_Owner>
         <Rev_Code>US</Rev_Code>
      </Details>
      <Details>
         <ID>01</ID>
         <Name>Name for 01</Name>
         <Owner>User1</Owner>
         <Rev>01-B</Rev>
         <Rev_Owner>User3</Rev_Owner>
         <Rev_Code>CN</Rev_Code>
      </Details>
      <Details>
         <ID>01</ID>
         <Name>Name for 01</Name>
         <Owner>User1</Owner>
         <Rev>02-C</Rev>
         <Rev_Owner>User5</Rev_Owner>
         <Rev_Code>JP</Rev_Code>
      </Details>
   </ItemID>

  <ItemID name="02">
      <Details>
         <ID>02</ID>
         <Name>Name for 02</Name>
         <Owner>User1</Owner>
         <Rev>02-A</Rev>
         <Rev_Owner>User4</Rev_Owner>
         <Rev_Code>MX</Rev_Code>
      </Details>
      <Details>
         <ID>02</ID>
         <Name>Name for 02</Name>
         <Owner>User1</Owner>
         <Rev>02-B</Rev>
         <Rev_Owner>User5</Rev_Owner>
         <Rev_Code>AU</Rev_Code>
      </Details>
   </ItemID>
  <ItemID name="03">
      <Details>
         <ID>03</ID>
         <Name>Name for 03</Name>
         <Owner>User1</Owner>
         <Rev>03-A</Rev>
         <Rev_Owner>User5</Rev_Owner>
         <Rev_Code>CA</Rev_Code>
      </Details>
   </ItemID>


</Items>

我现在想为“详细信息”的计数添加一个变量 = 3,例如(它实际上在 1,000-5,000 之间),然后期望低于输出

<Items>
  <Split>
      <ItemID name="01">
      <Details>
        <ID>01</ID>
        <Name>Name for 01</Name>
        <Owner>User1</Owner>
        <Rev>01-A</Rev>
        <Rev_Owner>User2</Rev_Owner>
        <Rev_Code>US</Rev_Code>
      </Details>
      <Details>
        <ID>01</ID>
        <Name>Name for 01</Name>
        <Owner>User1</Owner>
        <Rev>01-B</Rev>
        <Rev_Owner>User3</Rev_Owner>
        <Rev_Code>CN</Rev_Code>
      </Details>
      <Details>
        <ID>01</ID>
        <Name>Name for 01</Name>
        <Owner>User1</Owner>
        <Rev>02-C</Rev>
        <Rev_Owner>User5</Rev_Owner>
        <Rev_Code>JP</Rev_Code>
      </Details>
      </ItemID>
  </Split>
  <Split>
  <ItemID name="02">
    <Details>
      <ID>02</ID>
      <Name>Name for 02</Name>
      <Owner>User1</Owner>
      <Rev>02-A</Rev>
      <Rev_Owner>User4</Rev_Owner>
      <Rev_Code>MX</Rev_Code>
    </Details>
    <Details>
      <ID>02</ID>
      <Name>Name for 02</Name>
      <Owner>User1</Owner>
      <Rev>02-B</Rev>
      <Rev_Owner>User5</Rev_Owner>
      <Rev_Code>AU</Rev_Code>
    </Details>
  </ItemID>
  <ItemID name="03">
    <Details>
      <ID>03</ID>
      <Name>Name for 03</Name>
      <Owner>User1</Owner>
      <Rev>03-A</Rev>
      <Rev_Owner>User5</Rev_Owner>
      <Rev_Code>CA</Rev_Code>
    </Details>
  </ItemID>
  </Split>
  <Split>
     continued....

</Items>

非常感谢!

【问题讨论】:

Xpath: how to select an option based on its text not value property?的可能重复 您能否编辑您的问题以显示您到目前为止所获得的 XSLT。谢谢! 第一步,在变量中执行group-by="ID",然后在第二步中对第一步的结果使用位置分组。如果您需要进一步的帮助,请说明您使用/可以使用的 XSLT 处理器和/或 XSLT 版本。 【参考方案1】:

假设至少 XSLT 2 可以使用两个分组步骤,第一个是对 ID 子元素的简单 group-by,第二个然后对第一步的结果进行位置分组:

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

   <xsl:param name="split-size" as="xs:integer" select="3"/>

   <xsl:output indent="yes"/>

   <xsl:template match="Items">
       <xsl:copy>
           <xsl:variable name="groups">
               <xsl:for-each-group select="Details" group-by="ID">
                   <ItemID name="current-grouping-key()">
                       <xsl:copy-of select="current-group()"/>
                   </ItemID>
               </xsl:for-each-group>
           </xsl:variable>
           <xsl:for-each-group select="$groups/ItemID/Details" group-adjacent="(position() - 1) idiv $split-size">
               <split>
                   <xsl:copy-of select="current-group()/.."/>
               </split>
           </xsl:for-each-group>
       </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bFN1y9n

使用 XSLT 1,要执行两步转换,您需要使用 exsl:node-set 或类似方法(取决于使用的特定 XSLT 处理器)将结果树片段从第一个分组步骤转换回节点集,以便您可以选择和导航它;此外,位置“分组”或拆分需要沿兄弟轴进行一些选择:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    exclude-result-prefixes="exsl"
    version="1.0">

   <xsl:param name="split-size" select="3"/>

   <xsl:key name="group" match="Details" use="ID"/>

   <xsl:output indent="yes"/>
   <xsl:strip-space elements="*"/>

   <xsl:template match="Items">
       <xsl:copy>
           <xsl:variable name="groups">
               <xsl:for-each select="Details[generate-id() = generate-id(key('group', ID)[1])]">
                   <ItemID name="ID">
                       <xsl:copy-of select="key('group', ID)"/>
                   </ItemID>
               </xsl:for-each>
           </xsl:variable>
           <xsl:variable name="Details" select="exsl:node-set($groups)/ItemID/Details"/>
           <xsl:for-each select="$Details[position() mod $split-size = 1]">
               <split>
                   <xsl:copy-of select="(. | (following-sibling::Details | ../following-sibling::ItemID/Details)[position() &lt; $split-size])/.."/>
               </split>
           </xsl:for-each>
       </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bFN1y9n/2

【讨论】:

@Tim C 我已经用我目前拥有的 XSLT 编辑了这个问题 感谢您的回复。我需要使用 XSLT 1- 我很抱歉没有在最初的帖子中说明。 这正是我需要的,但使用 XSLT 1.0。非常感谢您的回复! @Chris,我添加了一个 XSLT 1 示例,由于使用了以下兄弟轴,因此在大输入上可能表现不佳。【参考方案2】:

除了您现有的密钥之外,我认为您还需要另一个密钥(将首先使用)来按 ID = 01 对详细信息进行分组

<xsl:key name="ItemGroupOne" match="Details" use="ID = '01'"/>

试试这个 XSLT

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

  <xsl:key name="ItemGroupOne" match="Details" use="ID = '01'"/>
  <xsl:key name="ItemGroup" match="Details" use="ID"/>

  <xsl:template match="/*">
    <Items>
      <xsl:apply-templates/>
    </Items>
  </xsl:template>

  <xsl:template match="Details[generate-id()=generate-id(key('ItemGroupOne',ID = '01')[1])]">
    <Split>
      <xsl:apply-templates select="key('ItemGroupOne',ID = '01')" mode="items" />
    </Split>
  </xsl:template>

  <xsl:template match="Details[generate-id()=generate-id(key('ItemGroup',ID)[1])]" mode="items">
    <ItemID name="ID">
      <xsl:copy-of select="key('ItemGroup',ID)"/>
    </ItemID>    
  </xsl:template>

  <xsl:template match="Details"/>

  <xsl:template match="Details" mode="items"/>
</xsl:stylesheet>

这里使用mode是为了避免模板冲突。

另外请注意,对于忽略Details 的最终模板,您不需要在此处的条件中使用not 逻辑,因为将元素与条件匹配的模板将比仅匹配的模板具有更高的优先级没有条件的元素。

在这里试试:http://xsltfiddle.liberty-development.net/gWvjQfr

或者,如果您想删除“模式”的使用和忽略元素的模板,也许可以这样写......

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

  <xsl:key name="ItemGroupOne" match="Details" use="ID = '01'"/>
  <xsl:key name="ItemGroup" match="Details" use="ID"/>

  <xsl:template match="/*">
    <Items>
      <xsl:apply-templates select="Details[generate-id()=generate-id(key('ItemGroupOne',ID = '01')[1])]" />
    </Items>
  </xsl:template>

  <xsl:template match="Details">
    <Split>
      <xsl:for-each select="key('ItemGroupOne',ID = '01')[generate-id()=generate-id(key('ItemGroup',ID)[1])]">
        <ItemID name="ID">
          <xsl:copy-of select="key('ItemGroup',ID)"/>
        </ItemID>    
      </xsl:for-each>
    </Split>
  </xsl:template>
</xsl:stylesheet>

【讨论】:

以上是关于XSL:根据属性计数和匹配创建组的主要内容,如果未能解决你的问题,请参考以下文章

两个值匹配pandas时的累计计数

匹配行的计数并从 hive 中的每个计数组中选择 30

唯一排序节点之前的 XSL 计数

NSFetchRequest 分组并按 dateField 计数

使用 xsl:number 递减变量计数器

两个值匹配 pandas 时的累积计数