LaTeXからFormedTextへの変換
著者:梅谷 武
語句:LaTeX, HTML, FormedText, Python, 正規表現
FormedTextはLaTeXによる文書資産をHTML化する目的で開発されたものです。したがって、LaTeXソースを変換してHTML文書を作成するという使い方を想定しています。ここではプログラミングに慣れていない人がPythonを使って、この変換を行なう方法について解説します。
作成:2006-05-13
更新:2011-03-10
 ここでは『整数論事始』の「Pythonの導入」に従って日本語環境用Pythonが導入されているものとします。OSはWindowsと仮定していますが、LinuxやMacでもほぼ同じです。
 まず作業用フォルダを作ってください。ここでは作業用フォルダがD:\workingであるとしましょう。コマンドプロンプトを立ち上げてこの作業用フォルダをカレントディレクトリにしてください。
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
 
C:¥Documents and Settings¥myacount>d:
 
D:¥>cd working
 
D:¥working>dir
ドライブ D のボリューム ラベルがありません。
ボリューム シリアル番号は 54A5-8EF0 です
 
D:¥working のディレクトリ
 
2006/05/13  14:09    <DIR>          .
2006/05/13  14:09    <DIR>          ..
0 個のファイル                   0 バイト
2 個のディレクトリ  40,403,615,744 バイトの空き領域
 
D:¥working>
この状態でUNICODEエディタを使ってutf-8で次のファイルを作成してください。
# coding: utf-8
 
print u"こんにちは。"
これを次のように実行することができれば準備完了です。
D:¥working>sample1.py
こんにちは。
 Pythonプログラムにコマンド行からファイル名などの引数を渡すには次のようにします。
# coding: utf-8
import sys
 
for arg in sys.argv:
print arg
D:¥working>sample2.py abc def
D:¥working¥sample2.py
abc
def
 Windows上でTeXを使っている方のほとんどはソースファイルの文字コードとしてシフトJISを使っていることでしょう。一方、FormedTextはUNICODEベースで文字コードとしてutf-8を使っていますから、文字コードを変換することが必要になります。日本語環境用Pythonではこのようなコーデック変換が自然にできるようになっています。
 ここではシフトJISファイルをutf-8へ変換するプログラムを示しておきます。

sample3.py

# coding: utf-8
import codecs, sys
 
#
#   コマンド行引数検査
#
if len( sys.argv ) != 3:
print u"sample3.py:シフトJISテキストファイルをutf-8へ変換します。"
print u"使い方: sample3.py [変換元ファイル名] [変換先ファイル名]"
sys.exit(0)
 
#
#   ファイルオープン
#
src = sys.argv[1]
dst = sys.argv[2]
print u"変換元ファイル名:", src
print u"変換先ファイル名:", dst
try:
fin  = codecs.open( src, "r", "shift_jis" )
fout = codecs.open( dst, "w", "utf_8" )
except IOError:
print u"ファイルオープンエラーです。"
sys.exit(0)
 
#
#   shift-jis から utf-8 への変換
#
str = unicode( fin.read() )
fout.write( str )
D:¥working>sample3.py test.tex test.txt
変換元ファイル名: test.tex
変換先ファイル名: test.txt
 
D:¥working>
 Pythonで正規表現を使うことに関してはPyJUGの「正規表現 HOWTO」に解説があります。正規表現を知っていればこのHOWTOとPythonマニュアルで必要な情報はすべて得られます。ここでは正規表現にあまり慣れていない人のために、LaTeXからFormedTextへ変換するプログラムを書くために必要となるような最低限の機能についてやさしく解説しておきます。
 ある文字列が与えられたときに、その文字列に含まれる部分文字列であって、一定のパターンをもつものを検索することが必要になったとしましょう。正規表現regular expressionとはこのパターンを表現するものです。
 例として1.3節のsample3.pyにおいて転送元ファイルがtexファイルであるかどうかを調べることについて考えてみます。texファイルであるかどうかはファイルの拡張子が"tex"であるかどうかで判定できるものとします。拡張子とはファイル名文字列に含まれる最後の"."より後の文字列のことです。Pythonでこのパターンを表現すると次のようになります。
r"¥.tex$"
ここで先頭の'r'はこの文字列がPythonの文字列ではなくて、生の文字列であることを示します。Pythonの文字列では'\'はエスケープ記号として使われますので、'r'を使わないで表現すると
"¥¥.tex$"
となります。正規表現では'\'を頻繁に使いますので、見易くするためにここではすべて生の文字列で表現することにします。
 さて、この正規表現:r"\.tex$"の意味ですが、これを説明する前に、まず正規表現におけるメタ文字について簡単に解説します。

正規表現のメタ文字

 正規表現は文字列のあるパターンを表わす文字列です。正規表現においてはほとんどの文字は、その文字そのものを表わしますが、例外的にその文字以外の意味をもつ文字があります。これをメタ文字meta characterと呼びます。まず4つのメタ文字の意味を示します。
. \n以外の任意の文字とマッチ(DOTALLの場合はすべての文字とマッチ)
^ 文字列の先頭とマッチ(MULTILINEの場合は\nの直後ともマッチ)
$ 文字列の末尾とマッチ(MULTILINEの場合は\nの直前ともマッチ)
次に続く文字の意味を切り替えるエスケープ記号
ファイル名の拡張子は最後のピリオド'.'より後の部分文字列ですから、ピリオド'.'とマッチさせなければなりませんが、これはメタ文字である'.'と重なってしまいます。このような場合はエスケープ記号'\'を付けて'\.'とするとピリオドの意味に切り替わります。
 ここまでの説明で正規表現:r"\.tex$"が与えられた文字列とマッチすることはその文字列の末尾が".tex"になっていることと同じであることがわかりました。この正規表現を使って、1.3節のsample3.pyを、引数の変換元ファイルがtexファイルであるかどうかを判定して、変換先ファイル名を拡張子を"txt"にすることで自動生成するように書き直してみましょう。

sample4.py

# coding: utf-8
import re, codecs, sys
 
#
#   コマンド行引数検査
#
if len( sys.argv ) != 2:
print u"sample4.py:シフトJIS texファイルをutf-8へ変換します。"
print u"使い方: sample4.py [texファイル名(.tex)]"
sys.exit(0)
 
#
#   ファイル名検査とファイルオープン
#
src = sys.argv[1]
if re.search( r"¥.tex$", src ) == None:
print u"sample4.py:シフトJIS texファイルをutf-8へ変換します。"
print u"使い方: sample4.py [texファイル名(.tex)]"
sys.exit(0)
dst = re.sub( r"¥.tex$", ".txt", src )
print u"変換元ファイル名:", src
print u"変換先ファイル名:", dst
try:
fin  = codecs.open( src, "r", "shift_jis" )
fout = codecs.open( dst, "w", "utf_8" )
except IOError:
print u"ファイルオープンエラーです。"
sys.exit(0)
 
#
#   shift-jis から utf-8 への変換
#
str = unicode( fin.read() )
fout.write( str )
D:¥working>sample4.py test.tex
変換元ファイル名: test.tex
変換先ファイル名: test.txt
 
D:¥working>
 Pythonではreモジュールによって正規表現に関する処理を行ないます。上のプログラムでは次の2つの機能が使われています。
re.search( pattern, string )
文字列 string を走査し、正規表現 pattern が最初にマッチするインスタンスを返す。 もしマッチしないなら None を返す。
re.sub( pattern, repl, string )
文字列 string 内で、正規表現 patternとマッチするものを文字列 repl で置換して得られた文字列を返す。
 FormedTextの文書構造はLaTeXの文書構造をHTMLへ変換することを目的として作られたものですので、LaTeXの文書構造をFormedTextへ変換することは比較的容易に行なえます。しかし、紙でできることとWebでできることには共通部分も多いですが、性質や用途がかなり異なる部分もあり、すべてを1対1に変換することはできません。ですから紙とWebそれぞれの特徴を生かした組版や編集をすることが求められます。
 ここでは私が個人的に書いている数学文書を例として、LaTeXからFormedTextへ変換するプログラムの作成の仕方を解説します。しかし、FormedTextはTeXやLaTeXのすべての機能をもっていませんから、変換できない部分に関しては手作業でHTMLを書いたり、FormedTextに新しい機能を追加したりすることが必要になったり、原文を書き換えることが必要になったりするということをお断りしておきます。
 特にFormedTextはどんなPCにでも搭載されている標準的なUNICODEフォントしか使えませんので、TeXの豊富なフォントを使うことはできません。この点に関しては変換作業をする前に十分に考慮しておく必要があります。
 それから数式については、FormedTextの数式タグはTeXの数式表現をそのまま使えるようにしたものですが、TeXの豊富な数式表現のすべてを実装しているわけではありません。またHTMLの性質からどうしても数式が間延びしてしまう傾向があります。このようなことから数式はHTMLでうまく表現できるように書き直すことが必要になる場合があります。
 TeXのフォントをUNICODEフォントへ変換するには次のようにします。
font_conv_table= [
( r"¥¥in", u"∈" ),
( r"¥¥nin", u"∉" ),
( r"¥¥ni", u"∋" ),
( r"¥¥nni", u"∌" ),
( r"¥¥emptyset", u"∅" ),
( r"¥¥subsetneqq", u"⊊" ),
( r"¥¥subsetneq", u"⊊" ),
( r"¥¥subseteqq", u"⊆" ),
( r"¥¥subset", u"⊂" ),
( r"¥¥supsetneqq", u"⊋" ),
( r"¥¥supsetneq", u"⊋" ),
( r"¥¥supseteqq", u"⊇" ),
( r"¥¥supseteq", u"⊇" ),
( r"¥¥supset", u"⊃" ),
( r"¥¥cup", u"∪" ),
( r"¥¥cap", u"∩" ) ]
 
for pair in font_conv_table:
str = re.sub( pair[0], pair[1], str )
ここで注意しなければならないのは、変換する順番と対応するフォントが存在しないときの代替手段を用意することです。
 TeXの数式モード $...$と\[...\] は、それぞれFormedTextのqtタグとqdタグに対応します。これは次のようにして変換することができます。変換後、タグ内の数式記述の調整が必要です。
# $...$ --> <qt>...</qt>
str = re.sub( r"¥$([^$]+)¥$", r"<qt>¥1</qt>", str )
 
# ¥[...¥] --> <qd>...</qd>
str = re.sub( r"¥¥¥[", "<qd>", str )
str = re.sub( r"¥¥¥]", "</qd>", str )
 ここで新しいメタ文字'()','[]'が出てきました。
(...) 括弧()で囲まれた部分の正規表現とマッチするグループを示す。
[...] 括弧[]で囲まれた文字集合のいずれかの文字とマッチする。
正規表現はグループに分割することができます。グループには番号がつけられ、0番は全体を表わし、1番以降は正規表現に含まれる括弧が表わすグループが左から順番に対応していきます。各グループにマッチする部分文字列は'\(グループ番号)'で参照することができます。
 また、文字集合を表わす[..]内ではメタ文字は通常の文字として扱われ、'^'はそれに続く文字の補集合を意味します。ですから"[^]"は''でない任意の文字を意味します。さらに繰り返しを意味するメタ文字'+'が出てきました。繰り返しを表わすメタ文字としては次の3つがよく使われます。
* 直前の正規表現の0回以上の繰り返し
+ 直前の正規表現の1回以上の繰り返し
? 直前の正規表現の0回または1回の繰り返し
 TeXの数式環境:equation、eqnarray*、eqnarrayは、それぞれFormedTextのeqn、qdnarray、eqnarrayタグに対応します。これは次のようにして変換することができます。これらの環境についてはLaTeXとFormedTextでは構文が異なっていますので変換後、タグ内の数式記述の修正が必要です。
# equation --> eqn
str = re.sub( r"¥¥begin¥{equation ¥*¥}", "<eqn>", str )
str = re.sub( r"¥¥end¥{equation ¥*¥}", "</eqn>", str )
 
# eqnarray* --> qdarray
str = re.sub( r"¥¥begin¥{eqnarray¥*¥}", "<qdarray>", str )
str = re.sub( r"¥¥end¥{eqnarray¥*¥}", "</qdarray>", str )
 
# eqnarray --> eqnarray
str = re.sub( r"¥¥begin¥{eqnarray¥}", "<eqnarray>", str )
str = re.sub( r"¥¥end¥{eqnarray¥}", "</eqnarray>", str )
 もしLaTeX側がFormedTextのブロック構造と同じような書き方をしていれば、次のように機械的に変換することができます。
# ¥section{...} --> ¥section{...}
str = re.sub( r"¥¥section¥{([^}]*)¥}",
r"¥section{¥1",} str )
 
# ¥subsection{...} --> ¥subsection{...}
str = re.sub( r"¥¥subsection¥{([^}]*)¥}",
r"¥subsection{¥1",} str )
 
# ¥subsubsection*{...} --> ¥paragraph{...}
str = re.sub( r"¥¥subsubsection¥*¥{([^}]*)¥}",
r"¥paragraph{¥1",} str )
 
# proposition
str = re.sub( r"¥¥begin¥{proposition¥}¥[([^¥]]*)¥]",
u"¥theorem{命題:"} + r"¥1", str )
str = re.sub( r"¥¥end¥{proposition¥}", "¥n", str )
str = re.sub( r"¥¥begin¥{proposition¥}",
u"¥theorem{命題",} str )
str = re.sub( r"¥¥end¥{proposition¥}", "¥n", str )
 
# proof
str = re.sub( r"¥¥begin¥{proof¥}¥[([^¥]]*)¥]",
r"¥proof{¥1",} str )
str = re.sub( r"¥¥end¥{proof¥}", "¥n", str )
str = re.sub( r"¥¥begin¥{proof¥}", u"¥proof{証明",} str )
str = re.sub( r"¥¥end¥{proof¥}", "¥n", str )
しかし、LaTeX側がFormedTextのブロック構造と対応しないときには、どのように対応させるかを変換プログラムに反映させなければなりません。
 ルビはブラウザによってはHTMLでも表現できますが、FormedTextではクロスブラウザ的なJavaScriptを使って、マウスをその語句にのせるとルビや注記が表示できる rem タグを用意していますので、ここでは rem タグに変換することにします。これは次のようにします。
# ルビ ¥ruby{¥1}{¥2} --> <rem>¥1:¥2</rem>
str = re.sub( r"¥¥ruby[¥s]*¥{([^}]*)¥}[¥s]*¥{([^}]*)¥}",
r"<rem>¥1:¥2</rem>", str )
ここで新しいメタ文字'\s'が出てきました。文字集合と意味するメタ文字には次のようなものがあります。
¥d 数字1文字とマッチする。
¥D ¥dの補集合。
¥s 空白文字1文字とマッチする。(空白文字:¥t,¥n,¥r,¥f,¥v)
¥S ¥sの補集合。
¥w 英数字1文字とマッチする。
¥W ¥wの補集合。
この正規表現は、¥rubyと{...}、{...}と{...}の間に空白文字が入ってもマッチするようになっています。
 TeXにおける箇条書きの書き方はさまざまですが、私の場合はすべてdescription環境で書いていて、FormedTextに対応するqlist、qdlistタグがあるので簡単に変換することができます。変換後の調整は必要です。
# description --> qlist
str = re.sub( r"¥¥begin¥{description¥}", "<qlist left='15'>", str )
str = re.sub( r"¥¥end¥{description¥}", "</qlist>", str )
 
# ¥item[...] --> ...:
str = re.sub( r"¥¥item¥[([^¥]]*)¥]", r"¥1:", str )
 この文書で説明した内容に関するサンプルプログラムを公開しています。自由にご利用ください。
数  学
正規表現 regular expression
メタ文字 meta character
 
Published by SANENSYA Co.,Ltd.