xsl numbering with same elements next to - xslt

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>

Related

XSL - How to ouput nested text-elements at the correct position

I am trying to create an xsl-stylesheet that outputs my xml-contents in the correct order.
Here is an example:
XML:
...<p>This is<mark> a nested <b>text</b></mark></p>...
XSL:
<?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>
<body>
<h2>
<xsl:value-of select="html/head/title"/>
</h2>
<div style="border:1px solid black;margin:30px;padding:30px;box-sizing:border-box;">
<xsl:for-each select="html/body/div[#class='toc']/table/tr/td/a">
<p><a>
<xsl:attribute name="href" namespace="uri">
<xsl:value-of select="current()/#href"/>
</xsl:attribute>
<xsl:value-of select="current()"/>
</a></p>
</xsl:for-each>
</div>
<xsl:for-each select="html/body/div[#class='chapter']">
<div style="border:1px solid black;margin:30px;padding:30px;box-sizing:border-box;">
<xsl:attribute name="id" namespace="uri"><xsl:value-of select ="current()/#id"/></xsl:attribute>
<p><xsl:value-of select ="current()/#id"/></p>
<xsl:call-template name="rec">
<xsl:with-param name="parents" select="current()"/>
</xsl:call-template>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template name="rec">
<xsl:param name="parents"></xsl:param>
<xsl:for-each select="$parents/*">
<xsl:if test="name() = 'img'">
<img class="{#class}" src="{#src}" style="max-width:100%;"/>
</xsl:if>
<xsl:if test="name() != 'img'">
<xsl:element name="{local-name()}">
<xsl:if test="name() != 'figure'">
<xsl:value-of select ="current()"/>
</xsl:if>
<xsl:call-template name="rec">
<xsl:with-param name="parents" select="current()"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This outputs:
This is a nested text
a nested text
text
What I am trying to get:
<p>This is<mark> a nested <b>text</b></mark></p>
I have tried just to include a CSS-Stylesheet (which would get rid of this particular problem), however this does not seem to work with images (e.g.), which won´t be displayed but will occure inside most documents.
The XSL-Stylesheet is supposed to be working with multiple documents (I wrote an exporter, that creates xml-files, that roughly follow the same syntax). The important part should only be the recursive function inside <xsl:template name="rec">.
Help would be greatly appreciated. Thanks!
Basic push style, structure and order preserving processing usually relies on the identity transformation template plus custom templates for each node you need to transform e.g.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="img">
<img class="{#class}" src="{#src}" style="max-width:100%;"/>
</xsl:template>
The duplicated text in your wrong output is created by the repeated use of xsl:value-of in the recursive, named template. If you treat text as nodes and let any copying be handled through adequate templates, like the identity transformation template, you don't output text values several times.

Condition getting getting called where it is not supposed to be

I've the below XML.
<?xml version="1.0" encoding="UTF-8"?>
<body>
<para><content-style font-style="bold">18/8/7</content-style> <content-style font-style="italic">(2) Here is the content</content-style>—Contributory negligence accident
<content-style font-style="italic">second v/w datav. </content-style> </para>
</body>
and the below XSL.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<hmtl>
<head>
<title>New Version!</title>
</head>
<xsl:apply-templates/>
</hmtl>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="para[node()[position() = 1 and self::content-style[matches(., '(\w+)/(\w+)')]]]">
<div class="para">
<xsl:analyze-string select="." regex="(\w+)/(\w+)/(\w+)">
<xsl:matching-substring>
<a name="{concat('P',regex-group(1),'-',regex-group(2),'-',regex-group(3))}"/>
<span class="phrase">
<xsl:value-of select="."/>
</span>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:analyze-string select="." regex="(\w+)/(\w+)">
<xsl:matching-substring>
<a name="{concat('P',regex-group(1),'-',regex-group(2))}"/>
<span class="phrase">
<xsl:value-of select="."/>
</span>
<xsl:text>     </xsl:text>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:non-matching-substring>
</xsl:analyze-string>
<xsl:apply-templates select="child::node()[not(self::content-style[1]/text())]"/>
</div>
</xsl:template>
<xsl:template match="content-style">
<xsl:choose>
<xsl:when test="./#format">
<span class="format-{#format}">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="fontStyle">
<xsl:value-of select="concat('font-style-',#font-style)"/>
</xsl:variable>
<span class="{$fontStyle}">
<xsl:apply-templates/>
</span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:transform>
here the problem that i face is the regex is getting called on both content-styles, but i want it to be called only on the content-style where there is no preceding data(text or node), i.e. only on the node like <para><content-style> but not on like <para>(text/node)<content-style>.
Current O/p:
<div class="para"><a name="P18-8-7"></a><span class="phrase">18/8/7</span> (2) Here is the content—Contributory negligence accident
second <a name="Pv-w"></a><span class="phrase">v/w</span> datav. —Contributory negligence accident
</div>
Expected O/P
<div class="para"><a name="P18-8-7"></a><span class="phrase">18/8/7</span> (2) Here is the content—Contributory negligence accident
second <span class="font-style-italic">second v/w datav.</span>
</div>
please let me know where am i going wrong and how can i fix this.
Working Demo
Thanks
Looking at your current XSLT, at the moment when you apply your regex (with analyze-string) you are positioned on a para element, not a content-style, and so doing select="." will get the string value of the entire para element, not just the first node.
What you may need to do is change it to this, so it just does analyse the first content-string (which you know is the first node from the condition in the template match)
<xsl:analyze-string select="content-style[1]" regex="(\w+)/(\w+)/(\w+)">
There is also a problem with this apply-templates in your current template
<xsl:apply-templates select="child::node()[not(self::content-style[1]/text())]"/>
I think what you are trying to do is select the child nodes of para apart from the first child-element. If so, you should change it to this
<xsl:apply-templates select="child::node()[position() > 1]"/>
This may give you what you need.
Alternatively, you could change your current template that matches para to one that matches the first content-style instead:
<xsl:template match="content-style[not(preceding-sibling::node())][matches(., '(\w+)/(\w+)')]">
As this has a condition on it, it will have a higher priority than the template that matches content-style on its own.
As a very simplistic example try this XSLT as a basis
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="content-style[not(preceding-sibling::node())][matches(., '(\w+)/(\w+)')]">
<content-style class="first">
<!-- Regex here! -->
<xsl:apply-templates />
</content-style>
</xsl:template>
<xsl:template match="content-style">
<content-style class="other">
<!-- Other -->
<xsl:apply-templates />
</content-style>
</xsl:template>
</xsl:stylesheet>
Note, you might need to add a strip-space element to your XSLT, otherwise any white space before the first content-style would also count as a proper node.
<xsl:strip-space elements="*" />

Can I DRY this XSLT for nested categories?

XSLT available is 1.0.
The XML and XSLT below is for building a dropdown navigation menu for nested categories. The level of categories may vary.
Sample XML:
<data>
<categories-nav>
<section id="11" handle="categories-1">Categories 1</section>
<entry id="65">
<name handle="air-rifles">Air Rifles</name>
<subcategories field-id="50" subsection-id="12" items="2">
<item id="66" quantity="1">
<name handle="rifles">Rifles</name>
<active>Yes</active>
<subcategories field-id="57" subsection-id="13" items="2">
<item id="67" quantity="1">
<name handle="b2-series">B2 Series</name>
<active>Yes</active>
</item>
<item id="112" quantity="1">
<name handle="junior-supergrade">Junior Supergrade</name>
<active>Yes</active>
</item>
</subcategories>
</item>
<item id="111" quantity="1">
<name handle="accessories">Accessories</name>
<active>Yes</active>
<subcategories field-id="57" subsection-id="13" items="0" />
</item>
</subcategories>
</entry>
<entry id="118">
<name handle="pistols">Pistols</name>
</entry>
<entry id="58">
<name handle="bb-softair-guns">BB Softair Guns</name>
</entry>
</categories-nav>
</data>
My current XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="categories-nav-entries" mode="navigation">
<li class="{name/#handle}-{#id}">
<xsl:value-of select="name"/>
<xsl:apply-templates select="subcategories" mode="navigation"/>
</li>
</xsl:template>
<!-- level 1 -->
<xsl:template match="/data/categories-nav" mode="navigation">
<ul>
<xsl:apply-templates select="entry" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
<!-- level 2 -->
<xsl:template match="/data/categories-nav/entry/subcategories" mode="navigation">
<ul>
<xsl:apply-templates select="item" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry/subcategories/item" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
<!-- level 3 -->
<xsl:template match="/data/categories-nav/entry/subcategories/item/subcategories" mode="navigation">
<ul>
<xsl:apply-templates select="item" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry/subcategories/item/subcategories/item" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
</xsl:stylesheet>
The only difference between the matches for the three different levels is repeated subcategories and item nodes.
I've successfully got the lis in their own named template, but is there a way I can avoid matching the three levels separately?
Also, it seems that because I'm using a mode on the initial match, I also have to use that mode on all subsequent matches - is that correct?
Edit: here's what I came up with after using relative paths as #michael's answer:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="categories-nav-list">
<li class="{name/#handle}">
<xsl:value-of select="name"/>
<xsl:apply-templates select="subcategories[#items > 0]"/>
</li>
</xsl:template>
<xsl:template match="categories-nav">
<ul>
<xsl:apply-templates select="entry"/>
</ul>
</xsl:template>
<xsl:template match="categories-nav/entry">
<xsl:call-template name="categories-nav-list"/>
</xsl:template>
<xsl:template match="categories-nav//subcategories[item/active='Yes']">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<xsl:template match="categories-nav//subcategories/item[active='Yes']" priority="1">
<xsl:call-template name="categories-nav-list"/>
</xsl:template>
<xsl:template match="categories-nav//subcategories/item" priority="0"/>
</xsl:stylesheet>
You don't need to put absolute paths in each #match attribute.
Why not something like this:
<xsl:template match="subcategories">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<xsl:template match="item">
<xsl:call-template name="categories-nav-entries"/>
</xsl:template>

XSLT increment variable

I have the following XML
<data>
<records>
<record name="A record">
<info>A1</info>
<info>A2</info>
</record>
<record name="B record"/>
<record name="C record">
<info>C1</info>
</record>
</records>
</data>
how can I transform into following output, the problem is how can I count between on record, and record/info?
<div id="1">
<p>A record</p>
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<p>C record</p>
<span id="3">C1</span>
</div>
Solution 1. Fine grained traversal. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="records">
<xsl:apply-templates select="*[1]"/>
</xsl:template>
<xsl:template match="record"/>
<xsl:template match="record[node()]">
<xsl:param name="pRecordNum" select="1"/>
<xsl:param name="pInfoNum" select="1"/>
<div id="{$pRecordNum}">
<xsl:apply-templates select="#*|*[1]">
<xsl:with-param name="pInfoNum" select="$pInfoNum"/>
</xsl:apply-templates>
</div>
<xsl:apply-templates select="following-sibling::record[node()][1]">
<xsl:with-param name="pRecordNum" select="$pRecordNum +1"/>
<xsl:with-param name="pInfoNum" select="$pInfoNum + count(info)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="info">
<xsl:param name="pInfoNum"/>
<span id="{$pInfoNum}">
<xsl:value-of select="."/>
</span>
<xsl:apply-templates select="following-sibling::info[1]">
<xsl:with-param name="pInfoNum" select="$pInfoNum +1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Output:
<div id="1">
<p>A record</p>
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<p>C record</p>
<span id="3">C1</span>
</div>
Solution 2: preceding axe. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="record"/>
<xsl:template match="record[node()]">
<div id="{count(preceding-sibling::record[node()])+1}">
<xsl:apply-templates select="#*|*"/>
</div>
</xsl:template>
<xsl:template match="info">
<span id="{count(preceding::info)+1}">
<xsl:value-of select="."/>
</span>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Solution 3: With fn:position() and preceding axe. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="records">
<xsl:apply-templates select="record[node()]"/>
</xsl:template>
<xsl:template match="record">
<div id="{position()}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="info">
<span id="{count(preceding::info)+1}">
<xsl:value-of select="."/>
</span>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Note: You need a explict pull style.
Edit: Missed any level numbering for span/#id.
There is a short way to do this in XSLT. Use the <xsl:number> instruction:
<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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record[info]">
<xsl:variable name="vPos">
<xsl:number count="record[info]"/>
</xsl:variable>
<div id="{$vPos}">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="info">
<xsl:variable name="vPos">
<xsl:number from="/" level="any" count="info" />
</xsl:variable>
<span id="{$vPos}"><xsl:apply-templates/></span>
</xsl:template>
<xsl:template match="record[not(info)]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<data>
<records>
<record name="A record">
<info>A1</info>
<info>A2</info>
</record>
<record name="B record"/>
<record name="C record">
<info>C1</info>
</record>
</records>
</data>
the wanted, correct result is produced:
<data>
<records>
<div id="1">
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<span id="3">C1</span>
</div>
</records>
</data>

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.