2012-07-09 83 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