correct approach for XSL templates - xslt

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)"/>.

Related

Select template based on attribute-defined order

I've got the following template (excerpt):
<xsl:template match="section[#visible='1']">
<dl>
<dt><xsl:call-template name="content"/></dt>
<xsl:apply-templates select="(page|file)[#visible='1']" />
<xsl:apply-templates select="section[#visible=1]" mode="child" />
</dl>
</xsl:template>
<xsl:template match="section[#visible='1']" mode="child">
<dd><xsl:apply-templates select="." /></dd>
</xsl:template>
My problem is with the two apply-template elements at the end. The source XML elements (page, file, section,...) all have a pos attribute containing a number, which defines when they should be added to the output. But the way I have it currently, prevents a section with a lower pos value to be displayed before a page element with a higher position for example.
How can I achieve that? I've tried this:
<xsl:template match="section[#visible='1']">
<dl>
<dt><xsl:call-template name="content"/></dt>
<xsl:call-template name="kids"/>
</dl>
</xsl:template>
<xsl:template name="kids">
<xsl:for-each select="node()">
<xsl:sort select="#pos"/>
<!-- what would go here? -->
</xsl:for-each>
</xsl:template>
But I don't know what to put into the for-each loop. I could just duplicate the existing 2 templates, slap a name on them, and then call them with the current node as parameter, but that wouldn't be DRY. There must be a better way.
Have you tried this?
<xsl:template match="section[#visible='1']">
<dl>
<dt><xsl:call-template name="content"/></dt>
<xsl:apply-templates select="(page|file|section)[#visible='1']" mode="m">
<xsl:sort select="#pos"/>
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="*" mode="m">
<xsl:apply-templates select="."/>
</xsl:template>
<xsl:template match="section" mode="m">
<xsl:apply-templates select="." mode="child"/>
</xsl:template>

Split large xslt into smaller ones

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.

How can I insert a Diazo theme parameter into some theme's class attribute?

I want to replace a class tag attribute in my static theme on-the-fly, based on a theme parameter.
I tried this:
<replace attributes="class" css:theme=".conteudo">conteudo-$section</replace>
And this:
<replace css:theme=".conteudo">
<xsl:attribute name="class">conteudo-$section</xsl:attribute>
<xsl:value-of select="."/>
</replace>
And even this:
<xsl:template match="//div[contains(concat(' ', normalize-space(#class), ' '), ' conteudo ')]">
<xsl:attribute name="class">
<xsl:value-of select="substring((body/#class), 'section-', 0)" />
</xsl:attribute>
</xsl:template>
Since I also have other rules referencing .conteudo element, it'd be also nice to get to know best practices on how to deal with those (after the desired transformation occurs), ie:
<replace
css:content-children="#portal-column-content"
css:theme-children=".conteudo" />
You can't reference a variable just anywhere, but need to do so from an XPath expression.
You can avoid interfering with your replacement of the children nodes by inserting the attribute "before" the children.
Here's what I would try:
<before css:theme-children=".conteudo">
<xsl:attribute name="class">conteudo-<xsl:value-of select="$section" /></xsl:attribute>
</before>
As David said, is the right way to do it.
And to set the attributes before the child elements, you have to do that:
<xsl:template match="//*[contains(#class, 'conteudo')]">
<xsl:copy>
<xsl:attribute name="class"><xsl:value-of select="$section" /></xsl:attribute>
<xsl:apply-templates select="#*[not(name()='class')]|node()" />
</xsl:copy>
</xsl:template>
<!--Identity template copies content forward -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>

XSL:Choose query

I've the code as shown below which gives Stylesheet Compilation error.
<xsl:template match="form">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:variable name="param" select="name(.)" />
<xsl:choose>
<xsl:when test="$param = 'name'">
<xsl:attribute name="name"><xsl:value-of
select="#name" /></xsl:attribute>
</xsl:when>
<xsl:when test="$param = 'action'">
<xsl:attribute name="action"><xsl:value-of
select="java:com.hp.cpp.proxy.util.URLUtils.rewriteAction($response, $baseurl, #action, $scope)" /></xsl:attribute>
</xsl:when>
<xsl:when test="$param = 'method'">
<xsl:attribute name="method">POST</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="$param"><xsl:value-of
select="." /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<input type="hidden" name="httpmethod">
<xsl:attribute name="value"> <xsl:value-of
select="#method" /></xsl:attribute>
</input>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
I'm trying to re-write FORM tag of HTML with quite complex requirement. Hope you'll be able to identify by the code-snap. I'm trying to re-write only few of the attributes of the tag and trying to retain the rest. Is it the right way? Any other way to do it? Any suggestion.
Thanks in advance.
-Rikin
Just a guess. Try to replace the last apply
<xsl:apply-templates select="node()|#*" />
by this
<xsl:apply-templates select="node()" />
About the compilation error, you don't provide enough information to help; Abel's conjecture that the compilation error has to do with your call to an extension function is plausible.
You also ask Is this the right way? to achieve your goal. Maybe. Your first problem here is the logic error that jvverde has already pointed to. The call to apply templates should not select attributes; you have already dealt with all the attributes. So it's unnecessary to process them again. It's also a bad idea: if you you try to handle the form element's attributes again, you'll get a run-time error because you've already written content to the element (namely that input element).
I think some XSLT programmers would write something that looks more like this:
<xsl:template match="form">
<xsl:copy>
<!--* don't use a for-each to handle the
* attributes; use templates. *-->
<xsl:apply-templates select="#*"/>
<!--* you don't need an xsl:attribute constructor
* if you want to use an expression within a
* literal result element; just braces in the
* attribute-value template.
*-->
<input type="hidden"
name="httpmethod"
value="{#method}" />
<!--* change your apply-templates call to
* select children, but not attributes.
*-->
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<!--* now the attributes ... *-->
<xsl:template match="form/#action">
<xsl:attribute name="action">
<xsl:value-of select="java:com.hp.cpp.proxy.util.URLUtils.rewriteAction(
$response, $baseurl, #action, $scope)" />
</xsl:attribute>
</xsl:template>
<xsl:template match="form/#method">
<xsl:attribute name="method">
<xsl:value-of select="'POST'"/>
</xsl:attribute>
</xsl:template>

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>