2004年5月分。
何度か挫折を繰り返してますが、最近またXSLTを勉強してます。ウェブ上の資料だけで学ぶのには流石に限界を感じたため本を買うことにしまして、少々高めながらも『XSLT実践ガイド』を先日購入。まだ読み途中ですが、分かり易くて良い本だと思います。
取り敢えずは短編の更新を楽にすることを目標に。
多少分かってくると色々と試してみたくなりますが、先日お見かけしたMini Sapphireの日記の4月26日分、属性値に文字参照を含めるで触れられていた問題について考えてみたり。といっても以前にねこめしにっきで同じ問題に触れられており、そこでも示唆されていた方法ですが、xsl:text要素とdisable-output-escaping属性を使ってタグ自体を文字列として直書きすればなんとかなりそうです。
例えば、 <mail>kits@akatsukinishisu.net</mail>
という要素を <a href="kits@akatsukinishisu.net">mail</a>
という風に変換する場合には、こんなテンプレートで。
<xsl:template match="mail">
<xsl:text disable-output-escaping="yes"><a href="</xsl:text>
<xsl:value-of select="substring-before(., '@')"/>
<xsl:text disable-output-escaping="yes">&#64;</xsl:text>
<xsl:value-of select="substring-after(., '@')"/>
<xsl:text disable-output-escaping="yes">">mail</a></xsl:text>
</xsl:template>
綺麗な方法とは言い難いですが、XSLT 1.0の範囲で何とかなるという点では色々と応用できるのではないかと思います。一応手元のXalan-Java, xsltproc(libxslt), XT, sabcmd(sablotron)では望みの出力を得ました。
と思ってたらそうならないXSLTプロセッサをひとつ発見。Mozilla内蔵のXSLTプロセッサは、このようにして出力したタグをXMLタグとして認識せず、ウィンドウに <a href="kits@akatsukinishisu.net">mail</a>
という文字列がそのまま表示されてました。うーむ、それはそれで正しい動作のような気も……。
XMLからXHTMLの変換における問題では、「brなどの空要素を出力する際に、 <br />
の"<br"と"/>"の間の空白が削られる」というのがありますが、それもこのようにすれば良いような(br要素をそのままbr要素として出力する例)。
<xsl:template match="br">
<xsl:text disable-output-escaping="yes"><br /> </xsl:text>
</xsl:template>
というのも既に述べられている話ではありますが。
(2004年5月2日)
「HTML鳩丸倶楽部」更新履歴の2001-10-03で提示され、agendaのカレントノードについて(XSLT)で言及されている問題について、別解を求めるべく挑戦してみました。一応問題を書いておくと、
<?xml version="1.0"?>
<document>
<foo>1</foo>
<foo>2</foo>
<foo>3</foo>
<foo>4</foo>
<foo>5</foo>
</document>
という文書を、
<?xml version="1.0"?>
<document>
<bar>
<foo>1</foo>
<foo>2</foo>
</bar>
<bar>
<foo>3</foo>
<foo>4</foo>
</bar>
<bar>
<foo>5</foo>
</bar>
</document>
とするにはどうすればよいか、というものです。
最初にXSLTスタイルシートを作った時は「やはりfor-eachを使うべきだろうか」と思ってそのようにしてみたのですが、できてみるとjintrickさん作成のものと殆ど同じになってしまったので、どうにかして何とか別のやり方ができないかと頭を捻ったところ、以下のスタイルシートができました。
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:param name="group" select="2"/>
<xsl:template match="/document">
<document>
<xsl:apply-templates select="
foo[(position() mod $group)=1 or $group=1]
"/>
</document>
</xsl:template>
<xsl:template match="foo">
<bar>
<xsl:copy-of select="
. | following-sibling::foo[position()<$group]
"/>
</bar>
</xsl:template>
</xsl:stylesheet>
つまりapply-template要素ではfoo要素の中で1, 3, 5, ... 番目のものだけにテンプレートを適用するようにして、テンプレートでは「カレントノードと、その後に続くfoo要素のうちカレントノートから数えて2番目未満のもの」をコピーしてbar要素の中に入れる、という方法。言わば諸々の条件分岐をすべてXPath式(select属性)に押し込んだ形であり、書いているうちはあまり深く考えていなかったのですが、実際できてみると「XPathって便利だなあ」と実感したものでした。
「幾つずつグループ化するか」というのはgroupパラメタで指定しているので、外から2以外の数を指定することもできます。例えばxsltprocを使ってgroupパラメタに3を渡すと、このように。
$ xsltproc --param group 3 [XSL file] [input XML file] <?xml version="1.0"?> <document> <bar> <foo>1</foo> <foo>2</foo> <foo>3</foo> </bar> <bar> <foo>4</foo> <foo>5</foo> </bar> </document>
一応 group=1 でも動作するようになってます。apply-templates要素のselect属性に or $group=1
という条件が入っているのはそのためで、本来の目標からすると無くてもよい部分だったり。
for-each要素を使わずにapply-templates要素とtemplate要素でなんとかできてみると、そこはかとない満足感を覚えるのは自分だけでしょうか。
(2004年5月4日)
HTML文書作成日記の4月22日分、シリーズ! XML + XSLT 化までの道のり #3で挙げられていた、「同一の DD 属性値を持つものに 01 から順に番号を添え、 DD 属性値が変化したらまた 01 に戻
」るようにする、という問題に挑戦してみました。
問題を単純化するため、以下の入力文書:
<?xml version="1.0"?>
<document>
<sect DD="01">1-1</sect>
<sect DD="01">1-2</sect>
<sect DD="02">2-1</sect>
<sect DD="02">2-2</sect>
<sect DD="02">2-3</sect>
<sect DD="03">3-1</sect>
<sect DD="04">4-1</sect>
<sect DD="04">4-2</sect>
</document>
から、以下のような出力を得ることを想定します(勿論sect要素内の文字列を参照したりするのは無しで)。
<?xml version="1.0"?>
<document>
<p id="d01n01">1-1</p>
<p id="d01n02">1-2</p>
<p id="d02n01">2-1</p>
<p id="d02n02">2-2</p>
<p id="d02n03">2-3</p>
<p id="d03n01">3-1</p>
<p id="d04n01">4-1</p>
<p id="d04n02">4-2</p>
</document>
実のところ、最初にこの問題を見た際にも一度挑戦していまして、XSLTのnumber要素でなんとかできないかと思っていたのですが、うまく行かず挫折したことがありました。しかし前回の問題でXPath式の強力さを実感したので、同じようなアプローチでなんとかできるのではと思いまして、試行錯誤した結果、以下のスタイルシートができました。
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/document">
<document>
<xsl:apply-templates select="
sect[@DD!=string(preceding-sibling::sect[1]/@DD)]
"/>
</document>
</xsl:template>
<xsl:template match="sect">
<xsl:for-each select="
. | following-sibling::sect[@DD=current()/@DD]
">
<p id="d{@DD}n{format-number(position(),'00')}">
<xsl:value-of select="."/>
</p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
つまりapply-templates要素では「sect要素の中で、DD属性値がその一つ前のsect要素のDD属性値と異なるもの」にだけテンプレートを適用するようにし、テンプレートでは「カレントノードと、その後に続くsect要素でDD属性値がカレントノードのDD属性値と同じものを選んでカレントノードリストとし、その中でfor-eachループを回して番号を振る」というもの(本当に前回と同じような感じだ……)。
ひとつ良く分からなかった部分がありました。apply-templates要素のselect属性内で強調で示した部分、ここは当初、以下のようにstring関数を使わなくともよいのではと思っていました。
@DD!=preceding-sibling::sect[1]/@DD
しかし、これだと先頭のsect要素では望む結果が得られませんでした。
整理すると、入力文書において、コンテキストノードが先頭のsect要素だった場合、コンテキストノードのDD属性( @DD
)は"01"を返します。一方、「その一つ前の兄弟ノードであるsect要素のDD属性」をXPath式で書くと "preceding-sibling::sect[1]/@DD
" となりますが、実際には「一つ前の兄弟ノードであるsect要素」がそもそも存在しないため、比較の際には空文字列として評価されるものと考えました。なので両者を演算子 != で比較した場合にはtrueが返ってくるものと思っていたのです。
しかし実際に
boolean(@DD!=preceding-sibling::sect[1]/@DD)
を取ってみると、これはfalseを返します。また次のような比較:
boolean(@DD=preceding-sibling::sect[1]/@DD)
を取ってみても、同じくfalseが返されました。試してみた限りでは、XSLTプロセッサによる違いはなさそうです。
このあたりの根拠がXPathの仕様のどの部分に当たるのか、未だ見つけられずにいるのですが、取り敢えず以下のようにすれば望む結果を得ることができるようでした。
@DD!=string(preceding-sibling::sect[1]/@DD)
not(@DD=preceding-sibling::sect[1]/@DD)
上記の比較式について、XPathにおけるノード集合とその他のオブジェクトとの比較に追記しました。
(2004年5月4日)
前回で挙げた、XPathで一方に空のノード集合がある時の比較式について、jintrickさんに解説頂きました(感謝)。
ここで私は、
3 != foo
(偽)にスポットを当ててみました。数値(3)とノード集合foo
を!=
で比較するとき、そのノード集合foo
に含まれるの各ノードにnumber
関数を適用して、3
に等しくないものが一つでもあれば真になります。number(foo)
と比較されるのではないのです(number(foo)
なら非数値(NaN)なので、3 != number(foo)
は真になりますし)。そしてこのノード集合foo
は空ですから、中にノードなんてありません。等しくないものが見つからないわけです。なんだかややこし過ぎて笑ってしまいました。
なるほどそういうことか……。てことはやはりXPath仕様書の以下の部分が該当するわけですね。ちょっと長いですが3.4 ブールの項より引用。
比較するオブジェクトが両方ともノード集合の場合は、1番目のノード集合内のノードと2番目のノード集合内のノードの文字列値を比較して、結果が真になるようなノードが両方のノード集合内にある場合のみ、比較結果は真になる。 比較するオブジェクトの1つがノード集合でもう1つが数値の場合は、number 関数を使用してノードの文字列値を数値に変換したものと比較対象の数値を比較して、結果が真になるようなノードがノード集合内にある場合のみ、比較結果は真になる。 比較するオブジェクトの1つがノード集合でもう1つが文字列の場合は、ノードの文字列値と比較対象の文字列を比較して、結果が真になるようなノードがノード集合内にある場合のみ、比較結果は真になる。 比較するオブジェクトの1つがノード集合でもう1つがブール値の場合には、boolean 関数を使用してノード集合をブール値に変換したものと比較対象のブール値を比較し、その結果が真になる場合のみ、比較結果は真になる。
改めて読むと漸く理解できました。また別の例を挙げると、
<?xml version="1.0">
<document>
<p>abc</p>
<p>def</p>
</document>
という文書を想定した場合、以下の比較式は両方とも真を返します(p要素を集めたノード集合には、各ノードの文字列値が'abc'に等しいものも、等しくないもの('def')も存在するため)。
'abc' = /document/p
'abc' != /document/p
しかし string(/document/p)
を想定した場合、string関数は文書順で最初のノードの文字列値を返す……つまり最初のp要素の文字列値である'abc'を返すので、以下のようになるわけです。
'abc' = string(/document/p)
は、真を返す。'abc' != string(/document/p)
は、偽を返す。XPathの比較式で比較の対象にノード集合を置く場合は、この辺りの仕組みを理解しておかないと。
(2004年5月5日)
CSSでデザインされているサイトの周辺では時折「××は窓から投げ捨てろ」(××にはブラウザの名前が入る)というような言葉を目にすることがあります。
Netscape 4.x は今すぐ窓から投げ捨てろ」
当サイトでもCSSを利用しており、光栄にも前記リンク集に掲載されていたりしますが、その言葉の考え方には今ひとつ賛同できないところです。
W3Cが策定しているHTMLの仕様は、環境への非依存と後方互換性についてかなり深く(素人が及びもしないほどに)考慮された設計となっています。要するに仕様に従ってHTML文書を書けば、それだけである程度誰にでもどこからでも(古いブラウザからでも)利用し易いページになるわけで、だからこそ仕様に妥当なHTML文書には強みがあるのであり、自分もそのようにしている次第です。
ある特定のブラウザを否定することは、そのような利点を持つHTMLを使って文章を公開する目的とは矛盾するように思います。
CSSによる高度なスタイル表現を行おうとしている人にとっては、CSSの仕様に対応しようとしない、または仕様と異なる表示を行うブラウザが厄介なものであることは、理解できます。しかしHTMLで文章を公開する目的を考えた場合、「CSSによる視覚表現を多くの人に見てほしい」「文章の内容を広く伝えたい」のどちらを取るかと問われれば迷うことなく後者なわけで。あるブラウザに対応するのが面倒だと思うことはあっても、否定する気はさらさらないです。
(2004年5月6日)
Wikiの手軽さに少々はまり気味です。
中野雅之さんのWeblogで、「application/xhtml+xmlで運営しているサイトなんて、 ここぐらいなものかもしれません。
」と書いてあったのを見て、いや結構見かけるような気がしますよ、と思ったのをきっかけにざっくりと作ってみました。他にもありましたら追加歓迎。
xstlprocやxmllintでcatalog fileというのを使うと、XML文書解析の際に外部ネットワークからDTDを取得しなくても済むらしいようだったので、ざっと関連文書などをメモしてみました。で、「おそらくXalan(-Java)にもそういう仕組みがあるはずだ!」という根拠の無い思い込みのもと探してみたところ、なんとか方法を見つけて導入に成功したものの、大いにはまったという。Xalanは便利だけどJavaはややこしい……。
試しに/itazuragaki/id/下のXHTML文書(300個)をxmllintで検証してみてみると、30秒くらいで終了できて爽快。ついでに御蔭でsamp要素を<sample>と書いていたのに気付き、慌てて修正。
(2004年5月26日)
問題は千字に満たない作品を減点する読者(投票者)が存在することだ。この場合、推敲しても千字に届く訳がない700字程度の作品は初めから不利となる。
運営者としては、字数1000字以内というのは単に競うための制限であり、1000字に近づけたりぴったりにすることにはあまり意味はないと思っています。それは決まりのところでも「作品に合った長さでお願いします
」と書いているとおり。個人的も、短くて面白い作品を幾つか見てきたので、作者の方にはあまり字数を気にせずに中身重視で書いて頂きたいところ。
ところで1000字に足りないことがそんなに負の要因となるようなら問題ではあるので、一応結果ページを「字数」で検索などしてみました。見てみると1000字に足りないことを不満とする感想もある一方で、短くて(も)良いという感想もところどころあるので、それほど気にすることはないように思います。
(2004年5月26日)