I have the following code, which I am using to transform a JSON file to another JSON.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:fcn="http://www.re.com/2018/local-functions" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs fn fcn exsl" version="3.0">
<xsl:param as="xs:string" name="json">[{
"results": [
{
"id": "5b33c2e8-8ab2-4314-82bf-e41a007c076f",
"profileId": "0f53bfe5-4ef3-4424-9ad2-ad8181007e22"
}
],
"_embedded": {
},
"paging": {
"pageNumber": 1,
"pageSize": 10,
"totalCount": 4
}
}]</xsl:param>
<xsl:output indent="yes" method="text" />
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="input-as-xml" select="json-to-xml($json)" />
<xsl:variable name="transformed-xml">
<array xmlns="http://www.w3.org/2005/xpath-functions">
<xsl:for-each select="fn:array[#key = 'results']/*">
<map>
<xsl:if test="*[#key = 'id'] != ''">
<string key="id">
<xsl:value-of select="fn:string[#key = 'id']" />
</string>
</xsl:if>
</map>
</xsl:for-each>
</array>
</xsl:variable>
<xsl:value-of select="xml-to-json($transformed-xml, map {'indent': true()})" />
</xsl:template>
</xsl:stylesheet>
I am trying to match the results object, but when I apply the stylesheet, I am getting an empty JSON output.
Some help with matching the results object correctly would be appreciated.
The input-as-xml:
<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
<map>
<array key="results">
<map>
<string key="id">5b33c2e8-8ab2-4314-82bf-e41a007c076f</string>
<string key="profileId">0f53bfe5-4ef3-4424-9ad2-ad8181007e22</string>
</map>
</array>
<map key="_embedded"/>
<map key="paging">
<number key="pageNumber">1</number>
<number key="pageSize">10</number>
<number key="totalCount">4</number>
</map>
</map>
</array>
For starters, I think the xsl:for-each should be something like <xsl:for-each select="$input-as-xml/fn:array[#key = 'results']/*">, so you're selecting within the right context.
But I don't think that's quite enough: The XML that comes from the conversion will contain an <fn:array> whose first child will be an <fn:map> which will have an entry of the form <fn:array key="results">, so you need to select deeper into the structure.
It's probably a good idea to print out the value of $input-as-xml (in indented form) for ease of diagnostics.
Related
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.
I have the following XML message, which I am trying to go through via an XSL stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
<map>
<array key="results">
<map>
<string key="id">5b33c2e8-8ab2-4314-82bf-e41a007c076f</string>
<string key="profileId">0f53bfe5-4ef3-4424-9ad2-ad8181007e22</string>
</map>
</array>
<map key="_embedded">
<array key="individuals">
<map>
<string key="id">0f53bfe5-4ef3-4424-9ad2-ad8181007e22</string>
<string key="name">John</number>
</map>
</array>
</map>
<map key="paging">
<number key="pageNumber">1</number>
<number key="pageSize">10</number>
<number key="totalCount">4</number>
</map>
</map>
</array>
My code is like the following:
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="input-as-xml" select="json-to-xml($json)"/>
<xsl:variable name="transformed-xml">
<array xmlns="http://www.w3.org/2005/xpath-functions">
<xsl:for-each select="$input-as-xml/fn:array/fn:map/fn:array[#key = 'results']/*">
<map xmlns="http://www.w3.org/2005/xpath-functions">
<xsl:variable name="profileId">
<xsl:value-of select="*[#key = 'profileId']"/>
</xsl:variable>
<xsl:if test="*[#key = 'id'] != ''">
<string key="id">
<xsl:value-of select="*[#key = 'id']"/>
</string>
</xsl:if>
<xsl:for-each select="../fn:map[#key = '_embeded']/fn:array[#key = 'individuals'][#id=$profileId]/*">
<array key="profiles">
<string key="id">
<xsl:value-of select="fn:map/*[#key = 'id']"/>
</string>
</array>
</xsl:for-each>
</map>
</xsl:for-each>
</array>
</xsl:variable>
<xsl:value-of select="xml-to-json($transformed-xml, map {'indent': true()})"/>
</xsl:template>
What I want to do is map the individuals/id with the results/profileId, but apparently the way I am matching the individuals is not correct.
I would need some help with matching correctly.
Based on your comment
the individuals array has a <string key="id"> , basically I want to
select values only if the id within the array has the value equal to
profileId
you might want to select fn:array[#key = 'individuals'][.//fn:string[#id = $profileId]], that is, an array having a descendant string with the matching id.
It is hard to give a more complete example as the input for instance has <string key="name">John</number> which is not well-formed.
As in previous questions, I think moving to template based matching instead of nested for-eachs in a single template would make the code more managable and easier to structure, read and maintain.
The below xsl works fine if I do not bring in the "other_location_postal_code" field, which is commented here.
This is because there are multiple records if I bring in that field.
How can I have this xsl evaluate each record so it would write this record twice, once for the one "other location postal code" and the other?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:e="http://www.taleo.com/ws/tee800/2009/01" xmlns:fct="http://www.taleo.com/xsl_functions" exclude-result-prefixes="e fct">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="no"/>
<xsl:param name="OUTBOUND_FOLDER"/>
<xsl:template match="/">
<source>
<xsl:apply-templates select="//e:Requisition"/>
</source>
</xsl:template>
<xsl:template match="e:Requisition">
<xsl:variable name="job_id" select="e:ContestNumber"/>
<xsl:variable name="other_location_postal_code" select="e:JobInformation/e:JobInformation/e:OtherLocations/e:Location/e:NetworkLocation/e:NetworkLocation/e:ZipCode"/>
<job>
<job_id>
<xsl:value-of select="concat('<','![CDATA[',$job_id,']]','>')"/>
</job_id>
<other_location_postal_code>
<xsl:value-of select="concat('![CDATA[',$other_location_postal_code,']]')"/>
</other_location_postal_code>
</job>
</xsl:template>
</xsl:stylesheet>
I want it to come out like so:
<?xml version="1.0" encoding="UTF-8"?>
<source>
<job>
<job_id><![CDATA[15000005]]></job_id>
<other_location_postal_code><![CDATA[77382]]></other_location_postal_code>
</job>
<job>
<job_id><![CDATA[15000005]]></job_id>
<other_location_postal_code><![CDATA[37567]]></other_location_postal_code>
</job>
</source>
The initial XML looks like so:
<?xml version="1.0" encoding="UTF-8"?>
-<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
-<soapenv:Body>
-<ns1:getDocumentByKeyResponse xmlns:ns1="http://www.taleo.com/ws/integration/toolkit/2005/07" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
-<Document xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07">
-<Attributes>
<Attribute name="count">1</Attribute>
<Attribute name="duration">0:00:00.088</Attribute>
<Attribute name="entity">Requisition</Attribute>
<Attribute name="mode">T-XML</Attribute>
<Attribute name="version">http://www.taleo.com/ws/tee800/2009/01</Attribute>
</Attributes>
-<Content>
-<ExportTXML xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07" xmlns:e="http://www.taleo.com/ws/tee800/2009/01">
-<e:Requisition>
<e:ContestNumber>15000005</e:ContestNumber>
-<e:JobInformation>
-<e:JobInformation>
-<e:OtherLocations>
-<e:Location>
<e:ZipCode>77002</e:ZipCode>
</e:Location>
-<e:Location>
<e:ZipCode>77050</e:ZipCode>
</e:Location>
</e:OtherLocations>
</e:JobInformation>
</e:JobInformation>
</e:Requisition>
</ExportTXML>
</Content>
</Document>
</ns1:getDocumentByKeyResponse>
</soapenv:Body>
</soapenv:Envelope>
The error message simply says that an argument to the concat function is a sequence of more than one node. So some of your variable selects two or more nodes and concat expects as single node for each of its arguments. You will need to decide what you want to output, either only the first node with $var[1] or the concatenation with string-join($var, ' ').
Note that you don't need all those attempts to output CDATA section, you can tell the XSLT processor the cdata-section-elements on the xsl:output direction.
As you have now posted more details here is one suggestion to map each ZipCode element to job element:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:e="http://www.taleo.com/ws/tee800/2009/01"
exclude-result-prefixes="e">
<xsl:output indent="yes" cdata-section-elements="job_id other_location_postal_code"/>
<xsl:template match="/">
<source>
<xsl:apply-templates select="//e:OtherLocations/e:Location/e:ZipCode"/>
</source>
</xsl:template>
<xsl:template match="e:ZipCode">
<xsl:apply-templates select="ancestor::e:Requisition">
<xsl:with-param name="zc" select="current()"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="e:Requisition">
<xsl:param name="zc"/>
<job>
<job_id><xsl:value-of select="e:ContestNumber"/></job_id>
<other_location_postal_code><xsl:value-of select="$zc"/></other_location_postal_code>
</job>
</xsl:template>
</xsl:stylesheet>
I have source xml looking like this :
<Data>
<ActionPlaces>
<ActionPlace>
<ActionPlaceID>74</ActionPlaceID>
<PlaceName>Theatre Of Classic</PlaceName>
</ActionPlace>
</ActionPlaces>
<Actions>
<CommonAction Id="2075" Name="King">
<Action>
<ActionID>4706</ActionID>
<ActionPlaceID>74</ActionPlaceID>
</Action>
</CommonAction>
</Actions>
</Data>
Which is to transform to this:
<category name="King">
<name>King</name>
<parent name="Theatre Of Classic" />
</category>
I want to use variable :
<xsl:template match="ActionPlaces">
<xsl:variable name="id" select="/ActionPlace/ActionPlaceID"/>
<xsl:template match="CommonAction" >
<category name="<xsl:value-of select="#name"/> >
<name><xsl:value-of select="#name"/></name>
<parent <xsl:if test="/Action/ActionPlaceID = $id">
name=/Action/ActionPlaceID/> <- how to get name of theatre here?
</xsl:template>
Can variable store not only id but name also? And how to get it? What is the most common approach to handle this ?
Here's one option using XSL keys (as #michael-hor257k suggested):
Input
<Root>
<ActionPlaces>
<ActionPlace>
<ActionPlaceID>74</ActionPlaceID>
<PlaceName>Theatre Of Classic</PlaceName>
</ActionPlace>
</ActionPlaces>
<Actions>
<CommonAction Id="2075" Name="King">
<Action>
<ActionID>4706</ActionID>
<ActionPlaceID>74</ActionPlaceID>
</Action>
</CommonAction>
</Actions>
</Root>
Stylesheet
<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"/>
<!-- Collect all <ActionPlace> elements into an XSL key -->
<xsl:key name="ActionPlaceById" match="ActionPlace" use="ActionPlaceID"/>
<xsl:template match="/">
<xsl:apply-templates select="Root/Actions/CommonAction"/>
</xsl:template>
<xsl:template match="CommonAction">
<category name="{#Name}">
<name>
<xsl:value-of select="#Name"/>
</name>
<!--
Using the ActionPlaceById key we created earlier, fetch the <ActionPlace>
element that has an <ActionPlaceID> child that has the same value as the
<ActionPlaceID> descendant of the current <CommonAction> element.
-->
<parent name="{key('ActionPlaceById', Action/ActionPlaceID)/PlaceName}"/>
</category>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="utf-8"?>
<category name="King">
<name>King</name>
<parent name="Theatre Of Classic"/>
</category>
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"/>