XSLT to output plain text table - xslt

I'm working on an XSL template to convert an XHTML/hResume document to plain text, and I'm having trouble with the table layout (no, not layout tables). At the moment I've got the following, using the excellent Dave Pawson's padding template:
<variable name="newline" select="'
'"/>
<template match="xhtml:table">
<variable name="maxWidth">
<for-each select="xhtml:tr/xhtml:th | xhtml:tr/xhtml:td">
<sort select="string-length(child::text()|child::node())" order="descending" data-type="number"/>
<if test="position() = 1">
<value-of select="string-length(child::text()|child::node())"/>
</if>
</for-each>
</variable>
<for-each select="xhtml:tr">
<for-each select="xhtml:th|xhtml:td">
<variable name="string">
<for-each select="child::text()|child::node()">
<value-of select="."/>
</for-each>
</variable>
<value-of select="$string"/>
<call-template name="append-pad">
<with-param name="length" select="$maxWidth - string-length($string)"/>
</call-template>
<text> </text>
</for-each>
<value-of select="$newline"/>
</for-each>
<value-of select="$newline"/>
</template>
This produces columns of equal width, but I'd like to improve it in a couple ways:
Find and use the max width of each column. For that it's necessary to store a flexible number of values. I can change maxWidth to do this in the simple cases, but how do you handle spanning columns?
Center the contents of spanning columns.
Are there any templates to do something like this?

With a "global" (for every cell in table) $maxWith you could handle colspans like this stylesheet (preserving your own logic):
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<output method="text"/>
<variable name="newline" select="'
'"/>
<template match="xhtml:table">
<variable name="maxWidth">
<for-each select="xhtml:tr/xhtml:th | xhtml:tr/xhtml:td">
<sort select="string-length(child::text()|child::node())" order="descending" data-type="number"/>
<if test="position() = 1">
<value-of select="string-length(child::text()|child::node())"/>
</if>
</for-each>
</variable>
<for-each select="xhtml:tr">
<for-each select="xhtml:th|xhtml:td">
<variable name="string">
<for-each select="child::text()|child::node()">
<value-of select="."/>
</for-each>
</variable>
<variable name="padding">
<choose>
<when test="#colspan">
<value-of select="$maxWidth * #colspan + #colspan - 1 - string-length($string)"/>
</when>
<otherwise>
<value-of select="$maxWidth - string-length($string)"/>
</otherwise>
</choose>
</variable>
<value-of select="$string"/>
<call-template name="append-pad">
<with-param name="length" select="$padding"/>
</call-template>
<text> </text>
</for-each>
<value-of select="$newline"/>
</for-each>
<value-of select="$newline"/>
</template>
<template name="append-pad">
<param name="length" select="0"/>
<if test="$length != 0">
<value-of select="' '"/>
<call-template name="append-pad">
<with-param name="length" select="$length - 1"/>
</call-template>
</if>
</template>
</stylesheet>
Input:
<table xmlns="http://www.w3.org/1999/xhtml">
<tr>
<th>First</th>
<th>Second</th>
<th>Third</th>
</tr>
<tr>
<td>One</td>
<td>Two</td>
<td>Three</td>
</tr>
<tr>
<td colspan="2">Uno</td>
<td>Tres</td>
</tr>
</table>
Output:
First Second Third
One Two Three
Uno Tres
EDIT: In order to center the cells with colspan, use this stylesheet (now with my own logic):
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<output method="text"/>
<variable name="newline" select="'
'"/>
<template match="xhtml:table">
<apply-templates>
<with-param name="maxWidth">
<for-each select="xhtml:tr/xhtml:th | xhtml:tr/xhtml:td">
<sort select="string-length(.)" order="descending" data-type="number"/>
<if test="position() = 1">
<value-of select="string-length(.)"/>
</if>
</for-each>
</with-param>
</apply-templates>
<value-of select="$newline"/>
</template>
<template match="xhtml:tr">
<param name="maxWidth"/>
<apply-templates>
<with-param name="maxWidth" select="$maxWidth"/>
</apply-templates>
<value-of select="$newline"/>
</template>
<template match="xhtml:th|xhtml:td">
<param name="maxWidth"/>
<variable name="string">
<for-each select="child::text()|child::node()">
<value-of select="."/>
</for-each>
</variable>
<variable name="padding">
<choose>
<when test="#colspan">
<value-of select="($maxWidth * #colspan + #colspan - 1 - string-length($string)) div 2"/>
</when>
<otherwise>
<value-of select="$maxWidth - string-length($string)"/>
</otherwise>
</choose>
</variable>
<if test="#colspan">
<call-template name="append-pad">
<with-param name="length" select="floor($padding)"/>
</call-template>
</if>
<value-of select="$string"/>
<call-template name="append-pad">
<with-param name="length" select="ceiling($padding)"/>
</call-template>
<text> </text>
</template>
<template name="append-pad">
<param name="length" select="0"/>
<if test="$length != 0">
<value-of select="' '"/>
<call-template name="append-pad">
<with-param name="length" select="$length - 1"/>
</call-template>
</if>
</template>
</stylesheet>
Output:
First Second Third
One Two Three
Uno Tres

Related

extract value from an array in xslt

I have variable 'temperatureQualifier' whose type is array. I need to read that array variable and extract each value from the array and use it in my XSLT.
Sample Input XML is
<document>
<item>
<gtin>1000909090</gtin>
<flex>
<attrGroupMany name="tradeItemTemperatureInformation">
<row>
<attr name="temperatureQualifier">[10, 20, 30, 40]</attr>
</row>
</attrGroupMany>
</flex>
</item>
</document>
Desired Output XML should be
<?xml version="1.0" encoding="UTF-8"?>
<CatalogItem>
<RelationshipData>
<Relationship>
<RelationType>Item_Master_TRADEITEM_TEMPERATURE_MVL</RelationType>
<RelatedItems>
<Attribute name="code">
<Value>10</Value>
</Attribute>
<Attribute name="code">
<Value>20</Value>
</Attribute>
<Attribute name="code">
<Value>30</Value>
</Attribute>
<Attribute name="code">
<Value>40</Value>
</Attribute>
</RelatedItems>
</Relationship>
</RelationshipData>
</CatalogItem>
I am using the below XSLT but it is giving me all values in 1 node only.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="document">
<CatalogItem>
<RelationshipData>
<Relationship>
<RelationType>Item_Master_TRADEITEM_TEMPERATURE_MVL</RelationType>
<RelatedItems>
<xsl:for-each select="item/flex/attrGroupMany[#name ='tradeItemTemperatureInformation']/row">
<Attribute name="code">
<Value>
<xsl:value-of select="attr[#name='temperatureQualifier']"/>
</Value>
</Attribute>
</xsl:for-each>
</RelatedItems>
</Relationship>
</RelationshipData>
</CatalogItem>
</xsl:template>
</xsl:stylesheet>
Note: Number of value in array can be 1 or more than 1.
Example for single value array is [10]
Example for multie value array is [10, 20, 30, 40]
With XST 1.0 you can use a recursive split:
<xsl:template name="split">
<xsl:param name="str" select="."/>
<xsl:choose>
<xsl:when test="contains($str, ',')">
<Attribute name="code">
<Value>
<xsl:value-of select="normalize-space(substring-before($str, ','))"/>
</Value>
</Attribute>
<xsl:call-template name="split">
<xsl:with-param name="str" select="substring-after($str, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<Attribute name="code">
<Value>
<xsl:value-of select="$str"/>
</Value>
</Attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
And call it:
<xsl:call-template name="split">
<xsl:with-param name="str" select="substring-before(
substring-after(
attr[#name='temperatureQualifier'], '[' )
,']' )"/>
</xsl:call-template>
Using the latest version of Saxon you could try XSLT 3.0 and
<xsl:for-each
select="item/flex/attrGroupMany[#name = 'tradeItemTemperatureInformation']/row/attr[#name = 'temperatureQualifier']/json-to-xml(.)//*:number">
<Attribute name="code">
<Value>
<xsl:value-of select="."/>
</Value>
</Attribute>
</xsl:for-each>

XSLT grouping with multiple group collection

I need some help with this XSLT. It is working the way it is suppose to however I have change in requirements ... :-) and I need to modify this to give me the expected output.
I'm looking for some guidance and help.
Explanation:
I have a source xml like this
<XML>
<Attributes>
<Attribute>
<Name/>
<Type/>
<Value/>
<FromIM/>
<collection/>
<Path />
</Attribute>
</Attributes>
</XML>
In the above xml I need to look at the node "Type" and group them by the type. For example, if I have 5 attribute where Type is common, and 4 attributes where Type is category, and 3 attributes where Type is Complex then group them like this.
<?xml version="1.0" encoding="utf-8"?>
<Data Schema="XML A">
<Items>
<Item id="" shortname="FT123" longname="FT123" categorypath="FamilyName//DepartmentName//GroupName" type="Product">
<Attributes type="common">
<Attr name="common 1" value="1" path=""/>
<Attr name="common 2" value="2" path=""/>
<Attr name="common 3" value="3" path=""/>
<Attr name="common 4" value="4" path=""/>
<Attr name="common 5" value="4" path=""/>
<Collection id="" name="Collection" path="">
<Complex>
<Attr name="UPC" value="Testing" valueKey="0" />
<Attr name="Color" value="Yellow" valueKey="0"/>
<Attr name="Size" value="10" valueKey="0"/>
</Complex>
</Collection>
</Attributes>
<Attributes type="category">
<Attr name="category1" value="1" />
<Attr name="category2" value="2" />
<Attr name="category3" value="3" />
<Attr name="category4" value="4" />
</Attributes>
</Item>
</Items>
</Data>
As you can see from above that I'm group first common & category and creating a group collection for Complex under common. This is working fine (Although I'm using Iteration ... :-))
The problem is I'm creating a Complex for only 1 attribute where Name = Collection and it is hard coded. However, the new requirement is that I have create a complex collection for another attribute where name=Cost.
This is where I'm having problem. How can I do this. Below are the sample source and output xml and XSLT. Thanks in Advance.
Source XML:
<?xml version="1.0" encoding="Windows-1252"?>
<XML>
<Attributes>
<Attribute>
<Name>FamilyName</Name>
<Type>common</Type>
<Value>Footwear</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>DepartmentName</Name>
<Type>common</Type>
<Value>Footwear</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>GroupName</Name>
<Type>common</Type>
<Value>Men's Boots</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Buyer ID</Name>
<Type>common</Type>
<Value>Lee</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Enviornment</Name>
<Type>common</Type>
<Value>Dev</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Retail</Name>
<Type>common</Type>
<Value></Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Gender</Name>
<Type>category</Type>
<Value>M</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Cost</Name>
<Type>Complex</Type>
<Value>20.00</Value>
<FromIM>yes</FromIM>
<collection>Y</collection>
<Path />
</Attribute>
<Attribute>
<Name>Collection</Name>
<Type>Complex</Type>
<Value>ing</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>UPC</Name>
<Type>Complex</Type>
<Value>Testing</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Color</Name>
<Type>Complex</Type>
<Value>Yellow</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Size</Name>
<Type>Complex</Type>
<Value>10</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Style</Name>
<Type>Complex</Type>
<Value>MA</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>UPC</Name>
<Type>Complex</Type>
<Value>24a</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Color</Name>
<Type>Complex</Type>
<Value>Green</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Size</Name>
<Type>Complex</Type>
<Value>22</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
<Attribute>
<Name>Style</Name>
<Type>Complex</Type>
<Value>AM</Value>
<FromIM>no</FromIM>
<collection>N</collection>
<Path />
</Attribute>
</Attributes>
</XML>
Expected Output:
I need 2 collection nodes also in date I need to put current date.
Note in the Collection Node I can have multiple Complex nodes. However, in the Cost I will have only 1 Complex node.
<?xml version="1.0" encoding="utf-8"?>
<Data Schema="XML A">
<Items>
<Item id="" shortname="FT123" longname="FT123" sku="FT123" action="ADD" categorypath="FamilyName//DepartmentName//GroupName" type="Product">
<Attributes type="common">
<Attr name="Buyer ID" value="Lee" path="" action="ADD" />
<Attr name="Enviornment" value="Dev" path="" action="ADD" />
<Attr name="Retail" value="" path="" action="ADD" />
<Collection id="" name="Collection" path="">
<Complex>
<Attr name="UPC" value="Testing" valueKey="0" />
<Attr name="Color" value="Yellow" valueKey="0"/>
<Attr name="Size" value="10" valueKey="0"/>
<Attr name="Style" value="MA" valueKey="0"/>
</Complex>
<Complex>
<Attr name="UPC" value="24a" valueKey="0"/>
<Attr name="Color" value="Green" valueKey="0"/>
<Attr name="Size" value="22" valueKey="0"/>
<Attr name="Style" value="AM" valueKey="0"/>
</Complex>
</Collection>
<Collection id="" name="Cost" path="">
<Complex>
<Attr name="Cost" value="22" valueKey="0" />
<Attr name="Date" value="" valueKey="0"/>
</Complex>
</Collection>
</Attributes>
<Attributes type="category">
<Attr name="Gender" value="M" />
</Attributes>
</Item>
</Items>
</Data>
XSLT: Updated based on Michael's comment
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="type" match="Attribute" use="Type"/>
<xsl:template match="/">
<Data Schema="XML A">
<Items>
<Item>
<xsl:variable name="fileName" select="XML/Attributes/Attribute[Name = 'PIFileNumber']/Value"/>
<xsl:attribute name="id"></xsl:attribute>
<xsl:attribute name="shortname">
<xsl:value-of select="$fileName"/>
</xsl:attribute>
<xsl:attribute name="longname">
<xsl:value-of select="$fileName"/>
</xsl:attribute>
<xsl:variable name="familyName" select="XML/Attributes/Attribute[Name = 'FamilyName'/id"/>
<xsl:variable name="deptName" select="XML/Attributes/Attribute[Name = 'DepartmentName']/id"/>
<xsl:variable name="groupName" select="XML/Attributes/Attribute[Name = 'GroupName']/id"/>
<xsl:variable name="catPath" select="concat($familyName,'//',$deptName,'//',$groupName)" />
<xsl:attribute name="categorypath" select="$catPath"/>
<xsl:attribute name="type">Product</xsl:attribute>
<xsl:apply-templates select="XML/Attributes/Attribute[generate-id() = generate-id(key('type', Type)[1])]">
<xsl:sort select="Type" order="descending"/>
</xsl:apply-templates>
</Item>
</Items>
</Data>
</xsl:template>
<xsl:template match="Attribute">
<xsl:variable name="compType" select="count(/XML/Attributes/Attribute[Type='Complex' and Name!='Collection'])"/>
<xsl:variable name="colid" select="/XML/Attributes/Attribute[Name = 'Collection']/id"/>
<xsl:variable name="colname" select="/XML/Attributes/Attribute[Name = 'Collection']/Name"/>
<xsl:variable name="colpath" select="/XML/Attributes/Attribute[Name = 'Collection']/Path"/>
<xsl:if test="Type!='Complex'">
<Attributes type="{Type}">
<xsl:apply-templates select="key('type',Type)" mode="out"/>
<xsl:if test="Type='common'">
<Collection id="{$colid}" name="{$colname}" path="{$colpath}" action="ADD">
<xsl:choose>
<xsl:when test="$compType > 0">
<xsl:call-template name="for.loop">
<xsl:with-param name="i">1</xsl:with-param>
<xsl:with-param name="count" select="count(/XML/Attributes/Attribute[Type='Complex' and Name='UPC'])" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<Complex refId="0">
<MaskValue />
<Attr id="" name="UPC" value="" valueKey="0"/>
<xsl:choose>
<xsl:when test="count(/XML/Attributes/Attribute[Name = 'Color']) > 0">
<Attr id="{//Attribute[Name = 'Color']/id}" name="Color" value="{//Attribute[Name = 'Color']/Value}" valueKey="0"/>
</xsl:when>
<xsl:otherwise>
<Attr id="" name="Color" value="Default" valueKey="0"/>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="count(/XML/Attributes/Attribute[Name = 'Size']) > 0">
<Attr id="{//Attribute[Name = 'Size']/id}" name="Color" value="{//Attribute[Name = 'Size']/Value}" valueKey="0"/>
</xsl:when>
<xsl:otherwise>
<Attr id="" name="Size" value="Default" valueKey="0"/>
</xsl:otherwise>
</xsl:choose>
<Attr id="" name="Style" value="" valueKey="0"/>
<Attr id="" name="Exclude" value="0" valueKey="0"/>
</Complex>
</xsl:otherwise>
</xsl:choose>
</Collection>
</xsl:if>
</Attributes>
</xsl:if>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<xsl:if test="FromIM = 'yes'">
<xsl:choose>
<xsl:when test="collection = 'Y' and Name!='Color' and Name!='Size'">
<Collection id="" name="{Name}" path="{Path}">
<Attr value="{Value}" uom="" locale="en_WW"/>
</Collection>
</xsl:when>
<xsl:otherwise>
<xsl:if test="Name!='FileNumber' and Name!='NotReqInIM' and Name!='Color' and Name!='Size'">
<Attr id="{id}" name="{Name}" value="{Value}" path="{Path}" action="ADD" uom="" Locale="en_WW"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<xsl:template match="Attribute[Type='Complex']" mode="out">
<xsl:if test="Name!='Collection'">
<Attr id="{id}" name="{Name}" value="{Value}" valueKey="0"/>
</xsl:if>
</xsl:template>
<!-- this is for loop code -->
<xsl:template name="for.loop">
<xsl:param name="i" />
<xsl:param name="count" />
<!--begin_: Line_by_Line_Output -->
<xsl:if test="$i <= $count">
<xsl:if test="Name!='Collection'">
<Complex refId="0">
<MaskValue />
<Attr id="{(//Attribute[Type='Complex' and Name = 'UPC'])[position() = $i]/id}" name="UPC" value="{(//Attribute[Type='Complex' and Name = 'UPC'])[position() = $i]/Value}" valueKey="0"/>
<Attr id="{(//Attribute[Type='Complex' and Name = 'Color'])[position() = $i]/id}" name="Color" value="{(//Attribute[Type='Complex' and Name = 'Color'])[position() = $i]/Value}" valueKey="0"/>
<Attr id="{(//Attribute[Type='Complex' and Name = 'Size'])[position() = $i]/id}" name="Size" value="{(//Attribute[Type='Complex' and Name = 'Size'])[position() = $i]/Value}" valueKey="0"/>
<Attr id="{(//Attribute[Type='Complex' and Name = 'Style'])[position() = $i]/id}" name="Style" value="{(//Attribute[Type='Complex' and Name = 'Style'])[position() = $i]/Value}" valueKey="0"/>
<Attr id="0" name="Exclude" value="0" valueKey="0"/>
</Complex>
</xsl:if>
</xsl:if>
<!--begin_: RepeatTheLoopUntilFinished-->
<xsl:if test="$i <= $count">
<xsl:call-template name="for.loop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Although far from perfect, here is one stylesheet that will produce the output you wanted. I'm not sure to what degree the attributes of each complex are fixed or the collection are fixed. Let me know if this helps at all.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="type" match="Attribute" use="Type"/>
<xsl:template match="/">
<Data Schema="XML A">
<Items>
<Item>
<xsl:variable name="fileName" select="XML/Attributes/Attribute/Name['PIFileNumber']/Value"/>
<xsl:attribute name="id"></xsl:attribute>
<xsl:attribute name="shortname">
<xsl:value-of select="$fileName"/>
</xsl:attribute>
<xsl:attribute name="longname">
<xsl:value-of select="$fileName"/>
</xsl:attribute>
<xsl:variable name="familyName" select="XML/Attributes/Attribute[Name='FamilyName']/Value"/>
<xsl:variable name="deptName" select="XML/Attributes/Attribute[Name='DepartmentName']/Value"/>
<xsl:variable name="groupName" select="XML/Attributes/Attribute[Name='GroupName']/Value"/>
<xsl:variable name="catPath" select="concat($familyName,'//',$deptName,'//',$groupName)" />
<xsl:attribute name="categorypath">
<xsl:value-of select="$catPath"/>
</xsl:attribute>
<xsl:attribute name="type">Product</xsl:attribute>
<xsl:apply-templates select="XML/Attributes/Attribute[generate-id() = generate-id(key('type', Type)[1])]">
<xsl:sort select="Type" order="descending"/>
</xsl:apply-templates>
</Item>
</Items>
</Data>
</xsl:template>
<xsl:template match="Attribute" mode="Collection">
<Collection id="" name='Collection'>
<xsl:apply-templates select="../Attribute[Name='Collection']" mode="Coll"/>
</Collection>
<Collection id="" name='Cost'>
<xsl:apply-templates select="../Attribute[Name='Cost']" mode="Cost"/>
</Collection>
</xsl:template>
<xsl:template match="Attribute[Type='Complex']"/>
<xsl:template match="Attribute[Type != 'Complex']">
<Attributes type="{Type}">
<xsl:variable name="Type" select="Type"/>
<xsl:apply-templates select="../Attribute[Type=$Type]" mode="Attr"/>
<xsl:if test="Type='common'">
<xsl:apply-templates select="." mode="Collection"/>
</xsl:if>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="Coll">
<xsl:apply-templates select="../Attribute[Name='UPC']" mode="UPC"/>
</xsl:template>
<xsl:template match="Attribute" mode="Cost">
<complex>
<Attr name="Cost" value="{../Attribute[Name='Cost']/Value}" valueKey="0"/>
<Attr name="Date" value="" valueKey="0"/>
</complex>
</xsl:template>
<xsl:template match="Attribute" mode="Attr">
<Attr name="{Name}" value="{Value}" valueKey="0"/>
</xsl:template>
<xsl:template match="Attribute" mode="UPC">
<complex>
<Attr name="UPC" value="{Value}" valueKey="0"/>
<Attr name="Color" value="{following::node()[Name='Color']/Value}" valueKey="0"/>
<Attr name="Size" value="{following::node()[Name='Size']/Value}" valueKey="0"/>
<Attr name="Style" value="{following::node()[Name='Style']/Value}" valueKey="0"/>
</complex>
</xsl:template>
</xsl:stylesheet>

XSLT check and create child node with default values if souurce is missing

I need to validate the source XML and look for Attributes/Attribute/Name. If Name = 'ComplexAttr' then make it child node of Data/Attributes(where #Type='common')/Collection/ComplexAttr. And if it is not present then create a node with default values. However, I have to validate all nodes with #Type='ComplexAttr' so it should be as dynamic as possible.
In the source XML you can see that I have only 1 node where #Type='ComplexAttr'. However, in the Transformed sample XML I have two nodes for <Attr>. This is I want to do with the following XSLT. Please let me know how I can do this.
Thanks in advance.
XSLT:
<!DOCTYPE xsl:stylesheet [<!ENTITY key "concat(Type[. != 'ComplexAttr'],substring('common',1 div (Type = 'ComplexAttr')))">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="type" match="Attribute" use="&key;"/>
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates
select="XML/Attributes/Attribute[
generate-id() = generate-id(key('type', &key;)[1])
]">
<xsl:sort select="&key;" order="descending"/>
</xsl:apply-templates>
<errorCodes>
<xsl:apply-templates select="XML/Attributes/Attribute"
mode="errors"/>
</errorCodes>
</Data>
</xsl:template>
<xsl:template match="Attribute">
<xsl:variable name="vCurrent-Grouping-Key" select="&key;"/>
<Attributes type="{$vCurrent-Grouping-Key}">
<xsl:apply-templates select="key('type',$vCurrent-Grouping-Key)"
mode="out"/>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="out" name="makeAttr">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute[Type='ComplexAttr']" mode="out">
<Collection id="" name="test">
<ComplexAttr refId="0">
<MaskValue />
<xsl:call-template name="makeAttr"/>
</ComplexAttr>
</Collection>
</xsl:template>
<xsl:template match="Attribute" mode="errors"/>
<xsl:template match="Attribute[Value='']" mode="errors">
<errorCode>"value for <xsl:value-of select="Name"/> is missing."</errorCode>
</xsl:template>
</xsl:stylesheet>
Source XML:
<?xml version="1.0" encoding="windows-1252"?>
<XML>
<Attributes>
<Attribute>
<id>5</id>
<Name>Buyer ID</Name>
<Type>common</Type>
<Value>Lee</Value>
</Attribute>
<Attribute>
<id>331</id>
<Name>Enviornment</Name>
<Type>common</Type>
<Value>Development</Value>
</Attribute>
<Attribute>
<id>79</id>
<Name>Retail</Name>
<Type>common</Type>
<Value></Value>
</Attribute>
<Attribute>
<id>402</id>
<Name>Gender</Name>
<Type>category</Type>
<Value>Men</Value>
</Attribute>
<Attribute>
<id>1197</id>
<Name>UPC</Name>
<Type>ComplexAttr</Type>
<Value>Testing</Value>
<Path />
</Attribute>
</Attributes>
---- Transformed XML
<Data Schema="XML A">
<Attributes type="common">
<Attr id="5" name="Buyer ID" value="Lee" />
<Attr id="331" name="Enviornment" value="Development" />
<Attr id="79" name="Retail" value="" />
<Collection id="" name="test">
<ComplexAttr refId="0">
<MaskValue />
<Attr id="1197" name="UPC" value="Testing" />
<Attr id="123" name="Size" value="Test" />
</ComplexAttr>
</Collection>
</Attributes>
<Attributes type="category">
<Attr id="402" name="Gender" value="Men" />
</Attributes>
<errorCodes>
<errorCode>"value for Retail is missing."</errorCode>
</errorCodes>
</Data>
Update: Complete stylesheet with more push style approach (it's late, you know...)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="type" match="Attribute" use="Type"/>
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates
select="XML/Attributes/Attribute[
generate-id() = generate-id(key('type', Type)[1])
]">
<xsl:sort select="Type" order="descending"/>
</xsl:apply-templates>
<errorCodes>
<xsl:apply-templates select="XML/Attributes/Attribute"
mode="errors"/>
</errorCodes>
</Data>
</xsl:template>
<xsl:template match="Attribute">
<xsl:if test="Type!='ComplexAttr'">
<Attributes type="{Type}">
<xsl:apply-templates select="key('type',Type)"
mode="out"/>
<xsl:if test="Type='common'">
<Collection id="" name="test">
<ComplexAttr refId="0">
<MaskValue />
<xsl:apply-templates
select="key('type','ComplexAttr')"
mode="out"/>
</ComplexAttr>
</Collection>
</xsl:if>
</Attributes>
</xsl:if>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute[Type='ComplexAttr']" mode="out">
<Attr id="{id}"
name="{Name}{substring('UPC',1 div not(Name[normalize-space()]))}"
value="{Value}{substring('Testing',1 div not(Value[normalize-space()]))}"/>
</xsl:template>
<xsl:template match="Attribute" mode="errors"/>
<xsl:template match="Attribute[Value='']" mode="errors">
<errorCode>"value for <xsl:value-of select="Name"/> is missing."</errorCode>
</xsl:template>
</xsl:stylesheet>
Output:
<Data Schema="XML A">
<Attributes type="common">
<Attr id="5" name="Buyer ID" value="Lee"/>
<Attr id="331" name="Enviornment" value="Development"/>
<Attr id="79" name="Retail" value=""/>
<Collection id="" name="test">
<ComplexAttr refId="0">
<MaskValue/>
<Attr id="1197" name="UPC" value="Testing"/>
</ComplexAttr>
</Collection>
</Attributes>
<Attributes type="category">
<Attr id="402" name="Gender" value="Men"/>
</Attributes>
<errorCodes>
<errorCode>
"value for Retail is missing."
</errorCode>
</errorCodes>
</Data>

XSLT combine node when attribute's value are different

During transformation, who I can combine one node into another. for example, when Attributes/Attribute/Type=ComplexAttr then it should go under Attributes/Attribute/Type=Common only.
Below is the sample XML & XSLT that I'm trying to use which is not working. TIA (Thanks in Advance)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="type" match="Attribute" use="Type"/>
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates select="XML/Attributes/Attribute">
<xsl:sort select="Type" order="descending"/>
</xsl:apply-templates>
<errorCodes>
<xsl:apply-templates select="XML/Attributes/Attribute"
mode="errors"/>
</errorCodes>
</Data>
</xsl:template>
<xsl:template
match="Attribute[generate-id()=generate-id(key('type', Type)[1])]">
<xsl:if test="Type != 'ComplexAttr'">
<Attributes type="{Type}">
<xsl:if test="Type = 'ComplexAttr'">
<xsl:value-of select="Common"/>
</xsl:if>
<xsl:apply-templates select="../Attribute[Type=current()/Type]" mode="out"/>
</Attributes>
</xsl:if>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute"/>
<xsl:template match="Attribute" mode="errors"/>
<xsl:template match="Attribute[Value='']" mode="errors">
<errorCode>"value for <xsl:value-of select="Name"/> is missing."</errorCode>
</xsl:template>
<xsl:template match="/Attribute">
<xsl:if test="Type = 'ComplexAttr'">
<Attributes type="Common">
<xsl:apply-templates select="../Attribute[Type=current()/Type]" mode="out"/>
<!--<Attr id="{id}" name="{Name}" value="{Value}"/>-->
</Attributes>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
---- Source XML ----
<?xml version="1.0" encoding="windows-1252"?>
<XML>
<Attributes>
<Attribute>
<id>5</id>
<Name>Buyer ID</Name>
<Type>common</Type>
<Value>Lee</Value>
</Attribute>
<Attribute>
<id>331</id>
<Name>Enviornment</Name>
<Type>common</Type>
<Value>Development</Value>
</Attribute>
<Attribute>
<id>79</id>
<Name>Retail</Name>
<Type>common</Type>
<Value></Value>
</Attribute>
<Attribute>
<id>402</id>
<Name>Gender</Name>
<Type>category</Type>
<Value>Men</Value>
</Attribute>
<Attribute>
<id>1197</id>
<Name>UPC</Name>
<Type>ComplexAttr</Type>
<Value>Testing</Value>
<Path />
</Attribute>
</Attributes>
</XML>
---- Transformed XML output
<?xml version="1.0" encoding="utf-8"?>
<Data Schema="XML A">
<Attributes type="common">
<Attr id="5" name="Buyer ID" value="Lee" />
<Attr id="331" name="Enviornment" value="Development" />
<Attr id="79" name="Retail" value="" />
<Attr id="41" name="PlusShip" value="False" />
<Collection id="" name="test">
<ComplexAttr refId="0">
<MaskValue />
<Attr id="1197" name="UPC" value="Testing" />
</ComplexAttr>
</Collection>
</Attributes>
<Attributes type="category">
<Attr id="402" name="Gender" value="Men" />
<Attr id="433" name="HeelHeight" value="" />
</Attributes>
<errorCodes>
<errorCode>"value for Retail is missing."</errorCode>
</errorCodes>
</Data>
If you want to group Attribute by Type with 'common' and 'ComplexAttr' in the same group, then you need to change the key value expression into something like:
<xsl:key name="type"
match="Attribute"
use="concat(
Type[. != 'ComplexAttr'],
substring(
'common',
1 div (Type = 'ComplexAttr')
)
)"/>
<xsl:template match="Attribute[
generate-id()
= generate-id(
key('type',
concat(
Type[. != 'ComplexAttr'],
substring(
'common',
1 div (Type = 'ComplexAttr')
)
)
)[1]
)
]">
EDIT: And in the group templates applying:
<xsl:apply-templates select="key('type',
concat(
Type[. != 'ComplexAttr'],
substring(
'common',
1 div (Type = 'ComplexAttr')
)
)
)"
mode="out"/>
EDIT: Full example. This stylesheet:
<!DOCTYPE xsl:stylesheet [
<!ENTITY key "concat(Type[. != 'ComplexAttr'],substring('common',1 div (Type = 'ComplexAttr')))">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="type" match="Attribute" use="&key;"/>
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates
select="XML/Attributes/Attribute[
generate-id() = generate-id(key('type', &key;)[1])
]">
<xsl:sort select="&key;" order="descending"/>
</xsl:apply-templates>
<errorCodes>
<xsl:apply-templates select="XML/Attributes/Attribute"
mode="errors"/>
</errorCodes>
</Data>
</xsl:template>
<xsl:template match="Attribute">
<xsl:variable name="vCurrent-Grouping-Key" select="&key;"/>
<Attributes type="{$vCurrent-Grouping-Key}">
<xsl:apply-templates select="key('type',$vCurrent-Grouping-Key)"
mode="out"/>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="out" name="makeAttr">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute[Type='ComplexAttr']" mode="out">
<Collection id="" name="test">
<ComplexAttr refId="0">
<MaskValue />
<xsl:call-template name="makeAttr"/>
</ComplexAttr>
</Collection>
</xsl:template>
<xsl:template match="Attribute" mode="errors"/>
<xsl:template match="Attribute[Value='']" mode="errors">
<errorCode>"value for <xsl:value-of select="Name"/> is missing."</errorCode>
</xsl:template>
</xsl:stylesheet>
Output:
<Data Schema="XML A">
<Attributes type="common">
<Attr id="5" name="Buyer ID" value="Lee" />
<Attr id="331" name="Enviornment" value="Development" />
<Attr id="79" name="Retail" value="" />
<Collection id="" name="test">
<ComplexAttr refId="0">
<MaskValue />
<Attr id="1197" name="UPC" value="Testing" />
</ComplexAttr>
</Collection>
</Attributes>
<Attributes type="category">
<Attr id="402" name="Gender" value="Men" />
</Attributes>
<errorCodes>
<errorCode>"value for Retail is missing."</errorCode>
</errorCodes>
</Data>

XSLT: reverse the string by comma

I have an XML file:
<schools>
<schcool>
school1, school2, school3, school4, school5
</schcool>
</schools>
I want to write XSLT (version 1.0) to change the result to a reverse order like this:
<schools>
<schcool>
school5, school4, school3, school2, school1
</schcool>
</schools>
Can anyone help me? Many thanks.
DY
<template name="split" xmlns="http://www.w3.org/1999/XSL/Transform">
<param name="s" />
<param name="withcomma" select="false()" />
<choose>
<when test="contains($s, ',')">
<!-- if there is still a comma, call me again
with everything after the first comma... -->
<call-template name="split">
<with-param name="s" select="substring-after($s, ',')" />
<with-param name="withcomma" select="true()" />
</call-template>
<!-- ...and print afterwards the current part -->
<value-of select="substring-before($s, ',')" />
<if test="$withcomma">
<text>, </text>
</if>
</when>
<otherwise>
<!-- No comma left in the remaining part: print the rest -->
<value-of select="$s" />
<if test="$withcomma">
<text>, </text>
</if>
</otherwise>
</choose>
</template>
You might have to fiddle a bit with the whitespace (look up the XPath function 'normalize-space()') to get the exact result, but the reverse sorting in principle is shown in the code above. Call it from your other templates like this:
<call-template name="split">
<with-param name="s" select="." />
</call-template>
A variant without xsl:choose:
<xsl:template name="reverse">
<xsl:param name="text"/>
<xsl:param name="comma" select="false()"/>
<xsl:variable name="item" select="substring-before(concat($text, ','), ',')"/>
<xsl:if test="normalize-space($item) != ''">
<xsl:call-template name="reverse">
<xsl:with-param name="text" select="substring-after(concat($text, ','), concat($item, ','))"/>
<xsl:with-param name="comma" select="true()"/>
</xsl:call-template>
</xsl:if>
<xsl:value-of select="$item"/>
<xsl:if test="$comma and $item != ''">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:template>
Thank you. You have save my time.
I remove the space by this:
<xsl:call-template name="split">
<xsl:with-param name="s" select="normalize-space(.)" />
</xsl:call-template>
Many thanks.
DY