怖いXSL-FO

とある事情でXSL-FOを解析して元のスタイル仕様を探り当てるという仕事をすることになってしましました.いままでXSL-FOは自分が作ったスタイルシートデバッグのために追っかけることはありましたが、このような目的の仕事はあまり馴染みがありません.

それでも実際にやってみて、これは自分も気を付けなければならないなと感じたのは次の項目です.XSL-FOって怖いですね、みんなわかってしまうのです.

・ XSL-FOからは、それを作ったスタイルシートと、スタイル設計のレベルが全て透けて見えてしまします.
・ XSL-FOからは、スタイルを作ったプログラマー(もしくはスタイル設計者)のXSl-FOの理解度がわかってしまいます.

たぶん解析対象のXSL-FOを作ったのは、XSLT 1.0のスタイルシートであることは間違いないのですが、XSLは1.1の機能を使用しています.それではどんな点を見てそのように感じたのかを紹介します.

1.fo:rootになんの継承属性の指定がない.


fo:rootには文書全体に適用すべき継承属性を定義するのが一般です.例えばfont-familyだったら地のフォントをここで指定します.ここで指定しないということは、デフォルトのフォントが採用されていましますから、およそ個々のオブジェクトですべてfont-familyを指定しなければなりません.これは無駄です.XSL-FOの継承機能を使用すれば良いのです.

あと、文書の言語に合わせてここにxml:langを指定するのがこれも一般です.Formatterはxml:langを見て、(あればですが)その言語特有の組版規則を適用します.いままで大抵のfo:rootにはxml:langがついていました.ないのは初めて見たように思います.

2.absolute-position="fixed"

<fo:block-container absolute-position="fixed" top="..." left="..." right="...">

aboslute-position="fixed"というのは、HTMLのような非ページメディアでウィンドウ上の定位置にそのコンテンツを表示するような場合に使用します.
ページメディアではあまり意味をなさなかったと思います.

7.6.1 "absolute-position"

3.レイアウトのために罫線なしのfo:tableを使う.

かつてのHTML時代からの悪しき慣習です.まあこのFOを作るスタイルシートは10年越しのものだそうなので、致し方がないかもしれません.

4.突如language="en"を使う

<fo:block><fo:inline font-family="Arial" font-weight="bold" font-size="10pt">Model No.</fo:inline>XX<fo:inline language="en">‑</fo:inline>9999</fo:block>

これは、U+2011がたまたま別のスクリプトと解釈されてフォントが選択されてしまう(例えば英語ドキュメントなのに日本語フォント)ので強制的に巣language="en"で英文フォントを割り当てているようです.&#x2011;(NON-BREAKING HYPEN)は、そこで行分割されたくないから使用するのでしょうが、こんなややこしいことをせずに、fo:blockのkeep条件でいけるように思えます.

5.@id属性が空

<fo:block id="" axf:destination-type="xyz-left-top">...</fo:block>

axf:destination-typeを指定すると、@id属性の値をPDFの外に対してexportし、そこを指定してジャンプされた場合の表示を決めます.

axf:destination-type / CSS -ah-destination-type

ですので@idの値がなければ全然意味をなしません.こんなfo:blockがごろごろしていました.これはどう見てもスタイルシートの手抜き以外の何物でもありません.

6.訳のわからない改行

<fo:inline linefeed-treatment="preserve">&#x2028;</fo:inline>

&#x2028;はLINE SEPARATORと呼ばれます.これは論理的な構造とは無縁なコードです.たまたまFormatterがサポートしてくれていると考えてよいのではないかと思います.

一般に段落(fo:block)の中で改行したければ、次のように書きます.HTMLの<br/>に対応します.

<fo:block>
  ...
  <fo:block/>
  ...
</fo:block>

もし、複数行改行したければ

<fo:block>
  ...
  <fo:block/>
  <fo:block>&#x00A0;</fo:blcok>
  ...
</fo:block>

などと書きます.このあたりはXSL-FOの世界ではジョーシキです.LINE SEPARATORはあまりいただけません.またlinefeed-treatment="preserve"も意味をなしません.これはU+000Aの取り扱いを規定します.

7.16.7 "linefeed-treatment"


7.リストのスペーシングを取らない.

<fo:list-item relative-align="baseline">
  ...
</fo:list-item>

御存じだと思いますが、リストのfo:list-otem-bodyの先頭に配置されるオブジェクトのspace-beforeと最後に配置されるオブジェクトのspace-afterは「効きません」.

6.8.3 fo:list-item

The block-progression-dimension of the content-rectangle of an area generated by the fo:list-item is just large enough so that the allocation-rectangles of all its child areas are contained in it. In particular, the space-before and space-after of the child areas have no effect on the spacing of the list item. For purposes of the block-stacking constraints the areas generated by fo:list-item are treated as if there they have a fence preceding and a fence following them.

もしリスト間でスペーシングを取るなら、fo:list-itemにspace-before(もしくは滅多につかいませんがspace-after)を指定します.このFOはそのような指定がまったくありませんでした.

このため、リストの項目と項目の間にスペーシングが取られず大変見づらいものになっていました.リストのスペーシングはfo:list-itemで必ず取るのがジョーシキです.

8.fo:markerで独立に一個のfo:blockを使用する

<fo:block keep-with-next.within-page="always" keep-with-next.within-column="always">
   <fo:marker marker-class-name="title">1.1 Title</fo:marker>
</fo:block>
<fo:block start-indent="0mm">
   <fo:block keep-together.within-page="always" 
             keep-together.within-column="always"
             >1.1  Title</fo:block>
</fo:block>

コンテンツがfo:markerホワイトスペースだけのfo:blockだから無害ですが、いちいちfo:markerを別のfo:blockへ分ける必要はまったくありません.

<fo:block start-indent="0mm" keep-together.within-page="always" 
          keep-together.within-column="always"
          ><fo:marker marker-class-name="title">1.1 Title</fo:marker>1.1  Title</fo:block>
</fo:block>

で十分です.fo:markerはfo:blockの中に配置されるとき、最初のオブジェクトでなければなりません.

6.5.2 fo:block

In addition this formatting object may have a sequence of zero or more fo:markers as its initial children, optionally followed by an fo:initial-property-set.

9.リスト用のプロパティをfo:blockに使う

<fo:block keep-together.within-page="always" keep-together.within-column="always"
          provisional-distance-between-starts="20mm">1.2  Packing</fo:block>

provisional-distance-between-startsというのはリストのラベルとボディの距離をfo:list-blockに指定して示します.fo:blockに指定してもなんの意味もありません.たぶん何かの間違いなのでしょう.

まだまだいろいろありますが、XSL-FOっていくら汚くても出来上がったPDFが「それなり」だとお客さんはそれで満足していしまいます.XSL-FOまでしみじみ覗いてみるなどという方はまずよっぽどの物好きでしょう.でも待っていただきたいのです、XSL-FOが汚ければ、それは生成するスタイルシートがグチャグチャであったり、スタイル設計がなっていないことの端的な表現なのです.

わたしも、他の方が見て恥ずかしくないXSL-FOを作ろうと思いました.