is shown by XSLT - xslt

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>

Related

<xsl:for-each> repeats first row

Below is what my XML looks like. I have also copied XSLT and output below. My problem is that, the output is always repeating the same first row. If I add more rows to input XML file then first row would repeat in output file for that many number of rows added. What could be the reason?
XML:
<Loans>
<Loan>
<loan_number>123</loan_number>
<loan_aqn_date>08-01-2016</loan_number>
</Loan>
<Loan>
<loan_number>456</loan_number>
<loan_aqn_date>10-01-2016</loan_number>
</Loan>
<Loan>
<loan_number>789</loan_number>
<loan_aqn_date>12-01-2016</loan_number>
</Loan>
</Loans>
Output:
loan_number|loan_aqn_date|
123|08-01-2016|
123|08-01-2016|
123|08-01-2016|
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:text>loan_number|loan_aqn_date|</xsl:text>
<xsl:for-each select="/Loans/Loan">
<xsl:value-of select="concat(/Loans/Loan/loan_number,'|')" />
<xsl:value-of select="concat(/Loans/Loan/loan_aqn_date,'|')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You are using an absolute path for "select" inside loop.
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:text>loan_number|loan_aqn_date|</xsl:text>
<xsl:for-each select="/Loans/Loan">
<xsl:value-of select="concat(loan_number,'|')" />
<xsl:value-of select="concat(loan_aqn_date,'|')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Replacing the <for-each> with a template could make your approach more general.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:strip-space elements="Loans" /> <!-- Removes surrounding spaces -->
<xsl:template match="/Loans">
<xsl:text>loan_number|loan_aqn_date|
</xsl:text>
<xsl:apply-templates /> <!-- Processes 'Loan' nodes -->
</xsl:template>
<xsl:template match="Loan">
<xsl:value-of select="concat(loan_number, '|', loan_aqn_date,'|')" />
<xsl:text>
</xsl:text> <!-- Adds newlines -->
</xsl:template>
</xsl:stylesheet>
Output:
loan_number|loan_aqn_date|
123|08-01-2016|
456|10-01-2016|
789|12-01-2016|

How to group elements based on their siblings using XSLT

I am trying to group several elements based on a starting and ending attribute of their surrounding siblings.
Sample XML:
<list>
<item>One</item>
<item class="start">Two</item>
<item>Three</item>
<item class="end">Four</item>
<item>Five</item>
<item class="start">Six</item>
<item class="end">Seven</item>
<item>Eight</item>
</list>
Desired Result:
<body>
<p>One</p>
<div>
<p>Two</p>
<p>Three</p>
<p>Four</p>
</div>
<p>Five</p>
<div>
<p>Six</p>
<p>Seven</p>
</div>
<p>Eight</p>
</body>
I come close to the desired results with the following XSLT. However, the following-sibling match doesn't stop after it reaches the ending attribute. Also, the standard matching repeats the elements that were already output from the following-sibling match.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform 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="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="list">
<body>
<xsl:apply-templates />
</body>
</xsl:template>
<xsl:template match="item">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
<xsl:template match="item[#class='start']">
<div>
<p><xsl:apply-templates /></p>
<xsl:apply-templates select="following-sibling::*[not(preceding-sibling::*[1][#class='end'])]" />
</div>
</xsl:template>
</xsl:transform>
Since you're using XSLT 2.0, why don't you take advantage of it:
<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="/list">
<body>
<xsl:for-each-group select="item" group-starting-with="item[#class='start']">
<xsl:for-each-group select="current-group()" group-ending-with="item[#class='end']">
<xsl:choose>
<xsl:when test="count(current-group()) gt 1">
<div>
<xsl:apply-templates select="current-group()" />
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:for-each-group>
</body>
</xsl:template>
<xsl:template match="item">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>

Get value of an attribute, where you don't know the name

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>

Referencing attribute value in xsl:param xsl:if test condition

I'm trying to retrieve the attribute value from an xsl:param and use it in an xsl:if test condition.
So given the following xml
<product>
<title>The Maze / Jane Evans</title>
</product>
and the xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="test" select="Jane"/>
<xsl:template match="title[contains(., (REFERENCE THE SELECT ATTRIBUTE IN PARAM))]">
<h2>
<xsl:value-of select="substring-before(., '/')"/>
</h2>
<p>
<xsl:value-of select="substring-after(., '/')"/>
</p>
</xsl:template>
<xsl:template match="title">
<h2><xsl:value-of select="."/></h2>
</xsl:template>
</xsl:stylesheet>
I would like to get back
The Maze
Jane Evans
You have a problem in this line:
<xsl:param name="test" select="Jane"/>
This defines an xsl:param named test, whose value is the child element of the current node ('/') named Jane. As the top element is <product> and not <Jane>, the test parameter has the value of an empty node-set (and a string value -- the empty string).
You want (notice the surrounding apostrophes):
<xsl:param name="test" select="'Jane'"/>
The whole processing task can be implemented rather easily:
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pTest" select="'Jane'"/>
<xsl:template match="title">
<xsl:choose>
<xsl:when test="contains(., $pTest)">
<h2>
<xsl:value-of select="substring-before(., '/')"/>
</h2>
<p>
<xsl:value-of select="substring-after(., '/')"/>
</p>
</xsl:when>
<xsl:otherwise>
<h2><xsl:value-of select="."/></h2>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<product>
<title>The Maze / Jane Evans</title>
</product>
produces the wanted, correct result:
<h2>The Maze </h2>
<p> Jane Evans</p>
Explanation:
The XSLT 1.0 syntax forbids variable/parameter references in a match pattern. This is why we have a single template matching any title and we specify inside the template the conditions for processing in a specific, wanted way.
An XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pTest" select="'Jane'"/>
<xsl:template match="title[contains(., $pTest)]">
<h2>
<xsl:value-of select="substring-before(., '/')"/>
</h2>
<p>
<xsl:value-of select="substring-after(., '/')"/>
</p>
</xsl:template>
<xsl:template match="title">
<h2><xsl:value-of select="."/></h2>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (above), again the same wanted, correct result is produced:
<h2>The Maze </h2>
<p> Jane Evans</p>
Explanation:
XSLT 2.0 doesn't have the limitations of XSLT 1.0 and variable/parameter references can be used within a match pattern.
The term $test refers to the value of the test parameter. Use $test
eg:
<xsl:template match="title[contains(., $test)]">
<h2>
<xsl:value-of select="substring-before(., '/')"/>
</h2>
<p>
<xsl:value-of select="substring-after(., '/')"/>
</p>
</xsl:template>

How to use XSLT to create distinct values

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.