2013/12/31

2014年賀状的なもの

今年は喪中だから年賀状じゃないんだからね。

%!
<< /PageSize [285 420] >> setpagedevice

/setrandcolor {
  /m exch .1 add def
  /r1 {rand 5 mod 1 div m add} def
  /r2 {rand 3 mod 3 div m add} def
  /r3 {rand 3 mod 3 div m add} def
  r1 r2 r3 setrgbcolor
} def

/f {% def
    dup dup dup
    4 mod 0 eq {(2) 4 1 roll -1 0 rmoveto} if 
    4 mod 1 eq {(0) 3 1 roll -3 0 rmoveto} if 
    4 mod 2 eq {(1) 2 1 roll -3 0 rmoveto} if 
    4 mod 3 eq {(4 ) -5 0 rmoveto} if
} def

/drawyear { % def
  4 1 64 { % for
    /i exch def
    /v { i i mul i mul 0.0003 mul } def
    /w { i 15 add 2 div } def
    /d { -360 w mul 2 3.1415 mul 65 mul div } def % -360w/(2pi*65)
    d rotate
    /Titania findfont
    2 w mul scalefont setfont
    v 0 rmoveto
    0.001 setrandcolor
    i f show
  } for
} def

1 1 20 { % for
    /n exch def

    93 160 translate 
    135 rotate
    0 0 moveto
    drawyear

    100 rotate
    150 -140 moveto
    0 setgray
    /Palatino-Bold-Italic findfont 12 scalefont setfont
    n =string cvs show
    0 -2 rmoveto (/) show
    0 -2 rmoveto (20) show
    showpage 
} for

例年に増して芸がない感じもするけどシンプルなのがいちばんということで。

2013/12/20

Learn You The TeX for Great Good!
 「第3章 マクロ初級講座」

[前回までのあらすじ]

LaTeX の原稿に記されている文字を別の文字に置き換えて出力したくて TeX マクロを書いてみたけど、なぜかうまく動かない。 TeX はチューリング完全なプログラミング言語だって聞いたけど、こんな基本的なテキスト編集の機能さえ持ち合わせてないのでは話にならない。 TeX も LaTeX も滅ぶべき。

Note:この記事は、TeX & LaTeX Advent Calendar 2013 の20日目の記事です。昨日は zr さんによる「テキストの装飾の指定: CSSとLaTeXの比較」です。明日の21日目は shigeyas さんの予定です。

LaTeX のことはきっぱり忘れてください(「TeX」といったら1982年に書き直されたいわゆる TeX82 と、その直系にあたるバージョン3のバグフィックス版たちのことです)。 ここでは TeX で文字の置換に「近い」ことをしてみましょう。 再帰的な TeX マクロをどうやって書くか、何となく感じてもらうのが本章のゴールです。

再帰!帰!!

いま、与えられた文字列中のすべての文字を「*」に変換して伏字化した結果が欲しいとします。「brainfuck」→「*********」という具合です。パスワードの入力欄みたいな雰囲気ですね。

ぱっと思いつくのは、こんな再帰的なマクロを使った例です。これは実際、上記の例「brainfuck」→「*********」ならうまく変換してくれます。

\def\ast#1{\recur#1!}
\def\recur#1{%
  \ifx!#1\else*\expandafter\recur\fi}

\ast{brainfuck} とすると TeX の中で何がおきるのか、順番に見ていきましょう。 \ast マクロは、引数の末尾に ! をつけてから、ブレース {} を取っ払った状態で \recur に引き渡します。 「 \recur brainfuck! 」という形で \recur マクロに引数が渡されるということです。

この状態で \recur マクロが引数だと思うのは、 brainfuck! という文字列ではなく、先頭の b だけです。 ナンデと思うかもしれませんが、ブレースがない状態でマクロに何かを渡すと、その先頭だけが引数としてマクロに渡されるのです。 これは、TeX において \def を使って定義できるのは、一般的なプログラミング言語における関数とかサブルーチンとかクロージャーとかではなく、「置き換えルール」だからです。

置き換えルールを \def で定義する際には、置き換えの対象となるパターンを指定するときのプレースホルダとして、 #1 のようなパラメータ引数が使えます。 たとえば \def\recur#1{foo #1 bar} によって「定義」されるのは、「\recur の直後に「何か」が一個あったら、それを #1 で示されるパラメータ引数に束縛して、 foobar で囲む」という置き換えルールです。

じゃあ、なぜ最初に \ast{brainfuck} としたときには、文字列 brainfuck#1 に束縛されたのでしょうか? それは、ブレースで囲まれて渡されたパラメータ引数は、そのブレースを取り除いた状態でまるごと束縛されることになってるからです。

トークンとトークンリスト

上記では「何か」とぼかしましたが、置き換えルールが適用される対象は「トークン」と呼ばれています。 トークンとは具体的には何なのでしょうか? 

簡単にいうと TeX のトークンとは、「文字(カテゴリコードという TeX 特有の属性つき)」または「コントロールシーケンス(いわゆる \ ではじまるやつ)」です。 TeXは、読み込んだファイルに含まれる文字や数字や記号を、まずこの「トークン」の列へと咀嚼します。 そうして得られたトークンの列は、トークンリストと呼ばれます。

Note:文字トークンとコントロールシーケンス以外に、マクロ定義に出てくる #1 みたいなパラメータ引数もトークンとみなされてるのですが、忘れてかまいません

このトークンリストには、原稿を書いた人が入力したコントロールシーケンスなんかもみんな含まれているわけですが、それを最終的に出力するデータ用のトークンリストへと展開するのがTeXの次の仕事です。

ここまでをまとめると、 \ast{brainfuck}\ast の定義によって \recur brainfuck! に置き換えられ、 \recur brainfuck!\recur の定義によって \ifx!b\else*\expandafter\recur\fi rainfuck! に置き換えられます。 b だけが \recur に渡された結果、残りの rainfuck! が後ろにくっついているのに注意してください。

\ifx は直後のトークンを比較して展開する場所を制御

\ifx は、直後の2つのトークンが「同じ」なら \else または \fi までを読み込み、違ったら読み飛ばすという TeX の命令です。

さきほど \ast マクロを利用したときに、ブレースを使って引数を {foo} のように囲んでグループとして渡せるといいました。 ブレースで囲んだ「何か」は、トークンではありません。 グループを開始する左ブレースというトークン、ブレースの間にあるトークンたち、それにグループを終了する右ブレースというトークンからなる、トークンリストの一部です。 したがって、たとえば \ifx {foo}\x のようにしても、 x が文字列 foo か調べて条件分岐することにはなりません。 もし \ifx {foo}\x と書いたら、 {f が「同じ」かどうか調べた挙句、当然 {f は「同じ」じゃないので、 \else または \fi まで読み飛ばされて意図しない結果になります。

Note\ifx は直後の2つのトークンをとって、それらを展開せずに、同じかどうかを調べます。 もしマクロ定義の中で \ifx #1! のように書いて(#1! が逆になってることに注目)、展開時に #1brainfuck が束縛されたとしたら、 先頭の2つのトークン br が比較されて常に \else または \fi まで読み飛ばされてしまいますよ。

\ast{brainfuck} の例に戻りましょう。 !b は「同じ」じゃないので、次の条件分岐は、

\ifx!b\else*\expandafter\recur\fi rainfuck!

else節のようなものに処理がわたってこんなふうな結果になるように思えるかもしれません。

*\expandafter\recur rainfuck!

しかし、実際にはこうなります。

*\recur rainfuck!

\expandafter はいったいどこにいってしまったんでしょう? 

実は \ifx のような条件分岐命令は、TeX が「展開」するトークンを条件に基づいて制御するものであり、「条件を満たさなかったら \else から \fi までの部分を実行する」ような代物ではありません。 この場合は、「条件を満たさなかったら \else までを読み飛ばす」だけです。 なので \ifx!b まで展開した TeX は、条件を満たさないので次の \else まで読み飛ばし、 *\expandafter\recur\fi rainfuck! の展開を試みることになります。

展開順の制御は \expandafter から

いまはトークンリストを展開しているので、最初に出てくる * は単なる文字ではなく、カテゴリコードという値がついたトークンです。 最終的にはアスタリスクが出力されるし、単なる文字との違いを意識しなければならないわけではないのですが、トークンリストに読み込まれているのは、あくまでも記号などの文字を意味するカテゴリコード12がついた文字トークンです。

その次にくるのが、\ifx によって消えてしまったかのように見える \expandafter です。これは「直後のトークンの展開を待たせる」という仕事をする TeX のプリミティブです。 ここに \expandafter があるため、 TeX はトークンリストで次にくる \recur のことは忘れて、 \fi を展開します。これは言うまでもなく、条件分岐が一つ終わりますという命令です。 なので、先に \ifx から始まった条件分岐は、この時点で無事に終わります。

ここで、先ほど \expandafter されていた \recur が元の位置に戻されます。 その後の rainfuck! は、全部文字トークンです。 結果として得られるトークンリストが *\recur rainfuck! というわけです。

というわけで、 \recur brainfuck!*\recur rainfuck! になりました。 ここで再び \recur が展開されて **\recur ainfuck! になり、さらに \recur が展開されて ***\recur infuck! になり、…… *********\recur ! まで同様に展開されていきます。 最後の ! は、 \recur に読まれると \ifx !! となり条件を満たすため、今度は \fi まで一気に読み飛ばされます。 その結果得られるトークンは何もないので、最終的に一連の文字トークン ********* が TeX の出力処理に回ることになります。 これが TeX プログラミングにおける末尾再帰です。まるでパズルですね。

さらに深く

この \ast には重大な制約があります。 まず明らかな制約として、再帰の基底ケースとして ! を使っていることから、 ! が含まれる文字には使えません。 さらに、文字列の中に対応するブレースで囲まれた部分があると、その全体が一つの * に変換されてしまうという問題があります ( \recur {foo}bar が何に展開されるかを上記と同じ要領で考えると、なぜだかわかりますよね)。

Note:ブレースで囲んで入力するとトークンリスト上に1つのトークンとしてまとめられるわけではないことに注意してください。このようなブレース区切りは、マクロ定義にあるパラメータ引数(#1 sなど)にトークンをマッチさせるときだけ意味をなします。 たとえば \ifx の条件チェックでブレース区切りが意味を持たないことは説明しましたが、さらに、たとえば \ifx ... \else ... \fi の中で左右のブレースが閉じていないようなマクロを書くことも可能です。 この性質は、実用的なマクロの多くでも実際に利用されています。

いちばん大きな制約というより欠陥は、この \ast マクロはスペースを文字として勘定してくれないことです。 TeX は、スペース(空白)を読み込むとき、暗黙に二通りの解釈をしてトークンリストを作ります。 空白のつもりでファイルに入力したのにトークンリストには何も詰め込まれなかったり、逆に無意識に入れていた何かが空白を表すトークンとしてトークンリストに詰め込まれたりするのです。 空白を意図通りにトークンリストに詰め込むためには、 TeX の文字解釈と展開とマクロのパラメータに対するさらなる理解が無駄に求められます。

そもそも、ここまで「文字列」とぶっちゃけて言ってきましたが、 TeX にそんなデータ型はありません。 ドキュメントを書くのに入力している文字たちは、あくまでもトークンリスト上のトークンを形成するものです。 それを意識しながら TeX ソースを読んだり書いたりすると、 TeX マクロってエンジニアリング的には微妙だけど仕組みとしては意外と面白いかもと感じてきます。 見た目のプログラミング言語っぽさに惑わされると不自由さにがっかりするかもしれませんが、トークンリストの展開順序をコントロールして遊ぶゲームだと思うとなかなか挑戦しがいがある時間泥棒だったりします。

最後に

!」の制約はおいといて(ほんとはおいといちゃだめだけど)、代入を使わずにブレースとスペースの問題に対処すると、「TeX 芸人二段」 という称号がもらえることになっています。 ひまなときにチャレンジしてみよう!

この記事は「Learn You A Haskell for Great Good!」というHaskellのチュートリアルのパロディーです。ほかの章はありません。TeXに関する説明には慎重を期したつもりですが、内容には執筆者の勘違いが含まれている可能性があるので、うそを見つけたらぜひコメントをください。

2013/12/18

colortbl で \cline を使いたい

日本でLaTeXが生きのこるには、縦横無尽に罫線が引かれた彩り豊かな表を描けなければならない。 でも、表のセルに色を塗るための colortbl パッケージと、横線を好きな場所に引くための標準的な方法である \cline には、罫線までもが色で塗りつぶされてしまうという困った問題があります。

\begin{tabular}
  {|>{\color{white}\columncolor{red}  }c
   |>{\color{white}\columncolor{green}}c
   |>{\color{white}\columncolor{blue} }c|} \hline
  a & b & c \\ \cline{1-1}\cline{3-3}
  1 &   & 3 \\ \hline
\end{tabular}

本当なら、こんな具合に、左右の列だけにセル間の横罫線が引かれてほしいわけです。

これは TeX & LaTeX Advent Calendar の18日目の記事です。 昨日はabenoriさんの「可換図式を書こう:tikz-cdを使う.」です。

公式見解

この \cline が上書きされてしまう現象は、 colortbl のマニュアルでも言及されている既知の問題です。 いわく、「これは \cline の仕様なので代わりに \hhline を使え」とあります。

10 Less fun with \cline
Lines produced by \cline are coloured if you use \arrayrulecolor but you may not notice as they are covered up by any colour pannels in the following row. This is a `feature' of \cline. If using this package you would probably better using the - rule type in a \hhline argument, rather than \cline.

実際、"TeX - LaTeX Stack Exchange" における質問でも、 \hhline を使った回答が寄せられているようです(参照1参照2)。 しかしぶっちゃけ \hhline はまったく解決策になりません。 それどころか、今度は罫線を引きたくない箇所に白い線が出現したり、縦罫線が切断されたりしてしまいます。

\usepackage{hhline}
...
\begin{tabular}
  {|>{\color{white}\columncolor{red}  }c
   |>{\color{white}\columncolor{green}}c
   |>{\color{white}\columncolor{blue} }c|} \hline
  a & b & c \\ \hhline{-|~|-}
  1 &   & 3 \\ \hline
\end{tabular}

これはひどい。

ごまかし方

colortbl で特定の列にだけ横罫線を引くことは不可能なのでしょうか。 実は、方法がないではないので、それを紹介します。ただし後述するように、厳密にいうとこの解法には落とし穴があるので注意してください。

そもそもなんで罫線が塗りつぶされるかというと、 colortbl では罫線を引くプロセスのあとにセルに色を塗っているからなのですが、これは TeX の仕組み的にはチートできません。 色を塗るにはセルの大きさが決定していなければならず、その段階では罫線も引き終わることになっているからです。

でも逆に考えれば、セルの高さに対して罫線の位置をはじめからずらして引くようにしてしまえば、罫線を微妙に避けて色を塗ってくれるはずです。 そこで次のような \cline の定義(もとは latex.ltx にあるけど colortbl パッケージで上書きされてるもの)を……

%% \@cine from colortbl.sty
\def\@cline#1-#2\@nil{%
  \omit
  \@multicnt#1%
  \advance\@multispan\m@ne
  \ifnum\@multicnt=\@ne\@firstofone{&\omit}\fi
  \@multicnt#2%
  \advance\@multicnt-#1%
  \advance\@multispan\@ne
  {\CT@arc@\leaders\hrule\@height\arrayrulewidth\hfill}%
  \cr
  \noalign{\vskip-\arrayrulewidth}}

次のように書き換えてプリアンブルに書きます(\makeatletter を忘れずに)。

\def\@cline#1-#2\@nil{%
  \noalign{\vskip-\arrayrulewidth}
  \omit
  \@multicnt#1%
  \advance\@multispan\m@ne
  \ifnum\@multicnt=\@ne\@firstofone{&\omit}\fi
  \@multicnt#2%
  \advance\@multicnt-#1%
  \advance\@multispan\@ne
  {\CT@arc@\leaders\hrule\@height\arrayrulewidth\hfill}%
  \cr}

元の定義で一番最後にあった \noalign{\vskip-\arrayrulewidth} を先頭にもってきただけで、ほかはそのままです。 これだけで、ふつうに \cline を使えば横罫線が出現するようになります。もちろん不要な箇所に白線が出ることもありません。

\begin{tabular}
  {|>{\color{white}\columncolor{red}  }c
   |>{\color{white}\columncolor{green}}c
   |>{\color{white}\columncolor{blue} }c|} \hline
  a & b & c \\ \cline{1-1}\cline{3-3}
  1 &   & 3 \\ \hline
\end{tabular}

やったね。

うまくないところ

この方法は、罫線を、罫線の幅だけ上にちょっとずらすことで、一見成功しているように見える結果を得ています。 なので、ずれてない本来の位置に罫線を一緒に引いてみると、アラがわかります。

\begin{tabular}
  {|>{\color{white}\columncolor{red}  }c
   |>{\color{white}\columncolor{green}}c
   |>{\color{white}\columncolor{blue} }c|} \hline
  a & b & c \\ \cline{1-1}\cline{3-3}\hilne
  1 &   & 3 \\ \hline
\end{tabular}

残念ですね。

2013/12/10

XML を変換するなら Haskell(HXT) で

思えば自分にとってプログラミングとは、XML っぽいドキュメントを操作することでした。 XML パーザを書いてみたりSXML でいろいろがんばったり、やる前から XSLT に挫折したり。 とにかく気持ちよく XML を操作する方法をこれまでいろいろ探してきたわけですが、今はこう断言できます。 XML をいじるのに最高な道具は HXT(Haskell XML Toolbox)であると。 そこで自分用のメモを兼ねて、「こんなふうに HXT を使ってます」を紹介しようと思います。 基本的な Haskell 力が高くないので、「理解を間違っている」とか「もっとこう使うべき」というツッコミ歓迎です。

HXT がうれしい理由

XML のような木構造のデータをいじろうと思ったとき、まず思いつくのは、根っこから順番に要素をたどりつつ処理していく方法です。 しかし、この方法は案外と融通が利きません。 常に部分木だけを見ればいい仕事なら気楽なのですが、親の要素に戻ったり、兄弟要素にアクセスしたりする必要があるときは、 「いま木全体のどこを処理してるか」という情報を持ち歩くしかないからです。 そして、「いま木全体のどこを処理してるか」を意識しながらコードを書くには、かなりの精神力が要求されます。

HXT では、これとはちょっと違う方法で XML の木構造をいじれます。 「木のこの部分を何かに変換」とか「木のここを取り出す」とか、そういう個別の処理を組み合わせることで全体の変換処理を書けるのです。 同様のアプローチをとるツールとしては XSLT がよく知られていますが、 HXT では各処理を「ふつうのプログラミング言語」である Haskell のコードとして書けるので、 はるかに柔軟だといえると思っています(なにせ XSLT に挫折しているので断言するほどの自信はない)。

組み合わせたい各処理は、「XML の木」から「XML の木」への関数だと思えます。それらをいろいろ組み合わせたいのだから、これは「XML の木構造、および、XML木→XML木な関数たち」から「XMLの木構造、および、XML木→XML木な関数たち」への対応だと思えます。というわけで、 HXT では変換処理を Haskell における圏の間の関手 >>> を使ってつなげたりできます。

とはいえ、 >>> だけだと条件分岐もできないし、大して面白い組み合わせが書けないので、 HXT ではもうちょっと広い概念である「アロー」を使って XML 木の変換を組み合わせられるようになっています。アローについて詳しくは『関数プログラミングの楽しみ』の第10章を読んでください(言うまでもなくこれは宣伝です)。

変換を組み合わせる前に

まずは変換処理が一つしかない場合から書いてみます。 <div class="chapter">...<div> という要素を <h1>...</h1> に包み直すという、単純な変換がやりたいとしましょう。 構造を変えるのではなく、属性に応じて要素名を書き換えるだけの処理です。

なにはともあれ、最初に HXT まわりのモジュールを読み込みます。

import Text.XML.HXT.Core
import Text.XML.HXT.Arrow.XmlArrow
import Control.Arrow

これから変換処理をアローとして書いていくわけですが、外界とのやり取りにもアローを使います。 ここでは、ファイルなどの URI を受け取って入力を行うアロー readDocument と、同じく出力を行うアロー writeDocument を使います。 それぞれ入出力の書式などを指定することもできて、 DTD の指定やインデントの有無を設定できるのですが、細かいことはドキュメントを参照してください。 さらに、 main の中でアローを実行するために、 runX という関数を使います。

readDocumentwriteDocument、 それにこれから書く予定の「divh1 に変換する」アローである chapterToH1 という三つのアローをすべて >>> でつないで一つのアローを作り、 それを runX で実行するだけの超単純な main は、こんな感じになります。

main :: IO ()
main = do
  runX (readDocument [] "test.html"
        >>>
        chapterToH1
        >>>
        writeDocument [] "result.html"
        )
  return ()

それでは、肝心の変換処理 chapterToH1 を書いてみましょう。やりたいことは、「もし divclass 属性に chapter を持っていたら要素名を h1 にして、 class 属性はいらないので消してしまう」です。これはそのまま、 XmlTree から XmlTree へのアロー ArrowXml として、こんな Haskell の関数で書けます。

chapterToH1 :: (ArrowXml a) => a XmlTree XmlTree
chapterToH1 =
    processBottomUp
    (ifA (hasName "div" >>> hasAttrValue "class" (=="chapter"))
             ((setElemName $ mkName "h1") >>> removeAttr "class")
             (this))

狙いの div がどこに出てくるかわからないので、 processBottomUp を使って木を再帰的に見るようにしています。 ifA は条件分岐のためのアローです。条件にマッチしない要素はそのまま残しておきたいので、 else に相当する部分では this アローを設定しています(もしマッチする要素だけ残したいなら none というアローを使います)。そのほかのアローの役割は関数名でわかりますね。こんな具合にアローをつなげて、目的の処理を表すアローを書くわけです。

Haskellのパワーを活用する

先ほどの例と同じ要領で「div 要素を class 属性に基づいて都合のいい名前の要素に変換する」アローをコピペで量産し、それら全部を >>> でつなげるだけでも、それなりに役に立つXML変換プログラムが書けそうですが、それはつらいので、できればアローを作る関数を一つだけ作って、それを使って作ったアローたちを >>> で fold できないものかなと考えますよねふつう。実際、アロー版のfoldといえる seqA が用意されているので、こんなふうに書けます。

(seqA . map (uncurry divClassToElem)
           $ [("chapter", "h1")
             ,("section", "h2")
             ,("para", "p")
             ])

これを runX の中に書けばいいわけです。 divClassToElem はこんなふうに定義すればいいでしょう。

divClassToElem :: (ArrowXml a) =>
                  String            -- if DIV is this class,
               -> String            -- turn that into this element.
               -> a XmlTree XmlTree
divClassToElem cls elm =
    processTopDown
    (ifA (hasName "div" >>> isClass cls)
             (tameClass elm)
             (this))

tameClass :: (ArrowXml a) => String -> a XmlTree XmlTree
tameClass elm = (setElemName $ mkName elm) >>>
                removeAttr "class"

isClass :: (ArrowXml a) => String -> a XmlTree XmlTree
isClass val = hasAttrValue "class" (==val)

兄弟をまとめる

もうちょっと現実的な例題として、いかにも組版ソフトが吐き出したっぽい HTML を、それなりに整った HTML に変換したいとします。つまり、こんなデータを……

<html>
<head></head>
<body>
<div class="chapter">XSLTを捨ててHXTを使おう</div>
<div class="para">HXTでXSLTの処理系さえ実装できる!</div>
<div class="section">Allowとは</div>
<div class="para">こまけーこたーどーでもいいんだよ。</div>
<div class="section">HXTの使い方</div>
<div class="bulletA"><span class="shell">cabal install hxt</span></div>
<div class="bulletB"><span class="haskell">import Text.XML.HXT.Core</span></div>
<div class="bulletB"><span class="haskell">let doc = readString [] text</span></div>
<div class="bulletC"><span class="haskell">runX doc</span></div>
</body>
</html>

こんなふうに変換したいとします。

<html>
  <head/>
  <body>
  <h1>XSLTを捨ててHXTを使おう</h1>
  <p>HXTでXSLTの処理系さえ実装できる!</p>
  <h2>Allowとは</h2>
  <p>こまけーこたーどーでもいいんだよ。</p>
  <h2>HXTの使い方</h2>
  <ul>
    <li>
      <code>cabal install hxt</code>
    </li>
    <li>
      <code>import Text.XML.HXT.Core</code>
    </li>
    <li>
      <code>let doc = readString [] text</code>
    </li>
    <li>
      <code>runX doc</code>
    </li>
  </ul>
  </body>
</html>

この例で面倒なのは、 class 属性の値だけに基づいて、元の XML で潰れてしまっている構造(この場合は ul 要素)を取り出さなければならないところです。 面倒とはいっても、リストみたいなデータ構造から「bulletA」~「bulletなんとか」までの連続する要素をとってくるような問題そのものは、Haskell であれば Data.ListgroupBy を使って割と単純に解けてしまいます。 特定の属性の値という条件のままで考えるとちょっと込み入ってしまうので、代わりに要素名がそれぞれ「bulletなんとか」になってると単純化して考えてみます。 HXT なら、要素名を属性の値に変換した木を作るアローを作って前段にかませばいいだけなので、こうみなしても後で困ることはありません。

import qualified Text.XML.HXT.DOM.XmlNode as XN
import Data.List

groupBullet :: [XmlTree] -> [XmlTree]
groupBullet ts = map bulletlines $ groupBy isBullet ts
  where bulletlines [x] = x
        bulletlines a@(x:xs) = XN.mkElement (mkName "ul") [] a

isBullet :: XmlTree -> XmlTree -> Bool
isBullet t1 t2 = case (XN.getElemName t1, XN.getElemName t2) of
  (Just x', Just y') -> let x = qualifiedName x'
                            y = qualifiedName y'
                        in    (isPrefixOf "bullet" x)
                           && (isPrefixOf "bullet" y)
                           && (not $ isPrefixOf "bulletA" y)
  (_, _) -> False

この groupBullet はアローではなく、 XML 木から XML 木への単なる関数です。あとでアローに持ち上げます。

その前に、「bulletなんとか」という class 属性を持つ要素の名前を、その属性の値に変換するアローを書いておきましょう。 これがないと、いま定義した groupBullet 関数を使っても意味がありません(だって、元の木では要素名が全部 div ですから)。

classValToName :: (ArrowXml a) => String -> a XmlTree XmlTree
classValToName cls = 
    setElemName $< ((isClassPrefixOf cls 
                     >>> getAttrValue "class" 
                     >>> arr mkName)
                    `orElse`
                    getElemName)

isClassPrefixOf :: (ArrowXml a) => String -> a XmlTree XmlTree
isClassPrefixOf val =     
    (hasAttrValue "class" (isPrefixOf val))

この classValToName というアローを使って、次のようなアローを mainrunX の中に仕込めば、つぶされていた構造を ul 要素として取り出せます。

processTopDown
(((getChildren >>> classValToName "bullet")
  >>. groupBullet)
 `when` (hasName "body"))

ここでポイントになるのが >>. という演算子です。 この >>. 演算子は、前段にあるアローの行先に対して、リストからリストへの関数を適用したアローを返します。 さきほど定義した groupBullet[XmlTree] -> [XmlTree] な関数だったので、 >>. でアローと接続することで、「bulletなんとか」をグループにして ul でくくるというアローになります。 なお、あからさまな条件を when で指定して全体をわざわざ囲っているのは、こうしないと groupBullet したい要素たちを取ってこれないからです。

最後に、 class 属性の値が「bulletなんとか」の div 要素をすべて li に変換しましょう。 基本は最初に書いた chapterToH1 とまったく同じですが、今度は条件分岐に choiceA アロー使い、上で定義した isClassPrefixOf を条件にしてこんなふうに書いてみました。

bulletToLi :: (ArrowXml a) => a XmlTree XmlTree
bulletToLi =
    choiceA [isClassPrefixOf "bullet" :-> (tameClass "li"),
             this :-> this]

この例の全体を gist に張っておきます。

まとめ

ここでは、ほとんど >>> を使うだけで済む例しか出てきませんでしたが、実際にはかなりたくさんのアローの組み合わせ方が HXT で提供されています。基本的な XML の操作に便利なアローも、あらかじめたくさん実装されています。さらに、 >>. のようなリストとの連携機能も豊富です。おかげで、 Haskell の関数としてなら容易に書けるような処理を、 XML 木の変換をするアローとしてそのまま使えるようになります。

今回の記事で最後に書いたような HTML データの簡単な変換処理は、編集の現場ではちょくちょく遭遇します。 実際、 InDesign のような DTP ソフトが吐き出す XML を整形するのに、 HXT を利用して同じようなスクリプトを書いています。 些細な原稿データを一回だけ変換するなら、エディタの正規表現による置換で済ませてしまうほうがお気楽ですが、もっと複雑だったり素性が知れなかったするXMLデータを繰り返しいじるなら、 HXT も選択肢にいれてあげてください。

というわけで、去年に引き続き、今年の Haskell Advent Calendar 2013 も「編集者のための Haskell 入門」をお届けしました。

2013/12/02

TUG 2013で日本語の巻末索引について発表しました

TUG 2013という、世界中からTeX関連の開発者と利用者が日本に集まるイベントで、日本語書籍の索引について発表する機会がありました。「発表してみないか」と実行委員の黒木さんに誘われたときは、仕事でやってることを10分くらいで紹介すればいいのかなと思って軽い気持ちで引き受けたのですが、実は「海外からの参加者向けに用意する日本語チュートリアルの一環なのでよろしく」という話でした。当日は早口でいろいろ詰め込んでしまったせいか、話の筋を見失った方もいると聞いたので(すみません)、いまさらですが発表のストーリーをまとめておきます。(TeX & LaTeX Advent Calendar 2013の2日目の記事です。1日目はzrさん、3日目はdoraTeXさん。)

  1. いろいろな言語の本の索引を比べてみると、索引項目(一般には単語)の「自然な」並べ方がある言語とない言語がある
    • アルファベットを使う言語(ラテンアルファベットだけでなくハングルも含む)や、広義のアルファベットを用いる言語(タイ語とか)では、文字は言語によっていろいろだけど、単語は字面だけを見て並べられる
    • 一方、アルファベットを持たない日本語と中国語では、単語の並べ方に工夫が必要
  2. そこで、日本語の本における索引の並べ方を概説
    • いわゆる50音順は、文字の順番じゃなく、シラブルの順番
    • 中国語の辞書などで採用されてるピンイン順との比較
    • 人名を並べるときなど、50音順を破って字面優先にする場合(いわゆる電話帳順)もあるよ
  3. さらに具体的に、LaTeXで日本語の索引をどうやって作ってるか
    • makeindexやxindyは使えないので、日本語専用のmendexというツールを使うしかない
    • 漢字の読み仮名を機械的に解決するには、辞書だけじゃなくて、わかち書きのために形態素解析器も必要
    • 原稿がマークアップで汚くなるのを回避するために、gitで索引用のブランチを作るといいよ
  4. いろいろ面倒だけど、索引重要
    • 索引は、本文から単語を探すツールであるだけじゃなくて、本文の圧縮版としての役割もあるんだよ
    • 本を書いたり編集したりする人は、どうせ書いたものを何度も読むことになるんだから、そのうちの一回を「索引を作る」にあてるべきだよ
    • 電子書籍には電子書籍の索引の形があるはずなので模索していくべき

発表後、主に欧米の方々から個別にいろいろ質問されました。

Q:索引項目は文字列として短いのに、それでも形態素解析器が必要なの?
A:日本語は、まず分かち書きをしないと辞書すらひけないから、そのためだけでも必要。

Q:バージョン管理には何を使ってるの?
A:git(実際にはgit-svnが多い)

Q:たとえば「山」の字は「さん」と「やま」と二通り読むらしいけど、索引ではどっちに載せるの?
A:どういう単語に「山」の字が含まれてるかによって、たいていは一通りに決まる。決まらない例として、「空リスト」は「からりすと」と「くうりすと」とが毎度議論になるけど、自分は両方載せちゃう。

Q:索引をちゃんと作ったほうがいいというお前の話はまったくそのとおりだ
A:ありがとうございます!

Q:xindyは文字の順番を定義できるけど、なんで日本語では使えないの?
A:設定ルールがアルファベット前提っぽいから、少なくとも何らかの工夫は必要そう。それ以上詳しいことは今はちょっとわからない。

Q:中国語の索引は画数って話だけど、同じ画数の漢字の順番はどう決まるの?(実際には質問されなかったけど、勝手に想定してた質問)
A:筆順で決まる。「横線」「縦線」「はらい」「点」「折れ」の順(らしい)。

英語で発表というのもあって、準備はものすごい大変だったけど、終わってみればとても面白かったです。声を掛けていただいた黒木さん、本当にありがとうございました。事前の練習にお付き合いいただいた関東在住のTeXユーザーの皆様にも本当にありがとうございました。

本音を言うと、「日本語チュートリアル」という4つしかない枠の1つをまるまる「索引」に割り当てるのは、はっきりいって頭がおかしい構成ではないかと思っていました。今でも思います。もしかしたら、日本で開催するTeXの国際会議の一コマとして、コンピュータによる日本語の「排列」(用語の標準的な並べ方)の話題に絞るほうがよかったのかなあとも思います。とはいえ、例えば上記のような質問は「(巻末)索引」という切り口だったからこそ出てくる問題なのだろうし、そもそも日本語の排列の一般論なんて自分にはお手上げだし、自分なりに全力は尽くしたと思いたい。

TUG 2013 雑感

内外のいろいろな方にお会いして、中古カメラの話とか子どもの話とか、TeX以外にもいろいろと盛り上がれてとても楽しかったです。

とくにフランク・ミッテルバッハさんがとてもかっこよく、すっかりファンになりました。LaTeXコンパニオンにサインもしてもらいました。自分の発表後に「お前の話はまったくそのとおりだ」といってくれたのも彼です(その前にプロジェクタの接続を手伝ってあげたのでリップサービスかもしれませんが気にしない)。これからはLaTeX3を使っていこうと思います。

Japanese LaTeXCompanion 1st ed. with autograph

2013/11/04

Personal Impression on TUG 2013

For me, TUG 2013 had started August 2012, when Yusuke Kuroki, a mastermind behind Japanese TeX users, asked me to describe indexes of Japanese books at the next TUG, which would be held in Tokyo. Having intended to apply for any presentation slot, over whatever topic, I agreed to his request. I didn't think that I'm a sort of authority on this topic, but in the context of TeX, I was sure that I had enough experiences on indexes.

So I undertook a brief tutorial on making back-of-the-book indexes using TeX. What I didn't expect was that the tutorial would be over an hour long. I wasn't sure if indexes in Japanese actually would be much interesting for the most of the attendees. In addition to that, I thought the whole tutorials seemed to be overly focused on the compositions and typesettings of Japanese, rather than TeX. Until the TUG 2013 actually started, I had been a little bit skeptical how much non-Japanese speakers would enjoy the whole tutorials.

My concerns proved to be unfounded. The art of formatting Japanese is likely to be realized by the non-Japanese TeX users, let alone by the developers of the various typesetting systems. It would be nice these tutorials let everyone think about what we need to avoid awful looking documents. To put it bluntly, that is one of the raison d'etre of TeX.

Of course, there's another reason why we use TeX. That is because TeX allows us to take advantage of computers in creating documents, unlike word processors which requires a bunch of hand work. And in this context too, there is some subtle point in regard to Japanese that we wanted to let the attendees know. I can't help but feel partially responsible for that. I think both my talk on arranging index entries and Kuroki-san's additional tutorial on input methods revealed the gimmicky way of handling Japanese in the computer and TeX.

As a matter of fact I'm not good at speeches even in my native tongue. I felt as if my stomach was yanked out of my mouth during my presentation. But finally I found myself enjoying that. It was the great experience to hand on my knowledge to the people, including legendary developers around the world. I was really excited when I was directly questioned by them after the presentation. Again I was upset, this time the other way round.

© Kenshi Muto

Anyway, I reckon TUG 2013 was the great international conference. I'd like to thank the organizers for their energetic effort, and my particular thanks to Kuroki-san for giving me a rare opportunity.