标签: 实用编程技术
XML 是可扩展标记语言(Extensible Markup Language)的缩写,其中的 标记(markup)是关键部分。标记语言从早期的私有公司和政府制定形式逐渐演变成标准通用标记语言(Standard Generalized Markup Language,SGML)、超文本标记语言(Hypertext Markup Language,HTML),并且最终演变成 XML。简单地说,XML是一种有特定格式的语言,这种语言可以精确的表示所记录的信息,最重要的是通用性很强,可以被多种软件或者系统按照语法格式来解析。
点击编译,竟然有错误!我们来看一下提示:
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 文档的第一行可以是一个 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");
}
首先需要实例化一个XML对象才能进行具体的操作。
bool CMarkup::SetDoc(MCD_PCSZ pDoc)
常用来写XML文档声明
bool CMarkup::AddElem(MCD_CSTR szName, MCD_CSTR szData=NULL, int nFlags=0)
:用来在当前位置添加元素,MCD_CSTR是一个结构体,我们输入C风格字符串类型即可。 bool CMarkup::IntoElem()
:使当前元素成为父元素。因此进入根元素之后再调用添加元素的函数,效果是给根元素添加了子元素。bool CMarkup::OutOfElem()
:使当前元素成为子元素。bool CMarkup::Save( MCD_CSTR_FILENAME szFileName )
:把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)
用来查找元素,参数为元素名称。当传入参数为空的时候,表示指向下一个元素。Math
元素有子元素test
,English
也有子元素test
,如果需要读取这两个test
元素的内容,只能分别进入Math
和English
元素再搜索各自的test
元素。此处我们以修改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");
}
FindChildElem()
,GetChildTagName()
,SetChildData()
可以在不进入子元素层的情况下查询、读取和设置元素SetData()
和SetChildData()
即可就像STL添加元素一样,向XML中添加元素也有两种方式,一种是在子元素层后面添加元素,另一种是在子元素层前面插入元素。
bool CMarkup::AddElem(MCD_CSTR szName, MCD_CSTR szData=NULL, int nFlags=0)
函数,仔细回忆下,我们使用该函数添加的元素是不是依次从前向后添加的呢?我们将要在Math
之前添加两个Chinese
元素,内容分别是classroom No.10
、classroom 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");
}
bool InsertElem( MCD_CSTR szName, MCD_CSTR szData=NULL, int nFlags=0 )
函数即可我们将删除刚刚添加的内容为classroom No.11
的Chinese
元素。此处笔者使用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");
}
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)
函数来实现该功能,具体代码如下:
bool AddAttrib( MCD_CSTR szAttrib, MCD_CSTR szValue, int nFlags=0 )
或者bool AddChildAttrib( MCD_CSTR szAttrib, MCD_CSTR szValue, int nFlags=0 )
来添加属性,不过比较奇怪的是这次属性添加的顺序是从后往前的,不过属性的顺序对于其操作没有任何影响SetAttrib
和AddAttrib
在此处效果一模一样虽然主函数很简单,甚至是有一点无趣,但还是想把它贴在这里:
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类对象的内容转化为各种形式的字符串,这里笔者列出两种常见的转化情况:
string sXML;
sXML = xml.GetDoc();
Char sztemp[200];
xml.SetDoc(szTemp);