Web ComponentsとXSLTの相性を調べてみた

将来を期待されているWeb Componentsですが、もしかしたらXSLTと相性がいいかもしれない、と思ったので調べてみました。
その結果、かなり相性がよさそうだということが分かりましたので公表します。
参考となるコードはGitHubに置いてあるので、これを見ながら、あるいはクローンして手元に置きながら説明をお読みください。
https://github.com/inomoto-hironobu/wc_and_xslt
## コード
まず、目標とすること設定します。
あるページから、カスタム要素によって別のページのOGPを抜き出して思い通りに表示する、というのを作ってみましょう。
次のページのOGPを表示するとします。
```xml:ogp.xhtml
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ogp="https://ogp.me/ns#">
<head>
<title></title>
<meta property="ogp:title" content="OGPを設定しているXHTML"/>
<meta property="ogp:url" content="/ogp.xhtml"/>
<meta property="ogp:image" content="/3108290_s.jpg"/>
<meta property="ogp:description" content="参考のためにOGPを設定しています。"/>
<meta property="ogp:type" content="article"/>
</head>
<body>
<p>参考のためだけのページ</p>
</body>
</html>
```
このうち、ogp:titleのところだけを抜き出す仕組みを見てみましょう。
firstフォルダを見てください。
ここにあらゆる応用の利く第一歩となるコードを収めています。
まずはXHTMLファイルから見ましょう。
```xml:first/sample.xhtml
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:t="urn:template">
<head>
<script src="../SaxonJS2.js"></script>
<script src="ogp.js"></script>
</head>
<body>
<p>
<internal-ogp path="../ogp.xhtml" />
</p>
<template id="ogp-template">
<div class="ogp">
title:<t:title />
</div>
</template>
</body>
</html>
```
まずXSLTの実行ですが、SanxonJSを使いますので、ダウンロードしてscript要素に定義します。
https://www.saxonica.com/html/download/javascript.html
そして、このXHTMLではinternal-ogpというカスタム要素が使われています。
さらにtemplate要素で結果としてどのように変換するかの構成を定義しています。
注目はt:title要素です。
名前空間urn:templateを持つこの要素をターゲットにogp:titleプロパティの値を入れていきます。
ちなみにXHTMLにしている理由は、話を簡単にするためです。説明は省きますが、HTMLだと話が複雑になってきます。
では次にJavaScriptです。
```javascript:first/ogp.js
class InternalOgp extends HTMLElement {
	constructor() {
		super();
		const options = {
			stylesheetLocation: "ogp.sef.json",
			//DocumentFragmentを取得しXSLTのソースとする
			sourceNode: document.getElementById('ogp-template').content,
			stylesheetParams:{
				//カスタム要素の値をXSLTのパラメーターに設定
				"path":this.getAttribute("path")
			},
			//結果をDOMで取得する
			destination:"document"
		};
		SaxonJS.transform(options, "async")
		//SaxonJSから
		.then(d=>{
			this
			//シャドールートにアタッチ
			.attachShadow({mode:"open"})
			//変換後のNodeを追加
			.appendChild(d.principalResult);
		 }).catch(v=>{
			console.log(v);
		 });
	}
}

window.addEventListener('DOMContentLoaded', ()=>{
	customElements.define("internal-ogp",InternalOgp);
});
```
よくあるWeb Componentsのコードですが、かなり違う点があることが分かると思います。
SaxonJS.transform関数を使っているのが分かるでしょうか。
本来JavaScriptで書くべき動作のコーディングをXSLTに移譲してくわけです。
では、XSLTを見てみましょう。
```xml:first/ogp.xsl
<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns:tとした場合 -->
<xsl:stylesheet version="3.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:t="urn:template"
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  xpath-default-namespace="http://www.w3.org/1999/xhtml">
  <xsl:output method="xml" />

  <!-- OGP情報を取得したいXHTMLのパス-->
  <xsl:param name="path" />

  <!-- 対象のDOM-->
  <xsl:variable name="target" select="document($path,.)"></xsl:variable>

  <!-- t:title要素を処理-->
  <xsl:template match="t:title">
    <xsl:value-of select="$target//meta[fn:resolve-QName(@property,.)=fn:QName('https://ogp.me/ns#','title')]/@content"></xsl:value-of>
  </xsl:template>

  <!-- その他一般の要素や属性をそのままコピーする-->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*" />
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>
```
XHTML要素を出力しながらt:title要素に関してはOGPの値を出力する、というロジックになっています。
ほとんどの要素をただコピーしながら、ある特定の条件がそろったノードに差し掛かった時だけ、ある動作をする、というのが定石になると思われます。
これで材料はそろったのですが、SaxonJSではXSLTを直接ロードすることはできず、SEF(Stylesheet Export File)というものにコンパイルし、それを読まなければなりません。
トップから
```bat
cd first
xslt3 -t -xsl:ogp.xsl -export:ogp.sef.json -nogo
```
とすればSEFが作られます。そしてトップで、
```bat
npm run server
```
と打てばサーバーが立ち上がりますので、`localhost:8080/first/sample.xhtml`にアクセスすれば確認ができます。
![FireShot Capture 023 -  - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115230/e2b06360-9666-bc90-7f9a-0d7ee4de828f.png)
secondフォルダではさらに進んだコードを収めています。ぜひ比較してください。

## まとめ
Web ComponentsにXSLTを絡ませるという試みをやってみたのですが、かなりいい手ごたえを感じました。
大体次のようなことが言えると思います。
* template要素の解析と処理といった、DOMの操作でXSLTの右に出るものはない
* 現在のXSLTは3.0になっており、非常に高機能かつ多機能で、活用しない手はない
* JavaScriptエンジニアの負担が減る
* 今回は急ぎで作ったが、SaxonのAPIは多様でさらに応用範囲の広いことができる

今まで影の薄いXSLTでしたが、フロントエンドで復活する未来が来ると思います。