XMLの論考: reStructuredText

軽量で強力な文書マークアップ

reStructuredTextという文書フォーマットはPython文書化の公式ソース・フォーマットの1つとして採用されていますが、他のタイプの文書化でも役立ちます。reStructuredTextは興味深いハイブリッド・テクノロジーであり、構文と外観においては他の「ほとんどプレーン・テキストの」フォーマットと似ていますが、セマンティクスとAPIにおいてはXMLによく似ています。Davidはこのフォーマットを調べ、既存のツールでreStructuredTextをいくつかのXML方言 (docutils、DocBook、OpenOffice) やLaTeX、HTML、およびPDFなどの他の有用なフォーマットに変換する方法を示します。

David Mertz, Ph.D (mertz@gnosis.cx), Author, Gnosis Software, Inc.

Photo of David MertzDavid Mertz氏は多くの分野で活躍しています。ソフトウェア開発や、それについて著述もしています。その他、学術政策理念について分野を問わず、関係する雑誌に記事も書いています。かなり以前には、超限集合論、ロジック、モデル理論などを研究していました。その後、労働組合組織者として活動していました。そして、David Mertz氏自身は人生の半ばにもまだ達していないと思っているので、これから何かほかの仕事をするかもしれません。



2003年 2月 01日

これまで、このカラムではXMLの代替物、つまりXMLの目的の多くを同じように満たす文書フォーマットを調べてきました。reStructuredText はその続きです。データ・フォーマットに適したYAMLとは対照的に、reStructuredTextは文書化用に設計されています。スマートASCIIとは対照的に、reStructuredTextはより重厚で、強力で、形式的に指定されています。これらのすべてのフォーマットは、XMLとは対照的に、標準的なテキスト・エディターで容易かつ自然に読み取って編集できます。XMLの処理には、多かれ少なかれ、これまで検討したような特殊なXMLエディターが必要です (参考文献を参照)。

reStructuredText (しばしばreSTと略される) はPython Docutilsプロジェクトの一部です。このプロジェクトの目標は、プレーン・テキスト文書の操作 (HTML、XML、およびTeXなどの構造化フォーマットにエクスポートすることを含む) を行うためのツールを作成することです。このプロジェクトはPythonコミュニティーから発していますが、その要件はPythonの範囲を超えています。どのタイプのプログラマーやライターも、README、HOWTO、FAQ、アプリケーション・マニュアル、およびPythonの場合はPEP (Python Enhancement Proposals) といった文書をしばしば作成します。これらのタイプの文書では、XMLやLaTeXなどの冗長で難解なフォーマットの処理をユーザーに要求することは、ユーザーがプログラマーであったとしても、一般に妥当ではありません。それでも、単純な表示以上 (索引付け、コンパイル、見栄えの良い印刷、フィルター操作など) のことを行いたいため、これらのタイプの文書を使用することが望ましいこともしばしばあります。

Docutilsツールは、JavaDocがJavaプログラマーを助けたりPODがPerlプログラマーを助けたりするのとほとんど同じように、Pythonプログラマーが必要とすることを行います。Pythonモジュール内の資料をDocutils文書ツリーに変換し、次いでさまざまな出力フォーマットに変換できます (通常は単一のスクリプトで)。しかしこの記事の場合、より興味深い用途は一般的な文書化です。私は、本記事や私の次の本を、スマートASCIIを使って作成しています。しかし、reStructuredTextの形式性を利用したほうがよいように感じ始めています (そして、既存の文書を変換するツールを開発するかもしれません)。

この記事の執筆の時点で、Docutilsプロジェクトは開発中であり、安定したバージョンがリリースされていません。既存のツールも優れていますが、プロジェクト全体は、期待、善意、部分的な資料、いくつかの実際の作業ツールの混合物となっています。ただし、進行は着実であり、今の時点で行えることでも非常に役立ちます。

reStructuredTextの例

reStructuredTextがどのようなものであるかは、簡潔な例を見ればより良く理解できます。以下のテキストはPEP 287 (まだ正式にはなっていないPEPの1つです) の例です。

リスト1. プレーン・テキスト・バージョンのPEP
Abstract
    This PEP proposes adding frungible doodads [1] to the
    core. It extends PEP 9876 [2] via the BCA [3] mechanism.
...
References and Footnotes
    [1] http://www.example.org/
    [2] PEP 9876, Let's Hope We Never Get Here
        http://www.python.org/peps/pep-9876.html
    [3] "Bogus Complexity Addition"

リスト1 のフォーマットは、PEPが287の前に持っていたフォーマットそのものです。reStructuredTextを使って同じPEPをマークアップすると、以下のようになります。

リスト2. reSTバージョンのPEP
Abstract
========
This PEP proposes adding `frungible doodads`_ to the core.
It *extends* PEP 9876 [#pep9876]_ via the BCA [#]_ mechanism.
...
References & Footnotes
======================
.. _frungible doodads: http://www.example.org/
.. [#pep9876] PEP 9876, Let's Hope We Never Get Here
.. [#] "Bogus Complexity Addition"

いくらかの詳細がプレーン・テキストと異なっています。特殊文字がほんの少し用いられていますが、実際の読み易さは害を受けていません。テキスト・エディターや印刷ページで見るときに、2回見る必要はないでしょう。

リスト2 のreSTフォーマットの文書は、Docutils Generic DTDによって定義されているようなXML方言に自動的に変換できます。

リスト3. Docutils XMLバージョンのPEP
<?xml version="1.0" encoding="UTF-8"?>
<document source="test">
  <section id="abstract" name="abstract">
    <title>Abstract</title>
    <paragraph>This PEP proposes adding <reference
      refname="frungible doodads">Frungible doodads</reference>
      to the core. It<emphasis>extends</emphasis><reference
      refuri="http://www.python.org/peps/pep-9876.html">
      PEP 9876</reference><footnote_reference auto="1" id="id1"
      refname="pep9876"/> via the BCA <footnote_reference
      auto="1" id="id2"/> mechanism.</paragraph>
    <paragraph>...</paragraph>
  </section>
  <section id="references-footnotes"
           name="references &amp; footnotes">
    <title>References &amp; Footnotes</title>
    <target id="frungible-doodads" name="frungible doodads"
            refuri="http://www.example.org/"/>
    <footnote auto="1" id="pep9876" name="pep9876">
      <paragraph><reference
        refuri="http://www.python.org/peps/pep-9876.html">PEP
        9876</reference>, Let&apos;s Hope We Never Get Here
      </paragraph>
    </footnote>
    <footnote auto="1" id="id3">
      <paragraph>&quot;Bogus Complexity Addition&quot;
      </paragraph>
    </footnote>
  </section>
</document>

これらの3つのフォーマットを対比すると、いくつかのことが分かります。最も劇的な違いは、XMLバージョンの読みにくさです。同時に、reStructuredTextツールがreST文書から読み取った情報の多さが注目に値します。複数のタイプの参照が適切に突き合わされ、文書セクションが識別され、文字レベルの活版印刷マークアップが追加されています。他の例では、リンクされたTOCが他の特殊ディレクティブとともに生成されます。


docutilsプロジェクトの構造

docutils パッケージは、かなり複雑に関係し合ったいくつかのサブパッケージで構成されています。PEP 258 (Docutils Design Specification) には、全体のパターンを理解するのに役立つ図表が含まれています。

図1. Docutilsプロジェクト・モデル
Docutilsプロジェクト・モデル

このPEPにはコンポーネント・サブパッケージのより完全な説明が含まれていますが、ここでは簡潔な説明を繰り返すだけで十分です。

reSTテキストをノードのツリーに変換する大変な作業は、docutils.parsers.rst サブパッケージによって行われます。reStructuredTextパーサーはソースを行単位で扱い、各行で状態遷移を探します。他の遷移パターンが見付からなければ、text 遷移がその行を獲得します。遷移は、インデントの変化や特殊な先頭シンボルなどの特性で構成されます。デフォルトでは、現行ノードのテキストとして次の行だけが含まれます。

この構造は、スマートASCIIパーサーのtxt2dw およびtxt2html で使用されているものと似ています。他のパーサーはdocutils.parsers 階層の下に存在することになりますが、現在提供されているものはありません。ただし、Pythonソース・ファイルを文書ツリーとして扱う実験的なPythonソース・コード・パーサーが存在します。

docutils.transforms サブパッケージが文書用のノードのツリーを生成すると、そのツリーをさまざまな仕方で操作できます。たとえば、目次を組み込むディレクティブを指定すると、リストされている項目を識別するように文書ツリーが走査されます。また、変換により、この段階で参照とリンクのクリーンアップがいくらか実行されます。最初のパスでは、未解決のエレメントが置かれるツリー内の位置には、後で変換の必要性があることをマークするプレースホルダーが埋められます。


イベント指向の出力

さまざまなdocutils.writers モジュールは、この記事のほとんどの読者にとっておそらく主な関心事でしょう。この記事の執筆時点で、より興味深いライターの一部は依然として実験的な "サンドボックス" 領域に保持されています (参考文献のDocutils Webサイトを参照) が、いずれの場合も原則は同じです。ライター・モジュールは、docutils.writers.Writer から継承するWriter クラスを定義する必要があります。このWriter クラスはいくつかの設定を定義しますが、たいていの場合は次のような.translate() メソッドを定義します。

リスト4. 典型的なカスタムWriter.translate() メソッド
def translate(self):
    visitor = DocBookTranslator(self.document)
    self.document.walkabout(visitor)
    self.output = visitor.astext()

見て分かるように、ライターは、各タイプのノードをどのように扱うかを知っているビジター (visitor) に依存します。ビジターは一般に、docutils.nodes.NodeVisitor から継承します。ビジターのプログラミングは、SAXexpatREXML、または他のイベント指向のXMLパーサーのプログラミングとよく似ています。ただし、ビジターは、Pythonのxmllib モジュールのプログラミング・スタイルにさらに似ています。つまり、ビジターは、大きな.startElement() およびendElement() メソッドの内部でタイプによって処理を切り替える代わりに、各タイプのノードごとに.visit_FOO() および.depart_FOO() メソッドを持ちます。OOPの純血主義者にはこのスタイルは好まれるでしょう。Docbook/XMLライターの単純な例は以下のとおりです。

class DocBookTranslator(nodes.NodeVisitor):
    [...lots of methods...]
    def visit_block_quote(self, node):
      self.body.append(self.starttag(node, 'blockquote'))
    def depart_block_quote(self, node):
      self.body.append('</blockquote>
      \n')
    [...lots more methods...]

カスタムのライター / ビジターのプログラミングはそれほど難しくなく、Docutils/XML、HTML、PEP-HTML、PseudoXML (開始タグとインデントを組み合わせ、終了タグを持たない、軽量XMLの一種)、LaTeX、DocBook/XML、PDF、OpenOffice/XML、およびWiki-HTML用のライターが存在しています。


ツリー指向の処理

reStructuredText文書は、DOMのような仕方で操作できるノードのツリーに変換できます。以下は、リスト2 で示したreST PEPの例を使用する例です。

リスト5. reSTノード・ツリーの作成
>>> txt = open('pep.txt').read()
>>> def rst2tree(txt):
...     import docutils.parsers.rst
...     parser = docutils.parsers.rst.Parser()
...     document = docutils.utils.new_document("test")
...     document.settings.tab_width = 4
...     document.settings.pep_references = 1
...     document.settings.rfc_references = 1
...     parser.parse(txt, document)
...     return document
...
>>> doc = rst2tree(txt)
>>> doc.children
[<section "abstract": <title...><paragraph...><paragraph...>,
 <section "references & footnotes": <title...>
   <target "frungible doodads"...><footnote "pep9 ...>]
>>> print doc.autofootnotes
[<footnote "pep9876": <paragraph...>, <footnote: <paragraph...>]
>>> print doc.autofootnotes[0].rawsource
PEP 9876, Let's Hope We Never Get Here

DOMと対比して注目できる点は、reStructuredTextはすでに固定された文書方言であるということです。したがって、汎用のメソッドを使用して一致するノードを検索する代わりに、意味に応じた名前を持つ属性を使用してノードを検索できます。.children 属性は一般に階層型ですが、ほとんどの属性は特定のタイプのノードを収集します。

reSTノードの便利なメソッドの1つは.pformat() で、見栄えの良い印刷のためにリスト6 のような文書ツリーの疑似XML表現を生成します。

リスト6. reSTノードの疑似XML表現
>>> print doc.autofootnotes[0].pformat('  ')
<footnote auto="1" id="pep9876" name="pep9876">
  <paragraph>
    <reference refuri="http://www.python.org/peps/pep-9876.html">
      PEP 9876,
    Let's Hope We Never Get Here

.remove().copy().append()、および.insert() などのノード・メソッドは、ツリーの枝取りや操作を行うのに役立ちます。

XMLプログラマーにとって、より望ましいAPIはDOMそのものかもしれません。幸い、このDOM APIを使うには単一のメソッド呼び出しするだけです。

リスト7. reSTツリーからDOMツリーへの変換
>>> dom = doc.asdom()
>>> foot0 = dom.getElementsByTagName('footnote')[0]
>>> print foot0.toprettyxml('  ')
<footnote auto="1" id="pep9876" name="pep9876">
  <paragraph>
    <reference refuri="http://www.python.org/peps/pep-9876.html">
      PEP 9876
    </reference>
    , Let's Hope We Never Get Here
  </paragraph>
</footnote>

残念ながら、この記事の執筆時点で、DOMツリーやXML文書をreStructuredTextに戻す ツールや機能はありません。Docutils Generic DTD用のリーダーがあれば特に良いでしょう。これがあれば、対応するXML用のreST文書ツリーを作成できます。そのツリーを.astext() ノード・メソッドによってreSTとして書き出すこともできます。そのようなリーダーの作成は大変ではなく、(おそらく私か読者の1人によって) そのうち行われると確信しています。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=241022
ArticleTitle=XMLの論考: reStructuredText
publish-date=02012003