In continuation with what is asked in below link regarding capitalising first character
Convert First character of each word to upper case
The above link assumes that there is space between each characters. How can I dynamically identify any non-alphanumeric character in between and then capitalise the following letters
For e.g. O'connel derrick should return as O'Connel Derrick
and
Adrian-merriel james should return as Adrian-Merriel James
I used below code and it works fine for string with space
<xsl:variable name='text' select='"dInEsh sAchdeV kApil Muk"' />
<xsl:variable name='lowers' select='"abcdefghijklmnopqrstuvwxyz"' />
<xsl:variable name='uppers' select='"ABCDEFGHIJKLMNOPQRSTUVWXYZ"' />
<xsl:template match="/">
<xsl:for-each select='str:split($text, " ")'>
<xsl:value-of select='concat(
translate(substring(., 1, 1), $lowers, $uppers),
translate(substring(., 2), $uppers, $lowers),
" "
)' />
</xsl:for-each>
</xsl:template>
Any help is highly appreciated.
For an XSLT 1.0 solution, you could setup a recursive template call that walks over each of the characters and tracks whether or not it has seen a alpha-numeric value, capitalizing the first that it sees, and then resetting when it encounters a non-alpha-numeric value:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:variable name='text' select='"dInEsh sAchdeV kApil Muk"' />
<xsl:variable name='lowers' select='"abcdefghijklmnopqrstuvwxyz"' />
<xsl:variable name='uppers' select='"ABCDEFGHIJKLMNOPQRSTUVWXYZ"' />
<xsl:variable name='numeric' select='0123456789'/>
<xsl:variable name='alpha-numeric' select="concat($lowers,$uppers,$numeric)"/>
<xsl:template match="/">
<xsl:call-template name="capitalize">
<xsl:with-param name="val" select="$text"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="capitalize">
<xsl:param name="val"/>
<xsl:param name="alphanumeric-seen" select="false()" />
<xsl:variable name="head" select="substring($val, 1, 1)"/>
<xsl:if test="$head">
<xsl:variable name="is-alpha-numeric" select="not(translate($head, $alpha-numeric, ''))"/>
<xsl:variable name="tail" select="substring($val, 2)"/>
<xsl:choose>
<xsl:when test="$is-alpha-numeric">
<xsl:choose>
<xsl:when test="$alphanumeric-seen">
<xsl:value-of select="translate($head, $uppers, $lowers)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate($head, $lowers, $uppers)"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="capitalize">
<xsl:with-param name="val" select="$tail"/>
<xsl:with-param name="alphanumeric-seen" select="true()"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$head"/>
<xsl:call-template name="capitalize">
<xsl:with-param name="val" select="$tail"/>
<xsl:with-param name="alphanumeric-seen" select="false()"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
With XSLT 2.0 you could use xsl:analyze-string and regex:
<xsl:stylesheet exclude-result-prefixes="#all" version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:variable name='text' select='"dInEsh sAchdeV kApil Muk"' />
<xsl:variable name='lowers' select='"abcdefghijklmnopqrstuvwxyz"' />
<xsl:variable name='uppers' select='"ABCDEFGHIJKLMNOPQRSTUVWXYZ"' />
<xsl:template match="/">
<xsl:analyze-string select="$text" regex="[a-zA-Z0-9]+">
<xsl:matching-substring>
<xsl:value-of select="upper-case(substring(., 1, 1)), lower-case(substring(., 2))" separator=""/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
I would also use a recursive named template - but instead of going over each character in text I would iterate on delimiters only:
XSLT 1.0 + EXSLT str:split() extension function
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="text">dInEsh sAchdeV kApil. muk O'connel derrick, Adrian-merriel james</xsl:param>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:variable name="punc" select="translate($text, concat($lower, upper), '')" />
<xsl:template match="/">
<output>
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="translate($text, $upper, $lower)"/>
<xsl:with-param name="delimiters" select="translate($text, concat($lower, $upper), '')"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="capitalize">
<xsl:param name="text"/>
<xsl:param name="delimiters"/>
<xsl:choose>
<xsl:when test="$delimiters">
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)"/>
<xsl:call-template name="capitalize">
<xsl:with-param name="text">
<xsl:for-each select="str:split($text, $delimiter)">
<xsl:value-of select="translate(substring(., 1, 1), $lower, $upper)"/>
<xsl:value-of select="substring(., 2)"/>
<xsl:if test="position()!=last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
</xsl:with-param>
<xsl:with-param name="delimiters" select="translate($delimiters, $delimiter, '')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate(substring($text, 1, 1), $lower, $upper)"/>
<xsl:value-of select="substring($text, 2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>Dinesh Sachdev Kapil. Muk O'Connel Derrick, Adrian-Merriel James</output>
However, this is not perfect: a sequence of consecutive delimiter character of the same kind will be reduced to a single character - e.g. alpha---bravo ==> Alpha-Bravo.
I've searched far and wide for an example of this, and have so far had no luck in applying a sum template to make my XSTL work.
This is the XML (number of lines varies on each planfeature)
<PlanFeatures>
<PlanFeature name="Line0001">
<CoordGeom>
<Line>
<Start pntRef="7540">5605 8950 1020</Start>
<End pntRef="7541">5605 8951 1019</End>
</Line>
<Line>
<Start pntRef="7541">5605 8951 1019</Start>
<End pntRef="7542">5605 8947 1019</End>
</Line>
<Line>
<Start pntRef="7542">5605 8947 1019</Start>
<End pntRef="7543">5605 8940 1011</End>
</Line>
<Line>
<Start pntRef="7543">5605 8940 1011</Start>
<End pntRef="7544">5605 8931 1020</End>
</Line>
</CoordGeom>
</PlanFeature>
</PlanFeatures>
This is where I'm at with the XSL, which uses a recursive call template to calculate the distance of each line segment.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:landxml="http://www.landxml.org/schema/LandXML-1.2" xmlns:hexagon="http://xml.hexagon.com/schema/HeXML-1.5" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-16" indent="no" omit-xml-declaration="yes"/>
<xsl:variable name="XML" select="/"/>
<xsl:variable name="fileExt" select="'txt'"/>
<xsl:variable name="fileDesc" select="'line distance report'"/>
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:for-each select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature">
<xsl:value-of select="#name"/><xsl:text>::</xsl:text>
<xsl:for-each select="landxml:CoordGeom/landxml:Line">
<xsl:value-of select="landxml:Start/#pntRef"/><xsl:text>-</xsl:text>
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:value-of select="landxml:End/#pntRef"/><xsl:text>: </xsl:text>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:variable name="seg" select= "((($x2 - $x1)*($x2 - $x1))+(($y2 - $y1)*($y2 - $y1))+(($z2 - $z1)*($z2 - $z1)))"/>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$seg"/>
</xsl:call-template>
<xsl:text>, </xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="root">
<xsl:param name="X"/>
<xsl:param name="xn" select="0"/>
<xsl:param name="xn_1" select="($X+1) div 2"/>
<xsl:choose>
<xsl:when test="string(number($X)) = 'NaN'">
<xsl:value-of select=" ' ' "/>
</xsl:when>
<xsl:when test="($xn_1 - $xn) * ($xn_1 - $xn) < 0.00000001">
<xsl:value-of select='format-number($xn_1, "#.000")'/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$X"/>
<xsl:with-param name="xn" select="$xn_1"/>
<xsl:with-param name="xn_1" select="($xn_1 + ($X div $xn_1)) div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I need to sum the value of X (distance) from the root call template, to create a value which represents the sum of each line segment. I think I need to use a match template, but so far it hand even got close to working.
Currently exporting LINEID::StartPt-EndPt: dist, StartPt-EndPt: dist, etc. I need the sum of the 'dist' to be shown at the end of each line as well. As below
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.720
but I would like
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.728 -- 28.184
Any help would be appreciated... the examples on this site have helped me so much already, but I just can't seem to get through this roadblock.
Cheers,
Chris
You can accomplish this with some relatively simple recursion and parameter passing. Try replacing your first template with these four templates:
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:apply-templates
select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature" />
</xsl:for-each>
</xsl:template>
<xsl:template match ="landxml:PlanFeature">
<xsl:value-of select="concat(#name, '::')" />
<xsl:apply-templates select="landxml:CoordGeom/landxml:Line[1]" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="landxml:Line">
<xsl:param name="total" select="0" />
<xsl:value-of
select="concat(landxml:Start/#pntRef, '-', landxml:End/#pntRef, ': ')"/>
<xsl:variable name="len">
<xsl:call-template name="root">
<xsl:with-param name="X">
<xsl:call-template name="seg" />
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$len"/>
<xsl:variable name="next" select="following-sibling::landxml:Line[1]" />
<xsl:variable name="newTot" select="$total + $len" />
<xsl:choose>
<xsl:when test="$next">
<xsl:text>, </xsl:text>
<xsl:apply-templates select="$next">
<xsl:with-param name="total" select="$newTot" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' -- ', format-number($newTot, '#.000'))" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="seg">
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:value-of select= "($x2 - $x1)*($x2 - $x1)+
($y2 - $y1)*($y2 - $y1)+
($z2 - $z1)*($z2 - $z1)"/>
</xsl:template>
When run on your sample input (after adjusting it to match the paths in your XSLT), the result is:
Line0001::7540-7541: 1.414, 7541-7542: 4.000, 7542-7543: 10.630, 7543-7544: 12.728 -- 28.772
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,,,,,