R.A. Epigonos et al.

[XSLT] libxslt の generate-id() 関数が生成する id は実行時依存

libxslt をバックエンドとして使っているプログラム (例えば xsltproc など) を使って XSLT しているケースは結構あるような気がする (成果物の id 属性に idmXXX とか idpXXX とかが含まれていることが目印の一つになる。docbook + xsltproc で生成された文書はこのような例の一つ)。generate-id() 関数が使われていると、同じソースを使って複数回生成すると別の生成物ができることになる。

libxslt を使う xsltproc を用いた id 生成の例

テストに使ったファイルおよび xsltproc のバージョンは以下。

$ cat data.xml
<?xml version="1.0" encoding="UTF-8"?>
<addressbook>
  <person>
    <name>Alice</name>
  </person>
  <person>
    <name>Bob</name>
  </person>
</addressbook>
$ cat test.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text"/>
  <xsl:template match="/">
    <xsl:for-each select="addressbook/person">
      <xsl:text>generate-id(): </xsl:text>
      <xsl:value-of select="generate-id(.)"/>
      <xsl:text>
</xsl:text>
      <xsl:text>number: </xsl:text>
      <xsl:number level="multiple" count="*"/>
      <xsl:text>
</xsl:text>
      <xsl:text>name: </xsl:text>
      <xsl:value-of select="name"/>
      <xsl:text>

</xsl:text>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>
$ xsltproc --version
Using libxml 20901, libxslt 10128 and libexslt 817
xsltproc was compiled against libxml 20902, libxslt 10128 and libexslt 817
libxslt 10128 was compiled against libxml 20902
libexslt 817 was compiled against libxml 20902
$ apt-cache depends xsltproc
xsltproc
  Depends: libc6
  Depends: libxml2
  Depends: libxslt1.1
$ dpkg-query --show xsltproc libc6 libxml2 libxslt1.1
libc6:amd64     2.19-18+deb8u4
libxml2:amd64   2.9.1+dfsg1-5+deb8u1
libxslt1.1:amd64        1.1.28-2+b2
xsltproc        1.1.28-2+b2

xsltproc でこれらのファイルを処理する。同じことを 2 回やって、比較すると以下のようになる。generate-id() 関数で生成された id は実行時依存している (同じノードに別の id が割り振られている) 事がわかる。

$ xsltproc --output test0.txt test.xsl data.xml
$ xsltproc --output test1.txt test.xsl data.xml
$ cat test0.txt
generate-id(): idp25111792
number: 1.1
name: Alice

generate-id(): idp25072576
number: 1.2
name: Bob

$ cat test1.txt
generate-id(): idp24575216
number: 1.1
name: Alice

generate-id(): idp24536000
number: 1.2
name: Bob

$ diff test0.txt test1.txt
1c1
< generate-id(): idp25111792
---
> generate-id(): idp24575216
5c5
< generate-id(): idp25072576
---
> generate-id(): idp24536000

プロジェクトごとの解決法

debian の reproducible builds ではそういうの許されないんじゃなかったっけ? と思ったら、debian の reproducible build 環境はこの点を克服した (generate-id() を実行時依存させないように libxslt/functions.c にパッチを当てた) libxslt が含まれる ExperimentalToolchain を sid に加えて作られているみたい。このパッチは上流に報告されたものを流用している様子だけど、上流側の master ブランチに取り込まれた形跡は今のところないみたい。

sid + ExperimentalToolchain の場合 xsltproc の依存パッケージとバージョンは以下のようになる。

$ xsltproc --version
Using libxml 20903, libxslt 10128 and libexslt 817
xsltproc was compiled against libxml 20903, libxslt 10128 and libexslt 817
libxslt 10128 was compiled against libxml 20903
libexslt 817 was compiled against libxml 20903
$ apt-cache depends xsltproc
xsltproc
  Depends: libc6
  Depends: libxml2
  Depends: libxslt1.1
$ dpkg-query --show xsltproc libc6 libxml2 libxslt1.1
libc6:amd64     2.22-7
libxml2:amd64   2.9.3+dfsg1-1
libxslt1.1:amd64        1.1.28-2.1.0~reproducible2
xsltproc        1.1.28-2.1.0~reproducible2

さらに、同じように xsltproc を実行すると以下のようになる。この場合は実行時依存しないidが割り当てられていることがわかる。

$ xsltproc --output test0.txt test.xsl data.xml
$ xsltproc --output test1.txt test.xsl data.xml
$ cat test0.txt
generate-id(): idm2
number: 1.1
name: Alice

generate-id(): idm4
number: 1.2
name: Bob

$ cat test1.txt
generate-id(): idm2
number: 1.1
name: Alice

generate-id(): idm4
number: 1.2
name: Bob

$ diff test0.txt test1.txt

docbook では generate.consistent.ids パラメータを使って id 生成関数を切り替える事が可能。このパラメータはデフォルトで0だけど、1にすることでgenerate-idの代わりに <xsl:number level="multiple" count="*"/> を使って id を生成するようになる。xsl:number を使う方針の問題は、生成されるidがノードの位置示すものなので、同じ内容に別の id が割り振られる可能性があるという点。上の例だとAliceとBobを入れ替えるとAliceに割り当てたidがBobに割り当てられることになる。このため、1.1 という id が Alice を示すものと期待は裏切られる。

id を付加したい要素の id をどのような変化に対して変化させるかを考えると以下のようになる。パッチを当てた xsltproc を使う方針も、xsl:number を id として使う方針も type1 (XPath と一対一対応する id が割り振られている) の様子。id を何に紐付けるのかというのは議論の余地があるけれど、内容に紐付けたいというケースも有るように思う。idを内容に対して割り当てるならば (type0)、文書内におけるその内容の位置が変わっても同じidでアクセスできるという点が利点。その代わり、同じ内容が複数の位置に現れた場合を考慮しないといけない (同じidを持つものが同じ文書内にあってはならないという意味で)。

id を付加したい要素の id
内容 XPath type0 type1 type2
保存 保存 保存 保存 保存
保存 変化 保存 変化 変化
変化 保存 変化 保存 変化
変化 変化 変化 変化 変化

リファレンス

  1. libxslt
  2. Bug 751621 – Please make the genered IDs deterministic
  3. ReproducibleBuilds/ExperimentalToolchain - Debian Wiki
  4. reproducible/libxslt - libxslt.git
  5. libxslt - XSLT transformation library
  6. generate.consistent.ids

ソーシャルブックマーク

  1. はてなブックマーク
  2. Google Bookmarks
  3. del.icio.us

ChangeLog

  1. Posted: 2008-01-26T13:27:06+09:00
  2. Modified: 2008-01-26T13:27:06+09:00
  3. Generated: 2023-08-27T23:09:15+09:00