Filtering and Grouping in xslt for xml data
I have an xml content and i am applying xslt 1.0 for transformation on it. I am also passing parameters for filteration. but i am not able to to grouping on filtered data in xslt 1.0.
I will pass "Country Value" (as like 'United States') as parameter for filteration. after filteration, Grouping will be applied on "Group" field for filtered data. and if only one group exist then dont group data. grouping applied only in case if more then one group becomes possible.
please help me on this.
thanks in advance.
Here is my sample XML content.
<?xml version="1.0" encoding="utf-8" ?>
<DataRows>
-<DataRow>
- <Country>
<Conty>United States</Conty&开发者_C百科gt;
<Conty>United Kingdom</Conty>
</Country>
<Group>Group 1</Group>
<Order>1</Order>
<Name>Name 1_1</Name>
<Title>Title 1</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>United Kingdom</Conty>
</Country>
<Group>Group 1</Group>
<Order>2</Order>
<Name>Name 2_2</Name>
<Title>Title 2</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
</Country>
<Group>Group 1</Group>
<Order>1</Order>
<Name>Name 3_1</Name>
<Title>Title 3</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>Germany</Conty>
</Country>
<Group>Group 1</Group>
<Order>2</Order>
<Name>Name 4_2</Name>
<Title>Title 4</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
</Country>
<Group>Group 2</Group>
<Order>4</Order>
<Name>Name 8_4</Name>
<Title>Title 8</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United Kingdom</Conty>
</Country>
<Group>Group 2</Group>
<Order>1</Order>
<Name>Name 9_1</Name>
<Title>Title 9</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>Germany</Conty>
</Country>
<Group>Group 2</Group>
<Order>3</Order>
<Name>Name 5_3</Name>
<Title>Title 5</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>Germany</Conty>
</Country>
<Group>Group 2</Group>
<Order>4</Order>
<Name>Name 6_4</Name>
<Title>Title 6</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
</Country>
<Group>Group 2</Group>
<Order>3</Order>
<Name>Name 7_3</Name>
<Title>Title 7</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>Germany</Conty>
</Country>
<Group>Group 1</Group>
<Order>1</Order>
<Name>Name 10_1</Name>
<Title>Title 10</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
</DataRows>
If you really wanted to do filtering first, then grouping, then you are looking at some sort of 'two pass' transform. This can be achieved by use of the node-set extension function, to create a result-tree fragment which contains the filtered data.
In the following example, I am using Microsoft's extension function, but depending on your platform, you may have to specify another. ( EXSLT is another common one. Use name space http://exslt.org/common for that. )
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="Conty">United Kingdom</xsl:param>
<xsl:variable name="FilteredData">
<xsl:apply-templates select="/DataRows/DataRow" mode="filter"/>
</xsl:variable>
<xsl:template match="DataRow" mode="filter">
<!-- Check this DataRow matches the filter -->
<xsl:if test="Country[Conty=$Conty]">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="filter"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Ignore Country node in the filter -->
<xsl:template match="Country" mode="filter"/>
<!-- Identity template for filter -->
<xsl:template match="@*|node()" mode="filter">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="filter"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/DataRows">
<xsl:copy>
<!-- Read the filtered data -->
<xsl:choose>
<!-- Check there is a Group which differs from the first group -->
<xsl:when test="msxsl:node-set($FilteredData)/DataRow[position() > 1][Group != msxsl:node-set($FilteredData)/DataRow[1]/Group]">
<xsl:apply-templates select="msxsl:node-set($FilteredData)"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="msxsl:node-set($FilteredData)" mode="nogroup"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<!-- Filtered data row -->
<xsl:template match="DataRow">
<!-- Is this DataRow the first in the group -->
<xsl:if test="not(preceding-sibling::DataRow[Group=current()/Group])"><!-- If so, create the group node -->
<Group>
<xsl:attribute name="name">
<xsl:value-of select="Group"/>
</xsl:attribute><!-- Get all the DataRow elements from the filter for the current group -->
<xsl:apply-templates select="../DataRow[Group=current()/Group]" mode="ingroup"/>
</Group>
</xsl:if>
</xsl:template>
<!-- Identity template for the group -->
<xsl:template match="@*|node()" mode="ingroup">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="ingroup"/>
</xsl:copy>
</xsl:template>
<!-- Ignore Group and Country node in the grouping -->
<xsl:template match="Group|Country" mode="ingroup"/>
<!-- Identity template for no grouping -->
<xsl:template match="@*|node()" mode="nogroup">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="nogroup"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this is used, the output should be as follows
<DataRows>
<Group name="Group 1">
<DataRow>
<Order>1</Order>
<Name>Name 1_1</Name>
<Title>Title 1</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
<DataRow>
<Order>2</Order>
<Name>Name 2_2</Name>
<Title>Title 2</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
<Group name="Group 2">
<DataRow>
<Order>1</Order>
<Name>Name 9_1</Name>
<Title>Title 9</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
</DataRows>
See Understanding the node-set function for more information.
Unless you did a two-phase transform, I think you should probably do the grouping first, and then the filtering.
Grouping would be achieved by the common Meunchain Grouping method. You first define a key to look up DataRow elements based on their Group
<xsl:key name="RowLookup" match="DataRow" use="Group"/>
And then, to get the unique group names, you match the DataRow elements which happen to be the first occuring element in your key for their particular group
<xsl:apply-templates select="DataRow[generate-id() = generate-id(key('RowLookup', Group)[1])]"/>
So, now you have grouped by the Group elements, so you need to check there it at least one DataRow element for the current group that matches the filter
<xsl:if test="../DataRow[Group=current()/Group]/Country[Conty=$Conty]">
And then to get all DataRow elements for the current group, you could use the key
<xsl:apply-templates select="key('RowLookup', Group)" mode="ingroup"/>
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="Conty">United Kingdom</xsl:param>
<xsl:key name="RowLookup" match="DataRow" use="Group"/>
<xsl:template match="/DataRows">
<xsl:copy>
<!-- Select unique groups -->
<xsl:apply-templates
select="DataRow[generate-id() = generate-id(key('RowLookup', Group)[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="DataRow">
<!-- Check any DataRow elements for the current group match the filter -->
<xsl:if test="../DataRow[Group=current()/Group]/Country[Conty=$Conty]">
<Group>
<xsl:attribute name="name">
<xsl:value-of select="Group"/>
</xsl:attribute>
<!-- Get all the DataRow elements for the current group -->
<xsl:apply-templates select="key('RowLookup', Group)" mode="ingroup"/>
</Group>
</xsl:if>
</xsl:template>
<xsl:template match="DataRow" mode="ingroup">
<!-- Check this DataRow matches the filter -->
<xsl:if test="Country[Conty=$Conty]">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Ignore Group and Country elements -->
<xsl:template match="Group|Country"/>
<!-- Standard Identity Transform for all other nodes -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When you apply this XSLT to your sample XML, you get the following results
<DataRows>
<Group name="Group 1">
<DataRow>
<Order>1</Order>
<Name>Name 1_1</Name>
<Title>Title 1</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
<DataRow>
<Order>2</Order>
<Name>Name 2_2</Name>
<Title>Title 2</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
<Group name="Group 2">
<DataRow>
<Order>1</Order>
<Name>Name 9_1</Name>
<Title>Title 9</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
</DataRows>
I am not sure if this is the exact structure you want, but I hope it gives you the general idea.
精彩评论