What is the difference between <copy-of> and <apply-templates>? - xslt

When should I use <copy-of> instead of <apply-templates>?
What is their unique role? Most of the time replacing <apply-templates> with <copy-of> gives out weird output. Why is that?

xsl:copy-of is an exact copy of the matched input xml element. No xslt processing takes place and the output from that element will be exactly the same as the input.
xsl:apply-templates tells the xslt engine to process templates that match the selected elements. xsl:apply-templates is what gives xslt its overriding capability, since the templates you create with match on elements can have different priorities, and the template with the highest priority will be executed.
Input:
<a>
<b>asdf</b>
<b title="asdf">asdf</b>
</a>
Xslt 1:
<xsl:stylesheet ... >
<xsl:template match="a">
<xsl:copy-of select="b" />
</xsl:template>
</xsl:stylesheet>
Xml output 1:
<b>asdf</b>
<b title="asdf">asdf</b>
Xslt 2:
<xsl:stylesheet ... >
<xsl:template match="a">
<xsl:apply-templates select="b" />
</xsl:template>
<xsl:template match="b" priority="0">
<b><xsl:value-of select="." /></b>
<c><xsl:value-of select="." /></c>
</xsl:template>
<xsl:template match="b[#title='asdf']" priority="1">
<b title="{#title}"><xsl:value-of select="#title" /></b>
</xsl:template>
</xsl:stylesheet>
Xml output 2:
<b>asdf</b>
<c>asdf</c>
<b title="asdf">asdf</b>

copy-of
will simply return you a dump of the XML in the supplied node-set
apply-templates
on the other hand will apply any templates applicable to the node-set passed it.

Related

XSL copy without values is it possible?

I want to compare two xmls.
1. First compare XML strucutre/schema.
2. Compare values.
I am using beyond compare tool to compare. Since these two xmls are different values, there are lot many differences in comparison report, for which I am not interested. Since, my focus now is to only compare structure/schema.
I tried to copy the xmls by following template, and other as well. But every time it is with values.
I surfed on google, xsl-copy command itself copies everything for selected node/element..
Is there any ways with which I can filter out values and only schema is copied ?
My Data :
<root>
<Child1>xxxx</Child1>
<Child2>yyy</Child2>
<Child3>
<GrandChild1>dddd<GrandChild1>
<GrandChild2>erer<GrandChild2>
</Child3>
</root>
Template used :
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:copy/>
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
OutPut Required :
Something like..
<root>
<Child1></Child1>
<Child2></Child2>
<Child3>
<GrandChild1><GrandChild1>
<GrandChild2><GrandChild2>
</Child3>
</root>
At the moment, you have a template matching text() to copy it. What you need to do is remove this match from that template, and have a separate template match, that matches only non-whitespace text, and remove it.
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
For white-space only text (as used in indentation), these will be matched by XSLT'S built-in templates.
For attributes, use xsl:attribute to create a new attribute, without a value, rather than using xsl:copy which will copy the whole attribute.
<xsl:attribute name="{name()}" />
Note the use of Attribute Value Templates (the curly braces) to indicate the expression is to be evaluated to get the string to use.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:attribute name="{name()}" />
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
</xsl:stylesheet>
Also note that attributes are considered to be unordered in XML, so although you have code to sort the attributes, and they probably will appear in the right order, you can't guarantee it.

How to fold by a tag a group of selected (neighbor) tags with XSLT1?

I have a set of sequential nodes that must be enclosed into a new element. Example:
<root>
<c>cccc</c>
<a gr="g1">aaaa</a> <b gr="g1">1111</b>
<a gr="g2">bbbb</a> <b gr="g2">2222</b>
</root>
that must be enclosed by fold tags, resulting (after XSLT) in:
<root>
<c>cccc</c>
<fold><a gr="g1">aaaa</a> <b gr="g1">1111</b></fold>
<fold><a gr="g2">bbbb</a> <b gr="g2">2222</b></fold>
</root>
So, I have a "label for grouping" (#gr) but not imagine how to produce correct fold tags.
I am trying to use the clues of this question, or this other one... But I have a "label for grouping", so I understand that my solution not needs the use of key() function.
My non-general solution is:
<xsl:template match="/">
<root>
<xsl:copy-of select="root/c"/>
<fold><xsl:for-each select="//*[#gr='g1']">
<xsl:copy-of select="."/>
</xsl:for-each></fold>
<fold><xsl:for-each select="//*[#gr='g2']">
<xsl:copy-of select="."/>
</xsl:for-each></fold>
</root>
</xsl:template>
I need a general solution (!), looping by all #gr and coping (identity) all context that not have #gr... perhaps using identity transform.
Another (future) problem is to do this recursively, with fold of foldings.
In XSLT 1.0 the standard technique to handle this sort of thing is called Muenchian grouping, and involves the use of a key that defines how the nodes should be grouped and a trick using generate-id to extract just the first node in each group as a proxy for the group as a whole.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:key name="elementsByGr" match="*[#gr]" use="#gr" />
<xsl:template match="#*|node()" name="identity">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<!-- match the first element with each #gr value -->
<xsl:template match="*[#gr][generate-id() =
generate-id(key('elementsByGr', #gr)[1])]" priority="2">
<fold>
<xsl:for-each select="key('elementsByGr', #gr)">
<xsl:call-template name="identity" />
</xsl:for-each>
</fold>
</xsl:template>
<!-- ignore subsequent ones in template matching, they're handled within
the first element template -->
<xsl:template match="*[#gr]" priority="1" />
</xsl:stylesheet>
This achieves the grouping you're after, but just like your non-general solution it doesn't preserve the indentation and the whitespace text nodes between the a and b elements, i.e. it will give you
<root>
<c>cccc</c>
<fold>
<a gr="g1">aaaa</a>
<b gr="g1">1111</b>
</fold>
<fold>
<a gr="g2">bbbb</a>
<b gr="g2">2222</b>
</fold>
</root>
Note that if you were able to use XSLT 2.0 then the whole thing becomes one for-each-group:
<xsl:template match="root">
<xsl:for-each-group select="*" group-adjacent="#gr">
<xsl:choose>
<!-- wrap each group in a fold -->
<xsl:when test="#gr">
<fold><xsl:copy-of select="current-group()" /></fold>
</xsl:when>
<!-- or just copy as-is for elements that don't have a #gr -->
<xsl:otherwise>
<xsl:copy-of select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>

Transform tag only if other kind of tag will follow

I'd like to amend an existing xsl file so that the tag <Empty> in the source document is only transformed if there will follow another sibling which is not this tag. A kind of truncate of these tags to the end.
Currently I have:
...
<xsl:template match="Expression">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="Empty">
<xsl:value-of select="."/>
</xsl:template>
Can this be achieved with xslt?
Sample input
<root>
<dummy1>Test1</dummy1>
<Empty>Empty1</Empty>
<Empty>Empty2</Empty>
<dummy2>Test2</dummy2>
<Empty>Empty3</Empty>
<Empty>Empty4</Empty>
<Empty>Empty5</Empty>
</root>
desired output:
Test1Empty1Empty2Test2
Please post a small input sample and the corresponding result you want to create with XSLT. There is a sibling axis in XPath so doing e.g.
<xsl:template match="Empty[following-sibling::*[1][not(self::Empty)]]">
<!-- now transform here as needed -->
</xsl:template>
matches any Empty elements followed by another element that is not an Empty element but I am not sure that is all you need and what you want to do inside of the template.
Assumed Input XML as:
<?xml version="1.0" encoding="utf-8"?>
<root>
<dummy1>test</dummy1>
<Empty>do-not-copy</Empty>
<Empty>copy-this1</Empty>
<dummy2>test</dummy2>
<Empty>do-not-copy-this2</Empty>
</root>
In the above XML .. 2nd element <Empty>do-not-copy</Empty> has immediate next sibling as <Empty>copy-this1</Empty> so it should not be selected. where as 3rd element is followed-by <dummy2/> so it should be copied ..
and 5th element is <Empty>copy-this2</Empty> it's not followed by any tag .. so it should be dropped as well :)
this is the XSL code for that:
<xsl:template match="Empty[following-sibling::*[1][name()!='Empty']]">
<xsl:value-of select="."/>
</xsl:template>
I am adding up a new answer ..
If this is your XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<dummy>test</dummy>
<Empty>copy-this</Empty>
<Empty>copy-this1</Empty>
<dummy>test</dummy>
<Empty>copy-this</Empty>
<Empty>copy-this1</Empty>
<dummy>test</dummy>
<Empty>donot-copy-this2</Empty>
<Empty>donot-copy-this2</Empty>
</root>
XSLT: sample code..
<xsl:template match="dummy">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="Empty[following-sibling::*[name()!='Empty']]">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="text()"/>
Output:
testcopy-thiscopy-this1testcopy-thiscopy-this1test
I modified the proposed solutions to the following:
<xsl:template match="Empty[following-sibling::*[not(self::Empty)]]">
<xsl:value-of select="."/>
</xsl:template>
which seems to work.

xsltproc generates more output than expected

I'm trying to write a simple .xslt to process .xml files. But I've been confused - why text in tags <tag>text</tag> has also been printed?
Please look at the example:
sample.xml
<source>
<employee>
<firstName>Joe</firstName>
<surname>Smith</surname>
</employee>
</source>
style.xsl
<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match="surname">
<div>
<xsl:value-of select="name()"/>
</div>
</xsl:template>
</xsl:stylesheet>
Why after calling: xsltproc style.xslt sample.xml I'm getting
Joe
<div>surname</div>
instead of
<div>surname</div>
only?
This is because Joe is being handled by default. Text nodes are normally output by default. You need to override the default behavior.
<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<!--Added to match all other nodes/attributes.-->
<xsl:template match="node()|#*">
<xsl:apply-templates select="node()|#*"/>
</xsl:template>
<xsl:template match="surname">
<div>
<xsl:value-of select="name()"/>
</div>
</xsl:template>
</xsl:stylesheet>
Processing starts with the document node and there are built-in templates that keep processing going until your templates match. You already got one suggestion to override the default templates, another approach in your case could be to explicitly select only the surname elements for processing with e.g.
<xsl:template match="/">
<xsl:apply-templates select="source/employee/surname"/>
</xsl:template>

How to remove <b/> from a document

I'm trying to have an XSLT that copies most of the tags but removes empty "<b/>" tags. That is, it should copy as-is "<b> </b>" or "<b>toto</b>" but completely remove "<b/>".
I think the template would look like :
<xsl:template match="b">
<xsl:if test=".hasChildren()">
<xsl:element name="b">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:template>
But of course, the "hasChildren()" part doesn't exist ... Any idea ?
dsteinweg put me on the right track ... I ended up doing :
<xsl:template match="b">
<xsl:if test="./* or ./text()">
<xsl:element name="b">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:template>
This transformation ignores any <b> elements that do not have any node child. A node in this context means an element, text, comment or processing instruction node.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="b[not(node()]"/>
</xsl:stylesheet>
Notice that here we use one of the most fundamental XSLT design patterns -- using the identity transform and overriding it for specific nodes.
The overriding template will be selected only for nodes that are elements named "b" and do not have (any nodes as) children. This template is empty (does not have any contents), so the effect of its application is that the matching node is ignored/discarded and is not reproduced in the output.
This technique is very powerful and is widely used for such tasks and also for renaming, changing the contents or attributes, adding children or siblings to any specific node that can be matched (avery type of node with the exception of a namespace node can be used as a match pattern in the "match" attribute of <xsl:template/>
Hope this helped.
Cheers,
Dimitre Novatchev
I wonder if this will work?
<xsl:template match="b">
<xsl:if test="b/text()">
...
See if this will work.
<xsl:template match="b">
<xsl:if test=".!=''">
<xsl:element name="b">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:template>
An alternative would be to do the following:
<xsl:template match="b[not(text())]" />
<xsl:template match="b">
<b>
<xsl:apply-templates/>
</b>
</xsl:template>
You could put all the logic in the predicate, and set up a template to match only what you want and delete it:
<xsl:template match="b[not(node())] />
This assumes that you have an identity template later on in the transform, which it sounds like you do. That will automatically copy any "b" tags with content, which is what you want:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Edit: Now uses node() like Dimitri, below.
If you have access to update the original XML, you could try using use xml:space=preserve on the root element
<html xml:space="preserve">
...
</html>
This way, the space in the empty <b> </b> tag is preserved, and so can be distinguished from <b /> in the XSLT.
<xsl:template match="b">
<xsl:if test="text() != ''">
....
</xsl:if>
</xsl:template>