XSLT help - XSL for various similar XML, Template usage - templates

I am very new to XSL and XPath. Apologies if this question shows some stupidity.
I have an XML something like
<root>
<widget name="status">
...
<component name="date">
<component name="day" label="Fri"/>
<component name="date" label="4"/>
</component>
<component name="time" label="11:23 AM"/>
....
</widget>
<widget name="foo">
</widget>
</root>
I need to create a DateTime tag which is compose of all the three values something like
Fri 4 11:23 AM
I am writing an XSL for it.
<DateTime>
<xsl:value-of select="(//widget[#name="status"]/component[#name='date'])[1]/#label"/>
<xsl:text> </xsl:text>
<xsl:value-of select="(//widget[#name="status"]/component[#name='date'])[2]/#label"/>
<xsl:text> </xsl:text>
<xsl:value-of select="//widget[#name="status"]/component[#name='time']/#label"/>
</DateTime>
Question:
I am passing the "widget[#name="date"]" to each of the select statement. Is there any better way to shorten the xpath.
I need to move this into a template and call the template. which one I should use call-template/apply-templates?
We have a set of similar applications which generate these XML. The above XML is from applicationA. ApplicationB might show the detail in little bit different way, something like <component name="datetime">Fri 4 11:23 AM</component>. We have almost 3-4 such application where they display the details in little bit different way.
DateTime is just an example, there are some other details which I also need to capture from these various applications.
I am thinking to write a single XSL to deal with all the applications.

One way to do it with your XML would be this:
<xsl:template match="widget">
<!-- ... -->
<xsl:apply-templates select="." mode="create-date-time" />
<!-- ... -->
</xsl:template>
<xsl:template match="widget" mode="create-date-time">
<xsl:variable name="date" select="component[#name='date']" />
<xsl:variable name="time" select="component[#name='time']" />
<DateTime>
<xsl:value-of select="normalize-space(
concat(
$date/component[#name='day']/#label, ' ',
$date/component[#name='date']/#label, ' ',
$time/#label
)
)" />
</DateTime>
</xsl:template>
I am passing the widget[#name="date"] to each of the select statement. Is there any better way to shorten the xpath.
Use <xsl:template>/<xsl:apply-templates>, and relative paths. Store things you need more than once in an <xsl:variable>. See above.
I need to move this into a template and call the template. which one I should use call-template/apply-templates?
The latter. Always go for <xsl:apply-templates> unless there is good reason not to. As a rule of thumb: If you are unsure, then there is no good reason.
We have a set of similar applications which generate these XML. The above XML is from applicationA. ApplicationB might show the detail in little bit different way, something like <component name="datetime">Fri 4 11:23 AM</component> We have almost 3-4 such application where they display the details in little bit different way.
You could expand the create-date-time template to accommodate for this:
<xsl:template match="widget" mode="create-date-time">
<xsl:variable name="date" select="component[#name='date']" />
<xsl:variable name="time" select="component[#name='time']" />
<xsl:variable name="dt" select="component[#name='datetime']" />
<DateTime>
<xsl:value-of select="normalize-space(
concat(
$dt/label, ' ',
$date/component[#name='day']/#label, ' ',
$date/component[#name='date']/#label, ' ',
$time/#label
)
)" />
</DateTime>
</xsl:template>
There will be no error if certain components are missing. normalize-space() makes sure that there are no excess spaces for any combination of components.
The above may fail if the date+time and datetime components are not mutually exclusive (I've assumed they are). If they are not, or if more complicated cases occur, create additional specific templates, like this one:
<xsl:template match="widget[component[name='datetime']]" mode="create-date-time">
<xsl:variable name="dt" select="component[#name='datetime']" />
<DateTime>
<xsl:value-of select="component[#name='datetime']/#label" />
</DateTime>
</xsl:template>
The <xsl:apply-templates> will make sure the correct one is called. Just create specific match= expressions for each case that can occur.

This simple transformation (16 lines, single template, completely "push" style, no variables, no modes):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match=
"component
[contains('|day|date|time|',
concat('|', #name, '|'))
]
">
<xsl:value-of select="concat(#label, ' ')"/>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<widget name="status">
...
<component name="date">
<component name="day" label="Fri"/>
<component name="date" label="4"/>
</component>
<component name="time" label="11:23 AM"/>
....
</widget>
<widget name="foo">
</widget>
</root>
produces exactly the wanted result:
Fri 4 11:23 AM

Related

Exclude first element of a certain type when doing apply-templates

This is my source XML:
<DEFINITION>
<DEFINEDTERM>criminal proceeding</DEFINEDTERM>
<TEXT> means a prosecution for an offence and includes –</TEXT>
<PARAGRAPH>
<TEXT>a proceeding for the committal of a person for trial or sentence for an offence; and</TEXT>
</PARAGRAPH>
<PARAGRAPH>
<TEXT>a proceeding relating to bail –</TEXT>
</PARAGRAPH>
<TEXT>but does not include a prosecution that is a prescribed taxation offence within the meaning of Part III of the Taxation Administration Act 1953 of the Commonwealth;</TEXT>
</DEFINITION>
This is my XSL:
<xsl:template name="DEFINITION" match="DEFINITION">
<xsl:element name="body">
<xsl:attribute name="break">before</xsl:attribute>
<xsl:element name="defn">
<xsl:attribute name="id" />
<xsl:attribute name="scope" />
<xsl:value-of select="DEFINEDTERM" />
</xsl:element>
<xsl:element name="text">
<xsl:value-of select="replace(TEXT[1],'–','--')" />
</xsl:element>
</xsl:element>
<xsl:apply-templates select="*[not(self::TEXT[1])]" />
</xsl:template>
As per my XSL, I want to do something with the DEFINEDTERM element and the TEXT element that immediately follows it.
Then I want to apply-templates to the rest of the elements, except for the DEFINEDTERM and TEXT element that have already been dealt with. Most importantly, I don't want to apply templates to the first TEXT element.
How do I achieve this, because my XSL above does not work.
I have other templates for TEXT and PARAGRAPH, but not DEFINEDTERM. I have <xsl:template match="*|#*" /> at the top of the XSL.
You did not post the expected result nor a minimal reproducible example, so I can only guess you want to do:
<xsl:template match="DEFINITION">
<body break="before">
<defn id="" scope="">
<xsl:value-of select="DEFINEDTERM" />
</defn>
<text>
<xsl:value-of select="replace(DEFINEDTERM/following-sibling::TEXT[1],'–','--')" />
</text>
</body>
<xsl:apply-templates select="* except (DEFINEDTERM | DEFINEDTERM/following-sibling::TEXT[1])" />
</xsl:template>
At least that's what I understand as:
I want to do something with the DEFINEDTERM element and the TEXT element that immediately follows it.
This is assuming you are using XSLT 2.0 or higher (otherwise you would not be able to use the replace() function).
--
P.S. You might want to make this a bit more efficient by defining DEFINEDTERM/following-sibling::TEXT[1] as a variable first, then referring to the variable instead.

func:function return result tree fragment

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>

XSLT: merge two tags using attribute from one, tag from another with a transform/mapping

I have two tags in the input file, variable and type:
<variable baseType="int" name="X">
</variable>
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
And I need to generate the following output file:
<Item name="Y">
<Field name="X" type="Long" />
</Item>
So conceptually my approach here has been to convert the type tag into the Item tage, the variable instance to the Field. That's working fine:
<xsl:for-each select="type[#baseType='structure']">
<Item>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:for-each select="variableInstance">
<Field>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:attribute name="type">**THIS IS WHERE I'M STUCK**</xsl:attribute>
</Field>
</xsl:for-each>
</Item>
</xsl:for-each>
The problem I'm stuck on is:
I don't know how to get the variableInstance/Field tag to match on the variable tag by name, so I can access the baseType.
I need to map "int" to "Long" once I'm able to do 1.
Thanks in advance!
PROBLEM 1.
For the first problem that you have you can use a key:
<xsl:key name="variable-key" match="//variable" use="#name" />
That key is going to index all variable elements in the document, using their name. So now, we can access any of those elements by using the following XPath expression:
key('variable-key', 'X')
Using this approach is efficient when you have a lot of variable elements.
NOTE: this approach is not valid if each variable has its own scope (i.e. you have local variables which are not visible in different parts of the document). In that case this approach should be modified.
PROBLEM 2.
For mapping attributes you could use a template like the following:
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
The meaning of this transformation is: each time that we match a baseType attribute with int value, it has to be replaced by a Long value.
This transformation would be in place for each #baseType attribute in the document.
Using the described strategies a solution could be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- Index all variable elements in the document by name -->
<xsl:key name="variable-key"
match="//variable"
use="#name" />
<!-- Just for demo -->
<xsl:template match="text()" />
<!-- Identity template: copy attributes by default -->
<xsl:template match="#*">
<xsl:copy>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
<!-- Match the structure type -->
<xsl:template match="type[#baseType='structure']">
<Item>
<xsl:apply-templates select="*|#*" />
</Item>
</xsl:template>
<!-- Match the variable instance -->
<xsl:template match="variableInstance">
<Field>
<!-- Use the key to find the variable with the current name -->
<xsl:apply-templates select="#*|key('variable-key', #name)/#baseType" />
</Field>
</xsl:template>
<!-- Ignore attributes with baseType = 'structure' -->
<xsl:template match="#baseType[. = 'structure']" />
<!-- Change all baseType attributes with long values to an attribute
with the same name but with an int value -->
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
That code is going to transform the following XML document:
<!-- The code element is present just for demo -->
<code>
<variable baseType="int" name="X" />
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
</code>
into
<Item name="Y">
<Field baseType="Long" name="X"/>
</Item>
Oki I've got a solution for point 1 xsltcake slice or with use of templates. For point two I would probably use similar template to the one that Pablo Pozo used in his answer

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>

handling different namespaces for call-template in xslt

I am working on call-template, where the source looks like this.
Source:
<Content>
<first>
<text>
Text
</text>
<link xmlns="Some namespace">
<AA>abcd</AA>
<BB>hi all</BB>
</link>
</first>
<second>
<link xmlns="Some other namespace">
<AA>abcd1</AA>
<BB>hi all21</BB>
</link>
</second>
<three>
<link xmlns="other namespace">
<AA>abcd2</AA>
<BB>hi all33</BB>
</link>
</three>
</Content>
XSLT written:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:n1="Some namespace" xmlns:n2="Some other namespace" xmlns:n3="other namespace">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Content">
<xsl:call-template name="process">
<xsl:with-param name="item" select="first/n1:link" />
</xsl:call-template>
<xsl:call-template name="process">
<xsl:with-param name="item" select="second/n2:link" />
</xsl:call-template>
<xsl:call-template name="process">
<xsl:with-param name="item" select="three/n3:link" />
</xsl:call-template>
</xsl:template>
<xsl:template name="process">
<xsl:param name="item" />
<xsl:value-of select="$item/AA" />
</xsl:template>
</xsl:stylesheet>
I am getting blank output.I know the reason because i didn't append the namespace prefix for it. like "n1:A" like that.
As the is coming multiple times. I wrote a template and called where ever needed. But the name space of each link is diffrent. How to do I modify my code so that I can reuse the template "process".
Can any one help, how Do I modify the "process" template accordingly to handle with diffrent namespace but same structure.
Thank you.
Instead of doing this
<xsl:value-of select="$item/AA" />
You could change the expression to this
<xsl:value-of select="$item/*[local-name() = 'AA'][namespace-uri()=namespace-uri($item)]" />
i.e. Check the name without namespace is 'AA', and that it has the same namespace as the parent element. This would mean if you had another 'AA' element within the 'link' element with a different namespace, it would not be picked up.
It's worth pointing out that this is a poorly designed XML document. Whenever you see someone creating two namespaces N1 and N2 such that the local names in N1 are the same as the local names in N2, you should detect a design smell. They are making the XML much more difficult for people to process.
When I see this kind of input (it sometimes arises if people mistakenly change the namespace URI for version 2 of their vocabulary) I usually reckon that the best approach to processing it is to write a pipeline in which the first phase is to normalize the namespaces, so that the "real" processing logic doesn't have to deal with multiple namespaces in the way your code is doing, which always ends up looking like spaghetti.