XSL getting out of context using dynamic XPATH - xslt

I'm trying to reformat an XML I get from an appliance into an HTML table, and it's format is not usual.
It use unique references in node name's, like this:
/network/content/host/content/REF_1/content
/network/content/network/content/REF_2/content
and then, it use the same references to another part of the file, as a value of a content node, like this:
/rules/content/rules/content/REF_3/content/sources/content/name = REF_1
/rules/content/rules/content/REF_3/content/destinations/content/name = REF_2
I'm trying to write a template for content that instead of getting me REF_ID which is unique, I try to get the name, in the other branch leaf. this mean I'm trying to find a value that is out of my actual context.
I'm able to retrieve the name XPATH using this variable:
<xsl:variable name='objName' select="concat('/storage/objects/',#linkclass,'/content/',#linktype,'/content/',current(),'/content/name/content')" />
but, I'm not able to use this XPATH in a query like:
<xsl:value-of select="{$objName}">
I suppose this doesn't work because it's out of context but when I ask statically for one of those XPATH I get the value.
My full code is not very complicated:
<xsl:template match="content">
<xsl:variable name='objXPATH' select="concat('/storage/objects/',#linkclass,'/content/',#linktype,'/content/',current(),'/content/name/content')" />
<xsl:variable name='obj' select="{$objXPATH}" />
<xsl:element name="a">
<xsl:attribute name="href">
#<xsl:value-of select="."/>
</xsl:attribute>
<xsl:value-of select="$obj"/>
<br />
</xsl:element>
</xsl:template>
I need help to fix this, I'm on it since one day with no evolution, and it's driving me crazy. I'm more like a script kiddie than a real developer.

Dynamic evaluation (treating a string in a variable as an XPath expression and evaluating it) is available as a vendor extension in a number of XSLT processors, and it becomes part of the standard with the introduction of xsl:evaluate in XSLT 3.0. If your XSLT processor doesn't have such an extension you may be able to write it yourself. Alternatively, if you explain the problem better, we may be able to suggest a solution that does not require dynamic evaluation.

Related

Is there a better way than xsl:variable to refer to an attribute value inside an XPath expression?

I am using XSLT 2.0 to transform some XML. The source XML looks similar to this:
<AnimalTest>
<AnimalTypes>
<AnimalType name="cat"/>
<AnimalType name="dog"/>
</AnimalTypes>
<Animals>
<Animal name="Sylvester" typeName="cat"/>
<Animal name="Fido" typeName="dog"/>
<Animal name="Tom" typeName="cat"/>
</Animals>
</AnimalTest>
Inside the XSL template to handle AnimalType tags, I want to use the name attribute of the AnimalType inside an XPath expression. The only way I have been able to achieve this, is by introducing a variable that holds the attribute #name and is referred from inside the XPath expression, like this:
<xsl:template match="AnimalType">
<xsl:variable name="typename" select="#name"/>
<xsl:apply-templates select="/AnimalTest/Animals/Animal[#typeName=$typename]"/>
</xsl:template>
This works, but I wonder whether I really have to use this temporary variable. Is there any better way to refer to that #name attribute? It looks like a detour to me.
If you really disliked using the variable, you could use the current() function to refer to the current context node (AnimalType in your case)
<xsl:apply-templates select="/AnimalTest/Animals/Animal[#typeName=current()/#name]"/>
If you had a more complex expression, using a variable can improve readability though, and you could potentially re-use in other places.
One thing to note is that his declaration
<xsl:variable name="typename" select="#name"/>
Is not quite the same as this declaration
<xsl:variable name="typename">
<xsl:value-of select="#name" />
</xsl:variable>
Although both variables will contain the same value. In the latter case (using xsl:value-of) you are creating a copy of the value of the name attribute. In the former case, you are referring to the attribute directly. Therefore using the latter format would be less efficient.
As a slight aside, you may consider using a key here to look up your Animal elements by their typeName
<xsl:key name="AnimalByType" match="Animal" use="#typeName" />
That way, your apply-templates expression can be simplified to just the following
<xsl:template match="AnimalType">
<xsl:apply-templates select="key('AnimalByType', #name)"/>
</xsl:template>

Using same data element name within for-each

More for reference than actual need: what is the XPath syntax to allow me to reference an element in a xsl:for-each block when the same element name is used elsewhere?
Please note, unfortunately this must be a 1.0 solution
For example, I have the following simple XML, and I want to match up the items with the same id value...
<data>
<block1>
<item><id>1</id><text>Hello</text></item>
<item><id>2</id><text>World</text></item>
</block1>
<block2>
<item><id>1</id><text>123</text></item>
<item><id>2</id><text>ABC</text></item>
</block2>
</data>
If I have a for-each on the block1, how can I reference both the id within the block1 and the id within the block2?
This will work, but I think it is messy...
<xsl:for-each select="//block1/item">
<xsl:variable name="id" select="id"/>
<xsl:value-of select="text"/> - <xsl:value-of select="//block2/item[id=$id]/text"/>
</xsl:for-each>
With the result of...
Hello - 123
World - ABC
Is there a simplified way of replacing the $id in select="//block2/item[id=$id]/text" so that it is referring to the id element from the for-each?
Another way to do it which you may find clearer, and will probably be faster, is to use keys:
<xsl:key name="b2" match="block2/item" use="id"/>
then
<xsl:value-of select="key('b2', id)/text"/>
What you have is correct and common as it is. There's no need to simplify it further; it's a standard idiom recognized and used by those working with XSLT.

Change text of elements identified by dynamic XPath

I have an XML with 2 XML fragments, 1st one is a fragment where the new values must be applied (which can have pretty complex elements) like
... some static parents
<a:element1>
<a:subelement tag="someString">
<a:s1>a</a:s1>
</a:subelement>
</a:element1>
<a:element2>b</a:element2>
<a:element3>c</a:element3>
... lots of other elements like the above ones
and 2nd fragment that has XPaths generated from the first XML and a new value, like
<field>
<xpath>/Parent/element1/subelement[#tag="someString"]/s1</xpath>
<newValue>1</newValue>
</field>
<field>
<xpath>/Parent/element2</xpath>
<newValue>2</newValue>
</field>
We might not have new values to apply for all the elements in the first fragment.
I'm struggling to make an XSLT transformation that should apply the new values to the places indicated by the XPaths.
The output should be:
... some static parents
<a:element1>
<a:subelement tag="someString">
<a:s1>1</a:s1>
</a:subelement>
</a:element1>
<a:element2>2</a:element2>
... lots of other elements like the above ones
I have access to xalan:evaluate to evaluate the dynamic xpath. I'm trying different solutions, I will write them here when they will start to make sense.
Any ideas of approaches are well received. Thanks
Oki, I found out how, and I will write the answer here maybe someone sometime will need this:
<xsl:template match="/">
<!-- static parents -->
<a:Root>
<xsl:apply-templates select="/a:Root/a:Parent" />
</a:Root>
</xsl:template>
<xsl:template match="#*|*|text()">
<xsl:variable name="x" select="generate-id(../.)" />
<xsl:variable name="y" select="//field[generate-id(xalan:evaluate(xpath)) = $x]" />
<xsl:choose>
<xsl:when test="$y">
<xsl:value-of select="$y/newValue" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|*|text()" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
And to explain the transformation:
I'm writing down part that is static and then call apply-templates on the fragment I'm interested in, that has a liquid structure.
Then I'm using a slightly modified identity transformation that copies everything from source to target (starting from the /a:Root/a:Parent fragment), except when we position ourselves on the text I'm interested in changing.
The text() I'm interested in will have as parent (../.) the element referred by an xpath string found in the second fragment. Variable x means, in the context of the when, this element.
Variable y finds a field element that has as child an xpath element that if evaluated using xalan will refer to the same element that the x variable relates to.
Now I used generate-id() in order to compare the physical elements, otherwise it would have compared by the toString of the element (which is wrong). If variable y doesn't exist, it means that I have no xpath element for this element that could have changed, and I'm leaving it alone. If the y variable exists, I can get from it the newValue and I'm currently positioned on the element which text I want to update.

XSLT to call secondary XML based on first XML element/attribute

love the stuff - newbie Æthelred here
I have a XSLT 1.0 file pulling in a secondary XML (to a variable) to build a table
<xsl:variable name="table_values" select="document('./table_variants/external_table.xml')/xml/channel_1"/>
I then get the values i need from the variable, eg:
<xsl:value-of select="$table_values/monkey/tennis/#medals"/>
<xsl:value-of select="$table_values/monkey/tennis/#bananas"/>
What i want to do is have the first XML trigger/steer where to look for the table data.
I hoped i could, within the triggered XML, state the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/>
but apparently i cannot create a xpath on the fly like that
Please - What can i do?
What i want to do is have the first XML trigger/steer where to look
for the table data. I hoped i could, within the triggered XML, state
the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/> but
apparently i cannot create a xpath on the fly like that
Please - What can i do?
This can easily be done just extending the code that you already have.
Change this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')/xml/channel_1"/>
to this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')
/xml/*[name() = $channelName"/>
Needless to say, the variable (or global, external param) $channelName should have a value that is the (string) name of the element you want to use in the last location step of the XPath expression.

Assigning parameter value to the xsl: for each

Can anybody who has worked with XSLT help me on this?
I am using XSL version 1.0.
I have declared a parameter in XSL file like:
<xsl:param name="HDISageHelpPath"/>
Now I am assigning the value to this parameter from an asp page . The value which I assign is "document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help". Now I want to assign this parameter to the <xsl for each> like
<xsl:for-each select="msxsl:node-set($HDISageHelpPath)" > (This does not work)
But it does not work. I checked the parameter value by debugging it as below
<debug tree="$HDISageHelpPath">
<xsl:copy-of select="$HDISageHelpPath"/>
</debug>
I'm able to print the value and it seems correct. In fact when I assign the static path ("document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help") by hard-coding it, it works
<xsl:for-each select="document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help"> (This works)
Can anyone please let me know why assigning the parameter to xsl:for-each does not work?
Note: I have referred the site "http://www.dpawson.co.uk/xsl/sect2/N1553.html"
You can't easily evaluate dynamic strings as XPath expressions in XSLT 1.0. They must be hard-coded, normally.
There's EXSLT's dyn:evaluate(), but I doubt you can use that with the MXSML processor.
As an alternative approach, you could either try passing the file path only:
<xsl:param name="HDISageHelpFilePath"/>
<!-- ... -->
<xsl:for-each select="document($HDISageHelpFilePath)/HelpFiles/Help">
</xsl:for-each>
or making placeholder, replacing it with search-and-replace before you load the actual XSL code into the processor (as a string). This is a bit messy and error-prone, but it could give you the possibility to use an actual dynamic XPath expression.
<xsl:for-each select="%HELP_FILE_XPATH%">
</xsl:for-each>
Load the file as text, replace %HELP_FILE_XPATH% with your actual XPath, feed it to the processor. If it loads, you are fine, if it doesn't, your input XPath was malformed.