2014/12/08

「版管理+自動組版」イベントの雑感

2014年12月6日、「版管理+自動組版」と題した勉強会というかイベントいうか集まりを開きました。 「同じようなことをバラバラにやってる人たちの横のつながりのきっかけが欲しいな」という程度の軽い気持ちで声を上げてみたら、予想以上に興味を持ってくれる人が膨らんで、様々な背景の方から発表に手を上げていただき、当日も会場から次々と質問が飛び交うという、実に活発で刺激的ですごく楽しい集まりになりました。ありがとうございました!

発表内容や概要は、公開されている各スライドや、武藤さんによる素晴らしいまとめを見ていただくとして、ここには個人的な感想文を晒しておこうと思います。参加してないと文脈が分かりづらい内容なのはご了承ください。

発表では、自分のゆるい話のあと、 @amedama こと宮川さんに Grifletという書籍のビルドシステムを紹介していただきました。 出版に継続的インテグレーションを適用するというのは、自分たちでも割と前から取り組んでいたのですが、この間ずっと未解決になっていた課題として、自動組版の環境をプロジェクトごとに固定化したりチーム間で共有したりしたいという要望がありました。 VM を使うとこまでは過去の本の重刷対応なんかでやっているのですが、Dockerのような仮想コンテナが使えることには気づけてませんでした。 それに気づいたのが、実は宮川さんの PyCon 2014 のスライドで、まさかその話をこうして直接聞ける機会がくるとは想像もしていませんでした。 今回の宮川さんの発表では、これを商品として提供するサービスの可能性にまで突っ込まれていて、商用フォント会社とうまい具合にライセンス契約ができれば同人誌の制作をしたいような人にとっても現実的な価格で再販できるのではとか、商業出版をやっている身としてもサービスを享受するという方向で夢が広がる話が伺えました。

商業出版という観点では、清水川さんによるかなり具体的な書籍制作フローの事例紹介がありました。 清水川さんは、執筆者としてドキュメント制作フローを語ったと思うのですが、それは自分たち出版社の人間から見てもなんら特別なものではなく、本作りのフローで必要なことは執筆者にとっても編集者にとっても共通する部分が多いという至極当たり前の事実を追認しました(ただ、スプレッドシートでSIer風にコメントを管理するのはつらいかもw)。

もう一つ追認したのは、上流でうまく回っているソースの版管理と最終的な本との間にギャップというかブラックボックスがあると、改訂版などへの再利用性は極端に低下するという事実です。 最後の最後まで版管理っていうのは、技術的なだけでなく政治的に難しい面もあるので、せめて、内容の編集までは出版社側の人間も reST ソースで完全に終わらせて、あとはレイアウト的な問題しか絶対に直さない、くらいの割り切りが必要かもしれません(レイアウト制作時に見つかった致命的な内容の問題が見つかったら必ずソースにも反映するようにする、といった抜け道は用意しておくとして)。

一方で、再利用性っていうのが「版管理+自動組版」をやりたい人にとって大前提の金科玉条になってもいけないと思いました。 そもそも何をどの程度に再利用可能にしたいのかは最終成果物によって大きく変わるはずで、必然的に、版管理する対象とか粒度、さらには制作システムのフローもまちまちにならざるを得ないでしょう。 で、そういう「共通化しにくさ」っていうのの具体例が、 taison さんの発表に出てきた 3つの事例かもなあと勝手に解釈していました。 taison さんの発表は、「どれも似たようなことをしているのに担保したいことが違って標準化できてない」という話をはじめ、あるある感で胸が痛くいっぱいになりました。

ところで、 taison さんのシステムで XML アプリケーションを固定していないのは、自分の経験としては正解だと感じてます。 DTD や Schema を用意するということは、梯子の下の表現レイヤを限定するということです。 もし、表現レイヤは決め打ちでいいというなら、わざわざ山かっこなんか使わず、人間にやさしい(⊃入力しやすい)マークアップを用意したほうがハッピーでしょう。 しかし、 XML という仕組みそのものには、文法的な一貫性を提供しつつ、ユーザが都合に合わせてマークアップを拡張できるという特徴があります。 表現レイヤを制限せず、なおかつ文法的に破たんしてないマークアップを提供したかったら、整形式であることだけを要求した XML を使うというのは割と理に適っているはずです。

マークアップを拡張することについては、小宮さんからも、拡張によるマークアップの方言化の是非という視点での発表がありました。 たとえば Sphinx でも、ベースとなるマークアップの reST を機能的に拡張するための API があって、それはすごく強力(なにぜPythonのフル機能がマークアップとして利用できるようになる!)なんだけど、それらの拡張が使える環境と使えない環境が生まれてしまう、さてどうしよう、という話だったと思います。 自分の知ってる TeX 界隈も、CTAN というオンラインのライブラリのおかげでマクロやパッケージの管理では比較的楽ができてますが、そもそもエンジンや PDF 生成機能がユーザから透過的とは言えない現状があります。 実際、 宮川さんの発表でも、LaTeX で Noto Sans を使いたかったけど TeX Live のエッジ(にしか入ってない dvipdfmx ですね)が必要で、システム全体を更新するわけにもいかないから、必要な Debian パッケージを入れた Docker コンテナを使ったという話がありました。 そんな風に環境ごとシステムの中の人が管理するという手はあるとはいえ、どうしたってユーザの利便性とか表現レイヤの自由度は下がるわけで、痛し痒しな気もします。

ひょっとすると、表現レイヤの自由度をあまり捨てずにマークアップの拡張による方言化を吸収できるかもしれない、そんな自動組版の最終兵器が、村上さんのプレゼンで紹介された CSS 組版なのかもしれません。 そこまで期待していいのかっていう内省もないではないんですが、技術的には夢の最終兵器としての可能性は間違いなく持ってるし、末端のユーザが実用できる日も遠くはない気がしています(実際、アンテナハウスの実装をはじめ、ちゃんと動いているものはあるわけで)。 むしろ心配なのは、 Web の世界では直接必要とされていない技術なので、ブラウザというプラットフォームで継続的に仕様と実装の開発が進むかどうかです。 TeX ユーザの集い2014 でも川端さんによるルビの標準化の話がありましたが、あれもお金がネックになって次の一手が出ないようだし、Web という巨大産業はどうやらそういう世界のようです。 そういう意味で、必要なのは銀の弾丸よりも金の弾丸なのでしょう……。 自動組版界隈、あんまりお金なさそうだし、どんな形で自分たちが利用できるようになるのかを含めて CSS 組版を盛り上げていくのに協力できればなと感じました。

最後の発表では、ここまで自動組版っぽい話題が多かったなかで、「同一性とは何か」まで考えさせられる版管理系のネタ を力武さんにぶつけていただきました。 図表の差分とれないのつらい、みたいな感想しか持ってなかったので、そもそも図表みたいなものの差分を取りたいのかという問題提起に付いてくだけで精一杯でしたが、 「差分を取るだけでなく、差分に意味を与えて差分をメンテナンスしてくのが編集という営みだろう(意訳)」という話が個人的には刺さりました。 git rebase -i に相当することができない版管理ツールを使うのはやめようと思いました(小並感)。

図表の差分の話もさることながら、 MS Word の原稿をどう比較するかという話への会場からの食いつきがよかったのが意外でした。 あんまり実体をしらないので、表面的な差分をとるだけならツールもけっこうあるし難しくない気がしてたんですが、そうでもないんですね……。 正直なところ、Word に限らず、閉じた世界でならものすごい利便性を提供するツールって、そこに身を投げれば版管理+自動組版が完全に解決するような錯覚に陥ることがあります。 図表の版管理にしても、閉じた世界で便利な解決策を提供しているツールがありそうなものなので、この辺りは寡聞にして知らない系の話というだけかも。

会の終了後は、主催者が参加できないことから公式の懇親会は企画しなかったのですが、一部の野良懇親会で濃い話が続いたりもしたようで、横のつながりの一助としても多少は機能できたっぽくてよかったです。 こういう特殊な軸で人が集まるのはとても面白いということがわかったので、同じ形になるかどうかはわかりませんが、また何かできればと思っています。

2014/12/02

LaTeX で git のメタ情報を使うパッケージ

2014年現在、「自動組版のためのPDF生成器である」と豪語できるツールは、どうやらまだまだ (La)TeX だけという状況っぽいです(異論は認める宗教戦争前夜)。 しかし、自動組版に使えると豪語するからには、 git のメタデータくらいは気軽に出力できないと困ります。にもかかわらず、どうも CATN にはこれといった便利パッケージが見当たらない†1。その愚痴を TeX & LaTeX Advent Calendar 2014 の2日目の記事としてぶつけます。(1日は ZR さん、明日3日は doraTeX さんです。大物に挟まれてつらい)

ところで、今週末の12/6(土)は、自動組版とバージョン管理の可能性を模索しようという「版管理+自動組版」という集まりがあります。 ものすごく楽しみです。 閑話休題。

さっそくですが、 git のメタ情報を (La)TeX のドキュメント中で利用するためのパッケージとしては、 gitinfo2vc といったものがあるようです。 いずれも、たとえば自分のドキュメントに git のコミットハッシュの文字列を含めたかったら、\gitHash(gitinfo2) とか \GITHash(vc)といった引数なしのコマンドを自分のドキュメントの中で指定して使う仕組みです。分かりやすいですね。

しかし、ここで厄介なのが、これらのパッケージのインストールと設定です。 いずれも TeX Live 2014 に入っているので、プリアンブルで \usepackage するだけで使えると実にうれしいのですが、残念なことにそんな安直な使い方をさせてくれません。事前に外部のスクリプトを自分で設定する必要があります。

vc パッケージの場合は、毎回ドキュメントをコンパイルする前に vc-git.awk というスクリプトを実行し、vc.tex というメタ情報が入ったファイルを生成して、このファイルをドキュメントから \include するという仕組みになっています。 なので、このスクリプトを手動で実行したり、 Makefile に記述しておいたり、 \write18 を使って vc-git.awk をドキュメントのコンパイル時に実行したりする必要があります。公式のドキュメントで説明されているのは \write18 を使う方法です。

gitinfo2 のほうは、 git に特化しているだけあって少しだけ先進的で、 git のフックスクリプトの機能を利用する仕組みになっています。 具体的には、

  • 特定のファイルに git log HEAD を出力するだけのコマンドシェルスクリプトを、ユーザが自分の .git/hooks ディレクトリに設置する
  • その特定のファイルをドキュメント中で \input で読み込んでおく
という具合です。 でもこれだと、GitHub をポーリングして CI を回している環境ではちょっと使いにくい(コミットフックで出力される特定のファイルをバージョン管理化におけばいいのですが、なんかいやだ)。

仕方がないので、自分ではこれらのパッケージを使っていません。 代わりに、 git のコミットハッシュを \write18 で取得するだけという、超低機能な自前コマンドを使っています。 これをプリアンブルに仕込んでおけば、 git の短縮版コミットハッシュの文字列が \gitRevision という引数なしのコマンドで得られます。

\immediate\write18{git show HEAD --pretty="\@percentchar h" -s > revision.tmp}
\def\gitRevision{\input{revision.tmp}}

さらに、みようみまねで buildinfo.sty という LaTeX2e 用のパッケージにしてみました。

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{buildinfo}[2014/12/06 provides git hash as a command]
\RequirePackage{etoolbox}
\RequirePackage{kvoptions}
\SetupKeyvalOptions{family=buildinfo, prefix=buildinfo@}
\DeclareBoolOption{long}
\ProcessKeyvalOptions*
\immediate\write18{date +\@percentchar c  > build.tmp}
\ifbool{buildinfo@long}
  {\immediate\write18{git show HEAD --pretty="\@percentchar H" -s > revision.tmp}}
  {\immediate\write18{git show HEAD --pretty="\@percentchar h" -s > revision.tmp}}
\newcommand{\buildDate}{\input{build.tmp}}
\newcommand{\gitRevision}{\input{revision.tmp}}

\usepackage{buildinfo} すれば、 短縮版のコミットハッシュ文字列に展開される \gitRevision コマンド(\usepackage[long]{buildinfo} とすれば40文字になる)と、 コマンド実行時の日時の文字列に展開される \buildDate コマンドが使えるようになります。


†1 思い返せば、Subversionを使っていたころにも、 svninfo というパッケージでファイル単位でしかメタ情報を扱えないことに苦慮していたことがありました。(いまや完全に git に移行したので、この問題は個人的には原理的になくなりましたが。)

2014/11/10

TeXユーザの集い2014でしたTUG2014の話

プレゼン資料だけだとあまりに情報量がなさそうなので、発表の準備のときに用意していた原稿も下の方に張り付けておきます。未編集です。



自己紹介
======
こんにちは、鹿野です。ふだんは神田の技術系出版社で書籍の企画をしたり編集をしたりしています。
むかしは半分趣味でTeXマクロを書いて遊んだりもしていましが、最近はもっぱら仕事上のおつきあいという感じです。
その仕事では、バージョン管理している原稿から印刷所に入稿するPDFを自動組版するのに、pLaTeXを使っています。

今日は、TUGのアニュアル・ミーティングという、この「TeXユーザの集い」のワールドワイド版みたいなものに参加してきた話をしたいと思います。
今年の集いは、ライトニングトークまで含めて、ものすごく濃厚な内容になっていますが、私の発表は技術的な内容は薄い、文字通りの体験記なので、気楽に聞いていただければ幸いです。

TUGについて
======
まず、そもそもTUGって何、という話から入ろうと思います。
TeXのユーザー会は、実は世界のいろいろな地方ごとにあって、その北米版が「ユーザーに「s」がつくほうのTeX Users Group」で、その略称がTUGです。
ちなみに、日本はこのオフィシャルなTeXのユーザー会がないんですよねー
今回の「集い」で事前に日本のTeXユーザーの組織化に関するアンケートがあったと思うんですが、このTeXユーザーグループに名を連ねようという布石なのかもですね。
いや、知らないんですが。

で、北米版のTeXのユーザー会であるTUGが、全世界のTeXユーザーグループでもあるという形になっています
ので、TUGの会員には、北米在住でなくても、年会費を払えば地球のだれでもなれます。わたしも一応TUGの会員に「個人で」なっています。年会費は$105です。
この年会費は、みなさんご存知のCTANの運用にも使われているらしいので、仕事とかでTeXを使っているので還元したい方は会員になってみるとよいかもしれません。

TUGboat
======
TUGの会員になると、TUGboatという雑誌が送られてきます。年に3回です。
これの編集と発行もTUGのお仕事です。
まあ、1年たつとすべての記事がオンラインで誰でも読めるようになるので、TUGboatを読みたいだけならば、会員になったりしなくても大丈夫です。

TUG年次総会
======
で、ここからだんだん本題になるんですが、TUGのもっとも大きな役割としてあるのが、世界中のTeXユーザーの交流の場となる、年に一回のイベントの主催です。
去年は、この中で参加した方も多いと思うのですが、東京で開催されました。通算、第34回だったんですが、日本では初めての開催だったということです。
150人くらいの参加者だったそうで、大成功だったようです。
私も、去年ちょこっとかかわらせていただいて、それがすごく楽しかったので、今年もぜひ参加したいと思って参加したのが、アメリカのポートランドで開催された第35回のTUGであるTUG 2014になります。

ポートランドについて
======
ポートランド、あんまり日本人にはなじみがない気もするので、ちょっとだけふれておきたいと思います。
ポートランドは、オレゴン州の北西にあって、オレゴン州でいちばん大きい都市です。
で、私もぜんぜん知らなかったんですが、TUGの本部がある場所なんだそうです。そんなわけで、過去に何回か、ポートランドでTUGが開催されてるみたいですね。

これも知らなかったのですが、ポートランドはバラの街としてしられているらしいです。
あと、自転車の環境がすごく整備されていて、みんな路面電車に自転車をもちこんだりしてるんですね。
街の真ん中にすごく大きな川、ウィラメット川が流れていて、写真にあるような鉄橋をはじめ何本も橋がかかってるんですが、それらにも自転車用の道が整備されていて、びゅんびゅん行きかってました。
自転車って、盗難とかコワイので、治安がよい場所じゃないとあんまり流行らないわけですが、実際、ポートランドはアメリカでも最も治安がいい街のひとつなんだそうです。
なので、まあ日本で夜に出歩くほど気楽というわけじゃないですが、夜になって飲みに繰り出しても、そんなに恐ろしくない町でした。
しかも地ビールの醸造所が何カ所もあるんですね。直営のバーとかもいっぱいあったり、これはもう夜は飲みに行くしかないだろう、という感じで、

ビール
======
しかも、ちょうどTUGの前日まで地ビール祭が開催されてたらしくて、私は間に合わなかったんですが、
前入りしてた黒木さん、去年のTUG2013の実行会長ですが、その黒木さんは、この地ビール祭であびるほどビールを飲んでたらしいです。
私のほうは、発表にも申し込んでいたので、飛行機の中とかで必死に発表の練習をしてたわけですが、その間にすっかりポートランド観光してたと。
この写真も、そんな黒木さんが私が到着する前に見つけてくれていたスーパーのビール売り場の様子です。
この一角だけでもこれだけの銘柄の地ビールが並んでいて、それでちゃんと経営が成り立ってるわけですね。
こう、アメリカっていうと、大量消費・車社会・治安も悪い、みたいな印象もあったのですが、ポートランドに限って言えば、すごく成熟した素晴らしい町でした。

会議の場所
======
そんなポートランドのダウンタウンの片隅に、マークスペンサーという小さいけれどいい感じのホテルがあって、そこの地下が今年のTUGの会場でした。
この写真の正面が下に続く階段になっていて、その奥の会議室が会場です。

ホテルなので、ほとんどの参加者は、私や黒木さんも含めてそこに滞在もしてたみたいです。
だから、本当に朝から晩まで、会議中もそれ以外の時間も顔を合わせるわけですね。

あと、このマークスペンサーホテルのよいところとして、すぐ近くに世界最大のチェーンじゃない書店があります。
TeXのユーザーの集まりなので、本とか大好きっこばかりが集まってるわけだから、みんな空いた時間にこのパウエルズという本屋さんに遊びにいくわけです。
とにかくすごい大きい本屋なのですが、誘い合わせていったわけでもないのに、ばったりほかの参加者にあったりもしました。

Powell's
======
この写真はパウエルズの店内で撮ったもので、まあ、宣伝です。
弊社で作ってる「マンガでわかる統計学」とかの英訳版がこんなふうに面出しされていて、かなりうれしかったです。
こっちはTeXの本のコーナーで、世界最大でもこれしかなかったので、ちょっとさびしい感じですね。

プレゼンの話(概要)
======
TUGの会議そのものは、一言でいってしまうと、プレゼン大会です。
3日間にわたって、35分ずつ、一日10本くらいのプレゼンがあって、大部分の参加者がすべてを聞いていたと思います。
実際、参加者の半分以上がプレゼンもするという構成です。

プレゼンの内容は、ものすごく自由で、TeXに関係するツールの話もあれば、パッケージの紹介もあれば、TeXでこんなことしてみましたという事例の話もあります。
いろんなツールやパッケージの開発者もいるので、こういうこと考えてよとか、そういう話もありました。
全部のプレゼンについて一つずつ説明していくのは時間的にも能力的にも無理なので、詳細はTUGのWebや、最初に紹介したジャーナルのTUGboatでチェックしてみてください。
プレゼンターが直接書いた記事があるものもあるし、TUG 2014全体の要約記事なんかもあります。
もし気になったのがあったら、聞いてもらえれば、ひょっとしたら「こんな話だったよ」と言えるかもしれません。

なので、いくつか個人的に気になったプレゼンをピックアップしてみたいと思います。

Astonishing
======
まずはなんといっても、JSBoxです。
これ、名前から何なのかまったく想像できないんですが、TeXのCによる再実装です。
WEB2Cのアウトプットを書き換えたとか、そんなちゃちなものじゃなくて、ゼロからこのダグ先生がCで書き直して、トリップテストも機能的にはパスさせてしまったというやつです。
ダグ先生の会社は、数学チックで再帰的な図案のデザインとかMacのコンサルとかやってるらしくて、
なんでCなんだよ、っていうのが個人的な感想ですが、それはまあおいといて、すごいですね。
内部的には21ビットのUnicodeで、それ向けにオリジナルTeXのアルゴリズムやデータ構造を全部再実装したらしいです。エンコーディングにはUTF-8を採用と言ってました。
OpenTypeフォントがなんでも使えるといってるので、そのまま日本語も使えるといいよねーという感じなんですが、TeXのcatcodeを勝手に拡張しちゃっていて、
catcode 16を名前空間のセパレータ用として予約しちゃっているんですね。なのでptex 向けのマクロやスタイルは全滅かもしれません。
「今度のTeX Liveにいれよう」みたいになってたので、そのうち使えるようになったら試してみたいですね。

もうひとつ会場を沸かしてたのは、カベーさんの発表です。
GUIでパラメータが設定できるようになっていて、そこの数値をスライダーとかで動かすだけでLaTeXのページ版面とか簡単に設定してしまうのをデモされてました。
いろいろなパッケージを内部で階層的に管理している部分がポイントらしくて、原理的には、なるほどねーという感じなんですが、実際デモを見るとすげーっていう感じでした。

Interesting
======
個人的に興味をもって聞いてたのは、TeXと、印刷用じゃない出力との関係についてのプレゼンでした。

日本だと割とすぐにEPUBとかに走っちゃう印象があるんですが

たとえばPDFのリフロー対応とか、
これ、たとえばdvipdfmxで作ったpdfでもAcrobatでタグ付きPDFとかに変換すると、Adobeリーダーでもちゃんとリフローが機能するPDFになったりするんですが、
pdftexが生成するpdfではスペースの出力がまずくて、全部の単語がくっついちゃってたらしいんですね。それを解消しました、みたいな発表があったり、

まあ、やっぱりほとんどAdobeのリーダー専用なんですが、PDFのアノテーションとかレイヤとかをTeXからもっと使おうぜとか、

あとは、LaTeXの原稿からWebブラウザで「読める」出力を得る方法を模索している話とかもありました。
たとえは、この写真のビルさんは、統計の先生なんですが、XMLと対応できるLaTeXの部分集合みたいなのを定義して、それをここ何年か開発しているとのことなのですが、
その中で数式をMathMLのサブセットとCSSでここまでブラウザ上で再現しました!という発表をされていたり、
これはまた別の人なんですが、アメリカ・インスティチュート・マスマティクスのジャーナルのLaTeX原稿をHTMLに変換した実例をデモする発表をする人がいたり、
これも単に変換するのではなくて、参考文献とか、クリックすると単なるリンク先に飛ぶのではなくちゃんとJavaScriptのアクションで本文の直後に詳細な情報を表示するようにしないとだめとか、そういう話が面白かったです。

あと、TeXとはまったく関係ない話もぽつぽつあって、
数学の教科書用のXMLアプリケーション、XML用語の「アプリケーション」ですね、これを使ってSAGEにも紙にも対応した教科書を作ってる話とか
Plottyっていうグラフ作成のWebアプリがあるのですが、それの中の人のプレゼンとか、
あと、この写真のおじさん、Wikiを発明したウォード・カニンガムです、この人がゲストスピーカーで、新しいWikiを作っていますという話をしたり、
面白かったのは、みんな、「TeXに関係は?」みたいな質問をされるんですよ。その答えが、どれも、MathJaxで数式を表示してます、でした。
たぶん、数式をWebブラウザとかで表示したい場合は、個人的にはビルさんのアプローチとかのほうが好きなんですが、もうMathJaxになるんだろうなあという思いを強くしました

プレゼン外
======
TUGそのものがプレゼン大会だっていうのは、さっき言ったとおりなんですが、
プレゼンだけなら、わざわざ太平洋を越えていかなくても、あとでスライドとか動画を見るのでもいいわけですよね。
というわけで、最後にプレゼン以外の話です。

去年のTUG 2013でもプレゼン外の雑談が有益だってって、寺田さんがブログに書いてらっしゃいましたが、
実際、TUG 2014もそうで、しかも朝から晩まで一緒の空間にいるので、合宿みたいなノリになってるわけですね。
昼休みや休憩はもとより、朝からホテルのラウンジでコーヒーを飲みながら雑談が始まるわけです。
毎年TUGに参加している人たちって、プレゼンを名目に旧交を深めて観光旅行してるだけなんじゃないかと。
とくにこのパブニートさんは、去年のTUGで仲良くなって、今年もすごく仲良くしてくれて、
ほとんどはTeXに関係ない話だったりもするわけですが、こういう雑談の時間がすごく有意義でした。
ちなみに、このプレゼンで使っている写真は、ほとんどは彼によるものです。使っていい?って聞いたら快諾してくれました。

夜も夜で、ドイツからきているフランク・ミッテルバッハさん、LaTeX2eの開発者で、LaTeXコンパニオンの著者です、このすごい人が、なんか飲みに誘ってくれるわけですね。
で、こんなふうにレストランでメニューを見ながら、これ何やってるのかというと、みんなでメニューの組版にケチをつけてるんですね。

こっちは懇親会の写真なんですが、こっちの二人はインドから来ているTeXニシャンで、この人は黒木さんです。
プレゼントに当選したときの様子です。隣のクリス・ローリーは、LaTeXコンパニオンの第2版の共著者の一人で、この懇親会の直前にビールをおごってくれました。
懇親会の後も、このクリスと、フランク、それから、こっちの奥にうつっているウィル・ロバートソン、fontspecとかの開発者です、それに黒木さんと私とでビールを飲みに行きました。
いま、こうやって話してても、なんて贅沢な時間だったんだろうと、なんか自分なんかがほんと図々しかったのではないかと恐縮に感じてしまいます。

というわけで、いろんなプレゼンを聞いてきた話よりも、こっちのほうが今日、メインに伝えたいことかもしれません。

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 入門」をお届けしました。