可凡倾听2012:TinyXML应用例子

来源:百度文库 编辑:中财网 时间:2024/04/28 06:38:01
完整显示   1 2

开发软件时经常需要把一些东西做成可配置的,于是就需要用到配置文件,以前多是用ini文件,然后自己写个类来解析。现在有了XML,许多应用软件就喜欢把配置文件做成XML格式。但是如果我们的程序本身很小,为了读取个配置文件却去用Xerces XML之类的库,恐怕会得不偿失。那么用TinyXML吧,它很小,只有六个文件,加到项目中就可以开始我们的配置文件之旅了。

前些时候我恰好就用TinyXML写了一个比较通用的配置文件类,基本可以适应大部分的场合,不过配置文件只支持两层结构,如果需要支持多层嵌套结构,那还需要稍加扩展一下。

从下面的源代码中,你也可以看到怎么去使用TinyXML,也算是它的一个应用例子了。

 

/*
** FileName:    config.h
** Author:        hansen
** Date:        May 11, 2007
** Comment:        配置文件类,主要用来读取xml配置文件中的一些配置信息
*/

 
#ifndef _CONFIG
#define _CONFIG
 
#include <string>
#include "tinyxml.h"
 
using namespace std;
 
class CConfig
{
public:
    explicit CConfig(const char* xmlFileName)
        :mXmlConfigFile(xmlFileName),mRootElem(0)
    {
        //加载配置文件
        mXmlConfigFile.LoadFile();    
        
        //得到配置文件的根结点
        mRootElem=mXmlConfigFile.RootElement();
    }
 
public:
    //得到nodeName结点的值
    string GetValue(const string& nodeName);
 
private:
    //禁止默认构造函数被调用
    CMmsConfig();
 
private:
    TiXmlDocument    mXmlConfigFile;
    TiXmlElement*    mRootElem;
 
};
 
#endif

 

 

/*
** FileName:    config.cpp
** Author:        hansen
** Date:        May 11, 2007
** Comment:        
*/

 
#include "config.h"
#include 
 
string CConfig::GetValue(const string& nodeName)
{
    if(!mRootElem)
    {
        cout<<"读取根结点出错"<        return "";
    }
 
    TiXmlElement* pElem=mRootElem->FirstChildElement(nodeName.c_str());
    if(!pElem)
    {
        cout<<"读取"<"结点出错"<        return "";
    }
 
    return pElem->GetText();
 
}
 
 
int main()
{
    CConfig xmlConfig("XmlConfig.xml");
 
    //获取Author的值
    string author = xmlConfig.GetValue("Author");
    cout<<"Author:"< 
    //获取Site的值
    string site = xmlConfig.GetValue("Site");
    cout<<"Site:"< 
    //获取Desc的值
    string desc = xmlConfig.GetValue("Desc");
    cout<<"Desc:"<    
    return 0;
}

 

假设配置文件是这样的:

 
"1.0" encoding="GB2312" ?>

    hansen
    www.hansencode.cn
    这是个测试程序

 

怎么使用上面的配置类来读取XmlConfig.xml文件中的配置呢?很简单:

int main()
{
    CConfig xmlConfig("XmlConfig.xml");
 
    //获取Author的值
    string author = xmlConfig.GetValue("Author");
    cout<<"Author:"< 
    //获取Site的值
    string site = xmlConfig.GetValue("Site");
    cout<<"Site:"< 
    //获取Desc的值
    string desc = xmlConfig.GetValue("Desc");
    cout<<"Desc:"<    
    return 0;
}

 

运行结果如下:

D:\config\Debug>config.exe
Author:hansen
Site:www.hansencode.cn
Desc:这是个测试程序

 

完整显示   1 2 六月 09

TinyXML中文指南

TinyXML 1 Comment »完整显示   1 2 3 4

 

译注:本文是TinyXML 2.5.2版本Tutorial的中文译文,经原作者Lee Thomason同意由hansen翻译,如有误译或者错漏,欢迎指正。
版权:版权归原作者所有,翻译文档版权归本人hansen所有,转载请注明出处。
原文:http://www.grinninglizard.com/tinyxmldocs/tutorial0.html


 

TinyXML 指南

 

这是什么?

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

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

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

在我们开始之前

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

example1.xml:

 


World

example2.xml:

 



   
      Alas
         Great World
            Alas (again)
   

example3.xml:

 



   
   

example4.xml:

 



   
   
      Welcome to MyApp
      Thank you for using 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:

 


World

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

 

DOCUMENT
+ DECLARATION
+ ELEMENT Hello
  + TEXT[World]

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

用程序建立文档对象

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

 

void build_simple_doc( )
{
    // 生成xml: World
    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,即:

 


World

结构构成都是:

 

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:

 



   
   
      Welcome to MyApp
      Thank you for using 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 
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;
    }
};
 
class ConnectionSettings
{
    public:
    string ip;
    double timeout;
};
 
class AppSettings
{
    public:
    string m_name;
    MessageMap m_messages;
    list 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::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运行会生成这个文件:

 



   
   
      Thanks for all the fish
      Don't Panic
   

   
      
   

   

从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    {
        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月做了更新