DOM 树

我们将使用此 XML 文档作为示例:

<book>
  <title>Test Book</title>
  <chapter>
    <title>Ch1</title>
    <para>p1.1</para>
    <para>p1.2</para>
    <para>p1.3</para>
  </chapter>
  <chapter>
    <title>Ch2</title>
    <para>p2.1</para>
    <para>p2.2</para>
  </chapter>
</book>

W3C DOM 将 XML 文档建模为节点树。上面 XML 的节点树可以可视化为:

document
 |
 +- element book
     |
     +- text "\n  "
     |
     +- element title
     |   |
     |   +- text "Test Book"
     |
     +- text "\n  "
     |
     +- element chapter
     |   |
     |   +- text "\n    "
     |   |
     |   +- element title
     |   |   |
     |   |   +- text "Ch1"
     |   |
     |   +- text "\n    "
     |   |
     |   +- element para
     |   |   |
     |   |   +- text "p1.1"
     |   |
     |   +- text "\n    "
     |   |
     |   +- element para
     |   |   |
     |   |   +- text "p1.2"
     |   |
     |   +- text "\n    "
     |   |
     |   +- element para
     |      |
     |      +- text "p1.3"
     |
     +- element
         |
         +- text "\n    "
         |
         +- element title
         |   |
         |   +- text "Ch2"
         |
         +- text "\n    "
         |
         +- element para
         |   |
         |   +- text "p2.1"
         |
         +- text "\n    "
         |
         +- element para
             |
             +- text "p2.2"

请注意,令人不安的"\n " -s 是换行符(此处用\n表示,FTL 字符串 Literals 中使用的转义序列)和标记之间的缩进空间。

有关 DOM 相关术语的 Comments:

  • 树的最高节点称为 root 。对于 XML 文档,它始终是“文档”节点,而不是最顶层的元素(在此示例中为book)。

  • 我们说节点 B 是节点 A 的“子代”,如果 B 是 A 的“后代”。例如,两个chapter元素节点是book元素节点的子代,但是para元素节点是不。

  • 我们说,如果 A 是节点 B 的“立即”上升点,也就是说,如果 B 是 A 的子项,则节点 A 是节点 B 的“父项”。例如,book元素节点是 B 的父项。两个chapter元素节点,但它不是para元素节点的父节点。

  • XML 文档中可能存在多种组件,例如元素,文本,Comments,处理指令等。所有此类组件都是 DOM 树中的节点,因此也有元素节点,文本节点,Comments 节点等。原则上,元素的属性也是树中的节点-它们是元素的子级-但是,通常,我们(和其他 XML 相关技术)通常将它们排除在元素子级之外。因此,基本上它们不算作子节点。

程序员将 DOM 树的文档节点放入 FreeMarker 数据模型中,然后模板作者可以使用该变量作为起点遍历 DOM 树。

FTL 中的 DOM 节点对应于 节点变量 。这是一种变量类型,类似于字符串,数字,哈希等类型。节点变量类型使 FreeMarker 可以获取节点的父节点和子节点。从技术上讲,这是允许模板作者在节点之间导航(例如,使用node built-insvisitrecurse指令)所必需的。我们将在后续章节中展示这些用法。