XSLT1.0 Rendering sequence of different elements stored in a variable as M x N table
I have the following XML (it is simplified and most attributes are omitted):
<Document>
<Transfer Name="" From="" To=""/>
<Transfer Name="" From="" To=""/>
<OtherElement/>
<OtherElement/>
<Flight AirLina="" From="" To=""/>
<Flight AirLina="" From="" To=""/>
<OtherElement/>
<Hotel Name="" Duration=""/>
<Hotel Name="" Duration=""/>
<OtherElement/>
<OtherElement/>
<Extras Name="" Price=""/>
<Extras Name="" Price=""/>
<Extras Name="" Price=""/>
<Extras Name="" Price=""/>
<Extras Name="" Price=""/>
<Extras Name="" Price=""/>
<OtherElement/>
<OtherElement/>
</Document>
I have a variable, containing different elements:
<xsl:variable name="packageElements"
select="/Document/Transfer | /Document/Coach | /Document/Flight | /Document/Hotel | /Document/Extras" />
I would like to display that data in a table with 2 columns. I am using XSLT1.0 and MSXSL processor.
I have been trying it out with the simplest solution I could think of:
<table开发者_运维问答>
<tbody>
<xsl:for-each select="$packageElements[position() mod 2 = 1]">
<tr>
<td>
<!-- current element -->
<xsl:value-of select="local-name()"/>
</td>
<td>
<!-- element following the current in the $packageElements variable -->
<!-- Here is where I'm stuck, I can't figure out how to correctly pick it up :( -->
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
Would really appreciate any help.
I think the complexity is in here:
element following the current in the $packageElements variable
This is a node in $packageElements node-set with a position() greater than current node. But, what is the position of the current node in $packegeElements node-set?
Check this. Dimitre builts a expression wich is the count for intersection between preceding nodes (in the document) of current node and the node-set.
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:variable name="vData" select="/*/*"/>
<xsl:template match="/">
<table border="1">
<xsl:apply-templates select="$vData[position() mod 2 = 1]"/>
</table>
</xsl:template>
<xsl:template match="nums/*">
<xsl:variable name="vPos" select="position()"/>
<tr>
<td><xsl:value-of select="name()"/></td>
<td><xsl:value-of select="$vData[position() = 2*$vPos]"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<nums>
<A>01</A>
<num>02</num>
<B>03</B>
<num>04</num>
<C>05</C>
<num>06</num>
<D>07</D>
<num>08</num>
<E>09</E>
<num>010</num>
</nums>
produces the wanted, correct result:
<table border="1">
<tr>
<td>A</td>
<td>02</td>
</tr>
<tr>
<td>B</td>
<td>04</td>
</tr>
<tr>
<td>C</td>
<td>06</td>
</tr>
<tr>
<td>D</td>
<td>08</td>
</tr>
<tr>
<td>E</td>
<td>010</td>
</tr>
</table>
Alright,
I have combined @Dimitre Novatchev's idea from this post's answers and @Tomalak's from [XSLT]: Rendering a node sequence as M x N table post. I really liked @Tomalak's solution with $perRow
variable and the <xsl:template name="filler">
template to cope with empty cells.
With the idea taken from @Dimitre Novatchev answer I'm holding on to $trStartPos
. I then calculate $lowerBoundry
and $upperBoundry
that are used to accumulate all elements that should appear in a row. There might be a more elegant way to do this calculation - please let me know if you come up with one, I would really appreciate it!
XSLT 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="*"/>
<!-- Select only required elements -->
<xsl:variable name="tableData"
select="/nums/A | /nums/B | /nums/C | /nums/D | /nums/E "/>
<xsl:variable name="perRow" select="2"/>
<xsl:template match="/">
<table border="1">
<tbody>
<xsl:apply-templates
select="$tableData[position() mod $perRow = 1]" mode="tr"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="nums/*" mode="tr">
<xsl:variable name="trStartPos" select="position()" />
<xsl:variable name="upperBoundry" select="$trStartPos * $perRow" />
<xsl:variable name="lowerBoundry" select="$upperBoundry - $perRow" />
<tr>
<xsl:variable name="tdsData"
select="$tableData[(position() > $lowerBoundry) and (position() <= $upperBoundry)]" />
<xsl:apply-templates select="$tdsData" mode="td"/>
<!-- fill up the last row - @Tomalak's solution -->
<xsl:if test="count($tdsData) < $perRow">
<xsl:call-template name="filler">
<xsl:with-param name="rest" select="$perRow - count($tdsData)" />
</xsl:call-template>
</xsl:if>
</tr>
</xsl:template>
<!-- Templates for specific elements could be easily added with appropriate info to
be displayed depending on the element. This one is general just to display
elements' name and value -->
<xsl:template match="nums/*" mode="td">
<td>
El. name: <xsl:value-of select="local-name()"/> -
El. value: <xsl:value-of select="."/>
</td>
</xsl:template>
<!-- @Tomalak solution (please read beginning of this answer for reference) -->
<xsl:template name="filler">
<xsl:param name="rest" select="0" />
<xsl:if test="$rest">
<td> </td>
<xsl:call-template name="filler">
<xsl:with-param name="rest" select="$rest - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
applied on the following XML
<nums>
<A>A-01</A>
<num>02</num>
<num>03</num>
<num>04</num>
<B>B-05</B>
<num>06</num>
<num>07</num>
<C>C-08</C>
<num>09</num>
<D>D-10</D>
<num>11</num>
<num>12</num>
<num>13</num>
<E>E-14</E>
<num>15</num>
</nums>
results in the following output
<table border="1">
<tbody>
<tr>
<td>
El. name: A -
El. value: A-01
</td>
<td>
El. name: B -
El. value: B-05
</td>
</tr>
<tr>
<td>
El. name: C -
El. value: C-08
</td>
<td>
El. name: D -
El. value: D-10
</td>
</tr>
<tr>
<td>
El. name: E -
El. value: E-14
</td>
<td> </td>
</tr>
</tbody>
</table>
精彩评论