DuiLib 源码分析之解析xml类CMarkup & CMarkupNode cpp文件
时隔5个月才有时间接着写未完成的实现部分,也是惭愧呀
选几个关键的函数来解析,一些get方法就忽略掉吧
CMarkupNode 与 CMarkUp 互为友元类,CMarkUp 实现解析,CMarkupNode 用于存储读取节点数据
1 void CMarkupNode::_MapAttributes() 2 { 3 m_nAttributes = 0; 4 LPCTSTR pstr = m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iStart; 5 LPCTSTR pstrEnd = m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iData; 6 pstr += _tcslen(pstr) + 1; 7 while( pstr < pstrEnd ) { 8 m_pOwner->_SkipWhitespace(pstr); 9 m_aAttributes[m_nAttributes].iName = pstr - m_pOwner->m_pstrXML;//位移 10 pstr += _tcslen(pstr) + 1; 11 m_pOwner->_SkipWhitespace(pstr); 12 if( *pstr++ != _T('\"') ) return; // if( *pstr != _T('\"') ) { pstr = ::CharNext(pstr); return; } 13 14 m_aAttributes[m_nAttributes++].iValue = pstr - m_pOwner->m_pstrXML;//位移 15 if( m_nAttributes >= MAX_XML_ATTRIBUTES ) return; 16 pstr += _tcslen(pstr) + 1; 17 } 18 }
这个函数的主要作用是将已经处理过的xml文件进行数据分割保存,这里保存的属性名和属性值都是xml在内存中的位移,最大属性支持64个
接下来详细说明CMarkUp类
有几个用于加载xml文件的函数:
bool CMarkup::Load(LPCTSTR pstrXML)//直接解析字符串
bool CMarkup::LoadFromMem(BYTE* pByte, DWORD dwSize, int encoding)//将二进制数据流转换为字符串再解析
bool CMarkup::LoadFromFile(LPCTSTR pstrFilename, int encoding)//解析xml文件,根据文件名解析,先判断资源是否被打包到zip压缩包中
1 bool CMarkup::_Parse()//解析入口, 先拓展节点保证有足够的节点存储,然后解析 2 { 3 _ReserveElement(); // Reserve index 0 for errors 4 ::ZeroMemory(m_szErrorMsg, sizeof(m_szErrorMsg)); 5 ::ZeroMemory(m_szErrorXML, sizeof(m_szErrorXML)); 6 LPTSTR pstrXML = m_pstrXML; 7 return _Parse(pstrXML, 0); 8 }
1 CMarkup::XMLELEMENT* CMarkup::_ReserveElement()//拓展节点数 2 { 3 if( m_nElements == 0 ) m_nReservedElements = 0; 4 if( m_nElements >= m_nReservedElements ) { 5 m_nReservedElements += (m_nReservedElements / 2) + 500; 6 m_pElements = static_cast<XMLELEMENT*>(realloc(m_pElements, m_nReservedElements * sizeof(XMLELEMENT)));//这里的realloc函数会将原来的内容复制到新申请的内存中 7 } 8 return &m_pElements[m_nElements++]; 9 }
1 bool CMarkup::_Parse(LPTSTR& pstrText, ULONG iParent) 2 { 3 _SkipWhitespace(pstrText);//跳过空格 4 ULONG iPrevious = 0; 5 for( ; ; ) 6 { 7 if( *pstrText == _T('\0') && iParent <= 1 ) return true;//退出条件,到结尾,或者无父节点 8 _SkipWhitespace(pstrText); 9 if( *pstrText != _T('<') ) return _Failed(_T("Expected start tag"), pstrText); 10 if( pstrText[1] == _T('/') ) return true; 11 *pstrText++ = _T('\0'); 12 _SkipWhitespace(pstrText); 13 // Skip comment or processing directive 跳过注释(<- ->)或指令(<? ?>) 14 if( *pstrText == _T('!') || *pstrText == _T('?') ) { 15 TCHAR ch = *pstrText; 16 if( *pstrText == _T('!') ) ch = _T('-'); 17 while( *pstrText != _T('\0') && !(*pstrText == ch && *(pstrText + 1) == _T('>')) ) pstrText = ::CharNext(pstrText); 18 if( *pstrText != _T('\0') ) pstrText += 2; 19 _SkipWhitespace(pstrText); 20 continue; 21 } 22 _SkipWhitespace(pstrText); 23 // Fill out element structure 24 XMLELEMENT* pEl = _ReserveElement(); 25 ULONG iPos = pEl - m_pElements; 26 pEl->iStart = pstrText - m_pstrXML; 27 pEl->iParent = iParent; 28 pEl->iNext = pEl->iChild = 0; 29 if( iPrevious != 0 ) m_pElements[iPrevious].iNext = iPos; 30 else if( iParent > 0 ) m_pElements[iParent].iChild = iPos; 31 iPrevious = iPos; 32 // Parse name 33 LPCTSTR pstrName = pstrText; 34 _SkipIdentifier(pstrText); 35 LPTSTR pstrNameEnd = pstrText; 36 if( *pstrText == _T('\0') ) return _Failed(_T("Error parsing element name"), pstrText); 37 // Parse attributes 38 if( !_ParseAttributes(pstrText) ) return false; //解析属性 39 _SkipWhitespace(pstrText); 40 if( pstrText[0] == _T('/') && pstrText[1] == _T('>') ) //结尾是/>情况 41 { 42 pEl->iData = pstrText - m_pstrXML; //保存节点的结尾位移 43 *pstrText = _T('\0'); 44 pstrText += 2; 45 } 46 else //结尾是>情况 47 { 48 if( *pstrText != _T('>') ) return _Failed(_T("Expected start-tag closing"), pstrText); 49 // Parse node data 50 pEl->iData = ++pstrText - m_pstrXML; 51 LPTSTR pstrDest = pstrText; 52 if( !_ParseData(pstrText, pstrDest, _T('<')) ) return false;//找到<符号 53 // Determine type of next element 54 if( *pstrText == _T('\0') && iParent <= 1 ) return true; //如果是结尾则返回 55 if( *pstrText != _T('<') ) return _Failed(_T("Expected end-tag start"), pstrText); 56 if( pstrText[0] == _T('<') && pstrText[1] != _T('/') ) 57 { 58 if( !_Parse(pstrText, iPos) ) return false; //递归解析子节点 59 } 60 if( pstrText[0] == _T('<') && pstrText[1] == _T('/') ) //处理</>情况 61 { 62 *pstrDest = _T('\0'); 63 *pstrText = _T('\0'); 64 pstrText += 2; 65 _SkipWhitespace(pstrText); 66 SIZE_T cchName = pstrNameEnd - pstrName; 67 if( _tcsncmp(pstrText, pstrName, cchName) != 0 ) return _Failed(_T("Unmatched closing tag"), pstrText); 68 pstrText += cchName; 69 _SkipWhitespace(pstrText); 70 if( *pstrText++ != _T('>') ) return _Failed(_T("Unmatched closing tag"), pstrText); 71 } 72 } 73 *pstrNameEnd = _T('\0'); 74 _SkipWhitespace(pstrText); 75 } 76 }
1 void CMarkup::_SkipWhitespace(LPCTSTR& pstr) const 2 { 3 while( *pstr > _T('\0') && *pstr <= _T(' ') ) pstr = ::CharNext(pstr); 4 } 5 6 void CMarkup::_SkipWhitespace(LPTSTR& pstr) const 7 { 8 while( *pstr > _T('\0') && *pstr <= _T(' ') ) pstr = ::CharNext(pstr); 9 } 10 11 void CMarkup::_SkipIdentifier(LPCTSTR& pstr) const 12 { 13 // 属性只能用英文,所以这样处理没有问题 14 while( *pstr != _T('\0') && (*pstr == _T('_') || *pstr == _T(':') || _istalnum(*pstr)) ) pstr = ::CharNext(pstr); 15 } 16 17 void CMarkup::_SkipIdentifier(LPTSTR& pstr) const 18 { 19 // 属性只能用英文,所以这样处理没有问题 20 while( *pstr != _T('\0') && (*pstr == _T('_') || *pstr == _T(':') || _istalnum(*pstr)) ) pstr = ::CharNext(pstr); 21 } 22 23 bool CMarkup::_ParseAttributes(LPTSTR& pstrText) 24 { 25 if( *pstrText == _T('>') ) return true; 26 *pstrText++ = _T('\0'); 27 _SkipWhitespace(pstrText); 28 while( *pstrText != _T('\0') && *pstrText != _T('>') && *pstrText != _T('/') ) { 29 _SkipIdentifier(pstrText); //跳过属性名 30 LPTSTR pstrIdentifierEnd = pstrText; 31 _SkipWhitespace(pstrText); //跳过空白 32 if( *pstrText != _T('=') ) return _Failed(_T("Error while parsing attributes"), pstrText); 33 *pstrText++ = _T(' '); //'='也赋值为空格 34 *pstrIdentifierEnd = _T('\0'); 35 _SkipWhitespace(pstrText); 36 if( *pstrText++ != _T('\"') ) return _Failed(_T("Expected attribute value"), pstrText);//必须为双引号 37 LPTSTR pstrDest = pstrText; 38 if( !_ParseData(pstrText, pstrDest, _T('\"')) ) return false;//解析属性数据 39 if( *pstrText == _T('\0') ) return _Failed(_T("Error while parsing attribute string"), pstrText); 40 *pstrDest = _T('\0'); 41 if( pstrText != pstrDest ) *pstrText = _T(' '); 42 pstrText++; 43 _SkipWhitespace(pstrText); 44 } 45 return true; 46 } 47 48 bool CMarkup::_ParseData(LPTSTR& pstrText, LPTSTR& pstrDest, char cEnd) 49 { 50 while( *pstrText != _T('\0') && *pstrText != cEnd ) { 51 if( *pstrText == _T('&') ) { 52 while( *pstrText == _T('&') ) { 53 _ParseMetaChar(++pstrText, pstrDest);//解析同义字符"等 54 } 55 if (*pstrText == cEnd) 56 break; 57 } 58 59 if( *pstrText == _T(' ') ) { 60 *pstrDest++ = *pstrText++; 61 if( !m_bPreserveWhitespace ) _SkipWhitespace(pstrText); 62 } 63 else { 64 LPTSTR pstrTemp = ::CharNext(pstrText); 65 while( pstrText < pstrTemp) { 66 *pstrDest++ = *pstrText++; 67 } 68 } 69 } 70 // Make sure that MapAttributes() works correctly when it parses 71 // over a value that has been transformed. 72 LPTSTR pstrFill = pstrDest + 1; 73 while( pstrFill < pstrText ) *pstrFill++ = _T(' ');//填充空格,比如存在"情况 74 return true; 75 } 76 77 void CMarkup::_ParseMetaChar(LPTSTR& pstrText, LPTSTR& pstrDest) 78 { 79 if( pstrText[0] == _T('a') && pstrText[1] == _T('m') && pstrText[2] == _T('p') && pstrText[3] == _T(';') ) { 80 *pstrDest++ = _T('&'); 81 pstrText += 4; 82 } 83 else if( pstrText[0] == _T('l') && pstrText[1] == _T('t') && pstrText[2] == _T(';') ) { 84 *pstrDest++ = _T('<'); 85 pstrText += 3; 86 } 87 else if( pstrText[0] == _T('g') && pstrText[1] == _T('t') && pstrText[2] == _T(';') ) { 88 *pstrDest++ = _T('>'); 89 pstrText += 3; 90 } 91 else if( pstrText[0] == _T('q') && pstrText[1] == _T('u') && pstrText[2] == _T('o') && pstrText[3] == _T('t') && pstrText[4] == _T(';') ) { 92 *pstrDest++ = _T('\"'); 93 pstrText += 5; 94 } 95 else if( pstrText[0] == _T('a') && pstrText[1] == _T('p') && pstrText[2] == _T('o') && pstrText[3] == _T('s') && pstrText[4] == _T(';') ) { 96 *pstrDest++ = _T('\''); 97 pstrText += 5; 98 } 99 else { 100 *pstrDest++ = _T('&'); 101 } 102 }
解析xml的基本原理就是,将xml加载到内存中,顺序解析节点,首先对节点进行存储,对xml进行改写(将<、>、/、"、'等改写为空格),获取节点
属性的时候进行分割存储。
简单举个例子会更清晰:
1 <?xml version="1.0" encoding="utf-8"?> 2 <Window size="800,572" sizebox="4,4,6,6" roundcorner="5,5" caption="0,0,0,90" mininfo="800,570"> 3 <Font name="宋体" size="13" bold="true" /> 4 <VerticalLayout bkcolor="#FFD1E8F5" bkcolor2="#FFC6E0F1" bordercolor="#FF768D9B" bordersize="1" borderround="5,5" inset="1,0,1,0"> 5 <HorizontalLayout> 6 <Container width="22" height="22" bkimage="file='icon.png' source='0,0,16,16' dest='5,4,21,20' " /> 7 <Text text="360安全卫士7.3" pos="22, 5, 200, 24" float="true" textcolor="#FF447AA1" font="0" /> 8 </HorizontalLayout> 9 </VerticalLayout> 10 </Window>
比如解析上述xml文件
\0Window\0size\0 800,572\0 sizebox\0 4,4,6,6\0 roundcorner\0 \05,5\0 caption\0 0,0,0,90\0 mininfo\0 800,570\0>
\0Font\0name\0 宋体\0 size\0 13\0 bold\0 true\0 \0>
\0VerticalLayout\0bkcolor\0 #FFD1E8F5\0 bkcolor2\0 #FFC6E0F1\0 bordercolor\0 #FF768D9B\0 bordersize\0 1\0 borderround\0 5,5\0 inset\0 1,0,1,0\0>
\0HorizontalLayout\0>
\0Container\0 width\0 22\0 height\0 22\0 bkimage\0 file\0' icon.png' source='0,0,16,16' dest='5,4,21,20' \0 \0>
\0Text\0 text\0 360安全卫士7.3\0 pos\0 22, 5, 200, 24\0 float\0 true\0 textcolor\0 #FF447AA1\0 font\0 0\0 \0>
\0\0HorizontalLayout>
\0\0VerticalLayout>
\0\0Window>
1 void CMarkupNode::_MapAttributes() 2 { 3 m_nAttributes = 0; 4 LPCTSTR pstr = m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iStart; 5 LPCTSTR pstrEnd = m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iData; 6 pstr += _tcslen(pstr) + 1; 7 while( pstr < pstrEnd ) { 8 m_pOwner->_SkipWhitespace(pstr); 9 m_aAttributes[m_nAttributes].iName = pstr - m_pOwner->m_pstrXML;//位移 10 pstr += _tcslen(pstr) + 1; 11 m_pOwner->_SkipWhitespace(pstr); 12 if( *pstr++ != _T('\"') ) return; // if( *pstr != _T('\"') ) { pstr = ::CharNext(pstr); return; } 13 14 m_aAttributes[m_nAttributes++].iValue = pstr - m_pOwner->m_pstrXML;//位移 15 if( m_nAttributes >= MAX_XML_ATTRIBUTES ) return; 16 pstr += _tcslen(pstr) + 1; 17 } 18 }
然后看获取属性的函数就一目了然了