简体   繁体   中英

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM