Sam
Sam

Reputation: 2452

How to group the XML data which are at the same level using XSLT?

I want to do a XSL transformation wherein I want group similar things.

But with the method like "key" "generate-id" I am not able to achieve this.

I am putting the source and expected format below.

I did have a look at this. So thought of relating the same example for simplification

Source:

<Root><!-- yes, I know I don't need a 'Root' element! Legacy code... -->
  <Plans>
  <Planner id="1">
    <Plan AreaID="1"></Plan>
      <Part ID="9122" Name="foo" />
      <Part ID="9126" Name="bar" />
    <Plan AreaID="1"></Plan>
      <Part ID="8650" Name="baz" />
    <Plan AreaID="2"></Plan>
      <Part ID="215" Name="quux" />
    <Plan AreaID="1" ></Plan>
      <Part ID="7350" Name="meh" />
    </Planner>

  <Planner id="2">
    <Plan AreaID="1"></Plan>
      <Part ID="9122" Name="foo" />
      <Part ID="9126" Name="bar" />
    <Plan AreaID="1"></Plan>
      <Part ID="8650" Name="baz" />
    <Plan AreaID="2"></Plan>
      <Part ID="215" Name="quux" />
    <Plan AreaID="1" ></Plan>
      <Part ID="7350" Name="meh" />
    </Planner>
  </Plans>
</Root>

Expected:

<Root><!-- yes, I know I don't need a 'Root' element! Legacy code... -->
    <Plans>
        <Planner id="1">
            <Plan AreaID="1"></Plan>
            <Part ID="9122" Name="foo" />
            <Part ID="9126" Name="bar" />
            <Part ID="8650" Name="baz" />
            <Part ID="7350" Name="meh" />
            <Plan AreaID="2"></Plan>
            <Part ID="215" Name="quux" />
        </Planner>

        <Planner id="2">
            <Plan AreaID="1"></Plan>
            <Part ID="9122" Name="foo" />
            <Part ID="9126" Name="bar" />
            <Part ID="8650" Name="baz" />
            <Part ID="7350" Name="meh" />
            <Plan AreaID="2"></Plan>
            <Part ID="215" Name="quux" />
        </Planner>
    </Plans>
</Root>

In my case since the Plan and Part are at the same level I am not able to group according to Plan which I want to do ideally.

Upvotes: 0

Views: 94

Answers (1)

Ian Roberts
Ian Roberts

Reputation: 122374

The trick to getting Muenchian grouping to work in this situation where you want to group within a particular parent rather than globally, is to include something unique to the parent as part of the grouping key. Typically you would use generate-id(..) but in this case you could simply use the id attribute off the Planner. For example

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

  <xsl:key name="partByArea" match="Part"
    use="concat(../@id, '|', preceding-sibling::Plan[1]/@AreaID)" />

  <!-- identity template - copy everything as-is except when overridden -->
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
  </xsl:template>

  <xsl:template match="Planner">
    <xsl:copy>
      <xsl:apply-templates select="@*" />
      <!-- select the first Part in each group -->
      <xsl:apply-templates mode="group" select="Part[generate-id() =
        generate-id(key('partByArea',
         concat(current()/@id, '|', preceding-sibling::Plan[1]/@AreaID))[1])]" />
    </xsl:copy>
  </xsl:template>

  <!-- template applied to the first Part in each group -->
  <xsl:template match="Part" mode="group">
    <!-- the Plan that heads this group -->
    <xsl:apply-templates select="preceding-sibling::Plan[1]" />
    <!-- all the matching Part elements -->
    <xsl:apply-templates select="key('partByArea',
         concat(../@id, '|', preceding-sibling::Plan[1]/@AreaID))" />
  </xsl:template>
</xsl:stylesheet>

Here we're grouping the Part elements by a combination of the id of their parent Planner and the AreaID of their nearest preceding Plan.

Upvotes: 4

Related Questions