VC处理XML的方法

2012/04/25 dev

目前调研到两种比较好的方法: CMarkupTinyXML-2.

以下是两者分别的文档: CMarkup, TinyXML-2

从上手的难易程度来看, 目前偏向于使用 CMarkup, 不过有机会一定要研究一下TinyXML-2的使用, 目前关于TinyXML-2的中文资料较少, 正好花时间翻译整理一下. 另外, 它不仅开源, 而且功能较CMarkup要全面, 文档更加齐全.

下面首先说一下CMarkup的基本使用方法:

首先在这里下载CMarkup, 里面包含一个Markup.hMarkup.cpp. 分别引入自己的项目下.

NOTE: 貌似CMarkup仅支持MFC, 在控制台程序中似乎无法使用.

在引入之后, 需在在Markup.cpp的头部加上#include "stdafx.h", 在下述testXML工程里, 不再赘述这一过程.

创建XML

  1. 新建一个MFC工程(testXML), 选择基于对话框的就好, 其余一切默认.
  2. 在对话框上新建一个按钮(Create), 添加BN_CLICKED控件事件.
  3. 在事件函数中写入一下代码:
void CtestXmlDlg::OnBnClickedBtmCreate()
{
    // 创建XML
    CMarkup xml;
    xml.AddElem( L"ORDER" );
    xml.IntoElem();
    xml.AddElem( L"ITEM" );
    xml.IntoElem();
    xml.AddElem( L"SN", L"132487A-J" );
    xml.AddElem( L"NAME", L"crank casing" );
    xml.AddElem( L"QTY", L"1" );

    xml.Save( L"F:\\Sample.xml" );
    MessageBox(L"Success!");
}

我们仅运用了两种方法: AddElemIntoElem, 前者可以理解为增加一个标签, 后者则是进入此标签, 很简单的逻辑.

以下就是生成的XML示例:

<ORDER>
<ITEM>
<SN>132487A-J</SN>
<NAME>crank casing</NAME>
<QTY>1</QTY>
</ITEM>
</ORDER>

千万不要忘记了保存. 即调用XML.Save();.

NOTE: 这里有一点很囧, 就是保存路径无法写相对路径. 如果有知道如何写入相对路径的, 请留言告知, 感激不尽.

读取XML

我们就来读上面写好的Sample.xml吧. 如果在同一函数中, 完全可以用GetDoc方法重新读取该文档:

MCD_STR strXML = xml.GetDoc();

Markup.h中定义了MCD_STR作为默认的字符串类型, 你也可以使用std::string或者CString. 然后可调用ResetPos方法来返回文档头部. 或者咱们重新写个函数, 专门来读:

*再在对话框上新建一个按钮(Read), 并添加BN_CLICKED事件. 写下代码: *

void CtestXmlDlg::OnBnClickedBtmRead()
{
    // 读取XML
    CMarkup xml;
    xml.Load(L"F:\\Sample.xml");
    //MCD_STR strXML = xml.GetDoc();
    //xml.SetDoc(strXML);

    xml.FindElem(); // root ORDER element
    xml.IntoElem(); // inside ORDER
    CString outPut;
    while ( xml.FindElem( L"ITEM") )
    {
        xml.IntoElem();
        xml.FindElem( L"SN" );
        MCD_STR strSN = xml.GetData();
        xml.FindElem( L"QTY" );
        int nQty = _wtoi( MCD_2PCSZ(xml.GetData()) );
        outPut.Format(L"SN:%s\nQTY:%d",strSN,nQty);
        xml.OutOfElem();
    }

    MessageBox(outPut);
}

首先, 需要声明一个CMarkup对象, 然后用Load方法引入需要解析的XML文档. 或者使用SetDoc方法(见被注释的两行代码).

然后再循环调用FindElemGetData来定位并获取标签内容. 这里如果需要的数字, 可以通过atoi来转化字符串, 如果在Unicode环境下, 需要用_wtoi来转换. MCD_2PCSZMarkup.h中有定义, 返回一个字符串的const指针.

最后要注意的就是, 每一次定位到某标签, 需要调用IntoElem来进入子标签, 并继而通过OutOfElem跳出. 每一次循环一定由一对IntoElemOutOfElem来包夹.

运行结果如下:

result

添加标签及属性

上面创建的XML仅仅包含一个ITEM标签. 下面再举一个例子, 通过已有的数据源创建多个ITEM. 然后通过SetAttrib来为SHIPMENT标签设置一个属性.

在对话框上新建一个按钮(Add), 并添加BN_CLICKED事件. 写下代码:

struct Items
{
    CString strSN;
    CString strName;
    int nQty;
};

void CtestXmlDlg::OnBnClickedBtnAdd()
{
    // 向XML中添加标签与属性
    CArray<Items,Items&> aItems;
    Items item1 = {L"132487A-J",L"crank casing",1};
    Items item2 = {L"4238764-A",L"bearing",15};
    aItems.Add(item1);
    aItems.Add(item2);

    CString strPOCType(L"non-emergency");
    CString strPOCName(L"John Smith");
    CString strPOCTel(L"555-1234");

    CMarkup xml;
    xml.AddElem(L"ORDER");
    xml.IntoElem(); // inside ORDER
    for(int nItem=0; nItem<aItems.GetSize(); ++nItem)
    {
        xml.AddElem( L"ITEM" );
        xml.IntoElem(); // inside ITEM
        xml.AddElem( L"SN", aItems[nItem].strSN );
        xml.AddElem( L"NAME", aItems[nItem].strName );
        xml.AddElem( L"QTY", aItems[nItem].nQty );
        xml.OutOfElem(); // back out to ITEM level
    }
    xml.AddElem( L"SHIPMENT" );
    xml.IntoElem(); // inside SHIPMENT
    xml.AddElem( L"POC" );
    xml.SetAttrib( L"type", strPOCType );
    xml.IntoElem(); // inside POC
    xml.AddElem( L"NAME", strPOCName );
    xml.AddElem( L"TEL", strPOCTel );

    xml.Save( L"F:\\Sample.xml" );
    MessageBox(L"Success!");
}

这段代码生成如下XML: 根标签ORDER包含了两个ITEM子标签和一个SHIPMENT标签. 该ITEM标签包括了SN,NAMEQTY子标签. SHIPMENT标签包含了一个POC子标签, POC拥有一个type属性, 和一个NAMETEL子标签.

<ORDER>
<ITEM>
<SN>132487A-J</SN>
<NAME>crank casing</NAME>
<QTY>1</QTY>
</ITEM>
<ITEM>
<SN>4238764-A</SN>
<NAME>bearing</NAME>
<QTY>15</QTY>
</ITEM>
<SHIPMENT>
<POC type="non-emergency">
<NAME>John Smith</NAME>
<TEL>555-1234</TEL>
</POC>
</SHIPMENT>
</ORDER>

查找标签

FindItem方法默认查找下一个相邻的标签. 如果其参数指定了标签名称, 则一直向下查找, 直到得到匹配的标签. 找到标签的位置将被视为当前位置, 下一次调用FindItem方法时, 将以当前位置为开端, 继续向下查找能够匹配的标签位置.

在FindItem循环查找过程中, 如果不想继续顺序查找下去, 而是希望返回到第一个标签的位置, 可以用ResetMainPos. 例如上述例子中, 如果你无法确定SN标签是否在QTY标签之前(而你恰好又是先找的QTY), 那就可以用ResetMainPos方法返回重新查找. 代码如下:

void CtestXmlDlg::OnBnClickedBtnFind()
{
    // 检索XML
    CMarkup xml;
    xml.Load(L"F:\\Sample.xml");
    //MCD_STR strXML = xml.GetDoc();
    //xml.SetDoc(strXML);

    xml.FindElem(); // root ORDER element
    xml.IntoElem(); // inside ORDER

    CString outXml;
    while ( xml.FindElem(L"ITEM") )
    {
        xml.IntoElem();
        xml.FindElem( L"QTY" );
        int nQty = _wtoi( MCD_2PCSZ(xml.GetData()) );
        xml.ResetMainPos();
        xml.FindElem( L"SN" );
        MCD_STR strSN = xml.GetData();
        xml.OutOfElem();

        outXml.Format(L"strSN:%s\nnQty:%d",strSN,nQty);
    }

    CString strFindSN(L"87890310-A");
    MCD_STR strSN;
    xml.ResetPos(); // top of document
    xml.FindElem(); // ORDER element is root
    xml.IntoElem(); // inside ORDER
    while ( xml.FindElem(L"ITEM") )
    {
        xml.FindChildElem( L"SN" );
        if ( xml.GetChildData() == strFindSN )
        {
            strSN = xml.GetChildData();
            break; // found
        }
    }

    xml.Save(L"F:\\Sample.xml");
    MessageBox(outXml+"\n"+strSN);
}

如果只是想找一个特殊的值(如上述代码中值为”87890310-A”的SN), 可以循环遍历ITEM标签, 并比对其子标签SN的值. 如果指定查找ITEM标签, 如上述代码, 那就不会去理其他标签, 如SHIPMENT等.

另外, 进出ITEM标签去对其子标签SN进行查找, 使用FindChildElemGetChildData方法要便捷的多.

这就完了么?

额, 准确的说, 上述这点玩意儿, 只是最最最基本的操作. 不过完全可以看出CMarkup类的便捷简单了吧?利用CMarkup操作XML虽说已经是很成熟的技术了, 但它的功能并不是太全面. 这只是一种轻量级的工具, 合适与否完全看个人需求了. 更多的方法API请见这里.

参考资料

姓名*:

电邮*:

网址(可选):

评论*:

Search

    Table of Contents