Extension API

《 初回公開:2022/03/26 , 最終更新:未 》

Python-MarkdownのためのExtensionを記述する

原文は

旧バージョンの記事は

【 目次 】

Python-Markdownには、拡張機能の作成者が独自のカスタム機能と構文をパーサーにプラグインするためのAPIが含まれています。
拡張機能は、パーサーの1つ以上のステージにパッチを適用します。

パーサーは、テキストをロードし、プリプロセッサーを適用し、ブロックプロセッサーとインラインプロセッサーからElementTreeオブジェクトを作成および構築し、ElementTreeオブジェクトをUnicodeテキストとしてレンダリングしてから、ポストプロセッサーを適用します。
拡張機能の作成を容易にするために提供されるクラスとヘルパーがあります。
APIの各部分については、以下のそれぞれのセクションで説明します。
さらに、the Tutorial on Writing Extensions(拡張機能の作成に関するチュートリアル)をウォークスルーできます。
利用可能な拡張機能とそのソースコードのいくつかを見てください。
いつものように、バグを報告したり、助けを求めたり、バグトラッカーで他のさまざまな問題について話し合ったりすることができます。

処理のフェーズ

Preprocessors

プリプロセッサは、Markdownパーサーに渡される前にソーステキストを変更します。
これは、不具合のある文字をクリーンアップしたり、パーサーが他の方法で誤った解釈をする可能性のある部分を、後の処理のために抽出したりするのに最適な場所です。
プリプロセッサはmarkdown.preprocessors.Preprocessorを継承し、ただ一つlinesパラメータを持ったrunメソッドを実装します。
このパラメータは、1行に1つずつ、Unicode文字列のリストとして保存されるソーステキスト全体です。
run(メソッド)は、処理されたUnicode文字列のリストを1行に1つずつ返す必要があります。

この簡単な例では、処理する前に「NO RENDER」の行を削除します。

from markdown.preprocessors import Preprocessor
import re

class NoRender(Preprocessor):
    """ Skip any line with words 'NO RENDER' in it. """
    def run(self, lines):
        new_lines = []
        for line in lines:
            m = re.search("NO RENDER", line)
            if not m:    
                # any line without NO RENDER is passed through
                new_lines.append(line)  
        return new_lines

使用状況

Markdownソースツリーの一部のプリプロセッサには次のものがあります。

Class 種類 説明
NormalizeWhiteSpace built-in タブを展開して空白を正規化し、行末の\rなどを修正
HtmlBlockPreprocessor built-in テキストからhtmlブロックを削除し、後で処理するために保存します
ReferencePreprocessor built-in テキストから参照定義を削除し、後で処理するために保存します
MetaPreprocessor extension ドキュメントの上部にあるメタデータを取り去り記録します
FootnotesPreprocessor extension テキストから脚注ブロックを削除し、後で処理するために保存します

Block Processors

ブロックプロセッサはテキストのブロックを解析し、ElementTreeに新しい要素を追加します。
空白行で他のテキストから分離されたテキストのブロックは、他のマークダウンとは異なる構文を持ち、異なる構造のツリーを生成する場合があります。
ブロックプロセッサは、コードの書式設定、数式のレイアウト、およびテーブルの処理に優れています。
ブロックプロセッサはmarkdown.blockprocessors.BlockProcessorを継承し、初期化時にmd.parserを渡され、testメソッドとrunメソッドの両方を実装します。

  • test(self, parent, block)は2つのパラメーター: parentは親のElementTree要素であり、blockは現在のブロックの単一の複数行のUnicode文字列です。
    testは、多くの場合正規表現の一致であり、ブロックプロセッサのrunメソッドを呼び出してそのブロックから処理を開始する必要がある場合、真の値を返します。
  • run(self, parent, blocks)には、testと同じparentパラメーターがあります。そしてblocksは、testに渡されたブロックから始まる、ドキュメント内の残りのすべてのブロックのリストです。
    runは、失敗を通知するためにFalse(Noneではない)を返す場合があります。これは、結局、ブロックを処理しなかったことを意味します。
    成功すると、runはblocksの前面から1つ以上のブロックをポップし、新しいノードをparentに接続することが期待されます。

ブロックプロセッサの作成は、他のプロセッサよりも複雑で柔軟性があり、ブロックのコンテンツの再帰的な解析を制御し、呼び出し全体で状態を管理します。
たとえば、インデントされたコードでは空白行が許可されているため、インラインコードプロセッサの2回目の呼び出しは、前の呼び出しで生成された要素ツリーに追加されます。
他のブロックプロセッサは、新しいテキストをブロックリストに挿入したり、それ自体の将来の呼び出しを通知したりする場合があります。
これらの複雑で面倒な記述をより扱いやすくするために、BlockProcessorの親クラスによって3つの便利な関数が提供されています。

  • lastChild(parent)は、指定された要素の最後の子要素を、もし子要素が無いならはNoneを返します。
  • detab(text)は、空白以外の行のインデントがより少なくなるまで、指定された複数行のテキスト文字列の各行の前から1レベルのインデント(デフォルトでは4つのスペース)を削除します。
  • looseDetab(text, level)は、テキストの各行の前から複数レベルのインデントを削除しますが、インデントのない行には影響しません。

また、BlockProcessorは、フィールドself.tab_length - タブ長(デフォルトは4)、および現在のBlockParserインスタンスであるself.parserを提供します。

BlockParser

BlockParserは、BlockProcessorと混同しないようでください、登録されているすべてのblock processorsを循環するためにMarkdownによって使用されるクラスです。
独自のインスタンスを作成する必要はありません。 代わりにself.parserを使用してください。
BlockParser インスタンスは、現在の状態の文字列のスタックを提供しますが、これはprocessorがself.parser.set(state)でpushしたり、self.parser.reset()でpopしたり、もしくはself.parser.isstate(state)で最上位の状態をチェックしたりできます。
コードで状態をpop,pushする事を確かめて下さい。
BlockParserインスタンスは、再帰的に呼び出すこともできます。つまり、block processor内からブロックを処理するために呼び出すこともできます。
3つのメソッドがあります:

  • parseDocument(lines)は、行のリストを解析します。各行は1行のUnicode文字列であり、完全なElementTreeを返します。
  • parseChunk(parent, text) は、単一の複数行、場合によっては複数ブロックのUnicode文字列テキストを解析し、結果のツリーをparentにアタッチします。
  • parseBlocks(parent, blocks) は、ブロックのリストを取得します。各ブロックは、空白行のない複数行のUnicode文字列であり、結果のツリーをparentにアタッチします。

見方を変えると、MarkdownはparseDocumentを呼び出し、parseChunkを呼び出し、parseBlocksを呼び出し、block processorを呼び出します。これにより、これらのルーチンの1つが呼び出される場合があります。

この例では、重要な段落にボーダー(境界)を与える事によって呼び出しています。
前後の感嘆符(!)のフェンスライン(垣根の行)を探し、フェンスで囲まれたブロックを新しいスタイルのdiv内にレンダリングします。
もし、終端のフェンスライン(終わりの垣根を示す行)が見つからなければ何もしません。
ほとんどのblock processorと同様に、私たちのコードは他の例よりも長くなっています。

def test_block_processor():
    class BoxBlockProcessor(BlockProcessor):
        RE_FENCE_START = r'^ *!{3,} *\n' # start line, e.g., `   !!!! `
        RE_FENCE_END = r'\n *!{3,}\s*$'  # last non-blank line, e.g, '!!!\n  \n\n'

        def test(self, parent, block):
            return re.match(self.RE_FENCE_START, block)

        def run(self, parent, blocks):
            original_block = blocks[0]
            blocks[0] = re.sub(self.RE_FENCE_START, '', blocks[0])

            # Find block with ending fence
            for block_num, block in enumerate(blocks):
                if re.search(self.RE_FENCE_END, block):
                    # remove fence
                    blocks[block_num] = re.sub(self.RE_FENCE_END, '', block)
                    # render fenced area inside a new div
                    e = etree.SubElement(parent, 'div')
                    e.set('style', 'display: inline-block; border: 1px solid red;')
                    self.parser.parseBlocks(e, blocks[0:block_num + 1])
                    # remove used blocks
                    for i in range(0, block_num + 1):
                        blocks.pop(0)
                    return True  # or could have had no return statement
            # No closing marker!  Restore and do nothing
            blocks[0] = original_block
            return False  # equivalent to our test() routine returning False

    class BoxExtension(Extension):
        def extendMarkdown(self, md):
            md.parser.blockprocessors.register(BoxBlockProcessor(md.parser), 'box', 175)

次の入力例から始めます。

A regular paragraph of text.

!!!!!
First paragraph of wrapped text.

Second Paragraph of **wrapped** text.
!!!!!

Another regular paragraph of text.

フェンスで囲まれたテキストは、2つの子を持つ1つのノードをツリーに追加します。

  • style属性付きのdiv。
    それは<div style="display: inline-block; border: 1px solid red;">...</div>としてレンダリングされます。
    • テキスト付のpFirst paragraph of wrapped text.
    • テキスト付のpSecond Paragraph of **wrapped** text.
      <strong>タグへの変換は、インラインプロセッサの実行時に発生します。これは、すべてのブロックプロセッサが完了した後に発生します。

出力例は次のように表示されます。

A regular paragraph of text.

First paragraph of wrapped text.

Second Paragraph of wrapped text.

Another regular paragraph of text.

使用状況

Markdownソースツリーの一部のブロックプロセッサには、次のものがあります。

クラス 種類 説明
HashHeaderProcessor built-in ブロックを分割する可能性のあるタイトルハッシュ(#)
HRProcessor built-in 水平線、例:---
OListProcessor built-in 順序付きリスト; 複雑で使用状態
Admonition extension 新しいdivで各警告をレンダリングします

Tree processors

ツリープロセッサは、ブロックプロセッサによって作成されたツリーを操作します。
まったく新しいElementTreeオブジェクトを作成することもできます。
これは、要約を作成したり、収集した参照を追加したり、直前の調整を行ったりするのに最適な場所です。
ツリープロセッサは、markdown.treeprocessors.Treeprocessorから継承する必要があります(大文字と小文字の区別に注意してください)。
ツリープロセッサは、ただ一つの引数rootを取るrunメソッドを実装する必要があります。
ほとんどの場合、rootはxml.etree.ElementTree.Elementインスタンスになります。 ただし、まれに、他のタイプのElementTreeオブジェクトである可能性があります。
runメソッドはNoneを返す場合があります。この場合、(変更された可能性のある)元のルートオブジェクトが使用されるか、まったく新しいElementオブジェクトが返され、既存のルートオブジェクトとそのすべての子が置き換えられます。
一般に、rootを所定の位置で変更し、Noneを返すことをお勧めします。これにより、メモリ内にドキュメントツリー全体の複数のコピーが作成されるのを回避できます。
ElementTreeの操作の詳細については、以下の「ElementTreeの操作」を参照してください。

仮の例

from markdown.treeprocessors import Treeprocessor

class MyTreeprocessor(Treeprocessor):
    def run(self, root):
        root.text = 'modified content'
        # No return statement is same as `return None`

使用状況

コアのInlineProcessorクラスはツリープロセッサです。
ツリーを渡り歩き、パターンを照合し、照合時にノードを分割して作成します。

Markdownのソースツリーの追加のツリープロセッサには、次のものが含まれます。

クラス 種類 説明
PrettifyTreeprocessor built-in HTMLドキュメントに改行を追加する
TocTreeprocessor extension 完成したツリーから目次を作成します
FootnoteTreeprocessor extension ドキュメントの最後に脚注用のdivを作成します
FootnotePostTreeprocessor extension FootnoteTreeprocessorによって作成された重複したdivを修正します

Inline Processors

以前はインラインパターンと呼ばれていたインラインプロセッサは、一致したパターンを新しい要素ツリーノードに置き換えることにより、**強調**などのフォーマットを追加するために使用されます。
インラインタグの新しい構文を追加するのに最適です。
インラインプロセッサコードは、多くの場合非常に短いものです。
インラインプロセッサはInlineProcessorを継承し、初期化され、handleMatchを実装します。

  • __init__(self, pattern, md=None)は、継承されたコンストラクターです。
    独自に実装する必要はありません。
    • patternは、handleMatchメソッドが呼び出されるためにコードブロックと一致する必要がある正規表現文字列です。
    • オプションのパラメーターであるmdは、markdown.Markdownのインスタンスへのポインターであり、InlineProcessorインスタンスでself.mdとして使用できます。
  • handleMatch(self, m, data)は、すべてのInlineProcessorサブクラスに実装する必要があります。
    • mは、__ init__に渡されたパターンによって検出された正規表現matchオブジェクトです。
    • dataは、パターンの周囲のテキストのブロック全体を含む単一の複数行のUnicode文字列です。
      ブロックは、空白行で区切られたテキストです。
    • 提供された一致が拒否されたことを示す(None, None, None)、または一致が正常に処理された場合は(el、start、end)のいずれかを返します。
      成功すると、elはツリーに追加される要素であり、startとendは、パターンによって「摂取され,使われた」データのインデックスです。
      「摂取された」スパンは、プレースホルダーに置き換えられます。
      同じインラインプロセッサが同じブロックで複数回呼び出される場合があります。

インラインプロセッサは、望ましくない祖先のリストまたはタプルのいずれかであるプロパティANCESTOR_EXCLUDESを定義できます。
プロセッサは、コンテンツがリストされたタグ名の1つの子孫になる場合はスキップされます。

便利なスクラスたち

InlineProcessorの便利なサブクラスは、一般的な操作のために提供されています。

  • SimpleTextInlineProcessorはmatchのgroup(1)のテキストを返します。
  • SubstituteTagInlineProcessorは、SubstituteTagInlineProcessor(pattern, tag)として初期化されます。 パターンが一致するたびに新しい要素タグを返します。
  • SimpleTagInlineProcessorは、SimpleTagInlineProcessor(pattern, tag)として初期化されます。 matchのgroup(2)のテキストフィールドを持つ要素タグを返します。

この例では、--strike--<del>strike</del>に変更します。

from markdown.inlinepatterns import InlineProcessor
from markdown.extensions import Extension
import xml.etree.ElementTree as etree


class DelInlineProcessor(InlineProcessor):
    def handleMatch(self, m, data):
        el = etree.Element('del')
        el.text = m.group(1)
        return el, m.start(0), m.end(0)

class DelExtension(Extension):
    def extendMarkdown(self, md):
        DEL_PATTERN = r'--(.*?)--'  # like --del--
        md.inlinePatterns.register(DelInlineProcessor(DEL_PATTERN, md), 'del', 175)

次の入力例を使用します。

First line of the block.
This is --strike one--.
This is --strike two--.
End of the block.

出力例は次のように表示されます。

First line of the block. This is strike one. This is strike two. End of the block.

  • handleMatchへの最初の呼び出しで
    • mは--strikeone--にマッチします
    • データは文字列になります:First line of the block.\nThis is --strike one--.\nThis is --strike two--.\nEnd of the block.

matchが成功したため、返された開始と終了の間の領域がプレースホルダートークンに置き換えられ、新しい要素がツリーに追加されます。

  • handleMatchへの2回目の呼び出し
    • mは--strike two--にマッチします
    • データは文字列First line of the block.\nThis is klzzwxh:0000.\nThis is --strike two--.\nEnd of the block.になります。

プレースホルダートークンklzzwxh:0000に注意してください。
これにより、個々の要素に含まれるテキストだけでなく、ブロック全体に対して正規表現を実行できます。
プレースホルダーは、後でパーサーによって実際の要素と交換されます。
実際には、上記のインラインプロセッサを作成する必要はありません。
実際のところ、その例はあまりDRY(Don’t Repeat Yourself)ではありません。

愚鈍人の独り言

DRY原則とは

strongテキストのパターンは、strong要素を作成することを除いて、ほとんど同じです。
したがって、Markdownは、いくつかの一般的な機能を提供できるいくつかの汎用InlineProcessorサブクラスを提供します。
たとえば、以下に示すように、SimpleTagInlineProcessorクラスのインスタンスを使用してstrikeを実装できます。
markdown.inlinepatternsにあるInlineProcessorサブクラスを自由に使用または拡張してください。

from markdown.inlinepatterns import SimpleTagInlineProcessor
from markdown.extensions import Extension

class DelExtension(Extension):
    def extendMarkdown(self, md):
        md.inlinePatterns.register(SimpleTagInlineProcessor(r'()--(.*?)--', 'del'), 'del', 175)

使用状況

いくつかの便利な機能と他の例を次に示します。

クラス 種類 説明
AsteriskProcessor built-in アスタリスク内のstrongとemをハンドリングするためのEmphasis processor
AbbrInlineProcessor extension プリプロセッサによって登録された略記にタグを適用します
WikiLinksInlineProcessor extension wikiへのリンク[[article names]]がメタデータで与えられます
FootnoteInlineProcessor extension テキスト内の脚注を下部の脚注のdivへのリンクに置き換えます

パターン

バージョン3.0では、新しい、より柔軟なインラインプロセッサ、markdown.inlinepatterns.InlineProcessorが追加されました。
markdown.inlinepatterns.Patternまたはその子の1つから継承する元のインラインパターンは引き続きサポートされますが、ユーザーは移行することをお勧めします。

新しいInlineProcessorとの比較

  1. インラインプロセッサはブロック全体と一致する必要がなくなったため、正規表現はr'^(.*?)'で始まり、r'(.*?)%'で終わる必要がなくなりました。
    これはより速く実行されます。
    返されるmatchオブジェクトには、パターンで明示的に一致するもののみが含まれ、拡張パターングループはm.group(1)で始まるようになりました。
  2. handleMatchメソッドは、指定されたパターンと一致するものだけでなく、分析中のブロック全体を示すdataと呼ばれる追加の入力を受け取るようになりました。
    このメソッドは、戻り要素が置き換えているデータ(通常はm.start(0)とm.end(0))に関連する要素とインデックスを返すようになりました。
    境界がNoneとして返される場合、一致は行われなかったと見なされ、データ内で何も変更されません。

これにより、正規表現で処理できるよりも複雑な構造を処理できます。たとえば、ネストされた角かっこを一致させたり、プロセッサが「使われ取り去られた」スパンを明示的に制御したりできます。

Inline Patterns

インラインパターンは、*emphasis*[links](http://example.com)などのMarkdownのインラインHTML要素構文を実装できます。
パターンオブジェクトは、markdown.inlinepatterns.Patternまたはその子の1つから継承するクラスのインスタンスである必要があります。
各パターンオブジェクトは単一の正規表現を使用し、次のメソッドが必要です。

  • getCompiledRegExp():
    コンパイルされた正規表現を返します。
  • handleMatch(m):
    matchオブジェクトを受け入れ、プレーンなUnicode文字列のElementTree要素を返します。

インラインパターンは、プロパティANCESTOR_EXCLUDESを定義できます。これは、望ましくない祖先のリストまたはタプルのいずれかです。
リストされたタグ名のいずれかの子孫になるコンテンツになる場合、パターンはスキップされます。
getCompiledRegExpによって返される正規表現は、ブロック全体をキャプチャする必要があることに注意してください。
したがって、それらはすべてr'^(.*?)' で始まり、r'(.*?)!'で終わる必要があります。
パターンで提供されるデフォルトのgetCompiledRegExp()メソッドを使用する場合、それなしで正規表現を渡すことができ、getCompiledRegExpは式をラップし、re.DOTALLフラグとre.UNICODEフラグを設定します。
これは、m.group(1)がパターンの前のすべてにマッチするため、matchの最初のグループがm.group(2)になることを意味します。
例として、この単純化されたemphasisパターンを考えてみましょう。

from markdown.inlinepatterns import Pattern
import xml.etree.ElementTree as etree

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

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

# an oversimplified regex
MYPATTERN = r'\*([^*]+)\*'
# pass in pattern and create instance
emphasis = EmphasisPattern(MYPATTERN)

Postprocessors

ElementTreeが文字列にシリアル化された後、ポストプロセッサはドキュメントを変更します。
ポストプロセッサを使用して、出力直前のテキストを処理する必要があります。
通常、これらは、プリプロセッサで抽出されたセクションの追加、送信エンコーディングの修正、またはドキュメント全体のラップに使用されます。
ポストプロセッサはmarkdown.postprocessors.Postprocessorを継承し、単一のパラメータテキスト(HTMLドキュメント全体を単一のUnicode文字列)を受け取るrunメソッドを実装します。
runは、出力の準備ができている単一のUnicode文字列を返す必要があります。
プリプロセッサは行のリストを使用し、ポストプロセッサは単一の複数行の文字列を使用することに注意してください。

これは、出力を生のhtmlを表示する1つの大きなページに変更する簡単な例です。

from markdown.postprocessors import Postprocessor import re

class ShowActualHtmlPostprocesor(Postprocessor):
    """ Wrap entire output in <pre> tags as a diagnostic. """
    def run(self, text):
        return '<pre>\n' + re.sub('<', '&lt;', text) + '</pre>\n'

使用状況

Markdownソースツリーの一部のポストプロセッサには次のものがあります。

クラス 種類 説明
raw_html built-in HTMLBlockPreprocessorによって保存されたhtmlStashから生のhtmlを復元し、ハイライターをコード化する
amp_substitute built-in リンクで使われるアンパサンドを&に置き換えます
unescape built-in リンクで使われる一部のエスケープ文字を整数から変換し直します。
FootnotePostProcessor extension 他のステージでセットされたとおり脚注のプレースホルダーをhtmlエンティティに置き換えます。

ElementTreeの操作

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

まず、ElementTreeモジュールをインポートします。

import xml.etree.ElementTree as etree

要素に挿入されたテキストを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>要素にクラス属性を追加する次の例について考えてみます。

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のドキュメントを参照してください。

Raw(生の)HTMLの操作

場合によっては、拡張機能がサードパーティのライブラリを呼び出す必要があります。このライブラリは、変更せずにドキュメントに挿入する必要のある、事前に作成された生のHTMLの文字列を返します。
生の文字列は、ElementTreeオブジェクトに変換するのではなく、htmlStashインスタンスを使用して後で取得するために隠しておくことができます。

愚鈍人の独り言

htmlStash ⇒ stashは隠し場所の事

self.md.htmlStash.store()に渡された生の文字列(生のHTMLである場合とそうでない場合があります)がstashに保存され、代わりにツリーに挿入されるプレースホルダー文字列が返されます。
ツリーがシリアル化された後、ポストプロセッサはプレースホルダーを生の文字列に置き換えます。
これにより、後続の処理ステップでHTMLデータが変更されるのを防ぎます。
例えば。

html = "<p>This is some <em>raw</em> HTML data</p>"
el = etree.Element("div")
el.text = self.md.htmlStash.store(html)

グローバルhtmlStashインスタンスをプロセッサから利用できるようにするには、markdown.MarkdownインスタンスをextendMarkdownからプロセッサに渡す必要があり、self.md.htmlStashとして利用できるようになります。

コードをMarkdownに統合する

拡張機能のさまざまな部分を構築したら、それらについてMarkdownに通知し、それらが適切な順序で実行されることを確認する必要があります。
Markdownは、拡張機能ごとに拡張機能インスタンスを受け入れます。
したがって、markdown.extensions.Extensionを拡張し、extendMarkdownメソッドをオーバーライドするクラスを定義する必要があります。
このクラス内で、拡張機能の構成オプションを管理し、さまざまなプロセッサーとパターンをMarkdownインスタンスに接続します。

さまざまなプロセッサとパターンの順序が重要であることに注意することが重要です。
たとえば、http://... のリンクを<a>要素に置き換えてから、インラインHTMLを処理しようとすると、混乱が発生します。
したがって、さまざまなタイプのプロセッサとパターンが、レジストリのmarkdown.Markdownクラスのインスタンス内に格納されます。
Extensionクラスは、これらのレジストリを適切に操作する必要があります。
プロセッサとパターンのインスタンスを適切な優先度で登録したり、組み込みインスタンスの登録を解除したり、組み込みインスタンスを独自のインスタンスに置き換えたりすることができます。

extendMarkdown

markdown.extensions.ExtensionクラスのextendMarkdownメソッドは、次の1つの引数を受け入れます。

  • md: markdown.Markdownクラスのインスタンスへのポインター。
    これを使用して、プロセッサとパターンのレジストリにアクセスする必要があります。
    これらは、次の属性の下にあります。

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

    markdown.Markdownインスタンスでアクセスしたい他のいくつかのものは次のとおりです。

    • md.htmlStash
    • md.output_formats
    • md.set_output_format()
    • md.output_format
    • md.serializer
    • md.registerExtension()
    • md.tab_length
    • md.block_level_elements
    • md.isBlockLevel()

警告

上記のアイテムにアクセスすると、理論的には、さまざまなmonkey_patchingテクニックを使用して何でも変更することができます。
ただし、Markdownの文書化されていないさまざまな部分が予告なしに変更される可能性があり、monkey_patchesが新しいリリースで破損する可能性があることに注意してください。
したがって、実際に行うべきことは、プロセッサとパターンをMarkdownパイプラインに挿入することです。
警告されていると考えてください!

愚鈍人の独り言

monkey_patching

簡単な例:

from markdown.extensions import Extension

class MyExtension(Extension):
    def extendMarkdown(self, md):
        # Register instance of 'mypattern' with a priority of 175
        md.inlinePatterns.register(MyPattern(md), 'mypattern', 175)

registerExtension

一部の拡張機能では、markdown.Markdownクラスを複数回実行する間に状態をリセットする必要がある場合があります。
たとえば、Footnotes拡張機能の次の使用を検討してください

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

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

拡張機能を登録するには、extendMarkdownメソッド内からmd.registerExtensionを呼び出します。

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

その後、markdown.Markdownインスタンスでresetが呼び出されるたびに、登録されている各拡張機能のresetメソッドも呼び出されます。
また、最初に初期化された後、登録された各拡張機能でリセットが呼び出されることにも注意してください。
拡張機能のリセットメソッドをオーバーライドするときは、このことに注意してください。

構成設定

拡張機能がユーザーが変更したいパラメータを使用する場合、それらのパラメータは次の形式で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]):
    指定されたキーに保存されている値を返します。キーが存在しない場合はデフォルトを返します。
    設定されていない場合、デフォルトは空の文字列を返します。
  • getConfigs():
    すべてのキー/値ペアのdictを返します。
  • getConfigInfo():
    すべての構成の説明をタプルのリストとして返します。
  • setConfig(key, value):
    指定された値でキーの構成設定を設定します。
    キーが不明な場合、KeyErrorが発生します。
    キーの前の値がブール値だった場合、値はブール値に変換されます。
    キーの前の値がNoneの場合、Noneの場合を除いて、値はブール値に変換されます。
    keyの前の値が文字列の場合、変換は行われません。
  • setConfigs(items):
    キー/値ペアの指示を指定して、複数の構成設定を設定します。

Extensionのネーミング

ライブラリリファレンスに記載されているように、拡張機能のインスタンスを直接markdown.Markdownに渡すことができます。
実際、これはサードパーティの拡張機能を使用するための推奨される方法です。
例えば:

import markdown
from path.to.module import MyExtension
md = markdown.Markdown(extensions=[MyExtension(option='value')])

ただし、Markdownは、拡張機能を直接(コマンドラインまたはテンプレート内から)インポートすることが実際的でない場合に備えて、「名前付き」のサードパーティ拡張機能も受け入れます。
「名前」は、登録されたエントリポイント、またはPythonのドット表記を使用した文字列のいずれかです。

エントリーポイント

エントリポイントは、Pythonパッケージのsetup.pyスクリプトで定義されます。 スクリプトは、エントリポイントをサポートするためにsetuptoolsを使用する必要があります。
Python-Markdown拡張機能をmarkdown.extensionsグループに割り当てる必要があります。
エントリポイントの定義は次のようになります。

from setuptools import setup

setup(
    # ...
    entry_points={
        'markdown.extensions': ['myextension = path.to.module:MyExtension']
    }
)

ユーザーが上記のスクリプトを使用して拡張機能をインストールした後、次のようにmyextension文字列名を使用して拡張機能を呼び出すことができます。

markdown.markdown(text, extensions=['myextension'])

同じグループ内の2つ以上のエントリポイントに同じ名前が割り当てられている場合、Python-Markdownは最初に見つかったエントリポイントのみを使用し、他のすべてのエントリポイントを無視することに注意してください。
したがって、extensionには必ず一意の名前を付けてください。

setup.pyスクリプトの記述の詳細については、プロジェクトのパッケージ化と配布に関するPythonのドキュメントを参照してください。

ドット表記

拡張機能にエントリポイントが登録されていない場合は、代わりにPythonのドット表記を使用できます。
拡張機能は、PythonモジュールとしてPYTHONPATHにインストールする必要があります。
通常、名前にはクラスを指定する必要があります。
クラスは名前の最後にあり、モジュールからコロンで区切る必要があります。
したがって、次のようにクラスをインポートする場合:

from path.to.module import MyExtension

次に、拡張機能を次のようにロードできます。

markdown.markdown(text, extensions=['path.to.module:MyExtension'])

この機能をサポートするために特別なことをする必要はありません。
拡張クラスをインポートできる限り、ユーザーは上記の構文で拡張クラスを含めることができます。
上記の2つの方法は、モジュール内に複数の拡張機能が存在する多数の拡張機能を実装する必要がある場合に特に役立ちます。
ただし、ユーザーが文字列にクラス名を含める必要がない場合は、モジュールごとに1つの拡張機能のみを定義する必要があり、そのモジュールには、**kwargsを受け入れて拡張機能インスタンスを返すmakeExtensionというモジュールレベルの関数が含まれている必要があります。

例えば:

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

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

markdown.Markdownに、拡張機能の「名前」がクラス(path.to.moduleなど)を含まないドット表記文字列として渡されると、モジュールがインポートされ、makeExtension関数が呼び出されて拡張機能が開始されます。

レジストリ

markdown.util.Registryクラスは、Markdownがさまざまなプロセッサとパターンの処理順序を決定するために内部的に使用する、優先度でソートされたレジストリです。
Registryインスタンスは、レジストリのデータを変更するための2つのpublicメソッドを提供します。登録と登録解除です。
レジスターを使用してアイテムを追加し、登録解除してアイテムを削除します。
詳細については、各メソッドを参照してください。
アイテムを登録するときは、「名前」と「優先度」を指定する必要があります。
すべてのアイテムは、「priority」パラメーターの値によって自動的にソートされ、値が最も高いアイテムが最初に処理されます。
「名前」は、アイテムの削除(登録解除)および取得に使用されます。
Registryインスタンスは、データを読み取るときのリスト(順序を維持する)のようなものです。
アイテムを繰り返し処理し、アイテムを取得して、すべてのアイテムのカウント(長さ)を取得できます。
レジストリにアイテムが含まれていることを確認することもできます。
アイテムを取得するときは、アイテムのインデックスまたは文字列ベースの「名前」のいずれかを使用できます。
例えば:

registry = Registry()
registry.register(SomeItem(), 'itemname', 20)
# Get the item by index
item = registry[0]
# Get the item by name
item = registry['itemname']

レジストリにアイテムが含まれていることを確認するときは、文字列ベースの「名前」または実際のアイテムへの参照のいずれかを使用できます。 例えば:

someitem = SomeItem()
registry.register(someitem, 'itemname', 20)
# Contains the name
assert 'itemname' in registry
# Contains the item instance
assert someitem in registry

markdown.util.Registryには次のメソッドがあります。

Registry.register(self, item, name, priority)

指定された名前と優先度でアイテムをレジストリに追加します。

パラメーター:

  • item: 登録されるアイテム。
  • name: アイテムを参照するために使用される文字列。
  • priority: すべてのアイテムに対してソートするために使用される整数または浮動小数点。

すでに存在する「名前」で登録されている場合は、既存のアイテムが新しいアイテムに置き換えられます。
古いアイテムは失われ、回復する方法がないため、慎重におこなって下さい。
新しいアイテムは優先度に従って並べ替えられ、古いアイテムの位置は保持されません。

Registry.deregister(self, name, strict=True)

レジストリからアイテムを削除します。 strict = Falseを設定すると無視されます。

Registry.get_index_for_name(self, name)

指定された名前のインデックスを返します。

ページのトップへ戻る