例子 xml 内容:
<?xml version="1.0" encoding="UTF-8" ?>
<Config>
<Database ip="192.168.1.33" port="3306" />
<List>
<Channel count="5">电视剧</Channel>
<Channel count="5">电影</Channel>
</List>
</Config>
1. 分析一段保存在内存中的 xml 内容 (代码见下方)
1) xml 内容中如果有中文,必须转成 UTF-8格式,否则可能会出问题,比如此例中,"电视剧"的中文 gb2312 编码会影响到后面的 "</Channel>",导致取这个节点的 text 时,得到的结果是: "电视剧</Channel>",而取下一个节点时将找不到节点。
2)使用 TiXmlDocument 与 TiXmlHandle 的区别: 一次取多级子节点元素时,当某一级节点不存在,用 doc (TiXmlDocument) 会出现异常,程序崩溃,而用 docHandle (TiXmlHandle) 则不会有异常。
比如:
databaseElement = doc.FirstChildElement( "Conf" )->FirstChildElement( "Database" ); // 异常,崩溃
databaseElement = docHandle.FirstChild( "Conf" ).FirstChild( "Database" ).ToElement(); // 不会异常,databaseElement 为 0
databaseElement = docHandle.FirstChildElement( "Conf" ).FirstChildElement( "Database" ).ToElement(); // 不会异常,databaseElement 为 0
后两种写法的效果是一样的。
以下为示例代码,buffer 中保存着上面例子的 xml 内容:
- void CxmlDlg::ParseXML()
- {
- char * buffer = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\
- <Config>\
- <Database ip=\"192.168.1.33\" port=\"3306\" />\
- <List>\
- <Channel count=\"5\">电视剧</Channel>\
- <Channel count=\"5\">电影</Channel>\
- </List>\
- </Config>";
- char utf8[256] = {0};
- if( MBSToUTF8( utf8, sizeof(utf8), buffer ) <= 0 ) // 此函数见我之前关于 TinyXML 的文章
- return;
- TiXmlDocument doc;
- doc.Parse(utf8);
- TiXmlElement* databaseElement = 0;
- TiXmlElement* listElement = 0;
- TiXmlElement* channelElement = 0;
- TiXmlHandle docHandle( &doc );
- databaseElement = docHandle.FirstChild( "Config" ).FirstChild( "Database" ).ToElement();
- assert( databaseElement );
- // 取得字符串属性的内容
- const char * ip = databaseElement->Attribute("ip");
- // 取得整型属性的值
- int port = 0;
- databaseElement->QueryIntAttribute("port", &port );
- int count = 0;
- char content[32] = {0};
- listElement = docHandle.FirstChild( "Config" ).FirstChild( "List" ).ToElement();
- assert( listElement );
- for( channelElement = listElement->FirstChildElement("Channel"); channelElement; channelElement = channelElement->NextSiblingElement("Channel") )
- {
- channelElement->QueryIntAttribute("count", &count );
- UTF8ToMBS( content, sizeof(content), channelElement->GetText() );
- }
- }
2. 合成一段 xml 内容保存在内存中 (代码见下方)
有两种写法:
1) 第一种使用栈空间,声明为局部变量,链接 TiXmlElement 的局部对象时必须用 Insert 系列的函数: InsertEndChild / InsertAfterChild / InsertBeforeChild,如果使用 LinkEndChild,在对象释放时会有异常。原因正如 hoyt00 所说:"因为 Insert 系列的函数插入的是结点的副本(包括所有子结点),而 LinkEndChild 插入的就是你创建的对象。"
2) 第二种使用堆空间,动态分配对象,链接 TiXmlElement 时必须用 LinkEndChild 函数,这样在最后 delete TiXmlDocument 对象时,TinyXML 内部才会帮你把动态生成的对象释放掉。
(1)TiXmlDocument对象最好在栈上创建, 如果在堆上创建了, 那你必须得自己销毁它, 千万不要像别的对象一样new出了就不管了.
(2)除了TiXmlDocument对象, 树中的别的结点对象, 必须是堆上创建的, 千万不要把栈上对象的地址链接(LinkEndChild)到树中, 因为栈上对象是不能用delete销毁的, 当然TinyXml也有对栈上对象插入的方法, 以下会说到.
(3)除了文档结点, new出的所有结点对象必须被链接到一个父亲结点上, 才可以不用管对象的delete, 而且必须不能管, 不然有可能整棵树会被破坏而得不到正确的遍历和析构, 如果new出的对象从来没链接到某棵树上, 而且将来也不打算链接, 无论有没有别的结点链接到它身上, 你都必须手动delete它.
(4)不要尝试链接已经被别的结点链接过的指针, 很显然, 这会造成无法估量的麻烦, 不用多说, 大家都懂的.
写法一:
- void CxmlDlg::MakeXML1()
- {
- // 生成 XML 内容
- TiXmlDocument doc;
- TiXmlElement config("Config");
- TiXmlElement database("Database");
- database.SetAttribute( "ip", "192.168.1.33" );
- database.SetAttribute( "port", 3306 );
- config.InsertEndChild( database );
- TiXmlElement list("List");
- char utf8[32] = {0};
- TiXmlElement channel1("Channel");
- channel1.SetAttribute( "count", 5 );
- MBSToUTF8( utf8, sizeof(utf8), "电视剧" );
- TiXmlText text1( utf8 );
- channel1.InsertEndChild( text1 );
- list.InsertEndChild( channel1 );
- TiXmlElement channel2("Channel");
- channel2.SetAttribute( "count", 5 );
- MBSToUTF8( utf8, sizeof(utf8), "电影" );
- TiXmlText text2( utf8 );
- channel2.InsertEndChild( text2 );
- list.InsertEndChild( channel2 );
- config.InsertEndChild( list );
- doc.InsertEndChild( config );
- TiXmlPrinter printer;
- printer.SetIndent( 0 ); // 设置缩进字符,设为 0 表示不使用缩进。默认为 4个空格,也可设为'\t'
- doc.Accept( &printer );
- char content[256] = {0};
- int size = printer.Size();
- assert( size < sizeof(content) );
- strcpy_s( content, sizeof(content), printer.CStr() );
- }
写法二:
- void CxmlDlg::MakeXML2()
- {
- // 生成 XML 内容
- TiXmlDocument *doc = new TiXmlDocument();
- TiXmlElement *config = new TiXmlElement("Config");
- TiXmlElement *database = new TiXmlElement("Database");
- database->SetAttribute( "ip", "192.168.1.33" );
- database->SetAttribute( "port", 3306 );
- config->LinkEndChild( database );
- TiXmlElement *list = new TiXmlElement("List");
- char utf8[32] = {0};
- TiXmlElement *channel1 = new TiXmlElement("Channel");
- channel1->SetAttribute( "count", 5 );
- MBSToUTF8( utf8, sizeof(utf8), "电视剧" );
- TiXmlText *text1 = new TiXmlText( utf8 );
- channel1->LinkEndChild( text1 );
- list->LinkEndChild( channel1 );
- TiXmlElement *channel2 = new TiXmlElement("Channel");
- channel2->SetAttribute( "count", 5 );
- MBSToUTF8( utf8, sizeof(utf8), "电影" );
- TiXmlText *text2 = new TiXmlText( utf8 );
- channel2->LinkEndChild( text2 );
- list->LinkEndChild( channel2 );
- config->LinkEndChild( list );
- doc->LinkEndChild( config );
- TiXmlPrinter printer;
- printer.SetIndent( 0 );
- doc->Accept( &printer );
- char content[512] = {0};
- int size = printer.Size();
- assert( size < sizeof(content) );
- memcpy( content, printer.CStr(), printer.Size() );
- delete doc;
- }
3.TinyXml 保存时如何处理缩进和行结尾
1) 处理缩进:
a. 如果是保存文件,TinyXML 默认用4个空格做为缩进,根据节点的深度不同,缩进为4个空格的倍数,如果想去掉缩进,需要查找 TinyXML 源文件中所有写4个空格的地方(一共是5处),注释掉或换成你想要的缩进字符,也可以对 TinyXML 做一个改进,增加一个函数用于设置缩进字符,可以设为NULL,即无缩进。
b. 如果是保存到内存中,在 TiXmlPrinter 中有一个函数:SetIndent(),可以设置缩进字符,也可以设为 NULL。
2) 处理行结尾:
a. 如果是保存文件,TinyXML 会在每行的结尾用fprintf()写一个 '\n',在 Windows 上,fprintf 写入文件的 '\n' 都会被转换为 "\r\n"。
b. 如果是保存到内存中,在 TiXmlPrinter 中有一个函数:SetLineBreak(),可以设置行结尾字符,也可以设为 NULL。