54440

XSLT finding distinct combinations

Question:

I am beginner to xslt. I want to find all unique combinations for Year, Month, Day, and Hour.

I have following xml file to transform:

<Plans> <Plan Name="Plan_1"> <Book Name="Book_1"> <Time Name="AAA"> <Property Name="Year" Value="2001"/> <Property Name="Month" Value="01"/> <Property Name="Day" Value="10"/> <Property Name="Hour" Value="12"/> </Time> <Time Name="BBB"> <Property Name="Year" Value="2001"/> <Property Name="Month" Value="01"/> <Property Name="Day" Value="10"/> <Property Name="Hour" Value="12"/> </Time> <Time Name="CCC"> <Property Name="Year" Value="2004"/> <Property Name="Month" Value="02"/> <Property Name="Day" Value="11"/> <Property Name="Hour" Value="04"/> </Time> <Time Name="DDD"> <Property Name="Year" Value="2004"/> <Property Name="Month" Value="03"/> <Property Name="Day" Value="20"/> <Property Name="Hour" Value="04"/> </Time> </Book> <Book Name="Book_22"> <Time Name="CCC"> <Property Name="Year" Value="2001"/> <Property Name="Month" Value="01"/> <Property Name="Day" Value="10"/> <Property Name="Hour" Value="12"/> </Time> <Time Name="DDD"> <Property Name="Year" Value="2002"/> <Property Name="Month" Value="03"/> <Property Name="Day" Value="23"/> <Property Name="Hour" Value="03"/> </Time> <Time Name="EEE"> <Property Name="Year" Value="2002"/> <Property Name="Month" Value="03"/> <Property Name="Day" Value="23"/> <Property Name="Hour" Value="03"/> </Time> <Time Name="FFF"> <Property Name="Year" Value="2004"/> <Property Name="Month" Value="02"/> <Property Name="Day" Value="11"/> <Property Name="Hour" Value="04"/> </Time> </Book> </Plan> </Plans>

Input xml has total eight total combinations (Four from each book).

I just want to know distinct combinations. So output should have 6 combinations because there are two same combinations. The name of books does not matter. I just want to see how many combinations there are. And just label from 1 to the number... It does not have to be in any order although it can be ordered by time... If it is too hard to label numbers, then I just need to get distinct combinations only...

<Times> <Time Value="1"> <Property Name="Year" Value="2001"/> <Property Name="Month" Value="01"/> <Property Name="Day" Value="10"/> <Property Name="Hour" Value="12"/> </Time> <Time Value="2"> <Property Name="Year" Value="2004"/> <Property Name="Month" Value="02"/> <Property Name="Day" Value="11"/> <Property Name="Hour" Value="04"/> </Time> <Time Value="3"> <Property Name="Year" Value="2004"/> <Property Name="Month" Value="03"/> <Property Name="Day" Value="20"/> <Property Name="Hour" Value="04"/> </Time> <Time Value="4"> <Property Name="Year" Value="2001"/> <Property Name="Month" Value="01"/> <Property Name="Day" Value="10"/> <Property Name="Hour" Value="12"/> </Time> <Time Value="5"> <Property Name="Year" Value="2002"/> <Property Name="Month" Value="03"/> <Property Name="Day" Value="23"/> <Property Name="Hour" Value="03"/> </Time> </Times>

I tried to read for-each-group. But I cannot make it work... Does it have to be four loops or something since there are four porperties? Please help me...

Answer1:

Similar to the other answers, you could also make use of <strong>xsl:for-each-group</strong> here.

<xsl:for-each-group select=".//Time" group-by="string-join(Property/@Value, '|')">

If your XSLT was using the Identity Template, you could then output the distinct <strong>Time</strong> elements within this group simply by doing this

<Time Value="{position()}"> <xsl:apply-templates /> </Time>

Try this XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" /> <xsl:template match="/*"> <Times> <xsl:for-each-group select=".//Time" group-by="string-join(Property/@Value, '|')"> <Time Value="{position()}"> <xsl:apply-templates /> </Time> </xsl:for-each-group> </Times> </xsl:template> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>

Now, you mention in comments (but not in the question) about having multiple <strong>Plan</strong> elements, but you have not said if you want to select distinct <strong>Time</strong> elements across all <strong>Plan</strong> elements, or have distinct elements for each separate <strong>Plan</strong>.

Suppose you did want to show distinct <strong>Time</strong> elements for each separate <strong>Plan</strong>, then just a small tweak will do. You would just effectively match the <strong>Plan</strong> element and have the grouping in that

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" /> <xsl:template match="Plan"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:for-each-group select=".//Time" group-by="string-join(Property/@Value, '|')"> <Time Value="{position()}"> <xsl:apply-templates /> </Time> </xsl:for-each-group> </xsl:copy> </xsl:template> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>

Do note these assume the order of your <strong>Property</strong> elements will always be the same ("Year", "Month", "Day", "Hour"). If not, you can get around this by doing the following

<xsl:for-each-group select=".//Time" group-by="concat( Property[@Name='Year']/@Value, '|', Property[@Name='Month']/@Value, '|', Property[@Name='Day']/@Value, '|', Property[@Name='Hour']/@Value, '|')">

Answer2:

Use

distinct-values(/Times/Time/string-join(Property/@Value, '|'))

to get the distinct values. Then if necessary split them up into their component fields using tokenize().

Answer3:

The following stylesheet builds upon the answer given by @Michael Kay. This is just to illustrate the solution and prove that it is working. Please accept <em>his</em> answer!

Numbering output elements is not hard, the easiest way is using position() in an attribute value template.

<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:template match="/Plans"> <Times> <xsl:for-each select="distinct-values(//Time/string-join(Property/@Value, '|'))"> <xsl:variable name="tokens" select="tokenize(.,'\|')"/> <Time Value="{position()}"> <Property Name="Year" Value="{$tokens[1]}"/> <Property Name="Month" Value="{$tokens[2]}"/> <Property Name="Day" Value="{$tokens[3]}"/> <Property Name="Hour" Value="{$tokens[4]}"/> </Time> </xsl:for-each> </Times> </xsl:template> </xsl:stylesheet>

<strong>Output</strong>

As mentioned by @Tim C, you are probably expecting 4 result elements.

<?xml version="1.0" encoding="UTF-8"?> <Times> <Time Value="1"> <Property Name="Year" Value="2001"/> <Property Name="Month" Value="01"/> <Property Name="Day" Value="10"/> <Property Name="Hour" Value="12"/> </Time> <Time Value="2"> <Property Name="Year" Value="2004"/> <Property Name="Month" Value="02"/> <Property Name="Day" Value="11"/> <Property Name="Hour" Value="04"/> </Time> <Time Value="3"> <Property Name="Year" Value="2004"/> <Property Name="Month" Value="03"/> <Property Name="Day" Value="20"/> <Property Name="Hour" Value="04"/> </Time> <Time Value="4"> <Property Name="Year" Value="2002"/> <Property Name="Month" Value="03"/> <Property Name="Day" Value="23"/> <Property Name="Hour" Value="03"/> </Time> </Times>

Recommend

  • OpenMP for dependent variables
  • Rails 4 order by virtual attribute
  • Taking mean across rows grouped by a variable in numpy
  • Grouping by blank nodes
  • R Leaflet Legend: specify order instead of alphabetical
  • Query to get the Top 2 from each group
  • How to implement limit with Nhibernate and Sybase
  • How to access meteor package name inside package?
  • How to get latest version of a artifact on Bintray using JSONP
  • Tell Git to stop prompting me for conflicts when none really exist?
  • Position: fixed nav does not stay fixed
  • Atlas images wrong size on iPad iOS 9
  • Breeze - Deleted Items nav properties bug
  • Splitting given String into two variables - php
  • NetLogo BehaviorSpace - Measure runs using reporters
  • Is my CUDA kernel really runs on device or is being mistekenly executed by host in emulation?
  • Read text file and split every line in MSBuild
  • javaw.exe and eclipse startup problems
  • How to add a column to a Pandas dataframe made of arrays of the n-preceding values of another column
  • Does CUDA 5 support STL or THRUST inside the device code?
  • Deserializing XML into class C#
  • ActionScript 2 vs ActionScript 3 performance
  • Build own AppleScript numerical error handling
  • Function pointer “assignment from incompatible pointer type” only when using vararg ellipsis
  • Rearranging Cells in UITableView Bug & Saving Changes
  • Run Powershell script from inside other Powershell script with dynamic redirection to file
  • InvalidAuthenticityToken between subdomains when logging in with Rails app
  • Unit Testing MVC Web Application in Visual Studio and Problem with QTAgent
  • SQL merge duplicate rows and join values that are different
  • Rails 2: use form_for to build a form covering multiple objects of the same class
  • embed rChart in Markdown
  • Can Visual Studio XAML designer handle font family names with spaces as a resource?
  • need help with bizarre java.net.HttpURLConnection behavior
  • LevelDB C iterator
  • How can I remove ASP.NET Designer.cs files?
  • python draw pie shapes with colour filled
  • Are Kotlin's Float, Int etc optimised to built-in types in the JVM? [duplicate]
  • Can't mass-assign protected attributes when import data from csv file
  • How to Embed XSL into XML
  • java string with new operator and a literal