基本的HTML文本解析器的设计和实现(C/C++源码),图文并茂

来源:互联网 发布:sql数据库优化方案 编辑:程序博客网 时间:2024/05/18 01:03

转自:http://blog.csdn.net/liigo/article/details/6153829


作者:庄晓立 (liigo)

日期:2011-1-19

原创链接:http://blog.csdn.net/liigo/archive/2011/01/19/6153829.aspx

转载请保持本文完整性,并注明出处:http://blog.csdn.net/liigo

关键字:HTML,解析器(Parser),节点(Node),标签(Tag)

 

这是进入2011年以来,本人(liigo)“重复发明轮子”系列博文中的最新一篇。本文主要探讨如何设计和实现一个基本的HTML文本解析器。

众所周知,HTML是结构化文档(Structured Document),由诸多标签(<p>等)嵌套形成的著名的文档对象模型(DOM, Document Object Model),是显而易见的树形多层次结构。如果带着这种思路看待HTML、编写HTML解析器,无疑将导致问题复杂化。不妨从另一视角俯视HTML文本,视其为一维线状结构:诸多单一节点的顺序排列。仔细审视任何一段HTML文本,以左右尖括号(<和>)为边界,会发现HTML文本被天然地分割为:一个标签(Tag),接一段普通文字,再一个标签,再一段普通文字…… 如下图所示:

HTML文本的单维结构图

标签有两种,开始标签(如<p>)和结束标签(</p>),它们和普通文字一起,顺序排列,共同构成了HTML文本的全部。

为了再次简化编程模型,我(liigo)继续将“开始标签”“结束标签”“普通文字”三者统一抽象归纳为“节点”(HtmlNode),相应的,“节点”有三种类型,要么是开始标签,要么是结束标签,要么是普通文字。现在,HTML在我们眼里更加单纯了,它就是“节点”的线性顺序组合,是一维的“节点”数组。如下图所示:HTML文本 = 节点1 + 节点2 + 节点3 + …… 

HTML是“节点”的线性顺序组合

在正式编码之前,先确定好“节点”的数据结构。作为“普通文字”节点,需要记录一个文本(text);作为“标签”节点,需要记录标签名称(tagName)、标签类型(tagType)、所有属性值(props);另外还要有个类型(type)以便区分该节点是普通文字、开始标签还是结束标签。这其中固然有些冗余信息,比如对标签来说不需要记录文本,对普通文字来说又不需要记录标签名称、属性值等,不过无伤大雅,简洁的编程模型是最大的诱惑。用C/C++语言语法表示如下:

[cpp] view plaincopy
  1. enum HtmlNodeType  
  2. {  
  3.     NODE_UNKNOWN = 0,  
  4.     NODE_START_TAG,  
  5.     NODE_CLOSE_TAG,  
  6.     NODE_CONTENT,  
  7. };  
  8. enum HtmlTagType  
  9. {  
  10.     TAG_UNKNOWN = 0,  
  11.     TAG_A, TAG_DIV, TAG_FONT, TAG_IMG, TAG_P, TAG_SPAN, TAG_BR, TAG_B, TAG_I, TAG_HR,   
  12. };  
  13. struct HtmlNodeProp  
  14. {  
  15.     WCHAR* szName;  
  16.     WCHAR* szValue;  
  17. };  
  18. #define MAX_HTML_TAG_LENGTH (15)  
  19. struct HtmlNode  
  20. {  
  21.     HtmlNodeType type;  
  22.     HtmlTagType  tagType;  
  23.     WCHAR tagName[MAX_HTML_TAG_LENGTH+1];  
  24.     WCHAR* text;  
  25.     int propCount;  
  26.     HtmlNodeProp* props;  
  27. };  
 

具体到编写程序代码,要比想象中容易的多。编码的核心要点是,以左右尖括号(<和>)为边界自然分割标签和普通文字。左右尖括号之间的当然是标签节点(开始标签或结束标签),左尖括号(<)之前(直到前一个右尖括号或开头)、右尖括号(>)之后(直到后一个左尖括号或结尾)的显然是普通文字节点。区分开始标签或结束标签的关键点是,看左尖括号(<)后面第一个非空白字符是否为'/'。对于开始标签,在标签名称后面,间隔至少一个空白字符,可能会有形式为“key1=value1 key2=value2 key3”的属性表,关于属性表,后文有专门的函数负责解析。此外有一点要注意,属性值一般有引号括住,引号内出现的左右尖括号应该不被视为边界分隔符。

下面就是负责把HTML文本解析为一个个节点(HtmlNode)的核心代码(不足百行,够精简吧):

[cpp] view plaincopy
  1. void HtmlParser::ParseHtml(const WCHAR* szHtml)  
  2. {  
  3.     m_html = szHtml ? szHtml : L"";  
  4.     freeHtmlNodes();  
  5.     if(szHtml == NULL || *szHtml == L'/0'return;  
  6.     WCHAR* p = (WCHAR*) szHtml;  
  7.     WCHAR* s = (WCHAR*) szHtml;  
  8.     HtmlNode* pNode = NULL;  
  9.     WCHAR c;  
  10.     bool bInQuotes = false;  
  11.     while( c = *p )  
  12.     {  
  13.         if(c == L'/"')  
  14.         {  
  15.             bInQuotes = !bInQuotes;  
  16.             p++; continue;  
  17.         }  
  18.         if(bInQuotes)  
  19.         {  
  20.             p++; continue;  
  21.         }  
  22.         if(c == L'<')  
  23.         {  
  24.             if(p > s)  
  25.             {  
  26.                 //Add Text Node  
  27.                 pNode = NewHtmlNode();  
  28.                 pNode->type = NODE_CONTENT;  
  29.                 pNode->text = duplicateStrUtill(s, L'<'true);  
  30.             }  
  31.             s = p + 1;  
  32.         }  
  33.         else if(c == L'>')  
  34.         {  
  35.             if(p > s)  
  36.             {  
  37.                 //Add HtmlTag Node  
  38.                 pNode = NewHtmlNode();  
  39.                 while(isspace(*s)) s++;  
  40.                 pNode->type = (*s != L'/' ? NODE_START_TAG : NODE_CLOSE_TAG);  
  41.                 if(*s == L'/') s++;  
  42.                 copyStrUtill(pNode->tagName, MAX_HTML_TAG_LENGTH, s, L'>'true);  
  43.                 //处理自封闭的结点, 如 <br/>, 删除tagName中可能会有的'/'字符  
  44.                 //自封闭的结点的type设置为NODE_START_TAG应该可以接受(否则要引入新的NODE_STARTCLOSE_TAG)  
  45.                 int tagNamelen = wcslen(pNode->tagName);  
  46.                 if(pNode->tagName[tagNamelen-1] == L'/')  
  47.                     pNode->tagName[tagNamelen-1] = L'/0';  
  48.                 //处理结点属性  
  49.                 for(int i = 0; i < tagNamelen; i++)  
  50.                 {  
  51.                     if(pNode->tagName[i] == L' ' //第一个空格后面跟的是属性列表  
  52.                         || pNode->tagName[i] == L'='//扩展支持这种格式: <tagName=value>, 等效于<tagName tagName=value>  
  53.                     {  
  54.                         WCHAR* props = (pNode->tagName[i] == L' ' ? s + i + 1 : s);  
  55.                         pNode->text = duplicateStrUtill(props, L'>'true);  
  56.                         int nodeTextLen = wcslen(pNode->text);  
  57.                         if(pNode->text[nodeTextLen-1] == L'/'//去掉最后可能会有的'/'字符, 如这种情况: <img src="..." mce_src="..." />  
  58.                             pNode->text[nodeTextLen-1] = L'/0';  
  59.                         pNode->tagName[i] = L'/0';  
  60.                         parseNodeProps(pNode); //parse props  
  61.                         break;  
  62.                     }  
  63.                 }  
  64.                 pNode->tagType = getHtmlTagTypeFromName(pNode->tagName);  
  65.             }  
  66.             s = p + 1;  
  67.         }  
  68.         p++;  
  69.     }  
  70.     if(p > s)  
  71.     {  
  72.         //Add Text Node  
  73.         pNode = NewHtmlNode();  
  74.         pNode->type = NODE_CONTENT;  
  75.         pNode->text = duplicateStr(s, -1);  
  76.     }  
  77. #ifdef _DEBUG  
  78.     dumpHtmlNodes(); //just for test  
  79. #endif  
  80. }  
 

下面是负责解析“开始标签”属性表文本(形如“key1=value1 key2=value2 key3”)的代码,parseNodeProps(),核心思路是按空格和等号字符进行分割属性名和属性值,由于想兼容HTML4.01及以前的不标准的属性表写法(如没有=号也没有属性值),颇费周折:

[cpp] view plaincopy
  1. //[virtual]  
  2. void HtmlParser::parseNodeProps(HtmlNode* pNode)  
  3. {  
  4.     if(pNode == NULL || pNode->propCount > 0 || pNode->text == NULL)  
  5.         return;  
  6.     WCHAR* p = pNode->text;  
  7.     WCHAR *ps = NULL;  
  8.     CMem mem;  
  9.     bool inQuote1 = false, inQuote2 = false;  
  10.     WCHAR c;  
  11.     while(c = *p)  
  12.     {  
  13.         if(c == L'/"')  
  14.         {  
  15.             inQuote1 = !inQuote1;  
  16.         }  
  17.         else if(c == L'/'')  
  18.         {  
  19.             inQuote2 = !inQuote2;  
  20.         }  
  21.         if((!inQuote1 && !inQuote2) && (c == L' ' || c == L'/t' || c == L'='))  
  22.         {  
  23.             if(ps)  
  24.             {  
  25.                 mem.AddPointer(duplicateStrAndUnquote(ps, p - ps));  
  26.                 ps = NULL;  
  27.             }  
  28.             if(c == L'=')  
  29.                 mem.AddPointer(NULL);  
  30.         }  
  31.         else  
  32.         {  
  33.             if(ps == NULL)  
  34.                 ps = p;  
  35.         }  
  36.         p++;  
  37.     }  
  38.     if(ps)  
  39.         mem.AddPointer(duplicateStrAndUnquote(ps, p - ps));  
  40.     mem.AddPointer(NULL);  
  41.     mem.AddPointer(NULL);  
  42.     WCHAR** pp = (WCHAR**) mem.GetPtr();  
  43.     CMem props;  
  44.     for(int i = 0, n = mem.GetSize() / sizeof(WCHAR*) - 2; i < n; i++)  
  45.     {  
  46.         props.AddPointer(pp[i]); //prop name  
  47.         if(pp[i+1] == NULL)  
  48.         {  
  49.             props.AddPointer(pp[i+2]); //prop value  
  50.             i += 2;  
  51.         }  
  52.         else  
  53.             props.AddPointer(NULL); //prop vlalue  
  54.     }  
  55.     pNode->propCount = props.GetSize() / sizeof(WCHAR*) / 2;  
  56.     pNode->props = (HtmlNodeProp*) props.Detach();  
  57. }  
 

根据标签名称取标签类型的getHtmlTagTypeFromName()方法,就非常直白了,查表,逐一识别:

[cpp] view plaincopy
  1. //[virtual]  
  2. HtmlTagType HtmlParser::getHtmlTagTypeFromName(const WCHAR* szTagName)  
  3. {  
  4.     //todo: uses hashmap  
  5.     struct N2T { const WCHAR* name; HtmlTagType type; };  
  6.     static N2T n2tTable[] =   
  7.     {  
  8.         { L"A", TAG_A },  
  9.         { L"FONT", TAG_FONT },  
  10.         { L"IMG", TAG_IMG },  
  11.         { L"P", TAG_P },  
  12.         { L"DIV", TAG_DIV },  
  13.         { L"SPAN", TAG_SPAN },  
  14.         { L"BR", TAG_BR },  
  15.         { L"B", TAG_B },  
  16.         { L"I", TAG_I },  
  17.         { L"HR", TAG_HR },  
  18.     };  
  19.     for(int i = 0, count = sizeof(n2tTable)/sizeof(n2tTable[0]); i < count; i++)  
  20.     {  
  21.         N2T* p = &n2tTable[i];  
  22.         if(wcsicmp(p->name, szTagName) == 0)  
  23.             return p->type;  
  24.     }  
  25.     return TAG_UNKNOWN;  
  26. }  
 

请注意,上文负责解析属性表的parseNodeProps()函数,和负责识别标签名称的getHtmlTagTypeFromName()函数,都是虚函数(virtual method)。我(liigo)这么设计是有深意的,给使用者留下了很大的定制空间,可以自由发挥。例如,通过在子类中覆盖/覆写(override)parseNodeProps()方法,可以采用更好的解析算法,或者干脆不做任何处理以提高HTML解析效率——将来某一时间可以调用基类同名函数专门解析特定标签的属性表;例如,通过在子类中覆盖/覆写(override)getHtmlTagTypeFromName()方法,使用者可以选择识别跟多的标签名称(包括自定义标签),或者识别更少的标签名称,甚至不识别任何标签名称(以便提高解析效率)。以编写网络爬虫程序为实例,它多数情况下通常只需识别<A>标签及其属性就足够了,没必要浪费CPU运算去识别其它标签、解析其他标签属性。

至于HTML文本解析器的用途,我目前想到的有:用于HTML格式检查或规范化,用于重新排版HTML文本,用于编写网络爬虫程序/搜索引擎,用于基于HTML模板的动态网页生成,用于HTML网页渲染前的基础解析,等等。

下面附上完整源码,仅供参考,欢迎指正。

HtmlParser.h:

[cpp] view plaincopy
  1. #include "common.h"  
  2. //HtmlParser类,用于解析HTML文本  
  3. //by liigo, @2010  
  4. enum HtmlNodeType  
  5. {  
  6.     NODE_UNKNOWN = 0,  
  7.     NODE_START_TAG,  
  8.     NODE_CLOSE_TAG,  
  9.     NODE_CONTENT,  
  10.     NODE_SOFT_LINE,  
  11. };  
  12. enum HtmlTagType  
  13. {  
  14.     TAG_UNKNOWN = 0,  
  15.     TAG_A, TAG_DIV, TAG_FONT, TAG_IMG, TAG_P, TAG_SPAN, TAG_BR, TAG_B, TAG_I, TAG_HR,   
  16.     TAG_COLOR, TAG_BGCOLOR, //非标准HTML标签, 可以这样使用: <color=red>, 等效于 <color color=red>  
  17. };  
  18. struct HtmlNodeProp  
  19. {  
  20.     WCHAR* szName;  
  21.     WCHAR* szValue;  
  22. };  
  23. #define MAX_HTML_TAG_LENGTH (15)  
  24. struct HtmlNode  
  25. {  
  26.     HtmlNodeType type;  
  27.     HtmlTagType  tagType;  
  28.     WCHAR tagName[MAX_HTML_TAG_LENGTH+1];  
  29.     WCHAR* text;  
  30.     int propCount;  
  31.     HtmlNodeProp* props;  
  32. };  
  33. class HtmlParser  
  34. {  
  35.     friend class HTMLView;  
  36. public:  
  37.     HtmlParser() {}  
  38. public:  
  39.     //html  
  40.     void ParseHtml(const WCHAR* szHtml);  
  41.     const WCHAR* GetHtml() const { return m_html.GetText(); }  
  42.     //nodes  
  43.     unsigned int getHtmlNodeCount();  
  44.     HtmlNode* getHtmlNodes();  
  45.     //props  
  46.     const HtmlNodeProp* getNodeProp(const HtmlNode* pNode, const WCHAR* szPropName);  
  47.     const WCHAR* getNodePropStringValue(const HtmlNode* pNode, const WCHAR* szPropName, const WCHAR* szDefaultValue = NULL);  
  48.     int getNodePropIntValue(const HtmlNode* pNode, const WCHAR* szPropName, int defaultValue = 0);  
  49. protected:  
  50.     //允许子类覆盖, 以便识别更多结点(提高解析质量), 或者识别更少结点(提高解析速度)  
  51.     virtual HtmlTagType getHtmlTagTypeFromName(const WCHAR* szTagName);  
  52. public:  
  53.     //允许子类覆盖, 以便更好的解析节点属性, 或者干脆不解析节点属性(提高解析速度)  
  54.     virtual void parseNodeProps(HtmlNode* pNode); //todo: make protected, after testing  
  55. private:  
  56.     HtmlNode* NewHtmlNode();  
  57.     void freeHtmlNodes();  
  58.     void dumpHtmlNodes();  
  59. private:  
  60.     CMem m_HtmlNodes;  
  61.     CMString m_html;  
  62. };  
  63. //一些文本处理函数  
  64. WCHAR* duplicateStr(const WCHAR* pSrc, unsigned int nChar);  
  65. void freeDuplicatedStr(WCHAR* p);  
  66. unsigned int copyStr(WCHAR* pDest, unsigned int nDest, const WCHAR* pSrc, unsigned int nChar);  
 

HtmlParser.cpp:

[cpp] view plaincopy
  1. #include "HtmlParser.h"  
  2. //HtmlParser类,用于解析HTML文本  
  3. //by liigo, @2010  
  4. const WCHAR* wcsnchr(const WCHAR* pStr, int len, WCHAR c)  
  5. {  
  6.     const WCHAR *p = pStr;  
  7.     while(1)  
  8.     {  
  9.         if(*p == c) return p;  
  10.         p++;  
  11.         if((p - pStr) == len) break;  
  12.     }  
  13.     return NULL;  
  14. }  
  15. const WCHAR* getFirstUnquotedChar(const WCHAR* pStr, WCHAR endcahr)  
  16. {  
  17.     WCHAR c;  
  18.     const WCHAR* p = pStr;  
  19.     bool inQuote1 = false, inQuote2 = false//'inQuote1', "inQuote2"  
  20.     while(c = *p)  
  21.     {  
  22.         if(c == L'/'')  
  23.         {  
  24.             inQuote1 = !inQuote1;  
  25.         }  
  26.         else if(c == L'/"')  
  27.         {  
  28.             inQuote2 = !inQuote2;  
  29.         }  
  30.         if(!inQuote1 && !inQuote2)  
  31.         {  
  32.             if(c == endcahr) return p;  
  33.         }  
  34.         p++;  
  35.     }  
  36.     return NULL;  
  37. }  
  38. //nDest and nChar can by -1  
  39. unsigned int copyStr(WCHAR* pDest, unsigned int nDest, const WCHAR* pSrc, unsigned int nChar)  
  40. {  
  41.     if(pDest == NULL || nDest == 0)  
  42.         return 0;  
  43.     if(pSrc == NULL)  
  44.     {  
  45.         pDest[0] = L'/0';  
  46.         return 0;  
  47.     }  
  48.     if(nChar == (unsigned int)-1)  
  49.         nChar = wcslen(pSrc);  
  50.     if(nChar > nDest)  
  51.         nChar = nDest;  
  52.     memcpy(pDest, pSrc, nChar * sizeof(WCHAR));  
  53.     pDest[nChar] = L'/0';  
  54.     return nChar;  
  55. }  
  56. int copyStrUtill(WCHAR* pDest, unsigned int nDest, const WCHAR* pSrc, WCHAR endchar, bool ignoreEndCharInQuoted)  
  57. {  
  58.     if(nDest == 0) return 0;  
  59.     pDest[0] = L'/0';  
  60.     const WCHAR* pSearched = (ignoreEndCharInQuoted ? getFirstUnquotedChar(pSrc,endchar) : wcschr(pSrc, endchar));  
  61.     if(pSearched <= pSrc) return 0;  
  62.     return copyStr(pDest, nDest, pSrc, pSearched - pSrc);  
  63. }  
  64. //nChar can be -1  
  65. WCHAR* duplicateStr(const WCHAR* pSrc, unsigned int nChar)  
  66. {  
  67.     if(nChar == (unsigned int)-1)  
  68.         nChar = wcslen(pSrc);  
  69.     WCHAR* pNew = (WCHAR*) malloc( (nChar+1) * sizeof(WCHAR) );  
  70.     copyStr(pNew, -1, pSrc, nChar);  
  71.     return pNew;  
  72. }  
  73. WCHAR* duplicateStrUtill(const WCHAR* pSrc, WCHAR endchar, bool ignoreEndCharInQuoted)  
  74. {  
  75.     const WCHAR* pSearched = (ignoreEndCharInQuoted ? getFirstUnquotedChar(pSrc,endchar) : wcschr(pSrc, endchar));;  
  76.     if(pSearched <= pSrc) return NULL;  
  77.     int n = pSearched - pSrc;  
  78.     return duplicateStr(pSrc, n);  
  79. }  
  80. void freeDuplicatedStr(WCHAR* p)  
  81. {  
  82.     if(p) free(p);  
  83. }  
  84. HtmlNode* HtmlParser::NewHtmlNode()  
  85. {  
  86.     static char staticHtmlNodeTemplate[sizeof(HtmlNode)] = {0};  
  87.     /* 
  88.     static HtmlNode staticHtmlNodeTemplate; //= {0}; 
  89.     staticHtmlNodeTemplate.type = NODE_UNKNOWN; 
  90.     staticHtmlNodeTemplate.tagName[0] = L'/0'; 
  91.     staticHtmlNodeTemplate.text = NULL; 
  92.     */  
  93.     m_HtmlNodes.Append(staticHtmlNodeTemplate, sizeof(HtmlNode));  
  94.     HtmlNode* pNode = (HtmlNode*) (m_HtmlNodes.GetPtr() + m_HtmlNodes.GetSize() - sizeof(HtmlNode));  
  95.     return pNode;  
  96. }  
  97. void HtmlParser::ParseHtml(const WCHAR* szHtml)  
  98. {  
  99.     m_html = szHtml ? szHtml : L"";  
  100.     freeHtmlNodes();  
  101.     if(szHtml == NULL || *szHtml == L'/0'return;  
  102.     WCHAR* p = (WCHAR*) szHtml;  
  103.     WCHAR* s = (WCHAR*) szHtml;  
  104.     HtmlNode* pNode = NULL;  
  105.     WCHAR c;  
  106.     bool bInQuotes = false;  
  107.     while( c = *p )  
  108.     {  
  109.         if(c == L'/"')  
  110.         {  
  111.             bInQuotes = !bInQuotes;  
  112.             p++; continue;  
  113.         }  
  114.         if(bInQuotes)  
  115.         {  
  116.             p++; continue;  
  117.         }  
  118.         if(c == L'<')  
  119.         {  
  120.             if(p > s)  
  121.             {  
  122.                 //Add Text Node  
  123.                 pNode = NewHtmlNode();  
  124.                 pNode->type = NODE_CONTENT;  
  125.                 pNode->text = duplicateStrUtill(s, L'<'true);  
  126.             }  
  127.             s = p + 1;  
  128.         }  
  129.         else if(c == L'>')  
  130.         {  
  131.             if(p > s)  
  132.             {  
  133.                 //Add HtmlTag Node  
  134.                 pNode = NewHtmlNode();  
  135.                 while(isspace(*s)) s++;  
  136.                 pNode->type = (*s != L'/' ? NODE_START_TAG : NODE_CLOSE_TAG);  
  137.                 if(*s == L'/') s++;  
  138.                 copyStrUtill(pNode->tagName, MAX_HTML_TAG_LENGTH, s, L'>'true);  
  139.                 //处理自封闭的结点, 如 <br/>, 删除tagName中可能会有的'/'字符  
  140.                 //自封闭的结点的type设置为NODE_START_TAG应该可以接受(否则要引入新的NODE_STARTCLOSE_TAG)  
  141.                 int tagNamelen = wcslen(pNode->tagName);  
  142.                 if(pNode->tagName[tagNamelen-1] == L'/')  
  143.                     pNode->tagName[tagNamelen-1] = L'/0';  
  144.                 //处理结点属性  
  145.                 for(int i = 0; i < tagNamelen; i++)  
  146.                 {  
  147.                     if(pNode->tagName[i] == L' ' //第一个空格后面跟的是属性列表  
  148.                         || pNode->tagName[i] == L'='//扩展支持这种格式: <tagName=value>, 等效于<tagName tagName=value>  
  149.                     {  
  150.                         WCHAR* props = (pNode->tagName[i] == L' ' ? s + i + 1 : s);  
  151.                         pNode->text = duplicateStrUtill(props, L'>'true);  
  152.                         int nodeTextLen = wcslen(pNode->text);  
  153.                         if(pNode->text[nodeTextLen-1] == L'/'//去掉最后可能会有的'/'字符, 如这种情况: <img src="..." mce_src="..." />  
  154.                             pNode->text[nodeTextLen-1] = L'/0';  
  155.                         pNode->tagName[i] = L'/0';  
  156.                         parseNodeProps(pNode); //parse props  
  157.                         break;  
  158.                     }  
  159.                 }  
  160.                 pNode->tagType = getHtmlTagTypeFromName(pNode->tagName);  
  161.             }  
  162.             s = p + 1;  
  163.         }  
  164.         p++;  
  165.     }  
  166.     if(p > s)  
  167.     {  
  168.         //Add Text Node  
  169.         pNode = NewHtmlNode();  
  170.         pNode->type = NODE_CONTENT;  
  171.         pNode->text = duplicateStr(s, -1);  
  172.     }  
  173. #ifdef _DEBUG  
  174.     dumpHtmlNodes(); //just for test  
  175. #endif  
  176. }  
  177. unsigned int HtmlParser::getHtmlNodeCount()  
  178. {  
  179.     return (m_HtmlNodes.GetSize() / sizeof(HtmlNode));  
  180. }  
  181. HtmlNode* HtmlParser::getHtmlNodes()  
  182. {  
  183.     return (HtmlNode*) m_HtmlNodes.GetPtr();  
  184. }  
  185. void HtmlParser::freeHtmlNodes()  
  186. {  
  187.     HtmlNode* pNodes = getHtmlNodes();  
  188.     for(int i = 0, count = getHtmlNodeCount(); i < count; i++)  
  189.     {  
  190.         HtmlNode* pNode = pNodes + i;  
  191.         if(pNode->text)  
  192.             freeDuplicatedStr(pNode->text);  
  193.         if(pNode->props)  
  194.             MFreeMemory(pNode->props); //see: CMem::Alloc  
  195.     }  
  196.     m_HtmlNodes.Empty();  
  197. }  
  198. //[virtual]  
  199. HtmlTagType HtmlParser::getHtmlTagTypeFromName(const WCHAR* szTagName)  
  200. {  
  201.     //todo: uses hashmap  
  202.     struct N2T { const WCHAR* name; HtmlTagType type; };  
  203.     static N2T n2tTable[] =   
  204.     {  
  205.         { L"A", TAG_A },  
  206.         { L"FONT", TAG_FONT },  
  207.         { L"IMG", TAG_IMG },  
  208.         { L"P", TAG_P },  
  209.         { L"DIV", TAG_DIV },  
  210.         { L"SPAN", TAG_SPAN },  
  211.         { L"BR", TAG_BR },  
  212.         { L"B", TAG_B },  
  213.         { L"I", TAG_I },  
  214.         { L"HR", TAG_HR },  
  215.         { L"COLOR", TAG_COLOR },  
  216.         { L"BGCOLOR", TAG_BGCOLOR },  
  217.     };  
  218.     for(int i = 0, count = sizeof(n2tTable)/sizeof(n2tTable[0]); i < count; i++)  
  219.     {  
  220.         N2T* p = &n2tTable[i];  
  221.         if(wcsicmp(p->name, szTagName) == 0)  
  222.             return p->type;  
  223.     }  
  224.     return TAG_UNKNOWN;  
  225. }  
  226. void skipSpaceChars(WCHAR*& p)  
  227. {  
  228.     if(p)  
  229.     {  
  230.         while(isspace(*p)) p++;  
  231.     }  
  232. }  
  233. const WCHAR* nextUnqotedSpaceChar(const WCHAR* p)  
  234. {  
  235.     const WCHAR* r = getFirstUnquotedChar(p, L' ');  
  236.     if(!r)  
  237.         r = getFirstUnquotedChar(p, L'/t');  
  238.     return r;  
  239. }  
  240. const WCHAR* duplicateStrAndUnquote(const WCHAR* str, unsigned int nChar)  
  241. {  
  242.     if( nChar > 1 && (str[0] == L'/"' && str[nChar-1] == L'/"') || (str[0] == L'/'' && str[nChar-1] == L'/'') )  
  243.     {  
  244.         str++; nChar-=2;  
  245.     }  
  246.     return duplicateStr(str, nChar);  
  247. }  
  248. //[virtual]  
  249. void HtmlParser::parseNodeProps(HtmlNode* pNode)  
  250. {  
  251.     if(pNode == NULL || pNode->propCount > 0 || pNode->text == NULL)  
  252.         return;  
  253.     WCHAR* p = pNode->text;  
  254.     WCHAR *ps = NULL;  
  255.     CMem mem;  
  256.     bool inQuote1 = false, inQuote2 = false;  
  257.     WCHAR c;  
  258.     while(c = *p)  
  259.     {  
  260.         if(c == L'/"')  
  261.         {  
  262.             inQuote1 = !inQuote1;  
  263.         }  
  264.         else if(c == L'/'')  
  265.         {  
  266.             inQuote2 = !inQuote2;  
  267.         }  
  268.         if((!inQuote1 && !inQuote2) && (c == L' ' || c == L'/t' || c == L'='))  
  269.         {  
  270.             if(ps)  
  271.             {  
  272.                 mem.AddPointer(duplicateStrAndUnquote(ps, p - ps));  
  273.                 ps = NULL;  
  274.             }  
  275.             if(c == L'=')  
  276.                 mem.AddPointer(NULL);  
  277.         }  
  278.         else  
  279.         {  
  280.             if(ps == NULL)  
  281.                 ps = p;  
  282.         }  
  283.         p++;  
  284.     }  
  285.     if(ps)  
  286.         mem.AddPointer(duplicateStrAndUnquote(ps, p - ps));  
  287.     mem.AddPointer(NULL);  
  288.     mem.AddPointer(NULL);  
  289.     WCHAR** pp = (WCHAR**) mem.GetPtr();  
  290.     CMem props;  
  291.     for(int i = 0, n = mem.GetSize() / sizeof(WCHAR*) - 2; i < n; i++)  
  292.     {  
  293.         props.AddPointer(pp[i]); //prop name  
  294.         if(pp[i+1] == NULL)  
  295.         {  
  296.             props.AddPointer(pp[i+2]); //prop value  
  297.             i += 2;  
  298.         }  
  299.         else  
  300.             props.AddPointer(NULL); //prop vlalue  
  301.     }  
  302.     pNode->propCount = props.GetSize() / sizeof(WCHAR*) / 2;  
  303.     pNode->props = (HtmlNodeProp*) props.Detach();  
  304. }  
  305. const HtmlNodeProp* HtmlParser::getNodeProp(const HtmlNode* pNode, const WCHAR* szPropName)  
  306. {  
  307.     if(pNode == NULL || pNode->propCount <= 0)  
  308.         return NULL;  
  309.     for(int i = 0; i < pNode->propCount; i++)  
  310.     {  
  311.         HtmlNodeProp* prop = pNode->props + i;  
  312.         if(wcsicmp(prop->szName, szPropName) == 0)  
  313.             return prop;  
  314.     }  
  315.     return NULL;  
  316. }  
  317. const WCHAR* HtmlParser::getNodePropStringValue(const HtmlNode* pNode, const WCHAR* szPropName, const WCHAR* szDefaultValue /*= NULL*/)  
  318. {  
  319.     const HtmlNodeProp* pProp = getNodeProp(pNode, szPropName);  
  320.     if(pProp)  
  321.         return pProp->szValue;  
  322.     else  
  323.         return szDefaultValue;  
  324. }  
  325. int HtmlParser::getNodePropIntValue(const HtmlNode* pNode, const WCHAR* szPropName, int defaultValue /*= 0*/)  
  326. {  
  327.     const HtmlNodeProp* pProp = getNodeProp(pNode, szPropName);  
  328.     if(pProp && pProp->szValue)  
  329.         return _wtoi(pProp->szValue);  
  330.     else  
  331.         return defaultValue;  
  332. }  
  333. void HtmlParser::dumpHtmlNodes()  
  334. {  
  335. #ifdef _DEBUG  
  336.     HtmlNode* pNodes = getHtmlNodes();  
  337.     WCHAR buffer[256];  
  338.     OutputDebugString(L"/n-------- dumpHtmlNodes --------/n");  
  339.     for(int i = 0, count = getHtmlNodeCount(); i < count; i++)  
  340.     {  
  341.         HtmlNode* pNode = pNodes + i;  
  342.         switch(pNode->type)  
  343.         {  
  344.         case NODE_CONTENT:  
  345.             wsprintf(buffer, L"%2d) type: NODE_CONTENT, text: %s", i, pNode->text);  
  346.             break;  
  347.         case NODE_START_TAG:  
  348.             wsprintf(buffer, L"%2d) type: NODE_START_TAG, tagName: %s (%d), text: %s", i, pNode->tagName, pNode->tagType, pNode->text);  
  349.             break;  
  350.         case NODE_CLOSE_TAG:  
  351.             wsprintf(buffer, L"%2d) type: NODE_CLOSE_TAG, tagName: %s", i, pNode->tagName);  
  352.             break;  
  353.         case NODE_UNKNOWN:  
  354.         default:  
  355.             wsprintf(buffer, L"%2d) type: NODE_UNKNOWN", i);  
  356.             break;  
  357.         }  
  358.         OutputDebugString(buffer);  
  359.         OutputDebugString(L"/n");  
  360.         if(pNode->propCount > 0)  
  361.         {  
  362.             OutputDebugString(L"    props: ");  
  363.             for(int i = 0; i < pNode->propCount; i++)  
  364.             {  
  365.                 HtmlNodeProp* prop = pNode->props + i;  
  366.                 if(prop->szValue)  
  367.                     wsprintf(buffer, L"%s = %s", prop->szName, prop->szValue);  
  368.                 else  
  369.                     wsprintf(buffer, L"%s", prop->szName);  
  370.                 OutputDebugString(buffer);  
  371.                 if(i < pNode->propCount - 1)  
  372.                 {  
  373.                     OutputDebugString(L", ");  
  374.                 }  
  375.             }  
  376.             OutputDebugString(L"/n");  
  377.         }  
  378.     }  
  379.     OutputDebugString(L"-------- end of dumpHtmlNodes --------/n");  
  380. #endif  
  381. }  
  382. //just for test  
  383. class TestHtmlParser  
  384. {  
  385. public:  
  386.     TestHtmlParser()  
  387.     {  
  388.         HANDLE CMem_GetProcessHeap();  
  389.         CMem_GetProcessHeap();  
  390.         HtmlParser htmlParser;  
  391.         HtmlNode node;  
  392.         node.text = L" a=1 b c=/'x y=0/' d = abc ";  
  393.         htmlParser.parseNodeProps(&node);  
  394.         htmlParser.ParseHtml(L"...<p>---<a href="url" mce_href="url">link</a>...");  
  395.         htmlParser.ParseHtml(L"<p>---< a   href=url >link</a>");  
  396.         htmlParser.ParseHtml(L"<p x=a y=b z = /"c <a href="url" mce_href="url">/" >");  
  397.     }  
  398. };  
  399. TestHtmlParser testHtmlParser;  
 

全文完,谢谢。


原创粉丝点击