Python-Markdown extensionを記述する(つたない翻訳)

Extension API — Python-Markdown 2.6.11 documentation」をつたない翻訳,勝手な解釈。

初回公開:2018/01/13
最終更新:2018/01/14

【 目次 】

Python-Markdownは独自のカスタム機能および/または構文解析をおこなうプラグインのための拡張機能extensionを記述するAPIが含まれています。

ソースがパーサに渡される前にソースを変更できるようにするプリプロセッサ(Preprocessors),インライン要素の構文を追加、削除、またはオーバーライドできるようにするインラインパターン(inline patterns),およびパーサーから返される前にパーサーの出力を取り消すことができるポストプロセッサー(Postprocessors)が含まれています。
真により深く内部に入り込んで処理をおこないたいなら、コアBlockParserの一部であるブロックプロセッサ(Blockprocessors)もあります。

パーサが後にUnicodeテキストとしてレンダリングされるElementTreeオブジェクトを構築する時に、ツリーの操作を容易にするヘルパーもいくつかあります。
APIの各部分については、以下のそれぞれのセクションで説明します。
さらに、いくつかの(利用可能なExtensions
)のソースを読む事も役立つかもしれません。
例えば、Footnotes extension
はここで説明している機能のほとんどを使用しています。

Preprocessors

プリプロセサはマークダウンのコアに渡される前にソーステキストを操作します。
これは、不正な構文を取り除き、そうでなければパーザの処理を妨げる可能性のあるものを抽出し、後で修正するためにそれを保存することさえできる優れた場所です。
プリプロセッサはmarkdown.preprocessors.Preprocessorを継承し、そして、一つの引数linesをもったrunメソッドを実装すべきです。
各プリプロセッサのrunメソッドは、ソーステキスト全体をUnicode文字列のリストとして渡されます。
おのおのの文字列には1行分テキストが含まれます。
runメソッドは、、そのリスト、もしくは変更されたUnicode文字列のリストのいずれかを返すべきです。

コードのイメージは以下のようになります:

from markdown.preprocessors import Preprocessor

class MyPreprocessor(Preprocessor):
    def run(self, lines):
        new_lines = []
        for line in lines:
            m = MYREGEX.match(line)
            if m:
                # do stuff
            else:
                new_lines.append(line)
        return new_lines

Inline Patterns

Inline Patterns(インラインパターン)は、*emphasis*または[links](http://example.com)のようなMarkdownのためのインラインHTML要素の構文を実装します。


訳者による蛇足

インラインHTML要素とは


パターンオブジェクトはmarkdown.inlinepatterns.Patternまたはその子クラスを継承するクラスのインスタンスである必要があります。
おのおののパターンオブジェクトは、単一の正規表現を使用し、以下のメソッドを持っている必要があります:

  • getCompiledRegExp():

    コンパイル済みの正規表現を返す。

  • handleMatch(m):

    matchオブジェクトを受け入れ、プレーンなUnicode文字列のElementTreeの要素を返します。

getCompiledRegExpによって返された任意の正規表現はブロック全体をキャプチャする必要がある事に注意してください。
したがって、それらはすべてr'^(.*?)'で始まり、r'(.*?)!'`で終わるべきです。

Patternクラスで提供されているデフォルトのgetCompiledRegExp()メソッドを使用すると、正規表現を渡すことができ、getCompiledRegExpは式をラップしてre.DOTALLre.UNICODEフラグを設定します。

これは、m.group(1)がパターンの前のすべてと一致するため、一致の最初のグループはm.group(2)になることを意味します。

たとえば、この単純化されたemphasis(強調)パターンを考えてみましょう。

from markdown.inlinepatterns import Pattern
from markdown.util import etree

class EmphasisPattern(Pattern):
    def handleMatch(self, m):
        el = etree.Element('em')
        el.text = m.group(3)
        return el

Markdownへのコードの統合で論じるように、このクラスのインスタンスをMarkdownに提供する必要があります。
そのインスタンスは次のように作成されます:

# あまりにも簡単な正規表現
MYPATTERN = r'\*([^*]+)\*'
# patternをわたしてインスタンスを作成
emphasis = EmphasisPattern(MYPATTERN)

実際には、そのパターンを作成する必要はありません(単にMarkdownにもっと洗練された強調パターンが存在するという理由だけではなく)。
事実、例のパターンはあまりドライ(無味乾燥)ではないということです。
**strong**テキストのパターンは、'strong'要素を作成することを除いて、ほぼ同じです。
したがって、Markdownにはいくつかの共通の機能を提供できる汎用パターンクラスがいくつか用意されています。
例えば、emphasisとstrongの両方が以下にリストされているSimpleTagPatternの別のインスタンスで実装されています。
markdown.inlinepatternsで見つかったパターンクラスを自由に使用したり拡張したりできます。

汎用のPatternクラス

  • SimpleTextPattern(pattern):

    patterngroup(2)の単純なテキストを返します。

  • SimpleTagPattern(pattern, tag):

    patterngroup(3)のtext属性を持つ"tag"型の要素を返します。 tagはHTML要素の文字列('em')である必要があります。

  • SubstituteTagPattern(pattern, tag):

    子要素やテキストのないタイプの"tag"要素(例えばbrのような)を返します。

Markdownソースには、拡張したり使う事ができる他のPatternクラスもあるでしょう。
ソースを読み、使用できるものがあるかどうかを確認してみてください。
あなたの特定の状況に対するさまざまなアプローチのためのいくつかのアイデアが得られるかもしれません。


訳者による蛇足

Patternクラスのソースは標準のインストール方法でインストールされたwindows,Python27ではC:\Python27\Lib\site-packages\markdown\inlinepatterns.pyにあり、そこにいろいろなPatternクラスが定義されている。

Inline PatternsクラスのベースクラスとなるPatternクラスより重要とおもわれる部分を抜粋すると

class Pattern(object):
    ...
    def __init__(self, pattern, markdown_instance=None):
        ...
        self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern,
                                      re.DOTALL | re.UNICODE)
    ...
    def getCompiledRegExp(self):
        return self.compiled_re

    def handleMatch(self, m):
        pass #pragma: no cover

Patternクラスはコンストラクタで渡された正規表現文字列の前後に任意の文字列とマッチする正規表現(.*?)を追加している事がわかる。
そして、re.DOTALLとre.UNICODEフラグを設定して、正規表現のコンパイルをおこなっている。
getCompiledRegExpメソッドは、このコンパイルされた正規表現オブジェクトを返す。

re.compileについては

re.DOTALLフラグは

特殊文字 '.' を、改行を含む任意の文字と、とにかくマッチさせます;このフラグがなければ、 '.' は、改行 以外の 任意の文字とマッチします。

re.UNICODEフラグは

Make the \w, \W, \b, \B, \d, \D, \s and \S sequences dependent on the Unicode character properties database. Also enables non-ASCII matching for IGNORECASE.

基底クラスPatternのhandleMatchメソッドは、空のメソッドで何もしていないが、このメソッドの引数mには正規表現の検索結果のmatchオブジェクトが渡される。
EmphasisPatternのhandleMatchメソッドのコードの例をみてみよう。
matchオブジェクトのgroup属性は最初にマッチしたものはm.group(1)に入るはずであるが、前述のように、「Patternクラスはコンストラクタで渡された正規表現文字列の前後に任意の文字列とマッチする正規表現(.*?)を追加」されるため、m.group(1)にはコンストラクタで渡された正規表現文字列の前のすべての文字列が入る事になる。
そして、m.group(2)にはコンストラクタで渡された正規表現文字列の最初に一致した文字列*が入る事になる。
m.group(3)には*の間に入っている文字列という事になる。
EmphasisPatternのhandleMatchメソッドではetreeクラスの新しいemタグ要素を作成して、そのテキスト要素にgroup(3)*に囲まれている文字列が入る事になる。
そして作成されたetreeオブジェクトを返している。

Treeprocessors

Treeprocessorsは、ElementTreeオブジェクトがコアのBlockParserを通過した後にそれを操作します。
ここでツリーの追加の操作が行われます。
さらに、InlineProcessorはツリーを辿り、ツリーの各要素のテキスト上のインラインパターンを実行するTreeprocessorです。

Treeprocessorはmarkdown.treeprocessors.Treeprocessorから継承すべきです。
そして、runメソッドをオーバーライドして1つの引数root(ElementTreeオブジェクト)を受け取り、そのルート要素を変更してNoneを返すか、あるいは新しいElementTreeオブジェクトを返します。

コードのイメージは以下のようになります:

from markdown.treeprocessors import Treeprocessor

class MyTreeprocessor(Treeprocessor):
    def run(self, root):
        root.text = 'modified content'

return文が定義されていない場合、デフォルトではPythonクラスのメソッドはNoneを返すことに注意してください。
さらに、すべてのPython変数は参照によってオブジェクトを参照します。
したがって、上記のrunメソッドは、ルート要素を変更し、Noneを返します。
ルート要素とその子要素に対する変更は保持されます。

変更されたルート要素を返す傾向があります。
それが機能する間、Treeprocessorが実行されるたびにElementTree全体のコピーが起きるでしょう。
したがって、通常、runメソッドはNoneまたは新しいElementTreeオブジェクトを返すことのみが予想されます。

ElementTreeの操作の詳細については、下記のElementTreeを扱うを参照してください。

Postprocessors

Postprocessorsは、ElementTreeが文字列にシリアライズされた後にドキュメントを操作します。
出力の直前にテキストを処理するには、ポストプロセッサを使用する必要があります。

Postprocessorはmarkdown.postprocessors.Postprocessorから継承し、runメソッドをオーバーライドする必要があります。このメソッドは1つの引数textをとり、Unicode文字列を返します。

Postprocessorsは、ElementTreeがUnicodeテキストにシリアル化された後に実行されます。
たとえば、これはドキュメントに目次を追加するのに適した場所です:

from markdown.postprocessors import Postprocessor

class TocPostprocessor(Postprocessor):
    def run(self, text):
        return MYMARKERRE.sub(MyToc, text)

訳者による蛇足

PostprocessorsクラスのはC:\Python27\Lib\site-packages\markdown\postprocessors.pyで実装されていて、
Postprocessorsの使用例については、Footnotes(脚注)のソースコード(C:\Python27\Lib\site-packages\markdown\extensions\footnotes.py)を参照。

BlockParser

時おり、Preprocessors, Treeprocessors, PostprocessorsおよびInline Patternsは、必要な処理を実行しないことがあります。
おそらく、コア解析に統合された新しいタイプのブロックタイプが必要に感じるでしょう。
このような状況において、コアBlockParserの機能を追加/変更/削除できます
BlockParserは、いくつかのBlockprocessorで構成されています。
BlockParserは、(空白行で分割された)テキストの各ブロックをステップ実行し、各ブロックを適切なブロックプロセッサに渡します。
Blockprocessorはブロックを解析し、それをElementTreeに追加します。
Definition Lists extensionは、ブロックプロセッサを追加/変更する拡張の良い例になります。

Blockprocessorはmarkdown.blockprocessors.BlockProcessorを継承し、testメソッドとrunメソッドの両方を実装する必要があります。

testメソッドは、BlockParserによってブロックのタイプを識別するために使用されます。
したがって、testメソッドはブール値を返す必要があります。
テストでTrueが返された場合、BlockParserはそのBlockprocessorのrunメソッドを呼び出します。
Falseを返すと、BlockParserは次のブロックプロセッサに移動します。

testメソッドは2つの引数をとります:

  • parent: ブロックの親ElementTree要素。
    これは、ブロックがリスト内にある場合など、ブロックを別の方法で扱う必要がある場合などに便利です。

  • block: テキストのカレントブロックの文字列。
    testは単純な文字列メソッド(block.startswith(some_text)など)でも複雑な正規表現でもかまいません。

run メソッドは2つの引数をとります:

  • parent: ブロックの親ElementTree要素へのポインタ。
    runメソッドは、おそらく追加のノードをこのparentに接続します。
    メソッドによって何も返されないことに注意してください。
    ElementTreeオブジェクトはここで変更されます。

  • blocks: ドキュメントの残りすべてのブロックのリスト。
    あなたのrunメソッドは、リストから最初のブロックを削除(ポップ)しなければなりません(変更される場所で - 戻れません)。そして、そのブロックを解析しなければなりません。
    テキストブロックに正当な形で複数のブロックタイプが含まれていることがあります。
    したがって、最初のタイプを処理した後で、残りのテキストを後で解析するためにブロックリストの先頭に挿入することができます。

1つのブロックが複数のテキストブロックにまたがる可能性があることに注意してください。
たとえば、公式のMarkdown構文規則では、空白行はコードブロックを終了しないと記述されています。
次のテキストブロックもインデントされている場合は、それは前のブロックの一部です。
したがって、BlockParserは、これらのタイプの状況に対処するために特別に設計されています。
したがって、最初のタイプを処理した後で、残りのテキストを後で解析するためにブロックリストの先頭に挿入することができます。
CoreBlockProcessorに気がついたら、コアの中のparentの最後の子をチェックすることに注意してください。
最後の子がコードブロック(<pre><code>...</code></pre>)の場合、新しいブロックを作成するのではなく、そのブロックを前のコードブロックに追加します。

各ブロックプロセッサには、次のユーティリティメソッドが使用可能です。

  • lastChild(parent):

    指定されたElementTree要素の最後の子を返します。もしくは子要素がない場合はNoneを返します。

  • detab(text):

    指定されたテキスト文字列の各行の先頭から1レベルのインデント(デフォルトでは4つのスペース)を削除します。

  • looseDetab(text, level):

    指定されたテキスト文字列の各行の先頭からインデントの"level"で指定されたレベル(デフォルトは1)を削除します。
    けれども、このメソッドはMarkdown構文のいくつかの部分と同じように二次的な行をインデントしないようにすることができます。

各Blockprocessorには、パーサーの状態を確認または変更するために使用できる、self.parserのBlockParserインスタンスへのポインタもあります。
BlockParserは、parser.stateのスタック内の状態を追跡します。
状態スタックは、Stateクラスのインスタンスです。

Statelistのサブクラスであり、追加のメソッドを持っています:

  • set(state):

    文字列state.に対して新しい状態を設定します。
    新しい状態はスタックの末尾に追加されます。

  • reset():

    スタックの一つ前に戻ります。
    最後に一番後のステートはスタックから削除されます。

  • isstate(state):

    スタックのトップ(カレントの)レベルが指定された文字列stateであることをテストします。

状態スタックが破損しないようにするには、ブロックに状態が設定されるたびに、パーサーがそのブロックの解析を終了したときにその状態をリセットする必要があります。
BlockParserのインスタンスはMarkdown.parserにあります。
BlockParserは以下のメソッドを持っています:

  • parseDocument(lines):

    行のリストをもとに、ElementTreeオブジェクトを返します。
    これは、ドキュメント全体を渡す必要があり、Markdownクラスが直接呼び出す唯一のメソッドです。

  • parseChunk(parent, text):

    複数のブロックで構成されるマークダウンテキストの断片であるtextを解析し、それらのブロックをparent(親要素)に結びつけます。
    parentはここで変更され、何も返されません。
    Extensionsがブロック解析するためにこのメソッドが使われる可能性が最も高いでしょう。

  • parseBlocks(parent, blocks):

    テキストブロックのリストを解析し、それらのブロックをparent(親要素)に結びつけます。
    parentはここで変更され、何も返されません。
    このメソッドは、通常、ネストされたテキストブロックを再帰的に解析するために内部的に使われるだけです。

isは推奨されませんが、ExtensionはBlockParserをサブクラス化または完全に置き換えることができます。
新しいクラスは同じパブリックAPIを提供する必要があります。
ただし、他のExtensionではコアパーサーが提供されていると予想され、そのような大幅に異なるパーサーでは機能しないことに注意してください。

ElementTreeを扱う

前述のように、Markdownパーサーは、ソースドキュメントをElementTreeオブジェクトに変換してからUnicodeテキストにシリアル化します。
Markdownは、Markdownモジュールのコンテキスト内での操作を簡単にするためにいくつかのヘルパーを提供しています。

まず、ElementTreeモジュールへのアクセスを取得するには、それを直接インポートするのではなく、ElementTreeをmarkdownからインポートします。
これにより、markdownとして同じバージョンのElementTreeを使用していることが保証されます。
モジュールはMarkdown内のmarkdown.util.etreeにあります。

from markdown.util import etree

markdown.util.etreeは、標準ライブラリモジュール(Python 2.5ではxml.etreeから)として、次にサードパーティパッケージ(ElementTree)として、既知の場所からElementTreeをインポートしようとします
それぞれのインスタンスでは、最初にcElementTreeのインポートを
試みるます、次にシステム上で高速なCの実装が利用できないならElementTreeのインポートを
試みます。

Inline Patternsによって解析された要素の中にテキストを挿入したいと望むでしょう。
このような状況では、通常どおりにテキストをシンプルに挿入します、そしてテキストは自動的にインラインパターンをとおして実行されます。
けれども、いくつかのテキストに対してインラインパターンでの解析を望まないなら、テキストをAtomicStringとして挿入します。

from markdown.util import AtomicString
some_element.text = AtomicString(some_text)

ここに、HTMLテーブルを作成する基本的な例を示します(2番目のセル(td2)の内容は、後でインラインパターンをとおして実行されます)。

table = etree.Element("table") 
table.set("cellpadding", "2")                      # Set cellpadding to 2
tr = etree.SubElement(table, "tr")                 # Add child tr to table
td1 = etree.SubElement(tr, "td")                   # Add child td1 to tr
td1.text = markdown.util.AtomicString("Cell content") # Add plain text content
td2 = etree.SubElement(tr, "td")                   # Add second td to tr
td2.text = "*text* with **inline** formatting."    # Add markup text
table.tail = "Text after table"                    # Add text after table

既存のツリーを操作することもできます。
<a>要素に対してclass属性を追加する次の例を考えてみましょう。

def set_link_class(self, element):
    for child in element: 
        if child.tag == "a":
            child.set("class", "myclass") #set the class attribute
        set_link_class(child) # run recursively on children

ElementTreeの操作の詳細については、ElementTreeのドキュメント(Python Docsを参照してください。

Markdownへのコードの統合

extensionのさまざまな部分を作成したら、Markdownを使って、それらが適切な順序で実行されていることを確認する必要があります。
Markdownは、おのおののextensionのExtensionインスタンスを受け入れます。
したがって、markdown.extensions.Extensionを拡張し、extendMarkdownメソッドをオーバーライドするクラスを定義する必要があります。
このクラスで、extensionのコンフィグレーション(設定)オプションを管理し、さまざまなプロセッサとパターンをMarkdownインスタンスに(アタッチ)
付け加えします。

さまざまなプロセッサとパターンの順序が重要であることに注意することが重要です。
たとえば、リンクhttp://...<a>要素に置き換えて、インラインHTMLを処理しようとすると、混乱することになります。
したがって、さまざまな種類のプロセッサとパターンは、OrderedDictのMarkdownクラスのインスタンス内に格納されます。
Extensionクラスは、それらのOrderedDictを適切に操作する必要があります。
OrderedDictの適切な場所にプロセッサーとパターンのインスタンスを挿入したり、組み込みのインスタンスを削除したり、組み込みのインスタンスを独自のインスタンスに置き換えたりすることができます。

extendMarkdown

markdown.extensions.ExtensionクラスのextendMarkdownメソッドは、2つの引数を受け取ります:

  • md:

    Markdownクラスのインスタンスへのポインタ。
    プロセッサーとパターンのOrderedDictsにアクセスするためにこれを使います。
    それらは次の属性のもとにあります。

    • md.preprocessors
    • md.inlinePatterns
    • md.parser.blockprocessors
    • md.treeprocessors
    • md.postprocessors

    markdownインスタンスでアクセスしたいいくつかのものがあります:

    • md.htmlStash
    • md.output_formats
    • md.set_output_format()
    • md.output_format
    • md.serializer
    • md.registerExtension()
    • md.html_replacement_text
    • md.tab_length
    • md.enable_attributes
    • md.smart_emphasis
  • md_globals:

    markdownモジュール内のさまざまなグローバル変数をすべて含みます。

警告

上記の項目にアクセスすると、理論的には、さまざまなモンキーパッチテクニックによって変更を加えるオプションがあります。
しかし、markdownの様々なドキュメント化されていない部分が予告なしに変更され、あなたのモンキーパッチが新しいリリースによって突然使えなくなる恐れがあるあることに注意してください。
したがって、本当にすべきことは、プロセッサとパターンをマークダウンパイプラインに挿入することです。
よく考え、あなた自身に言い聞かせて下さい。

簡単な例:

from markdown.extensions import Extension

class MyExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        # Insert instance of 'mypattern' before 'references' pattern
        md.inlinePatterns.add('mypattern', MyPattern(md), '<references')

OrderedDict

OrderedDictは、項目の順序を保持する辞書オブジェクトのようなものです。
項目はOrderedDictに追加された順に並べられます。
ただし、項目は、OrderedDictのなかに格納済みの項目に関連した特定の位置に挿入することもできます。

OrderedDictは、リストと辞書の組み合わせとして、両方に共通のメソッドを持っていると考えてください。
たとえば、od[key] = value構文を使って項目を取得および設定する事ができて、そしてkeys(), values(),およびitems()メソッドはキー,値,および項目が適切な順序で返されるよう期待どおりにはたらきます。
それに加えて、リストと同じように、insert(), append(),およびindex()を使うことができます。

一般的に言えば、Markdown extensionsでは、既存のOrderedDictに項目を追加するために特別なヘルパーメソッドadd()が使われます。

add()メソッドは3つの引数を受け取ります:

  • key: 文字列。 キーは、後で項目を参照するために使われます。

  • value: この項目に格納されているオブジェクトインスタンス。

  • location: オプション。 他の項目と関連した項目の位置。

    位置はいくつかの異なる値で指定できる事に注意してください:

    • 特別な文字列 "_begin""_end"は、それぞれOrderedDictの先頭または末尾に項目を挿入します。

    • 不等号"<"の後ろに既存のキー(例えば"<somekey"`)がある場合、項目は既存のキーの前に挿入されます。

    • 不等号">"の後ろに既存のキー(例えば">somekey"`)がある場合、項目は既存のキーの後ろに挿入されます。

次の例を考えてみましょう:

>>> from markdown.odict import OrderedDict
>>> od = OrderedDict()
>>> od['one'] =  1           # The same as: od.add('one', 1, '_begin')
>>> od['three'] = 3          # The same as: od.add('three', 3, '>one')
>>> od['four'] = 4           # The same as: od.add('four', 4, '_end')
>>> od.items()
[("one", 1), ("three", 3), ("four", 4)]

OrderedDictに順番に項目を設定,追加するとき、addメソッドの特別な機能は役に立ちません、そして必要ないことに注意してください。
しかし、既存のOrderedDictを操作するときには、addは非常に役に立ちます。
そこで、別の項目をOrderedDictに挿入しましょう。

>>> od.add('two', 2, '>one')         # Insert after 'one'
>>> od.values()
[1, 2, 3, 4]

今度は別の項目を挿入してみましょう。

>>> od.add('two-point-five', 2.5, '<three') # Insert before 'three'
>>> od.keys()
["one", "two", "two-point-five", "three", "four"]

「two-point-five」の位置を「twoの後」(すなわち'>two')に設定できることに注意してください。
けれども、extensionがロードされる順序をコントロールすることは難しく、OrderedDictの最終ソート順に影響を及ぼす可能性があります。
例えば、上記の例の"two-point-five"を追加するextensionが、'two'を追加する別のextensionの前にロードされたとします。
さまざまなmarkdownのOrderedDictsにextensionコンポーネントを追加するときは、これを考慮する必要があります。

一度、OrderedDict を作成すると、項目は、キーを介して利用可能です。:

MyNode = od['somekey']

したがって、既存のアイテムを削除するには:

del od['somekey']

既存の項目の値を変更する(位置は変更しない):

od['somekey'] = MyNewObject()

既存の項目の位置を変更するには:

t.link('somekey', '<otherkey')

訳者による蛇足

上記のt.linkのコードが訳者にはわかりずらかったので、以下のコードで確認してみた。

from markdown.odict import OrderedDict

od = OrderedDict()
od['one'] =  1           # The same as: od.add('one', 1, '_begin')
od['three'] = 3          # The same as: od.add('three', 3, '>one')
od['four'] = 4           # The same as: od.add('four', 4, '_end')

print od.items()

od.link('one', '<four')

print od.items()

実行結果

[('one', 1), ('three', 3), ('four', 4)]
[('three', 3), ('one', 1), ('four', 4)]

なるほど、説明どうり、登録されている項目の順番を変更できるのネ!

ちなみに、python-markdownを使わずともcollectionsモジュールをimportするととOrderedDictオブジェクトを使う事ができる。

from collections import OrderedDict

しかし、linkメソッドはサポートされていないようだ。

registerExtension

一部のextensionsでは、Markdownクラスを複数回実行する間に状態をリセットする必要があります。
たとえば、次のFootnotesextensionの使用を考えてみましょう。

md = markdown.Markdown(extensions=['footnotes'])
html1 = md.convert(text_with_footnote)
md.reset()
html2 = md.convert(text_without_footnote)

resetを呼び出さないと、最初のドキュメントの脚注定義は、クラスインスタンス内にまだ格納されているため、2番目のドキュメントに挿入されます。
したがって、Extensionクラスは、extensionの状態をリセットするresetメソッドを定義する必要があります(すなわちself.footnotes = {})。
ただし、多くのextensionはresetが必要ないため、resetは登録されているextensionでのみ呼び出されます。

エクステンションを登録するには、extendMarkdownメソッドからmd.registerExtensionを呼び出します。

def extendMarkdown(self, md, md_globals):
    md.registerExtension(self)
    # insert processors and patterns here

次に、resetがMarkdownインスタンスから呼び出されるたびに、登録された各extensionのresetメソッドも呼び出されます。
最初に初期化された後、登録された各extensionでresetが呼び出されることにも注意してください。
extensionのresetメソッドをオーバーライドしているときは、そのことを覚えておいてください。

コンフィグレーション設定

もしextensionでユーザーが変更したいパラメータを使用しているなら、これらのパラメータはあなたのmarkdown.extensions.Extensionクラスのself.configに次の形式で格納する必要があります:

class MyExtension(markdown.extensions.Extension):
    def __init__(self, **kwargs):
        self.config = {'option1' : ['value1', 'description1'],
                       'option2' : ['value2', 'description2'] }
        super(MyExtension, self).__init__(**kwargs)

このように実装すると、実行時にコンフィグレーションパラメータを上書きすることができます(つまり、superに対する呼び出し)。 例えば:

markdown.Markdown(extensions=[MyExtension(option1='other value'])

self.configにまだ定義されていないキーワードが渡された場合、KeyErrorが発生することに注意してください。

markdown.extensions.Extensionクラスおよびそのサブクラスには、コンフィグレーション設定の操作を支援するための次のメソッドがあります:

  • getConfig(key [, default]):

    あたえられたkeyに対する値もしくはkeyが存在しない場合は、defaultを返します。
    defaultが指定されていない場合は、空の文字列を返します。

  • getConfigs():

    辞書(dict)の中のすべてのキーと値のペアをかえします。

  • getConfigInfo():

    すべてのコンフィグレーションをタプルのリストとして返します。

  • setConfig(key, value):

    指定されたvalueを持つkeyをコンフィグレーションに設定します。 keyが未知の場合、KeyErrorが発生します。
    keyの前の値がブール値だった場合、valueはブール値に変換されます。
    以前のkeyの値がNoneの場合、valueNoneの場合を除いて、それはブール値に変換されます。
    keyの前の値が文字列である場合、変換は行われません。

  • setConfigs(items):

    キーと値のペアからなる辞書を指定して、複数のコンフィグレーションを設定します。

makeExtension

ライブラリリファレンスに記載されているように、extensionのインスタンスはMarkdownに直接渡すことができます。
実際、サードパーティのextensionで使われている好ましい方法です。

例えば:

import markdown
import myextension
myext = myextension.MyExtension(option='value')
md = markdown.Markdown(extensions=[myext])

(コマンドラインから、またはテンプレート内から)直接extensionをインポートすることが実際的でない時、その場合のために
Markdownはサードパーティのextensionを「名前付き」でもまた受け入れます。

extensionの"名前"は、Pythonのドット表記を使用してモジュールへのインポート可能なパスからなる文字列でなければなりません。
したがって、ユーザーにライブラリを提供していて、ライブラリ内にカスタムmarkdown extension を含める場合、そのextension は "mylib.mdext.myext"という名前になります。ここで、mylib/mdext/myext.pyにはextensionとmylibのデレクトリがPYTHONPATHに含まれています。

文字列には、コロンで区切られたクラスの名前を含めることもできます。したがって、このようなクラスをインポートするなら:

from path.to.module import SomeExtensionClass

名前付けされたextensionは次の文字列からなります:

"path.to.module:SomeExtensionClass"

この機能をサポートするために特別な操作を行う必要はありません。
extensionクラスをインポートすることができれば、ユーザーは上記の構文でextensionクラスを組み込むことができます。

上記の2つの方法は、モジュール内に複数のextensionが存在する場合に、多数のextensionを実装する必要がある場合に特に便利です。
ただし、ユーザーが文字列にクラス名を含める必要がない場合は、モジュールごとに1つのextensionのみを定義する必要があり、そのモジュールには、**kwargsを受け入れ、extensionのインスタンスを返すmakeExtensionというモジュールレベルの関数を含める必要があります 。

例えば:

class MyExtension(markdown.extensions.Extension)
    # Define extension here...

def makeExtension(**kwargs):
    return MyExtension(**kwargs)

Markdownにあなたのextensionの"名前"がドット表記文字列として渡されると、モジュールがインポートされ、makeExtension関数が呼び出されてextensionが開始されます。

ページのトップへ戻る