2012-07-09 87 views
3

我有多种类型的xml消息,我需要通过将多个节点分组到同一个父节点(同一个父节点共享相同的节点名称,并声明的每个属性也相同)来“压缩”。例如: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> 
    </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所,开始和结束属性,所以我需要的是使用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消息创建自定义代码。

回答

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()生成。


II。通用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的valye。

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

+0

这似乎真的是我想要的,除了我被1.0困住了。我会看看我能否做点什么。感谢您的详细解答。 – 2012-07-10 12:24:16

+1

@EdFox:在XSLT 1.0中,使用了相同的想法,但使用两遍转换,第一遍创建每个元素的副本并添加包含签名的特殊新元素(或属性)。在第二遍中,我们对这个特殊元素/属性进行简单的Muenchian分组。 – 2012-07-10 12:28:59

+0

你可以添加1.0版本到你的文章?对不起,我仍然有点被xslt所淹没,所以我不确信我可以将你的解释翻译成实际的代码。 – 2012-07-10 13:11:09

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元素均按其属性和其对应的TopLevelCodeTL属性的组合进行分组。

+0

这似乎工作,但有2个TopLevel节点具有不同的代码但具有相同的孩子(它们被分组在文件中出现的第一个节点下)时失败。例如 http://pastebin.com/0EPpnycL – 2012-07-10 04:00:56

+0

@EdFox - 好点。我们应该在'Rating'组键中包含祖父'@ CodeTL'。看我的编辑。 – 2012-07-10 06:35:22