Amara XML ToolsはXMLデータバインディングのためのPythonツールコレクションである。Pythonで書かれたのは単なる気まぐれではなく、ゼロから徹底的にPythonのイディオムが使われ、また他のプログラミング言語に対するPythonの多くのアドバンテージが利用されている。
Amaraは4Suiteに基盤としている。しかし4SuiteがよりXML標準の従順な実装を目指しているのに対して、Amaraは4Suiteの力にPython風のインタフェイスを追加するものである。
Amaraの主要な構成要素:
他の構成要素:
使い始めるもっとも簡単な方法は、EasyInstallを使って、Amaraとその依存関係にあるパッケージ全てを一気にインストールしてしまうことだ。easy_install amaraとタイプするだけで全て設定される。実行時トラブルがあるなら、EasyInstallを持っていないからだろうが、これはとても簡単にセットアップされる。それでも問題がある場合、メーリングリストで彼らに報告してほしい。
次の例は、シンプルなXMLファイル monty.xml、からバインディングを作成する方法を示す。(取り上げられている全ての例示用ファイルは、Amaraパッケージのdemoディレクトリに置かれている。)
まず、monty.xmlの内容は次のようになっている:
<?xml version="1.0" encoding="utf-8"?> <monty> <python spam="eggs"> What do you mean "bleh" </python> <python ministry="abuse"> But I was looking for argument </python> </monty>
ここで、バインディングを作成するコードは次のようになる。
import amara doc = amara.parse('monty.xml')
doc
はデータバインディングの結果であり、XMLを表現するオブジェクトである。XML文書全体をバインダーに与えると、トップレベル要素を表現したメンバーを持つ文書、それそのものを表現したオブジェクトを得ることができた。
それは簡単なものだ。"eggs"値を(Python Unicodeオブジェクトとして)得るには、次のように書ける:
doc.monty.python.spam
あるいは"But I was looking for argument"という内容をもった要素を得るには、こう書ける:
doc.monty.python[1]
parse
関数には、file-likeオブジェクト(stream)も、文字列も渡すことができる。ローカルファイルパスやURLからXMLを取得したXMLをパースするためのparse_path
関数もある(※)。
※ 訳注: 手持ちのamara version 1.2.0.2では、amara.parse_path
関数がなく、amara.parse
関数に統一されているようだ。そして、amara.binderytools
モジュールにおいて、引数のタイプ別にbind_file
関数, bind_stream
関数, bind_string
関数, bind_uri
関数の4つに細分化されている。
※訳注: amara.parse
の引数には、(Unicodeオブジェクトではない)XML文字列、open-file-likeオブジェクト、ファイルパス、URIを与えることができる。
parse_path
関数にファイルパスを与えるとFt.Lib.UriException
が投げられる場合がある。bind_file
関数ならこの問題は起こらないようだ。
以下の例はXBELファイル(Python専用のXML-SIGが開発したブックマークのための人気のXMLフォーマット)よりバインディングを作成する方法を示す:
doc = amara.parse('xbel.xml')
次のサンプルは最初と二番目のブックマークタイトルをprintしている。
print doc.xbel.folder.bookmark.title print doc.xbel.folder.bookmark[1].title
このバインダリはできるだけ自然な感じに扱おうと試みるのが特徴だ。通常、子要素にアクセスするにはただそれらの名前を使えば良い。一つ以上の同じ名前のものがあった場合、最初のものをバインドする。list index(list[num]
)を用いて、同じ名前をもった複数の子要素の一つを特定することができる。本来、より明示的な方法で、最初のブックマークのタイトルを得る方法は、次のようになる:
print doc.xbel.folder.bookmark[0].title
titleはテキストのみを含んだ要素である。表現doc.xbel.folder.bookmark[0].title
はこの要素を表現したバインディングオブジェクトを返す。そのようなオブジェクトは、Unicode変換メソッドを持っているため、それらの要素の子孫テキスト(その要素のサブツリーのすべてのテキストノード)を得ることができる。上記のコードラインはそれらをprintする。次の4つのコードラインは、全て等価である:
print doc.xbel.folder.bookmark.title print doc.xbel.folder.bookmark[0].title print unicode(doc.xbel.folder.bookmark.title) print unicode(doc.xbel.folder.bookmark[0].title)
Amaraにおいて、unicode変換をelementノードでcallするのは、XPathにおいて、string関数をelementノードでcallするのに非常に似ている。
次のコード片は、ファイル内のすべてのブックマークURLをプリントアウトする再帰関数である。
def all_titles_in_folder(folder): #Warning: folder.bookmark will raise an AttributeError if there are no bookmarks for bookmark in folder.bookmark: print bookmark.href if hasattr(folder, "folder"): #There are sub-folders for folder in folder.folder: all_titles_in_folder(folder) return for folder in doc.xbel.folder: all_titles_in_folder(folder)
しかし、実際の使用では恐らくこういうことはしないだろう。このタスクはXPathを使って、より小さなコードで、再帰を用いず、そしてより速い方法で実現できる。これは後述のセクションでカバーする。
同名の要素の数をカウントしたいなら、len
を使う:
len(doc.xbel.folder)
これでトップレベルフォルダの数を得ることができる。
amara.parse()
において、validate=True
を設定することで、基礎的なパーサーにDTDによる検証を行わせることができる。
デフォルトでは、バインドされたXML要素は専用オブジェクトとなる。つまりそれぞれの一般識別子(要素名)に対して、bindery.element_base
を継承したPythonクラスが作られる。一方属性は、単純なデータメンバ、つまり属性値をもったUnicodeオブジェクト値となる。
要素オブジェクトは単一オブジェクトとして扱えるようにするために特別に構築されている。(そのケースでは対応した名前の最初の子要素が選択されるが、リストアイテムアクセスすることも、イテレートすることもできる)
例に戻ろう。binding.xbel.folder.bookmark
は、binding.xbel.folder.bookmark[0]
と同じである。どちらも最初のフォルダの最初のブックマークを返す。最初のフォルダの二番目のブックマークを得るには、binding.xbel.folder.bookmark[1]
を使う。
バインダリは、ソースXMLの順番に関する情報を初期より保持していて、文書オブジェクトと子要素リストを通じてこれにアクセス可能だ:
folder.xml_children
子要素リストにおいて、子要素はそれぞれに対応するバインディングオブジェクトを使って、子テキストは単純にUnicodeオブジェクトなって表現される。バインドされた子テキストはデフォルトで正規化される、つまりバインディングはxml_children
において隣り合う二つのテキストノードには一切置き換わらないことに注意すべきだ。
要素ノードが子要素とテキストを内容としていた場合に、どうやってテキストノードにアクセスするのだろうか。要素の子テキストを得るにはxml_child_text
プロパティを使う。文書、<a>1<b>2</b>3<c/></a>
において、a.xml_child_text
はu'13'
を返す。一方、Unicode変換(unicode(a)
)はu'123'
を返す。
xml_doc
メソッドをcallすることで、最もバインダリなオブジェクトの構造についての情報を得ることができる。
>>> import amara >>> doc = amara.parse('monty.xml') >>> print doc.xml_doc() Object references based on XML child elements: monty (1 element) based on 'monty' in XML >>> print doc.monty.xml_doc() Object references based on XML child elements: python (2 elements) based on 'python' in XML >>> print doc.monty.python.xml_doc() Object references based on XML attributes: spam based on 'spam' in XML Object references based on XML child elements: >>> print doc.monty.python.spam eggs
これは「人が読むための」便利なツールである。しかし「計算機が読むための」形式をしたノードの構造情報も得たいだろう。xml_property
はXMLの属性と要素のオブジェクト参照名を表現するkeyをもった辞書を返す。
>>> import amara >>> doc = amara.parse("<a x='1'>hello<b/>lovely<c/>world</a>") >>> doc.a.xml_properties {u'x': u'1', u'c': <amara.bindery.c object at 0xb7bbcdcc>, u'b': <amara.bindery.b object at 0xb7bbcb8c>}
xml_child_elements
は似たような辞書を返すが、子要素だけに絞られている。
>>> #Continuing from the above snippet >>> doc.a.xml_child_elements {u'c': <amara.bindery.c object at 0xb7bbcdcc>, u'b': <amara.bindery.b object at 0xb7bbcb8c>}
文書順に関する情報の保持は、バインダリがバインディングオブジェクトをXMLフォームに戻すために必要であり、その為にはxml
メソッドを使う。
print doc.xml()
xml
メソッドは符号化されたテキストを返す。Unicodeではない。デフォルトの符号化方式はUTF-8である。文書の一部分をシリアライズすることもできる。
print doc.xbel.folder.xml() #Just the first folder
streamに出力を渡すこともできる。
doc.xml(sys.stdout)
出力する符号化方式を制御することもできる。綺麗にプリントしたり、XML宣言を出力したり等、XSLTのxsl:outputと同等の出力制御パラメータを用いることができる。たとえば、omitXmldeclaration=u"yes"
を与えたなら、XML宣言は出力されない。その他の設定可能なパラメタを示す:
Amaraに(ルートノードや)要素で、関連付けられていようといまいと、名前空間宣言を強制することができる。これは要素の内容においてQNameを使う際に特に便利である(隠れた名前空間)。
doc.xml(force_nsdecls={u'x': u'http://example.com/'})
force_nsdecls
はkeyにUnicodeの接頭辞、valueに名前空間URIをもった辞書である。これはたとえば、知られている全ての名前空間宣言をトップに強制する("分別のある"文書)のを簡単にする:
doc.xml(force_nsdecls=doc.prefixes)
訳注:1.2.0.2でprefixesプロパティは存在しないが……
警告:文書中の名前空間宣言は、上記のような方法で強制されたそれに常に打ち勝つ。これはデフォルト名前空間をnullにする暗黙的な宣言を含む。言い換えると、XML名前空間を使っていない文書を、前述のような簡単な行程でデフォルト名前空間を持つ文書に変化させることはできない。
バインダリはXPathのサブセットをサポートしており、これはほとんど標準に準拠している。サポートされていないXPathの特長は非常にレアなものであり、実際に使用する場面においては、完全なサポートであるとみなしても良いだろう。次の例は全てのトップレベルフォルダを取得する:
tl_folders = doc.xbel.xml_xpath(u'folder') for folder in tl_folders: print folder.title
XPathクエリのコンテクストにしたいオブジェクトのxml_xpath
メソッドを呼び出している。最初のフォルダの最初のブックマークの最初の子要素を(要素名を指定せず)得るには、このようにするか:
oc.xbel.folder.bookmark.xml_xpath(u'*[1]')
あるいは:
doc.xbel.xml_xpath(u'folder[1]/bookmark[1]/*[1]')
あるいは:
doc.xbel.xml_xpath(u'/folder[1]/bookmark[1]/*[1]')
あるいは:
doc.xml_xpath(u'xbel/folder[1]/bookmark[1]/*[1]')
等のようにする。
警告:Pythonでは、リストのインデックスは0から始まるが、XPathでは1から始まる。
訳注:XPathのそれはリストのインデックスではない。[position() = index]の省略形に過ぎない。
注意:このXPathはノード集合を返し、Pythonではノードのリストとして扱われる。一つのノードを含むリストということになるが、しかし[0]を使って抽出しなければならない。
戻り値はXPath表現(expr)に依存する。
次の例はファイル内の全てのブックマークURLをプリントアウトする。しかしもっとシンプルでより簡潔なコードを前述している。
bookmarks = doc.xml_xpath(u'//bookmark') for bookmark in bookmarks: print bookmark.href
次は、attributeクエリを使って、単に文書内に登場する全てのhrefを返す:
hrefs = doc.xml_xpath(u'//@href') for href in hrefs: print unicode(href)
次は4Suiteプロジェクトへのブックマークのタイトルをプリントする:
url = u"http://4suite.org/" title_elements = doc.xml_xpath('//bookmark[@url="%s"]/title'%url) #XPath node set expression always returns a list print unicode(title_elements[0])
バインダリ文書または要素に対して、直接XSLTを適用することができる。もちろんxml()
してシリアライズしたものをXSLTに適用することもできるが、恐らくノードに対して直接行ったほうが簡単だし効果的だ。これを行うには、xml_xslt()
を使う。
警告: この関数はかなり制限されており、最も簡単な変換しか使わない。この方法で変換が動作しない場合、xml()
でシリアライズしてから、Ft.Xml.Xslt.transform
を使うこと。これは完全なXSLT適合である。
result_string = doc.xml_xslt('transform.xslt')
必要なただ一つの引数は、XSLTスタイルシートへの参照である。これは(Unicodeでない)文字列、file-likeオブジェクト(stream)、ファイルパス、URIまたはFt.Xml.InputSource.InputSource
インスタンスのいずれでもよい。文字列かstreamの場合、それはself-contained XMLでなければならない。すなわち、外部実体参照やinclude等の他のリソースへのアクセスを要求してはならない。
オプションのparam
引数にはスタイルシートパラメタからなる辞書を指定できる。このkeyは、名前空間を持っていないならUnicodeオブジェクトで、持っている場合(uri, localname)
のtupleで与えることができる。
オプションのoutput
引数は、出力が(行程とともに徐々に)書き出されるfile-likeオブジェクトである。
次の例を見てみよう:
params = params={u'id' : u'2', (u'http://example.com/ns', u'tags') : [u'a', u'b', u'c']} result_string = doc.xml_xslt('transform.xslt', params=params)
これはid
という(接頭辞のない)トップレベルパラメタと、xがhttp://example.com/ns
の接頭辞、x
をもつx:tags
というトップレベルパラメタを渡したXSLTを実行する。後者はテキストノードのノード集合をパラメタとして提供している。
doc.xml_xslt(TRANSFORM_STRING, output=open('/tmp/foo.txt', 'w'))
これは文字列として与えられたXSLTを実行し、ファイル/tmp/foo.txt
に出力ストリームを吐く(XSLT出力は、テキスト、XMLまたはHTMLになり得る点に留意されたい)。このようにoutput
が明示されている場合、戻り値はない。
result_string = doc.xml_xslt(open('transform.xslt'))
これはXSLTファイルをopen fileオブジェクトから読み込む。
バインダリは名前空間をもった文書に対応している。次の例はRSS1.0フィードの中身の概要を表示する:
import amara #Set up customary namespace bindings for RSS #These are used in XPath query and XPattern rules RSS10_NSS = { u'rdf': u'http://www.w3.org/1999/02/22-rdf-syntax-ns#', u'dc': u'http://purl.org/dc/elements/1.1/', u'rss': u'http://purl.org/rss/1.0/', } doc = amara.parse('rss10.rdf', prefixes=RSS10_NSS) #Create a dictionary of RSS items items = {} item_list = doc.xml_xpath(u'//rss:item') items = dict( [ ( item.about, item) for item in item_list ] ) print items for channel in doc.RDF.channel: print "Channel:", channel.about print "Title:", channel.title print "Items:" for item_ref in channel.items.Seq.li: item = items[item_ref.resource] print "\t", item.link print "\t", item.title
次はバインダリオブジェクトにおいて名前空間の構成要素がどのように保持されるかを示している。
#Show the namespace particulars of the rdf:RDF element print doc.RDF.namespaceURI print doc.RDF.localName print doc.RDF.prefix
属性については、xml_attributes
が、その要素の全ての属性の名前空間の情報を含んだ辞書である。詳細に関しては以下のAttributesの章を参照されたい。
名前空間はXPathで自然に機能する:
#Get the RSS item with a given URL item_url = u'http://www.oreillynet.com/cs/weblog/view/wlg/532' matching_items = doc.RDF.xml_xpath(u'//rss:item[@rdf:about="%s"]'%item_url) print matching_items assert matching_items[0].about == item_url
上の例では名前空間接頭辞と名前空間名のマッピングを手動でセットしてある。しかし、XML名前空間が行儀よく使用されるなら、このステップを踏む必要はないだろう。バインダリのdocumentオブジェクトはパースした文書であれば、トップレベル要素に作られた名前空間宣言を自動で記憶する。
amaraバインディングで使用されるPythonオブジェクトの参照及びクラス名は、対応したXML IDに基づいて作られている。しかしそのようなマッピングには制限がある。その一例として、XMLはPythonのIDで許されない、-(ダッシュ)のような文字をもを許してしまう。このようなケースでは、Amaraは名前を壊し、デフォルトではアンダースコアを使用する。<a-1 b-1="" />
のような文書のケースでは、doc.a_1
を使って要素にアクセスでき、またdoc.a_1.b_1
を使って属性にアクセスできる。
これはXMLを照会する一般的なコードを書く際に問題をはらんでいる。何故なら文書のコンテクストによっては異なる方法で要素名が壊されるかもしれないからである。正しいXMLの名前を使ったオブジェクトへのアクセスは、XPathを用いればいつでも可能だ:
doc.xml_xpath(u"a-1/b-1")
またPythonのマッピングプロトコルを使ってオブジェクトにアクセスすることもできる:
doc[u'a-1'][u'b-1']
これは次と等価である:
doc[None, u'a-1'][None, u'b-1']
このNone
は、名前空間名だが、望まれるオブジェクトが実際にオリジナルの文書における名前空間にあるなら、もちろんUnicodeオブジェクトとして明示すべきである。Amaraはかなりのレベルで曖昧性の除去を行う:
from xml.dom import Node E = Node.ELEMENT_NODE doc[E, None, u'a-1'][E, None, u'b-1']
これは属性の名前がそのownerの子要素の名前と衝突するという、非常にまれなシチュエーションに対応する。たとえば、<a-1 b-1=""><b-1/></a-1>
では上記のようにしてb-1
要素にアクセス可能であり、同じ名前の属性には、A = Node.ATTRIBUTE_NODE
を済ませているならdoc[E, None, u'a-1'][A, None, u'b-1']
でアクセスできる。
オリジナルのXMLの名前に関する情報は、DOM由来のプロパティを使って得ることができる。
element.nodeName
element.localName
element.namespaceURI
element.prefix
そして属性に関する似たような情報を得るには、elementノードのxml_attribute
の辞書を利用できる。
巨大なXMLファイルを扱っている場合、同時に全てのデータバインディングをメモリに保持したいとは思わないかも知れない。少しずつインスタンス化したいはずだ。もし文書をどのように解体したいのかについて明確なパターンをつかんでいるなら、amara.pushbind
関数を使うことができる。次の例を見てみよう:
import amara for folder in amara.pushbind('xbel.xml', u'/xbel/folder'): title = folder.title bm_count = len(list(folder.bookmark)) print "Folder", title, "has", bm_count, "top level bookmarks"
すばらしいのは、このプログラムが一時にバインディング全体をメモリに保持しない点である。それぞれのイテレーションにおいて、これはただ各トップレベルのfolder要素を表現するのに必要なだけのXMLをロードする。pushbind
は、呼び出される毎に、それぞれ小さなサブツリーをyieldするジェネレータである。
一般的に、pushbind
のサブツリーは、その最初の引数(※)として与えられたXSLTパターン(訳注:XPath patternのことらしい)に基づいたamaraバインダリオブジェクトである。XMLソース、文字列(string = keyword
を使う)、URIまたはfile名(source = keyword
を使う)を渡すこともできる。
※訳注:上の例では二番目の引数に与えているように見えるのだが
インタラクティブなセッション:
>>> XML="""\ ... <doc> ... <one><a>0</a><a>1</a></one> ... <two><a>10</a><a>11</a></two> ... </doc> ... """ >>> import amara >>> chunks = amara.pushbind(XML, u'a') >>> a = chunks.next() >>> print a 0 >>> print a.xml() <a>0</a> >>> a = chunks.next() >>> print a.xml() <a>1</a> >>> a = chunks.next() >>> print a.xml() <a>10</a> >>> a = chunks.next() >>> print a.xml() <a>11</a> >>> a = chunks.next() Traceback (most recent call last): File "<stdin>", line 1, in ? StopIteration
上の例において、XPath pattern "a
"はファイル内のこの名前を持つ要素全てにマッチする。このコードにおいては、"a"要素の「外側の」XMLは原則的に無視されるので、その他の関心のある全ての部分を含めるにはXPath patternを調整する必要が出てくる。次の例では、最初の二つの"a"要素だけが、ジェネレータのyieldに含まれている。
>>> chunks = amara.pushbind(XML, u'one/a') >>> a = chunks.next() >>> print a.xml() <a>0</a> >>> a = chunks.next() >>> print a.xml() <a>1</a> >>> a = chunks.next() Traceback (most recent call last): File "<stdin>", line 1, in ? StopIteration
/doc/one/a
というパターンも、サンプル文書について似た結果をもたらすだろう。
これらのパターンに名前空間を使うには、pushbind
関数に接頭辞を定義した辞書を渡せば良い。
import amara #Set up customary namespace bindings for RSS #These are used in XPath query and XPattern rules RSS10_NSS = { u'rdf': u'http://www.w3.org/1999/02/22-rdf-syntax-ns#', u'dc': u'http://purl.org/dc/elements/1.1/', u'rss': u'http://purl.org/rss/1.0/', } #Print out all titles in the RSS feed items = amara.pushbind('demo/rss10.rdf', u'rss:item', prefixes=RSS10_NSS) for item in items: print item.title #Print out all titles in the RSS feed, slightly different approach chunks = amara.pushbind('demo/rss10.rdf', u'rss:item/rss:title', prefixes=RSS10_NSS) for title in chunks: print title
バインディングに変更を加えるのはとても容易である。属性値の変更は、簡単な代入でよい。
import amara doc = amara.parse('monty.xml') doc.monty.foo = u'bar' doc.monty.spam = u'[attr modified]'
テキストノードを一つだけもった要素の内容も、似たような書き方で置き換えられる:
doc.monty.python = u'[elem 1 modified]\n' doc.monty.python[1] = u'[elem 2 modified]\n'
上記のコードの結果はこうなる:
<monty> <python spam="[attr modified]" foo="bar">[elem 1 modified] </python> <python ministry="abuse">[elem 2 modified] </python> </monty>
要素のすべての子を空にするにはこう:
doc.monty.python.xml_clear()
新たな要素やテキストを加えよう。newという名前の新しい空要素を作成し、それを最初のpython要素の最後の子供として追加してみる:
doc.monty.python.xml_append(doc.xml_create_element(u'new'))
テキストノードを追加する。
doc.monty.python.new.append(u'New Content')
訳注: これは恐らく次のtypo:
doc.monty.python.new.xml_append(u'New Content')
存在する子ノードの前後といった特定の位置に新しい要素やテキストを挿入することもできる。
#Create a new `python` element as the second element child of `monty` doc.monty.xml_insert_after(doc.monty.python, doc.xml_create_element(u'python')) #Create a new `python` element as the first element child of `monty` doc.monty.xml_insert_before(doc.monty.python, doc.xml_create_element(u'python'))
特定の要素、属性、テキストノードを削除することもできる。
del doc.monty.python
これは次と同じである:
del doc.monty.python[0]
xml_remove_child
メソッドを使うこともできる:
child = doc.monty.python doc.monty.xml_remove_child(child)
削除方法ついて徹底的に列挙してもいい。あるオブジェクトがその兄弟ノードの中でどの位置にあるかを、それを親から取り除くために使用することができる。この「位置」を得るにはxml_index_on_parent
プロパティを用いる。
ix = doc.monty.python.xml_index_on_parent
最初のpython要素はmontyの二番目の子なので、これはix
に値「1」を代入する。
ix = doc.monty.python[1].xml_index_on_parent
二番目のpython要素はmontyの4番目の子であるため、これはix
に値「3」を代入する。indexを得たなら、それをxml_remove_child_at
に渡すことで特定の子要素を削除するのに使用することができる。
doc.monty.xml_remove_child_at(3)
これは上で決定されたindexを用いて、二番目のpython要素を削除する。これはテキストノードにも同様に動作する:
doc.monty.xml_remove_child_at(0)
これは最初のテキストノードを削除する。このメソッドを呼ぶときにはindexを省略することもできる。デフォルトではそれは最後の子ノードを削除する:
doc.monty.xml_remove_child_at()
もし名前に関するあれこれの章で取り上げたような一意的な名前に関する問題に突き当たったなら、変更修正に関するマッピングプロトコルを使用可能だ。たとえば「b-1」という子要素を、次の構造
<a-1 b-1=""><b-1/></a-1>
から削除したいなら、その一つの方法はこのようになる:
from xml.dom import Node E = Node.ELEMENT_NODE del doc[E, None, u'a-1'][E, None, u'b-1']
名前空間をもった新しい要素を作成するにはこのようにする:
e = doc.xml_create_element(element_qname, element_ns)
これは次と等価だ:
e = doc.xml_create_element(element_qname, namespace=element_ns)
訳注:amara version 1.2.0.2ではnamespaceキーワード引数を使うとTypeError: xml_create_element() got an unexpected keyword argument 'namespace'と言われる。ns
というキーワードならあるようだ。
要素生成時、属性も簡単に追加できる:
import amara doc = amara.parse('monty.xml') #Create a third python element e = doc.xml_create_element(u'python', attributes={u'life': u'brian'}) doc.monty.xml_append(e) print doc.xml()
これは次のような出力を得る:
<?xml version="1.0" encoding="UTF-8"?> <monty> <python spam="eggs"> What do you mean "bleh" </python> <python ministry="abuse"> But I was looking for argument </python> <python life="brian"/></monty>
新しい要素に属性がどう現れるかが分かる。
辞書、attributes={u'life': u'brian'}
は実際には省略形である。正しくは次のように書く:
{ (<attr1_qname>, <attr1_namespace>): attr1_value}, (<attr2_qname>, <attr2_namespace>): attr2_value}, ... (<attrN_qname>, <attrN_namespace>): attrN_value}, }
訳注:書き間違いだと思うので次のように訂正しておく:
{ (<attr1_qname>, <attr1_namespace>): attr1_value, (<attr2_qname>, <attr2_namespace>): attr2_value, ... (<attrN_qname>, <attrN_namespace>): attrN_value, }
名前空間がNone(即ちその属性が名前空間を持っていないなら)、(<attrN_qname>, <attrN_namespace>)
は単に<attrN_qname>
と書くことができる。
したがって名前空間で修飾された属性は次のように追加できる:
import amara NS = u'urn:bogus' doc = amara.parse('monty.xml') #Create a third python element e = doc.xml_create_element( u'python', attributes={(u'ns:life', NS): u'brian'}, content=u'unfortunate' ) doc.monty.xml_append(e) print doc.xml()
今回ちょっとした「内容」も入れたことに留意のこと。結果はこうなる:
<?xml version="1.0" encoding="UTF-8"?> <monty> <python spam="eggs"> What do you mean "bleh" </python> <python ministry="abuse"> But I was looking for argument </python> <python xmlns:ns="urn:bogus" ns:life="brian">unfortunate</python></monty>
要素を作成した後に属性を作成するには、xml_set_attribute
メソッドを使う。先述の例で使用した名前空間で修飾された属性を追加するには、次のようにする:
import amara NS = u'urn:bogus' doc = amara.parse('monty.xml') #Create a third python element e = doc.xml_create_element( u'python', content=u'unfortunate' ) doc.monty.xml_append(e) doc.monty.python[2].xml_set_attribute((u'ns:life', NS), u'brian') print doc.xml()
これは上と同じXMLを生成する。名前空間を必要としないなら、xml_set_attribute
の最初の引数は、単にその属性の名前になる。
xml_set_attribute
メソッドはその結果生成される属性の名前を返す。
Pythonの文法を使って属性値をセットすることもできる:
doc.monty.python[2].life = u'Pi'
名前空間の情報を含めて、要素の属性について情報は、xml_attributes
に保持されている。これは、それぞれの属性のローカル名をkeyに、名前空間で修飾された属性名(訳注:QNameのことらしい)とその名前空間URLのtupleを値にした辞書である。たとえば次のようなコードがあるとしよう:
import amara NS = u'urn:bogus' doc = amara.parse('monty.xml') #Create a third python element e = doc.xml_create_element( u'python', content=u'unfortunate' ) doc.monty.xml_append(e) #Add a namespace qualified attribute doc.monty.python[2].xml_set_attribute((u'ns:life', NS), u'brian') #Add an attribute with no namespace doc.monty.python[2].xml_set_attribute(u'foo', u'bar')
このときdoc.monty.python[2].xml_attributes
は次の値を持つことになる:
{u'life': (u'ns:life', u'urn:bogus'), u'foo': (u'foo', None)}
お遊びだが、XML名前空間の特別な状態を示す興味深いバリエーションがある:
import amara from xml.doc import XML_NAMESPACE as XML_NS doc = amara.parse('monty.xml') #Create a third python element e = doc.xml_create_element( u'python', attributes={(u'xml:lang', XML_NS): u'en'}, content=u'Ni!' ) doc.monty.xml_append(e) print doc.xml()
これは次のような出力を得る。
<?xml version="1.0" encoding="UTF-8"?> <monty> <python spam="eggs"> What do you mean "bleh" </python> <python ministry="abuse"> But I was looking for argument </python> <python xml:lang="en">Ni!</python></monty>
標準が許すように、XML名前空間の宣言がない点に留意のこと。
Amaraでは、要素のxml_append_fragment
メソッドを通じて、XML文書を書き加える上で強力な「利便性」を提供している。リテラルなXMLの断片文字列(Unicodeではない、なぜならこれはパースの操作なのだから)をこのメソッドに渡すと、それはパースされ、要素に追加される。XML断片はwell-formedな解析される外部実態でなければならない。基本的に複数のルート要素も許されるが、それらは正確に均衡を保ち、特別な文字列はエスケープされる、等々が要求される。文書型宣言は禁止されている。XMLの規則によると、符号化文字列はUTF-8かUTF-16であるとみなされるが、XML宣言(訳注:原文ではXML text declaration)(<?xml version="1.0" encoding="ENC"?>
を書くか、またはこの関数に符号化についてのパラメタを渡すことで上書き可能だ。
import amara doc = amara.parse('monty.xml') doc.monty.xml_append_fragment('<py3 x="1">p</py3><py4 y="2">q</py4>') print doc.monty.xml_child_elements.keys()
xml_append_fragment
の呼び出しにおいて二つの要素が追加され、出力結果は[u'python', u'py3', u'py4']となる。
オプションの「encoding」はXML断片をパースする際に用いられる符号化された文字列である。このパラメタが明示されると、XML断片のいかなる宣言も上書きされる。
#Latin-1 ordinal 230 is the a-e ligature doc.monty.xml_append_fragment('<q>P%an</q>'%chr(230), 'latin-1')
文書全体をゼロから作ることもできる:
doc = amara.create_document() doc.xml_append(doc.xml_create_element(u"hello")) doc.xml()
これは次を生成する:
<?xml version="1.0" encoding="UTF-8"?> <hello/>
空の文書に特定のルート要素を作成することができる。
doc = amara.create_document(u"hello") doc.hello.xml_append(doc.xml_create_element(u"world")) doc.xml()
これは次を生成する:
<?xml version="1.0" encoding="UTF-8"?> <hello><world/></hello>
これは次と等価である:
doc = amara.create_document() doc.xml_append(doc.xml_create_element(u"hello")) doc.hello.xml_append(doc.xml_create_element(u"world")) doc.xml()
ルート要素に、ns
パラメタを用いて名前空間をセットすることが可能である。他にもたくさんの便利なパラメタがある。詳細はimport amara; help(amara.create_document)
をPythonプロンプトで試してみよう。
XSLTに通じているなら、xsl:copy-ofと同じことをAmaraで行うにはどうすればいいかを疑問に思うかもしれない。このような深いコピーを作成するには、Python標準ライブラリのcopy
モジュールを使う。
import copy import amara doc = amara.parse('monty.xml') #Clone the document doc2 = copy.deepcopy(doc) #This modification only affects the clone doc2.monty.python.spam = u"abcd" #This output will just like what was read in print doc.xml() #This output will show the change from "eggs" to "abcd" print doc2.xml()
バインダリはかなり自然な方法でXMLの構築をサポートする。ソース文書にPIやコメントがあるなら、PIやコメントを表現するオブジェクトがある:
import amara DOC = """\ <?xml-stylesheet url="xxx.css"?> <!--A greeting for all--> <hello-world/> """ doc = amara.parse(DOC) print doc.xml_children
これは次のリストとなる:
[ <amara.bindery.pi_base instance at 0x433f6c>, <amara.bindery.comment_base instance at 0x433fac>, <amara.bindery.hello_world object at 0x433fec> ]
最初のアイテムはバインドされたPIオブジェクトで、二番目はバインドされたコメントである。より深く掘り下げることもできる:
pi = doc.xml_children[0] comment = doc.xml_children[1] print repr(pi.target) #shows u'xml-stylesheet' print repr(pi.data) #shows u'url="xxx.css"' print repr(comment.data) #shows u'A greeting for all'
これらのオブジェクトを作成することも変更することも可能だ:
doc = amara.create_document() pi = bindery.pi_base(u"xml-stylesheet", u'url="xxx.css"') doc.xml_append(pi) doc.xml_append(doc.xml_create_element(u"A"))
訳注:最後の行は明らかに意図するコードではないはず。実際にコメントノードを生成するには、次のようにする:
from amara import bindery cmt = bindery.comment_base(u"this is comment.")
xml_append
に渡せば他のノードと同じ用に文書にコメントノードを追加できる。
文書型宣言のためのAPIもある。
文書型宣言を作成するには、次のようにする:
doc = amara.create_document( u"xsa", pubid=u"-//LM Grashol//DTD XML Software Autoupdate 1.0//EN//XML", sisid=u"http://www.garshol.priv.no/download/xsa/xsa.dtd" )
結果は文書ツリーにおいて次と等価である:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xsa PUBLIC "-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML" "http://www.garshol.priv.no/download/xsa/xsa.dtd"> <xsa/>
これが文書要素を(別途xml_append
を必要とせずに)自動的に作成する点に注意。文書要素の属性や内容を生成するのに、これをさらに詳細に指定することができる:
from xml.dom import XML_NAMESPACE as XML_NS doc = amara.create_document( u"xsa", attributes={(u'xml:lang', XML_NS): u'en'}, content=u' ', pubid=u"-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML", sysid=u"http://www.garshol.priv.no/download/xsa/xsa.dtd" )
これは次の結果となる:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xsa PUBLIC "-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML" "http://www.garshol.priv.no/download/xsa/xsa.dtd"> <xsa xml:lang="en"> </xsa>
すると文書型宣言の詳細にアクセスすることができる:
assert doc.xml_pubid == u"//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML" assert doc.xml_sysid == u"http://www.garshol.priv.no/download/xsa/xsa.dtd" assert doc.xml_doctype_name == u"xsa"
文書型宣言のある文書をパースした時点でルートノードが文書要素のQName、公開識別子、システム識別子を含むというアイデアはまだ動作しないが、これはPython/XMLライブラリの「癖」に起因している。次のバージョンでこれらのライブラリへの依存をなくし、これを改善すべきだ。これが動作するようになったなら次のように書けるのが望ましい:
#DOES NOT YET WORK XSA = """\ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xsa PUBLIC "-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML" "http://www.garshol.priv.no/download/xsa/xsa.dtd"> <xsa> <vendor> <name>Fourthought, Inc.</name> <email>info@fourthought.com</email> <url>http://fourthought.com</url> </vendor> <product id="FourSuite"> <name>4Suite</name> <version>1.0a1</version> <last-release>20030327</last-release> <info-url>http://4suite.org</info-url> <changes> - Begin the 1.0 release cycle </changes> </product> </xsa> """ doc = amara.parse(XSA) assert doc.xml_pubid == u"-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML" assert doc.xml_sysid == u"http://www.garshol.priv.no/download/xsa/xsa.dtd" assert doc.xml_doctype_name == u"xsa"
内部のDTDサブセットの構造を、バインディングは保持しない。
バインダリは、XMLノードをイテレートし、ノードタイプ他の詳細によって引き金をひかれた規則の集合を「発射」することによって機能する。デフォルトのバインディングは、それぞれのノードタイプに登録されたデフォルトの規則の結果をバインディングした結果なのだが、ユーザ定義の規則を登録することを許可することによって、簡単に微調整できる。
XBELのtitle要素は常にシンプルなテキストであり、それらに対して豊潤なPythonオブジェクトを作成するのは、多くの場合「やり過ぎ」である。それらは要素内容のUnicode値というシンプルなデータメンバでしかないということもあり得る。この最適化をバインダリに行うには、simple_string_element_rule
規則のインスタンスを登録する。この規則は、どの要素が簡素化されているかを指し示すXSLTパターン(訳注:XPathパターンのことらしい)のリストを(引数に)取る。
import amara from amara import binderytools #Specify (using XSLT patterns) elements to be treated similarly to attributes rules = [ binderytools.simple_string_element_rule(u'title') ] #Execute the binding doc = amara.parse('xbel.xml', rules=rules) #title is now simple unicode print doc.xbel.folder.bookmark.title.__class__ #訳注:<type 'unicode'>
訳注:上の例では、binderytools.simple_string_element_rule
クラスの__init__
にただ一つのXPathパターンu'title'
が与えられてインスタンス化されているが、これは原文でも前述されているようにリストでもよい:
rules = [ binderytools.simple_string_element_rule([u'title', u'url']) ]
それより個人的には、amara.parse
関数のrules
キーワード引数が取るのは、iterableなオブジェクトで、各アイテムが規則のインスタンスでなければならない点にこそ注意が必要。
文書の一部分にだけ焦点を当てたい、またメモリを節約し、複雑さを取り除くために、関心の外にある特定の要素をバインディング時には無視したい。そのようなケースではomit_element_rule
を使うことができる。
次の例はフォルダのタイトルには一切バインディングを作成しない(しかしブックマークのタイトルは保持される):
import amara from amara import binderytools #Specify (using XSLT patterns) elements to be ignored rules = [ binderytools.omit_element_rule(u'folder/title') ] #Execute the binding doc = amara.parse('xbel.xml', rules=rules) #Following would now raise an exception #print doc.xbel.folder.title
PI、コメント、テキストノードなども、omit_nodetype_rule
規則を使うことで文書から省くことができる。この規則はただ一つのパラメタを取り、それはxml.dom.Node.COMMENT_NODE
, xml.dom.Node.PROCESSING_INSTRUCTION_NODE
, またはxml.dom.Node.TEXT_NODE
という3つのノードタイプのうちの一つである。
次の例には、コメントノードのバインディングが存在しない:
import amara from amara import binderytools from xml.dom impot Node DOCUMENT = """<!-- Printing the best language --> <language>Python</language>""" rules = [binderytools.omit_nodetype_rule(Node.COMMENT_NODE)] doc = amara.parse(DOCUMENT, rules=rules) print doc.xml()
出力結果はこうなる:
<?xml version="1.0" encoding="UTF-8"?> <language>Python</language>
純粋に空白文字だけから成るノードを取り除いて「children」リストを汚さないようにしたいというのは、共通のニーズだろう。バインダリはこれに応えるためws_strip_element_rule
規則を提供する。このパターンにマッチする要素は、空白文字を除去される。
import amara from amara import binderytools #Specify (using XSLT patterns) elements to be stripped #In this case select all top-level elements for stripping rules = [ binderytools.ws_strip_element_rule(u'/*') ] #Excecute the binding doc = amara.parse('xbel.xml', rules=rules)
空白の文字を除去し、さらに特定要素を無視するといった、複数の規則を組み合わせることもできる。
要素と属性の構造が関心の全てであり、そのテキスト内容はどうでもいいのなら、element_skeleton_rule
が使える。
このパターンにマッチする要素は、全ての文字データを除去される。
import amara from amara import binderytools #Specify (using XSLT patterns) elements to be bound as skeletons #In this case select all elements rules = [ binderytools.element_skeleton_rule(u'*') ] #Execute the binding doc = amara.parse('xbel.xml', rules=rules)
データバインディングの基礎的な考え方は、XMLをネイティブデータ型に変換することである。Amaraは、値がネイティブなPythonデータ型、具体的にはint, float, datetimeの値に推論可能かを知るために各XMLノード検査する規則を提供する。
TYPE_MIX = """\ <?xml version="1.0" encoding="utf-8"?> <a a1="1"> <b b1="2.1"/> <c c1="2005-01-31"> <d>5</d> <e>2003-01-30T17:48:07.848769Z</e> </c> <g>good</g> </a>""" import amara from amara import binderytools rules=[binderytools.type_inference()] doc = amara.parse(TYPE_MIX, rules=rules) doc.a.a1 == 1 #type int doc.a.b.b1 == 2.1 #type float doc.a.c.c1 == datetime.datetime(2005, 1, 31) #type datetime.
ビルトインのカスタム規則はXPathパターンを使用しており、それらは名前空間を明示するために接頭辞を用いる。バインダーツールに、どのような名前空間バインディングがあるかを教える必要があるだろう。
import amara from amara import binderytools #Set up customary namespace bindings for RSS #These are used in XPath query and XPattern rules RSS10_NSS = { 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'dc': 'http://purl.org/dc/elements/1.1/', 'rss': 'http://purl.org/rss/1.0/', } rules = [ binderytools.simple_string_element_rule(u'title') ] #Execute the binding doc = amara.parse('rss10.rdf', prefixes=RSS10_NSS, rules=rules)
しかしながら、先述の議論のように、トップレベル要素において全ての名前空間を宣言しているのなら、それらを明示的に接頭辞の辞書で繰り返す必要はない。
プッシュバインドにも規則を使用することができる。「a」要素から得たいのがそのテキスト内容だけだったとしよう。このとき次のようにすることができる。注意して見てほしい。このサンプル文書は今度はわずかに違っている。
>>> XML="""\ ... <doc> ... <one><a>0</a><b>1</b></one> ... <two><a>10</a><b>11</b></two> ... </doc> ... """ >>> import amara >>> from amara import binderytools >>> #This rule says "treat all elements at the third level of depth as simple strings" >>> rule = binderytools.simple_string_element_rule(u'/*/*/*') >>> #Push back bindings of all second level elements ('one' and 'two') >>> chunks = amara.pushbind(XML, u'/*/*', rules=[rule]) >>> elem = chunks.next() >>> print elem.a.__class__ <type 'unicode'> >>> print elem.a u'0' >>> print elem.b.__class__ <type 'unicode'> >>> print elem.b u'1'
もっと洗練された微調整を必要とするなら、ユーザが独自にカスタマイズしたバインディングクラスを登録したいと思うのではないだろうか。次の例はbookmark要素にあるメソッドを与える。retrieve
である。これはウェブページの本文を取得する:
import urllib from xml.dom import Node import amara from amara import bindery #Subclass from the default binding class #We're adding a specialized method for accessing a bookmark on the net class specialized_bookmark(bindery.element_base): def retrieve(self): try: stream = urllib.urlopen(self.href) content = stream.read() stream.close() return content except IOError import sys; sys.stderr.write("Unable to access %s\n"%self.href) doc = amara.parse(XML, binding_classes={(None, u'bookmark'): specialized_bookmark}) #Show specialized instance print doc.xbel.folder.bookmark.__class__ #Exercise the custom method print "Content of first bookmark:" print doc.xbel.folder.bookmark.retrieve()
次の行を見てみよう:
doc = amara.parse(XML, binding_classes={(None, u'bookmark'): specialized_bookmark})
ある要素型のバインドにおいて使用するクラスを登録するときは、その要素の名前空間URIとローカル名を明示する。XBELの例のように名前空間に属していないことを知っているなら、None
を使う。None
はほとんどのPython/XMLツールにおいて「名前空間に属していない」ことを示す正しいシグナルであり、空文字列""ではない。
accesstrigger.pyのデモも参照のこと。
バインダリはバインディングから書き戻したXMLの辻褄が合うように、そしてXPathは期待された結果を返すように何とかしようとするのだが、バインディングをカスタマイズするとヘンテコな結果を引き起こすことになりやすい。
一つの例として、simple_string_element_rule
を使用しxml
メソッドで再シリアライズするなら、その単純化された要素は子要素ではなく、属性として書き出されてしまうだろう。もしバインディングをカスタマイズした結果そのような不本意な結果に遭遇してしまったら、通常の処方箋はカスタマイズしたxml
メソッドを書くか、それに特化したXPathのラッパーを書くかである(XPathラッピングについてはbinderyxpath.xpath_wrapper_mixin
を参照)。
バインダリは拡張性を考慮してデザインされている。しかしXMLの表現の巨大なフレキシビリティであるとか、開発者たちが多くの異なる方法でPythonオブジェクトを生成したいであろうことを考えると(逆もまた同様)、これがただ一つのやり方とはいえない。バインダリの拡張を書くことにより、どのようなニーズにでも応えられるのだが、管理しやすくするための幾つかの基礎的なルールはある。
[TODO: more on this section to come. If you try tweaking bindery extensions and have some useful notes, please pitch in by sending them along.]
ScimitarはISO Schematronの実装で、SchematronスキーマをPythonのvalidator scriptにコンパイルする。
ScimitarはISO Schematronのspec草案を全てサポートしている。Schimitar convenienceにおける既知のギャップについてはTODOファイルを参照。
Scimitarを利用する典型的な二つの局面を紹介しよう。Schematronスキーマ「shcema1.stron」があり、それに対して複数のXMLファイル「instance1.xml, instance2.xml, instance3.xml」をvalidateしたいとしよう。
最初に、Scimitarのコンパイラスクリプト、scimitar.pyを通じてschema1.stronを実行する。
scimitar.py schema1.stron
schema1.py(同名が.py拡張子で置き換えられる)というファイルが現在の作業ディレクトリに生成される。異なるロケーション、異なるファイル名を使いたいときは、"-o"オプションを使う。生成されたファイルはPythonで書かれたvalidatorスクリプトであり、schema1.stronで定義されたSchematron規則をチェックする。
これで、検証したいXMLファイルに対して、生成されたvalidatorスクリプトを実行することができるようになった:
python schema1.py intance1.xml
検証結果のレポートは、デフォルトでは標準出力に生成される。さもなくば"-o"オプションを使ってファイルにリダイレクトすることもできる。
検証結果レポートは解析済みXML外部実体である。言い換えると、well-formedなXML文書によく似たファイルであるが、埋め込まれたタグを許可するために、いくつかの制限が解かれている。
Schematron 1.5 勧告から持ってきた例を使って実際に検証してみる:
$ cat simple1.stron <?xml version="1.0" encoding="UTF-8"?> <sch:schema xmlns:sch="http://www.ascc.net/xml/schematron" version="ISO"> <sch:title>Schematron Schema例</sch:title> <sch:pattern> <sch:rule context="dog"> <sch:assert test="count(ear) = 2" >'dog' 要素は二つの'ear'要素を内容に持つべきです。</sch:assert> <sch:report test="bone" >この dog は bone を持っています。</sch:report> </sch:rule> </sch:pattern> </sch:schema> $ scimitar.py simple1.stron $ ls simple*.py simple1-stron.py $ cat instance2.xml <dog><ear/></dog> $ python simple1-stron.py instance2.xml <?xml version="1.0" encoding="UTF-8"?> Processing schema: Example Schematron Schema Processing pattern: [unnamed] Assertion failure: 'dog' 要素は二つの'ear'要素を内容に持つべきです。
DOMはJavaの世界からやってきたものであり、Python風のAPIからは程遠い(これまで述べてきたバインダリの手順を参照)。いくつかのDOM風の実装、たとえば4SuiteのDomletteにも、幾分Python風のイディオムが紛れ込んでいる。AmaraのDOMツールはさらにそれを推し進めたものだ。
xml.dom.pulldomに通じている人もいるだろう。これはSAXとDOMの素晴らしいハイブリッドを提供し、SAX風のやり方で文書の重要な部分を効率的に断片化する。そしてきめ細かい操作のためにDOMを使うのである。Amaraのpushdomは、このプロセスを幾分便利にしている。これにXPatternの集合を渡すと、そのパターンにしたがったDOMの断片を次々生成するジェネレータを提供してくれる。
これにより、非常に小さなメモリの使用で巨大なファイルをプロセスすることが可能になるにもかかわらず、DOMの利便性のほとんどを利用できる。
for docfrag in domtools.pushdom('demo/labels.xml', u'/labels/label'): label = docfrag.firstChild name = label.xpath('string(name)') city = label.xpath('string(address/city)') if name.lower().find('eliot') != -1: print city.encode('utf-8')
これは "Stamford"をプリントする。
このXML-DEV メッセージも参照のこと。
ジェネレータツールの詳細に関しては、"Generating DOM Magic"という記事を参照のこと。
domtools.abs_path
で、ノードの絶対パスを得ることができる。次のコード:
from amara import domtools from Ft.Xml.Domlette import NonvalidatingReader from Ft.Lib import Uri file_uri = Uri.OsPathToUri('labels.xml', attemptAbsolute=1) doc = NonvalidatingReader.parseUri(file_uri) print domtools.abs_path(doc) print domtools.abs_path(doc.documentElement) for node in doc.documentElement.childNodes: print domtools.abs_path(node)
これは次を表示する:
/ /labels[1] /labels[1]/text()[1] /labels[1]/label[1] /labels[1]/text()[2] /labels[1]/label[2] /labels[1]/text()[3] /labels[1]/label[3] /labels[1]/text()[4] /labels[1]/label[4] /labels[1]/text()[5]
abs_path
に関する詳細は、"Location, Location, Location"という記事を参照のこと。
Tenorsax(amara.saxtools.tenorsax
)はSAXのロジックを「リニアライズする」ためのフレームワークであり、多少自然な流れを作りとともに必要とされる技術的なステートメントが少なくて済む。
これを文書化する時間がないので、例として、test/saxtools/xhtmlsummary.py を参照のこと。
また このXML-DEV messageも参照のこと。
Flextyperは、Jeni TennisonのData Type Library Language(DTLL: ISO Document Schema Definition Languages(DSDL)のpart 5になる方向である)の実装である。Flextyperは4SuiteのRELAX NGライブラリで使えるデータタイプを含んだPythonモジュールを生成する。
FlextyperはDTLLファイルを、含まれたデータタイプを実装するPythonモジュールにコンパイルする。次のように実行する:
flextyper.py dtll.xml
DTLLで定義されたデータタイプ名前空間一つ一つについてファイルがあり、そのセットが作成される。デフォルトでは出力ファイル名は入力に基づいて決められる。すなわち、dtll-datatypes1.py, dtll-datatypes2.py等々である。
これで4SuiteのRELAX NG実装のプロセッサインスタンスでこれらのデータタイプモジュールを登録できるようになる。