Grouping multiple elements with same name - xslt

This is an extension to the question I asked earlier - XSLT 1.0 Grouping with multiple elements with same name
The output format has changed and hence reposting.
I have an XML that looks like -
<resultset>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning1</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A~level2A~level3A</Hierarchy>
<Hierarchy>level1B~level2B~level3B</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning2</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A~level2A~level3B</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning3</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A~level2B~level3C</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning4</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A~level2B~level3B</Hierarchy>
<Hierarchy>level1A~level2B~level3C</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning5</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1B~level2B~level3B</Hierarchy>
</ITEM>
</content>
</hit>
</resultset>
Note that there are multiple hierarchy elements which is a concatenated string of level1~level2~level3 I am looking to transform this into something like this -
<TREE>
<LEVELS>
<LEVEL1 name="level1A">
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM Name="Office Cleaning1"/>
</LEVEL3>
<LEVEL3 name="level3B">
<ITEM Name="Office Cleaning2"/>
</LEVEL3>
</LEVEL2>
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM Name="Office Cleaning4"/>
</LEVEL3>
<LEVEL3 name="level3C">
<ITEM Name="Office Cleaning3"/>
<ITEM Name="Office Cleaning4"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1B">
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM Name="Office Cleaning1"/>
<ITEM Name="Office Cleaning5"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
</TREE>
Basically each item has multiple hierarchy associated with it. I need to group them together and each levels to be grouped together as well.
I can actually change the values in the HIERARCHY element in the input XML to any format that might be easier to extract. For e.g. I can make it look like LEVEL1:level1A~LEVEL2:level2A~LEVEL3:level3A
but I cannot add new elements.

I got it working myself. Ignore the extra namespaces. I edited the hierarchy field to have format of level1:value~level2:value~level3:value for ease of substring.
I am sure there is a better way but this works for me.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:autn="http://schemas.autonomy.com/aci/">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="TOPLEVEL" match="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY" use="substring-before(substring-after(.,'LEVEL1:'),'~')"/>
<xsl:key name="MIDLEVEL" match="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY" use="substring-before(substring-after(.,'LEVEL2:'),'~')"/>
<xsl:key name="BOTTOMLEVEL" match="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY" use="substring-before(substring-after(.,'LEVEL3:'),'~')"/>
<xsl:template match="/">
<TREE>
<xsl:for-each select="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY[generate-id() = generate-id(key('TOPLEVEL',substring-before(substring-after(.,'LEVEL1:'),'~') )[1])]">
<xsl:variable name="TOP" select="substring-before(substring-after(.,'LEVEL1:'),'~')"/>
<LEVEL1>
<xsl:attribute name="name"><xsl:value-of select="substring-after($TOP,'+')"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="substring-before($TOP,'+')"/></xsl:attribute>
<xsl:for-each select="//autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY[substring-before(substring-after(.,'LEVEL1:'),'~')=$TOP and generate-id() = generate-id(key('MIDLEVEL',substring-before(substring-after(.,'LEVEL2:'),'~') )[1])]">
<xsl:variable name="MID" select="substring-before(substring-after(.,'LEVEL2:'),'~')"/>
<LEVEL2>
<xsl:attribute name="name"><xsl:value-of select="substring-after($MID,'+')"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="substring-before($MID,'+')"/></xsl:attribute>
<xsl:for-each select="//autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY[substring-before(substring-after(.,'LEVEL1:'),'~')=$TOP and substring-before(substring-after(.,'LEVEL2:'),'~')=$MID and generate-id() = generate-id(key('BOTTOMLEVEL',substring-before(substring-after(.,'LEVEL3:'),'~') )[1])]">
<xsl:variable name="BOTTOM" select="substring-before(substring-after(.,'LEVEL3:'),'~')"/>
<LEVEL3>
<xsl:attribute name="name"><xsl:value-of select="substring-after($BOTTOM,'+')"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="substring-before($BOTTOM,'+')"/></xsl:attribute>
<xsl:apply-templates select="//HIERARCHY[substring-before(substring-after(.,'LEVEL1:'),'~')=$TOP and substring-before(substring-after(.,'LEVEL2:'),'~')=$MID and substring-before(substring-after(.,'LEVEL3:'),'~')=$BOTTOM]"/>
</LEVEL3>
</xsl:for-each>
</LEVEL2>
</xsl:for-each>
</LEVEL1>
</xsl:for-each>
</TREE>
</xsl:template>
<xsl:template match="HIERARCHY">
<ITEM>
<xsl:attribute name="name"><xsl:value-of select="../DRETITLE"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="../ID"/></xsl:attribute>
</ITEM>
</xsl:template>
</xsl:stylesheet>

Related

XSLT fails when envelop is added

I have a perfectly working XSL (thanks to you / StackOverflow):
https://xsltfiddle.liberty-development.net/3NgtZRc
Input xml
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<Container>
<Name>INTERIEUR1</Name>
<Number>1</Number>
<SSCC>111</SSCC>
<SubContainers>
<Container>
<Id>I1371851</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>ACCESSOIRE1</Name>
<Barcode>181001371851</Barcode>
<Items>
<Item>
<Id>I8709475</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
<Item>
<Id>I8709476</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
<Container>
<Id>I1371852</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>PANEEL1</Name>
<Barcode>181001371852</Barcode>
<Items>
<Item>
<Id>I8709492</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
</SubContainers>
</Container>
XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xpath-default-namespace="https://www.uniconcreation.com/2021/IvenzaShippingContainer" xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="Containers">
<xsl:variable name="sscc" select="Container/SSCC"/>
<xsl:copy>
<xsl:for-each select="//Container">
<xsl:variable name="level" select="count(ancestor::*)"/>
<xsl:choose>
<xsl:when test="$level = 1">
<xsl:copy>
<xsl:copy-of select="Id"/>
<xsl:copy-of select="SalesOrderNumber"/>
<xsl:copy-of select="ProductionOrderNumber"/>
<xsl:copy-of select="Name"/>
<xsl:copy-of select="Type"/>
<xsl:copy-of select="Number"/>
<xsl:copy-of select="SSCC"/>
<xsl:copy-of select="Barcode"/>
<xsl:copy-of select="StartedAt"/>
<xsl:copy-of select="CompletedAt"/>
<xsl:choose>
<xsl:when test="SubContainers">
<Items>
<xsl:for-each select="SubContainers">
<Item>
<Id>
<xsl:value-of select="Container[1]/Id"/>
</Id>
<SalesOrderRowId>
<xsl:value-of select="Container[1]/Items/Item[1]/SalesOrderRowId"/>
</SalesOrderRowId>
<Quantity>1</Quantity>
</Item>
</xsl:for-each>
</Items>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="Items"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<Container>
<Name>INTERIEUR1</Name>
<Number>1</Number>
<SSCC>111</SSCC>
<Items>
<Item>
<Id>I1371851</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
</Containers>
*Especially the xpath-default-namespace helped me preventing getting an empty xmlns="" for the Items Literal
But then I found out SAP PI puts an envelop around my message:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Messages xmlns:ns0="http://sap.com/xi/XI/SplitAndMerge">
<ns0:Message1>
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<Container>
<Name>INTERIEUR1</Name>
<Number>1</Number>
<SSCC>111</SSCC>
<SubContainers>
<Container>
<Id>I1371851</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>ACCESSOIRE1</Name>
<Barcode>181001371851</Barcode>
<Items>
<Item>
<Id>I8709475</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
<Item>
<Id>I8709476</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
<Container>
<Id>I1371852</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>PANEEL1</Name>
<Barcode>181001371852</Barcode>
<Items>
<Item>
<Id>I8709492</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
</SubContainers>
</Container>
</Containers>
</ns0:Message1>
</ns0:Messages>
And I ended up with this:
<?xml version="1.0" encoding="UTF-8"?>
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer"
xmlns:ns0="http://sap.com/xi/XI/SplitAndMerge"/>
I tried prefixing all namespaces but I hope there's another way :(
Required output
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Messages xmlns:ns0="http://sap.com/xi/XI/SplitAndMerge">
<ns0:Message1>
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<Container>
<Name>INTERIEUR1</Name>
<Number>1</Number>
<SSCC>111</SSCC>
<Items>
<Item>
<Id>I1371851</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
</Containers>
</ns0:Message1>
</ns0:Messages>
Kind regards,
Mike
With two wrapper elements, the value of <xsl:variable name="level" select="count(ancestor::*)"/> is going to change so perhaps your check <xsl:when test="$level = 1"> needs to be converted to <xsl:when test="$level = 3">.
You will also need to add some identity template to make sure the wrapper elements are copied.

How to improve performance on grouping/sorting in XSLT1.0

I have an XML that looks like -
<autnresponse>
<responsedata>
<autn:hit>
<autn:content>
<DOCUMENT>
<DRETITLE>Request Audio Visual Services</DRETITLE>
<HIERARCHY>LEVEL1:8+My Workplace#4~LEVEL2:33+Meetings~LEVEL3:151+Book meeting room~</HIERARCHY>
<HIERARCHY>LEVEL1:8+My Workplace#4~LEVEL2:33+Meetings~LEVEL3:154+Support for Meeting Room Equipment~</HIERARCHY>
</DOCUMENT>
</autn:content>
</autn:hit>
<autn:hit>
<autn:content>
<DOCUMENT>
<DRETITLE>View outage calendar for SAP ICERPS system</DRETITLE>
<HIERARCHY>LEVEL1:4+IT Services & Access#1~LEVEL2:8+IT for IT~LEVEL3:45+SAP Services~</HIERARCHY>
<HIERARCHY>LEVEL1:4+IT Services & Access#1~LEVEL2:21+Corp and Consumer Systems~LEVEL3:85+Market and Sell systems~</HIERARCHY>
<HIERARCHY>LEVEL1:4+IT Services & Access#1~LEVEL2:23+Support Function Systems~LEVEL3:128+SAP Systems~</HIERARCHY>
<HIERARCHY>LEVEL1:4+IT Services & Access#1~LEVEL2:23+Support Function Systems~LEVEL3:129+Supply Chain Planning Services Systems ~</HIERARCHY>
</DOCUMENT>
</autn:content>
</autn:hit>
</responsedata>
</autnresponse>
Note that there are multiple hierarchy elements which is a concatenated string of level1~level2~level3. Each levels are of the form, LEVEL:LevelID+LevelName. LEVEL1's have an additional value like # to sort on. Basically I need to sort all level1s by this # number and other levels alphabetically.
I am looking to transform this into something like this -
<TREE>
<LEVEL1 name="IT Services & Access" id="4">
<LEVEL2 name="Corp and Consumer Systems" id="21">
<LEVEL3 name="Market and Sell systems" id="85">
<ITEM id="1000" name="View outage calendar for SAP ICERPS system">
</ITEM>
</LEVEL3>
</LEVEL2>
<LEVEL2 name="IT for IT" id="8">
<LEVEL3 name="SAP Services" id="45">
<ITEM id="1000" name="View outage calendar for SAP ICERPS system"></ITEM>
</LEVEL3>
</LEVEL2>
<LEVEL2 name="Support Function Systems" id="23">
<LEVEL3 name="SAP Systems" id="128">
<ITEM id="1000" name="View outage calendar for SAP ICERPS system">
</ITEM>
</LEVEL3>
<LEVEL3 name="Supply Chain Planning Services Systems " id="129">
<ITEM id="1000" name="View outage calendar for SAP ICERPS system">
</ITEM>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="My Workplace" id="8">
<LEVEL2 name="Meetings" id="33">
<LEVEL3 name="Book meeting room" id="151">
<ITEM id="100" name="Request Audio Visual Services"></ITEM>
</LEVEL3>
<LEVEL3 name="Support for Meeting Room Equipment" id="154">
<ITEM id="100" name="Request Audio Visual Services"></ITEM>
</LEVEL3>
</LEVEL2>
</LEVEL1>
</TREE>
Basically each DOCUMENT has multiple hierarchy associated with it. I need to group them together and each levels to be grouped together as well.
My XSL that works looks like this -
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:autn="http://schemas.autonomy.com/aci/">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="TOPLEVEL" match="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY" use="substring-before(substring-after(.,'LEVEL1:'),'#')"/>
<xsl:key name="MIDLEVEL" match="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY" use="substring-before(substring-after(.,'LEVEL2:'),'~')"/>
<xsl:key name="BOTTOMLEVEL" match="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY" use="substring-before(substring-after(.,'LEVEL3:'),'~')"/>
<xsl:template match="/">
<TREE>
<xsl:for-each select="autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY[generate-id() = generate-id(key('TOPLEVEL',substring-before(substring-before(substring-after(.,'LEVEL1:'),'~'),'#') )[1])]">
<xsl:sort select="substring-after(substring-before(substring-after(.,'LEVEL1:'),'~'),'#')"/>
<xsl:variable name="TOP" select="substring-before(substring-after(.,'LEVEL1:'),'#')"/>
<LEVEL1>
<xsl:attribute name="name"><xsl:value-of select="substring-after($TOP,'+')"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="substring-before($TOP,'+')"/></xsl:attribute>
<xsl:for-each select="//autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY[substring-before(substring-after(.,'LEVEL1:'),'#')=$TOP and generate-id() = generate-id(key('MIDLEVEL',substring-before(substring-after(.,'LEVEL2:'),'~') )[1])]">
<xsl:sort select="substring-after(substring-before(substring-after(.,'LEVEL2:'),'~'),'+')"/>
<xsl:variable name="MID" select="substring-before(substring-after(.,'LEVEL2:'),'~')"/>
<LEVEL2>
<xsl:attribute name="name"><xsl:value-of select="substring-after($MID,'+')"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="substring-before($MID,'+')"/></xsl:attribute>
<xsl:for-each select="//autnresponse/responsedata/autn:hit/autn:content/DOCUMENT/HIERARCHY[substring-before(substring-after(.,'LEVEL1:'),'#')=$TOP and substring-before(substring-after(.,'LEVEL2:'),'~')=$MID and generate-id() = generate-id(key('BOTTOMLEVEL',substring-before(substring-after(.,'LEVEL3:'),'~') )[1])]">
<xsl:sort select="substring-after(substring-before(substring-after(.,'LEVEL3:'),'~'),'+')"/>
<xsl:variable name="BOTTOM" select="substring-before(substring-after(.,'LEVEL3:'),'~')"/>
<LEVEL3>
<xsl:attribute name="name"><xsl:value-of select="substring-after($BOTTOM,'+')"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="substring-before($BOTTOM,'+')"/></xsl:attribute>
<xsl:apply-templates select="//HIERARCHY[substring-before(substring-after(.,'LEVEL1:'),'#')=$TOP and substring-before(substring-after(.,'LEVEL2:'),'~')=$MID and substring-before(substring-after(.,'LEVEL3:'),'~')=$BOTTOM]">
<xsl:sort select="../DREDRETITLE"/>
</xsl:apply-templates>
</LEVEL3>
</xsl:for-each>
</LEVEL2>
</xsl:for-each>
</LEVEL1>
</xsl:for-each>
</TREE>
</xsl:template>
<xsl:template match="HIERARCHY">
<ITEM>
<xsl:attribute name="id"><xsl:value-of select="../ID"/></xsl:attribute>
<xsl:attribute name="name"><xsl:value-of select="../DREDRETITLE"/></xsl:attribute>
</ITEM>
</xsl:template>
</xsl:stylesheet>
The performance on this is not great and I feel its not the best of solutions. I wonder if there is a better alternative solution for this that works faster. Basically I have more than 1200 DOCUMENT elements and the XSL tranform takes about 15sec
This stylesheet preprocesses the document into the output format without doing any grouping and then makes a second pass to perform the grouping task, this should cut down on the amount of string processing you're doing and might lead to a performance increase. It's hard to test for performance increases without having a complete sample and knowing all the details of your implementation.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:autn="http://schemas.autonomy.com/aci/"
exclude-result-prefixes="exsl">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="TOPLEVEL" match="LEVEL1" use="#name"/>
<xsl:key name="MIDLEVEL" match="LEVEL2" use="concat(#name,parent::LEVEL1/#name)"/>
<xsl:key name="BOTLEVEL" match="LEVEL3" use="concat(#name,parent::LEVEL2/#name,ancestor::LEVEL1/#name)"/>
<xsl:template match="/">
<xsl:variable name="preprocess">
<xsl:apply-templates mode="preprocess"/>
</xsl:variable>
<TREE>
<xsl:for-each
select="exsl:node-set($preprocess)/LEVEL1[generate-id() = generate-id(key('TOPLEVEL', #name)[1])]">
<xsl:variable name="top" select="#name"/>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:for-each
select="exsl:node-set($preprocess)//LEVEL2[generate-id() = generate-id(key('MIDLEVEL', concat(#name,$top))[1])]">
<xsl:variable name="mid" select="#name"/>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:for-each
select="exsl:node-set($preprocess)//LEVEL3[generate-id() = generate-id(key('BOTLEVEL', concat(#name,$mid,$top))[1])]">
<xsl:copy>
<xsl:copy-of select="(#*|ITEM)"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</TREE>
</xsl:template>
<xsl:template mode="preprocess" match="#*|node()">
<xsl:apply-templates mode="preprocess" select="#*|node()"/>
</xsl:template>
<xsl:template mode="preprocess" match="HIERARCHY">
<xsl:variable name="TOP" select="substring-before(substring-after(.,'LEVEL1:'),'#')"/>
<LEVEL1
name="{substring-after($TOP,'+')}"
id="{substring-before($TOP,'+')}">
<xsl:variable name="MID" select="substring-before(substring-after(.,'LEVEL2:'),'~')"/>
<LEVEL2
name="{substring-after($MID,'+')}"
id="{substring-before($MID,'+')}">
<xsl:variable name="BOT" select="substring-before(substring-after(.,'LEVEL3:'),'~')"/>
<LEVEL3
name="{substring-after($BOT,'+')}"
id="{substring-before($BOT,'+')}">
<ITEM
id="{../ID}"
name="{../DRETITLE}"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
</xsl:template>
</xsl:stylesheet>
Some unscientific testing, in Saxon 6.5.5 with your sample data I actually saw a decrease in performance, but about a 25% increase in Saxon 9.4.0.3.

XSLT Sorting with first letter of family name

I am trying to modify the following xsl so that the output matches the desired output shown below. I am trying to sort the records alphabetically by the first name of the family Initial. However at the moment it only sorts by the creators initial I need it to include the editors initial where the record does not have a creators element.
XML:
<records>
<record>
<creators>
<item>
<name>
<family>Smith</family>
<given>Tim</given>
</name>
</item>
</creators>
</record>
<record>
<creators>
<item>
<name>
<family>Lambert</family>
<given>John</given>
</name>
</item>
</creators>
<editors>
<item>
<name>
<family>testEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
<record>
<editors>
<item>
<name>
<family>ZambertEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
XSL:
<xsl:key name="initial" match="record" use="substring(creators/item/name/family,1,1)"/>
<xsl:template match="/">
<xsl:for-each select="//record[generate-id(.)= generate-id(key('initial', substring(creators/item/name/family,1,1))[1])]">
<xsl:sort select="substring(creators/item/name/family,1,1)"/>
<xsl:for-each select="key('initial', substring(creators/item/name/family,1,1))">
<xsl:if test="position() = 1">
<br /><h3 class="border">
<xsl:value-of select="substring(creators/item/name/family,1,1)"/>
</h3>
</xsl:if>
<p>
<xsl:value-of select="creators/item/name/family"/>
</p>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
Desired output:
L
Lambert
S
Smith
Z
ZambertEDITOR
This is a simple grouping problem:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kWith1stLetter" match="family" use="substring(.,1,1)"/>
<xsl:template match="/*">
<xsl:apply-templates select=
"*/*/*/*/family
[generate-id()
=
generate-id(key('kWith1stLetter',substring(.,1,1))[1])
]">
<xsl:sort select="substring(.,1,1)" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match= "family">
<h3 class="border">
<xsl:value-of select="substring(.,1,1)"/>
</h3>
<xsl:apply-templates mode="inGroup"
select="key('kWith1stLetter',substring(.,1,1))"/>
</xsl:template>
<xsl:template match="family" mode="inGroup">
<p><xsl:value-of select="."/></p>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<records>
<record>
<creators>
<item>
<name>
<family>Smith</family>
<given>Tim</given>
</name>
</item>
</creators>
</record>
<record>
<creators>
<item>
<name>
<family>Lambert</family>
<given>John</given>
</name>
</item>
</creators>
<editors>
<item>
<name>
<family>testEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
<record>
<editors>
<item>
<name>
<family>ZambertEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
</records>
the wanted, correct result is produced:
<h3 class="border">L</h3>
<p>Lambert</p>
<h3 class="border">S</h3>
<p>Smith</p>
<h3 class="border">t</h3>
<p>testEDITOR</p>
<h3 class="border">Z</h3>
<p>ZambertEDITOR</p>
and it is displayed by the browser as:
L
Lambert
S
Smith
t
testEDITOR
Z
ZambertEDITOR
Explanation:
Proper use of the Muenchian Grouping Method.

XSLT 1.0 Grouping with multiple elements with same name

I have an XML that looks like -
<resultset>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
<Hierarchy>level1B:level2B:level3B</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning1</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning2</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2B:level3C</Hierarchy>
</ITEM>
</content>
</hit>
</resultset>
Note that there are multiple hierarchy elements which is a concatenated string of level1:level2:level3
I am looking to transform this into something like this -
<TREE>
<LEVELS>
<LEVEL1 name="level1A">
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM Name="Office Cleaning"/>
<ITEM Name="Office Cleaning1"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1B">
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM Name="Office Cleaning"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1A">
<LEVEL2 name="level2B">
<LEVEL3 name="level3C">
<ITEM Name="Office Cleaning2"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
</LEVELS>
</TREE>
Basically each item has multiple hierachy associated with it. I need to group them together.
I got only as far as -
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:autn="http://schemas.autonomy.com/aci/">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:key name="HIERARCHYLEVELS" match="resultset/hit/content/ITEM" use="HIERARCHY" />
<xsl:template match="/">
<TREE>
<xsl:for-each select="resultset/hit/content/ITEM[generate-id()=generate-id(key('HIERARCHYLEVELS', HIERARCHY)[1])]">
<xsl:for-each select="HIERARCHY">
<xsl:variable name="level" select="HIERARCHY"/>
<HIERARCHY name="{$level}" >
<xsl:variable name="name" select="TITLE"/>
<ITEM name="{$name}"/>
</HIERARCHY>
</xsl:for-each>
</xsl:for-each>
</TREE>
</xsl:template>
</xsl:stylesheet>
But the problem is I only get the first matching hierarchy tag. For e.g. I dont get to see "Office cleaning1".
What can I do to make sure all hierarchy elements are considered? I still need to split it into various levels.
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kItemByHier" match="ITEM" use="Hierarchy"/>
<xsl:key name="kHierByVal" match="Hierarchy" use="."/>
<xsl:template match="/*">
<xsl:apply-templates select=
"*/*/*/Hierarchy[generate-id()=generate-id(key('kHierByVal',.)[1])]"/>
</xsl:template>
<xsl:template match="Hierarchy">
<xsl:call-template name="makeTree">
<xsl:with-param name="pHier" select="string()"/>
<xsl:with-param name="pItems" select="key('kItemByHier', .)"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="makeTree">
<xsl:param name="pHier"/>
<xsl:param name="pDepth" select="1"/>
<xsl:param name="pItems" select="/.."/>
<xsl:choose>
<xsl:when test="not($pHier)">
<xsl:for-each select="$pItems">
<ITEM name="{TITLE}"/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:element name="LEVEL{$pDepth}">
<xsl:attribute name="name">
<xsl:value-of select="substring-before(concat($pHier,':'), ':')"/>
</xsl:attribute>
<xsl:call-template name="makeTree">
<xsl:with-param name="pHier"
select="substring-after($pHier,':')"/>
<xsl:with-param name="pDepth" select="$pDepth+1"/>
<xsl:with-param name="pItems" select="$pItems"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<resultset>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
<Hierarchy>level1B:level2B:level3B</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning1</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2A:level3A</Hierarchy>
</ITEM>
</content>
</hit>
<hit>
<content>
<ITEM>
<TITLE>Office Cleaning2</TITLE>
<DESCRIPTION>blah blah blah</DESCRIPTION>
<Hierarchy>level1A:level2B:level3C</Hierarchy>
</ITEM>
</content>
</hit>
</resultset>
produces the wanted, correct result:
<LEVEL1 name="level1A">
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM name="Office Cleaning"/>
<ITEM name="Office Cleaning1"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1B">
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM name="Office Cleaning"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1A">
<LEVEL2 name="level2B">
<LEVEL3 name="level3C">
<ITEM name="Office Cleaning2"/>
</LEVEL3>
</LEVEL2>
</LEVEL1>
For interest, here is a draft effort at a solution. It is close, but not quiet right, as you can see from the output, as it uses different grouping rules. I am still trying to understand the required grouping rules. I will update if I get a better understanding.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="xsl exsl">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="phase-1-output">
<xsl:apply-templates select="/" mode="phase-1" />
</xsl:variable>
<xsl:variable name="phase-2-output">
<xsl:apply-templates select="exsl:node-set($phase-1-output)" mode="phase-2" />
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$phase-2-output" />
</xsl:template>
<!--================ Phase 1 ===============================-->
<xsl:template match="/" mode="phase-1">
<t>
<xsl:apply-templates select="*/*/*/ITEM/Hierarchy" mode="phase-1" />
</t>
</xsl:template>
<xsl:template match="Hierarchy" mode="phase-1">
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="levels" select="." />
<xsl:with-param name="item" select="../TITLE" />
</xsl:call-template>
</xsl:template>
<xsl:template name="analyze-hierarchy"><!-- phase-1 -->
<xsl:param name="levels" />
<xsl:param name="item" />
<xsl:variable name="level" select="substring-before(concat($levels,':'),':')" />
<xsl:variable name="e-level" select="
translate(
substring($level,1,string-length($level) - 1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<xsl:choose>
<xsl:when test="$level">
<xsl:element name="{$e-level}">
<xsl:attribute name="name"><xsl:value-of select="$level" /></xsl:attribute>
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="levels" select="substring-after($levels,':')" />
<xsl:with-param name="item" select="$item" />
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<ITEM Name="{$item}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--================ Phase 2 ===============================-->
<xsl:key name="kLevel"
match="*[starts-with(name(),'LEVEL')]"
use="concat(generate-id(..),'|',#name)" />
<xsl:template match="/" mode="phase-2">
<TREE>
<LEVELS>
<xsl:variable name="t" select="concat(generate-id(t),'|')" />
<xsl:apply-templates select="t/LEVEL1[
generate-id() = generate-id( key('kLevel',concat($t,#name))[1])
]" mode="phase-2-head" />
</LEVELS>
</TREE>
</xsl:template>
<xsl:template match="*[starts-with(name(),'LEVEL')]" mode="phase-2-head">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates select="key('kLevel',concat(generate-id(..),'|',#name))" mode="phase-2" />
<xsl:copy-of select="ITEM" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'LEVEL')]" mode="phase-2">
<xsl:variable name="p" select="concat(generate-id(.),'|')" />
<xsl:apply-templates select="*[starts-with(name(),'LEVEL')][
generate-id() = generate-id( key('kLevel',concat($p,#name))[1])
]" mode="phase-2-head" />
</xsl:template>
</xsl:stylesheet>
...with sample input produces this (not quiet correct output)...
<TREE>
<LEVELS>
<LEVEL1 name="level1A">
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM Name="Office Cleaning" />
</LEVEL3>
</LEVEL2>
<LEVEL2 name="level2A">
<LEVEL3 name="level3A">
<ITEM Name="Office Cleaning1" />
</LEVEL3>
</LEVEL2>
<LEVEL2 name="level2B">
<LEVEL3 name="level3C">
<ITEM Name="Office Cleaning2" />
</LEVEL3>
</LEVEL2>
</LEVEL1>
<LEVEL1 name="level1B">
<LEVEL2 name="level2B">
<LEVEL3 name="level3B">
<ITEM Name="Office Cleaning" />
</LEVEL3>
</LEVEL2>
</LEVEL1>
</LEVELS>
</TREE>
UPDATE
Ok, round 2. I copied Dimitre's grouping rule, which is all or nothing on the content of the Hierarchy element. This solution produces the expected output for the sample input. Note that in contrast to Dimitre's <xsl:element name="LEVEL{$pDepth}"> method, I have derived the LEVEL1 style element names from the Hierarchy steps. I am not sure if this is correct.
<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:strip-space elements="*" />
<xsl:key name="kLevel" match="Hierarchy" use="." />
<xsl:template match="/">
<TREE>
<LEVELS>
<xsl:apply-templates select="*/*/*/ITEM/Hierarchy[
generate-id() = generate-id( key('kLevel',.)[1])
]" mode="group" />
</LEVELS>
</TREE>
</xsl:template>
<xsl:template match="Hierarchy" mode="group">
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="key" select="." />
<xsl:with-param name="levels" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="analyze-hierarchy">
<xsl:param name="key" />
<xsl:param name="levels" />
<xsl:variable name="level" select="substring-before(concat($levels,':'),':')" />
<xsl:variable name="e-level" select="
translate(
substring($level,1,string-length($level) - 1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<xsl:choose>
<xsl:when test="$level">
<xsl:element name="{$e-level}">
<xsl:attribute name="name"><xsl:value-of select="$level" /></xsl:attribute>
<xsl:call-template name="analyze-hierarchy">
<xsl:with-param name="key" select="$key" />
<xsl:with-param name="levels" select="substring-after($levels,':')" />
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="key('kLevel',$key)" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Hierarchy">
<ITEM Name="{../TITLE}" />
</xsl:template>
</xsl:stylesheet>

XSLT: Converting a structure of items based on attribute values

Can somebody help me with the following problem, here is the input XML, the XSLT I'm using and the expected output. Actually I know it is because of unique generateid not getting generated this xslt failing to generate desired output, but I don't know where that code should be inserted.
XML:
<item id="N65537" text="catalog">
<item id="N65540" text="cd">
<item id="N65542" text="title">
<item id="N65543" img="VAL" text="Empire Burlesque" />
</item>
<item id="N65545" text="artist">
<item id="N65546" img="VAL" text="Bob Dylan" />
</item>
<item id="N65548" text="country">
<item id="N65549" text="attr1" img="ATTR">
<item id="N65549_N65549" text="primary" img="ATTRVAL" />
</item>
<item id="N65550" img="VAL" text="USA" />
</item>
<item id="N65552" text="company">
<item id="N65553" text="attr2" img="ATTR">
<item id="N65553_N65553" text="main" img="ATTRVAL" />
</item>
<item id="N65554" img="VAL" text="Columbia" />
</item>
<item id="N65556" text="price">
<item id="N65557" img="VAL" text="10.90" />
</item>
<item id="N65559" text="year">
<item id="N65560" img="VAL" text="1985" />
</item>
</item>
<item id="N65563" text="cd">
<item id="N65565" text="title">
<item id="N65566" img="VAL" text="Hide your heart" />
</item>
<item id="N65568" text="artist">
<item id="N65569" img="VAL" text="Bonnie Tyler" />
</item>
<item id="N65571" text="country">
<item id="N65572" img="VAL" text="UK" />
</item>
<item id="N65574" text="company">
<item id="N65575" img="VAL" text="CBS Records" />
</item>
<item id="N65577" text="price">
<item id="N65578" img="VAL" text="9.90" />
</item>
<item id="N65580" text="year">
<item id="N65581" img="VAL" text="1988" />
</item>
</item>
</item>
XSLT:
<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:template match="/">
<xsl:call-template name="dispatch">
<xsl:with-param name="nodes" select="node()"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="dispatch">
<xsl:param name="nodes"/>
<xsl:choose>
<xsl:when test="text()">
<xsl:call-template name="apply" >
<xsl:with-param name="select" select="node()" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="apply" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="apply">
<xsl:param name="select" select="node()" />
<xsl:for-each select="$select">
<xsl:if test='local-name() !=""'>
<xsl:variable name="ename">
<xsl:for-each select="#*">
<xsl:if test='name()="img1"'>
<xsl:text><xsl:value-of select="." /></xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="aname">
<xsl:for-each select="#*">
<xsl:if test='name()="img"'>
<xsl:text><xsl:value-of select="." /></xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="#*">
<xsl:variable name="tname">
<xsl:text><xsl:value-of select="." /></xsl:text>
</xsl:variable>
<xsl:choose>
<xsl:when test='name() ="text" and normalize-space($ename) = "VAL" and normalize-space($aname) != "ATTR"'>
<xsl:element name="{$tname}">
<xsl:for-each select="$select">
<xsl:call-template name="dispatch"/>
</xsl:for-each>
</xsl:element>
</xsl:when>
<xsl:when test='name() ="text" and normalize-space($ename) = "VAL" '>
<xsl:value-of select="$tname" />
</xsl:when>
<xsl:when test='name() ="text" and normalize-space($aname) = "ATTR"'>
<xsl:attribute name="id"><xsl:value-of select="$aname" /></xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Expected output:
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country attr1="primary">USA</country>
<company attr2="main">Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
EDIT: Modified answer after detail was added to the question.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<!-- normal items become an ordinary element -->
<xsl:template match="item">
<xsl:element name="{#text}">
<!-- attributes must be created before any other contents -->
<xsl:apply-templates select="item[#img='ATTR']" />
<!-- now process sub-elements and values (i.e. "anything else") -->
<xsl:apply-templates select="item[not(#img='ATTR')]" />
</xsl:element>
</xsl:template>
<!-- items with "ATTR" become an attribute -->
<xsl:template match="item[#img='ATTR']">
<xsl:attribute name="{#text}">
<xsl:value-of select="item[#img='ATTRVAL']/#text" />
</xsl:attribute>
</xsl:template>
<!-- items with "VAL" become a simple text -->
<xsl:template match="item[#img='VAL']">
<xsl:value-of select="#text" />
</xsl:template>
</xsl:stylesheet>
gives
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country attr1="primary">USA</country>
<company attr2="main">Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
The stylesheet works because the XSL processor chooses templates based on the specificity of their match expressions. match="item[#img='ATTR']" is more specific than match="item", so for each <item> processed (through <xsl:apply-templates select="item" />) the engine picks the right template automatically.
The main problem I see in your XSLT solution is that you use xsl:if and xsl:choose instead of 'select' to filter nodes. This makes your XSLT difficult to read and understand (at least for me).
Try this:
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes"/>
<xsl:template match="/item[#text='catalog']">
<catalog>
<xsl:apply-templates select="item[#text='cd']"></xsl:apply-templates>
</catalog>
</xsl:template>
<xsl:template match="item[#text='cd']">
<cd>
<title><xsl:value-of select="item[#text='title']/item[#img1='VAL']/#text"/></title>
<artist><xsl:value-of select="item[#text='artist']/item[#img1='VAL']/#text"/></artist>
<country><xsl:value-of select="item[#text='country']/item[#img1='VAL']/#text"/></country>
<company><xsl:value-of select="item[#text='company']/item[#img1='VAL']/#text"/></company>
<price><xsl:value-of select="item[#text='price']/item[#img1='VAL']/#text"/></price>
<year><xsl:value-of select="item[#text='year']/item[#img1='VAL']/#text"/></year>
</cd>
</xsl:template>
</xsl:stylesheet>
Solution does not cover the ATTR nodes, since they are not part of described result.
If you can possibly change the input xml, do so. XML is supposed to carry some meaning in the tag names and in its structure. Calling everything item just makes it unreadable.
Making such a change will also allow you to write readable XSLT that doesn't resort to node hierarchy selector tricks.
How about this:
<?xml version="1.0" encoding="ISO-8859-1"?>
<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:template match="/">
<catalog>
<xsl:for-each select="/item[#text='catalog']/item[#text='cd']">
<cd>
<xsl:for-each select="item">
<xsl:variable name="ename" select="string(#text)"/>
<xsl:variable name="value" select="item/#text"/>
<xsl:element name="{$ename}">
<xsl:value-of select="$value"/>
</xsl:element>
</xsl:for-each>
</cd>
</xsl:for-each>
</catalog>
</xsl:template>
</xsl:stylesheet>
Not as nice as Tomalaks solution - but maybe slightly clearer as to the intention.