Duilib 源码分析之 xml 解析篇
来源:互联网 发布:淘宝开通花呗条件 编辑:程序博客网 时间:2024/06/07 02:03
上一篇文章介绍了 Duilib 是如何读取 xml 的 (Duilib 源码分析之 xml 加载篇),接下来介绍 Duilib 是如何解析加载到内存的 xml 的。
首先列出解析 xml 的几个关键类:
- UIDlgBuilder
- UIMarkup
- CMarkupNode
在 xml 加载篇的帖子中,已经介绍过,在窗口创建的消息相应函数 OnCreate
中,调用了 UIDlgBuilder ::Create
方法来加载 xml。 而实际把 xml 加载到内存并进行解析是由类 UIMarkup
完成的。
下面我们来着重介绍一下类 UIMarkup:
关键的数据结构类型:
XMLELEMENT
typedef struct tagXMLELEMENT{ ULONG iStart; ULONG iChild; ULONG iNext; ULONG iParent; ULONG iData;} XMLELEMENT;
- iStart : 节点的开头位置,例如节点
xxxxx <Label xxxxx /> xxxxx
, iStart = 指向L
的指针 -m_pstrXML
- iChild :第一个子节点的序号,这个序号指的是节点的序号,第一个有效节点的序号为 1, 节点 0 预留给解析错误的节点
- iNext : 第一个兄弟节点的序号
- iParent : 父节点的序号
- iData : 节点结束的位置,以
/>
结束的节点结束位置为符号/
的位置;以<xxx>***</xxx>
形式出现的节点,结束位置为<xxx>
的下一个字符位置
- iStart : 节点的开头位置,例如节点
解析的关键几个成员为类 UIMarkup 的几个私有函数:
XMLELEMENT* _ReserveElement()
申请保存节点所需内存,并返回当前节点的数据结构,保存节点信息的成员函数为m_pElements
,这是一个数组, 刚开始就申请500*sizeof(XMLELEMENT)
大小的内存控件用来保存节点信息,m_nReservedElements
刚开始为 500,代表预留节点数,当实际解析到的节点数大于这个值时,再重新申请更大的内存空间void _SkipWhitespace(LPTSTR& pstr) const
跳过接下来的空格、回车、换行符。判断方式为*pstr <= _T(' ')
,回车和换行的 ASCII 码分别为 13 和 10, 空格的 ASCII 码为 32void _SkipIdentifier(LPCTSTR& pstr)
这个函数实现为(*pstr == _T('_') || *pstr == _T(':') || _istalnum(*pstr))
,跳过冒号、下划线、字母和数字,在代码中此函数有 2 处调用,第一处是开始解析某一节点时跳过此节点的名称,第二处是调用_ParseAttributes
时跳过当前属性处理下一个属性用。void _ParseMetaChar(LPTSTR& pstrText, LPTSTR& pstrDest)
解析 xml 中可能出现的&
、<
、>
、"
、'
,分别代表字符 &<>"'bool _ParseAttributes(LPTSTR& pstrText)
处理节点的所有属性,注意,这个函数并没有解析出这个节点有哪些属性,各个属性的值是多少,整个_Parse
的过程其实只是构造了所有的节点信息:m_pElements
,并修改了内存中的 xml 字符串的内容:m_pstrXML
指向的这部分内存,修改 xml 字符串的内容只是为了解析属性做准备,后续会根据所有的节点信息逐个取得属性并创建控件,这个稍后具体再讲。此函数的退出条件是当前解析到的字符为 / 或 >。bool _Parse(LPTSTR& pstrText, ULONG iParent)
- 这个函数主要就是一个 for 循环 ,每一个 for 循环代表对一个节点的解析
- 每个 for 循环的开始都会调用
_SkipWhitespace
,为了跳过 2 个节点之间的空白部分 以下代码片段:
if( *pstrText == _T('!') || *pstrText == _T('?') ) { TCHAR ch = *pstrText; if( *pstrText == _T('!') ) ch = _T('-'); while( *pstrText != _T('\0') && !(*pstrText == ch && *(pstrText + 1) == _T('>')) ) pstrText = ::CharNext(pstrText); if( *pstrText != _T('\0') ) pstrText += 2; _SkipWhitespace(pstrText); continue;}
这是为了跳过 xml 开头的声明部分,和 xml 中的所有注释部分 ,注释和声明格式为
<!-- -->
和<? xxxx>
,所以才会在 if 语句中判断字符等于 ! 或者 ?。此函数中有一个递归调用, 是出现在节点有子节点的情况。条件是当调用
_ParseAttributes
结束后,发现当前字符不是 /,也就意味着可能有子节点,之所以说可能,是因为有<xxx></xxx>
的情况。所以还需判断接下来不是</
开头的字符才递归调用。整个
_Parse
函数执行完之后,就像前面所说,只是构造了所有的节点信息:m_pElements
,而且m_pstrXML
指向的一段内存数据也大变样了。我们来看个实例:<?xml version="1.0" encoding="utf-8" standalone="yes" ?><Window size="920,490" showshadow="true" shadowimage = "Image\shadow.png" shadowcorner="23,13,23,33" caption="0,0,0,100" > <Include source="font\DefaultCfg.dat"/> <VerticalLayout bkcolor="#FFEEEEEE"> <HorizontalLayout height="30" inset="10,10,0,0" bkcolor="#FF000000"> <Label text="TestDuilib&'"<>" textcolor="#FFFFFFFF" /> <Control /> <Button name="btnClose" padding="0,0,10,0" height="18" width="18" normalimage="Image\close_default.png" hotimage="close_hover.png" /> </HorizontalLayout> </VerticalLayout> <!-- --></Window>
以上是一个简单的 xml,执行
_Parse
解析之后,m_pstrXML
指向的内存数据为:\0?xml version="1.0" encoding="utf-8" standalone="yes" ?>\0Window\0size\0"920,490\0 showshadow\0"true\0 shadowimage\0 "Image\shadow.png\0 shadowcorner\0"23,13,23,33\0 caption\0"0,0,0,100\0 > \0Include\0source\0"font\DefaultCfg.dat\0\0> \0VerticalLayout\0bkcolor\0"#FFEEEEEE\0> \0HorizontalLayout\0height\0"30\0 inset\0"10,10,0,0\0 bkcolor\0"#FF000000\0> \0Label\0text\0"TestDuilib&'"<>\0 textcolor\0"#FFFFFFFF\0 \0> \0Control\0\0> \0Button\0 name\0"btnClose\0 padding\0"0,0,10,0\0 height\0"18\0 width\0"18\0 normalimage\0"Image\close_default.png\0 hotimage\0"close_hover.png\0 \0> \0/HorizontalLayout> \0/VerticalLayout> \0!-- -->\0/Window>
为了阅读方便,回车换行并没有用实际的字符表示。大家可以看到,解析后,主要的变化就是多出了很多
\0
字符,这就是为了解析每个节点的属性信息做准备的,后续会在类CMarkupNode
中做实际的属性解析。
接下来我们来看一下类 CMarkupNode,一个 CMarkupNode
的实例对应一个 XMLELEMENT
的数据,我们来看一下成员函数:
CMarkupNode GetParent()
获取父节点,也就是XMLELEMENT
的iParent
对应的节点CMarkupNode GetSibling()
获取兄弟节点,也就是XMLELEMENT
的iNext
对应的节点CMarkupNode GetChild()
获取第一个子节点,也就是XMLELEMENT
的iChild
对应的节点CMarkupNode GetChild(LPCTSTR pstrName)
获取指定节点名称的子节点,其实是先获取子节点,然后逐个获取子节点的下一个节点,直到取得指定名称的节点或遍历结束void _MapAttributes()
解析出这个节点有几个属性,每个属性的值是什么
我们来看个例子:\0Button\0 name\0"btnClose\0 padding\0"0,0,10,0\0 height\0"18\0 width\0"18\0 normalimage\0"Image\close_default.png\0 hotimage\0"close_hover.png\0 \0>
再看一下
void _MapAttributes()
函数的源码:m_nAttributes = 0;LPCTSTR pstr = m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iStart;LPCTSTR pstrEnd = m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iData;pstr += _tcslen(pstr) + 1;while( pstr < pstrEnd ) { m_pOwner->_SkipWhitespace(pstr); m_aAttributes[m_nAttributes].iName = pstr - m_pOwner->m_pstrXML; pstr += _tcslen(pstr) + 1; m_pOwner->_SkipWhitespace(pstr); if( *pstr++ != _T('\"') ) return; // if( *pstr != _T('\"') ) { pstr = ::CharNext(pstr); return; } m_aAttributes[m_nAttributes++].iValue = pstr - m_pOwner->m_pstrXML; if( m_nAttributes >= MAX_XML_ATTRIBUTES ) return; pstr += _tcslen(pstr) + 1;}
While
之前的代码pstr += _tcslen(pstr) + 1
跳过了节点的名称,此时pstr
指向name\0xxxxxx
的开头,然后开始循环,每次循环都将属性名称的开头字符位置和属性的 Value 的字符位置记录下来,循环结束后数组m_aAttributes
就记录了所有属性的名称起始位置和值的起始位置,接下来通过函数LPCTSTR GetAttributeName(int iIndex)
和函数LPCTSTR GetAttributeValue(int iIndex)
获取实际的名称和值。
- Duilib 源码分析之 xml 解析篇
- Duilib 源码分析之 xml 加载篇
- duilib之源码分析
- duilib之源码分析
- Duilib之源码分析
- duilib之源码分析
- Duilib 源码分析之字体篇
- Duilib 源码分析之控件 name 篇
- Duilib 源码分析之消息流程篇
- Duilib 源码分析之 CResourceManager 篇
- Duilib 源码分析之 CControlUI 篇
- Duilib 源码分析之 Timer 篇
- Duilib 源码分析之 PaintBorder 篇
- Duilib 源码分析之 ToolTip 篇
- Duilib 源码分析之 Shortcut 篇
- Duilib 源码分析之 CScrollBarUI 篇
- Duilib 源码分析之 CLabelUI 篇
- Duilib 源码分析之 CTextUI 篇
- 几行代码实现AJAX延迟请求
- char类型和short,int类型之类的转换
- 03-树3 Tree Traversals Again (25分)
- 利用Boost.Python实现Python C/C++混合编程
- TensorFlow实战15:TensorBoard的使用(tensorflow的可视化工具)
- Duilib 源码分析之 xml 解析篇
- ::before和::after伪元素的使用
- 面试感悟----一名3年工作经验的程序员应该具备的技能
- golang学习之旅:使用go语言操作mysql数据库
- 大数据商业智能的十大戒律
- 将字符串"yyyy-MM-dd'T'hh:mm:ss"转换其他时间格式
- QT 缩放功能
- 解决The APR based Apache Tomcat Native library which allows optimal performance in production environm
- aria2出现accetp2错误