XML:
<t>
<ScreenSize>
<Width>1440</Width>
<Height>900</Height>
</ScreenSize>
<ConfigurationHotSpots>
<Rectangle>
<Location>
<X>0</X>
<Y>0</Y>
</Location>
<Size>
<Width>50</Width>
<Height>50</Height>
</Size>
<X>0</X>
<Y>0</Y>
<Width>50</Width>
<Height>50</Height>
</Rectangle>
</ConfigurationHotSpots>
</t>
Desired Output XML:
<t>
<ScreenSizeWidth>1440</ScreenSizeWidth>
<ScreenSizeWidth>900</ScreenSizeWidth>
<ConfigurationHotSpotsRectangleLocationX>0</ConfigurationHotSpotsRectangleLocationX>
<ConfigurationHotSpotsRectangleLocationY>0</ConfigurationHotSpotsRectangleLocationY>
<ConfigurationHotSpotsRectangleSizeWidth>50</ConfigurationHotSpotsRectangleSizeWidth>
<ConfigurationHotSpotsRectangleSizeHeight>50</ConfigurationHotSpotsRectangleSizeHeight>
<ConfigurationHotSpotsRectangleX>0</ConfigurationHotSpotsRectangleX>
<ConfigurationHotSpotsRectangleY>0</ConfigurationHotSpotsRectangleY>
<ConfigurationHotSpotsRectangleWidth>50</ConfigurationHotSpotsRectangleWidth>
<ConfigurationHotSpotsRectangleHeight>50</ConfigurationHotSpotsRectangleHeight>
</t>
Rules:
- For every element in a defined nodeset (in this case
<ScreenSize> | <ConfigurationHotSpots>
), do the following: process all leaf descendants (i.e., those without children) such that a new element is created; the name of this new element should be the concatenation of all elements between the current node and the childless descendant. - There is a variable number of these "blocks" throughout the document, so no manual templates (i.e., one that processes only descendants of
<ScreenSize>
, one that processes only descendants of<ConfigurationHotSpots>
, etc.)
What I Currently Have:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="ScreenSize|ConfigurationHotSpots">
<xsl:apply-templates select="descendant::*[not(*)]" mode="descendants" />
</xsl:template>
<xsl:template match="*" mode="descendants">
<xsl:element name="{concat(name(ancestor::*[not(self::t)]), name())}">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The problem seems to be the name(ancestor::*[not(self::t)])
portion; it's not doing what I would like it to be doing (magically outputting the names of those elements, one after another). Instead, this is what I get:
<?xml version="1.0" encoding="UTF-8"?>
<t>
<ScreenSizeWidth>1440</ScreenSizeWidth>
<ScreenSizeHeight>900</ScreenSizeHeight>
<ConfigurationHotSpotsX>0</ConfigurationHotSpotsX>
<ConfigurationHotSpotsY>0</ConfigurationHotSpotsY>
<ConfigurationHotSpotsWidth>50</ConfigurationHotSpotsWidth>
<ConfigurationHotSpotsHeight>50</ConfigurationHotSpotsHeight>
<ConfigurationHotSpotsX>0</ConfigurationHotSpotsX>
<ConfigurationHotSpotsY>0</ConfigurationHotSpotsY>
<ConfigurationHotSpotsWidth>50</ConfigurationHotSpotsWidth>
<ConfigurationHotSpotsHeight>50</ConfigurationHotSpotsHeight>
</t>
Thanks in advance!
This is one of the shortest possible solutions. It isn't as efficient as passing the accumulated path as parameter, but for not too-deeply nested structures this will not be significant:
When this transformation is applied on the provided XML document:
the wanted, correct result is produced:
Here is a tweak on Dimitre's solution. It is simpler and leverages off the known root element and known positions of the ScreenSize etc., as per OP's comments...
Here is an alternate form of the match condition...
I'm not actually sure which is better. If performance isn't a big deal, go with which ever reads easier. Just beware that this means two whole document scans for each template match attempt.
Doing
name(ancestor::*[not(self::t)])
will not return a list of names, but just the name of the last one it matches (or is it the first?).A slightly different approach you can take, not that far off from what you are currently doing, is rather than jumping straight to the 'leaf' element, match each level in turn, but keep a running concatenation of the element names, which are passed from one level to anoter by parameters.
Try this XSLT
When applied to your sample XML, the following is output