libxml2库解析带命名空间的xml文件

libxml2库使用xpath解析带命名空间的xml文件

前言

由于项目需求需要解析一些xml文件,于是开始接触libxml2相关知识。但是在解析带命名空间的xml文件时,折腾了很久。查阅网上资料,也很少有详细说明解析方法的,因此在此处做记录,希望能帮助到有相同困境的人。
ps:环境搭建看这篇文章Linux libxml2源码编译教程

预备知识

libxml2的测试xml文件

使用的测试xml文件如下:

<?xml version='1.0' encoding="UTF-8"?>
<bookstore xmlns="urn:newbooks-schema">
	<book genre="novel" style="hardcover">
		<title>The Handmaid's Tale</title>
		<author>
			<first-name>Margaret</first-name>
			<last-name>Atwood</last-name>
		</author>
		<price>19.95</price>
	</book>
	<book genre="novel" style="other">
		<title>The Poisonwood Bible</title>
		<author>
			<first-name>Barbara</first-name>
			<last-name>Kingsolver</last-name>
		</author>
		<price>11.99</price>
	</book>
</bookstore>

libxml2中重要的结构体

  1. xml节点结构体定义,构建了一个树和链表混合的数据结构,同一级别的xml节点使用链表链接,xml节点的子节点使用二叉树链接:
    struct _xmlNode {
        void *	_private	: application data
        xmlElementType	type	: type number, must be second !
        const xmlChar *	name	: the name of the node, or the entity
        struct _xmlNode *	children	: parent->childs link
        struct _xmlNode *	last	: last child link
        struct _xmlNode *	parent	: child->parent link
        struct _xmlNode *	next	: next sibling link
        struct _xmlNode *	prev	: previous sibling link
        struct _xmlDoc *	doc	: the containing document End of common part
        xmlNs *	ns	: pointer to the associated namespace
        xmlChar *	content	: the content
        struct _xmlAttr *	properties	: properties list
        xmlNs *	nsDef	: namespace definitions on this node
        void *	psvi	: for type/PSVI information
        unsigned short	line	: line number
        unsigned short	extra	: extra data for XPath/XSLT
    } xmlNode;
    
  2. xml节点类型定义,我目前用的比较多的主要是XML_ELEMENT_NODE XML_TEXT_NODE ,text节点的context中包含element节点的数据内容,attribute节点是干嘛的目前还不清楚。。。
    enum xmlElementType {
        XML_ELEMENT_NODE = 1
        XML_ATTRIBUTE_NODE = 2
        XML_TEXT_NODE = 3
        XML_CDATA_SECTION_NODE = 4
        XML_ENTITY_REF_NODE = 5
        XML_ENTITY_NODE = 6 /* unused */
        XML_PI_NODE = 7
        XML_COMMENT_NODE = 8
        XML_DOCUMENT_NODE = 9
        XML_DOCUMENT_TYPE_NODE = 10 /* unused */
        XML_DOCUMENT_FRAG_NODE = 11
        XML_NOTATION_NODE = 12 /* unused */
        XML_HTML_DOCUMENT_NODE = 13
        XML_DTD_NODE = 14
        XML_ELEMENT_DECL = 15
        XML_ATTRIBUTE_DECL = 16
        XML_ENTITY_DECL = 17
        XML_NAMESPACE_DECL = 18
        XML_XINCLUDE_START = 19
        XML_XINCLUDE_END = 20 /*  XML_DOCB_DOCUMENT_NODE= 21 removed */
    };
    

libxml2解析xml文件后的数据结构

原本打算自己画的,结果看官网的时候发现官方已经提供很详细的说明,更多信息查看官网吧,仅把结构体图搬运过来,此处是原文链接
在这里插入图片描述

正文

不能使用xpath定位带命名空间的xml文件原因

  1. 在加载xml文件时,未将注册对应的xmlns和prefix
    • 在载入xml生成上下文的时候,需要给定一个命名空间,在后续使用xpath查询的时候,使用这个命名空间查询。
  2. 注册xmlns和prefix后,在使用xpath查询时,未指定对应的xmlns ,请参照官方例程/libxml2-master/example/xpath1.c
    此处仅截取注册xmlns函数,其关键函数是xmlXPathRegisterNs(),其余部分都是解析输入的nsList,得到对应的namespaceprefix(就是你对命名空间的简称):
    /**
     * register_namespaces:
     * @xpathCtx:		the pointer to an XPath context.
     * @nsList:		the list of known namespaces in 
     *			"<prefix1>=<href1> <prefix2>=href2> ..." format.
     *
     * Registers namespaces from @nsList in @xpathCtx.
     *
     * Returns 0 on success and a negative value otherwise.
     */
    int 
    register_namespaces(xmlXPathContextPtr xpathCtx, const xmlChar* nsList) {
        xmlChar* nsListDup;
        xmlChar* prefix;
        xmlChar* href;
        xmlChar* next;
        
        assert(xpathCtx);
        assert(nsList);
    
        nsListDup = xmlStrdup(nsList);
        if(nsListDup == NULL) {
    	fprintf(stderr, "Error: unable to strdup namespaces list\n");
    	return(-1);	
        }
        
        next = nsListDup; 
        while(next != NULL) {
    	/* skip spaces */
    	while((*next) == ' ') next++;
    	if((*next) == '\0') break;
    
    	/* find prefix */
    	prefix = next;
    	next = (xmlChar*)xmlStrchr(next, '=');
    	if(next == NULL) {
    	    fprintf(stderr,"Error: invalid namespaces list format\n");
    	    xmlFree(nsListDup);
    	    return(-1);	
    	}
    	*(next++) = '\0';	
    	
    	/* find href */
    	href = next;
    	next = (xmlChar*)xmlStrchr(next, ' ');
    	if(next != NULL) {
    	    *(next++) = '\0';	
    	}
    
    	/* do register namespace */
    	if(xmlXPathRegisterNs(xpathCtx, prefix, href) != 0) {
    	    fprintf(stderr,"Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", prefix, href);
    	    xmlFree(nsListDup);
    	    return(-1);	
    	}
        }
        
        xmlFree(nsListDup);
        return(0);
    }
    
    
编码验证
  1. 编译测试代码:
    gcc -Wall -g ./xpath1.c -o ./xpath1 -lxml2
    在这里插入图片描述

  2. 运行测试

    1. 不带prefix的xpath查询: 在这里插入图片描述
    2. 带prefix的xpath查询: 在这里插入图片描述
  • 示例中的xmlns是我随便起的命名空间简称
  • xmlns=urn:newbooks-schema是将xml文件中的urn:newbooks-schema命名空间,自定义为xmlns,这样我们后续就可以使用这个简化后的xmlns做查询了
  • 当你使用xpath定位带命名空间的xml文件时,xpath的每个表达式都需要指定prefix: 在这里插入图片描述

拓展例程

如何递归打印xmlNode信息

在官方例程中,只提示找到了xpath标定的节点,但是并未打印出具体节点的内容。因此根据解析后的xmlNode数据组织方式(树和链表的混合结构),在原有例程上进行一定修改,支持递归打印元素节点中所有元素数值,其核心函数是xmlNodeGetContent()xmlChildElementCount()

/**
 * parsing_node_data
 * @notes Pass in an xmlNodePtr,
 *      recursively parse all its child nodes , and output the values
 *      contained in each element node
 */
void printf_table(int recursive_depth)
{
    for(int i=0; i< recursive_depth ; i++)
    {
        printf("\t");
    }
}

/*Tree-linking Table traversal*/
void parsing_node_data(xmlNodePtr node)
{
    static int recursive_depth=1;

    if(xmlChildElementCount(node) >0 )
    {
        printf("Tree traversal: current Node %s is element Node, have %ld subNode \n ", 
            node->name , xmlChildElementCount(node));             
        /*get cur node's children node */
        xmlNodePtr child_node = node->children; 
        recursive_depth++;
        parsing_node_data(child_node);     
    }
    else
    {
        int j = 0;
        while( j < xmlChildElementCount(node->parent) )
        { 
            //printf("Get my parent elementCount %ld \n", xmlChildElementCount(node->parent));
            if(xmlChildElementCount(node) > 0 )
            {
                printf("Link traversal: current Node %s is element Node, have %ld subNode \n", 
                    node->name , xmlChildElementCount(node));               
                /*get cur node's children node */
                xmlNodePtr child_node = node->children; 
                recursive_depth++;                
                parsing_node_data(child_node);  
                j++;
            }
            else
            {                
                xmlChar * result_string=NULL;
                if(node->type == XML_ELEMENT_NODE)     
                {             
                    result_string = xmlNodeGetContent(node);    
                    printf_table(recursive_depth);              
                    printf("Node %d , name %s , value %s   \n" , j , node->name 
                            , result_string  );   
                    j++;
                }  
            }
            
            if(node->next == NULL)
                break;
            else
                node = node->next;   
        }  
        recursive_depth--;
    }
}

在例程print_xpath_nodes()中函数添加parsing_node_data():
在这里插入图片描述
再次编译运行,结果如下,每个元素节点名称和包含的内容都被打印出来了:
在这里插入图片描述

如何修改xml文件中某项数据并保存
  1. 查找到对应xml元素节点,并设置该xml元素节点数据

                        if(!xmlStrcmp(node->name , BAD_CAST"first-name"))
                        {
                            printf("Get Target info Node Order %d \n",j);
                            printf("Before data %s \t",result_string);
                            xmlNodeSetContent(node , BAD_CAST"hello-word");
                            printf("After data %s \n",xmlNodeGetContent(node));
                        }
    

    此处插入代码:
    在这里插入图片描述
    测试结果如下

    在这里插入图片描述

  2. 以覆盖文件的形式保存对该xml元素节点的修改

    xmlSaveFile(filename,doc);
    

    在xpath1.c execute_xpath_expression()函数中添加此函数
    在这里插入图片描述
    再次编译运行:
    在这里插入图片描述
    可以看到保存成功

参考资料

  1. 官网教程
  2. 官方参考例程
  3. 官网API参考手册
  4. XML简单教程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值