Choose statement messing up my variable? - xslt

I've got a choose statement that should be setting my variable but for some reason the code, though it works elsewhere, does not work in this instance.
Here's my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="url-show"/>
<xsl:param name="url-min"/>
<xsl:param name="url-max"/>
<xsl:template match="data">
<xsl:variable name="show" select="$url-show"/>
<xsl:choose>
<!-- With Min/Max -->
<xsl:when test="$url-min != '' and $url-max != ''">
<xsl:variable name="total" select="timeshare-search-results/pagination/#total-entries"/>
</xsl:when>
<!-- Without Min/Max -->
<xsl:otherwise>
<xsl:variable name="total" select="timeshare-listings/pagination/#total-entries"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="default" select="$increment"/>
<xsl:choose>
<!-- With Show Variable -->
<xsl:when test="$show != ''">
<xsl:if test="$show < $total or $show = $total">
<!-- stuff -->
</xsl:if>
</xsl:when>
<!-- Without Show Variable -->
<xsl:otherwise>
<xsl:if test="$default < $total or $default = $total">
<!-- stuff -->
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I trimmed out the non-essential bits. The funny thing is that when I just have one variable or the other they work just fine. The problem is, they do not show the same data thus I need to choose one or the other based on two URL parameters.
Thing is, I do this elsewhere on this very same page—though I trimmed it out for the example—and it works perfectly fine!
Why isn't it working in this instance? Is there a way I can get around it?

Variables are immutable in XSLT, and they are scoped and visible from the point of instantiation forward to the end of their parent element.
<xsl:when ...>
<xsl:variable ...>
</xsl:when>
scopes the variable to just the <xsl:when> block. If there is a variable with that name defined before the <xsl:choose> block, it will appear to be 'restored' when the block is past, since it is no longer shadowed.
The proper way to set a variable is to wrap the <xsl:variable> definition around the <xsl:choose> block, like this:
<xsl:variable name="total">
<xsl:choose>
<!-- With Min/Max -->
<xsl:when test="$url-min != '' and $url-max != ''">
<xsl:value-of select="timeshare-search-results/pagination/#total-entries"/>
</xsl:when>
<!-- Without Min/Max -->
<xsl:otherwise>
<xsl:value-of select="timeshare-listings/pagination/#total-entries"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Now, $total will be scoped to the parent of the <xsl:variable> block, which is the <xsl:template match="data"> block.

Related

Passing node and field dynamically to xslt function

I am trying to make something to act as a null check decimal fields for use in xslt transformations. I was thinking I could also make similar for other data types as I need. I made this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/CR_Question"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:this="urn:this-stylesheet"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:function name="this:NullCheckField" as="xsd:string">
<xsl:param name="node" as="xsd:string"/>
<xsl:param name="fieldName" as="xsd:string"/>
<xsl:variable name="variableName">
<xsl:choose>
<xsl:when test="not(exists(concat($node, '/', $fieldName)))">
<xsl:value-of select="format-number(0, '#.00')"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="x">
<xsl:value-of select="concat($node, '/', $fieldName)"/>
</xsl:variable>
<xsl:variable name="y">
<xsl:value-of select="$x"/>
</xsl:variable>
<xsl:value-of select="format-number(number($y), '#.00')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$variableName"/>
</xsl:function>
<xsl:output method="text" indent="yes"/>
<xsl:template match="wd:Report_Entry">
<xsl:value-of select="this:NullCheckField('wd:Report_Data/wd:Report_Entry','wd:deduction_amount')"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
and i tried calling it with this line
<wd:Report_Data xmlns:wd="urn:com.workday.report/CR_Question">
<wd:Report_Entry>
<wd:Employee_ID>1555111</wd:Employee_ID>
<wd:Check_Date>2022-04-28</wd:Check_Date>
<wd:NegDeductIndc>0</wd:NegDeductIndc>
<wd:Deduction_YTD>0</wd:Deduction_YTD>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Employee_ID>1555222</wd:Employee_ID>
<wd:Check_Date>2022-04-28</wd:Check_Date>
<wd:NegDeductIndc>0</wd:NegDeductIndc>
<wd:Deduction_YTD>0</wd:Deduction_YTD>
</wd:Report_Entry>
</wd:Report_Data>
When I run this, the test should fail and use the hard coded 0 since the node is not there, but instead of dropping into
<xsl:when test="not(exists(concat($node, '/', $fieldName)))">
<xsl:value-of select="format-number(0, '#.00')"/>
</xsl:when>
It keeps dropping into the and failing out. I am not sure of two things.
The first is why is my exist check not working correctly and falling into the
Other question - am I able to pass the node names and concat them like this?
Edit: Updated function as of now
<!-- param type 1 = decimal -->
<!-- param type 2 = integer -->
<!-- param type 3 = string -->
<xsl:function name="this:NullCheckField" as="xsd:string">
<xsl:param name="node"/>
<xsl:param name="type" as="xsd:integer"/>
<xsl:variable name="variableName">
<xsl:choose>
<xsl:when test="not(exists($node))">
<xsl:choose>
<xsl:when test="$type = 1">
<xsl:value-of select="format-number(0, '#.00')"/>
</xsl:when>
<xsl:when test="$type = 2">
<xsl:value-of select="0"/>
</xsl:when>
<xsl:otherwise>
<xsl:text/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="x">
<xsl:value-of select="$node[0]"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$type = 1">
<xsl:value-of select="format-number(number($x), '#.00')"/>
</xsl:when>
<xsl:when test="$type = 2">
<xsl:value-of select="number($x)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$x"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$variableName"/>
</xsl:function>
The problem I am trying to resolve is my integration will not always produce the empty nodes. Sometimes there is a missing node and I have been doing checks like this
<xsl:choose>
<xsl:when test="not(exists(wd:DPDEEYDEDS))">
<xsl:value-of select="' '"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="wd:DPDEEYDEDS"/>
</xsl:otherwise>
</xsl:choose>
Some of these are decimal, some are integer, some are spaces. I'm trying to do a dynamic null check. The function will be called, the field node evaluated, if it is not a missing or empty node then it formats the value, otherwise return a default 0|0.00|' '.
Unless you want to move to XSLT with xsl:evaluate support the whole idea of passing in strings with XPath fragments is nonsense.
If, in the context of template match="wd:Report_Entry", you want to pass that Report_Entry element to a function use . e.g. this:my-function(., 'deduction_amount'). The first function argument should be of type node() or element(), certainly you don't want to atomize the node to a string.
Then you could check e.g. test="$node/*[local-name() = $fieldName]" to check for the existence of a child element with local name $fieldName.
I don't see any need to work with element names as as strings, you can simply pass in e.g. this:my-function(wd:deduction_amount), with the type of the paramter being node()? or node()* and then do any exists check on that, if needed.
The whole construction of nested, untyped variables is unnecessarily complicated and inefficient.
I don't even see the need for the function, doing e.g. format-number((wd:deduction_amount, 0)[1], '#.00') inside the template suffices.

xslt keep multiple variables in scope depending on a single test

I have many variables and only two cases.
My original approach goes out of scope:
<xsl:choose>
<xsl:when test='$something = 6'>
<xsl:variable name="foo">x</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
<xsl:when test='$something = 7'>
<xsl:variable name="foo">y</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
</xsl:choose>
ie. later, with saxon, XPST0008: Variable x has not been declared (or its declaration is not in scope)
I think it would work if I would choose inside an xsl:variable tag, but then the tests would be repeated over and over and over and over and over:
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test='$something = 6'>x</xsl:when>
<xsl:when test='$something = 7'>y</xsl:when>
</xsl:choose>
</xsl:variable>
<!-- 50 other variables, the two tests repeated for each... -->
Is there a way to keep the variables in scope but also don't repeat myself?
update 1
adding the complete 'sscce' files on request
original approach:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="something">6</xsl:variable>
<xsl:choose>
<xsl:when test="$something = '6'">
<xsl:variable name="foo">x</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
<xsl:when test="$something = '7'">
<xsl:variable name="foo">y</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
</xsl:choose>
<xsl:value-of select="$foo"/>
</xsl:template>
</xsl:stylesheet>
approach that works but forces to repeat myself:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="something">6</xsl:variable>
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test='$something = 6'>x</xsl:when>
<xsl:when test='$something = 7'>y</xsl:when>
</xsl:choose>
</xsl:variable>
<!-- 50 other variables, the two tests repeated for each... -->
<xsl:value-of select="$foo"/>
</xsl:template>
</xsl:stylesheet>
example xml file: <xml/>. example saxon command-line: java -jar saxon9he.jar -s:in.xml -xsl:in.xsl -o:out.html
Well, I'd rather process that way :
<xsl:variable name="something">6</xsl:variable>
<xsl:variable name="var.set">
<xsl:choose>
<xsl:when test="$something = '6'">
<foo>x</foo>
<bar>xx</bar>
<!-- 50 other variables, to be inserted as tag -->
</xsl:when>
<xsl:when test="$something = '7'">
<foo>y</foo>
<bar>yy</bar>
<!-- 50 other variables, to be inserted as tag -->
</xsl:when>
</xsl:choose>
</xsl:variable>
The variable var.set will be a nodeset that you will be able to read thanks to the exsl:node-set() extension.
For example to retrieve the value for foo (stored as a node and not as a variable anymore), use something like: <xsl:value-of select="exsl:node-set($var.set)/foo" />. Like this you will handle a single variable, quite as if it were an array of values.
PS: on the root tag of your stylesheet, do not forget to add the exsl namespace declaration xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"
If you know your mappings beforehand, you can store them in their own file.
So instead of having something like this
<xsl:variable name="var1">
<xsl:if test="something = 5">x</xsl:if>
</xsl:variable>
<xsl:value-of select="$var1"/>
You could have this
<xsl:value-of select="document('other.xml')/root/scheme[#number = 5]/#value"/>
With this as other.xml
<root>
<scheme number="5" value="x"/>
<scheme number="6" value="y"/>
</root>
You'll probably want a more complex other.xml, with various groups of colours for each colour scheme, but the idea would be the same, and avoids tests and variables completely.

XSLT: Set multiple variables depending on condition

I want to assign multiple variables depending on one condition environment. I know how to do that for only one variable:
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test="$someCondition">
<xsl:value-of select="3"/>
<xsl:when>
<xsl:otherwise>
<xsl:value-of select="4711"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
But what if I want to assign two variables depending on the same condition $someCondition?
I don't want to write the same xsl:choose statement again, because it is somewhat lengthy and computation intensive in the real example.
The environment in question is libxslt (xslt 1.0) with exslt extensions.
EDIT: What i want is a behaviour similar to
if (condition) {
foo = 1;
bar = "Fred";
}
else if (...) {
foo = 12;
bar = "ASDD";
}
(... more else ifs...)
else {
foo = ...;
bar = "...";
}
What you could is have your main variable return a list of elements; one for each variable you want to set
<xsl:variable name="all">
<xsl:choose>
<xsl:when test="a = 1">
<a>
<xsl:value-of select="1"/>
</a>
<b>
<xsl:value-of select="2"/>
</b>
</xsl:when>
<xsl:otherwise>
<a>
<xsl:value-of select="3"/>
</a>
<b>
<xsl:value-of select="4"/>
</b>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Then, using the exslt function, you can convert this to a 'node set' which can then be used to set your individual variables
<xsl:variable name="a" select="exsl:node-set($all)/a"/>
<xsl:variable name="b" select="exsl:node-set($all)/b"/>
Don't forget you'll need to declare the namepsace for the exslt functions in the XSLT for this to work.
But what if I want to assign two variables depending on the same
condition $someCondition?
I don't want to write the same xsl:choose statement again, because it
is somewhat lengthy and computation intensive in the real example.
Assuming the values of the variables are not nodes, this code doesn't use any extension function to define them:
<xsl:variable name=vAllVars>
<xsl:choose>
<xsl:when test="$someCondition">
<xsl:value-of select="1|Fred"/>
<xsl:when>
<xsl:when test="$someCondition2">
<xsl:value-of select="12|ASDD"/>
<xsl:when>
<xsl:otherwise>
<xsl:value-of select="4711|PQR" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="foo" select="substring-before($vAllVars, '|')"/>
<xsl:variable name="bar" select="substring-after($vAllVars, '|')"/>

xslt 1.0 rewrite values of a node according to a map

While transforming a document, I need to 'look up' certain node contents in a 'map', and write those values.
I inlined my 'map' in the transformation.
<xsl:variable name="inlinedmap">
<kat id="stuff">value</kat>
<!-- ... -->
</xsl:variable>
<xsl:variable name="map" select="document('')/xsl:stylesheet/xsl:variable[#name='inlinedmap']" />
<xsl:template match="/">
<xsl:for-each select="/*/foo">
<!-- 'bar' contents should equal to contents of 'kat' -->
<xsl:variable name="g" select="$map/key[.=bar]"/>
<xsl:choose>
<xsl:when test="$g != ''">
<xsl:value-of select="$g/#id"/>
</xsl:when>
<xsl:otherwise>
ERROR
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
I'm always getting ERROR value.
I can't put map value's into attributes, because they contain letters that get escaped.
How can I make it work?
I think there are a few problems here:
You seem to be looking for key elements in your variable, but they're called kat there (typo?)
You seem to be trying to reference the bar child of the context node inside the loop, but you need to use current() to do that
You should create this map as elements in your own namespace instead of an xsl:variable
Here's a complete example. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my">
<my:vars>
<kat id="stuff">value</kat>
<!-- ... -->
</my:vars>
<xsl:variable name="map" select="document('')/*/my:vars/*"/>
<xsl:template match="/">
<xsl:for-each select="/*/foo">
<!-- 'bar' contents should equal to contents of 'kat' -->
<xsl:variable name="g" select="$map[.=current()/bar]"/>
<xsl:choose>
<xsl:when test="$g != ''">
<xsl:value-of select="$g/#id"/>
</xsl:when>
<xsl:otherwise>
ERROR
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Applied to this input:
<root>
<foo><bar>value</bar></foo>
<foo><bar>value1</bar></foo>
<foo><bar>value2</bar></foo>
<foo><bar>value3</bar></foo>
</root>
Produces this output (one match):
stuff
ERROR
ERROR
ERROR

XSLT Assignment of element value to variable: Works with Altova XML Spy, but fails in .NET app

I want to tightly grasp the hair on the back of a Microsoft employee's head, using it as leverage to pound his head forcefully and repeatedly against a hard surface! That would make me feel nearly as good as solving this problem right now.
I've got a simple XML message that looks like this:
<?xml version="1.0" encoding="utf-8"?>
<message>
<cmd id="instrument_status">
<status_id>1</status_id>
</cmd>
</message>
A web service on the device I'm working with returns several such messages and I'm converting them to a different format. For the above message the new format would look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<grf:message xmlns:grf="http://www.company.com/schemas/device/version001">
<grf:messageHeader>
<grf:messageType>instrumentStatus</grf:messageType>
</grf:messageHeader>
<grf:messageBody>
<grf:instrumentStatusBody>
<grf:statusId>Running</grf:statusId>
</grf:instrumentStatusBody>
</grf:messageBody>
</grf:message>
There is a mapping for status_id integer values in the XML as follows:
status-id Meaning
========= =======
0 Ready
1 Running
2 NotReady
3 PoweringUp
4 PoweringDown
5 PoweredUp
6 PoweredDown
7 Tuning
8 Error
My XSLT is working correctly and giving me the correct output when I use Altova XMLSpy, but when I run my .NET application, I'm getting a failure at the point where the mapping for the status_id integer is converted to one of the allowable enumerated strings. Instead of getting the enumerated value, the MS XSLT processor returns an empty string and I get an empty <status_id/> element in the output XML.
The following is my XSLT code with some sections removed to reduce the amount of space:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:grf="http://www.company.com/schemas/device/version001"
exclude-result-prefixes="#default">
<xsl:template match="/">
<xsl:apply-templates select="message"/>
</xsl:template>
<xsl:template match="message">
<xsl:element name="grf:message">
<xsl:apply-templates select="/message/cmd/#id"/>
</xsl:element>
</xsl:template>
<xsl:template match="/message/cmd/#id">
<xsl:variable name="_commandType" select="/message/cmd/#id"/>
<!-- Following line works in Altova XMLSpy, but fails in .NET app. ??? -->
<xsl:variable name="_statusIdValue" select="/message/cmd/status_id"/>
<xsl:element name="grf:messageHeader">
<xsl:element name="grf:messageType">
<xsl:choose>
<xsl:when test="$_commandType = 'api_info'">
<xsl:text>apiInfo</xsl:text>
</xsl:when>
<xsl:when test="$_commandType = 'instrument_status'">
<xsl:text>instrumentStatus</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:element>
</xsl:element>
<xsl:element name="grf:messageBody">
<xsl:choose>
<xsl:when test="$_commandType = 'api_info'">
<xsl:element name="grf:apiInfoBody">
<xsl:element name="grf:apiVersion">
<xsl:value-of select="/message/cmd/api-version"/>
</xsl:element>
<xsl:element name="grf:apiBuild">
<xsl:value-of select="/message/cmd/api-build"/>
</xsl:element>
</xsl:element>
</xsl:when>
<xsl:when test="$_commandType = 'instrument_status'">
<xsl:element name="grf:instrumentStatusBody">
<xsl:element name="grf:statusId">
<xsl:choose>
<xsl:when test="$_statusIdValue = '0'">
<xsl:text>Ready</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '1'">
<xsl:text>Running</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '2'">
<xsl:text>NotReady</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '3'">
<xsl:text>PoweringUp</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '4'">
<xsl:text>PoweringDown</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '5'">
<xsl:text>PoweredUp</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '6'">
<xsl:text>PoweredDown</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '7'">
<xsl:text>Tuning</xsl:text>
</xsl:when>
<xsl:when test="$_statusIdValue = '8'">
<xsl:text>Error</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:element>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Is there XSLT 1.0 code that will behave the same in both Altova XMLSpy and the MS XSLT processor?
Thanks,
AlarmTripper
One thing to note is that in the template that matches the "message" element, you do this
<xsl:apply-templates select="/message/cmd/#id"/>
This will actually try to match the very first message in the XML relative to the document root, regardless of what message you are currently on. It is not selecting relative to the current node. In your case, it looks like there will only ever be one message, so it won't be an issue here, but it would be in other cases.
It is also probably more common to match on elements, rather than attributes, especially where you want to process child elements of an element. So, you would probably replace the above line with this instead
<xsl:apply-templates select="cmd"/>
Then, for the template that matches it, instead of doing this currently
<xsl:template match="/message/cmd/#id">
You would do this instead
<xsl:template match="cmd">
Next, within this template, you could try replacing your variables with simpler select statements
<xsl:variable name="_commandType" select="#id"/>
<xsl:variable name="_statusIdValue" select="status_id"/>
See if that makes a difference.
You are way over-complicating your transformation. Try this considerably simpler stylesheet:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.company.com/schemas/device/version001"
>
<xsl:output indent="yes" encoding="utf-8" />
<!-- main template / entry point -->
<xsl:template match="message">
<message>
<messageHeader>
<xsl:apply-templates select="cmd" mode="head" />
</messageHeader>
<messageBody>
<xsl:apply-templates select="cmd" mode="body" />
</messageBody>
</message>
</xsl:template>
<!-- header templates -->
<xsl:template match="cmd[#id = 'api_info']" mode="head">
<messageType>apiInfo</messageType>
</xsl:template>
<xsl:template match="cmd[#id = 'instrument_status']" mode="head">
<messageType>instrumentStatus</messageType>
</xsl:template>
<!-- body templates -->
<xsl:template match="cmd[#id = 'api_info']" mode="body">
<apiInfoBody>
<apiVersion><xsl:value-of select="api-version" /></apiVersion>
<apiBuild><xsl:value-of select="api-build" /></apiBuild>
</apiInfoBody>
</xsl:template>
<xsl:template match="cmd[#id = 'instrument_status']" mode="body">
<instrumentStatusBody>
<statusId>
<xsl:choose>
<xsl:when test="status_id = 0">Ready</xsl:when>
<xsl:when test="status_id = 1">Running</xsl:when>
<xsl:when test="status_id = 2">NotReady</xsl:when>
<xsl:when test="status_id = 3">PoweringUp</xsl:when>
<xsl:when test="status_id = 4">PoweringDown</xsl:when>
<xsl:when test="status_id = 5">PoweredUp</xsl:when>
<xsl:when test="status_id = 6">PoweredDown</xsl:when>
<xsl:when test="status_id = 7">Tuning</xsl:when>
<xsl:when test="status_id = 8">Error</xsl:when>
<!-- just in case… -->
<xsl:otherwise>
<xsl:text>Unknown status_id: </xsl:text>
<xsl:value-of select="status_id" />
</xsl:otherwise>
</xsl:choose>
</statusId>
</instrumentStatusBody>
</xsl:template>
</xsl:stylesheet>
I got rid of all your seemingly superfluous namespace definitions (add them back as you need them) and put your stylesheet into a default namespace. This means you don't need a 'grf:' prefix on every element anymore, without changing the actual result:
<?xml version="1.0" encoding="utf-8"?>
<message xmlns="http://www.company.com/schemas/device/version001">
<messageHeader>
<messageType>instrumentStatus</messageType>
</messageHeader>
<messageBody>
<instrumentStatusBody>
<statusId>Running</statusId>
</instrumentStatusBody>
</messageBody>
</message>
Note how I use different match expressions and different template modes to output the appropriate elements in the right situations. This way any <xsl:variable> or <xsl:choose> become unnecessary, making for a cleaner and more maintainable stylesheet.
Also, usually there is no need to define <xsl:element> explicitly, unless you want to output elements with dynamic names. In all other cases, you can write the element straight-away.
I'm sorry that I can't say for sure why your stylesheet does not run as intended. It works for me, and it looks okay(ish).
Do not hesitate to ask if any of the above is unclear.
It's been a long time since I've coded up any xslt's but based on what I'm seeing you might be able to change this line:
<xsl:variable name="_statusIdValue" select="/message/cmd/status_id"/>
to
<xsl:variable name="_statusIdValue" select="/message/cmd/status_id/."/>
That should tell it to select the content of the element vs the node itself.
Kind of like when you do a value-of operation and you want the node's text content. you would do the same thing.
For example if you wanted to spit back out the status id number you could use the following:
<xsl:value-of select="/message/cmd/status_id/."/>
OK, I found out that if I use the following line to assign the _statusIdValue variable, then the code will function correctly with the XslCompiledTransform class:
<xsl:variable name="_statusIdValue" select="msxsl:node-set(/message/cmd/*)/text()"/>
This replaces the original line which was:
<xsl:variable name="_statusIdValue" select="/message/cmd/status_id"/>
However, the assignment that works in for the XslCompiledTransform class doesn't work with Altova XMLSpy. Is there a variant of the assignment that will work correctly in both the Altova XMLSpy editor and with the XslCompiledTransform class?
Thanks,
AlarmTripper
This is such terrible coding I'm actually glad it doesn't work in .NET, I suggest you rewrite your stylesheet.
Try this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:grf="http://www.company.com/schemas/device/version001">
<xsl:template match="message">
<xsl:variable name="commmand-type" select="cmd/#id"/>
<xsl:variable name="status-id" select="cmd/status_id/text()"/>
<grf:message>
<grf:messageHeader>
<grf:messageType>
<xsl:choose>
<xsl:when test="$commmand-type = 'api_info'">apiInfo</xsl:when>
<xsl:when test="$commmand-type = 'instrument_status'">instrumentStatus</xsl:when>
</xsl:choose>
</grf:messageType>
</grf:messageHeader>
<grf:messageBody>
<xsl:choose>
<xsl:when test="$commmand-type = 'api_info'">
<grf:apiInfoBody>
<grf:apiVersion>
<xsl:value-of select="cmd/api-version"/>
</grf:apiVersion>
</grf:apiInfoBody>
<grf:apiBuild>
<xsl:value-of select="cmd/api-build"/>
</grf:apiBuild>
</xsl:when>
<xsl:when test="$commmand-type = 'instrument_status'">
<grf:instrumentStatusBody>
<grf:statusId>
<xsl:choose>
<xsl:when test="$status-id = '0'">Ready</xsl:when>
<xsl:when test="$status-id = '1'">Running</xsl:when>
<xsl:when test="$status-id = '2'">NotReady</xsl:when>
<xsl:when test="$status-id = '3'">PoweringUp</xsl:when>
<xsl:when test="$status-id = '4'">PoweringDown</xsl:when>
<xsl:when test="$status-id = '5'">PoweredUp</xsl:when>
<xsl:when test="$status-id = '6'">PoweredDown</xsl:when>
<xsl:when test="$status-id = '7'">Tuning</xsl:when>
<xsl:when test="$status-id = '8'">Error</xsl:when>
</xsl:choose>
</grf:statusId>
</grf:instrumentStatusBody>
</xsl:when>
</xsl:choose>
</grf:messageBody>
</grf:message>
</xsl:template>
</xsl:stylesheet>
I'm using only one <xsl:template>, you can refactor if you feel it's appropiate.