I have some code that loads in an xml file and applies an XSLT to it using “XslCompiledTransform”. I now need to change the code as the input file may not be XML, it could also be a CSV file. I have an XSLT that will convert the CSV to the format I want but it uses a hardcoded path. The problem I have is that the input filename will change so can I use a widecard in the name? Secondly, in code can I pass in the file as a string to an XSLT object and have the result fall out the bottom? I’ve been doing some googling and not found any code to do this. Is it even possible?
Here is the XSLT I am using. Not the hardcoded path and filename.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="fn"
exclude-result-prefixes="xs fn">
<xsl:output indent="yes" encoding="US-ASCII"/>
<xsl:param name="pathToCSV" select="'file:///C:/Downloads/inputcsv.csv'"/>
<xsl:function name="fn:getTokens" as="xs:string+">
<xsl:param name="str" as="xs:string"/>
<xsl:analyze-string select="concat($str, ',')" regex='(("[^"]*")+|[^,]*),'>
<xsl:matching-substring>
<xsl:sequence select='replace(regex-group(1), "^""|""$|("")""", "$1")'/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:function>
<xsl:template match="/" name="main">
<xsl:choose>
<xsl:when test="unparsed-text-available($pathToCSV)">
<xsl:variable name="csv" select="unparsed-text($pathToCSV)"/>
<xsl:variable name="lines" select="tokenize($csv, '
')" as="xs:string+"/>
<xsl:variable name="elemNames" select="fn:getTokens($lines[1])" as="xs:string+"/>
<root>
<xsl:for-each select="$lines[position() > 0]">
<row>
<xsl:variable name="lineItems" select="fn:getTokens(.)" as="xs:string+"/>
<xsl:for-each select="$elemNames">
<xsl:variable name="pos" select="position()"/>
<column>
<xsl:value-of select="$lineItems[$pos]"/>
</column>
</xsl:for-each>
</row>
</xsl:for-each>
</root>
</xsl:when>
<xsl:otherwise>
<xsl:text>Cannot locate : </xsl:text><xsl:value-of select="$pathToCSV"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Well XslCompiledTransform is an XSLT 1.0 processor so using XSLT 2.0 features like unparsed-text is not possible with XslCompiledTransform, you need to use Saxon 9 or XmlPrime or AltovaXML for XSLT 2.0.
Then you can pass in a parameter to your posted stylesheet as it has a global xsl:param named pathToCSV, how you pass in that parameter depends on the API of the XSLT processor you want to use (or on its command line parameters if you simply want to execute the stylesheet from the command line).
Related
I have a two phases XSLT transformation with xml as output. When I set a breakpoint in one of templates during my first phase and start my xslt transformation in debug mode with XML Spy Professional 2020, I can see an xml structure in XSL Output.xml as the processed result before my template with breakpoint is applied.
My question is, is there a way in one template in the same phase to access this structure, which is a temporary result of transformation, which is not yet completed?
For development I use XML Spy Professional 2020 and for transformation in application I use Saxon Professional Edition SaxonPE9-9-1-3J.
My problem is following:
Input is a plain text https://gist.github.com/jia2/35143e79213864153b57ad0323a440a8#file-input-txt
Based on this format rules https://gist.github.com/jia2/76d676b90935cb7f33f5028180557af3,
the expected XML output like this:
https://gist.github.com/jia2/daaa4b2de5d1dadcb834f9f91c65d45b
Here my template:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:csb="http://www.dbcargo.org/csb" exclude-result-prefixes="#all" version="3.0">
<!-- <xsl:param name="msg" as="xs:string">H0 EVU_DBSRD PVG Z24 ABF-RF IR ExternalPartnerID_uuuuuuuuuuuuuuuuu0202017-03-16-07.27.40.864320NJNJ M1 80281261300008 M2 16.03.201707:27:00Z1 H62430 16.03.2017 16.03.201707:00:00+0027R1 00131800820664780201703154023641201703151159043706346965 000 JJ R1 02031800819657480201703154045545201703151159306557346965 000 NN </xsl:param> -->
<xsl:param name="msg" as="xs:string">H0 EVU_DBSRD PVG Z24 ABF-RF IR ExternalPartnerID_uuuuuuuuuuuuuuuuu0202017-03-16-07.27.40.864320NJNJJJ M1 80281261300008 M2 16.03.201707:27:00Z1 H62430 16.03.2017 16.03.201707:00:00+0027R1 00131800820664780201703154023641201703151159043706346965 000 JJ R1 02031800819657480201703154045545201703151159306557346965 000 NN </xsl:param>
<xsl:param name="relatviePath2MFL" as="xs:string" select="'./format.xml'"/>
<xsl:variable name="MFL" select="document($relatviePath2MFL)"/>
<xsl:output method="xml" indent="yes"/>
<xsl:mode name="unroll" on-no-match="shallow-copy"/>
<xsl:strip-space elements="*"/>
<xsl:template match="StructFormat[#repeat]" mode="unroll">
<xsl:variable name="this" select="."/>
<xsl:choose>
<xsl:when test="$this/#repeat != '*' ">
<xsl:for-each select="1 to #repeat">
<xsl:choose>
<xsl:when test="$this/#delimOptional = 'n' and $this/TagField and contains($msg, $this/TagField)">
<xsl:copy select="$this">
<xsl:apply-templates select="#* except #repeat, node()" mode="#current"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="repeat" select="count(tokenize($msg, $this/TagField/#value)) - 1"/>
<xsl:for-each select="1 to $repeat">
<xsl:copy select="$this">
<xsl:apply-templates select="#* except #repeat, node()" mode="#current"/>
</xsl:copy>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="StructFormat[not(#repeat)]" mode="unroll">
<xsl:variable name="this" select="."/>
<xsl:choose>
<xsl:when test="$this/TagField and not(contains($msg, $this/TagField/#value)) ">
</xsl:when>
<xsl:otherwise>
<xsl:copy select="$this">
<xsl:apply-templates select="#* except #repeat, node()" mode="#current"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="FieldFormat[#repeat]" mode="unroll">
<xsl:variable name="this" select="."/>
<xsl:for-each select="1 to #repeat">
<xsl:copy select="$this">
<xsl:apply-templates select="#* except #repeat, node()" mode="#current"/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:variable name="complete-struct">
<xsl:apply-templates select="$MFL/*" mode="unroll"/>
</xsl:variable>
<xsl:template match="/">
<xsl:element name="{$MFL/MessageFormat/#name}">
<xsl:apply-templates select="$complete-struct/*"/>
</xsl:element>
</xsl:template>
<xsl:template match="StructFormat">
<xsl:element name="{#name}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="FieldFormat">
<xsl:variable name="precedingFieldFormatsLength" select="sum(preceding::FieldFormat/#length)"/>
<xsl:variable name="offset">
<xsl:value-of select="string-length(string-join(./preceding::TagField/#value, ''))"/>
</xsl:variable>
<xsl:element name="{#name}">
<xsl:variable name="value" select="substring($msg, 1 + $precedingFieldFormatsLength + $offset, #length)"/>
<xsl:value-of select="csb:formatField(.,$value)"/>
</xsl:element>
</xsl:template>
<!-- format output -->
<xsl:function name="csb:formatField" as="xs:string">
<xsl:param name="field" as="element()"/>
<xsl:param name="value" as="xs:string"/>
<xsl:choose>
<xsl:when test="$field/#length = '1' and $value = ' '">
<xsl:value-of select="''"/>
</xsl:when>
<!-- remove leading and trailing space -->
<xsl:when test="$field/#trimLeading = ' ' and $field/#trimTrailing = ' '">
<xsl:value-of select="fn:replace($value, '^\s+|\s+$', '')"/>
</xsl:when>
<!-- remove ONLY leading space -->
<xsl:when test="$field/#trimLeading = ' ' and fn:not(fn:exists($field//#trimTrailing))">
<xsl:value-of select="fn:replace($value, '^\s+', '')"/>
</xsl:when>
<!-- remove ONLY trailing space -->
<xsl:when test="$field/#trimTrailing = ' ' and fn:not(fn:exists($field//#trimLeading))">
<xsl:value-of select="fn:replace($value, '\s+$', '')"/>
</xsl:when>
<!-- remove leading 0 -->
<xsl:when test="$field/#type = 'Numeric' and $field/#trimLeading = '0' and fn:not(fn:exists($field//#trimTrailing))">
<!-- <xsl:value-of select="fn:replace($value, '^0+', '')"/> -->
<xsl:if test="number($value) != number($value)">
<xsl:message terminate="yes" ><xsl:value-of select="concat('Transformation failed. The field', $field, ' has invalid value')" /></xsl:message>
</xsl:if>
<xsl:value-of select="number($value)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
</xsl:stylesheet>
My xslt template https://gist.github.com/jia2/5f7387e549e6f83601dbfac23ceb3acf is reading this format.xml as input and the plain text is passed by as parameter. It works for some inputs. but it will fail, when the value, which is used for marking begin of "StructFormat", exist in other positions.
For example:
<StructFormat name='HandoverTakeover' delimOptional='n' optional='y'>
<TagField type='String' value='U1 '/>
This means, StructFormat should be generated, when the input has "U1 " at a position. Now I'm just checking if input text contains "U1 " (<xsl:when test="$this/TagField and not(contains($msg, $this/TagField/#value)) ">), but this is not enough, I need to check, if the "U1 " comes in the "right" position range, not in the whole input.
I though if I can access currently build result tree, I can count the length until now to to cut the text before this position where I am checking.
Thanks
Dingjun
XSLT is a functional language; it therefore disallows operations whose result would depend on the order of execution. The fact that particular processors organise the processing in a particular way (even when two different processors choose the same strategy) doesn't mean it is something that can be relied on; in a few years time, for example, parallel execution strategies may be much more common.
More specifically, the fact that the two phases of your transformation are executing "concurrently" (one starts before the other finishes) is an internal optimization that you cannot exploit or rely on, and this is by design.
No doubt the transformation you are trying to effect can be achieved in some completely different way within the paradigm of a declarative functional language. I haven't studied the particular problem; like many people answering questions on StackOverflow, I'm not prepared to follow links to code that's off-site.
I am trying to concatenate two list of nodes which have child elements with a string-join but I am loosing the xml tags in the child elements
Input:
node1: hello I am trying <abc> some </abc> xslt code
node2: but not working
expected output
hello I am trying <abc> some </abc> xslt code, but not working
If you have a sequence of two element nodes containing the content you have shown and you want to create an output with a separator , between those nodes then one way would be to push the elements through a template that adds the separator:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="root">
<xsl:param name="seq1" select="node1, node2"/>
<xsl:copy>
<xsl:apply-templates select="$seq1"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node1 | node2">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pPJ8LVh
If you want to work on a string level with string-join then you first need to serialize the contents e.g.
<xsl:output method="text"/>
<xsl:template match="root">
<xsl:param name="seq1" select="node1, node2"/>
<xsl:copy>
<xsl:value-of select="$seq1 ! serialize(node())" separator=", "/>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/pPJ8LVh/1
<xsl:output method="text"/>
<xsl:template match="root">
<xsl:param name="seq1" select="node1, node2"/>
<xsl:variable name="str1" select="string-join($seq1 ! serialize(node()), ', ')"/>
<xsl:copy>
<xsl:value-of select="$str1"/>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/pPJ8LVh/2
As you can see, the last two examples create strings output as text, it seems more likely that you want to create a result nodes, like done in the first suggestion.
I have this structure
<ROWS>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>great</good>
<day>month</day>
</ROW>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>Fun</good>
<day>morning</day>
</ROW>
</ROWS>
How do I change that to
<statement> This is a great month, this is a Fun morning </statement>
Using only XSLT 1.0?
The original XML can change tag name. But not the structure! Any ideas?
This seems somewhat similar to creating form letters from a template. Assuming the example is not to be meant literally, you could try something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<statements>
<xsl:for-each select="ROWS/ROW/TEXT">
<statement>
<xsl:call-template name="merge">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</statement>
</xsl:for-each>
</statements>
</xsl:template>
<xsl:template name="merge">
<xsl:param name="string"/>
<xsl:param name="sep" select="'#'"/>
<xsl:choose>
<xsl:when test="contains($string, $sep) and contains(substring-after($string, $sep), $sep)">
<xsl:value-of select="substring-before($string, $sep)" />
<xsl:variable name="placeholder" select="substring-before(substring-after($string, $sep), $sep)" />
<xsl:value-of select="../*[name() = $placeholder]" />
<!-- recursive call -->
<xsl:call-template name="merge">
<xsl:with-param name="string" select="substring-after(substring-after($string, $sep), $sep)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Given an input of:
<ROWS>
<ROW>
<TEXT>The quick brown #animal# jumps over the #property# dog.</TEXT>
<animal>fox</animal>
<property>lazy</property>
</ROW>
<ROW>
<TEXT>A journey of a #number# miles #action# with a single #act#.</TEXT>
<number>thousand</number>
<action>begins</action>
<act>step</act>
</ROW>
</ROWS>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<statements>
<statement>The quick brown fox jumps over the lazy dog.</statement>
<statement>A journey of a thousand miles begins with a single step.</statement>
</statements>
Look at the XSLT 1.0 spec; find the section on string functions. Study the contains, substring-before, and substring-after functions. The solution to your problem should become clear; if it doesn't, you should at least be able to get far enough on your problem to pose a question that does not look as if it could be paraphrased as "Please do my homework for me."
I'm trying to use xsl:key to lookup items in an external XML document, using the XSL document() function. I am able to get the xsl:key part to work if, instead of using document(), I just merge the two XML files (using XmlDocument in C#). However both XML files are very large, and I'm starting to get "out of memory" errors in some cases. Also I need to be able to use xls:key, otherwise the process takes hours.
In XSLT 2.0, I believe you can do something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="lookupDoc" select="document('CodeDescriptions.xml')" />
<xsl:key name="LookupDescriptionByCode" match="Code/#description" use="../#code" />
<xsl:template match="ItemCode">
<xsl:call-template name="MakeSpanForCode">
<xsl:with-param name="code" select="text()" />
</xsl:call-template>
</xsl:template>
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<xsl:value-of select="$lookupDoc/key('LookupDescriptionByCode', $code)" />
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
How do you accomplish this in XSLT 1.0 though?
You have two possibilities:
without key
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<xsl:value-of select="$lookupDoc/*/Code[#code = $code]/#description" />
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
with key
The key definition applies to all documents, but you need to change the context node before using the key() function:
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<!-- trick: change context node to external document -->
<xsl:for-each select="$lookupDoc">
<xsl:value-of select="key('LookupDescriptionByCode', $code)"/>
</xsl:for-each>
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
Also see two great mailing list answers from Mike Kay and Jeni Tennison on this topic
I have a little problem.
A node in my XML may contains and integer, and i have to replace this integer by a string.
Each number match with a string.
For example i have:
Integer - String
1 - TODO
2 - IN PROGRESS
3 - DONE
4 - ERROR
5 - ABORTED
Original XML:
<root>
<status>1</status>
</root>
Converted XML:
<root>
<status>TODO</status>
</root>
So i want replace 1 by "TODO", 2 by "IN PROGRESS" ...
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root/status">
<root>
<status>
<xsl:variable name="text" select="." />
<xsl:choose>
<xsl:when test="contains($text, '1')">
<xsl:value-of select="'TODO'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</status></root>
</xsl:template>
</xsl:stylesheet>
I'am asking if there is another way to do that.
There are a number of ways of doing this. Where the translation is from consecutive integers in the range 1 to N, I would use
<xsl:variable name="index" select="xs:integer(status)"/>
<xsl:value-of select="('TODO', 'IN PROGRESS', 'DONE', 'ERROR', 'ABORTED')[$index]"/>
In other cases where there's a small number of values I might use template rules:
<xsl:template match="status[.='1']" mode="lookup">TODO</xsl:template>
<xsl:template match="status[.='2']" mode="lookup">IN PROGRESS</xsl:template>
etc.
In other cases a lookup table makes sense (note that Dimitre's version with its cumbersome document('') call is designed for XSLT 1.0 - it's considerably simpler if you're using 2.0. When people don't say what version they are using I generally assume 2.0 and Dimitre generally assumes 1.0.)
I'm increasingly seeing people make the mistake of using contains() when they mean "=". If you want to test whether the content of a node is "X", use $node = "X", not contains($node, "X").
One way to do this, is to create a sort of 'look-up' table of values. This could be embedded in the XSLT, or put in a separate file. For example, if you put it in the XSLT file, it would look something like this..
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lookup="lookup">
<lookup:data>
<status code="1">TO DO</status>
<status code="2">IN PROGRESS</status>
<status code="3">DONE</status>
</lookup:data>
Then, you would also create a variable to access this data
<xsl:variable name="lookup" select="document('')/*/lookup:data"/>
Finally, to look up the value, you would simply do this
<xsl:value-of select="$lookup/status[#code = '1']/>
Here is the full XSLT in this case
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lookup="lookup">
<xsl:output method="xml" indent="yes"/>
<lookup:data>
<status code="1">TO DO</status>
<status code="2">IN PROGRESS</status>
<status code="3">DONE</status>
</lookup:data>
<xsl:variable name="lookup" select="document('')/*/lookup:data"/>
<xsl:template match="status/text()">
<xsl:value-of select="$lookup/status[#code = current()]" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<root>
<status>TODO</status>
</root>
It could be better to have these in a separate file though, as then they can be re-used in other stylesheets. To do this, just create a file, called 'lookup.xml', and add the XML
<data>
<status code="1">TO DO</status>
<status code="2">IN PROGRESS</status>
<status code="3">DONE</status>
</data>
Note, you don't need namespaces in this case. Then just change the definition of the variable to the following
<xsl:variable name="lookup" select="document('lookup.xml')/data"/>
You have lots of unnecessary code in your solution. The following is a simplified version which works the same way:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root/status">
<root>
<status>
<xsl:choose>
<xsl:when test="contains(.,'1')">TODO</xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
</status>
</root>
</xsl:template>
</xsl:stylesheet>
The simplest approach is to start with the identity transform and then add special cases:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="status[. = '1']">
<status>TODO</status>
</xsl:template>
<!-- likewise for status[. = '2'] etc. -->
<!-- copy everything else -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT Version 3.0 you can use a map type:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="MyMap" select="
map {
'1' : 'TODO',
'2' : 'IN PROGRESS',
'3' : 'DONE',
'4' : 'ERROR',
'5' : 'ABORTED'}">
</xsl:variable>
<xsl:template match="/root/status">
<status>
<xsl:variable name="text" select="."/>
<xsl:value-of select="$MyMap( $text )"/>
</status>
</xsl:template>
</xsl:stylesheet>
My word. XSLT is not easy and I don't think it should be made any harder than need be by showing off your knowledge of the inner workings as shown in some of the other answers.
For ease you've hit the nail on the head, use a Choose statement. I'd probably pull it out into a separate templates (I uses these like methods in other languages) simply to ease testing and help clean up your code a little for ease of reading.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="PrintStatus">
<!-- param we can pass a value into or default to the current node -->
<xsl:param name="text" select="." />
<xsl:choose>
<xsl:when test="contains($text, '1')">
<xsl:value-of select="'TODO'"/>
</xsl:when>
<!-- Assume your others go here -->
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/root/status">
<root>
<status>
<xsl:call-template name="PrintStatus" />
</status>
</root>
</xsl:template>
</xsl:stylesheet>
Keep it simple unless you need the extra complications.
A trick I sometimes use in cases like this is to use a list of values in one string, and take a substring, like this:
<xsl:variable name="statuslist">TODO IN PROGRESSDONE ERROR ABORTED </xsl:variable>
<xsl:template match="status/text()">
<xsl:value-of select="normalize-space(substring($statuslist, ( . - 1 ) * 11 , 11))" />
</xsl:template>
Note, the values in the 'statuslist' are exactly 11 characters apart (the length of your longest value), hence the * 11 and ,11 in your substring. Because you count from 1 not 0, you have to subtract 1 from your index. Alternatively you could pad the variable with 11 spaces at the beginning rather than subtract 1, it's up to you. The normalize-space call just strips the excess spaces from the extracted value.
If you want to make it neater, you could put a separator between each value, and use *12,11 in that substring call instead.
It's not a solution that scales well if you have a large number of possible values, and obviously it needs your possible ids to be in a sequence, but if there's only a few values it's fairly compact.