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>
Related
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>
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>
I am a newcomer to XML/XSLT and, so far, in searching SO and the W3SChools sites for clever stuff on Meunchian grouping I haven't been able to grasp an effective solution to my challenge.
Essentially I have one large XML file exported from a database (which means I can't directly edit the XML) which contains invoicing information.
I want to utilise XSLT (1.0) to apply a transformation of the XML into HTML (I am using Saxon) so that each invoice is displayed as a table. However, in the XML, there are many product lines which relate to the same invoice (denoted by the <invoiceNum> element as the identifier).
I don't want to have to create & display a new table for each product line which is part of the same invoice. In my current XSL file, I am trying to create a table for the first instance of a duplicated <invoiceNum> element, and then add only the unique elements from successive product lines (<ProductID>, <ProductName>, <ProductDescription> etc.) and leave out the duplicated shipping information.
In the XML code snippet, you can see the layout of the XML. In the XSL snippet, I hope you can see how I am trying to construct the table. For each successive <invoices_snet> instance in the XML file, I want to add just the product information to the table through the transformation. Using for-each on the <invoices_snet> element simply creates a new table each time.
Should I use conditional logic here, compare the equality of the value of the <invoices_snet> element, use templates?
Your help is much appreciated!
<database>
<invoices>
<invoices_snet>
<invoiceNum NAME="invoiceNum" TYPE="SMALLINT">368</invoiceNum>
<ProductID NAME="ProductID" TYPE="VARCHAR">SS106</ProductID>
<ProductName NAME="ProductName" TYPE="VARCHAR">Senna Sunglasses</ProductName>
<ProductDescription NAME="ProductDescription" TYPE="VARCHAR">Lively sunglasses</ProductDescription>
<Quantity NAME="Quantity" TYPE="SMALLINT">34</Quantity>
<UnitPrice NAME="UnitPrice" TYPE="CURRENCY">40.0000</UnitPrice>
<ExtendedPrice NAME="ExtendedPrice" TYPE="CURRENCY">1360.0000</ExtendedPrice>
<contactName NAME="contactName" TYPE="VARCHAR">Jeff</contactName>
<shippingStreet NAME="shippingStreet" TYPE="VARCHAR">11 Acacia Avenue</shippingStreet>
<shippingCity NAME="shippingCity" TYPE="VARCHAR">Huddersfield</shippingCity>
<shippingCounty NAME="shippingCounty" TYPE="VARCHAR">Yorkshire</shippingCounty>
<shippingPostcode NAME="shippingPostcode" TYPE="VARCHAR">YO12 8LA</shippingPostcode>
<saleDate NAME="salesDate" TYPE="DATETIME">30. Mar. 16</saleDate>
</invoices_snet>
<invoices_snet>
<invoiceNum NAME="invoiceNum" TYPE="SMALLINT">368</invoiceNum>
<ProductID NAME="ProductID" TYPE="VARCHAR">SS368</ProductID>
<ProductName NAME="ProductName" TYPE="VARCHAR">Senna T-shirts</ProductName>
<ProductDescription NAME="ProductDescription" TYPE="VARCHAR">T-shirts of beige colour with cream piping</ProductDescription>
<Quantity NAME="Quantity" TYPE="SMALLINT">20</Quantity>
<UnitPrice NAME="UnitPrice" TYPE="CURRENCY">15.00</UnitPrice>
<ExtendedPrice NAME="ExtendedPrice" TYPE="CURRENCY">300.00</ExtendedPrice>
<contactName NAME="contactName" TYPE="VARCHAR">Jeff</contactName>
<shippingStreet NAME="shippingStreet" TYPE="VARCHAR">11 Acacia Avenue</shippingStreet>
<shippingCity NAME="shippingCity" TYPE="VARCHAR">Huddersfield</shippingCity>
<shippingCounty NAME="shippingCounty" TYPE="VARCHAR">Yorkshire</shippingCounty>
<shippingPostcode NAME="shippingPostcode" TYPE="VARCHAR">YO12 8LA</shippingPostcode>
<saleDate NAME="salesDate" TYPE="DATETIME">30. Mar. 16</saleDate>
</invoices_snet>
</invoices>
</database>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/database">
<html>
<head>
<title>Invoice</title>
<link rel="stylesheet" href="invoicingCSS.css"/>
</head>
<body>
<h1>Invoice</h1>
<br></br>
<xsl:for-each select="invoices/invoices_snet">
<h2>Order for Invoice Number: <xsl:value-of select="invoiceNum"/> </h2>
<table>
<tr>
<th>Product ID</th>
<th>Product Name</th>
<th>Product Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Extended Price</th>
<th>Contact Name</th>
<th>Shipping Address</th>
<th>Sales Date</th>
</tr>
<tr>
<td>
<xsl:value-of select="ProductID"/>
</td>
<td>
<xsl:value-of select="ProductName"/>
</td>
<td>
<xsl:value-of select="ProductDescription"/>
</td>
<td>
<xsl:value-of select="Quantity"/>
</td>
<td>
<xsl:value-of select="UnitPrice"/>
</td>
<td>
<xsl:value-of select="ExtendedPrice"/>
</td>
<td>
<xsl:value-of select="contactName"/>
</td>
<td>
<xsl:value-of select="concat(
shippingStreet,' ',
shippingCity,', ',
shippingCounty,', ',
shippingPostcode)"
/>
</td>
<td>
<xsl:value-of select="saleDate"/>
</td>
</tr>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Grouping is much easier to do in XSLT 2.0, with its built-in xsl:for-each-group instruction.
Try this as your starting point:
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:variable name="theader">
<tr>
<th>ProductID</th>
<th>Quantity</th>
<!-- add more as required -->
</tr>
</xsl:variable>
<xsl:template match="/database">
<html>
<xsl:for-each-group select="invoices/invoices_snet" group-by="invoiceNum">
<table border="1">
<xsl:copy-of select="$theader"/>
<xsl:apply-templates select="current-group()"/>
</table>
</xsl:for-each-group>
</html>
</xsl:template>
<xsl:template match="invoices_snet">
<tr>
<td>
<xsl:value-of select="ProductID"/>
</td>
<td>
<xsl:value-of select="Quantity"/>
</td>
<!-- add more as required -->
</tr>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="invoices_snet">
<tr>
<td>
<xsl:value-of select="ProductID"/>
</td>
<td>
<xsl:value-of select="Quantity"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
First do you self a favor and have a look for xslt grouping for XSLT 1.0 this will be muenchian grouping e.g. this
And here the basic structure for a adaption to your xml:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:key name="kinvoices_snet" match="invoices_snet" use="invoiceNum"/>
<xsl:template match="invoices">
<xsl:for-each select="invoices_snet [
count ( key('kinvoices_snet',./invoiceNum)[1] | . ) = 1 ]">
<xsl:variable name="this" select="." />
<g>
<gh>
<xsl:value-of select="invoiceNum" />
</gh>
<!-- group stuff -->
<xsl:for-each select=" key('kinvoices_snet',$this/invoiceNum)">
<!-- group member stuff -->
<gm>
<xsl:value-of select="ProductID" />
</gm>
</xsl:for-each>
</g>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I am working on improving on some stylesheets that I have inherited and converting them from using <xsl:for-each> to <xsl:apply-templates>. A very simplified version of one of the XML files I will be working with is:
<Root>
<Row ID="123" Region="AMS">
<First>Graham</First>
<Last>Smith</Last>
<Sales>12345.85</Sales>
<Team>Team A</Team>
</Row>
<Row id="321">
<First>John</First>
<Last>Brown</Last>
<Sales>18765.85</Sales>
<Team>Team C</Team>
</Row>
<Row id="456" Region="EMEA">
<First>Anne</First>
<Last>Jones</Last>
<Sales>34567.85</Sales>
<Team>Team B</Team>
</Row>
</Root>
The new stylesheet I have is:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:variable name="RowCount" select="count(/*/*)"/>
<xsl:template match="/#* | node()">
<style>
body * {font-family:Arial;font-size:11pt}
table {border-collapse:collapse}
td {border-bottom:1px solid #D8D8D8;padding:7px}
tr.row1 {background:#F9F9F9;}
td.tdHeader {border-bottom:2px solid #DDD;font-weight:700}
</style>
<table>
<thead>
<tr>
<xsl:apply-templates select="*[1]/#*" mode="headerAttributes" />
<xsl:apply-templates select="*[1]/*" mode="headerFields"/>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="*"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/#*" mode="headerAttributes">
<td class="tdHeader">
<xsl:value-of select="name()" />
</td>
</xsl:template>
<xsl:template match="/*/*/*" mode="headerFields">
<td class="tdHeader">
<xsl:value-of select="name()" />
</td>
</xsl:template>
<xsl:template match="/*/*">
<tr class="row{position() mod 2}">
<xsl:apply-templates select="#*" mode="attributes"/>
<xsl:apply-templates select="*" mode="fields"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/#*" mode="attributes">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
<xsl:template match="/*/*/*" mode="fields">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
</xsl:stylesheet>
but, due to the second node in the XML is missing the <Region> attribute, the cells on the result are mis-aligned, with first name now in the Region column, Last name in First name column and so on. This also happens if there is a missing child node on the Row node. for example, no team element
I have tried to test for a missing node, before calling the apply-template, and within the last two templates, but to no avail.
Any ideas? What am I missing here? I am only just starting to get my head around using apply-templates, but other methods of writing stylesheets I am fine with.
Provisionally, this works for your input:
<xsl:template match="Row">
<tr class="row{position() mod 2}">
<td><xsl:apply-templates select="#ID|#id" mode="attr2"/></td>
<td><xsl:apply-templates select="#Region" mode="attr2"/></td>
<xsl:apply-templates select="*" mode="fields"/>
</tr>
</xsl:template>
<xsl:template match="#ID|#id|#Region" mode="attr2">
<b><xsl:value-of select="." /></b>
</xsl:template>
-- and you may remove the catch-all for mode="attributes".
This forces the inclusion of a <td>..</td> pair even if there is no id|ID or Region attribute.
Saxon 8.8 reports an error:
The attribute axis starting at a document-node() node will never select anything
because of your
<xsl:template match="/#* | node()">
Changing it to <xsl:template match="node()">, or, preferably, to <xsl:template match="Root"> will fix this. As I said in my comment, try to use * as little as possible. References to Root and Row can be changed to exact matches.
In brief, my problem is that I want to loop through the contents of one child among identical children, but when I try to do so, it loops through the contents of all the children, giving me as many duplicates of the data as there are children.
Example code:
Input.xml
<?xml version="1.0"?>
<base>
<item name="item1">
<foo>
<type1>1</type1>
<type2>2</type2>
</foo>
<bar>
<type3>3</type3>
</bar>
</item>
<item name="item2">
<foo>
<type1>4</type1>
<type2>5</type2>
</foo>
<bar>
<type3></type3>
<!-- NOTE! This value is missing. Therefore we must put a blank value in the table-->
</bar>
</item>
<item name="item3">
<foo>
<type1>7</type1>
<type2></type2>
<!-- NOTE! This value is missing. Therefore we must put a blank value in the table-->
</foo>
<bar>
<type3>9</type3>
</bar>
</item>
</base>
tableMaker.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cyg="http://CygNetSCADA.com/Schemas/CygNetEnterpriseObjects730">
<xsl:output method="html" encoding="UTF-8" />
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Name</th>
<xsl:for-each select="base/item/*/*">
<th>
<xsl:value-of select="local-name()"/>
</th>
</xsl:for-each>
</tr>
<xsl:for-each select="base/item">
<tr>
<th>
<xsl:value-of select="#name"/>
</th>
<xsl:for-each select="foo/*">
<xsl:choose>
<xsl:when test=".[node()]">
<td>
<xsl:value-of select="." />
</td>
</xsl:when>
<xsl:otherwise>
<td />
<!-- This is for that empty value in item3 -->
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:for-each select="bar/*">
<xsl:choose>
<xsl:when test=".[node()]">
<td>
<xsl:value-of select="." />
</td>
</xsl:when>
<xsl:otherwise>
<td />
<!-- This is for that empty value in item2 -->
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
What happens in this example is that the generated HTML has 10 columns - one for "name", and "type1", "type2", "type3" three times (three times for the three elements; if there were 4 elements in my input there would be 3 more columns). I only want "type1", "type2", and "type3" to display once in the column. How might I go about doing this?
All help is appreciated and thanks in advance!
You're using XSL 2.0 so presumably you have access to the xsl:for-each-group element. It's what you need in this situation:
<tr>
<th>Name</th>
<xsl:for-each-group select="base/item/*/*" group-by="local-name()">
<th>
<xsl:value-of select="current-grouping-key()"/>
</th>
</xsl:for-each>
</tr>