Fatal Error! Too many nested apply-templates calls. The stylesheet may be looping.

もしご覧のあなたがXSLTスタイルシート開発者で、このメッセージにお目にかかった経験がおありならたぶんなんとかこの障害を乗り越えたのではないかと思います.でも今回私は非常にドジを踏んで、解決にはやたらに時間をかけてしまいました.

それはDITA-OTのとあるインスタンスのビルドで発生しました.お客様からいただいたデータ2種類でビルドしたのですが、一つはなんの問題もなく成功し、もう一個は非常に不確実なタイミングでこのエラーが出ます.Saxon 9.1なのですが、もちろんXSLTプロセッサはそこで止まってしまいます.

非常に不確実と言うのは、なぜかビルドのパラメータで変化するのです.DITA-OTにはvalidate=yes/noというパラメータがありますが、それをyesとすると成功しました.しかしnoとすると失敗します.おかしいので

[xslt] C:\DITA-OT1.7.5\plugins\xx_pdf\xsl\xx_dita2fo_numberingmap.xsl:373: Fatal Error! Too many nested apply-templates calls. The stylesheet may be looping.

と出た直前でxsl:apply-templates しているデータをデバッグ出力してみましたが正常に出力されます.

実行したマシンはWindows7 32bit 4GBメモリーのデスクトップですが、これを自分のノートPCのWinodws7 64bit 8GBメモリーでやると何回同じ条件でやっても成功します.32bit/64bit OSの違いか?と思ってJavaコマンドラインの-Xmx256mの値を増やしてみましたがやはり同じところでこけてしまいます.

そのうちデスクトップでvalidate=yesとやっても発生するようになってしまいました.こうなると手がつけられません.お客さんには成功したときのPDFだけ送って調査中で待ってもらうことにしました.

ここで先ほどのデバッグ出力して対象要素の数を調べてみました.なんと553個ありました.別にこれを単に子要素としてxsl:apply-templatesしているならなんら問題ありません.しかしよくよくソースコードを見てみるとその箇所は

<xsl:apply-templates select="following-sibling::*[1]"/>

となっていました.いわゆる末尾再帰です.これだと553個分のxsl:apply-templatesがスタックされます.

そうでした.問題はヒープの-Xmxではなくスタックの-Xssパラメータでした.なんのことはなく-Xss64mとやったら正常終了するようになりました.2度とエラーは発生しません.たぶん64bit WindowsJavaの方がスタックサイズが大きいのですね.

実は同じようなことを昔もやっていて、スタックサイズを増やして解決して、お客様に提出するマニュアルにも明記していたのですが、もう前のことだったので恥ずかしながらすっかり忘れていました.この553個と言うのはbookmapにぶら下がっているtopicの数です.topicなんて文書の量に比例してどんどん増えるので、その分だけスタックが必要というのはあまり良い実装とはいえません.しかし元のスタイルシートがXSLT1.0の時代に作った代物なので、xsl:for-each-groupのようなグルーピングも出来ず、最初のtopicから最後のtopicに向かってxsl:apply-templatesでひたすら再帰してゆくしか手がなかったのです.

いったん運用に乗ってしまうといくら自分の書いたスタイルシートであってもおいそれと直せなくなります.ともかくお客様はプロダクションレベルで使用されているのですから、そう簡単に修正できません.そしてバグならなんとしても直さねばならないのですが、このようなXSLT1.0時代の名残のようなコードは非常に困ります.工数さえいただければXSLT2.0で書きなおせると思うのですが、運用にのってもう何年も経っているとお客様はなかなか開発予算を捻出するのも難しいのです.

DITA-OTもかつてはXSLT1.0でシコシコやっていた時代がありました.XSLT2.0を使うようになってから結構書き直したのですが、やはりすべてを最適化するという訳にはゆきませんでした.なかなか実運用とつきあってスタイルシートをメンテナンスしてゆくのは大変です.