XSLT: Using variables in a key function - xslt

I have a following XML file:
<titles>
<book title="XML Today" author="David Perry"/>
<book title="XML and Microsoft" author="David Perry"/>
<book title="XML Productivity" author="Jim Kim"/>
<book title="XSLT 1.0" author="Albert Jones"/>
<book title="XSLT 2.0" author="Albert Jones"/>
<book title="XSLT Manual" author="Jane Doe"/>
</titles>
I want to eliminate some elements and apply the following XSLT:
<?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="xml" indent="yes"/>
<xsl:key name="author1-search" match="book[starts-with(#author, 'David')]" use="#title"/>
<xsl:template match="book [key('author1-search', #title)]" />
<xsl:key name="author2-search" match="book[starts-with(#author, 'Jim')]" use="#title"/>
<xsl:template match="book [key('author2-search', #title)]" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
Is it possible to use an inline xsl variable
<xsl:variable name="Author">
<name>David</name>
<name>Jim</name>
</xsl:variable>
instead of "author1-search", "author2-search", and so on to loop through names?
I can use only XSLT 1.0 (2.0 is currently not supported).
Thanks in advance,
Leo

No, patterns (in XSLT 1.0) cannot contain variable/parameter references.
One way to perform such a task would be like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pAuthor" select="'David Perry'"/>
<xsl:key name="kBookaByAuthor" match="book"
use="#author"/>
<xsl:template match="/">
Books written by <xsl:value-of select="$pAuthor"/> :<xsl:text/>
<xsl:apply-templates
select="key('kBookaByAuthor', $pAuthor)"/>
</xsl:template>
<xsl:template match="book">
<!-- One can do more formatting here -->
<xsl:text>
</xsl:text>
<xsl:value-of select="#title"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<titles>
<book title="XML Today" author="David Perry"/>
<book title="XML and Microsoft" author="David Perry"/>
<book title="XML Productivity" author="Jim Kim"/>
<book title="XSLT 1.0" author="Albert Jones"/>
<book title="XSLT 2.0" author="Albert Jones"/>
<book title="XSLT Manual" author="Jane Doe"/>
</titles>
the wanted, correct result is produced:
Books written by David Perry :
XML Today
XML and Microsoft
Update: In a comment the OP has clarified that:
"I thought I fully specified my requirements in the initial question.
As I mentioned in my question and in my first comment, it would be
helpful to me to see the approach for dealing with more than one
author"
Here is a solution that truly uses keys (note that the "key" in the answer by #Flynn1179 doesn't build any index and is just a constant sequence of strings-- so the function key() using that xsl:key is actually finding a string in a list of strings -- which is O(N) as contrasted to, typically, O(1) for searching in a true index):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output method="text"/>
<xsl:param name="pAuthors">
<x>David Perry</x>
<x>Jane Doe</x>
</xsl:param>
<xsl:variable name="vParams" select=
"ext:node-set($pAuthors)/*"/>
<xsl:key name="kBookByAuthor" match="book"
use="#author"/>
<xsl:template match="/">
Books written by : <xsl:text/>
<xsl:apply-templates select="$vParams"/>
<xsl:apply-templates select=
"key('kBookByAuthor', $vParams)"/>
</xsl:template>
<xsl:template match="book">
<!-- One can do more formatting here -->
<xsl:text>
</xsl:text>
<xsl:value-of select="concat('"', #title, '"')"/>
</xsl:template>
<xsl:template match="x">
<xsl:if test="not(position() = 1)">, </xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the provided XML document (above), the wanted, correct result is produced:
Books written by : David Perry, Jane Doe
"XML Today"
"XML and Microsoft"
"XSLT Manual"
Do note: In this solution the Exslt function node-set() is used. This is done only for convenience here. In a real usage, the value of the parameter will be specified externally and then the ext:node-set() function isn't necessary.
Efficiency: This solution uses the true power of keys in XSLT. An experiment made using MSXML (3, 4 and 6) XSLT processors shows that if we search for 10000 authors the transformation time with different XSLT processors ranges from: 32ms to 45ms.
Interestingly, the solution presented by #Flynn1179 doesn't indeed make key index and with many XSLT processors it takes (for the same number (10000) of authors) from 1044ms to 5564ms:
MSXML3: 5564 ms.,
MSXML4: 2526ms,
MSXML6: 4867 ms,
AltovaXML: 1044ms.
This is quite inferior to the performance one gets with true key indexing (32ms to 45ms).

Patterns in XSLT 1.0 are not allowed to contain variable or parameter references so you couldn't use variable or parameter references in those key definitions or in the template match attributes you have.

Rather than using variables, you could just include an element in your XSLT sheet in it's own namespace, and refer to that, like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my">
<xsl:key name="authors" use="document('')/*/my:authors/my:name" match="/" />
<my:authors>
<my:name>David Perry</my:name>
<my:name>Jim Kim</my:name>
</my:authors>
<xsl:template match="book[not(key('authors',#author))]" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The book template matches those that do NOT have a corresponding my:name element for their author, and outputs nothing. The identity template outputs everything else, including the book elements you DO care about. The key's a bit of a hack, it essentially matches the whole document where the name exists, rather than matching the my:name element that matches. Since you only care about it's existence, this shouldn't be a problem.
Alternatively, if you'd rather be able to pass in a list of authors, you can use this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="authors" select="'David Perry,Jim Kim'" />
<xsl:template match="book">
<xsl:if test="contains(concat(',',$authors,','),concat(',',#author,','))">
<xsl:call-template name="identity" />
</xsl:if>
</xsl:template>
<xsl:template match="#* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The variable's used in an <xsl:if> rather than in the template match, but it does the same job. This particular code needs the list of authors specified as a comma separated list, but it should be easy enough to adapt it if you'd rather use a different format.

Related

XSL: how to use document() to read xml files in a folder

I need to compare xml files from two folders and collect those xml elements that only show up in one of the xml file.
The xml files in two folder has same file name.
Below is the sample of what I want to do:
old/booklist1.xml
<books>
<book #type="fiction">
<isn>12345678</isn>
<name>xxxx</name>
</book>
</books>
new/booklist1.xml
<books>
<book #type="fiction">
<isn>12345678</isn>
<name>xxxx</name>
</book>
<book #type="history">
<isn>23456789</isn>
<name>yyyyy</name>
</book>
</books>
I will need the output of the booklist1.xml as the below:
<books>
<book #type="history">
<isn>23456789</isn>
<name>yyyyy</name>
</book>
</books>
I have below findDiff.xsl that works when I specify / hardcode the xml file name:
<xsl:key name="book" match="book" use="." />
<xsl:template match="/books">
<xsl:copy>
<xsl:copy-of select="book[not(key('book', ., document('old_booklist1.xml')))]"/>
</xsl:copy>
</xsl:template>
The fidDiff.xsl current is associated with new/booklist1.xml and I copied the old/booklist1.xml to the same folder with new/booklist1.xml and made the name as old_booklist1.xml and above xsl works with the hard coded uri.
I have to loop throw xml file in folder new and then compare it with the same named xml file in folder old.
I am thinking to use the following way to build the xml file URI:
loop in the new and get the file uri
build the file uri for xml file in old folder
<xsl:variable name="xmlPath" select="document-uri()"/>
<xsl:variable name="compareWithPath" select=" replace($xmlFilePath, 'new', 'old')"/>
then pass the compareWithPath to below template:
<xsl:template match="/books">
<xsl:copy>
<xsl:copy-of select="book[not(key('book',., document($compareWithPath)))]"></xsl:copy-of>
</xsl:copy>
</xsl:template>
But I got the error that The system cannot find the file specified
file:/C:/Users/phyllis/Documents/old/booklist1.xml
Michael Kay mentioned that we can convert the file name to URI and use doc() or document() to load it. I build the filename URI exactly the same way that I got from document-uri(). What am I wrong here?
The converted file URI looks like this:
<compareWithPath>file:/C:/Users/phyllis/Documents/old/booklist1.xml</compareWithPath>
Returns false when check above file URI using:
<fileExist><xsl:value-of select="doc-available($compareWithPath)"/></fileExist>
The below xsl code works well for my problem:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="book" match="book" composite="yes" use="*"/>
<xsl:template match="books">
<xsl:param name="compareWith"/>
<xsl:copy>
<xsl:copy-of
select="book[not(key('book', *, document($compareWith)))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:copy>
<xsl:iterate select="uri-collection('new') ! doc(.)">
<xsl:variable name="fileUri" select=" concat('update/', tokenize( document-uri(.),'/')[last()])"/>
<xsl:result-document method="xml" href="{$fileUri}">
<xsl:apply-templates select="books">
<xsl:with-param name="compareWith" select="concat('old/', tokenize( document-uri(.),'/')[last()])"/>
</xsl:apply-templates>
</xsl:result-document>
</xsl:iterate>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Only a key() is able to eliminate the duplicates, find diff, compare file, etc. Never thought it could be so easy to solve this kind of problem using xsl but once and once xsl proved his power when it comes to xml.
<xsl:iterate select="uri-collection() ! doc(.)">...</xsl:iterate> iterate through uri-collection() makes it easy to loop through the folders.

xsl not transforming to desired output

I am trying to replicate the example located here Click Here
However, I am unable to achieve the desired results.
The output is not at all getting generated as per desired.
The input I have used :
<?xml version="1.0" encoding="UTF-8" ?>
<Order xmlns="http://www.book.org">
<Bundle>
<authors>
<author>
<authorId>100</authorId>
<authorName>Kathisiera</authorName>
</author>
<author>
<authorId>200</authorId>
<authorName>Bates</authorName>
</author>
<author>
<authorId>300</authorId>
<authorName>Gavin King</authorName>
</author>
</authors>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
**The Schema I have used in for my transformation**
<!-- begin snippet: js hide: false console: true babel: false -->
The XSLT I am using for my transformation :
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:socket="http://www.oracle.com/XSL/Transform/java/oracle.tip.adapter.socket.ProtocolTranslator"
xmlns:oracle-xsl-mapper="http://www.oracle.com/xsl/mapper/schemas"
xmlns:dvm="http://www.oracle.com/XSL/Transform/java/oracle.tip.dvm.LookupValue"
xmlns:mhdr="http://www.oracle.com/XSL/Transform/java/oracle.tip.mediator.service.common.functions.MediatorExtnFunction"
xmlns:oraxsl="http://www.oracle.com/XSL/Transform/java"
xmlns:oraext="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
xmlns:ns0="http://www.book.org"
exclude-result-prefixes="oracle-xsl-mapper xsi xsd xsl ns0 socket dvm mhdr oraxsl oraext xp20 xref">
<oracle-xsl-mapper:schema>
<!--SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY.-->
<oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:source type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:source>
</oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:mapTargets>
<oracle-xsl-mapper:target type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:target>
</oracle-xsl-mapper:mapTargets>
<!--GENERATED BY ORACLE XSL MAPPER 12.2.1.2.0(XSLT Build 161003.0739.0018) AT [THU JAN 23 13:13:32 IST 2020].-->
</oracle-xsl-mapper:schema>
<!--User Editing allowed BELOW this line - DO NOT DELETE THIS LINE-->
<xsl:key name="k" match="ns0:Order/ns0:Bundle/ns0:books/ns0:book" use="ns0:orderId"/>
<xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:books/ns0:author" use="ns0:authorId"/>
<xsl:template match="/ns0:Order">
<xsl:copy>
<xsl:apply-templates select="//ns0:books"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//ns0:books">
<xsl:apply-templates select="ns0:book[generate-id(.) = generate-id(key('k', ns0:orderId))]"/>
</xsl:template>
<xsl:template match="ns0:book">
<Bundle>
<authors>
<xsl:apply-templates select="key('a', string(key('k', ns0:orderId)/ns0:bookAuthorId ))" />
</authors>
<books>
<xsl:copy-of select="key('k', ns0:orderId)"/>
</books>
</Bundle>
</xsl:template>
<xsl:template match="ns0:author">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However the output I am getting is :
<?xml version = '1.0' encoding = 'UTF-8'?>
<Order xmlns="http://www.book.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.book.org file:/C:/JDeveloper/mywork/SOADevelopment/XsltLearner/SOA/Schemas/BooksOrder.xsd">
<Bundle>
<authors/>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
<Bundle>
<authors/>
<books>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
The node is not getting populated.
I have tried using copy-of to see the output for the second key but it prints nothing.
Could you please suggest me where I am going wrong.
Any Idea what I am doing incorrect?
Please help
Thanks!
The pattern for the key would <xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:authors/ns0:author" use="ns0:authorId"/>, as far as I understand your posted document sample. I would also remove the string call in any use of the key function e.g. use <xsl:apply-templates select="key('a', key('k', ns0:orderId)/ns0:bookAuthorId )"/>.

comparing element using XSLT dynamically

I have XML document something like below.
<root>
<Book>
<Book_title/>
<author_name/>
</Book>
<Book>
<Book_title/>
<author_name/>
</Book>
<author_details>
<author_name/>
<author_DOB/>
<author_details/>
</root>
So can we compare Book/author_name with author_details/author_name dynamically with XSLT ...??
Define a key
<xsl:key name="author" match="author_details" use="autor_name"/>
then write a template for
<xsl:template match="Book/author_name">
<xsl:copy-of select=". | key('author', .)/author_DOB"/>
</xsl:template>
handle root and Book by the identity transformation (e.g. <xsl:mode on-no-match="shallow-copy"/> in XSLT 3) and add an empty
<xsl:template match="author_details"/>
to prevent copying/outputting those elements.

Odd behavior with xslt identity transform

I am experiencing something odd with the xslt transform that may be a real issue or may simply be my forgetting something. Anything that has xsl:apply-templates attached to it results in a blank space, and I don't understand why.
The xml I am using is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<TEI.2>
<teiHeader>
<fileDesc>
<titleStmt>
<title>William of Palerne: An Electronic Edition</title>
<author>Anonymous</author>
<editor>Edited by G. H. V. Bunt</editor>
<respStmt>
<resp>
<hi rend="bold">Computer Consultants and Programmers</hi>
</resp>
<name> Susan Gants, Susan Munson, Daniel Pitti, and John Unsworth.</name>
</respStmt>
</titleStmt>
</fileDesc>
</teiHeader>
</TEI>
The xslt I am applying to is is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tei="http://www.tei-c.org/ns/1.0"
exclude-result-prefixes="xs tei"
version="2.0">
<xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="TEI.2">
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<xsl:apply-templates/>
</TEI>
</xsl:template>
<xsl:template match="teiHeader">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
I expect my result to be the original XML with TEI.2 converted to TEI and the namespace added:
<?xml version="1.0" encoding="UTF-8"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<teiHeader>
<fileDesc>
<titleStmt>
<title>William of Palerne: An Electronic Edition</title>
<author>Anonymous</author>
<editor>Edited by G. H. V. Bunt</editor>
<respStmt>
<resp>
<hi rend="bold">Computer Consultants and Programmers</hi>
</resp>
<name> Susan Gants, Susan Munson, Daniel Pitti, and John Unsworth.</name>
</respStmt>
</titleStmt>
</fileDesc>
</teiHeader>
</TEI>
Instead, TEI.2 changes to TEI as expected but teiHeader does not appear:
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<fileDesc xmlns="">
<titleStmt>
<title>William of Palerne: An Electronic Edition</title>
<author>Anonymous</author>
<editor>Edited by G. H. V. Bunt</editor>
<respStmt>
<resp>
<hi>Computer Consultants and Programmers</hi>
</resp>
<name> Susan Gants, Susan Munson, Daniel Pitti, and John Unsworth.</name>
</respStmt>
</titleStmt>
</fileDesc>
</TEI>
I'm sure I've made an error or am overlooking something, but I cannot for the life of me figure out what it is. IF someone could tell me what's messing me up and how to rectify it I would appreciate it.
The teiHeader does not appear because of this template:
<xsl:template match="teiHeader">
<xsl:apply-templates/>
</xsl:template>
You are matching teiHeader, but once matched you are not copying it, but instead passing on control to its child nodes, resulting in no teiHeader being written in the output.
Now, you could simply remove this template, and the teiHeader will be created. However, you will see it will be output like so
<teiHeader xmlns="">
This is because in the input XML the teiHeader does not belong to any namespace, and so the identity template is simply creating the same element in the output in no namespace either.
Even though you are creating the root TEI element in the namespace, this won't automatically add any child elements you create to that namespace. You need to add separate templates for that.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tei="http://www.tei-c.org/ns/1.0"
exclude-result-prefixes="xs tei"
version="2.0">
<xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="TEI.2">
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<xsl:apply-templates/>
</TEI>
</xsl:template>
<xsl:template match="*" priority="2">
<xsl:element name="{local-name()}" namespace="http://www.tei-c.org/ns/1.0">
<xsl:apply-templates select="node()|#*" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The priority="2" is used to ensure the template gets higher priority than the identity template.
Also note how it does <xsl:apply-templates select="node()|#*" /> and not <xsl:apply-templates /> because the latter only selects nodes, and not attibrutes.

Storing into a variable and displaying the unique entries using XSL

In that, I want to display only the unique fruit entries in it. Here is the XML tag what I'm using for parsing
<main>
<local id="1" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>Orange</fruit>
</summary>
</local>
<local id="2" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>mango</fruit>
</summary>
</local>
</main>
The expected result should be in the below format
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>Mango</fruit>
Here are the code snippet what I'm trying to use
<xsl:for-each select="main/local">
<xsl:for-each select="symbol/fruit">
<xsl:copy-of select="."/>
<xsl:copy-of select="fruit[not(.=$fruit)]"/>
</xsl:for-each>
</xsl:for-each>
But I'm not getting any output display for this, Can you please help me how can I remove this duplicate redundancy from here.? Thank You in advance
To do this in XSLT1.0 you can make use of a technique called 'Meunchian' grouping. First you define a key to 'look-up' the fruit elements based on the value
<xsl:key name="fruit" match="fruit" use="." />
Then, to get the unique fruit names, you match fruit elements that happen to be the first fruit element in the key (and to check two nodes are the same the generate-id() method is used)
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="fruit" match="fruit" use="." />
<xsl:template match="/">
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output:
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>mango</fruit>