Reputation: 341
I have a very tricky XSLT question. Imagine I have the following input:
<OtherCharges>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>1</Item1>
<Item2>2</Item2>
<Item3>3</Item3>
</Items>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>4</Item1>
</Items>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>5</Item1>
<Item2>6</Item2>
</Items>
</OtherCharges>
and as an output I would like to have this:
<OtherCharges>
<LineId>P</LineId>
<OtherChargesValues>
<value>1</value>
<value>2</value>
<value>3</value>
</OtherChargesValues>
</OtherCharges>
<OtherCharges>
<LineId>P</LineId>
<OtherChargesValues>
<value>4</value>
</OtherChargesValues>
</OtherCharges>
<OtherCharges>
<LineId>P</LineId>
<OtherChargesValues>
<value>5</value>
<value>6</value>
</OtherChargesValues>
</OtherCharges>
Where I can have an unlimited number of lines but each line has maximum of 3 items. I've tried the following code:
<xsl:for-each select="/OtherCharges/LineId">
<Id>
<xsl:value-of select="Id"/>
<Id>
<xsl:variable name="ChargeLine" select="."/>
<xsl:for-each select="following-sibling::Items[preceding-sibling::LineId[1] = $ChargeLine]">
<xsl:if test="name(.)='Items'">
<xsl:if test="Item1">
<value>
<xsl:value-of select="Item1"/>
</value>
</xsl:if>
<xsl:if test="Item2">
<value>
<xsl:value-of select="Item2"/>
</value>
</xsl:if>
<xsl:if test="Item3">
<value>
<xsl:value-of select="Item3"/>
</value>
</xsl:if>
</xsl:if>
</xsl:for-each>
If the ID's are different it works well but the problem is when I have the same ID values (as is in the example). Anyone can help me with this?
Thanks.
Upvotes: 1
Views: 425
Reputation: 243449
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="kFollowing" match="*[not(self::LineId)]"
use="generate-id(preceding-sibling::LineId[1])"/>
<xsl:template match="LineId">
<OtherCharges>
<LineId><xsl:value-of select="."/></LineId>
<OtherChargesValues>
<xsl:apply-templates mode="inGroup"
select="key('kFollowing', generate-id())"/>
</OtherChargesValues>
</OtherCharges>
</xsl:template>
<xsl:template match="Items/*" mode="inGroup">
<value><xsl:value-of select="."/></value>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<OtherCharges>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>1</Item1>
<Item2>2</Item2>
<Item3>3</Item3>
</Items>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>4</Item1>
</Items>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>5</Item1>
<Item2>6</Item2>
</Items>
</OtherCharges>
produces the wanted, correct result:
<OtherCharges>
<LineId>P</LineId>
<OtherChargesValues>
<value>1</value>
<value>2</value>
<value>3</value>
</OtherChargesValues>
</OtherCharges>
<OtherCharges>
<LineId>P</LineId>
<OtherChargesValues>
<value>4</value>
</OtherChargesValues>
</OtherCharges>
<OtherCharges>
<LineId>P</LineId>
<OtherChargesValues>
<value>5</value>
<value>6</value>
</OtherChargesValues>
</OtherCharges>
Upvotes: 1
Reputation: 52858
starts-with()
may help...
XML Input
<OtherCharges>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>1</Item1>
<Item2>2</Item2>
<Item3>3</Item3>
</Items>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>4</Item1>
</Items>
<LineId>
<Id>P</Id>
</LineId>
<Items>
<Item1>5</Item1>
<Item2>6</Item2>
</Items>
</OtherCharges>
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="Id">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Items/*[starts-with(name(),'Item')]">
<value>
<xsl:apply-templates select="@*|node()"/>
</value>
</xsl:template>
</xsl:stylesheet>
XML Output (not well-formed)
<Id>P</Id>
<value>1</value>
<value>2</value>
<value>3</value>
<Id>P</Id>
<value>4</value>
<Id>P</Id>
<value>5</value>
<value>6</value>
Upvotes: 1
Reputation: 619
In general, avoid usng value-of and learn about apply-templates, and this will become a really easy XSLT question. Your output is not well-formed XML by the way, as it doesn't have a single root wrapper element.
<xsl:strip-space elements="OtherCharges" />
<xsl:template match="OtherCharges">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="OtherCharges/LineId">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="OtherCharges/Items">
<xsl:apply-templates/>
</xsl:template>
<!--* now the real work *-->
<xsl:template match="OtherCharges/LineId/Id">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="OtherCharges/Items/*">
<value><xsl:apply-templates/></value>
</xsl:template>
Upvotes: 1