it's been days trying to perform a transformation to get a JSON file from XML documents. My xml doc have different levels of sub nodes, all examples I found on internet don't catch my case.
Here is my xml example:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo id="1" group="B" />
<foo id="2" group="A" />
<foo id="3", group="A">
<foo id="4" group="A" />
<foo id="5" group="A">
<foo id="6" group="A" />
<foo id="7" group="A" />
<foo id="8" group="A" />
</foo>
</foo>
<foo id="9" group="A"></foo>
</root>
desired JSON:
{
"B": {
"id": 1
},
"A": {
"id": 2
},
"A": [{
"id": 4
},
{
"A": [{
"id": 6
},
{
"id": 7
},
{
"id": 8
}
]
}
],
"A": {
"id": 9
}
}
Means whenever I have nested <foo> elements, child elements are grouped with the parent element, and so on.
I tried some xsl code (see1 and see2) and failed to make them work for my case.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<xsl:text>{</xsl:text>
<xsl:apply-templates select="//foo"/>
<xsl:text>}</xsl:text>
</xsl:template>
<xsl:template match="//foo">
<xsl:choose>
<xsl:when test=" count(ancestor::foo) = 1 and child::foo"/>
<xsl:when test="foo[child::foo]"/>
<xsl:otherwise>
<xsl:text>"</xsl:text><xsl:value-of select="#group"/><xsl:text>"</xsl:text>:<xsl:text> {
</xsl:text>
<xsl:text> "</xsl:text><xsl:value-of select="#id/name()"/><xsl:text>"</xsl:text>:<xsl:value-of select=" concat(' ',#id)"/>
<xsl:text>
},</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Please Check this code:
<?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="xs" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="root">
<xsl:text>{</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="foo">
<xsl:if test="not(child::foo)">
<xsl:text> "</xsl:text><xsl:value-of select="#group"/><xsl:text>": </xsl:text><xsl:text>{</xsl:text><xsl:text>
</xsl:text>
<xsl:text> "id": </xsl:text><xsl:value-of select="#id"/><xsl:text>
</xsl:text>
<xsl:text> }</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:for-each select="foo">
<xsl:if test="not(child::foo)">
<xsl:text> "</xsl:text><xsl:value-of select="#group"/><xsl:text>": </xsl:text><xsl:if test="parent::foo"><xsl:text>[</xsl:text></xsl:if><xsl:text>{</xsl:text><xsl:text>
</xsl:text>
<xsl:text> "id": </xsl:text><xsl:value-of select="#id"/><xsl:text>
</xsl:text>
<xsl:text> },</xsl:text><xsl:text>
</xsl:text>
<xsl:text> {</xsl:text><xsl:text>
</xsl:text>
<xsl:text> "</xsl:text><xsl:value-of select="#group"/><xsl:text>": </xsl:text><xsl:text>[</xsl:text><xsl:text>
</xsl:text>
</xsl:if>
<!-- <xsl:text> "</xsl:text><xsl:value-of select="#group"/><xsl:text>": </xsl:text><xsl:text>[{</xsl:text><xsl:text>
</xsl:text>-->
<xsl:for-each select="foo">
<xsl:text> {</xsl:text><xsl:text>
</xsl:text>
<xsl:text> "id": </xsl:text><xsl:value-of select="#id"/><xsl:text>
</xsl:text>
<xsl:text> }</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if><xsl:text>
</xsl:text>
<xsl:if test="position()=last()">
<xsl:text> ]</xsl:text><xsl:text>
</xsl:text>
</xsl:if>
<xsl:if test="position()=last()">
<xsl:text> }</xsl:text><xsl:text>
</xsl:text>
</xsl:if>
<xsl:if test="position()=last()">
<xsl:text>],</xsl:text><xsl:text>
</xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:template>
</xsl:stylesheet>
It is not quite clear which result you want as the desired "JSON" has duplicated properties if there are various foo group="A" elements but in general of XML to JSON generation you can use XSLT 3 with support for XPath 3.1 maps and arrays (https://www.w3.org/TR/xpath-31/#id-maps-and-arrays) and the json output method or the xml-to-json function (https://www.w3.org/TR/xslt-30/#json, https://www.w3.org/TR/xslt-30/#func-xml-to-json), that way you have two ways of creating JSON and outputting it, you can either transform your XML directly to XPath 3.1 maps/arrays and serialize them with <xsl:output method="json"/> or you can use the usual XSLT processing to transform your input XML to the JSON XML representation the xml-to-json function expects and apply it to get the JSON as text.
Here is an example of the first approach:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:strip-space elements="*"/>
<xsl:output method="json" indent="yes"/>
<xsl:template match="*[*]">
<xsl:map>
<xsl:for-each-group select="foo" group-by="#group">
<xsl:map-entry key="string(current-grouping-key())">
<xsl:choose>
<xsl:when test="not(tail(current-group()))">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="group-transform" as="map(*)*">
<xsl:apply-templates select="current-group()"/>
</xsl:variable>
<xsl:sequence select="array { $group-transform }"/>
</xsl:otherwise>
</xsl:choose>
</xsl:map-entry>
</xsl:for-each-group>
</xsl:map>
</xsl:template>
<xsl:template match="*[not(*)]">
<xsl:sequence select="map { 'id' : data(#id) }"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6r5Gh3i
And here one of the second:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
xmlns="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<xsl:variable name="json-xml">
<xsl:apply-templates/>
</xsl:variable>
<xsl:template match="/">
<xsl:sequence select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
<xsl:template match="*[*]">
<map>
<xsl:for-each-group select="foo" group-by="string(#group)">
<xsl:choose>
<xsl:when test="not(tail(current-group()))">
<map key="{current-grouping-key()}">
<string key="id">{#id}</string>
</map>
</xsl:when>
<xsl:otherwise>
<array key="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</array>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</map>
</xsl:template>
<xsl:template match="*[not(*)]">
<map>
<string key="id">{#id}</string>
</map>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6r5Gh3i/5
XSLT 3 is available using Saxon 9.8 or 9.9 HE for the Java platform on Sourceforge and Maven https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE/9.9.1-2 and for the .NET platform on NuGet https://www.nuget.org/packages/Saxon-HE/.
Related
I want to convert a XML to JSON using XSLT. But I am facing few issues.
Input XML
<notifications xmlns="http://soap.sforce.com/2005/09/outbound">
<OrganizationId>123</OrganizationId>
<ActionId>123</ActionId>
<SessionId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<EnterpriseUrl>qwe</EnterpriseUrl>
<PartnerUrl>qwe</PartnerUrl>
<Notification>
<Id>123</Id>
<sObject xsi:type="sf:Opportunity" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sf="urn:sobject.enterprise.soap.sforce.com">
<sf:Id>ao123</sf:Id>
<sf:Amount>60000.0</sf:Amount>
<sf:CreatedDate>2014-11-26T14:45:52.000Z</sf:CreatedDate>
<sf:IsClosed>false</sf:IsClosed>
</sObject>
</Notification>
</notifications>
Expected Output JSON
{
"notifications": {
"OrganizationId": "123",
"ActionId": "123",
"SessionId": {
"#nil": "true"
},
"EnterpriseUrl": "qwe",
"PartnerUrl": "qwe",
"Notification": [
{
"Id": "ao123",
"sObject": {
"#type": "sf:Opportunity",
"Id": "ao123",
"Amount": "60000.0",
"CreatedDate": "2014-11-26T14:45:52.000Z",
"IsClosed": "false"
}
}
]
}
}
From this answer I got XSLT and I have tried it. This is the XSLT code I have tried. Fiddle link
<?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"
xmlns="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
version="3.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="json-xml">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
<xsl:template match="*[not(*)]">
<string key="{local-name()}">{.}</string>
</xsl:template>
<xsl:template match="*[*]">
<xsl:param name="key" as="xs:boolean" select="false()"/>
<map>
<xsl:if test="$key">
<xsl:attribute name="key" select="local-name()"/>
</xsl:if>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:choose>
<xsl:when test="current-group()[2]">
<array key="{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>
So below are few issues which I am facing
notifications node is missing in json output, it is the root node in the xml.
Notification should be a json array even if I receive one
item in XML
Please note that I don't want to hard code node names other than notifications and Notification in XSLT code as I may receive different nodes under node Notification.
I am looking for XSLT which can handle my requirements
Some of the requirements (outer map/JSON object, always making Notification an array) can of course be inserted into the XSLT code by modifying what you linked to (please make sure next time you show the XSLT in the question as well):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
version="3.0">
<xsl:output method="text"/>
<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>
<xsl:template match="*[not(*)]">
<string key="{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="local-name()"/>
</xsl:if>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:choose>
<xsl:when test="current-group()[2] or current-grouping-key() = QName('http://soap.sforce.com/2005/09/outbound', 'Notification')">
<array key="{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>
The code you grabbed from another question/answer I think was not written with XML having elements with attributes in mind; I have tried to adapt it below with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
version="3.0">
<xsl:output method="text"/>
<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>
<xsl:template match="*[not(*)]">
<string key="{local-name()}">{.}</string>
</xsl:template>
<xsl:template match="#*">
<string key="#{local-name()}">{.}</string>
</xsl:template>
<xsl:template match="*[* or #*]">
<xsl:param name="key" as="xs:boolean" select="true()"/>
<map>
<xsl:if test="$key">
<xsl:attribute name="key" select="local-name()"/>
</xsl:if>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:choose>
<xsl:when test="current-group()[2] or current-grouping-key() = QName('http://soap.sforce.com/2005/09/outbound', 'Notification')">
<array key="{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>
That seems to give the wanted attributes as #name properties of a JSON object/map for your sample; I can't guarantee it will work in general.
I have a requirement, where I have to send data from SAP to API. For this I need to convert XML to JSON format.I am new to XSLT. I have tried converting XML to JSON using XSLT Transformation. After converting, output should not contain Root nodes. And also is there a way of converting output JSON to x-www-form-urlencoded JSON.
Input Data :
<?xml version="1.0" encoding="UTF-8"?>
<ns0:MT_Sales xmlns:ns0="urn:SD:Sales">
<RequestData>
<DODate>2020-02-10</DODate>
<DONumber>1900200009</DONumber>
<OrderNumber>3900002600</OrderNumber>
</RequestData>
<RequestData>
<DODate>2020-02-11</DODate>
<DONumber>1900200010</DONumber>
<OrderNumber>3900002603</OrderNumber>
</RequestData>
<RequestData>
<DODate>2020-02-11</DODate>
<DONumber>1900200011</DONumber>
<OrderNumber>3900002604</OrderNumber>
</RequestData>
</ns0:MT_Sales>
I have used below XSLT coding :
<?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: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>
</xsl:stylesheet>
Output I received :
{
"ns0:MT_SAP_SecondarySales" : { "RequestData" :[{
"DODate" : "2020-02-10",
"DONumber" : "1900200009",
"OrderNumber" : "3900002600"
},{
"DODate" : "2020-02-11",
"DONumber" : "1900200011",
"OrderNumber" : "3900002604"
}] }}
Output I need:
{"RequestData":[{
"DODate" : "2020-02-10",
"DONumber" : "1900200009",
"OrderNumber" : "3900002600"
},{
"DODate" : "2020-02-11",
"DONumber" : "1900200011",
"OrderNumber" : "3900002604"
}] } ```
Help is much appreciated.
It's easier to write a specific stylesheet that fits your structure than adapt a generic one:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/*">
<xsl:text>{"RequestData":[</xsl:text>
<xsl:for-each select="*">
<xsl:text>{
</xsl:text>
<xsl:for-each select="*">
<xsl:text> "</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>" : "</xsl:text>
<xsl:value-of select="."/>
<xsl:text>"</xsl:text>
<xsl:if test="position()!=last()">,</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>}</xsl:text>
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
<xsl:text>]}</xsl:text>
</xsl:template>
</xsl:stylesheet>
All the whitespace characters ( ,
) are of course optional.
I have a XSLT that works the way I want when it outputs XML, however I would like to change the XSLT it to output JSON instead. The problem appears to me with the inner most for-each loop, but I'm not sure. If there is way to make this more efficient I'm also interested in your suggestions.
Sample input XML
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xs="http://www.w3.org/2001/XMLSchema">
<Metric measType="1526727075"
measResult="0"
endTime="2016-08-25T04:30:00-07:00"
measObjLdn="LTHBC0126858/GTPU:Board Type=MPT, Cabinet No.=0, Subrack No.=1, Slot No.=7"
Element_Type="ENODEB"
Key1="LTHBC0126858"
TableName="HH_ENODEB"
ColumnName="H1526727075"
H1526727075="0"/>
<Metric measType="1526727076"
measResult="0"
endTime="2016-08-25T04:30:00-07:00"
measObjLdn="LTHBC0126858/GTPU:Board Type=MPT, Cabinet No.=0, Subrack No.=1, Slot No.=7"
Element_Type="ENODEB"
Key1="LTHBC0126858"
TableName="HH_ENODEB"
ColumnName="H1526727076"
H1526727076="0"/>
<Metric measType="1526727077"
measResult="0"
endTime="2016-08-25T04:30:00-07:00"
measObjLdn="LTHBC0126858/GTPU:Board Type=MPT, Cabinet No.=0, Subrack No.=1, Slot No.=7"
Element_Type="ENODEB"
Key1="LTHBC0126858"
TableName="HH_ENODEB"
ColumnName="H1526727077"
H1526727077="0"/>
</root>
This is the XSLT that outputs XML and works the way I expect
<?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" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<root>
<xsl:for-each-group select="Metric" group-by="#measObjLdn">
<xsl:sort select="current-grouping-key()"/>
<xsl:variable name="curr_key" select="current-grouping-key()"/>
<xsl:for-each-group select="current-group()" group-by="#TableName">
<xsl:sort select="current-grouping-key()"/>
<xsl:if test="current-grouping-key() != ''">
<Table TableName="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<xsl:attribute name="Stamp">
<xsl:value-of select="#endTime"/>
</xsl:attribute>
<xsl:attribute name="measObjLdn">
<xsl:value-of select="$curr_key"/>
</xsl:attribute>
<xsl:attribute name="Element_Type">
<xsl:value-of select="#Element_Type"/>
</xsl:attribute>
<xsl:attribute name="Key1">
<xsl:value-of select="#Key1"/>
</xsl:attribute>
<xsl:for-each select="#*">
<xsl:if test="starts-with(name(), 'H')">
<xsl:attribute name="{name()}">
<xsl:value-of select="number(.)"/>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</Table>
</xsl:if>
</xsl:for-each-group>
</xsl:for-each-group>
</root>
</xsl:template>
</xsl:stylesheet>
This is my JSON output code, but not working as expected.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="text" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:text>{"root":{</xsl:text>
<xsl:for-each-group select="Metric" group-by="#TableName">
<xsl:sort select="current-grouping-key()"/>
<xsl:if test="current-grouping-key() != ''">
<xsl:text>"Table":[</xsl:text>
<xsl:text>{"TableName":"</xsl:text>
<xsl:value-of select="current-grouping-key()"/>
<!--<Table TableName="{current-grouping-key()}">-->
<xsl:text>",</xsl:text>
<xsl:for-each-group select="current-group()" group-by="#measObjLdn">
<xsl:sort select="current-grouping-key()"/>
<xsl:for-each select="current-group()">
<xsl:text>"Stamp":"</xsl:text>
<xsl:value-of select="#endTime"/>
<xsl:text>",</xsl:text>
<xsl:text>"measObjLdn":"</xsl:text>
<xsl:value-of select="current-grouping-key()"/>
<xsl:text>",</xsl:text>
<xsl:text>"Element_Type":"</xsl:text>
<xsl:value-of select="#Element_Type"/>
<xsl:text>",</xsl:text>
<xsl:text>"Key1":"</xsl:text>
<xsl:value-of select="#Key1"/>
<xsl:text>",</xsl:text>
<xsl:for-each select="#attribute()">
<xsl:if test="starts-with(name(), 'H')">
<xsl:text>"</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>":"</xsl:text>
<xsl:value-of select="number(.)"/>
<xsl:text>"</xsl:text>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each-group>
<!--</Table>-->
<xsl:text>}</xsl:text>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:if>
</xsl:for-each-group>
<xsl:text>}</xsl:text>
</xsl:template>
</xsl:stylesheet>
Good point, so the XML version groups all the attributes for the same table together; Given the sample above the XML output is as follows (some reformatting applied for clarity):
<root xmlns:xs="http://www.w3.org/2001/XMLSchema">
<Table
TableName="HH_ENODEB"
Stamp="2016-08-25T04:30:00-07:00"
measObjLdn="LTHBC0126858/GTPU:Board Type=MPT, Cabinet No.=0, Subrack No.=1, Slot No.=7"
Element_Type="ENODEB"
Key1="LTHBC0126858"
H1526727075="0"
H1526727076="0"
H1526727077="0"/>
</root>
JSON example output is not a JSON version of the XML version which I expected (some whitespace reformatting applied for clarity).
{
"root": {
"Table": [
{
"TableName":"HH_ENODEB",
"Stamp":"2016-08-25T04:30:00-07:00",
"measObjLdn":"LTHBC0126858/GTPU:Board Type=MPT, Cabinet No.=0, Subrack No.=1, Slot No.=7",
"Element_Type":"ENODEB",
"Key1":"LTHBC0126858",
"H1526727075":"0"
"Stamp":"2016-08-25T04:30:00-07:00",
"measObjLdn":"LTHBC0126858/GTPU:Board Type=MPT, Cabinet No.=0, Subrack No.=1, Slot No.=7",
"Element_Type":"ENODEB",
"Key1":"LTHBC0126858",
"H1526727076":"0"
"Stamp":"2016-08-25T04:30:00-07:00",
"measObjLdn":"LTHBC0126858/GTPU:Board Type=MPT, Cabinet No.=0, Subrack No.=1, Slot No.=7",
"Element_Type":"ENODEB",
"Key1":"LTHBC0126858",
"H1526727077":"0"
}}
(The above is not even well-formed JSON)
You didn't actually say what your expected JSON should look like, but based on your XML, I am assuming it should look like this
{
"root":
{
"Table":
[
{
"TableName":"HH_ENODEB",
"Stamp":"2016-08-25T04:30:00-07:00",
"measObjLdn":"HH_ENODEB",
"Element_Type":"ENODEB",
"Key1":"LTHBC0126858",
"H1526727075":"0",
"H1526727076":"0",
"H1526727077":"0"
}
]
}
}
The issue is that in the XSLT that produces the XML output, you have the creation of the xsl:attributes within the statement <xsl:for-each select="current-group()">, but that means you are repeatedly outputting the same attribute names for a single Table element. In this case, XSLT will just replace any existing attribute with the latest one created, so you don't notice what is going on.
When outputting JSON (or rather, when outputting text, which just happens to be in JSON format), you do end up with the repeated attributes as it is just text.
The solution is to move the creation of the main attributes outside the inner loop.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:text>{
"root":
{
</xsl:text>
<xsl:for-each-group select="Metric" group-by="#measObjLdn">
<xsl:sort select="current-grouping-key()"/>
<xsl:variable name="curr_key" select="current-grouping-key()"/>
<xsl:text> "Table":
[
</xsl:text>
<xsl:for-each-group select="current-group()" group-by="#TableName">
<xsl:sort select="current-grouping-key()"/>
<xsl:if test="current-grouping-key() != ''">
<xsl:text> {
"TableName":"</xsl:text>
<xsl:value-of select="current-grouping-key()"/>
<xsl:text>",
</xsl:text>
<xsl:text> "Stamp":"</xsl:text>
<xsl:value-of select="#endTime"/>
<xsl:text>",
</xsl:text>
<xsl:text> "measObjLdn":"</xsl:text>
<xsl:value-of select="$curr_key"/>
<xsl:text>",
</xsl:text>
<xsl:text> "Element_Type":"</xsl:text>
<xsl:value-of select="#Element_Type"/>
<xsl:text>",
</xsl:text>
<xsl:text> "Key1":"</xsl:text>
<xsl:value-of select="#Key1"/>
<xsl:text>"</xsl:text>
<xsl:for-each select="current-group()">
<xsl:for-each select="#*[starts-with(name(), 'H')]">
<xsl:text>,
</xsl:text>
<xsl:text> "</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>":"</xsl:text>
<xsl:value-of select="number(.)"/>
<xsl:text>"</xsl:text>
</xsl:for-each>
</xsl:for-each>
<xsl:text>
}
</xsl:text>
</xsl:if>
</xsl:for-each-group>
<xsl:text> ]
</xsl:text>
</xsl:for-each-group>
<xsl:text> }
}</xsl:text>
</xsl:template>
</xsl:stylesheet>
My xml:
<workorder>
<specifications>
<hpath>94 \ 72</hpath>
<classdesc></classdesc>
</specifications>
</workorder>
my xslt:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="workorder">
<xsl:apply-templates select="specifications" />
</xsl:template>
<xsl:template match="specifications">
<xsl:text>{
"Hierarchy Path"="</xsl:text>
<xsl:value-of select="translate(//workorder/specifications/hpath,'\','\\')" />
<xsl:text>"
}</xsl:text>
</xsl:template>
</xsl:stylesheet>
Expecting output is:
{
"Hierarchy Path"="94 \\ 72"
}
Current output is :
{
"Hierarchy Path"="94 \ 72"
}
Problem is: When I send json format as "Current output", not a valid json format.
If I send as "Expected output", it is a valid json format.
Please help. Thanks in advance.
With XSLT 1.0 you need a recursive approach.
You may try something like this named template.
<xsl:template name="jsonescape">
<xsl:param name="str" select="."/>
<xsl:choose>
<xsl:when test="contains($str, '\')">
<xsl:value-of select="concat(substring-before($str, '\'), '\\' )"/>
<xsl:call-template name="jsonescape">
<xsl:with-param name="str" select="substring-after($str, '\')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
And call it with your strings(for example).
<xsl:call-template name="jsonescape">
<xsl:with-param name="str" select="//workorder/specifications/hpath"/>
</xsl:call-template>
I have a XSL associate with an XML file. This XSL aim to create Mysql queries but in my XML I had some special characters like apostroph ' which break my queries. Do you know how I can sanitize my XSL template in order to have safe queries?
Example of my XML file
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="fnac.xsl"?>
<products xmlns="http://zanox.com/productdata/exportservice/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://zanox.com/productdata/exportservice/v1 http://productdata.zanox.com/exportservice/schema/export-1.1.xsd">
<product>
<name>jack o'connor</name>
<program>3467</program>
</product>
</products>
And my XSL file :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:v1="http://zanox.com/productdata/exportservice/v1">
<xsl:output method="text" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//v1:product"/>
</xsl:template>
<xsl:template match="v1:product">
<xsl:text>insert into fnac (name, program) values(</xsl:text>
<xsl:value-of select="./v1:name"/>
<xsl:text>,'</xsl:text>
<xsl:value-of select="./v1:program"/>
<xsl:text>'); </xsl:text>
</xsl:template>
</xsl:stylesheet>
Thanks for your inputs!
If you want to remove apostrope from your XML file, you should use this xslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:v1="http://zanox.com/productdata/exportservice/v1">
<xsl:output method="text" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//v1:product"/>
</xsl:template>
<xsl:template match="v1:product">
<xsl:text>insert into fnac (name, program) values(</xsl:text>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="./v1:name"/>
<xsl:with-param name="from">'</xsl:with-param>
<xsl:with-param name="to" select="' '"/>
</xsl:call-template>
<xsl:text>,'</xsl:text>
<xsl:value-of select="./v1:program"/>
<xsl:text>'); </xsl:text>
</xsl:template>
<xsl:template name="replace-string">
<xsl:param name="text"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>
<xsl:when test="contains($text, $from)">
<xsl:variable name="before" select="substring-before($text, $from)"/>
<xsl:variable name="after" select="substring-after($text, $from)"/>
<xsl:copy-of select="$before"/>
<xsl:value-of select="$to" disable-output-escaping="yes"/>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$after"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
which would generate output:
insert into fnac (name, program) values(jack o connor,'3467');
If you only like/need to replace the apostrope , this could be done lot easier with translate().
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:v1="http://zanox.com/productdata/exportservice/v1">
<xsl:output method="text" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//v1:product"/>
</xsl:template>
<xsl:template match="v1:product">
<xsl:text>insert into fnac (name, program) values(</xsl:text>
<xsl:value-of select='translate(./v1:name,"'", " ")'/>
<xsl:text>,'</xsl:text>
<xsl:value-of select="./v1:program"/>
<xsl:text>'); </xsl:text>
</xsl:template>
</xsl:stylesheet>