我们来了解一下JSON、XML、HTML的转义。
2,ASCII码里面特殊字符的转义,比如换行符还可以用”\n” 表示。
3,从开发者角度来看,Javascript中的所有字符都是Unicode字符,所以换行符还可以用”\u0012”来表示,其中0012是换行符的Unicode编码。
如果涉及到中文的处理,会发现如果统一采用第3种方式来对字符编码,会消除非ASCII码对编码环境的要求。
Javascript中有个方法JSON.stringify可以起到一定的辅助作用,帮助我们找到需要编码的字符。首先用shell命令得到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\",\"\\x%s%s\", JSON.stringify(\"\\x%s%s\"));\n",$1, p, $1, p, $1)}’
得到结果,我们发现有些字符被转化为“\uxxxx”的格式,有些没有。我们的实现也按照这一规则。当然那些没有变成这样格式的字符我们也可以如此表示,但为了可读性以及节省输出字符的长度,我们还是用其他方式来表示。
其中,退格键(0x08,\b),制表符(0x09,\t),换行符(0x0a,\n),回车(0x0d,\r),换页符(0x0c,\f),双引号(0x22,“),反斜杠(0x5c,\)这7个符号需要特殊处理,其他的都不做处理。这几个字符处理完之后在我们的map中相对应的位置设置为0或是1都没有影响。其他字符在0x00-0x1F内的转为\uxxxx格式,在0x20-0x7F内的字符不做处理。在0x80-0x7F内的字符原则上可以不做处理,这里为了防止在其他平台上解析出问题,统一转为\uxxxx格式。
再来看xml和html,他们两者都有表示转义字符的方法:实体名字和实体编号。比如“<”,用实体名字表示就是“<”用实体编号表示是“<”。当然实体编号还可以用16进制表示为“<”
用实体(Entity)名字的好处是比较好理解,但是其劣势在于并不是所有的浏览器都支持最新的Entity名字。而实体(Entity)编号,各种浏览器都能处理。
& & 0x26
< < 0x3C
> > 0x3E
“ " 0x22
‘ ' 0x27
0x20
除了这几个符号之外,其他的符号理论上来说不需要做特殊处理。但是有些不可见字符,还有回车换行符等,最好还是转义一下,反正转义多了不会出错,只会带来人眼识别上的困难。所以最终我们的代码实现如下:
对JSON字符进行转义的原理跟上一节讨论的那几个函数不太一样。
在Javascript中的字符有多种表达形式:
1,ASCII码的8进制转义,16进制转义,比如换行符可以用”\x0A” 和 “\12” 表示 。2,ASCII码里面特殊字符的转义,比如换行符还可以用”\n” 表示。
3,从开发者角度来看,Javascript中的所有字符都是Unicode字符,所以换行符还可以用”\u0012”来表示,其中0012是换行符的Unicode编码。
如果涉及到中文的处理,会发现如果统一采用第3种方式来对字符编码,会消除非ASCII码对编码环境的要求。
Javascript中有个方法JSON.stringify可以起到一定的辅助作用,帮助我们找到需要编码的字符。首先用shell命令得到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\",\"\\x%s%s\", JSON.stringify(\"\\x%s%s\"));\n",$1, p, $1, p, $1)}’
得到结果,我们发现有些字符被转化为“\uxxxx”的格式,有些没有。我们的实现也按照这一规则。当然那些没有变成这样格式的字符我们也可以如此表示,但为了可读性以及节省输出字符的长度,我们还是用其他方式来表示。
其中,退格键(0x08,\b),制表符(0x09,\t),换行符(0x0a,\n),回车(0x0d,\r),换页符(0x0c,\f),双引号(0x22,“),反斜杠(0x5c,\)这7个符号需要特殊处理,其他的都不做处理。这几个字符处理完之后在我们的map中相对应的位置设置为0或是1都没有影响。其他字符在0x00-0x1F内的转为\uxxxx格式,在0x20-0x7F内的字符不做处理。在0x80-0x7F内的字符原则上可以不做处理,这里为了防止在其他平台上解析出问题,统一转为\uxxxx格式。
代码实现如下:
std::string CXCode::encodeJSONComponent(const std::string& sData)
{
std::string sUCValue = UCS2(sData);
T_UC* bpos = (T_UC*)&sUCValue[0];
const T_UC* epos = bpos + (sUCValue.size()/sizeof(T_UC));
T_UC * tUC = new T_UC[sData.size() * 4];
T_UC * ptUC = tUC;
while (bpos < epos)
{
if (*bpos == '\\' || *bpos== '\"' )
{
*ptUC++ = '\\';
*ptUC++ = *bpos;
}
else if (*bpos == '\n')
{
*ptUC++ = '\\';
*ptUC++ = 'n';
}
else if (*bpos == '\r')
{
*ptUC++ = '\\';
*ptUC++ = 'r';
}
else if (*bpos == '\b')
{
*ptUC++ = '\\';
*ptUC++ = 'b';
}
else if (*bpos == '\f')
{
*ptUC++ = '\\';
*ptUC++ = 'f';
}
else if (*bpos == '\t')
{
*ptUC++ = '\\';
*ptUC++ = 't';
}
else
{
*ptUC++ = *bpos;
}
++bpos;
}
bpos = tUC;
epos = ptUC;
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,
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, 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, 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, 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
};
std::string sValue = __encodeBase(s_esc, bpos, epos, "\\u00", "\\u", "");
if (tUC != NULL)
{
delete[] tUC;
}
if (CXCode::GetCharSet()==CXCode::CHARSET_UCS2)
{
CXCode x2(CXCode::CHARSET_UTF8);
return UCS2(sValue);
}
return sValue;
}
再来看xml和html,他们两者都有表示转义字符的方法:实体名字和实体编号。比如“<”,用实体名字表示就是“<”用实体编号表示是“<”。当然实体编号还可以用16进制表示为“<”
用实体(Entity)名字的好处是比较好理解,但是其劣势在于并不是所有的浏览器都支持最新的Entity名字。而实体(Entity)编号,各种浏览器都能处理。
我们这里为了兼容性,统一用实体编号来进行转义。
xml中有几个字符必须要转义,他们是 & < > " ' ,在HTML中还有个空格。他们的实体名字和16进制编码分别为
& & 0x26
< < 0x3C
> > 0x3E
“ " 0x22
‘ ' 0x27
0x20
除了这几个符号之外,其他的符号理论上来说不需要做特殊处理。但是有些不可见字符,还有回车换行符等,最好还是转义一下,反正转义多了不会出错,只会带来人眼识别上的困难。所以最终我们的代码实现如下:
std::string encodeXMLComponent(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, 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
};
std::string sUCValue = UCS2(sData);
T_UC* bpos = (T_UC*)&sUCValue[0];
const T_UC* epos = bpos + (sUCValue.size()/sizeof(T_UC));
while (bpos < epos)
{
if (*bpos < 32 && *bpos!=13 && *bpos!=10)
{
*bpos = T_UC('?');
}
++bpos;
}
bpos = (T_UC*)&sUCValue[0];
std::string sValue = __encodeBase(s_esc, bpos, epos, "&#x", "&#x", ";");
return sValue;
}