Say I have the following XML:
<a>
<b>1</b>
<b>2</b>
<b>3</b>
</a>
... and require:
Header
1
2
3
... but an xslt like:
<xsl:template match = "/" >
<xsl:variable name="headed" select="false()"/>
<xsl:for-each select = "a/*" >
<xsl:if test="not($headed)">
<xsl:text>Header</xsl:text>
<!--
this next line causes a problem due to
the attempted reassignment of $headed
-->
<xsl:variable name="headed" select="true()"/>
</xsl:if>
<xsl:value-of select="." />
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>
is invalid, can anybody recommend a brief and readable solution? and perhaps a good book to learn a functional mindset from :)
Cheers
Simon
------------------------------ addendum --------------------------
After pondering the answers I've been presented with I realised I've lost some of the key components of the problem I was trying to tackle.
my data is more like:
<address>
<line1>street</line1>
<line2>town</line2>
<line3>city</line3>
<country>uk</country>
</address>
and my desired output is more like:
<table>
<tr><td rowspan="6" valign="top">Address</td><td>street</td></tr>
<tr><td>town</td></tr>
<tr><td>city</td></tr>
<tr><td>uk</td></tr>
</table>
any further help would be greatly appreciated.
<?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="a">Header
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="a/b">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Is practically what you want, and simpler than what you have now.
Just take <xsl:text>Header</xsl:text> out of for-each...
The end result was more like:
<?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="/a">
<html><body><table>
<xsl:apply-templates select="*"/>
</table></body></html>
</xsl:template>
<xsl:template match="a/*">
<xsl:if test="not(.='')">
<TR>
<xsl:if test="position()=1">
<!--
<TD rowspan="6" valign="top">Address</TD>
improved based on Tomalak's suggestion
-->
<xsl:element name="TD">
<xsl:attribute name="rowspan" >
<!--
<cough />
<xsl:value-of select="count(*)"/>
-->
<xsl:value-of select="count(*[not(.='')]"/>
</xsl:attribute>
<xsl:attribute name="valign" >
<xsl:text>top</xsl:text>
</xsl:attribute>
<xsl:text>Address</xsl:text>
</xsl:element>
</xsl:if>
<TD>
<xsl:value-of select="."/>
</TD>
</TR>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Related
Is there a way to determine if a a root level node contains any child nodes?
I have this code file that builds up a navigation menu for a drop-down menu, but for the root nodes that have no nodes below them I want to apply a different template to them:
<?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"/>
<xsl:template match="/Home">
<xsl:apply-templates select="root" />
</xsl:template>
<xsl:template match="root">
<ul class="nav navbar-nav">
<xsl:apply-templates select="node">
<xsl:with-param name="level" select="0"/>
</xsl:apply-templates>
</ul>
</xsl:template>
<xsl:template match="node">
<xsl:param name="level" />
<xsl:choose>
<xsl:when test="$level=0">
<li>
<xsl:attribute name="class">
<xsl:if test="#breadcrumb = 1">active</xsl:if>
<xsl:if test="node">
<xsl:text> dropdown</xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:choose>
<xsl:when test="#enabled = 1">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<xsl:attribute name="class">
<xsl:if test="node">
<xsl:text>dropdown-toggle</xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:if test="node">
<xsl:attribute name="data-toggle">dropdown</xsl:attribute>
</xsl:if>
<xsl:value-of select="#text" />
<xsl:if test="node">
<b class="caret"></b>
</xsl:if>
</a>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#text" />
</xsl:otherwise>
</xsl:choose>
<xsl:if test="node">
<ul class="dropdown-menu">
<xsl:apply-templates select="node">
<xsl:with-param name="level" select="$level + 1" />
</xsl:apply-templates>
</ul>
</xsl:if>
</li>
</xsl:when>
<xsl:otherwise>
<li>
<xsl:attribute name="class">
<xsl:if test="#breadcrumb = 1">active</xsl:if>
<xsl:if test="node">
<xsl:text> dropdown</xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:choose>
<xsl:when test="#enabled = 1">
<a href="{#url}">
<xsl:value-of select="#text" />
</a>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#text" />
</xsl:otherwise>
</xsl:choose>
</li>
<xsl:if test="node">
<!-- no extra level in default bootstrap -->
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Your question is not clear. If the root node does not have any child nodes, then the XML document is empty. Perhaps you meant the root element; there will be exactly one element like that and it's easy to see if it has any child nodes by using:
test="/*/node()"
in an xsl:if or xsl:when instruction.
Alternatively, you could use two templates - one matching a root element with child nodes:
<xsl:template match="/*[node()]">
and one for the other case:
<xsl:template match="/*[not(node())]">
You can use this XSLT-file:
<?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" />
<xsl:template match="/*[count(descendant::*) = 0]">
No siblings
</xsl:template>
<xsl:template match="/*[count(descendant::*) > 0]">
Has siblings
</xsl:template>
</xsl:stylesheet>
With an input XML file with one or more siblings of the root element like this
<?xml version="1.0"?>
<root>
<a />
</root>
it will output "Has siblings".
And with an input file with an empty root tag like this
<?xml version="1.0"?>
<root>
</root>
it will output "No siblings".
If I understand the question correctly, try adding the template rule
<xsl:template match="root[not(*)]"/>
My XML contains description of some resource. I want to build HTML with JSON, XML, etc. representation of this resource.
Here is XML:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<resource name="item">
<parameters>
<parameter name="id">
<example-value>1234</example-value>
<type>integer</type>
</parameter>
<parameter name="name">
<example-value>parameter</example-value>
<type>string</type>
</parameter>
<parameter name="timestamp">
<example-value>1466589751</example-value>
<type>integer</type>
</parameter>
<parameter name="status">
<example-value>1</example-value>
<type>integer</type>
</parameter>
</parameters>
</resource>
</resources>
And here is how I do it:
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="resources">
<body>
<xsl:apply-templates/>
</body>
</xsl:template>
<xsl:template match="resource">
<div>
<h2>
<xsl:value-of select="#name"/>
</h2>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="parameters">
<h3>
<xsl:text>Examples</xsl:text>
</h3>
<div>
<xsl:call-template name="CSVExample"/>
<xsl:call-template name="TableExample"/>
<xsl:call-template name="JSONExample"/>
<xsl:call-template name="XMLExample"/>
</div>
</xsl:template>
<xsl:template name="CSVExample">
<div>
<pre>
<xsl:for-each select="parameter">
<xsl:value-of select="concat('"',#name,'"')"/>
<xsl:if test="position() < last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:for-each select="parameter">
<xsl:value-of select="concat('"',example-value,'"')"/>
<xsl:if test="position() < last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
</pre>
</div>
</xsl:template>
<xsl:template name="TableExample">
<div>
<pre>
<table>
<tr>
<xsl:for-each select="parameter">
<th>
<xsl:value-of select="#name"/>
</th>
</xsl:for-each>
</tr>
<tr>
<xsl:for-each select="parameter">
<td>
<xsl:value-of select="example-value"/>
</td>
</xsl:for-each>
</tr>
</table>
</pre>
</div>
</xsl:template>
<xsl:template name="JSONExample">
<div>
<pre>
<xsl:text>{</xsl:text>
<xsl:for-each select="parameter">
<xsl:value-of select="concat('"',#name,'":')"/>
<xsl:if test="type = 'string'">
<xsl:text>"</xsl:text>
</xsl:if>
<xsl:value-of select="example-value"/>
<xsl:if test="type = 'string'">
<xsl:text>"</xsl:text>
</xsl:if>
<xsl:if test="position() < last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>}</xsl:text>
</pre>
</div>
</xsl:template>
<xsl:template name="XMLExample">
<div>
<pre>
<parameters>
<xsl:for-each select="parameter">
<xsl:element name="{#name}">
<xsl:value-of select="example-value"/>
</xsl:element>
</xsl:for-each>
</parameters>
</pre>
</div>
</xsl:template>
</xsl:stylesheet>
Do You think this is right way to do such a case? I mean templates structure, calls, applys, etc.
Should I mayby replace for-each with some apply-templates? I read somewhere that is better to avoid for-each in favour of apply-templates. But is that true in this case?
I'm sure we could discuss many alternative ways to rework your xsl but to be honest this is not the correct forum for such things -- You really ought to be using that code review forum on Stack Exchange.
That said, I think your xsl structure is generally good - better than most we see on this site!
However, as it stands if <parameters> exits without any child <<parameter> nodes the call-templates wil still generate unwanted(?) content.
You would be better to use apply-templates with a mode attribute.
<xsl:template match="properties" mode="CSVExample">
....
</xsl:template>
and replace the call-templates with
<xsl:apply-templates select="." mode="CSVExample"/>
As for deciding betwee the for-each or apply-templates : When using a select="", I dont think it generally matters which way you go. Personally, I opt for apply-templates every time, it's more flexible and only use for-each whenever the code looks neater or is easier to understand that way.
I have this XML snippet
<MediaTypes>
<DVD>true</DVD>
<HDDVD>false</HDDVD>
<BluRay>true</BluRay>
</MediaTypes>
And I want to create an output that looks like this
DVD / Blu-ray
or like this
DVD
or like this
DVD / HD-DVD / Blu-ray
depending on the true/false states
But I'm a total beginner in the XSLT game. :-/
Here's a snippet of the full XML
<?xml version="1.0" encoding="Windows-1252"?>
<Collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DVD>
<ProfileTimestamp>2012-06-07T05:20:51Z</ProfileTimestamp>
<ID>0044005507027.5</ID>
<MediaTypes>
<DVD>true</DVD>
<HDDVD>false</HDDVD>
<BluRay>false</BluRay>
</MediaTypes>
<UPC>0-044005-507027</UPC>
<CollectionNumber>448</CollectionNumber>
<CollectionType IsPartOfOwnedCollection="true">Owned</CollectionType>
<Title>The Big Lebowski</Title>
<DistTrait />
<OriginalTitle />
<CountryOfOrigin>United States</CountryOfOrigin>
...
</DVD>
<DVD>
...
</DVD>
</Collection>
I tried to follow the advices given below (regarding the loop), but it doesn't seem to work.
This is the XSL I have so far (complete):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="MediaTypes" match="MediaTypes">
Test
<xsl:for-each select="*[.='true']">
<xsl:value-of select="name()"/>
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<html>
<head>
<title>DVD Profiler Double Dips</title>
</head>
<body>
<div>
<h1>DVD Profiler Double Dips</h1>
</div>
<table border="1" cellpadding="3" cellspacing="0">
<tr>
<th>Title</th>
<th>Edition</th>
<th>Production Year</th>
<th>DVD /</th>
<th>Purchase Date</th>
<th>Collection Type</th>
<th>Original Title</th>
<th>Sort Title</th>
</tr>
<xsl:for-each select="/Collection/DVD">
<tr>
<td>
<xsl:value-of select="Title"/>
</td>
<td>
<xsl:if test="DistTrait != ''">
<xsl:value-of select="DistTrait"/>
</xsl:if>
<xsl:if test="DistTrait = ''">
</xsl:if>
</td>
<td>
<xsl:if test="ProductionYear != ''">
<xsl:value-of select="ProductionYear"/>
</xsl:if>
<xsl:if test="ProductionYear = ''">
</xsl:if>
</td>
<td>
<xsl:call-template name="MediaTypes" />
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
But where
<xsl:call-template name="MediaTypes" />
is standing, only "Test" comes out
Yay!
You guys are awesome. Yes
<xsl:apply-templates select="MediaTypes"/>
Was my weapon of choice.
My final template looks like this:
<xsl:template name="MediaTypes" match="MediaTypes">
<xsl:for-each select="*[.='true']">
<xsl:value-of select="name()"/>
<xsl:if test="position()!=last()">
<xsl:text> / </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:if test="CustomMediaType != ''">
<xsl:if test="*[.='true']">
<xsl:text> / </xsl:text>
</xsl:if>
<xsl:value-of select="CustomMediaType"/>
</xsl:if>
</xsl:template>
begins to understand how stuff works
Thanks for all your help!
The question is rather trivial - the real problem is that you only show a snippet of the XML document. XSLT is very much context-dependent.
Here is a template to match any <MediaTypes> element and output the required string; hopefully you'll know how to incorporate it into your own stylesheet:
<xsl:template match="MediaTypes">
<xsl:for-each select="*[.='true']">
<xsl:value-of select="name()"/>
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
I'd advice you to change the xml a bit
Here is an example
<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" ?>
<MediaTypes>
<Media name="DVD">true</Media>
<Media name="HDDVD">false</Media>
<Media name="BluRay">true</Media>
</MediaTypes>
This way you may write XPATH expressions like //MediaTypes/Media
However did #michael.hor257k write a nice solution, jacking this xml pattern into that would look like this
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="//MediaTypes">
<xsl:for-each select="*[.='true']">
<xsl:value-of select="#name"/>
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This will render DVD/BluRay
Test here http://xslttest.appspot.com/
i am trying to write an xslt code that will check whether the description element exist or not if it exist then it will show the description element but if it does not exist then it should not show the description element.but my code below still show element although there is no value in it.how can we code it so that it wont show out the description element if there is no description for a services.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Service">
<xsl:element name="equipment">
<xsl:if test="description !='' ">
<xsl:value-of select="description" />
</xsl:if>
<xsl:if test="not(description)">
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
as there is the an empty equipment element being returned.i want it to return only the first 2 equipment element that is not empty.
Updated solution is follows; please check
<xsl:template match="Services">
<xsl:for-each select="Service">
<xsl:if test="count(description) > 0 and description!=''">
<equipment>
<xsl:value-of select="description"/>
</equipment>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="/">
<xsl:apply-templates select="//Service"/>
</xsl:template>
<xsl:template match="Service">
<xsl:if test="description !='' ">
<xsl:element name="equipment">
<xsl:value-of select="description" />
</xsl:element>
</xsl:if>
</xsl:template>
or
<xsl:template match="/">
<xsl:apply-templates select="//Service"/>
</xsl:template>
<xsl:template match="Service">
<xsl:if test="child::description[text()]">
<xsl:element name="equipment">
<xsl:value-of select="description" />
</xsl:element>
</xsl:if>
</xsl:template>
Does this work for you?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- place <result /> as root to produce wellformed XML -->
<xsl:template match="/">
<result><xsl:apply-templates /></result>
</xsl:template>
<!-- rewrite those <Service /> that have a <description /> -->
<xsl:template match="Service[./description]">
<equipment><xsl:value-of select="description" /></equipment>
</xsl:template>
<!-- remove those who do not -->
<xsl:template match="Service[not(./description)]" />
</xsl:transform>
Let's say we have following data:
<all>
<item id="1"/>
<item id="2"/>
...
<item id="N"/>
</all>
What is the most elegant, xslt-ish way to group those items?
For example, imagine we want a table with two cells in each row.
Off the top of my head I can imagine (not tested though)
in template, matching item, I can call this very item, selecting following-sibling.
But even in this case I should pass additional param, to make recursion finite.
As row-count can be variable .. am passing it as a param to the template .. :)
<?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="/all[node]">
<table>
<xsl:for-each select="node[1]">
<xsl:call-template name="whoaa">
<xsl:with-param name="count" select="'1'"/>
<xsl:with-param name="row_count" select="'10'"/>
<!--maximum row_count is set to 10 -->
</xsl:call-template>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="whoaa">
<xsl:param name="count"/>
<xsl:param name="row_count"/>
<!--check if we have crossed row_count-->
<xsl:if test="not ($row_count < $count)">
<tr>
<td>
<xsl:value-of select="."/>
</td>
<td>
<!--copy next column-->
<xsl:for-each select="following-sibling::node[1]">
<xsl:value-of select="."/>
</xsl:for-each>
</td>
</tr>
<!--Select next row .. call the same template untill we reach (row_count > count)-->
<xsl:for-each select="following-sibling::node[2]">
<xsl:call-template name="whoaa">
<xsl:with-param name="count" select="$count+2"/>
<xsl:with-param name="row_count" select="$row_count"/>
</xsl:call-template>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
use position and mod, e.g.
<xsl:template match="/all">
<table>
<xsl:apply-templates name="item" mode="group"/>
</table>
</xsl:template>
<xsl:template match="item[position() mod 2=1]" mode="group">
<tr>
<td><xsl:apply-templates select="." mode="render"/></td>
<td><xsl:apply-templates select="following-sibling::item[1]" mode="render"/></td>
</tr>
</xsl:template>
<xsl:template match="item[position() mod 2=0]"></xsl:template>
<xsl:template match="item" mode="render">item: <xsl:value-of select="#id"/></xsl:template>