tinyXML源码刨析(上)--解析树的生成

tinyXML是我接触的第一个源码,花了几天的时间把整个源码研究了一遍,虽然有些细节的实现还不是很懂,但整个程序的框架和各部分之间的关系已经有了些了解,把学习的成果写下来,也算整理一下思路,可能有不对的地方,希望各位看官批评指正。
对XML文件的介绍请转向: https://www.ibm.com/developerworks/cn/xml/x-newxml/#list1
使用tinyXML解析XML文件的实例,请看 https://blog.csdn.net/u012796139/article/details/46706853

各个文件之间的组织关系:
tinyxml.h头文件写了几乎所有类的声明,与之对应的类的实现分别在tinyxml.cpp,tinyxmlerror,tinyxmlparser中。
tinystr.h头文件中声明了tinyxml自己定义的字符串类(程序没有使用STL的string类),与之对应的类的定义在tinystr.cpp中。
xmltest.cpp是测试程序,包含了tinyxml.h和tinystr.h两个头文件。(运行前还需要把类的实现,即三个cpp文件添加到工程中)

tinyXML定义了TiXmlBase一个虚基类,是其他所有类的基类,各个类之间的关系:在这里插入图片描述tinyXML采用文档对象模型(DOM)来解析XML文件,即把整个文档解析为一个多个对象组成的树形结构,方便用户修改对象的内容。

拿下面这个官方文档里给的例子来解释

 <?xml version="1.0" standalone=no>
 <!-- Our to do list data -->
 <ToDo>
       <Item priority="1"> Go to the <bold>Toy store!</bold></Item>
       <Item priority="2"> Do bills</Item>
 </ToDo>

TiXmlDocument:指向整个文档
TiXmlDeclaration:文件的声明部分,对应 <?xml version=“1.0” standalone=no>
TiXmlComment:文件的注释部分,对应 < !-- Our to do list data -->
TiXmlElement:xml文件的元素,对应 < ToDo > < /ToDo >, < Item priority=“1”>< /Item>, < bold> < /bold>等
TiXmlText:文本内容,对应“Go to the”,“Do bills”等。
其他部分都是TiXmlUnknown。

tinyXML把文档解析为这样的树:

TiXmlDocument     "demo.xml"
            TiXmlDeclaration   "version='1.0'" "standalone=no"
            TiXmlComment    " Our to do list data"
            TiXmlElement    "ToDo"
                       TiXmlElement   "Item" Attribtutes: priority = 1
                                TiXmlText   "Go to the "
                                TiXmlElement  "bold"
                                     TiXmlText  "Toy store!"
                       TiXmlElement   "Item" Attributes: priority=2
             TiXmlText   "Do bills"

在介绍图中的各个类之前,首先介绍几个工具 类/结构体

  1. struct TiXmlCursor 类似指针作用的类,指示解析到了文档的哪一行哪一列
  2. class TiXmlParsingData 包含要解析数据的所有信息----在文档中的位置,要解析的char型数组等信息。
  3. class TiXmlAttributeSet, 用于在多个TiXmlAttribute对象间构建循环链表,主要用于TiXmlElement 的属性中,因为TiXmlElement 属性的类型和数目都不确定,所以需要统一组织。但是TiXmlDeclaration由于属性只有version,encoding,standalone三个,而且顺序已经定死,所以不需要再用TiXmlAttributeSet管理。

TiXmlBase类:
派生类直接继承的成员:

  • 静态成员–Entity 结构体数组,用来定义5个实体引用
  • 静态成员–bool型变量condenseWhiteSpace,用来决定是否要压缩空格
  • 静态成员函数-- ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ),用来把UTF32转换为UTF8
  • 静态成员函数–static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding );输入的字符是否为字母。
  • 静态成员函数–static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding );输入的字符是否为数字或字母
  • 静态内联函数–inline static int ToLower( int v, TiXmlEncoding encoding )把大写字母转化为小写
  • 静态字符串数组static const char* errorString[TIXML_ERROR_STRING_COUNT ];定义了发生错误时输出的表示错误类型的字符串
  • TiXmlCursor 型对象–TiXmlCursor location ,用来保存文档解析的位置
    TiXmlAttribute类:
    定义了属性的名字和值,以及指向前方和后方属性的指针,用于在TiXmlElement中组建TiXmlAttributeSet对象。
	TIXML_STRING name;
	TIXML_STRING value;
	TiXmlAttribute*	prev;
	TiXmlAttribute*	next;
	const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )

最重要的,是TiXmlAttribute的parse函数
它首先调用TiXmlParsingData::stamp()函数标记开始解析处在文档中的位置
再调用readname,把属性的名字写入TIXML_STRING name中
最后调用ReadText函数,把属性的值写入value中

TiXmlDeclaration类:
定义了三个 TIXML_STRING对象来保存对应的属性。
在TiXmlDeclaration的parse函数中按顺序分别判断属性是否为version,encoding,standalone,判断一致后创建三次TiXmlAttribute对象来解析,解析的结果存储在三个TIXML_STRING中。因为有自己定义的三个TIXML_STRING,所以就没有用从TiXmlNode继承来的TIXML_STRING value。

 

TiXmlNode类
定义了所有节点类都需要的数据成员:
指向父节点,子节点和兄弟节点的指针TiXmlNode* parent,firstChild,lastChild,prev,next;
保存每个节点内容的TIXML_STRING value;

TiXmlDocument类:
独有的成员:int tabsize;
独有的函数:
bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ).
LoadFile函数做了什么?
1.利用TiXmlFOpen函数把xml文件读入文件流中
2.再用fread函数把流中的文件写入一个char型数组中
3.把char型数组中"\r\n",’\r’,统一转换为’\n’,以统一不同系统的文件换行方式。
4.用parse函数对第三步转化得到的char型数组进行解析,并按节点形成树型结构

第四步是整个程序的核心,接下来仔细的解析整个parse的过程。

const char* TiXmlDocument::Parse(const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding)
{
	ClearError();
	if ( !p || !*p )
	{
		SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
		return 0;
	}

	// Note that, for a document, this needs to come
	// before the while space skip, so that parsing
	// starts from the pointer we are given.
	location.Clear();
	if ( prevData )
	{
		location.row = prevData->cursor.row;
		location.col = prevData->cursor.col;
	}
	else
	{
		location.row = 0;
		location.col = 0;
	}
	TiXmlParsingData data( p, TabSize(), location.row, location.col );//需要解析的数据
	location = data.Cursor();//返回解析数据的位置

   //当不知道编码模式时,检查UTF-8先导字节,如果存在,则用UTF-8解码文件
	if ( encoding == TIXML_ENCODING_UNKNOWN )
	{
		// Check for the Microsoft UTF-8 lead bytes.
		const unsigned char* pU = (const unsigned char*)p;
		if (	*(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0
			 && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1
			 && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 )
		{
			encoding = TIXML_ENCODING_UTF8;
			useMicrosoftBOM = true;
		}
	}
   //跳过重复的空格字符
    p = SkipWhiteSpace( p, encoding );
	if ( !p )
	{
		SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
		return 0;
	}
   //这个while循环是关键,按节点把整个文档解析为一棵树
	while ( p && *p )
	{
	//Identify语句识别出下一个节点的类型,创建一个空的对象,并把它的父指针设为document,返回对象指针。
		TiXmlNode* node = Identify( p, encoding );
		if ( node )
		{    //通过动态绑定,调用对应类型的节点的parse函数进行解析。
			p = node->Parse( p, &data, encoding );
			LinkEndChild( node );//解析出来的节点链接到父节点的最后一个孩子上
		}
		else
		{
			break;
		}

		// 如果没有识别出来UTF-8的先导字节,那再去找declaration里的encoding信息
		if (    encoding == TIXML_ENCODING_UNKNOWN
			 && node->ToDeclaration() )
		{
			TiXmlDeclaration* dec = node->ToDeclaration();
			const char* enc = dec->Encoding();
			assert( enc );

			if ( *enc == 0 )
				encoding = TIXML_ENCODING_UTF8;
			else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) )
				encoding = TIXML_ENCODING_UTF8;
			else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) )
				encoding = TIXML_ENCODING_UTF8;	// incorrect, but be nice
			else 
				encoding = TIXML_ENCODING_LEGACY;
		}
        //跳过空格,\r,\n等字符
		p = SkipWhiteSpace( p, encoding );
	}

	// Was this empty?
	if ( !firstChild ) {
		SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding );
		return 0;
	}

	// All is well.
	return p;
}

以上面那个解析树为例,在TiXmldocument的解析程序中,

  1. 第一次循环identify函数识别出 TiXmlDeclaration,然后调用 TiXmlDeclaration::parse(),解析出来的结果保存在 TiXmlDeclaration定义的string中。
  2. 第二次循环identify函数识别出TiXmlComment,调用TiXmlComment::parse(),把注释的内容保存到从TiXmlNode::value中。
  3. 第三次循环identify函数识别出 TiXmlElement,调用 TiXmlElement::parse(),把Element的值读入TiXmlNode::value中,再读入TiXmlElement中可能有的属性值。读完之后重头戏来了,要开始读这个< ToDo>…</ ToDo>中间夹的东西。中间可能包含:TiXmlText, TiXmlDeclaration,TiXmlComment,TiXmlElement。
    识别出来中间包含的东西之后,建立对应的节点,例如识别出来另一个TiXmlElement,就建立一个TiXmlElement对象,然后把它加入树中。如果这个TiXmlElement中又包含了其他节点就继续识别,深度优先的把包含的 TiXmlElement识别完,再退回去识别其他的内容。
    至此整个document已经全部解析完,生成了一棵解析树 。

如何输出这个解析树,并且更改树的节点的内容,在下一篇文章《tinyXML源码刨析(下)–解析树的使用》中总结。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值