Captain Normal
Captain Normal

Reputation: 471

XPath 1.0, Interpret hex attribute as a number

I need to compare XML attributes which represent integers, but may be given in decimal or hex (with 0x prefix), using XPath/XSLT-1.0.

Here is a (not working) XSLT to demonstrate:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />

    <xsl:template match="//node">
        <xsl:if test="@value &gt; 2">
            <xsl:value-of select="@value"/>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

Here is an XML to input:

<?xml version="1.0" encoding="UTF-8"?>
<body>
    <node value="1"/>
    <node value="3"/>
    <node value="0x03"/>
</body>

Here is the desired output. The format is not important; important is only that there is a match on the second and third nodes:

3 0x03

There is only a match on the second node; the hex node is not interpreted as a number by XML. Can anyone think of a reasonable solution to this problem?

Upvotes: 1

Views: 2131

Answers (2)

Tomalak
Tomalak

Reputation: 338376

Since you said that your processor is MSXSL, you can tap into the msxsl extensions, which allow you to define a script that you can use for work that the XSLT processor itself cannot do.

The following uses a small JScript function that converts all hexadecimal numbers that start with 0x to their decimal counterpart.

<xsl:stylesheet
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:script="http://tempuri.org/script"
  exclude-result-prefixes="msxsl script"
>
  <xsl:output method="xml" encoding="UTF-8" indent="yes"/>

  <xsl:template match="/body">
    <xsl:copy>
      <xsl:copy-of select="node[script:hexToDec(@value) &gt; 2]" />
    </xsl:copy>
  </xsl:template>

  <msxsl:script language="jscript" implements-prefix="script"><![CDATA[
    function hexToDec(nodeList) {
      var firstNode, matches;
      if (nodeList.length) {
        firstNode = nodeList.nextNode();
        matches = /^\s*0x0*([0-9A-F]+)\s*$/i.exec(firstNode.text);
        return matches ? parseInt(matches[1], 16) : firstNode.text;
      }
      return "";
    }
  ]]></msxsl:script>
</xsl:stylesheet>

The msxsl namespace also allows more advanced ways of extending the XSLT processor, for example with COM DLLs or .NET code, but for this simple scenario JScript does just fine.

Upvotes: 1

michael.hor257k
michael.hor257k

Reputation: 117140

The format is not important;

Then for convenience I will demonstrate with an XML format as the output:

XSLT 1.0

<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:strip-space elements="*"/>

<xsl:template match="/body">
    <xsl:copy>
        <xsl:for-each select="node">
            <xsl:variable name="decimal">
                <xsl:choose>
                    <xsl:when test="starts-with(@value, '0x')">
                        <xsl:call-template name="hex2num">
                            <xsl:with-param name="hex" select="substring-after(@value, '0x')"/>
                        </xsl:call-template>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="@value"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <xsl:if test="$decimal > 2">
                <xsl:copy-of select="."/>
            </xsl:if>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

<xsl:template name="hex2num">
    <xsl:param name="hex"/>
    <xsl:param name="num" select="0"/>
    <xsl:param name="MSB" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
    <xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $MSB))"/>
    <xsl:param name="result" select="16 * $num + $value"/>
    <xsl:choose>
        <xsl:when test="string-length($hex) > 1">
            <xsl:call-template name="hex2num">
                <xsl:with-param name="hex" select="substring($hex, 2)"/>
                <xsl:with-param name="num" select="$result"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$result"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Applied to the following test input:

<body>
    <node value="1"/>
    <node value="0x02"/>
    <node value="3"/>
    <node value="0x04"/>
    <node value="0xB1"/>
</body>

produces this result:

<?xml version="1.0" encoding="UTF-8"?>
<body>
   <node value="3"/>
   <node value="0x04"/>
   <node value="0xB1"/>
</body>

Upvotes: 4

Related Questions