工作中遇到的一个问题,我们系统是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。
只是个人学习的记录哈,欢迎指正,但是别喷俺哈~~