解析 修改 xml文件

解析

解析xml的思路,也很清晰,就是先获取文件指针,找到根节点,再找到子节点去遍历。
还是仿照之前网上的例子修改的。


#include <stdio.h>
#include <assert.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#define DEFAULT_XML_FILE "osd.xml"


static int parse_osd(xmlDocPtr doc, xmlNodePtr cur)
{
    assert(doc || cur);
    xmlChar *key;

    cur = cur->xmlChildrenNode;
    while (cur != NULL)
    {
    
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"str")))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            printf("str: %s\t", key);
            xmlFree(key);
        }
    
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"size"))) 
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            printf("size: %s\t", key);
            xmlFree(key);
        }
    
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"color"))) 
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            printf("color: %s\n", key);
            xmlFree(key);
        }
         if ((!xmlStrcmp(cur->name, (const xmlChar *)"x"))) 
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            printf("x: %s\n", key);
            xmlFree(key);
        }
         if ((!xmlStrcmp(cur->name, (const xmlChar *)"y"))) 
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            printf("y: %s\n", key);
            xmlFree(key);
        }
    cur = cur->next;
    }
    return 0;
}

static int parse_osd_block(const char *file_name)
{
    assert(file_name);

    xmlDocPtr doc;   //xml file tree
    xmlNodePtr cur;  //xml node
    xmlChar *id;     

    //parse xml file tree
   // doc = xmlParseFile(file_name);
   doc = xmlReadFile(file_name, NULL, XML_PARSE_NOBLANKS);
    if (doc == NULL) 
    {
        fprintf(stderr, "Failed to parse xml file:%s\n", file_name);
        goto FAILED;
    }

    //parse xml node
    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) 
    {
        fprintf(stderr, "Root is empty.\n");
        goto FAILED;
    }

    if ((xmlStrcmp(cur->name, (const xmlChar *)"osd_block"))) 
    {
        fprintf(stderr, "The root is not osd_block.\n");
        goto FAILED;
    }

    //node path traversal
    cur = cur->xmlChildrenNode;
    while (cur != NULL) 
    {
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"osd"))) 
        {
            id = xmlGetProp(cur, "num");
            printf("num:%s\t",id);
            parse_osd(doc, cur);
        }
        cur = cur->next;
    }
    xmlFreeDoc(doc);
    return 0;
FAILED:
    if (doc) 
    {
        xmlFreeDoc(doc);
    }
    return -1;
}

int main(int argc, char*argv[])
{
    char *xml_file = DEFAULT_XML_FILE;

    if (argc == 2) 
    {
        xml_file = argv[1];
    }

    if (parse_osd_block(xml_file) != 0) 
    {
        fprintf(stderr, "Failed to parse osd block.\n");
        return -1;
    }

    return 0;
}

运行结果

num:1   str: 初始化中   size: 4 color: 1
x: 50
y: 50
num:1   str: 初始化中   size: 4 color: 1
x: 50
y: 50
num:1   str: 初始化中   size: 4 color: 1
x: 50
y: 50

修改

也许已经注意到上面三个osd子节点的属性num都是 1,这在实际应用的很不合理,所以我们要改成自然顺序。
可以直接在查找子节点的地方加上

        sprintf(tmpc,"%d",tmp);
    xmlSetProp(cur,(const xmlChar *)"num",(const xmlChar *)tmpc);   
    tmp+=1;

编译运行会发现解析的打印和xml文件osd子节点已经被修改成自然顺序了

<?xml version="1.0" encoding="UTF-8"?>
<osd_block>
  <osd num="1">
    <str>初始化中</str>
    <size>4</size>
    <color>1</color>
    <x>50</x>
    <y>50</y>
  </osd>
  <osd num="2">
    <str>初始化中</str>
    <size>4</size>
    <color>1</color>
    <x>50</x>
    <y>50</y>
  </osd>
  <osd num="3">
    <str>初始化中</str>
    <size>4</size>
    <color>1</color>
    <x>50</x>
    <y>50</y>
  </osd>
</osd_block>

XPath

上面的方法是我们一步步的找到文件指针,子节点,全部遍历找到修改的文件,其实实际运用中,往往用不到我们去全部遍历,经常用到的是找到特定的几个节点,修改几个特定的节点内容。这里有个便捷的方法:XPath。

XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。
XPath 使用路径表达式来选取 XML 文档中的节点或节点集。节点是通过沿着路径 (path) 或者步 (steps) 来选取的。
[w3school]

XPath 常用的路径表达式

nodename   选取次节点的所有节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑他们的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

比如我们想修改属性num为 2的osd节点里的x值,路径可以写 “/osd_block/osd[@num= ‘2’]”
添加以下代码

xmlChar *xpath = ("/osd_block/osd[@num= '2']");
    xmlXPathObjectPtr  app_result = getNodeset(doc,xpath);
    if(app_result ==NULL)
    {
        printf("app_result is NULL\n");
        return ;
    }

    int i =0;
    
    xmlChar *values;
    if(app_result)
    {
        xmlNodeSetPtr nodeset = app_result->nodesetval;
        xmlNodePtr cur2;
        
        for(i=0;i<nodeset->nodeNr;i++)
        {
            cur = nodeset->nodeTab[i];
            cur = cur->xmlChildrenNode;
            
            while(cur != NULL)
            {

                if(!xmlStrcmp(cur->name, (const xmlChar *)"x"))
                {
                    printf("%s\n",((char*)XML_GET_CONTENT(cur->xmlChildrenNode)));
                    xmlNodeSetContent(cur,(const xmlChar *)"56");
                }                   

                cur = cur->next;
            }
        }

        xmlXPathFreeObject(app_result);
    }

是把属性num为2的osd节点的x值改成56。其中getNodeset是

static xmlXPathObjectPtr getNodeset(xmlDocPtr doc,const xmlChar *xpath)
{
    xmlXPathContextPtr context;
    xmlXPathObjectPtr result;
    context = xmlXPathNewContext(doc);
    
    if(context == NULL)
    {
        printf("context is NULL\n");
        return NULL;
    }

    result = xmlXPathEvalExpression(xpath,context);
    xmlXPathFreeContext(context);
    if(result == NULL)
    {
        printf("xmlXPathEvalExpression return NULL\n");
    }

    if(xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        xmlXPathFreeObject(result);
        printf("nodeset is empty\n");
        return NULL;    
    }
    
    return result;
}

基本的操作可以使用了,另外,添加节点元素使用 xmlNewTextChild ,添加节点属性,用xmlNewProp。
下面的工作就是写一个小web页面(html)和xml文件交互了。

源码:https://pan.baidu.com/s/1qXHsP56

    原文作者:喵帕斯
    原文地址: https://www.jianshu.com/p/857ae5c2edd3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞