Skip to content
k16shikano edited this page Jan 13, 2012 · 2 revisions
<title lang="ja"> # XMLタグからLaTeXへの変換をお気軽に </title>

There's so many XML-like documents.

世間有很多XML風的文件

世の中にはXMLっぽいドキュメントがたくさんあります。 ここで、わざわざXML「っぽい」と言ったのは、XMLに対して「人間が読み書きするものではない」という意見が根強いからです。 厳密なXMLについては、確かにそのとおりだと思います。 でも、開始タグと終了タグの「入れ子」というアイデアそのものは、まっとうなドキュメントの構造を表現するには妥当な仕掛けではないでしょうか。 実際、みんなHTMLくらいであれば苦もなく手書きできますよね。 それをブラウザでレンダリングして、表示が意図したものでなかったら、 タグの綴じ忘れの可能性もありますが、そもそも自分で書き下した文書の構造に欠陥がある場合も少なくないでしょう。

HTMLやDocbookのような素性のはっきりしているXMLであれば、どこかに体の良いLaTeXへのコンバーターを作っている人がいるかもしれません。 でもXMLっぽいタグで構造を与えられているだけのテキストファイルには、そんな出来合いのコンバーターは望めません。自分で作るしかないのです。

そんな野良XMLからLaTeXへのコンバーターを何回か作ってみると誰でも気づきますが、やることは毎回だいたい同じです。 つまり、

  1. ある構造を見つけたら
  2. 適切なLaTeXの環境やコマンドを適用し
  3. その構造の中身を再帰的に処理
  4. その構造より後ろをやはり再帰的に処理
  5. 3と4を連接

です。

しかも、このうちで「XMLっぽいもの」の種類に応じて変化するのは、2の部分だけです。 ようするに「どのタグをどうLaTeXのコマンドに置き換えるか」というルールだけが毎回異なるわけです。 そこで、適当なルールを与えてやるとコンバーターを生成してくれうような仕組みを作ることにしました。

ルールの書き方

ルールとして与えるのは、最初にやること、間にやること、最後にやること、の三つです。 それぞれ出力したい文字列か、もしくはString -> Stringなプロシージャを指定します。 文字列を指定するのは、出力したい文字列が固定的なものである場合です。 タグの本体の文字列に対してなんらかの処理をした結果を出力したいときは、プロシージャを指定します。

たとえば段落タグ用の処理ルールであればこんな感じになります。

(define-rule
  ""
  trim
  "\n\n")

先頭には何も出力せず("")、本体の文字列については基本的なエスケープ処理などを行い(trimプロシージャ)、 最後にはLaTeXの段落を作るために改行を二つ出力しています(\n\n)。

このルールをpタグに関連づけるには、次のようにします。

(define-tag p
  (define-rule
    ""
    trim
    "\n\n"))

デフォルトでは、これをrules.scmに登録することで、pタグに対する変換ルールとして利用されます。

<title lang="ja"> ## 包括的なルールの適用 </title>

前節で登場したdefine-tagdefine-ruleを使えば、自分の手元にある野良XMLを見ながらルールをどんどん追加していくだけで、 LaTeXファイルへの変換が簡単に扱えるようになります。 ところが、実際にルールをどんどん追加していく作業をしてみると、似たようなルールがたくさん繰り替えされることに気がつくでしょう。> 名前だけ違う同じようなルールをいちいち追加していくのも面倒な話です。

そこで、似たようなLaTeXの出力になってほしい複数のタグに対して一気にルールを適用する仕組みも用意しました。 ルール定義define-ruleとタグへのマッピングdefine-tagが別々のレイヤに分かれているのは、これがしたいからです。

たとえば、タイプライタフォントを意味する<tt>タグを\ttコマンドにするのも、イタリックフォントを意味する<it>タグを\itコマンドにするのも、 「これこれという名前のLaTeXコマンドになるべし」という共通のルール(make-latex-cmd name)としてくくってしまうことができます。

(define (make-latex-cmd name)
  (define-rule
    (lambda ()
      (list "\\" name "{"))
    trim
    (lambda () (list "}"))))

(define-tag tt (make-latex-cmd "tt"))

(define-tag it (make-latex-cmd "it"))

make-latex-cmdが一種の「ルール生成器」であり、ttもitも同じ生成器からルールを作ってしまうという発想です。 さらに一歩進んで、「この名前のタグには同じ生成器を単純に適用しろ」という仕組みdefine-simple-rulesも用意しています。

(define-simple-rules make-latax-cmd
  tt it)

後になって<footnote>の変換も同じルール生成器make-latax-cmdを適用すればいいだけだと気がついたら、単純に付け足すだけです。

(define-simple-rules make-latax-cmd
  tt it footnote)
<title lang="ja"> ## 親子とか属性とか </title>

野良XMLのいいところは、構造がついていることです。 しかしその利点は、あまり構造的とはいえないLaTeXへの変換では厄介な面もあります。 たとえば、この文書では<chapter>と<sect1>の中に<title>という同じタグがあるのですが、 これをLaTeXでそれぞれ\chapterと\sectionに変換するには、<title>用のルールから上位の構造である<chapter>や<sect1>が見える必要があります。

全体の木構造を参照すればいいだけなので難しいことはないのですが、そのたびに木を見にいくコードを書くのは面倒です。 ルールを書くときに$parentとか$childといった変数で参照できるようになっていれば便利ですよね。 <title>ならこんなふうにルールを定義できます。

(define-tag title
  (define-rule
    (lambda ()
      (list
       (cond (($parent? 'chapter)  "\\chapter{")
             (($parent? 'sect1)    "\\section{")
             (else (error "title" $parent)))))
    trim
    "}"))

もうひとつ野良XMLで面倒なのは、属性です。 これも木を見ればいいだけではあるけれど、簡単にするために、($@ lang)などとして属性(この場合はlang)の値を取り出せるようにしてあります。 たとえば<image src="fig1.png">に対応するルールはこんなふうに書けます。

(define-tag image
  (define-rule
    (list "\\includegraphics"
          #`"{,(ifstr ($@ 'src))}"
    trim
    ""))
Clone this wiki locally