nXMLでXInclude入りDocBook文書を検査する

nXMLで1つのXMLファイルに複数のRELAX NGを適用しようとしたけれども、その必要はなかったというお話です。

## nXML Mode？

Emacsには [nXML mode](https://www.gnu.org/software/emacs/manual/html_mono/nxml-mode.html) というXMLのためのメジャーモードがあります。RELAX NGスキーマに従って編集しているXML文書の妥当性を確認してくれたり、補完で候補を出してくれたりして便利です。

## 追加でスキーマを読み込む

デフォルトで読み込まれるRELAX NGだけでは足りないと思うこともあります。そういったときには、[Locating a schema](https://www.gnu.org/software/emacs/manual/html_mono/nxml-mode.html#Locating-a-schema) のように、読み込むスキーマを追加することもできます。

### DocBook の RELAX NGスキーマファイル

例えばDocBook 5.1文書を編集したいとしましょう。これはデフォルトではnXMLは備えていないので、自分で設定する必要があります。

DocBookはRELAX NGで定義されています（[Relaxing with DocBook](https://tdg.docbook.org/tdg/5.1/ch01.html#introduction-rng)）。macOSであればHomebrewから入手することもできます。オープンソースソフトウェアのドキュメントはDocBookで書かれているものもあるので、知らないうちに入っていた、ということはありそうです。

```shell-session
$ brew info docbook
docbook: stable 5.1 (bottled)
Standard SGML representation system for technical documents
https://docbook.org/
/usr/local/Cellar/docbook/5.1_1 (199 files, 8.9MB) *
...

$ brew install docbook
...
```

そして私の環境では、バージョン5.1のコンパクトRELAX NGスキーマファイルは `/usr/local/Cellar/docbook/5.1_1/docbook/xml/5.1/schemas/rng/docbook.rnc` に置かれました。

### スキーマの場所を指定する

スキーマ読み込みのための設定ファイルはXMLによるものです。以下のような内容を適当な場所の適当なファイル名で保存します。今回は `~/.emacs.d/schemas.xml` とします。`namespace` 規則を使用しましたが、他にもXMLファイルにマッチさせるための方法はいくつかあります。

```xml:~/.emacs.d/schemas.xml
<?xml version="1.0"?>
<locatingRules xmlns="http://thaiopensource.com/ns/locating-rules/1.0">
  <namespace ns="http://docbook.org/ns/docbook" uri="/usr/local/Cellar/docbook/5.1_1/docbook/xml/5.1/schemas/rng/docbook.rnc"/>
</locatingRules>
```

### nXMLの設定

そして、Emacsの設定ファイルで nXML mode の設定にもこの `~/.emacs.d/schemas.xml` について書く必要があります。ここでは leaf.elでは以下のようになります。

```emacs-lisp:~/.emacs.d/init.el
...
(leaf nxml
  :ensure t
  :config
  (eval-after-load "rng-loc"
    '(add-to-list 'rng-schema-locating-files "~/.emacs.d/schemas.xml"))
  :defvar rng-schema-locating-files)
...
```

また、Customであれば以下のように設定します。これは手で編集するのではなく、`M-x customize-variable RET rng-schema-locating-files RET`とすればCustomの設定画面が開くので、そこからパスを指定します。

```emacs-lisp:~/.emacs.d/init.el
(custom-set-variables
 ...
 '(rng-schema-locating-files '("~/.emacs.d/schemas.xml"))
 ...
```

これにて純粋なDocBook5.1文書を快適に編集することができます。

## XIncludeしたい

DocBookには `programlisting` 要素があり、コードブロックを表現することができます。このとき、別のファイルに取り込まれるソースコードを書いておきたいときがあります。
また、文書が長大になってくると、複数のファイルに分割したいと思うことがあります。

XMLで外部ファイルを読み込む方法は主に2つあるのですが、どちらを採用すればよいでしょうか。

[DocBook The Definitive Guide (Version 1.5.3 for DocBook 5.1)](https://tdg.docbook.org/tdg/5.1/ch02.html#ch02-physdiv) には以下の記述があります。したがって普段使いではXIncludeによる方法を取るようにしています。

> As time passes, the use of DTD-based mechanisms like entities is diminishing. If you have an eye on the future, to the extent that it is practical, it is probably better to use XInclude than entities.

### XIncudeのコンパクトRELAX NGスキーマ

[XIncludeの規格](https://www.w3.org/TR/2006/REC-xinclude-20061115/)にはRELAX NGによるスキーマは載っていません。ここでは[Trang](https://relaxng.org/jclark/trang.html)を使って変換することにしました。変換結果は以下です。

```rnc:~/.emacs.d/schemas/xinclude.rnc
default namespace = "http://www.w3.org/2001/XInclude"
namespace a0 = "http://relaxng.org/ns/compatibility/annotations/1.0"

start |= \include
\include = element include { includeType }
includeType =
  (fallback
   | element * { text }
   | element * { text })*,
  attribute href { xsd:anyURI }?,
  [ a0:defaultValue = "xml" ] attribute parse { parseType }?,
  attribute xpointer { xsd:string }?,
  attribute encoding { xsd:string }?,
  attribute accept { xsd:string }?,
  attribute accept-language { xsd:string }?,
  attribute * { text }
parseType = "xml" | "text"
start |= fallback
fallback = element fallback { fallbackType }
fallbackType =
  (\include
   | element * { text }
   | element * { text })*,
  attribute * { text }
```

これでRELAX NGファイルの準備が整いました。

## 複数のRELAX NGを結合したい

前節まででRELAX NGファイルの用意はできましたが、問題はこれを1つのDocBook文書に適用する方法です。nXML modeのマニュアルを読むに、1つの文書に対して1つのスキーマを当てる方法はありますが、1つの文書に対して2つ以上のスキーマを当てる方法については説明がありません（はずです）。

### external?

それではRELAX NG的に解決しようとするとどうなるのか。[RELAX NG Compact Syntax Tutorial](https://relaxng.org/compact-tutorial-20030326.html)を斜め読みしたところ、 `external` が良さそうです。2つのRELAX NGスキーマファイル、DocBook用とXInclude用を両方 `external` で読み込みます。

```rnc:~/.emacs.d/schemas/docbook_xinclude.rnc
external "/usr/local/Cellar/docbook/5.1_1/docbook/xml/5.1/schemas/rng/docbook.rnc" | external "xinclude.rnc"
```

スキーマの場所指定もそれにともなって変えておきます。DocBookファイルの編集には先のコンパクトRELAX NGスキーマファイルを使います。

```xml:~/.emacs.d/schemas.xml
<?xml version="1.0"?>
<locatingRules xmlns="http://thaiopensource.com/ns/locating-rules/1.0">
  <namespace ns="http://docbook.org/ns/docbook" uri="schemas/docbook_xinclude.rnc"/>
</locatingRules>
```

### 結果

これでうまくいきそうではあるのですが、実際にはXIncludeの要素の部分で警告が出ています。DocBookのスキーマ定義では、ある要素の子要素にXIncludeの要素（例えば `xi:include` 要素、ただし `xi` はXIncludeの名前空間であるとする）を含む、とは書いてありません。したがってnXML modeは妥当性がないと判断するのでしょう。

それではXIncludeのスキーマ読み込みは効果がないのかというとそうではなく、XInclude要素内の属性については補完もされますし、妥当性の検査もされます。例えば `xi:include` 要素中の `href` 属性や `parse` 属性については補完されますし、妥当であると認められます。

個人的には、XIncludeを使う部分は `simpara` 要素や `literal` 要素ほどおびただしいわけではないので、これでもわりと充分だったりします。

## XIncludeしたい - その2

実はDocBookが配布しているRELAX NGスキーマにはXIncludeを加味したバージョンも付属しています。いい感じにDocBookとXIncludeが融合されているのでこちらを使いましょう。ディレクトリ名やファイル名に`docbookxi`が入っているのが目印です。

## 展望

素のDocBookでも充分ではありますが、略記や独自の要素で拡張したくなるときがあります。今回はありもので済んでしまいましたが、そうしたカスタマイズを加えたくなったときに、気軽にスキーマやスタイルシートを変更できるようになると良さそうです。