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

67 篇文章 1 订阅
41 篇文章 2 订阅

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

 

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

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

 

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

 

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

 

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

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

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

HtmlParser.h:

 

HtmlParser.cpp:

 

全文完,谢谢。



 

2011-1-22 liigo 补记:本文所提供的源代码,目前有未完善之处,如没有考虑到内嵌JavaScrip代码和HTML注释中的特殊字符(特别是尖括号)对解析器的影响,另外还可能有其他疏漏和bug,故代码仅可用于学习参考研究使用。我今后也将继续改进此HTML语法解析器。特此声明。



 

2012-5-5 liigo 补记:在刚刚过去的半个多月里,我又对此HTML解析器做了很多改进(并将持续改进),目前应该说是比较成熟和完善了。源代码已经放到GitHub: https://github.com/liigo/html-parser 。另外,本文嵌入的代码已经很旧了(且其中C/C++转义字符被CSDN博客系统粗暴替换),但主要的设计和实现思路依然有效。我也有计划新写一篇本文的2.0版。


  • 2
    点赞
  • 141
    收藏
    觉得还不错? 一键收藏
  • 69
    评论
### 回答1: 虚拟机是一种允许在一个操作系统中运行另一个操作系统的软件,它使用软件模拟硬件的功能。虚拟机有很多种类型和实现方式,其中以基于C/C++语言来设计实现虚拟机的方式比较常见。 设计实现C/C++虚拟机的过程可以分为以下几个步骤: 第一步,定义虚拟机的指令集。指令集是虚拟机运行的基本单位,它包含了虚拟机能够执行的基本操作。根据实际需求可以选择一些常用的指令,比如加载、存储、计算等。 第二步,定义虚拟机的内存模型。虚拟机的内存模型包括了堆、栈、全局变量等内存区域以及它们的管理机制。 第三步,实现虚拟机的运行时环境。运行时环境负责管理虚拟机的运行状态,包括指令的解析和执行、内存的分配和回收等。可以使用C/C++编写一个运行时环境库,提供给用户编写的虚拟机程序调用。 第四步,实现虚拟机的编译器。编译器将用户编写的高级语言程序转换为虚拟机指令集形式,这样虚拟机就可以执行用户编写的程序了。编译器的实现可以利用C/C++的编译技术,比如词法分析、语法分析、语义分析等。 第五步,测试和调试。设计实现虚拟机后,需要进行测试和调试,确保虚拟机的正常运行。 总之,设计实现C/C++虚拟机需要先定义指令集和内存模型,然后实现虚拟机的运行时环境和编译器,并进行测试和调试,以确保虚拟机的正常运行。这样,用户就可以使用虚拟机来运行各种类型的应用程序了。 ### 回答2: 虚拟机的设计实现是一个庞大而复杂的任务,需要考虑多个方面,并且要兼顾性能和灵活性。下面我将就这一问题进行回答。 虚拟机的设计首先需要考虑指令集架构的选择。常见的选择有基于栈的架构和基于寄存器的架构。栈架构可以简化指令集的设计实现,但性能相对较低;而寄存器架构则可以提高性能,但实现较为复杂。根据具体需求和考虑因素,可以选择适合的指令集架构。 在虚拟机的实现中,需要实现指令解码、执行和存储管理等核心功能。指令解码将字节码转化为可执行的机器指令,执行则是根据解码结果执行相应的操作。存储管理包括变量的分配、回收和访问控制等。这些功能需要细致地设计实现,以保证虚拟机的正确运行和高效性能。 此外,在虚拟机的设计中,还需要考虑辅助功能的实现,如异常处理、线程管理、垃圾回收等。异常处理用于处理运行时错误,线程管理用于支持多线程程序的并发执行,垃圾回收则是通过自动回收不再使用的内存,提高内存利用率。这些功能需要结合具体应用场景和需求进行设计实现。 最后,虚拟机的性能优化也是设计实现的重要一环。通过使用一些优化技术,如即时编译、代码缓存、预热等,可以提高虚拟机的执行效率。此外,还可以通过对指令集的扩展和优化,以及对存储管理的优化,进一步提升虚拟机的性能。 总而言之,虚拟机的设计实现是一个复杂的任务,需要考虑多个方面,包括指令集架构选择、核心功能的实现、辅助功能的实现以及性能优化等。这些方面的设计实现需要根据具体需求和考虑因素来进行,以保证虚拟机的正确运行和高效性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值