"xml.dom.minidom" --- 最小限の DOM の実装
*****************************************

**ソースコード:** Lib/xml/dom/minidom.py

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

"xml.dom.minidom" は、 Document Object Model インターフェースの最小の
実装です。他言語の実装と似た API を持ちます。このモジュールは、完全な
DOM に比べて単純で、非常に小さくなるように意図されています。 DOM につ
いて既に熟知しているユーザを除き、 XML 処理には代わりに
"xml.etree.ElementTree" モジュールを使うことを検討すべきです。

警告:

  "xml.dom.minidom" モジュールは悪意を持って作成されたデータに対して安
  全ではありません。信頼できないデータや認証されていないデータをパース
  する必要がある場合は XML の脆弱性 を参照してください。

DOM アプリケーションは通常、XML を DOM に解析 (parse) することで開始し
ます。 "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 パーザオブジェクトでなければなりません。この関数はパ
   ーザの文書ハンドラを変更し、名前空間サポートを有効にします; (エンテ
   ィティリゾルバ (entity resolver) のような) 他のパーザ設定は前もって
   おこなわなければなりません。

XML データを文字列で持っている場合、 "parseString()" を代わりに使うこ
とができます:

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

   *string* を表わす "Document" を返します。このメソッドは、文字列に対
   する "io.StringIO" オブジェクトを作成し、それを "parse()" に渡しま
   す。

これらの関数は両方とも、文書の内容を表現する "Document" オブジェクトを
返します。

"parse()" や "parseString()" といった関数が行うのは、 XML パーザを、何
らかの SAX パーザからくる解析イベント (parse event) を受け取って DOM
ツリーに変換できるような "DOM ビルダ (DOM builder)" に結合することです
。関数は誤解を招くような名前になっているかもしれませんが、インターフェ
ースについて学んでいるときには理解しやすいでしょう。文書の解析はこれら
の関数が戻るより前に完結します; 要するに、これらの関数自体はパーザ実装
を提供しないということです。

"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"

When you are finished with a DOM tree, you may optionally call the
"unlink()" method to encourage early cleanup of the now-unneeded
objects.  "unlink()" is an "xml.dom.minidom"-specific extension to the
DOM API that renders the node and its descendants essentially useless.
Otherwise, Python's garbage collector will eventually take care of the
objects in the tree.

参考:

  Document Object Model (DOM) Level 1 Specification
     "xml.dom.minidom" でサポートされている W3C の DOM に関する勧告。


DOM オブジェクト
================

Python の DOM API 定義は "xml.dom" モジュールドキュメントの一部として
与えられています。この節では、 "xml.dom" の API と "xml.dom.minidom"
との違いについて列挙します。

Node.unlink()

   DOM との内部的な参照を破壊して、循環参照ガベージコレクションを持た
   ないバージョンの Python でもガベージコレクションされるようにします
   。循環参照ガベージコレクションが利用できる場合でも、このメソッドを
   使えば大量のメモリをすぐに使えるようにできるため、不要になったらす
   ぐに DOM オブジェクトに対してこのメソッドを呼ぶのが良い習慣です。こ
   のメソッドは "Document" オブジェクトに対して呼び出すだけでよいので
   すが、あるノードの子ノードを破棄するために子ノードに対して呼び出し
   てもかまいません。

   "with" ステートメントを使用することで、このメソッドを明示的に呼ばな
   いようにできます。 "with" ブロックから出る時に自動的に次のコードが
   *dom* を unlink します:

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

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

   XML を *writer* オブジェクトに書き込みます。 *writer* は入力として
   テキストは受け付けますが、バイト列は受け付けません。 *writer* はフ
   ァイルオブジェクトインターフェースの "write()" に該当するメソッドを
   持たなければなりません。 *indent* 引数には現在のノードのインデント
   を指定します。 *addindent* 引数には現在のノードの下にサブノードを追
   加する際のインデント増分を指定します。 *newl* には、改行時に行末を
   終端する文字列を指定します。

   "Document" ノードでは、追加のキーワード引数 *encoding* を使って XML
   ヘッダの encoding フィールドを指定することができます。

   同様に、*standalone* 引数を明示的に指定すると、スタンドアロン文書宣
   言がXMLのプロローグに追加されます。値が "True" の場合、
   "standalone="yes"" が追加され、それ以外の場合 ""no"" が設定されます
   。引数を指定しない場合は文書から宣言が省略されます。

   バージョン 3.8 で変更: "writexml()" メソッドはユーザーが指定した属
   性の順序を保持するようになりました。

   バージョン 3.9 で変更: *standalone* パラメータが追加されました。

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

   DOM ノードによって表わされる XML を含んだ文字列またはバイト文字列を
   返します。

   明示的に *encoding* [1] 引数を渡すと、結果は指定されたエンコードの
   バイト文字列になります。*encoding* 引数なしだと、結果は unicode 文
   字列です。また、結果として生じる文字列の中の XML 宣言はエンコーディ
   ングを指定しません。XML のデフォルトエンコーディングは UTF-8 なので
   、この文字列を UTF-8 以外でエンコードすることはおそらく正しくありま
   せん。

   *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" オブジェクト上で利用可能な生成関数 (creator function) を
  使わなければなりません。派生インターフェースでは基底インターフェース
  の全ての演算 (および属性) に加え、新たな演算をサポートします。

* 演算はメソッドとして使われます。DOM では "in" パラメタのみを使うので
  、引数は通常の順番 (左から右へ) で渡されます。オプション引数はありま
  せん。  "void" 演算は "None" を返します。

* IDL 属性はインスタンス属性に対応付けられます。OMG IDL 言語における
  Python への対応付けとの互換性のために、属性 "foo" はアクセサメソッド
  "_get_foo()" および "_set_foo()" でもアクセスできます。 "readonly"
  属性は変更してはなりません; とはいえ、これは実行時には強制されません
  。

* "short int" 、 "unsigned int" 、 "unsigned long long" 、および
  "boolean" 型は、全て Python 整数オブジェクトに対応付けられます。

* "DOMString" 型は Python 文字列型に対応付けられます。
  "xml.dom.minidom" ではバイト列か文字列のどちらかに対応づけられますが
  、通常文字列を生成します。 "DOMString" 型の値は、W3C の DOM 仕様で、
  IDL "null" 値になってもよいとされている場所では "None" になることも
  あります。

* "const" 宣言を行うと、
  ("xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE" のように) 対応す
  るスコープ内の変数に対応付けを行います; これらは変更してはなりません
  。

* "DOMException" は現状では "xml.dom.minidom" でサポートされていません
  。その代わり、 "xml.dom.minidom" は、 "TypeError" や
  "AttributeError" といった標準の Python 例外を使います。

* "NodeList" オブジェクトは Python の組み込みのリスト型を使って実装さ
  れています。これらのオブジェクトは DOM 仕様で定義されたインターフェ
  ースを提供していますが、以前のバージョンの Python では、公式の API
  をサポートしていません。しかしながら、これらの API は W3C 勧告で定義
  されたインターフェースよりも "Python 的な" ものになっています。

以下のインターフェースは "xml.dom.minidom" では全く実装されていません:

* "DOMTimeStamp"

* "EntityReference"

これらの大部分は、ほとんどの DOM のユーザにとって一般的な用途として有
用とはならないような XML 文書内の情報を反映しています。

-[ 脚注 ]-

[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 を参照してください。
