最近有个需求,很简单,就是从一个XML文件中,删除掉一个节点。 根据以前的经验,轻车熟路的编译好libxml2库,写好xpath语句,执行程序,然后懵逼了……
const char* cszXPath = "/document//picture";
pstXPathResult = xmlXPathEvalExpression(BAD_CAST cszXPath, pstCTX);
返回的竟然是空白结果,也就是说,没有找到节点。 经过反复确认,/document/item/richtext/par/picture这个节点确实存在在,XPath语句/document//picture也没有错误。一时之间,感觉无法下手,然后网上一查,发现了突破点!
网上大量的值资料都显示,在节点具备显式的命名空间时,如果用XPath检索,那么需要显式的表明命名空间,回头再看看xml文件:
<document xmlns="http://www.lotus.com/dxl" version="8.5" maintenanceversion="3.0" replicaid="492581A600324B45" form="MainTopic">
嗯,大写的xmlns,没跑了,就是它!
接下来上网再找找,看看怎么检索带命名空间的节点。
找了一大顿,结果比较有用的只有这样一段话。
使用libxml2的xpath进行节点查询时。在测试中十分正常。后来在应用中发现总是无法找到结果。返回结果数为空。
对比测试文件与系统生成xml文件发现关于xmlns属性变化引起异常。
当使用无prefix命名空间时,会引起此异常xmlns="http://www..com.cn/xxx.xsd"
百度N个网页后找到替代方案"//*[local-name()='UserTag'][@name=\'%s\']"
即在//*[local-name()='UserTag']中UserTag为节点名称@name=\'%s\'为属性name值
老实说,我道行浅薄,到现在也没看懂啥意思,倒是咬住了另一个关键字:perfix。然后我又上网找到了这么一段示例代码,贴到源程序里,看看效果:
const char* cszXPath = "/a:document//picture";
pstXPathResult = xmlXPathEvalExpression(BAD_CAST cszXPath, pstCTX);
执行,看到了一条libxml库输出的错误消息:
XPath error : Undefined namespace prefix
出现了prefix,这个好眼熟啊,错误提示的内容是说prefix没有定义,嗯,我好像摸到点儿门路了……
然后接下来我找到了libxml2中的这个API!
XMLPUBFUN int XMLCALL xmlXPathRegisterNs (xmlXPathContextPtr ctxt,
const xmlChar *prefix,
const xmlChar *ns_uri);
我终于明白了,全明白了。
整理一下,答案应该是在这样的,libxml2正在使用xpath之前,需要创建context对象,如果节点带有命名空间(xmlns属性)那么在搜索前,就要向context对象指定命名空间,每一个命名空间对应XPath中的一个prefix。
所以,前文所述的问题,解决方案如下。
/* 示例文件 */
const char* cszFile = "../Simple.xml";
/* DOM对象 */
xmlDocPtr pstDOM;
/* Context对象 */
xmlXPathContextPtr pstContext;
/* XPath语句 */
const char* cszXPath = "/nodesns:document//nodesns:picture";
/* XPath搜索结果 */
xmlXPathObjectPtr pstXPathResult;
/* 打开文件 */
pstDOM = xmlParseFile(cszFileName);
/* 创建Context对象 */
pstContext = xmlXPathNewContext(pstDOM);
/* 重点1:向Context对象中注册namespace并声明perfix。*/
/* 第二个参数的“nodesns”就是perfix,它出现在XPath语句里,第三个参数中的字符串就是xml文件中,xmlns的真值。 */
/* 这样,XML中的namespace和XPath中的perfix的联系就建立起来了。 */
xmlXPathRegisterNs(pstContext, BAD_CAST "nodesns", BAD_CAST "http://www.lotus.com/dxl");
/* 搜索节点... */
pstXPathResult = xmlXPathEvalExpression(BAD_CAST cszXPath, pstContext);
/* 对搜索结果的处理,略。 */
......
/* 销毁搜索结果,释放资源 */
xmlXPathFreeObject(pstXPathResult);
/* 重点2:清理注册的perfix */
/* 由于一个xml中可能存在多个namespace,本次在一个namespace中搜索,下一次可能需要在另一个namespace中搜索, */
/* 所以,多次搜索动作如果想要正在同一个Context对象中完成的话,理论上,每次搜索完成后都要清理注册的perfix。 */
/* 新的搜索开始前重新注册新的perfix。 */
xmlXPathRegisteredNsCleanup(pstContext);
/* 销毁对象释放资源关闭文件不解释... */
xmlXPathFreeContext(pstContext);
xmlFreeDoc(pstDOM);
至此,问题解决。