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
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>
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>
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.
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.
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!