"xml.dom.minidom" --- 最小化的 DOM 实现
***************************************

**源代码:** Lib/xml/dom/minidom.py

======================================================================

"xml.dom.minidom" 是文档对象模型接口的最小化实现，具有与其他语言类似的
API。 它的目标是比完整 DOM 更简单并且更为小巧。 对于 DOM 还不十分熟悉
的用户则应当考虑改用 "xml.etree.ElementTree" 模块来进行 XML 处理。

警告:

  "xml.dom.minidom" 模块对于恶意构建的数据是不安全的。 如果你需要解析
  不受信任或未经身份验证的数据，请参阅 XML 漏洞。

DOM 应用程序通常会从将某个 XML 解析为 DOM 开始。 使用
"xml.dom.minidom" 时，这是通过各种解析函数来完成的:

   from xml.dom.minidom import parse, parseString

   dom1 = parse('c:\\temp\\mydata.xml')  # parse an XML file by name

   datasource = open('c:\\temp\\mydata.xml')
   dom2 = parse(datasource)  # parse an open file

   dom3 = parseString('<myxml>Some data<empty/> some more data</myxml>')

"parse()" 函数可接受一个文件名或者打开的文件对象。

xml.dom.minidom.parse(filename_or_file, parser=None, bufsize=None)

   根据给定的输入返回一个 "Document"。 *filename_or_file* 可以是一个文
   件名，或是一个文件类对象。 如果给定 *parser* 则它必须是一个 SAX2 解
   析器对象。 此函数将修改解析器的处理程序并激活命名空间支持；其他解析
   器配置（例如设置一个实体求解器）必须已经提前完成。

如果你将 XML 存放为字符串形式，则可以改用 "parseString()" 函数:

xml.dom.minidom.parseString(string, parser=None)

   返回一个代表 *string* 的 "Document"。 此方法会为指定字符串创建一个
   "io.StringIO" 对象并将其传递给 "parse()"。

两个函数均返回一个代表文档内容的 "Document" 对象。object representing
the content of the document.

"parse()" 和 "parseString()" 函数所做的是将 XML 解析器连接到一个 "DOM
构建器"，它可以从任意 SAX 解析器接收解析事件并将其转换为 DOM 树结构。
这两个函数的名称可能有些误导性，但在学习此接口时是很容易掌握的。 文档
解析操作将在这两个函数返回之前完成；简单地说这两个函数本身并不提供解析
器实现。

你也可以通过在一个 "DOM 实现" 对象上调用方法来创建 "Document"。 此对象
可通过调用 "xml.dom" 包或者 "xml.dom.minidom" 模块中的
"getDOMImplementation()" 函数来获取。 一旦你获得了一个 "Document"，你
就可以向它添加子节点来填充 DOM:

   from xml.dom.minidom import getDOMImplementation

   impl = getDOMImplementation()

   newdoc = impl.createDocument(None, "some_tag", None)
   top_element = newdoc.documentElement
   text = newdoc.createTextNode('Some textual content.')
   top_element.appendChild(text)

一旦你得到了 DOM 文档对象，你就可以通过其属性和方法访问对应 XML 文档的
各个部分。 这些属性定义在 DOM 规格说明当中；文档对象的主要特征属性是
"documentElement"。 它给出了 XML 文档中的主元素：即包含了所有其他元素
的元素。 以下是一个示例程序:

   dom3 = parseString("<myxml>Some data</myxml>")
   assert dom3.documentElement.tagName == "myxml"

当你完成对一个 DOM 树的处理时，你可以选择调用 "unlink()" 方法来鼓励尽
早清除已不再需要的对象。 "unlink()" 是针对 DOM API 的
"xml.dom.minidom" 专属扩展，它会将特定节点及其下级标记为不再有用。 在
其他情况下，Python 的垃圾回收器将负责最终处理树结构中的对象。

参见:

  文档对象模型 (DOM) 第 1 层级规格说明
     被 "xml.dom.minidom" 所支持的 W3C 针对 DOM 的建议。


DOM 对象
========

Python 的 DOM API 定义被作为 "xml.dom" 模块文档的一部分给出。  这一节
列出了该 API 和 "xml.dom.minidom" 之间的差异。

Node.unlink()

   破坏 DOM 的内部引用以便它能在没有循环 GC 的 Python 版本上垃圾回收器
   回收。 即使在循环 GC 可用的时候，使用此方法也可让大量内存更快变为可
   用，因此当 DOM 对象不再被需要时尽早调用它们的这个方法是很好的做法。
   此方法只须在 "Document" 对象上调用，但也可以在下级节点上调用以丢弃
   该节点的下级节点。

   你可以通过使用 "with" 语句来避免显式调用此方法。 以下代码会在
   "with" 代码块退出时自动取消链接 *dom*:

      with xml.dom.minidom.parse(datasource) as dom:
          ... # Work with dom.

Node.writexml(writer, indent='', addindent='', newl='', encoding=None, standalone=None)

   将 XML 写入到写入器对象。 写入器接受文本而非字节串作为输入，它应当
   具有与文件对象接口相匹配的 "write()" 方法。 *indent* 形参是当前节点
   的缩进层级。 *addindent* 形参是用于当前节点的下级节点的缩进量。
   *newl* 形参指定用于一行结束的字符串。

   对于 "Document" 节点，可以使用附加的关键字参数 *encoding* 来指定
   XML 标头的编码格式字段。

   类似地，显式指明 *standalone* 参数将会使单独的文档声明被添加到 XML
   文档的开头部分。 如果将该值设为 "True"，则会添加 "standalone="yes""
   ，否则它会被设为 ""no""。 未指明该参数将使文档声明被省略。

   在 3.8 版更改: "writexml()" 方法现在会保留用户指定的属性顺序。

   在 3.9 版更改: 增加了 *standalone* 形参。

Node.toxml(encoding=None, standalone=None)

   返回一个包含 XML DOM 节点所代表的 XML 的字符串或字节串。

   带有显式的 *encoding* [1] 参数时，结果为使用指定编码格式的字节串。
   没有 *encoding* 参数时，结果为 Unicode 字符串，并且结果字符串中的
   XML 声明将不指定编码格式。 使用 UTF-8 以外的编码格式对此字符串进行
   编码通常是不正确的，因为 UTF-8 是 XML 的默认编码格式。

   *standalone* 参数的行为与 "writexml()" 中的完全一致。

   在 3.8 版更改: "toxml()" 方法现在会保留用户指定的属性顺序。

   在 3.9 版更改: 增加了 *standalone* 形参。

Node.toprettyxml(indent='\t', newl='\n', encoding=None, standalone=None)

   返回文档的美化打印版本。 *indent* 指定缩进字符串并默认为制表符；
   *newl* 指定标示每行结束的字符串并默认为 "\n"。

   *encoding* 参数的行为类似于 "toxml()" 的对应参数。

   *standalone* 参数的行为与 "writexml()" 中的完全一致。

   在 3.8 版更改: "toprettyxml()" 方法现在会保留用户指定的属性顺序。

   在 3.9 版更改: 增加了 *standalone* 形参。


DOM 示例
========

此示例程序是个相当实际的简单程序示例。 在这个特定情况中，我们没有过多
地利用 DOM 的灵活性。

   import xml.dom.minidom

   document = """\
   <slideshow>
   <title>Demo slideshow</title>
   <slide><title>Slide title</title>
   <point>This is a demo</point>
   <point>Of a program for processing slides</point>
   </slide>

   <slide><title>Another demo slide</title>
   <point>It is important</point>
   <point>To have more than</point>
   <point>one slide</point>
   </slide>
   </slideshow>
   """

   dom = xml.dom.minidom.parseString(document)

   def getText(nodelist):
       rc = []
       for node in nodelist:
           if node.nodeType == node.TEXT_NODE:
               rc.append(node.data)
       return ''.join(rc)

   def handleSlideshow(slideshow):
       print("<html>")
       handleSlideshowTitle(slideshow.getElementsByTagName("title")[0])
       slides = slideshow.getElementsByTagName("slide")
       handleToc(slides)
       handleSlides(slides)
       print("</html>")

   def handleSlides(slides):
       for slide in slides:
           handleSlide(slide)

   def handleSlide(slide):
       handleSlideTitle(slide.getElementsByTagName("title")[0])
       handlePoints(slide.getElementsByTagName("point"))

   def handleSlideshowTitle(title):
       print(f"<title>{getText(title.childNodes)}</title>")

   def handleSlideTitle(title):
       print(f"<h2>{getText(title.childNodes)}</h2>")

   def handlePoints(points):
       print("<ul>")
       for point in points:
           handlePoint(point)
       print("</ul>")

   def handlePoint(point):
       print(f"<li>{getText(point.childNodes)}</li>")

   def handleToc(slides):
       for slide in slides:
           title = slide.getElementsByTagName("title")[0]
           print(f"<p>{getText(title.childNodes)}</p>")

   handleSlideshow(dom)


minidom 和 DOM 标准
===================

"xml.dom.minidom" 模块实际上是兼容 DOM 1.0 的 DOM 并带有部分 DOM 2 特
性（主要是命名空间特性）。

Python 中 DOM 接口的用法十分直观。 会应用下列映射规则:

* 接口是通过实例对象来访问的。 应用程序不应实例化这些类本身；它们应当
  使用 "Document" 对象提供的创建器函数。 派生的接口支持上级接口的所有
  操作（和属性），并添加了新的操作。

* 操作以方法的形式使用。 因由 DOM 只使用 "in" 形参，参数是以正常顺序传
  入的（从左至右）。 不存在可选参数。 "void" 操作返回 "None"。

* IDL 属性会映射到实例属性。 为了兼容针对 Python 的 OMG IDL 语言映射，
  属性 "foo" 也可通过访问器方法 "_get_foo()" 和 "_set_foo()" 来访问。
  "readonly" 属性不可被修改；运行时并不强制要求这一点。

* "short int", "unsigned int", "unsigned long long" 和 "boolean" 类型
  都会映射为 Python 整数类型。

* "DOMString" 类型会映射为 Python 字符串。 "xml.dom.minidom" 支持字节
  串或字符串，但通常是产生字符串。 "DOMString" 类型的值也可以为 "None"
  ，W3C 的 DOM 规格说明允许其具有 IDL "null" 值。

* "const" 声明会映射为它们各自的作用域内的变量 (例如
  "xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE")；它们不可被修改
  。

* "DOMException" 目前不被 "xml.dom.minidom" 所支持。 "xml.dom.minidom"
  会改为使用标准 Python 异常例如 "TypeError" 和 "AttributeError"。

* "NodeList" 对象是使用 Python 内置列表类型来实现的。 这些对象提供了
  DOM 规格说明中定义的接口，但在较早版本的 Python 中它们不支持官方 API
  。 相比在 W3C 建议中定义的接口，它们要更加的 "Pythonic"。

下列接口未在 "xml.dom.minidom" 中实现:

* "DOMTimeStamp"

* "EntityReference"

这些接口所反映的 XML 文档信息对于大多数 DOM 用户来说没有什么帮助。

-[ 备注 ]-

[1] 包括在 XML 输出中的编码格式名称应当遵循适当的标准。 例如，"UTF-8"
    是有效的，但 "UTF8" 在 XML 文档的声明中是无效的，即使 Python 接受
    其作为编码格式名称。 详情参见 https://www.w3.org/TR/2006/REC-
    xml11-20060816/#NT-EncodingDecl 和
    https://www.iana.org/assignments/character-sets/character-
    sets.xhtml。
