XSLT | Json-xml conversion | remove few elements from the json array - xslt

Try to convert Json to xml using xslt, the Json is having an array. I want only few elements of that array in my xml file.
But the value of the elements left out elements are also getting added in the xml.
Input Json:
{
"a": "v1",
"arr": [
{
"ar1": "av1",
"ar2": "av2",
"ar3": "av3",
"ar4": "av4",
"ar5": "av5",
"ar6": "av6"
}
],
"b": "v2",
"c": "v3"
}
xslt used for conversion:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="fn" expand-text="yes">
<xsl:strip-space elements="*" />
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
<xsl:param name="jsonText" />
<xsl:param name="json" select ="parse-json($jsonText)"/>
<xsl:template name="init">
<body>
<envelope>
<dataset>
<AA>{$json?a}</AA>
<xsl:apply-templates select="$json => serialize(map { 'method' : 'json'} ) => json-to-xml()"/>
</dataset>
</envelope>
</body>
</xsl:template>
<xsl:template match="fn:array[#key = 'arr']">
<xsl:iterate select="*">
<arr>
<ar1>{fn:string[#key = 'ar1']}</ar1>
<ar2>{fn:string[#key = 'ar2']}</ar2>
</arr>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
Output I got using the above xslt:
<body>
<envelope>
<dataset>
<AA>v1</AA>
<arr>
<ar1>av1</ar1>
<ar2>av2</ar2>
</arr>v1v2v3
</dataset>
</envelope>
</body>
expected output:
<body>
<envelope>
<dataset>
<AA>v1</AA>
<arr>
<ar1>av1</ar1>
<ar2>av2</ar2>
</arr>
</dataset>
</envelope>
</body>

It seems instead of using <xsl:apply-templates select="$json => serialize(map { 'method' : 'json'} ) => json-to-xml()"/> you could just use
<arr>
<ar1>{$json?arr?*?ar1}</ar1>
<ar2>{$json?arr?*?ar2}</ar2>
</arr>
Online sample here.
If you really want to go the route of using both XPath 3.1 selection into the XDM maps/arrays as well as to work with the json-to-xml conversion then I think you want to change <xsl:apply-templates select="$json => serialize(map { 'method' : 'json'} ) => json-to-xml()"/> to e.g. <xsl:apply-templates select="($json => serialize(map { 'method' : 'json'} ) => json-to-xml())//fn:array[#key = 'arr']"/> or to declare <xsl:mode on-no-match="shallow-skip"/>.

Related

XSLT - Get data from HTTP Json endpoint and transform it

I am trying to retrieve JSON data from the HTTP endpoint and use that values in XSLT.
Need help to retrieve data from HTTP URL of JSON data and transform it.
//sample json
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
// xslt
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="*">
<person>
<xsl:variable name="title" select="json-to-xml(document('https://jsonplaceholder.typicode.com/todos/1'))/title"/>
<xsl:element name="Field">
<xsl:attribute name="name">Title</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="$title"/>
</xsl:attribute>
</xsl:element>
</person>
</xsl:template>
</xsl:stylesheet> ```
Getting error like Error: org.xml.sax.SAXParseException; systemId: https://jsonplaceholder.typicode.com/todos/1; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
To load JSON, you can use json-doc e.g. json-doc('https://jsonplaceholder.typicode.com/todos/1'). That gives you an XDM map for your sample, you can process it with XPath 3.1 e.g.
<xsl:variable name="json" select="json-doc('https://jsonplaceholder.typicode.com/todos/1')"/>
<person>
<Field name="Title" value="{$json?title}"/>
</person>

XSLT: Convert JSON to XML, then transform, in one XSLT

I'm trying to convert JSON to a specific XML format, all in one XSLT. (It doesn't have to be in one step, but, you know,...)
I can convert the JSON to generic XML from here: How to use XPath/XSLT fn:json-to-xml
Converting the resultant generic XML to the XML I want is then simple.
But I can't work out how to combine the XSLTs so I can do it in one step, do JSON-to-XML and then the XML transformation. I've tried with variables, include, import, but can't get it to work.
I suspect it's straightforward! It needs to be in (just) XSLT.
So, from the question linked to above, I start with JSON (in XML tags)
<root>
<data>{
"desc" : "Distances between several cities, in kilometers.",
"updated" : "2014-02-04T18:50:45",
"uptodate": true,
"author" : null,
"cities" : {
"Brussels": [
{"to": "London", "distance": 322},
{"to": "Paris", "distance": 265},
{"to": "Amsterdam", "distance": 173}
],...
and transform to
<map xmlns="http://www.w3.org/2005/xpath-functions">
<string key="desc">Distances between several cities, in kilometers.</string>
<string key="updated">2014-02-04T18:50:45</string>
<boolean key="uptodate">true</boolean>
<null key="author"/>
<map key="cities">
<array key="Brussels">
<map>
<string key="to">London</string>
<number key="distance">322</number>
</map>
<map>
<string key="to">Paris</string>
<number key="distance">265</number>
</map>...
using
<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:output indent="yes"/>
<xsl:template match="data">
<xsl:copy-of select="json-to-xml(.)"/>
</xsl:template>
</xsl:stylesheet>
Now I can apply this stylesheet to the 'intermediate' XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.w3.org/2005/xpath-functions">
<xsl:output indent="yes"/>
<xsl:template match="/">
<Distances>
<xsl:for-each select="f:map/f:map/f:array">
<Start>
<StartPoint><xsl:value-of select="#key"/></StartPoint>
<xsl:for-each select="f:map">
<Distance>
<xsl:attribute name="end"><xsl:value-of select="f:string"/></xsl:attribute>
<xsl:attribute name="value"><xsl:value-of select="f:number"/></xsl:attribute>
</Distance>
</xsl:for-each>
</Start>
</xsl:for-each>
</Distances>
</xsl:template>
</xsl:stylesheet>
and get my desired structure:
<?xml version="1.0" encoding="UTF-8"?>
<Distances xmlns:f="http://www.w3.org/2005/xpath-functions">
<Start>
<StartPoint>Brussels</StartPoint>
<Distance end="London" value="322"/>
<Distance end="Paris" value="265"/>
<Distance end="Amsterdam" value="173"/>
</Start>...
So, is it possible to combine the JSON-to-XML and the XML transformation XSLs in one?
I am guessing you want to do:
XSLT 3.0
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="f">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/root">
<Distances>
<xsl:for-each select="json-to-xml(data)/f:map/f:map/f:array">
<Start>
<StartPoint>
<xsl:value-of select="#key"/>
</StartPoint>
<xsl:for-each select="f:map">
<Distance end="{f:string}" value="{f:number}"/>
</xsl:for-each>
</Start>
</xsl:for-each>
</Distances>
</xsl:template>
</xsl:stylesheet>
Untested, because no code suitable for testing was provided.
To do it the way you were proposing, you can do
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)"/>
</xsl:template>
and then add template rules to transform the generic XML produced by json-to-xml() to your application-specific XML.
But I think the approach suggested by #michael.hor257k is probably better.

How to create 1 group to sum 4 elements with xslt

I am trying to use muenchian to do a group/sum, but I can't make it work.
Any help available?! :)
XML I have:
<RLA760910>
<G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>401100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>-20</SumBegBalance>
<SumDebitPeriod>10</SumDebitPeriod>
<SumCreditPeriod>-5</SumCreditPeriod>
<SumEndBalance>-15</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
<G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>401100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>100</SumBegBalance>
<SumDebitPeriod>10</SumDebitPeriod>
<SumCreditPeriod>-5</SumCreditPeriod>
<SumEndBalance>105</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>411100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>-30</SumBegBalance>
<SumDebitPeriod>5</SumDebitPeriod>
<SumCreditPeriod>-10</SumCreditPeriod>
<SumEndBalance>-35</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>451100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>80</SumBegBalance>
<SumDebitPeriod>20</SumDebitPeriod>
<SumCreditPeriod>-10</SumCreditPeriod>
<SumEndBalance>90</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
</RLA760910>
I've so far:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
<xsl:key name="AcctSub"
match="/RLA760910/G_L_By_Object_Account___Localization_S10"
use="#Zsz_ObjectAcctSub_OSBOW_ID3" />
<xsl:template match="Zsz_ObjectAcctSub_OSBOW_ID3">
<result>
<!-- Match the first acct element for a specific group -->
<xsl:apply-templates select="/RLA760910/G_L_By_Object_Account___Localization_S10/[generate-id() = generate-id(key('AcctSub', #Zsz_ObjectAcctSub_OSBOW_ID3)[1])]" />
</result>
</xsl:template>
<xsl:template match="/RLA760910/G_L_By_Object_Account___Localization_S10">
<total type="{#Zsz_ObjectAcctSub_OSBOW_ID3}">
<!-- Sum all the elements from the #type group -->
<xsl:value-of select="sum(key('AcctSub', #Zsz_ObjectAcctSub_OSBOW_ID3)/#Zsz_ObjectAcctSub_OSBOW_ID3)" />
</total>
</xsl:template>
</xsl:stylesheet>
Expected result will sum
SumBegBalance, SumDebitPeriod, SumCreditPeriod, SumEndBalance Group BY Zsz_ObjectAcctSub_OSBOW_ID3
Use the following script:
<?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" omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="AcctSub"
match="G_L_By_Object_Account___Localization_S10"
use="Zsz_ObjectAcctSub_OSBOW_ID3"/>
<xsl:template match="RLA760910">
<xsl:copy>
<xsl:for-each select="*[generate-id()=generate-id(
key('AcctSub', Zsz_ObjectAcctSub_OSBOW_ID3)[1])]">
<result>
<xsl:variable name="Objects" select="key('AcctSub',
Zsz_ObjectAcctSub_OSBOW_ID3)"/>
<Zsz_ObjectAcctSub_OSBOW_ID3>
<xsl:value-of select="Zsz_ObjectAcctSub_OSBOW_ID3"/>
</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>
<xsl:value-of select="sum($Objects/SumBegBalance)"/>
</SumBegBalance>
<SumDebitPeriod>
<xsl:value-of select="sum($Objects/SumDebitPeriod)"/>
</SumDebitPeriod>
<SumCreditPeriod>
<xsl:value-of select="sum($Objects/SumCreditPeriod)"/>
</SumCreditPeriod>
<SumEndBalance>
<xsl:value-of select="sum($Objects/SumEndBalance)"/>
</SumEndBalance>
</result>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
A few notes:
Zsz_ObjectAcctSub_OSBOW_ID3 is not an atribute but an element, so #
is not needed.
As the name of the root tag I used the same name as in your input.
Each result output tag contains Zsz_ObjectAcctSub_OSBOW_ID3 tag
(the grouping key) and sums of your 4 tags (within the group).
Try this as your starting point:
XSLT 1.0
<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:key name="grp" match="G_L_By_Object_Account___Localization_S10" use="Zsz_ObjectAcctSub_OSBOW_ID3" />
<xsl:template match="/RLA760910">
<result>
<xsl:for-each select="G_L_By_Object_Account___Localization_S10[generate-id() = generate-id(key('grp', Zsz_ObjectAcctSub_OSBOW_ID3)[1])]" >
<group type="{Zsz_ObjectAcctSub_OSBOW_ID3}">
<total-of-begbalance>
<xsl:value-of select="sum(key('grp', Zsz_ObjectAcctSub_OSBOW_ID3)/SumBegBalance)"/>
</total-of-begbalance>
<!-- add more totals here-->
</group>
</xsl:for-each>
</result>
</xsl:template>
</xsl:stylesheet>
When this is applied to the following well-formed example input:
XML
<RLA760910>
<G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>401100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>-20</SumBegBalance>
<SumDebitPeriod>10</SumDebitPeriod>
<SumCreditPeriod>-5</SumCreditPeriod>
<SumEndBalance>-15</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
<G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>401100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>100</SumBegBalance>
<SumDebitPeriod>10</SumDebitPeriod>
<SumCreditPeriod>-5</SumCreditPeriod>
<SumEndBalance>105</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
<G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>411100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>-30</SumBegBalance>
<SumDebitPeriod>5</SumDebitPeriod>
<SumCreditPeriod>-10</SumCreditPeriod>
<SumEndBalance>-35</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
<G_L_By_Object_Account___Localization_S10>
<Zsz_ObjectAcctSub_OSBOW_ID3>451100.900</Zsz_ObjectAcctSub_OSBOW_ID3>
<SumBegBalance>80</SumBegBalance>
<SumDebitPeriod>20</SumDebitPeriod>
<SumCreditPeriod>-10</SumCreditPeriod>
<SumEndBalance>90</SumEndBalance>
</G_L_By_Object_Account___Localization_S10>
</RLA760910>
the result will be:
<?xml version="1.0" encoding="utf-8"?>
<result>
<group type="401100.900">
<total-of-begbalance>80</total-of-begbalance>
</group>
<group type="411100.900">
<total-of-begbalance>-30</total-of-begbalance>
</group>
<group type="451100.900">
<total-of-begbalance>80</total-of-begbalance>
</group>
</result>
Note that your expressions using #Zsz_ObjectAcctSub_OSBOW_ID3 fail because Zsz_ObjectAcctSub_OSBOW_ID3 is an element, not an attribute.

Dynamic variable access within XSLT

I would like to define an xslt sheet where variables and access functions are defined. The sheet should be then used within another stylesheet. The main idea is to have single point of defining key value pairs for interface mapping tasks. The following code does not produce a result.
EDIT: The real world problem is a system integration problem. Think of a "type"-property. In System A the type "Car" is encoded with the key "1". In System B (where i have to import a message from System A) the type "Car" is encoded with the key "X". Example: 1 <-> Car <-> X. So when i map messages from System A to messages from System B i need kind of a map. I would like to define this map at a single place (single XSLT sheet) and use it in multiple other XSLT sheet (via incluce command). As long with the map i also would like to define convenient access functions like this: getTargetKey(mapWhereTheKeyIsIn, 'sourceKey'). So where ever i have to map message from System A into the schema of System B i would like to type:
<Type><xsl:value-of select="getTargetKey(typeMap, '1')" /><Type> and get <Type>X</Type>
<?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"/>
<xsl:variable name="var1">
<Entry key="1" value="ABC" />
<Entry key="2" value="CDF" />
</xsl:variable>
<xsl:template match="/">
<xsl:value-of select="myFunc('var1', '1')" />
</xsl:template>
<xsl:function name="myFunc">
<xsl:param name="variable_name"/>
<xsl:param name="key"/>
<xsl:value-of select="document('')/*/xsl:variable[#name=$variable_name]/Entry[#key = $key]/#value"/>
</xsl:function>
</xsl:stylesheet>
Sample Input
<?xml version="1.0" encoding="UTF-8"?>
<order_system_a type="1" />
Desired Output
<?xml version="1.0" encoding="UTF-8"?>
<order_system_b type="X" />
Here is the requested generic solution to the problem:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="my xs">
<xsl:import href="C:/temp/delete/GlobalTypeMappings.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="order_system_a">
<order_system_b>
<xsl:apply-templates select="#*"/>
</order_system_b>
</xsl:template>
<xsl:template match="order_system_a/#type">
<xsl:attribute name="type" select=
"my:convertType('order_system_a', 'order_system_b', .)"/>
</xsl:template>
</xsl:stylesheet>
C:/temp/delete/GlobalTypeMappings.xsl:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="my xs">
<xsl:key name="kMapFromLocal" match="map"
use="concat(#system, '+', #local-type)"/>
<xsl:key name="kMapFromValue" match="map"
use="concat(#system, '+', #value)"/>
<my:TypeMappings>
<map system="order_system_a" local-type="1" value="type1"/>
<map system="order_system_a" local-type="2" value="type2"/>
<map system="order_system_a" local-type="3" value="type3"/>
<map system="order_system_a" local-type="4" value="type4"/>
<map system="order_system_b" local-type="X" value="type1"/>
<map system="order_system_b" local-type="Y" value="type2"/>
<map system="order_system_b" local-type="Z" value="type3"/>
<map system="order_system_b" local-type="T" value="type4"/>
<!-- Other type mappings here -->
</my:TypeMappings>
<xsl:function name="my:convertType" as="xs:string?">
<xsl:param name="pfromSystem" as="xs:string"/>
<xsl:param name="ptoSystem" as="xs:string"/>
<xsl:param name="pfromValue" as="xs:string"/>
<xsl:variable name="vthisDoc" select="document('')"/>
<xsl:sequence select=
"key('kMapFromValue',
concat($ptoSystem,
'+',
key('kMapFromLocal',
concat($pfromSystem, '+', $pfromValue),
$vthisDoc
)
/#value
),
$vthisDoc
)
/#local-type
"/>
</xsl:function>
</xsl:stylesheet>
When the above transformation is applied to the provided XML document:
<order_system_a type="1" />
the wanted, correct result is produced:
<order_system_b type="X"/>

attribute value using msxml from xml

I have the following XML
<?xml version="1.0" encoding="ISO-8859-1" ?>
- <DEVICEMESSAGES>
<VERSION xml="1" checksum="" revision="0" envision="33050000" device="" />
<HEADER id1="0001" id2="0001" content="Nasher[<messageid>]: <!payload>" />
<MESSAGE level="7" parse="1" parsedefvalue="1" tableid="15" id1="24682" id2="24682" eventcategory="1003010000" content="Access to <webpage> was blocked due to its category (<info> by <hostname>)" />
</DEVICEMESSAGES>
I am using the following xslt
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="DEVICEMESSAGES">
<xsl:value-of select="#id2"/>,<xsl:text/>
<xsl:value-of select="#content"/>,<xsl:text/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when i use MSXML i just get ,, whereas i want to have something like
id2, content
0001 , Nasher[<messageid>]: <!payload>"
The DEVICEMESSAGES element doesn't have attributes at all.
Change:
<xsl:template match="DEVICEMESSAGES">
to:
<xsl:template match="DEVICEMESSAGES/HEADER">