最近一直在研究BASE64编解码,偶然想到用XSLT来实现,在网络上搜索了半天,没有找到一篇相关的文章和说明。偶在O'REILLY的网站上看到有人求解,只见答复XSLT1.0无解,XSLT2.0才有相关方法函数,然而XSLT2.0的应用还不是很普遍。
熟悉XSLT&XPath的你一定知道,两者都没有相关方法函数可以取字符的ASCII值。而这一功能正是BASE64编解码的基本要素之一。
因为取不到ASCII值,用XSLT来实现BASE64编解码的想法几乎要放弃了。难道就这么放弃?这不是我的风格,我喜欢迎难而进!
受BASE64解码思想和之前自己用XSLT&XPath写的循环输出A-Z字符代码的启发,借助XPath中的几个字符串处理函数,终于实现了XSLT1.0环境下BASE64的编解码。
BASE64编码的原理就不再重复了,这里只附上wikipedia的BASE64编码的原理,同时介绍如何通过XSLT&XPath实现字符到“ASCII值”的转换原理:
基本的ASCII字符对应的ASCII为0-127,其中0-31、127共33个字符为控制字符,不可显示,32-126为可显示字符。据此,我们可以建立一个映射表,即“ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~,”(注意:控制字符因为无法显示, 这里暂不考虑),其在映射表中的次序加上31即得到“ASCII值”。
附wikipedia的BASE64编码的原理:
转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲区中剩下的Bit用0补足。然后,每次取出6个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。
如果最后剩下两个输入数据,在编码结果后加1个「=」;如果最后剩下一个输入数据,编码结果后加2个「=」;如果没有剩下任何数据,就什么都不要加,这样才可以保证资料还原的正确性。
BASE64的解码实为编码的逆过程。
XSLT1.0实现BASE64编解码:
<?xml version="1.0" encoding="gb2312" ?>
<!--Encoding&Decoding base64 with XSLT1.0-->
<!--BASE64编解码之XSLT1.0实现-->
<!--Author:Qr http://Qr.blogger.org.cn-->
<!--Date:2009/06/29-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="gb2312" indent="yes"/>
<xsl:output doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"/>
<xsl:output doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
<xsl:variable name="CODEPAD" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'"/>
<xsl:variable name="ASCII"><![CDATA[ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~,]]></xsl:variable>
<!--原始字符串-->
<xsl:variable name="CHAR" select="'Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.'"/>
<!--原始字符串长度-->
<xsl:param name="STRLENGTH" select="string-length($CHAR)"/>
<!--循环次数-->
<xsl:param name="CYCLE_CNT" select="floor($STRLENGTH div 3)"/>
<!--模值-->
<xsl:param name="MOD" select="$STRLENGTH mod 3"/>
<!--待解码字符串-->
<xsl:variable name="STR" select="'TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4='"/>
<!--待解码字符串长度-->
<xsl:param name="STRLEN" select="string-length($STR)"/>
<!--循环次数-->
<xsl:param name="CYCLECNT" select="floor($STRLEN div 4)"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="gb2312" lang="gb2312">
<head>
<title>BASE64编解码之XSLT1.0实现 - Encoding&Decoding base64 with XSLT1.0 by Qr</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
</head>
<body>
<div style="margin:50px 168px;">
<h1 style="text-align:center;">BASE64编解码之XSLT1.0实现</h1>
<xsl:text>原始字符串:[Thomas Hobbes's Leviathan文句]</xsl:text>
<textarea rows="6" cols="80" style="width:100%">
<xsl:value-of select="$CHAR"/>
</textarea>
<xsl:text>编码字符串:</xsl:text>
<textarea rows="6" cols="80" style="width:100%">
<xsl:call-template name="base64_encode">
<xsl:with-param name="number" select="1"/>
</xsl:call-template>
<xsl:if test="$MOD != 0">
<xsl:value-of select="substring($CODEPAD,floor((string-length(substring-before($ASCII,substring($CHAR,$CYCLE_CNT*3+1,1)))+1+31) div 4)+1,1)"/>
<xsl:variable name="sec">
<xsl:choose>
<xsl:when test="1=$MOD">0</xsl:when>
<xsl:otherwise><xsl:value-of select="floor((string-length(substring-before($ASCII,substring($CHAR,$CYCLE_CNT*3+2,1)))+31) div 16)"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="substring($CODEPAD,((string-length(substring-before($ASCII,substring($CHAR,$CYCLE_CNT*3+1,1)))+1+31) mod 4)*16+$sec+1,1)"/>
<xsl:choose>
<xsl:when test="1=$MOD"><xsl:value-of select="'='"/></xsl:when>
<xsl:otherwise><xsl:value-of select="substring($CODEPAD,((string-length(substring-before($ASCII,substring($CHAR,$CYCLE_CNT*3+2,1)))+1+31) mod 16)*4+1,1)"/></xsl:otherwise>
</xsl:choose>
<xsl:value-of select="'='"/>
</xsl:if>
</textarea>
<xsl:text>解码字符串:</xsl:text>
<textarea rows="6" cols="80" style="width:100%">
<xsl:call-template name="base64_decode">
<xsl:with-param name="number" select="1"/>
</xsl:call-template>
<xsl:value-of select="substring($ASCII,(((string-length(substring-before($CODEPAD,substring($STR,$CYCLECNT * 4 - 3,1)))+1) mod 256 - 1)*4) + floor(((string-length(substring-before($CODEPAD,substring($STR,$CYCLECNT * 4 - 2,1)))) mod 256) div 16)-31,1)"/>
<xsl:choose>
<!--第三、四个字符为“=”-->
<xsl:when test="substring($STR,$CYCLECNT * 4 - 1,1)='='"></xsl:when>
<xsl:otherwise>
<!--第三个字符不为“=”-->
<xsl:value-of select="substring($ASCII,(((string-length(substring-before($CODEPAD,substring($STR,$CYCLECNT * 4 - 2,1)))) mod 16)*16 - 0) + floor(((string-length(substring-before($CODEPAD,substring($STR,$CYCLECNT * 4 - 1,1)))+1) mod 256) div 4) -31,1)"/>
<xsl:choose>
<!--全无“=”-->
<xsl:when test="substring($STR,$CYCLECNT * 4,1)!='='">
<xsl:value-of select="substring($ASCII,(((string-length(substring-before($CODEPAD,substring($STR,$CYCLECNT * 4 - 1,1)))) mod 4)*64 - 1) + floor(((string-length(substring-before($CODEPAD,substring($STR,$CYCLECNT * 4,1)))+1) mod 255)) - 31,1)"/>
</xsl:when>
<!--末字符“=”-->
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</textarea>
</div>
</body>
</html>
</xsl:template>
<!--Encoding&Decoding base64 with XSLT1.0-->
<!--Author:Qr http://Qr.blogger.org.cn-->
<xsl:template name="base64_encode">
<xsl:param name="number"/>
<xsl:if test="not($number > ($STRLENGTH - $MOD))">
<!--3转4字节第1字符-->
<xsl:value-of select="substring($CODEPAD,floor((string-length(substring-before($ASCII,substring($CHAR,$number,1)))+1+31) div 4)+1,1)"/>
<!--3转4字节第2字符-->
<xsl:value-of select="substring($CODEPAD,((string-length(substring-before($ASCII,substring($CHAR,$number,1)))+1+31) mod 4)*16+floor((string-length(substring-before($ASCII,substring($CHAR,$number+1,1)))+1+31) div 16)+1,1)"/>
<!--3转4字节第3字符-->
<xsl:value-of select="substring($CODEPAD,((string-length(substring-before($ASCII,substring($CHAR,$number+1,1)))+1+31) mod 16)*4 + floor((string-length(substring-before($ASCII,substring($CHAR,$number+2,1)))+31) div 64)+1,1)"/>
<!--3转4字节第4字符-->
<xsl:value-of select="substring($CODEPAD,((string-length(substring-before($ASCII,substring($CHAR,$number+2,1)))+1+31) mod 64)+1,1)"/>
<!--根据base64的编码规则,每76个字符需要一个换行。根据实际需要取舍-->
<!--Firefox未通过。-->
<!--number始于1,故只须+2,而不必+3-->
<xsl:if test="0=(($number+2) mod 19)">
</xsl:if>
<xsl:call-template name="base64_encode">
<xsl:with-param name="number" select="$number + 3"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--Encoding&Decoding base64 with XSLT1.0-->
<!--Author:Qr http://Qr.blogger.org.cn-->
<xsl:template name="base64_decode">
<xsl:param name="number"/>
<xsl:if test="$number < ($STRLEN - 4)">
<!--4转3字节第1字符-->
<xsl:value-of select="substring($ASCII,(((string-length(substring-before($CODEPAD,substring($STR,$number,1)))+1) mod 256 - 1)*4) + floor(((string-length(substring-before($CODEPAD,substring($STR,$number+1,1)))) mod 256) div 16)-31,1)"/>
<!--4转3字节第2字符-->
<xsl:value-of select="substring($ASCII,(((string-length(substring-before($CODEPAD,substring($STR,$number+1,1)))) mod 16)*16 - 0) + floor(((string-length(substring-before($CODEPAD,substring($STR,$number+2,1)))+1) mod 256) div 4)-31,1)"/>
<!--4转3字节第3字符-->
<xsl:value-of select="substring($ASCII,(((string-length(substring-before($CODEPAD,substring($STR,$number+2,1)))) mod 4)*64 - 1) + floor(((string-length(substring-before($CODEPAD,substring($STR,$number+3,1)))+1) mod 255))-31,1)"/>
<xsl:call-template name="base64_decode">
<xsl:with-param name="number" select="$number + 4"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--Encoding&Decoding base64 with XSLT1.0-->
<!--Author:Qr http://Qr.blogger.org.cn-->
</xsl:stylesheet>
编解码例句这里还是采用Thomas Hobbes's Leviathan文句:
Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.
编码结果:
TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
以上结果按base64的编码规则76字符换行,解除码时,须将换行相关字符去除再转换,但以上代码没有把这个问题考虑进去,只是将除去换行相关字符的待解码串直接赋值给变量STR。
解码结果:
Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.
由于映射表中无法将ASCII的控制字符(不可显示字符)加入,因此,待编码字符串只能包含ASCII值小于32和等于127的字符(故TAB和换行等均无法正确编解码),否则,编解码将无法顺利进行,同时也得不到正确的结果。大家如果有兴趣,可以来完善这个功能。
熟悉XSLT&XPath的你一定知道,字符处理并不是XSLT的强项,这项工作本也不该XSLT来做。本文之所以用XSLT来,纯粹学术讨论。
最后,说明一下,编解码过程中,如果把编解码后的字符串组联合起来作为变量再次递归恐怕会影响编解码的速度,所以本例将每组(3转4或4转3)编解码结果直接输出到textarea中,而没有将结果作为变量再次传入命名模板中递归。