I have XML like this:
<items>
<item>
<products>
<product>laptop</product>
<product>charger</product>
</products>
</item>
<item>
<products>
<product>laptop</product>
<product>headphones</product>
</products>
</item>
</items>
I want it to output like
laptop
charger
headphones
I was trying to use distinct-values() but I guess i m doing something wrong. Can anyone tell me how to achieve this using distinct-values()? Thanks.
<xsl:template match="/">
<xsl:for-each select="//products/product/text()">
<li>
<xsl:value-of select="distinct-values(.)"/>
</li>
</xsl:for-each>
</xsl:template>
but its giving me output like this:
<li>laptop</li>
<li>charger</li>
<li>laptop></li>
<li>headphones</li>
An XSLT 1.0 solution that uses key and the generate-id() function to get distinct values:
<?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" encoding="UTF-8" indent="yes"/>
<xsl:key name="product" match="/items/item/products/product/text()" use="." />
<xsl:template match="/">
<xsl:for-each select="/items/item/products/product/text()[generate-id()
= generate-id(key('product',.)[1])]">
<li>
<xsl:value-of select="."/>
</li>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here's an XSLT 1.0 solution that I've used in the past, I think it's more succinct (and readable) than using the generate-id() function.
<xsl:template match="/">
<ul>
<xsl:for-each select="//products/product[not(.=preceding::*)]">
<li>
<xsl:value-of select="."/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
Returns:
<ul xmlns="http://www.w3.org/1999/xhtml">
<li>laptop</li>
<li>charger</li>
<li>headphones</li>
</ul>
You don't want "output (distinct-values)", but rather "for-each (distinct-values)":
<xsl:template match="/">
<xsl:for-each select="distinct-values(/items/item/products/product/text())">
<li>
<xsl:value-of select="."/>
</li>
</xsl:for-each>
</xsl:template>
I came to this problem while working with a Sitecore XSL rendering. Both the approach that used key() and the approach that used the preceding axis performed very slowly. I ended up using a method similar to key() but that did not require using key(). It performs very quickly.
<xsl:variable name="prods" select="items/item/products/product" />
<xsl:for-each select="$prods">
<xsl:if test="generate-id() = generate-id($prods[. = current()][1])">
<xsl:value-of select="." />
<br />
</xsl:if>
</xsl:for-each>
distinct-values(//product/text())
I found that you can do what you want with XSLT 1.0 without generate-id() and key() functions.
Here is Microsoft-specific solution (.NET's XslCompiledTransform class, or MSXSLT.exe or Microsoft platfocm COM-objects).
It is based on this answer. You can copy sorted node set to variable ($sorted-products in the stylesheet below), then convert it to node-set using ms:node-set function. Then you able for-each second time upon sorted node-set:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:ms="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="ms">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<xsl:variable name="sorted-products">
<xsl:for-each select="//products/product">
<xsl:sort select="text()" />
<xsl:copy-of select=".|#*" />
</xsl:for-each>
</xsl:variable>
<xsl:variable name="products" select="ms:node-set($sorted-products)/product" />
<xsl:for-each select="$products">
<xsl:variable name='previous-position' select="position()-1" />
<xsl:if test="normalize-space($products[$previous-position]) != normalize-space(./text())">
<li>
<xsl:value-of select="./text()" />
</li>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
output:
<li>charger</li>
<li>headphones</li>
<li>laptop</li>
You can try it out in online playground.
Related
I need to format some xml data with the following structure
<list>
<item>
Test
</item>
<item>
Testt
</item>
<or-item>
TestOr
</or-item>
<or-item>
TestOrr
</or-item>
<item>
Testtt
</item>
<or-item>
TestOrrr
</or-item>
<item>
Testttt
</item>
</list>
with xsl:number the or-item must be formatted with the second level count on that position. I know it would be better to structure the or-item inside that item but the data is given like that.
I need a way to count the or-item next to the current or-item to calculate the numbering for xsl:number
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.1"
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output encoding="UTF-8" method="html" indent="yes"/>
<xsl:template match="list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number count="item"/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number value="count(//or-item)" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
Edit
I am using XSLT 1.1 with xsltproc on linux but 2.0 whould be possible if neccessary
As the target format is HTML, it seems you could rely on creating the appropriate nested HTML ordered lists by using xsl:for-each-group and group-starting-with="item":
<xsl:template match="list">
<ol>
<xsl:for-each-group select="*" group-starting-with="item">
<li>
<xsl:value-of select="."/>
<xsl:where-populated>
<ol>
<xsl:apply-templates select="tail(current-group())"/>
</ol>
</xsl:where-populated>
</li>
</xsl:for-each-group>
</ol>
</xsl:template>
<xsl:template match="or-item">
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM
That example uses some XSLT/XPath 3 stuff like were-populated and tail but in case that XSLT 2 compatility is needed then it could be replaced by <xsl:if test="subsequence(current-group(), 2)"><ol><xsl:apply-templates select="subsequence(current-group(), 2)"/></xsl:if>.
And of course the use of HTML ordered lists is not necessary, if needed/wanted you could just transform the input to nested divs with the used grouping approach and then in a second step use format-number as you seem to want to do:
<xsl:template match="list">
<xsl:variable name="nested-list">
<xsl:for-each-group select="*" group-starting-with="item">
<xsl:copy>
<xsl:value-of select="."/>
<xsl:copy-of select="tail(current-group())"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:variable>
<div>
<xsl:apply-templates select="$nested-list"/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM/1
You can produce the expected output by simply adjusting the xsl:number instruction:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number level="any" from="item" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
I want print attribute and put some values to it.
Input :
<figure id="fig_1">
<dis>text</dis>
</figure>
my output:
<image ref="fig_1"
comment="text the
"/>
Tried code :
<xsl:template match="dis[parent::figure]">
<xsl:variable name="fig_name" select="parent::fig/#id"/>
<image ref="{$fig_name}">
<xsl:attribute name="comment">
<xsl:value-of select="text()"/>
</xsl:attribute>
</tps:image>
</xsl:template>
I want to remove all
. How can I do it.
use normalize-space() function to remove unnecessary white space.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="long-desc[parent::fig]">
<xsl:variable name="fig_name" select="parent::fig/#id"/>
<image ref="{$fig_name}">
<xsl:attribute name="comment">
<xsl:value-of select="normalize-space(text())"/>
</xsl:attribute>
</image>
</xsl:template>
</xsl:stylesheet>
Say if I have some XML with a line such as:
<machine center="10" left="25" right="162" />
and, using XSLT, I want to turn that in to something like:
<measurement type="center">10</measurement>
<measurement type="left">25</measurement>
<measurement type="right">162</measurement>
How do I do that? At the moment I have the following, but am missing one crucial part:
<measurement>
<xsl:for-each select="#">
<xsl:attribute name="type">
<xsl:value-of name="name()">
</xsl:attribute name="type">
<xsl:value-of name="[WHAT_GOES_HERE?]" />
</xsl:for-each>
</measurement>
Try it this way;
<xsl:template match="machine">
<xsl:for-each select="#*">
<measurement type="{name()}">
<xsl:value-of select="." />
</measurement>
</xsl:for-each>
</xsl:template>
This solution is both: simpler/shorter, avoids <xsl:for-exch> and is fully in the spirit of XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="machine">
<xsl:apply-templates select="#*"/>
</xsl:template>
<xsl:template match="machine/#*">
<measurement type="{name()}"><xsl:value-of select="."/></measurement>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<machine center="10" left="25" right="162" />
the wanted, correct result is produced:
<measurement type="center">10</measurement>
<measurement type="left">25</measurement>
<measurement type="right">162</measurement>
A way to achieve this is:
<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="machine">
<xsl:for-each select="#*">
<xsl:element name="measurement">
<xsl:attribute name="type">
<xsl:value-of select="local-name()" />
</xsl:attribute>
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have below scenario for my XML.
<content>
<para>text-1 <emphasis type="bold">text-2</emphasis> text-3</para>
</content>
I want to parse it like below
<content>
<p>text-1 <b>text-2</b> text-3</p>
</content>
I have created my XSLT as below
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="ISO-8859-1" indent="no"/>
<xsl:template name="para">
<p>
<xsl:value-of select="text()" disable-output-escaping="yes"/>
<xsl:for-each select="child::*">
<xsl:if test="name()='emphasis'">
<xsl:call-template name="emphasis"/>
</xsl:if>
</xsl:for-each>
</p>
</xsl:template>
<xsl:template name="emphasis">
<xsl:if test="attribute::type = 'bold'">
<b>
<xsl:value-of select="text()" disable-output-escaping="yes"/>
</b>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<content>
<xsl:for-each select="content/child::*">
<xsl:if test="name()='para'">
<xsl:call-template name="para"/>
</xsl:if>
</xsl:for-each>
</content>
</xsl:template>
</xsl:stylesheet>
XSLT provided above is generating output like below
<content>
<p>text-1 text-3<b>text-2 </b></p>
</content>
Please guide me with your suggestions, how can I get my desire output?
To do this, you just need to extend the standard Identity Transform with special cases for matching your para and emphasis elements. For example, for para elements you would the following to replace para with p and then continue matching all the child nodes
<xsl:template match="para">
<p>
<xsl:apply-templates select="#*|node()"/>
</p>
</xsl:template>
So, given the following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- This is the Identity Transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Replace para with p -->
<xsl:template match="para">
<p>
<xsl:apply-templates select="#*|node()"/>
</p>
</xsl:template>
<!-- Replace emphasis with b -->
<xsl:template match="emphasis[#type='bold']">
<b>
<xsl:apply-templates select="node()"/>
</b>
</xsl:template>
</xsl:stylesheet>
When applied to the following input XML
<content>
<para>text-1 <emphasis type="bold">text-2</emphasis> text-3</para>
</content>
The following is output
<content>
<p>text-1 <b>text-2</b> text-3</p>
</content>
You should be able to see how easy it is to extend to other cases should you input XML have more tags to transform.
do it like this ;)
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="content">
<content><xsl:apply-templates select="para" /></content>
</xsl:template>
<xsl:template match="emphasis [#type='bold']">
<b><xsl:value-of select="." /></b>
</xsl:template>
</xsl:stylesheet>
when you do it like this the default template will catch text-1 and text-3
this question relates to
XSLT to generate html tags specified in XML
where I had an xml docment and used an xsl to generate html tags using
<xsl:element name="{Type}" >
the problem I have is that I want to specify some html attributes in my xml for example
<Page>
<ID>Site</ID>
<Object>
<ID>PostCode</ID>
<Type>div</Type>
<Attributes>
<Attribute name="class">TestStyle</Attribute>
<Attribute name="id">TestDiv</Attribute>
</Attributes>
<Class>display-label</Class>
<Value>PostCode</Value>
</Object>
</Page>
So does any one know how I could populate the xsl:element with the two attributes using xsl?
Thanks
Building from the stylesheet that I posted in the previous question, within the element declaration you can iterate through each of the Attributes/Attribute elements and construct the attributes for the element that you are constructing.
You are "standing" on the Object element node inside that for-loop, so you can then iterate over it's Attributes/Attribute elements like this:
<xsl:for-each select="Attributes/Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="current()"/></xsl:attribute>
</xsl:for-each>
Applied to your stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<head>
</head>
<body>
<xsl:for-each select="Page/Object">
<xsl:element name="{Type}" >
<xsl:for-each select="Attributes/Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="current()"/></xsl:attribute>
</xsl:for-each>
<xsl:value-of select="Value"/>
</xsl:element>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This is an alternative way to achieve the same output, but in a little more delcarative fashion, using apply-templates instead of for-each.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<head>
</head>
<body>
<xsl:apply-templates select="Page/Object" />
</body>
</html>
</xsl:template>
<xsl:template match="Object">
<xsl:element name="{Type}" >
<xsl:apply-templates select="Attributes/Attribute" />
<xsl:apply-templates select="Value" />
</xsl:element>
</xsl:template>
<xsl:template match="Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
You need to fix the Attributes element in your source sample, it's not closed.
You can use xsl:for-each or xsl:apply-templates with select="Attributes/Attribute", to invoke an xsl:attribute element that looks a bit like this:
<xsl:attribute name="{#name}"><xsl:value-of select="text()"/></xsl:attribute>
The thing you need to watch out for is that xsl:attribute must come before anything that adds children to the {Type} element.
<xsl:element name="Attribute">
<xsl:attribute name="class">TestStyle</xsl:attribute>
<xsl:attribute name="id">TestDiv</xsl:attribute>
</xsl:element>