TinyXML2是一个解析XML文件的开源库,它支持对XML的读写操作,其代码出自Lee Thomason之手,在拜读TinyXML2的过程中学习到了很多,向Lee Thomason致敬。
TinyXML2的设计思路
XML格式
XML文件是一种树状结构,必须包含一个根节点,该节点是其他节点的父节点(当然XML声明和注释可以和root节点为兄弟关系)。常规的XML文件格式如下:
<? xml declaration?>
<!--xml comment-->
<rootElement>
<childElement1>
<subchildElement11>Text</subchildElement11>
<subchildElement12 AttributeKey = "AttributeValue"></subchildElement12>
</childElement1>
<childElement2>
<subchildElement21>"<![CDATA[CDATA Text]]>"</subchildElement21>
</childElement2>
</rootElement>
类设计
由于XML的树状结构,TinyXML2将XML的节点抽象为XMLNode,XML中除了把属性key-value抽象为XMLAttribute类型外,其余的都看作XMLNode的子类,首先将整个XML文档抽象为XMLDocument,将声明部分抽象为XMLDeclaration,将注释抽象为XMLComment,将元素抽象为XMLElement,将文本抽象为XMLText。
READ
TinyXML2的读取XML的设计思想是将XML文件一次性的读入内存中,对字符串逐字解析,期间使用深度优先遍历(DFS)和多态特性进行不同类型元素的解析。
XMLDocument的Identify函数起到实例化XML节点的作用,它主要根据XML的数据头判断其XMLNode的类型并进行实例化。每个XMLNode子类都有自身的解析函数,这是解析多态的体现。
start with | type |
---|---|
"<?" | XMLDeclaration |
"<!--" | XMLComment |
"<![CDATA[" | XMLText |
"<" | XMLElement |
other | XMLText |
WRITE
TinyXML2为写XML提供了两种方式,一种是构建XMLDocument,一种是直接push属性建立。两者都使用了XMLPrinter类。不同的是构建XMLDocument方式XMLPrinter类为隐式调用,不需要程序员去构建XMLPrinter对象。
设计模式
场景 | 设计模式 | 用途 |
---|---|---|
READ | 组合模式 | XMLDocument由?个XMLDeclaration、*个XMLComment、XMLElement、XMLText、XMLAttribute构建而成,XMLDocument的parse解析XML的函数是由各叶子节点的解析而成的 |
WRITE | 访问者模式 | XMLPrinter是XMLVisitor的子类,XMLVisitor是访问者模式在TinyXML的应用,主要读取节点信息。 |
TinyXML2设计之我见
TinyXML2是Lee Thomason 重要的开源贡献,本人对其非常尊敬。本着学习的态度,认为TinyXML2设计非常好的几点:
- TinyXML2低成本、接口设计优秀
一个良好的开源库的使用应该是学习代价低的,而TinyXML2的代码整体几千行代码,适合快速掌握。同时TinyXML2的库的接口含义明确,且接口的使用方式明确。 - 良好的内存管理机制
C\C++的内存管理对所有的程序员都是灾难,在C++11中引入智能指针解决了部分问题,而却导致了部分效率的小的下降。TinyXML2采用的方式是把内存管理控制起来,给出了MemPool和MemPoolT的实现,把XML内部的对象实例化控制起来,避免了用户直接去创建对象而导致的内存泄漏问题。一个设计精良的库需要将复杂性包装在内部而非暴露给用户,而这在TinyXML2的各XMLNode具体对象实例化时感受深刻。 - 对问题的抽象
TinyXMl2作为轻量级的XML解析库,它本身良好的代码风格、对XML解析的抽象、对共性及变化的抽离做的非常好。 - 对错误的处理
TinyXMl2对错误的处理较为细致,可以准确的告知XML解析时格式错误类别及错误行号,而作为从业者对异常或错误的处理大都稍显粗糙。而开源库的设计在这方面需要做大量的工作来保证用户可以更清晰准确的发现问题。 - 使用较少的内存,处理速度更快
TinyXMl2将XML文件整个读入内存,其他解析及存储部分都是对这片内存区域进行处理,几乎没有内存拷贝,提高了解析效率。 - 调试代码库非常完善
一个出色的程序员的代码应该是包含大量测试用例保证的,目前TDD的流行就是印证,大量的测试用例保证了代码的健壮性及重构的可操作性。 - 对细节的追求
XML的数据类型较多,而其中double、float类型处理稍显复杂,而TinyXML2提供了很优秀的方式,并在这方面精益求精。
站在2019年当下去评判这套代码稍显苛刻,但个人认为的一些缺憾。
- 对函数式编程支持的缺乏
由于TinyXML2在前几年编写,当时函数式编程尚不流行,但是仍然觉得缺憾,比如对一个Element设置属性时,如果有多个属性需要设置就会有大量的重复代码产生,而只需对源码做一点很小的变化就会让链式编程发挥作用,减少冗余代码。 - 友元的设计破坏封装
TinyXML2将XML里的类抽象为XMLNode类的子类,而由于这些子类之间有很强的耦合性,并且考虑到合理的接口设计,非用户功能不暴露给用户,这样在代码中大量使用了友元的设计,而友元的设计在系统设计阶段由于其对封装性的破坏而不被推荐。但是这也是在对外接口设计与类抽象之间做的无奈的折中选择。当然作者认识到了这个问题,并且在代码中有提到对C++语法的期待。
TinyXML2源码解析
StrPair类
StrPair类是TinyXML2的字符串类,主要作用用于解析读入的XML文件的内存,并使用start和end分别记录字符串的开始和结束字符。Flag标识中NEEDS_ENTITY_PROCESSING作用是处理实体(e.g "<" ">" "&" """ "'" 中),NEEDS_WHITESPACE_COLLAPSING用于空白字符的处理,XML对空白字符的处理有两种方式,一是保留空白字符串,二是空白字符崩溃,其实是将连续空白字符变为1个空白字符。
DynArray类
DynArray类是TinyXML2中的动态数组类,其实现与std::vector类似,在容量不够的情况下申请2倍的空间。但是其保存的信息需为POD类型,因为其实现没有调用所存数据的构造或析构函数。
MemPoolT类
MemPool类只是内存池的接口定义,MemPoolT类是TinyXML2的内存实现,其对内存的实现简单而精炼,而union的巧妙使用也减少了内存资源的浪费。
XMLVisitor类
XMLVisitor类是访问者模式的基类,它主要定义了访问者的接口,而在XMLNode的子类的accept方法中调用这些方法来完成对自身的访问。
XMLUtil类
XMLUtil类是工具类,提供TinyXML2解析过程中可用到的工具函数。
XMLNode类
XMLNode类是几乎XML所有元素(XMLAttribute除外)的基类,XML本质是一种树形结构,而整个XML就是由许多的节点(XMLNode)组成,在TinyXML2中每个XMLNode节点都保存了父亲、前驱、后继、孩子头节点和孩子尾节点信息,便于查询、插入、检索。
XMLText类
XMLText类主要是处理XML文本的类,文本信息又分为CDATA和普通文本。CDATA是有专属的开始字符"<![CDATA[",而普通的文本存储形式如">text<"。理解其存储位置对于读取其解析代码大有裨益。
XMLComment类
XMLComment类主要是处理XML注释的类,注释的存储形式为""。
XMLDeclaration类
XMLDeclaration类主要是处理XML中声明的类,声明的存储形式为"<? declaration ?>"。
XMLUnknown类
XMLUnknown类存储形式为"<! unknown>"。
XMLAttribute类
XMLAttribute类是解析XML的属性的类,XML中的属性都与XML的Element绑定,并且为key-value类型。
XMLElement类
XMLElement类是XMLNode中最重要的一个类,其存储方式有<foo/>和<foo></foo>两种形式,它包含了一个XMLAttribute的根指针,这个root指针指向XMLAttribute的第一个属性键值对。
XMLDocument类
XMLDocument类是TinyXML2的XML整体类,可以认为是组合模式中的整体,而其他类都是部分或者叶子。TinyXML2中只有XMLDocument类可以被实例化,其他的类必须通过XMLDocument提供的new方法进行实例化,而不能直接实例化。XMLNode的其他实体类把构造函数定义为protected,不能被外部实例化,这样保证使用XMLDocument进行内存的管理,避免产生内存泄漏的风险。
XMLHandle类
XMLHandle类同样是设计精巧的类,内容不多,但有效的避免了C++中讨厌的空指针问题,在一定程度上也支持了函数式编程,这也为开源库的设计指明了方向。开源库接口设计不但要考虑功能性,用户体现及书写的便捷性同样重要。
XMLPrinter类
XMLPrinter类是XMLVisitor类的子类,主要实现的写XML的功能,其提供了两种书写方式,一是构建XMLDocument,二是直接push字段。
TinyXML2简单易用,设计巧妙,向Lee Thomason致敬。
WalkeR-ZG