wknauf
wknauf

Reputation: 400

How to handle JSON input with Saxon XSLT

Recent Saxon releases contain a command line argument "-json:myfile.json" to input a JSON file.

But how do I implement the XSLT to parse this JSON? I did not find any doc which handles this directly (without making use of "json-to-xml" or similar).

I only found this: how to convert json to xml with saxonjs?

But this does not help me, because my json starts with an array:

[
  {
    "eid": "2122.5",
    "ecat": "show",
    "day": "1629410400",
    "spcat": "Bühne",
    "time": "19:30",
    "text": "Welle",
    "remarks": "",
    "location": ""
  },
  {
    "eid": "2122.6",
    "ecat": "show",
    "day": "1629496800",
    "spcat": "Bühne",
    "time": "19:30",
    "text": "Welle",
    "remarks": "",
    "location": ""
  }
]

By writing an intermediate XSLT using the function "json-to-xml", I can convert this JSON to xml that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
   <map>
      <string key="eid">2122.5</string>
      <string key="ecat">show</string>
      <string key="day">1629410400</string>
      <string key="spcat">Bühne</string>
      <string key="time">19:30</string>
      <string key="text">Welle</string>
      <string key="remarks"/>
      <string key="location"/>
   </map>
   <map>
      <string key="eid">2122.6</string>
      <string key="ecat">show</string>
      <string key="day">1629496800</string>
      <string key="spcat">Bühne</string>
      <string key="time">19:30</string>
      <string key="text">Welle</string>
      <string key="remarks"/>
      <string key="location"/>
   </map>
</array>

How can I create a template that matches the root item, and how can I call "apply-templates" to trigger another template that handles the items?

Upvotes: 0

Views: 940

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167706

The JSON you have shown is an array so it will be mapped to the XPath/XSLT XDM type array(*), or, in your case, array(map(xs:string, xs:string)). To match on that use e.g. <xsl:template match=".[. instance of array(*)]">..</xsl:template> or e.g. <xsl:template match=".[. instance of array(map(xs:string, xs:string))]">..</xsl:template>.

More complete example would be online at the fiddle, doing:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match=".[. instance of array(map(xs:string, xs:string))]">
    <items>
      <xsl:apply-templates select="?*"/>
    </items>
  </xsl:template>
  
  <xsl:template match=".[. instance of map(xs:string, xs:string)]">
    <xsl:variable name="map" select="."/>
    <item>
      <xsl:iterate select="map:keys(.)">
        <value key="{.}">{$map(.)}</value>
      </xsl:iterate>      
    </item>
  </xsl:template>
  
</xsl:stylesheet>

As for grouping:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match=".[. instance of array(map(xs:string, xs:string))]">
    <items>
      <xsl:for-each-group select="?*" group-by="?text">
        <group name="{current-grouping-key()}">
          <xsl:apply-templates select="current-group()"/>
        </group>
      </xsl:for-each-group>
    </items>
  </xsl:template>
  
  <xsl:template match=".[. instance of map(xs:string, xs:string)]">
    <xsl:variable name="map" select="."/>
    <item>
      <xsl:iterate select="map:keys(.)[not(. = current-grouping-key())]">
        <value key="{.}">{$map(.)}</value>
      </xsl:iterate>      
    </item>
  </xsl:template>
  
</xsl:stylesheet>

Upvotes: 1

Related Questions