在C++/MFC中利用CMarkup类对XML进行操作

在C++/MFC中利用CMarkup类对XML进行操作

标签: 实用编程技术

一、什么是XML1

XML 是可扩展标记语言(Extensible Markup Language)的缩写,其中的 标记(markup)是关键部分。标记语言从早期的私有公司和政府制定形式逐渐演变成标准通用标记语言(Standard Generalized Markup Language,SGML)、超文本标记语言(Hypertext Markup Language,HTML),并且最终演变成 XML。简单地说,XML是一种有特定格式的语言,这种语言可以精确的表示所记录的信息,最重要的是通用性很强,可以被多种软件或者系统按照语法格式来解析。

二、使用CMarkup类的一些准备

  1. 在官网下载头文件Markup.h和源文件Markup.cpp.
  2. 在VC6.0或者VS中新建一个工程,此处笔者在VC6.0中新建了一个简单地控制台应用程序,并将其命名为CMarkupTest
  3. 将下载头文件和源文件Copy到该工程的根目录下,并且添加到工程中去。这里需要说明的是VC6.0由于和office2007版本及以上版本在Windows中有冲突,向工程添加文件会使VC6.0卡死并且闪退,可以用加载插件的方法解决该问题,网上方法很多,在此不赘述。
  4. 在CMarkuptest.cpp中包含头文件Markup.h
  5. 点击编译,竟然有错误!我们来看一下提示:

    fatal error C1010: unexpected end of file while looking for precompiled header directive

    这是添加文件到工程常见的错误,解决方法有两个:

    解决方案1: 在菜单栏进行设置,工程->设置->C/C++->分类:预编译的头文件,设置为第一项:不使用预补偿页眉

    解决方案2:在CMarkup.cpp文件开头添#include”stdafx.h”

    此处笔者使用解决方案2. 此时再编译,又提示了新的错误:

    error LNK2001: unresolved external symbol __endthreadex
    error LNK2001: unresolved external symbol __beginthreadex

    这个错误笔者以前在MFC中解析XML时并没有发生过,很可能是控制台应用程序没有包含应有的库而产生的。仔细观察提示的错误,未解决的外部符号__endthreadex/__beginthreadex,笔者猜测可能是解析XML时使用了多线程函数,这个好办,解决方法如下:
    工程->设置->C/C++->分类:Code Generation->Use run-time library: DebugMultithreaded

    点击编译,0 error,完成!

三、创建XML文件2

XML 文档的第一行可以是一个 XML 声明。这是文件的可选部分,它将文件识别为 XML 文件,有助于工具和人类识别 XML。可以将这个声明简单地写成 <?xml?>,或包含 XML 版本<?xml version="1.0"?>,甚至包含字符编码,比如针对 Unicode 的<?xml version="1.0" encoding="utf-8"?>

根元素

根元素的开始和结束标记用于包围XML文档的内容,一个文件只能有一个根元素。

子元素

一个文件可以拥有若干个子元素,并且子元素可以嵌套新的子元素。创建名称时可以使用英文字母、数字和特殊字符,比如下划线(_)。下面给出命名时需要注意的地方:

  • 元素名中不能出现空格
  • 名称只能以英文字母开始,不能是数字或符号。(在第一个字母之后就可以使用字母、数字或规定的符号,或它们的混合)。
  • 对大小写没有限制,但前后要保持一致,以免造成混乱。

每一个元素以开始标记开始,以结束标记结束,中间夹上该元素的内容或者子元素。例如我们现在创建一个XML文档,来表示学生和课程内容。具体如下:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <Math>
        <Advanced_Mathematics>classroom No.1</Advanced_Mathematics>
        <Mathematical_Analysis>classroom No.2</Mathematical_Analysis>
        <Linear_Algebra>classroom No.3</Linear_Algebra>
        <Probability_Theory>classroom No.4</Probability_Theory>
    </Math>
    <English/>
    <Student>Xiao_Ming</Student>
    <Student>Li_Hua</Student>
</Root>

在此处,笔者将用void CreateXML(void)函数来创建XML文档,代码如下:

void CreateXML()
{
    printf("开始创建XML文件...\n");

    CMarkup xml;//实例化一个CMarkup对象
    xml.SetDoc("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");//双引号需要转义

    xml.AddElem("Root");//添加元素Root
    xml.IntoElem();//进入根元素

    xml.AddElem("Math");//添加Math元素
    xml.IntoElem();//进入Math元素
    xml.AddElem("Advanced_Mathematics", "classroom No.1");
    xml.AddElem("Mathematical_Analysis", "classroom No.2");
    xml.AddElem("Linear_Algebra", "classroom No.3");
    xml.AddElem("Probability_Theory", "classroom No.4");
    xml.OutOfElem();//退出Math元素

    xml.AddElem("English");//添加English元素
    xml.AddElem("Student","Xiao_Ming");//添加内容为Xiao_Ming的Student元素
    xml.AddElem("Student","Li_Hua");//添加内容为Li_Hua的Student元素

    xml.OutOfElem();//退出根元素
    xml.Save("myxml.xml");//默认创建路径为该工程的根目录
    printf("创建完成!\n\n");
}
  • 运行结果
    在C++/MFC中利用CMarkup类对XML进行操作_第1张图片
  • XML文件
  • 首先需要实例化一个XML对象才能进行具体的操作。

  • bool CMarkup::SetDoc(MCD_PCSZ pDoc)常用来写XML文档声明

  • bool CMarkup::AddElem(MCD_CSTR szName, MCD_CSTR szData=NULL, int nFlags=0):用来在当前位置添加元素,MCD_CSTR是一个结构体,我们输入C风格字符串类型即可。
    • 第一个参数表示元素的名称,不能为空
    • 第二个参数表示元素的内容,默认为空
    • 第三个参数表示添加元素的方式,一般使用默认0
    • 一般在声明完之后立即调用该函数创建根元素。什么是当前位置?可以这么理解,实例化的CMarkup类对象xml中有一个类似于指针的变量,指向XML中某个元素。
  • bool CMarkup::IntoElem():使当前元素成为父元素。因此进入根元素之后再调用添加元素的函数,效果是给根元素添加了子元素。
  • bool CMarkup::OutOfElem():使当前元素成为子元素。
  • bool CMarkup::Save( MCD_CSTR_FILENAME szFileName ):把xml对象中的内容以文本文件的形式保存起来,参数为存储路径。

四、解析XML文件

当你作为一个XML文件接收者的时候,最主要的操作就是读取该文件中的内容,笔者把这种操作叫做解析XML。此处假设我们接收的文件就是我们刚才创建的XML文件,任务是获取所有学生的姓名。此处笔者使用void AnalyzeXML(void)函数实现学生姓名提取,具体代码如下:

void AnalyzeXML()
{
    printf("开始解析XML文件...\n");

    CMarkup xml;
    bool bLoad = false;

    bLoad = xml.Load("myxml.xml");
    if (!bLoad)
    {
        printf("没有找到XML文件!\n");
        return ;
    }

    xml.ResetMainPos();//把xml对象指向的位置初始化,使其指向文件开始
    xml.FindElem();//查找任意元素,此处查找到的第一个元素即为根元素
    xml.IntoElem();//进入Root

    while (xml.FindElem("Student"))//不能使用if,因为要遍历所有的Student元素
    {
        CString strTagName;//元素名称
        CString strData;//元素内容
        strTagName = xml.GetTagName();//获取元素名称
        strData = xml.GetData();////获取元素内容
        printf("%s: %s\n", strTagName, strData);
    }

    printf("解析完成!\n\n");
}
  • 运行结果
  • bool Load(MCD_CSTR_FILENAME szFileName)参数为文件的路径与名称,当不写路径的时候默认为当前工程的根目录。当载入文件成功时,返回值为TURE;载入文件失败时,返回值为FALSE。后面if语句用来检查文件载入是否成功,如果失败则提示
  • bool CMarkup::FindElem(MCD_CSTR szName)用来查找元素,参数为元素名称。当传入参数为空的时候,表示指向下一个元素。
  • 其他的函数功能如注释所示,比较简单,不再赘述
  • 当搜索某一个元素的时候,使用while搜索只能遍历和当前元素同属一个父元素的元素。比如Math元素有子元素testEnglish也有子元素test,如果需要读取这两个test元素的内容,只能分别进入MathEnglish元素再搜索各自的test元素。

五、修改XML

此处我们以修改Probability_Theory的内容为例讲解XML修改,要求将其内容从classroom No.4修改为classroom No.5。此处笔者使用void ModifyXML(void)函数来实现该功能,具体代码如下:

void Modify()
{
    printf("开始修改XML文件...\n");

    CMarkup xml;
    bool bLoad = false;
    bLoad = xml.Load("myxml.xml");

    if (!bLoad)
    {
        printf("没有找到XML文件!\n");
        return ;
    }

    xml.ResetMainPos();//把xml对象指向的位置初始化,使其指向文件开始
    xml.FindElem();//查找任意元素,此处查找到的第一个节点即为根节点
    xml.IntoElem();//进入Root

    xml.FindChildElem("Probability_Theory");//可以不进入子元素层却查找子元素

    CString strTagName;//子元素名称
    CString strData = "classroom No.5";//节点内容
    strTagName = xml.GetChildTagName();//获取子元素名称
    strData = xml.SetChildData(strData);////设置子元素内容
    xml.Save("myxml.xml");//默认创建路径为该工程的根目录

    strData = xml.GetChildData();////读取子元素内容
    printf("%s的内容被修改为%s \n", strTagName, strData);
    printf("修改完成!\n\n");
}
  • 运行结果
  • XML文件
    在C++/MFC中利用CMarkup类对XML进行操作_第2张图片
  • FindChildElem()GetChildTagName()SetChildData()可以在不进入子元素层的情况下查询、读取和设置元素
  • 修改XML文件只要找到需要修改的元素再再调用SetData()SetChildData()即可
  • 修改完以后记得保存

六、添加元素

就像STL添加元素一样,向XML中添加元素也有两种方式,一种是在子元素层后面添加元素,另一种是在子元素层前面插入元素。

6.1在后面添加元素

  • 在后面添加元素使用的就是创建XML文件时bool CMarkup::AddElem(MCD_CSTR szName, MCD_CSTR szData=NULL, int nFlags=0)函数,仔细回忆下,我们使用该函数添加的元素是不是依次从前向后添加的呢?
  • 记得保存

6.2在前面插入元素

我们将要在Math之前添加两个Chinese元素,内容分别是classroom No.10classroom No.11. 此处笔者使用void InsertXML(void)函数来实现该功能,具体代码如下:

void InsertXML(void)
{
    printf("开始在前面插入元素...\n");

    CMarkup xml;
    bool bLoad = false;
    bLoad = xml.Load("myxml.xml");

    if (!bLoad)
    {
        printf("没有找到XML文件!\n");
        return ;
    }

    xml.ResetMainPos();//把xml对象指向的位置初始化,使其指向文件开始
    xml.FindElem();//查找任意元素,此处查找到的第一个节点即为根节点
    xml.IntoElem();//进入Root

    xml.InsertElem("Chinese", "classroom No.11");
    xml.InsertElem("Chinese", "classroom No.10");
    xml.Save("myxml.xml");//默认创建路径为该工程的根目录

    printf("Chinese已经插入在Math前面\n");
    printf("插入完成!\n\n");
}
  • 运行结果
    在C++/MFC中利用CMarkup类对XML进行操作_第3张图片
  • XML文件
  • 只需要找到需要插入的子元素层,调用bool InsertElem( MCD_CSTR szName, MCD_CSTR szData=NULL, int nFlags=0 )函数即可
  • 记得保存

七、删除元素

我们将删除刚刚添加的内容为classroom No.11Chinese元素。此处笔者使用void DeleteXML(void)函数来实现该功能,具体代码如下:

void DeleteXML()
{
    printf("开始删除元素...\n");

    CMarkup xml;
    bool bLoad = false;
    bool bFind = false;
    bLoad = xml.Load("myxml.xml");

    if (!bLoad)
    {
        printf("没有找到XML文件!\n");
        return ;
    }

    xml.ResetMainPos();//把xml对象指向的位置初始化,使其指向文件开始
    xml.FindElem();//查找任意元素,此处查找到的第一个节点即为根节点
    xml.IntoElem();//进入Root

    while (xml.FindElem("Chinese"))
    {
        CString strData = xml.GetData();
        if (strData == "classroom No.11")
        {
            xml.RemoveElem();
            xml.Save("myxml.xml");//默认创建路径为该工程的根目录
        }
    }

    printf("内容为`classroom No.11`的`Chinese`元素已经被删除\n");
    printf("删除元素完成!\n\n");
}
  • 运行结果
    在C++/MFC中利用CMarkup类对XML进行操作_第4张图片
  • XML文件
    在C++/MFC中利用CMarkup类对XML进行操作_第5张图片
  • 找到要删除元素
  • 调用bool CMarkup::RemoveElem()或者bool CMarkup::RemoveChildElem()来删除该元素,如果这个元素有子元素的话,子元素也会被一并删除
  • bool CMarkup::RemoveNode()有什么用?和bool CMarkup::RemoveElem()有什么区别?请查阅官网资料
  • 记得保存

八、属性操作

XML文档中的元素不仅仅有名称和内容,还有属性,属性写在开始标记的方括号中。比如说Li_Hua是一个Student,但是Li_Hua本身还有性别、年龄等属性,写成XML为如下的形式:

<Student Sex="Male" Age="23">Li_Hua</Student>

元素属性的相关操作和元素名称的相关操作十分相似,笔者此处只讲解元素属性的添加,置于元素属性的访问、删除和修改就留给各位读者自己探究了。此处笔者使用void AddElemAttribXML(void)函数来实现该功能,具体代码如下:

  • 运行结果
    在C++/MFC中利用CMarkup类对XML进行操作_第6张图片
  • XML文件
    在C++/MFC中利用CMarkup类对XML进行操作_第7张图片
  • 找到需要添加属性的元素
  • 调用bool AddAttrib( MCD_CSTR szAttrib, MCD_CSTR szValue, int nFlags=0 )或者bool AddChildAttrib( MCD_CSTR szAttrib, MCD_CSTR szValue, int nFlags=0 )来添加属性,不过比较奇怪的是这次属性添加的顺序是从后往前的,不过属性的顺序对于其操作没有任何影响
  • SetAttribAddAttrib在此处效果一模一样
  • 记得保存

九、主函数

虽然主函数很简单,甚至是有一点无趣,但还是想把它贴在这里:

int main(int argc, char* argv[])
{
    printf("\t/*--- 欢迎访问moverzp的博客:http://blog.csdn.net/xuelabizp ---*/\n");

    CreateXML();
    AnalyzeXML();
    Modify();
    InsertXML();
    DeleteXML();
    AddElemAttribXML();

    return 0;
}

十、补充

其实并不是每次都从XML文件中读取内容或者每次创建完XML格式的内容都要以文件形式保存,有时候需要把CMarkup类对象的内容转化为各种形式的字符串,这里笔者列出两种常见的转化情况:

  • CMarkup转string
string sXML;
sXML = xml.GetDoc();
  • char*转CMarkup
Char sztemp[200];
xml.SetDoc(szTemp);

十一、笔者使用CMarkup类的情况

  • 上位机程序和KUKA工业机器人进行通信,以XML格式发送C风格字符串命令来通信,可以控制其速度、加速度、以及运动轨迹
  • 存储极少量数据,这个时候使用数据库感觉是牛刀杀鸡,把数据以XML格式存储在本地是个好方法
  • 脚本的制作和导入
  1. XML新手入门知识 ↩
  2. VC解析XML–使用CMarkup类解析XML ↩

你可能感兴趣的:(C++,xml,mfc)