Try to convert Json to xml using xslt, the Json is having an array. I want only few elements of that array in my xml file.
But the value of the elements left out elements are also getting added in the xml.
Input Json:
{
"a": "v1",
"arr": [
{
"ar1": "av1",
"ar2": "av2",
"ar3": "av3",
"ar4": "av4",
"ar5": "av5",
"ar6": "av6"
}
],
"b": "v2",
"c": "v3"
}
xslt used for conversion:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="fn" expand-text="yes">
<xsl:strip-space elements="*" />
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
<xsl:param name="jsonText" />
<xsl:param name="json" select ="parse-json($jsonText)"/>
<xsl:template name="init">
<body>
<envelope>
<dataset>
<AA>{$json?a}</AA>
<xsl:apply-templates select="$json => serialize(map { 'method' : 'json'} ) => json-to-xml()"/>
</dataset>
</envelope>
</body>
</xsl:template>
<xsl:template match="fn:array[#key = 'arr']">
<xsl:iterate select="*">
<arr>
<ar1>{fn:string[#key = 'ar1']}</ar1>
<ar2>{fn:string[#key = 'ar2']}</ar2>
</arr>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
Output I got using the above xslt:
<body>
<envelope>
<dataset>
<AA>v1</AA>
<arr>
<ar1>av1</ar1>
<ar2>av2</ar2>
</arr>v1v2v3
</dataset>
</envelope>
</body>
expected output:
<body>
<envelope>
<dataset>
<AA>v1</AA>
<arr>
<ar1>av1</ar1>
<ar2>av2</ar2>
</arr>
</dataset>
</envelope>
</body>
It seems instead of using <xsl:apply-templates select="$json => serialize(map { 'method' : 'json'} ) => json-to-xml()"/> you could just use
<arr>
<ar1>{$json?arr?*?ar1}</ar1>
<ar2>{$json?arr?*?ar2}</ar2>
</arr>
Online sample here.
If you really want to go the route of using both XPath 3.1 selection into the XDM maps/arrays as well as to work with the json-to-xml conversion then I think you want to change <xsl:apply-templates select="$json => serialize(map { 'method' : 'json'} ) => json-to-xml()"/> to e.g. <xsl:apply-templates select="($json => serialize(map { 'method' : 'json'} ) => json-to-xml())//fn:array[#key = 'arr']"/> or to declare <xsl:mode on-no-match="shallow-skip"/>.
I am trying to retrieve JSON data from the HTTP endpoint and use that values in XSLT.
Need help to retrieve data from HTTP URL of JSON data and transform it.
//sample json
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
// xslt
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="*">
<person>
<xsl:variable name="title" select="json-to-xml(document('https://jsonplaceholder.typicode.com/todos/1'))/title"/>
<xsl:element name="Field">
<xsl:attribute name="name">Title</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="$title"/>
</xsl:attribute>
</xsl:element>
</person>
</xsl:template>
</xsl:stylesheet> ```
Getting error like Error: org.xml.sax.SAXParseException; systemId: https://jsonplaceholder.typicode.com/todos/1; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
To load JSON, you can use json-doc e.g. json-doc('https://jsonplaceholder.typicode.com/todos/1'). That gives you an XDM map for your sample, you can process it with XPath 3.1 e.g.
<xsl:variable name="json" select="json-doc('https://jsonplaceholder.typicode.com/todos/1')"/>
<person>
<Field name="Title" value="{$json?title}"/>
</person>
I'm trying to convert JSON to a specific XML format, all in one XSLT. (It doesn't have to be in one step, but, you know,...)
I can convert the JSON to generic XML from here: How to use XPath/XSLT fn:json-to-xml
Converting the resultant generic XML to the XML I want is then simple.
But I can't work out how to combine the XSLTs so I can do it in one step, do JSON-to-XML and then the XML transformation. I've tried with variables, include, import, but can't get it to work.
I suspect it's straightforward! It needs to be in (just) XSLT.
So, from the question linked to above, I start with JSON (in XML tags)
<root>
<data>{
"desc" : "Distances between several cities, in kilometers.",
"updated" : "2014-02-04T18:50:45",
"uptodate": true,
"author" : null,
"cities" : {
"Brussels": [
{"to": "London", "distance": 322},
{"to": "Paris", "distance": 265},
{"to": "Amsterdam", "distance": 173}
],...
and transform to
<map xmlns="http://www.w3.org/2005/xpath-functions">
<string key="desc">Distances between several cities, in kilometers.</string>
<string key="updated">2014-02-04T18:50:45</string>
<boolean key="uptodate">true</boolean>
<null key="author"/>
<map key="cities">
<array key="Brussels">
<map>
<string key="to">London</string>
<number key="distance">322</number>
</map>
<map>
<string key="to">Paris</string>
<number key="distance">265</number>
</map>...
using
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math" version="3.0">
<xsl:output indent="yes"/>
<xsl:template match="data">
<xsl:copy-of select="json-to-xml(.)"/>
</xsl:template>
</xsl:stylesheet>
Now I can apply this stylesheet to the 'intermediate' XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.w3.org/2005/xpath-functions">
<xsl:output indent="yes"/>
<xsl:template match="/">
<Distances>
<xsl:for-each select="f:map/f:map/f:array">
<Start>
<StartPoint><xsl:value-of select="#key"/></StartPoint>
<xsl:for-each select="f:map">
<Distance>
<xsl:attribute name="end"><xsl:value-of select="f:string"/></xsl:attribute>
<xsl:attribute name="value"><xsl:value-of select="f:number"/></xsl:attribute>
</Distance>
</xsl:for-each>
</Start>
</xsl:for-each>
</Distances>
</xsl:template>
</xsl:stylesheet>
and get my desired structure:
<?xml version="1.0" encoding="UTF-8"?>
<Distances xmlns:f="http://www.w3.org/2005/xpath-functions">
<Start>
<StartPoint>Brussels</StartPoint>
<Distance end="London" value="322"/>
<Distance end="Paris" value="265"/>
<Distance end="Amsterdam" value="173"/>
</Start>...
So, is it possible to combine the JSON-to-XML and the XML transformation XSLs in one?
I am guessing you want to do:
XSLT 3.0
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="f">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/root">
<Distances>
<xsl:for-each select="json-to-xml(data)/f:map/f:map/f:array">
<Start>
<StartPoint>
<xsl:value-of select="#key"/>
</StartPoint>
<xsl:for-each select="f:map">
<Distance end="{f:string}" value="{f:number}"/>
</xsl:for-each>
</Start>
</xsl:for-each>
</Distances>
</xsl:template>
</xsl:stylesheet>
Untested, because no code suitable for testing was provided.
To do it the way you were proposing, you can do
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)"/>
</xsl:template>
and then add template rules to transform the generic XML produced by json-to-xml() to your application-specific XML.
But I think the approach suggested by #michael.hor257k is probably better.
I have the following code, which I am using to transform a JSON file to another JSON.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:fcn="http://www.re.com/2018/local-functions" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs fn fcn exsl" version="3.0">
<xsl:param as="xs:string" name="json">[{
"results": [
{
"id": "5b33c2e8-8ab2-4314-82bf-e41a007c076f",
"profileId": "0f53bfe5-4ef3-4424-9ad2-ad8181007e22"
}
],
"_embedded": {
},
"paging": {
"pageNumber": 1,
"pageSize": 10,
"totalCount": 4
}
}]</xsl:param>
<xsl:output indent="yes" method="text" />
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="input-as-xml" select="json-to-xml($json)" />
<xsl:variable name="transformed-xml">
<array xmlns="http://www.w3.org/2005/xpath-functions">
<xsl:for-each select="fn:array[#key = 'results']/*">
<map>
<xsl:if test="*[#key = 'id'] != ''">
<string key="id">
<xsl:value-of select="fn:string[#key = 'id']" />
</string>
</xsl:if>
</map>
</xsl:for-each>
</array>
</xsl:variable>
<xsl:value-of select="xml-to-json($transformed-xml, map {'indent': true()})" />
</xsl:template>
</xsl:stylesheet>
I am trying to match the results object, but when I apply the stylesheet, I am getting an empty JSON output.
Some help with matching the results object correctly would be appreciated.
The input-as-xml:
<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
<map>
<array key="results">
<map>
<string key="id">5b33c2e8-8ab2-4314-82bf-e41a007c076f</string>
<string key="profileId">0f53bfe5-4ef3-4424-9ad2-ad8181007e22</string>
</map>
</array>
<map key="_embedded"/>
<map key="paging">
<number key="pageNumber">1</number>
<number key="pageSize">10</number>
<number key="totalCount">4</number>
</map>
</map>
</array>
For starters, I think the xsl:for-each should be something like <xsl:for-each select="$input-as-xml/fn:array[#key = 'results']/*">, so you're selecting within the right context.
But I don't think that's quite enough: The XML that comes from the conversion will contain an <fn:array> whose first child will be an <fn:map> which will have an entry of the form <fn:array key="results">, so you need to select deeper into the structure.
It's probably a good idea to print out the value of $input-as-xml (in indented form) for ease of diagnostics.
I have some code (from GeoNetwork) which needs to convert Geography Markup Language (in XML) into GeoJSON. I'm currently trying to add functionality to read a polygon formed from a posList, but I'm having a hard time conceptualizing/drafting out what I would need to do.
The 'input' is basically a string consisting of a bunch of coordinates. So it might look something like this
<gml:LinearRing gml:id="p21" srsName="http://www.opengis.net/def/crs/EPSG/0/4326">
<gml:posList srsDimension="2">45.67 88.56 55.56 88.56 55.56 89.44 45.67 89.44</gml:posList>
</gml:LinearRing >
(Borrowed from Wikipedia's sample).
I can chunk this up in XSLT using something like
<xsl:variable name="temp" as="xs:string*" select="tokenize(gml:LinearRing/gml:posList))" '\s'/>
which should give me Temp =
('45.67', '88.56', '55.56', '88.56', '55.56', '89.44', '45.67', '89.44')
Problem 1: GeoJSON wants everything in WGS 84 (EPSG 4326) and in the (long, lat) order - but strict adherence to WGS 84 rules (which I expect gml follows) means the coordinates are in (lat, long) order - so the list needs to be re-ordered. (I think - this is very confusing to me still)
Problem 2: GeoJSON wants coordinate pairs, but I just have a list of coordinates.
My current idea is to do something like this:
<geom>
<xsl:text>{"type": "Polygon",</xsl:text>
<xsl:text>"coordinates": [
[</xsl:text>
<xsl:variable name="temp" as="xs:string*" select="tokenize(gml:LinearRing/gml:posList))" '\s'/>
<xsl:for-each select="$temp">
<xsl:if test="position() mod 2 = 0">
<xsl:value-of select="concat('[', $saved, ', ', ., ']')" separator=","/>
</xsl:if>
<xsl:variable name="saved" value="."/>
</xsl:for-each>
<xsl:text>]
]
}</xsl:text>
</geom>
but I'm unsure whether XSL will let me continuously write a variable like this, and whether there might be a better/more-efficient solution to the problem. (I have a lot of experience in MATLAB, where I would solve this quickly, if not efficiently, using for-loops)
Ideally I would get output similar to
<geom>
{"type": "Polygon",
"coordinates": [
[
[88.56, 45.67],
[88.56, 55.56],
[89.44, 55.56],
[89.44, 45.67]
]
]
}
</geom>
(There's a whole other can-of-worms to be had with figuring out whether the polygon is right or left handed, I think)
This stylesheet with any input (not used)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:my="dummy"
exclude-result-prefixes="my">
<xsl:template match="/">
<xsl:sequence select="
my:reverseByTuple(
('45.67', '88.56', '55.56', '88.56', '55.56', '89.44', '45.67', '89.44')
)"/>
</xsl:template>
<xsl:function name="my:reverseByTuple">
<xsl:param name="items"/>
<xsl:sequence
select="if (empty($items))
then ()
else ($items[2], $items[1], my:reverseByTuple($items[position()>2]))"
/>
</xsl:function>
</xsl:stylesheet>
Output
88.56 45.67 88.56 55.56 89.44 55.56 89.44 45.67
I really don't understand why you are serializating the JSON instead of ussing a well documented library like the functions in XSLT 3.0... But just for fun, this stylesheet
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:my="dummy"
exclude-result-prefixes="my">
<xsl:template match="/">
<xsl:value-of
select="
my:encloseWithBracket(
my:reverseByTupleEncloseWithBracket(
('45.67', '88.56', '55.56', '88.56', '55.56', '89.44', '45.67', '89.44')
)
)"/>
</xsl:template>
<xsl:function name="my:reverseByTupleEncloseWithBracket">
<xsl:param name="items"/>
<xsl:sequence
select="if (empty($items))
then ()
else (my:encloseWithBracket(($items[2],$items[1])),
my:reverseByTupleEncloseWithBracket($items[position()>2]) )"
/>
</xsl:function>
<xsl:function name="my:encloseWithBracket">
<xsl:param name="items"/>
<xsl:value-of select="concat('[',string-join($items,','),']')"/>
</xsl:function>
</xsl:stylesheet>
Output
[[88.56,45.67],[88.56,55.56],[89.44,55.56],[89.44,45.67]]
XSLT 3 with XPath 3.1 support can represent JSON as maps/arrays and serialize them as JSON so you could compute an XPath map from your sequence of coordinates:
serialize(
map {
'type' : 'polygon',
'coordinates' : array {
let $seq := tokenize(gml:LinearRing/gml:posList, '\s+')
return $seq[position() mod 2 = 0]![., let $p := position() return $seq[($p - 1) * 2 + 1]]
}
},
map { 'method' : 'json', 'indent' : true() }
)
https://xsltfiddle.liberty-development.net/gWvjQfu/1
To get JSON numbers in the arrays use let $seq := tokenize(., '\s+')!number() instead of let $seq := tokenize(gml:LinearRing/gml:posList, '\s+').
If you have access to an XSLT 3 processor like Saxon PE or EE or Altova supporting higher-order functions you could reduce that to
serialize(
map {
'type': 'polygon',
'coordinates': array {
let $seq := tokenize(gml:LinearRing/gml:posList, '\s+'),
$odd := $seq[position() mod 2 = 1],
$even := $seq[position() mod 2 = 0]
return
for-each-pair($odd, $even, function ($c1, $c2) {
[$c2, $c1]
})
}
},
map {
'method': 'json',
'indent': true()
}
)
You can use the following XSLT-2.0 stylesheet to get your desired outcome. It makes use of the xsl:analyze-string function to separate the values in 2-tuples. The template includes error handling and removes the target namespace gml from the output with exclude-result-prefixes="gml". You may have to adjust the XML paths of the template and the xsl:analyze-string expression. But I guess that you can handle this.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:gml="http://www.opengis.net/def/crs/EPSG/0/4326" exclude-result-prefixes="gml">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<geom><xsl:text>
{"type": "Polygon",
"coordinates": [
[
</xsl:text>
<xsl:analyze-string select="gml:LinearRing/gml:posList"
regex="\s*(\d\d)\.(\d\d)\s+(\d\d)\.(\d\d)\s*">
<xsl:matching-substring>
<xsl:value-of select="concat(' [',regex-group(3),'.', regex-group(4),', ',regex-group(1),'.', regex-group(2),']
')"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:message terminate="yes">=============================
=== ERROR: Invalid input! ===
=============================</xsl:message>
</xsl:non-matching-substring>
</xsl:analyze-string>
<xsl:text> ]
]
}
</xsl:text>
</geom>
</xsl:template>
</xsl:stylesheet>
Its output is:
<geom>
{"type": "Polygon",
"coordinates": [
[
[88.56, 45.67]
[88.56, 55.56]
[89.44, 55.56]
[89.44, 45.67]
]
]
}
</geom>%