XML to JSON using XSLT on selected nodes - json

I am in middle of my development, I have an XML response for which i have to convert to JSON, Using data mapper i am able to convert it, But i am facing issue when converting some of the xml nodes, all
<avalues>
are not showing up in json.
For Eg my xml response is
<data>
<avalue1>1</avalue1>
<avalue2>2</avalue2>
<avalue3>3</avalue3>
<data>
and i want to convert it as below
{
"cn":
{
"avalue1": "1",
"avalue2": "2",
"avalue3": "3"
},
}
i am using xslt
<xsl:for-each select="data/avalue"> <avalue> <xsl:value-of select="(.,'string')[. ne ''][1]"/> </avalue> </xsl:for-each>
thanks--

I suppose you XML can be not only with one data block, so for visible example I took your part into XML as below (edited):
<?xml version="1.0" encoding="UTF-8"?>
<cn>
<data>
<avalue1>1</avalue1>
<avalue2>2</avalue2>
<avalue3>3</avalue3>
</data>
<data>
<avalue4>4</avalue4>
<avalue5>5</avalue5>
<avalue6>6</avalue6>
</data>
</cn>
So, in XSL you can firstly create your string per data block and then combine them all as below (edited):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:variable name="output">
<xsl:for-each select="/cn/data">
<xsl:variable name="avalues">
<xsl:for-each select="*">
<!--create string per data as e.g. "avalue1": "1", "avalue2": "2", "avalue3": "3" -->
<xsl:value-of select="concat('"', name(), '"',': ', '"', ., '"', ', ')"/>
</xsl:for-each>
</xsl:variable>
<!--combine per each data block-->
<xsl:value-of select="concat('{ ', substring($avalues, 0, string-length($avalues)-1), ' },')"/>
</xsl:for-each>
</xsl:variable>
<!--perform final result with required format-->
<xsl:value-of select="concat('{', '"', 'cn', '"', ':[', substring($output, 1, string-length($output)-1), ']}')"/>
</xsl:template>
</xsl:stylesheet>
Result (edited) will be as expected (in final value-of you can add your own format - spaces, signs etc.):
{"cn":[
{ "avalue1": "1", "avalue2": "2", "avalue3": "3" },
{ "avalue4": "4", "avalue5": "5", "avalue6": "6" }
]}

Related

Parsing nested JSON to CSV

I am trying to parse nested JSON to CSV, using XSLT transformation.
In this particular case each child object counting from "datasheet", e.g. "result-sheet" and "balance-sheet", should end up in one CSV file (output) each. Currently I am however just elaborating getting out "result-sheet" only.
I noticed that the content of arrays are getting merged togehter.
Data:
<data>
{
"datasheets": {
"result-sheet": {"bank": [1,3], "credit": [2,6]},
"balance-sheet": {"loans": [4,5], "inventory": [9,0]}
}
}
</data>
XSL:
<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"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
>
<xsl:output method="text" indent="yes"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)"/>
</xsl:template>
<xsl:template match="*">
<h2>Result sheet</h2>
<xsl:text>
</xsl:text>
<xsl:value-of select="*/(string-join(head(*)/*/#key, ','), *!string-join(*, ','))" separator="
"/>
</xsl:template>
</xsl:stylesheet>
Result:
Result sheet
bank,credit
13,26
45,90
Wanted result:
bank,credit
1, 2,
3, 6
I don't quite understand which data you want to have in each line, the following templates creates a line using for-each-pair on each pair of fn:number elements in the two fn:array children of the fn:map with the #key being result-sheet:
<xsl:template match="*:map[#key = 'result-sheet']">
<xsl:value-of select="for-each-pair(*:array[1]/*:number, *:array[2]/*:number, function($n1, $n2) { $n1 || ', ' || $n2 })"
separator="
"/>
</xsl:template>

How to convert single child xml element to Json Array using XSLT

I am using a generic xslt(http://www.bizcoder.com/convert-xml-to-json-using-xslt) to convert xml into json which works just fine when there are multiple array elements in request and now I want to convert a particular xml element even tough it has single child element.For example:
Sample XML
<messages>
<message>
<to>Karim</to>
<from>Tom</from>
<heading>Reminder</heading>
<body>Please check your email !</body>
</message>
</messages>
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Info id="10485824">
<Data tipus="11" megnevezes="APEH hátralék (rendezetlen)">
<Value num="1" subtype="xbool">false</Value>
</Data>
</Info>
</Response>
Sample JSON:
{
"messages": {
"message": [{
"to": "Karim",
"from": "Tom",
"heading": "Reminder",
"body": "Please check your email !"
}]
}
}
Is there any we can add in the xslt to filter only this element to return as json array always?
Change the condition to create the array to
<!-- Object Properties -->
<xsl:template name="Properties">
<xsl:variable name="childName" select="name(*[1])"/>
<xsl:choose>
<xsl:when test="not(*|#*)">"<xsl:value-of select="."/>"</xsl:when>
<xsl:when test="$childName = 'message' or count(*[name()=$childName]) > 1">{ "<xsl:value-of select="$childName"/>" :[<xsl:apply-templates select="*" mode="ArrayElement"/>] }</xsl:when>
<xsl:otherwise>{
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
}</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>

XSLT 3.0 iterate over JSON array wrapped in XML

I am processing various XML files with XSLT. In one XML I found a wrapped JSON list:
<list><![CDATA[[
{
"title": "Title 1",
"value": "Value 1",
"order": 1
},
{
"title": "Title 2",
"value": "Value 2",
"order": 2
}
]]]>
</list>
My problem is that I need to iterate over the list. For example:
<xsl:variable name="listVar">
<!-- do something with list -->
</xsl:variable>
<xsl:for-each select="$listVar">
<!-- do something with objects in list e.g. -->
<xsl:value-of select="title"/>
<xsl:value-of select="value"/>
</xsl:for-each>
How to do this with XSLT? I use XSLT 3.0 and Saxon engine, version 9.8 HE.
Considered solutions:
1.
Use parse-json function:
But then I cannot iterate over the result because of XPathException: "Required item type of the context item for the child axis is node(); supplied value (.) has item type array(function(*))" or "Maps cannot be atomized". I found that there are
functions that probably I should take into account like map:get, map:entry, but I've failed to use them in my case so far.
2.
Addidiotnal transform before the one mentioned above:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:output method="xml" encoding="UTF-8" indent="no"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="list">
<list>
<xsl:copy-of select="json-to-xml(.)"/>
</list>
</xsl:template>
</xsl:stylesheet>
And then:
<xsl:variable name="listVar" select="list/array/map"/>
But it does not work - probably due to added namespace
<list>
<array xmlns="http://www.w3.org/2005/xpath-functions">
<map>
...
Your JSON structure when parsed with parse-json gives you on the XSLT/XPath side an array of maps and the most straightforward way to process the individual array items is with the ?* lookup operator, then you can use for-each or even apply-templates:
<?xml version="1.0" encoding="UTF-8"?>
<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:template match="list">
<xsl:apply-templates select="parse-json(.)?*"/>
</xsl:template>
<xsl:template match=".[. instance of map(xs:string, item())]">
<xsl:value-of select="?title, ?value"/>
</xsl:template>
</xsl:stylesheet>
where to access the map values you can again use ?foo as shown above.
As for working with the XML returned by json-to-xml, it returns elements in the XPath function namespace so to select them, as with any other elements in a namespace you need to make sure you set up a namespace with e.g. xpath-default-namespace for the section you want to process elements from that namespace or you can use the namespace wild card e.g. *:array/*:map.

Need to provide separate output for empty element tag and content element tag of same element using XSLT

I'm having the "text" tag for separate parent tag, in that some times for that text tag content will be empty, that time i want that tag with separate output and content element "text" needs to be in separate tags:
My Input XML is:
<introText>
<text/>
</introText>
<directionText>
<text>CLICK ON EACH CATEGORY TO GET STARTED, AND WHEN YOU ARE FINISHED, EVALUATE YOUR LISTS. WHICH LIST IS LONGER?</text>
</directionText>
XSL I used as:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:json="http://json.org/" xmlns:mf="http://example.com/mf" exclude-result-prefixes="#all">
<xsl:template match="introText">
"introText": {
<xsl:apply-templates/>
},
</xsl:template>
<xsl:template match="directionText">
"directionText": {
<xsl:apply-templates/>
}
</xsl:template>
<xsl:param name="length" as="xs:integer" select="100"/>
<xsl:param name="pattern" as="xs:string" select="concat('((.{1,', $length, '})( |$))')"/>
<xsl:param name="sep" as="xs:string" select="' +
'"/>
<xsl:function name="mf:break" as="xs:string">
<xsl:param name="input" as="xs:string"/>
<xsl:variable name="result">
<xsl:analyze-string select="$input" regex="{$pattern}">
<xsl:matching-substring>
<xsl:value-of select="concat('"', regex-group(2), ' ', '"')"/>
<xsl:if test="position() ne last()">
<xsl:value-of select="$sep"/>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:sequence select="$result"/>
</xsl:function>
<xsl:template match="text">
"text": <xsl:sequence select="mf:break(normalize-space(string-join(node()/serialize(., $ser-params), '')))"/>,
</xsl:template>
</xsl:stylesheet>
My getting the output as:
introText: {
"text": ""
},
directionText: {
"text": "CLICK ON EACH CATEGORY TO GET STARTED, AND WHEN YOU ARE" +
"FINISHED, EVALUATE YOUR LISTS. WHICH LIST IS LONGER?",
}
But i want the empty element "text" have to be like below output file
Expected Output file:
introText: {
"text":' "" '
},
directionText: {
"text": "CLICK ON EACH CATEGORY TO GET STARTED, AND WHEN YOU ARE" +
"FINISHED, EVALUATE YOUR LISTS. WHICH LIST IS LONGER?",
}
The extra single quote need to cover that double quotes if that text is empty, Likewise lot of "text" tag coming in article, So i want to write the XSL for "text" template alone. Thanks in advance
If you want to handle an empty text element separately, simply add a new template to match such an element, like so:
<xsl:template match="text[not(normalize-space())]">
"text": ' "" ',
</xsl:template>
The normalize-space() will mean it will also pick up text nodes that contain nothing but whitespace.

rest extension will not transform json

I have a MarkLogic Rest extension which needs to transform JSON into OBI objects. For starters I created an XML input and created transforms for it, therse work.
Now the real data is slightly different and in JSON, so I need to transform the JSON to XML. I have had an example xsl transform that I cannot seem to get to work...
The JSON doc that I want to load via the rest extension:
{
"wifi_raw": [
{
"id": "4354279",
"hostname": "rb-0046",
"mac": "00:0C:43:00:08:F4",
"firstseen": "2015-08-12 13:54:50",
"lastseen": "2015-08-12 13:54:50",
"rssi": "-1",
"packets": "1",
"bssid": "24:A4:3C:53:19:62",
"probes": "",
"processed": "0"
},
{
"id": "4354257",
"hostname": "rb-0046",
"mac": "00:0E:58:BC:E9:03",
"firstseen": "2015-08-12 13:48:45",
"lastseen": "2015-08-12 13:52:10",
"rssi": "-58",
"packets": "3",
"bssid": "B8:E9:37:17:DA:EF",
"probes": "sonos_hbbzdjrspta2htbcqeb0gcjouc",
"processed": "0"
},
{
"id": "4354273",
"hostname": "rb-0046",
"mac": "00:0E:58:BC:E9:03",
"firstseen": "2015-08-12 13:48:45",
"lastseen": "2015-08-12 13:54:32",
"rssi": "-61",
"packets": "4",
"bssid": "B8:E9:37:17:DA:EF",
"probes": "sonos_hbbzdjrspta2htbcqeb0gcjouc",
"processed": "0"
}
]
}
On ingest I want to transform this to OBI objects which are all defined and work for the equivalent XML input doc...
If I do
json:transform-from-json($source)
In the ingest extension the JSON transforms to this XML:
<?xml version="1.0"?>
<json xmlns="http://marklogic.com/xdmp/json/basic" type="object">
<wifi__raw type="array">
<json type="object">
<id type="string">4354279</id>
<hostname type="string">rb-0046</hostname>
<mac type="string">00:0C:43:00:08:F4</mac>
<firstseen type="string">2015-08-12 13:54:50</firstseen>
<lastseen type="string">2015-08-12 13:54:50</lastseen>
<rssi type="string">-1</rssi>
<packets type="string">1</packets>
<bssid type="string">24:A4:3C:53:19:62</bssid>
<probes type="string"/>
<processed type="string">0</processed>
</json>
<json type="object">
<id type="string">4354257</id>
<hostname type="string">rb-0046</hostname>
<mac type="string">00:0E:58:BC:E9:03</mac>
<firstseen type="string">2015-08-12 13:48:45</firstseen>
<lastseen type="string">2015-08-12 13:52:10</lastseen>
<rssi type="string">-58</rssi>
<packets type="string">3</packets>
<bssid type="string">B8:E9:37:17:DA:EF</bssid>
<probes type="string">sonos_hbbzdjrspta2htbcqeb0gcjouc</probes>
<processed type="string">0</processed>
</json>
<json type="object">
<id type="string">4354273</id>
<hostname type="string">rb-0046</hostname>
<mac type="string">00:0E:58:BC:E9:03</mac>
<firstseen type="string">2015-08-12 13:48:45</firstseen>
<lastseen type="string">2015-08-12 13:54:32</lastseen>
<rssi type="string">-61</rssi>
<packets type="string">4</packets>
<bssid type="string">B8:E9:37:17:DA:EF</bssid>
<probes type="string">sonos_hbbzdjrspta2htbcqeb0gcjouc</probes>
<processed type="string">0</processed>
</json>
</wifi__raw>
</json>
My minimum xsl transform now is this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xdmp="http://marklogic.com/xdmp"
xmlns:json="http://marklogic.com/xdmp/json"
xmlns:basic="http://marklogic.com/xdmp/json/basic"
xmlns:map="http://marklogic.com/xdmp/map"
xmlns:obj="http://marklogic.com/solutions/obi/object"
extension-element-prefixes="xdmp">
<xdmp:import-module namespace="http://marklogic.com/xdmp/json" href="/MarkLogic/json/json.xqy"/>
<xsl:param name="params" as="map:map"/>
<xsl:variable name="ingest-time" select="(map:get($params, 'ingest-time'), 'false')[1] = 'true'"/>
<xsl:template match="/text()[empty(../*)]">
<xsl:apply-templates select="json:transform-from-json(.)"/>
</xsl:template>
<xsl:template match="basic:json">
<results>
<objects>
<!-- test -->
<xsl:text>example</xsl:text>
</objects>
<links>
<!-- links go here-->
<xsl:text>example</xsl:text>
</links>
</results>
</xsl:template>
</xsl:stylesheet>
I get NO results back from the
let $result := eput:apply-document-transform(fn:concat($dataset, '-transform'), $params, $context, $source)/element()
In the ingest extension.
As I understood this the match "match="/text()[empty(../*)]">" would be true for the JSON document node, so the same transform-from-json would generate the XML version of the original JSON doc also posted here above so I could at least find the root node with "match="basic:json""
What am I missing here?
EDIT WORKING SOLUTION
This is basically what worked. First we made sure the new JSON object-node() is matched, then you have to be really carefull with the matching XSL templates. I had two templates matched for "basic:json" witch resulted in only the last to be executed...
Now the root is matched with
<xsl:template match="/basic:json">
And the one inside the array is matched with ANY child of wifi__raw like
<xsl:template match="basic:wifi__raw/*">
The complete XSL now looks like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xdmp="http://marklogic.com/xdmp"
xmlns:json="http://marklogic.com/xdmp/json"
xmlns:basic="http://marklogic.com/xdmp/json/basic"
xmlns:map="http://marklogic.com/xdmp/map"
xmlns:obj="http://marklogic.com/solutions/obi/object"
extension-element-prefixes="xdmp">
<xdmp:import-module namespace="http://marklogic.com/xdmp/json" href="/MarkLogic/json/json.xqy"/>
<xsl:param name="params" as="map:map"/>
<xsl:variable name="ingest-time" select="(map:get($params, 'ingest-time'), 'false')[1] = 'true'"/>
<!-- voor MarkLogic 7 -->
<xsl:template match="/text()[empty(../*)]">
<xsl:apply-templates select="json:transform-from-json(.)"/>
</xsl:template>
<!-- voor MarkLogic 8 -->
<xsl:template match="/node()[not(self::*)]">
<xsl:apply-templates select="json:transform-from-json(.)"/>
</xsl:template>
<xsl:template match="/basic:json">
<xsl:apply-templates select="basic:wifi__raw"/>
</xsl:template>
<xsl:template match="basic:wifi__raw">
<xsl:variable name="objects" as="element()*">
<xsl:apply-templates select="*" />
</xsl:variable>
<results>
<objects>
<xsl:sequence select="$objects" />
</objects>
</results>
</xsl:template>
<xsl:template match="basic:wifi__raw/*">
<foundId>
<xsl:value-of select="basic:id" />
</foundId>
</xsl:template>
</xsl:stylesheet>
This is caused by MarkLogic 8 handling JSON datatype differently from MarkLogic 7 and before. In MarkLogic 8, JSON data gets processed into object-node() data, not into text(). Unfortunately, you cannot explicitly test for object-node() in XSLT, but there are a few ways to work around that. Change the match for text() like this:
<xsl:template match="/node()[not(self::*)]">
<xsl:apply-templates select="json:transform-from-json(.)"/>
</xsl:template>
This should work in both MarkLogic 7 and 8.
HTH!