php simplexml_load_string 字符編碼

工做中遇到的一個問題,咱們系統是utf-8編碼的,對方系統是GBK編碼的,雙方通訊使用xml格式的數據。php

使用simplexml_load_string解析xml字符串的時候遇到兩個問題:html

一、gbk編碼的xml字符串,在沒有encoding描述信息的時候,解析出錯;函數

二、在有encoding這些meta信息的時候,解析正確而且解析出來的數據是utf-8編碼的。學習

本人對這些不熟悉了,就查了查代碼,下面的是我跟蹤代碼的一些記錄了。this

simplexml_load_string 的實現是依賴於libxml2庫的,下面是php使用的libxml2的函數(ext/simplexml/Simplexml.c)編碼

docp = xmlReadMemory(data, data_len, NULL, NULL, options);

關於Libxml2庫的字符編碼解介紹能夠查看:spa

http://xmlsoft.org/encoding.htmlcode

從描述裏,libxml2在讀取字符串的時候就作了編碼的轉換。orm

xmlReadMemory 函數裏調用的xmlDoRead函數,看代碼xml

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。


只是我的學習的記錄哈,歡迎指正,可是別噴俺哈~~

相關文章
相關標籤/搜索