kramdownに関する覚え書き

〜 markdown, html, LaTeX の変換処理 〜

最終更新日: 2013/11/04

統計解析ソフトRで生成した表やグラフとその説明文を統合的に扱うのに html, LaTeX がいいだろうと思い、それをなるべく簡単に生成するツールとしてkramdownに着目しました。
Rやkramdownを一緒に扱うサンプルは、次のところに掲載しています。
rrxutil.rb および rrx ver 1.03 の追加機能について
回帰分析の簡単なレポート作成
よかったら覗いてみて下さい。

当サイトで紹介しているスクリプトは、kmd_a.zipに含まれています。解説文も同梱されています。必要に応じてダウンロードして下さい。


《目次》


《はじめに》

 kramdownは、markdown記法をhtmlやlatexに変換する機能を持つruby用のライブラリです。Thomas Leitner 氏が開発しています。

 プレーンテキストに近くて書きやすいmarkdownの原稿を、複雑なhtmlやLaTeXに変換できるのが魅力です。統計解析ソフトRで生成した表やグラフとその説明文を一つに取りまとめるのに html, LaTeX がいいだろうとは思ってましたが、それをなるべく簡単に行うにはどうするかと見渡し、kramdownに着目しました。

 kramdownのhome webは次のところです。

http://kramdown.rubyforge.org/index.html

    

 markdownは、なるべくシンプルなプレインテキストに近い形で文書構造等を表現する目的で考案された約束ごとです。

 基本的なmarkdownのルールは、とてもシンプルで表現できる事柄が限られているため、実際にmarkdownを扱うソフトウェアでは拡張仕様が採用されています。

 kramdownでは PHP Markdown Extra などの仕様を採用しています。また、それに加えて独自の拡張仕様もあるようです。

    

 markdown記法の基本については、例えば下のサイトが参考になります。

Markdown - Wikipedia

    

 PHP Markdown Extra に関しては、例えば次のサイトがあります。

開発などブログ ≫ PHP Markdown Extra 仕様の全訳(意訳)

    

 ここでは、rubyスクリプトの中でkramdownを利用する方法について、覚え書きを記します。markdown記法やkramdownの包括的な文法の解説はしていません。

 なお、用いる ruby のバージョンは、1.9以降を前提にします。サンプルスクリプトは、1.9.3, 2.0.0 で確認しました。

 文字エンコードに関する encode() とか force_encoding() の箇所を書き換えれば、ほとんどの場合、1.8系でも動くようになると思います。

 1.8系に対応させやすくするため、一応「require "rubygems"」を記述していますが、1.9系以降では不要です。

    

 それから、rubyスクリプトの文字コードは、Windows-31J にしました。私がよく利用しているのが日本語版の MS-Windows だからということもありますが、utf-8以外の文字コードを用いることによって、kramdown利用時の文字コードのあり方を確認できると考えたものです。

 ちなみに、kramdown関連のファイルは、いずれもマジックコメントがutf-8になっています。なので、kramdownを利用するrubyのスクリプトは、utf-8で書くのが無難ではあります。


1. kramdownのインストール

 kramdownは、多くのruby用ライブラリと同じように、gemでインストールできます。

  gem install kramdown [enter]

 上のように実行すると、最新版のkramdownがインストールされます。

    

 また、下に掲げるサイトからkramdown関連の圧縮ファイルをダウンロードして、その中にある setup.rb を実行するとインストールできます。

 setup.rbが置かれているディレクトリに入ってから、下のように実行します。

  ruby setup.rb [enter]

    

RubyForge: kramdown: ファイルリスト

    

 上のサイトには、最新版だけでなく旧版の圧縮ファイルもあります。

△ 目次に戻る


2. いくつかのフォーマット変換

 kramdownは、markdown または html の素材データを markdown, html, LaTeX に変換する機能を備えています。

 言い換えると、入力フォーマットとして markdown, html を用いることができ、出力フォーマットとしては html, LaTeX 及び markdown を指定できます。

 もう少し詳しく記すと、kramdownのマニュアルには、入力フォーマットとして kramdown(markdownの拡張版), markdown, Github Flavored Markdown そして html が指定でき、一方、出力フォーマットとして html, LaTeX, kramdown, RemoveHtmlTags(htmlのタグを削除する特別な変換,通常はLaTeXまたはkramdown変換に関連して用いる)が指定できる旨の記述があります。

 指定可能なフォーマットすべてについてサンプルを示すのは私には難しいので、代表的なもののみ取り上げます。

    

(1) markdown→html | LaTeXの変換

 markdownの原稿が文字列 md_str に代入されている場合、それをhtmlに変換するスクリプトは次のとおり。

html_str = Kramdown::Document.new(md_str).to_html

 上をあえて面倒な書き方に変更すると、下のように書くことができます。後で kramdown の細かな操作に関わるかもしれないので、参考まで記します。

kdoc = Kramdown::Document.new(md_str)
html_str, warnings = Kramdown::Converter::Html.convert(
    kdoc.root, kdoc.options)

 warnings には警告メッセージがセットされます。警告が何もない時は、空配列の [] になります。

 エッセンス部分だけ示されても分かりにくいと思うので、実際に動かすことのできるrubyスクリプトを下に掲げます。表(table)を扱う例です。

    

−−−− kmd01.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ## markdownの原稿を設定
md_str = <<EOS
|名称|電話
|--------
|時報|117
|天気予報|177
|電話の新設・移転等相談|116
|災害用伝言ダイヤル|171
EOS
        ## markdown→html変換
html_str = Kramdown::Document.new(md_str).to_html
puts html_str
−−−− kmd01.rb ここまで

    

 ここには掲げませんが、「面倒な書き方」をしたスクリプトを kmd01b.rb として同梱してあります。

 上のスクリプトで、to_html を to_latex に変更すれば、LaTeXに変換できます。

 「面倒な書き方」に即していうと、「::Html」のところを「::Latex」に書き換えれば LaTeX に変換できます。


(2) html→markdown | LaTeXの変換

 今度は、htmlからmarkdownに変換する例を示します。

 素材の原稿がhtmlである場合、:inputオプションを用いて、その旨をkramdownに知らせる必要があります。このオプションの指定がない時は、素材原稿がmarkdownで書かれていると仮定されるようです。

 具体的には次のように書きます。

    

kdoc = Kramdown::Document.new(html_str, :input=>'html')
md_str = kdoc.to_kramdown

    

 実際に動かすことのできるスクリプトは下のとおり。箇条書き(ul)を取り上げています。

    

−−−− kmd02.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ## htmlの原稿を設定
html_str = <<EOS
<h1>都道府県の人口</h1>
<ul>
  <li>東京都  1,321万6,221人</li>
  <li>神奈川県  907万2,133人</li>
  <li>大阪府   886万3,324人</li>
  <li>愛知県   742万5,952人</li>
</ul>
EOS
        ## html→markdown変換
kdoc = Kramdown::Document.new(html_str, :input=>'html')
md_str = kdoc.to_kramdown
puts md_str
−−−− kmd02.rb ここまで

    

 LaTeXに変換したい時は、to_kramdown を to_latex に変更します。

 「面倒な書き方」は、markdown→html変換の例から容易に類推できるとおり、下のようになります。

    

kdoc = Kramdown::Document.new(html_str, :input=>'html')
md_str, warnings = Kramdown::Converter::Kramdown.convert(
    kdoc.root, kdoc.options)

    

 上に出てくる「::Kramdown」のところを「::Latex」に書き換えれば、LaTeXへの変換になります。


(3) yamlへの変換

 kramdownのDocumentオブジェクトには to_yaml というメソッドがあります。これまでのサンプルで to_html あるいは to_kramdown と書いたところを to_yaml にすると、Documentがyamlフォーマットに変換されます。

 yamlフォーマットには様々なオプション情報が含まれています。また、その字下げの様子から、kramdownがどのような構造でDocumentを管理しているのかが分かります。

 このyamlフォーマットを実践的に利用することはあまりないと思いますが、kramdownの解説を読む際、具体的なyamlフォーマットの出力があると、イメージをつかみやすくなります。kramdownの内部構造に踏み込んだプログラムを書く場合も役に立ちます。

△ 目次に戻る


3. スタンドアロンのhtmlファイルの生成

 これまで掲げたサンプルスクリプトで生成されるのは、htmlにしろLaTeXにしろ、部分的なものでした。なので、生成されたhtmlをブラウザで開くことはできません。

 ブラウザで開くことのできるファイル(スタンドアロンのファイル)を生成するためには、「<html> …… <body> …… </body></html>」といったタグを書き込んでやる必要があります。

 そうした「書き込み」を行う単純な方法は、言うまでもなく自分でタグを書き込む方法です。最も自由度が高く捨てがたい方法ではありますが、毎回それをやるのは少々おっくうな感じがします。「書き込み」を手作業でなくrubyスクリプトでやるにしても、いつもそうするのには面倒な感じが残ります。

 rails とか sinatra と組み合わせてkramdownを利用する場合は、スタンドアロン生成を意識しなくてもいいと思いますが、ここではkramdownのテンプレート機能を用いたスタンドアロンhtmlの生成を取り上げてみます。

    

(1) テンプレートを利用したスタンドアロンのhtmlの生成

 kramdownのDocumentを新たに作るとき、オプションによってテンプレートを指定することができます。テンプレートは、予めファイルとして作っておく必要があります。オプションではそのファイル名を指定します。

 例えば、test.tpl というファイルのテンプレートがあるとすれば、次のようにしてDocumentを生成します。

kdoc = Kramdown::Document.new(md_str, :template=>"test.tpl")
html_str = kdoc.to_html

 適切なテンプレートであれば、html_str にはスタンドアロンのhtmlが代入されるはずです。

 テンプレートの作り方を本格的に追求すると、kramdownの内部構造に踏み込んだものにならざるを得ませんが、とりあえず使える簡単なものを示すと下のようになります。

 kramdownは、テンプレートをerbにかけるので、それを意識した書き方になります。

    

<!DOCTYPE html>
<html>
<head>
<title>test page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<%= @body %>
</body>
</html>

    

 上の「<%= @body %>」がerbで処理される部分です。

 @bodyには、kramdownによって変換されたhtmlの部分的な記述が代入されているはずです。それがテンプレート中で展開されることにより、スタンドアロンのhtmlが生成されます。

 このテンプレートの場合、webのtitleが「test page」に固定されてしまいます。これをbodyの中に出てくる最初の <h1>xxxx</h1> の xxxx と同じものにしたい、というように考え始めると、kramdownの内部構造に踏み込んだプログラム記述が必要になってきます。

 charsetも utf-8 に固定なので、html_str の文字コードを utf-8 に変換してやる必要があります。もし md_str の文字コードに合わせて柔軟に(そして自動的に)charsetが設定されるようにしたい、というように考え始めると、なかなか面倒になってきます。

    

 とりあえず、上に掲げた簡単なテンプレートを使う例は、下のようになります。

 テンプレートは、test.tpl というファイルとして作成しますが、最後に削除してしまうので残りません。

 スタンドアロンのhtmlは、test.htm として書き出します。定義リスト(dl)を取り上げています。

−−−− kmd03.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
require "nkf"
        ## markdownの原稿を設定
md_str = <<EOS
# 労働関係調査の用語\n
就業者
: 雇用や自営業等の職業に就いている者\n
失業者
: 就業していないが、働く意思があり就職活動している者\n
労働力人口
: 満15歳以上の人口のうち就業者と失業者の合計\n
EOS
        ## テンプレートファイルの作成
template_str = <<EOS2
<!DOCTYPE html>
<html>
<head>
<title>test page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<%= @body %>
</body>
</html>
EOS2
File.open("test.tpl", "w") {|ff|  ff.write template_str}
        ## markdown→html変換
kdoc = Kramdown::Document.new(md_str, :template=>"test.tpl")
html_str = kdoc.to_html
html_str = NKF.nkf("-m0w", html_str)
File.open("test.htm", "w") {|ff|  ff.write html_str}
File.unlink("test.tpl")
puts "'test.htm' を出力しました."
−−−− kmd03.rb ここまで

(2) デフォルトのテンプレートを新たに設けてhtmlを生成

 実は、テンプレートファイルを自前で用意しなくても、kramdownのデフォルトのテンプレートを利用するという手があります。

 「:template=>"document"」というオプションを指定すれば、デフォルトのテンプレートを使うとの意思表示になります。

 これを使うと、webのタイトルが弾力的に設定されます。「<title>xxxx</title>」の xxxx の部分には、bodyで最初に出てくる見出し、つまり「<h1>xxxx</h1>」などの xxxx の部分が割り当てられます。「test page」のような固定されたものがタイトルになる心配はありません。

    

 ただ、私の経験からいうと、このデフォルトのテンプレートだと、charsetの設定が正しく行われません。ちょっとだけhtmlの記述が奇妙です。マルチバイト文字を含むhtmlの場合、ブラウザで見た時に文字化けする可能性があります。

 そこで、インストール済みのテンプレートを少し修正してみます。

    

 kramdown関連の所定のディレクトリに document.html というファイルがありますが、それがデフォルトのテンプレートです。LaTeXのテンプレートは document.latex です。

 所定のディレクトリというのは、例えば次のようなものです。

  C:/ruby193/lib/ruby/gems/1.9.1/gems/kramdown-1.2.0/data/kramdown

 このディレクトリは、「Kramdown.data_dir」で取得することができます。

 このディレクトリに document02.html というファイルを作っておけば、

:template=>"document02"

というオプション指定でそのテンプレートを利用することができます。

    

 以下に document02.html を書き出すスクリプトを掲げます。日本語を扱えるテンプレートです。ruby ver 1.8系でも使えます。

 このスクリプトは、unix系OSの場合、root権限の下でないと実行できないかもしれません。要は、kramdown をインストールした時のユーザーの権限であれば実行できます。

    

−−−− kmd04.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ##
template_str = <<EOS
<% if RUBY_VERSION >= '1.9'
     enc = @converter.root.options[:encoding]
     enc = Encoding.default_external unless enc
     cset = enc.to_s =~ /windows-31j|cp932|sjis|shift.jis/i ?
         'Shift_JIS' : enc.to_s
   else
     enc = $KCODE.to_s[0,1].downcase
     enc = 'w' unless ['e', 's'].include?(enc)
     cset = {'e'=>'euc-jp', 's'=>'Shift_JIS', 'w'=>'utf-8'}[enc]
   end
%>\
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=<%= cset %>" />
<% extend ::Kramdown::Utils::Html
title = ''
h = @converter.root.children.find {|c| c.type == :header}
if h
  collector = lambda {|c| c.children.collect {|cc| cc.type == :text ?
  escape_html(cc.value, :text) : collector.call(cc)}.join('')}
  title = collector.call(h)
  title = title.encode(enc) if RUBY_VERSION >= '1.9'
end %>\
<title><%= title %></title>
<meta name="generator" content="kramdown <%= ::Kramdown::VERSION %>" />
</head>
<body>
<%= @body %>
</body>
</html>
EOS
        ##
template_dir = Kramdown.data_dir
template_file = "document02.html"
filename = sprintf("%s/%s", template_dir, template_file)
File.open(filename, "w") {|ff|  ff.write template_str}
puts "create a template file: '#{template_file}' in '#{template_dir}'"
−−−− kmd04.rb ここまでw

 一応、上のスクリプトの実行が成功し、デフォルトのテンプレートとして document02 を使えるようになったものと仮定します。

 このデフォルトのテンプレートを利用するサンプルが下の kmd05.rb です。脚注(footnote)の書き方の例になっています。

 document02.html の書き出しがうまくいかなかった場合は、圧縮ファイルに同梱してある kmd05b.rb を使ってみて下さい。document02.html を通常のテンプレートファイルとして書き出した上で、それを利用するようにしています。

    

−−−− kmd05.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ## markdownの原稿を設定
md_str = <<EOS
# kramdownの文法\n
 kramdownの文法は、Markdownの文法に基礎を置きつつ、Maruku[^1]、
PHP Markdown Extra および Pandoc[^2]のような他のMarkdown実装に
みられる拡張仕様を取り入れてきた。\n
 ただし、kramdownの文法は、一定の規則に即した厳密な文法の装備に
努めており、そのためMarkdownと完全な互換性があるわけではない。
とはいえ、ほとんどのMarkdown文書は、kramdownで読み込む際に正しく
処理されるはずである。\n
[^1]: ruby用のmarkdown処理ライブラリの一つ\n
[^2]: 多様なフォーマット(html, LaTeX, Word, pdfなど)で出力する機能を装備\n
EOS
        ## markdown→html変換
html_str = Kramdown::Document.new(md_str,
    :template=>'document02').to_html
File.open("test.htm", "w") {|ff|  ff.write html_str}
puts "'test.htm' を出力しました."
−−−− kmd05.rb ここまで

 document02.html について少し補足しておきます。

 ruby ver 1.8系でこのテンプレートを使った場合、$KCODE によって文字コードの判別を行います。なので、変換元の原稿の文字コードが $KCODE が指すものと異なる場合は適切に変換されません。例えば、$KCODEがShift_JISであるにもかかわらず、原稿がutf-8だと、うまく変換されません。

 また、$KCODEが指す文字コードが euc-jp, Shift_JIS のどちらでもない場合、utf-8 とみなします。少々乱暴な仕様になっているので注意して下さい。

 ruby ver 1.9系以降でこのテンプレートを使う場合は、そうした問題はありません。


《参考》 Pandocについて

 先のスクリプト中に出てきたPandocは、MS-Wordの *.docx なども作成できるコマンドです。つまり kramdown がサポートしていない出力フォーマットを書き出すことができます。

 pandocは、rubyなどのプログラミング言語用のライブラリではなく、単体のコマンドです。MS-Windowsでは pandoc.exe です。

 kramdown用に書いたmarkdownの原稿をPandocで処理できるかどうかは微妙なところです。kramdownの独自の仕様に沿った書き方をしていると、pandocで処理できない可能性があります。少し修正が必要になるかもしれません。場合によっては、kramdownでhtmlやLaTeXを生成し、それをpandocで処理する方がスムーズかもしれません。

 Pandocのサイトは次のところです。

Pandoc - About pandoc

 markdownの原稿をhtml または LaTeX に変換するためのpandocの起動方法は次のとおり。

  pandoc -s -f markdown -t html -o output.htm input.mkd [enter]
  pandoc -s -f markdown -t latex -o output.tex input.mkd [enter]

 markdownの原稿 input.mkd の文字コードは、予め utf-8 にしておきます。

 -s オプションは、スタンドアロンを作るための指定です。

 htmlに変換する際、数式を扱うために MathJaxを使うようにしたければ --mathjax というオプションを指定します。


(3) もう少しこったhtml用テンプレートの例

 次の2点に対応するテンプレートを考えます。

    

a. MathJaxへの対応

 MathJax対応にするための記述は、いろいろなサイトで紹介されていますが、次の4行を「<head>……</head>」の中に組み入れます。

 4行といっても、長い行があるので見た目は5行になるかもしれません。

<script type="text/javascript"
  src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML">
</script>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">

 上のMathJax対応記述は、「\( …… \)」や「\[ …… \]」の記法を可能にするものです。「……」の箇所にLaTeX形式の数式を書き入れます。

 kramdownでは '\' がエスケープ文字として用いられるので、markdownの原稿を書く時は、「\\(……\\)」のように '\' を2つ重ねて書く必要があります。

 更に、markdownの原稿をrubyスクリプトの "……" のダブルクォーテーションで囲む時は、rubyも '\' をエスケープ文字として使うため、結局「\\\\( …… \\\\)」と書く必要があります。'\' を4つも重ねなければなりません(ダブルクォーテーションでなくシングルクォーテーションで囲む場合は2つ重ねで大丈夫です)。

 ただ、実際にはこの「\( …… \)」の記法を使うことは少ないと思います。

 kramdownにはマスブロック(math block)というのがあります。これを利用すると、「$$ …… $$」の形で数式を書くことができます。半角のドル記号2つで囲まれた中では基本的に '\' の重ね書きが必要ありません。これについては後に出てくるサンプルスクリプトを参考にして下さい。

    

b. htmlのtitleの弾力的設定

 「htmlのtitleがbodyの最初に出てくる見出しと同じになるようにする」という件については、kramdownの内部構造をチェックすして実現します。erbの「<% …… %>」の形でプログラムを書き入れます。最初に出てくる見出しをチェックするためのプログラムです。

 具体的には次のような記述をテンプレートに盛り込みます。デフォルトのテンプレート(document.html)に盛り込まれているものと同じです。

    

<%
extend ::Kramdown::Utils::Html
title = ''
h = @converter.root.children.find {|c| c.type == :header}
if h
collector = lambda {|c| c.children.collect {|cc| cc.type == :text ?
escape_html(cc.value, :text) : collector.call(cc)}.join('')}
title = collector.call(h)
end
%>

    

 上は、入り組んでいて分かりにくいプログラムです。これを理解するにはkramdownの詳細を知る必要がありますが、それについてはkramdownのサイトを参照して下さい。

 ただ、上に出てくる「@converter.root」というのが先述の「面倒な書き方」に出てくる「kdoc.root」に該当することだけ付記しておきたいと思います。

 そのことを示すサンプルスクリプト kmd06.rb を下に掲げます。最初に出てくる見出しだけでなく、すべての見出し(h1〜h6の全レベルとも)を出力します。

    

−−−− kmd06.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ## markdownの原稿を設定
md_str = <<EOS
# kramdownクイックリファレンス\n
 kramdownは、主に2種類の要素から構成されている。ブロック階層および
スパン階層の2種類の要素である。ブロック階層の要素は、段落、見出し、
箇条書きなどを設けるのに用いる。一方、スパン階層は、協調、リンクなどを
表すためテキスト中のフレーズをマークアップするのに用いる。\n
## ブロック階層の要素 〜 主要構成要素\n
- 段落(paragraphs)
- 見出し(header)
- ブロッククォート
- コードブロック
- 水平線
- 箇条書き
- 定義リスト
- 表
- HTML要素
- ブロック属性(tableにborder属性を付加するなど)
- 拡張仕様(comment, nomarkdown, optionsの一時的変更等)\n
## スパン階層の要素 〜 テキストの修飾記法\n
- 協調
- リンクと画像
- インラインコード
- 脚注
- 略語(abbr)
- HTML要素
- インライン属性\n
EOS
        ## markdown→html変換
kdoc = Kramdown::Document.new(md_str)
extend ::Kramdown::Utils::Html
headers = []
kdoc.root.children.each do |h|
  next if !h or h.type != :header
  collector = lambda {|c| c.children.collect {|cc| cc.type == :text ?
  escape_html(cc.value, :text) : collector.call(cc)}.join('')}
  title = collector.call(h)
  title = title.encode(__ENCODING__) if RUBY_VERSION >= '1.9'
  headers << title
end
puts "** htmlの見出し一覧"
puts headers
−−−− kmd06.rb ここまで

    

 話が横道にそれました。テンプレートに話を戻します。

 先に示したプログラム記述をテンプレートに書き入れてやれば、bodyの中で最初に出てくる見出し(<h1>……</h1>など)がwebのtitleになります。その都度titleを指定する手間が省けます。

 そうしたテンプレートを利用する形にしたのが kmd07.rb です。

 MathJaxも使えるようにして、数式の表示を行ってみます。

 ちょっと煩雑で長くなりますが、下に掲げておきます。

    

−−−− kmd07.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ## テンプレートファイルの作成
template_str = <<EOS
<% if RUBY_VERSION >= '1.9'
     enc = @converter.root.options[:encoding]
     enc = Encoding.default_external unless enc
     cset = enc.to_s =~ /windows-31j|cp932|sjis|shift.jis/i ?
         'Shift_JIS' : enc.to_s
   else
     enc = $KCODE.to_s[0,1].downcase
     enc = 'w' unless ['e', 's'].include?(enc)
     cset = {'e'=>'euc-jp', 's'=>'Shift_JIS', 'w'=>'utf-8'}[enc]
   end
%>\
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=<%= cset %>" />
<script type="text/javascript"
  src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML">
</script>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">
<% extend ::Kramdown::Utils::Html
title = ''
h = @converter.root.children.find {|c| c.type == :header}
if h
  collector = lambda {|c| c.children.collect {|cc| cc.type == :text ?
  escape_html(cc.value, :text) : collector.call(cc)}.join('')}
  title = collector.call(h)
  title = title.encode(enc) if RUBY_VERSION >= '1.9'
end %>\
<title><%= title %></title>
<meta name="generator" content="kramdown <%= ::Kramdown::VERSION %>" />
</head>
<body>
<%= @body %>
</body>
</html>
EOS
File.open("test.tpl", "w") {|ff|  ff.write template_str}
        ## markdown→html変換
md_str = DATA.read
kdoc = Kramdown::Document.new(md_str, :template=>"test.tpl")
html_str = kdoc.to_html
File.open("test.htm", "w") {|ff|  ff.write html_str}
File.unlink("test.tpl")
puts "'test.htm' を出力しました."
__END__
# 数式の表示(マスブロックを利用)

分数の計算: $$ \frac{1}{2} + \frac{1}{3} = \frac{3 + 2}{6} = \frac{5}{6} $$

相加平均の計算:

$$ μ = \frac{1}{n}\sum_{i=1}^{n} x_i = \frac{x_1 + x_2 + \cdots + x_n}{n} $$
−−−− kmd07.rb ここまで

△ 目次に戻る


4. スタンドアロンのLaTeXファイルの生成

 markdown→LaTeXの変換、html→LaTeXの変換の方法については触れましたが、その変換で得られるのは部分的なものであって、いわゆるスタンドアロンではありません。

 LaTeX関連のコマンドを用いてdviファイルを作ったり、pdfファイルを生成したり、あるいは印刷したりするためには、スタンドアロンのLaTeXファイルが必要です。

 LaTeXの初心者としての立場から、pdfファイル生成を1つの目標にして、覚え書きを記してみます。

    

 なお、MS-WindowsにおけるLaTeXのインストールについて、私が参考にしたのは下のサイトです。

W32TeX

    

(1) 日本語文書用のテンプレートの作成

 htmlの時と同じように、スタンドアロンを生成するのにテンプレートを用います。ただ、LaTeXの場合、テンプレートの中に、利用するパッケージを書き込む必要があります。これがなかなか大変です。

 kramdownでは、表(table)を含む文書の場合、longtable というパッケージが必要になります。そのため次の1行をLaTeX文書の最初の方に書く必要があります。

  \usepackage{longtable}

 また、脚注(footnote)を使おうとすれば fancyvrb というパッケージが必要です。

    

 ごく小さな文書は別として、パッケージの指定を手作業で行うのは骨が折れます。特に、kramdown仕様のmarkdownの原稿を書いている時は、パッケージとの関連性が見えにくくなるのでなおさらです。

 そこで、デフォルトのテンプレート document.latex を参考にして、必要なパッケージが自動的に列記されるテンプレートを作ります。

 document.latexをそのまま利用してもいいのですが、日本語文書用のドキュメントクラス(jarticleなど)には対応していないので、その辺を調整したテンプレートを作ってみます。

 なお、document.latexを利用する場合は、utf8x.defを含むucsパッケージが必要になります。これについては後で簡単に触れようと思います。

    

 まずは、ドキュメントクラスとして jarticle を用いるテンプレートの例を示してみます。下の kmd08.rb がそれです。

 markdownの原稿の中で、コードブロック(code block)および表(脚注を含む)を取り上げています。コードブロックの部分は、rubyスクリプトのソースコードです。表は、LaTeXの和文用ドキュメントクラスを列記したものです。

 ちょっと長いですが、全部を掲げておきます。

    

−−−− kmd08.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ## markdownの原稿
md_str = <<EOS
# プログラムソース掲載の例\n
コードブロックの例として、素数表示のrubyスクリプトを掲げます。\n
~~~~~~~~ ruby
limit = 1000
nums = Array.new(limit+1)
count = 0
for n in 2..limit
  next if nums[n] != nil
  puts "prime #\#{count+=1}: \#{n}"
  n.step(limit, n) {|n2| nums[n2] = n}
end
~~~~~~~~\n
* * * * * * * *\n
# LaTeXの和文用ドキュメントクラス\n
和文用ドキュメントクラスには次のものがあります。\n
|摘要|横書き|縦書き
|--------
|章を含まない小さめの文書|jarticle|tarticle
|章を含む大きい文書|jreport|treport
|両面印刷を前提とした本の印刷|jbook|tbook
|奥村晴彦氏作のjarticle改良版[^1]|jsarticle|
|仝jbook改良版|jsbook|\n
[^1]: より豊富なフォントサイズ、容易なラベル変更等が特徴\n
EOS
        ## テンプレートの書き出し
template_str = <<EOS2
\\documentclass{jarticle}
\\usepackage{listings}
\\usepackage{hyperref}
<% @converter.data[:packages].each {|pkg| %>\
\\usepackage{<%= pkg %>}
<% } %>\
<% if @converter.data[:packages].include?('fancyvrb') %>\
\\VerbatimFootnotes
<% end %>\
<% if @converter.data[:packages].include?('acronym') %>\
<%   @converter.root.options[:abbrev_defs].each_pair do |k,v| %>\
\\acrodef{<%= @converter.normalize_abbreviation_key(k) %>}[\
<%= k %>]{<%= @converter.escape(v) %>}
<%   end %>\
<% end %>\
\\hypersetup{colorlinks=true,urlcolor=blue}
\\begin{document}
<%= @body %>
\\end{document}
EOS2
tfile = "jpn.tpl"
File.open(tfile, "w") {|ff|  ff.write template_str}
        ## LaTeX文書に変換
kdoc = Kramdown::Document.new(md_str, :template=>tfile)
tex_str = kdoc.to_latex
File.open("test.tex", "w") {|ff|  ff.write tex_str}
File.unlink(tfile)
puts "'test.tex' を出力しました."
−−−− kmd08.rb ここまで

    

 「\usepackage{listings}」と「\usepackage{hyperref}」の2つは、kramdownでは必ず指定されるもののようです。なので、ここでも盛り込んでみました。

 listings は、コードブロック(プログラムのソースコードなどを文書中に組み入れる時に用いる)を処理するためのものです。

 hyperref は、ハイパーリンクの処理に関するもので、pdfファイルを作った時に機能するもののようです。

    

 ドキュメントクラスとして「\documentclass{jarticle}」と最も簡単な書き方にしていますが、用紙サイズとして a4paper を指定したり、文字の大きさとして 12pt を指定したりしてもかまいません。例えば次のとおり。

  \documentclass[a4paper,12pt]{jarticle}

    

 ちなみに、w32tex関連のサイトをみると、jarticle よりも jsarticle(奥村晴彦氏作の新ドキュメントクラス)がお勧めという意見をよく見掛けます。

 新たなデフォルトのテンプレート document02.latex を設けて、それを利用することにより、必要に応じて jarticle にしたり jsarticle にしたりするサンプルを後で取り上げたいと思います。

    

 この kmd08.rb を実行すると test.tex が作成されます。

 これを基にしてdviファイルを作成する時は platex というコマンドを用います。

  platex test.tex [enter]

 こうすると test.dvi というファイルが生成されます。このファイルからpdfファイルを作るためには dvipdfmx というコマンドを使います。

  dvipdfmx test.dvi [enter]

 いろいろ警告メッセージが出るかもしれませんが、test.pdf が作成されると思います。

    

 pdfの作成をもっと簡単にすませたいなら、ptex2pdf というコマンドも使えます。

  ptex2pdf -l test.tex [enter]

 上のように実行すると、test.pdf が生成されます。-l オプションは、LaTeXの基本フォーマットを用いるとの指定です。

 この場合も、実際にやっていることは前述した2つのコマンドを使った時と同じだと思いますが、一応 参考まで。


(2) kramdownのデフォルトのテンプレートの利用

 Kramdown::Document.new() のオプションとして、「:template=>'document'」を指定すれば、デフォルトのテンプレートを利用できます。

 デフォルトのテンプレートでは、ドキュメントクラスとして scrartcl が用いられます。

 kramdownのドキュメントをみると、デフォルトのテンプレートを利用する際に必要とされるパッケージとして上げられているのは次のものです。

    

  1. 常時必要なパッケージ
  2. 文書の内容に応じて必要になるもの

    

 kramdownのデフォルトのテンプレートを利用する場合、日本語の原稿は、その文字コードを utf-8 にしておかないと正しく処理されないと思いますが、utf-8の原稿を引きわたすと、次の1行が書き加えられます。

  \usepackage[utf8x]{inputenc}

 このutf8xの指定があるために、LaTeX関連のディレクトリのどこかに utf8x.def というファイルがないと、platexでdviファイルを作成しようとした時にエラーが発生します。

 utf8x.defとその関連のファイルは、ucs.zipという圧縮ファイルに含まれています。その入手先は次のサイトです。

CTAN: tex-archive/macros/latex/contrib/ucs

 上のサイトから ucs.zip をダウンロードして解凍すると、ucsというディレクトリができ、その中にutf8x.defなどのファイルがあります。

 仮に、MS-Windows環境下で C:/usr/w32tex というディレクトリにLaTeXをインストールした場合でいうと、

  C:/usr/w32tex/share/texmf-local/tex/latex

というディレクトリの下に、ucsをディレクトリごとコピーします。とりあえずのインストールは、それで終了です。

 結果、utf8x.def のフルパスは次のようになります。

  C:/usr/w32tex/share/texmf-local/tex/latex/ucs/utf8x.def

    

 他にもインストールした方がいいパッケージ類があると思いますが、上のucsを導入しておくと、kramdownで生成したLaTeX文書を処理できるようになります。

 以下に、デフォルトのテンプレートを利用して test.tex を作成するスクリプトを掲げておきます。数式を取り上げています。

    

−−−− kmd09.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
require "nkf"
        ## markdownの原稿を設定
md_str = <<EOS
# kramdownのマスブロック\n
1つのマスブロックは、ドル記号2個の重ね書きで囲む。\n
終端のドル記号は、数式と同じ行であれ別の行であれ、行末に置く。\n
マスブロックは、LaTeX文書に変換した時に、通常、
「\\\\begin\{displaymath\} …… \\\\end\{displaymath\}」で囲まれる。
ただし、\\\\beginで始まる記述がある時は別である。\n
数式を行の中に盛り込むのも容易である。数式を2つのドル記号の
重ね書きで囲めばよい。数式を記述したいわけではない時は、
ドル記号をエスケープする。すると単なるドル記号となる。\n
次は、kramdownのドキュメントにある数式の例である。\n
EOS
fml_str = <<'EOS2'
$$
\begin{align*}
  & \phi(x,y) = \phi \left(\sum_{i=1}^n x_ie_i, \sum_{j=1}^n y_je_j \right)
  = \sum_{i=1}^n \sum_{j=1}^n x_i y_j \phi(e_i, e_j) = \\
  & (x_1, \ldots, x_n) \left( \begin{array}{ccc}
      \phi(e_1, e_1) & \cdots & \phi(e_1, e_n) \\
      \vdots & \ddots & \vdots \\
      \phi(e_n, e_1) & \cdots & \phi(e_n, e_n)
    \end{array} \right)
  \left( \begin{array}{c}
      y_1 \\
      \vdots \\
      y_n
    \end{array} \right)
\end{align*}
$$
EOS2
        ## markdown→LaTeX変換
md_str = md_str + fml_str
md_str = NKF.nkf("-m0w", md_str)  # utf-8に変換
kdoc = Kramdown::Document.new(md_str, :template=>'document')
tex_str = kdoc.to_latex
tex_str = NKF.nkf("-m0w", tex_str)  # なくてもよいが念のため
File.open("test.tex", "w") {|ff|  ff.write tex_str}
puts "'test.tex' を出力しました."
−−−− kmd09.rb ここまで

    

 kramdownで変換する前に、markdownの原稿の文字コードをutf-8に変換しています。

 そして、変換処理した後の文字コードを確認すると、当然ながら utf-8 になっています。

 ただし、このスクリプトを ruby ver 1.9 以降で実行した場合、tex_str.encoding() が返す値は Windows-31J です。実態がutf-8であるにもかかわらず、そうなります。

 これは、kramdown が Encoding.default_external の値を参照して、tex_strのエンコードをそれに合わせて調整するからだと思われます。

 上のスクリプトのように、tex_str を単にファイルに書き出すだけなら、このエンコードの不一致を放置しておいてもかまいませんが、念のためnkfで改めてutf-8に変換しています(過剰防衛的ではありますが)。

 ruby ver 1.9 以降であれば、わざわざnkfで変換しなくても、

  tex_str.force_encoding('utf-8')

とすれば大丈夫です。


《参考》 LaTeXに変換する際のマスブロックに関する注意点

 マスブロックは、ドル記号2個の重ね書きで囲みます。例えば次のように書きます。

  $$ x^{2} + 2x + 1 = (x + 1)^{2} $$

    

 短い数式なら、独立した行にしなくても、文章中に組み込んでかまいません。例えば次のとおり。

  $$ y = x^{2} $$ のグラフは、放物線になる。

    

 気をつけなければならないのは、何行かにわたって数式を書く場合です。例えば、下の2行を変換したとします。

  $$ x^{2} + 2x + 1 = (x + 1)^{2}\\
     x^{2} - 2x + 1 = (x - 1)^{2} $$

    

 上をkramdownで変換すると次の4行になります。

  \begin{displaymath}
     x^{2} + 2x + 1 = (x + 1)^{2}\\
     x^{2} - 2x + 1 = (x - 1)^{2}
  \end{displaymath}

    

 ここで、始点のドル記号2個を独立の行にしてみます。つまり、ドル記号2個のすぐ後で改行します。

  $$
    x^{2} + 2x + 1 = (x + 1)^{2}\\
     x^{2} - 2x + 1 = (x - 1)^{2}
  $$

    

 上の4行をkramdownで変換すると、空白行1行が入り、次の5行になります。

  \begin{displaymath}
    《ここが空白行になる》
     x^{2} + 2x + 1 = (x + 1)^{2}\\
     x^{2} - 2x + 1 = (x - 1)^{2}
  \end{displaymath}

    

 この空白行がトラブルのタネになることがあります。

 LaTeX文書では、複数行にわたって数式を記述する際、空白行を入れないのが原則です。

 なので、kramdownのマスブロックを記述する場合、始点のドル記号2個のすぐ後で改行するのは避けて、その後に数式を書くのが無難だと思います。

    

 あるいは、「\begin{align}」などの環境指定を明記するのがいいかもしれません。例えば次のように書きます。

  $$
  \begin{align*}
    x^{2} + 2x + 1 = (x + 1)^{2}\\
     x^{2} - 2x + 1 = (x - 1)^{2}
  \end{align*}
  $$

    

 上のように書くと、空白行が生じてしまうことはありません。

 ちょっと面倒ではありますが、align などの数式の環境指定を明示するのが望ましい、と感じます。

 なお、数式の環境指定としては次のようなものがあるようです。

 displaymath, align, equation, eqnarray

 equationは、《1行のみの数式ではあるが、文章から切り離して別立てで表示したい》という時に指定するようです。

 それぞれの詳細については他のサイトを参照して下さい。


(3) デフォルトのテンプレートを新たに設けてLaTeXを生成

 ここでは、新たなデフォルトのテンプレート document02.latex を作ります。ドキュメントクラスを柔軟に取り扱えるようにしてみます。

 変換処理する前に、グローバル変数 $dclass にドキュメントクラスに関する記述を代入しておくと、それがテンプレートに反映されるようにします。

  $dclass = '\documentclass{jsarticle}'

 上のようにした上で変換処理すれば、ドキュメントクラスとして jsarticle が採用されるようにします。

 $dclass が未定義なら jarticle になる、という仕様です。

    

 まず、document02.latex を所定のディレクトリに書き出すスクリプトを示します。

    

−−−− kmd10.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
        ##
template_str = <<EOS
<% if !$dclass then dclass = '\\documentclass{jarticle}'
   else dclass = $dclass.chomp
   end %>\
<%= dclass %>
\\usepackage{listings}
\\usepackage{hyperref}
<% @converter.data[:packages].each {|pkg| %>\
\\usepackage{<%= pkg %>}
<% } %>\
<% if @converter.data[:packages].include?('fancyvrb') %>\
\\VerbatimFootnotes
<% end %>\
<% if @converter.data[:packages].include?('acronym') %>\
<%   @converter.root.options[:abbrev_defs].each_pair do |k,v| %>\
\\acrodef{<%= @converter.normalize_abbreviation_key(k) %>}[\
<%= k %>]{<%= @converter.escape(v) %>}
<%   end %>\
<% end %>\
\\hypersetup{colorlinks=true,urlcolor=blue}
\\begin{document}
<%= @body %>
\\end{document}
EOS
        ##
template_dir = Kramdown.data_dir
template_file = "document02.latex"
filename = sprintf("%s/%s", template_dir, template_file)
File.open(filename, "w") {|ff|  ff.write template_str}
puts "create a template file: '#{template_file}' in '#{template_dir}'"
−−−− kmd10.rb ここまで

    

 上のスクリプトの実行が成功して、document02.latex が作成されたものとします。

 そのテンプレートを利用するサンプルが下のものです。

 ドキュメントクラスとして jsarticle を用い、\section が「第1章」とか「第2章」のように変換されるようにしています。

 数式の表示(マスブロック)と、rubyスクリプトの表示(コードブロック)のサンプルになっています。

    

−−−− kmd11.rb ここから
#! ruby -Ks
  # (coding: Windows-31J)
require "rubygems"
require "kramdown"
require "erb"
        ## markdownの原稿
md_str = <<EOS
# ゼータ関数\n
リーマン予想に出てくるゼータ関数:ζ(s)は、自然数のs乗の逆数の和である。\n
ζ(2) の値が円周率の平方を6で割ったものになることを
検証したのはオイラーだった。\n
<%= fml_str %>
* * * * * * * *\n
# ゼータ関数の計算プログラム\n
ζ(2) を実際に計算するrubyスクリプトを下に示す。\n
もちろん、無限に存在する自然数全部について計算することは
できないので、1〜5万までに限って計算してみる。\n
~~~~~~~~ ruby
limit = 50000
z = 0.0
1.step(limit,1) {|n| z += 1.0/(n*n).to_f}
puts Math.sqrt(z*6.0)
~~~~~~~~\n
それなりに円周率に近い値が出力されるのではないかと思う。\n
EOS
        ## ゼータ関数の式
fml_str = <<'EOS2'
$$
\begin{align}
   \zeta(s) = \sum_{n=1}^\infty \frac{1}{n^{s}}\\
   \zeta(2) = \sum_{n=1}^\infty \frac{1}{n^{2}} = \frac{\pi^{2}}{6}
\end{align}
$$
EOS2
        ## LaTeX文書に変換
$dclass = <<'EOS3'
\documentclass{jsarticle}
\renewcommand{\presectionname}{第}
\renewcommand{\postsectionname}{章}
EOS3
kdoc = Kramdown::Document.new(ERB.new(md_str).result(binding),
    :template=>'document02')
tex_str = kdoc.to_latex
File.open("test.tex", "w") {|ff|  ff.write tex_str}
puts "'test.tex' を出力しました."
−−−− kmd11.rb ここまで

    

 $dclass に3行分のデータを代入していますが、それがLaTeXの先頭に置かれることになります。ドキュメントクラスとそのオプションの指定です。

 ほんとは $dclass のようなグローバル変数を用いるのではなく、kramdownのオプションに間借りする方がいいのかもしれませんが、かってにkramdownのオプションに入り込んでいいのかどうか躊躇があり、やめました。

    

 計算式は、本文(md_str)とは別に fml_str に代入しています。その上で md_str の中に挿入しています。

 計算式を記述する際、'\' の重ね書きを避けるため、「'EOS2'」としてシングルクォーテーションで囲む形にしています。

△ 目次に戻る


 今回は、この辺でおわりにします。

 kramdownについては、グラフなどの画像の扱い、目次の自動生成、独自仕様に関する記法など、覚え書きとして記しておいた方がいい事柄がいくつかありますが、なかなか書き進めることができないので、また別の機会にしたいと思います。

P.S. kramdown ver 1.3.1(2014/01/10 release)では、他のコマンドを用いることなくpdfファイルを書き出せるようになりました(rubyライブラリのprawnを活用)。
その方法についてのメモをプログラミング雑記帳に「kramdown ver 1.3.1 によるpdfファイルの生成」として記しました。

また、上記の続編をkpdf:rubyのkramdown&prawnによりLaTeXなしでmarkdownをpdfに変換に掲載しました。表をうまく変換できなかった点を修正(2015.01.28)。


Copyright (C) T. Yoshiizumi, 2013 All rights reserved.