2005年 01月 21日

名前空間が null?

IRC ネタ。

2005年1月の指向性メモcreateElement()で作られたエレメントノードは名前空間がnullになるはずなのに、親要素のデフォルト名前空間を引き継いでしまっている。 って書いてあるけど、何か違う気がする。

確かに DOM Core の createElement の項には localName, prefix, and namespaceURI set to null. とは書いてある (これを根拠にしているかは定かじゃない) けれど、これは名前空間っていう概念がないから、名前空間関連のプロパティにはとりあえず null という値を入れておけよってことで、名前空間URI を空値に設定するっていう意味じゃない気がする。code でマークアップされてるしね?

そもそも自分も書いたことあるんだけれど、名前空間がnullになるっていうのが何かおかしいかもしれないとも思う。Extensible Markup Language (XML) 1.0 にも Namespaces in XML にも null という単語が出てこない。名前空間が null っていう表現が出てくるのは XPath の仕様の日本語訳で、原文の null には code 要素がついていないので、ただたんに“空”といいたいだけなんじゃないかとか。でもそうだったら empty って書くかなぁ……仕様書だし紛らわしいふうには書かないから、あるいは俺の読解力が糞なおかげで違うかもしれない……

まー結局のところ createElement に名前空間の概念がそもそもないので単純に引数の nodeName を引数の名前にするよってことで、それ以上は実装依存なんじゃまいか。みたいな? 併用することは稀なので。。。

しかし自分の解釈があっているかどうかは永遠に謎だ。確かめようがないから困る。誰に聞けばいいのか。その人が言っていることが正しいのか、正しいとしても自分がそれを正しいまま受け取れているかは確かめようがない。あーアレだね。アレ。理解は誤解の総体 (だっけ?) ってヤツ。 わかりあえているように感じるにはできるだけ曖昧な表現をすればいい。アレとかソレとかを、明確にしない「ありえねー」とか。

2004年 12月 31日

結果ツリーフラグメントの誤解

2004年12月の指向性メモ で言及されてるのを読んでもう一度仕様書を読み直してみる。

Variables introduce an additional data-type into the expression language. This additional data type is called result tree fragment. A variable may be bound to a result tree fragment instead of one of the four basic XPath data-types (string, number, boolean, node-set). A result tree fragment represents a fragment of the result tree. A result tree fragment is treated equivalently to a node-set that contains just a single root node. However, the operations permitted on a result tree fragment are a subset of those permitted on a node-set. An operation is permitted on a result tree fragment only if that operation would be permitted on a string (the operation on the string may involve first converting the string to a number or boolean). In particular, it is not permitted to use the /, //, and [] operators on result tree fragments. When a permitted operation is performed on a result tree fragment, it is performed exactly as it would be on the equivalent node-set.

強調した部分が重要らしい。最初の強調がややこしい。原文引用なのは訳文がまたさらにややこしいから (謎)

結果ツリーフラグメントに使える操作はノード集合の一部 (最初の強調) でその許される操作ってのは文字列にできるやつだけ (二つ目の強調)。あとはその後に書いてある通り、/ とかの演算子は使えない。

結局、結果ツリーフラグメントが格納されている $rtf がある場合において <xsl:apply-templates select="$rtf/child::node()"/>XSLT1.0 的にはエラーらしい。

そんなこんなで、XSLT1.0 でマトモな変換を書こうとするとかなり冗長なことをせざるを得ない状況が生まれるみたいです。めんどくさー

関係ないけど、どういう経緯でこういう制限がついたんだろう。

2004年 12月 25日

XSLT で行をマークあっぴ

汎用っぽいテンプレ作っていたら、XSLT だけで一行ごとに l 要素とかソレっぽいのでマークアップできることに気付いた……っていうかアレだ。

<xsl:template name="split">
<xsl:param name="value"/>
<xsl:param name="splitter"/>
<xsl:param name="element-name" select="'t:item'"/>
<xsl:choose>
<xsl:when test="contains($value, $splitter)">
<xsl:element name="{$element-name}">
<xsl:value-of select="substring-before($value, $splitter)"/>
</xsl:element>
<xsl:call-template name="split">
<xsl:with-param name="value" select="substring-after($value, $splitter)"/>
<xsl:with-param name="splitter" select="$splitter"/>
<xsl:with-param name="element-name" select="$element-name"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$element-name}">
<xsl:value-of select="$value"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:call-template name="split">
<xsl:with-param name="value" select="'aaa#x0a;bbbb#x0a;cccc#x0a;z'"/>
<xsl:with-param name="splitter" select="'#x0a;'"/>
<xsl:with-param name="element-name" select="'l'"/>
</xsl:call-template>

正規表現使いたい……

2004年 12月 23日

XSLT の XPath の

ネームスペース宣言の集合は、式が現れるアトリビュートを持つエレメントのスコープに含まれるものと同じである。この集合には、XML ネームスペース勧告 (XML Namespaces Recommendation) [XML Names] が必要とする、暗黙的に示されたプレフィックス xml の宣言も含まれる。デフォルトのネームスペース (xmlns を用いて宣言されたもの) は、この集合の一部ではない。 とか書いてあったりする。ソースツリーのデフォルトネームスペース URI が null (特に名前空間を全く宣言していない場合とか) 以外の時は絶対にプリフィックス無しではマッチとかしない。

<foo>
<bar>baz</bar>
</foo>

この場合に string(/foo/bar) = 'baz' は true。

<foo xmlns="http://foo/">
<bar>baz</bar>
</foo>

この場合は string(/foo/bar) = 'baz' は false。/foo/bar は何も選択しない。例え XSLT 側のデフォルト名前空間が http://foo/ であっても何も選択されないxmlns:f="http://foo/" とかやって /f:foo/f:bar ってやらなきゃいけない。

で、困るっていうかよくわからんのはソースツリーのデフォルトネームスペースと結果ツリーのデフォルトネームスペースを同じにしたいときなんですよと。必然的に xmlns:f="http://foo/"xmlns="http://foo/" とか (順番も重要) やるわけですよ。exclude-result-prefixes="f" とかやるわけですよ。そうすると仕様書的に正しいかはよくわからないけど Sablotron の場合はどっちとも (xmlns, xmlns:f) 消えるんですよ (msxsl では大丈夫)。で、どうすんねんと哀さんと話していた次第(謎

xsl:namespace-alias とか利用すんのかなぁと思っていくつかソレっぽく書いてみたけどダメだった……なんかセオリー的なやり方ってないのかな。

XSLT と DOM との相違

XSLT では属性ノードとその親ノード (要素) との関係は片方向……属性ノード側からは @attr[. = ../../@attr] (省略しない場合: attribute::attr[self::node() = parent::node()/parent::node()/attribute::attr])) とかいう風に親がちゃんと親に見える (っておかしいな) けど、その親からは (attribute:: としているように) 軸が違う。ここで親から child::attr とアクセスできたら困るわけだけど、ややこしい。どうも俺は属性を子ノード的にイメージしていて attribute っていう軸がイメージしにくい。

DOM の場合は属性に親ノードはなく (parentNode は null) ownerElement に親要素が入ってる。軸が完全に分離してるっつうのかなぁ。DOM だとあんまり混乱しない。ただのプロパティでしかないからかなぁ。

XSLT の場合も DOM っぽい考え方をすればいい気がするのでちゃんと書いて、どこがどう違うかを考えてみてる。軸をプロパティと考えればいいの鴨。

// わけわかんコードだ(w
contextNode = current();
with (contextNode) {
contextNode = child["*"];
with (contextNode) {
contextNode = attribute["attr"];
with (contextNode) {
text();
}
}
}
2004年 12月 22日

結果ツリーフラグメント

注意 これは間違ってるかもしれない。実際に実装を使って確かめたわけじゃない。今はめんどくさくて確かめたくないので覚書的なもの。

変数バインドエレメント (xsl:param, xsl:variable) では select でノードセット格納するときと、子要素にテンプレートを書いてごちゃるのとでは違うらしい。後者は結果ツリーフラグメント (Result Tree Fragment) になる。これはルートノードを含む。(select の場合でもルートノードを含むように選択すればルートノードは含まれる。例えば document() を単体で使えば必ずルートノードがノードセットの入る)

<xsl:variable name="foo" select="/bar"/>
<xsl:apply-templates select="$foo"/>
<xsl:variable name="foo">
<xsl:copy-of select="/bar"/>
</xsl:variable>
<xsl:apply-templates select="$foo"/>

前者の場合、ルートノードを含まないノードセットなので、無限ループに陥らずにちゃんとなる。後者の書き方によって $foo に格納されるのは結果ツリーフラグメントで、結果ツリーフラグメントはルートノードを含むので、(現在適用しているスタイルシートの match="/" にマッチして) 無限ループ。

結果ツリーフラグメントをノードセットと混同するとエラい目にあう。例えば Mozilla がクラッシュしたりとか。

  • 結果ツリーフラグメントは、結果ツリーの断片 (フラグメント) を表す。結果ツリーフラグメントは、ルートノードを1つだけ含むノード集合と同様に扱われる。
  • 変数バインドエレメントが select アトリビュートを持たず、コンテンツが空でない場合 (つまり、変数バインドエレメントが1つまたは複数の子ノードを持っている場合)、変数バインドエレメントのコンテンツが変数の値になる。変数バインドエレメントのコンテンツはテンプレートであり、このテンプレートをインスタンス化すると、変数の値が得られる。この値は結果ツリーフラグメントであり、このフラグメントはテンプレートをインスタンス化して生成した一連のノードを子に持つルートノードを1つだけを含むノード集合と同等である。

あっていれば xsl:param にノード集合を渡す。 はノード集合ではなく正確には結果ツリーフラグメントだ。まぁ扱い方的はあまり変わらないけれど……

ってことは <xsl:param name="ggg" select="'unke'"/><xsl:param name="ggg">unke</xsl:param> は違うんだ。実際これらを <xsl:value-of select="$ggg"/> とかやると文字列に変換されるので同じように見えるだけか。この場合前者の方が変換がなくてほんの少し高速かな。

2004年 11月 29日

XSLT と XPath の理解

とりあえずカレントノードとコンテキストノード。

カレントノード = current() で、コンテキストノード = self::node()。カレントノードは XSLT のもの。XPath には current() という関数はないし、そもそも必要ない。

current() が変わるのは xsl:template 又は xsl:for-each。選択 (select) されたノードが current() になる。

self::node() が変わるのは /, [ ]。(別に変わるとは限らない……)

基準点とノードテストがまだあんまり理解できてない……

画像をてきとーに一覧表示する。

/view-img/2003/ みたいな。

RDF は画像ファイル自身に埋め込んだのを取り出していちいち動的に合成してる。現状では同じディレクトリに samp-meta.rdf があるからそっち直接読んでもいいんだけど……実験ってことで……

合成するとき REXML 使ってるから怪しい XML (名前空間接頭辞が他のファイルと違うとか) があるとたぶんパースエラーになる。稀なケースだし Ruby のライブラリでガッチリキッチリ実装した使いやすいやつを知らないので仕方ない。

ローカル側では RDF を埋め込む (ファイル名に -meta.rdf をつけたやつを突っ込む) ときに画像サイズが一定以上だったらサムネイルを作って、その情報 (foaf:thumbnail) も追加して埋め込む。

サーバー側は同じディレクトリの画像をスキャンして RDF を取り出し、@rdf:about を書き換えて合成。合成したヤツを XSLT エンジンに渡す。あとはまぁ普通に XSLT テンプレの仕事で……

ちなみにファイル名のリストは別に XML 作って渡してる。丁度いい語彙があれば RDFRDF として突っ込んだほうがスマートだけど考えるのが面倒だった。

だいぶソースが汚い。

2004年 11月 28日

Sablotron にノード集合を渡す。

XSLT エンジンにノード集合渡したいことが多々あるけど、params にナマの XML を渡しても当たり前にサニタライズされたただの文字列が渡るだけ。じゃあどうすりゃいいねんと諦めていたわけだけど、FOAF Explorer のソース見ててわかった。arguments のほうに XML とか XSLT とか渡すのと同じように渡せばいいんだ。

Sablotron は arguments を内部的に URI として (スキーム arg:) 扱っているから、そっちに適当に渡してやればやりほうだいらしい。エロイ。

$arguments = array(
'/_xml' => file_get_contents($xml),
'/_xsl' => file_get_contents($xsl),
'/foo' => "<test><f>aa</f><f>bb</f></test>"
);
$result = xslt_process($xh, 'arg:/_xml', "arg:/_xsl", NULL, $arguments);

で、XSLT 側からは document('arg:/foo') でアクセスできる。結局 <xsl:param name="foo" select="document('arg:/foo')"/> とかやっとけば OK。Sablotron イイ

2004年 11月 27日

FOAF Explorer でのエラー

なんかエラーでるので原因を特定すると、foaf:nick に rdf:Alt があるとエラーになる。FOAF Explorer の XSLT を見てみると <xsl:template mode="title" match="*"> というテンプレートの中身が悪さしてる。xsl:choose で選択しているので上のほうに書いてある要素で rdf:Alt を含まなければエラーにならないんじゃないかと思ってやってみたらヒット。foaf:name は書いていなかったので適当に書いておいた。この回避法だと foaf:name を既に書いていてエラーが出る場合無理。あっちの対策を待ちましょうみたいな?

でもこっちで回避するより何かフィードバック送ったほうがいいだろうなぁ。っていっても既に誰か送ってると思うんだけど……むしろエンジンのエラーな気がする。