XSLT merge rows if they have the same value - html

I'm fairly new to XML and XSLT documents. I have created an XML document that has records of weather data which spans over a few months. I want to use an XSLT document to group together records of the same month within each table row and then print their months (if month = 2, Feb will be printed) on the column which spans across all the rows in the same month. Is there any way I can use if-else statements to print out the corresponding months and how do I merge the table rows if they have the same month value?
XML:
<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="S3(1).xsl" ?>
<forecast xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="S3.xsd"
qTime="28/10/20 10:00 PM">
<weather yyyymmdd="20200430">
<year>2020</year>
<month>04</month>
<date>30</date>
<comment>Plenty of sunshine</comment>
<code>sunny</code>
<highest>32.6</highest>
<lowest>28.4</lowest>
</weather>
<weather yyyymmdd="20200218">
<year>2020</year>
<month>02</month>
<date>18</date>
<comment>Plenty of sunshine</comment>
<code>sunny</code>
<highest>34.6</highest>
<lowest>30.5</lowest>
</weather>
<weather yyyymmdd="20200710">
<year>2020</year>
<month>07</month>
<date>10</date>
<comment>Partly sunny</comment>
<code>partlySunny</code>
<highest>33.1</highest>
<lowest>29.2</lowest>
</weather>
<weather yyyymmdd="20200616">
<year>2020</year>
<month>06</month>
<date>16</date>
<comment>Considerable clouds</comment>
<code>cloudy</code>
<highest>30.5</highest>
<lowest>25.4</lowest>
</weather>
<weather yyyymmdd="20200612">
<year>2020</year>
<month>06</month>
<date>12</date>
<comment>Cloudy with a thunderstorm</comment>
<code>thunderstorm</code>
<highest>29.1</highest>
<lowest>23.2</lowest>
</weather>
<weather yyyymmdd="20200421">
<year>2020</year>
<month>04</month>
<date>21</date>
<comment>Plenty of sunshine</comment>
<code>sunny</code>
<highest>32.2</highest>
<lowest>29.8</lowest>
</weather>
<weather yyyymmdd="20200628">
<year>2020</year>
<month>06</month>
<date>28</date>
<comment>A morning shower, then rain</comment>
<code>rain</code>
<highest>30.2</highest>
<lowest>22.7</lowest>
</weather>
<weather yyyymmdd="20200502">
<year>2020</year>
<month>05</month>
<date>02</date>
<comment>Cloudy with a thunderstorm</comment>
<code>thunderstorm</code>
<highest>28.1</highest>
<lowest>26.9</lowest>
</weather>
<weather yyyymmdd="20200428">
<year>2020</year>
<month>04</month>
<date>28</date>
<comment>A morning shower</comment>
<code>rain</code>
<highest>28.8</highest>
<lowest>22.2</lowest>
</weather>
<weather yyyymmdd="20200410">
<year>2020</year>
<month>04</month>
<date>10</date>
<comment>Partly sunny</comment>
<code>partlySunny</code>
<highest>33.7</highest>
<lowest>29.3</lowest>
</weather>
<weather yyyymmdd="20200730">
<year>2020</year>
<month>07</month>
<date>30</date>
<comment>Plenty of sunshine</comment>
<code>sunny</code>
<highest>32.3</highest>
<lowest>28.4</lowest>
</weather>
<weather yyyymmdd="20200706">
<year>2020</year>
<month>07</month>
<date>06</date>
<comment>Plenty of sunshine</comment>
<code>sunny</code>
<highest>34.5</highest>
<lowest>30.6</lowest>
</weather>
</forecast>
This is my XSL file so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="forecast">
<h1>
<span> <xsl:value-of select="#qLocation"/></span>
<span>[<xsl:value-of select="#qTime"/>]</span>
</h1>
<table style="border:1px solid black;">
<tr>
<th>Date</th>
<th>Weather data</th>
<th>Highest</th>
<th>Lowest</th>
</tr>
<xsl:for-each select="weather">
<xsl:sort select="month" order="ascending"/>
<xsl:sort select="date" order="ascending"/>
<tr>
<td>
<span><xsl:value-of select="date"/>/</span>
<span><xsl:value-of select="month"/></span>
</td>
<td>
<li>
<xsl:value-of select="date"/>/
<xsl:value-of select="month"/>/
<xsl:value-of select="year"/>,
from
<xsl:value-of select="lowest"/>C
to
<xsl:value-of select="highest"/>C,
<xsl:value-of select="comment"/>
</li>
</td>
<td>
<xsl:value-of select="highest"/>C
<!-- <img src="img1.jpg"> -->
</td>
<td>
<xsl:value-of select="lowest"/>C
<!-- <img src="img2.jpg"> -->
</td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The output I'm trying to achieve is something like this:
Image

For this, I would use xsl:for-each-group specifying the month value for the grouping-key: <xsl:for-each-group select="weather" group-by="month">
Inside of the loop, iterate over the current-group() to process each of the grouped weather elements to produce the list: <xsl:for-each select="current-group()">
You can obtain the min() and max() values from the grouped set of weather elements: max(current-group()/highest)
Using the month number as a position, you can use a predicate to select from a sequence of month abbreviations. There was a leading zero, so use number() to convert i.e. 02 to 2: <xsl:sequence select="('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[number(current-grouping-key())]"/>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="forecast">
<h1>
<span> <xsl:value-of select="#qLocation"/></span>
<span>[<xsl:value-of select="#qTime"/>]</span>
</h1>
<table style="border:1px solid black;">
<tr>
<th>Date</th>
<th>Weather data</th>
<th>Highest</th>
<th>Lowest</th>
</tr>
<xsl:for-each-group select="weather" group-by="month">
<xsl:sort select="month" order="ascending"/>
<xsl:sort select="date" order="ascending"/>
<tr>
<td>
<span><xsl:value-of select="year"/>/</span>
<span><xsl:sequence select="('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[number(current-grouping-key())]"/></span>
</td>
<td>
<xsl:for-each select="current-group()">
<li>
<xsl:value-of select="date"/>/
<xsl:value-of select="month"/>/
<xsl:value-of select="year"/>,
from
<xsl:value-of select="lowest"/>C
to
<xsl:value-of select="highest"/>C,
<xsl:value-of select="comment"/>
</li>
</xsl:for-each>
</td>
<td>
<xsl:value-of select="max(current-group()/highest)"/>C
<!-- <img src="img1.jpg"> -->
</td>
<td>
<xsl:value-of select="min(current-group()/lowest)"/>C
<!-- <img src="img2.jpg"> -->
</td>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>

Related

XSLT convert HTML table to XML structured elements

I have this HTML table:
<?xml version="1.0" encoding="UTF-8"?>
<table class="names">
<tbody>
<tr class="names">
<td>
<p><strong class="strong">name</strong></p>
</td>
<td>
<p><strong class="strong">surname</strong></p>
</td>
<td>
<p><strong class="strong">aff</strong></p>
</td>
</tr>
<tr class="names">
<td>
<p><span class="contrib">John</span></p>
</td>
<td>
<p><span class="contrib">Smith</span></p>
</td>
<td>
<p><span class="contrib">1,3</span></p>
</td>
</tr>
<tr class="names">
<td>
<p><span class="contrib">Michael</span></p>
</td>
<td>
<p><span class="contrib">Jordan</span></p>
</td>
<td>
<p><span class="contrib">1,2</span></p>
</td>
</tr>
</tbody>
</table>
I would like to transform it to structured XML elements like this:
<contrib>
<person>
<name>John</name>
<surname>Smith</surname>
<number>1</number>
<number>3</number>
</person>
<person>
<name>Michael</name>
<surname>Jordan</surname>
<number>1</number>
<number>2</number>
</person>
</contrib>
And I created this XSLT so far:
<xsl:template name="article-meta">
<contrib>
<person>
<name>
<xsl:value-of select=".//td[1]//span[#class='contrib']"/>
</name>
<surname>
<xsl:value-of select=".//td[2]//span[#class='contrib']"/>
</surname>
<xsl:for-each select="//td[3]//span[#class='contrib']">
<number><xsl:value-of select="normalize-space(.)"/></number>
</xsl:for-each>
</person>
</contrib>
</xsl:template>
I've been playing the whole day, but it seems I'm unable to produce multiple xml blocks. I'm always getting all results inside single element. Is it even possible somehow to create the wanted structure above if all <span> elements inside <td> cells only have class "contrib" and nothing else? Also, the last cell should be tokenized I believe, but I also don't know how to address it.
Is there a reason why you cannot do simply:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/table">
<contrib>
<xsl:for-each select="tbody/tr[position() > 1]">
<person>
<name>
<xsl:value-of select="td[1]"/>
</name>
<surname>
<xsl:value-of select="td[2]"/>
</surname>
<xsl:for-each select="tokenize(td[3], ',')">
<number>
<xsl:value-of select="."/>
</number>
</xsl:for-each>
</person>
</xsl:for-each>
</contrib>
</xsl:template>
</xsl:stylesheet>

XSLT grouping data from 2 different tags and removing duplicates

I have this XML:
<CombinedResults>
<Results>
<Results>
<AccountNumber>5678</AccountNumber>
<Accounts>
<DomainAccount>
<AccountNumber>1234</AccountNumber>
</DomainAccount>
<DomainAccount>
<AccountNumber>9999</AccountNumber>
</DomainAccount>
<DomainAccount>
<AccountNumber>8888</AccountNumber>
</DomainAccount>
</Accounts>
</Results>
<Results>
<AccountNumber>0427</AccountNumber>
</Results>
<Results>
<AccountNumber>1234</AccountNumber>
<Accounts>
<DomainAccount>
<AccountNumber>5678</AccountNumber>
</DomainAccount>
<DomainAccount>
<AccountNumber>9999</AccountNumber>
</DomainAccount>
<DomainAccount>
<AccountNumber>8888</AccountNumber>
</DomainAccount>
</Accounts>
</Results>
</Results>
</CombinedResults>
I need to create a table where each row has the AccountNumber value. The table must contains both the values from the Results/AccountNumber tag and the values from Results/Accounts/DomainAccount/AccountNumber tag and also no duplicates.
I managed to do it, but I was not able to remove the duplicates.
My code was:
<xsl:for-each select="CombinedResults/Results/Results">
<tr>
<td><xsl:value-of select="AccountNumber"/></td>
</tr>
</xsl:for-each>
<xsl:for-each select="CombinedResults/Results/Results/Accounts/DomainAccount">
<tr>
<td><xsl:value-of select="AccountNumber"/></td>
</tr>
</xsl:for-each>
I need to get the values from the Results tag first, and then the ones from DomainAccount, with no duplicates.
This is the output I need to get:
<tr>
<td>5678</td>
</tr>
<tr>
<td>0427</td>
</tr>
<tr>
<td>1234</td>
</tr>
<tr>
<td>9999</td>
</tr>
<tr>
<td>8888</td>
</tr>
To get the output you want in XSLT-1.0, you have to use a technique called Muenchian Grouping. So create an xsl:key with all of your values and then select only the first item in an xsl:for-each.
An XSLT-1.0 stylesheet realizing that is the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="items" match="Results/Results/AccountNumber | Results/Results/Accounts/DomainAccount/AccountNumber" use="text()" />
<xsl:template match="/CombinedResults">
<xsl:for-each select="//AccountNumber[generate-id() = generate-id(key('items',text())[1])]">
<tr>
<td style="text-align:center;">
<!-- <xsl:value-of select="."/> -->
<xsl:choose>
<xsl:when test="key('items',text())[parent::Results]">
<xsl:copy-of select="key('items',text())[parent::Results]"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="key('items',text())[not(parent::Results)]"/>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Its output is:
<tr>
<td style="text-align:center;">5678</td>
</tr>
<tr>
<td style="text-align:center;">1234</td>
</tr>
<tr>
<td style="text-align:center;">9999</td>
</tr>
<tr>
<td style="text-align:center;">8888</td>
</tr>
<tr>
<td style="text-align:center;">0427</td>
</tr>
The order is not as you mentioned, but I cannot infer any ordering system from your desired output. Use an xsl:sort in the xsl:for-each loop if you need another order.
In XSLT-2.0 this is easier. You would just use the xsl:for-each-group function:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/CombinedResults">
<xsl:for-each-group select="Results/Results/AccountNumber | Results/Results/Accounts/DomainAccount/AccountNumber" group-by="text()">
<tr>
<td style="text-align:center;">
<!-- <xsl:value-of select="current-group()[1]"/> -->
<xsl:value-of select="if (current-group()[name(..) = 'Results']) then current-group()[name(..) = 'Results'][1] else current-group()[name(..) != 'Results'][1]"/>
</td>
</tr>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
The result is the same.

XSL: for each within another for each iterating data

I need to perform a cyclical sequence within another public secuanci and get the data of each person and each status, the problem that I have with the code is that it does not iterate the data that I am delivering. I need help in the second for-each when I have empty fields and then I can work with Word content controls
XML file
<?xml version="1.0"?>
<emailList>
<person>
<name>name 1</name>
<email>g#gmail.com</email>
<status>
<active>1</active>
<active>2</active>
<active>3</active>
</status>
</person>
<person>
<name>name 2</name>
<email>n#hotmail.com</email>
<status>
<active>4</active>
<active>5</active>
</status>
</person>
</emailList>
XSL file
<xsl:stylesheet version="1.0">
<html xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:version="1.0">
<head>
<title>Email Listing</title>
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>E-mail Address</th>
<th>Status</th>
</tr>
<xsl:for-each select="emailList/person">
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="email"/></td>
<td>
<xsl:for-each select="emailList/person/status">
<xsl:value-of select="active"/>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
You inner xsl:for-each will be relative to the outer one, which is selecting person.
Try changing it to this...
<xsl:for-each select="status/active">
<xsl:value-of select="."/>
</xsl:for-each>
Where . selects the current node.
If you wanted to separate the values by commas, you could do this....
<xsl:for-each select="status/active">
<xsl:if test="position() > 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
Better still, upgrade to XSLT 2.0 and do away with this xsl:for-each entirely..
<td>
<xsl:value-of select="status/active" separator="," />
</td>
<xsl:template match="/">
<html>
<head>
<title>Email Listing</title>
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>E-mail Address</th>
<th>Status</th>
</tr>
<xsl:for-each select="emailList/person">
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="email"/></td>
<td>
<xsl:for-each select="status">
<xsl:value-of select="active"/>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>

table looping xml xsl is not displaying

I need to use below xml code file and xsl code file in order to display content on the browser. for some reason, I cannot figure out anything:
The display should look like following, but I cannot figure out what in the world I need to do:
XML file:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="invoices.xsl"?>
<invoices>
<invoice number="25" date="February 28, 2001">
<patient firstname="Jeff" familyname="Smith" SSN="123456789">
<phone type="home" number="123-4567890"/>
<phone number="321-76543321" type="work"/>
<address type="home" line1="123 Street" city="City" state="US" zip="12345"/>
</patient>
<insurance name="Humongous First Medical Insurance" plannumber="12345" planname="The Client Company">
<phone number="098-76543321"/>
<address type="business" line1="321 Street" city="City" state="US" zip="54321"/>
</insurance>
<procedure code="123" name="Cleaning nose" cost="50.00" insurance_estimate="50.00"/>
<procedure code="124" name="Tarot reading of illnesses" cost="150.00" insurance_estimate="120.00"/>
<procedure code="125" name="Just for fun" cost="100.00" insurance_estimate="80.00"/>
</invoice>
<invoice number="27" date="February 28, 2001">
<patient firstname="James" familyname="Smith" SSN="123456765">
<phone type="home" number="123-4562245"/>
<address type="home" line1="432 Street" city="City" state="US" zip="12343"/>
</patient>
<insurance name="Humongous Second Medical Insurance" plannumber="3455" planname="Another Client Company">
<phone number="098-76543321"/>
<address type="business" line1="344 Street" city="Some City" state="US" zip="54323"/>
</insurance>
<procedure code="123" name="Cleaning nose" cost="50.00" insurance_estimate="50.00"/>
<procedure code="124" name="Tarot reading of illnesses" cost="150.00" insurance_estimate="120.00"/>
</invoice>
<invoice number="29" date="February 28, 2001">
<patient firstname="Neil" familyname="Smith" SSN="123456345">
<phone type="home" number="125-4345890"/>
<address type="home" line1="187 Street" city="Lost City" state="US" zip="42145"/>
</patient>
<insurance name="Humongous Third Medical Insurance" plannumber="12345" planname="The Lost City Client Company">
<phone number="198-76345321"/>
<address type="business" line1="342 Street" city="Completely Lost City" state="US" zip="111111-0000"/>
</insurance>
<procedure code="123" name="Cleaning nose" cost="50.00" insurance_estimate="50.00"/>
<procedure code="125" name="Maybe they wouldn't see this line..." cost="100.00" insurance_estimate="80.00"/>
</invoice>
</invoices>
XSL file code:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<HTML>
<BODY bgcolor="#FFFFE0">
<!-- -->
<TABLE border="0" width="100%">
<xsl:for-each select="/invoices/invoice">
<tr>
<td>
<H1>Invoice #
<xsl:value-of select="#number"/>,<BR/>
<xsl:value-of select="#date"/>
</H1>
<TD align="right"><img src="sax_extractData_logo.gif"/>
</td>
</td>
</TR>
</xsl:for-each>
<!-- -->
</TABLE>
<TABLE border="0" width="100%">
<TR valign="top">
<TD>
<xsl:for-each select="/invoice/patient">
To: <xsl:value-of select="#firstname"/><xsl:text> </xsl:text>
<xsl:value-of select="#familyname"/>
<BR/>Account #<xsl:value-of select="#SSN"/>
<BR/>
<xsl:value-of select="address/#line1"/><BR/>
<xsl:if test="address/#line2!=''">
<xsl:value-of select="address/#line2"/><BR/>
</xsl:if>
<xsl:value-of select="address/#city"/>,
<xsl:value-of select="address/#state"/>
<xsl:value-of select="address/#zip"/><BR/>
</xsl:for-each>
</TD>
<TD>
<xsl:for-each select="/invoice/insurance">
Insurance: <xsl:value-of select="#name"/><BR/>
Plan name: <xsl:value-of select="#planname"/><BR/>
Plan #<xsl:value-of select="#plannumber"/><BR/>
<xsl:value-of select="address/#line1"/><BR/>
<xsl:if test="address/#line2!=''">
<xsl:value-of select="address/#line2"/><BR/>
</xsl:if>
<xsl:value-of select="address/#city"/>,
<xsl:value-of select="address/#state"/>
<xsl:value-of select="address/#zip"/><BR/>
<xsl:value-of select="phone/#number"/><BR/>
</xsl:for-each>
</TD>
</TR>
</TABLE>
<P> </P>
<TABLE border="1" width="100%">
<TR>
<TD width="20%">Code</TD>
<TD width="20%">Name</TD>
<TD width="20%">Cost</TD>
<TD width="20%">Insurance estimate</TD>
</TR>
<xsl:for-each select="/invoice/procedure">
<TR>
<TD width="20%"><xsl:value-of select="#code"/></TD>
<TD width="20%"><xsl:value-of select="#name"/></TD>
<TD width="20%"><xsl:value-of select="#cost"/></TD>
<TD width="20%"><xsl:value-of select="#insurance_estimate"/></TD>
</TR>
</xsl:for-each>
</TABLE>
<P> </P>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
In situations like this, rather than write a whole load of XSLT and wonder why it is not working, take a step back, and start with something simple that does work, and build upon that. You have started off all right with selecting the individual invoices, so you could initially just get it to output invoice numbers
<xsl:for-each select="/invoices/invoice">
<H1>
Invoice #<xsl:value-of select="#number"/>
</H1>
<xsl:for-each>
But your problems lie with how you select the patient element. You are doing this....
<xsl:for-each select="/invoice/patient">
But there are two problems. Firstly, this is an absolute expression, not relative to the node you are currently positioned on. The first / represents the top-level document node, so it is looking for root node of invoice in the XML, which does not exist.
Secondly, the loop is in the wrong place anyway. It needs to go inside the xsl:for-each loop for selecting invoice elements, not after it. Then, you can write this....
<xsl:for-each select="/invoices/invoice">
<xsl:for-each select="patient">
To: <xsl:value-of select="#firstname"/> <xsl:value-of select="#familyname"/>
<BR/>
</xsl:for-each>
</xsl:for-each>
Note how the expression is now patient and relative to the current invoice item. You would do similar for the insurance and procedure elements.
Actually, it is better to use xsl:apply-templates here, rather than xsl:for-each, as if nothing else it avoids excess indentation. So, your for-each statement becomes this...
<xsl:apply-templates select="patient" />
And then your have a separate template to output the details
<xsl:template match="patient">
To: <xsl:value-of select="#firstname"/> <xsl:value-of select="#familyname"/>
<BR/>
</xsl:template>
Try this XSLT as a starter. Note, I am not outputting many HTML tables here, or all of the fields, but it should give you idea, so you can build upon it:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<HTML>
<BODY bgcolor="#FFFFE0">
<xsl:apply-templates select="invoices/invoice" />
</BODY>
</HTML>
</xsl:template>
<xsl:template match="invoice">
<H1>
Invoice #<xsl:value-of select="#number"/>
</H1>
<img src="sax_extractData_logo.gif"/>
<br/>
<xsl:apply-templates select="patient" />
<xsl:apply-templates select="insurance" />
<table>
<xsl:apply-templates select="procedure" />
</table>
</xsl:template>
<xsl:template match="patient">
To: <xsl:value-of select="#firstname"/> <xsl:value-of select="#familyname"/>
<BR/>
</xsl:template>
<xsl:template match="insurance">
Insurance: <xsl:value-of select="#name"/>
<BR/>
</xsl:template>
<xsl:template match="procedure">
<tr>
<td><xsl:value-of select="#code"/></td>
<td><xsl:value-of select="#name"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>

Counting unique XML element attributes using XSLT

I've just started using XSLT and am trying to figure this out.
Your help would be greatly appreciated.
Example XML file
<purchases>
<item id = "1" customer_id = "5">
</item>
<item id = "2" customer_id = "5">
</item>
<item id = "7" customer_id = "4">
</item>
</purchases>
Desired HTML Output:
<table>
<tr><th>Customer ID</th><th>Number of Items Purchased</th></tr>
<tr><td>5</td><td>2</td></tr>
<tr><td>4</td><td>1</td></tr>
</table>
Customer with id number 5 has bought 2 items.
Customer with id number 4 has bought 1 item.
XSLT 1.0 solution ....
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="purchases/item"/>
</table>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(
preceding-sibling::*[#customer_id=current()/#customer_id]
)">
<tr><td><xsl:value-of select="#customer_id"/></td>
<td><xsl:value-of select="count(following-sibling::item
[#customer_id=current()/#customer_id])+1"/></td>
</tr>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
A possible Xslt 1.0 solution would be
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:key name="kCustomer" match="item" use="#customer_id"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="purchases">
<xsl:element name="table">
<xsl:element name="tr">
<xsl:element name="th">Customer ID</xsl:element>
<xsl:element name="th">Number of Items Purchased</xsl:element>
<xsl:for-each select="item[generate-id(.) = generate-id(key('kCustomer', #customer_id))]">
<xsl:variable name="total" select="count(/purchases/item[#customer_id=current()/#customer_id]/#id)" />
<xsl:element name="tr">
<xsl:element name="td">
<xsl:value-of select="./#customer_id" />
</xsl:element>
<xsl:element name="td">
<xsl:value-of select="$total" />
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Adopted from Dimitres answer on How can I Group and sort based on sum.
As pointed out by Dimitre the purchases template can be simplified to
<xsl:template match="purchases">
<table>
<tr>
<th>Customer ID</th>
<th>Number of Items Purchased</th>
</tr>
<xsl:for-each select="item[generate-id(.) = generate-id(key('kCustomer', #customer_id))]">
<xsl:variable name="total" select="count(/purchases/item[#customer_id=current()/#customer_id]/#id)" />
<tr>
<td>
<xsl:value-of select="./#customer_id" />
</td>
<td>
<xsl:value-of select="$total" />
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
If you want XSLT 2.0 then the following XSLT should do the trick:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes"/>
<xsl:template match="/purchases">
<table>
<tr>
<th>Customer ID</th>
<th>Number of Items Purchased</th>
</tr>
<xsl:for-each-group select="item" group-by="#customer_id">
<tr>
<td>
<xsl:value-of select="current-grouping-key()"/>
</td>
<td>
<xsl:value-of select="count(current-group())"/>
</td>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>