libxml2剖析(3):使用教程

    本文整理自官方使用教程 http://xmlsoft.org/tutorial/index.html。

    示例文档story.xml如下:



  
    John Fleck
    June 2, 2002
    example keyword
  
  
    This is the headline
    This is the body text.
  
    1、解析xml文档
    解析文档时只需要文档名和一个函数调用,再加上错误处理。下面代码查找keyword节点并打印节点下的文本内容,如下:
#include 
#include 
#include 
#include 
#include 

/* 解析storyinfo节点,打印keyword节点的内容 */
void parseStory(xmlDocPtr doc, xmlNodePtr cur){
	xmlChar* key;
	cur=cur->xmlChildrenNode;
	while(cur != NULL){
		/* 找到keyword子节点 */
		if(!xmlStrcmp(cur->name, (const xmlChar *)"keyword")){
			key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
			printf("keyword: %s\n", key);
			xmlFree(key);
		}
		cur=cur->next; /* 下一个子节点 */
	}

	return;
}

/* 解析文档 */
static void parseDoc(char *docname){
	/* 定义文档和节点指针 */
	xmlDocPtr doc;
	xmlNodePtr cur;
	
	/* 进行解析,如果没成功,显示一个错误并停止 */
	doc = xmlParseFile(docname);
	if(doc == NULL){
		fprintf(stderr, "Document not parse successfully. \n");
		return;
	}

	/* 获取文档根节点,若无内容则释放文档树并返回 */
	cur = xmlDocGetRootElement(doc);
	if(cur == NULL){
		fprintf(stderr, "empty document\n");
		xmlFreeDoc(doc);
		return;
	}

	/* 确定根节点名是否为story,不是则返回 */
	if(xmlStrcmp(cur->name, (const xmlChar *)"story")){
		fprintf(stderr, "document of the wrong type, root node != story");
		xmlFreeDoc(doc);
		return;
	}

	/* 遍历文档树 */
	cur = cur->xmlChildrenNode;
	while(cur != NULL){
		/* 找到storyinfo子节点 */
		if(!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo")){
			parseStory(doc, cur); /* 解析storyinfo子节点 */
		}
		cur = cur->next; /* 下一个子节点 */
	}

	xmlFreeDoc(doc); /* 释放文档树 */
	return;
}

int main(int argc, char **argv){
	char *docname;
	if(argc <= 1){
		printf("Usage: %s docname\n", argv[0]);
		return 0;
	}
	docname=argv[1];
	parseDoc(docname);
	return 1;
}
    解析XML文档的基本流程如下:
    (1)定义文档指针和节点指针。
    (2)调用xmlParseFile()解析文档。如果不成功,注册一个错误并停止。一个常见错误是不适当的编码。XML标准文档除了用默认的UTF-8或UTF-16外,还可显式指定用其它编码保存。如果文档是这样,libxml2将自动地为你转换到UTF-8。更多关于XML编码信息包含在XML标准中。
    (3)调用xmlDocGetRootElement()获取文档根节点,若无根节点则释放文档树并返回。
    (4)确认文档是正确的类型,通过检查根节点名称来判断。
    (5)检索节点的内容,这需要遍历文档树。对每个节点,遍历其子节点都需要一个循环。先用cur = cur->xmlChildrenNode获取第一个子节点,然后通过cur = cur->next不断向前遍历,直到cur==NULL。查找找指定节点时使用xmlStrcmp()函数,如果你指定的名称相同,就找到了你要的节点。通常把查找某个子节点的过程封装成函数。
    (6)获取节点中的内容。查找到指定节点后,调用xmlNodeListGetString()获取节点下的文本。注意在XML中,包含在节点中的文本是这个节点的子节点,因此获取的是cur->xmlChildrenNode中的字符串。xmlNodeListGetString()会为返回的字符串分配内存,因此记得要用xmlFree()来释放它。
    (7)调用xmlFreeDoc()释放文档树指针。
     2、使用XPath查询信息
    在xml文档中查询信息是一项核心工作。Libxml2支持使用XPath表达式来查找匹配的节点集。简而言之,XPath之于xml,好比SQL之于关系数据库。要在一个复杂的xml文档中查找所需的信息,XPath简直是必不可少的工具。下面代码查询所有keyword元素的内容。

#include 
#include 

/* 解析文档 */
xmlDocPtr getdoc(char *docname){
	xmlDocPtr doc;
	doc = xmlParseFile(docname);
	if(doc == NULL){
		fprintf(stderr, "Document not parsed successfully. \n");
		return NULL;
	}

	return doc;
}

/* 查询节点集 */
xmlXPathObjectPtr getnodeset(xmlDocPtr doc, xmlChar *xpath){
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result; /* 存储查询结果 */

	/* 创建一个xpath上下文 */
	context = xmlXPathNewContext(doc);
	if(context == NULL){
		printf("Error in xmlXPathNewContext\n");
		return NULL;
	}
	/* 查询XPath表达式 */
	result = xmlXPathEvalExpression(xpath, context);
	xmlXPathFreeContext(context); /* 释放上下文指针 */
	if(result == NULL){
		printf("Error in xmlXPathEvalExpression\n");
		return NULL;
	}
	/* 检查结果集是否为空 */
	if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
		xmlXPathFreeObject(result); /* 如为这空就释放 */
		printf("No result\n");
		return NULL;
	}
	return result;
}

int main(int argc, char ** argv){
	char *docname;
	xmlDocPtr doc;
	/* 查找所有keyword元素,而不管它们在文档中的位置 */
	xmlChar *xpath=(xmlChar*)"//keyword";
	xmlNodeSetPtr nodeset;
	xmlXPathObjectPtr result;
	int i;
	xmlChar *keyword;

	if(argc <= 1){
		printf("Usage: %s docname\n", argv[0]);
		return(0);
	}

	docname = argv[1];
	doc = getdoc(docname);
	result = getnodeset(doc, xpath);
	if(result){
		/* 得到keyword节点集 */
		nodeset = result->nodesetval;
		for(i=0; i < nodeset->nodeNr; i++){ /* 打印每个节点中的内容 */
			keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1);
			printf("keyword: %s\n", keyword);
			xmlFree(keyword);
		}
		xmlXPathFreeObject(result); /* 释放结果集 */
	}

	xmlFreeDoc(doc); /* 释放文档树 */
	xmlCleanupParser(); /* 清除库内存 */
	return(1);
}
    可以在story.xml中多插入几个keyword元素,然后运行一下本程序看看效果。使用XPath查询信息的基本流程如下:
    (1)调用xmlXPathNewContext()给文档树创建一个上下文指针。
    (2)调用xmlXPathEvalExpression(),传入XPath表达式和上下文指针,返回一个xmlXPathObjectPtr结果集指针。nodesetval对象包含keyword节点个数(nodeNr)和节点列表(nodeTab)。在使用之前要和xmlXPathNodeSetIsEmpty()检查nodesetval节点列表是否为空。
    (3)遍历节点列表nodeTab,用xmlNodeListGetString()获取每个keyword节点的内容。
    (4)用xmlXPathFreeObject()释放查询结果,用xmlFreeDoc()释放文档树。
    更多关于Xpath的内容可以参考XPath官方规范http://www.w3.org/TR/xpath/。XPath语法的介绍,可参考w3school上的教程http://www.w3school.com.cn/xpath/index.asp,或者http://w3schools.com/xpath/default.asp。只有掌握XPath,才能掌握使用大型XML文件获取信息的方法,否则每寻找一个节点都要从根节点找起,很耗时耗力。
     3、修改xml文档
    这与上面的过程类似,首先遍历文档树,找到要插入(或删除)的节点处,然后插入(或删除)相关的内容。下面代码在storyinfo节点下插入一个keyword元素。
#include 
#include 
#include 
#include 
#include 

void
parseStory(xmlDocPtr doc, xmlNodePtr cur, const xmlChar* keyword) {
	/* 在当前节点下插入一个keyword子节点 */
	xmlNewTextChild(cur, NULL, (const xmlChar*)"keyword", keyword);
    return;
}

xmlDocPtr
parseDoc(char *docname, char *keyword) {

	xmlDocPtr doc;
	xmlNodePtr cur;

	doc = xmlParseFile(docname);
	
	if (doc == NULL ) {
		fprintf(stderr,"Document not parsed successfully. \n");
		return (NULL);
	}
	
	cur = xmlDocGetRootElement(doc);
	
	if (cur == NULL) {
		fprintf(stderr,"empty document\n");
		xmlFreeDoc(doc);
		return (NULL);
	}
	
	if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
		fprintf(stderr,"document of the wrong type, root node != story");
		xmlFreeDoc(doc);
		return (NULL);
	}
	
	cur = cur->xmlChildrenNode;
	while (cur != NULL) {
		if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))){
			parseStory (doc, cur, (const xmlChar*)keyword);
		}
		 
	cur = cur->next;
	}
	return(doc);
}

int
main(int argc, char **argv) {

	char *docname;
	char *keyword;
	xmlDocPtr doc;

	if (argc <= 2) {
		printf("Usage: %s docname, keyword\n", argv[0]);
		return(0);
	}

	docname = argv[1];
	keyword = argv[2];
	doc = parseDoc(docname, keyword);
	if (doc != NULL) {
		xmlSaveFormatFile(docname, doc, 0);
		xmlFreeDoc(doc);
	}
	
	return (1);
}
    这里xmlNewTextChild函数在当前节点指针上添加一个子元素。如果希望元素有名字空间,则可以在这里加上。添加完后,就要用xmlSaveFormatFile()把修改后的文档写入到文件。我们这里使用原来doc文档指针,因此会覆盖原来的文件。第三个参数如果设置为1,则输出的文档会自动缩进。
    若要删除某个节点,可以使用以下代码:
if(!xmlStrcmp(cur->name, BAD_CAST "keyword")){
	xmlNodePtr tempNode;
	tempNode = cur->next;
	xmlUnlinkNode(cur);
	xmlFreeNode(cur);
	cur = tempNode;
	continue;
}
    注意libxml2并没有xmlDelNode或者xmlRemoveNode之类的函数。我们需要将当前节点从文档中断链(unlink),文档就不会再包含这个子节点。这样做需要使用一个临时变量来存储断链节点的后续节点,并记得要手动删除断链节点的内存。
    若要给节点添加属性,可以这样:
xmlDocPtr
parseDoc(char *docname, char *uri) {
	xmlDocPtr doc;
	xmlNodePtr cur;
	xmlNodePtr newnode;
	xmlAttrPtr newattr;

	doc = xmlParseFile(docname);	
	if (doc == NULL ) {
		fprintf(stderr,"Document not parsed successfully. \n");
		return (NULL);
	}
	
	cur = xmlDocGetRootElement(doc);	
	if (cur == NULL) {
		fprintf(stderr,"empty document\n");
		xmlFreeDoc(doc);
		return (NULL);
	}
	
	if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
		fprintf(stderr,"document of the wrong type, root node != story");
		xmlFreeDoc(doc);
		return (NULL);
	}
	
	newnode = xmlNewTextChild(cur, NULL, "reference", NULL);
	newattr = xmlNewProp(newnode, "uri", uri);
	return(doc);
}
    我们用xmlAttrPtr声明一个属性指针。在找到story元素后,用xmlNewTextChild()新建一个reference子元素,用xmlNewProp()给这个子元素新建一个uri属性。文档修改完后要用xmlSaveFormatFile()写入到磁盘。
    查询属性的过程类似。如下:
void
getReference(xmlDocPtr doc, xmlNodePtr cur) {
	xmlChar *uri;
	cur = cur->xmlChildrenNode;
	while (cur != NULL) {
	    if ((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) {
		    uri = xmlGetProp(cur, "uri");
		    printf("uri: %s\n", uri);
		    xmlFree(uri);
	    }
	    cur = cur->next;
	}
	return;
}
    关键函数为xmlGetProp(),用来获取节点中的指定属性。注意如果你使用DTD为属性声明一个固定的或默认的值,则该函数也查找这些值。
     4、创建xml文档
    有了上面的基础,创建一个xml文档显得非常简单,就是一个不断插入节点的过程。其流程如下:
    (1)用xmlNewDoc函数创建一个文档指针doc;
    (2)用xmlNewNode函数创建一个节点指针root_node;
    (3)用xmlDocSetRootElement将root_node设置为doc的根结点;
    (4)用xmlAddChild()给root_node添加一系列的子节点,并设置子节点的内容和属性;
    (5)用xmlSaveFile将xml文档存入文件;
    (6)用xmlFreeDoc函数关闭文档指针,并清除本文档中所有节点动态申请的内存。    
    下面代码创建一个xml文档:
#include 
#include 
#include 
#include 
using namespace std;

int main(int argc, char* argv[]){
	//定义文档和节点指针
	xmlDocPtr doc=xmlNewDoc(BAD_CAST"1.0");
	xmlNodePtr root_node=xmlNewNode(NULL,BAD_CAST"root");
	//设置根节点
	xmlDocSetRootElement(doc,root_node);
	//在根节点中直接创建节点
	xmlNewTextChild(root_node, NULL, BAD_CAST"newNode1", BAD_CAST"newNode1 content");
	xmlNewTextChild(root_node, NULL, BAD_CAST"newNode2", BAD_CAST"newNode2 content");
	xmlNewTextChild(root_node, NULL, BAD_CAST"newNode3", BAD_CAST"newNode3 content");
	//创建一个节点,设置其内容和属性,然后加入根结点
	xmlNodePtr node=xmlNewNode(NULL, BAD_CAST"node2");
	xmlNodePtr content=xmlNewText(BAD_CAST"NODE CONTENT");
	xmlAddChild(root_node,node);
	xmlAddChild(node,content);
	xmlNewProp(node,BAD_CAST"attribute",BAD_CAST"yes");
	//创建一个儿子和孙子节点
	node=xmlNewNode(NULL,BAD_CAST"son");
	xmlAddChild(root_node,node);
	xmlNodePtr grandson=xmlNewNode(NULL,BAD_CAST"grandson");
	xmlAddChild(node,grandson);
	xmlAddChild(grandson,xmlNewText(BAD_CAST"This is a grandson node"));
	//存储xml文档
	int nRel=xmlSaveFile("CreatedXml.xml",doc);
	if(nRel!=-1){
		cout<<"一个xml文档被创建,写入"<
    编译并运行这个程序,将创建CreatedXml.xml文档,内容如下:

	newNode1 content
	newNode2 content
	newNode3 content
	NODE CONTENT
	
		This is a grandson node
	
    注意,有多种方式可以添加子节点。第一是用xmlNewTextChild直接添加一个文本子节点;第二是先创建新节点,然后用xmlAddChild将新节点加入上层节点。
     5、编码转换
    数据编码兼容性问题是很多开发人员都会遇到的一大难题,特别是在使用libxml时。libxml内部使用UTF-8格式存储和操作数据。你的应用程序数据如果使用其他格式的编码,例如ISO-8859-1编码,则在传给libxml之前必须转换成UTF-8格式。如果你的应用输出想用非UTF-8格式的编码,也需要进行转换。
    Libxml2本身只支持把UTF-8, UTF-16和ISO-8859-1格式的外部数据转换成内部使用的UTF-8格式,以及处理完后输出成这些格式的数据。对其他的字符编码,需要使用libiconv(当然你也可以使用其他的国际化库,例如ICU)。当前libiconv支持150多种不同的字符编码,libiconv的实现尽量保证支持所有我们听过的编码格式。在使用libxml之前,一般是通过libiconv把数据先转换UTF-8格式。在使用libxml处理完之后,再通过libiconv把数据输出成你要的编码格式。
    一个常见的错误是一份代码的不同部分的数据使用不同的编码格式。例如内部数据使用ISO-8859-1格式的应用程序,联合使用libxml,而它的内部数据格式为UTF-8。这样应用程序在运行不同的代码段时要不同地对待内部数据,这有可能导致解析数据出现错误。
     例子1:使用Libxml内建的编码处理器
    下面的例子创建一个简单的文档,添加从命令行得到的数据到文档根元素,并以合适的编码格式输出到stdout。对提供的数据我们使用ISO-8859-1编码,处理过程为从ISO-8859-1到UTF-8,再到ISO-8859-1。命令行上输入的字符串从ISO-8859-1格式转换成UTF-8格式,以供libxml使用,输出时又重新转换成ISO-8859-1格式。
#include 
#include 

/* 对指定编码格式的外部数据,转换成libxml使用UTF-8格式 */
unsigned char*
convert(unsigned char *in, char *encoding){
	unsigned char *out;
    int ret,size,out_size,temp;
	/* 定义一个编码处理器指针 */
    xmlCharEncodingHandlerPtr handler;

    size = (int)strlen((const char*)in)+1; /* 输入数据长度 */
    out_size = size*2-1; /* 输出数据长度 */
    out = (unsigned char*)malloc((size_t)out_size); /* 存放输出数据 */

    if (out) {
		/* 查找内建的编码处理器 */
        handler = xmlFindCharEncodingHandler(encoding);
        if(!handler) {
            free(out);
            out = NULL;
        }
    }
    if(out) {
        temp=size-1;
		/* 对输入数据进行编码转换 */
        ret = handler->input(out, &out_size, in, &temp);
        if(ret || temp-size+1) { /* 转换不成功 */
            if (ret) { /* 转换失败 */
                printf("conversion wasn't successful.\n");
            } else { /* 只转换了一部分数据 */
                printf("conversion wasn't successful. converted: %i octets.\n",temp);
            }
            free(out);
            out = NULL;
        }else { /* 转换成功 */
            out = (unsigned char*)realloc(out,out_size+1);
            out[out_size]=0; /* 输出的末尾加上null终止符 */
                        
        }
    } else {
        printf("no mem\n");
    }
    return (out);
}	

int
main(int argc, char **argv) {
	unsigned char *content, *out;
	xmlDocPtr doc;
	xmlNodePtr rootnode;
	char *encoding = "ISO-8859-1";
	
	if (argc <= 1) {
		printf("Usage: %s content\n", argv[0]);
		return(0);
	}

	content = (unsigned char*)argv[1];
	/* 转换成libxml2使用的UTF-8格式 */
	out = convert(content, encoding);
	doc = xmlNewDoc (BAD_CAST "1.0");
	rootnode = xmlNewDocNode(doc, NULL, (const xmlChar*)"root", out);
	xmlDocSetRootElement(doc, rootnode);
	/* 以ISO-8859-1格式输出文档内容 */
	xmlSaveFormatFileEnc("-", doc, encoding, 1);
	return (1);
}
    编译运行这个程序,假设在命令行上提供的数据"zhou"是ISO-8859-1格式(我的系统中不是),则输出文档为:

zhou
    编码转换的基本流程如下:
    (1)用xmlCharEncodingHandlerPtr定义一个编码处理器指针,用xmlFindCharEncodingHandler()查找libxml2中指定的编码处理器。libxml2内建只支持把UTF-8, UTF-16和ISO-8859-1格式的外部数据转换成内部使用的UTF-8格式。如果要转换其他格式的数据(如中文编码),则要使用独立的libiconv库给libxml2注册新编码处理器。
    (2)调用编码处理器的input()函数,把外部数据转换成libxml2使用的格式。
    (3)进行xml处理,处理完若要保存成非UTF-8格式的文档,使用xmlSaveFormatFileEnc()函数。若保存的编码格式libxml2不支持,则只能用libiconv把保存的文档转换成需要的编码格式。
     例子2:通过iconv库给Libxml注册新的编码处理器    
    下面例子先编写GBK的编码处理器gbk_input()和gbk_output(),前者是GBK到UTF-8输入处理,后者是UTF-8到GBK输出处理,这两个处理器都要用到iconv转换函数。然后调用xmlNewCharEncodingHandler()注册输入输出处理器。对输入输出数据的编码转换由convertToUTF8From()和utf8ConvertTo()来完成,它们都是调用xmlFindCharEncodingHandler()查找已注册的处理器,然后在处理器上调用input()或output()对数据进行编码转换。
#include 
#include 
#include 
#include 
#include 

/* 输入编码处理器:GBK到UTF-8 */
int gbk_input(unsigned char *out, int *outlen, 
		const unsigned char *in, int *inlen){

	char *outbuf = (char *) out;
	char *inbuf = (char *) in;
	iconv_t iconv_from; /* gbk到utf-8的转换描述符 */
	size_t len1, len2, rslt;
	/* 注意一般不直接从int*到size_t*的转换
	   这在32位平台下是正常的,但到了64平台下size_t为64位,
	   那(size_t*)inlen将是一个未知的数据 
	*/
	len1 = *inlen;
	len2 = *outlen;
	/* 分配一个从GBK到UTF-8的转换描述符 */
	iconv_from = iconv_open("utf-8","gbk");
	/* 根据转换描述符,对数据进行编码转换 */
	rslt = iconv(iconv_from, &inbuf, &len1, &outbuf, &len2);
	if(rslt < 0){
		return rslt;
	}
	iconv_close(iconv_from); /* 释放描述符 */
	*outlen = ((unsigned char *) outbuf - out);
	*inlen = ((unsigned char *) inbuf - in);
	return *outlen;
}

/* 输出编码处理器:UTF-8到GBK */
int gbk_output(unsigned char *out, int *outlen, 
				const unsigned char *in, int *inlen){

	char *outbuf = (char *) out;
	char *inbuf = (char *) in;
	iconv_t iconv_to; /* utf-8到gbk的转换描述符 */
	size_t len1, len2, rslt;
	/* 注意一般不直接从int*到size_t*的转换
	   这在32位平台下是正常的,但到了64平台下size_t为64位,
	   那(size_t*)inlen将是一个未知的数据 
	*/
	len1 = *inlen;
	len2 = *outlen;
	/* 分配一个从UTF-8到GBK的转换描述符 */
	iconv_to=iconv_open("gbk","utf-8");
	/* 根据转换描述符,对数据进行编码转换 */
	rslt = iconv(iconv_to, &inbuf, &len1, &outbuf, &len2);
	if(rslt < 0){
		return rslt;
	}
	iconv_close(iconv_to); /* 释放描述符 */
	*outlen = ((unsigned char *) outbuf - out);
	*inlen = ((unsigned char *) inbuf - in);
	return *outlen;
}

/**
 * convertToUTF8From:
 * 把encoding编码的输入数据in转换成utf-8格式返回
 * 出错则返回NULL
 */
xmlChar *convertToUTF8From(const char *in, const char *encoding){
    xmlChar *out;
    int ret;
    int size;
    int out_size;
    int temp;
    xmlCharEncodingHandlerPtr handler;
    if (in == 0)
		return 0;
	/* 查找内建的编码处理器 */
    handler = xmlFindCharEncodingHandler(encoding);
    if (!handler) {
        printf("convertToUTF8From: no encoding handler found for '%s'\n",
               encoding ? encoding : "");
        return 0;
    }
    size = (int)strlen(in) + 1;  /* 输入数据长度 */
    out_size = size*2 - 1;  /* 输出数据长度 */
	/* 存放输出数据 */
    out = (unsigned char *) xmlMalloc((size_t) out_size);
	memset(out, 0, out_size);

    if(out != NULL) {
        temp = size - 1;
		/* 对输入数据进行编码转换,成功后返回0 */
        ret = handler->input(out, &out_size, (const xmlChar *) in, &temp);
        if(ret || temp - size + 1) {  /* 转换不成功 */
            if(ret){  /* 转换失败 */
                printf("convertToUTF8From: conversion wasn't successful.\n");
            }else{  /* 只转换了一部分数据 */
                printf("convertToUTF8From: conversion wasn't successful. converted: %i octets.\n", temp);
            }
            xmlFree(out); /* 释放输出缓冲区 */
            out = 0;
        }else{  /* 转换成功,在输出末尾加上null终止符 */
            out = (unsigned char *) xmlRealloc(out, out_size + 1);
            out[out_size] = 0;
        }
    } else {
        printf("convertToUTF8From: no mem\n");
    }
    return out;
}

/**
 * utf8ConvertTo:
 * 把utf-8的数据转换成encoding编码返回
 * 出错则返回NULL
 */
char *utf8ConvertTo(xmlChar *in, const char *encoding){
    char *out;
    int ret;
    int size;
    int out_size;
    int temp;
    xmlCharEncodingHandlerPtr handler;

    if (in == 0)
        return 0;

    handler = xmlFindCharEncodingHandler(encoding);

    if (!handler) {
        printf("utf8ConvertTo: no encoding handler found for '%s'\n",
               encoding ? encoding : "");
        return 0;
    }

    size = (int) strlen((char*)in) + 1;  /* 输入数据长度 */
    out_size = size * 2 - 1;  /* 输出数据长度 */
    out = (char *) malloc((size_t) out_size);  /* 存放输出数据 */
	memset(out,0,out_size);
    if(out != NULL) {
        temp = size - 1;
		/* 对输入数据进行编码转换,成功后返回0 */
        ret = handler->output((xmlChar*)out, &out_size, (const xmlChar *) in, &temp);
        if(ret || temp - size + 1){
            if(ret){
                printf("utf8ConvertTo: conversion wasn't successful.\n");
            }else{
                printf("utf8ConvertTo: conversion wasn't successful. converted: %i octets.\n", temp);
            }
            free(out);
            out = 0;
        }else{
            out = (char *) realloc(out, out_size + 1);
            out[out_size] = 0;  /* 末尾加上null终止符 */
        }
    }else{
        printf("utf8ConvertTo: no mem\n");
    }

    return out;
}

int main(int argc, char **argv){
	const char *content;
	xmlChar *out;
	xmlDocPtr doc;
	xmlNodePtr rootnode;
	
	if (argc <= 1) {
		printf("Usage: %s content\n", argv[0]);
		return(0);
	}
	content = (const char*)argv[1];

	/* 添加gbk编码支持 */
	xmlNewCharEncodingHandler("gbk", gbk_input, gbk_output);
	/* 添加gb2312编码支持:仍然可以使用GBK的输入输出处理器 */
	xmlNewCharEncodingHandler("gb2312", gbk_input, gbk_output);

	/* 输入的GBK数据转换成libxml2使用的UTF-8格式 */
	out = convertToUTF8From(content, "gbk");
	/* 创建xml文档 */
	doc = xmlNewDoc(BAD_CAST "1.0");
	rootnode = xmlNewDocNode(doc, NULL, (const xmlChar*)"root", out);
	xmlDocSetRootElement(doc, rootnode);
	/* 以gb2312格式保存文档内容:"-"表示输出到终端 */
	xmlSaveFormatFileEnc("-", doc, "gb2312", 1);
	
	xmlCleanupCharEncodingHandlers();/* 释放编码处理器资源 */
	return (1);
}
    这个例子在32位与64位Linux平台下测试通过。iconv库是Linux默认自带的组件,因此在Linux中使用libxml非常方便。我们先建立utf-8编码与gbk编码的转换接口,并将接口插入到libxml2库中,这样xml库就支持对gb2312和gbk编码的支持了。当然,这个转换不会自动完成,我们需要使用从libxml库中查找特定编码的接口,libxml支持一些基本的编码接口,如ISO-8859-1,UTF-16等编码,但不支持gbk,所以在上述代码中,我们定义了gbk_input,与gbk_output两个接口,这两个接口的原型声明是libxml库的标准声明,即xmlCharEncodingInputFunc和xmlCharEncodingOutputFunc。在使用完libxml库之后,我们需要释放libxml库的转换资源。
     例子3:直接使用iconv库进行转换
    下面例子直接使用iconv函数对输入输出进行编码转换,而不是通过注册编码处理器的方式。
#include 
#include 
#include 
#include 
#include 

/* 代码转换:从一种编码转为另一种编码 */
int encoding_convert(const char *from_charset, const char *to_charset, 
			char *inbuf, int inlen, 
			char* outbuf, int outlen){

    iconv_t cd;
	size_t len1, len2, rslt;

	/* 注意一般不直接从int*到size_t*的转换
	   这在32位平台下是正常的,但到了64平台下size_t为64位,
	   那(size_t*)inlen将是一个未知的数据 
	*/
	len1 = inlen;
	len2 = outlen;
	/* 分配一个转换描述符 */
    cd = iconv_open(to_charset,from_charset);
    if(cd == 0)
       return -1;
    memset(outbuf,0,len2); 
	/* 执行编码转换 */
    rslt=iconv(cd, &inbuf, &len1, &outbuf, &len2);
    if(rslt== -1)
		return -1;  

    iconv_close(cd); /* 释放描述符 */
    return 0;  

}

/* GB2312转换为UTF-8 
 * 成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL
 */
char *gb2312_utf8(char *inbuf){
	int nOutLen = 2*strlen(inbuf)-1;
	char *szOut=(char*)xmlMalloc(nOutLen);
	if(-1 == encoding_convert("gb2312","uft-8",inbuf,strlen(inbuf),szOut,nOutLen)){
		xmlFree(szOut);
		szOut=NULL;
	}
	return szOut;
}

/* UTF-8转换为GB2312
 * 成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL
 */
char *utf8_gb2312(char *inbuf){
	int nOutLen = 2* strlen(inbuf)-1;
	char *szOut=(char*)xmlMalloc(nOutLen);
	if(-1 == encoding_convert("utf-8","gb2312",inbuf,strlen(inbuf),szOut,nOutLen)){
		xmlFree(szOut);
		szOut=NULL;
	}
	return szOut;
}

int main(int argc, char **argv){
	/* 定义文档节点和指针 */
	xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
	xmlNodePtr root_node=xmlNewNode(NULL, BAD_CAST "root");
	/* 设置根节点 */
	xmlDocSetRootElement(doc, root_node);

	/* 一个中文字符串转换为UTF-8字符串,然后写入 */
	char *szOut=gb2312_utf8("节点1的内容");
	/* 在根节点中直接创建节点 */
	xmlNewTextChild(root_node, NULL, BAD_CAST "newNode1", BAD_CAST "newNode1 content");
	xmlNewTextChild(root_node, NULL, BAD_CAST "newNode2", BAD_CAST "newNode2 content");
    xmlNewTextChild(root_node, NULL, BAD_CAST "newNode3", BAD_CAST "newNode3 content");
	xmlNewChild(root_node, NULL, BAD_CAST "node1",BAD_CAST szOut);
	xmlFree(szOut);

	/* 创建一个节点,设置其内容和属性,然后加入根结点 */
    xmlNodePtr node = xmlNewNode(NULL,BAD_CAST "node2");
    xmlNodePtr content = xmlNewText(BAD_CAST "NODE CONTENT");
    xmlAddChild(root_node,node);
    xmlAddChild(node,content);
    szOut = gb2312_utf8("属性值");
    xmlNewProp(node,BAD_CAST "attribute",BAD_CAST szOut);
    xmlFree(szOut);

	/* 创建一个中文节点 */
    szOut = gb2312_utf8("中文节点");
    xmlNewChild(root_node, NULL, BAD_CAST szOut,BAD_CAST "content of chinese node");
    xmlFree(szOut);

    /* 存储xml文档 */
    int nRel = xmlSaveFormatFileEnc("CreatedXml_cn.xml",doc,"GB2312",1);
    if (nRel != -1){
		printf("一个xml文档被创建,写入%d个字节", nRel);
    }

    xmlFreeDoc(doc);
    return 1;
}
    这个例子中,当把中文数据写入到XML节点时,使用gb2312_utf8()直接转换成UTF-8格式,这种直接通过iconv转换的方式更高效。编译并运行程序,输出文档如下:


	newNode1 content
	newNode2 content
	newNode3 content
	节点1的内容
	NODE CONTENT
	<中文节点>content of chinese node
	
    6、一个真实的例子
    内容整理自 http://xmlsoft.org/example.html。
    下面是一个真实的例子。应用程序数据的内容不使用DOM树,而是使用内部数据结构来保存。这是一个基于XML存储结构的数据库,它保存了与Gnome相关的任务。如下:


  

    
      
      GBackup
      Development

      
        Open
        Mon, 07 Jun 1999 20:27:45 -0400 MET DST
        USD 0.00
      

      
        
        
      

      
        Nathan Clemons
        [email protected]
        
        
        
        
        
        
        
        
        
        
      

      
      The program should be released as free software, under the GPL.
      

      
      

      
      A GNOME based system that will allow a superuser to configure 
      compressed and uncompressed files and/or file systems to be backed 
      up with a supported media in the system.  This should be able to 
      perform via find commands generating a list of files that are passed 
      to tar, dd, cpio, cp, gzip, etc., to be directed to the tape machine 
      or via operations performed on the filesystem itself. Email 
      notification and GUI status display very important.
      

    

  
    把XML文件加载到一个内部DOM树中只是调用几个函数的问题,而遍历整个树来收集数据,并生成内部结构则更困难,也更容易出错。
    对输入结构的定义法则是非常宽松的。属性的顺序无关紧要(XML规范清楚地说明了这一点),不要依赖于一个节点的子节点顺序通常是一个好的主意,除非这样做真的使事情变得更困难了。下面是解析person信息的一段代码:
/*
 * 一个person记录
 */
typedef struct person {
    char *name;
    char *email;
    char *company;
    char *organisation;
    char *smail;
    char *webPage;
    char *phone;
} person, *personPtr;

/*
 * 解析person的代码
 */
personPtr parsePerson(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur) {
    personPtr ret = NULL;

DEBUG("parsePerson\n");
    /*
     * 为结构分配内存
     */
    ret = (personPtr) malloc(sizeof(person));
    if (ret == NULL) {
        fprintf(stderr,"out of memory\n");
        return(NULL);
    }
    memset(ret, 0, sizeof(person));

    /* 我们不关心顶层的元素名是什么 */
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
        if ((!strcmp(cur->name, "Person")) && (cur->ns == ns))
            ret->name = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
        if ((!strcmp(cur->name, "Email")) && (cur->ns == ns))
            ret->email = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
        cur = cur->next;
    }

    return(ret);
}
    下面是要注意的一些事项:
    (1)通常一个递归的解析风格是更方便的:XML数据天然地遵循重复式地构造,并且是高度结构化的。
    (2)两个参数是xmlDocPtr和xmlNsPtr类型,即指向XML文档和应用程序保留的命名空间的指针。文档信息非常广泛,为你的应用程序数据集定义一个命名空间并测试元素和属性是否属性这个空间是一个好的编程实践。这只需一个简单的相等测试(cur->ns == ns)。
    (3)为了查询文本和属性值,你可以使用函数xmlNodeListGetString()来获取所有文本,和由DOM输出生成的引用节点,并生成一个单一的文本字符串。
    下面是解析另外一个结构的代码片段:
#include 
/*
 * 一个Job的描述
 */
typedef struct job {
    char *projectID;
    char *application;
    char *category;
    personPtr contact;
    int nbDevelopers;
    personPtr developers[100]; /* using dynamic alloc is left as an exercise */
} job, *jobPtr;

/*
 * 解析Job的代码
 */
jobPtr parseJob(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur) {
    jobPtr ret = NULL;

DEBUG("parseJob\n");
    /*
     * 为结构分配内存
     */
    ret = (jobPtr) malloc(sizeof(job));
    if (ret == NULL) {
        fprintf(stderr,"out of memory\n");
        return(NULL);
    }
    memset(ret, 0, sizeof(job));

    /* 我们不关心顶层元素名是什么 */
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
        
        if ((!strcmp(cur->name, "Project")) && (cur->ns == ns)) {
            ret->projectID = xmlGetProp(cur, "ID");
            if (ret->projectID == NULL) {
                fprintf(stderr, "Project has no ID\n");
            }
        }
        if ((!strcmp(cur->name, "Application")) && (cur->ns == ns))
            ret->application = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
        if ((!strcmp(cur->name, "Category")) && (cur->ns == ns))
            ret->category = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
        if ((!strcmp(cur->name, "Contact")) && (cur->ns == ns))
            ret->contact = parsePerson(doc, ns, cur);
        cur = cur->next;
    }

    return(ret);
}

    一旦你会使用libxml2,编写这种类型的代码是非常简单的,也很无趣。最终,你可以写一个拥有C数据结构和一组XML文档例子或一个XML DTD的桩模块,并生成在C数据和XML存储之间导入和导出数据的代码。

    7、详细代码示例
    对Libxml2更详细的使用介绍,可参考官方的详细代码示例http://xmlsoft.org/examples/index.html。上面提供了Libxml2各个组件怎么使用的详细代码示例,包括以下部分:
    xmlWriter: 测试xmlWriter的各个API,包括写入到文件、写入到内存缓冲区、写入到新的文档或子树、字符串编码转换、对输出文档进行序列化。
    InputOutput: 演示使用xmlRegisterInputCallbacks来建立一个客户I/O层,这被用在XInclude方法上下文中,以显示怎样构建动态文档。还演示使用xmlDocDumpMemory来输出文档到字符缓冲区中。
    Parsing: 演示使用xmlReadMemory()读取XML文档,xmlFreeDoc()释放文档树;使用xmlCreatePushParserCtxt()和xmlParseChunk()一块一块地读取XML文档到文档树中。演示为XML文档创建一个解析上下文,然后解析并验证这个文档;创建一个文档树,检查并验证结果,最后用xmlFreeDoc()释放文档树。演示使用xmlReadFile()读取XML文档并用xmlFreeDoc()释放它。
    Tree: 演示怎样创建文档和节点,并把数据dump到标准输出或文件中。演示使用xmlDocGetRootElement()获取根元素,然后遍历文档并打印各个元素名。
    XPath: 演示怎样计算XPath表达式,并在XPath上下文注册名称空间,打印结果节点集。演示怎么加载一个文档、用XPath定位到某个子元素、修改这个元素并保存结果。这包含了加载/编辑/保存的一个完整来回。
    xmlReader: 演示使用xmlReaderForFile()解析XML文档,并dump出节点的信息。演示在用xmlReaderForFile()解析时验证文档的内容,激活各种选项,诸如实体替换、DTD属性不一致等。演示使用xmlTextReaderPreservePattern()提取XML文档中某一部分的子文档。演示重用xmlReader对象来解析多个XML文档。

你可能感兴趣的:(开源库探索系列)