Reputation: 375
I have an xml that needs to be converted to JSON and am using XSLT to transform it. In one of the element am not required to pass pair name, only values. See below
My XML:
<?xml version="1.0" encoding="UTF-8"?>
<map xmlns="http://www.w3.org/2005/xpath-functions">
<array key="cars">
<map>
<string key="doors">4</string>
<string key="price">6L</string>
</map>
<map>
<string key="doors">5</string>
<string key="price">13L</string>
</map>
</array>
</map>
Using XSL: https://xsltfiddle.liberty-development.net/b4GWVd
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:value-of select="translate(xml-to-json(., map { 'indent' : true() }),'Ø', 'ø')"/>
</xsl:template>
</xsl:stylesheet>
Gives output: Which is correct
{ "cars" :
[
{ "doors" : "4",
"price" : "6L" },
{ "doors" : "5",
"price" : "13L" } ] }
But then I would like to get a JSON with below structure, Without Pair Name(Reason: Its the structure required to be submitted to an API)
{ "cars" :
[
{ "4",
"6L" },
{ "5",
"13L" } ] }
Upvotes: 0
Views: 1111
Reputation: 167716
As the XSLT 3.0 spec also provides an implementation of xml-to-json
as an XSLT 3.0 package you could use that code and override templates where you want to eliminate the "keys" of JSON objects/XDM map items:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:j="http://www.w3.org/2013/XSLT/xml-to-json"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
version="3.0">
<xsl:use-package name="http://www.w3.org/2013/XSLT/xml-to-json" package-version="1.0">
<xsl:override>
<!-- Template rule for fn:map elements, representing JSON objects , overridden for content of cars array -->
<xsl:template match="fn:array[@key = 'cars']/fn:map" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<!--<xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
<xsl:text> : </xsl:text>-->
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:array[@key = 'cars']/fn:map/*" mode="no-indent">
<xsl:value-of>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<!--<xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
<xsl:text>:</xsl:text>-->
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
</xsl:override>
</xsl:use-package>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select="j:xml-to-json(., map { 'indent' : true() })"/>
</xsl:template>
</xsl:stylesheet>
The above can be run with Saxon 10 HE and higher or Saxon 9.8 PE or EE and later by using the command line -s:your-xml.xml -xsl:above-xslt-xsl -lib:w3c-xml-to-json.xsl
where the last option refers to the file https://www.w3.org/TR/xslt-30/xml-to-json.xsl linked from the XSLT 3 spec with one path/errata added, namely the declaration of the default mode with <xsl:mode name="j:xml-to-json"/>
so it would look like
<xsl:package
name="http://www.w3.org/2013/XSLT/xml-to-json"
package-version="1.0"
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:j="http://www.w3.org/2013/XSLT/xml-to-json"
exclude-result-prefixes="xs fn j" default-mode="j:xml-to-json" version="3.0">
<xsl:variable name="quot" visibility="private">"</xsl:variable>
<xsl:param name="indent-spaces" select="2"/>
<!-- The static parameter STREAMABLE controls whether the stylesheet is declared as streamable -->
<xsl:param name="STREAMABLE" static="yes" as="xs:boolean" select="true()"/>
<!-- fix for https://github.com/w3c/qtspecs/blob/master/errata/xslt-30/errata.xml#L1154 -->
<xsl:mode name="j:xml-to-json"/>
<xsl:mode name="indent" _streamable="{$STREAMABLE}" visibility="public"/>
<xsl:mode name="no-indent" _streamable="{$STREAMABLE}" visibility="public"/>
<xsl:mode name="key-attribute" streamable="false" on-no-match="fail" visibility="public"/>
<!-- The static parameter VALIDATE controls whether the input, if untyped, should be validated -->
<xsl:param name="VALIDATE" static="yes" as="xs:boolean" select="false()"/>
<xsl:import-schema namespace="http://www.w3.org/2005/xpath-functions" use-when="$VALIDATE"/>
<!-- Entry point: function to convert a supplied XML node to a JSON string -->
<xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
<xsl:param name="input" as="node()"/>
<xsl:sequence select="j:xml-to-json($input, map{})"/>
</xsl:function>
<!-- Entry point: function to convert a supplied XML node to a JSON string, supplying options -->
<xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
<xsl:param name="input" as="node()"/>
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="input" as="node()" use-when="$VALIDATE">
<xsl:copy-of select="$input" validation="strict"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$options('indent') eq true()">
<xsl:apply-templates select="$input" mode="indent">
<xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
select="$options('fallback')" tunnel="yes"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$input" mode="no-indent">
<xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
select="$options('fallback')" tunnel="yes"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<!-- A document node is ignored -->
<xsl:template match="/" mode="indent no-indent">
<xsl:apply-templates mode="#current"/>
</xsl:template>
<!-- Template rule for fn:map elements, representing JSON objects -->
<xsl:template match="fn:map" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
<xsl:text> : </xsl:text>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:map" mode="no-indent">
<xsl:value-of>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
<xsl:text>:</xsl:text>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<!-- Template rule for fn:array elements, representing JSON arrays -->
<xsl:template match="fn:array" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>[</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:array" mode="no-indent">
<xsl:value-of>
<xsl:text>[</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:value-of>
</xsl:template>
<!-- Template rule for fn:string elements in which
special characters are already escaped -->
<xsl:template match="fn:string[@escaped='true']" mode="indent no-indent">
<xsl:sequence select="concat($quot, ., $quot)"/>
</xsl:template>
<!-- Template rule for fn:string elements in which
special characters need to be escaped -->
<xsl:template match="fn:string[not(@escaped='true')]" mode="indent no-indent">
<xsl:sequence select="concat($quot, j:escape(.), $quot)"/>
</xsl:template>
<!-- Template rule for fn:boolean elements -->
<xsl:template match="fn:boolean" mode="indent no-indent">
<xsl:sequence select="xs:string(xs:boolean(.))"/>
</xsl:template>
<!-- Template rule for fn:number elements -->
<xsl:template match="fn:number" mode="indent no-indent">
<xsl:value-of select="xs:string(xs:double(.))"/>
</xsl:template>
<!-- Template rule for JSON null elements -->
<xsl:template match="fn:null" mode="indent no-indent">
<xsl:text>null</xsl:text>
</xsl:template>
<!-- Template rule matching a key within a map where
special characters in the key are already escaped -->
<xsl:template match="fn:*[@key-escaped='true']/@key" mode="key-attribute">
<xsl:value-of select="concat($quot, ., $quot)"/>
</xsl:template>
<!-- Template rule matching a key within a map where
special characters in the key need to be escaped -->
<xsl:template match="fn:*[not(@key-escaped='true')]/@key" mode="key-attribute">
<xsl:value-of select="concat($quot, j:escape(.), $quot)"/>
</xsl:template>
<!-- Template matching "invalid" elements -->
<xsl:template match="*" mode="indent no-indent">
<xsl:param name="fallback" as="(function(element()) as xs:string)?"
tunnel="yes" required="yes"/>
<xsl:choose>
<xsl:when test="exists($fallback)">
<xsl:value-of select="$fallback(snapshot(.))"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">>Inc</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Template rule matching (and discarding) whitespace text nodes in the XML -->
<xsl:template match="text()[not(normalize-space())]" mode="indent no-indent"/>
<!-- Function to escape special characters -->
<xsl:function name="j:escape" as="xs:string" visibility="final">
<xsl:param name="in" as="xs:string"/>
<xsl:value-of>
<xsl:for-each select="string-to-codepoints($in)">
<xsl:choose>
<xsl:when test=". gt 65535">
<xsl:value-of select="concat('\u', j:hex4((. - 65536) idiv 1024 + 55296))"/>
<xsl:value-of select="concat('\u', j:hex4((. - 65536) mod 1024 + 56320))"/>
</xsl:when>
<xsl:when test=". = 34">\"</xsl:when>
<xsl:when test=". = 92">\\</xsl:when>
<xsl:when test=". = 08">\b</xsl:when>
<xsl:when test=". = 09">\t</xsl:when>
<xsl:when test=". = 10">\n</xsl:when>
<xsl:when test=". = 12">\f</xsl:when>
<xsl:when test=". = 13">\r</xsl:when>
<xsl:when test=". lt 32 or (. ge 127 and . le 160)">
<xsl:value-of select="concat('\u', j:hex4(.))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="codepoints-to-string(.)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:value-of>
</xsl:function>
<!-- Function to convert a UTF16 codepoint into a string of four hex digits -->
<xsl:function name="j:hex4" as="xs:string" visibility="final">
<xsl:param name="ch" as="xs:integer"/>
<xsl:variable name="hex" select="'0123456789abcdef'"/>
<xsl:value-of>
<xsl:value-of select="substring($hex, $ch idiv 4096 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch idiv 256 mod 16 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch idiv 16 mod 16 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch mod 16 + 1, 1)"/>
</xsl:value-of>
</xsl:function>
<!-- Function to output whitespace indentation based on
the depth of the node supplied as a parameter -->
<xsl:function name="j:indent" as="text()" visibility="public">
<xsl:param name="depth" as="xs:integer"/>
<xsl:value-of select="'
', string-join((1 to ($depth + 1) * $indent-spaces) ! ' ', '')"/>
</xsl:function>
</xsl:package>
To run code with Java code you basically need to use an XsltCompiler
created from Processor
e.g.
Processor processor = new Processor(true);
XsltCompiler xsltCompiler = processor.newXsltCompiler();
XsltPackage xmlToJsonPackage = xsltCompiler.compilePackage(new StreamSource("w3c-xml-to-json.xsl"));
xsltCompiler.importPackage(xmlToJsonPackage);
XsltExecutable xsltExecutable = xsltCompiler.compile(new StreamSource("sheet.xsl"));
Xslt30Transformer xslt30Transformer = xsltExecutable.load30();
// now run stylesheet with e.g. transform() or applyTemplates()
xslt30Transformer.transform(new StreamSource("input.xml"), xslt30Transformer.newSerializer(System.out));
Upvotes: 1