开发者

Xslt transformation / how to group entity

I want to use XSLT to modify :

<Office Code="1" OtherAttribute="5">
   <Customer CustomerId="0010" CodeModifier="A"/>
   <Customer CustomerId="0011" CodeModifier="B"/>
   <Customer CustomerId="0012" CodeModifier="B"/>
</Office>
<Office Code="2" OtherAttribute="6">
   <Customer CustomerId="2010" CodeModifier="A"/>
   <Customer CustomerId="0011" CodeModifier="C"/>
</Office>

into :

<Office Code="1A" OtherAttribute="5">
   <Customer CustomerId="0010"/>
</Office>
<Office Code="1B" OtherAttribute="5">
   <Customer CustomerId="0011"/>
   <Customer CustomerId="0012"/>
</Office>
<Office Code="2A" OtherAttribute="6">
   <Customer CustomerId="2010"/>
</Office>
<Office Code="2C" OtherAttribute="6">
   <Customer CustomerId="0011"/>
</Office>

My goals :

  • group every Customer entity with the same CodeModifier into Office entities. If there are some multiple CodeModifier, I will add Office entity. The Code attribute into Office will be modified (concatenation of CodeModifier of Client into the Office)
  • (facultative but trivial I think) suppress the CodeModifier attribute and ke开发者_高级运维ep all other attribute

Does someone know how to do that ?


This stylesheet, applied to your input sample:

<!-- a key to group Customers by their office code + modifier -->
<xsl:key name="kCustomerGroup" match="Customer" 
  use="concat(../@Code, @CodeModifier)" 
/>

<!-- identity template: copies everything that is not handled otherwise -->
<xsl:template match="node() | @*">
  <xsl:copy>
    <xsl:apply-templates select="node() | @*" />
  </xsl:copy>
</xsl:template>

<!-- apply templates directly to customers. modify as necessary -->
<xsl:template match="/">
  <xsl:apply-templates select="//Customer" mode="CustomerGroup" />
</xsl:template>

<xsl:template match="Customer" mode="CustomerGroup">
  <xsl:variable name="officeCode" select="concat(../@Code, @CodeModifier)" />
  <!-- if this Customer is first of his respective group... -->
  <xsl:if test="
    generate-id() 
    =
    generate-id(key('kCustomerGroup', $officeCode)[1])
  ">
    <!-- use for-each to switch the context node to the parent -->
    <xsl:for-each select="..">
      <xsl:copy>
        <xsl:apply-templates select="@*" />
        <!-- overwrite the @Code attribute -->
        <xsl:attribute name="Code">
          <xsl:value-of select="$officeCode" />
        </xsl:attribute>
        <!-- now handle the Customer group members -->
        <xsl:apply-templates select="key('kCustomerGroup', $officeCode)" />
      </xsl:copy>
    </xsl:for-each>
  </xsl:if>
</xsl:template>

<!-- remove unwanted attribute with empty template -->    
<xsl:template match="Customer/@CodeModifier" />

returns

<Office Code="1A" OtherAttribute="5">
  <Customer CustomerId="0010"></Customer>
</Office>
<Office Code="1B" OtherAttribute="5">
  <Customer CustomerId="0011"></Customer>
  <Customer CustomerId="0012"></Customer>
</Office>
<Office Code="2A" OtherAttribute="6">
  <Customer CustomerId="2010"></Customer>
</Office>
<Office Code="2C" OtherAttribute="6">
  <Customer CustomerId="0011"></Customer>
</Office>

Note

  • The use of template modes. I made a template specifically for Customer grouping. Normal nodes processing happens in the identity template.
  • The use of a single-node xsl:for-each loop to change the context node for xsl:copy.
  • The use of an empty template to remove nodes from the output.
  • That you can copy attributes with xsl:copy and still overwrite one of them later.
  • That the output is in document order.

To force a particular order, use something like this:

<xsl:apply-templates select="//Customer" mode="CustomerGroup">
  <xsl:sort select="../@Code" data-type="text" order="ascending" />
</xsl:apply-templates>

and

<xsl:apply-templates select="key('kCustomerGroup', $officeCode)">
  <xsl:sort select="@CodeModifier" data-type="number" order="ascending" />
</xsl:apply-templates>


Here's another approach, using matching templates only.


Tested as XSLT 1.0 under MSXSL 4.0

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

    <xsl:key name="kCustomerGroup" match="Customer" 
        use="concat(../@Code, @CodeModifier)" 
        />

    <xsl:template match="Office">
        <xsl:apply-templates select="Customer[generate-id() 
            =
            generate-id(key('kCustomerGroup', 
            concat(../@Code, @CodeModifier))[1])]"
            />
    </xsl:template>

    <xsl:template match="Customer">
        <Office 
            Code="{concat(../@Code,@CodeModifier)}" 
            OtherAttribute="{../@OtherAttribute}">

            <xsl:apply-templates select="key('kCustomerGroup', 
                concat(../@Code,@CodeModifier))" mode="copy"/>

        </Office>
    </xsl:template>

    <xsl:template match="Customer" mode="copy">
        <xsl:copy>
            <xsl:copy-of select="@CustomerId"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Outputs to:

<?xml version="1.0" encoding="UTF-8"?>
<Office Code="1A" OtherAttribute="5">
   <Customer CustomerId="0010"/>
</Office>
<Office Code="1B" OtherAttribute="5">
   <Customer CustomerId="0011"/>
   <Customer CustomerId="0012"/>
</Office>
<Office Code="2A" OtherAttribute="6">
   <Customer CustomerId="2010"/>
</Office>
<Office Code="2C" OtherAttribute="6">
   <Customer CustomerId="0011"/>
</Office>


A complete and short XSLT 2.0 solution:

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

 <xsl:template match="/*/*">
  <xsl:for-each-group select="Customer" group-by="@CodeModifier">
   <Office>
     <xsl:copy-of select="../@*"/>
     <xsl:attribute name="Code" select=
         "concat(../@Code, current-grouping-key())"/>
     <xsl:copy-of select="current-group()"/>
   </Office>
  </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>

when applied on the following XML document (based on the provided XML fragment and just wrapping it into a single top element to make this a well-formed XML document):

<company>
    <Office Code="1" OtherAttribute="5">
        <Customer CustomerId="0010" CodeModifier="A"/>
        <Customer CustomerId="0011" CodeModifier="B"/>
        <Customer CustomerId="0012" CodeModifier="B"/>
    </Office>
    <Office Code="2" OtherAttribute="6">
        <Customer CustomerId="2010" CodeModifier="A"/>
        <Customer CustomerId="0011" CodeModifier="C"/>
    </Office>
</company>

produces the wanted, correct result:

<Office Code="1A" OtherAttribute="5">
   <Customer CustomerId="0010" CodeModifier="A"/>
</Office>
<Office Code="1B" OtherAttribute="5">
   <Customer CustomerId="0011" CodeModifier="B"/>
   <Customer CustomerId="0012" CodeModifier="B"/>
</Office>
    <Office Code="2A" OtherAttribute="6">
   <Customer CustomerId="2010" CodeModifier="A"/>
</Office>
<Office Code="2C" OtherAttribute="6">
   <Customer CustomerId="0011" CodeModifier="C"/>
</Office>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜