xslt merge sub nodes to one root node - xslt

My input document consist of a root with several sub nodes.
...
<root>
<top>
<number>999</number>
<attr attr-name="Numbervalue">
<value>184</value>
</attr>
<attr attr-name="Initials">
<value>A.C.</value>
</attr>
<attr attr-name="Givenname">
<value>Anne</value>
</attr>
<attr attr-name="Surname">
<value>Bakker</value>
</attr>
<attr attr-name="Function">
<value>Developer</value>
</attr>
</top>
<top>
<number>999</number>
<attr attr-name="Numbervalue">
<value>1034</value>
</attr>
<attr attr-name="Initials">
<value>A.C.</value>
</attr>
<attr attr-name="Givenname">
<value>Anne</value>
</attr>
<attr attr-name="Surname">
<value>Bakker</value>
</attr>
<attr attr-name="Function">
<value>Consultant</value>
</attr>
</top>
</root>
...
I want to merge all subnodes from top into a merged top. Same attributes but different values should result in a multi-valued "value" node. Other nodes/fields (as "number") should be copied. Something like this:
...
<root>
<top>
<number>999</number>
<attr attr-name="Numbervalue">
<value>184</value>
<value>1034</value>
</attr>
<attr attr-name="Initials">
<value>A.C.</value>
</attr>
<attr attr-name="Givenname">
<value>Anne</value>
</attr>
<attr attr-name="Surname">
<value>Bakker</value>
</attr>
<attr attr-name="Function">
<value>Developer</value>
<value>Consultant</value>
</attr>
</top>
</root>
...
What i have tried:
...
<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="top">
<xsl:for-each select="./attr[#attr-name]">
<xsl:if test="string-length(.)"/>
<xsl:variable name="name" select="./#attr-name"/>
<xsl:variable name="value" select="normalize-space(./value)"/>
<attr>
<xsl:attribute name="attr-name">
<xsl:value-of select="$name"/>
</xsl:attribute>
<value>
<xsl:value-of select="$value"/>
</value>
</attr>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...
But this result in:
...
<root>
<attr attr-name="Numbervalue">
<value>184</value>
</attr>
<attr attr-name="Initials">
<value>A.C.</value>
</attr>
<attr attr-name="Givenname">
<value>Anne</value>
</attr>
<attr attr-name="Surname">
<value>Bakker</value>
</attr>
<attr attr-name="Function">
<value>Developer</value>
</attr>
<attr attr-name="Numbervalue">
<value>1034</value>
</attr>
<attr attr-name="Initials">
<value>A.C.</value>
</attr>
<attr attr-name="Givenname">
<value>Anne</value>
</attr>
<attr attr-name="Surname">
<value>Bakker</value>
</attr>
<attr attr-name="Function">
<value>Consultant</value>
</attr>
</root>
...
With this i am missing the top node and nodes like "number"
If the result looks like this (below) it is also acceptable:
...
<root>
<top>
<number>999</number>
<attr attr-name="Numbervalue">
<value>184</value>
<value>1034</value>
</attr>
<attr attr-name="Initials">
<value>A.C.</value>
<value>A.C.</value>
</attr>
<attr attr-name="Givenname">
<value>Anne</value>
<value>Anne</value>
</attr>
<attr attr-name="Surname">
<value>Bakker</value>
<value>Bakker</value>
</attr>
<attr attr-name="Function">
<value>Developer</value>
<value>Consultant</value>
</attr>
</top>
</root>
...
I am also experimenting with "Muenchian Grouping" but up till now this only results in errors.
A working solution should use only xslt 1.0.
Suggestion are very welcome!

If I am guessing correctly, you want to do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="k1" match="value" use="../#attr-name" />
<xsl:key name="k2" match="value" use="concat(., '|', ../#attr-name)" />
<xsl:template match="/root">
<root>
<top>
<xsl:copy-of select="top[1]/number"/>
<xsl:for-each select="top[1]/attr">
<attr attr-name="{#attr-name}">
<!-- get only distinct values of this attr -->
<xsl:copy-of select="key('k1', #attr-name)[count(. | key('k2', concat(., '|', ../#attr-name))[1]) = 1]"/>
</attr>
</xsl:for-each>
</top>
</root>
</xsl:template>
</xsl:stylesheet>
This takes the list of attr elements from the 1st top, gathers all the value elements that belong to the current attr (using the k1 key), and filters them using the Muenchian grouping expression (utilizing the k2 key) to output only the distinct values among them.

Related

Need help in forming target xml using XSLT

I am facing difficult while creating child elements to parent node using XSLT , as the format of the input message coming from external source is bit different. requesting you kindly help on the XSLT code. Please also let me know if i can optimize the existing xslt copied below.
Input Message coming from external source:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="aoi00037607">
<attr attr-name="A">
<new-value>adi00010210</new-value>
</attr>
<attr attr-name="B">
<new-value>99</new-value>
</attr>
<attr attr-name="C">
<new-value>active</new-value>
</attr>
<attr attr-name="D">
<new-value>
<child1>iop00010538</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>cid2313213</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>hri00075562</child1>
<child2>1</child2>
</new-value>
</attr>
<attr attr-name="E">
<new-value>
<child3>spi00010021</child3>
<child4>1</child4>
</new-value>
<new-value>
<child3>vuh000123</child3>
<child4>1</child4>
</new-value>
</attr>
</PurchaseOrder>
XSLT Code written to transform The XSLT code also covers separation of values with | symbol if values are coming from same source multiple times.
<?xml version="1.0" encoding="UTF-8"?>
<ns0:stylesheet version="2.0" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:template match="/">
<ns1:PurchaseOrderMSG xmlns:ns1="urn:demo:PurchaseOrder">
<Orders>
<Order>
<ns0:attribute name="id" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:value-of select="/*/#id"/>
</ns0:attribute>
<ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:with-param name="Attr_value">A</ns0:with-param>
<ns0:with-param name="New_Attr">A</ns0:with-param>
</ns0:call-template>
<ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:with-param name="Attr_value">B</ns0:with-param>
<ns0:with-param name="New_Attr">B</ns0:with-param>
</ns0:call-template>
<ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:with-param name="Attr_value">C</ns0:with-param>
<ns0:with-param name="New_Attr">C</ns0:with-param>
</ns0:call-template>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>
</ns0:template>
<ns0:template name="copy_attr">
<ns0:param name="Attr_value"/>
<ns0:param name="New_Attr" select="$Attr_value"/>
<ns0:param name="length" select="100000"/>
<ns0:param name="values">
<ns0:for-each select="//attr[#attr-name = $Attr_value]/new-value">
<ns0:if test="position()!=1">
<ns0:text>|</ns0:text>
</ns0:if>
<ns0:value-of select="."/>
</ns0:for-each>
</ns0:param>
<ns0:element name="{$New_Attr}">
<ns0:value-of select="substring($values,1,number($length))"/>
</ns0:element>
</ns0:template>
</ns0:stylesheet>
I am facing difficult write code to add child1/child2 (both child can be repeated multiple times) field to parentnode D (occurance 0 to unbounded) and child3/child4 to parentnode E respectively.
Expected Result:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A/>
<B/>
<C/>
<D>
<Child1/>
<Child2/>
</D>
<E>
<Child3/>
<Child4/>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
please find the changed input..I have retained old input as well
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="aoi00037607">
<attr attr-name="A">
<new-value>adi00010210</new-value>
</attr>
<attr attr-name="B">
<new-value>99</new-value>
</attr>
<attr attr-name="B">
<new-value>101</new-value>
</attr>
<attr attr-name="C">
<new-value>active</new-value>
</attr>
<attr attr-name="D">
<new-value>
<child1>iop00010538</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>cid2313213</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>hri00075562</child1>
<child2>1</child2>
</new-value>
</attr>
<attr attr-name="E">
<new-value>
<child3>spi00010021</child3>
<child4>1</child4>
</new-value>
<new-value>
<child3>vuh000123</child3>
<child4>1</child4>
</new-value>
</attr>
<attr attr-name="C">
<new-value>inactive</new-value>
</attr>
</PurchaseOrder>
Please find the expected output
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>adi00010210</A>
<B>99|101</B>
<C>active|inactive</C>
<D>
<Child1>iop00010538</Child1>
<Child2>2</Child2>
</D>
<D>
<child1>cid2313213</child1>
<child2>2</child2>
</D>
<D>
<child1>hri00075562</child1>
<child2>1</child2>
</D>
<E>
<Child3>spi00010021</Child3>
<Child4>1</Child4>
</E>
<E>
<child3>vuh000123</child3>
<child4>1</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
Before we go into the solution, I would like to suggest one thing about retaining the prefix of namespace http://www.w3.org/1999/XSL/Transform as xsl instead of ns0 since it is widely accepted and easier to understand. However it is not mandatory and a personal choice. The solution below uses xsl as the prefix.
To start with, we need to prepare a list of nodes based on the value of attr-name of <attr> elements. This can be achieved by using <xsl:for-each> on the <attr> element and using attribute value templates {} for the element name.
<xsl:for-each select="attr">
<xsl:element name="{#attr-name}">
....
</xsl:element>
</xsl:for-each>
Next is the grouping of the values for the parent node and separating them using | separator. This can be achieved by defining <xsl:key> in XSLT 1.0).
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
If using XSLT 2.0, <xsl:for-each-group> can be used.
<xsl:for-each-group select="attr" group-by="#attr-name">
XSLT 1.0 solution
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns1:PurchaseOrderMSG>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$nodeName}">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:value-of select="new-value" />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
XSLT 2.0 solution
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="PurchaseOrder">
<ns1:PurchaseOrderMSG>
<Orders>
<Order id="{#id}">
<xsl:for-each-group select="attr" group-by="#attr-name">
<xsl:choose>
<xsl:when test="current-group()/new-value/*/node()">
<xsl:for-each select="current-group()/new-value">
<xsl:element name="{current-grouping-key()}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{current-grouping-key()}">
<xsl:value-of select="current-group()/new-value" separator="|" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
Both solutions transform the updated input XML in the output shown below.
<ns1:PurchaseOrderMSG xmlns:ns1="urn:demo:PurchaseOrder">
<Orders>
<Order id="aoi00037607">
<A>adi00010210</A>
<B>99|101</B>
<C>active|inactive</C>
<D>
<child1>iop00010538</child1>
<child2>2</child2>
</D>
<D>
<child1>cid2313213</child1>
<child2>2</child2>
</D>
<D>
<child1>hri00075562</child1>
<child2>1</child2>
</D>
<E>
<child3>spi00010021</child3>
<child4>1</child4>
</E>
<E>
<child3>vuh000123</child3>
<child4>1</child4>
</E>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>

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 unable to group/sort nodes based on value

I'm trying to transform this xml. However I'm having formatting issues. Could someone please guide me to solve this problem. Thanks in advance
<?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>433</id>
<Name>HeelHeight</Name>
<Type>category</Type>
<Value></Value>
</Attribute>
<Attribute>
<id>41</id>
<Name>PlusShip</Name>
<Type>common</Type>
<Value>False</Value>
<Path></Path>
</Attribute>
</Attributes>
</XML>
Into the following XML. Could someone please give me some tips in how to transform this xml based on the value of Attributes/Attribute/Type
<?xml version="1.0" encoding="utf-8" ?>
<Data Schema="XML A">
<Attributes type="Common">
<Attr id="" name="Buyer ID" value="Lee" />
<Attr id="" name="Enviornment" value="Development" />
<Attr id="" name="Retail" value="" />
<Attr id="" name="PlusShip" value="False" />
</Attributes>
<Attributes type="Category">
<Attr id="" name="Gender" value="Men" />
<Attr id="" name="HeelHeight" value="" />
</Attributes>
The following stylesheet produces the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates/>
</Data>
</xsl:template>
<xsl:template match="Attribute[not(Type=following::Type)]">
<Attributes type="{Type}">
<xsl:for-each select="../Attribute[Type=current()/Type]">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:for-each>
</Attributes>
</xsl:template>
<xsl:template match="Attribute"/>
</xsl:stylesheet>
Output on your source document:
<Data Schema="XML A">
<Attributes type="category">
<Attr id="402" name="Gender" value="Men"/>
<Attr id="433" name="HeelHeight" value=""/>
</Attributes>
<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"/>
</Attributes>
</Data>
Edit: OK, let's get rid of that ugly for-each:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates/>
</Data>
</xsl:template>
<xsl:template match="Attribute[not(Type=following::Type)]">
<Attributes type="{Type}">
<xsl:apply-templates
select="../Attribute[Type=current()/Type]" mode="out"/>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute"/>
</xsl:stylesheet>
I feel much better.
Edit #2: Using the Muenchian Method (with sorting):
<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>
</Data>
</xsl:template>
<xsl:template
match="Attribute[generate-id()=generate-id(key('type', Type)[1])]">
<Attributes type="{Type}">
<xsl:apply-templates
select="../Attribute[Type=current()/Type]" mode="out"/>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute"/>
</xsl:stylesheet>
Produces the following (ordered) 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=""/>
<Attr id="41" name="PlusShip" value="False"/>
</Attributes>
<Attributes type="category">
<Attr id="402" name="Gender" value="Men"/>
<Attr id="433" name="HeelHeight" value=""/>
</Attributes>
</Data>

XSLT convert some elements into attribute and sorted by attribute value [duplicate]

I'm trying to transform this xml. However I'm having formatting issues. Could someone please guide me to solve this problem. Thanks in advance
<?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>433</id>
<Name>HeelHeight</Name>
<Type>category</Type>
<Value></Value>
</Attribute>
<Attribute>
<id>41</id>
<Name>PlusShip</Name>
<Type>common</Type>
<Value>False</Value>
<Path></Path>
</Attribute>
</Attributes>
</XML>
Into the following XML. Could someone please give me some tips in how to transform this xml based on the value of Attributes/Attribute/Type
<?xml version="1.0" encoding="utf-8" ?>
<Data Schema="XML A">
<Attributes type="Common">
<Attr id="" name="Buyer ID" value="Lee" />
<Attr id="" name="Enviornment" value="Development" />
<Attr id="" name="Retail" value="" />
<Attr id="" name="PlusShip" value="False" />
</Attributes>
<Attributes type="Category">
<Attr id="" name="Gender" value="Men" />
<Attr id="" name="HeelHeight" value="" />
</Attributes>
The following stylesheet produces the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates/>
</Data>
</xsl:template>
<xsl:template match="Attribute[not(Type=following::Type)]">
<Attributes type="{Type}">
<xsl:for-each select="../Attribute[Type=current()/Type]">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:for-each>
</Attributes>
</xsl:template>
<xsl:template match="Attribute"/>
</xsl:stylesheet>
Output on your source document:
<Data Schema="XML A">
<Attributes type="category">
<Attr id="402" name="Gender" value="Men"/>
<Attr id="433" name="HeelHeight" value=""/>
</Attributes>
<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"/>
</Attributes>
</Data>
Edit: OK, let's get rid of that ugly for-each:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Data Schema="XML A">
<xsl:apply-templates/>
</Data>
</xsl:template>
<xsl:template match="Attribute[not(Type=following::Type)]">
<Attributes type="{Type}">
<xsl:apply-templates
select="../Attribute[Type=current()/Type]" mode="out"/>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute"/>
</xsl:stylesheet>
I feel much better.
Edit #2: Using the Muenchian Method (with sorting):
<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>
</Data>
</xsl:template>
<xsl:template
match="Attribute[generate-id()=generate-id(key('type', Type)[1])]">
<Attributes type="{Type}">
<xsl:apply-templates
select="../Attribute[Type=current()/Type]" mode="out"/>
</Attributes>
</xsl:template>
<xsl:template match="Attribute" mode="out">
<Attr id="{id}" name="{Name}" value="{Value}"/>
</xsl:template>
<xsl:template match="Attribute"/>
</xsl:stylesheet>
Produces the following (ordered) 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=""/>
<Attr id="41" name="PlusShip" value="False"/>
</Attributes>
<Attributes type="category">
<Attr id="402" name="Gender" value="Men"/>
<Attr id="433" name="HeelHeight" value=""/>
</Attributes>
</Data>