工作中遇到的一个问题,我们系统是utf-8编码的,对方系统是GBK编码的,双方通信使用xml格式的数据。
使用simplexml_load_string解析xml字符串的时候遇到两个问题:
1、gbk编码的xml字符串,在没有encoding描述信息的时候,解析出错;
2、在有encoding这些meta信息的时候,解析正确并且解析出来的数据是utf-8编码的。
本人对这些不熟悉了,就查了查代码,下面的是我跟踪代码的一些记录了。
simplexml_load_string 的实现是依赖于libxml2库的,下面是php使用的libxml2的函数(ext/simplexml/Simplexml.c)
docp = xmlReadMemory(data, data_len, NULL, NULL, options);
关于Libxml2库的字符编码解介绍可以查看:
http://xmlsoft.org/encoding.html
从描述里,libxml2在读取字符串的时候就做了编码的转换。
xmlReadMemory 函数里调用的xmlDoRead函数,看代码
xmlDocPtr
xmlReadMemory(const char *buffer, int size, const char *URL, const char *encoding, int options)
{
xmlParserCtxtPtr ctxt;
ctxt = xmlCreateMemoryParserCtxt(buffer, size);
if (ctxt == NULL)
return (NULL);
return (xmlDoRead(ctxt, URL, encoding, options, 0));
}
static xmlDocPtr
xmlDoRead(xmlParserCtxtPtr ctxt, const char *URL, const char *encoding,
int options, int reuse)
{
xmlDocPtr ret;
xmlCtxtUseOptions(ctxt, options);
if (encoding != NULL) {
xmlCharEncodingHandlerPtr hdlr;
hdlr = xmlFindCharEncodingHandler(encoding);
if (hdlr != NULL)
xmlSwitchToEncoding(ctxt, hdlr);
}
if ((URL != NULL) && (ctxt->input != NULL) &&
(ctxt->input->filename == NULL))
ctxt->input->filename = (char *) xmlStrdup((const xmlChar *) URL);
xmlParseDocument(ctxt);
if ((ctxt->wellFormed) || ctxt->recovery)
ret = ctxt->myDoc;
else {
ret = NULL;
if (ctxt->myDoc != NULL) {
xmlFreeDoc(ctxt->myDoc);
}
}
ctxt->myDoc = NULL;
if (!reuse) {
xmlFreeParserCtxt(ctxt);
}
return (ret);
}
从php的调用结合代码,程序进入xmlParseDocument函数
xmlParseDocument函数会做一些初始化和检查。并调用xmlParseXMLDecl解析xml的描述信息,还是看代码
xmlParseXMLDecl代码片段
……
version = xmlParseVersionInfo(ctxt);
if (version == NULL) {
xmlFatalErr(ctxt, XML_ERR_VERSION_MISSING, NULL);
} else {
if (!xmlStrEqual(version, (const xmlChar *) XML_DEFAULT_VERSION)) {
/*
* TODO: Blueberry should be detected here
*/
xmlWarningMsg(ctxt, XML_WAR_UNKNOWN_VERSION,
"Unsupported version '%s'\n",
version, NULL);
}
if (ctxt->version != NULL)
xmlFree((void *) ctxt->version);
ctxt->version = version;
}
/*
* We may have the encoding declaration
*/
if (!IS_BLANK_CH(RAW)) {
if ((RAW == '?') && (NXT(1) == '>')) {
SKIP(2);
return;
}
xmlFatalErrMsg(ctxt, XML_ERR_SPACE_REQUIRED, "Blank needed here\n");
}
//下面是字符编码的描述信息解析
xmlParseEncodingDecl(ctxt);
if (ctxt->errNo == XML_ERR_UNSUPPORTED_ENCODING) {
/*
* The XML REC instructs us to stop parsing right here
*/
return;
}
……
const xmlChar *
xmlParseEncodingDecl(xmlParserCtxtPtr ctxt) {
xmlChar *encoding = NULL;
SKIP_BLANKS;
if (CMP8(CUR_PTR, 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g')) {
SKIP(8);
SKIP_BLANKS;
if (RAW != '=') {
xmlFatalErr(ctxt, XML_ERR_EQUAL_REQUIRED, NULL);
return(NULL);
}
NEXT;
SKIP_BLANKS;
if (RAW == '"') {
NEXT;
encoding = xmlParseEncName(ctxt);
if (RAW != '"') {
xmlFatalErr(ctxt, XML_ERR_STRING_NOT_CLOSED, NULL);
} else
NEXT;
} else if (RAW == '\''){
NEXT;
encoding = xmlParseEncName(ctxt);
if (RAW != '\'') {
xmlFatalErr(ctxt, XML_ERR_STRING_NOT_CLOSED, NULL);
} else
NEXT;
} else {
xmlFatalErr(ctxt, XML_ERR_STRING_NOT_STARTED, NULL);
}
/*
* UTF-16 encoding stwich has already taken place at this stage,
* more over the little-endian/big-endian selection is already done
*/
if ((encoding != NULL) &&
((!xmlStrcasecmp(encoding, BAD_CAST "UTF-16")) ||
(!xmlStrcasecmp(encoding, BAD_CAST "UTF16")))) {
if (ctxt->encoding != NULL)
xmlFree((xmlChar *) ctxt->encoding);
ctxt->encoding = encoding;
}
/*
* UTF-8 encoding is handled natively
*/
else if ((encoding != NULL) &&
((!xmlStrcasecmp(encoding, BAD_CAST "UTF-8")) ||
(!xmlStrcasecmp(encoding, BAD_CAST "UTF8")))) {
if (ctxt->encoding != NULL)
xmlFree((xmlChar *) ctxt->encoding);
ctxt->encoding = encoding;
}
else if (encoding != NULL) {
xmlCharEncodingHandlerPtr handler;
if (ctxt->input->encoding != NULL)
xmlFree((xmlChar *) ctxt->input->encoding);
ctxt->input->encoding = encoding;
//下面这行查找编码处理的函数
handler = xmlFindCharEncodingHandler((const char *) encoding);
if (handler != NULL) {
//在这里转编码
xmlSwitchToEncoding(ctxt, handler);
} else {
xmlFatalErrMsgStr(ctxt, XML_ERR_UNSUPPORTED_ENCODING,
"Unsupported encoding %s\n", encoding);
return(NULL);
}
}
}
return(encoding);
}
xmlFindCharEncodingHandler的函数又会依赖于libiconv的字符编码转换,
xmlFindCharEncodingHandler代码片段
……
#ifdef LIBXML_ICONV_ENABLED
/* check whether iconv can handle this */
//藏在这里呢,转换到utf-8编码。
icv_in = iconv_open("UTF-8", name);
icv_out = iconv_open(name, "UTF-8");
if ((icv_in != (iconv_t) -1) && (icv_out != (iconv_t) -1)) {
enc = (xmlCharEncodingHandlerPtr)
xmlMalloc(sizeof(xmlCharEncodingHandler));
if (enc == NULL) {
iconv_close(icv_in);
iconv_close(icv_out);
return(NULL);
}
enc->name = xmlMemStrdup(name);
enc->input = NULL;
enc->output = NULL;
enc->iconv_in = icv_in;
enc->iconv_out = icv_out;
#ifdef DEBUG_ENCODING
xmlGenericError(xmlGenericErrorContext,
"Found iconv handler for encoding %s\n", name);
#endif
//enc返回用来处理编码转换
return enc;
……
libiconv这个有兴趣的可以再继续跟踪代码去了解。本人就没继续跟踪了解了。
那到这里应该就清楚了,libxml根据encoding的描述信息处理字符转换到utf-8
如果没有描述信息,libxml只能探测字符串是UTF-8 or UTF-16,否则会产生encoding error。
只是个人学习的记录哈,欢迎指正,但是别喷俺哈~~