Sort attributes in specific order for output - xslt

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>

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>

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 pass attributes to child elements if child elements do not already have the same attribute

How can I pass attributes to child elements only if the child elements do not already have the same attribute?
XML:
<section>
<container attribute1="container1" attribute2="container2">
<p attribute1="test3"/>
<ol attribute2="test4"/>
<container>
<section/>
Output should look like this:
<section>
<p attribute1="test3" attribute2="test2"/>
<ol attribute1="container1" attribute2="test4"/>
</section>
This is what i tried:
<xsl:template match="container">
<xsl:apply-templates mode="passAttributeToChild"/>
</xsl:template>
<xsl:template match="*" mode="passAttributeToChildren">
<xsl:element name="{name()}">
<xsl:for-each select="#*">
<xsl:choose>
<xsl:when test="name() = name(../#*)"/>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
Any help would be greatly appreciated ;) Thank you in advance!
Attributes declared more than once overwrite each other, so this is easy.
<xsl:template match="container/*">
<xsl:copy>
<xsl:copy-of select="../#*" /> <!-- take default from parent -->
<xsl:copy-of select="#*" /> <!-- overwrite if applicable -->
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
This assumes you want all parent attributes, as your sample seems to indicate. Of course you can decide which attributes you want to inherit:
<xsl:copy-of select="../#attribute1 | ../#attribute2" />
<xsl:copy-of select="#attribute1 | #attribute2">
Try this.
<!-- root and static content - container -->
<xsl:template match="/">
<section>
<xsl:apply-templates select='section/container/*' />
</section>
</xsl:template>
<!-- iteration content - child nodes -->
<xsl:template match='*'>
<xsl:element name='{name()}'>
<xsl:apply-templates select='#*|parent::*/#*' />
</xsl:element>
</xsl:template>
<!-- iteration content - attributes -->
<xsl:template match='#*'>
<xsl:attribute name='{name()}'><xsl:value-of select='.' /></xsl:attribute>
</xsl:template>
On outputting each child node, we iteratively transfer across its attributes and those of the parent.
<xsl:apply-templates select='#*|parent::*/#*' />
Templates are applied to nodes in the order they appear in the XML. So the parent (container) node appears before the child nodes (of course), so it's the parent's attributes that are handled first by the attributes template.
This is handy because it means the template will always show preference to the child nodes' own attributes if they already exist, because they are handled last and thus take precedence over any attributes with the same name from the parent. Thus, the parent cannot overrule them.
Working demo at this XMLPlayground.

XSLT apply templates to tree part

I have the XML tree:
<text>
<plain>abcd<c>efgh</c>ijklm</plain>
<plain>nopq<c>rst</c>uvw<c>xyz</c></plain>
<rp><first><c>asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c>bbbb</c>ccccc<c>xyz</c></plain>
</text>
Then I have code in my XSLT stylesheet ($product_text contains above tree):
<xsl:template name="text_list">
<xsl:if test="$text_count > 0">
<xsl:apply-templates mode="text_item" select="$product_text/text">
<xsl:sort select="#rating" order="descending" data-type="number" />
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template mode="text_item" match="*">
<div class="cmp-post">
<xsl:copy-of select="./*" />
</div>
</xsl:template>
This fragment copies all tree as-is. But I need all "c" nodes to be replaced/modified like this:
<c>efgh</c>
to
<cmp attr="efgh">efgh</c>
<c>rst</c>
to
<cmp attr="rst">rst</c>
etc
(edited) Result I expect:
<div class="cmp-post">
<plain>abcd<c attr="efgh">efgh</c>ijklm</plain>
<plain>nopq<c attr="rst">rst</c>uvw<c attr="xyz">xyz</c></plain>
<rp><first><c attr="asdasd">asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c attr="bbbb">bbbb</c>ccccc<c attr="xyz">xyz</c></plain>
</div>
How should I modify text_item template?
Basically, you want to do apply-templates instead of copy-of. copy-of just copies the node; it doesn't do template matching and invocation on the copied elements.
As such, you'll need a few additional templates to get what you want.
<!-- Copy attributes as-is -->
<xsl:template match="#*" mode="text_item">
<xsl:copy-of select="."/>
</xsl:template>
<!-- By default, copy element and text as-is then apply matching on children -->
<xsl:template match="node()" mode="text_item">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="text_item"/>
</xsl:copy>
</xsl:template>
<!-- For 'text' elements, use div instead of direct copy -->
<xsl:template match="text" mode="text_item">
<div class="cmp-post">
<xsl:apply-templates mode="text_item" />
</div>
</xsl:template>
<xsl:template match="c" mode="text_item">
<xsl:copy>
<xsl:attribute name='attr'><xsl:value-of select="."/></xsl:attribute>
<xsl:apply-templates mode="text_item" />
</xsl:copy>
</xsl:template>
(Note that the #* template is just for completeness. Your current input doesn't have any attributes, but if it did, this would copy them to the output.)
Running the above templates on your input with this as a caller
<xsl:template match="/">
<xsl:apply-templates select="." mode="text_item">
<xsl:sort select="#rating" order="descending" data-type="number" />
</xsl:apply-templates>
</xsl:template>
gives the output
<div class="cmp-post">
<plain>abcd<c attr="efgh">efgh</c>ijklm</plain>
<plain>nopq<c attr="rst">rst</c>uvw<c attr="xyz">xyz</c></plain>
<rp><first><c attr="asdasd">asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c attr="bbbb">bbbb</c>ccccc<c attr="xyz">xyz</c></plain>
</div>
It should be the same when called against a node variable.

How not to copy some attributes?

I need to copy from input document to output document all attributes but one.
My input is like this:
<mylink id="nextButton" type="next" href="javascript:;" />
And I need output like this:
<a id="nextButton" href="javascript:;" />
If I use the following XSL:
<xsl:template match="mylink">
<a><xsl:copy-of select="attribute::*"/></a>
</xsl:template>
I get all attributes to output like this:
<a id="nextButton" type="next" href="javascript:;" />
But I want to ignore the "type" attribute.
I've tried the following but none of them seems to work the way I need:
<xsl:copy-of select="attribute::!type"/>
<xsl:copy-of select="attribute::!'type'"/>
<xsl:copy-of select="attribute::*[!type]"/>
<xsl:copy-of select="attribute::not(type)"/>
How should I write my stylesheet to get needed output?
Shortest form:
<xsl:template match="mylink">
<a><xsl:copy-of select="#*[name()!='type']"/></a>
</xsl:template>
Longer one (that's the first thing I came up with, I leave it for reference):
<xsl:template match="mylink">
<a>
<xsl:for-each select="#*">
<xsl:if test="name() != 'type'">
<xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:if>
</xsl:for-each>
</a>
</xsl:template>
In XSLT 2.0:
<xsl:template match="mylink">
<a><xsl:copy-of select="#* except #type"/></a>
</xsl:template>