hello world xml -> json template - xslt-3.0

I'm trying to transform an xml input to a json output. My XSLT 1.0 is pretty proficient my XSLT 2.0/3.0 not so.
I thought I'd start with a hello world style template and build from there.
My belief is that you can simply create an output as map/array data structure and then some magic will map that into the desired output, so this is my first attempt (I've not defined an input, because any old xml will do in this example, it ignores it):
<?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="3.0">
<xsl:output method="json" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="foo">
<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>
<map>
<string key="to">Amsterdam</string>
<number key="distance">173</number>
</map>
</array>
</map>
</map>
</xsl:variable>
<xsl:value-of select="xml-to-json($foo)"/>
</xsl:template>
</xsl:stylesheet>
this almost works but I get a string output...(the '"' chars exist in the output file includeing all the escaping, so not a valid json output).
"{\"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}]}}"
If there are any basic guides to do this, then please let me know, the web is awash with odd examples, out of date instruction based on XSLT 1.0/2.0 or hard to understand pdfs discussing more in depth scenarios.

The function you use already gives you a string with the JSON (see https://www.w3.org/TR/xpath-functions-31/#func-xml-to-json) so if you want to write that to a file just use <xsl:output method="text"/>.
The json output method mainly makes sense if you construct XDM/XPath 3.1 maps/arrays and want to serialize them as JSON.
For your sample I would also use <xsl:template name="xsl:initial-template"> instead of <xsl:template match="/">, then you don't need to provide any dummy input XML at all but can just start with that default named template using e.g. -it from the command line or callTemplate(null, ..) from the API.

Related

How to map repeating xml elements in Excel

I'm dealing with a problem on how to map repeating xml elements, the moment I imported the XML below as an XML map in excel, I see only 1 for the 4 records i need "<club_LIST>" of course this doesn't produce the 4 entries in the output XML.
Any idea how can this be solved in Excel ?
I from Microsoft support:
Additionally, the contents of an XML mapping cannot be exported if the contents contain one of the following XML schema constructs:
List of lists One list of items contains a second list of items.
Any way around you could suggest to produce the xml?
Here below the sample of my data:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<PartnersProfile xmlns:xsi="http://www.w3.org/2001/xmlschema-instance">
<ID>10</ID>
<NAME>Table 10</NAME>
<Record>
<PARTNER_ID>1</PARTNER_ID>
<DESCRIPTION>customer</DESCRIPTION>
<subscription>2004</subscription>
<club_LIST>1</club_LIST>
<club_LIST>4</club_LIST>
<club_LIST>6</club_LIST>
<club_LIST>9</club_LIST>
</Record>
<Record>
<PARTNER_ID>1</PARTNER_ID>
<DESCRIPTION>customer</DESCRIPTION>
<subscription>2004</subscription>
<club_LIST>1</club_LIST>
<club_LIST>4</club_LIST>
<club_LIST>6</club_LIST>
<club_LIST>9</club_LIST>
</Record>
</PartnersProfile>
Consider transforming the XML to an itemized version by each club_LIST with repeating values for ancestor elements. You can run the below XSLT 1.0 by many tools and programming languages including Perl (from your profile) or Excel VBA.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/PartnersProfile">
<xsl:copy>
<xsl:apply-templates select="descendant::club_LIST"/>
</xsl:copy>
</xsl:template>
<xsl:template match="club_LIST">
<Record>
<xsl:copy-of select="ancestor::PartnersProfile/*[name()!='Record']"/>
<xsl:copy-of select="ancestor::Record/*[name()!='club_LIST']"/>
<club_LIST><xsl:apply-templates select="node()"/></club_LIST>
</Record>
</xsl:template>
</xsl:stylesheet>
Online Demo

Azure Logic App ignores indent="yes" when transforming XML

I'm using Azure Logic App to transform a CSV file to XML, everything was initially set up in BizTalk first to generate the relevant XSDs and XSL which worked perfectly fine. But when I use Azure Logic App the output XML file is all in one line even though I made sure it has indent="yes" in the XSL file.
I know I can use notepad++ to pretty print the result and save the file, but surely there's a way to automatically do that in Logic App?
For those interested, I've found a setting within the Logic App, simply select Apply XSLT output attributes and that's it, no validation needed either!
I manage to get indentation when using XSLT 3.0 with e.g. the stylesheet/map doing
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:next-match/>
<xsl:comment xmlns:saxon="http://saxon.sf.net/">Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
</xsl:stylesheet>
then a request of e.g.
<root><item>a</item><item>b</item></root>
is transformed to the output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>a</item>
<item>b</item>
</root>
<!--Run with SAXON HE 9.8.0.8 -->
I don't know how they run the XSLT 1.0 processor to ignore the xsl:output settings, seems a flaw or quirk in the pipeline.

Does Saxon-js perform XML syntax checking on a xsl:param?

I run this XSL script thru Saxon-js. It updates a cost field on the main input XHTML using the XML received in the transform call using the stylesheetParams. All good. The problem is that no syntax checking is done on the param-XML (you can see what it looks like in the commented-out line). It is on the XHTML and the transform will generate an error but not on the param-XML. It just allows it to enter and then the key-function just doesn't update the XHTML. Is there a way to do the checking for properly formed XML parameter in the same transform call, or do I have to use 2 transform calls: call transform on the param-XSL to syntax-check, then call this main transform to update the XHTML?
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="html" omit-xml-declaration="yes" encoding="UTF-8" include-content-type="no"/>
<xsl:param name="cost-data"/>
<!-- <supplier><product><key>3</key><pcost uptype="1"><key>21341</key><cost>12.99</cost></pcost></product></supplier> -->
<!-- </xsl:param> -->
<xsl:key name="cost" match="product/pcost[#uptype = 1]/cost" use="'cost' || ancestor::product/key"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="td[#name][key('cost', #name, fn:parse-xml($cost-data))]/text()">{key('cost', ../#name, fn:parse-xml($cost-data))}%</xsl:template>
<xsl:template match="/" name="xsl:initial-template">
<xsl:next-match/>
</xsl:template>
</xsl:stylesheet>
If your stylesheet code is
<xsl:param name="cost-data">
<supplier><product><key>3</key><pcost uptype="1"><key>21341</key>
<cost>12.99</cost></pcost></product></supplier>
</xsl:param>
then that's well-formed XML and no error should be reported.
However, fn:parse-xml($cost-data) is wrong. The value of the parameter is a node tree, not a string, and fn:parse-xml() expects lexical XML in a string. The effect of calling fn:parse-xml() on this node tree will be to first atomize the node, producing the untyped atomic value "32134112.99", and then attempt to parse this string "32134112.99" as lexical XML, which should fail.
To avoid confusion like this it's good practice to always declare the expected type of your parameters, for example as="xs:string" or as="document-node()".
If you want the default value of $cost-data to be a string containing lexical XML, try
<xsl:param name="cost-data" as="xs:string"><![CDATA[
<supplier><product><key>3</key><pcost uptype="1"><key>21341</key>
<cost>12.99</cost></pcost></product></supplier>
]]></xsl:param>
all on one line without whitespace (or if you need whitespace for readability, put it before a ">" delimiter).
Note: the literal answer to your question is: No. Saxon-JS doesn't perform this checking. The XML parser performs it, long before Saxon-JS gets to see the data.

ignore node using XSLT

I have below xml file which is coming form my vendor.
<?xml version="1.0" encoding="UTF-8"?>
<nm:MT_employee xmlns:nm="http://firstscenario.com"xmlns:tl="http://secondscenario.com">
<EMployeeDetails>
<Name>Janardhan</Name>
<id>1234</id>
<Address>India</Address>
</EMployeeDetails>
<tl:Extension>
<tl:Number>5678</tl:Number>
<tl:Salary>2345678</tl:Salary>
</tl:Extension>
</nm:MT_employee>
In the above xml I want to ignore the entire tl:Extension node. the final output should be like below
<?xml version="1.0" encoding="UTF-8"?>
<nm:MT_employee xmlns:nm="http://firstscenario.com"xmlns:tl="http://secondscenario.com">
<EMployeeDetails>
<Name>Janardhan</Name>
<id>1234</id>
<Address>India</Address>
</EMployeeDetails>
</nm:MT_employee>
I tried to with different XSLT codes but it's not working. Could you please suggest how can I achieve this?
Regards,
Janardhan
The general rule to "ignore" an element from the source XML
is to write an "empty" template for this element, in your case:
<xsl:template match="tl:Extension"/>
As this template refers to tl namespace, it must be specified
in the xsl:transform tag.
Of course, to copy the rest of the source content, your script
must include the identity template.
Below you have an example script:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tl="http://secondscenario.com" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="tl:Extension"/>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>

Use XML schema to specify default namespace in XML instance

I'm not sure if it matters, but I'm using BizTalk 2009 to generate the XML.
Is there a way to specify in my XML schema that the generated XML instance should use the target namespace as the default namespace?
If I have an xsd file like this:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.com/">
<xs:element name="example">
<xs:complexType>
<xs:attribute name="value" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:schema>
It creates an XML file like this:
<ns0:example value="something" xmlns:ns0="http://example.com/" />
But I want it to create an XML file like this:
<example value="something" xmlns="http://example.com/" />
I know that they're technically equivalent, but the consumers (vendor APIs) are poorly implemented and I'd like to give them what they expect.
I would expect that it depends on the software generating the instance, not the schema. XSD Schema was developed for validation of XML instances against a schema, not for generating instances from it. So it is unlikely to be present explicitly in XMLSchema. The generating tools might, however, use the fact that elements were (un)qualified
elementFormDefault="(un)qualified"
to trigger the prefixing.
Not completely in scope, but the following is worth reading for schema design: http://www.xfront.com/HideVersusExpose.html
One way would be to define a schema without the namespace. Map the BizTalk schema to the newly defined schema without namespace. From a BizTalk viewpoint, you would have a schema which represents the actual contract with the consumers. (i.e. without namespaces) Also, BizTalk uses namespace#rootnodename to define messageTypes. In this example, you would have two schemas
somenamespace#somerootnodename
#somerootnodename
The possible drawbacks of this approach are this would limiting the usage of this schema (#rootnodename) to 1 instance with the BizTalk group.
This is the default behaviour of BizTalk working this XML schemas and, as far as I know, there is no builtin way to change this.
What you really want, however, is that outbound messages conform to a cleaner and more liberal format than what is used by BizTalk. You can do this by using a custom pipeline component (and a custom send pipeline) to process the outgoing message before it leaves BizTalk.
The idea is to change the namespace prefix as part of sending the message outside BizTalk. The transformation happens during the processing of the send pipeline.
Nic Barden has blogged and provided some source code about this here. You can use his sample as the basis for performing replacement of namespace prefixes, rather than replacing namespaces themselves.
I strongly encourage you to check out the whole series of posts he's done about Developing Streaming Pipeline Components. Nic has made an extensive and thorough job of describing all that's needed to author robust and enterprise-class pipeline components.
Part 1
Part 2
Part 3
Part 4
Part 5
The ns0 prefix is added whenever a BizTalk btm maps a message. It shouldn't matter as this is still valid xml, however this could be a problem when sending messages to partners with legacy or incomplete xml parsers.
You can remove the ns0 prefix and instead make ns0 your default namespace on the output message by changing your btm from a visual map to an .xslt map.
e.g. Once you have converted your map to xslt, change the xslt from:
<?xml version="1.0" encoding="utf-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl s0"
version="1.0"
xmlns:ns0="http://targetns"
xmlns:s0="http://sourcens"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="s0:FromRoot" />
</xsl:template>
<xsl:template match="s0:FromRoot">
<ns0:ToRoot>
<xsl:for-each select="s0:FromElement">
<ns0:ToElement>
<xsl:value-of select="text()"/>
</ns0:ToElement>
</xsl:for-each>
</ns0:ToRoot>
</xsl:template>
</xsl:stylesheet>
To:
<?xml version="1.0" encoding="utf-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var"
exclude-result-prefixes="msxsl s0"
version="1.0"
xmlns="http://targetns"
xmlns:s0="http://sourcens"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="s0:FromRoot" />
</xsl:template>
<xsl:template match="s0:FromRoot">
<ToRoot>
<xsl:for-each select="s0:FromElement">
<ToElement>
<xsl:value-of select="text()"/>
</ToElement>
</xsl:for-each>
</ToRoot>
</xsl:template>
</xsl:stylesheet>
i.e. change the default xmlns and then remove the ns0 prefixes automatically.
A more generic solution is also possible (e.g. similar to Firras' answer here), which could be useful e.g. to place as a send port map to strip out all prefixes from elements. However, one needs to be wary if there are more than one xmlns on the output message!

Resources