XSLT: copy attributes from child element - xslt

Input:
<a q='r'>
<b x='1' y='2' z='3'/>
<!-- other a content -->
</a>
Desired output:
<A q='r' x='1' y='2' z='3'>
<!-- things derived from other a content, no b -->
</A>
Could someone kindly give me a recipe?

Easy.
<xsl:template match="a">
<A>
<xsl:copy-of select="#*|b/#*" />
<xsl:apply-templates /><!-- optional -->
</A>
</xsl:template>
The <xsl:apply-templates /> is not necessary if you have no further children of <a> you want to process.
Note
the use of <xsl:copy-of> to insert source nodes into the output unchanged
the use of the union operator | to select several unrelated nodes at once
that you can copy attribute nodes to a new element as long as it is the first thing you do - before you add any child elements.
EDIT: If you need to narrow down which attributes you copy, and which you leave alone, use this (or a variation of it):
<xsl:copy-of select="(#*|b/#*)[
name() = 'q' or name() = 'x' or name() = 'y' or name() = 'z'
]" />
or even
<xsl:copy-of select="(#*|b/#*)[
contains('|q|x|y|z|', concat('|', name(), '|'))
]" />
Note how the parentheses make the predicate apply to all matched nodes.

XSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<A>
<xsl:apply-templates select="#*|b/#*|node()"/>
</A>
</xsl:template>
<xsl:template match="b"/>
</xsl:stylesheet>
output
<A q="r" x="1" y="2" z="3"><!-- other a content --></A>

Related

XSLT - Is it possible to output template matches into one span tag

The following is an XSLT Transform that outputs HTML. Is it possible to output both Target A and Target B into one span?
xsl
<xsl:template match="targetA | targetB">
<span> Is it possible to output both targets in one span</span>
</xsl:template>
xml
<doc>
<targetA>
Target A Content
</targetA>
<targetB>
Target B Content
</targetB>
</doc>
expecting
<span>
Target A and Target B
</span>
This is similar to a previous question (Output once if both xml tags occur at the same time), so you can do this by having a template like this....
<xsl:template match="targetA | targetB[not(../targetA)]">
<span>
<xsl:value-of select="../targetA" />
<xsl:value-of select="../targetB" />
</span>
</xsl:template>
(You would probably use an xsl:text if you wanted a space between then).
However, you would also need a template to match, and ignore targetB to avoid that being output again. (Assuming you are using the identity template, for example).
Try this XSLT...
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="targetA | targetB[not(../targetA)]">
<span>
<xsl:value-of select="../targetA" />
<xsl:value-of select="../targetB" />
</span>
</xsl:template>
<xsl:template match="targetB" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note, this approach would fail if you have more that one targetA or targetB under the same parent.

I want to find the count of current element within its parent element using XSLT

<a>
<z/>
<b/>
<b/>
<b/>
<c/>
</a>
I want to find the count of 'b' within 'a' when my parsing current node is 'c' using XSLT.
Is it possible to do this using XSLT?
I am not aware of what the element name 'b' would be, i.e. for its preceding sibling.
If you are positioned on c tag, or whatever the element is actually called, then to get the count of the preceding siblings, you would do this...
<xsl:value-of select="count(preceding-sibling::*)" />
EDIT: In answer to your comment, if you don't want to count all siblings, but only the count of the immediately preceding one, and the ones with the same name before that, you could try this...
<xsl:value-of select="count(preceding-sibling::*[name() = name(current()/preceding-sibling::*[1])])" />
This would not work though in the case you had multiple c nodes under one parent...
<a>
<z/>
<b/>
<b/>
<b/>
<c/>
<z/>
<b/>
<c/>
</a>
In this case, you could define a key like this, to group elements by the unique id of the first following element with a different name:
<xsl:key name="keyc" match="*" use="generate-id(following-sibling::*[name() != name(current())][1])" />
Then you can get the count like so:
<xsl:value-of select="count(key('keyc', generate-id()))" />
Here are the three options in action....
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="keyc" match="*" use="generate-id(following-sibling::*[name() != name(current())][1])" />
<xsl:template match="c">
<c>
<example1>
<xsl:value-of select="count(preceding-sibling::*)" />
</example1>
<example2>
<xsl:value-of select="count(preceding-sibling::*[name() = name(current()/preceding-sibling::*[1])])" />
</example2>
<example3>
<xsl:value-of select="count(key('keyc', generate-id()))" />
</example3>
</c>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Use xsl:number. It prints out the numer of the current element, formatted as required.
There are various options concerning how to perform the numeration,
e.g. multi-level or alphabetic one.
Actually it is quite a powerful tool.
I want to find the count of 'b' within 'a' when my parsing current node is 'c'
Let me rephrase that:
you want to count all occurrences of <b> which are on the same level as <c>.
This XSLT does the job by calling an <xsl:template> with a parameter:
the local-name of the element to be counted (in this case 'b'):
<?xml version="1.0"?>
<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="//c"> <!-- select the desired target element -->
<xsl:call-template name="count">
<xsl:with-param name="elemName" select="'b'" /> <!-- name of the element -->
</xsl:call-template>
</xsl:template>
<xsl:template name="count"> <!-- count all matching elements before and after -->
<xsl:param name="elemName" />
<xsl:value-of select="count(preceding-sibling::*[local-name() = $elemName]) + count(following-sibling::*[local-name() = $elemName])" />
</xsl:template>
</xsl:stylesheet>
In the case of your example the output simply is:
3

What is this XSLT code doing?

I'm new to XSLT. I have a block code that I don't understand.
In the following block what does '*','*[#class='vcard']' and '*[#class='fn']' mean?
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" encoding="utf-8"/> <xsl:template match="/">
<script type="text/javascript">
<xsl:text><![CDATA[function show_hcard(info) {
win2 = window.open("about:blank", "HCARD", "width=300,height=200," + "scrollbars=no menubar=no, status=no, toolbar=no, scrollbars=no");
win2.document.write("<h1>HCARD</h1><hr/><p>" + info + "</p>"); win2.document.close();
}]]></xsl:text>
</script>
<xsl:apply-templates/> </xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy> </xsl:template>
<xsl:template match="*[#class='vcard']">
<xsl:apply-templates/> </xsl:template>
<xsl:template match="*[#class='fn']">
<u>
<a>
<xsl:attribute name="onMouseDown">
<xsl:text>show_hcard('</xsl:text>
<xsl:value-of select="text()"/>
<xsl:text>')</xsl:text>
</xsl:attribute>
<xsl:value-of select="text()"/>
</a>
</u> </xsl:template> </xsl:stylesheet>
* matches all elements, *[#class='vcard'] pattern matches all elements with class attribute of vcard value. From that you can figure out what *[#class='fn'] may mean ;-)
I'd also suggest that you start here.
Your stylesheet has four template rules. In English these rules are:
(a) starting at the top (match="/"), first output a script element, then process the next level down (xsl:apply-templates) in the input.
(b) the default rule for elements (match="*") is to create a new element in the output with the same name and attributes as the original, and to construct its content by processing the next level down in the input.
(c) the rule for elements with the attribute class="vcard" is to do nothing with this element, other than to process the next level down in the input.
(d) the rule for elements with the attribute class="fn" is to output
<u><a onMouseDown="show_hcard('X')">X</a></u>
where X is the text content of the element being processed.
A more experienced XSLT user would have written the last rule as
<xsl:template match="*[#class='fn']">
<u>
<a onMouseDown="show_hcard('{.}')">
<xsl:value-of select="."/>
</a>
</u>
</xsl:template>

XSLT: add node inner text

This is a slightly version of other question posted here:
XSLT: change node inner text
Imagine i use XSLT to transform the document:
<a>
<b/>
<c/>
</a>
into this:
<a>
<b/>
<c/>
Hello world
</a>
In this case i can't use neither the
<xsl:strip-space elements="*"/>
element or the [normalize-space() != ''] predicate since there is no text in the place where i need to put new text. Any ideas? Thanks.
Here is what I would do:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<!-- identity template to copy everything unless otherwise noted -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- match the first text node directly following a <c> element -->
<xsl:template match="text()[preceding-sibling::node()[1][self::c]]">
<!-- ...and change its contents -->
<xsl:text>Hello world</xsl:text>
</xsl:template>
</xsl:stylesheet>
Note that text nodes contain "surrounding" whitespace - in the sample XML in the question the matched text node is whitespace only, which is why the above works. It will stop to work as soon as the input document looks like this:
<a><b/><c/></a>
because here is no text node following <c>. So if this is too brittle for your use case, an alternative would be:
<!-- <c> nodes get a new adjacent text node -->
<xsl:template match="c">
<xsl:copy-of select="." />
<xsl:text>Hello world</xsl:text>
</xsl:template>
<!-- make sure to remove the first text node directly following a <c> node-->
<xsl:template match="text()[preceding-sibling::node()[1][self::c]]" />
In any case, stuff like the above makes clear why intermixing of text nodes and element nodes is best avoided. This is not always possible (see XHTML). But when you have the chance and the XML is supposed to be purely a container for structural data, staying clear of mixed content makes your life easier.
This transformation inserts the desired text (for generality) after the element named a7:
<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="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a7">
<xsl:call-template name="identity"/>
<xsl:text>Hello world</xsl:text>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<a>
<a1/>
<a2/>
.....
<a7/>
<a8/>
</a>
the desired result is produced:
<a>
<a1/>
<a2/>
.....
<a7/>Hello world
<a8/>
</a>
Do note:
The use of the identity rule for copying every node of the source XML document.
The overriding of the identity rule by a specific template that carries out the insertion of the new text.
How the identity rule is both applied (on every node) and called by name (for a specific need).
edit: fixed my fail to put proper syntax in.
<xsl:template match='a'>
<xsl:copy-of select="." />
<xsl:text>Hello World</xsl:text>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>

Merging adjacent nodes of same type (XSLT 1.0)

Is it possible to merge every sequence of nodes of the same specified type? ('aaa' in this case) (not just first occurrence of sequence)
Here is my XML input:
<block>
<aaa>text1</aaa>
<aaa>text2</aaa>
<aaa><xxx>text3</xxx></aaa>
<bbb>text4</bbb>
<aaa>text5</aaa>
<bbb><yyy>text6</yyy></bbb>
<bbb>text7</bbb>
<aaa>text8</aaa>
<aaa><zzz>text9</zzz></aaa>
<aaa>texta</aaa>
</block>
And I want following output:
<block>
<aaa>text1text2<xxx>text3</xxx></aaa>
<bbb>text4</bbb>
<aaa>text5</aaa>
<bbb><yyy>text6</yyy></bbb>
<bbb>text7</bbb>
<aaa>text8<zzz>text9</zzz>texta</aaa>
</block>
Any help appreciated
Here is another way to do this.
First, match on all the child nodes of the block element
<xsl:template match="block/child::*">
Next, check if the element's most direct sibling has a different name, indicating this is the first of one or more adjacent elements:
<xsl:if test="local-name(preceding-sibling::*[position()=1]) != $name">
If so, you can copy that node. Then, you need to copy of following siblings with the same name. I did this by recursively calling a template on each immediately following sibling with the same name
<xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
Putting this all together gives
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Match children of the block element -->
<xsl:template match="block/child::*">
<xsl:variable name="name" select="local-name()"/>
<!-- Is this the first element in a sequence? -->
<xsl:if test="local-name(preceding-sibling::*[position()=1]) != $name">
<xsl:copy>
<xsl:apply-templates />
<!-- Match the next sibling if it has the same name -->
<xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Recursive template used to match the next sibling if it has the same name -->
<xsl:template match="block/child::*" mode="next">
<xsl:variable name="name" select="local-name()"/>
<xsl:apply-templates />
<xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
</xsl:template>
<!-- Template used to copy a generic node -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Assuming you only have one block, Muenchian method is the most optimized way to do this:
<!-- group nodes by name -->
<xsl:key name="block-children-by-name" match="block/*" use="name()"/>
<!-- for nodes that aren't first in their group, no output -->
<xsl:template match="block/*" />
<!-- for nodes that are first in their group, combine group children and output -->
<xsl:template match="block/*[generate-id() =
generate-id(key('block-children-by-name', name())[1])]">
<xsl:copy>
<xsl:copy-of select="key('block-children-by-name', name())/*"/>
</xsl:copy>
</xsl:template>
Note that this only merges the child nodes, and not e.g. any attributes that may occur on aaa and bbb themselves.
Here's another approach, without using recursive templates.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="aaa">
<xsl:if test="not(preceding-sibling::*[1]/self::aaa)">
<xsl:variable name="following"
select="following-sibling::aaa[
not(preceding-sibling::*[
not(self::aaa) and
not(following-sibling::aaa = current())
])
]"/>
<xsl:copy>
<xsl:apply-templates select="$following/#*"/>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="$following/node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The rather convoluted XPath expression for selecting the following sibling aaa nodes which are merged with the current one:
following-sibling::aaa[ # following 'aaa' siblings
not(preceding-sibling::*[ # if they are not preceded by
not(self::aaa) and # a non-'aaa' node
not(following-sibling::aaa = current()) # after the current node
])
]