XML to JSON (Webhooks slack) - json

I would like to convert an XML to JSON using xslt transformation.The purpose is to POST into my slack channel using Incoming Webhooks.
XML File :
<?xml version="1.0" encoding="UTF-8" ?>
<Asset version="1.0">
<Process>
<Date>2017-01-24 14:47:35</Date>
<Status>Success</Status
> <Profile>TEST</Profile>
<Station>DESKTOP</Station>
<User>Système</User>
<Application>APP</Application>
</Process>
<Source>
</Source>
<Target>
<Name>Hello.mp4</Name>
</Target>
</Asset>
I tried this :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="txt" omit-xml-declaration="yes" encoding="UTF-8"/>
<xsl:template match="/">
{
"text":"<xsl:value-of select="Asset/Process/Profile"/>",
"text":"<xsl:value-of select="Asset/Process/Date"/>",
"text":"<xsl:value-of select="Asset/Target/Name"/>",
"text":"<xsl:value-of select="Asset/Process/Status"/>",
}
</xsl:template>
</xsl:stylesheet>
But i've got this error : 500 - missing_text_or_fallback_or_attachments
Do you have any idea ?
I need to have an JSON like this :
{ "text": "Date: 2017-01-24 14:47:35\n Status: Success\n Profile: TEST\n Station: DESKTOP\n User: Système\n Application: APP"}
https://api.slack.com/incoming-webhooks#sending_messages

Here is the XSLT which produces the desired JSON String:
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" omit-xml-declaration="yes" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:text>{ "text": "</xsl:text>
<xsl:for-each select="Asset/Process/*">
<xsl:choose>
<xsl:when test="position()=1">
<xsl:value-of select="concat(local-name(),': ',.,'\n')"/>
</xsl:when>
<xsl:when test="position()=last()">
<xsl:value-of select="concat(' ',local-name(),': ',.)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' ',local-name(),': ',.,'\n')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:text>"}</xsl:text>
</xsl:template>
</xsl:stylesheet>

Related

Appending nodes with similar names using xslt in wso2

I have some xml data with multiple Name nodes. Based on the presence or absence of id node, I need to segregate the nodes. On converting to JSON, I want all the similar nodes to be merged into an JSON array. Below is my XML data
<Names>
<CustName>
<Name>Name1</Name>
<id>3</id>
</CustName>
<CustName >
<Name>Name2</Name>
</CustName>
<CustName>
<Name>Name3</Name>
<id>32</id>
</CustName>
</Names>
The XSLT that I have tried is as follows. But this creates two nodes for Update and one node for Create. Whereas I want 1st and 3rd Name nodes to be under Update node and 2nd Name node under Create
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames>
<xsl:for-each select="//Names/CustName">
<xsl:choose>
<xsl:when test="id !=''">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:when>
<xsl:otherwise>
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Transformed xml should look like:
<CustomerNames>
<Update>
<CustName>Name1</CustName>
<id>3</id>
</Update>
<Update>
<CustName>Name3</CustName>
<id>32</id>
</Update>
<Create>
<Name>Name2</Name>
</Create>
</CustomerNames>
On converting to json, I want the similar nodes to be appended in an array. Like this
{
"CustomerNames":{
"Update":[
{
"CustName":"Name1",
"id":"3"
},
{
"CustName":"Name3",
"id":"32"
}
],
"Create":[
{
"Name":"Name2"
}
]
}
}
How can I achieve this in XSL 1.0?
It seems the order nodes are placed matters when auto-converting XML to a JSON. Hence update your XSLT to something like the one below.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames>
<xsl:for-each select="//Names/CustName[id]">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:for-each>
<xsl:for-each select="//Names/CustName[not(id)]">
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Alternatively, you can group the XML nodes by using XSL Sort
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames xmlns="">
<xsl:for-each select="//Names/CustName">
<xsl:sort select="id"/>
<xsl:choose>
<xsl:when test="id !=''">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:when>
<xsl:otherwise>
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Another approach would be to use the Payloadfactry mediator after the XSLT mediator to group the nodes.Example below.
<payloadFactory media-type="xml">
<format>
<CustomerNames>
$1
$2
</CustomerNames>
</format>
<args>
<arg expression="//Update"/>
<arg expression="//Create"/>
</args>
</payloadFactory>

Output multiple elements as a JSON array in XSLT

I have an XSL Code as shown below.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:variable name="inlineAddressArray">
<addrline>Home1</addrline>
<addrline>Home2</addrline>
<addrline>Home3</addrline>
</xsl:variable>
<xsl:variable xmlns:exsl="http://exslt.org/common" name="data" select="exsl:node-set($inlineAddreddArray)" />
<addressLines>
<xsl:value-of select="$data/addrline" />
</addressLines>
</xsl:stylesheet>
My expected JSON output should be:
"addressLines":"[Home1,Home2,Home3]"
Output that I'm getting
"addressLines": "Home1Home2Home3"
Basically it is just concatenating every element as a single element.
But, I should get three elements separately as shown above.
Can anyone please help me on this? Don't mind if it's a silly query. I'm very new to XSLT :)
No need for Newtonsoft.Json.JsonConvert.SerializeXmlNode() .
Given this xml:
<inlineAddressArray>
<addrline>Home1</addrline>
<addrline>Home2</addrline>
<addrline>Home3</addrline>
</inlineAddressArray>
Using this xslt 2.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="inlineAddressArray">
<xsl:text>"addressLines":"[</xsl:text>
<xsl:value-of select="addrline" separator=","/>
<xsl:text>]"</xsl:text>
</xsl:template>
</xsl:stylesheet>
or using xslt 1.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="inlineAddressArray">
<xsl:text>"addressLines":"[</xsl:text>
<xsl:apply-templates select="addrline"/>
<xsl:text>]"</xsl:text>
</xsl:template>
<xsl:template match="addrline">
<xsl:value-of select="."/>
<xsl:if test="not(position()=last())">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
results in this :
"addressLines":"[Home1,Home2,Home3]"
UPDATE
if you also want to deal with empty addrline like i.e.
<inlineAddressArray>
<addrline>Home1</addrline>
<addrline>Home2</addrline>
<addrline></addrline>
</inlineAddressArray>
Your xslt 1.0 could look like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="inlineAddressArray">
<xsl:text>"addressLines":"[</xsl:text>
<xsl:apply-templates select="addrline"/>
<xsl:text>]"</xsl:text>
</xsl:template>
<xsl:template match="addrline">
<xsl:value-of select="."/>
<xsl:if test=" following-sibling::addrline[(normalize-space(.))]">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="addrline[not(normalize-space(.))]"/>
</xsl:stylesheet>

XSLT Comma separating list, without grouping

I'm trying to convert the following XML document:
<method>
<desc_signature>
<desc_name>
Some method name
</desc_name>
</desc_signature>
<desc_content>
<paragraph>Paragraph1</paragraph>
<image>Image1</image>
<paragraph>Paragraph2</paragraph>
<literal_block>Codesnippet1</literal_block>
<image>Image2</image>
<paragraph>Paragraph3</paragraph>
<image>Image3</image>
<literal_block>Codesnippet2</literal_block>
</desc_content>
</method>
To the following JSON format:
{
"title":"Some method name",
"elements":[
"Paragraph1",
"Paragraph2",
"Codesnippet1",
"Paragraph3",
"Codesnippet2",
]
}
What I basically want to achieve, is to comma separate a list, but only include paragraph and literal_blocks, and still keeping the original order.
My first attempt was an XSLT that looks like this, with this union selector:
<xsl:for-each select="desc_content/paragraph|desc_content/literal_block">
Like this:
<!-- Method template -->
<xsl:template name="method">
{
"title":"<xsl:value-of select="normalize-space(desc_signature/desc_name)" />",
"elements":[
<xsl:for-each select="desc_content/paragraph|desc_content/literal_block">
<xsl:choose>
<xsl:when test="self::paragraph">
<xsl:call-template name="paragraph"/>
</xsl:when>
<xsl:when test="self::literal_block">
<xsl:call-template name="code"/>
</xsl:when>
</xsl:choose>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
]
}
</xsl:template>
However, it seems like it is grouping paragraphs and then literal_blocks:
{
"title":"Some method name",
"elements":[
"Paragraph1",
"Paragraph2",
"Paragraph3",
"Codesnippet1",
"Codesnippet2",
]
}
Another approach was to select all:
<xsl:for-each select="desc_content/*">
That, however, yields too many commas, as position() accounts for all elements, even if not used:
{
"title":"Some method name",
"elements":[
"Paragraph1",,
"Paragraph2",
"Codesnippet1",,
"Paragraph3",,
"Codesnippet2",
]
}
How would I be able to achieve the desired behaviour?
Thanks for the help!
EDIT - XSLT 1.0 solution:
I've solved the issue with the solution from #Christian Mosz, using apply-templates and checking following-sibling:
<!-- Method template -->
<xsl:template name="method">
{
"title":"<xsl:value-of select="normalize-space(desc_signature/desc_name)" />",
"elements":[
<xsl:apply-templates select="desc_content/*"/>
]
}
</xsl:template>
<!-- Paragraph template -->
<xsl:template match="paragraph">
{"paragraph":"<xsl:value-of select="normalize-space()"/>"}
<xsl:if test="following-sibling::paragraph|following-sibling::literal_block">,</xsl:if>
</xsl:template>
<!-- Code snippet template -->
<xsl:template match="literal_block">
{"code":"<xsl:call-template name="code"/>"}
<xsl:if test="following-sibling::paragraph|following-sibling::literal_block">,</xsl:if>
</xsl:template>
For XSLT 3.0, please check approved answer.
Using XSLT 3 (e.g. with Saxon 9.8 or later or AltovaXML 2017 R3 or later) you have two options, you can either construct an XPath 3.1 map:
map {
'title' : normalize-space(desc_signature/desc_name),
'elements' : array {
data(desc_content/(paragraph | literal_block))
}
}
i.e.
<?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:output method="json" indent="yes" />
<xsl:template match="method">
<xsl:sequence
select="map {
'title' : normalize-space(desc_signature/desc_name),
'elements' : array {
data(desc_content/(paragraph | literal_block))
}
}"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pNmC4HJ
or you transform to the XML representation of JSON the xml-to-json functions supports:
<?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"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="text" indent="yes" />
<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="method">
<map>
<string key="title">{normalize-space(desc_signature/desc_name)}</string>
<array key="elements">
<xsl:apply-templates select="desc_content/(paragraph | literal_block)"/>
</array>
</map>
</xsl:template>
<xsl:template match="desc_content/*">
<string>{.}</string>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pNmC4HJ/1

how to apply parallel template definitions in XSLT 2.0

I'm very new to XSLT, and this sample looked like a great way to learn, but for the life of me can't get it to work. I'm trying to tokenize 2 different strings in parallel and combine them into a JSON array. So I want to tokenize measTypes[0] and match to measResults[0], then measTypes[1] and match to measResults[1], and so on
Sample XML
<?xml version="1.0" encoding="UTF-8"?>
<measInfo measInfoId="1542455297">
<measTypes>1542455297 1542455298 1542455299 1542455300 1542455301 1542455302 1542455303 1542455304 1542455305 1542455306 1542455307 1542460296 1542460297 </measTypes>
<measValue measObjLdn="LTHAB0113422/ETHPORT:Cabinet No.=0, Subrack No.=1, Slot No.=7, Port No.=0, Subboard Type=BASE_BOARD">
<measResults>116967973 585560 496041572 682500 0 12583680 72080 520454 46670568 73432 2205837 1000000 1000000 </measResults>
</measValue>
<measValue measObjLdn="LTHAB0113422/ETHPORT:Cabinet No.=0, Subrack No.=1, Slot No.=7, Port No.=1, Subboard Type=BASE_BOARD">
<measResults>0 0 0 0 0 0 0 0 0 0 0 0 0 </measResults>
</measValue>
</measInfo>
XSLT 2.0 that I have so far
<?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="text" encoding="UTF-8" media-type="text/plain"/>
<xsl:strip-space elements="*"/>
<!--
<xsl:template match="/measInfo/measValue/measResults" name="measRes">
<xsl:for-each select=".">
{'measResult':{<xsl:value-of select="tokenize(normalize-space(.),'\s+')"/>}
</xsl:for-each>
</xsl:template>
-->
<xsl:template match="/" name="measObj">
[
<xsl:for-each select="measInfo/measValue">
'measObjLdn':'<xsl:value-of select="#measObjLdn"/>'
<xsl:call-template name="types"/>
</xsl:for-each>
]
<xsl:apply-templates />
</xsl:template>
<xsl:template match="/measInfo" name="types" >
'Metrics':[
<xsl:for-each select="tokenize(normalize-space(measTypes),'\s+')">
{'measType':'<xsl:value-of select="."/>'},<!-- <xsl:call-template name="measRes"/> -->
</xsl:for-each>
]
</xsl:template>
</xsl:stylesheet>
The JSON results I'm hoping for
[
'measObjLdn':"LTHAB0113422/ETHPORT:Cabinet No.=0, Subrack No.=1, Slot No.=7, Port No.=0, Subboard Type=BASE_BOARD",
'Metrics':[
{'measType':'1542455297','measResult':116967973},
{'measType':'1542455298','measResult':585560},
{'measType':'1542455299','measResult':496041572},
{'measType':'1542455300','measResult':682500},
{'measType':'1542455301','measResult':0},
{'measType':'1542455302','measResult':12583680},
{'measType':'1542455303','measResult':72080},
{'measType':'1542455304','measResult':520454},
{'measType':'1542455305','measResult':46670568},
{'measType':'1542455306','measResult':73432},
{'measType':'1542455307','measResult':2205837},
{'measType':'1542460296','measResult':1000000},
{'measType':'1542460297','measResult':1000000}
]
'measObjLdn':"LTHAB0113422/ETHPORT:Cabinet No.=0, Subrack No.=1, Slot No.=7, Port No.=1, Subboard Type=BASE_BOARD",
'Metrics':[
{'measType':'1542455297','measResult':0},
{'measType':'1542455298','measResult':0},
{'measType':'1542455299','measResult':0},
{'measType':'1542455300','measResult':0},
{'measType':'1542455301','measResult':0},
{'measType':'1542455302','measResult':0},
{'measType':'1542455303','measResult':0},
{'measType':'1542455304','measResult':0},
{'measType':'1542455305','measResult':0},
{'measType':'1542455306','measResult':0},
{'measType':'1542455307','measResult':0},
{'measType':'1542460296','measResult':0},
{'measType':'1542460297','measResult':0}
]
]
XPath 3.0 (and therefore XSLT 3.0) has features you can combine to do just this:
<xsl:value-of select="for-each-pair(tokenize(measTypes), tokenize(measInfo/measValue), function($t, $v) {
serialize(map{'measType':$t, 'measValue':$v}, map{'method':'json'})
})" separator=",&#a;"/>
Here is one way to process two sequences pairwise:
<?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="2.0">
<xsl:strip-space elements="*"/>
<xsl:output media-type="text"/>
<xsl:template match="measInfo">
<xsl:variable name="types" select="tokenize(normalize-space(measTypes), '\s+')"/>
[
<xsl:apply-templates select="measValue">
<xsl:with-param name="types" select="$types"/>
</xsl:apply-templates>
]
</xsl:template>
<xsl:template match="measValue">
<xsl:param name="types"/>
{ 'measObjLdn' : '<xsl:value-of select="#measObjLdn"/>' },
<xsl:variable name="values" select="tokenize(normalize-space(), '\s+')"/>
'Metrics' :
[
<xsl:for-each select="$types">
<xsl:variable name="pos" select="position()"/>
<xsl:if test="$pos > 1">,
</xsl:if>
{ 'measTypes' : '<xsl:value-of select="."/>', 'measResult' : '<xsl:value-of select="$values[$pos]"/>' }
</xsl:for-each>
]
</xsl:template>
</xsl:stylesheet>
The output is not well-formatted but you should be able to improve that using xsl:text yourself.

How to call template with name as a variable in XSLT?

I have the following XML document which needs to be parsed with an XSLT to HTML.
<root>
<c>
<c1>
<id>1</id>
<text>US</text>
</c1>
<c1>
<id>2</id>
<text>UK</text>
</c1>
</c>
</root>
The XSLT for converting this to HTML is given below.
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<xsl:template match="root">
<html>
<xsl:for-each select="c/c1">
**<xsl:variable name="vTemplate" select="text"/>
<xsl:apply-templates select="$vTemplate[#name='text'"/>**
</xsl:for-each>
</html>
</xsl:template>
<xsl:template match="xsl:template[#name='text']" name="text">
<select>
<xsl:attribute name="id">
<xsl:value-of select="id"/>
</xsl:attribute>
</select>
</xsl:template>
</xsl:stylesheet>
I need to call a template depends up on the text field. So for the value US, one template will be executed and for UK, another will executed.
How to achieve this with a variable as a template name while calling the template? I just made a try but it gives error. Can someone help me to figure out where i made wrong?
I think it is not possible to choose name of template to be called dynamically. What could be done is xsl:choose utilization (perhaps with combination with mode attribute), like this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<html>
<xsl:for-each select="c/c1">
<xsl:choose>
<xsl:when test="text = 'US'">
<xsl:apply-templates select="text" mode="US"/>
</xsl:when>
<xsl:when test="text = 'UK'">
<xsl:apply-templates select="text" mode="UK"/>
</xsl:when>
<xsl:otherwise>
<xsl:comment>Something's wrong</xsl:comment>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</html>
</xsl:template>
<xsl:template match="text" mode="US">
<xsl:comment>US mode</xsl:comment>
<select>
<xsl:attribute name="id">
<xsl:value-of select="preceding-sibling::id"/>
</xsl:attribute>
</select>
</xsl:template>
<xsl:template match="text" mode="UK">
<xsl:comment>UK mode</xsl:comment>
<select>
<xsl:attribute name="id">
<xsl:value-of select="preceding-sibling::id"/>
</xsl:attribute>
</select>
</xsl:template>
</xsl:stylesheet>
Or you can use match with appropriate predicate and avoid for-each like this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<html>
<xsl:apply-templates select="//c1" />
</html>
</xsl:template>
<xsl:template match="c1[text = 'US']">
<xsl:comment>US mode</xsl:comment>
<select id="{id}" />
</xsl:template>
<xsl:template match="c1[text = 'UK']">
<xsl:comment>UK mode</xsl:comment>
<select id="{id}" />
</xsl:template>
</xsl:stylesheet>
The id attribute of select can be also filled by "Attribute value templates" (xpath in curly brackets) as shown in previous sample.