使用XML技术可以方便的完成数据文件的存储及解析读取,其格式化、解析过程由XML引擎完成,在Windows平台上可使用MSXML引擎,在Linux环境下可使用libxml2库完成操作。本文简要整理了在Linux环境下使用libxml2进行XML操作的C语言编程方式,包括文件创建、读写操作。
Libxml2库提供DOM、SAX操作接口,也实现了DTD、Scheme方式的验证,支持XPath语法查询,加上其稳定性及可移植性,满足了一般项目的要求,有关Libxml2具体介绍可参考【1】。
1. 环境初始化
使用Libxml2库,应确保在编译环境中(尤其对于客户机编译安装的分发软件)需要相关头文件及链接库,则可以通过Libxml2预定义宏确认:
- #include <LIBXML parser.h>
- #include <LIBXML tree.h>
- #include <LIBXML encoding.h>
- #include <LIBXML xmlwriter.h>
- #include <LIBXML xmlreader.h>
- #include <LIBXML xpath.h>
- #include <LIBXML xpathInternals.h>
- #if !(defined(LIBXML_WRITER_ENABLED) && defined(LIBXML_OUTPUT_ENABLED) /
- && defined LIBXML_READER_ENABLED)
- #error "cannot use xml lib"
- #endif
#include #include #include #include #include #include #include #if !(defined(LIBXML_WRITER_ENABLED) && defined(LIBXML_OUTPUT_ENABLED) / && defined LIBXML_READER_ENABLED) #error "cannot use xml lib" #endif
使用Libxml2进行XML文件解析,则初始化解析环境,使其分配相应资源、设定变量。在实验中,省略该步骤并未影响操作结果,但在内存检查中会出现警告,可能导致未预期结果。
- int prj_xml_init()
- {
- xmlInitParser();
- LIBXML_TEST_VERSION
- return 0;
- }
int prj_xml_init() { xmlInitParser(); LIBXML_TEST_VERSION return 0; }
2. 写入XML文件
XML文件为树形组织结构,以惟一根元素开始,层次式记录各属性、元素等信息,例如下文件为描述一目录树结构:
- <?xml version="1.0" encoding="ISO-8859-1"?>
- <FOLDER_ROOT>
- <FOLDER name="Folder1" attrib="rw">
- <FILE name="FileA" />
- </FOLDER>
- <FOLDER name="Folder2" attrib="rw" />
- </FOLDER_ROOT>
Libxml2使用XML文件指针操作XML文件,使用XML节点指针操作节点,可使用xmlNewDoc()和xmlDocSetRootElement()创建,使用xmlSaveFileEnc保存,例如:
- #define PRJ_XML_ENCODING "ISO-8859-1" /* 编码方式 */
- #define PRJ_XML_FILEPATH "/home/prj/prjconf.xml" /* 文件路径 */
- #define PRJ_XML_ROOT "FOLDER_ROOT" /* 根节点标签 */
- static xmlDocPtr pxmldoc = NULL; /*** XML数据文件文档实例 ***/
- static xmlNodePtr pxmlroot = NULL; /*** XML数据文件根节点 ***/
- int prj_xml_writefile()
- {
- /*新建文件 */
- pxmldoc = xmlNewDoc(BAD_CAST XML_DEFAULT_VERSION);
- if (NULL == pxmldoc)
- {
- return -1;
- }
- /* 构造xml文件句柄 */
- pxmlroot = xmlNewDocNode(pxmldoc, NULL, BAD_CAST PRJ_XML_ROOT, NULL);
- if (NULL == pxmlroot)
- {
- /* 失败时清理资源 */
- xmlFreeDoc(pxmldoc);
- pxmldoc = NULL;
- return -1;
- }
- /* 构造xml根节点 */
- (void)xmlDocSetRootElement(pxmldoc, pxmlroot);
- /* 保存新XML数据文件 */
- (void)xmlSaveFileEnc(PRJ_XML_PATH, pxmldoc, PRJ_XML_ENCODING);
- }
#define PRJ_XML_ENCODING "ISO-8859-1" /* 编码方式 */ #define PRJ_XML_FILEPATH "/home/prj/prjconf.xml" /* 文件路径 */ #define PRJ_XML_ROOT "FOLDER_ROOT" /* 根节点标签 */ static xmlDocPtr pxmldoc = NULL; /*** XML数据文件文档实例 ***/ static xmlNodePtr pxmlroot = NULL; /*** XML数据文件根节点 ***/ int prj_xml_writefile() { /*新建文件 */ pxmldoc = xmlNewDoc(BAD_CAST XML_DEFAULT_VERSION); if (NULL == pxmldoc) { return -1; } /* 构造xml文件句柄 */ pxmlroot = xmlNewDocNode(pxmldoc, NULL, BAD_CAST PRJ_XML_ROOT, NULL); if (NULL == pxmlroot) { /* 失败时清理资源 */ xmlFreeDoc(pxmldoc); pxmldoc = NULL; return -1; } /* 构造xml根节点 */ (void)xmlDocSetRootElement(pxmldoc, pxmlroot); /* 保存新XML数据文件 */ (void)xmlSaveFileEnc(PRJ_XML_PATH, pxmldoc, PRJ_XML_ENCODING); }
3. 写入节点信息
在XML文件树中创建子节点并写入节点信息,需要指明该节点的标签及父节点,使用xmlNewChild()创建;增加、修改属性信息使用xmlNewProp()和xmlSetProp(),例如:
- #define PRJ_XML_FOLDER " FOLDER" /* Folder节点标签 */
- #define PRJ_XML_FILE "FILE" /* File节点标签 */
- int prj_xml_newfolder() /* 增加Folder节点 */
- {
- xmlNode *pnode = NULL;
- pnode = xmlNewChild(pxmlroot,NULL, BAD_CAST PRJ_XML_FOLDER, NULL);
- if (NULL == pnode)
- {
- return NULL;
- }
- /* 增加属性 */
- (void)xmlNewProp(pnode, BAD_CAST "name", BAD_CAST "");
- (void)xmlNewProp(pnode, BAD_CAST "attrib", BAD_CAST "");
- return pnode;
- }
- int prj_xml_setprop(xmlNode *pnode, const char *attrib, const char *value) /* 修改属性 */
- {
- return xmlSetProp(pnode, BAD_CAST attrib, BAD_CAST value);
- }
#define PRJ_XML_FOLDER " FOLDER" /* Folder节点标签 */ #define PRJ_XML_FILE "FILE" /* File节点标签 */ int prj_xml_newfolder() /* 增加Folder节点 */ { xmlNode *pnode = NULL; pnode = xmlNewChild(pxmlroot,NULL, BAD_CAST PRJ_XML_FOLDER, NULL); if (NULL == pnode) { return NULL; } /* 增加属性 */ (void)xmlNewProp(pnode, BAD_CAST "name", BAD_CAST ""); (void)xmlNewProp(pnode, BAD_CAST "attrib", BAD_CAST ""); return pnode; } int prj_xml_setprop(xmlNode *pnode, const char *attrib, const char *value) /* 修改属性 */ { return xmlSetProp(pnode, BAD_CAST attrib, BAD_CAST value); }
4. 读取节点信息
Libxml2使用一个树型结构组织XML文件的全部信息,包括节点的子节点、属性等,则可以通过直接使用节点指针读取信息,但不是个通用方式。使用xmlReader和xmlWriter模块可完成大量读写操作,可参考【2】。
对于基本的的读取,可使用xmlGetProp()完成,它返回一个xmlChar类型的字符数组包含属性信息,在使用完成后手动释放资源,例如:
- int prj_xml_readprop(xmlNode *pnode, const char *attrib) /* 读取属性 */
- {
- xmlChar *strres = NULL;
- strres = xmlGetProp(pnode, BAD_CAST attrib);
- if (NULL == strres)
- {
- return -1;
- }
- /* 其他操作 */
- /* 清理资源 */
- xmlFree(strres);
- return 0;
- }
int prj_xml_readprop(xmlNode *pnode, const char *attrib) /* 读取属性 */ { xmlChar *strres = NULL; strres = xmlGetProp(pnode, BAD_CAST attrib); if (NULL == strres) { return -1; } /* 其他操作 */ /* 清理资源 */ xmlFree(strres); return 0; }
5. 使用XPath查询节点集
使用XPath语法可以表示特定的节点集,执行XPath语句则完成了查找特定节点集的功能。基本流程为:生成XPath查询字符串、初始化XPath查询环境、执行查询获得结果集、操作结果集、清理XPath查询环境。
XPath语法有丰富的路径表达方式、运算符及运算函数,可以完成复杂的节点查询。例如:
查询某Folder节点pnode下的所有文件名以”File”开头的文件节点。使用GetNodePath()得到pnode的路径字符串,使用start-with函数查询。
- char expr[64]={0}; /* 存放XPath语句 */
- xmlChar *npath;
- npath = xmlGetNodePath(pnode); /* 得到pnode的路径字符串 */
- (void)snprintf(expr, 64, "%s/%s[starts-with(@path,'%s/')]",
- (const char *)npath, PRJ_XML_FILE, "File");
- xmlFree(npath);
char expr[64]={0}; /* 存放XPath语句 */ xmlChar *npath; npath = xmlGetNodePath(pnode); /* 得到pnode的路径字符串 */ (void)snprintf(expr, 64, "%s/%s[starts-with(@path,'%s/')]", (const char *)npath, PRJ_XML_FILE, "File"); xmlFree(npath);
又如查询名为”Folder2”的文件夹,但在Windows上名称对大小写不敏感,则可以使用lower-case()函数将节点属性转为小写再比较。对于Libxml2,暂未支持这个在xml2.0定义的函数,因此需要translat()函数代替,自然有性能的消耗:
FOLDER_ROOT/FOLDER[translate(@name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')='Folder2']
生成XPath语句后,则可进行查询得到查询结果集,集合可能含有0、1或多个元素,然后进行具体操作:
- int prj_xml_xpath_evaluate(const xmlChar* expr, xmlNodePtr *ppnode)
- {
- xmlXPathContextPtr pctx = NULL;
- xmlXPathObjectPtr pobj = NULL;
- if (NULL == expr)
- {
- return -1;
- }
- pctx = xmlXPathNewContext(pxmldoc); /* 初始化XPath查询环境 */
- if (NULL == pctx)
- {
- return -1;
- }
- pobj = xmlXPathEvalExpression(BAD_CAST expr,pctx); /* 执行查询 */
- if (NULL == pobj)
- {
- xmlXPathFreeContext(pctx);
- return -1;
- }
- if (0 == pobj->nodesetval->nodeNr) /* 结果集为空 */
- {
- /* 其他操作 */
- }
- else
- {
- /* 其他操作 */
- }
- xmlXPathFreeObject(pobj); /* 清理XPath查询环境 */
- xmlXPathFreeContext(pctx);
- return 0;
- }
int prj_xml_xpath_evaluate(const xmlChar* expr, xmlNodePtr *ppnode) { xmlXPathContextPtr pctx = NULL; xmlXPathObjectPtr pobj = NULL; if (NULL == expr) { return -1; } pctx = xmlXPathNewContext(pxmldoc); /* 初始化XPath查询环境 */ if (NULL == pctx) { return -1; } pobj = xmlXPathEvalExpression(BAD_CAST expr,pctx); /* 执行查询 */ if (NULL == pobj) { xmlXPathFreeContext(pctx); return -1; } if (0 == pobj->nodesetval->nodeNr) /* 结果集为空 */ { /* 其他操作 */ } else { /* 其他操作 */ } xmlXPathFreeObject(pobj); /* 清理XPath查询环境 */ xmlXPathFreeContext(pctx); return 0; }
6. 环境清理
在完成XML文件操作后,需进行资源清理。清理前保证修改信息写入文件,可使用上文提到xmlSaveFileEnc()完成。清理环境操作如:
- int32 prj_xml_clear()
- {
- if (NULL != pxmldoc)
- {
- /* 清理文档链表资源 */
- xmlFreeDoc(pxmldoc);
- pxmldoc = NULL;
- pxmlroot = NULL;
- }
- /* 通知xml解析结束 */
- xmlCleanupParser();
- return 0;
- }
int32 prj_xml_clear() { if (NULL != pxmldoc) { /* 清理文档链表资源 */ xmlFreeDoc(pxmldoc); pxmldoc = NULL; pxmlroot = NULL; } /* 通知xml解析结束 */ xmlCleanupParser(); return 0; }
以上是在Linux环境是使用Libxml2库完成XML操作的基本方式,完成复杂、具体需求的操作也可以通过参考资源信息进行实验完成。
Stone&Ice
From: http://blog.youkuaiyun.com/stoneandice
参考资源:
1. http://xmlsoft.org/ Libxml2官方网站
2. http://www.w3school.com.cn/x.asp W3school的XML参考手册