I am trying to transform this document but am fairly new to xslt and having tons of fun trying to get it right. The core node(truncated for simplicity) looks like this
<Product prod_id="6352">
<brandId>221</brandId>
<brand>Oscar Mayer</brand>
<images>
<smallimage>text</simage>
<medimage>text</medimage>
<largeimage>text</limage>
</images>
<nutrition>
<nutritionShow>Y</nutritionShow>
<servingSize>1 SLICE</servingSize>
<servingsPerContainer>12</servingsPerContainer>
<totalCalories>60</totalCalories>
<fatCalories>35</fatCalories>
<totalFat>4</totalFat>
<totalFatPercent>6</totalFatPercent>
<totalFatUnit>g</totalFatUnit>
<saturatedFat>1.5</saturatedFat>
<saturatedFatPercent>8</saturatedFatPercent>
<saturatedFatUnit>g</saturatedFatUnit>
<transFat>0</transFat>
<transFatUnit>g</transFatUnit>
<cholesterolUnit>mg</cholesterolUnit>
</nutrition>
<prodId>6352</prodId>
</Product>
In the end I want to sub-nodes that are grouped logically to be a single node with appropriate attribute names.
The end result should look like this
<Product prod_id="6352">
<brandId>221</brandId>
<brand>Oscar Mayer</brand>
<images>
<smallimage>text</smallimage>
<medimage>text</medimage>
<largeimage>text</largeimage>
</images>
<nutrition>
<nutritionShow>Y</nutritionShow>
<servingSize>1 SLICE</servingSize>
<servingsPerContainer>12</servingsPerContainer>
<totalCalories>60</totalCalories>
<fatCalories>35</fatCalories>
<totalFat amount="4" percent="6" unit="g" />
<saturatedFat amount="1.5" percent="8" unit="g"/>
<transFat amount="0" unit="g"</>
</nutrition>
<prodId>6352</prodId>
Some key features are
Thanks in advance for helping me to understand this fairly complex transformation.
One possible solution is following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="nutrition/*">
<xsl:variable name="cName" select="name()"/>
<xsl:choose>
<xsl:when test="following-sibling::node()[name()=concat($cName,'Unit')]">
<xsl:copy>
<xsl:attribute name="amount">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:if test="following-sibling::node()[name()=concat($cName,'Percent')]">
<xsl:attribute name="percent">
<xsl:value-of select="following-sibling::node()[name()=concat($cName,'Percent')]"/>
</xsl:attribute>
</xsl:if>
<xsl:attribute name="unit">
<xsl:value-of select="following-sibling::node()[name()=concat($cName,'Unit')]"/>
</xsl:attribute>
</xsl:copy>
</xsl:when>
<xsl:when test="contains(name() ,'Unit') or contains(name() ,'Percent')"/>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied to your input XML produces the ouput
<Product prod_id="6352">
<brandId>221</brandId>
<brand>Oscar Mayer</brand>
<images>
<smallimage>text</smallimage>
<medimage>text</medimage>
<largeimage>text</largeimage>
</images>
<nutrition>
<nutritionShow>Y</nutritionShow>
<servingSize>1 SLICE</servingSize>
<servingsPerContainer>12</servingsPerContainer>
<totalCalories>60</totalCalories>
<fatCalories>35</fatCalories>
<totalFat amount="4" percent="6" unit="g"></totalFat>
<saturatedFat amount="1.5" percent="8" unit="g"></saturatedFat>
<transFat amount="0" unit="g"></transFat>
</nutrition>
<prodId>6352</prodId>
</Product>
The first template is an Identity transform and copies all nodes and attributes without any changes.
The second temmplate matches all child elements/nodes of nutrition
.
In case the current element has a following sibling with a local name matching the current local name and ending with Unit
<xsl:when test="following-sibling::node()[name()=concat($cName,'Unit')]">
the current node has to be a node containing the amount
.
The value of the current node is written as value of the amount
attribute
<xsl:attribute name="amount">
<xsl:value-of select="."/>
</xsl:attribute>
and in case a following sibling with matching Percent
exists
<xsl:if test="following-sibling::node()[name()=concat($cName,'Percent')]">
the Percent
attribute is written accordingly:
<xsl:attribute name="percent">
<xsl:value-of select="following-sibling::node()[name()=concat($cName,'Percent')]"/>
</xsl:attribute>
Same applies to Unit
without previously checking if a matching Unit
exists (which could be added if necessary).
The empty
<xsl:when test="contains(name() ,'Unit') or contains(name() ,'Percent')"/>
removes the Unit
and Percent
nodes that has been written as attributes as well as the cholesterolUnit
.
Finally, all other non groupable nutrition
elements are just copied:
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>