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 |
保存 | 保存 | 保存 | 保存 | 保存 |
保存 | 変化 | 保存 | 変化 | 変化 |
変化 | 保存 | 変化 | 保存 | 変化 |
変化 | 変化 | 変化 | 変化 | 変化 |