Xavier S.
Xavier S.

Reputation: 1167

XSLT: replace an integer by a string

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.

Upvotes: 0

Views: 1810

Answers (7)

CJCombrink
CJCombrink

Reputation: 3950

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>

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163458

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").

Upvotes: 3

Flynn1179
Flynn1179

Reputation: 12075

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.

Upvotes: -1

Pete Duncanson
Pete Duncanson

Reputation: 3246

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.

Upvotes: -1

Nick Jones
Nick Jones

Reputation: 6493

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> 

Upvotes: 1

Tim C
Tim C

Reputation: 70638

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"/>

Upvotes: 2

Ian Roberts
Ian Roberts

Reputation: 122394

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>

Upvotes: 0

Related Questions