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

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>

Related

XSLT xml-to-json skips root node + how to copy attribute as child node

I am trying to convert XML to json using XSLT 3.0, lowercasing all keys and moving the first attribute, if any, as a JSON child. So given following (dummy) input XML:
<FOO id="1">
<BAR xy="2">
<SPAM>N</SPAM>
</BAR>
</FOO>
I am expecting
{
"foo" : {
"id" : "1",
"bar" : {
"xy" : "2",
"spam" : "N"
}
}
}
Using this XSLT:
<?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="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="xml" indent='true' omit-xml-declaration='yes'/>
<xsl:template match="dummy">
<xsl:variable name="json-xml">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
<!--no children-->
<xsl:template match="*[not(*)]">
<string key="{lower-case(local-name())}">{.}</string>
</xsl:template>
<xsl:template match="*[*]">
<xsl:param name="key" as="xs:boolean" select="true()"/>
<map>
<xsl:if test="$key">
<xsl:attribute name="key" select="lower-case(local-name())"/>
</xsl:if>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:choose>
<xsl:when test="current-group()[2]">
<array key="{lower-case(local-name())}">
<xsl:apply-templates select="current-group()">
<xsl:with-param name="key" select="false()"/>
</xsl:apply-templates>
</array>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()">
<xsl:with-param name="key" select="true()"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</map>
</xsl:template>
</xsl:stylesheet>
I get (note the dummy pattern to skip json conversion),
<map xmlns="http://www.w3.org/2005/xpath-functions" key="foo">
<map key="bar">
<string key="spam">N</string>
</map>
</map>
Looks good to me but when I invoke the JSON conversion (by replacing dummy with /), I get:
{ "bar" :
{ "spam" : "N" } }
-> the foo node is gone.
I haven't figured out how to "move" the first attribute (arbitrary, could have any name) as a child node - if someone knows, appreciate a little snippet.
Lastly, not a big-deal but I am lowercasing keys in each template. Is it possible to do the transformation at once, either before in the source XML, or after templating, in the Result XML (before jsonifying) ?
See - https://xsltfiddle.liberty-development.net/jyfAiDC/2
(thanks btw to #Martin Honnen for this very useful tool !!)
You can use
<xsl:template match="/">
<xsl:variable name="json-xml">
<map>
<xsl:apply-templates/>
</map>
</xsl:variable>
<xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
perhaps to get closer to what you might want. But I haven't understood what you want to do with attributes in the XML.
The key attribute only make sense for an element that represents an entry in a map, so there's no point including it in an element unless that element has a parent named map. You want another layer of map in your structure.
Thanks to Martin & Michael for the map wrapper tip. Agreed though it is an unnecessary level in tree.
For rendering a XML attribute as a child node - assuming there is one only -,
I added the following after the first test (conditional map attribute) in the template:
<xsl:if test="#*[1]">
<string key="{name(#*[1])}">{#*[1]}</string>
</xsl:if>
Lastly, for converting to lowercase all intermediary key attributes in one-go instead of individually in multiple templates, it would require I think parsing the result tree before passing on to the xml-to-json function.
Not worth it... but it would be a nice featured option in XSLT 4.0 (?) i.e. a new xml-to-json option force-key-case = lower/upper

XSLT Property name manipulation

I have an XML file that I would like to convert it to JSON with using XSLT. And I would like to manipulate property names of output JSON inside XSLT. But I could not manage to do that. Here is what I tried...
I have following XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="xslt-trial.xsl"?>
<Invoice
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:AccountingCost>Cost Center</cbc:AccountingCost>
<cbc:BuyerReference>Buyer reference</cbc:BuyerReference>
<cac:InvoicePeriod>
<cbc:StartDate>2020-02-11</cbc:StartDate>
<cbc:EndDate>2020-02-21</cbc:EndDate>
</cac:InvoicePeriod>
<cac:ContractDocumentReference>
<cbc:ID>Agreement Id</cbc:ID>
</cac:ContractDocumentReference>
</Invoice>
and "xslt-trial.xsl" is:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">{
<xsl:apply-templates select="*"/>}
</xsl:template>
<!-- Object or Element Property-->
<xsl:template match="*">
"<xsl:value-of select="name()"/>" :<xsl:call-template name="Properties">
<xsl:with-param name="parent" select="'Yes'"> </xsl:with-param>
</xsl:call-template>
</xsl:template>
<!-- Array Element -->
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="Properties"/>
</xsl:template>
<!-- Object Properties -->
<xsl:template name="Properties">
<xsl:param name="parent"></xsl:param>
<xsl:variable name="childName" select="name(*[1])"/>
<xsl:choose>
<xsl:when test="not(*|#*)"><xsl:choose><xsl:when test="$parent='Yes'"> <xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text></xsl:when>
<xsl:otherwise>"<xsl:value-of select="name()"/>":"<xsl:value-of select="."/>"</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="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>
<!-- Attribute Property -->
<xsl:template match="#*">"<xsl:value-of select="name()"/>" : "<xsl:value-of select="."/>",
</xsl:template>
</xsl:stylesheet>
I get this output from Edge browser:
{
"Invoice" :{
"cbc:AccountingCost" :"Cost Center",
"cbc:BuyerReference" :"Buyer reference",
"cac:InvoicePeriod" :{
"cbc:StartDate" :"2020-02-11",
"cbc:EndDate" :"2020-02-21"
},
"cac:ContractDocumentReference" :{
"cbc:ID" :"Agreement Id"
}
}}
What I want to get is:
{
"Invoice" :{
"AccountingCost" :"Cost Center",
"BuyerReference" :"Buyer reference",
"InvoicePeriod" :{
"StartDate" :"2020-02-11",
"EndDate" :"2020-02-21"
},
"ContractDocumentReference" :{
"ID" :"Agreement Id"
}
}}
As you can see, I would like to remove "cac:" and "cbc:" from property names. But I cannot manage to filter these values out.
Does anybody have a suggestion?
If you use the function local-name() instead of the function name() then you should get element names without prefixes.

XML to JSON using XSLT on selected nodes

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" }
]}

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!

Remove root tag in JSON using XSLT when converting XML to JSON

I am trying to convert a XML to JSON using XSLT definition. I have got the required output but i want a root tag from my json output to be removed while converting please help me out with this.
XML input
<ArrayOfApiInvoiceReport xmlns="http://schemas.datacontract.org/2004/07/RepSpark.WebServices.Models">
<BillingAddress1>10 N LUMINA AVE</BillingAddress1>
<BillingAddress2/>
<BillingCity>WRIGHTSVILLE BEACH</BillingCity>
<BillingCountry>US</BillingCountry>
<BillingCustomerCode>3003800</BillingCustomerCode>
<BillingCustomerName>SWEETWATER SURF SHOP</BillingCustomerName>
<BillingState>NC</BillingState>
<BillingZip>28480</BillingZip>
<InvoiceAmount>372.72</InvoiceAmount>
<InvoiceCreatedDate>20141110</InvoiceCreatedDate>
<InvoiceItems>
<InvoiceItemSizes>
<InvoiceLineNumber>1</InvoiceLineNumber>
<InvoicedQuantity>3</InvoicedQuantity>
<SizeCode>L</SizeCode>
<UPC>9348282095644</UPC>
</InvoiceItemSizes>
<InvoiceLineNumber>1</InvoiceLineNumber>
<InvoiceNumber>101000005</InvoiceNumber>
<InvoiceItems>
</ArrayOfApiInvoiceReport
XSLT used
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes" indent="yes"/>
<!-- Object or Element Property-->
<xsl:template match="*">
<xsl:call-template name="Properties"/>
</xsl:template>
<!-- Array Element -->
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="Properties"/>
</xsl:template>
<!-- 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="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>
<!-- Attribute Property -->
<xsl:template match="#*">"<xsl:value-of select="name()"/>" : "<xsl:value-of select="."/>",
</xsl:template>
Current JSON output
"ArrayOfApiInvoiceReport" : [{
"BillingAddress1" : "10 N LUMINA AVE",
"BillingAddress2" : "",
"BillingCity" : "WRIGHTSVILLE BEACH",
"BillingCountry" : "US",
"BillingCustomerCode" : "3003800",
"BillingCustomerName" : "SWEETWATER SURF SHOP",
"BillingState" : "NC",
"BillingZip" : "28480",
"InvoiceAmount" : "372.72",
"InvoiceCreatedDate" : "20141110",
"InvoiceItems" : [{
"InvoiceItemSizes" : [{
"InvoiceLineNumber" : "1",
"InvoicedQuantity" : "3",
"SizeCode" : "L",
"UPC" : "9348282095644"
}],
"InvoiceLineNumber" : "1",
"InvoiceNumber" : "101000005",
"InvoicedQuantity" : "3",
Required Output is a JSON without "ArrayOfApiInvoiceReport"[{
"BillingAddress1": "10 N LUMINA AVE",
"BillingAddress2": "",
"BillingCity": "WRIGHTSVILLE BEACH",
"BillingCountry": "US",
"BillingCustomerCode": "3003800",
"BillingCustomerName": "SWEETWATER SURF SHOP",
"BillingState": "NC",
"BillingZip": "28480",
"InvoiceAmount": "372.72",
"InvoiceCreatedDate": "20141110",
"InvoiceItems": [
{
"InvoiceItemSizes": [
{
"InvoiceLineNumber": "1",
"InvoicedQuantity": "3",
"SizeCode": "L",
"UPC": "9348282095644"
}
],
"InvoiceLineNumber": "1",
"InvoiceNumber": "101000005",
"InvoicedQuantity": "3",
Please help me with this.
Please see my points on the original question.
Here is a stylesheet that converts the XML into JSON without the containing node.
This stylesheet also omits the final comma. If you want to keep that you will need to add it in.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:variable name="nl"><xsl:text>
</xsl:text></xsl:variable>
<xsl:variable name="tb"><xsl:text> </xsl:text></xsl:variable>
<xsl:template match="/*">
<!-- Open the root array -->
<xsl:text>[{</xsl:text>
<xsl:value-of select="$nl" />
<!-- Process all the child nodes of the root -->
<xsl:apply-templates select="*" mode="subitem" >
<xsl:with-param name="indent" select="$tb" />
</xsl:apply-templates>
<!-- Close the root array -->
<xsl:value-of select="$nl" />
<xsl:text>}]</xsl:text>
</xsl:template>
<xsl:template match="*" mode="subitem" >
<!-- child element at any level. The indent parameter allows for better layout of the output JSON -->
<xsl:param name="indent"></xsl:param>
<!-- newindent is used as the indent for children of this node -->
<xsl:variable name="newindent"><xsl:value-of select="$indent" /><xsl:value-of select="$tb"/></xsl:variable>
<!-- output the name of this node in quotes, ready for the content to follow -->
<xsl:value-of select="$indent" /><xsl:text>"</xsl:text><xsl:value-of select="name()" /><xsl:text>" :</xsl:text>
<!-- check if this node has children, if not, simply output the text value, otherwise outoput an array -->
<xsl:choose>
<xsl:when test=" count( ./* ) = 0 ">
<!-- This is a text value only -->
<xsl:text> "</xsl:text>
<!-- Make sure that any embedded quotes are escaped -->
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="." />
<xsl:with-param name="replace">"</xsl:with-param>
<xsl:with-param name="by">\"</xsl:with-param>
</xsl:call-template>
<xsl:text>"</xsl:text>
<!-- check if we need a comma and a new line, not required if this is the last output value -->
<xsl:if test=" position() != last() ">
<xsl:text>,</xsl:text>
<xsl:value-of select="$nl" />
</xsl:if>
</xsl:when>
<xsl:otherwise>
<!-- This node has children, so we need to process them as an array -->
<!-- Array opening -->
<xsl:text>[</xsl:text><xsl:value-of select="$nl" />
<xsl:value-of select="$newindent" /><xsl:text>{</xsl:text><xsl:value-of select="$nl" />
<!-- Process all the elements in the array (recursive call to this template) -->
<xsl:apply-templates select="*" mode="subitem" >
<xsl:with-param name="indent"><xsl:value-of select="$newindent" /></xsl:with-param>
</xsl:apply-templates>
<!-- Close the array -->
<xsl:value-of select="$nl" /><xsl:value-of select="$newindent" /><xsl:text>}</xsl:text>
<xsl:value-of select="$nl" /><xsl:value-of select="$indent" /><xsl:text>]</xsl:text>
<!-- If this is not the last node then we need a comma and line feed -->
<xsl:if test=" position() != last() ">
<xsl:text>,</xsl:text>
<xsl:value-of select="$nl" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="string-replace-all">
<!-- This code provided thanks to #codesling on stackoverflow -->
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With (corrected) XML the output is:
[{
"BillingAddress1" : "10 N LUMINA AVE",
"BillingAddress2" : "",
"BillingCity" : "WRIGHTSVILLE BEACH",
"BillingCountry" : "US",
"BillingCustomerCode" : "3003800",
"BillingCustomerName" : "SWEETWATER SURF SHOP",
"BillingState" : "NC",
"BillingZip" : "28480",
"InvoiceAmount" : "372.72",
"InvoiceCreatedDate" : "20141110",
"InvoiceItems" :[
{
"InvoiceItemSizes" :[
{
"InvoiceLineNumber" : "1",
"InvoicedQuantity" : "3",
"SizeCode" : "L",
"UPC" : "9348282095644"
}
],
"InvoiceLineNumber" : "1",
"InvoiceNumber" : "101000005"
}
]
}]