xmlxsltxslt-3.0

transform external produced json file to xml using XSLT 3


I have been checking related entries in SO and so far no luck, as my use case seems to have some commonalities but also differences with provided solutions (like: Converting json array to xml using XSLT or Convert JSON to XML using XSLT 3.0 functions).

so hopefully somebody can help me here please.

I call the transformation like this:

java -jar SaxonHE/saxon-he-11.4.jar -s:not_used.xml -xsl:rework_map.xsl -o:report-map.xml p1="list.json"

INPUT: list.json - file produced externally and not embedded in xml, containing array of entries, like:

[
  {
    "tc_id": "A_S_0001",
    "file_name": "\\scripts\\A_S_0001.cs",
    "version": "19",
    "is_automated": true
  },
  {
    "tc_id": "A_S_0002",
    "file_name": "\\scripts\\A_S_0002.cs",
    "version": "25",
    "is_automated": false
  }
]

EXPECTED OUTPUT: something like this:

<list>
<test_case>
<tc_id>A_S_0001</tc_id>
<file_name>\\scripts\\A_S_0001.cs</file_name>
<version>19</version>
<is_automated>true</is_automated>
</test_case>
<test_case>
<tc_id>A_S_0002</tc_id>
<file_name>\\scripts\\A_S_0002.cs</file_name>
<version>25</version>
<is_automated>false</is_automated>
</test_case>
</list>

my template rework_map.xsl (not working)

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:array="http://www.w3.org/2005/xpath-functions/array"
    exclude-result-prefixes="xs math map array fn"
    version="3.0">


    <!-- OUTPUT -->
    <xsl:output method="xml" indent="yes"/>
    <!-- PARAMETERS -->
    <xsl:param name="p1"></xsl:param>    <!-- list.json -->

    <!-- VARIABLES -->
    <xsl:variable name="json-array" select="json-doc($p1)"/>

    <xsl:template match="/">
        <!-- <xsl:call-template name="common.INFO"/> -->

        <list>
            <xsl:apply-templates select="$json-array/*"/>

        </list>
    </xsl:template>

    <xsl:template match="fn:map">
        <test_case>
            <xsl:apply-templates/>
        </test_case>
    </xsl:template>

    <xsl:template match="fn:map/fn:*">
        <xsl:element name="{@key}">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Solution

  • One option with Saxon 11 is to feed the JSON files with the -json:list.json option (don't use a -s option) and write code along the lines of

    <?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 indent="yes"/>
    
      <xsl:template match=".[. instance of array(map(*))]" name="xsl:initial-template">
        <list>
          <xsl:apply-templates select="?*"/>
        </list>
      </xsl:template>
      
      <xsl:template match=".[. instance of map(*)]">
        <test_case>
          <xsl:variable name="map" select="."/>
          <xsl:iterate select="map:keys(.)">
            <xsl:element name="{.}">{$map(.)}</xsl:element>
          </xsl:iterate>
        </test_case>
      </xsl:template>
      
    </xsl:stylesheet>
    

    This directly processes the JSON as XPath 3.1 arrays/maps, the disadvantage is that XPath 3.1 maps have no order for the items in them so the result can be e.g.

    <list>
       <test_case>
          <version>19</version>
          <is_automated>true</is_automated>
          <file_name>\scripts\A_S_0001.cs</file_name>
          <tc_id>A_S_0001</tc_id>
       </test_case>
       <test_case>
          <version>25</version>
          <is_automated>false</is_automated>
          <file_name>\scripts\A_S_0002.cs</file_name>
          <tc_id>A_S_0002</tc_id>
       </test_case>
    </list>
    

    The other option is to start with -it for the initial-template and use json-to-xml and just write templates e.g.

    <?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:fn="http://www.w3.org/2005/xpath-functions"
      exclude-result-prefixes="#all"
      expand-text="yes">
    
      <xsl:output indent="yes"/>
      
      <xsl:param name="json-string" as="xs:string" select="unparsed-text('list.json')"/>
    
      <xsl:template name="xsl:initial-template">
        <list>
          <xsl:apply-templates select="json-to-xml($json-string)/*"/>
        </list>
      </xsl:template>
      
      <xsl:template match="fn:map[not(@key)]">
        <test_case>
          <xsl:apply-templates/>
        </test_case>
      </xsl:template>
      
      <xsl:template match="fn:*[@key and not(*)]">
        <xsl:element name="{@key}">{.}</xsl:element>
      </xsl:template>
      
    </xsl:stylesheet>