Reputation: 11
I want to transform the data received in one XML into another XML file using XSLT.
The data in XML to be parsed contains "_" (underscore). I want to generate elements from this XML and group them based on the names extracted from element names.
XML to be parsed
<data>
<fruits_apple_red>1</fruits_apple_red>
<animal_carnivorous_cat_tiger_white>5</animal_carnivorous_cat_tiger_white>
<animal_reptiles_lizard>3</animal_reptiles_lizard>
<animal_carnivorous_lion>4</animal_carnivorous_lion>
<fruits_orange_orange>2</fruits_orange_orange>
<animal_carnivorous_cat_hyena>6</animal_carnivorous_cat_hyena>
</data>
XML needed
<?xml version="1.0" encoding="UTF-8"?>
<data>
<fruits>
<apple_red>1</apple_red>
<orange_orange>2</orange_orange>
</fruits>
<animal>
<carnivorous>
<cat>
<tiger_white>5</tiger_white>
<hyena>6</hyena>
</cat>
<lion>4</lion>
</carnivorous>
<reptiles_lizard>3</reptiles_lizard>
</animal>
</data>
I'm new to XSLT and I have tried using XSLT string functions(substring) but unable to get XML in this format. The problem I faced is that the element names are dynamic and we need to extract names out the element names. So, I can't really write code with name "data" as root element or "animal/fruits" as 2nd level element names.
Another thing that we need is the multi level (like for animal) although I feel that this may require some hardcoded checks(using xsl:if statement) at some stage. But the point is too group the data multi-level as far as possible.
Could anyone please let me know how can this can be done either using XSLT.
Note: This is sample data but real data contains element names separated by "_". Client needs it this way so that they can directly bind the required part to respective grid.
Upvotes: 0
Views: 297
Reputation: 167571
I think it is possible in XSLT 2 or 3 using a recursive function and xsl:for-each-group group-by
as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="element()*">
<xsl:param name="elements" as="element()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$elements" group-by="tokenize(local-name(), '_')[$level]">
<xsl:choose>
<xsl:when test="not(current-group()[2])">
<xsl:element name="{string-join(tokenize(local-name(), '_')[position() ge $level], '_')}">
<xsl:apply-templates/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{current-grouping-key()}">
<xsl:sequence select="mf:group(current-group(), $level + 1)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="data">
<xsl:copy>
<xsl:sequence select="mf:group(*, 1)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
That results in (see http://xsltfiddle.liberty-development.net/gWcDMeh)
<data>
<fruits>
<apple_red>1</apple_red>
<orange_orange>2</orange_orange>
</fruits>
<animal>
<carnivorous>
<cat>
<tiger_white>5</tiger_white>
<hyena>6</hyena>
</cat>
<lion>4</lion>
</carnivorous>
<reptiles_lizard>3</reptiles_lizard>
</animal>
</data>
so it has the right elements although the order is different, I could not infer from your posting what determines the order. It shouldn't be too difficult however to add some other ordering, if necessary by pushing the result of the function to another mode that establishes the order you want.
As for an explanation: The function gets the child elements *
of the data
elements as the initial input and uses for-each-group
recursively by tokenizing the local-name()
(e.g. fruits_apple_red
) of each element on the separator _
to get a sequence of name tokens (e.g. fruits
, apple
, red
) and using the first token (e.g. fruits
) as the grouping key on the first call (with the [$index]
as [1]
predicate) and then the second token (e.g. apple
) on the second call and so on. Recursion is stopped when there is only one element in a group as I inferred that to be the condition why e.g. apple_red
is not further split.
Upvotes: 0