XSLT 3.0(XPath 3.1)JSONをXMLに変換せずに利用する

先週はXMLの形にしたJSONを、XMLにしないまま扱う方法について紹介します。CSLはでてきません。

XPath 3.1*1で名前にJSONが入る関数は4つです。

  • fn:parse-json($json-text as xs:string?, $options as map(*))
  • fn:json-doc($href as xs:string?, $options as map(*))
  • fn:json-to-xml($json-text as xs:string?, $options as map(*))
  • fn:xml-to-json($input as node()?, $options as map(*))

$optionsについては説明しません。JSONをテキストとして引数にとるのがfn:parse-json()fn:json-to-xml()
fn:json-doc()は外部JSONファイルを読み込むときなどに指定します。fn:unparsed-text()で取得したテキストをfn:parse-json()へ適用するのと
大体同じことを行います。今回取り上げるのはfn:parse-json()fn:json-doc()についてです。

mapとarray

関係する名前空間は次に挙げるものです。

  • fn=”http://www.w3.org/2005/xpath-functions”
  • map=”http://www.w3.org/2005/xpath-functions/map”
  • array=”http://www.w3.org/2005/xpath-functions”

XPath 3.1のmap構造とarray構造は一般的なプログラミング言語におけるそれとほぼ同じもので、JSONオブジェクトをそのままの形で格納できます。
先週登場したJSONを見てみましょう。

{
    "items": [
        {
            "id": "7646893/2E3MJB9A",
            "type": "book",
            "title": "スタイルシート開発の基礎",
            "publisher": "アンテナハウス株式会社",
            "publisher-place": "Tokyo",
            "event-place": "Tokyo",
            "ISBN": "978-4-900552-23-4",
            "language": "ja",
            "author": [
                {
                    "family": "アンテナハウス株式会社",
                    "given": ""
                }
            ],
            "issued": {
                "date-parts": [
                    [
                        2016,
                        5
                    ]
                ]
            }
        }
    ]
}

このJSONを外部ファイルとして取り込むには次のように記述します。

<xsl:param name="input" >'exported-data.json'</xsl:param>
...
<xsl:variable as="map(*)" name="jsonMap" select="fn:json-doc($input)"/>
<!-- または -->
<xsl:variable as="xs:string" name="json-text" select="fn:unparsed-text($input)"/>
<xsl:variable as="map(*)" name="jsonMap" select="parse-json($json-text)"/>

XMLに変換した先週と、mapやarrayを活用する今回はこの後がまったく異なります。

次の記述で、先頭のtitleを一息で取得します。

<xsl:value-of select="map:get(array:head(map:get($jsonMap, 'items')), 'title')" />

まず、最も外側のmapから、itemsのキーを持つ要素を取得します(map:get($jsonMap, 'items'))。
キーitemsの値はarray型です。このままmap:get()を行おうとしてもできませんから、arrayの先頭を取り出すarray:head()を使用しています。ところで上の記述はパイプ演算子を使えば次のように書けます。

<xsl:value-of select="map:get($jsonMap, 'items') => array:head() => map:get('title')" />

mapのarrayでは、map:find()を利用することで指定したキーの値を配列で得ることもできます。挙動の詳細はXPath 3.1*1かXSLT 3.0*2のページを確認してください。

typeの値が何種類かに決まっていて、その種類を元にif文の判定をしたいのであれば、次のように書けます。

     <xsl:variable as="map(*)" name="item" select="map:get($jsonMap, 'items') => array:head()" />
      <xsl:if test="map:get($item,  'type') =  'book' or 'proceedings'">
      <xsl:value-of select="map:get($item, 'type')" /> <!-- book -->
      </xsl:if>

もちろんXMLの構造に変換しても同じことはできますが、より一般的なプログラミング言語に近い形で扱えています。

mapやarrayとして扱うために、JSONテキストとして記述してパースしたり、select="map{"key":value}"のような書き方をする以外に、<xsl:map><map-entry>を使うことができます。

mapの操作はエントリの追加やキー指定での削除などの他、map:merge()によるmapの統合、map:for-each()によるmap要素単位での関数適用などが可能です。arrayの方はarray:fold-left()array:fold-right()の畳み込み関数や、array:for-each()でarrayの要素ごとに関数適用などなど、XML形式を扱うよりもすっきりした構文で記述が可能な関数が用意されています。

注意しなければならないこととして、mapであるかarrayであるかを間違えるとうまく値を取り出せません。$itemnodeでもないので、直接xml-to-json()は使えません。

仕様やSaxonのドキュメントとにらめっこをしながら紹介してきたXSLT 3.0とXPath 3.1のJSONの扱いですが、誤りなどありましたらご指摘ください。

*1 https://www.w3.org/TR/xpath-31/
*2 https://www.w3.org/TR/xslt-30/


参考資料