`
wangqisen
  • 浏览: 47271 次
文章分类
社区版块
存档分类
最新评论

简易html解析器之源码

阅读更多

转载本文的出发点是想要搞一个简易浏览器,就是抓到网页然后使用html解析器来解析的过程。虽然网上有许多开源解析器现成可用,但是,我的本意并非直接使用。而是想要实现一个简易的解析器。在看了源码之后,发现没有相关代码结构的文档实在是很难下手,于是决定看一下网上有没有相关的文档。终于找到一个。转来看看。

作者:庄晓立 (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++语言语法表示如下:

  1. enumHtmlNodeType
  2. {
  3. NODE_UNKNOWN=0,
  4. NODE_START_TAG,
  5. NODE_CLOSE_TAG,
  6. NODE_CONTENT,
  7. };
  8. enumHtmlTagType
  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. structHtmlNodeProp
  14. {
  15. WCHAR*szName;
  16. WCHAR*szValue;
  17. };
  18. #defineMAX_HTML_TAG_LENGTH(15)
  19. structHtmlNode
  20. {
  21. HtmlNodeTypetype;
  22. HtmlTagTypetagType;
  23. WCHARtagName[MAX_HTML_TAG_LENGTH+1];
  24. WCHAR*text;
  25. intpropCount;
  26. HtmlNodeProp*props;
  27. };

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

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

  1. voidHtmlParser::ParseHtml(constWCHAR*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. WCHARc;
  10. boolbInQuotes=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. //AddTextNode
  27. pNode=NewHtmlNode();
  28. pNode->type=NODE_CONTENT;
  29. pNode->text=duplicateStrUtill(s,L'<',true);
  30. }
  31. s=p+1;
  32. }
  33. elseif(c==L'>')
  34. {
  35. if(p>s)
  36. {
  37. //AddHtmlTagNode
  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. inttagNamelen=wcslen(pNode->tagName);
  46. if(pNode->tagName[tagNamelen-1]==L'/')
  47. pNode->tagName[tagNamelen-1]=L'/0';
  48. //处理结点属性
  49. for(inti=0;i<tagNamelen;i++)
  50. {
  51. if(pNode->tagName[i]==L''//第一个空格后面跟的是属性列表
  52. ||pNode->tagName[i]==L'=')//扩展支持这种格式:<tagName=value>,等效于<tagNametagName=value>
  53. {
  54. WCHAR*props=(pNode->tagName[i]==L''?s+i+1:s);
  55. pNode->text=duplicateStrUtill(props,L'>',true);
  56. intnodeTextLen=wcslen(pNode->text);
  57. if(pNode->text[nodeTextLen-1]==L'/')//去掉最后可能会有的'/'字符,如这种情况:<imgsrc="..."mce_src="..."/>
  58. pNode->text[nodeTextLen-1]=L'/0';
  59. pNode->tagName[i]=L'/0';
  60. parseNodeProps(pNode);//parseprops
  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. //AddTextNode
  73. pNode=NewHtmlNode();
  74. pNode->type=NODE_CONTENT;
  75. pNode->text=duplicateStr(s,-1);
  76. }
  77. #ifdef_DEBUG
  78. dumpHtmlNodes();//justfortest
  79. #endif
  80. }

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

  1. //[virtual]
  2. voidHtmlParser::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. CMemmem;
  9. boolinQuote1=false,inQuote2=false;
  10. WCHARc;
  11. while(c=*p)
  12. {
  13. if(c==L'/"')
  14. {
  15. inQuote1=!inQuote1;
  16. }
  17. elseif(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. CMemprops;
  44. for(inti=0,n=mem.GetSize()/sizeof(WCHAR*)-2;i<n;i++)
  45. {
  46. props.AddPointer(pp[i]);//propname
  47. if(pp[i+1]==NULL)
  48. {
  49. props.AddPointer(pp[i+2]);//propvalue
  50. i+=2;
  51. }
  52. else
  53. props.AddPointer(NULL);//propvlalue
  54. }
  55. pNode->propCount=props.GetSize()/sizeof(WCHAR*)/2;
  56. pNode->props=(HtmlNodeProp*)props.Detach();
  57. }

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

  1. //[virtual]
  2. HtmlTagTypeHtmlParser::getHtmlTagTypeFromName(constWCHAR*szTagName)
  3. {
  4. //todo:useshashmap
  5. structN2T{constWCHAR*name;HtmlTagTypetype;};
  6. staticN2Tn2tTable[]=
  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(inti=0,count=sizeof(n2tTable)/sizeof(n2tTable[0]);i<count;i++)
  20. {
  21. N2T*p=&n2tTable[i];
  22. if(wcsicmp(p->name,szTagName)==0)
  23. returnp->type;
  24. }
  25. returnTAG_UNKNOWN;
  26. }

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

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

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

HtmlParser.h:

  1. #include"common.h"
  2. //HtmlParser类,用于解析HTML文本
  3. //byliigo,@2010
  4. enumHtmlNodeType
  5. {
  6. NODE_UNKNOWN=0,
  7. NODE_START_TAG,
  8. NODE_CLOSE_TAG,
  9. NODE_CONTENT,
  10. NODE_SOFT_LINE,
  11. };
  12. enumHtmlTagType
  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>,等效于<colorcolor=red>
  17. };
  18. structHtmlNodeProp
  19. {
  20. WCHAR*szName;
  21. WCHAR*szValue;
  22. };
  23. #defineMAX_HTML_TAG_LENGTH(15)
  24. structHtmlNode
  25. {
  26. HtmlNodeTypetype;
  27. HtmlTagTypetagType;
  28. WCHARtagName[MAX_HTML_TAG_LENGTH+1];
  29. WCHAR*text;
  30. intpropCount;
  31. HtmlNodeProp*props;
  32. };
  33. classHtmlParser
  34. {
  35. friendclassHTMLView;
  36. public:
  37. HtmlParser(){}
  38. public:
  39. //html
  40. voidParseHtml(constWCHAR*szHtml);
  41. constWCHAR*GetHtml()const{returnm_html.GetText();}
  42. //nodes
  43. unsignedintgetHtmlNodeCount();
  44. HtmlNode*getHtmlNodes();
  45. //props
  46. constHtmlNodeProp*getNodeProp(constHtmlNode*pNode,constWCHAR*szPropName);
  47. constWCHAR*getNodePropStringValue(constHtmlNode*pNode,constWCHAR*szPropName,constWCHAR*szDefaultValue=NULL);
  48. intgetNodePropIntValue(constHtmlNode*pNode,constWCHAR*szPropName,intdefaultValue=0);
  49. protected:
  50. //允许子类覆盖,以便识别更多结点(提高解析质量),或者识别更少结点(提高解析速度)
  51. virtualHtmlTagTypegetHtmlTagTypeFromName(constWCHAR*szTagName);
  52. public:
  53. //允许子类覆盖,以便更好的解析节点属性,或者干脆不解析节点属性(提高解析速度)
  54. virtualvoidparseNodeProps(HtmlNode*pNode);//todo:makeprotected,aftertesting
  55. private:
  56. HtmlNode*NewHtmlNode();
  57. voidfreeHtmlNodes();
  58. voiddumpHtmlNodes();
  59. private:
  60. CMemm_HtmlNodes;
  61. CMStringm_html;
  62. };
  63. //一些文本处理函数
  64. WCHAR*duplicateStr(constWCHAR*pSrc,unsignedintnChar);
  65. voidfreeDuplicatedStr(WCHAR*p);
  66. unsignedintcopyStr(WCHAR*pDest,unsignedintnDest,constWCHAR*pSrc,unsignedintnChar);

HtmlParser.cpp:

  1. #include"HtmlParser.h"
  2. //HtmlParser类,用于解析HTML文本
  3. //byliigo,@2010
  4. constWCHAR*wcsnchr(constWCHAR*pStr,intlen,WCHARc)
  5. {
  6. constWCHAR*p=pStr;
  7. while(1)
  8. {
  9. if(*p==c)returnp;
  10. p++;
  11. if((p-pStr)==len)break;
  12. }
  13. returnNULL;
  14. }
  15. constWCHAR*getFirstUnquotedChar(constWCHAR*pStr,WCHARendcahr)
  16. {
  17. WCHARc;
  18. constWCHAR*p=pStr;
  19. boolinQuote1=false,inQuote2=false;//'inQuote1',"inQuote2"
  20. while(c=*p)
  21. {
  22. if(c==L'/'')
  23. {
  24. inQuote1=!inQuote1;
  25. }
  26. elseif(c==L'/"')
  27. {
  28. inQuote2=!inQuote2;
  29. }
  30. if(!inQuote1&&!inQuote2)
  31. {
  32. if(c==endcahr)returnp;
  33. }
  34. p++;
  35. }
  36. returnNULL;
  37. }
  38. //nDestandnCharcanby-1
  39. unsignedintcopyStr(WCHAR*pDest,unsignedintnDest,constWCHAR*pSrc,unsignedintnChar)
  40. {
  41. if(pDest==NULL||nDest==0)
  42. return0;
  43. if(pSrc==NULL)
  44. {
  45. pDest[0]=L'/0';
  46. return0;
  47. }
  48. if(nChar==(unsignedint)-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. returnnChar;
  55. }
  56. intcopyStrUtill(WCHAR*pDest,unsignedintnDest,constWCHAR*pSrc,WCHARendchar,boolignoreEndCharInQuoted)
  57. {
  58. if(nDest==0)return0;
  59. pDest[0]=L'/0';
  60. constWCHAR*pSearched=(ignoreEndCharInQuoted?getFirstUnquotedChar(pSrc,endchar):wcschr(pSrc,endchar));
  61. if(pSearched<=pSrc)return0;
  62. returncopyStr(pDest,nDest,pSrc,pSearched-pSrc);
  63. }
  64. //nCharcanbe-1
  65. WCHAR*duplicateStr(constWCHAR*pSrc,unsignedintnChar)
  66. {
  67. if(nChar==(unsignedint)-1)
  68. nChar=wcslen(pSrc);
  69. WCHAR*pNew=(WCHAR*)malloc((nChar+1)*sizeof(WCHAR));
  70. copyStr(pNew,-1,pSrc,nChar);
  71. returnpNew;
  72. }
  73. WCHAR*duplicateStrUtill(constWCHAR*pSrc,WCHARendchar,boolignoreEndCharInQuoted)
  74. {
  75. constWCHAR*pSearched=(ignoreEndCharInQuoted?getFirstUnquotedChar(pSrc,endchar):wcschr(pSrc,endchar));;
  76. if(pSearched<=pSrc)returnNULL;
  77. intn=pSearched-pSrc;
  78. returnduplicateStr(pSrc,n);
  79. }
  80. voidfreeDuplicatedStr(WCHAR*p)
  81. {
  82. if(p)free(p);
  83. }
  84. HtmlNode*HtmlParser::NewHtmlNode()
  85. {
  86. staticcharstaticHtmlNodeTemplate[sizeof(HtmlNode)]={0};
  87. /*
  88. staticHtmlNodestaticHtmlNodeTemplate;//={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. returnpNode;
  96. }
  97. voidHtmlParser::ParseHtml(constWCHAR*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. WCHARc;
  106. boolbInQuotes=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. //AddTextNode
  123. pNode=NewHtmlNode();
  124. pNode->type=NODE_CONTENT;
  125. pNode->text=duplicateStrUtill(s,L'<',true);
  126. }
  127. s=p+1;
  128. }
  129. elseif(c==L'>')
  130. {
  131. if(p>s)
  132. {
  133. //AddHtmlTagNode
  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. inttagNamelen=wcslen(pNode->tagName);
  142. if(pNode->tagName[tagNamelen-1]==L'/')
  143. pNode->tagName[tagNamelen-1]=L'/0';
  144. //处理结点属性
  145. for(inti=0;i<tagNamelen;i++)
  146. {
  147. if(pNode->tagName[i]==L''//第一个空格后面跟的是属性列表
  148. ||pNode->tagName[i]==L'=')//扩展支持这种格式:<tagName=value>,等效于<tagNametagName=value>
  149. {
  150. WCHAR*props=(pNode->tagName[i]==L''?s+i+1:s);
  151. pNode->text=duplicateStrUtill(props,L'>',true);
  152. intnodeTextLen=wcslen(pNode->text);
  153. if(pNode->text[nodeTextLen-1]==L'/')//去掉最后可能会有的'/'字符,如这种情况:<imgsrc="..."mce_src="..."/>
  154. pNode->text[nodeTextLen-1]=L'/0';
  155. pNode->tagName[i]=L'/0';
  156. parseNodeProps(pNode);//parseprops
  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. //AddTextNode
  169. pNode=NewHtmlNode();
  170. pNode->type=NODE_CONTENT;
  171. pNode->text=duplicateStr(s,-1);
  172. }
  173. #ifdef_DEBUG
  174. dumpHtmlNodes();//justfortest
  175. #endif
  176. }
  177. unsignedintHtmlParser::getHtmlNodeCount()
  178. {
  179. return(m_HtmlNodes.GetSize()/sizeof(HtmlNode));
  180. }
  181. HtmlNode*HtmlParser::getHtmlNodes()
  182. {
  183. return(HtmlNode*)m_HtmlNodes.GetPtr();
  184. }
  185. voidHtmlParser::freeHtmlNodes()
  186. {
  187. HtmlNode*pNodes=getHtmlNodes();
  188. for(inti=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. HtmlTagTypeHtmlParser::getHtmlTagTypeFromName(constWCHAR*szTagName)
  200. {
  201. //todo:useshashmap
  202. structN2T{constWCHAR*name;HtmlTagTypetype;};
  203. staticN2Tn2tTable[]=
  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(inti=0,count=sizeof(n2tTable)/sizeof(n2tTable[0]);i<count;i++)
  219. {
  220. N2T*p=&n2tTable[i];
  221. if(wcsicmp(p->name,szTagName)==0)
  222. returnp->type;
  223. }
  224. returnTAG_UNKNOWN;
  225. }
  226. voidskipSpaceChars(WCHAR*&p)
  227. {
  228. if(p)
  229. {
  230. while(isspace(*p))p++;
  231. }
  232. }
  233. constWCHAR*nextUnqotedSpaceChar(constWCHAR*p)
  234. {
  235. constWCHAR*r=getFirstUnquotedChar(p,L'');
  236. if(!r)
  237. r=getFirstUnquotedChar(p,L'/t');
  238. returnr;
  239. }
  240. constWCHAR*duplicateStrAndUnquote(constWCHAR*str,unsignedintnChar)
  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. returnduplicateStr(str,nChar);
  247. }
  248. //[virtual]
  249. voidHtmlParser::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. CMemmem;
  256. boolinQuote1=false,inQuote2=false;
  257. WCHARc;
  258. while(c=*p)
  259. {
  260. if(c==L'/"')
  261. {
  262. inQuote1=!inQuote1;
  263. }
  264. elseif(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. CMemprops;
  291. for(inti=0,n=mem.GetSize()/sizeof(WCHAR*)-2;i<n;i++)
  292. {
  293. props.AddPointer(pp[i]);//propname
  294. if(pp[i+1]==NULL)
  295. {
  296. props.AddPointer(pp[i+2]);//propvalue
  297. i+=2;
  298. }
  299. else
  300. props.AddPointer(NULL);//propvlalue
  301. }
  302. pNode->propCount=props.GetSize()/sizeof(WCHAR*)/2;
  303. pNode->props=(HtmlNodeProp*)props.Detach();
  304. }
  305. constHtmlNodeProp*HtmlParser::getNodeProp(constHtmlNode*pNode,constWCHAR*szPropName)
  306. {
  307. if(pNode==NULL||pNode->propCount<=0)
  308. returnNULL;
  309. for(inti=0;i<pNode->propCount;i++)
  310. {
  311. HtmlNodeProp*prop=pNode->props+i;
  312. if(wcsicmp(prop->szName,szPropName)==0)
  313. returnprop;
  314. }
  315. returnNULL;
  316. }
  317. constWCHAR*HtmlParser::getNodePropStringValue(constHtmlNode*pNode,constWCHAR*szPropName,constWCHAR*szDefaultValue/*=NULL*/)
  318. {
  319. constHtmlNodeProp*pProp=getNodeProp(pNode,szPropName);
  320. if(pProp)
  321. returnpProp->szValue;
  322. else
  323. returnszDefaultValue;
  324. }
  325. intHtmlParser::getNodePropIntValue(constHtmlNode*pNode,constWCHAR*szPropName,intdefaultValue/*=0*/)
  326. {
  327. constHtmlNodeProp*pProp=getNodeProp(pNode,szPropName);
  328. if(pProp&&pProp->szValue)
  329. return_wtoi(pProp->szValue);
  330. else
  331. returndefaultValue;
  332. }
  333. voidHtmlParser::dumpHtmlNodes()
  334. {
  335. #ifdef_DEBUG
  336. HtmlNode*pNodes=getHtmlNodes();
  337. WCHARbuffer[256];
  338. OutputDebugString(L"/n--------dumpHtmlNodes--------/n");
  339. for(inti=0,count=getHtmlNodeCount();i<count;i++)
  340. {
  341. HtmlNode*pNode=pNodes+i;
  342. switch(pNode->type)
  343. {
  344. caseNODE_CONTENT:
  345. wsprintf(buffer,L"%2d)type:NODE_CONTENT,text:%s",i,pNode->text);
  346. break;
  347. caseNODE_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. caseNODE_CLOSE_TAG:
  351. wsprintf(buffer,L"%2d)type:NODE_CLOSE_TAG,tagName:%s",i,pNode->tagName);
  352. break;
  353. caseNODE_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(inti=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"--------endofdumpHtmlNodes--------/n");
  380. #endif
  381. }
  382. //justfortest
  383. classTestHtmlParser
  384. {
  385. public:
  386. TestHtmlParser()
  387. {
  388. HANDLECMem_GetProcessHeap();
  389. CMem_GetProcessHeap();
  390. HtmlParserhtmlParser;
  391. HtmlNodenode;
  392. node.text=L"a=1bc=/'xy=0/'d=abc";
  393. htmlParser.parseNodeProps(&node);
  394. htmlParser.ParseHtml(L"...<p>---<ahref="url"mce_href="url">link</a>...");
  395. htmlParser.ParseHtml(L"<p>---<ahref=url>link</a>");
  396. htmlParser.ParseHtml(L"<px=ay=bz=/"c<ahref="url"mce_href="url">/">");
  397. }
  398. };
  399. TestHtmlParsertestHtmlParser;


分享到:
评论

相关推荐

    JAVA上百实例源码以及开源项目源代码

    Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来...

    java简易版开心农场源码-Note:前端

    告知浏览器的解析器用什么文档标准解析这个文档。DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。 (2)标准模式的排版 和JS运作模式都是以该浏览器支持的最高标准运行。在兼容模式中,页面以宽松的向后兼容的...

    JAVA上百实例源码以及开源项目

     Java实现HTTP连接与浏览,Java源码下载,输入html文件地址或网址,显示页面和HTML源文件,一步步的实现过程请下载本实例的Java源码,代码中包括丰富的注释,对学习有帮助。 Java实现的FTP连接与数据浏览程序 1个...

    50个微信小程序源码.zip

    50个微信小程序源码: AppleMusic B站首页界面设计:附详细教程 cnode社区版 dribbble FlexLayout布局 gank HIapp IT-EBOOK leantodu LOL战绩查询 movecss效果 Railay:整体框架 redux绑定 TCP,IP长连接 todo list v2...

    我记录网站综合系统 1.6源码

    我记录网站综合系统 1.6源码 网站部分新增: 微博:微博功能增强,实现全功能微博 采集:新闻采集功能,包括定时采集、图片抓取和数据导入 wiki:将通用页面转化为可以多人协作编辑的wiki形式 群组:重新设计...

    《Java2范例入门与提高》所有实例源码

    《Java2范例入门与提高》的内容包含了开发实例的所有程序源码,所有程序源码都是在Java2上编译通过的。要运行本光盘上的实例,用户需要安装Java编译环境,并去掉其只读属性,除非特殊说明,一般经编译即可直接运行。...

    MVC源码学习:打造自己的MVC框架

    2.1、Asp.net管线事件简易说明 ........................................................................................................................ 6 2.2、Asp.net中常见的HttpHandler类型 ...............

    Google Android SDK开发范例大全(完整版附部分源码).pdf

    8.13 移动RSS阅读器——利用SAXParser解析XML 8.14 远程下载安装Android程序——APKInstaller的应用 8.15 手机下载看3gp影片——Runnable混搭SurfaceView 8.16 访问网站LoginAPI——远程服务器验证程序运行权限 ...

    java开源包8

    JOpt Simple 是一个简单的、测试驱动的命令行解析器,支持 POSIX getopt() 和 GNU getopt_long() Java的HTTP代理服务器 Smart Cache Smart Cache 是一个采用 Java 开发的 HTTP/1.1代理服务器,也可以用来作为Web的...

    经典:Java2范例入门与提高

    本书所附光盘的内容包含了开发实例的所有程序源码,所有程序源码都是在Java2上编译通过的。要运行本光盘上的实例,用户需要安装Java编译环境,请将光盘上的实例拷贝到本机硬盘上,并去掉其只读属性,除非特殊说明,...

    八年PHP文件管理器 v2.6.7.zip

    支持 js css html php 代码 压缩格式化,编辑器配色,代码加亮查看 支持在线 ZIP tgz 压缩 解压 zip和文件夹 预览 支持 文件备注 支持 安全 防爆码 支持 危险文件扫描、ipc 查询、中英文翻译、批量缩图、邮件...

    八年PHP文件管理器 v2.6.7

    支持 js css html php 代码 压缩格式化,编辑器配色,代码加亮查看 支持在线 ZIP tgz 压缩 解压 zip和文件夹 预览 支持 文件备注 支持 安全 防爆码 支持 危险文件扫描、ipc 查询、中英文翻译、批量缩图、邮件...

    .net开源的综合开发框架wojilu框架.zip

    wojilu Log一个轻量级 Json 解析器一个简易的前端 Ajax 库(弹窗、局部刷新、验证、上传等)2. 我记录网站综合系统 2.0名称: 我记录网站综合系统 2.0网址: http://www.wojilu.com下载: 请到论坛置顶帖子中下载。...

    java开源包1

    JOpt Simple 是一个简单的、测试驱动的命令行解析器,支持 POSIX getopt() 和 GNU getopt_long() Java的HTTP代理服务器 Smart Cache Smart Cache 是一个采用 Java 开发的 HTTP/1.1代理服务器,也可以用来作为Web的...

    java开源包11

    JOpt Simple 是一个简单的、测试驱动的命令行解析器,支持 POSIX getopt() 和 GNU getopt_long() Java的HTTP代理服务器 Smart Cache Smart Cache 是一个采用 Java 开发的 HTTP/1.1代理服务器,也可以用来作为Web的...

    java开源包2

    JOpt Simple 是一个简单的、测试驱动的命令行解析器,支持 POSIX getopt() 和 GNU getopt_long() Java的HTTP代理服务器 Smart Cache Smart Cache 是一个采用 Java 开发的 HTTP/1.1代理服务器,也可以用来作为Web的...

    java开源包3

    JOpt Simple 是一个简单的、测试驱动的命令行解析器,支持 POSIX getopt() 和 GNU getopt_long() Java的HTTP代理服务器 Smart Cache Smart Cache 是一个采用 Java 开发的 HTTP/1.1代理服务器,也可以用来作为Web的...

    java开源包6

    JOpt Simple 是一个简单的、测试驱动的命令行解析器,支持 POSIX getopt() 和 GNU getopt_long() Java的HTTP代理服务器 Smart Cache Smart Cache 是一个采用 Java 开发的 HTTP/1.1代理服务器,也可以用来作为Web的...

    java开源包5

    JOpt Simple 是一个简单的、测试驱动的命令行解析器,支持 POSIX getopt() 和 GNU getopt_long() Java的HTTP代理服务器 Smart Cache Smart Cache 是一个采用 Java 开发的 HTTP/1.1代理服务器,也可以用来作为Web的...

Global site tag (gtag.js) - Google Analytics