extract value from an array in xslt - 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>

Related

XSLT - Grouping Elements after Index value

I'm writing a XSLT that needs to output certain data elements but into fixed length containers. While typically the source data will contain fewer elements than the fixed length, there is potential for the source data to contain more elements than the fixed length. When the latter occurs, I need to output the elements individually to a certain index, then group the remaining siblings based on a property so I can use concatenation and summation on the groups. I'm able to achieve all of this with the exception of the grouping of siblings after a given index value.
Below is a simplified version of what I'm trying to achieve. Assume the fixed length of the container is 5 and the only Type values to be outputted are 1 and 2, omitting 3.
Sample input XML
<?xml version="1.0" encoding="utf-8"?>
<Company>
<Locations>
<Location>
<Id>1</Id>
<Shoppers>
<Shopper>
<Products>
<Product>
<Amount>5.00</Amount>
<Id>1</Id>
<Name>A</Name>
<Type>1</Type>
</Product>
<Product>
<Amount>10.00</Amount>
<Id>1</Id>
<Name>B</Name>
<Type>2</Type>
</Product>
<Product>
<Amount>15.00</Amount>
<Id>1</Id>
<Name>C</Name>
<Type>3</Type>
</Product>
<Product>
<Amount>20.00</Amount>
<Id>1</Id>
<Name>D</Name>
<Type>1</Type>
</Product>
</Products>
</Shopper>
<Shopper>
<Products>
<Product>
<Amount>25.00</Amount>
<Id>1</Id>
<Name>E</Name>
<Type>2</Type>
</Product>
<Product>
<Amount>30.00</Amount>
<Id>1</Id>
<Name>F</Name>
<Type>1</Type>
</Product>
<Product>
<Amount>35.00</Amount>
<Id>1</Id>
<Name>G</Name>
<Type>2</Type>
</Product>
<Product>
<Amount>40.00</Amount>
<Id>1</Id>
<Name>H</Name>
<Type>1</Type>
</Product>
</Products>
</Shopper>
</Shoppers>
</Location>
<Location>
<Id>2</Id>
<Shoppers>
<Shopper>
<Products>
<Product>
<Amount>5.00</Amount>
<Id>2</Id>
<Name>I</Name>
<Type>A</Type>
</Product>
<Product>
<Amount>10.00</Amount>
<Id>2</Id>
<Name>J</Name>
<Type>B</Type>
</Product>
</Products>
</Shopper>
<Shopper>
<Products>
<Product>
<Amount>25.00</Amount>
<Id>2</Id>
<Name>K</Name>
<Type>A</Type>
</Product>
<Product>
<Amount>30.00</Amount>
<Id>2</Id>
<Name>L</Name>
<Type>B</Type>
</Product>
<Product>
<Amount>35.00</Amount>
<Id>2</Id>
<Name>M</Name>
<Type>B</Type>
</Product>
</Products>
</Shopper>
</Shoppers>
</Location>
</Locations>
</Company>
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl customCode" xmlns:customCode="urn:customCode">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:key name="product-distinct" match="Product" use="concat(Id,Type)"/>
<xsl:template match="/">
<Locations>
<xsl:for-each select="Company/Locations/Location">
<xsl:variable name="id" select="Id"/>
<!--Ideally, I would like to use a variable as to not repeat the same XPath throughout the code to simplify any necessary changes-->
<xsl:variable name="productList" select="Shoppers/Shopper/Products/Product[Type != '3']"/>
<Location>
<Products>
<xsl:choose>
<xsl:when test="count($productList) > 5">
<xsl:choose>
<xsl:when test="count($productList[generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]) = 2">
<xsl:for-each select="$productList[position() < 4]">
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="Amount"/>
<xsl:with-param name="productName" select="Name"/>
<xsl:with-param name="type" select="Type"/>
</xsl:call-template>
</xsl:for-each>
<xsl:choose>
<xsl:when test="count($productList[position() > 3][generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]) = 2">
<xsl:for-each select="$productList[position() > 3][generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]">
<xsl:variable name="keyGroup" select="key('product-distinct', concat($id,Type))"/>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="sum($keyGroup/Amount)"/>
<xsl:with-param name="productName" select="$keyGroup"/>
<xsl:with-param name="type" select="$keyGroup/Type"/>
</xsl:call-template>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="$productList[4]/Amount"/>
<xsl:with-param name="productName" select="$productList[4]/Name"/>
<xsl:with-param name="type" select="$productList[4]/Type"/>
</xsl:call-template>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="sum($productList[position() > 4]/Amount)"/>
<xsl:with-param name="productName">
<xsl:call-template name="CombineProductNames">
<xsl:with-param name="products" select="$productList[position() > 4]"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="type" select="$productList[5]/Type"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$productList[position() < 5]">
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="Amount"/>
<xsl:with-param name="productName" select="Name"/>
<xsl:with-param name="type" select="Type"/>
</xsl:call-template>
</xsl:for-each>
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="sum($productList[position() > 4]/Amount)"/>
<xsl:with-param name="productName">
<xsl:call-template name="CombineProductNames">
<xsl:with-param name="products" select="$productList[position() > 4]"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="type" select="$productList[5]/Type"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$productList">
<xsl:call-template name="Product">
<xsl:with-param name="amount" select="Amount"/>
<xsl:with-param name="productName" select="Name"/>
<xsl:with-param name="type" select="Type"/>
</xsl:call-template>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</Products>
</Location>
</xsl:for-each>
</Locations>
</xsl:template>
<xsl:template name="Product">
<xsl:param name="amount"/>
<xsl:param name="productName"/>
<xsl:param name="type"/>
<Product>
<xsl:attribute name="Amount">
<xsl:value-of select="format-number($amount, '0.00')"/>
</xsl:attribute>
<xsl:attribute name="Name">
<xsl:value-of select="$productName"/>
</xsl:attribute>
<xsl:attribute name="Type">
<xsl:value-of select="$type"/>
</xsl:attribute>
</Product>
</xsl:template>
<xsl:template name="CombineProductNames">
<xsl:param name="products"/>
<xsl:for-each select="$products/Name">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Current Output
<?xml version="1.0" encoding="utf-8"?>
<Locations>
<Location>
<Products>
<Product Amount="5.00" Name="A" Type="1" />
<Product Amount="10.00" Name="B" Type="2" />
<Product Amount="20.00" Name="D" Type="1" />
<Product Amount="25.00" Name="E" Type="2" />
<Product Amount="105.00" Name="F, G, H" Type="1" />
</Products>
</Location>
<Location>
<Products>
<Product Amount="5.00" Name="I" Type="A" />
<Product Amount="10.00" Name="J" Type="B" />
<Product Amount="25.00" Name="K" Type="A" />
<Product Amount="30.00" Name="L" Type="B" />
<Product Amount="35.00" Name="M" Type="B" />
</Products>
</Location>
</Locations>
Expected Output
<?xml version="1.0" encoding="utf-8"?>
<Locations>
<Location>
<Products>
<Product Amount="5.00" Name="A" Type="1" />
<Product Amount="10.00" Name="B" Type="2" />
<Product Amount="20.00" Name="D" Type="1" />
<Product Amount="60.00" Name="E, G" Type="2" />
<Product Amount="70.00" Name="F, H" Type="1" />
</Products>
</Location>
<Location>
<Products>
<Product Amount="5.00" Name="I" Type="A" />
<Product Amount="10.00" Name="J" Type="B" />
<Product Amount="25.00" Name="K" Type="A" />
<Product Amount="30.00" Name="L" Type="B" />
<Product Amount="35.00" Name="M" Type="B" />
</Products>
</Location>
</Locations>

XSLT 1.0: recursively flatten/denormalize a structure

I'm trying to recursively flatten / normalize a structure below with no luck.
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<item name="e" />
</model>
</models>
The expected result is the following:
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
I have tried using in a recursive way. But the main issue is that several models can refer to a single model. Eg. AAA -> CCC and BBB -> CCC.
This short and simple transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kmodelByName" match="model" use="#name"/>
<xsl:key name="ksubmodelByRef" match="submodel" use="#ref"/>
<xsl:template match="/*">
<xsl:apply-templates select="model[not(key('ksubmodelByRef', #name))]"/>
</xsl:template>
<xsl:template match="model|item">
<xsl:param name="pPath"/>
<xsl:value-of select="concat('
', $pPath, '/', #name)"/>
<xsl:apply-templates select="item|*/item|*/submodel">
<xsl:with-param name="pPath" select="concat($pPath, '/', #name)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="submodel">
<xsl:param name="pPath"/>
<xsl:apply-templates select="key('kmodelByName', #ref)">
<xsl:with-param name="pPath" select="$pPath"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<item name="e"/>
</model>
</models>
produces the wanted, correct result:
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
Explanation:
Proper use of keys makes the transformation short, easy to express and efficient.
Proper use of templates.
Proper use of parameter - passing to templates.
Firstly, I suspect you want to math the model that is marked as a root
<xsl:apply-templates select="model[#root='true']" />
Then you would have a template to match model elements, but one that takes a parameter that contains the current 'path' to the parent model
<xsl:template match="model">
<xsl:param name="path" />
You could output the full path like so
<xsl:variable name="newpath" select="concat($path, '/', #name)" />
<xsl:value-of select="concat($newpath, '
')" />
You could then output the items but passing in the new path as a parameter
<xsl:apply-templates select="items/item|item">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
To match the sub-models, ideally a key could be used
<xsl:key name="models" match="model" use="#name" />
Then you could match the sub-models like so
<xsl:apply-templates select="key('models', submodels/submodel/#ref)">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
This would recursively match the same model template.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:key name="models" match="model" use="#name" />
<xsl:template match="/models">
<xsl:apply-templates select="model[#root='true']" />
</xsl:template>
<xsl:template match="model">
<xsl:param name="path" />
<xsl:variable name="newpath" select="concat($path, '/', #name)" />
<xsl:value-of select="concat($newpath, '
')" />
<xsl:apply-templates select="items/item|item">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
<xsl:apply-templates select="key('models', submodels/submodel/#ref)">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:param name="path" />
<xsl:value-of select="concat($path, '/', #name, '
')" />
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
I'm not sure what your rules of transformation are. This is a bit of a guess, but the following XSLT 1.0 style-sheet does transform your supplied input to the expected output.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="models/model[#root='true']">
<xsl:with-param name="base" select="''" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="model|item">
<xsl:param name="base" />
<xsl:variable name="new-base" select="concat($base,'/',#name)" />
<xsl:value-of select="concat($new-base,'
')" />
<xsl:apply-templates select="items/item | item | submodels/submodel/#ref">
<xsl:with-param name="base" select="$new-base" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#ref">
<xsl:param name="base" />
<xsl:apply-templates select="../../../../model[#name=current()]">
<xsl:with-param name="base" select="$base" />
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
There is probably an easier way to do this, but I was able to get the output you wanted with the following xslt:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="exsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common">
<xsl:variable name="root" select="/models/model[#root = 'true']/#name"/>
<xsl:template match="models">
<xsl:for-each select="model">
<xsl:variable name="savedNode" select="."/>
<xsl:variable name="precedingValue">
<xsl:call-template name="preceding">
<xsl:with-param name="modelName" select="#name"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="not(exsl:node-set($precedingValue)/preceding)">
<xsl:call-template name="model">
<xsl:with-param name="node" select="$savedNode"/>
</xsl:call-template>
</xsl:if>
<xsl:for-each select="exsl:node-set($precedingValue)/preceding">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:variable name="precedingTmp" select="normalize-space(.)"/>
<xsl:variable name="normalizedPreceding" select="translate($precedingTmp, ' ', '')"/>
<xsl:call-template name="model">
<xsl:with-param name="node" select="$savedNode"/>
<xsl:with-param name="preceding" select="$normalizedPreceding"/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="model">
<xsl:param name="node"/>
<xsl:param name="preceding"/>
\<xsl:value-of select="$preceding"/><xsl:value-of select="$node/#name"/>
<xsl:for-each select="$node/items/item">
\<xsl:value-of select="$preceding"/><xsl:value-of select="$node/#name"/>\<xsl:value-of select="#name"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="preceding">
<xsl:param name="modelName"/>
<xsl:for-each select="preceding::model">
<xsl:for-each select="submodels/submodel">
<xsl:choose>
<xsl:when test="contains(#ref, $modelName)">
<preceding>
<xsl:call-template name="preceding">
<xsl:with-param name="modelName" select="$modelName"></xsl:with-param>
</xsl:call-template>
<xsl:value-of select="../../#name"/>\
</preceding>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="items">
<xsl:for-each select="item">
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Input
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<items>
<item name="e"/>
</items>
</model>
</models>
Output
\AAA
\AAA\a
\AAA\b
\AAA\BBB
\AAA\BBB\c
\AAA\BBB\d
\AAA\BBB\CCC
\AAA\BBB\CCC\e
\AAA\CCC
\AAA\CCC\e
I hope it helps.

XSLT grouping recursive problem

guys, recursion with grouping is not working. I am using XSLT 1.0.
I need some help to figure this out. Thanks in Advance.
Background:
I have 3 different Types (common, category, & complex) in the xml. My goal is to.
1 - Group xml nodes based on Types.
2 - Create a sub-group for complex under Type=common.
3 - For Type=complex create a number of collections depending upon the source xml. In each collection it should list only 4 elements where Name='A' or 'B' or 'C' or 'D'.
This is where I'm having the problem. Grouping and sub-grouping is working fine. However, when I try to create a collection using recursion then it is not giving me the intending output. For reference please see the expected out xml sample.
Source XML:
<?xml version="1.0" encoding="Windows-1252"?>
<XML>
<Attributes>
<Attribute>
<Name>Buyer ID</Name>
<Type>common</Type>
<Value>Lee</Value>
</Attribute>
<Attribute>
<Name>Enviornment</Name>
<Type>common</Type>
<Value>Dev</Value>
</Attribute>
<Attribute>
<Name>Retail</Name>
<Type>common</Type>
<Value></Value>
</Attribute>
<Attribute>
<Name>Gender</Name>
<Type>category</Type>
<Value>M</Value>
</Attribute>
<Attribute>
<Name>Collection</Name>
<Type>Complex</Type>
<Value>ing</Value>
<Path />
</Attribute>
<Attribute>
<Name>A</Name>
<Type>Complex</Type>
<Value>Testing</Value>
<Path />
</Attribute>
<Attribute>
<Name>B</Name>
<Type>Complex</Type>
<Value>Yellow</Value>
<Path />
</Attribute>
<Attribute>
<Name>C</Name>
<Type>Complex</Type>
<Value>10</Value>
<Path />
</Attribute>
<Attribute>
<Name>D</Name>
<Type>Complex</Type>
<Value>MA</Value>
<Path />
</Attribute>
<Attribute>
<Name>A</Name>
<Type>Complex</Type>
<Value>24a</Value>
<Path />
</Attribute>
<Attribute>
<Name>B</Name>
<Type>Complex</Type>
<Value>Green</Value>
<Path />
</Attribute>
<Attribute>
<Name>C</Name>
<Type>Complex</Type>
<Value>22</Value>
<Path />
</Attribute>
<Attribute>
<Name>D</Name>
<Type>Complex</Type>
<Value>AM</Value>
<Path />
</Attribute>
</Attributes>
</XML>
Expected Output:
<?xml version="1.0" encoding="utf-8"?>
<Data Schema="XML A">
<Items>
<Item>
<Attributes type="common">
<Attr name="Buyer ID" value="Lee" />
<Attr name="Enviornment" value="Dev" />
<Attr name="Retail" value="" />
<Collection name="Collection" >
<Complex>
<Attr name="A" value="Testing" />
<Attr name="B" value="Yellow" />
<Attr name="C" value="10" />
<Attr name="D" value="MA" />
</Complex>
<Complex>
<Attr name="A" value="24a" />
<Attr name="B" value="Green" />
<Attr name="C" value="22" />
<Attr name="D" value="AM" />
</Complex>
</Collection>
</Attributes>
<Attributes type="category">
<Attr name="Gender" value="M" />
</Attributes>
<errorCodes>
<errorCode>value for Retail is missing.</errorCode>
</errorCodes>
</Item>
</Items>
</Data>
Here is the XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="type" match="Attribute" use="Type"/>
<xsl:variable name="group" select="4"/>
<xsl:template match="/">
<Data Schema="XML A">
<Items>
<Item>
<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>
</Item>
</Items>
</Data>
</xsl:template>
<xsl:template match="Attribute">
<xsl:variable name="compType" select="count(/XML/Attributes/Attribute[Type='Complex' and Name!='Collection'])"/>
<xsl:if test="Type!='Complex'">
<Attributes type="{Type}">
<xsl:apply-templates select="key('type',Type)" mode="out"/>
<xsl:if test="Type='common'">
<Collection>
<xsl:for-each select="/XML/Attributes/Attribute[Type='Complex']">
<xsl:choose>
<xsl:when test="(Name='A' or Name='B' or Name='C' or Name='D')">
<xsl:if test="(($compType > 0) and (Name!='Collection'))">
<xsl:apply-templates select="key('type','Complex')" mode="out"/>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<Complex>
<Attr id="" name="A" value="Default" />
<Attr id="" name="B" value="Default" />
<Attr id="" name="C" value="Default" />
<Attr id="" name="D" value="" />
</Complex>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Collection>
</xsl:if>
</Attributes>
</xsl:if>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<Collection>
<Attr name="{Name}" value="{Value}"/>
</Collection>
</xsl:template>
<xsl:template match="Attribute[Type='Complex']" mode="out">
<xsl:apply-templates select="XML/Attributes/Attribute[not(Name='Collection')]
[position() mod $group = 1]" mode="group"/>
</xsl:template>
<xsl:template match="Name" mode="group">
<xsl:if test="Name!='Collection'">
<Attr name="{Name}" value="{Value}"/>
</xsl:if>
</xsl:template>
<xsl:template match="Attribute">
<Complex>
<xsl:apply-templates
select=".|following-sibling::Attribute[position() < $group]" mode="inner" />
</Complex>
</xsl:template>
<xsl:template match="Attribute" mode="errors">
<xsl:if test="(Name='Retail' or Name='Product Description') and Value=''">
<errorCode>value for <xsl:value-of select="Name"/> is missing.</errorCode>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I am not sure I understand all your requirements but the following sample
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:data="http://example.com/data"
exclude-result-prefixes="data"
version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="att-by-type" match="Attributes/Attribute" use="Type"/>
<xsl:variable name="complex" select="key('att-by-type', 'Complex')"/>
<xsl:template match="XML">
<Data Schema="XML A">
<Items>
<Item>
<xsl:apply-templates select="Attributes/Attribute[Type = 'common' or Type = 'category'][generate-id() = generate-id(key('att-by-type', Type)[1])]" mode="group"/>
</Item>
</Items>
</Data>
</xsl:template>
<xsl:template match="Attribute" mode="group">
<Attributes type="{Type}">
<xsl:apply-templates select="key('att-by-type', Type)"/>
<xsl:if test="Type = 'common'">
<Collection name="Collection">
<xsl:apply-templates select="$complex[Name = 'A']" mode="comp-group"/>
</Collection>
</xsl:if>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="comp-group">
<Complex>
<xsl:variable name="pos" select="position()"/>
<xsl:apply-templates select="$complex[Name = 'A'][position() = $pos] |
$complex[Name = 'B'][position() = $pos] |
$complex[Name = 'C'][position() = $pos] |
$complex[Name = 'D'][position() = $pos]"/>
</Complex>
</xsl:template>
<xsl:template match="Attribute">
<Attr name="{Name}" value="{Value}"/>
</xsl:template>
</xsl:stylesheet>
produces the output you posted (with the exception of the errorCodes, I left that out as it seemed unrelated to the other problem).

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>