Split large xslt into smaller ones - xslt

I have a large xslt file that is giving problems during deployment
com.sun.org.apache.bcel.internal.generic.ClassGenException: Branch target offset too large for short
at com.sun.org.apache.bcel.internal.generic.BranchInstruction.dump(BranchInstruction.java:99)
at com.sun.org.apache.bcel.internal.generic.InstructionList.getByteCode(InstructionList.java:980)
at com.sun.org.apache.bcel.internal.generic.MethodGen.getMethod(MethodGen.java:616)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Mode.compileNamedTemplate(Mode.java:556)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Mode.compileTemplates(Mode.java:566)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Mode.compileApplyTemplates(Mode.java:818)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Stylesheet.compileModes(Stylesheet.java:615)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Stylesheet.translate(Stylesheet.java:730)
at com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC.compile(XSLTC.java:370)
at com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC.compile(XSLTC.java:445)
For this, I need to split this large xslt into smaller ones.
I've seen xsl:include tag, but seems like this works for seperate templates.
In my case, its a single parent tag with multiple assignments like this
<xsl:template match="/">
<ns5:taskListResponse>
<xsl:for-each select="/tns:taskListResponse/task:task">
<ns7:task>
<xsl:if test="task:title">
<ns7:title>
<xsl:value-of select="task:title"/>
</ns7:title>
</xsl:if>
<xsl:if test="task:taskDefinitionURI">
<ns7:taskDefinitionURI>
<xsl:value-of select="task:taskDefinitionURI"/>
</ns7:taskDefinitionURI>
</xsl:if>
<xsl:if test="task:creator">
<ns7:creator>
<xsl:value-of select="task:creator"/>
</ns7:creator>
</xsl:if>
........100 more tags like this.....
...................
</xsl:for-each>
</ns5:taskListResponse>
How can I split this xsl?
I want to put some tags in another file and include those inside the
Appreciate your help
Regards
Ravi

I would consider splitting this up into separate templates, for example each of the if tests could be replaced by apply-templates, and the following template to do the work:
<xsl:template match="task:*">
<xsl:element name="ns7:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
If you don't need to re-order the children then the entire stylesheet boils down to
<xsl:template match="/">
<ns5:taskListResponse>
<xsl:apply-templates select="/tns:taskListResponse/task:task" />
</ns5:taskListResponse>
</xsl:template>
<xsl:template match="task:task">
<ns7:task><xsl:apply-templates select="*" /></ns7:task>
</xsl:template>
<xsl:template match="task:*">
<xsl:element name="ns7:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
It gets slightly more complex if you do need to re-order things, then you'll need 100 separate <xsl:apply-templates select="task:foo" /> in place of the <xsl:apply-templates select="*" />, but it's still smaller and more modular.

Related

correct approach for XSL templates

I started on XSL a few weeks ago, and I have developed a pattern for templates.
My templates will handle some of the content of the matched element, then pass off its children to other templates that might match them.
But I exclude elements that it has already dealt with, so as not to process the same elements again.
For example:
<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:apply-templates select="TEXT" />
</xsl:element>
<xsl:apply-templates select="*[not(self::DEFINEDTERM|self::TEXT)]" />
</xsl:template>
Is this the correct approach? Is there a better way?
It seems you don't need any xsl:element or xsl:attribute but could just use literal result elements and attribute value templates e.g. <body break="before"><xsl:apply-templates select="DEFINEDTERM, TEXT"/></body> (that is XSLT 2 or 3, for XSLT 1 use <body break="before"><xsl:apply-templates select="DEFINEDTERM"/> <xsl:apply-templates select="TEXT"/></body>) plus obviously a template
<xsl:template matched="DEFINEDTERM">
<defn id="" scope="">
<xsl:value-of select="."/>
</defn>
</xsl:template>
In XSLT 2/3 you can write <xsl:apply-templates select="*[not(self::DEFINEDTERM|self::TEXT)]" /> as e.g. <xsl:apply-templates select="* except (DEFINEDTERM, TEXT)"/>.

XSLT Expertise required duplicate values

Question edited, more information added
viv:tokenize=str:tokenize
viv:value-of=str:value-of
Part1 - Declaration and assigning value
<declare name="searchhistories" />
<set-var name="searchhistories">
<value-of select="concat(viv:value-of('searchquery','var'),'|',viv:replace(viv:value-of('searchhistory', 'var'),concat(viv:value-of('searchquery','var'),'\|'),'','g'))" />
</set-var>
Part 2: tokenize and de-duplicate
<xsl:for-each select="viv:tokenize($searchhistories,'|',false, false)">
<xsl:variable name="i" select="position()"/>
<xsl:if test="$i < 11">
<xsl:value-of select="." /> |
</xsl:if>
</xsl:for-each>
Able to tokenize but de-duplication not working
What should be code for de-duplication
<xsl:for-each select=***distinct-values***("viv:tokenize($searchhistories,'|',false, false)")>
Something like this ?
Try
<xsl:for-each select="set:distinct(viv:tokenize($searchhistories,'|',false, false))">
with the stylesheet declaring xmlns:set="http://exslt.org/sets" e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:set="http://exslt.org/sets" exclude-result-prefixes="set">
The answer is based on the documentation you linked to in your comment, I am not able to test that.
But http://xsltransform.net/ej9EGcy uses the EXSLT version of tokenize and works fine:
<xsl:template match="item">
<xsl:copy>
<xsl:for-each select="set:distinct(str:tokenize(., '|'))">
<xsl:if test="position() > 1">|</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>

Change namespaces using XSLT

I'm writing a wrapper layer over an existing out of the box service. For this, I need to convert the namespaces of the xml that is returned by the service to my namespaces
The xml has different namespaces for different tags, and I need to map them to another set of namespaces
In other words, the prefix
ns1 to be modified to myns1
ns2 to myns2, and so on...
Here is how my XML looks like
<ns1:Response>
<ns2:task>..</ns2:task>
<ns2:address>..</ns2:address>
<ns2:pin>..</ns2:pin>
<ns3:address>
<ns4:add1>..</ns4:add1>
<ns4:add2>..</ns4:add2>
<ns4:add3>
<ns5:asdf>..</ns5:asdf>
<ns5:qwe>..</ns5:qwe>
</ns4:add3>
<ns4:add4>..</ns4:add4>
</ns3:address>
<ns2:query>..</ns2:query>
</ns1:Response>
And I want to change this to
<myns1:Response>
<myns2:task>..</myns2:task>
<myns2:address>..</myns2:address>
<myns2:pin>..</myns2:pin>
<myns3:address>
<myns4:add1>..</myns4:add1>
<myns4:add2>..</myns4:add2>
<myns4:add3>
<myns5:asdf>..</myns5:asdf>
<myns5:qwe>..</myns5:qwe>
</myns4:add3>
<myns4:add4>..</myns4:add4>
</myns3:address>
<myns2:query>..</myns2:query>
</myns1:Response>
Basically, just the namespace prefixes change from one set to another.
Can you please suggest me how to do this in xslt?
I've tried using apply-templates, but i'm not able to figure out a solution
Regards
Ravi
The following stylesheet worked!
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myns1="http://www.w3.org/TR/html4/1"
xmlns:myns2="http://www.w3.org/TR/html4/2"
xmlns:myns3="http://www.w3.org/TR/html4/3"
xmlns:myns4="http://www.w3.org/TR/html4/4"
xmlns:myns5="http://www.w3schools.com/furniture"
xmlns:ns1="http://www.w3.org/TR/html4/1"
xmlns:ns2="http://www.w3.org/TR/html4/2"
xmlns:ns3="http://www.w3.org/TR/html4/3"
xmlns:ns4="http://www.w3.org/TR/html4/4"
xmlns:ns5="http://www.w3schools.com/furniture">
<xsl:template match="*">
<myns1:taskListResponse>
<xsl:apply-templates/>
</myns1:taskListResponse>
</xsl:template>
<xsl:template match="ns2:*">
<xsl:element name="myns2:{local-name()}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="ns3:*">
<xsl:element name="myns3:{local-name()}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="ns4:*">
<xsl:element name="myns4:{local-name()}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="ns5:*">
<xsl:element name="myns5:{local-name()}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
But seriously, I'm not able to understand how this works.
The snippet
<xsl:template match="ns2:*">
<xsl:element name="myns2:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
means match all elements that start with ns2: and replace with myns2:
But when I used the same logic for all, it gave only the first 2 level elements
Ex :
Stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myns1="http://www.w3.org/TR/html4/1"
xmlns:myns2="http://www.w3.org/TR/html4/2"
xmlns:myns3="http://www.w3.org/TR/html4/3"
xmlns:myns4="http://www.w3.org/TR/html4/4"
xmlns:myns5="http://www.w3schools.com/furniture"
xmlns:ns1="http://www.w3.org/TR/html4/1"
xmlns:ns2="http://www.w3.org/TR/html4/2"
xmlns:ns3="http://www.w3.org/TR/html4/3"
xmlns:ns4="http://www.w3.org/TR/html4/4"
xmlns:ns5="http://www.w3schools.com/furniture">
<xsl:template match="/">
<myns1:taskListResponse>
<xsl:apply-templates/>
</myns1:taskListResponse>
</xsl:template>
<xsl:template match="ns2:*">
<xsl:element name="myns2:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="ns3:*">
<xsl:element name="myns3:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="ns4:*">
<xsl:element name="myns4:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="ns5:*">
<xsl:element name="myns5:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
returned
<?xml version="1.0" encoding="UTF-8"?><myns1:taskListResponse xmlns:ns2="http://www.w3.org/TR/html4/2" xmlns:ns3="http://www.w3.org/TR/html4/3" xmlns:ns4="http://www.w3.org/TR/html4/4" xmlns:ns5="http://www.w3schools.com/furniture" xmlns:myns1="http://www.w3.org/TR/html4/1" xmlns:myns2="http://www.w3.org/TR/html4/2" xmlns:myns3="http://www.w3.org/TR/html4/3" xmlns:myns4="http://www.w3.org/TR/html4/4" xmlns:myns5="http://www.w3schools.com/furniture" xmlns:ns1="http://www.w3.org/TR/html4/1">
<myns2:task>..</myns2:task>
<myns2:address>..</myns2:address>
<myns2:pin>..</myns2:pin>
<myns3:address>
..
..
..
..
..
</myns3:address>
<myns2:query>..</myns2:query>
</myns1:taskListResponse>
Here, you can see that ns4 & ns5 elements are completely missed. Where for the same template, instead of <xsl:value-of select="." />, If i use <xsl:apply-templates/>, it worked, meaning replaced all the level nodes. I seriously dont understand this as the match I used is * in the template.
Appreciate your response.
Regards
Ravi

How can this XSL be refactored to take advantage of apply-templates?

Taking this question about beautiful XSL but getting more specific, how should I refactor this XSL to take advantage of apply-templates and/or keys.
I tend to "over use" for-each elements to control the context of the source and I can imagine that apply-templates can help. Despite much Google-ing, I still don't understand how to control context within the multiple templates.
In the below example, how can the repetitive XPath segments be reduced by refactoring?
<xsl:template match="/">
<xsl:element name="Body">
<xsl:element name="Person">
<xsl:if test="/source/dbSrc/srv/v[#name='name']/text()='false'">
<xsl:element name="PhoneNumber" />
<xsl:element name="Zip">
<xsl:value-of
select="/source/req[1]/personal-info/address-info/zip-code" />
</xsl:element>
</xsl:if>
<xsl:if test="/source/dbSrc/srv/v[#name='name']/text()='true'">
<xsl:element name="PhoneNumber" />
<xsl:element name="Zip">
<xsl:value-of select="/source/req[3]/personal-info/address-info/zip-code" />
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
One initial way of refactoring the given code would be the following:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Body>
<Person>
<PhoneNumber/>
<Zip>
<xsl:apply-templates select=
"/*/dbSrc/srv/v[#name='name']"/>
</Zip>
</Person>
</Body>
</xsl:template>
<xsl:template match="v[#name='name' and .='true']">
<xsl:value-of select=
"/*/req[3]/personal-info/address-info/zip-code"/>
</xsl:template>
<xsl:template match="v[#name='name' and .='false']">
<xsl:value-of select=
"/*/req[1]/personal-info/address-info/zip-code"/>
</xsl:template>
</xsl:stylesheet>
Do note: The refactored code doesn't contain any conditional xslt instructions.
Further refactoring can let us get rid of the last too templates, because in this case additional templates aren't actually needed -- the code only creates a single element and depends on a single condition:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vCond" select=
"/*/dbSrc/srv/v[#name='name']/text()='true'"/>
<xsl:variable name="vInd" select=
"3*$vCond + 1*not($vCond)"/>
<xsl:template match="/">
<Body>
<Person>
<PhoneNumber/>
<Zip>
<xsl:value-of select=
"/*/req[position()=$vInd]
/personal-info/address-info/zip-code"/>
</Zip>
</Person>
</Body>
</xsl:template>
</xsl:stylesheet>
Note: Here we assume that /*/dbSrc/srv/v[#name='name']/text() can have only two possible values: 'true' or 'false'
In XSLT 2.0 I would write this as:
<xsl:template match="/">
<Body>
<Person>
<PhoneNumber/>
<Zip>
<xsl:variable name="index" as="xs:integer"
select="if (/source/dbSrc/srv/v[#name='name']='true') then 3 else 1"/>
<xsl:value-of select="/source/req[$index]/personal-info/address-info/zip-code"/>
</Zip>
</Person>
</Body>
</xsl:template>
With 1.0, the xsl:variable becomes a bit more complicated but otherwise it's the same.
Note the use of literal result elements and variables to reduce the size of the code; also the avoidance of "/text()", which is nearly always bad practice.
There's very little mileage in using template rules here because you're using so little of the input data, and because you seem to know exactly where to find it. Template rules would come into their own if you wanted to be less rigid about knowing exactly where you are looking in the source: they help to make code more resilient to variability and change in the input. But without seeing the source and knowing more of the background, we can't tell you where that flexibility is needed. The hard-coding of the indexes "1" and "3" looks like a danger signal to me, but only you can judge that.

Sort attributes in specific order for output

How do you write element attributes in a specific order without writing it explicitly?
Consider:
<xsl:template match="Element/#1|#2|#3|#4">
<xsl:if test="string(.)">
<span>
<xsl:value-of select="."/><br/>
</span>
</xsl:if>
</xsl:template>
The attributes should appear in the order 1, 2, 3, 4. Unfortunately, you can't guarantee the order of attributes in XML, it could be <Element 2="2" 4="4" 3="3" 1="1">
So the template above will produce the following:
<span>2</span>
<span>4</span>
<span>3</span>
<span>1</span>
Ideally I don't want to test each attribute if it has got a value. I was wondering if I can somehow set an order of my display? Or will I need to do it explicitly and repeating the if test as in:
<xsl:template match="Element">
<xsl:if test="string(./#1)>
<span>
<xsl:value-of select="./#1"/><br/>
</span>
</xsl:if>
...
<xsl:if test="string(./#4)>
<span>
<xsl:value-of select="./#4"/><br/>
</span>
</xsl:if>
</xsl:template>
What can be done in this case?
In an earlier question you seemed to use XSLT 2.0 so I hope this time too an XSLT 2.0 solution is possible.
The order is not determined in the match pattern of a template, rather it is determined when you do xsl:apply-templates. So (with XSLT 2.0) you can simply write a sequence of the attributes in the order you want e.g. <xsl:apply-templates select="#att2, #att1, #att3"/> will process the attributes in that order.
XSLT 1.0 doesn't have sequences, only node-sets. To produce the same result, use xsl:apply-templates in the required order, such as:
<xsl:apply-templates select="#att2"/>
<xsl:apply-templates select="#att1"/>
<xsl:apply-templates select="#att3"/>
Do not produce XML that relies on the order of the attributes. This is very brittle and I would consider it bad style, to say the least. XML was not designed in that way; <elem a="1" b="2" /> and <elem a="1" b="2" /> are explicitly equivalent.
If you want ordered output, order your output (instead of relying on ordered input).
Furthermore, match="Element/#1|#2|#3|#4" is not equivalent to match="Element/#1|Element/#2|Element/#3|Element/#4", but I'm sure you mean the latter.
That being said, you can do:
<xsl:template match="Element/#1|Element/#2|Element/#3|Element/#4">
<xsl:if test="string(.)">
<span>
<xsl:value-of select="."/><br/>
</span>
</xsl:if>
</xsl:template>
<xsl:template match="Element">
<xsl:apply-templates select="#1|#2|#3|#4">
<!-- order your output... -->
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:template>
EDIT: I'll take it as read that #1 etc are just examples, because names cannot actually start with a number in XML.
I'd use xsl:sort on the local-name of the attribute to get the result you want. I'd also use a different mode so the results don't get called by accident somewhere else.
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Element">
<xsl:apply-templates select="#*" mode="sorted">
<xsl:sort select="local-name()" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Element/#a|#b|#c|#d" mode="sorted">
<xsl:if test="string(.)">
<span>
<xsl:value-of select="."/><br/>
</span>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The clue was is the answer by Martin Honnen
To copy attributes and conditionally add a new attribute to the end of the list of attributes.
Add rel="noopener noreferrer" to all external links.
<xsl:template match="a">
<xsl:copy>
<xsl:if test="starts-with(./#href,'http')">
<xsl:apply-templates select="./#*"/>
<!-- Insert rel as last node -->
<xsl:attribute name="rel">noopener noreferrer</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a/#href|a/#target|a/#rel">
<!--
Allowed attribute on anchor
-->
<xsl:attribute name="{name()}">
<xsl:value-of select="."></xsl:value-of>
</xsl:attribute>
</xsl:template>
You can also specify the attribute sequence by calling apply templates with each select in the order you want.
<xsl:template match="a">
<xsl:copy>
<xsl:if test="starts-with(./#href,'http')">
<xsl:apply-templates select="./#id"/>
<xsl:apply-templates select="./#href"/>
<xsl:apply-templates select="./#target"/>
<!-- Insert rel as last node -->
<xsl:attribute name="rel">noopener noreferrer</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>