xslt-1.0 depth first pre-order traversal numbering - xslt

I am looking for an identity transform that adds an ordering attribute to each node. I want to have the explicit document position of each node available as an integer number.
I believe the desired ordering (as in https://en.wikipedia.org/wiki/Tree_traversal#/media/File:Sorted_binary_tree_preorder.svg) is the default ordering indeed as a selection on the descendants axis produces the correct ordering numbers:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method='xml' encoding='utf-8' indent='yes'/>
<xsl:template match="/*">
<root>
<xsl:apply-templates select="descendant::*">
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="doc_order">
<xsl:value-of select="position()"/>
</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
but I need to preserve the input document structure as well,
as code above generates a flat list of all nodes instead.
example input:
<root>
<f>
<b>
<a/>
<d>
<c/>
<e/>
</d>
</b>
</f>
<g>
<i>
<h/>
</i>
</g>
</root>
desired output with explicit document order:
<root>
<f doc_order="1">
<b doc_order="2">
<a doc_order="3"/>
<d doc_order="4">
<c doc_order="5"/>
<e doc_order="6"/>
</d>
</b>
</f>
<g doc_order="7">
<i doc_order="8">
<h doc_order="9"/>
</i>
</g>
</root>

Learn about xsl:number, it is powerful:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*//*">
<xsl:copy>
<xsl:attribute name="doc_order">
<xsl:number level="any" count="*" from="root"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See http://xsltransform.net/94rmq6n for an online sample.

Related

Remove namespaces for all except for one node

Given the following source xml:
<?xml version="1.0" encoding="UTF-8"?>
<Test xmlns="http://someorg.org">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Some text</p>
<p>Some text</p>
</div>
</text>
</Test>
I would like to have the same output as the above source xml (the source xml contains many other xml nodes but for this section, I want to output it as it is, with no changes.) I have the following xslt (see below) which strips the elements of their namespaces as desired. Unfortunately, it also strips the div elements of their name spaces but I want to retain them. The closest I got to achieving my aim is the following xslt but it outputs the div element twice because of the apply-templates but I only want the div element once with its namespace.
This is my xslt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://someorg.org"
xmlns="http://someorg.org"
exclude-result-prefixes="f xsl">
<xsl:template match="*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match = "f:text/f:status">
<status value ="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<xsl:apply-templates/>
</div>
</xsl:template>
</xsl:stylesheet>
Instead of trying to remove namespaces for all elements (as handled by your <xsl:template match="*"> template, you can only target elements in the "http://someorg.org" namespace. Simply change the template match to this
<xsl:template match="f:*">
For the elements in the "http://www.w3.org/1999/xhtml" namespace, you could use the identity template to pick up everything else
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://someorg.org">
<xsl:output method="xml" indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="f:*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
add a template
<xsl:template match="*[local-name()='div'
and namespace-uri() = 'http://www.w3.org/1999/xhtml']">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
to perform a copy instead of creating a namespace-stripped element.

xsl match template by attribute except one

I've a global match on an attribut in my stylesheet but I want to exclude the f - element. How can I do that?
Example XML:
<a>
<b formatter="std">...</b>
<c formatter="abc">...</c>
<d formatter="xxx">
<e formatter="uuu">...</e>
<f formatter="iii">
<g formatter="ooo">...</g>
<h formatter="uuu">...</h>
</f>
</d>
</a>
Current solution:
<xsl:template match="//*[#formatter]">
...
</xsl:template>
I've tried something like this, but that didn't worked.
<xsl:template match="f//*[#formatter]">
...
</xsl:template>
<xsl:template match="//f*[#formatter]">
...
</xsl:template>
Either //f[#formatter] or f[#formatter] would have worked (the // is not necessary). When this XSLT is run on your example input:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[#formatter]">
<xsl:element name="transformed-{local-name()}">
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="f[#formatter]">
<xsl:apply-templates select="node()" />
</xsl:template>
</xsl:stylesheet>
The result is:
<a>
<transformed-b formatter="std">...</transformed-b>
<transformed-c formatter="abc">...</transformed-c>
<transformed-d formatter="xxx">
<transformed-e formatter="uuu">...</transformed-e>
<transformed-g formatter="ooo">...</transformed-g>
<transformed-h formatter="uuu">...</transformed-h>
</transformed-d>
</a>
As you can see, the f is excluded. Does this answer your issue, or have I misunderstood what you want to do?

How to Add to an Element, Creating it First If Needed

I need to add elements to an element, creating it first if it doesn't already exist.
My desired final result, after adding ABC and DEF, is:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Q/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
<A>
I thought that the following would accomplish this:
<?xml version="1.0" encoding="ISO-8859-1"?>
<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>
<!-- Insert a B element if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B/>
</xsl:copy>
</xsl:template>
<xsl:template match="B">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<string>ABC</string>
<string>DEF</string>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If I start with the following, in which <B> already exists, it works fine, returning the result above:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org
<Q/>
<B/>
</A>
However, if I don't have a <B>, as in below:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org
<Q/>
</A>
then it creates the <B> as below, but doesn't insert ABC and DEF:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org
<Q/>
<B/>
</A>
So, what am I missing? Thanks in advance.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Insert a B element with string elements if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B>
<xsl:call-template name="add-strings"/>
</B>
</xsl:copy>
</xsl:template>
<!-- Add string elements to existing B if missing -->
<xsl:template match="B[not(string)]">
<xsl:copy>
<xsl:call-template name="add-strings"/>
</xsl:copy>
</xsl:template>
<!-- Add string elements to caller -->
<xsl:template name="add-strings">
<string>ABC</string>
<string>DEF</string>
</xsl:template>
</xsl:stylesheet>
You will have to add the sub tags of B too when B does not exist, as follows:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Insert a B element if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
</xsl:copy>
</xsl:template>
<xsl:template match="B">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<string>ABC</string>
<string>DEF</string>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied to
<?xml version="1.0" encoding="UTF-8"?>
<root>
<A>
<Q/>
</A>
<A>
<Q/>
<B/>
</A>
</root>
this gives
<?xml version="1.0" encoding="UTF-8"?>
<root>
<A>
<Q/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
</A>
<A>
<Q/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
</A>
</root>
Empo's answer was very close, but if <B> already contained <string> elements, the new <string>s weren't added. I made two minor changes, which solved that:
<?xml version="1.0" encoding="ISO-8859-1"?>
<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>
<!-- Insert a B element with string elements if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B>
<xsl:call-template name="add-strings"/>
</B>
</xsl:copy>
</xsl:template>
<!-- Add string elements to existing B element. -->
<xsl:template match="B"> <!-- Whether there are <string>s or not. -->
<xsl:copy>
<xsl:apply-templates select="#* | node()"/> <!-- Keep existing <string>s. -->
<xsl:call-template name="add-strings"/>
</xsl:copy>
</xsl:template>
<!-- Add string elements to caller. -->
<xsl:template name="add-strings">
<string>ABC</string>
<string>DEF</string>
</xsl:template>
</xsl:stylesheet>

How to remove XML elements dynamically

I am basically looking for an XSLT (say a callable template), which will take as input an xml AND an element to be deleted in the XML and give me back the final XML after deleting that particular element in the XML.
Example:
<Request>
<Activity1>XYZ</Activity1>
<Activity2>ABC</Activity2>
</Request>
Now i need an xslt for which i must give the above xml as input and the element to be deleted (Say <Activity1>) as input. The XSLT must return the final xml after deleting the element passed to it.
You can use a modified copy-template:
<xsl:stylesheet ...>
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:variable name="removeNode">Activity1</xsl:variable>
<xsl:template match="node()">
<xsl:if test="not(name()=$removeNode)">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="#*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
How to pass the parameter to yout template depends on your used XSLT-processor.
Edit
Another possibility is to ignore the node when needed:
<xsl:template match="/">
<xsl:apply-templates select="*/*[not(self::element-to-ignore)]"
mode="renderResult"/>
</xsl:template>
<xsl:template match="#*|node()" mode="renderResult">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="renderResult"/>
</xsl:copy>
</xsl:template>
This is a generic transformation that accepts a global (externally specified) parameter with the name of the element to be deleted:
<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:param name="pDeleteName" select="'c'"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:if test="not(name() = $pDeleteName)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (for example the following):
<a>
<b>
<c/>
<d>
<e>
<c>
<f/>
</c>
<g/>
</e>
</d>
</b>
</a>
the correct result is produced -- the source XML document in which any element having name the same as the string in the pDeleteName parameter -- is deleted:
<a>
<b>
<d>
<e>
<g/>
</e>
</d>
</b>
</a>
As can be clearly seen, any occurence of the element <c> has been deleted.

XSLT. How to add a node in the leftmost leaf, with the given name?

I woud like to add a node in the leftmost leaf, with the given name.
For example,
<root>
<aaa name="a">
<aaa name="x"/>
</aaa>
<aaa name="b">
<aaa name="y">
<aaa name="z"/>
</aaa>
</aaa>
<aaa name="c">
<aaa name="z"/>
</aaa>
</root>
Given name= "z" and given new node is <aaa name="w">.
New tree should be the following form:
<root>
<aaa name="a">
<aaa name="x"/>
</aaa>
<aaa name="b">
<aaa name="y">
<aaa name="z">
<aaa name="w"/>
</aaa>
</aaa>
</aaa>
<aaa name="c">
<aaa name="z"/>
</aaa>
</root>
If by leftmost you mean the 'z' node with the greatest depth, you could first define a variable to work out the depth of the left-most 'z', and then add the 'w' node when you match a node at such a depth
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Work out depth of left most 'z' node -->
<xsl:variable name="LeftMost">
<xsl:for-each select="//*[#name='z']">
<xsl:sort select="count(ancestor::*)" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="count(ancestor::*)"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:if test="#name='z' and count(ancestor::*) = $LeftMost">
<aaa name="w"/>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Using this, if you had two 'z' nodes at the same depth you would end up with both be given a 'w' node.
An alternative approach is to use generate-id() to get the ID of the fist 'z' at the greatest depth, and then add the 'w' when you match the node with the same id. This would then only add a 'w' node to the first 'z' node it finds at the greatest depth.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="LeftMost">
<xsl:for-each select="//*[#name='z']">
<xsl:sort select="count(ancestor::*)" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="generate-id()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:if test="#name='z' and generate-id() = $LeftMost">
<aaa name="w"/>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
A variant of #Tim C's variable based approach would involve an <xsl:key> and the preceding axis, like this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:key
name="kCountPreceding" match="aaa[#name='z']"
use="count(preceding::aaa[#name='z'])"
/>
<xsl:variable name="vLeftMostId" select="
generate-id(key('kCountPreceding', 0))"
/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
<xsl:if test="generate-id() = $vLeftMostId">
<aaa name="w" />
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Downside of this approach is, that no variables are allowed in a match expression. This means this cannot be made dynamic, the key's match="aaa[#name='z']" must be hard-coded.
You can start with something like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:if test="#name='z'">
<aaa name="w"/>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Why don't you use preceding axis?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="*[#name='z' and not(preceding::*[#name='z'])]">
<xsl:copy>
<xsl:copy-of select="#*"/>
<aaa name="w" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
BTW, the leftmost leaf is the first leaf in the document order, isn't it?
<xsl:for-each select="/descendant::*[#name='z' and not(*)][1]">
...
<aaa name="w" />
...
</xsl:for-each>
the leftmost leaf is the first leaf in
the document order, isn't it?
yes, the first leaf, with given name