XSLT for-each counter - how to access data - xslt

for performance testing purposes I want to take a small XML file and create a bigger one from it - using XSLT. Here I plan to take each entity (Campaign node in the example below) in the original XML and copy it n times, just changing its ID.
The only way I can think of to realize this, is a xsl:for-each select "1 to n". But when I do this I do not seem to be able to access the entity node anymore (xsl:for-each select="campaigns/campaign" does not work in my case). I am getting a processor error: "cannot be used here: the context item is an atomic value".
It seems that by using the "1 to n" loop, I am loosing the access to my actual entity. Is there any XPath expression that gets me access back or does anyone have a completely different idea how to realize this?
Here is what I do:
Original XML
<campaigns>
<campaign id="1" name="test">
<campaign id="2" name="another name">
</cmpaigns>
XSLT I try to use
<xsl:template match="/">
<xsl:element name="campaigns">
<xsl:for-each select="1 to 10">
<xsl:for-each select="campaigns/campaign">
<xsl:element name="campaign">
<xsl:copy-of select="#*[local-name() != 'id']" />
<xsl:attribute name="id"><xsl:value-of select="#id" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>

Define a variable as the first thing in the match, like so:
<xsl:variable name="foo" select="."/>
This defines a variable $foo of type nodeset. Then access it like this
<xsl:for-each select="$foo/campaigns/campaign">
...
</xsl:for-each>

Related

trying to pick key and value in xslt in the following payload i am getting as empty responce as null

this is the xml paylode i am trying to strip by using xslt.
<products>
<sequenceNum>123456</sequenceNum>
<ecostQuoteType>0</ecostQuoteType>
<specifications>
<key>SP_ACC_PROVIDER</key>
<code>VZB</code>
<value> Business - VZB</value>
</specifications>
<specifications>
<key>SP_ACC_TECH</key>
<code>TDM or DWDM</code>
<value>TDM or DWDM</value>
</specifications>
<specifications>
<key>SP_APP_PERF_LEVEL</key>
<code>Platinum</code>
<value>Platinum</value>
</specifications>
this is how i am writing the xslt for pick that key and value from the above xml.
<optimazationspecs>
<xsl:for-each select="./products/specifications">
<xsl:for-each select="key" />
<xsl:for-each select="value" />
</xsl:for-each>
</optimazationspecs>
You've got a couple of empty xsl:for-each elements. So you're saying "for each key do nothing", and "for each value do nothing". So you shouldn't be surprised that the stylesheet essentially does nothing. But you don't say what output you actually want, other than that you are "trying to strip" the document (whatever that means), which makes it hard to correct your code.
What output do you expect? Your for-each-Loops are empty as Michael mentioned. At least add <xsl:apply-templates/> to get the information you need.
Try something like:
<xsl:template match="products">
<xsl:for-each select="specification">
<xsl:for-each select="key">
<xsl:apply-templates/>
</xsl:for-each>
<xsl:for-each select="value">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
But as I said I don't know what sense this should make. And <optimazationspecs> is a XSLT compile error.

xslt assigning variable value to string literal

I have
<xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" ></xsl:variable>
</xsl:for-each>
below I have href and i want to set the value this expression variable in href #######
Delete
I also tried with :
Delete
None of them worked.
Can Anybody help me out how to do that?
Basically my task is to find the expression for current node and send the expression for the same in href?
Adding More Info :
<br/><xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" />
</xsl:for-each>
<b>Click Me</b>
when above xsl code is transformed it is giving below error:
Variable or parameter 'expression' is undefined.
In XSLT1.0, you could try setting the variable like so:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
</xsl:for-each>
</xsl:variable>
So, assuming the following XML structure
<As>
<a>
<Bs>
<b>5</b>
</Bs>
</a>
<a>
<Bs>
<b>9</b>
</Bs>
</a>
<a/>
<a>
<Bs>
<b>12</b>
<b>14</b>
<b>15</b>
</Bs>
</a>
</As>
If you were positioned on the b element with the value of 14, then expression would be set to /As/a/Bs/b
However, this does not take into account multiple nodes with the same name, and so would not be sufficient if you wanted accurate XPath to select the node.
Instead, you could try the following:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="count(preceding-sibling::*[name() = $name]) + 1"/>
<xsl:text>]</xsl:text>
</xsl:for-each>
</xsl:variable>
This would return /As[1]/a[4]/Bs[1]/b[2], which may be what you want.
Very simply, variables are scoped in XSL and exist only within the containing tag. Thus the variable named expression exists only within the for-each block. Also, variables can only be set once. Attempting to set a variable value a second time has no effect.
Therfore you have to declare the variable at or above the level where you want to use it, and put all the code to generate the value inside the variable declaration. If you can use XSLT2, the following will do what you want:
string-join(for $n in ancestor-or-self::* return name($n), '/')
I know it can also be done in XSLT 1 with a recursive template, but I don't have an example handy.

Find the position of an element within its parent with XSLT / XPath

Apart from rewriting a lot of XSLT code (which I'm not going to do), is there a way to find the position of an element within its parent, when the context is arbitrarily set to something else? Here's an example:
<!-- Here are my records-->
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<!-- At this point, I could use position() -->
<!-- Set the context to the current record -->
<xsl:for-each select="$record">
<!-- At this point, position() is meaningless because it's always 1 -->
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
<!-- This template expects the current context being set to a record -->
<xsl:template name="SomeTemplate">
<!-- it does stuff with the record's fields -->
<xsl:value-of select="SomeRecordField"/>
<!-- How to access the record's position in /path/to or in any other path? -->
</xsl:template>
NOTE: This is a simplified example. I have several constraints keeping me from implementing obvious solutions, such as passing new parameters to SomeTemplate, etc. I can really only modify the internals of SomeTemplate.
NOTE: I'm using Xalan 2.7.1 with EXSLT. So those tricks are available
Any ideas?
You could use
<xsl:value-of select="count(preceding-sibling::record)" />
or even, generically,
<xsl:value-of select="count(preceding-sibling::*[name() = name(current())])" />
Of course this approach will not work if you process a list of nodes that is not uniform, i.e.:
<xsl:apply-templates select="here/foo|/somewhere/else/bar" />
Position information is lost in such a case, unless you store it in a variable and pass that to the called template:
<xsl:variable name="pos" select="position()" />
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate">
<xsl:with-param name="pos" select="$pos" />
</xsl:call-template>
</xsl:for-each>
but obviously that would mean some code rewriting, which I realize you want to avoid.
Final hint: position() does not tell you the position of the node within its parent. It tells you the position of the current node relative to the list of nodes you are processing right now.
If you only process (i.e. "apply templates to" or "loop over") nodes within one parent, this happens to be the same thing. If you don't, it's not.
Final hint #2: This
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
is is equivalent to this:
<xsl:for-each select="/path/to/record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
but the latter works without destroying the meaning of position(). Calling a template does not change context, so . will refer to the correct node withing the called template.

In XSLT, how can you sort using an indirect key?

I am having trouble getting xsl:sort to understand the scope of the attributes I am referencing. Here is an XML sample document to illustrate:
<Root>
<DrinkSelections>
<Drink id=1000 name="Coffee"/>
<Drink id=1001 name="Water"/>
<Drink id=1002 name="Tea"/>
<Drink id=1003 name="Almost But Not Quite Entirely Unlike Tea"/>
</DrinkSelections>
<CustomerOrder>
<Drinks>
<Drink oid="1001"/>
<Drink oid="1002"/>
<Drink oid="1003"/>
</Drinks>
</CustomerOrder
</Root>
I want to produce a list of drinks (sorted by name) contained in the CustomerOrder. Here is the XSLT code I am fiddling with:
<xsl:for-each select="/Root/CustomerOrder/Drinks/Drink">
<xsl:sort select="/Root/DrinkSelections/Drink[#id = #oid]/#name"/>
<xsl:variable name=var_oid select="#oid"/>
<xsl:value-of select="/Root/DrinkSelections/Drink[#id = $var_oid]/#name"/>
</xsl:for-each>
Apparently, the xsl:sort command is trying to apply the "oid" attribute to the Drink elements in DrinkSelections, rather than local Drink element.
I can get around this using a variable, as in the xsl:value-of statement. But since xsl:sort must be the first statement after the xsl:for-each statement, I can't insert the xsl:variable statement before xsl:sort.
Is there a way to explicitly state that the attribute value should be taken from the "local" element?
You are missing the current() function.
<xsl:for-each select="/Root/CustomerOrder/Drinks/Drink">
<xsl:sort select="/Root/DrinkSelections/Drink[#id = current()/#oid]/#name"/>
<xsl:value-of select="/Root/DrinkSelections/Drink[#id = current()/#oid]/#name"/>
</xsl:for-each>
But more prominently, you are missing XSL keys, for readability and performance:
<xsl:key name="kDrinkById" match="DrinkSelections/Drink" use="#id" />
<!-- ... later ... -->
<xsl:for-each select="/Root/CustomerOrder/Drinks/Drink">
<xsl:sort select="key('kDrinkById', #oid)/#name"/>
<xsl:value-of select="key('kDrinkById', #oid)/#name"/>
</xsl:for-each>
An you are probably not using templates right, because if you did, your xsl:for-each select expression would not start at the root.
<xsl:template match="Root">
<xsl:apply-templates select="CustomerOrder/Drinks" />
</xsl:template>
<xsl:template match="CustomerOrder/Drinks">
<xsl:apply-templates select="Drink">
<xsl:sort select="key('kDrinkById', #oid)/#name"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="CustomerOrder/Drinks/Drink">
<xsl:value-of select="key('kDrinkById', #oid)/#name"/>
</xsl:template>
Note that I removed the for-each also. Every xsl:for-each avoided is a step towards better XSLT code (very rare exceptions apply).

XSL: How best to store a node in a variable and then use it in future xpath expressions?

I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>