<xsl:stylesheet

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template name="url-decode">

  <xsl:param name="in" as="xs:string"/>
  <xsl:param name="seq" as="xs:string*"/>

  <xsl:variable name="unreserved" as="xs:integer+"
    select="(45, 46, 48 to 57, 65 to 90, 95, 97 to 122, 126)"/>

  <xsl:choose>
    <xsl:when test="not($in)">
      <xsl:sequence select="string-join($seq, '')"/>
    </xsl:when>
    <xsl:when test="starts-with($in, '%')">
      <xsl:choose>
        <xsl:when test="matches(substring($in, 2, 2), '^[0-9A-Fa-f][0-9A-Fa-f]$')">
          <xsl:variable name="s" as="xs:string" select="substring($in, 2, 2)"/>
          <xsl:variable name="d" as="xs:integer" select="p:hex-to-dec(upper-case($s))"/>
          <xsl:choose>
            <xsl:when test="$d = $unreserved">
              <xsl:variable name="c" as="xs:string" select="codepoints-to-string($d)"/>
              <xsl:sequence select="p:pct-decode-unreserved(substring($in, 4), ($seq, $c))"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:sequence select="p:pct-decode-unreserved(substring($in, 4), ($seq, '%', $s))"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:when>
        <xsl:when test="contains(substring($in, 2), '%')">
          <xsl:variable name="s" as="xs:string" select="substring-before(substring($in, 2), '%')"/>
          <xsl:sequence select="p:pct-decode-unreserved(substring($in, 2 + string-length($s)), ($seq, '%', $s))"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:sequence select="string-join(($seq, $in), '')"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:when test="contains($in, '%')">
      <xsl:variable name="s" as="xs:string" select="substring-before($in, '%')"/>
      <xsl:sequence select="p:pct-decode-unreserved(substring($in, string-length($s)+1), ($seq, $s))"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:sequence select="string-join(($seq, $in), '')"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<!-- Private function to convert a hexadecimal string into decimal -->
<xsl:function name="p:hex-to-dec" as="xs:integer">
  <xsl:param name="hex" as="xs:string"/>

  <xsl:variable name="len" as="xs:integer" select="string-length($hex)"/>
  <xsl:choose>
    <xsl:when test="$len eq 0">
      <xsl:sequence select="0"/>
    </xsl:when>
    <xsl:when test="$len eq 1">
      <xsl:sequence select="
               if ($hex eq '0')       then 0
          else if ($hex eq '1')       then 1
          else if ($hex eq '2')       then 2
          else if ($hex eq '3')       then 3
          else if ($hex eq '4')       then 4
          else if ($hex eq '5')       then 5
          else if ($hex eq '6')       then 6
          else if ($hex eq '7')       then 7
          else if ($hex eq '8')       then 8
          else if ($hex eq '9')       then 9
          else if ($hex = ('A', 'a')) then 10
          else if ($hex = ('B', 'b')) then 11
          else if ($hex = ('C', 'c')) then 12
          else if ($hex = ('D', 'd')) then 13
          else if ($hex = ('E', 'e')) then 14
          else if ($hex = ('F', 'f')) then 15
          else error(xs:QName('p:hex-to-dec'))
        "/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:sequence select="
        (16 * p:hex-to-dec(substring($hex, 1, $len - 1)))
        + p:hex-to-dec(substring($hex, $len))"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>