Pythonコードの強調表示を備えたHabr PDFプリンター

このプログラム(およびそれ以降の記事)を書くために、私はこの投稿に動機付けられました。 すべてを思い出すことは不可能であり、いつ役立つかはわからないので、読んだ記事をできる限り保管する習慣があるのは偶然です。 そのため、前述の投稿を読んで、WikipediaページをPDFに印刷する機会を思い出してから、Habr用の同じ「プリンター」を作成して、自分の個人的なアーカイブに興味を起こさせる記事を手に入れたいと思いました。



最初の試みは、著者から親切に提供されたプログラムを使用することでした。 そして、ほとんどすぐに、無視するレーキが私の強さよりも高いレーキがありました。 このレーキはコードの強調表示です。



すぐに予約しますが、私はHabréを初めて使いますが、それがどのように機能するかは非常に曖昧なコンセプトです。 しかし、コードの断片が提示されている記事のあるページのソースを見ると、問題の原因が明らかになりました。 そして彼*ドラムロール*は、JavaScriptがコードを着色している​​ことです。 いいえ、ブラウザで読む場合、これは確かに優れたものですが、ページをPDFでレンダリングすることに取り組んでいるpython pisaは、原則としてカラーリングコードを実行できません。



アイデアがありました-何かを考え出す必要があります。



アーティクルのソースコードを変更して、アーティクルでコードが表示される言語の主要な構造が特別なhtmlタグで囲まれるようにする必要があります。 次に、CSSに数行追加するだけで、ブラウザーと印刷されたpisa PDFの両方で表示できる構文強調表示付きのコードを確認できます。 これには何が必要ですか? まず、言語のトークンそのものを強調します。 そして、ここから楽しみが始まります。 まあ、各言語に対して同じ本格的なパーサーを実行しないでください! ただし、これは必須ではありませんでした。



理論を思い出してください。 プログラミング言語は、原則として、コンテキストに依存しない文法によって記述される言語のクラスに属し、通常の文法のサブセットとして含まれます。 通常の文法は、言語の基本的な要素、つまり他のすべての構文構造が構築されるトークンを記述します。 誰もが好きなコードの強調表示は、特定の種類のトークンを異なる色で強調表示することに専念し、コードを読みやすく、より楽しくします。 つまり、タスクは次のように要約されます。強調表示されたトークンのすべてのクラスの正規表現を作成し、正規表現ですべての一致を見つけて、対応するhtmlタグでフレーム化します。 ただし、問題はこれです。長い正規表現を作成するには時間と労力がかかります。 言語ごとにこのような式がいくつかあります。 そして、多くの言語があります。 また、表現自体は見た目ほど単純ではありません。 たとえば、C言語のデータ型に一致する正規表現を定義してみましょう(多数あるため、数を制限しています)。 何がそんなに複雑なの? 最初のくそー:

r'int|short|long|char'
      
      





そうだね いや このような正規表現は、たとえば、chelintano行で一致を検出し、単語の途中でハイライトを取得します。 解決策は明らかです-最初と最後に空白を追加します

 r'\s+(int|short|long|char)\s+'
      
      





再び間違っています。 型の前に括弧、角括弧、中括弧を付けることができます。型名が型キャスト操作を意味することを覚えていれば、そのすべてがあります。 タイプ名(文字、数字、アンダースコア)を前に付けることはできないと言う方が簡単です。 したがって、結果として、まさにそのような規則性が得られます。

 r'(?P<prefix>^|[^a-zA-Z0-9_])(?P<body>int|short|long|char)(?P<postfix>$|[^a-zA-Z0-9_])'
      
      





トークンのクラスごとにこのような手間がかかることを想像してください。 言語ごとに。 基礎だけが示されていればいいのですが、定期的なスケジュールはそれ自体で作成されます。 そしてそれを行うことができます。



言語トークンのクラスをINIファイルのような構造を持つファイルに書き込むことは、私にとって最も成功した決定のように思えました。 トークンのクラスごとに、主要コンポーネントを区別できます。プレフィックス-トークンの前に表示できる文字(文字のシーケンス)、トークンの本文、および後置記号-トークンの後に表示できる文字。 各コンポーネントは、単純な式-intやfunctionなどの通常の文字列、または[0-9] +(\。[0-9] +)などの正規表現で構成できます。 (浮動小数点数の正規表現)。 したがって、.iniファイルの各ブロックで次のパラメーターを設定できます。



語彙

前に



正規表現



regexprパラメーターの値は正規表現です。 このパラメーターは複数回使用でき、結果の正規表現はすべてのパラメーター値に一致します。 最初の3つのパラメーターでは、値は原則としてセット(列挙)であり、Pythonの最高の伝統では、リストと同様に角括弧で記述されています。 値を特殊文字で区切ると便利な場合があります。その場合は、区切り文字パラメーターを使用します(デフォルトでは、区切り文字は空の文字列です。つまり、すべての文字が正規の本文と見なされます)。 このパラメーターは、パラメーター値の別の定義が検出されるまで、ファイルブロック内の区切り文字を変更します。 字句列挙の行の先頭または末尾が繰り返されることがあります。これの鮮明な例は、C ++プリプロセッサディレクティブ(#include、#define、#pragma)の定義です。 書きすぎないように(そして突然それらの多くが実際にあります)、prefixおよびpostfixパラメーターの値を指定できます。 これらの値は、語彙目録の各行の最初と最後にそれぞれ追加されます。

同じC ++の例を示します



[クラス名]



区切り文字=;

接尾辞= \ s +

before = [クラス]

regexpr = [a-zA-Z _] [a-zA-Z0-9 _] *



eqstyle = typeword



[番号]



区切り文字=

regexpr = [0-9] +(\。[0-9] +)?

regexpr = 0x [0-9a-fA-F] +

before = {abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ_}

後= {abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ_}



ここで、eqstyleパラメーターを確認できます。 強調表示を定義するときに不必要なエントリ(この場合、ラッパータグとcssファイルのエントリ)を追加しないように、実用的な理由のためにのみ導入されました。 eqstyleパラメーターの定義は、「このクラスのトークンの場合、クラス<value>の場合と同じものを使用する」と読む必要があります



小さなことは、このファイルを読み取り、各トークンクラスの正規表現をコンパイルすることです。 その結果、シンボリックキー-トークンクラス名、および値-コンパイルされた正規表現(この辞書を言語の「スタイル」と呼びます)を含む辞書を取得します。 「スタイル」の各表現を使用して、目的の記事で公開されているコードのブロックを駆動することが残っています。



 def modify(style, eqstyles, stylename, block) : if style == None : return block #     html    block = fromHTML(block) wraps = [] for tag in style.keys() : _left = 0 word = style[tag] if tag in eqstyles : tag = eqstyles[tag] m = word.search(block) while m != None : prefix = m.group('prefix') postfix = m.group('postfix') tag_left = _left + m.start() + len(prefix) tag_right = _left + m.end() - len(postfix) unique = True #  ,       ,   for (start, end, t) in wraps : if (tag_left <= start) and (tag_right >= end) : #        wraps.remove( (start, end, t) ) elif tag_left >= start and tag_right <= end : #      ,   unique = False break if unique : wraps.append( (tag_left, tag_right, tag) ) _left = _left + m.end() m = word.search(block[ _left : ]) wraps = sortByF(wraps, (lambda (x1,y1,z1), (x2,y2,z2) : x1 < x2)) mod_block = "" _left = 0 for w in wraps : mod_block = mod_block + block[_left : w[0]] mod_block = mod_block + ( "<%s_%s>%s</%s_%s>" % (stylename, w[2], toHTML(block[w[0] : w[1]]), stylename, w[2]) ) _left = w[1] mod_block = mod_block + block[_left :] return mod_block
      
      







実際にここで何が起こっているのか。 正規表現ごとに、最初の一致が検索されます。 そのため、プレフィックスとポストフィックスの長さだけでなく、先頭に対するトークンのオフセットも取得します。 一致のリストにトリプルを追加します(<token position>、<token end position>、<token class>)。 開始位置は、コードの先頭を基準とした一致のオフセット+プレフィックスの長さ(終了位置の場合も同じ)として計算されます。 途中で、トークンが他のトークンと重複しているかどうか、およびトークンが重複しているかどうかがチェックされます。 これはどういう意味ですか? トークンは単なる文字列であり、ある行が別の行の部分文字列になることを忘れないでください。 そして、このもう1つもトークンである場合、ネストされたものと「オーバーラップ」します。 たとえば、「intは整数の4バイト数を格納する変数のタイプです」という行がある場合、intという単語を強調表示する必要はありません。これは行の一部にすぎません-トークンとして強調表示されます。 トークンを処理した後、検索文字列は、見つかったトークンが終了する位置まで左に切り捨てられ、残りのテキストでの一致の新しい検索が実行され、一致が終了するまで続きます。



最も簡単なことは残っています-トークンの位置のリストを使用して、htmlタグでフレーム化します。 タグは単純にコンパイルされます:<"言語名" _ "トークンクラス">。 その結果、html-markupで補完されたコードブロックが取得されます。 これに、これらの各タグのスタイル定義を追加します。ブラウザとピサの両方が同じようにうまく機能することを強調したコードが表示されます。



これが、 この記事の印刷ページの1つです。 コードのブロックは黒曜石のスタイルで装飾されており、私の愛するNotepad ++から借用しています。







提示された色付け方法は、完全性の観点(提示されたパラメーターではトークンを正確に決定するのに十分ではない場合もあります)やパフォーマンスの観点からは明らかに理想的ではありません。 しかし、私が印刷した記事では、彼は重大な失敗をしませんでした。 さらに、シャトルを宇宙に打ち上げていません。ここではエラーの価格は最小限であり、最適化する理由はありません。 バックライトを整理する他の方法を知っている人がいれば(正直、これに興味を持ったことは一度もありません)-私は喜んで聞き、読みます。



プログラムはここからダウンロードできます。



PS:言語やバックライトスタイルの説明を書くのを手伝いたいと思っているすべての人に感謝します。



All Articles