简单谈谈XML DOM parser

定义

DOM是什么?DOM的全称是Document Object Model,即文档对象模型。

可能前端同学对这个会比较熟悉;事实上,HTML中的DOM和XML中的DOM是同一个概念。我们来看看W3C对DOM的定义:

“The DOM” is an API for accessing and manipulating documents (in particular, HTML and XML documents)

也就是说,DOM是一个对文档(尤其是指的是HTML和XML中的文档)进行访问和修改的API。

所以,顾名思义,DOM parser就是一个对DOM进行解析的工具,把XML加载到内存中,解析成树型结构,也就是所谓的DOM树。并且,树的每一个结点都是对象,封装了数据,并且提供了相应的操作方法。

为什么是树

为什么是树,而不是别的结构?因为,文档具有层次结构的特点,属于那种类型相同(都是element),并且层数可变的数据。对于这种数据,使用树能够有效降低深度,提供比较高的检索效率。当然,这种结构也有一个弱点,就是因为类型相同,约束实际上很弱,只需要满足很少的一些条件就可以被称为是“格式良好”的;因此,我们引入了词汇表,包括DTD和Schema,来对语义做进一步的校验,以确保XML是“合法”的。

格式良好的XML

格式良好其实是客观需求,至少在当时是。因为,小型设备往往缺乏解释“糟糕”的标记语言的计算资源/计算能力,所以需要我们满足一些规范,以减少解析的开销。

格式良好的XML需要满足以下几个条件,也就是所谓的“5 + 1规则”。其中,5条定义语法结构,1条保证一致性:

  • 单根元素

    • 所有的XML文档都只能有一个根元素
    • 树结构,而非森林结构
  • 元素标签规则

    • 同时有开始标签<>和结束标签
    • 没有内容的元素可以简写
    • 可以拥有键值对形式的属性a=""
  • 元素嵌套规则

    • 可以任意嵌套任意多层的子元素
    • 子元素关闭前不能提前关闭父元素
  • 元素规则

    命名

    • 首字母必须是大小写字母或者下划线
    • 后面可以接任意长度的字符、数字、连字符
    • 大小写敏感(与HTML不同,HTML大小写不敏感)
    • 不能有空格
    • 不能以XML(以及XML的任意大小写组合)开头,这是保留字段

    内容

    • XML文档由使用标签对表示的元素、可选的属性和可选的在开始标签和结束标签之间的数据构成

    • 混合内容:数据 + 其他内容

    • PCDATA(被解析的字符数据):提取并检查

      • 需要对预定义实体进行转义,如>
      • 默认使用
    • CDATA(字符数据):使用进行包装,不处理

      • HTML、XML、JS代码、需要大量转义的文本,等等

      • 内部的文本遵循源语言的转义规则

        类似于其他语言中的raw string,我没有暗示Python的意思。

        要不然叫template string吧。

  • 元素属性

    • 给元素附加信息
    • 键值对,其中key必须是合法的XML名称
    • 属性必须有值,用""或’'包裹
    • 可以有多个属性
  • XML声明(一致性)

    • 可选的XML声明,出现在第一行
    • encoding指定编码
    • standalone表达完整性:是否依赖于文档外的信息?

处理过程

DOM的解析过程分为下面几步:

  1. 需要使用DOM的应用程序创建DOM parser,parser一般是单例的。
  2. parser读取XML文档,以及相应的词汇表(也就是校验规则),比如DTD(已经过时),或者Schema。当然,如果不需要校验,可以只读取XML文档。
  3. parser向应用程序报告解析过程中遇到的错误,包括但不限于:
    1. 格式不良好,也就是不满足5 + 1规则;
    2. I/O异常;
    3. 解析器自身的异常;
    4. 如果有词汇表,并且与词汇表所定义的语义不符;
  4. parser生成DOM树
  5. 应用程序对DOM树进行CRUD操作;
  6. 应用程序输出最终版本的XML。当然,如果应用程序只需要读取XML,这一步就可以没有。

DOM结构

理想情况下,DOM应该是很简单的。但是,实际使用时,为了可读性(没有谁会愿意读那种全部放在一行的XML吧),会插入很多换行符和空白符,这些换行符和空白符也会以text结点的形式出现在DOM树中。同时,结点还会拥有属性,这就让整个DOM的模型复杂了很多。

不过,归根结底,它们都是结点,都拥有共同的操作方法,这给我们的处理过程带来了便利。

DOM结点

通用接口

在DOM中,所有的内容都是结点,包含这些属性:

  • 结点名称
  • 结点类型,比如element,text,attribute
  • 与其他结点的关系,比如parent,siblings,children

包含一些通用的方法:

  • 增加子结点
  • 替换子结点
  • 删除子结点
结点类型

有四种主要的结点类型:

  • Document
  • Element
  • Attr
  • Text

以及一些不那么常见的结点类型:

  • DocumentFragment
    • 这个类型比较有趣,因为它可以不满足之前所说的单根原则。
    • 在HTML中,一般用以实现shadow DOM,注入一些HTML片段。并且,由于它的内容并不是原始DOM的一部分,不会触发重新渲染。
  • CDATASection
    • 这个就是之前所说的CDATA的部分,用以实现“raw string”。
  • Comment
  • Processing Instruction
  • Entity / EntityReference
Document结点

比较特殊的是,DOM会有一个document结点,包含全部的DOM元素。至于为什么,刚才已经提到过了,格式良好的文档应该满足单根元素规则,也就是只有一个根结点,并且是树结构而不是森林结构。可以想象,如果没有document结点,因为还会有别的元素出现,整个DOM就会变成森林结构。

从某种程度上来说,document类似于所谓的“哑结点”。

空白符

刚才提到,为了可读性,我们会往XML里插入很多换行符和空白符,这些换行符和空白符也会以text结点的形式出现在DOM树中。这个有时候会带来一些让人迷惑的现象,比如:

<book ibsn="123-765">
    <author>Tomauthor>
    <title>Footitle>
    <price>$6price>
book>

请问book有几个子节点?答案是7个,3个元素,4个空白符形成的text结点。

属性结点

比较神秘的是,属性不“属于”DOM结构的内容。因为,属性结点没有父结点,也没有子结点。在HTML中,属性是以NamedNodeMap的形式出现在父结点的属性中的。

DOM应用

一个DOM应用,一般来说,会有四个步骤:

  • 创建parser
  • 处理parser的错误
  • 遍历document,不过这个说法听起来性能很差,也许叫浏览会好一点
  • 处理document

这些就是一些纯粹的实现细节上的问题,就不再赘述了。

值得一提的是,在处理DOM parser的异常时,可能会有两种处理方式,一种是原生的try-catch,另一种是SAX风格的错误处理,通过使用setErrorHandler方法来指定错误处理的逻辑。如果大家还有印象的话,这个其实就是命令模式。它还有一个更通俗的叫法,“回调函数”。

你可能感兴趣的:(软件工程,xml,dom)