XSLT apply templates to tree part - xslt

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.

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>

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.

How do I Handle multiple XSLT copy statements on same match?

Are there any XSLT statements that will execute in consideration of other XSLT statements within the same stylesheet?
For example, if I have two copy statements matched to the same node (but only desire one copied node that contains the modifications declared in BOTH copy statements) is there a statement that will do this?
Assume that I cannot put all the transformations in one copy node, but instead have to use two or more.
---Clearer example---
//XML
<toy></toy>
//XSLT
<xsl:template match="toy">
<xsl:copy>
<xsl:attribute name="label">SOME TOY</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match="toy">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
My desired result would be a new toy node that is copied to a new file that has both things applied to it, so something like:
<toy label='SOME TOY'>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
Not two different copies
Is this possible? Is there some way I can redo the first template so that will make this one outcome?
There is rule in XSLT specification, which forbid this - Conflict Resolution for Template Rules.
If node fits several templates - only one template will be executed - in relation to the template import precedence, priority or document order, etc.
But you can separate it with named templates:
<xsl:template match="toy">
<xsl:call-template name="toyAttribute" />
<xsl:call-template name="toyElements" />
</xsl:template>
<xsl:template name="toyAttribute">
<xsl:copy>
<xsl:attribute name="label">SOME TOY</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template name="toyElements">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
Update:
If you asking about only updating <toy> node with attribute and elements you don't need separate templates:
<!--toy template -->
<xsl:template match="/toys/toy">
<!--copy toy node with namespaces -->
<xsl:copy>
<!-- copy toy node attributes -->
<xsl:apply-templates select="#*" />
<!-- add new attribute or xsl:call-template name="toyAttribute"-->
<xsl:attribute name="label">SOME TOY</xsl:attribute>
<!-- copy toy node child elements -->
<xsl:apply-templates select="node()" />
<!-- add new elements - or xsl:call-template name="toyElements"-->
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
<!--Copy node content -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
which for XML:
<?xml version="1.0" encoding="UTF-8"?>
<toys>
<toy name="a">
<toy-part/>
</toy>
<toy name="b">
<toy-part/>
</toy>
</toys>
will give following result:
<?xml version="1.0" encoding="utf-8"?><toys>
<toy name="a" label="SOME TOY">
<toy-part/>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
<toy name="b" label="SOME TOY">
<toy-part/>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
</toys>

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>

how to merge element using xslt?

I have an reference type of paragraph with element.
Example
Input file:
<reference>
<emph type="bold">Antony</emph><emph type="bold">,</emph> <emph type="bold">R.</emph>
<emph type="bold">and</emph> <emph type="bold">Micheal</emph><emph type="bold">,</emph> <emph type="bold">V.</emph>
<emph type="italic">reference title</emph></reference>
Output received now:
<p class="reference"><strong>Antony</strong><strong>,</strong> <strong>R.</strong>
<strong>and</strong> <strong>Micheal</strong><strong>,</emph>
<emph type="bold">V.</strong> <em>reference title></em></p>
Required output file:
<p class="reference"><strong>Antony, R. and Micheal, V.</strong> <em>reference title</em></p>
My xslt scripts:
<xsl:template match="reference">
<p class="reference"><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="emph">
<xsl:if test="#type='bold'">
<strong><xsl:apply-templates/></strong>
</xsl:if>
<xsl:if test="#type='italic'">
<em><xsl:apply-templates/></em>
</xsl:if>
</xsl:template>
What needs to be corrected in xslt to get the <strong> element single time like the required output file?
Please advice anyone..
By,
Antny.
This is an XSLT 1.0 solution:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" encoding="utf-8" />
<!-- the identity template copies everything verbatim -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<!-- this matches the first <emph> nodes of their kind in a row -->
<xsl:template match="emph[not(#type = preceding-sibling::emph[1]/#type)]">
<xsl:variable name="elementname">
<xsl:choose>
<xsl:when test="#type='bold'">strong</xsl:when>
<xsl:when test="#type='italic'">em</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:if test="$elementname != ''">
<!-- the first preceding node with a different type is the group separator -->
<xsl:variable
name="boundary"
select="generate-id(preceding-sibling::emph[#type != current()/#type][1])
" />
<xsl:element name="{$elementname}">
<!-- select all <emph> nodes of the row with the same type... -->
<xsl:variable
name="merge"
select=". | following-sibling::emph[
#type = current()/#type
and
generate-id(preceding-sibling::emph[#type != current()/#type][1]) = $boundary
]"
/>
<xsl:apply-templates select="$merge" mode="text" />
</xsl:element>
</xsl:if>
</xsl:template>
<!-- default: keep <emph> nodes out of the identity template mechanism -->
<xsl:template match="emph" />
<!-- <emph> nodes get their special treatment here -->
<xsl:template match="emph" mode="text">
<!-- effectively, this copies the text node via the identity template -->
<xsl:apply-templates />
<!-- copy the first following node - if it is a text node
(this is to get interspersed spaces into the output) -->
<xsl:if test="
generate-id(following-sibling::node()[1])
=
generate-id(following-sibling::text()[1])
">
<xsl:apply-templates select="following-sibling::text()[1]" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It results in:
<reference>
<strong>Antony, R. and Micheal, V.</strong>
<em>reference title</em>
</reference>
I'm not overly happy with
<xsl:variable
name="merge"
select=". | following-sibling::emph[
#type = current()/#type
and
generate-id(preceding-sibling::emph[#type != current()/#type][1]) = $boundary
]"
/>
if someone has a better idea, please tell me.
Here is my method, which uses recursive calls of a template to match elements with the same type.
It first matchs the first 'emph' element, and them recursively calls a template matching 'emph' elements of the same type. Next, it repeats the process matching the next 'emph' element of a type different to the one currently matched.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<!-- Match root element -->
<xsl:template match="reference">
<p class="reference">
<!-- Match first emph element -->
<xsl:apply-templates select="emph[1]"/>
</p>
</xsl:template>
<!-- Used to match first occurence of an emph element for any type -->
<xsl:template match="emph">
<xsl:variable name="elementname">
<xsl:if test="#type='bold'">strong</xsl:if>
<xsl:if test="#type='italic'">em</xsl:if>
</xsl:variable>
<xsl:element name="{$elementname}">
<xsl:apply-templates select="." mode="match">
<xsl:with-param name="type" select="#type"/>
</xsl:apply-templates>
</xsl:element>
<!-- Find next emph element with a different type -->
<xsl:apply-templates select="following-sibling::emph[#type!=current()/#type][1]"/>
</xsl:template>
<!-- Used to match emph elements of a specific type -->
<xsl:template match="*" mode="match">
<xsl:param name="type"/>
<xsl:if test="#type = $type">
<xsl:value-of select="."/>
<xsl:apply-templates select="following-sibling::*[1]" mode="match">
<xsl:with-param name="type" select="$type"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Where this currently fails though, is that it doesn't match the whitespace in between the 'emph' elements.