3

我有多种类型的 xml 消息,我需要通过将多个节点分组到同一个父节点下来“压缩”(相同的父节点意味着它们共享相同的节点名称,并且声明的每个属性也是相等的)。例如:

<TopLevel CodeTL="Something">
    <Ratings>
          <Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
              <RatingByNumber Code="X" Rating="10" Number="1">
              <RatingByNumber Code="X" Rating="19" Number="2">
          </Rating>
    </Ratings>
</TopLevel>
    <TopLevel CodeTL="Something">
    <Ratings>
          <Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
              <RatingByNumber Code="X" Rating="10" Number="1">
              <RatingByNumber Code="X" Rating="19" Number="2">
          </Rating>
    </Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
    <Ratings>
          <Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
              <RatingByNumber Code="X" Rating="10" Number="1">
              <RatingByNumber Code="X" Rating="19" Number="2">
          </Rating>
    </Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
    <Ratings>
          <Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
              <RatingByNumber Code="X" Rating="30" Number="3">
              <RatingByNumber Code="X" Rating="39" Number="4">
          </Rating>
    </Ratings>
</TopLevel>

请注意它们如何共享相同的 CodeTL 属性,最后 2 个共享相同的 CodeA、Start 和 End 属性,所以我需要使用 xslt 生成以下输出

<TopLevel CodeTL="Something">
    <Ratings>
          <Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
              <RatingByNumber Code="X" Rating="10" Number="1">
              <RatingByNumber Code="X" Rating="19" Number="2">
          </Rating>
          <Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
              <RatingByNumber Code="X" Rating="10" Number="1">
              <RatingByNumber Code="X" Rating="19" Number="2">
          </Rating>
          <Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
              <RatingByNumber Code="X" Rating="10" Number="1">
              <RatingByNumber Code="X" Rating="19" Number="2">
              <RatingByNumber Code="X" Rating="30" Number="3">
              <RatingByNumber Code="X" Rating="39" Number="4">
          </Rating>
    </Ratings>
</TopLevel>

这更清洁,并且根据使用它的应用程序,它可能会节省处理时间并节省空间。

我遇到的问题是我有不同类型的 xml 消息,它们具有不同的节点名称和属性(以及属性数量),但它们都共享我在这里展示的相同结构。这将是一种很好的通用方式来处理所有这些,但我会感谢 XSLT 来转换我提供的示例,这样我就可以为我需要发送的每条 xml 消息创建自定义代码。

4

2 回答 2

1

这个 XSLT 1.0 样式表产生了期望的结果:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="byCodeTL" match="TopLevel" use="@CodeTL"/>
    <xsl:key name="byAttrs" match="Rating" 
             use="concat(../../@CodeTL, '|', @CodeA, '|', @Start, '|', @End)"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="TopLevel[generate-id()=
                                  generate-id(key('byCodeTL', @CodeTL)[1])]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <Ratings>
                <xsl:apply-templates 
                        select="key('byCodeTL', @CodeTL)/Ratings/*"/>
            </Ratings>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Rating[generate-id()=
                                generate-id(key('byAttrs', 
            concat(../../@CodeTL, '|', @CodeA, '|', @Start, '|', @End))[1])]">
        <xsl:copy>
            <xsl:apply-templates select="@*|key('byAttrs', 
                concat(../../@CodeTL, '|', @CodeA, '|', @Start, '|', @End))/*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="TopLevel"/>
    <xsl:template match="Rating"/>
</xsl:stylesheet>

所有TopLevel元素都按其CodeTL属性分组。所有元素都按其属性及其对应Rating的属性的组合进行分组。CodeTLTopLevel

于 2012-07-09T22:42:35.073 回答
1

这个通用的 XSLT 2.0 转换

<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" exclude-result-prefixes="xs my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
     <t>
       <xsl:sequence select="my:grouping(*)"/>
     </t>
 </xsl:template>

 <xsl:function name="my:grouping" as="node()*">
   <xsl:param name="pElems" as="element()*"/>

   <xsl:if test="$pElems">
       <xsl:for-each-group select="$pElems" group-by="my:signature(.)">
         <xsl:copy>
          <xsl:copy-of select="@*"/>

            <xsl:sequence select="my:grouping(current-group()/*)"/>
         </xsl:copy>
       </xsl:for-each-group>
   </xsl:if>
 </xsl:function>

 <xsl:function name="my:signature" as="xs:string">
  <xsl:param name="pElem" as="element()"/>

  <xsl:variable name="vsignAttribs" as="xs:string*">
      <xsl:for-each select="$pElem/@*">
       <xsl:sort select="name()"/>

       <xsl:value-of select="concat(name(), '=', .,'|')"/>
      </xsl:for-each>
  </xsl:variable>

  <xsl:sequence select=
  "concat(name($pElem), '|', string-join($vsignAttribs, ''))"/>
 </xsl:function>
</xsl:stylesheet>

当应用于提供的 XML 时(包装到单个顶部元素中以成为格式良好的 XML 文档):

<t>
    <TopLevel CodeTL="Something">
        <Ratings>
              <Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
                  <RatingByNumber Code="X" Rating="10" Number="1"/>
                  <RatingByNumber Code="X" Rating="19" Number="2"/>
              </Rating>
        </Ratings>
    </TopLevel>
        <TopLevel CodeTL="Something">
        <Ratings>
              <Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
                  <RatingByNumber Code="X" Rating="10" Number="1"/>
                  <RatingByNumber Code="X" Rating="19" Number="2"/>
              </Rating>
        </Ratings>
    </TopLevel>
    <TopLevel CodeTL="Something">
        <Ratings>
              <Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
                  <RatingByNumber Code="X" Rating="10" Number="1"/>
                  <RatingByNumber Code="X" Rating="19" Number="2"/>
              </Rating>
        </Ratings>
    </TopLevel>
    <TopLevel CodeTL="Something">
        <Ratings>
              <Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
                  <RatingByNumber Code="X" Rating="30" Number="3"/>
                  <RatingByNumber Code="X" Rating="39" Number="4"/>
              </Rating>
        </Ratings>
    </TopLevel>
</t>

产生想要的正确结果

<t>
   <TopLevel CodeTL="Something">
      <Ratings>
         <Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
            <RatingByNumber Code="X" Rating="10" Number="1"/>
            <RatingByNumber Code="X" Rating="19" Number="2"/>
         </Rating>
         <Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
            <RatingByNumber Code="X" Rating="10" Number="1"/>
            <RatingByNumber Code="X" Rating="19" Number="2"/>
         </Rating>
         <Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
            <RatingByNumber Code="X" Rating="10" Number="1"/>
            <RatingByNumber Code="X" Rating="19" Number="2"/>
            <RatingByNumber Code="X" Rating="30" Number="3"/>
            <RatingByNumber Code="X" Rating="39" Number="4"/>
         </Rating>
      </Ratings>
   </TopLevel>
</t>

说明

  1. 执行的分组在函数中实现my:grouping()并且是递归的。

  2. 顶部元素在其级别上是单一的,除了自身的浅拷贝之外不需要任何其他分组。然后在这个浅拷贝的主体内部,较低级别的分组由函数执行my:grouping()

  3. 该函数my:grouping()有一个参数,它是上一级组中所有元素的所有子元素。它返回当前级别的所有组。

  4. 作为参数传递给函数的元素序列根据它们的签名进行分组——元素名称与其属性的所有名称-值对及其对应值的串联,并使用适当的分隔符将它们分开。元素的签名由函数生成my:signature()


二、通用 XSLT 1.0 解决方案

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

         <xsl:variable name="vrtfPass1">
          <xsl:apply-templates select="/*"/>
         </xsl:variable>

         <xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)"/>

         <xsl:template match="/">
          <xsl:apply-templates select="$vPass1/*" mode="pass2"/>
         </xsl:template>

         <xsl:template match="/*" mode="pass2">
             <xsl:copy>
               <xsl:call-template name="my:grouping">
                <xsl:with-param name="pElems" select="*"/>
               </xsl:call-template>
             </xsl:copy>
         </xsl:template>

         <xsl:template name="my:grouping">
           <xsl:param name="pElems" select="/.."/>

           <xsl:if test="$pElems">
             <xsl:for-each select="$pElems">
              <xsl:variable name="vPos" select="position()"/>

              <xsl:if test=
               "not(current()/@my:sign
                   = $pElems[not(position() >= $vPos)]/@my:sign
                   )">

                 <xsl:element name="{name()}">
                  <xsl:copy-of select="namespace::*[not(. = 'my:my')]"/>
                  <xsl:copy-of select="@*[not(name()='my:sign')]"/>
                   <xsl:call-template name="my:grouping">
                    <xsl:with-param name="pElems" select=
                    "$pElems[@my:sign = current()/@my:sign]/*"/>
                   </xsl:call-template>
                 </xsl:element>
               </xsl:if>

             </xsl:for-each>
           </xsl:if>
         </xsl:template>

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

     <xsl:template match="*/*">
      <xsl:variable name="vSignature">
       <xsl:call-template name="signature"/>
      </xsl:variable>
      <xsl:copy>
       <xsl:copy-of select="@*"/>
       <xsl:attribute name="my:sign">
        <xsl:value-of select="$vSignature"/>
       </xsl:attribute>

       <xsl:apply-templates/>
      </xsl:copy>
     </xsl:template>

     <xsl:template name="signature">
       <xsl:variable name="vsignAttribs">
         <xsl:for-each select="@*">
          <xsl:sort select="name()"/>

                <xsl:value-of select="concat(name(), '=', .,'|')"/>
             </xsl:for-each>
        </xsl:variable>

        <xsl:value-of select=
          "concat(name(), '|', $vsignAttribs)"/>
     </xsl:template>
</xsl:stylesheet>

当此转换应用于同一个 XML 文档(如上)时,再次产生相同的正确结果

<t>
   <TopLevel>
      <Ratings>
         <Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
            <RatingByNumber Code="X" Rating="10" Number="1"/>
            <RatingByNumber Code="X" Rating="19" Number="2"/>
         </Rating>
         <Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
            <RatingByNumber Code="X" Rating="10" Number="1"/>
            <RatingByNumber Code="X" Rating="19" Number="2"/>
         </Rating>
         <Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
            <RatingByNumber Code="X" Rating="10" Number="1"/>
            <RatingByNumber Code="X" Rating="19" Number="2"/>
            <RatingByNumber Code="X" Rating="30" Number="3"/>
            <RatingByNumber Code="X" Rating="39" Number="4"/>
         </Rating>
      </Ratings>
   </TopLevel>
</t>

说明

  1. 这是一个两遍转换。

  2. 在每个元素的第一遍中,计算一个签名,它成为一个新属性的值my:sign

  3. 使用与 XSLT 2.0 解决方案相同的递归分组算法。

于 2012-07-10T05:02:24.213 回答