中文字符编码的相互转换(四)

在代码编写中遇到字符串的地方少不了需要转义。为何要转义、何时转义、如何转义这几个问题也让很多开发者困扰不已。而且,编码和转义关系也是非常密切的。

一、为什么要转义。

以C语言做例子,我想声明一个char字符,该字符表示一个换行。但ASCII码中没有表示换行的文字符号,所以必须用转义字符来表示。于是我们可以这样定义:
char c1=‘\n’;
其中’\n’ 用两个ASCII码字符表示了一个换行符。编译器在遇到反斜杠时,会将其与后面的字符合在一起当作一个字符来处理。

再一种情况,这次我想声明的char字符表示一个单引号(‘)。但是在C语言中单引号是有特殊含义的,两个单引号之间是一个字符。如果中间再加一个单引号,那么编译器就没法正确理解这几个单引号的含义。所以,我们需要如下定义:
char c2=‘\’’;
编译器遇到的第一个单引号是C语法字符,遇到的第二个单引号因为是在反斜杠之后,会被编译器认定是真正的单引号,遇到的第三个单引号同第一个一样,是C语法字符。

第三种情况,因为ASCII码只能表示0-127的字符,那大于128的字符如何表示呢?比如大小为255的字符我么可以这么表示:
char c3=‘\xff’;

现在我们来总结使用转义字符的原因,主要是有两个:
1,有些字符没有文字字符表示,比如ASCII中的空字符、换行符等,还有些字符超出了ASCII码的范围,即那些大于128的字符,这些字符需要用转义字符来表示。
2,某些特定字符在编程语言中有特殊用途,在这些编程语言中就失去了字符原有的含义。如C语言中的引号,HTML中的<等。

二、何时转义

转义的目的有两个:
1,通过转义字符来表示无法表示的内容。
2,通过转义将原本的字符意义改变。在实际使用中处于安全考虑会做。如在执行SQL语句时,若某些数据是外来输入,则需要将这些数据进行转义,以防止注入攻击。

转义操作发生在输入时。为了让输入内容本义改变而做转义操作。实际使用中很多需要在输出时做转义,但这些输出也只是为了成为别人的输入。

三、如何转义
这里我拿web中最常用的三个转义方法来做例子,分别是escape,encodeURI,encodeURIComponent。在此我们用C语言来实现这几个方法,达到对其完全理解的目的。

这三个方法是在web开发中最常用的对字符串编码的函数,均是将特殊字符转换成为%xx格式的编码(xx等于该字符的16进制编码),但还是有些不太一样。

encodeURI,encodeURIComponent:将非ASCII码转为UTF-8格式,然后特殊字符用16进制表示。特殊字符是非ASCII码以及ASCII码中需要转义的字符。两者的区别是字符集的不同。前者比后者要少一点。
escape:先将ASCII码转为UCS-2格式,然后特殊字符用16进制表示。

非ASCII码肯定是需要转义的字符,ASCII码中的待转义字符可以利用Javascript来辅助找到。在Shell中通过以下命令可以得到判断128个ASCII码是否为待转义字符的Javascript代码:

awk 'BEGIN{for(i=0;i<8;i++){for(j=0;j<16;j++){printf("%X\n", i*16+j)}}}' | awk '{p="";if(NR<=16)p="0";printf("console.log(\"%s\",escape(\"\\x%s%s\")==\"\\x%s%s\");\n",$1, p, $1, p, $1)}'


可以得到128行代码:
console.log("0",escape("\x00")=="\x00");
console.log("1",escape("\x01")=="\x01");
console.log("2",escape("\x02")=="\x02");
console.log("3",escape("\x03")=="\x03");
console.log("4",escape("\x04")=="\x04");
console.log("5",escape("\x05")=="\x05");
console.log("6",escape("\x06")=="\x06");
console.log("7",escape("\x07")=="\x07");
console.log("8",escape("\x08")=="\x08");
console.log("9",escape("\x09")=="\x09");
console.log("A",escape("\x0A")=="\x0A");
console.log("B",escape("\x0B")=="\x0B");
console.log("C",escape("\x0C")=="\x0C");
console.log("D",escape("\x0D")=="\x0D");
console.log("E",escape("\x0E")=="\x0E");
console.log("F",escape("\x0F")=="\x0F");


在Chrome下面打开控制台,执行以上代码,可以得到如下:

0 false
1 false 
2 false
3 false 
4 false 
5 false 


其中false代表需要转义的字符,true代表无需转义的字符,这样我就可以得到128个字符中转义成%XX的字符数了。当然为了保险起见,这些被转义的字符是否真的变成%XX,还可以通过这条命令的结果在Javascript中验证一下。
awk 'BEGIN{for(i=0;i<8;i++){for(j=0;j<16;j++){printf("%X\n", i*16+j)}}}' | awk '{p="";if(NR<=16)p="0";printf("if(escape(\"\\x%s%s\")!=\"\\x%s%s\")console.log(\"%s\", escape(\"\\x%s%s\")==\"%%%s%s\");\n", p, $1, p, $1, $1, p, $1, p, $1)}’

经检验,与我们开始的判断完全吻合,大家可以自己试一下。现在,我们用CPP代码来实现escape:

/*! 
 * \brief 将UCS-2编码文本转义
 * \param[in] sUCValue: sUCValue UCS2格式字符串
 * \return string 转换后的字符串
 */  
std::string escape(const std::string& sUCValue)
{
     const static bool s_esc[256] =
     {
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
          1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
     };

     const T_UC* bpos = (T_UC*)&sUCValue[0];
     const T_UC* epos = bpos + (sUCValue.size()/sizeof(T_UC));

     std::string sValue = __encodeBase(s_esc, bpos, epos, "%", "%u", "");
     return sValue;
}

其中__encodeBase的实现如下:

char* ITOX2(int x, char vx[2])
{
     static char MAPX[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
     vx[0] = MAPX[(unsigned char)x>>4];
     vx[1] = MAPX[x&0x0F];
     return vx;
}

char* ITOX4(int x, char vx[4])
{
     ITOX2((unsigned short)(x&0xFF00)>>8, vx);
     ITOX2(x&0xFF, vx+2);
     return vx;
}
std::string __encodeBase(const bool esc[256], const T_UC* bpos, const T_UC* epos, const char* prefix2, const char* prefix4, const char* subfix)
{
     int bSize2 = strlen(prefix2);
     int bSize4 = strlen(prefix4);
     int eSize = strlen(subfix);
    
     char v2[16] = {0};
     char v4[16] = {0};
     char* p2 = v2+bSize2;
     char* p4 = v4+bSize4;

     memcpy(v2, prefix2, bSize2);
     memcpy(v4, prefix4, bSize4);
     memcpy(p2+2, subfix, eSize);
     memcpy(p4+4, subfix, eSize);

     int s2 = bSize2+2+eSize;
     int s4 = bSize4+4+eSize;

     std::string sValue((bSize4+eSize+4)*(epos-bpos), 0);
     char* tpos = &sValue[0];

     while (bpos < epos)
     {
          if (*bpos & 0xff00)
          {
               ITOX4(*bpos, p4);
               memcpy(tpos, v4, s4);
               tpos += s4;
               ++bpos;
          }
          else if (esc[*bpos & 0xff])
          {
               ITOX2(*bpos, p2);
               memcpy(tpos, v2, s2);
               tpos += s2;
               ++bpos;
          }
          else
          {
               *tpos++ = *bpos++;
          }
     }
     sValue.resize(tpos - &sValue[0]);

     return sValue;
}

同理我们来实现encodeURIComponent。先得到表示256个字符是否需要转义的状态map,通过以下结果得到Javascript代码:

awk 'BEGIN{for(i=0;i<8;i++){for(j=0;j<16;j++){printf("%X\n", i*16+j)}}}' | awk '{p="";if(NR<=16)p="0";printf("console.log(\"%s\",encodeURIComponent(\"\\x%s%s\")==\"\\x%s%s\");\n",$1, p, $1, p, $1)}'


CPP代码实现:

/*! 
 * \brief 将UTF-8编码文本转义
 * \param[in] sData: utf-8格式字符串
 * \return string 转换后的字符串
 */  
std::string encodeURIComponent(const std::string& sData)
{
     const static bool s_esc[256] =
     {
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
          1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
          1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
     };

     std::string sValue;
     sValue.reserve(sData.size() * 4);

     T_UTF8 *bpos = (T_UTF8*)&sData[0];
     T_UTF8 *epos = bpos + sData.size();
    
     while (bpos<epos)
     {
          if (!s_esc[*bpos & 0xff])
          {
               sValue += *bpos;
          }
          else
          {
               char vx[4]={'%', 0, 0, 0};
               ITOX2(*bpos, vx+1);
               sValue += vx;
          }
          bpos++;
     }    
     return sValue;
}

两者对应的decode函数分别如下:

/*! 
 * \brief 将文本解码成utf-8
 * \param[in] sData, URIComponent之后的字符串
 * \return string 转换后的字符串,utf-8格式
 */  
std::string decodeURIComponent(const std::string& sData)
{
     std::string sResult = sData;
     int x, y;

     for (x = 0, y = 0; sData[y]; x++, y++)
     {
          if((sResult[x] = sData[y]) == '%')
          {
               sResult[x] = x2c(&sData[y+1]);
               y += 2;
          }
     }
    
     return sResult.substr(0, x);
}

/*! 
 * \brief 将文本解码成unicode
 * \param[in] sData, escape之后的字符串
 * \return string 转换后的字符串,unicode格式
 */  
std::string unescape(const std::string& sData)
{
     struct HEX
     {
          static bool isHex(T_UC ch)
          {
               return (ch>='0' && ch<='9' || ch>='A' && ch<='F' || ch>='a' && ch<='f');
          }
     };


     std::string sUCValue = UCS2(sData);
     std::string sValue(sUCValue.size(), 0);

     T_UC *ucbpos = (T_UC *)&sUCValue[0];
     T_UC *ucepos = ucbpos + sUCValue.size()/sizeof(T_UC);

     T_UC *ucbResult =  (T_UC *)&sValue[0];

     #define HEXUCTOI(uc) ((uc >= 'a')? (uc - 'a' + 10) : (uc >= 'A') ? (uc - 'A' + 10) : (uc - '0'))

     while (ucbpos < ucepos)
     {
          if (*ucbpos != 0x25)          // %
          {
               *ucbResult++ = *ucbpos++;
          }
          else if((*(ucbpos+1)=='u' || *(ucbpos+1)=='U') && (ucepos>ucbpos+5) && HEX::isHex(*(ucbpos+2)) && HEX::isHex(*(ucbpos+3)) && HEX::isHex(*(ucbpos+4)) && HEX::isHex(*(ucbpos+5)))
          {
               *ucbResult++ = (HEXUCTOI(*(ucbpos+2))<<12) | (HEXUCTOI(*(ucbpos+3))<<8) | (HEXUCTOI(*(ucbpos+4))<<4) | (HEXUCTOI(*(ucbpos+5)));
               ucbpos=ucbpos+6;
          }
          else if ((ucepos>ucbpos+2) && HEX::isHex(*(ucbpos+1)) && HEX::isHex(*(ucbpos+2)) )
          {
               *ucbResult++ = (HEXUCTOI(*(ucbpos+1))<<4) | (HEXUCTOI(*(ucbpos+2)));
               ucbpos=ucbpos+3;
          }
          else
          {
               *ucbResult++ = *ucbpos++;
          }
     }
     sValue.resize((ucbResult-(T_UC *)&sValue[0])*sizeof(T_UC));
     return sValue;
}

另外,还有json、xml、html的转义,后续再介绍。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值