XSL入門 1 FOの大雑把な構造

4 min read読了の目安(約4400字

XSL Advent Calendar 2020 17日目の記事です。今は19日だが。

https://adventar.org/calendars/5027

そろそろ入門をちゃんと書こうと思ってXSLTバイブル(Michael Kay著, 日本語版はインプレス刊)を引っぱり出してきてみたんですが、これあれば何もいらないんだよなってなった。XSLT 2.0版は日本語版ないのでガッツリ読む気持ちになるかリファレンスとしてじゃないとつらい。

今回、フィーリングをお伝えしていくので、興味が出てきたら参考資料をどうぞということで。

先行事例

入門にあたって用意するもの

  • XSLT処理系
    • 3.0系が望ましい
  • XSL-FO処理系
    • Apache FOPの範囲でやるつもりですが、商用はやっぱりすごいんだよな
  • CommonMark処理系

エディタはひだるまも高いの持ってないので。VS Codeに拡張入れればXSLTはそれなりなんですが、XSL-FO支援まではなかなか。

流しこむ文書の用意

それではCommonMark文書と、処理系に喰わせてXMLのASTにしたヤツを用意します。

# XSL入門

入門用の文章。**入門**って何かは知らないけれど。

こちらの文章を喰わせて、吐かせる。

$ cmark -t xml doc.md > doc.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE document SYSTEM "CommonMark.dtd">
<document xmlns="http://commonmark.org/xml/1.0">
  <heading level="1">
    <text>XSL入門</text>
  </heading>
  <paragraph>
    <text>入門用の文章。</text>
    <strong>
      <text>入門</text>
    </strong>
    <text>って何かは知らないけれど。</text>
  </paragraph>
</document>

なんということでしょう。3行のテキストがあっという間に10行に。マークアップの意味については以前の記事とかを読んでみたりしてほしい。

XSL-FOの構造

かなり削った構造が下のようになる。
ちゃんとしたことやそれなりに論理的な話は参考資料を見て。

  • root
    • layout-master-set
      • page-master,page-master...
        • region,region...
    • page-sequence
      • static-content
        • block,block-container...
      • flow
        • block,block-container...

まずルートがルート。ルートで指定できるプロパティもまああるけど、形式上あると思ってもらっても今はよい。

その下のレイアウトマスタセット。この子にページサイズなど、土台となるページについて記述するページマスタなどを書く。XSL 1.1としては単純ページマスタしか無くて「単純でないページマスタis」となるが、AH Formatter拡張だと見開きページマスタがあったりしますね。ページマスタが外せないやつだが、ページシーケンスマスタやフローマップなど、いきなり覚えるには厄介なのが色々あるので今は飛ばす。

実際に変換されたXMLをガンガン流し込むのがページシーケンス。とくにフロー。流し込むからフローだよ。

「ページサイズ」「文章を置いていい場所」を記述したのがページマスタ、そのページマスタの設定に沿って実際に記述されていくのがページシーケンス、という対応関係といって一応大丈夫かな。
ページマスタにはページサイズの他、master-nameが必要で、ページシーケンスはmaster-referenceとしてこの名前のページマスタを参照する。

一般的な日本語GUIワープロソフトなんかだと「ページ設定」ではページの判型、文字サイズ、文字数などまで決めるかもしれないが、XSL-FOではこの場所より上位、あるいは下位でそれらは決める。というか、判型、ページ余白までがページマスタ、文字サイズとかを決めているのはページシーケンス側と考えるとそんなにワープロソフトともモデルは乖離していない。

先程区画を飛ばした。あ、regionね。region-start、region-end、region-before、region-after、region-bodyがある。

start、end、before、afterについては最近はCSSでも使えるようになってきたのではないだろうか。(別に端の領域にも書けるが)真ん中のregion-bodyの四方の領域をそれぞれ端からどのくらい取るか指定していく。「right、left、top、bottomじゃダメなのか」というのは横組の住人意識が強いのでちょっとほぐしましょう。 「縦組における上下左右はどこだ」 と考えてみてほしい。右から左の語圏もあるし、下から上、ぐるぐる回りながら組むものもあるかもしれない。
で、これら4つは何に使われるかというと、ページシーケンスの子のstatic-contentが参照する区画として存在する。フローに対しstaticなもの、常にページ上の決まった位置に置かれるものがここに来るのだ。ヘッダやフッタだったり、ページ番号だったりですね。「ページごとに内容が変わるのでは?」でも位置は一緒でしょ?

ということで、あらためてレイアウト記述とその対応を簡単に示すと

  • ページマスタ - ページシーケンス
  • region-start,end,before,after - static-content
  • region-body - フロー

ブロックとインライン

CommonMarkのDTD見たとき、blockとinlineという区別があったのは覚えていますか? HTMLでも似たようなもんですが、CSSでdisplay変えると何でもアリなんで学習アプローチとしてはちょっとね。

ちゃんとした定義を見るとちゃんとしてますが、ざっくりブロックは「改行を含むようなオブジェクト」でしょうか。例えばparagraphって、他のparagraphと行を分けて書かれますよね、大抵。

ブロックは子にブロックオブジェクトやインラインオブジェクトを持てます。

幸いなことにCommonmark ASTにおけるブロックとインラインとのずれは少なくとも今見るところでは存在しないので、CommonMarkのブロックをブロックに、インラインをインラインにすれば良い。


<xsl:template match="/">
  ...
</xsl:template>

<xsl:template match="document">
  <xsl:apply-templates />
</xsl:template>

<xsl:template match="heading">
  <fo:block><xsl:apply-templates /></fo:block>
</xsl:template>

<xsl:template match="paragraph">
  <fo:block><xsl:apply-templates /></fo:block>
</xsl:template>

<xsl:template match="strong">
  <fo:inline><xsl:apply-templates /></fo:inline>
</xsl:template>

<xsl:template match="text">
  <xsl:apply-templates />
</xsl:template>

blockは<fo:block>に、inlineは<fo:inline>に。textくんはどうせブロックもしくはインラインの内側で登場するのでそのまま通す。

ところで<fo:inline>で囲っただけでは強調も何もあったもんではない。headingとparagraphもこのままだと同じ<fo:block>だしね。しかし、「表示」を念頭に置いたとき、見出しも段落も、次のオブジェクトに移るときに改行されるような、ブロックオブジェクトであることは示されている。勿論行見出しなどをやりたければそうすればいいけれども。

こういったブロックとインライン(とその仲間)をフロー内にガンガン流し込み、XSL-FOプロセッサに流し込みの終わったFOファイルを渡すとPDFが出来るワケです。別にPDFに限らないけれども。

<fo:page-sequence master-reference="...">
  <fo:flow flow-name="xsl-region-body">
    <fo:block>XSL入門</fo:block><fo:block>入門用の文章。<fo:inline>入門</fo:inline>って何かは知らないけれど。</fo:block>
  </fo:flow>
</fo:page-sequence>

次回、attribute。あと6日でAdvent Calendar終わるんだが。

参考資料