Pygments - formatterコンポーネント

初回公開:2018/10/14
最終更新:未

【 目次 】

formatterクラス

formatterクラスは次のクラスから派生してる。

class pygments.formatter.Formatter(**options)
lexerクラスと同様に、このクラスのコンストラクタはオプションを処理してから、基本クラスの__init __()を呼び出さなければならない。
基本クラスFormatterクラスはstyle, full そして titleオプションを認識する。
その派生クラスのformatterクラスがこれらを使用するかどうかは、formatterによって異なる。

すべてのformatterは以下のオプションをサポートする。

encoding
"utf-8"のようなエンコーディング名でなければならない。
これは、トークン文字列(Unicode文字列)を出力のバイト文字列に変換するために使用されます(デフォルト:None)。
fullオプションが指定されている場合は、ドキュメントフォーマットに適したエンコーディング宣言(例えば、HTMLのメタ コンテンツタイプディレクティブまたは LaTeXのinputencパッケージの呼び出し)で記述されます。
これが""またはNoneの場合、Unicode文字列は出力ファイルに書き込まれます。
ほとんどのファイルライクなオブジェクトはサポートしません。
たとえば、pygments.highlight()は、outfile引数を指定しないで呼び出された場合Unicode文字列を返し、Unicode 引数をwrite()にサポートするStringIO.StringIOオブジェクトを使用するため、エンコーディング がNoneに設定されたformatterを返します。
通常のファイルオブジェクトを使用すると動作しません。
outencoding
コマンドラインからPygmentsを使用すると、指定されたエンコードオプションがlexerとformatterに渡されます。
たとえば、入力エンコーディングを"guess"(推測)に設定する場合など、これは望ましいことではありません。
したがって、outencodingが導入された場合、formatterのエンコーディングをオーバーライドします。

formatterクラスのメソッド

get_style_defs(arg='')
このメソッドは、後続のハイライトテキストを定義するための文や宣言(例えばHTMLFormatterにおけるCSSクラス)を返す必要がある。

オプション引数argは、世代を変更するために使用でき、フォーマッタに依存します(コマンドラインで与えることができるため、標準化されています)。

このメソッドは、コマンドラインオプション-Sによって呼び出される。
argは-aオプションによってあたえられ、HTMLではCSSのクラス属性を指定するために使われる。

format(tokensource, outfile)
このメソッドは、tokenソースのiterableからトークンをフォーマットしなければならない、そしてフォーマットされたバージョンをファイルオブジェクトoutfileに書き込む。
Formatterのオプションは、どのように正しくトークンを変換するかをコントロールできる。

formatterクラスの属性

フォーマッタには、組み込みのルックアップメカニズムで使用される次の属性が必要である。

name
人が読める形式で書式のフルネーム。
aliases
get_formatter_by_name()を使用するなど、リストからフォーマッタを検索するために使用できる短い一意の識別子のリスト。
filenames
このフォーマッタが出力を生成できるファイル名と一致するfnmatchパターンのリスト。
このリストのパターンは、すべてのフォーマッタ間で一意である必要があります。

HtmlFormatterのこれらの属性を出力してみると

from pygments.formatters import HtmlFormatter

print(HtmlFormatter.name)
print(HtmlFormatter.aliases)
print(HtmlFormatter.filenames)

実行結果

HTML
['html']
['*.html', '*.htm']

HtmlFormatter

おそらくほとんどの場合、HtmlFormatter以外のフォーマッターを使用する事は無いと思われる。
そのためここでは、HtmlFormatterにしぼってみていく。

HtmlFormatterクラス

上記の公式ドキュメントにはHtmlFormatterは以下のように記述されている。

class HtmlFormatter
  Short names:  html
  Filenames:  *.html, *.htm

トークンはHTML4として<div>タグで囲まれた(ラップされた)<pre>タグ内の <span>タグにフォーマットされる。
<div>内のCSSクラスはCssClassオプションによって設定される。

linenosオプションを設定するとtableとして表示される。
以下の例のように'<TABLE>'タグの内側にさらに'<PRE>'タグにラップされ、片方は行番号を含み,もう一方はコードを含んだ、1 つの行と 2 つのセルで構成される。

<div class="highlight" >
<table><tr>
  <td class="linenos" title="click to toggle"
    onclick="with (this.firstChild.style)
             { display = (display == '') ? 'none' : '' }">
    <pre>1
    2</pre>
  </td>
  <td class="code">
    <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar):
      <span class="Ke">pass</span>
    </pre>
  </td>
</tr></table></div>

(明瞭さを高めるために空白が追加されています)。

ラップは、nowrapオプションを使用して無効にすることができます。

hl_linesオプションを使用して行のリストを指定して、これらの行をハイライト表示することができます。

fullオプションはスタイル定義を<style>タグの内部、あるいはcssfileオプションが指定されていれば分離されたファイルに含んだ、完全なHTML 4ドキュメントを出力する。

tagsfileがctagsのインデックスファイルのパスに設定されているなら、それは名前からそれらに定義されたハイパーリンクを生成するために使かわれる。
lineanchorsを有効にし、-nオプションを指定してctagsを実行して、これを機能させる必要があります。

この機能を使用するために、PyPIからpython-ctagsモジュールをインストールする必要がある。
そうでなければ、RuntimeErrorが発生してしまうだろう。

HtmlFormatterのget_style_defs(arg=’‘)メソッドは、フォーマッタによって使われるCSSクラスのCSS規則を含む文字列を返えす。
引数argは、クラスの先頭に追加されるCSSセレクタを指定するために使用できる。
formatter.get_style_defs( 'td .code')を呼び出すと、次のCSSクラスが生成される。

td .code .kw { font-weight: bold; color: #00FF00 }
td .code .cm { color: #999999 }
...

リストまたはタプルをget_style_defs()メソッドに渡して、トークンのプレフィックスを複数要求することもできる。

div.syntax pre .kw,
pre.syntax .kw { font-weight: bold; color: #00FF00 }
div.syntax pre .cm,
pre.syntax .cm { color: #999999 }
...

HtmlFormatterの受け入れ可能な追加オプション

HtmlFormatterには以下のような受け入れ可能な追加オプションが指定できる。

nowrap
Trueに設定すると、トークンをすべてwrapしない。
たとえ<pre>タグ内でさえもwrapしなくなる。、
これにより他のほとんどのオプションは無効になります(デフォルト:False)。

wrapしないとはどういう意味なんだろう?

てっきりcssのnowrap属性なのかと思ったが。
cssのnowrapには2種類の機能があり。

そもそも英語のwrapの意味は?

実際に以下のコードでnowrap属性がTrueとFalseの場合の出力されるHTMLコードの違いを比較してみると。

from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter

code = 'print("Hello World")'
lexer = get_lexer_by_name("python")

with open("wrap.html", 'w') as outfile:
    highlight(code, lexer, HtmlFormatter(nowrap=False),outfile)

formatter = HtmlFormatter(nowrap=True)
with open("nowrap.html", 'w') as outfile:
    highlight(code, lexer, HtmlFormatter(nowrap=True),outfile)

nowrap属性がTrueの場合、コードの前後の以下のdiv,pre,spanタグが取り除かれてしまうようだ。

<div class="highlight"><pre><span></span>...</pre></div>

でも、このnowrapオプションってどんな場合に必要なのだろう?

full
フォーマッタに "完全な"ドキュメント、つまり完全な自己完結型ドキュメントを出力するように指示します(デフォルト:False)。
これは、言い換えるとstyles指定などを外部のcssファイルを使わずにhtmlファイルの内部に記述して、htmlのヘッダーも含んだhtmlファイルだけで完結できる出力をおこなうという事になる。

サンプルコード

title
fullオプションをTrueに指定した場合に出力されるhtmlのheadのtitleタグ内のページタイトルを指定(デフォルト:「」)。

サンプルコード

style
使用するスタイルは、文字列かStyleサブクラスです(デフォルト: 'default')。
このオプションは、cssfile およびnoclobber_cssfileオプションが指定され、cssfileで指定されたファイルが存在する場合は 無効です。

サンプルコード

noclasses
trueに設定すると、トークン<span>タグはCSSクラスを使用せず、インラインスタイルを使用します。
コードサイズが大きい場合は、出力サイズがかなり大きくなるため、これはお勧めできません(デフォルト:False)。
classprefix
トークン型は比較的短いクラス名を使用するため、独自のクラス名と衝突する可能性があります。
この場合、classprefixオプションを使用して、すべてのPygmentsが生成したトークンタイプのCSSクラス名の前に文字列を付けることができます。
このオプションはget_style_defs()の出力にも影響することに注意してください。

サンプルコード

cssclass
コードをラッピングしている<div>タグのCSSクラス(デフォルト:'highlight')を指定。
このオプションを設定すると、get_style_defs()のデフォルトのセレクタ がこのクラスになります。
table'行番号を選択すると、ラッピングテーブルにはこの文字列のCSSクラスと'table'が追加されます。
デフォルトはそれに応じて'highlighttable'になります。

サンプルコード

cssstyles
ラップする<div>タグのインラインCSSスタイル(デフォルト:'')
prestyles
<pre>タグのインラインCSSスタイル(デフォルト:'')。

サンプルコード

cssfile
fullオプションがtrueで、このオプションが指定した場合、cssのstyleを出力する外部ファイルの名前を指定しなければなりません。
ファイル名に絶対パスが含まれていない場合、ファイルのパスは、メイン出力ファイルのパス(相対パスが見つかる可能性がある場合)との相対パスであるとみなされます。
スタイルシートはHTMLファイルの代わりにこのファイルに書き込まれます。

サンプルコード

noclobber_cssfile
cssfileが与えられ、指定したファイルが存在している場合、CSSファイルは上書きされません。
これにより、ユーザ指定のCSSファイルと組み合わせてフルオプションを使用することができます。
デフォルトはFalseです。
linenos
'table'に設定すると、行番号は2つのセルを持つテーブルとして出力され、1つは行番号を含み、もう1つはコード全体です。
これはコピー&ペーストに適していますが、一部のブラウザやフォントの位置合わせに問題が生じる可能性があります。
'inline'に設定されている場合、行番号はコードを含む<pre>タグに統合されます。
デフォルト値はFalseです。これは、行番号が全くないことを意味します。

注意:デフォルトの( "テーブル")行番号の仕組みでは、囲む<pre>タグに明示的な行高さの CSSプロパティを指定しない限り、行番号とコードはInternet Explorerで異なる行の高さを持つことができます:行の高さ 125% )。

サンプルコード

hl_lines
ハイライトされる行のリストを指定します。

サンプルコード

linenostart
最初の行の行番号(デフォルトは1)。

サンプルコード

linenostep
数n> 1に設定すると、n番目の行番号だけが印刷されます。

サンプルコード

linenospecial
n> 0の数に設定すると、n番目の行番号ごとにCSSクラスに"special"(デフォルト:0)が指定されます。

サンプルコード

nobackground
Trueに設定されている場合、フォーマッタはラッピング要素の背景色を出力しません(wrapping要素がない場合は自動的にFalseに設定されます(例:get_syntax_defsメソッドの引数なし ))(デフォルト:False)。

get_syntax_defsメソッドとはどのようなメソッドなのかはググってみたが情報が無い。

nobackgroundオプションはcssファイルやnoclassesオプション指定時のstyle属性に影響を与えるようで、単にnobackgroundオプションだけを指定しても出力結果は変わらないようだ。

サンプルコード

lineseparator
この文字列は、コード行の間に出力されます。デフォルトでは"\ n"になります。これは<pre>タグ内の行を分割するのには十分ですが、HTML行の区切りを得るには"<br>"に設定します。

サンプルコード

lineanchors
空でない文字列、例えばfooに設定されている場合、フォーマッタは各出力行をfoo-linenumberという名前のアンカータグにラップします。
これにより、特定の行に簡単にリンクすることができます。

サンプルコード

linespans
空でない文字列、たとえばfooに設定されている場合、フォーマッタは各出力行をidのfoo-linenumberを持つspanタグにラップします。
これにより、javascript経由で簡単に行にアクセスできます。

サンプルコード

anchorlinenos
Trueに設定すると、<a>タグに行番号がラップされます。linenosおよびlineanchorsと組み合わせて使用されます。

サンプルコード

tagsfile
tagsfileオプションにctagsファイルへのパスが設定すると、それらの定義へのリンクを持つアンカータグに名前をラップする。
lineanchorsを使用し、tagsファイルに行番号を指定する必要があります(ctagsの-nオプションを参照)。
tagurlformat
ctags定義へのリンクを生成するために使用される文字列フォーマットパターン。
利用可能な変数は%(path)s, %(fname)sそして%(fext)s。
デフォルトでは空文字列になり、#prefix-行番号のリンクになります。
filename
filenameオプションを指定した場合と指定しない場合の生成されるhtmlを比較してみるとfilenameオプションにxyzを指定した場合、<pre>ブロックの前に以下のタグが挿入されるようだ。
<span class="filename">xyz</span>

サンプルコード

HtmlFormatterとctags

HtmlFormatterにはctagsに関係するオプションtagsfileとtagurlformatが用意されている。
そこでctagsについて調べてみた。

Ctagsとは

さまざまなプログラミング言語で定義されているオブジェクト - 変数,関数,クラス,クラスのメンバ等がインデックス化されエディタ等のタグジェンプに利用できるツールのようだ。

サクラエディタ とctags

サクラエディタでもctagsを使ってタグジェンプをする事が可能なもよう。

pythonのctagsパッケージ

Pygmentsでもctagsを利用してHtmlFormatterのtagsfileオプションやtagurlformatオプションを指定する事でオブジェクトの定義へのリンクを生成できそうなので試してみようと思ったのだが。

tagsfileオプションのエラー

tagsfileオプションを指定するといかのようなエラーがでてしまった。

raise RuntimeError('The "ctags" package must to be installed '
RuntimeError: The "ctags" package must to be installed to be able to use the "tagsfile" feature.

どうやらctagsパッケージをインストールする必要があるようだ。

pythonのctagsパッケージを検索してみると

C:\Python34\Scripts> python -m pip search ctags
django-livinglots-generictags - A set of helpers for creating Django template tags
                            for models with generic relations.
z3c.recipe.tag            - Generate ctags from eggs for development.
ckanext-semantictags      - Associates local tags with semantic resources
python-ctags              - Exuberant Ctags indexing python bindings
cucutags                  - Generates ctags for BDD .feature/behave steps
pyctags                   - Python ctags interface.
TracTags                  - Tags plugin for Trac
python-ctags3             - Ctags indexing python bindings

python-ctagsあたりをインストールすれば良いのかな?

ところが、Python2、7にpython-ctagsをインストールしようとすると

C:\Python27\Scripts> pip install python-ctags

error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat). Get
it from http://aka.ms/vcpython27

どうやら、http://aka.ms/vcpython27から「Microsoft Visual C++ Compiler for Python 2.7」をダウンロードしてインストールする必要があるようだ。

わざわざVisual C++ 9.0をインストールするのも嫌だったのでpython-ctagsのインストールは断念。
どうせ、ctagsの機能は使う事はあまり無いだろうと自分に言い聞かせた。

pygments.formattersモジュールに定義されているAPI関数

formatterクラスのインスタンスを取得する。

pygments.formatters.get_formatter_by_name(alias, **options)
alias引数にFormatterクラスの別名リスト(Available formattersに記述されているFormatterのShort namesの値 - HtmlFormatter
の場合htmlのみ)にふくまれる別名に該当するFormatterのサブクラスのインスタンスを返す。
インスタンス化時にoptions引数によってFormatterのオプションを指定する。
エイリアス名に該当するFormatterクラスが見つからない場合はpygments.util.ClassNotFoundが発生する 。
pygments.formatters.get_formatter_for_filename(fn, **options)
fnに一致するFormatterのFilenamesのリスト(HtmlFormatter
の場合*.html, *.htm)に含まれるファイル名のパターンを持つFormatterサブクラスのインスタンスを返します。
インスタンス化時にoptions引数によってFormatterのオプションを指定する。
エイリアス名に該当するFormatterクラスが見つからない場合はpygments.util.ClassNotFoundが発生する。

以下に、formatterクラスのインスタンスを取得する複数の例を示す。

import pygments.formatters

# 直接formatterのコンストラクタを使ってHtmlFormatterクラスのインスタンスを取得

html_formatter = pygments.formatters.HtmlFormatter();                      print(type(html_formatter))

# get_formatter_by_name関数の例

html_formatter = pygments.formatters.get_formatter_by_name("html");        print(type(html_formatter))

# get_formatter_for_filename関数の例

html_formatter = pygments.formatters.get_formatter_for_filename("*.html"); print(type(html_formatter))

html_formatter = pygments.formatters.get_formatter_for_filename("*.htm");  print(type(html_formatter))

実行結果

<class 'pygments.formatters.html.HtmlFormatter'>
<class 'pygments.formatters.html.HtmlFormatter'>
<class 'pygments.formatters.html.HtmlFormatter'>
<class 'pygments.formatters.html.HtmlFormatter'>
<class 'MyFormatter'>
pygments.formatters.load_formatter_from_file(filename, formattername="CustomFormatter", **options)
指定されたファイルからロードされたFormatterサブクラスのインスタンスを、現在のディレクトリを基準にして返します。
このファイルには、formattername(デフォルトではCustomFormatter)という名前のFormatterクラスが含まれている必要があります。
このメソッドは入力ファイルに対してevalを実行するのと同じですので、ユーザーは入力に非常に注意する必要があります。
インスタンス化時にoptions引数によってFormatterのオプションを指定する。
Formatterをロードする際にエラーが発生した場合は、 ClassNotFoundが発生します。
Lexerのload_lexer_from_fileに相当するAPIと思われる。

load_formatter_from_fileの例を示す。

Formatterクラスを継承した空のクラスMyFormatterを記述したmy_lexer_class.pyというpythonのソースファイルを用意する。

my_formatter_class.py

from pygments.formatter import Formatter

class MyFormatter(Formatter):
  pass

load_formatter_from_file関数を実行

load_formatter_from_file関数を実行

from pygments.formatters import load_formatter_from_file

html_formatter = load_formatter_from_file('my_formatter_class.py', formattername="MyFormatter")
print(type(html_formatter))

実行結果

<class 'MyFormatter'>

HTMLフォーマッタのサブクラス化

HTMLフォーマッタは、簡単にサブクラス化できるように構築され、出力HTMLコードをカスタマイズするようになりました。
format()メソッドは(1, line)のタプルが得られるgeneratorを返すself._format_lines()を呼び出す。
タプルの一つ目の要素1は、二つ目の要素lineがフォーマット後のソースコードの行であることを示している。

NOWRAPのオプションが設定されるなら、generatorは反復処理されHTMLの結果が出力される。
それ以外の場合、format()はself.wrap ()を呼び出し、ジェネレータを他のジェネレータとラップします。
_format_lines()によって生成されたいくらかのHTMLコードに対して追加するかもしれない。
後者によって生成された行を修正して、(1, line)の行に対してもう一度処理をおこなう。
および,または(0, html)に対して他のHTMLコードの前後の行を生み出す。
ソース行と他のコードとの区別により、ジェネレータを複数回ラップすることができる。

デフォルトのwrap()実装では<、div>タグと<pre>タグが追加される。
カスタムHtmlFormatterサブクラスは次のようになります。

class CodeHtmlFormatter(HtmlFormatter):

    def wrap(self, source, outfile):
        return self._wrap_code(source)

    def _wrap_code(self, source):
        yield 0, '<code>'
        for i, t in source:
            if i == 1:
                # it's a line of formatted code
                t += '<br>'
            yield i, t
        yield 0, '</code>'

これにより、書式設定された行が<code>タグでラップされて(包まれて)、ソース行が<br>タグを使って区切られます。

wrap()を呼び出した後、 format()は、もし関係するオプションが設定されていれば「行番号」と(あるいは)「fullドキュメント」ラッパーを追加する。
その後、ラップされたジェネレータによって生成されたすべてのHTMLが出力される。

公式ドキュメントを読んでもよくわからなかったので実際に以下のコードを使って、HtmlFormatterによるいHTML出力とHtmlFormatterをサブクラス化したCodeHtmlFormatterのHTML出力を比較してみる。

最初の<div class="highlight"><pre><span></span><code>に、最後の</pre></div></code>に置き換わっているのが確認できる。
また、フォーマット後のソースコード行の各行の最後に<br>が追加されている。
デバッガーでi, tの値をウォッチしてみるとiには1がtにはフォーマット後のソースコードの行が格納されている。

これをみると_wrap_codeメソッドの最初の行のyieldで0と最初に挿入したいHTMLコードを,最後のyield行で0と最後に挿入したいHTMLコードを,そしてソースコード行はfor文の中でtの変数の内容を加工処理すれば良い事がわかる。

フォーマッタを自作する

公式サイトにある以下のドキュメントが参考になりそう。

ページのトップへ戻る