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!
Related
I need your help in converting JSON format into XML format using XSLT.
My JSON
{
"SARIETransferRq": {
"SARIETransferRqHeader": {
"CorporateID": "ABCD0001",
"CorpReferenceNumber": "4716936"
},
"SARIETransferRqBody": {
"AccountNumber": "02020202020",
"DebitCurrency": "SAR",
"Instructions2": "luaba",
"Instructions3": "fadjiugu",
"Instructions4": "rifdij",
"Description": "sample description for service fee",
"AMLPurposeCode": "BB"
}
},
"Signature": {
"SignatureValue": "sample"
}
}
Output XML:
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<SARIETransferRq>
<SARIETransferRqHeader>
<CorporateID>ABCD0001</CorporateID>
<CorpReferenceNumber>4716936</CorpReferenceNumber>
</SARIETransferRqHeader>
<SARIETransferRqBody>
<AccountNumber>02020202020</AccountNumber>
<DebitCurrency>SAR</DebitCurrency>
<Instructions2>luaba</Instructions2>
<Instructions3>fadjiugu</Instructions3>
<Instructions4>rifdij</Instructions4>
<Description>sample description for service fee</Description>
<AMLPurposeCode>BB</AMLPurposeCode>
</SARIETransferRqBody>
</SARIETransferRq>
<Signature>
<SignatureValue>sample</SignatureValue>
</Signature>
</root>
In most of JSON to XML conversion i see there is xml tag in json file but my required doesn't have this XML tag in json file.
Can anyone suggest what needs to be corrected here:
Assuming XSLT 3 where you start with a named template and pass in the JSON as a string parameter (you could use a file name/URI to the JSON file as well and make that param then <xsl:param name="json" select="unparsed-text('input.json')"/>)
<?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"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/" name="xsl:initial-template">
<root>
<xsl:apply-templates select="json-to-xml($json)/*"/>
</root>
</xsl:template>
<xsl:template match="*[#key]">
<xsl:element name="{#key}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:param name="json" as="xs:string" expand-text="no">
{
"SARIETransferRq": {
"SARIETransferRqHeader": {
"CorporateID": "ABCD0001",
"CorpReferenceNumber": "4716936"
},
"SARIETransferRqBody": {
"AccountNumber": "02020202020",
"DebitCurrency": "SAR",
"Instructions2": "luaba",
"Instructions3": "fadjiugu",
"Instructions4": "rifdij",
"Description": "sample description for service fee",
"AMLPurposeCode": "BB"
}
},
"Signature": {
"SignatureValue": "sample"
}
}
</xsl:param>
</xsl:stylesheet>
Online sample with SaxonJS.
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 want to convert JSON to XML using XSLT. But not able to achieve the expected output.
Below is the JSON request:
{
"Store": [
{
"Book": "Cartoons",
"ID": "ABC"
}
]
}
The XSLT which I tried:
<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"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:emp="http://www.semanticalllc.com/ns/employees#"
xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:j="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="xs math xd h emp"
version="3.0"
expand-text="yes">
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="json-to-xml(.)/*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#key]" xpath-default-namespace="http://www.w3.org/2005/xpath-functions">
<xsl:element name="{#key}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
But I'm getting empty Response.
You need to pass the JSON as a parameter or read it from a file, the input to your XSLT is either XML or you can start with a named template:
<?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"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="json" as="xs:string" expand-text="no">{
"Store": [
{
"Book": "Cartoons",
"ID": "ABC"
}
]
}</xsl:param>
<xsl:output indent="yes"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence select="json-to-xml($json)"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/93wniTJ
The result of the function for your sample is
<map xmlns="http://www.w3.org/2005/xpath-functions">
<array key="Store">
<map>
<string key="Book">Cartoons</string>
<string key="ID">ABC</string>
</map>
</array>
</map>
but of course you can run it through further templates to transform it to a different format.
The pattern match="/" matches a document node (the root of an XML tree). It won't match your input if the input is JSON.
XSLT 3.0 isn't actually that good at processing JSON using template rules: it can be done, but it isn't very convenient. It's usually more convenient to use functions. You can supply the JSON input as the value of an xsl:param and process it in code from your xsl:initial-template; or if you're feeling more ambitious you could actually initiate the XSLT processing by invoking a public xsl:function that takes the JSON input as a parameter.
The traditional match="/" entry to a stylesheet only works for the traditional use of XSLT to process an XML input document.
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" }
]}
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.