zoukankan      html  css  js  c++  java
  • TinyXML中文指南

    TinyXML 指南

    这是什么?

    这份指南有一些关于如何有效地使用TinyXML的技巧和建议。

    我也会尝试讲一些诸如怎样使字符串与整型数相互转化的C++技巧。这与TinyXML本身没什么关系,但它也许会对你的项目有所帮助,所以我还是把它加进来了。

    如果你不知道基本的C++概念,那么这份指南就没什么用了。同样的,如果你不知道什么是DOM,那先从其它地方找来看看吧。

    在我们开始之前

    一些将会被用到的XML数据集/文件。

    example1.xml:

    <?xml version="1.0" ?>
    <Hello>World</Hello>

    example2.xml:

    <?xml version="1.0" ?>
    <poetry>
       <verse>
          Alas
             Great World
                Alas (again)
       </verse>
    </poetry>

    example3.xml:

    <?xml version="1.0" ?>
    <shapes>
       <circle name="int-based" x="20" y="30" r="50" />
       <point name="float-based" x="3.5" y="52.1" />
    </shapes>

    example4.xml:

    <?xml version="1.0" ?>
    <MyApp>
       <!– Settings for MyApp –>
       <Messages>
          <Welcome>Welcome to MyApp</Welcome>
          <Farewell>Thank you for using MyApp</Farewell>
       </Messages>
       <Windows>
          <Window name="MainFrame" x="5" y="15" w="400" h="250" />
       </Windows>
       <Connection ip="192.168.0.1" timeout="123.456000" />
    </MyApp>

    开始

    把文件加载成XML

    把一个文件加载成TinyXML DOM的最简单方法是:

    TiXmlDocument doc( "demo.xml" );
    doc.LoadFile();

    一个更接近于现实应用的例子如下。它加载文件并把内容显示到标准输出STDOUT上:

    // 加载指定的文件并把它的结构输出到STDOUT上
    void dump_to_stdout(const char* pFilename)
    {
        TiXmlDocument doc(pFilename);
        bool loadOkay = doc.LoadFile();
        if (loadOkay)
        {
            printf("\n%s:\n", pFilename);
            dump_to_stdout( &doc ); // 稍后在指南中定义
        }
        else
        {
            printf("Failed to load file \"%s\”\n", pFilename);
        }
    }

    在main中使用此函数的一个简单应用示范如下:

    int main(void)
    {
        dump_to_stdout("example1.xml");
        return 0;
    }

    回想example1的XML:

    <?xml version="1.0" ?>
    <Hello>World</Hello>

    用这个XML运行程序就会在控制台/DOS窗口中显示:

    DOCUMENT
    + DECLARATION
    + ELEMENT Hello
      + TEXT[World]

    ”dump_to_stdout“函数稍后会在这份指南中定义,如果你想要理解怎样递归遍历一个DOM它会很有用。

    用程序建立文档对象

    这是用程序建立example1的方法:

    void build_simple_doc( )
    {
        // 生成xml: <?xml ..><Hello>World</Hello>
        TiXmlDocument doc;
        TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0""""" );
        TiXmlElement * element = new TiXmlElement( "Hello" );
        TiXmlText * text = new TiXmlText( "World" );
        element->LinkEndChild( text );
        doc.LinkEndChild( decl );
        doc.LinkEndChild( element );
        doc.SaveFile( "madeByHand.xml" );
    }

    然后可以用以下方法加载并显示在控制台上:

    dump_to_stdout("madeByHand.xml"); // 此函数稍后会中指南中定义

    你会看到跟example1一模一样:

    madeByHand.xml:
    Document
    + Declaration
    + Element [Hello]
      + Text: [World]

    这段代码会产生相同的XML DOM,但它以不同的顺序来创建和链接结点:

    void write_simple_doc2( )
    {
        // 实现与 write_simple_doc1一样的功能,(译注:我想它指是build_simple_doc)
        // 但尽可能早地把结点添加到树中。
        TiXmlDocument doc;
        TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0""""" );
        doc.LinkEndChild( decl );
        
        TiXmlElement * element = new TiXmlElement( "Hello" );
        doc.LinkEndChild( element );
        
        TiXmlText * text = new TiXmlText( "World" );
        element->LinkEndChild( text );
        
        doc.SaveFile( "madeByHand2.xml" );
    }

    两个都产生同样的XML,即:

    <?xml version="1.0" ?>
    <Hello>World</Hello>

    结构构成都是:

    DOCUMENT
    + DECLARATION
    + ELEMENT Hello
      + TEXT[World]

    属性

    给定一个存在的结点,设置它的属性是很容易的:

    window = new TiXmlElement( "Demo" ); 
    window->SetAttribute("name""Circle");
    window->SetAttribute("x", 5);
    window->SetAttribute("y", 15);
    window->SetDoubleAttribute("radius", 3.14159);

    你也可以用TiXmlAttribute对象达到同样的目的。

    下面的代码向我们展示了一种(并不只有一种)获取某一元素属性并打印出它们的名字和字符串值的方法,如果值能够被转化为整型数或者浮点数,也把值打印出来:

    // 打印pElement的所有属性。
    // 返回已打印的属性数量。
    int dump_attribs_to_stdout(TiXmlElement* pElement, unsigned int indent)
    {
        if ( !pElement ) return 0;
        
        TiXmlAttribute* pAttrib=pElement->FirstAttribute();
        int i=0;
        int ival;
        double dval;
        const char* pIndent=getIndent(indent);
        printf("\n");
        while (pAttrib)
        {
            printf( "%s%s: value=[%s]", pIndent, pAttrib->Name(), pAttrib->Value());
            
            if (pAttrib->QueryIntValue(&ival)==TIXML_SUCCESS) printf( " int=%d", ival);
            if (pAttrib->QueryDoubleValue(&dval)==TIXML_SUCCESS) printf( " d=%1.1f", dval);
            printf( "\n" );
            i++;
            pAttrib=pAttrib->Next();
        }
        return i;
    }

    把文档对象写到文件中

    把一个已经建立好的DOM写到文件中是非常简单的:

    doc.SaveFile( saveFilename );

    回想一下,比如example4:

    <?xml version="1.0" ?>
    <MyApp>
       <!– Settings for MyApp –>
       <Messages>
          <Welcome>Welcome to MyApp</Welcome>
          <Farewell>Thank you for using MyApp</Farewell>
       </Messages>
       <Windows>
          <Window name="MainFrame" x="5" y="15" w="400" h="250" />
       </Windows>
       <Connection ip="192.168.0.1" timeout="123.456000" />
    </MyApp>

    以下函数建立这个DOM并把它写到“appsettings.xml”文件中:

    void write_app_settings_doc( ) 

        TiXmlDocument doc; 
        TiXmlElement* msg;
        TiXmlDeclaration* decl = new TiXmlDeclaration( "1.0""""" ); 
        doc.LinkEndChild( decl ); 
        
        TiXmlElement * root = new TiXmlElement( "MyApp" ); 
        doc.LinkEndChild( root ); 
        
        TiXmlComment * comment = new TiXmlComment();
        comment->SetValue(" Settings for MyApp " ); 
        root->LinkEndChild( comment ); 
        
        TiXmlElement * msgs = new TiXmlElement( "Messages" ); 
        root->LinkEndChild( msgs ); 
        
        msg = new TiXmlElement( "Welcome" ); 
        msg->LinkEndChild( new TiXmlText( "Welcome to MyApp" )); 
        msgs->LinkEndChild( msg ); 
        
        msg = new TiXmlElement( "Farewell" ); 
        msg->LinkEndChild( new TiXmlText( "Thank you for using MyApp" )); 
        msgs->LinkEndChild( msg ); 
        
        TiXmlElement * windows = new TiXmlElement( "Windows" ); 
        root->LinkEndChild( windows ); 
        
        TiXmlElement * window;
        window = new TiXmlElement( "Window" ); 
        windows->LinkEndChild( window ); 
        window->SetAttribute("name""MainFrame");
        window->SetAttribute("x", 5);
        window->SetAttribute("y", 15);
        window->SetAttribute("w", 400);
        window->SetAttribute("h", 250);
        
        TiXmlElement * cxn = new TiXmlElement( "Connection" ); 
        root->LinkEndChild( cxn ); 
        cxn->SetAttribute("ip""192.168.0.1");
        cxn->SetDoubleAttribute("timeout", 123.456); // 浮点数属性
        
        dump_to_stdout( &doc );
        doc.SaveFile( "appsettings.xml" ); 
    }

    dump_to_stdout函数将显示如下结构:

    Document
    + Declaration
    + Element [MyApp]
      (No attributes)
      + Comment: [ Settings for MyApp ]
      + Element [Messages]
      (No attributes)
        + Element [Welcome]
      (No attributes)
          + Text: [Welcome to MyApp]
        + Element [Farewell]
      (No attributes)
          + Text: [Thank you for using MyApp]
      + Element [Windows]
      (No attributes)
        + Element [Window]
          + name: value=[MainFrame]
          + x: value=[5] int=5 d=5.0
          + y: value=[15] int=15 d=15.0
          + w: value=[400] int=400 d=400.0
          + h: value=[250] int=250 d=250.0
          5 attributes
      + Element [Connection]
        + ip: value=[192.168.0.1] int=192 d=192.2
        + timeout: value=[123.456000] int=123 d=123.5
        2 attributes

    TinyXML默认以其它APIs称作“pretty”格式的方式来输出XML,对此我感到惊讶。这种格式修改了元素的文本结点中的空格,以使输出来的结点树包含一个嵌套层标记。

    我还没有仔细看当写到一个文件中时是否有办法关闭这种缩进——这肯定很容易做到。(译注:这两句话大概是Ellers说的

    [Lee:在STL模式下这很容易做到,只需要cout << myDoc就行了。在非STL模式下就总是用“pretty”格式了,加多一个开关是一个很好的特性,这已经被要求过了。]

    XML与C++对象的相互转化

    介绍

    这个例子假设你在用一个XML文件来加载和保存你的应用程序配置,举例来说,有点像example4.xml。

    有许多方法可以做到这点。例如,看看TinyBind项目:http://sourceforge.net/projects/tinybind

    这一节展示了一种普通老式的方法来使用XML加载和保存一个基本的对象结构。

    建立你的对象类

    从一些像这样的基本类开始:

    #include <string>
    #include <map>
    using namespace std;
     
    typedef std::map<std::string,std::string> MessageMap;
     
    // 基本的窗口抽象 - 仅仅是个示例
    class WindowSettings
    {
        public:
        int x,y,w,h;
        string name;
        
        WindowSettings()
            : x(0), y(0), w(100), h(100), name("Untitled")
        {
        }
        
        WindowSettings(int x, int y, int w, int h, const string& name)
        {
            this->x=x;
            this->y=y;
            this->w=w;
            this->h=h;
            this->name=name;
        }
    };
    &nbsp;
    class ConnectionSettings
    {
        public:
        string ip;
        double timeout;
    };
     
    class AppSettings
    {
        public:
        string m_name;
        MessageMap m_messages;
        list<WindowSettings> m_windows;
        ConnectionSettings m_connection;
        
        AppSettings() {}
        
        void save(const char* pFilename);
        void load(const char* pFilename);
        
        // 仅用于显示它是如何工作的
        void setDemoValues()
        {
            m_name="MyApp";
            m_messages.clear();
            m_messages["Welcome"]="Welcome to "+m_name;
            m_messages["Farewell"]="Thank you for using "+m_name;
            m_windows.clear();
            m_windows.push_back(WindowSettings(15,15,400,250,"Main"));
            m_connection.ip="Unknown";
            m_connection.timeout=123.456;
        }
    };

    这是一个基本的mian(),它向我们展示了怎样创建一个默认的settings对象树,怎样保存并再次加载:

    int main(void)
    {
        AppSettings settings;
        
        settings.save("appsettings2.xml");
        settings.load("appsettings2.xml");
        return 0;
    }

    接下来的main()展示了如何创建,修改,保存和加载一个settings结构:

    int main(void)
    {
        // 区块:定制并保存settings
        {
            AppSettings settings;
            settings.m_name="HitchHikerApp";
            settings.m_messages["Welcome"]="Don’t Panic";
            settings.m_messages["Farewell"]="Thanks for all the fish";
            settings.m_windows.push_back(WindowSettings(15,25,300,250,"BookFrame"));
            settings.m_connection.ip="192.168.0.77";
            settings.m_connection.timeout=42.0;
            
            settings.save("appsettings2.xml");
        }
        
        // 区块:加载settings
        {
            AppSettings settings;
            settings.load("appsettings2.xml");
            printf("%s: %s\n", settings.m_name.c_str(), 
            settings.m_messages["Welcome"].c_str());
            WindowSettings & w=settings.m_windows.front();
            printf("%s: Show window ’%s’ at %d,%d (%d x %d)\n"
            settings.m_name.c_str(), w.name.c_str(), w.x, w.y, w.w, w.h);
            printf("%s: %s\n", settings.m_name.c_str(),

                               settings.m_messages["Farewell"].c_str());
        }
        return 0;
    }

    当save()和load()完成后(请看下面),运行这个main()就会在控制台看到:

    HitchHikerApp: Don’t Panic
    HitchHikerApp: Show window ‘BookFrame’ at 15,25 (300 x 100)
    HitchHikerApp: Thanks for all the fish

    把C++状态编码成XML

    有很多方法能够做到把文档对象保存到文件中,这就是其中一个:

    void AppSettings::save(const char* pFilename)
    {
        TiXmlDocument doc; 
        TiXmlElement* msg;
        TiXmlComment * comment;
        string s;
        TiXmlDeclaration* decl = new TiXmlDeclaration( "1.0""""" ); 
        doc.LinkEndChild( decl ); 
        
        TiXmlElement * root = new TiXmlElement(m_name.c_str()); 
        doc.LinkEndChild( root ); 
        
        comment = new TiXmlComment();
        s=" Settings for "+m_name+" ";
        comment->SetValue(s.c_str()); 
        root->LinkEndChild( comment ); 
        
        // 区块:messages
        {
            MessageMap::iterator iter;
            
            TiXmlElement * msgs = new TiXmlElement( "Messages" ); 
            root->LinkEndChild( msgs ); 
            
            for (iter=m_messages.begin(); iter != m_messages.end(); iter++)
            {
                const string & key=(*iter).first;
                const string & value=(*iter).second;
                msg = new TiXmlElement(key.c_str()); 
                msg->LinkEndChild( new TiXmlText(value.c_str())); 
                msgs->LinkEndChild( msg ); 
            }
        }
        
        // 区块:windows
        {
            TiXmlElement * windowsNode = new TiXmlElement( "Windows" ); 
            root->LinkEndChild( windowsNode ); 
            
            list<WindowSettings>::iterator iter;
            
            for (iter=m_windows.begin(); iter != m_windows.end(); iter++)
            {
                const WindowSettings& w=*iter;
                
                TiXmlElement * window;
                window = new TiXmlElement( "Window" ); 
                windowsNode->LinkEndChild( window ); 
                window->SetAttribute("name", w.name.c_str());
                window->SetAttribute("x", w.x);
                window->SetAttribute("y", w.y);
                window->SetAttribute("w", w.w);
                window->SetAttribute("h", w.h);
            }
        }
        
        // 区块:connection
        {
            TiXmlElement * cxn = new TiXmlElement( "Connection" ); 
            root->LinkEndChild( cxn ); 
            cxn->SetAttribute("ip", m_connection.ip.c_str());
            cxn->SetDoubleAttribute("timeout", m_connection.timeout); 
        }
        
        doc.SaveFile(pFilename); 
    }

    用修改过的main运行会生成这个文件:

    <?xml version="1.0" ?>
    <HitchHikerApp>
       <!– Settings for HitchHikerApp –>
       <Messages>
          <Farewell>Thanks for all the fish</Farewell>
          <Welcome>Don&apos;t Panic</Welcome>
       </Messages>
       <Windows>
          <Window name="BookFrame" x="15" y="25" w="300" h="250" />
       </Windows>
       <Connection ip="192.168.0.77" timeout="42.000000" />
    </HitchHikerApp>

    从XML中解码出状态

    就像编码一样,也有许多方法可以让你从自己的C++对象结构中解码出XML。下面的方法使用了TiXmlHandles。

    void AppSettings::load(const char* pFilename)
    {
        TiXmlDocument doc(pFilename);
        if (!doc.LoadFile()) return;
        
        TiXmlHandle hDoc(&doc);
        TiXmlElement* pElem;
        TiXmlHandle hRoot(0);
        
        // 区块:name
        {
            pElem=hDoc.FirstChildElement().Element();
            // 必须有一个合法的根结点,如果没有则温文地处理(译注:直接返回
            if (!pElem) return;
            m_name=pElem->Value();
            
            // 保存起来以备后面之用
            hRoot=TiXmlHandle(pElem);
        }
        
        // 区块:string table
        {
            m_messages.clear(); // 清空已有的table
            
            pElem=hRoot.FirstChild( "Messages" ).FirstChild().Element();
            for( pElem; pElem; pElem=pElem->NextSiblingElement())
            {
                const char *pKey=pElem->Value();
                const char *pText=pElem->GetText();
                if (pKey && pText) 
                {
                    m_messages[pKey]=pText;
                }
            }
        }
        
        // 区块:windows
        {
            m_windows.clear(); // 清空链表
            
            TiXmlElement* pWindowNode=hRoot.FirstChild( "Windows" )

                                           .FirstChild().Element();
            for( pWindowNode; pWindowNode;

                 pWindowNode=pWindowNode->NextSiblingElement())
            {
                WindowSettings w;
                const char *pName=pWindowNode->Attribute("name");
                if (pName) w.name=pName;
                
                pWindowNode->QueryIntAttribute("x", &w.x); // 如果失败,原值保持现状
                pWindowNode->QueryIntAttribute("y", &w.y);
                pWindowNode->QueryIntAttribute("w", &w.w);
                pWindowNode->QueryIntAttribute("hh", &w.h);
                
                m_windows.push_back(w);
            }
        }
        
        // 区块:connection
        {
            pElem=hRoot.FirstChild("Connection").Element();
            if (pElem)
            {
                m_connection.ip=pElem->Attribute("ip");
                pElem->QueryDoubleAttribute("timeout",&m_connection.timeout);
            }
        }
    }

    dump_to_stdout的完整列表

    下面是一个可直接运行的示例程序,使用上面提到过的递归遍历方式,可用来加载任意的XML文件并把结构输出到STDOUT上。

    // 指南示例程序
    #include "stdafx.h"
    #include "tinyxml.h"
     
    // ———————————————————————-
    // STDOUT输出和缩进实用函数
    // ———————————————————————-
    const unsigned int NUM_INDENTS_PER_SPACE=2;
     
    const char * getIndent( unsigned int numIndents )
    {
        static const char * pINDENT=" + ";
        static const unsigned int LENGTH=strlen( pINDENT );
        unsigned int n=numIndents*NUM_INDENTS_PER_SPACE;
        if ( n > LENGTH ) n = LENGTH;
        
        return &pINDENT[ LENGTH-n ];
    }
     
    // 与getIndent相同,但最后没有“+”
    const char * getIndentAlt( unsigned int numIndents )
    {
        static const char * pINDENT=" ";
        static const unsigned int LENGTH=strlen( pINDENT );
        unsigned int n=numIndents*NUM_INDENTS_PER_SPACE;
        if ( n > LENGTH ) n = LENGTH;
        
        return &pINDENT[ LENGTH-n ];
    }
     
    int dump_attribs_to_stdout(TiXmlElement* pElement, unsigned int indent)
    {
        if ( !pElement ) return 0;
        
        TiXmlAttribute* pAttrib=pElement->FirstAttribute();
        int i=0;
        int ival;
        double dval;
        const char* pIndent=getIndent(indent);
        printf("\n");
        while (pAttrib)
        {
            printf( "%s%s: value=[%s]", pIndent, pAttrib->Name(), pAttrib->Value());
            
            if (pAttrib->QueryIntValue(&ival)==TIXML_SUCCESS) printf( " int=%d", ival);
            if (pAttrib->QueryDoubleValue(&dval)==TIXML_SUCCESS) printf( " d=%1.1f", dval);
            printf( "\n" );
            i++;
            pAttrib=pAttrib->Next();
        }
        return i; 
    }
     
    void dump_to_stdout( TiXmlNode* pParent, unsigned int indent = 0 )
    {
        if ( !pParent ) return;
        
        TiXmlNode* pChild;
        TiXmlText* pText;
        int t = pParent->Type();
        printf( "%s", getIndent(indent));
        int num;
        
        switch ( t )
        {
            case TiXmlNode::DOCUMENT:
                printf( "Document" );
                break;
            
            case TiXmlNode::ELEMENT:
                printf( "Element [%s]", pParent->Value() );
                num=dump_attribs_to_stdout(pParent->ToElement(), indent+1);
                switch(num)
                {
                    case 0: printf( " (No attributes)"); break;
                    case 1: printf( "%s1 attribute", getIndentAlt(indent)); break;
                    default: printf( "%s%d attributes", getIndentAlt(indent), num); break;
                }
                break;
            
            case TiXmlNode::COMMENT:
                printf( "Comment: [%s]", pParent->Value());
                break;
            
            case TiXmlNode::UNKNOWN:
                printf( "Unknown" );
                break;
            
            case TiXmlNode::TEXT:
                pText = pParent->ToText();
                printf( "Text: [%s]", pText->Value() );
                break;
            
            case TiXmlNode::DECLARATION:
                printf( "Declaration" );
                break;
                default:
                break;
        }
        printf( "\n" );
        for ( pChild = pParent->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) 
        {
            dump_to_stdout( pChild, indent+1 );
        }
    }
     
    // 加载指定的文件并把它的结构输出到STDOUT上
    void dump_to_stdout(const char* pFilename)
    {
        TiXmlDocument doc(pFilename);
        bool loadOkay = doc.LoadFile();
        if (loadOkay)
        {
            printf("\n%s:\n", pFilename);
            dump_to_stdout( &doc ); 
        }
        else
        {
            printf("Failed to load file \"%s\”\n", pFilename);
        }
    }
     
    // ———————————————————————-
    // main(),打印出从命令行指定的文件
    // ———————————————————————-
    int main(int argc, char* argv[])
    {
        for (int i=1; i<argc; i++)
        {
            dump_to_stdout(argv[i]);
        }
        return 0;
    }

    从命令行或者DOS窗口运行它,例如:

    C:\dev\tinyxml> Debug\tinyxml_1.exe example1.xml
    example1.xml:
    Document
    + Declaration
    + Element [Hello]
      (No attributes)
      + Text: [World]

    作者与修改

    • Ellers写于2005年4,5,6月
    • Lee Thomason于2005年9月略加编辑后集成到文档系统中
    • Ellers于2005年10月做了更新
  • 相关阅读:
    Splay模板(bzoj 1588)
    PDD----配对堆
    [BZOJ4025] 二分图 LCT/(线段树分治+并查集)
    FFT模板 生成函数 原根 多项式求逆 多项式开根
    [BZOJ4566][Haoi2016]找相同字符 后缀自动机+dp
    [BZOJ1503] [NOI2004]郁闷的出纳员 splay
    [loj#2566][BZOJ5333] [Sdoi2018]荣誉称号 树形dp
    [BZOJ4824][Cqoi2017]老C的键盘 树形dp+组合数
    [BZOJ5305][Haoi2018]苹果树 组合数
    [BZOJ2669][cqoi2012]局部极小值 状压dp+容斥原理
  • 原文地址:https://www.cnblogs.com/flying_bat/p/955332.html
Copyright © 2011-2022 走看看