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

DOM ツリーを使い終えたとき、 "unlink()" メソッドを呼び出して不要になっ
たオブジェクトが早く片付けられるように働きかけることができます。
"unlink()" は、 DOM API に対する "xml.dom.minidom"  特有の拡張で、ノー
ドとその下位ノードを本質的に無意味なものとします。このメソッドを呼び出
さなくても、 Python のガベージコレクタがいつかはツリーのオブジェクトを
後片付けします。

参考:

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