func:function return result tree fragment - xslt

I am using Xalan-j 2.7.1. I have written a function using xalans implementation of exslt func:function extensions. I am trying to make my xslt cleaner by using repeatable portion of output xml into functions. The following function is a representation of what I am trying to do.
The expected output is a xml tree fragment but I am not seeing any output. I dont know why this doesn't work though it is mentioned in exslt.org documentation
xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:func="http://exslt.org/functions"
xmlns:common="http://exslt.org/common"
xmlns:my="http://my.org/my"
exclude-result-prefixes="func common my">
<xsl:output type="xml" indent="yes" />
<func:function name="my:personinfo">
<xsl:param name="name" />
<xsl:param name="address" />
<func:result>
<xsl:element name="details">
<xsl:element name="name" select="$name" />
<xsl:element name="address" select="$address" />
</xsl:element>
</func:result>
</func:function>
<xsl:element name="results">
<xsl:value-of select="my:personinfo('john', '02-234 pudding lane, london')" />
</xsl:element>
</xsl:stylesheet>

Well if you have nodes in a result tree fragment and want to output them to the result tree you need to use <xsl:copy-of select="my:personinfo('john', '02-234 pudding lane, london')"/>, not value-of.
Note however that xsl:element does not take a select attribute, if you want to create elements either simply use literal result elements like
<details>
<name><xsl:value-of select="$name"/></name>
<address><xsl:value-of select="$address"/></address>
</details>
or if you want to use xsl:element make sure you populate elements with the proper syntax e.g.
<xsl:element name="name"><xsl:value-of select="$name"/></xsl:element>

Related

XSLT copy-of - attribute left blank

I am trying to modify an existing XSLT to include the originalfilename attribute of the file node in the input XML as the file attribute of feedback in the output xml. I think I'm misunderstanding the copy-of statement and I would be very extremely grateful for any help. At the moment my desired output is displaying an empty file attribute on the feedback node.
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<document>
<xsl:for-each select="files/file/segmentpair[Comments/Comment]">
<xsl:apply-templates select="Comments/Comment" />
<xsl:copy-of select="files|file|source|target" />
</xsl:for-each>
</document>
</xsl:template>
<xsl:template match="Comment">
<feedback>
<xsl:attribute name="id">
<xsl:value-of select="count(preceding::Comments)+1" />
</xsl:attribute>
<xsl:attribute name="file">
<xsl:value-of select="files/file/#originalfilename" />
</xsl:attribute>
<xsl:value-of select="." />
</feedback>
</xsl:template>
</xsl:stylesheet>
Input
<files>
<file originalfilename="C:\Users\A\Documents\Studio 2014\Projects\15_002_\de-DE\master\advanced-materials-and-processes-msc-hons.xml">
<segmentpair id="1" locked="False" color="245,222,179" match-value="86">
<source>Advanced Materials and Processes (M.Sc.hons.)</source>
<target>Advanced Materials and Processes (MSc)</target>
<Comments>
<Comment>[ic14epub 20.01.2015 09:28:43] 'hons' taken out (discussion of this still ongoing as far as I'm aware)</Comment>
</Comments>
</segmentpair>
</file>
</files>
Desired Output
<feedback id="1" file="C:\Users\A\Documents\Studio 2014\Projects\15_002_\de-DE\master\advanced-materials-and-processes-msc-hons.xml">[ic14epub 20.01.2015 09:28:43] 'hons' taken out (discussion of this still ongoing as far as I'm aware)</feedback>
<source>Advanced Materials and Processes (M.Sc.hons.)</source>
<target>Advanced Materials and Processes (MSc)</target>
You want the #originalfilename of the first <file> ancestor for that <Comment>.
<xsl:template match="Comment">
<feedback
id="{count(preceding::Comments)+1}"
file="{ancestor::file[1]/#originalfilename}"
>
<xsl:value-of select="." />
</feedback>
</xsl:template>
Note the attribute value templates (curly braces). They can save quite a lot of typing.
You can get your desired output simply by adding a / before files in your <xsl:value-of>
<xsl:value-of select="/files/file/#originalfilename"/>
However, I would recommend to use
<xsl:value-of select="../../../#originalfilename"/>
instead of the absolute path, so it will still works if you have more files.

XSLT - how to apply a template to every node of the type?

I am quite new to xsl and functional programming, so I'll be grateful for help on this one:
I have a template that transforms some xml and provides an output. The problem is that there are many elements of type xs:date, all in different contexts, that must be localized. I use a concatenation of substrings of these xs:dates to produce a localized date pattern strings.
As you can guess this causes a lot of copy-paste "substring-this and substring-that". How can I write a template that will automatically transform all the elements of type xs:date to localized strings preserving all the context-aware transformations?
My xsl is something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
...
<input value="{substring(/select/a/date 9,2)}.{substring(/select/a/date, 6,2)}.{substring(/select/a/date 1,4)}">
...
<!-- assume that following examples are also with substrings -->
<div><xsl:value-of select="different-path/to_date"/></div>
...
<table>
<tr><td><xsl:value-of select="path/to/another/date"/></td></tr>
</table>
<apply-templates/>
</xsl:template>
<xsl:template match="something else">
<!-- more dates here -->
</xsl:template>
</xsl:stylesheet>
I hope I managed to make my question clear =)
UPD: Here is an example of xml:
<REQUEST>
<header>
<... />
<ref>
<ref_date type="xs:date">1970-01-01</ref_date>
</ref>
</header>
<general>
<.../>
<info>
<.../>
<date type="xs:date">1970-01-01</date>
<ExpireDate type="xs:date">1970-01-01</ExpireDate>
<RealDate type="xs:date">1970-01-01</RealDate>
<templateDetails>template details</templateDetails>
<effectiveDate type="xs:date">1970-01-01</effectiveDate>
</info>
<party>
<.../>
<date type="xs:date">1970-01-01</date>
</party>
<!-- many other parts of such kind -->
</general>
</REQUEST>
As for the output, there are many different options. The main thing is that these values must be set as a value of different html objects, such as tables, input fields and so on. You can see an example in the xsl listing.
P.S. I'm using xsl 1.0.
If you did a schema-aware XSLT 2.0 transformation, you wouldn't need all those type='xs:date' attributes: defining it in the schema as a date would be enough. You could then match the attributes with
<xsl:template match="attribute(*, xs:date)">
What you could do is add a template to match any element which has an #type attribute of 'xs:date', and do you substring manipulation in there
<xsl:template match="*[#type='xs:date']">
<xsl:value-of select="translate(., '-', '/')" />
</xsl:template>
In this case I am just replacing the hyphens by slashes as an example.
Then, instead of using xsl:value-of....
<div><xsl:value-of select="different-path/to_date"/></div>
You could use xsl:apply-templates
<div><xsl:apply-templates select="different-path/to_date"/></div>
Consider this XSLT as an example
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[#type='xs:date']">
<xsl:copy>
<xsl:value-of select="translate(., '-', '/')" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In this case, all this XSLT is doing is copying the XML document as-is, but changing the date elements.
If you wanted to use the date template for other elements, or values, you could also make it a named-template, like so
<xsl:template match="*[#type='xs:date']" name="date">
<xsl:param name="date" select="." />
<xsl:value-of select="translate($date, '-', '/')" />
</xsl:template>
This would allow you to also call it much like a function. For example, to format a data and add as an attribute you could do the following:
<input>
<xsl:attribute name="value">
<xsl:call-template name="date">
<xsl:with-param name="date" select="/select/a/date" />
</xsl:call-template>
</xsl:attribute>
</input>

XPath/XSLT nested predicates: how to get the context of outer predicate?

It seems that this question was not discussed on stackoverflow before, save for Working With Nested XPath Predicates ... Refined where the solution not involving nested predicates was offered.
So I tried to write the oversimplified sample of what I'd like to get:
Input:
<root>
<shortOfSupply>
<food animal="doggie"/>
<food animal="horse"/>
</shortOfSupply>
<animalsDictionary>
<cage name="A" animal="kittie"/>
<cage name="B" animal="dog"/>
<cage name="C" animal="cow"/>
<cage name="D" animal="zebra"/>
</animals>
</root>
Output:
<root>
<hungryAnimals>
<cage name="B"/>
<cage name="D"/>
</hungryAnimals>
</root>
or, alternatively, if there is no intersections,
<root>
<everythingIsFine/>
</root>
And i want to get it using a nested predicates:
<xsl:template match="cage">
<cage>
<xsl:attribute name="name">
<xsl:value-of select="#name"/>
</xsl:attribute>
</cage>
</xsl:template>
<xsl:template match="/root/animalsDictionary">
<xsl:choose>
<!-- in <food> in <cage> -->
<xsl:when test="cage[/root/shortOfSupply/food[ext:isEqualAnimals(./#animal, ?????/#animal)]]">
<hungryAnimals>
<xsl:apply-templates select="cage[/root/shortOfSupply/food[ext:isEqualAnimals(#animal, ?????/#animal)]]"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
So what should i write in place of that ??????
I know i could rewrite the entire stylesheet using one more template and extensive usage of variables/params, but it makes even this stylesheet significantly more complex, let alone the real stylesheet i have for real problem.
It is written in XPath reference that the dot . sign means the current context node, but it doesn't tell whether there is any possibility to get the node of context before that; and i just can't believe XPath is missing this obvious feature.
XPath 2.0 one-liner:
for $a in /*/animalsDictionary/cage
return
if(/*/shortOfSupply/*[my:isA($a/#animal, #animal)])
then $a
else ()
When applied on the provided XML document selects:
<cage name="B"/>
<cage name="D"/>
One cannot use a single XPath 1.0 expression to find that a given cage contains a hungry animal.
Here is an XSLT solution (XSLT 2.0 is used only to avoid using an extension function for the comparison -- in an XSLT 1.0 solution one will use an extension function for the comparison and the xxx:node-set() extension to test if the RTF produced by applying templates in the body of the variable contains any child element):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:Dict>
<a genName="doggie">
<name>dog</name>
<name>bulldog</name>
<name>puppy</name>
</a>
<a genName="horse">
<name>horse</name>
<name>zebra</name>
<name>pony</name>
</a>
<a genName="cat">
<name>kittie</name>
<name>kitten</name>
</a>
</my:Dict>
<xsl:variable name="vDict" select=
"document('')/*/my:Dict/a"/>
<xsl:template match="/">
<root>
<xsl:variable name="vhungryCages">
<xsl:apply-templates select=
"/*/animalsDictionary/cage"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$vhungryCages/*">
<hungryAnimals>
<xsl:copy-of select="$vhungryCages"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</root>
</xsl:template>
<xsl:template match="cage">
<xsl:if test="
/*/shortOfSupply/*[my:isA(current()/#animal,#animal)]">
<cage name="{#name}"/>
</xsl:if>
</xsl:template>
<xsl:function name="my:isA" as="xs:boolean">
<xsl:param name="pSpecName" as="xs:string"/>
<xsl:param name="pGenName" as="xs:string"/>
<xsl:sequence select=
"$pSpecName = $vDict[#genName = $pGenName]/name"/>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (corrected to be well-formed):
<root>
<shortOfSupply>
<food animal="doggie"/>
<food animal="horse"/>
</shortOfSupply>
<animalsDictionary>
<cage name="A" animal="kittie"/>
<cage name="B" animal="dogs"/>
<cage name="C" animal="cow"/>
<cage name="D" animal="zebras"/>
</animalsDictionary>
</root>
the wanted, correct result is produced:
<root>
<hungryAnimals>
<cage name="B"/>
<cage name="D"/>
</hungryAnimals>
</root>
Explanation: Do note the use of the XSLT current() function.
XPath 1.0 is not "relationally complete" - it can't do arbitrary joins. If you're in XSLT, you can always get round the limitations by binding variables to intermediate nodesets, or (sometimes) by using the current() function.
XPath 2.0 introduces range variables, which makes it relationally complete, so this limitation has gone.
Doesn't <xsl:when test="cage[#animal = /root/shortOfSupply/food/#animal]"> suffice to express your test condition?
Notice The dot operator in XPath is related to the current context. In XSLT the current template context_ is given by the function current(), which most of the time (not always) coincides with the ..
You can perform the test (and the apply templates as well), using the parent axis abbreviation (../):
cage[#animal=../../shortOfSupply/food/#animal]
Moreover the match pattern in the the first template is wrong, it should be relative to the root:
/root/animalsDictionary
#Martin suggestion is also obviously correct.
Your final template slightly modified:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root/animalsDictionary">
<xsl:choose>
<xsl:when test="cage[#animal=../../shortOfSupply/food/#animal]">
<hungryAnimals>
<xsl:apply-templates select="cage[#animal
=../../shortOfSupply/food/#animal]"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="cage">
<cage name="{#name}"/>
</xsl:template>
</xsl:stylesheet>

XSLT key() lookup

I'm trying out a sample of look-up tables in XSLT and am not able to get it to work
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" />
<xsl:key name="classification-lookup" match="classification" use="id" />
<xsl:variable name="classification-top" select="document('')/*/classifications" />
<xsl:template match="BusinessListing">
<listing>
<id>
<xsl:value-of select="id" />
</id>
<xsl:apply-templates select="$classification-top">
<xsl:with-param name="curr-label" select="." />
</xsl:apply-templates>
</listing>
</xsl:template>
<xsl:template match="classifications">
<xsl:param name="curr-label" />
<category>
<xsl:value-of select="key('classification-lookup', $curr-label/listingData/classifications/classificationId)/description" />
</category>
</xsl:template>
<classifications>
<classification>
<id>7981</id>
<description>Category1</description>
</classification>
<classification>
<id>7982</id>
<description>Category2</description>
</classification>
<classification>
<id>7983</id>
<description>Category3</description>
</classification>
<classification>
<id>7984</id>
<description>Category4</description>
</classification>
</classifications>
</xsl:stylesheet>
and the source is as below .
<?xml version="1.0"?>
<BusinessListings>
<BusinessListing>
<id>1593469</id>
<listingData>
<classifications>
<classificationId>7982</classificationId>
<classificationId>7983</classificationId>
</classifications>
</listingData>
</BusinessListing>
</BusinessListings>
In the result below , The category is empty but I need the Classification Id from the source to be matched with the id in the classification tag and the category generated .
<?xml version="1.0" encoding="UTF-8"?>
<listing>
<id>1593469</id> -- Empty I need the Category2 and Category3 here
<category/>
</listing>
I know that I may be wide off mark but I've just started off with XSLT and referred the sample here http://www.ibm.com/developerworks/xml/library/x-xsltip.html . Thanks for the help .
Your XSLT stylesheet contains an error -- according to spec, any child-element of xsl:stylesheet (aka top-level element) must be in a non-null namespace:
"*In addition, the xsl:stylesheet
element may contain any element not
from the XSLT namespace, provided that
the expanded-name of the element has a
non-null namespace URI. "
If the XSLT processor you are using doesn't raise an error, then it is non-compliant and buggy and shouldn't be used. Find and use a compliant XSLT processor (I am using .NET XslCompiledTransform, Saxon 6.5.5, ..., etc).
There are other errors, too.
Solution:
Define a new namespace with prefix (say) "x:":
Change the embedded <classifications> to <x:classifications> -- now this conforms to the Spec.
Perform more changes to the code until you get this transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="my:x" exclude-result-prefixes="x">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="classification-lookup" match="classification"
use="id" />
<xsl:template match="BusinessListing">
<listing>
<id>
<xsl:value-of select="id" />
</id>
<xsl:apply-templates/>
</listing>
</xsl:template>
<xsl:template match="classificationId">
<xsl:variable name="vCur" select="."/>
<xsl:for-each select="document('')">
<category>
<xsl:value-of select=
"key('classification-lookup',$vCur)/description" />
</category>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
<x:classifications>
<classification>
<id>7981</id>
<description>Category1</description>
</classification>
<classification>
<id>7982</id>
<description>Category2</description>
</classification>
<classification>
<id>7983</id>
<description>Category3</description>
</classification>
<classification>
<id>7984</id>
<description>Category4</description>
</classification>
</x:classifications>
</xsl:stylesheet>
.4. In the above code notice the line: <xsl:for-each select="document('')"> .
The purpose of this is to make the stylesheet the current document. The key() function operates only on the current document and if you want the embedded classification elements to be indexed and used, you must change the current document (usually in this way). In XSLT 2.0 the key() function allows a 3rd argument which is a node from the document whose index should be used.
When this transformation is applied to the provided XML document:
<BusinessListings>
<BusinessListing>
<id>1593469</id>
<listingData>
<classifications>
<classificationId>7982</classificationId>
<classificationId>7983</classificationId>
</classifications>
</listingData>
</BusinessListing>
</BusinessListings>
the wanted, correct result is produced:
<listing>
<id>1593469</id>
<category>Category2</category>
<category>Category3</category>
</listing>

XSLT: need to replace document('')

I've the following xslt file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- USDomesticCountryList - USE UPPERCASE LETTERS ONLY -->
<xsl:variable name="USDomesticCountryList">
<entry name="US"/>
<entry name="UK"/>
<entry name="EG"/>
</xsl:variable>
<!--// USDomesticCountryList -->
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:value-of select="normalize-space(document('')//xsl:variable[#name='USDomesticCountryList']/entry[#name=$country]/#name)"/>
</xsl:template>
</xsl:stylesheet>
I need to replace the "document('')" xpath function, what should I use instead?
I've tried to remove it completely but the xsl document doesn't work for me!
I need to to so because the problem is :
I am using some XSLT document that uses the above file, say document a.
So I have document a that includes the above file (document b).
I am using doc a from java code, I am do Caching for doc a as a javax.xml.transform.Templates object to prevent multiple reads to the xsl file on every transformation request.
I found that, the doc b is re-calling itself from the harddisk, I believe this is because of the document('') function above, so I wanna replace/remove it.
Thanks.
If you want to access the nodes inside a variable you normally use the node-set() extension function. The availability and syntax depends on the processor you use. For MSXML and Saxon you can use exsl:node-set(). To use the extension function you will have to include the namespace that defines the function.
E.g. (tested wiht MSXML, returns US for countryName = 'US'):
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
>
<xsl:output method="xml"/>
<!-- USDomesticCountryList - USE UPPERCASE LETTERS ONLY -->
<xsl:variable name="USDomesticCountryList">
<entry name="US"/>
<entry name="UK"/>
<entry name="EG"/>
</xsl:variable>
<!--// USDomesticCountryList -->
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:value-of select="exsl:node-set($USDomesticCountryList)/entry[#name=$country]/#name"/>
</xsl:template>
</xsl:stylesheet>
If you're trying to make the IsUSDomesticCountry template work without using document(''), you could rewrite the template to
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:choose>
<xsl:when test="$country='US'">true</xsl:when>
<xsl:when test="$country='UK'">true</xsl:when>
<xsl:when test="$country='EG'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:template>
or
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country" select="normalize-space($countryParam)"/>
<xsl:value-of select="$country='US' or $country='UK' or $country='EG'"/>
</xsl:template>
or even
<xsl:template name="IsUSDomesticCountry">
<xsl:param name="countryParam"/>
<xsl:variable name="country"
select="concat('-', normalize-space($countryParam),'-')"/>
<xsl:variable name="domesticCountries" select="'-US-UK-EG-'"/>
<xsl:value-of select="contains($domesticCountries, $country)"/>
</xsl:template>
Personally, I find the variant using document('') to be more readable.