Can input parameters be transformed into xml structure? - xslt

I need to transform input paramater string like "1-410000 54-420987 63-32000" into the structure (like below) inside xslt to use its data later in xslt:
<config:categories>
<category>
<value>410000</value>
<label>1</label>
</category>
<category>
<value>420987</value>
<label>54</label>
</category>
<category>
<value>32000</value>
<label>63</label>
</category>
</config:categories>
P.S.
Are there any other options to parse string like "1-410000 54-420987 63-32000" in order to use its data in xslt to extract the right part (after the '-') if the left part is found in input document?

This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:config="some:config" exclude-result-prefixes="config">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pData" select="'1-410000 54-420987 63-32000'"/>
<xsl:template match="/*">
<config:categories>
<xsl:call-template name="gen"/>
</config:categories>
</xsl:template>
<xsl:template name="gen">
<xsl:param name="pGen" select="$pData"/>
<xsl:if test="$pGen">
<xsl:variable name="vChunk" select=
"substring-before(concat($pGen, ' '), ' ')"/>
<category>
<value><xsl:value-of select="substring-after($vChunk,'-')"/></value>
<label><xsl:value-of select="substring-before($vChunk,'-')"/></label>
</category>
<xsl:call-template name="gen">
<xsl:with-param name="pGen" select="substring-after($pGen, ' ')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), produces the wanted, correct result:
<config:categories xmlns:config="some:config">
<category>
<value>410000</value>
<label>1</label>
</category>
<category>
<value>420987</value>
<label>54</label>
</category>
<category>
<value>32000</value>
<label>63</label>
</category>
</config:categories>
Explanation:
Proper use of substring-before() and substring-after() plus recursion.

As Dimitre shows, parsing strings in XSLT 1.0 is quite cumbersome. This is an area where XSLT 2.0 is far superior. It can be done like this:
<categories>
<xsl:for-each select="tokenize($param, '\s+')">
<category>
<label><xsl:value-of select="substring-after(., '-')"/></label>
<value><xsl:value-of select="substring-before(., '-')"/></value>
</category>
</xsl:for-each>
</categories>
And of course in XSLT 2.0 you can then use the categories structure as a first-class node value without having to use the node-set() extension to get inside it.

to make XML from a string I think you have to use Java. I am not aware of an XSLT functioniality to achieve that.
For your second question:
There is substring(), string-length(), substring-before() and substring-after() that might help with your request.
Please refer: http://www.w3schools.com/xpath/xpath_functions.asp

Related

Using XSL:sort within xsl:if

I'm just figuring out how XSL works... mostly. I have 3 nodes (STUX, KbnApp, KbnStorge) that I want to transform in the same way except for how their list of "Servers" children are sorted. I wanted to do this with a single template that uses an xsl:if to choose an alternate sorting method for the "STUX". But I couldn't get it to work. The STUX Servers should end up in descending order, the others should stay in whatever order they are currently in.
Here is my XML
<?xml version="1.0" encoding="UTF-8"?>
<Categories>
<STUX>
<Servers>stuxsh01</Servers>
<Servers>stuxsh03</Servers>
<Servers>stuxsh02</Servers>
<UnitTest>Pass</UnitTest>
</STUX>
<KbnApp>
<Servers>stks01</Servers>
<Servers>stks03</Servers>
<Servers>stks02</Servers>
<UnitTest>Pass</UnitTest>
</KbnApp>
<KbnStorage>
<Servers>stksnfs01</Servers>
<Servers>stksnfs02</Servers>
<UnitTest>Fail</UnitTest>
</KbnStorage>
</Categories>
This transform gives me the result that I want, but I've had to make two templates that are almost identical, wasting lines. I think there should be a way to do this with one template and an xsl:if with an xsl:sort inside it. Can anyone tell me how I would format XSL to do that? Every time I've tried putting an xsl:if the XSL can't be parsed/won't apply and I don't know why. PS I'm SURE there is things I've done here that aren't proper, but by golly I got it to work :) I'm definitely open to learning how to do it better!
EDIT: Corrected a typo
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<categories>
<countCategories><xsl:value-of select="count(child::*)" /></countCategories>
<xsl:apply-templates />
</categories>
</xsl:template>
<xsl:template match="KbnApp|KbnStorage">
<Category>
<Name><xsl:value-of select="name()" /></Name>
<countServers><xsl:value-of select="count(Servers)" /></countServers>
<xsl:copy-of select="UnitTest" />
<xsl:for-each select="Servers">
<Server><xsl:value-of select="."/></Server>
</xsl:for-each>
</Category>
</xsl:template>
<xsl:template match="STUX">
<Category>
<Name><xsl:value-of select="name()" /></Name>
<countServers><xsl:value-of select="count(Servers)" /></countServers>
<xsl:copy-of select="UnitTest" />
<xsl:for-each select="Servers">
<xsl:sort order="descending"/>
<Server><xsl:value-of select="."/></Server>
</xsl:for-each>
</Category>
</xsl:template>
</xsl:stylesheet>
For reference...The desired result
<categories>
<countCategories>1</countCategories>
<Category>
<Name>STUX</Name>
<countServers>3</countServers>
<UnitTest>Pass</UnitTest>
<Server>stuxsh03</Server>
<Server>stuxsh02</Server>
<Server>stuxsh01</Server>
</Category>
<Category>
<Name>KbnApp</Name>
<countServers>3</countServers>
<UnitTest>Pass</UnitTest>
<Server>stks01</Server>
<Server>stks03</Server>
<Server>stks02</Server>
</Category>
<Category>
<Name>KbnStorage</Name>
<countServers>2</countServers>
<UnitTest>Fail</UnitTest>
<Server>stksnfs01</Server>
<Server>stksnfs02</Server>
</Category>
</categories>
Try changing:
<xsl:sort order="descending"/>
to:
<xsl:sort select="self::*[parent::STUX]" order="descending"/>
Then you can use this in a template that matches all your categories, and have only the STUX category sorted.

XSLT: Replace string with Abbreviations

I would like to know how to replace the string with the abbreviations.
My XML looks like below
<concept reltype="CONTAINS" name="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" type="NUM">
<code meaning="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" value="18074-5" schema="LN" />
<measurement value="5.7585187646">
<code value="cm" schema="UCUM" />
</measurement>
<content>
<concept reltype="HAS ACQ CONTEXT" name="Image Mode" type="CODE">
<code meaning="Image Mode" value="G-0373" schema="SRT" />
<code meaning="2D mode" value="G-03A2" schema="SRT" />
</concept>
</content>
</concept>
and I am selecting some value from the xml like,
<xsl:value-of select="concept/measurement/code/#value"/>
Now what I want is, I have to replace cm with centimeter. I have so many words like this. I would like to have a xml for abbreviations and replace from them.
I saw one similar example here.
Using a Map in XSL for expanding abbreviations
But it replaces node text, but I have text as attribute. Also, it would be better for me If I can find and replace when I select text using xsl:valueof select instead of having a separate xsl:template. Please help. I am new to xslt.
I have created XSLT v "1.1". For abbreviations I have created XML file as you have mentioned:
Abbreviation.xml:
<Abbreviations>
<Abbreviation>
<Short>cm</Short>
<Full>centimeter</Full>
</Abbreviation>
<Abbreviation>
<Short>m</Short>
<Full>meter</Full>
</Abbreviation>
</Abbreviations>
XSLT:
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" />
<xsl:param name="AbbreviationDoc" select="document('Abbreviation.xml')"/>
<xsl:template match="/">
<xsl:call-template name="Convert">
<xsl:with-param name="present" select="concept/measurement/code/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Convert">
<xsl:param name="present"/>
<xsl:choose>
<xsl:when test="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]">
<xsl:value-of select="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]/Full"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$present"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
INPUT:
as you have given <xsl:value-of select="concept/measurement/code/#value"/>
OUTPUT:
centimeter
You just need to enhance this Abbreviation.xml to keep short and full value of abbreviation and call 'Convert' template with passing current value to get desired output.
Here a little shorter version:
- with abbreviations in xslt file
- make use of apply-templates with mode to make usage shorter.
But with xslt 1.0 node-set extension is required.
<?xml version="1.0" encoding="utf-8"?>
<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" indent="yes"/>
<xsl:variable name="abbreviations_txt">
<abbreviation abbrev="cm" >centimeter</abbreviation>
<abbreviation abbrev="m" >meter</abbreviation>
</xsl:variable>
<xsl:variable name="abbreviations" select="exsl:node-set($abbreviations_txt)" />
<xsl:template match="/">
<xsl:apply-templates select="concept/measurement/code/#value" mode="abbrev_to_text"/>
</xsl:template>
<xsl:template match="* | #*" mode="abbrev_to_text">
<xsl:variable name="abbrev" select="." />
<xsl:variable name="long_text" select="$abbreviations//abbreviation[#abbrev = $abbrev]/text()" />
<xsl:value-of select="$long_text"/>
<xsl:if test="not ($long_text)">
<xsl:value-of select="$abbrev"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

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: 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.