how to get values from xml to json using XSLT - json

I got a question, I try to convert an XML to JSON using XSLT version 1.0.
As far as the name goes I get it right but the value is another story.
<datapost>
<fields>
<field>
<id>emailId</id>
<name>emailName</name>
<values>
<value>info#example.com</value>
</values>
</field>
</fields>
AS IT CURRENTLY IS:
At the moment I get only the "name" correct but the "value" (emailIdName & emailId & info#example.com) is all the values squashed together what I obviously don't want.
{
"emailName":{
"emailIdemailNameinfo#example.com"
}
}
EXPECTED TO BE:
I want to get only the "name" and the "value" in values (info#example.com)
This is the result that I WANT to get:
{
"emailName":{
"info#example.com"
}
}
This is the code I use:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no" encoding="UTF-16" omit-xml-declaration="yes"/>
<xsl:template match="/">
<body>{
<xsl:call-template name="fieldsName"></xsl:call-template>
<xsl:text>
</xsl:text>
</body>
</xsl:template>
<xsl:template name="fieldsName">
<xsl:for-each select="//datapost[position()=1]/fields/field">
"
<xsl:value-of select="name"/>" :
<xsl:call-template name="fieldsValue"/>}
</xsl:for-each>
</xsl:template>
<!-- Array Element -->
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="fieldsValue"/>
</xsl:template>
<!-- Object Properties -->
<xsl:template name="fieldsValue">
<xsl:variable name="childName" select="//datapost[position()=1]/fields/field/values/value"/>
<xsl:choose>
<xsl:when test="not(*|#*)">"
<xsl:value-of select="."/>"
</xsl:when>
<xsl:when test="count(*[name()=$childName]) > 1">{ "
<xsl:value-of select="$childName"/>" :[
<xsl:apply-templates select="*" mode="ArrayElement"/>] }
</xsl:when>
<xsl:otherwise>{
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
}
</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>
<!-- Attribute Property -->
<xsl:template match="#*">"
<xsl:value-of select="name"/>" : "
<xsl:value-of select="."/>",
</xsl:template>
</xsl:stylesheet>

The element names in your XML are poorly chosen, and this has probably confused you (it certainly confused me). One would expect "field" to contain a single field, but it actually contains all of them. So change
<xsl:for-each select="//datapost[position()=1]/fields/field">
to
<xsl:for-each select="//datapost[position()=1]/fields/field/*">
or better still,
<xsl:for-each select="datapost/fields/field/*">
since the other parts of the expression are redundant verbiage.
Then you need to look at the template containing the variable
<xsl:variable name="childName" select="//datapost[position()=1]/fields/field/values/value"/>
This is selecting all the values in the document, not just the values of the current element. I'm not entirely sure what you're trying to achieve here, and it doesn't seem to be covered by your test data, but I suspect you're trying to do some kind of grouping of elements that have the same name. For that you need to use grouping facilities: xsl:for-each-group in XSLT 2.0+, Muenchian grouping in XSLT 1.0. You haven't said which XSLT version you're using, but all of this would be an awful lot easier if you used XSLT 2.0+.

Related

XSLT xml-to-json skips root node + how to copy attribute as child node

I am trying to convert XML to json using XSLT 3.0, lowercasing all keys and moving the first attribute, if any, as a JSON child. So given following (dummy) input XML:
<FOO id="1">
<BAR xy="2">
<SPAM>N</SPAM>
</BAR>
</FOO>
I am expecting
{
"foo" : {
"id" : "1",
"bar" : {
"xy" : "2",
"spam" : "N"
}
}
}
Using this XSLT:
<?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"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="xml" indent='true' omit-xml-declaration='yes'/>
<xsl:template match="dummy">
<xsl:variable name="json-xml">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
<!--no children-->
<xsl:template match="*[not(*)]">
<string key="{lower-case(local-name())}">{.}</string>
</xsl:template>
<xsl:template match="*[*]">
<xsl:param name="key" as="xs:boolean" select="true()"/>
<map>
<xsl:if test="$key">
<xsl:attribute name="key" select="lower-case(local-name())"/>
</xsl:if>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:choose>
<xsl:when test="current-group()[2]">
<array key="{lower-case(local-name())}">
<xsl:apply-templates select="current-group()">
<xsl:with-param name="key" select="false()"/>
</xsl:apply-templates>
</array>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()">
<xsl:with-param name="key" select="true()"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</map>
</xsl:template>
</xsl:stylesheet>
I get (note the dummy pattern to skip json conversion),
<map xmlns="http://www.w3.org/2005/xpath-functions" key="foo">
<map key="bar">
<string key="spam">N</string>
</map>
</map>
Looks good to me but when I invoke the JSON conversion (by replacing dummy with /), I get:
{ "bar" :
{ "spam" : "N" } }
-> the foo node is gone.
I haven't figured out how to "move" the first attribute (arbitrary, could have any name) as a child node - if someone knows, appreciate a little snippet.
Lastly, not a big-deal but I am lowercasing keys in each template. Is it possible to do the transformation at once, either before in the source XML, or after templating, in the Result XML (before jsonifying) ?
See - https://xsltfiddle.liberty-development.net/jyfAiDC/2
(thanks btw to #Martin Honnen for this very useful tool !!)
You can use
<xsl:template match="/">
<xsl:variable name="json-xml">
<map>
<xsl:apply-templates/>
</map>
</xsl:variable>
<xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
perhaps to get closer to what you might want. But I haven't understood what you want to do with attributes in the XML.
The key attribute only make sense for an element that represents an entry in a map, so there's no point including it in an element unless that element has a parent named map. You want another layer of map in your structure.
Thanks to Martin & Michael for the map wrapper tip. Agreed though it is an unnecessary level in tree.
For rendering a XML attribute as a child node - assuming there is one only -,
I added the following after the first test (conditional map attribute) in the template:
<xsl:if test="#*[1]">
<string key="{name(#*[1])}">{#*[1]}</string>
</xsl:if>
Lastly, for converting to lowercase all intermediary key attributes in one-go instead of individually in multiple templates, it would require I think parsing the result tree before passing on to the xml-to-json function.
Not worth it... but it would be a nice featured option in XSLT 4.0 (?) i.e. a new xml-to-json option force-key-case = lower/upper

Handle excluded element in a foreach

In first template, I am intentionally excluding an element ('milk') because the parsed data map is relatively flat and I would like to use XSLT to categorize and structure the data. The aim is to process the excluded element ('milk') in the second template. The both templates works running them one at a time. Running the templates together will not show the result of the excluded element ('milk') which should set another attribute name and attribute value.
JSON:
<data>
{
"storage": {
"pencils": 12,
"milk": 8,
"rulers": 4
}
}
</data>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform
version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:storage="http://www.exammple.com/1"
xmlns:office="http://www.exammple.com/2"
xmlns:item="http://www.exammple.com/3"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<storage:one>
<xsl:apply-templates select="json-to-xml(.)"/>
</storage:one>
</xsl:template>
<!-- Print map -->
<!-- <xsl:template match="*[#key = 'storage']"> <xsl:copy-of select=".."/> </xsl:template> -->
<xsl:template match="*[#key='storage']">
<xsl:for-each select="*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="*[#key='milk']">
<xsl:for-each select=".">
<xsl:element name="item:{#key}">
<xsl:attribute name="beverage">plant-2</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:transform>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
xmlns:office="http://www.exammple.com/2"
xmlns:storage="http://www.exammple.com/1">
<item:pencils office="plant-1">12</item:pencils>
<item:rulers office="plant-1">4</item:rulers>
</storage:one>
Wanted result:
<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
xmlns:office="http://www.exammple.com/2"
xmlns:storage="http://www.exammple.com/1">
<item:pencils office="plant-1">12</item:pencils>
<item:rulers office="plant-1">4</item:rulers>
<item:milk beverage="plant-2">8</item:milk>
</storage:one>
Your second template is never matched, because it is never reached. All elements are processed by <xsl:template match="*[#key='storage']"> - which doesn't have an <xsl:apply-templates ...> to reach further templates.
Your first template does not recurse into its children. So add an <xsl:apply-templates select="*" /> to the end of the first template:
<xsl:template match="*[#key='storage']">
<xsl:for-each select="*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
<xsl:apply-templates select="*" />
</xsl:template>
This will try to apply further templates at the level of "storage" and therefore match the second template.
I would write templates for each different output type and if the order of the output is different from the order of the input throw in an xsl:sort or an XPath 3.1 sort call to change the order:
<xsl:template match="data">
<storage:one>
<xsl:apply-templates select="json-to-xml(.)"/>
</storage:one>
</xsl:template>
<xsl:template match="*[#key = 'storage']">
<xsl:apply-templates select="sort(*, (), function($el) { $el/#key = 'milk' })"/>
</xsl:template>
<xsl:template match="*[#key='storage']/*[not(#key='milk')]">
<xsl:element name="item:{#key}">
<xsl:attribute name="office">plant-1</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[#key='storage']/*[#key='milk']">
<xsl:element name="item:{#key}">
<xsl:attribute name="beverage">plant-2</xsl:attribute>
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:template>

How to replace return ( | ) by the string "\n" in xslt 1.0 ? [XML multiline base64 to json]

I got a xml that I need to turn into a json.
I'm mostly fine except with a base64 multiline
<file>TU0...AAA
FOO...BCD
FOO...012
FOO...ZYX</file>
In json multiline is not possible, this should be rewritten in 1 line only as
"file":"TU0...AAA\nFOO...BCD\nFOO...012\nFOO...ZYX"
With "real" two-char string "\n" to concatenate each line.
Can I do that in xslt 1.0 ?
I know I can use translate but that's for one char only.
I'll try
translate(.,'
',' ')
This will replace returns by space and maybe this won't break the base64 decoding of the json.
But, if i want to do it the "right way", I guess I'll need custom funcs.
In my case returns seems to be "
".
But if someone comes with a solution that works with all combinations (
) that would be great.
My primary target is chrome web browser but running fine in all browsers would be great.
If you just want to get rid of the linefeeds you could use the normalize-space($string)function, e.g.:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
exclude-result-prefixes="xd"
version="1.0">
<xd:doc scope="stylesheet">
<xd:desc>
<xd:p><xd:b>Created on:</xd:b> Apr 22, 2020</xd:p>
<xd:p><xd:b>Author:</xd:b> bwb</xd:p>
<xd:p>generates a normalized text output of the file element</xd:p>
</xd:desc>
</xd:doc>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="file"/>
</xsl:template>
<xsl:template match="file">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
</xsl:stylesheet>
You could then still replace the whitespaces by something else( forJSON maybe with ,)
If you definitely want the \nthough, you could try the following stylesheet:
<?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"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
exclude-result-prefixes="xs math xd"
version="1.0">
<xd:doc scope="stylesheet">
<xd:desc>
<xd:p><xd:b>Created on:</xd:b> Apr 22, 2020</xd:p>
<xd:p><xd:b>Author:</xd:b> bwb</xd:p>
<xd:p>Override default text() template by adding a search and replace funtionality</xd:p>
</xd:desc>
</xd:doc>
<xsl:output method="text"/>
<xd:doc scope="component">
<xd:desc>The string that should be searched and replaced by $param-replaceString</xd:desc>
</xd:doc>
<xsl:param name="param-searchString" select="'
'"/><!-- actually you also wnat to replace the whitespaces, that's why the searchString looks so strange -->
<xd:doc>
<xd:desc>The string that replace any occurence of $param-searchString</xd:desc>
</xd:doc>
<xsl:param name="param-replaceString" select="'\n'"/>
<xd:doc scope="component">
<xd:desc>Override for default text() template testing for $param-searchString presence and calling replace template</xd:desc>
</xd:doc>
<xsl:template match="text()">
<xsl:choose>
<xsl:when test="contains(., $param-searchString)">
<xsl:call-template name="replace">
<xsl:with-param name="InputString" select="."/>
<xsl:with-param name="searchString" select="$param-searchString"/>
<xsl:with-param name="replaceString" select="$param-replaceString"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="InputString"/>
<xsl:param name="searchString"/>
<xsl:param name="replaceString"/>
<xsl:choose>
<xsl:when test="contains($InputString, $searchString)">
<xsl:variable name="token-before-first-match" select="substring-before($InputString, $searchString)"/>
<xsl:variable name="token-after-first-match" select="substring-after(., concat($token-before-first-match, $searchString))"/>
<xsl:value-of select="concat($token-before-first-match, $replaceString)"/>
<xsl:choose>
<xsl:when test="contains($token-after-first-match, $searchString)">
<xsl:call-template name="replace">
<xsl:with-param name="InputString" select="$token-after-first-match"/>
<xsl:with-param name="searchString" select="$searchString"/>
<xsl:with-param name="replaceString" select="$replaceString"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$token-after-first-match"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$InputString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Transform a xml to a string using xslt?

How can I transform the xml in format1 to a string as mentioned in format2 using XSLT?
format 1
<children>
<child data="test1">
<content><name>test1</name>
<child>
<content><name>test1child</name>
</child>
</child>
</children>
format 2
"<root>"
+"<item id='test1'>"
+"<content><name>test1</name></content>"
+"<item parent_id='test1'>"
+"<content><name>test1child</name>"
+"</content>"
+"</item>"
+</item>
+"<root>"
so children should replace with root, child should replaced with item and child of child should be replaced with *item parent_id of parent id*. Is it possible to do with xslt?
Assuming XSLT 2.0, here is some suggestion on how to approach that, assuming you really want some string output with quotes and plus symbols (to get Javascript or similar code to construct XML as a string?):
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:template match="*">
<xsl:param name="name" select="name()"/>
<xsl:text><</xsl:text>
<xsl:value-of select="$name"/>
<xsl:apply-templates select="#*"/>
<xsl:text>></xsl:text>
<xsl:apply-templates/>
<xsl:text></</xsl:text>
<xsl:value-of select="$name"/>
<xsl:text>></xsl:text>
</xsl:template>
<xsl:template match="#*">
<xsl:param name="name" select="name()"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$name"/>
<xsl:text>='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
</xsl:template>
<xsl:template match="text()[matches(., '^\s+$')]">
<xsl:text>"
+"</xsl:text>
</xsl:template>
<xsl:template match="/children">
<xsl:text>"</xsl:text>
<xsl:next-match>
<xsl:with-param name="name" select="'root'"/>
</xsl:next-match>
<xsl:text>"</xsl:text>
</xsl:template>
<xsl:template match="child">
<xsl:next-match>
<xsl:with-param name="name" select="'item'"/>
</xsl:next-match>
</xsl:template>
<xsl:template match="child//child">
<xsl:variable name="copy" as="element()">
<xsl:copy>
<xsl:attribute name="parent_id" select="ancestor::child[1]/#data"/>
<xsl:copy-of select="#* , node()"/>
</xsl:copy>
</xsl:variable>
<xsl:apply-templates select="$copy"/>
</xsl:template>
</xsl:stylesheet>
That transforms the input
<children>
<child data="test1">
<content><name>test1</name></content>
<child>
<content><name>test1child</name></content>
</child>
</child>
</children>
into the result
"<root>"
+"<item data='test1'>"
+"<content><name>test1</name></content>"
+"<item parent_id='test1'>"
+"<content><name>test1child</name></content>"
+"</item>"
+"</item>"
+"</root>"
The code so far has many shortcomings however as it would not construct well-formed XML with properly escaped attribute values and also does not care to output namespace declarations. You could however adapt more sophisticated solutions like http://lenzconsulting.com/xml-to-string/.

Randomly get nodes from XML using XSL

I am making a website using a CMS, in the home page I need to get some Pictures and discriptions randomly,, I need each time i refresh the page to get new nodes.
This is how I am calling the nodes in XSL:
<xsl:for-each select ="TSPRoot/Blocks/Block[#ID=134]/Articles/Article[position() < 4]">
<div class="layout-08">
<a class="title" href="detailed.aspx?id={ID}">
<xsl:choose >
<xsl:when test ="string-length(ImageURL) > 0">
<img src="_handlers/resizeimage.ashx?src={ImageURL}&height=149&width=206" title="{Headline}"/>
</xsl:when>
<xsl:otherwise>
<img src="_frontend/ux/images/en_logo.png" width="206" height="149" title="{Headline}"/>
</xsl:otherwise>
</xsl:choose>
</a>
<div class="bg">
<xsl:value-of select ="Headline"/>
</div>
</div>
</xsl:for-each>
This gets the newest (3) nodes, I need random (3) nodes each time i refresh.
I would give you only some ideas of possible solution.
Technically XSLT doesn't offer a method random numbers. However, this problem can be solved in at least three different ways:
Generate pseudo-random number in Java/C#/PHP code before executing XSLT template and pass the generated number as an XSLT parameter.
Use the extension function: http://exslt.org/random/functions/random-sequence/index.html
Use current-dateTime() (XSLT 2.0) function as a seed for your random number procedure. With a couple of string and math operations you could convert it into desired quasi-random number. I think that for your needs this solution is enough.
EDIT - Ad 3.
I have made some experiments with this idea. And I have created some sample code. I know that this code generate a sequence of really pseudo random numbers, but for simple web page like in the question it might be enough.
The following XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:rnd="http://whatever.org/random">
<xsl:output indent="yes" />
<xsl:function name="rnd:pseudoRandomInternal">
<xsl:param name="numSeed" as="xs:decimal"/>
<xsl:variable name="squareSeed" select="xs:string($numSeed*$numSeed)"/>
<xsl:variable name="oneDivSeed" select="substring($squareSeed, 3, string-length($squareSeed) - 3)"/>
<xsl:variable name="cc" select="if (string-length($oneDivSeed) > 6) then
substring($oneDivSeed, string-length($oneDivSeed) - 6) else
$oneDivSeed"/>
<xsl:value-of select="xs:decimal($cc)"/>
</xsl:function>
<xsl:function name="rnd:pseudoRandom" as="xs:decimal*">
<xsl:param name="range" as="xs:integer"/>
<xsl:choose>
<xsl:when test="$range = 1">
<xsl:variable name="seed" select="substring(xs:string(current-time()), 1, 12)" as="xs:string"/>
<xsl:variable name="numSeed" select="xs:decimal(translate($seed, ':.+-', ''))"/>
<xsl:sequence select="(rnd:pseudoRandomInternal($numSeed))" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="subSequence" select="rnd:pseudoRandom($range - 1)"/>
<xsl:variable name="newValue" select="rnd:pseudoRandomInternal($subSequence[1])" as="xs:decimal*"/>
<xsl:sequence select="($newValue, $subSequence)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="/">
<xsl:variable name="rr" select="rnd:pseudoRandom(10)" as="xs:decimal*"/>
<output>
<xsl:for-each select="$rr">
<item>
<xsl:value-of select=". * 3 mod 11" />
</item>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
generated the following output:
<output>
<item>10</item>
<item>9</item>
<item>6</item>
<item>9</item>
<item>9</item>
<item>10</item>
<item>3</item>
<item>5</item>
<item>9</item>
<item>4</item>
</output>