I have a question about some sort af random function in XSLT.
I have an XML-file that very simplified look similar to this:
<node id="1198">
<node id="1201">
<data alias="name">Flemming</data>
<data alias="picture">1200</data>
</node>
<node id="1207">
<data alias="name">John</data>
<data alias="picture">1205</data>
</node>
<node id="1208">
<data alias="name">Michael</data>
<data alias="picture">1206</data>
</node>
</node>
I would like to have some XSLT, that ramdomly took one of the nodes id's and put it into a variable called "choosenNode".
Like this, if the node with the ID of 1207 was the selected one:
<xsl:variable name="choosenNode" value="1207" />
How can i do this? Is there a random-function in XSLT?
By the way, I would like the variable to be refreshed on every page where the XSLT is included.
And I work in Umbraco CMS, if that helps you guys.
Thanks,
-Kim
In Umbraco you can do something like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:Stylesheet [ <!ENTITY nbsp " "> ]>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library"
xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath"
exclude-result-prefixes="msxml umbraco.library Exslt.ExsltMath">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/>
<!-- This should probably be a macro parameter so you can use this elsewhere-->
<xsl:variable name="parentNode" select="1048"/>
<xsl:template match="/">
<xsl:variable name="numberOfNodes" select="count(umbraco.library:GetXmlNodeById($parentNode)/node)"/>
<xsl:variable name="randomPosition" select="floor(Exslt.ExsltMath:random() * $numberOfNodes) + 1"/>
<xsl:variable name="randomNode" select="umbraco.library:GetXmlNodeById($parentNode)/node [position() = $randomPosition]"/>
<!--
You now have the node in the $randomNode variable
If you just want the id then you can do an XPath query on the variable
or you can modify the XPath above to get the property you are after rather than
the whole node
-->
<xsl:value-of select="$randomNode/#nodeName" />
</xsl:template>
</xsl:stylesheet>
Hope this helps.
Tim
Getting random number in xslt is not an easy task.
There's something that can do it but you probably has to provide seed for random generator
http://fxsl.sourceforge.net/articles/Random/Casting%20the%20Dice%20with%20FXSL-htm.htm
Maybe the processor you are using to do xsl transformation has ability to extend xsl expressions with outside functions. In that case maybe you can use outside random function.
All you need is a random number generator. There is none in XSLT thus the random number must be provided by something outside of XSLT. You will need to call a method from an external library to do this and the implemention of this library will depend on if you're on Windows (.NET or WIN32) or Linux and the XSLT processor.
XSLT can do math but it lacks a lot of date/time related functions which happens to include a random number generator.
However, XSLT does have a XPath function called generate-id() which will generate an unique ID. If you could transform this to a nuimber somehow, it might be used to create a random number, although it would be predictable and some numbers might occur more often than others. I wouldn't use it.
If you use MSXSL to process your stylesheet then you can include JavaScript to generate random numbers within the stylesheet. (Or C# script when using .NET.)
Getting the node itself is easy, once you know the number of child nodes. Just ask for the node at the random position. Something like /node/node[5] would return the 5th node.
This solution works in a shell script that uses xsltproc and text utilities.
RandomElement=$(xsltproc style.xsl file.xml | tr ' ' '\n' | sort -uR | head -n 1)
It's assumed that the style.xsl file will select the required element set and return it's values, one per line in the output text file. The tr command should put each element onto a separate line. The sort -uR should produce a unique, random list of the elements selected by the style.xsl style sheet commands. The head -n 1 then pulls out the first line of the unique, random list.
The following assumes the XSLT processor supports EXSLT extensions (e.g., xsltproc).
This will return the content of the randomly selected "node" (it must be a child of a "node", i.e. a "node/node" element).
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
extension-element-prefixes="math" >
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="node">
<xsl:variable name='selected'>
<xsl:value-of select="ceiling(math:random() * count(node))"/>
</xsl:variable>
<xsl:copy-of select="node[position() = $selected]"/>
</xsl:template>
</xsl:stylesheet>
This might be a useful snippet to process the content of the selected node:
<xsl:variable name="randomNode" select="node[position() = $selectNode]"/>
<id><xsl:value-of select="$randomNode/#id"/></id>
<name><xsl:value-of select="$randomNode/data[#alias='name']"/></name>
<picture><xsl:value-of select="$randomNode/data[#alias='picture']"/></picture>
Note that the above does not return the xslt definition of the variable, it uses that variable to copy the selected node.
To set the 'value' attribute of an xsl:variable element, try an attribute template like:
<xsl:variable name='chosenNode' value='{node[position() = $selected]/#id}'/>
Related
I have several sql statements that produce a list of elements that 'should' be columns in a database but are not.
I wish to use these lists to drive transforming an xml file into the same minus various specific children elements that reference those columns.
so for example for TABLE WO I have the list:
'NORMDATE','ORIGRATE','ORIGUNITCOST','PREVRATE','PREVUNITCOST'
I wish to transform this:
<BASE>
<OBJECT>WO</OBJECT>
<CHILD1>
<NAME>ONE</NAME>
</CHILD1>
<CHILD1>
<NAME>PREVUNITCOST</NAME>
</CHILD1>
<CHILD2>
<REFNAME>ONE</REFNAME>
</CHILD2>
<CHILD2>
<REFNAME>PREVUNITCOST</REFNAME>
</CHILD2>
</BASE>
into this:
<BASE>
<OBJECT>WO</OBJECT>
<CHILD1>
<NAME>ONE</NAME>
</CHILD1>
<CHILD2>
<REFNAME>ONE</REFNAME>
</CHILD2>
</BASE>
I'm not sure how to iterate this list which I have to manually prepare to do this - can someone give me a pointer or reference? I assume I use the identity pattern referenced here:
[Text]How to remove elements from xml using xslt with stylesheet and xsltproc?
my IntelliJ reports this: Apache Software Foundation (Xalan XSLTC)1.0
I plan on using IntelliJ to transform the data into a usable output file which will be applied later in the target system.
I will not use this in production, but simply to prepare some data that itself will hopefully be usable in my target systems. So this is for a data preparation task.
If you are using the Xalan-J processor, you should be able to take advantage of the EXSLT str:tokenize() extension function - for example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="exclude-columns">NORMDATE,ORIGRATE,ORIGUNITCOST,PREVRATE,PREVUNITCOST</xsl:param>
<xsl:template match="/BASE">
<xsl:copy>
<xsl:copy-of select="*[not(REFNAME = str:tokenize($exclude-columns, ','))]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that the above assumes all CHILDn elements use a REFNAME element to reference the column. In your example, some use REFNAME and some use NAME.
I'm using xslt 1.0, and I need a union of two variables which means I have to use node-set function.
The test case below creates a union of a single node with a set that contains this node. Since A contains B, the union operation should return B. But I get a new set with duplicate A's.
If I use xpath directly, union behaves as expected. If I use variables and node-set function, I face the unexpected case. My scenario requires that I use node-set. I've simplified the test case as much as I can.
This is the xml content:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<event>
<id>3</id>
<eventType>TYPE1</eventType>
</event>
<event>
<id>2</id>
<eventType>TYPE2</eventType>
<parent>3</parent>
</event>
<event>
<id>1</id>
<eventType>TYPE2</eventType>
<parent>3</parent>
</event>
</root>
and this is the xslt:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="t2-events">
<xsl:copy-of
select="root/event
[eventType = 'TYPE2' and parent = '3']"/>
</xsl:variable>
<xsl:variable name="specific-t2">
<xsl:copy-of
select="root/event
[eventType = 'TYPE2' and id = '2']"/>
</xsl:variable>
<DEBUG>
<results>
<xsl:variable name="dummy"
select="root/event
[eventType = 'TYPE2']
|
root/event[id = '2']"/>
<xsl:variable name="dummy2"
select="ext:node-set($t2-events)
| ext:node-set($specific-t2) "/>
<xsl:comment>Works as expected</xsl:comment>
<dummy>
<xsl:copy-of select="$dummy"/>
</dummy>
<dummy2>
<xsl:comment>There are 3 elements here, where we should have 2</xsl:comment>
<xsl:copy-of select="$dummy2"/>
</dummy2>
</results>
</DEBUG>
</xsl:template>
</xsl:stylesheet>
How do I apply a union on two node-set() calls?
I don't think node-set() is the culprit here.
You've defined your variables t2-events and specific-t2 using xsl:copy-of. So your variables should (and do) contain fresh copies (new element nodes) of the nodes selected by the select expressions on xsl:copy-of. It follows that none of the event elements in the two variables is identical with any of the event elements in the input, and similarly that the two copies of event 2 made in the two variables are two copies, not two references to a single copy.
Note that when we add a few more variables to your debugging code (nice job cutting the example down, by the way!), the union happens as we might expect, regardless of whether we use node-set() or not. (At least in xsltproc.)
After your initial two variables, I added two more:
<xsl:variable name="T2-EVENTS"
select="root/event
[eventType = 'TYPE2' and parent = '3']"/>
<xsl:variable name="SPECIFIC-T2"
select="root/event
[eventType = 'TYPE2' and id = '2']"/>
Then within your DEBUG element, I added:
<xsl:variable name="dummy3"
select="$T2-EVENTS | $SPECIFIC-T2 "/>
<xsl:variable name="dummy4"
select="ext:node-set($T2-EVENTS)
| ext:node-set($SPECIFIC-T2) "/>
and
<dummy3>
<xsl:comment>How many here?</xsl:comment>
<xsl:copy-of select="$dummy3"/>
</dummy3>
<dummy4>
<xsl:comment>How many here?</xsl:comment>
<xsl:copy-of select="$dummy4"/>
</dummy4>
When I run the resulting stylesheet with xsltproc, I get two elements in dummy3 and two in dummy4; the call to node-set() made no difference.
(That said, I cannot see anything in the description of the node-set() function on the EXSLT site that says anything about node-set() preserving node identity or not preserving it. So I would be leery of relying on it either way.)
I'm doing some very complex XSLT 1.0 transformation (currently using 8 XSLT passes). I want to combine this 8 passes without merging them in one file (this would be too complex). My solution would be using xsl:include and exsl:node-set to merge the passes and store temporary results in variables.
But I have one problem: My transformation passes copies most of the nodes and modifying only certain aspects. Therefore I need to process the same nodes in every pass, but with different xsl:template! But how do I do that? How to tell that after the first pass I want to apply templates from other XSLT stylesheet?
Very simplified example what I'm currently doing (2 XSLT passes):
Source:
<h>something here</h>
After XSLT pass 1:
<h someattribute="1">something here</h>
After XSLT pass 2:
<h someattribute="1" somemoreattribute="2">something here, and even more</h>
My current approach is to call the XSLT processor twice and saving the results temporary on disk:
xsltproc stylesheet1.xsl input.xml >temp.xml
xsltproc stylesheet2.xsl temp.xml >finalresult.xml
One possible solution would be to change each of the stylesheets to use a distinct mode. Then you could import them all to your master stylesheet and do multiple passes by applying templates using each mode in turn:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0">
<xsl:import href="stylesheet1.xsl"/> <!-- assuming mode="stylesheet1" -->
<xsl:import href="stylesheet2.xsl"/> <!-- assuming mode="stylesheet2" -->
<xsl:import href="stylesheet3.xsl"/> <!-- assuming mode="stylesheet3" -->
<xsl:template match="/">
<xsl:variable name="temp1">
<xsl:apply-templates select="." mode="stylesheet1"/>
</xsl:variable>
<xsl:variable name="temp2">
<xsl:apply-templates mode="stylesheet2" select="exsl:node-set($temp1)"/>
</xsl:variable>
<xsl:apply-templates mode="stylesheet3" select="exsl:node-set($temp2)"/>
</xsl:template>
</xsl:stylesheet>
The downside is that you need to modify the original stylesheets, adding appropriate mode attributes to each xsl:templateand xsl:apply-templates. You can still make the stylesheets also work independently by adding an extra template like this in each of them:
<xsl:template match="/">
<xsl:apply-templates select="." mode="stylesheet1"/>
</xsl:template>
Why not use
<xsl:param name="iteration"/>
And pass the iteration number to the stylesheet? You can then use it like this
<xsl:if test="$iteration = 1">
...
</xsl:if>
...or in other contexts
You can set the parameter with
javax.xml.transform.Transformer.setParameter("iteration", 1);
Or with ant:
<xslt ...>
<param name="iteration" expression="1"/>
</xslt>
I'm adapting an XSLT from a third party which transforms an arbitrary number of XMLs into a single HTML document. It's a pretty complex script and it will be revised in the future, so I'm trying to do a minimal adaptation in order to get it to work for our needs.
The following is a stripped down version of the XSLT (containing the essentials):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:param name="files" select="document('files.xml')//File"/>
<xsl:param name="root" select="document($files)"/>
<xsl:template match="/">
<xsl:for-each select="$root/RootNode">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="RootNode">
<xsl:for-each select="//Node">
<xsl:text>Node: </xsl:text><xsl:value-of select="."/><xsl:text>, </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Now files.xml contains a list of all the URLs of the files to be included (in this case the local files file1.xml and file2.xml). Because we want to read XMLs from memory rather than from disk, and because the invocation of the XSLT only allows for a single XML source, I have combined the two files in a single XML document. The following is a combination of two files (there may be more in a real situation)
<?xml version="1.0" encoding="UTF-8"?>
<TempNode>
<RootNode>
<Node>1</Node>
<Node>2</Node>
</RootNode>
<RootNode>
<Node>3</Node>
<Node>4</Node>
</RootNode>
</TempNode>
where the first RootNode originally resided in file1.xml and the second in file2.xml.
Due to the complexity of the actual XSLT, I've figured that my best shot is to try to alter the $root-param. I've tried the following:
<xsl:param name="root" select="/TempNode"/>
The problem is this. In the case of <xsl:param name="root" select="document($files)"/>, the XPath expression "//Node" in <xsl:for-each select="//Node"> selects the Node's from file1.xml and file2.xml independently, i.e. producing the following (desired) list:
Node: 1, Node: 2, Node: 3, Node: 4,
However, when I combine the content of the two files into a single XML and parse this (and use the suggested $root-definition), the expression "//Node" will select all Node's that are children of the TempNode. (In other words, the desired list, as represented above, is produced twice due to the combination with the outer <xsl:for-each select="$root/RootNode"> loop.)
(A side note: as observed in comment a) in this page, document() apparently changes the root node, perhaps explaining this behavior.)
My question becomes:
How can I re-define $root, using the combined XML as source instead of a multi-source through document(), so that the list is only produced once, without touching the remainder of the XSLT? It's like if $root defined using the document()-function, there is no common root node in the param. Is it possible to define a param with two "separate" node trees?
Btw: I've tried defining a document like this
<xsl:param name="root">
<xsl:for-each select="/TempNode/*">
<xsl:document>
<xsl:copy-of select="."/>
</xsl:document>
</xsl:for-each>
</xsl:param>
thinking it might solve the problem, but the "//Node" expression still fetches all the Nodes. Is the context node in the <xsl:template match="RootNode">-template actually somewhere in the input document and not the param? (Honestly, I'm pretty confused when it comes to context nodes.)
Thanks in advance!
(Updated more)
OK, some of the problem is becoming clear. First, just to make sure I understand, you aren't actually passing parameters for $files and $root to the XSLT processor invocation, right? (They might as well be variables rather than params?)
Now to the main issues... In XPath, when you evaluate an expression that begins with "/" (including "//"), the context node is ignored [mostly]. Therefore, when you have
<xsl:template match="RootNode">
<xsl:for-each select="//Node">
the matched RootNode is ignored. Maybe you wanted
<xsl:template match="RootNode">
<xsl:for-each select=".//Node">
in which the for-each would select Node elements that are descendants of the matched RootNode? This would fix your problem of generating the desired node list twice.
I inserted [mostly] above because I recalled that an "absolute location path" starts from "the root node of the document containing the context node". So the context node does affect what document is used for "//Node". Maybe that's what you intended all along? I guess I was slow to catch on to that.
(A side note: as observed in comment
a) in this page, document() apparently
changes the root node, perhaps
explaining this behavior.)
Or more precisely,
An absolute location path ["/..."]
followed by a relative location
path... selects the set of nodes that
would be selected by the relative
location path relative to the root
node of the document containing the
context node.
document() doesn't actually change anything, in the sense of side effects; rather, it returns a set of nodes contained (usually) by different documents than the primary source document. XSLT instructions like xsl:apply-templates and xsl:for-each establish new values for the context node inside the scope of their template bodies. So if you use xsl:apply-templates and xsl:for-each with select="document(...)/...", the context node inside the scope of those instructions will belong to an external document, so any use of "/..." as an XPath will start from that external document.
Updated again
How can I re-define $root, using the
combined XML as source instead of a
multi-source through document(), so
that the list is only produced once,
without touching the remainder of the
XSLT?
As #Alej hinted, it's really not possible given the above constraint. If you're selecting "//Node" in each iteration of the loop over "$root/RootNode", then in order for each iteration not to select the same nodes as the other iterations, each value of "$root/RootNode" must be in a different document. Since you're using the combined XML source, instead of a multi-source, this is not possible.
But if you don't insist that your <xsl:for-each select="//..."> XPath expression cannot change, it becomes very easy. :-) Just put a "." before the "//".
It's like if $root defined using the document()-function, there is no common root node
in the param.
The value of the param is a node-set. All nodes in the set may be contained in the same document, or they may not, depending on whether the first argument to document() is a nodeset or just a single node.
Is it possible to define a param with two "separate" node trees?
I believe by "separate", you mean "belonging to different documents"? Yes it is, but I don't think you can do it in XSLT 1.0 unless you're selecting nodes that belong to different documents in the first place.
You mentioned trying
<xsl:param name="root">
<xsl:for-each select="/TempNode/*">
<xsl:document>
<xsl:copy-of select="."/>
</xsl:document>
</xsl:for-each>
</xsl:param>
but <xsl:document> is not defined in XSLT 1.0, and your stylesheet says version="1.0". Do you have XSLT 2.0 available? If so, let us know and we can pursue this option. To be honest, <xsl:document> is not familiar territory for me. But I'm happy to learn along with you.
You can apply only nodes you need:
Input:
<?xml version="1.0" encoding="UTF-8"?>
<TempNode>
<RootNode>
<Node>1</Node>
<Node>2</Node>
</RootNode>
<RootNode>
<Node>3</Node>
<Node>4</Node>
</RootNode>
</TempNode>
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="TempNode/RootNode"/>
</xsl:copy>
</xsl:template>
<xsl:template match="RootNode">
<xsl:value-of select="concat('RootNode-', generate-id(.), '
')"/>
<xsl:apply-templates select="Node"/>
</xsl:template>
<xsl:template match="Node">
<xsl:value-of select="concat('Node', ., '
')"/>
</xsl:template>
</xsl:stylesheet>
Output:
RootNode-N65540
Node1
Node2
RootNode-N65549
Node3
Node4
I would like to dynamically generate xmlns attributes.
I want to generate this in XSL :
<Common:MainPageBase xmlns:Common="clr-namespace:ThisPartIsDynamic;assembly=ThisPartIsDynamic">
</Common:MainPageBase>
How can I do that in XSL?
Thanks,
Alex
Update:
To be more precise, here is what I need to generate. The parts that I want to be able to change with variables are "THISPARTISDYNAMIC":
<Common:MainPageBase
xmlns:Common="clr-namespace:THISPARTISDYNAMIC;assembly=THISPARTISDYNAMIC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:df="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:uc="clr-namespace:THISPARTISDYNAMIC"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
></Common:MainPageBase>
Any ideas?
You can set the namespace of an element dynamically:
<param name="ns1" >http://localhost/ns1</param>
...
<xsl:element name="test" namespace="{$ns1}" >... </xsl:element>
But that doesn't output a namespace prefix -- it changes the default namespace on that element.
I don't think there is a way to output the prefixes with a dynamic namespace URI.
Something like: <xyz:test xmlns:xyz="{$ns1}">
outputs exactly that literally: <xyz:test xmlns:xyz="{$ns1}">
If that is really the exact output you require, then I think you either have
to modify the serializer, or just produce the output with a placeholder URI and
do a text replacement on the output xml text.
[ XSLT does not process XML syntax. It processes XML trees.
Parsing the input and serializing the output are outside of it's realm. ]
Take a look at the article Namespaces in XSLT, and at the section XSLT 1.0: Creating dynamic namespace nodes in particular.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:variable name="vDynamicPart1" select="'DynPArt1'"/>
<xsl:variable name="vDynamicPart2" select="'DynPArt2'"/>
<xsl:template match="/">
<xsl:element name="Common:MainPageBase"
namespace="clr-namespace:{$vDynamicPart1};assembly={$vDynamicPart2}"/>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), produces the desired result.