Grouping elements and deleting duplicate nodes - XSLT 1.0 - xslt

I have looked at Muenchian Grouping - group within a node, not within the entire document but it is not quite working for me. The Muenchian method alone does not do it either for me.
I have also looked at XSLT 1.0: grouping and removing duplicate but cannot follow it completely.
I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<MT_MATERIALDATA>
<items item="475053">
<Recordset>
<CodeBusinessUnit>99</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
</items>
<items item="475054">
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>255.34</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>10</CodeBusinessUnit>
<PriceValue>299</PriceValue>
</Recordset>
</items>
</MT_MATERIALDATA>
The outcome should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<MT_MATERIALDATA>
<Mi item="475053">
<PriceList>
<Prices>
<Price Value="250"/>
<PriceConfig>
<Stores>99,1</Stores>
</PriceConfig>
</Prices>
</PriceList>
</Mi>
<Mi item="475054">
<PriceList>
<Prices>
<Price Value="255.34"/>
<PriceConfig>
<Stores>1</Stores>
</PriceConfig>
</Prices>
<Prices>
<Price Value="299"/>
<PriceConfig>
<Stores>10</Stores>
</PriceConfig>
</Prices>
</PriceList>
</Mi>
</MT_MATERIALDATA>
So for matching <PriceValue> elements
in <Recordset>, all respective <CodeBusinessUnits> need to be listed in <Stores>.
If not, an extra <Prices> node needs to be created.
I have been trying for hours but either the Store-numbers are always duplicate or they are not aggregated even if the PriceValue is the same.

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:key name="kPriceByValAndItem" match="PriceValue"
use="concat(../../#item, '|', .)"/>
<xsl:template match="/*">
<MT_MATERIALDATA>
<xsl:apply-templates/>
</MT_MATERIALDATA>
</xsl:template>
<xsl:template match="items">
<MI item="{#item}">
<PriceList>
<xsl:for-each select=
"*/PriceValue
[generate-id()
=
generate-id(key('kPriceByValAndItem',
concat(../../#item, '|', .)
)[1]
)
]
">
<Prices>
<Price Value="{.}"/>
<PriceConfig>
<Stores>
<xsl:for-each select=
"key('kPriceByValAndItem',
concat(../../#item, '|', .)
)">
<xsl:value-of select="../CodeBusinessUnit"/>
<xsl:if test="not(position()=last())">,</xsl:if>
</xsl:for-each>
</Stores>
</PriceConfig>
</Prices>
</xsl:for-each>
</PriceList>
</MI>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<MT_MATERIALDATA>
<items item="475053">
<Recordset>
<CodeBusinessUnit>99</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
</items>
<items item="475054">
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>255.34</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>10</CodeBusinessUnit>
<PriceValue>299</PriceValue>
</Recordset>
</items>
</MT_MATERIALDATA>
produces the wanted, correct result:
<MT_MATERIALDATA>
<MI item="475053">
<PriceList>
<Prices>
<Price Value="250"/>
<PriceConfig>
<Stores>99,1</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
<MI item="475054">
<PriceList>
<Prices>
<Price Value="255.34"/>
<PriceConfig>
<Stores>1</Stores>
</PriceConfig>
</Prices>
<Prices>
<Price Value="299"/>
<PriceConfig>
<Stores>10</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
</MT_MATERIALDATA>

I think the following solves the problem, at least for the grouping:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="items/Recordset" use="concat(generate-id(..), '|', PriceValue)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<Mi item="{#item}">
<PriceList>
<xsl:apply-templates select="Recordset[generate-id() = generate-id(key('k1', concat(generate-id(..), '|', PriceValue))[1])]"/>
</PriceList>
</Mi>
</xsl:template>
<xsl:template match="Recordset">
<Prices>
<Price Value="{PriceValue}"/>
<PriceConfig>
<Stores>
<xsl:apply-templates select="key('k1', concat(generate-id(..), '|', PriceValue))/CodeBusinessUnit"/>
</Stores>
</PriceConfig>
</Prices>
</xsl:template>
<xsl:template match="CodeBusinessUnit">
<xsl:if test="position() > 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>

I'm also going to post an stylesheet, because everybody do it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kBUnitByItem-Price"
match="CodeBusinessUnit"
use="concat(../../#item, '++', ../PriceValue)"/>
<xsl:template match="/">
<MT_MATERIALDATA>
<xsl:apply-templates/>
</MT_MATERIALDATA>
</xsl:template>
<xsl:template match="items">
<MI item="{#item}">
<PriceList>
<xsl:apply-templates/>
</PriceList>
</MI>
</xsl:template>
<xsl:template match="CodeBusinessUnit[
count(.|key('kBUnitByItem-Price',
concat(../../#item,'++',../PriceValue)
)[1]
) = 1
]">
<Prices>
<Price Value="{../PriceValue}"/>
<PriceConfig>
<Stores>
<xsl:apply-templates
select="key('kBUnitByItem-Price',
concat(../../#item,'++',../PriceValue))"
mode="sequence"/>
</Stores>
</PriceConfig>
</Prices>
</xsl:template>
<xsl:template match="text()"/>
<xsl:template match="node()" mode="sequence">
<xsl:if test="position()!=1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Note: Grouping stores by item and price. A little more pull than push style (That's because there is no duplicate #item.)
Output:
<MT_MATERIALDATA>
<MI item="475053">
<PriceList>
<Prices>
<Price Value="250" />
<PriceConfig>
<Stores>99,1</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
<MI item="475054">
<PriceList>
<Prices>
<Price Value="255.34" />
<PriceConfig>
<Stores>1</Stores>
</PriceConfig>
</Prices>
<Prices>
<Price Value="299" />
<PriceConfig>
<Stores>10</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
</MT_MATERIALDATA>
I think we cover all the variations: key value, push-pull, sequence separator condition.

Related

Conversion for multiqual

I am trying to convert the following input XML based on grouping of qualifier but its not working and not giving me expected output.
Below is the Input XML which has to be comverted.
<document>
<item>
<gtin>1000909090</gtin>
<attrGroupMany name="foodAndBevPreparationInfo">
<row>
<attr name="preparationType">BOILING</attr>
<attrQualMany name="preparationInstructions">
<value qual="en">Prep 8</value>
<value qual="en">Prep 9</value>
<value qual="ar">Test</value>
</attrQualMany>
</row>
</attrGroupMany>
</item>
</document>
The XSLT which I am using but not giving me expected output.
XSLT:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="prepmvl" match="preparationInstructions" use="concat(generate-id(..), '|', #qual)" />
<xsl:template match="document">
<CatalogItem>
<RelationshipData>
<xsl:for-each select="item/attrGroupMany[#name ='foodAndBevPreparationInfo']/row">
<Relationship>
<RelationType>Item_Master_Food_And_Bev_Prep_MVL</RelationType>
<RelatedItems count="{count(attrQualMany[#name='preparationInstructions']/value[generate-id() = generate-id(key('prepmvl', concat(generate-id(..), '|', #qual))[1])])}">
<xsl:apply-templates select="attrQualMany[#name='preparationInstructions']/value[generate-id() = generate-id(key('prepmvl', concat(generate-id(..), '|', #qual))[1])]"/>
</RelatedItems>
</Relationship>
</xsl:for-each>
</RelationshipData>
</CatalogItem>
</xsl:template>
<xsl:template match="preparationInstructions">
<RelatedItem1 referenceKey="{concat('Food_And_Bev_Prep_MVL','-',ancestor::item/gtin,'-',attr[#name='preparationType'],'-',#qual)}"/>
</xsl:template>
</xsl:stylesheet>
And the expected output should be
<?xml version="1.0" encoding="UTF-8"?>
<CatalogItem>
<RelationshipData>
<Relationship>
<RelationType>Item_Master_Food_And_Bev_Prep_MVL</RelationType>
<RelatedItems count="2">
<RelatedItem1 referenceKey="Food_And_Bev_Prep_MVL-1000909090-BOILING-en" />
<RelatedItem1 referenceKey="Food_And_Bev_Prep_MVL-1000909090-BOILING-ar" />
</RelatedItems>
</Relationship>
</RelationshipData>
</CatalogItem>
you need to change
<xsl:key name="prepmvl" match="preparationInstructions" use="concat(generate-id(..), '|', #qual)" />
to
<xsl:key name="prepmvl" match="value" use="concat(generate-id(..), '|', #qual)" />
and
<xsl:template match="preparationInstructions">
<RelatedItem1 referenceKey="{concat('Food_And_Bev_Prep_MVL','-',ancestor::item/gtin,'-',attr[#name='preparationType'],'-',#qual)}"/>
</xsl:template>
to
<xsl:template match="value">
<RelatedItem1 referenceKey="{concat('Food_And_Bev_Prep_MVL','-',ancestor::item/gtin,'-',../preceding-sibling::attr[#name='preparationType'],'-',#qual)}"/>
</xsl:template>

XSLT: How to represent OR in key match

I have the input XML as
<document>
<item>
<gtin>4341</gtin>
<functionalName lang="en">Filte</functionalName>
<functionalName lang="en">test1</functionalName>
<functionalName lang="chi">Filters2</functionalName>
<functionalName lang="hin">Filters3</functionalName>
<gtinName lang="en">gtinName1</gtinName>
<gtinName lang="en">gtinName2</gtinName>
<gtinName lang="hin">gtinName3</gtinName>
</item>
<item>
<gtin>4342</gtin>
<functionalName lang="en">Filte</functionalName>
<functionalName lang="chi">Filters</functionalName>
<functionalName lang="en">Filters1</functionalName>
<gtinName lang="en">gtinName1</gtinName>
<gtinName lang="chi">gtinName2</gtinName>
<gtinName lang="chi">gtinName3</gtinName>
</item>
</document>
I want to loop through each language and get the count with respect to group by of language
Expected output XML should be
<?xml version="1.0" encoding="UTF-8"?>
<CatalogItem>
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="3">
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-4341-en" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-4341-chi" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-4341-hin" />
</RelatedItems>
</Relationship>
</RelationshipData>
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="2">
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-4342-en" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-4342-chi" />
</RelatedItems>
</Relationship>
</RelationshipData>
</CatalogItem>
Sample XSLT which is used by me but its not giving me expected Output
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="functional" match="gtinName" use="concat(generate-id(..), '|', #lang)" />
<xsl:template match="document">
<CatalogItem>
<xsl:for-each select="item">
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="{count(gtinName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])])}">
<xsl:apply-templates select="gtinName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])]"/>
</RelatedItems>
</Relationship>
</RelationshipData>
</xsl:for-each>
</CatalogItem>
</xsl:template>
<xsl:template match="gtinName|functionalName">
<RelatedItem1 referenceKey="{concat('ITEM_DESCRIPTION','-',ancestor::item/gtin,'- ',#lang)}"/>
</xsl:template>
</xsl:stylesheet>
If you want the count of language attributes on either "functionName" or "gtimName" then change your key to this
<xsl:key name="functional" match="gtinName|functionalName" use="concat(generate-id(..), '|', #lang)" />
Then, to get the count of the languages, do this
count((functionalName|gtinName)[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])])}">
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="functional" match="gtinName|functionalName" use="concat(generate-id(..), '|', #lang)" />
<xsl:template match="document">
<CatalogItem>
<xsl:for-each select="item">
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<xsl:variable name="lang" select="(functionalName|gtinName)[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])]" />
<RelatedItems count="{count($lang)}">
<xsl:apply-templates select="$lang"/>
</RelatedItems>
</Relationship>
</RelationshipData>
</xsl:for-each>
</CatalogItem>
</xsl:template>
<xsl:template match="gtinName|functionalName">
<RelatedItem1 referenceKey="{concat('ITEM_DESCRIPTION','-',ancestor::item/gtin,'- ',#lang)}"/>
</xsl:template>
</xsl:stylesheet>
As an alternative, if only "gtinName" and "functionalName" have a "lang" attribute, you could use the syntax *[#lang] instead....
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="functional" match="*[#lang]" use="concat(generate-id(..), '|', #lang)" />
<xsl:template match="document">
<CatalogItem>
<xsl:for-each select="item">
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<xsl:variable name="lang" select="*[#lang][generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])]" />
<RelatedItems count="{count($lang)}">
<xsl:apply-templates select="$lang"/>
</RelatedItems>
</Relationship>
</RelationshipData>
</xsl:for-each>
</CatalogItem>
</xsl:template>
<xsl:template match="*[#lang]">
<RelatedItem1 referenceKey="{concat('ITEM_DESCRIPTION','-',ancestor::item/gtin,'- ',#lang)}"/>
</xsl:template>
</xsl:stylesheet>

XSLT for XML Filtering the repeating group

I have the input XML as
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<document>
<item>
<functionalName lang="en">Filte</functionalName>
<functionalName lang="hin">test1</functionalName>
<functionalName lang="chi">Filters2</functionalName>
<functionalName lang="hin">Filters3</functionalName>
</item>
<item>
<functionalName lang="en">Filte</functionalName>
<functionalName lang="chi">Filters</functionalName>
<functionalName lang="en">Filters1</functionalName>
</item>
And the desired output after parsing through the XSLT is mentioned below
XSLT is mentioned at the end of the query
Output XML:
<?xml version="1.0" encoding="UTF-8"?>
<CatalogItem>
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="3">
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-en" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-hin" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-chi" />
</RelatedItems>
</Relationship>
</RelationshipData>
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="2">
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-en" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-chi" />
</RelatedItems>
</Relationship>
</RelationshipData>
XSLT which I am using is not giving me the desired response. Please help
<xsl:output method="xml" indent="yes" />
<xsl:key name="functional" match="functionalName" use="#lang" />
<xsl:template match="document">
<CatalogItem>
<xsl:for-each select="item">
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems>
<xsl:attribute name="count">
<xsl:value-of select="count(functionalName[generate-id(.)=generate-id(key('functional',#lang)[1])])"/>
</xsl:attribute>
<xsl:apply-templates select="functionalName[generate-id(.)=generate-id(key('functional',#lang)[1])]"/>
</RelatedItems>
</Relationship>
</RelationshipData>
</xsl:for-each>
</CatalogItem>
</xsl:template>
<xsl:template match="functionalName">
<xsl:for-each value="{#lang}">
<xsl:variable name="language" select="../#lang" />
<RelatedItem1>
<xsl:attribute name="referenceKey">
<xsl:value-of select="concat('ITEM_DESCRIPTION','-','functionalName','-',../#lang)"/>
</xsl:attribute>
</RelatedItem1>
</xsl:for-each>
</xsl:template>
Change your key from
<xsl:key name="functional" match="functionalName" use="#lang" />
to
<xsl:key name="functional" match="functionalName" use="concat(generate-id(..), '|', #lang)" />
then you can use
<RelatedItems count="{count(functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])])}">
and
<xsl:apply-templates select="functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])]"/>
and finally you can simplify the template for functionaName to
<xsl:template match="functionalName">
<RelatedItem1 referenceKey="{concat('ITEM_DESCRIPTION','-','functionalName','-', #lang)}"/>
</xsl:template>
So the complete samples is
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="functional" match="functionalName" use="concat(generate-id(..), '|', #lang)" />
<xsl:template match="document">
<CatalogItem>
<xsl:for-each select="item">
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="{count(functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])])}">
<xsl:apply-templates select="functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])]"/>
</RelatedItems>
</Relationship>
</RelationshipData>
</xsl:for-each>
</CatalogItem>
</xsl:template>
<xsl:template match="functionalName">
<RelatedItem1 referenceKey="{concat('ITEM_DESCRIPTION','-','functionalName','-', #lang)}"/>
</xsl:template>
</xsl:stylesheet>

XSLT: iteration on string with indexes

I am having a trouble with very easy XSLT transformation. Let's assume that this is the input XML document:
<root>
<myString>ABBCD</myString>
</root>
The output XML should be:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
Is there an easy way to split such string and increment index over it?
It's pretty easy in XSLT 2.0...
XML Input
<root>
<myString>ABBCD</myString>
</root>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="myString">
<myCharacters>
<xsl:analyze-string select="." regex=".">
<xsl:matching-substring>
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="."/></value>
</character>
</xsl:matching-substring>
</xsl:analyze-string>
</myCharacters>
</xsl:template>
</xsl:stylesheet>
XML Output
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
It's not terrible in 1.0 either. You can use a recursive template. The following will produce the same output:
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="myString">
<myCharacters>
<xsl:call-template name="analyzeString">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</myCharacters>
</xsl:template>
<xsl:template name="analyzeString">
<xsl:param name="pos" select="1"/>
<xsl:param name="string"/>
<character>
<id><xsl:value-of select="$pos"/></id>
<value><xsl:value-of select="substring($string,1,1)"/></value>
</character>
<xsl:if test="string-length($string)>=2">
<xsl:call-template name="analyzeString">
<xsl:with-param name="pos" select="$pos+1"/>
<xsl:with-param name="string" select="substring($string,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I. XSLT 1.0 Solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vStyle" select="document('')"/>
<xsl:template match="myString">
<xsl:variable name="vStr" select="."/>
<root>
<myCharacters>
<xsl:for-each select=
"($vStyle//node()|$vStyle//#*|$vStyle//namespace::*)
[not(position() > string-length($vStr))]">
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="substring($vStr,position(),1)"/></value>
</character>
</xsl:for-each>
</myCharacters>
</root>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<myString>ABBCD</myString>
</root>
the wanted, correct result is produced:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
Alternatively, with FXSL one can use the str-map template like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:testmap="testmap" xmlns:f="http://fxsl.sf.net/"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="xsl f ext testmap">
<xsl:import href="str-dvc-map.xsl"/>
<testmap:testmap/>
<xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="myString">
<xsl:variable name="vrtfChars">
<xsl:call-template name="str-map">
<xsl:with-param name="pFun" select="$vTestMap"/>
<xsl:with-param name="pStr" select="."/>
</xsl:call-template>
</xsl:variable>
<myCharacters>
<xsl:apply-templates select="ext:node-set($vrtfChars)/*"/>
</myCharacters>
</xsl:template>
<xsl:template name="enumChars" match="*[namespace-uri() = 'testmap']"
mode="f:FXSL">
<xsl:param name="arg1"/>
<character>
<value><xsl:value-of select="$arg1"/></value>
</character>
</xsl:template>
<xsl:template match="character">
<character>
<id><xsl:value-of select="position()"/></id>
<xsl:copy-of select="*"/>
</character>
</xsl:template>
</xsl:stylesheet>
to produce the same correct result:
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
II. XSLT 2.0 solution -- shorter and simpler than other answers:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="myString">
<root>
<myCharacters>
<xsl:for-each select="string-to-codepoints(.)">
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="codepoints-to-string(.)"/></value>
</character>
</xsl:for-each>
</myCharacters>
</root>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document (above), produces the wanted, correct result:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>

Joining data of the same type with XSLT

I would like to join all data of the same type with XSLT. I have the following XML:
<ZE1MARAM>
<ZE1KONDM SEGMENT="1">
<VKORG>NL01</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NL01</KONDWERT>
<MENGE> 70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE> 70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKA0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE> 33.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
</ZE1MARAM>
so each ZE1KONDM with the same VKORG value in the result xml has to be appended to the same element. So the result would be something like that:
<result>
<prices value="NL01">
<price type="VKP0">
70.00
</price>
</prices>
<prices value="NLWS">
<price type="VKP0">
70.00
</price>
<price type="VKA0">
55.00
</price>
</prices>
I tried to work with keys, and do something like that:
<xsl:key name="myKey" match="ZE1KONDM" use="normalize-space(VKORG)" />
<xsl:for-each select="ZE1KONDM">
<xsl:choose>
<xsl:when test="KONDART='VKP0'">
<xsl:element name="prices">
<xsl:element name="price">
<xsl:value-of select="key('myKey', normalize-space(VKORG))/MENGE"/>
</xsl:element>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:for-each>
but it does not work because it takes just one key..
There is some way to solve this problem with xslt?
There's probably a better way, but try this:
http://www.xmlplayground.com/2A3C7H
(see output source)
I. XSLT 1.0 Solution:
Here is a classical application of the Muenchian grouping method:
<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="kZByM" match="ZE1KONDM" use="VKORG"/>
<xsl:template match="/*">
<result>
<xsl:apply-templates select=
"*[generate-id() = generate-id(key('kZByM', VKORG)[1])]"/>
</result>
</xsl:template>
<xsl:template match="ZE1KONDM">
<prices value="{VKORG}">
<xsl:apply-templates select="key('kZByM', VKORG)" mode="inGroup"/>
</prices>
</xsl:template>
<xsl:template match="ZE1KONDM" mode="inGroup">
<price type="{KONDART}">
<xsl:value-of select="MENGE"/>
</price>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<ZE1MARAM>
<ZE1KONDM SEGMENT="1">
<VKORG>NL01</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NL01</KONDWERT>
<MENGE>70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE>70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKA0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE>33.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
</ZE1MARAM>
the wanted, correct result is produced:
<result>
<prices value="NL01">
<price type="VKP0">70.00</price>
</prices>
<prices value="NLWS">
<price type="VKP0">70.00</price>
<price type="VKA0">33.00</price>
</prices>
</result>
Do note: The Muenchian grouping method is probably the fastest XSLT 1.0 grouping method, because it uses keys. Other methods (such as comparing siblings values) are way too slower (O(N^2)) which is prohibitive fro using them on large data sizes.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<result>
<xsl:for-each-group select="*" group-by="VKORG">
<prices value="{VKORG}">
<xsl:apply-templates select="current-group()"/>
</prices>
</xsl:for-each-group>
</result>
</xsl:template>
<xsl:template match="ZE1KONDM">
<price type="{KONDART}">
<xsl:value-of select="MENGE"/>
</price>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct result is produced:
<result>
<prices value="NL01">
<price type="VKP0">70.00</price>
</prices>
<prices value="NLWS">
<price type="VKP0">70.00</price>
<price type="VKA0">33.00</price>
</prices>
</result>
Explanation:
Proper use of xsl:for-each-group with the group-by attribute, and the current-group() function.