XSLT1.0中IPv6地址转十进制数的方法及地址范围校验实现咨询
在XSLT 1.0中判断IPv6地址是否在指定范围的可行方案
嘿,这个问题确实戳中了XSLT 1.0的一个小痛点——它对超大数字的支持非常有限,而IPv6对应的128位十进制数(足足39位左右)完全超出了XSLT 1.0能处理的整数范围。所以直接转成十进制来比较其实是不现实的,不过我们可以换个更聪明的思路:直接对IPv6地址的分段进行逐段比较,这样既避开了超大数字的问题,又能高效完成范围校验。
核心思路
IPv6地址本质是8组16位的十六进制数(用冒号分隔),即使是带::零压缩的地址,我们也可以先把它展开成完整的8段格式,然后把每一段转成10进制整数(每段最大65535,XSLT 1.0完全能处理),再从第一段到第八段逐段对比待校验地址和范围边界:
- 如果待校验地址的某一段小于起始地址的对应段 → 不在范围内
- 如果大于结束地址的对应段 → 不在范围内
- 如果等于,就继续比较下一段
- 所有段都落在对应边界之间(或等于边界)→ 就在范围内
具体实现代码
下面是完整的XSLT 1.0模板,包含地址展开、十六进制转十进制、范围校验三个核心部分:
1. 展开带零压缩的IPv6地址
这个模板会把::替换成足够数量的0段,把地址转换成完整的8段格式:
<xsl:template name="expand-ipv6"> <xsl:param name="address"/> <xsl:choose> <xsl:when test="contains($address, '::')"> <xsl:variable name="before" select="substring-before($address, '::')"/> <xsl:variable name="after" select="substring-after($address, '::')"/> <xsl:variable name="count-before" select="count(tokenize($before, ':'))"/> <xsl:variable name="count-after" select="count(tokenize($after, ':'))"/> <!-- 计算需要填充的零段数 --> <xsl:variable name="zero-segments" select="8 - $count-before - $count-after"/> <xsl:variable name="zeros"> <xsl:call-template name="generate-zeros"> <xsl:with-param name="remaining" select="$zero-segments"/> </xsl:call-template> </xsl:variable> <!-- 拼接完整地址 --> <xsl:value-of select="concat( $before, if ($before != '') then ':' else '', $zeros, if ($after != '') then ':' else '', $after )"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$address"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="generate-zeros"> <xsl:param name="remaining"/> <xsl:if test="$remaining > 0"> <xsl:text>0</xsl:text> <xsl:if test="$remaining > 1"> <xsl:text>:</xsl:text> </xsl:if> <xsl:call-template name="generate-zeros"> <xsl:with-param name="remaining" select="$remaining - 1"/> </xsl:call-template> </xsl:if> </xsl:template>
2. 十六进制段转十进制
把IPv6的每一段十六进制字符串转换成十进制整数:
<xsl:template name="hex-to-decimal"> <xsl:param name="hex"/> <xsl:param name="decimal" select="0"/> <xsl:if test="$hex != ''"> <xsl:variable name="char" select="substring($hex, 1, 1)"/> <xsl:variable name="value"> <xsl:choose> <xsl:when test="$char >= '0' and $char <= '9'"> <xsl:value-of select="number($char)"/> </xsl:when> <xsl:when test="$char >= 'a' and $char <= 'f'"> <xsl:value-of select="10 + string-length(substring('abcdef', 1, string-length(substring-before('abcdef', $char))))"/> </xsl:when> <xsl:when test="$char >= 'A' and $char <= 'F'"> <xsl:value-of select="10 + string-length(substring('ABCDEF', 1, string-length(substring-before('ABCDEF', $char))))"/> </xsl:when> </xsl:choose> </xsl:variable> <xsl:call-template name="hex-to-decimal"> <xsl:with-param name="hex" select="substring($hex, 2)"/> <xsl:with-param name="decimal" select="$decimal * 16 + $value"/> </xsl:call-template> </xsl:if> <xsl:if test="$hex = ''"> <xsl:value-of select="$decimal"/> </xsl:if> </xsl:template>
3. 校验IPv6地址是否在范围
整合上面两个模板,逐段对比地址:
<xsl:template name="ipv6-in-range"> <xsl:param name="test-addr"/> <xsl:param name="start-addr"/> <xsl:param name="end-addr"/> <!-- 展开所有地址为完整8段格式 --> <xsl:variable name="expanded-test"> <xsl:call-template name="expand-ipv6"> <xsl:with-param name="address" select="$test-addr"/> </xsl:call-template> </xsl:variable> <xsl:variable name="expanded-start"> <xsl:call-template name="expand-ipv6"> <xsl:with-param name="address" select="$start-addr"/> </xsl:call-template> </xsl:variable> <xsl:variable name="expanded-end"> <xsl:call-template name="expand-ipv6"> <xsl:with-param name="address" select="$end-addr"/> </xsl:call-template> </xsl:variable> <!-- 分割成数组段 --> <xsl:variable name="test-segments" select="tokenize($expanded-test, ':')"/> <xsl:variable name="start-segments" select="tokenize($expanded-start, ':')"/> <xsl:variable name="end-segments" select="tokenize($expanded-end, ':')"/> <!-- 逐段比较 --> <xsl:call-template name="compare-segments"> <xsl:with-param name="test-segs" select="$test-segments"/> <xsl:with-param name="start-segs" select="$start-segments"/> <xsl:with-param name="end-segs" select="$end-segments"/> <xsl:with-param name="index" select="1"/> </xsl:call-template> </xsl:template> <xsl:template name="compare-segments"> <xsl:param name="test-segs"/> <xsl:param name="start-segs"/> <xsl:param name="end-segs"/> <xsl:param name="index"/> <xsl:if test="$index <= 8"> <!-- 转换当前段为十进制 --> <xsl:variable name="test-dec"> <xsl:call-template name="hex-to-decimal"> <xsl:with-param name="hex" select="$test-segs[$index]"/> </xsl:call-template> </xsl:variable> <xsl:variable name="start-dec"> <xsl:call-template name="hex-to-decimal"> <xsl:with-param name="hex" select="$start-segs[$index]"/> </xsl:call-template> </xsl:variable> <xsl:variable name="end-dec"> <xsl:call-template name="hex-to-decimal"> <xsl:with-param name="hex" select="$end-segs[$index]"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$test-dec < $start-dec"> <xsl:text>false</xsl:text> </xsl:when> <xsl:when test="$test-dec > $end-dec"> <xsl:text>false</xsl:text> </xsl:when> <xsl:when test="$index = 8"> <xsl:text>true</xsl:text> </xsl:when> <xsl:otherwise> <xsl:call-template name="compare-segments"> <xsl:with-param name="test-segs" select="$test-segs"/> <xsl:with-param name="start-segs" select="$start-segs"/> <xsl:with-param name="end-segs" select="$end-segs"/> <xsl:with-param name="index" select="$index + 1"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:template>
使用示例
在你的主模板里这样调用就能完成校验:
<xsl:template match="/"> <!-- 你的测试地址和范围边界 --> <xsl:variable name="test-addr" select="'ff14:fe80:1::99'"/> <xsl:variable name="start-addr" select="'ff14:0:1::99'"/> <xsl:variable name="end-addr" select="'ff14:fe80:1::108'"/> <is-in-range> <xsl:call-template name="ipv6-in-range"> <xsl:with-param name="test-addr" select="$test-addr"/> <xsl:with-param name="start-addr" select="$start-addr"/> <xsl:with-param name="end-addr" select="$end-addr"/> </xsl:call-template> </is-in-range> </xsl:template>
注意事项
- 确保输入的IPv6地址格式合法,
::只能出现一次,没有无效字符; - 如果你真的非要把IPv6转成十进制,XSLT 1.0几乎做不到——因为没有原生支持超大整数的运算,手动实现大数运算会异常复杂且低效,完全没必要;
- 这个方案的性能不错,因为只需要线性遍历8段,每段的转换和比较都是轻量操作。
内容的提问来源于stack exchange,提问作者Swathi S
相关产品推荐
相关产品推荐

