Using xsl param (if exists) to replcae attribute value - xslt

I would like an xsl that replaces the value attribute of the data elements only if the relevant param names are passed (they are passed from the calling java program).
Input
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="localhost"/>
<data name="PORT" value="8080"/>
<data name="SIZE" value="1000"/>
</applicationVariables>
So for example if passing in a param HOST1=myHost and PORT=9080 the output should be:
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="myHost"/>
<data name="PORT" value="9080"/>
<data name="SIZE" value="1000"/>
</applicationVariables>
Note that HOST and PORT where replaced but SIZE was not replaced because there was no parameter with name SIZE
I don't want a hardcoded check for each name, as below:
<xsl:when test="not($HOST)"> <!-- parameter has not been supplied -->
<xsl:attribute name="value"><xsl:value-of select="#value"/></xsl:attribute>
</xsl:when>
<xsl:otherwise> <!--parameter has been supplied -->
<xsl:attribute name="value"><xsl:value-of select="$HOST"/></xsl:attribute>
</xsl:otherwise>
I want a generic way of saying: replace the value attribute only if a param with the same name exists.
But how do i check if a param with name = #name exists?

In such case it is much better to pass all params as elements of a single <xsl:param>:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pParams">
<p name="HOST">myHost</p>
<p name="PORT">9080</p>
</xsl:param>
<xsl:variable name="vParams" select=
"document('')/*/xsl:param[#name='pParams']/*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#value">
<xsl:attribute name="value">
<xsl:value-of select=
"$vParams[#name=current()/../#name]
|
current()[not($vParams[#name=current()/../#name])]
"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="localhost"/>
<data name="PORT" value="8080"/>
<data name="SIZE" value="1000"/>
</applicationVariables>
the wanted, correct result is produced:
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="myHost"></data>
<data name="PORT" value="9080"></data>
<data name="SIZE" value="1000"></data>
</applicationVariables>

You can combine logical conditions with xsl:if.

Related

Get a collection of the attributes of a nodeset

I have a collection of nodes like this
<node id="1">
<languaje>c</languaje>
<os>linux</os>
</node>
<node id="2">
<languaje>c++</languaje>
<os>linux</os>
</node>
<node id="3">
<languaje>c#</languaje>
<os>window</os>
</node>
<node id="4">
<languaje>basic</languaje>
<os>mac</os>
</node>
And i want to create a new collection of all the properties id's like this
<root>
<token>1</token>
<token>2</token>
<token>3</token>
<token>4</token>
</root>
How can do that
If you can use XQuery you can do it like this:
<root>
{ ($document/node/<node>{string(#id)}</node>) }
</root>
which is imho the clearest solution.
Otherwise you could create a string (not a document) containing your desired result with XPath 2 by concatenating the tags and your ids :
concat("<root>", string-join(for $i in /base/node/#id return concat("<node>",$i,"</node>"), " ") , "</root>")
All you need is
<xsl:output indent="yes"/>
<xsl:template match="*[node]">
<root>
<xsl:apply-templates select="node"/>
</root>
</xsl:template>
<xsl:template match="node">
<token><xsl:value-of select="#id"/></token>
</xsl:template>
If you want to store the result in a variable you can create a result tree fragment with XSLT 1.0 with e.g.
<xsl:variable name="rtf1">
<xsl:apply-templates select="node()" mode="m1"/>
</xsl:variable>
<xsl:template match="*[node]" mode="m1">
<root>
<xsl:apply-templates select="node" mode="m1"/>
</root>
</xsl:template>
<xsl:template match="node" mode="m1">
<token><xsl:value-of select="#id"/></token>
</xsl:template>
Then you can do <xsl:copy-of select="$rtf1"/> to use the result tree fragment, or with 'exsl:node-set` you can process the created nodes with XPath and XSLT e.g.
<xsl:apply-templates select="exsl:node-set($rtf1)/root/token"/>
With XSLT 2.0 there are no longer result tree fragments so you can use the variable like any input without the need for an extension function.
If you wrap all the nodes under a tag, like <nodes> this works:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<root>
<xsl:apply-templates select="*" />
</root>
</xsl:template>
<!-- templates -->
<xsl:template match="node">
<token><xsl:value-of select="#id" /></token>
</xsl:template>
</xsl:stylesheet>
Tested on XsltCake
http://www.xsltcake.com/slices/E937yH

Insert element at specific point in output document during XSLT transformation

I am wondering if you can access the result-document during processing.
The reason I ask is that I am transforming an input document and would like to insert elements depending on some conditions but this would have to occur when I have traversed the tree and I am nearly at end of creating it.
The transformed xml looks something similar to this:
<xform>
<xforms>
<model>
<instance>
<data />
<data />
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
I intend, during transformation (before the above xml is serialized), to access the <instance> tag and insert additional <data> elements.
Note
The input document is different from the above xml - the above xml is what the transformation should produce.
Similarly, I would want to access the <xform> element and insert additional <bind> nodes.
So the final document would look like this (assuming I added 2 data nodes and 2 bind nodes):
<xform>
<xforms>
<model>
<instance>
<data />
<data />
<data>new data node</data>
<data>second new data node</data>
</instance>
</model>
<bind />
<bind />
<bind />
<bind>new bind node</bind>
<bind>second new bind node</bind>
</xforms>
</xform>
Any help is appreciated.
No, you can't access a result-document, you can however create temporary trees in variables and then process them again, if needed with templates with a different mode. So instead of e.g.
<xsl:template match="/">
<xsl:result-document href="example.xml">
<xform>
<xforms>
<model>
<instance>
<data>
</data>
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
</xsl:result-document>
</xsl:template>
you would create the first result in a variable and then process it further as in e.g.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="temp1">
<xform>
<xforms>
<model>
<instance>
<data>
</data>
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
</xsl:variable>
<xsl:result-document href="example.xml">
<xsl:apply-templates select="$temp1/*"/>
</xsl:result-document>
</xsl:template>
<xsl:template match="instance">
<xsl:copy>
<xsl:apply-templates/>
<data>...</data>
</xsl:copy>
</xsl:template>
That sample does not use modes but I often use them with variables and different processing steps to cleanly seperate the templates for each step from other steps.
Yes, the way to do this is with multi-pass processing:
<xsl:stylesheet version="2.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()|#*" mode="#default pass2">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates select="$vPass1/node()" mode="pass2"/>
</xsl:template>
<xsl:template match="instance" mode="pass2">
<instance>
<xsl:apply-templates mode="pass2"/>
<data>2</data>
<data>3</data>
</instance>
</xsl:template>
<xsl:template match="model" mode="pass2">
<model>
<xsl:apply-templates mode="pass2"/>
<bind>1</bind>
<bind>2</bind>
<bind>3</bind>
</model>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<xform>
<xforms>
<model>
<instance>
<data>
</data>
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
it transforms it to itself using the identity rule and the result of this first pass is captured in the variable $vPass1. Then the second pass processes the current results in $vPass1 and adds two new data children under the instance element and three bind children under the model element -- so the final result is:
<xform>
<xforms>
<model>
<instance>
<data/>
<data>2</data>
<data>3</data>
</instance>
<bind>1</bind>
<bind>2</bind>
<bind>3</bind>
</model>
<bind/>
<bind/>
<bind/>
</xforms>
</xform>

summing nodes' values grouping by pattern of other nodes' values

The set-up
I have an XML file with (this is simplified from actual):
<feeds xmlns...>
<feed>
<week>
<start-date>...</start-date>
<end-date>...</end-date>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/news/releases/2011-12-05/xyzzy"/>
<numeric name="bar" value="463284">
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/en-US"/>
<numeric name="bar" value="4332">
</entry>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/"/>
<numeric name="bar" value="23232">
</entry>
</week>
...
</feed>
...
</feeds>
Each week has many entrys; each entry has just two data elements, one with name="foo" and the other with name="path", and a single numeric element with name="bar" and value an integer. There can be partial-duplicate entrys, even within a week: entrys can have the same foo or the same path, but no two entrys within a week that have the same foo and the same path.
What I want
I'd like to separate my paths into categories. For example, I want all paths matching the regex /ISOcodes/ to be considered separately (as "ISOcodes", say) and all paths matching ^/news as a separate category ("news").
I'm trying to sum the value of bar across multiple entrys within a single week, grouping by foo and by type (as in previous paragraph) of path. That is, for each week, for each value of foo, for each category of path (as in the preceding paragraph), I want the sum() of the values of bar.
Is there a way to do this? How?
An XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="week">
<xsl:for-each-group select="entry"
group-by="concat(data[#name='foo']/#value, '-',
if (matches(data[#name='path']/#value, '/ISOcodes/'))
then 'ISOcodes'
else if (matches(data[#name='path']/#value, '^/news'))
then 'news'
else 'no_category')">
[<xsl:value-of select="current-grouping-key()"/>]
<xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
On the following input:
<feeds>
<feed>
<week>
<start-date>...</start-date>
<end-date>...</end-date>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/news/releases/2011-12-05/xyzzy"/>
<numeric name="bar" value="463284"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/test"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/en-US"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/japan"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/"/>
<numeric name="bar" value="23232"/>
</entry>
</week>
</feed>
</feeds>
Produces:
[bar-news]
463284
[baz-ISOcodes]
12996
[bar-no_category]
23232
Obviously, you'll need to format additional elements to taste, but this should demonstrate the grouping method.
XSLT 2.0 solution:
<xsl:template match="/">
<xsl:for-each select="//week">
<xsl:for-each-group select="entry" group-by="./data[#name = 'foo']/#value">
<xsl:for-each-group select="current-group()" group-by="data[#name = 'path']/#value">
<xsl:message>
<xsl:choose>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '/ISOcodes/')]">
Sum of ISO codes : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '^/news')]">
Sum of news : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:otherwise>
Sum of other categories : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:message>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each>
</xsl:template>
When applied to this .xml file :
<feeds>
<feed>
<week>
<start-date>...</start-date>
<end-date>...</end-date>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/news/releases/2011-12-05/xyzzy"/>
<numeric name="bar" value="463284"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/en-US"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/"/>
<numeric name="bar" value="23232"/>
</entry>
</week>
...
</feed>
...
</feeds>
The output is:
[xslt] Sum of news : 463284
[xslt]
[xslt] Sum of other categories : 23232
[xslt]
[xslt] Sum of ISO codes : 4332
Edit:
I thought you wanted the sum? So my code printed the sum :)
<xsl:template match="/">
<xsl:for-each select="//week">
<xsl:for-each-group select="entry" group-by="./data[#name = 'foo']/#value">
<xsl:variable name="foo" select="current-grouping-key()"/>
<xsl:for-each-group select="current-group()" group-by="data[#name = 'path']/#value">
<xsl:message>
<xsl:choose>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '/ISOcodes/')]">
Sum of <xsl:value-of select="$foo"/>-<xsl:value-of select="current-grouping-key()"/> : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '^/news')]">
Sum of <xsl:value-of select="$foo"/>-<xsl:value-of select="current-grouping-key()"/> : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:otherwise>
Sum of other categories : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:message>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each>
</xsl:template>
This prints all the bells and whistles too :)

copy an xml to another but changing node attribute and edge attributes

I have an xml with the format
<graph id=1>
<nodes>
<node id =2>
<name value=node1/>
</node>
<node id =3>
<name value=node3/>
</node>
<edges>
<edge id=11 source=2 target=3/>
</edges>
</graph>
Now i want to change the id of node using generate-id() but that should change in all edges too.Eg i change the id of node1 to '1a1' so it should change the source of edge to '1a1' everwhere in xml.
It should do this for all nodes and edges.The remaining xml should be as it is.
My xsl
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#id[parent::node]">
<xsl:attribute name="id">
<xsl:value-of select="generate-id()"/>
</xsl:attribute>
</xsl:template>
this changes the node id but i want to compare the edges source and target and change them too.
The edge source and target are some nodes id .
Any help would be greatly appreciated.
Thanks
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kElementById" match="*[#id]" use="#id"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#id">
<xsl:attribute name="id">
<xsl:value-of select="generate-id(..)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="#source|#target">
<xsl:attribute name="{name()}">
<xsl:value-of select="generate-id(key('kElementById',.))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With this well formed input:
<graph id="1">
<nodes>
<node id ="2">
<name value="node1"/>
</node>
<node id ="3">
<name value="node3"/>
</node>
<edges>
<edge id="11" source="2" target="3"/>
</edges>
</nodes>
</graph>
Output:
<graph id="IDAEQBBB">
<nodes>
<node id="IDAHQBBB">
<name value="node1"></name>
</node>
<node id="IDALQBBB">
<name value="node3"></name>
</node>
<edges>
<edge id="IDAQQBBB" source="IDAHQBBB" target="IDALQBBB"></edge>
</edges>
</nodes>
</graph>
Add this section to the XSL that you already have.
<xsl:template match="#source[parent::edge]|#target[parent::edge]">
<xsl:attribute name="{name()}">
<xsl:value-of select="generate-id(//node[#id=current()]/#id)"/>
</xsl:attribute>
</xsl:template>

xsl to group nodes between other nodes?

I can't figure out how create xsl to group some nodes between other nodes. Basically, everytime I see a 'SPLIT' I have to end the div and create a new one.
The xml looks like this:
<data name="a" />
<data name="b" />
<data name="c" />
<data name="SPLIT" />
<data name="d" />
<data name="e" />
<data name="SPLIT" />
<data name="f" />
<data name="g" />
<data name="h" />
The output needs to look like this
<div>
a
b
c
</div>
<div>
d
e
</div>
<div>
f
g
h
</div>
I know how to do this by 'cheating', but would like to know if there is a proper way to do it:
<div>
<xsl:for-each select="data">
<xsl:choose>
<xsl:when test="#name='SPLIT'">
<xsl:text disable-output-escaping="yes"> </div> <div></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#name"/>
</xsl:otherwise>
</xsl:for-each>
</div>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:apply-templates
select="node()[1]|following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="data">
<div>
<xsl:call-template name="open"/>
</div>
<xsl:apply-templates
select="following-sibling::data[#name='SPLIT'][1]
/following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="data" mode="open" name="open">
<xsl:value-of select="concat(#name,'
')"/>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="open"/>
</xsl:template>
<xsl:template match="data[#name='SPLIT']" mode="open"/>
</xsl:stylesheet>
Output:
<div>
a
b
c
</div>
<div>
d
e
</div>
<div>
f
g
h
</div>
Note: Fine grained traversal.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kFollowing" match="data[not(#name='SPLIT')]"
use="generate-id(preceding-sibling::data[#name='SPLIT'][1])"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="data[#name='SPLIT']" name="regularSplit">
<div>
<xsl:apply-templates mode="copy"
select="key('kFollowing', generate-id())"/>
</div>
</xsl:template>
<xsl:template match="data[#name='SPLIT'][
not(preceding-sibling::data[#name='SPLIT'])]">
<div>
<xsl:apply-templates mode="copy"
select="preceding-sibling::data"/>
</div>
<xsl:call-template name="regularSplit"/>
</xsl:template>
<xsl:template match="*" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="data[not(#name='SPLIT')]"/>
</xsl:stylesheet>
when applied on the provided XML document (wrapped into a top element to become well-formed):
<t>
<data name="a" />
<data name="b" />
<data name="c" />
<data name="SPLIT" />
<data name="d" />
<data name="e" />
<data name="SPLIT" />
<data name="f" />
<data name="g" />
<data name="h" />
</t>
produces the wanted, correct result:
<t>
<div>
<data name="a"></data>
<data name="b"></data>
<data name="c"></data>
</div>
<div>
<data name="d"></data>
<data name="e"></data>
</div>
<div>
<data name="f"></data>
<data name="g"></data>
<data name="h"></data>
</div>
</t>
Do note: Keys are used to specify conveniently and verry efficiently all "non-SPLIT" elements that follow immediately a "SPLIT" element.