Transpose XML using Muenchian method - multiple groupings - xslt

I need to transpose an XML file such as this:
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 1" />
<product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC1" manufacturer="Some Company 1" />
<product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC2" manufacturer="Some Company 2" />
<product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC2" manufacturer="Some Company 2" />
<product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC3" manufacturer="Some Company 3" />
<product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC3" manufacturer="Some Company 3" />
<product id="11" name="Widget 2" price="21.99" category_code="V" category="Video Games" manufacturer_code="SC4" manufacturer="Some Company 4" />
<product id="12" name="Widget 3" price="10.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 2" />
</products>
to a comma delimited text file, or a properly formated HTML table that includes only one line for each product, something like this:
id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3
10, Widget 1, 15.99, T, Toys, E, Electronics, SC1, Some Company 1, SC2, Some Company 2, SC3, Some Company 3
11, Widget 2, 21.99, V, Video Games,,, SC4, Some Company 4,,,,
12, Widget 3, 10.99, T, Toys,,, SC1, Some Company 2,,,,
As you'd notice, the XML data can be thought of as the result of three tables joined together: product, product_category, and product_manufacturer. Each product can belong to multiple categories and have multiple manufacturers. Of course the real data that I'm dealing with is more complex and in a totally different domain, but this sample depicts the problem properly.
I have very limited knowledge of XSLT, and with help of SO and other resources on the Interent, have put together a stylesheet that partly provides me what I need:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="key_product_group" match="product" use="#id"/>
<xsl:key name="key_category_group" match="product" use="concat(
#id,
#category_code,
#category)"/>
<xsl:key name="key_manufacturer_group" match="product" use="concat(
#id,
#manufacturer_code,
#manufacturer)"/>
<xsl:variable name="var_max_category_group" >
<xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',#id) )]">
<xsl:sort select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_category_group',
concat(#id,
#category_code,
#category)))])" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_category_group',
concat(#id,
#category_code,
#category)))])"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="var_max_manufacturer_group">
<xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',#id) )]">
<xsl:sort select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_manufacturer_group',
concat(#id,
#category_code,
#category)))])" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_manufacturer_group',
concat(#id,
#manufacturer_code,
#manufacturer)))])"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:text>id,</xsl:text>
<xsl:text>name,</xsl:text>
<xsl:text>price,</xsl:text>
<xsl:call-template name="loop_pcat">
<xsl:with-param name="count" select="$var_max_category_group"/>
</xsl:call-template>
<xsl:call-template name="loop_pmf">
<xsl:with-param name="count" select="$var_max_manufacturer_group"/>
</xsl:call-template>
<br></br>
<xsl:variable name="var_group"
select="//product[generate-id(.) = generate-id(key('key_product_group',#id)[1])]"/>
<xsl:for-each select="$var_group">
<xsl:sort order="ascending" select="#id"/>
<xsl:value-of select="#id"/>,
<xsl:value-of select="#name"/>,
<xsl:value-of select="#price"/>,
<xsl:for-each select="key('key_product_group',#id )[generate-id() = generate-id(key('key_category_group',
concat(#id,
#category_code,
#category)))]">
<xsl:value-of select="#category_code"/>,
<xsl:value-of select="#category"/>,
</xsl:for-each>
<xsl:for-each select="key('key_product_group',#id )[generate-id() = generate-id(key('key_manufacturer_group',
concat(#id,
#manufacturer_code,
#manufacturer)))]">
<xsl:value-of select="#manufacturer_code"/>,
<xsl:value-of select="#manufacturer"/>,
</xsl:for-each>
<br></br>
</xsl:for-each>
</xsl:template>
<xsl:template name="loop_pcat">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="concat('category_code_',$limit - $count)"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="concat('category_',$limit - $count)"/>
<xsl:text>,</xsl:text>
<xsl:call-template name="loop_pcat">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="loop_pmf">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="concat('manufacturer_code_',$limit - $count)"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="concat('manufacturer_',$limit - $count)"/>
<xsl:text>,</xsl:text>
<xsl:call-template name="loop_pmf">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The above stylesheet produces the following results:
id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3,
10, Widget 1, 15.99, T, Toys, E, Electronics, SC1, Some Company 1, SC2, Some Company 2, SC3, Some Company 3,
11, Widget 2, 21.99, V, Video Games, SC4, Some Company 4,
12, Widget 3, 10.99, T, Toys, SC1, Some Company 2,
The output has at least one major problem: all columns do not exist in each line, for example line 2 and 3 are missing category_code_2, and category_2, and manufacture_code and manufacturer 2 and 3. I'm sure there are other problems with the stylesheet as well, and I have no idea how it would perform on a relatively large xml file, but for now I would greatly appreciate your help with making the stylesheet produce the desired output format.
Thank you
MRSA

I had already begun with my own implementation, but then you posted and I was able to reuse some of your code to speed things up a bit. Main differences are:
in the templates, delimiters are added in front instead of after data; that way, you can end your lines properly.
the keys only use the ids and the codes (not the names).
the concatenation for the keys also uses a delimiter ('#').
the main template only provides the header line.
the product template has simpler calls to the data templates.
modified the data templates accordingly…
Here is my resulting stylesheet and its output:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-8"/>
<xsl:key name="byProduct" match="product" use="#id"/>
<xsl:key name="byProductAndCategory" match="product" use="concat(#id,'#',#category_code)"/>
<xsl:key name="byProductAndManufacturer" match="product" use="concat(#id,'#',#manufacturer_code)"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:variable name="eol" select="'
'"/>
<xsl:variable name="maxManufacturers">
<xsl:for-each select="//product[count(.|key('byProduct',#id)[1])=1]">
<xsl:sort select="count(key('byProductAndCategory',concat(#id,'#',#category_code)))"/>
<xsl:if test="position()=last()">
<xsl:value-of select="count(key('byProductAndCategory',concat(#id,'#',#category_code)))"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="maxCategories">
<xsl:for-each select="//product[count(.|key('byProduct',#id)[1])=1]">
<xsl:sort select="count(key('byProductAndManufacturer',concat(#id,'#',#manufacturer_code)))"/>
<xsl:if test="position()=last()">
<xsl:value-of select="count(key('byProductAndManufacturer',concat(#id,'#',#manufacturer_code)))"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<!--create the file header-->
<xsl:text>id</xsl:text>
<xsl:value-of select="$delimiter"/>
<xsl:text>name</xsl:text>
<xsl:value-of select="$delimiter"/>
<xsl:text>price</xsl:text>
<!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
<xsl:call-template name="loop_pcat_header">
<xsl:with-param name="count" select="$maxCategories"/>
</xsl:call-template>
<!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
<xsl:call-template name="loop_pmf_header">
<xsl:with-param name="count" select="$maxManufacturers"/>
</xsl:call-template>
<xsl:value-of select="$eol"/>
<xsl:apply-templates select="//product[count(.|key('byProduct',#id)[1])=1]"/>
</xsl:template>
<xsl:template match="product">
<!-- id -->
<xsl:value-of select="#id"/>
<xsl:value-of select="$delimiter"/>
<!-- name -->
<xsl:value-of select="#name"/>
<xsl:value-of select="$delimiter"/>
<!-- price -->
<xsl:value-of select="#price"/>
<!-- category stuff -->
<xsl:call-template name="loop_pcat_data">
<xsl:with-param name="data" select="key('byProductAndManufacturer',concat(#id,'#',#manufacturer_code))"/>
<xsl:with-param name="count" select="$maxCategories"/>
</xsl:call-template>
<!-- manufacturer stuff -->
<xsl:call-template name="loop_pmf_data">
<xsl:with-param name="data" select="key('byProductAndCategory',concat(#id,'#',#category_code))"/>
<xsl:with-param name="count" select="$maxManufacturers"/>
</xsl:call-template>
<xsl:value-of select="$eol"/>
</xsl:template>
<!--recursive loop templates for file header and data-->
<xsl:template name="loop_pcat_header">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="concat('category_code_',$limit - $count)"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="concat('category_',$limit - $count)"/>
<xsl:call-template name="loop_pcat_header">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="loop_pmf_header">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="concat('manufacturer_code_',$limit - $count)"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="concat('manufacturer_',$limit - $count)"/>
<xsl:call-template name="loop_pmf_header">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="loop_pcat_data">
<xsl:param name="data"/>
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="$data[$limit - $count]/#category_code"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="$data[$limit - $count]/#category"/>
<xsl:call-template name="loop_pcat_data">
<xsl:with-param name="data" select="$data"/>
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="loop_pmf_data">
<xsl:param name="data"/>
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="$data[$limit - $count]/#manufacturer_code"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="$data[$limit - $count]/#manufacturer"/>
<xsl:call-template name="loop_pmf_data">
<xsl:with-param name="data" select="$data"/>
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The requested output:
id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3
10,Widget 1,15.99,T,Toys,E,Electronics,SC1,Some Company 1,SC2,Some Company 2,SC3,Some Company 3
11,Widget 2,21.99,V,Video Games,,,SC4,Some Company 4,,,,
12,Widget 3,10.99,T,Toys,,,SC1,Some Company 2,,,,

So this is what I came up with, I still have to take care of extra comma at the end, also deal with special characters such as single or double qoutes in the attribute values. Any help with optimizing the code is greatly appreciated.
The following stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes"/>
<!--Generate keys, first is the product group-->
<xsl:key name="key_product_group" match="product" use="#id"/>
<!--Here is keys for groupings that are needed, note concatenation of attributes-->
<xsl:key name="key_category_group" match="product" use="concat(
#id,
#category_code,
#category)"/>
<!--another key-->
<xsl:key name="key_manufacturer_group" match="product" use="concat(
#id,
#manufacturer_code,
#manufacturer)"/>
<!--we need the maximum number of distinct groups for each product so that can format the layout properly-->
<xsl:variable name="var_max_category_group" >
<xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',#id) )]">
<xsl:sort select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_category_group',
concat(#id,
#category_code,
#category)))])" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_category_group',
concat(#id,
#category_code,
#category)))])"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!--maximum for the second group-->
<xsl:variable name="var_max_manufacturer_group">
<xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',#id) )]">
<xsl:sort select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_manufacturer_group',
concat(#id,
#category_code,
#category)))])" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_manufacturer_group',
concat(#id,
#manufacturer_code,
#manufacturer)))])"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!--delmiter and line break characters defined here-->
<xsl:variable name="var_delimiter" select="','"/>
<xsl:variable name="var_line_break" select="'
'"/>
<!--main procedure starts here-->
<xsl:template match="/">
<!--create the file header-->
<xsl:text>id</xsl:text>
<xsl:value-of select="$var_delimiter"/>
<xsl:text>name</xsl:text>
<xsl:value-of select="$var_delimiter"/>
<xsl:text>price</xsl:text>
<xsl:value-of select="$var_delimiter"/>
<!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
<xsl:call-template name="loop_pcat_header">
<xsl:with-param name="count" select="$var_max_category_group"/>
</xsl:call-template>
<!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
<xsl:call-template name="loop_pmf_header">
<xsl:with-param name="count" select="$var_max_manufacturer_group"/>
</xsl:call-template>
<xsl:value-of select="$var_line_break"/>
<!--select products and order ascending, the ordering is not really needed-->
<xsl:variable name="var_group"
select="//product[generate-id(.) = generate-id(key('key_product_group',#id)[1])]"/>
<xsl:for-each select="$var_group">
<xsl:sort order="ascending" select="#id"/>
<!--select non-repeatable attributes-->
<xsl:value-of select="#id"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="#name"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="#price"/>
<xsl:value-of select="$var_delimiter"/>
<!--select repeatable groups for each product-->
<xsl:for-each select="key('key_product_group',#id )[generate-id() = generate-id(key('key_category_group',
concat(#id,
#category_code,
#category)))]">
<xsl:value-of select="#category_code"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="#category"/>
<xsl:value-of select="$var_delimiter"/>
<!--count number of groups for each product and add blanks for the difference between the max and current instance-->
<xsl:variable name="var_max_category_group_instance" select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_category_group',
concat(#id,
#category_code,
#category)))])"/>
<xsl:call-template name="loop_pcat_data">
<xsl:with-param name="count" select="$var_max_category_group - $var_max_category_group_instance"/>
</xsl:call-template>
</xsl:for-each>
<!--select repeatable groups for each product-->
<xsl:for-each select="key('key_product_group',#id )[generate-id() = generate-id(key('key_manufacturer_group',
concat(#id,
#manufacturer_code,
#manufacturer)))]">
<xsl:value-of select="#manufacturer_code"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="#manufacturer"/>
<xsl:value-of select="$var_delimiter"/>
<!--count number of groups for each product and add blanks for the difference between the max and current instance-->
<xsl:variable name="var_max_manufacturer_group_instance" select="count(key('key_product_group',#id )[generate-id() = generate-id(key('key_manufacturer_group',
concat(#id,
#manufacturer_code,
#manufacturer)))])"/>
<xsl:call-template name="loop_pmf_data">
<xsl:with-param name="count" select="$var_max_manufacturer_group - $var_max_manufacturer_group_instance"/>
</xsl:call-template>
</xsl:for-each>
<xsl:value-of select="$var_line_break"/>
</xsl:for-each>
</xsl:template>
<!--recursive loop templates for file header and data-->
<xsl:template name="loop_pcat_header">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="concat('category_code_',$limit - $count)"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="concat('category_',$limit - $count)"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:call-template name="loop_pcat_header">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="loop_pcat_data">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:call-template name="loop_pcat_data">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="loop_pmf_header">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="concat('manufacturer_code_',$limit - $count)"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="concat('manufacturer_',$limit - $count)"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:call-template name="loop_pmf_header">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="loop_pmf_data">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:value-of select="$var_delimiter"/>
<xsl:value-of select="$var_delimiter"/>
<xsl:call-template name="loop_pmf_data">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied to this XML
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 1" />
<product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC1" manufacturer="Some Company 1" />
<product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC2" manufacturer="Some Company 2" />
<product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC2" manufacturer="Some Company 2" />
<product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC3" manufacturer="Some Company 3" />
<product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC3" manufacturer="Some Company 3" />
<product id="11" name="Widget 2" price="21.99" category_code="V" category="Video Games" manufacturer_code="SC4" manufacturer="Some Company 4" />
<product id="12" name="Widget 3" price="10.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 2" />
</products>
generates the following output:
id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3,
10,Widget 1,15.99,T,Toys,E,Electronics,SC1,Some Company 1,SC2,Some Company 2,SC3,Some Company 3,
11,Widget 2,21.99,V,Video Games,,,SC4,Some Company 4,,,,,
12,Widget 3,10.99,T,Toys,,,SC1,Some Company 2,,,,,

Related

XSLT 2.0 Type checking: I get error "Unknown type xsl:string"

I am working on a complex XSLT transformation. I am using XSLT 2.0. This page, https://www.ibm.com/developerworks/library/x-schemaawarexslt/index.html, explains that you can add type checking to your templates. This does not work with me.
Here is my example XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="columnWidths"/>
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="row">
<xsl:with-param name="columnWidths" select="$columnWidths"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row">
<xsl:param name="columnWidths"/>
<line>
<xsl:variable name="first">
<xsl:value-of select="field[position() = 1]/text()"/>
</xsl:variable>
<xsl:variable name="second">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 2]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 2, 1)"/>
<xsl:with-param name="align" select="'left'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="third">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 3]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 3, 1)"/>
<xsl:with-param name="align" select="'right'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="string-join(($first, $second, $third), ' ')"/>
</line>
</xsl:template>
<xsl:template name="padString">
<xsl:param name="value"/>
<xsl:param name="length"/>
<xsl:param name="align"/>
<xsl:choose>
<xsl:when test="string-length($value) >= number($length)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="appended">
<xsl:call-template name="generalAppend">
<xsl:with-param name="in" select="$value"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="padString">
<xsl:with-param name="value" select="$appended/append/*[local-name()=$align]/text()"/>
<xsl:with-param name="length" select="$length"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="generalAppend">
<xsl:param name="in" as="xsl:string"/>
<append>
<left><xsl:value-of select="concat($in, ' ')"/></left>
<right><xsl:value-of select="concat(' ', $in)"/></right>
</append>
</xsl:template>
</xsl:stylesheet>
My XSLT processor gives the following error:
line [57] column [43]: Unknown type xsl:string
This line reads:
<xsl:param name="in" as="xsl:string"/>
What is going wrong?
You try to get the type "string" from the wrong namespace. The type "string" lives in the XML Schema namespace which differs from the XSLT namespace. Here is the correct version of the above XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name="columnWidths"/>
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="row">
<xsl:with-param name="columnWidths" select="$columnWidths"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row">
<xsl:param name="columnWidths"/>
<line>
<xsl:variable name="first">
<xsl:value-of select="field[position() = 1]/text()"/>
</xsl:variable>
<xsl:variable name="second">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 2]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 2, 1)"/>
<xsl:with-param name="align" select="'left'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="third">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 3]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 3, 1)"/>
<xsl:with-param name="align" select="'right'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="string-join(($first, $second, $third), ' ')"/>
</line>
</xsl:template>
<xsl:template name="padString">
<xsl:param name="value"/>
<xsl:param name="length"/>
<xsl:param name="align"/>
<xsl:choose>
<xsl:when test="string-length($value) >= number($length)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="appended">
<xsl:call-template name="generalAppend">
<xsl:with-param name="in" select="$value"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="padString">
<xsl:with-param name="value" select="$appended/append/*[local-name()=$align]/text()"/>
<xsl:with-param name="length" select="$length"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="generalAppend">
<xsl:param name="in" as="xs:string"/>
<append>
<left><xsl:value-of select="concat($in, ' ')"/></left>
<right><xsl:value-of select="concat(' ', $in)"/></right>
</append>
</xsl:template>
</xsl:stylesheet>
A namespace prefix "xs" has been introduced for the XML Schema namespace. The definition of the parameter type then references this prefix:
<xsl:param name="in" as="xs:string"/>

Attaching parent attributes to child nodes with multiple children

this is linked from Attaching ancestor attributes to child nodes
I'm extracting names from a large xml dataset, I need to extract displayname, and the other name types (currently I am only picking out Synonyms and SystematicNames). Right now with the help of an awesome person I've gotten so far, but it only extracts the first of each type...
Sample XML
<Chemical id="0000103902" displayFormula="C8-H9-N-O2" displayName="Acetaminophen [USP:JAN]">
<NameList>
<DescriptorName>Acetaminophen<SourceList><Source>MeSH</Source></SourceList></DescriptorName>
<NameOfSubstance>Acetaminophen<SourceList><Source>HSDB</Source><Source>MeSH</Source></SourceList></NameOfSubstance>
<NameOfSubstance>Acetaminophen [USP:JAN]<SourceList><Source>NLM</Source></SourceList></NameOfSubstance>
<MixtureName>Actifed Plus<SourceList><Source>MeSH</Source></SourceList></MixtureName>
<MixtureName>Jin Gang<SourceList><Source>NLM</Source></SourceList></MixtureName>
<MixtureName>Talacen<SourceList><Source>NLM</Source></SourceList></MixtureName>
<SystematicName>Acetamide, N-(4-hydroxyphenyl)-<SourceList><Source>EPA SRS</Source><Source>MeSH</Source><Source>TSCAINV</Source></SourceList></SystematicName>
<SystematicName>Acetaminophen<SourceList><Source>CCRIS</Source></SourceList></SystematicName>
<SystematicName>Acetanilide, 4'-hydroxy-<SourceList><Source>RTECS</Source></SourceList></SystematicName>
<SystematicName>Paracetamol<SourceList><Source>ECHA</Source><Source>EINECS</Source></SourceList></SystematicName>
<Synonyms>4-13-00-01091 (Beilstein Handbook Reference)<SourceList><Source>RTECS</Source></SourceList></Synonyms>
<Synonyms>Abensanil<SourceList><Source>HSDB</Source><Source>RTECS</Source></SourceList></Synonyms>
<Synonyms>Acetagesic<SourceList><Source>HSDB</Source><Source>RTECS</Source></SourceList></Synonyms>
<Synonyms>Acetamide, N-(p-hydroxyphenyl)-<SourceList><Source>RTECS</Source></SourceList></Synonyms>
</NameList>
</Chemical>
Current code
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:variable name="FS">
<!-- Field seperator -->
<xsl:text>;</xsl:text>
</xsl:variable>
<xsl:variable name="LT">
<!-- Line terminator -->
<xsl:text>
</xsl:text>
</xsl:variable>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:for-each select="//Chemical[#displayName != '' and #displayName != 'INDEX NAME NOT YET ASSIGNED']">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="#id" />
<xsl:with-param name="val2" select="#displayName" />
</xsl:call-template>
<xsl:if test="normalize-space(NameList/SystematicName/text()) != ''">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="#id" />
<xsl:with-param name="val2" select="normalize-space(NameList/SystematicName/text())" />
</xsl:call-template>
</xsl:if>
<xsl:if test="normalize-space(NameList/Synonyms/text()) != ''">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="#id" />
<xsl:with-param name="val2" select="normalize-space(NameList/Synonyms/text())" />
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="printValues">
<xsl:param name="val1" />
<xsl:param name="val2" />
<!-- constants -->
<xsl:variable name="url" select="'https://chem.nlm.nih.gov/chemidplus/sid/startswith/'" />
<xsl:variable name="src" select="'nlm'" />
<xsl:text>"</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="paramStr" select="$val2" />
</xsl:call-template>
<xsl:text>"</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="concat($url, $val1)" />
<xsl:text>"</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="$src" />
<xsl:text>"</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="escapeQuote">
<xsl:param name="paramStr" />
<xsl:if test="string-length($paramStr) > 0">
<xsl:value-of select="substring-before(concat($paramStr, '"'), '"')" />
<xsl:if test="contains($paramStr, '"')">
<xsl:text>\"</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="paramStr" select="substring-after($paramStr, '"')" />
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
However this only gives:-
"Acetaminophen [USP:JAN]","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetamide, N-(4-hydroxyphenyl)-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"4-13-00-01091 (Beilstein Handbook Reference)","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
How do I go about extracting all the children in the same manner?
Printing of the text values in the <SystematicName> and <Synonyms> nodes can be achieved by adding <xsl:for-each> loop for those elements. The <xsl:if> condition can also be handled in the <xsl:for-each> selection.
Please modify the <xsl:template match="/"> as shown below.
<xsl:template match="Chemical[#displayName != '' and #displayName != 'INDEX NAME NOT YET ASSIGNED']">
<xsl:variable name="idValue" select="#id" />
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="$idValue" />
<xsl:with-param name="val2" select="#displayName" />
</xsl:call-template>
<xsl:for-each select="NameList/SystematicName[text() != '']">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="$idValue" />
<xsl:with-param name="val2" select="normalize-space(text())" />
</xsl:call-template>
</xsl:for-each>
<xsl:for-each select="NameList/Synonyms[text() != '']">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="$idValue" />
<xsl:with-param name="val2" select="normalize-space(text())" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
Output
"Acetaminophen [USP:JAN]","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetamide, N-(4-hydroxyphenyl)-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetaminophen","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetanilide, 4'-hydroxy-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Paracetamol","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"4-13-00-01091 (Beilstein Handbook Reference)","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Abensanil","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetagesic","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetamide, N-(p-hydroxyphenyl)-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"

Complex selection of XSL 1.0 node set

(This question is a less simplified version of my problem. The more simplified version which was already answered can be found here. I'm posting this more complicated question due to a comment by michael.hor257k who suggested that there may be an alternative approach that could solve it - possibly using select in a loop, or possibly a completely different approach.)
I'd like to process an XML file, over whose format I have no control, to generate C++ code. I need to process functions defined in XML in several different ways to produce different parts of the code. As part of this I need to select a subset of function parameters that match a complicated criteria and pass this selection to several named templates; the named templates need to be able to access the original document.
This example creates a complex selection of C++ function parameters that do not have constant values (ie the same min and max), where the min and max may be decimal or hexadecimal, using the "GenerateNonFixedParameters" template. The parameters refer to enumerations which are located elsewhere in the document, and these definitions are referenced by the named template call "ListParameterValues".
There are two problems.
The creation of the variable "nonFixedParameters" does not use select. I cannot work out how to use select for such a complicated case (XSL 1.0), but maybe there is a way.
A copy of the nodes does not suffice, as the "ListParameterValues" template as it currently stands needs to operate on an original set of nodes from the document.
Example XSL with the locations of these two problems marked:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. This does not use 'select' therefore it does not work. This is XSL 1.0 so as="node()*" cannot be used. -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<xsl:for-each select="$parameters">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore this does not work. -->
<xsl:for-each select="//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<!-- Here a copy is clearly the wrong approach! -->
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
Simple XML to feed the above:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<dictionary>
<enum name="EnumName">
<value name="firstValue" val="1" />
<value name="secondValue" val="2" />
<value name="thirdValue" val="3" />
<value name="forthValue" val="4" />
<value name="fifthValue" val="5" />
</enum>
</dictionary>
<function name="FunctionOne">
<parameter name="p1" type="enum" enum="EnumName" min="2" max="0x4"/>
<parameter name="p2" type="enum" enum="EnumName" min="0x03" max="3"/>
</function>
</body>
Wanted output. Note that p1 has all names within [min..max] listed, but p2 has none listed because min and max have the same value.
p1[secondValue thirdValue forthValue ] p2[]
I think your stylesheet can be made to work with XSLT 1.0 if you use an extension function like exsl:node-set to convert your result tree fragment into a node-set and if you store the root node of the primary input tree into a global variable or parameter as then you will be able to compare nodes in your primary input document to nodes of the newly constructed, temporary tree.
Based on these suggestions the code would look like
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:exsl="http://exslt.org/common">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:variable name="main-root" select="/"/>
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. Using exsl:node-set or similar you can convert that result tree fragment into a node set to process it further -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<!-- <xsl:for-each xmlns:ms="urn:schemas-microsoft-com:xslt" select="ms:node-set($parameters)/parameter"> for MSXML or XslTransform -->
<xsl:for-each select="exsl:node-set($parameters)/parameter">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore using the global variable works. -->
<xsl:for-each select="$main-root//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
The example is online at http://xsltransform.net/94hvTzi/1.
Let me show a different approach that actually selects and processes the original nodes, in their original context - as was discussed in the previous thread. Consider:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text" encoding="utf-8"/>
<xsl:template match="/">
<xsl:for-each select="body/function">
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="parameter"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="select-parameters">
<xsl:param name="input-set"/>
<xsl:param name="output-set" select="dummy-node"/>
<xsl:variable name="current-node" select="$input-set[1]" />
<xsl:choose>
<xsl:when test="$current-node">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#max" />
</xsl:call-template>
</xsl:variable>
<!-- recursive call -->
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="$input-set[position() > 1]"/>
<xsl:with-param name="output-set" select="$output-set | $current-node[$min != $max]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- call a template to process the currently selected node-set -->
<xsl:call-template name="process-parameters">
<xsl:with-param name="input-set" select="$output-set"/>
</xsl:call-template>
<!-- call more templates here, if required -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:key name="enum-by-name" match="enum" use="#name" />
<xsl:template name="process-parameters">
<xsl:param name="input-set"/>
<xsl:for-each select="$input-set">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat(#name, '[')"/>
<xsl:for-each select="key('enum-by-name', #enum)/value[#val >= $min and #val <= $max]">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The problem with this approach is that it works exactly as advertised; the nodes selected at the end of the selecting processes are the original, unmodified parameters. As a result, they still carry the mixture of decimal and hexadecimal values, and you must convert these again when processing the selected set.
So it might well be more worthwhile to pre-process the parameters by normalizing the values to a common base, then use the result (converted to a node-set) for the rest of the processing. I wouldn't spend so much effort at selecting those that meet the criteria - because once the values are consistent, the selection becomes trivial. If you like, I will post a demo showing that.

Camel case conversion for special case also in XSLT

Below XSLT converts First character of each word to upper case.But Fails for special case (example:for input-> the 'lion ,king output is-> The 'Lion ,King). Need solution for the special case also.
<xsl:template name="Split">
<xsl:param name="pText"/>
<xsl:if test="string-length($pText)">
<xsl:variable name="vConvertedWord" select="concat(translate(substring(substring-before(concat($pText,$vSeparator),$vSeparator),1,1), $smallcase,$uppercase),substring(substring-before(concat($pText,$vSeparator),$vSeparator),2))"/>
<xsl:value-of select="$vConvertedWord"/>
<xsl:value-of select="$vSeparator"/>
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="substring-after($pText,$vSeparator)"/>
</xsl:call-template>
</xsl:template>
I think what you are asking is that you may have multiple separators, not just a space, but a comma too? And also maybe you want some 'small' words not to be capitalised?
Just for my own personal challenge, the solution I came up with is to iterate over the text one character at a time, building up a 'word' until you reach a 'separator'. You can then capitalise the word you have built up, unless it happens to be a 'small' word. You then continue iterating with a new empty word to be built up.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="smallcase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>
<xsl:variable name="vSeparator"> ,.</xsl:variable>
<xsl:variable name="vProp"> a an the and </xsl:variable>
<xsl:template match="text()">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="Split">
<xsl:param name="pText"/>
<xsl:param name="letterPos" select="1" />
<xsl:param name="currentWord" select="''" />
<xsl:variable name="nextChar" select="translate(substring($pText, $letterPos, 1), $uppercase ,$smallcase)" />
<xsl:variable name="separatorFound" select="($nextChar = '' or contains($vSeparator, $nextChar))" />
<xsl:if test="$separatorFound">
<xsl:choose>
<xsl:when test="contains($vProp, concat(' ', $currentWord, ' ')) and $letterPos > string-length($currentWord) + 1">
<xsl:value-of select="$currentWord" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate(substring($currentWord, 1, 1), $smallcase ,$uppercase)" />
<xsl:value-of select="substring($currentWord, 2, string-length($currentWord) - 1)" />
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$nextChar" />
</xsl:if>
<xsl:variable name="newWord">
<xsl:if test="not($separatorFound)"><xsl:value-of select="concat($currentWord, $nextChar)"/></xsl:if>
</xsl:variable>
<xsl:if test="$letterPos <= string-length($pText)">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="$pText" />
<xsl:with-param name="letterPos" select="$letterPos + 1" />
<xsl:with-param name="currentWord" select="$newWord" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to this XML
<text>The Lion king, the tiger queen and the hippo prince</text>
The following is output
The Lion King, the Tiger Queen and the Hippo Prince
EDIT - Alternate solution
If you want to capitalise letters that occur after , or ., then try this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="smallcase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>
<xsl:variable name="vSeparator">,.</xsl:variable>
<xsl:variable name="vProp"> a an the and </xsl:variable>
<xsl:template match="text()">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="Split">
<xsl:param name="pText"/>
<xsl:param name="letterPos" select="1" />
<xsl:param name="currentWord" select="''" />
<xsl:param name="startOfBlock" select="1" />
<xsl:variable name="nextChar" select="translate(substring($pText, $letterPos, 1), $uppercase ,$smallcase)" />
<xsl:variable name="separatorFound" select="($nextChar = '' or $nextChar = ' ' or contains($vSeparator, $nextChar))" />
<xsl:if test="$separatorFound">
<xsl:choose>
<xsl:when test="contains($vProp, concat(' ', $currentWord, ' ')) and $startOfBlock = 0">
<xsl:value-of select="$currentWord" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate(substring($currentWord, 1, 1), $smallcase ,$uppercase)" />
<xsl:value-of select="substring($currentWord, 2, string-length($currentWord) - 1)" />
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$nextChar" />
</xsl:if>
<xsl:variable name="newWord">
<xsl:if test="not($separatorFound)"><xsl:value-of select="concat($currentWord, $nextChar)"/></xsl:if>
</xsl:variable>
<xsl:variable name="newStartOfBlock">
<xsl:choose>
<xsl:when test="contains($vSeparator, $nextChar)"><xsl:value-of select="1" /></xsl:when>
<xsl:when test="$nextChar = ' ' and $currentWord != ''"><xsl:value-of select="0" /></xsl:when>
<xsl:otherwise><xsl:value-of select="$startOfBlock" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="$letterPos <= string-length($pText)">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="$pText" />
<xsl:with-param name="letterPos" select="$letterPos + 1" />
<xsl:with-param name="currentWord" select="$newWord" />
<xsl:with-param name="startOfBlock" select="$newStartOfBlock" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to this XML
<text>The Lion king, the tiger queen and the hippo prince.... And cats, bats and rats.</text>
The following is output:
The Lion King, The Tiger Queen and the Hippo Prince.... And Cats, Bats and Rats.

How to convert json to xml using xslt

How would you convert JSON to XML?
Consider:
<sampleTag>
{
"Order": {
"InvestmentAccount": { "AccountNumber": "10" },
"Parcel": {
"Limit": "0",
"ExpiryDate": "1900-01-01T00:00:00",
"Asset": [
{
"Open": "25.15",
"High": "25.15",
"Low": "25.11",
"Close": "25.87"
}
{
"Open": "25.15",
"High": "25.15",
"Low": "25.11",
"Close": "25.87"
}]
},
"OrderDate": "2012-10-11T21:46:03.6489906+11:00",
}
}
</sampleTag>
After transformation, the document is as follows:
<Order>
<InvestmentAccount>
<AccountNumber>10</AccountNumber>
</InvestmentAccount>
<Parcel>
<Limit>0</Limit>
<ExpiryDate>1900-01-01T00:00:00</ExpiryDate>
<Asset>
<Open>25.15</Open>
<High>25.15</High>
<Low>25.11</Low>
<Close>25.87</Close>
</Asset>
<Asset>
<Open>25.15</Open>
<High>25.15</High>
<Low>25.11</Low>
<Close>25.87</Close>
</Asset>
</Parcel>
<OrderDate>2012-10-11T21:46:03.6489906+11:00</OrderDate>
</Order>
My work on JSON parsing doesn't cover the full JSON grammar.
And the task of "translating" any JSON document to an XML document doesn't have a solution. There are JSON constructs, which cannot be translated to XML without defining additional conventions and introducing additional elements -- so the final XML structure isn't a true and natural representation of the original JSON object.
In XSLT 3.0 there is a function to parse any JSON object -- parse-json() -- to a map -- a new data type introduced in XSLT 3.0. Read about this here:
http://www.w3.org/TR/xslt-30/#json
Actually, it is not that hard. The way to approach it is to examine the syntax of jason, and view each production like it was a template. I was just about to write a solution, when I considered the possibility that the OP forgot to google for pre-existing solutions. I searched and lo and behold ....
http://dnovatchev.wordpress.com/2007/07/05/transforming-json/
UPDATE
Here is a JSon to XML converter. But it only works on a subset of json. Hopefully, the subset is broad enough for your particular needs. In particular the limitations are:
The only simple type supported is string. No integer, boolean or null.
Json object names must be valid xml element names.
No escape codes permitted inside string values. This means that you cant transport values that include, for instance, the " character (without rolling your own encoding layer).
This XSLT 1.0 style-sheet...*
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:json="http://www.ibm.com/xmlns/prod/2009/jsonx"
xmlns:exsl="http://exslt.org/common"
xmlns:so="http://stackoverflow.com/questions/13007280"
exclude-result-prefixes="xsl xs json so exsl">
<xsl:output indent="yes" encoding="UTF-8" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="quot" select="'"'" />
<xsl:template match="/*">
<xsl:variable name="t1">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="exsl:node-set($t1)/so:output/*" mode="copy-sans-namespace" />
</xsl:template>
<xsl:template match="*" mode="copy-sans-namespace">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates mode="copy-sans-namespace" />
</xsl:element>
</xsl:template>
<xsl:template name="field">
<!-- Input like: "Open": "25.15" bla -->
<!-- output like: <so:output><Open>25.15</Open></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:variable name="field-name" select="substring-before(substring-after($json-in,$quot),$quot)" />
<xsl:variable name="remainder" select="substring-after($json-in,':')" />
<xsl:call-template name="value">
<xsl:with-param name="json-in" select="$remainder" />
<xsl:with-param name="parent-ele" select="$field-name" />
</xsl:call-template>
</xsl:template>
<xsl:template name="fields">
<!-- Input like: "Open": "25.15" , "High": "25.15" } bla -->
<!-- output like: <so:output><Open>25.15</Open><High>25.15</High></so:output> <so:extra>} bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:variable name="n" select="normalize-space($json-in)" />
<xsl:choose>
<xsl:when test="substring($n,1,1) = $quot">
<xsl:variable name="t1">
<xsl:call-template name="field">
<xsl:with-param name="json-in" select="$n" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t2" select="normalize-space( exsl:node-set($t1)/so:extra) " />
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="substring($t2,1,1)=','">
<xsl:call-template name="fields">
<xsl:with-param name="json-in" select="substring-after($t2,',')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$t2">
<so:extra><xsl:value-of select="$t2" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:copy-of select="exsl:node-set($t1)/so:output/* | exsl:node-set($t3)/so:output/*" />
</so:output>
<xsl:copy-of select="exsl:node-set($t3)/so:extra" />
</xsl:when>
<xsl:when test="$n">
<so:extra><xsl:value-of select="$n" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="object">
<!-- Input like: { X } bla -->
<!-- output like: <so:output>fields(X)</so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" select="''" />
<xsl:variable name="t1" select="normalize-space(substring-after($json-in,'{'))" />
<xsl:variable name="t2">
<xsl:call-template name="fields">
<xsl:with-param name="json-in" select="$t1" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t3" select="normalize-space(substring-after( exsl:node-set($t2)/so:extra, '}'))" />
<so:output>
<xsl:choose>
<xsl:when test="$parent-ele">
<xsl:element name="{$parent-ele}">
<xsl:copy-of select="exsl:node-set($t2)/so:output/node()" />
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="exsl:node-set($t2)/so:output/node()" />
</xsl:otherwise>
</xsl:choose>
</so:output>
<xsl:if test="$t3">
<so:extra><xsl:value-of select="$t3" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="objects">
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="n" select="normalize-space($json-in)" />
<xsl:choose>
<xsl:when test="substring($n,1,1) = '{'">
<xsl:variable name="t1">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="$n" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t2" select="normalize-space( exsl:node-set($t1)/so:extra) " />
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="substring($t2,1,1)='{'">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="$t2" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$t2">
<so:extra><xsl:value-of select="$t2" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:copy-of select="exsl:node-set($t1)/so:output/* | exsl:node-set($t3)/so:output/*" />
</so:output>
<xsl:copy-of select="exsl:node-set($t3)/so:extra" />
</xsl:when>
<xsl:when test="$n">
<so:extra><xsl:value-of select="$n" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="array">
<!-- Input like: [ X1 X2 ] bla -->
<!-- output like: <so:output><Y>X1</Y><Y>X2</Y></so:output> <so:extra>}bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="t1" select="normalize-space(substring-after($json-in,'['))" />
<xsl:variable name="t2">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="$t1" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t3" select="normalize-space(substring-after( exsl:node-set($t2)/so:extra, ']'))" />
<xsl:copy-of select="exsl:node-set($t2)/so:output" />
<xsl:if test="$t3">
<so:extra><xsl:value-of select="$t3" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="value">
<!-- Input like either array, object or string -->
<!-- output like either array, object or string -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="first-letter" select="substring(normalize-space($json-in),1,1)" />
<xsl:choose>
<xsl:when test="$first-letter='{'">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$first-letter='['">
<xsl:call-template name="array">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$first-letter=$quot">
<xsl:call-template name="string">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<so:output>ERROR</so:output>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="string">
<!-- Input like: "X" bla -->
<!-- output like: <so:output><Y>X</Y></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="value" select="substring-before(substring-after($json-in,$quot),$quot)" />
<xsl:variable name="remainder" select="normalize-space(substring-after(substring-after($json-in,$quot),$quot))" />
<so:output>
<xsl:element name="{$parent-ele}">
<xsl:value-of select="$value" />
</xsl:element>
</so:output>
<xsl:if test="$remainder">
<so:extra><xsl:value-of select="$remainder" /></so:extra>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
...applied to this input (modified from OP supplied to remove an extraneous comma)...
<sampleTag>
{
"Order": {
"InvestmentAccount": { "AccountNumber": "10" },
"Parcel": {
"Limit": "0",
"ExpiryDate": "1900-01-01T00:00:00",
"Asset": [
{
"Open": "25.15",
"High": "25.15",
"Low": "25.11",
"Close": "25.87"
}
{
"Open": "25.15",
"High": "25.15",
"Low": "25.11",
"Close": "25.87"
}]
},
"OrderDate": "2012-10-11T21:46:03.6489906+11:00"
}
}
</sampleTag>
..yields...
<Order>
<InvestmentAccount>
<AccountNumber>10</AccountNumber>
</InvestmentAccount>
<Parcel>
<Limit>0</Limit>
<ExpiryDate>1900-01-01T00:00:00</ExpiryDate>
<Asset>
<Open>25.15</Open>
<High>25.15</High>
<Low>25.11</Low>
<Close>25.87</Close>
</Asset>
<Asset>
<Open>25.15</Open>
<High>25.15</High>
<Low>25.11</Low>
<Close>25.87</Close>
</Asset>
</Parcel>
<OrderDate>2012-10-11T21:46:03.6489906+11:00</OrderDate>
</Order>
I tweaked Sean B. Durkin's template a bit and thought I'd share.
Updates include:
Support for numbers
Support for booleans
Fix for object array elements separated by a comma (per JSON spec)
Non-update changes:
Array elements are displayed in their own XML elements with the element name as the object key suffixed with _element
Still not supported:
Escaped characters (quotes) in strings
Here's the template:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:json="http://www.ibm.com/xmlns/prod/2009/jsonx"
xmlns:exsl="http://exslt.org/common"
xmlns:so="http://stackoverflow.com/questions/13007280"
exclude-result-prefixes="xsl xs json so exsl">
<xsl:output indent="yes" encoding="UTF-8" />
<xsl:strip-space elements="*" />
<xsl:variable name="quot" select="'"'" />
<xsl:variable name="numbers" select="'0123456789'"/>
<xsl:variable name="booleans" select="'tf'"/>
<xsl:template match="/*">
<xsl:variable name="t1">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="exsl:node-set($t1)/so:output/*" mode="copy-sans-namespace" />
</xsl:template>
<xsl:template match="*" mode="copy-sans-namespace">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates mode="copy-sans-namespace" />
</xsl:element>
</xsl:template>
<xsl:template name="field">
<!-- Input like: "Open": "25.15" bla -->
<!-- output like: <so:output><Open>25.15</Open></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:variable name="field-name" select="substring-before(substring-after($json-in,$quot),$quot)" />
<xsl:variable name="remainder" select="substring-after($json-in,':')" />
<xsl:call-template name="value">
<xsl:with-param name="json-in" select="$remainder" />
<xsl:with-param name="parent-ele" select="$field-name" />
</xsl:call-template>
</xsl:template>
<xsl:template name="fields">
<!-- Input like: "Open": "25.15" , "High": "25.15" } bla -->
<!-- output like: <so:output><Open>25.15</Open><High>25.15</High></so:output> <so:extra>} bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:variable name="n" select="normalize-space($json-in)" />
<xsl:choose>
<xsl:when test="substring($n,1,1) = $quot">
<xsl:variable name="t1">
<xsl:call-template name="field">
<xsl:with-param name="json-in" select="$n" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t2" select="normalize-space( exsl:node-set($t1)/so:extra) " />
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="substring($t2,1,1)=','">
<xsl:call-template name="fields">
<xsl:with-param name="json-in" select="substring-after($t2,',')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$t2">
<so:extra><xsl:value-of select="$t2" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:copy-of select="exsl:node-set($t1)/so:output/* | exsl:node-set($t3)/so:output/*" />
</so:output>
<xsl:copy-of select="exsl:node-set($t3)/so:extra" />
</xsl:when>
<xsl:when test="$n">
<so:extra><xsl:value-of select="$n" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="object">
<!-- Input like: { X } bla -->
<!-- output like: <so:output>fields(X)</so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" select="''" />
<xsl:variable name="t1" select="normalize-space(substring-after($json-in,'{'))" />
<xsl:variable name="t2">
<xsl:call-template name="fields">
<xsl:with-param name="json-in" select="$t1" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t3" select="normalize-space(substring-after( exsl:node-set($t2)/so:extra, '}'))" />
<so:output>
<xsl:choose>
<xsl:when test="$parent-ele">
<xsl:element name="{$parent-ele}">
<xsl:copy-of select="exsl:node-set($t2)/so:output/node()" />
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="exsl:node-set($t2)/so:output/node()" />
</xsl:otherwise>
</xsl:choose>
</so:output>
<xsl:if test="$t3">
<so:extra><xsl:value-of select="$t3" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="objects">
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="n" select="normalize-space($json-in)" />
<xsl:choose>
<xsl:when test="substring($n,1,1) = '{'">
<xsl:variable name="t1">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="$n" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t2" select="normalize-space( exsl:node-set($t1)/so:extra) " />
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="substring($t2,1,1)='{'">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="$t2" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="substring($t2,1,1)=',' and substring(normalize-space(substring-after($t2,',')),1,1)='{'">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="normalize-space(substring-after($t2,','))" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$t2">
<so:extra><xsl:value-of select="$t2" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:copy-of select="exsl:node-set($t1)/so:output/* | exsl:node-set($t3)/so:output/*" />
</so:output>
<xsl:copy-of select="exsl:node-set($t3)/so:extra" />
</xsl:when>
<xsl:when test="$n">
<so:extra><xsl:value-of select="$n" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="array">
<!-- Input like: [ X1 X2 ] bla -->
<!-- output like: <so:output><Y>X1</Y><Y>X2</Y></so:output> <so:extra>}bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="t1" select="normalize-space(substring-after($json-in,'['))" />
<xsl:variable name="t2">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="$t1" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="contains(substring-before(exsl:node-set($t2)/so:extra,']'),',')">
<xsl:value-of select="normalize-space(substring-after(exsl:node-set($t2)/so:extra,','))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-after(exsl:node-set($t2)/so:extra,']'))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="t4">
<xsl:element name="{$parent-ele}">
<xsl:for-each select="$t2/so:output/*[local-name(.)=$parent-ele]">
<xsl:variable name="self" select="."/>
<xsl:variable name="tempResult">
<xsl:element name="{concat($parent-ele,'_element')}">
<xsl:copy-of select="exsl:node-set($self/*)" />
</xsl:element>
</xsl:variable>
<xsl:copy-of select="exsl:node-set($tempResult)"/>
</xsl:for-each>
</xsl:element>
</xsl:variable>
<xsl:variable name="t5" select="exsl:node-set($t4)"/>
<so:output>
<xsl:copy-of select="$t5"/>
</so:output>
<xsl:if test="$t3">
<so:extra><xsl:value-of select="$t3" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="value">
<!-- Input like either array, object or string -->
<!-- output like either array, object or string -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="first-letter" select="substring(normalize-space($json-in),1,1)" />
<xsl:choose>
<xsl:when test="$first-letter='{'">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$first-letter='['">
<xsl:call-template name="array">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$first-letter=$quot">
<xsl:call-template name="string">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($numbers,$first-letter)">
<xsl:call-template name="number">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($booleans,$first-letter)">
<xsl:call-template name="boolean">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<so:output>ERROR</so:output>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="string">
<!-- Input like: "X" bla -->
<!-- output like: <so:output><Y>X</Y></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="value" select="substring-before(substring-after($json-in,$quot),$quot)" />
<xsl:variable name="remainder" select="normalize-space(substring-after(substring-after($json-in,$quot),$quot))" />
<so:output>
<xsl:element name="{$parent-ele}">
<xsl:value-of select="$value" />
</xsl:element>
</so:output>
<xsl:if test="$remainder">
<so:extra><xsl:value-of select="$remainder" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="number">
<!-- Input like: "X" bla -->
<!-- output like: <so:output><Y>X</Y></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="normalize-space(substring-before($json-in,'}'))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="normalize-space(substring-before($json-in,']'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-before($json-in,','))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="remainder">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="concat('}',normalize-space(substring-after($json-in,'}')))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="concat(']',normalize-space(substring-after($json-in,']')))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(',',normalize-space(substring-after($json-in,',')))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:element name="{$parent-ele}">
<xsl:value-of select="$value" />
</xsl:element>
</so:output>
<xsl:if test="$remainder">
<so:extra><xsl:value-of select="$remainder" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="boolean">
<!-- Input like: "X" bla -->
<!-- output like: <so:output><Y>X</Y></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="normalize-space(substring-before($json-in,'}'))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="normalize-space(substring-before($json-in,']'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-before($json-in,','))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="remainder">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="concat('}',normalize-space(substring-after($json-in,'}')))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="concat(']',normalize-space(substring-after($json-in,']')))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(',',normalize-space(substring-after($json-in,',')))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:element name="{$parent-ele}">
<xsl:value-of select="$value" />
</xsl:element>
</so:output>
<xsl:if test="$remainder">
<so:extra><xsl:value-of select="$remainder" /></so:extra>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
So with this (adjusted) input:
<?xml version="1.0" encoding="UTF-8"?>
<sampleTag><![CDATA[
{
"Order": {
"InvestmentAccount": { "AccountNumber": "10" },
"Parcel": {
"Limit": 0,
"ExpiryDate": "1900-01-01T00:00:00",
"valid": true,
"Asset": [
{
"Open": 25.15,
"High": 25.15,
"Low": 25.11,
"Close": 25.87
},
{
"Open": 25.15,
"High": 25.15,
"Low": 25.11,
"Close": 25.87
}
]
},
"OrderDate": "2012-10-11T21:46:03.6489906+11:00"
}
}
]]></sampleTag>
I get this output:
<?xml version="1.0" encoding="UTF-8"?>
<Order>
<InvestmentAccount>
<AccountNumber>10</AccountNumber>
</InvestmentAccount>
<Parcel>
<Limit>0</Limit>
<ExpiryDate>1900-01-01T00:00:00</ExpiryDate>
<valid>true</valid>
<Asset>
<Asset_element>
<Open>25.15</Open>
<High>25.15</High>
<Low>25.11</Low>
<Close>25.87</Close>
</Asset_element>
<Asset_element>
<Open>25.15</Open>
<High>25.15</High>
<Low>25.11</Low>
<Close>25.87</Close>
</Asset_element>
</Asset>
</Parcel>
<OrderDate>2012-10-11T21:46:03.6489906+11:00</OrderDate>
</Order>
Updating Samuel Murphy answer.
Updates include:
Support for null
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:json="http://www.ibm.com/xmlns/prod/2009/jsonx"
xmlns:exsl="http://exslt.org/common"
xmlns:so="http://stackoverflow.com/questions/13007280"
exclude-result-prefixes="xsl xs json so exsl">
<xsl:output indent="yes" encoding="UTF-8" />
<xsl:strip-space elements="*" />
<xsl:variable name="quot" select="'"'" />
<xsl:variable name="numbers" select="'0123456789'"/>
<xsl:variable name="booleans" select="'tf'"/>
<xsl:variable name="nulls" select="'n'"/>
<xsl:template match="/*">
<xsl:variable name="t1">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="exsl:node-set($t1)/so:output/*" mode="copy-sans-namespace" />
</xsl:template>
<xsl:template match="*" mode="copy-sans-namespace">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates mode="copy-sans-namespace" />
</xsl:element>
</xsl:template>
<xsl:template name="field">
<!-- Input like: "Open": "25.15" bla -->
<!-- output like: <so:output><Open>25.15</Open></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:variable name="field-name" select="substring-before(substring-after($json-in,$quot),$quot)" />
<xsl:variable name="remainder" select="substring-after($json-in,':')" />
<xsl:call-template name="value">
<xsl:with-param name="json-in" select="$remainder" />
<xsl:with-param name="parent-ele" select="$field-name" />
</xsl:call-template>
</xsl:template>
<xsl:template name="fields">
<!-- Input like: "Open": "25.15" , "High": "25.15" } bla -->
<!-- output like: <so:output><Open>25.15</Open><High>25.15</High></so:output> <so:extra>} bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:variable name="n" select="normalize-space($json-in)" />
<xsl:choose>
<xsl:when test="substring($n,1,1) = $quot">
<xsl:variable name="t1">
<xsl:call-template name="field">
<xsl:with-param name="json-in" select="$n" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t2" select="normalize-space( exsl:node-set($t1)/so:extra) " />
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="substring($t2,1,1)=','">
<xsl:call-template name="fields">
<xsl:with-param name="json-in" select="substring-after($t2,',')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$t2">
<so:extra><xsl:value-of select="$t2" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:copy-of select="exsl:node-set($t1)/so:output/* | exsl:node-set($t3)/so:output/*" />
</so:output>
<xsl:copy-of select="exsl:node-set($t3)/so:extra" />
</xsl:when>
<xsl:when test="$n">
<so:extra><xsl:value-of select="$n" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="object">
<!-- Input like: { X } bla -->
<!-- output like: <so:output>fields(X)</so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" select="''" />
<xsl:variable name="t1" select="normalize-space(substring-after($json-in,'{'))" />
<xsl:variable name="t2">
<xsl:call-template name="fields">
<xsl:with-param name="json-in" select="$t1" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t3" select="normalize-space(substring-after( exsl:node-set($t2)/so:extra, '}'))" />
<so:output>
<xsl:choose>
<xsl:when test="$parent-ele">
<xsl:element name="{$parent-ele}">
<xsl:copy-of select="exsl:node-set($t2)/so:output/node()" />
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="exsl:node-set($t2)/so:output/node()" />
</xsl:otherwise>
</xsl:choose>
</so:output>
<xsl:if test="$t3">
<so:extra><xsl:value-of select="$t3" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="objects">
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="n" select="normalize-space($json-in)" />
<xsl:choose>
<xsl:when test="substring($n,1,1) = '{'">
<xsl:variable name="t1">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="$n" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t2" select="normalize-space( exsl:node-set($t1)/so:extra) " />
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="substring($t2,1,1)='{'">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="$t2" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="substring($t2,1,1)=',' and substring(normalize-space(substring-after($t2,',')),1,1)='{'">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="normalize-space(substring-after($t2,','))" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$t2">
<so:extra><xsl:value-of select="$t2" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:copy-of select="exsl:node-set($t1)/so:output/* | exsl:node-set($t3)/so:output/*" />
</so:output>
<xsl:copy-of select="exsl:node-set($t3)/so:extra" />
</xsl:when>
<xsl:when test="$n">
<so:extra><xsl:value-of select="$n" /></so:extra>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="array">
<!-- Input like: [ X1 X2 ] bla -->
<!-- output like: <so:output><Y>X1</Y><Y>X2</Y></so:output> <so:extra>}bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="t1" select="normalize-space(substring-after($json-in,'['))" />
<xsl:variable name="t2">
<xsl:call-template name="objects">
<xsl:with-param name="json-in" select="$t1" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="t3">
<xsl:choose>
<xsl:when test="contains(substring-before(exsl:node-set($t2)/so:extra,']'),',')">
<xsl:value-of select="normalize-space(substring-after(exsl:node-set($t2)/so:extra,','))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-after(exsl:node-set($t2)/so:extra,']'))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="t4">
<xsl:element name="{$parent-ele}">
<xsl:for-each select="$t2/so:output/*[local-name(.)=$parent-ele]">
<xsl:variable name="self" select="."/>
<xsl:variable name="tempResult">
<xsl:element name="{concat($parent-ele,'_element')}">
<xsl:copy-of select="exsl:node-set($self/*)" />
</xsl:element>
</xsl:variable>
<xsl:copy-of select="exsl:node-set($tempResult)"/>
</xsl:for-each>
</xsl:element>
</xsl:variable>
<xsl:variable name="t5" select="exsl:node-set($t4)"/>
<so:output>
<xsl:copy-of select="$t5"/>
</so:output>
<xsl:if test="$t3">
<so:extra><xsl:value-of select="$t3" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="value">
<!-- Input like either array, object or string -->
<!-- output like either array, object or string -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="first-letter" select="substring(normalize-space($json-in),1,1)" />
<xsl:choose>
<xsl:when test="$first-letter='{'">
<xsl:call-template name="object">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$first-letter='['">
<xsl:call-template name="array">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$first-letter=$quot">
<xsl:call-template name="string">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele" />
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($numbers,$first-letter)">
<xsl:call-template name="number">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($booleans,$first-letter)">
<xsl:call-template name="boolean">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($nulls,$first-letter)">
<xsl:call-template name="boolean">
<xsl:with-param name="json-in" select="$json-in" />
<xsl:with-param name="parent-ele" select="$parent-ele"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<so:output>ERROR</so:output>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="string">
<!-- Input like: "X" bla -->
<!-- output like: <so:output><Y>X</Y></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="value" select="substring-before(substring-after($json-in,$quot),$quot)" />
<xsl:variable name="remainder" select="normalize-space(substring-after(substring-after($json-in,$quot),$quot))" />
<so:output>
<xsl:element name="{$parent-ele}">
<xsl:value-of select="$value" />
</xsl:element>
</so:output>
<xsl:if test="$remainder">
<so:extra><xsl:value-of select="$remainder" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="number">
<!-- Input like: "X" bla -->
<!-- output like: <so:output><Y>X</Y></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="normalize-space(substring-before($json-in,'}'))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="normalize-space(substring-before($json-in,']'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-before($json-in,','))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="remainder">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="concat('}',normalize-space(substring-after($json-in,'}')))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="concat(']',normalize-space(substring-after($json-in,']')))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(',',normalize-space(substring-after($json-in,',')))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:element name="{$parent-ele}">
<xsl:value-of select="$value" />
</xsl:element>
</so:output>
<xsl:if test="$remainder">
<so:extra><xsl:value-of select="$remainder" /></so:extra>
</xsl:if>
</xsl:template>
<xsl:template name="boolean">
<!-- Input like: "X" bla -->
<!-- output like: <so:output><Y>X</Y></so:output> <so:extra>bla</so:extra> -->
<xsl:param name="json-in" />
<xsl:param name="parent-ele" />
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="normalize-space(substring-before($json-in,'}'))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="normalize-space(substring-before($json-in,']'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-before($json-in,','))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="remainder">
<xsl:choose>
<xsl:when test="contains(substring-before($json-in,','),'}')">
<xsl:value-of select="concat('}',normalize-space(substring-after($json-in,'}')))"/>
</xsl:when>
<xsl:when test="contains(substring-before($json-in,','),']')">
<xsl:value-of select="concat(']',normalize-space(substring-after($json-in,']')))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(',',normalize-space(substring-after($json-in,',')))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<so:output>
<xsl:element name="{$parent-ele}">
<xsl:value-of select="$value" />
</xsl:element>
</so:output>
<xsl:if test="$remainder">
<so:extra><xsl:value-of select="$remainder" /></so:extra>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The question is tagged for XSLT 1.0, so I do not know if this answer will help the original question. But if you can use XSLT 3.0, the function json-to-xml does exactly what you need.
https://www.w3.org/TR/xslt-30/#func-json-to-xml
As well as Dimitre's XSLT parsing framework, there is also Gunther Rademacher's Rex parser generator, which also includes JSON as one of its sample grammars:
http://www.bottlecaps.de/rex/
Try this lib:
https://github.com/bramstein/xsltjson
Looks very nice.
Its a 2.0 XSLT solution, although he also points to 1.0 version.
XSLT has many strengths and a few big weaknesses. Text processing is its weakness at least version 1.0
Although it would be technically possible to process that text with XSLT 1.0, I can't think of any situation where it would be a very good idea, and where it wouldn't be a very fragile conversion. The code you would have to produce would be very unwieldy.
Is there no other language available to you to do the processing?