I am trying to convert the GetCapabilities of the GeoMet WMS XML response.data to a JSON that can be used in a v-treeview Vuetify component such as in this link.
testfunc: function () {
axios.get('https://geo.weather.gc.ca/geomet?lang=en&service=WMS&version=1.3.0&request=GetCapabilities').then((response) => {
const xslt = `<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"
xmlns:mf="http://example.com/mf"
expand-text="yes"
version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output method="json" build-tree="no"/>
<xsl:template match="/Layer" priority="5">
<xsl:map>
<xsl:apply-templates/>
</xsl:map>
</xsl:template>
<xsl:template match="*[not(*)]">
<xsl:map-entry key="local-name()" select="data()"/>
</xsl:template>
<xsl:template match="Layer[1]">
<xsl:map-entry key="'children'">
<xsl:sequence select="array { mf:apply-templates(*) }"/>
</xsl:map-entry>
</xsl:template>
<xsl:template match="Layer[position() > 1]"/>
<xsl:function name="mf:apply-templates" as="item()*">
<xsl:param name="elements" as="element(*)*"/>
<xsl:apply-templates select="$elements"/>
</xsl:function>
</xsl:stylesheet>`
const jsonResult = SaxonJS.XPath.evaluate(`
transform(
map {
'source-node' : parse-xml($xml),
'stylesheet-text' : $xslt,
'delivery-format' : 'raw'
}
)?output`,
[],
{ 'params': { 'xml': response.data, 'xslt': xslt } }
)
console.log(jsonResult)
})
}
My test function returns all the infromations and does not really parse the XML response the way I need it to and I am new to XSLT. I need something that will return only the <Name> and <Title> innerHTML of the <Layer> tags and their children in arrays called children that looks like :
{
title: 'Title Level 1'
name: 'Name Level 1'
children: [
{
title: 'Title Level 2'
name: 'Name Level 2'
children: [
{
title: 'Title Level 3-1'
name: 'Name Level 3-1'
},
{
title: 'Title Level 3-2'
name: 'Name Level 3-2'
}
]
]
}
EDIT : Sample XML of the full XML which has one root with only a title that has 14 groups of
<Layer queryable="1">
<Title>MSC GeoMet — GeoMet-Weather 2.14.1</Title>
<Layer queryable="1">
<Name>Regional Deterministic Prediction System (RDPS) [10 km]</Name>
<Title>Regional Deterministic Prediction System (RDPS) [10 km]</Title>
<Layer queryable="1">
<Name>RDPS - Coupled to Gulf of St. Lawrence (RDPS-CGSL)</Name>
<Title>RDPS - Coupled to Gulf of St. Lawrence (RDPS-CGSL)</Title>
<Layer queryable="1" opaque="0" cascaded="0">
<Name>CGSL.ETA_ICEC</Name>
<Title>CGSL.ETA.ICEC - Ice cover fraction</Title>
...
An XSLT 3 sample to process only Layer elements (in that particular namespace http://www.opengis.net/wms) and the first Title and Name plus recursively the child Layers would be
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
xpath-default-namespace="http://www.opengis.net/wms"
xmlns="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
version="3.0">
<xsl:strip-space elements="*"/>
<xsl:mode on-no-match="shallow-skip"/>
<xsl:output method="json" build-tree="no" indent="yes"/>
<xsl:template match="/WMS_Capabilities/Capability/Layer" priority="5">
<xsl:map>
<xsl:apply-templates/>
</xsl:map>
</xsl:template>
<xsl:template match="Layer/Title[1] | Layer/Name[1]">
<xsl:map-entry key="local-name()" select="data()"/>
</xsl:template>
<xsl:template match="Layer[1]">
<xsl:map-entry key="'children'">
<xsl:sequence select="array { ../Layer/mf:apply-templates(.) }"/>
</xsl:map-entry>
</xsl:template>
<xsl:template match="Layer[position() > 1]"/>
<xsl:function name="mf:apply-templates" as="item()*">
<xsl:param name="elements" as="element(*)*"/>
<xsl:map>
<xsl:apply-templates select="$elements/*"/>
</xsl:map>
</xsl:function>
</xsl:stylesheet>
Related
I am trying to parse nested JSON to CSV, using XSLT transformation.
In this particular case each child object counting from "datasheet", e.g. "result-sheet" and "balance-sheet", should end up in one CSV file (output) each. Currently I am however just elaborating getting out "result-sheet" only.
I noticed that the content of arrays are getting merged togehter.
Data:
<data>
{
"datasheets": {
"result-sheet": {"bank": [1,3], "credit": [2,6]},
"balance-sheet": {"loans": [4,5], "inventory": [9,0]}
}
}
</data>
XSL:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
>
<xsl:output method="text" indent="yes"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)"/>
</xsl:template>
<xsl:template match="*">
<h2>Result sheet</h2>
<xsl:text>
</xsl:text>
<xsl:value-of select="*/(string-join(head(*)/*/#key, ','), *!string-join(*, ','))" separator="
"/>
</xsl:template>
</xsl:stylesheet>
Result:
Result sheet
bank,credit
13,26
45,90
Wanted result:
bank,credit
1, 2,
3, 6
I don't quite understand which data you want to have in each line, the following templates creates a line using for-each-pair on each pair of fn:number elements in the two fn:array children of the fn:map with the #key being result-sheet:
<xsl:template match="*:map[#key = 'result-sheet']">
<xsl:value-of select="for-each-pair(*:array[1]/*:number, *:array[2]/*:number, function($n1, $n2) { $n1 || ', ' || $n2 })"
separator="
"/>
</xsl:template>
I am trying convert XML to Json with some transformation in XSLT 3. I have sample XML as shown below
Sample XML:
<Root>
<Employees xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Employee>
<name>abc</name>
</Employee>
<Employee>
<name>def</name>
</Employee>
<summary>
<Age>15</Age>
<tag1>dd</tag1>
<tag2>dd</tag2>
<tag2>dd</tag2>
</summary>
</Employees>
</Root>
My XSLT template
<?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:strip-space elements="*"/>
<xsl:output method="json" indent="yes"/>
<xsl:template match="/Root">
<xsl:for-each select="Employees">
<xsl:sequence select="map { 'Root' :
array {
Employee[name!=''] ! map {
'Name':data(name),
'Date': format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01].[f0000001]Z'),
'Summary': 'Employee: ' || data(name) ||' Summary : ' || serialize(../summary)
}
}
}"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
{
"Root": [
{
"Summary":"Employee: abc Summary : <summary xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xmlns:xsd=\"http:\/\/www.w3.org\/2001\/XMLSchema\"><Age>15<\/Age><tag1>dd<\/tag1><tag2>dd<\/tag2><tag2>dd<\/tag2><\/summary>",
"Name":"abc",
"Date":"2021-02-08T09:03:22.4740000Z"
},
{
"Summary":"Employee: def Summary : <summary xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xmlns:xsd=\"http:\/\/www.w3.org\/2001\/XMLSchema\"><Age>15<\/Age><tag1>dd<\/tag1><tag2>dd<\/tag2><tag2>dd<\/tag2><\/summary>",
"Name":"def",
"Date":"2021-02-08T09:03:22.4740000Z"
}
]
}
Fiddle:https://xsltfiddle.liberty-development.net/6q1SDkM/5
I am facing issues where the summary node shows the namespaces in output. Looks like namespaces comes from XML tree which is mentioned in Employee node.How i can remove namespace.
Also in date formatting can we get fraction seconds upto 7 digits. Right now i am able to get only milliseconds
As I said in a comment to your previous question, you can push the element through a mode that explicitly doesn't copy namespaces:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output method="json" indent="yes"/>
<xsl:template match="/Root">
<xsl:for-each select="Employees">
<xsl:sequence select="map { 'Root' :
array {
Employee[name!=''] ! map {
'Name':data(name),
'Date': format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01].[f0000001]Z'),
'Summary': 'Employee: ' || data(name) ||' Summary : ' || ../summary => mf:strip-namespaces() => serialize()
}
}
}"/>
</xsl:for-each>
</xsl:template>
<xsl:mode name="strip-namespaces" on-no-match="shallow-copy"/>
<xsl:template mode="strip-namespaces" match="*">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="#* | node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:function name="mf:strip-namespaces" as="node()">
<xsl:param name="node" as="node()"/>
<xsl:apply-templates select="$node" mode="strip-namespaces"/>
</xsl:function>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6q1SDkM/6
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
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>
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.