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

バージョン 2.0 で追加.

**ソースコード:** 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[, bufsize]])

   与えられた入力から "Document" を返します。 *filename_or_file* はフ
   ァイル名でもファイルオブジェクトでもかまいません。 *parser* を指定
   する場合、SAX2 パーザオブジェクトでなければなりません。この関数はパ
   ーザの文書ハンドラを変更し、名前空間サポートを有効にします; (エンテ
   ィティリゾルバ (entity resolver) のような) 他のパーザ設定は前もって
   おこなわなければなりません。

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

xml.dom.minidom.parseString(string[, parser])

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

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

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

"DOM 実装" オブジェクトのメソッドを呼び出して "Document" を生成するこ
ともできます。このオブジェクトは、 "xml.dom" パッケージ、または
"xml.dom.minidom" モジュールの "getDOMImplementation()" 関数を呼び出し
て取得できます。 "xml.dom.minidom" モジュールの実装を使うと、常に
minidom 実装の "Document" インスタンスを返します。一方、 "xml.dom" 版
の関数では、別の実装によるインスタンスを返すかもれません (PyXML
package がインストールされているとそうなるでしょう)。 "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"  特有の拡張です。ノ
ードに対して "unlink()" を呼び出した後は、ノードとその下位ノードは本質
的には無意味なものとなります。このメソッドを呼び出さなくても、 Python
のガベージコレクタがいつかはツリーのオブジェクトを後片付けします。

参考:

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


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

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

Node.unlink()

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

Node.writexml(writer, indent="", addindent="", newl="")

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

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

   バージョン 2.1 で変更: 美しい出力をサポートするため、新たなキーワー
   ド引数 *indent* 、 *addindent* 、および *newl* が追加されました.

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

Node.toxml([encoding])

   DOM が表現している XML を文字列にして返します。

   引数がなければ、 XML ヘッダは encoding を指定せず、文書内の全ての文
   字をデフォルトエンコード方式で表示できない場合、結果は Unicode 文字
   列となります。この文字列を UTF-8 以外のエンコード方式でエンコードす
   るのは不正であり、なぜなら UTF-8 が XML のデフォルトエンコード方式
   だからです。

   明示的な *encoding* [1] 引数があると、結果は指定されたエンコード方
   式によるバイト文字列となります。引数を常に指定するよう推奨します。
   表現不可能なテキストデータの場合に "UnicodeError" が送出されるのを
   避けるため、encoding 引数は "utf-8" に指定するべきです。

   バージョン 2.3 で変更: *encoding* が追加されました。 "writexml()"
   を参照して下さい。

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

   文書の整形されたバージョンを返します。 *indent* はインデントを行う
   ための文字で、デフォルトはタブです; *newl* には行末で出力される文字
   列を指定し、デフォルトは "\n" です。

   バージョン 2.1 で追加.

   バージョン 2.3 で変更: encoding 引数が追加されました。 "writexml()"
   を参照して下さい。

以下の標準 DOM メソッドは、 "xml.dom.minidom" では特別な注意をする必要
があります:

Node.cloneNode(deep)

   このメソッドは Python 2.0 にパッケージされているバージョンの
   "xml.dom.minidom" にはありましたが、これには深刻な障害があります。
   以降のリリースでは修正されています。


19.9.2. 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 "<title>%s</title>" % getText(title.childNodes)

   def handleSlideTitle(title):
       print "<h2>%s</h2>" % getText(title.childNodes)

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

   def handlePoint(point):
       print "<li>%s</li>" % getText(point.childNodes)

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

   handleSlideshow(dom)


19.9.3. 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" ではバイト文字列 (byte string) および Unicode 文字
  列のどちらかに対応づけられますが、通常 Unicode 文字列を生成します。
  "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 の組み込みリスト型を使って実装さ
  れ ています。 Python 2.2 からは、これらのオブジェクトは DOM 仕様で定
  義 されたインタフェースを提供していますが、それ以前のバージョンの
  Python では、公式の API をサポートしていません。しかしながら、これら
  の API は W3C 勧告で定義されたインタフェースよりも "Python 的な" も
  のになっています。

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

* "DOMTimeStamp"

* "DocumentType" (added in Python 2.1)

* "DOMImplementation" (added in Python 2.1)

* "CharacterData"

* "CDATASection"

* "Notation"

* "Entity"

* "EntityReference"

* "DocumentFragment"

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

-[ 注記 ]-

[1] XML 出力に含まれるエンコーディング文字列は適切な規格に従ってい
    なけ ればなりません。例えば、 "UTF-8" は有効ですが、 "UTF8" はそう
    では ありません。https://www.w3.org/TR/2006/REC-
    xml11-20060816/#NT- EncodingDecl と
    https://www.iana.org/assignments/character-sets /character-
    sets.xhtml を参照してください。
