I have a source xml like below. Im trying to convert to a desired format using xslt as shown below
**Sample XML**
<table>
<row>
<tablename>table1</tablename>
<columnname>col1</columnname>
<columnDesc>col1_desc</columnDesc>
<columnDataType>Number</columnDataType>
<ultimateSourceTable>sourceTable1</ultimateSourceTable>
</row>
<row>
<tablename>table1</tablename>
<columnname>col1</columnname>
<columnDesc>col1_desc</columnDesc>
<columnDataType>Number</columnDataType>
<ultimateSourceTable>sourceTable2</ultimateSourceTable>
</row>
<row>
<tablename>table2</tablename>
<columnname>table2_col1</columnname>
<columnDesc>table2_col1_desc</columnDesc>
<columnDataType>String</columnDataType>
<ultimateSourceTable>sourceTable2</ultimateSourceTable>
</row>
</table>
current output
<Prod>
<dataBase>
<physicalTableName>
<tableName>table1</tableName>
</physicalTableName>
<columnList>
<name>col1</name>
<name>col1</name>
<name>table2_col1</name>
</columnList>
<finalSourceList>
<column>
<columnName>col1</columnName>
<ultimateSourceTable>sourceTable1</ultimateSourceTable>
</column>
<column>
<columnName>col1</columnName>
<ultimateSourceTable>sourceTable2</ultimateSourceTable>
</column>
</finalSourceList>
<physicalTableName>
<tableName>table2</tableName>
</physicalTableName>
<columnList>
<name>col1</name>
<name>col1</name>
<name>table2_col1</name>
</columnList>
<finalSourceList>
<column>
<columnName>table2_col1</columnName>
<ultimateSourceTable>sourceTable2</ultimateSourceTable>
</column>
</finalSourceList>
</dataBase>
Desired output:
<Prod>
<dataBase>
<physicalTableName>
<tableName>table1</tableName>
</physicalTableName>
<columnList>
<name>col1</name>
<columnDesc>col1_desc</columnDesc>
<columnDataType>Number</columnDataType>
</columnList>
<finalSourceList>
<column>
<columnName>col1</columnName>
<ultimateSourceTable>sourceTable1</ultimateSourceTable>
</column>
<column>
<columnName>col1</columnName>
<ultimateSourceTable>sourceTable2</ultimateSourceTable>
</column>
</finalSourceList>
<physicalTableName>
<tableName>table2</tableName>
</physicalTableName>
<columnList>
<name>table2_col1</name>
<columnDesc>table2_col1_desc</columnDesc>
<columnDataType>String</columnDataType>
</columnList>
<finalSourceList>
<column>
<columnName>table2_col1</columnName>
<ultimateSourceTable>sourceTable2</ultimateSourceTable>
</column>
</finalSourceList>
</dataBase>
</Prod>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:template match="/">
<Prod>
<xsl:apply-templates />
</Prod>
</xsl:template>
<xsl:key name="kElsByGroup" match="row" use="tablename" />
<xsl:key name="TableColByGroup" match="row" use="concat(tablename,'|',columnname)" />
<xsl:template match="row">
<xsl:apply-templates />
</xsl:template>
<xsl:template
match="row[generate-id()=generate-id(key('kElsByGroup',tablename)[1])]">
<dataBase>
<physicalTableName>
<tableName>
<xsl:value-of select="tablename"></xsl:value-of>
</tableName>
</physicalTableName>
<columnList>
<xsl:for-each
select="//row[generate-id()=generate-id(key('TableColByGroup',concat(tablename,'|',columnname))[1])]">
<xsl:element name="column">
<name>
<xsl:value-of
select="columnname"></xsl:value-of>
</name>
</xsl:element>
</xsl:for-each>
</columnList>
<finalSourceList>
<xsl:for-each
select="key('kElsByGroup',tablename)">
<xsl:element name="column">
<columnName>
<xsl:value-of
select="columnname"></xsl:value-of>
</columnName>
<sourceTable>
<xsl:value-of
select="ultimateSourceTable"></xsl:value-of>
</sourceTable>
</xsl:element>
</xsl:for-each>
</finalSourceList>
</dataBase>
</xsl:template>
<xsl:template
match="row[not(generate-id()=generate-id(key('kElsByGroup',tablename)[1]))]" />
</xsl:stylesheet>
So, I basically want to have only unique value in my columnList tag. I'm trying to subgroup using muenchian grouping but, I still 2 entries of col1 in my columnList tag. Can someone help me out?
With a single table element in the source containing rows of different tables it seems you can solve it using modes to process rows twice, once to create the data of a result table and the second time to create the details of each result table:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="table" match="row" use="tablename"/>
<xsl:key name="col" match="row" use="concat(tablename, '|', columnname)"/>
<xsl:template match="table">
<Prod>
<dataBase>
<xsl:apply-templates select="row[generate-id() = generate-id(key('table', tablename)[1])]" mode="table"/>
</dataBase>
</Prod>
</xsl:template>
<xsl:template match="row" mode="table">
<physicalTableName>
<xsl:value-of select="tablename"/>
</physicalTableName>
<columnList>
<xsl:apply-templates select="key('table', tablename)[generate-id() = generate-id(key('col', concat(tablename, '|', columnname))[1])]/columnname"/>
</columnList>
<finalSourceList>
<xsl:apply-templates select="key('table', tablename)"/>
</finalSourceList>
</xsl:template>
<xsl:template match="row/columnname">
<name>
<xsl:value-of select="."/>
</name>
</xsl:template>
<xsl:template match="row">
<column>
<xsl:apply-templates select="*[not(self::tablename)]" mode="source-list"/>
</column>
</xsl:template>
<xsl:template match="row/columnname" mode="source-list">
<columnName>
<xsl:value-of select="."/>
</columnName>
</xsl:template>
<xsl:template match="row/ultimateSourceTable" mode="source-list">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/naZYrpu/1
Related
I have some xml data with multiple Name nodes. Based on the presence or absence of id node, I need to segregate the nodes. On converting to JSON, I want all the similar nodes to be merged into an JSON array. Below is my XML data
<Names>
<CustName>
<Name>Name1</Name>
<id>3</id>
</CustName>
<CustName >
<Name>Name2</Name>
</CustName>
<CustName>
<Name>Name3</Name>
<id>32</id>
</CustName>
</Names>
The XSLT that I have tried is as follows. But this creates two nodes for Update and one node for Create. Whereas I want 1st and 3rd Name nodes to be under Update node and 2nd Name node under Create
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames>
<xsl:for-each select="//Names/CustName">
<xsl:choose>
<xsl:when test="id !=''">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:when>
<xsl:otherwise>
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Transformed xml should look like:
<CustomerNames>
<Update>
<CustName>Name1</CustName>
<id>3</id>
</Update>
<Update>
<CustName>Name3</CustName>
<id>32</id>
</Update>
<Create>
<Name>Name2</Name>
</Create>
</CustomerNames>
On converting to json, I want the similar nodes to be appended in an array. Like this
{
"CustomerNames":{
"Update":[
{
"CustName":"Name1",
"id":"3"
},
{
"CustName":"Name3",
"id":"32"
}
],
"Create":[
{
"Name":"Name2"
}
]
}
}
How can I achieve this in XSL 1.0?
It seems the order nodes are placed matters when auto-converting XML to a JSON. Hence update your XSLT to something like the one below.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames>
<xsl:for-each select="//Names/CustName[id]">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:for-each>
<xsl:for-each select="//Names/CustName[not(id)]">
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Alternatively, you can group the XML nodes by using XSL Sort
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames xmlns="">
<xsl:for-each select="//Names/CustName">
<xsl:sort select="id"/>
<xsl:choose>
<xsl:when test="id !=''">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:when>
<xsl:otherwise>
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Another approach would be to use the Payloadfactry mediator after the XSLT mediator to group the nodes.Example below.
<payloadFactory media-type="xml">
<format>
<CustomerNames>
$1
$2
</CustomerNames>
</format>
<args>
<arg expression="//Update"/>
<arg expression="//Create"/>
</args>
</payloadFactory>
I add all the nodes' attributes which have the same index atrribute. I multiply this sum by a particular number (extracted from a secontary xml) and I have the step_1 product. I repeat the proceedure multiplying each time by a different particular number, and I add the new product to the product of the previous step and so on up to the last node. But in each step of the loop the sum functions looses the runningsum keeping only the current product. I would appreciate any help.
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="koinohrista.xsl"?>
<Features>
<Feature code="a" price="1"></Feature>
<Feature code="a" price="2"></Feature>
<Feature code="b" price="3"></Feature>
<Feature code="b" price="4"></Feature>
<Feature code="c" price="5"></Feature>
</Features>
The secondary xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="hiliosta.xsl"?>
<Features>
<Feature name="a1" koinoh="10" elev="20" heat="30"></Feature>
</Features>
The xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="Inventory" select="document('C:\Kapopoulos\Inventory.xml')"/>
<xsl:key name="KeyCode" match="Feature" use="#code"/>
<xsl:template match="Features">
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="Feature"/>
<xsl:with-param name="sum" select="0"/>
</xsl:call-template>
</xsl:template>
<!-- Sum Function -->
<xsl:template name="sum">
<xsl:param name="nodes"/>
<xsl:param name="sum"/>
<xsl:variable name="curr" select="$nodes[1]"/>
$sum1 = <xsl:value-of select="$sum"/><br/> <!-- for view reasons -->
<table>
<xsl:for-each select="Feature[generate-id() = generate-id(key('KeyCode',#code)[1])]">
<xsl:sort select="#code" order="ascending"/>
<xsl:if test="$curr">
Step1 = <xsl:value-of select="$sum"/> ||| <!-- for view reasons -->
<xsl:variable name="attrName">
<xsl:choose>
<xsl:when test="#code='a' or #code='c' or #code='d'">koinoh</xsl:when>
<xsl:when test="#code='b'">elev</xsl:when>
<xsl:when test="#code='e'">heat</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="runningsum" select="$sum + sum(key('KeyCode',#code)/#price)
* $Inventory/*/Feature[#name='a1']/#*[name()=$attrName] div sum($Inventory//Feature/#*[name()=$attrName])"/>
RunSum2 = <xsl:value-of select="$runningsum"/><br/> <!-- for view reasons -->
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
<xsl:with-param name="sum" select="$runningsum"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
<xsl:if test="not($curr)"> Final=
<xsl:value-of select="$sum"/>
</xsl:if>
</table>
</xsl:template>
</xsl:stylesheet>
Desired calculation steps and output
1) SumOfPrice for #code="a" : 1+2=3
2) ProductOfSum : 3 * "koinoh"=3*10=30
3) RunningSum = 30
4) SumOfPrice for #code="b" : 3+4=7
5) ProductOfSum : 7 * "#elev"=7*20=140
6) RunningSum = 30+140=170
7) SumOfPrice for #code="c" : 5
8) ProductOfSum : 5 * "#heat" = 5*30=150
6) RunningSum = 30+140+150=320
DesiredOutput = 320
To get only the final result of 320, you could skip the grouping part and just process each individual Feature on its own. However, it might be useful to minimize the lookups to the "Inventory" document, so let's keep the grouping:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="path-to-inventory">C:\Kapopoulos\Inventory.xml</xsl:param>
<xsl:variable name="inventory" select="document($path-to-inventory)/Features/Feature[#name='a1']"/>
<xsl:key name="feature-by-code" match="Feature" use="#code" />
<xsl:template match="/Features">
<result>
<xsl:call-template name="summarize">
<!-- select distinct codes only -->
<xsl:with-param name="items" select="Feature[count(. | key('feature-by-code', #code)[1]) = 1]"/>
</xsl:call-template>
</result>
</xsl:template>
<xsl:template name="summarize">
<xsl:param name="items" select="/.."/>
<xsl:param name="balance" select="0"/>
<xsl:choose>
<xsl:when test="$items">
<xsl:variable name="code" select="$items[1]/#code" />
<xsl:variable name="sum-price" select="sum(key('feature-by-code', $code)/#price)" />
<xsl:variable name="factor">
<xsl:choose>
<xsl:when test="$code='a'">
<xsl:value-of select="$inventory/#koinoh" />
</xsl:when>
<xsl:when test="$code='b'">
<xsl:value-of select="$inventory/#elev" />
</xsl:when>
<xsl:when test="$code='c'">
<xsl:value-of select="$inventory/#heat" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="product" select="$sum-price * $factor" />
<!-- recursive call -->
<xsl:call-template name="summarize">
<xsl:with-param name="items" select="$items[position() > 1]"/>
<xsl:with-param name="balance" select="$balance + $product"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$balance" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The XML matrix with csv rows
<matrix ROWS="3" COLS="4">
<row>"1,2,3,4"</row>
<row>"5,6,7,8"</row>
<row>"9,10,11,12"</row>
</matrix>
is to be transformed with XSLT 1.0 without extensions into a HTML table
<table>
<tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
<tr><td>5</td><td>6</td><td>7</td><td>8</td></tr>
<tr><td>9</td><td>10</td><td>11</td><td>12</td></tr>
</table>
The number of rows and columns (ROWS, COLS) are variable.
I appreciate any help.
How about:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/matrix">
<table>
<xsl:for-each select="row">
<tr>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring(., 2, string-length(.) - 2)"/>
</xsl:call-template>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="','"/>
<xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" />
<xsl:if test="$token">
<td>
<xsl:value-of select="$token"/>
</td>
</xsl:if>
<xsl:if test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I have XML file:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<Data>
<Records>
<Record>
<AddInfo>
<Info>
</Info>
</AddInfo>
</Record>
</Records>
</Data>
and XSL file:
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Dane">
<html>
<link rel="stylesheet" type="text/css" href="report.css"></link>
<body>
<h2>Table1</h2>
<table border="1" cellspacing="0">
<tr>
<th>XXX</th>
</tr>
<xsl:for-each select="Records/Record">
<tr>
<td>
<xsl:value-of select="XXX"/>
</td>
</tr>
</xsl:for-each>
</table>
<h2>SecondTable</h2>
<table border="1" cellspacing="0">
<tr>
<th>YYY</th>
<th>ZZZ</th>
</tr>
<xsl:for-each select="Records/Record/AddInfo/Info">
<tr>
<td>
<xsl:value-of select="YYY"/>
</td>
<td>
<xsl:value-of select="ZZZ"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
And I want to make it like this: if node exists, display table with "Info" nodes, and if not, display SOME TEXT.
I've been trying
<xsl:if test="following-sibling::AddInfo">
</xsl:if>
and
<xsl:if test="AddInfo">
</xsl:if>
But it is not working.
I want it like this:
Table1
---------------------
| | | |
(condition: if inside XML will be node, I want to display second table, under Table1)
SecondTable
-------------
| | |
How I can do this?
This outputs Yep if <AddInfo> exists as immediate child of <Record>, and Nope otherwise:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<xsl:for-each select="Records/Record">
<xsl:choose>
<xsl:when test="AddInfo">Yep</xsl:when>
<xsl:otherwise>Nope</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note that you don't need for-each, you should let a second template match each <Record>:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="Data/Records/Record">
<xsl:choose>
<xsl:when test="AddInfo">Yep</xsl:when>
<xsl:otherwise>Nope</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You could also avoid choose and use two independent if conditions:
<xsl:template match="Data/Records/Record">
<xsl:if test="AddInfo">Yep</xsl:if>
<xsl:if test="not(AddInfo)">Nope</xsl:if>
</xsl:template>
If you don't want to limit it to immediate children, use .//AddInfo instead.
Consider the following stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<xsl:apply-templates select="Records/Record"/>
</xsl:template>
<xsl:template match="Data/Records/Record">
<table class="one"></table>
<xsl:if test="AddInfo">
<table class="two"></table>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It outputs
<table class="one"></table>
if there's no <AddInfo> node in <Record>, and
<table class="one"></table>
<table class="two"></table>
otherwise.
You can solve this with neither using if nor choose. XML:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<Data>
<AddInfo>
<Info>This is ignored</Info>
</AddInfo>
<Records>
<Record>
<AddInfo>
<Info>One,</Info>
<Info>Two,</Info>
<Info>Three</Info>
</AddInfo>
</Record>
<Record>
<Info>Ignored as well</Info>
</Record>
<Record>
<Nested>
<AddInfo>
<Info>So is this</Info>
</AddInfo>
</Nested>
</Record>
</Records>
</Data>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<root>
<xsl:apply-templates select="Records/Record"/>
</root>
</xsl:template>
<xsl:template match="Data/Records/Record">
<xsl:copy>
<table id="one"></table>
<xsl:apply-templates select="AddInfo"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data/Records/Record/AddInfo">
<table id="two">
<xsl:apply-templates select="Info"/>
</table>
</xsl:template>
<xsl:template match="Data/Records/Record/AddInfo/Info">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<Record>
<table id="one" />
<table id="two">One,Two,Three</table>
</Record>
<Record>
<table id="one" />
</Record>
<Record>
<table id="one" />
</Record>
</root>
To check if node exist in xml this XSLT code works
<xsl:choose>
<xsl:when test="XMLNodeName">
<Cell ss:Index="2" ss:StyleID="s110">
<Data ss:Type="String">
<xsl:value-of select="NodeOne"/>
</Data>
</Cell>
</xsl:when>
<xsl:otherwise>
<Cell`enter code here` ss:Index="2" ss:StyleID="s110">
<Data ss:Type="String">
<xsl:value-of select="NodeTwo"/>
</Data>
</Cell>
</xsl:otherwise>
</xsl:choose>
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>