tags:

views:

54

answers:

2

I have the following XML data:

<Activity>
<ObjectGroup type="default">
<Object id="1874" name="PR1010Date" type="reference label" index="10" columnNo="0" dynamic="true">
      <Description>Date</Description>      
      <Value instance="0">30/06/2010</Value>
    </Object>
    <Object id="1875" name="PR1020LoggedBy" type="reference label" index="20" columnNo="1" dynamic="true">
      <Description>Request Logged By</Description>     
      <Value>Site Administrator</Value>
    </Object>
    <Object id="1876" name="PR1030Comments" type="large text box" index="30" columnNo="0" dataType="Text">
      <Description>Comments</Description>      
      <Value instance="0">Test</Value>
    </Object>
<ObjectGroup>
</Activity>

I need to create an XSL which will produce the following output:

<html>
<table>
<tr>
<td width="50%">30/06/2010</td>
<td width="50%">Site Admin</td>
</tr>
<tr>
<td width="100%">Test</td>
</tr>
</table>

In the above XML the index attribute along with the columnNo drives how many rows and columns are generated. What the end result is determined on the ColumnNo so if the ObjectGroup has Objects with incremental columnNo then they are all rendered into one row with appropriate width's for each of the column.

A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kObjByCol" match="Object"
  use="@columnNo"/>

 <xsl:key name="kRow" match="Object[@columnNo != 0]"
  use="generate-id(preceding-sibling::Object
                                 [@columnNo = 0][1]
                   )"/>

 <xsl:template match="/*">
   <html>
     <table border="1">
       <xsl:apply-templates select=
       "key('kObjByCol', 0)"/>
     </table>
   </html>
 </xsl:template>

 <xsl:template match="Object">
   <xsl:variable name="vcellNodes"
    select=".|key('kRow',generate-id())"/>
   <tr>
     <xsl:apply-templates mode="row" select=
        "$vcellNodes">
       <xsl:with-param name="pCount"
            select="count($vcellNodes)"/>
     </xsl:apply-templates>
   </tr>
 </xsl:template>

 <xsl:template match="Object" mode="row">
   <xsl:param name="pCount"/>
   <td width="{100 div $pCount}%">
     <xsl:value-of select="Value"/>
   </td>
 </xsl:template>
</xsl:stylesheet>

when applied to the provided XML document (corrected to be well-formed):

<Activity>
    <ObjectGroup type="default">
        <Object id="1874" name="PR1010Date"
         type="reference label" index="10"
         columnNo="0" dynamic="true">
            <Description>Date</Description>
            <Value instance="0">30/06/2010</Value>
        </Object>
        <Object id="1875" name="PR1020LoggedBy"
         type="reference label" index="20"
         columnNo="1" dynamic="true">
            <Description>Request Logged By</Description>
            <Value>Site Administrator</Value>
        </Object>
        <Object id="1876" name="PR1030Comments"
         type="large text box" index="30"
         columnNo="0" dataType="Text">
            <Description>Comments</Description>
            <Value instance="0">Test</Value>
        </Object>
    </ObjectGroup>
</Activity>

produces the wanted, correct result:

<html>
    <table border="1">
        <tr>
            <td width="50%">30/06/2010</td>
            <td width="50%">Site Administrator</td>
        </tr>
        <tr>
            <td width="100%">Test</td>
        </tr>
    </table>
</html>
Dimitre Novatchev
+1  A: 

Following the excellent response from Dimitre, another style sheet without keys.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output indent="yes"/>
    <xsl:template match="ObjectGroup">
        <html>
            <table border="1">
                <xsl:apply-templates select="Object[@columnNo=0]"/>
            </table>
        </html>
    </xsl:template>
    <xsl:template match="Object">
        <xsl:variable name="td"
                      select=".|following-sibling::*[@columnNo!=0][
                                      count(current()|
                                            preceding-sibling::*[@columnNo=0][1]
                                      )=1]"/>
        <tr>
            <xsl:apply-templates select="$td/Value">
                <xsl:with-param name="td-count" select="count($td)"/>
            </xsl:apply-templates>
        </tr>
    </xsl:template>
    <xsl:template match="Value">
        <xsl:param name="td-count"/>
        <td width="{100 div $td-count}%">
            <xsl:value-of select="."/>
        </td>
    </xsl:template>
</xsl:stylesheet>

However, most XSLT processors allow the use of keys.

Now, for colspan:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output indent="yes"/>
    <xsl:variable name="td-max">
        <xsl:call-template name="max">
            <xsl:with-param name="nodes" select="/*/*/Object[@columnNo=0]"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:template name="max">
        <xsl:param name="nodes"/>
        <xsl:if test="$nodes">
            <xsl:variable name="head"
                              select="count($nodes[1]/following-sibling::*[@columnNo!=0][
                                          count($nodes[1]|
                                                preceding-sibling::*[@columnNo=0][1]
                                          )=1])+1"/>
            <xsl:variable name="tail">
                <xsl:call-template name="max">
                    <xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:choose>
                <xsl:when test="$head > $tail or $tail=''">
                    <xsl:value-of select="$head"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$tail"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:template>
    <xsl:template match="ObjectGroup">
        <html>
            <table border="1">
                <xsl:apply-templates select="Object[@columnNo=0]"/>
            </table>
        </html>
    </xsl:template>
    <xsl:template match="Object">
        <xsl:variable name="td"
                          select=".|following-sibling::*[@columnNo!=0][
                                          count(current()|
                                                preceding-sibling::*[@columnNo=0][1]
                                          )=1]"/>
        <tr>
            <xsl:apply-templates select="$td/Value">
                <xsl:with-param name="td-count" select="count($td)"/>
            </xsl:apply-templates>
        </tr>
    </xsl:template>
    <xsl:template match="Value">
        <xsl:param name="td-count"/>
        <td width="{100 div $td-count}%">
            <xsl:if test="position()=last() and $td-max > $td-count">
                <xsl:attribute name="colspan">
                    <xsl:value-of select="$td-max - $td-count + 1"/>
                </xsl:attribute>
            </xsl:if>
            <xsl:value-of select="."/>
        </td>
    </xsl:template>
</xsl:stylesheet>

Edit: Added dynamic colspan. It could be less verbose if you don't care multiple colspan="1".

Edit 2: Better max pattern.

Alejandro
Thank you for the responses. The stylesheet provided by Dimitre helped me out and pointed me in correct direction.
Manthan
@Manthan: You are wellcome! Also, I must say that you should mark Dimitre post as the right answer in order to help others.
Alejandro
@Dimitre: Is there a way that I can also calculate the colspan for the rows whose width is going to be 100% so that the HTML will display correctly as currently when I use 100% as width for rows with only one columns it puts the HTML all out of whack. Is it possible within the stylesheet that I can traverse the output of the xslt in later stage so that I can adjust the colspan of the rows with one column only.
Manthan
@Manthan: For sure there is a way to calculate the colspan: the same way you calculate de width. The amount of cols could be fixed (like the 100%) or you could calculate. See my edit.
Alejandro
@Alejandro: Thanks for the update I will try that.
Manthan
I had additional question if I have the xml as<Activity><ObjectGroup type="default"> <Object columnNo="0" /> <Object columnNo ="1" /> <Object columnNo ="0" /></ObjectGroup><ObjectGroup type="group"><Object columnNo="0" /><Object columnNo="0" /></ObjectGroup></Activity>Is there a way that I can only generate the above dynamic HTML table but only for Objects that are part of ObjectGroup that have type = default. In essence I am trying to group only within a parent that has a given attribute value. Is that even possible or am I complicating it too much.
Manthan
For grouping the items based on attribute of the parent I used the solution provided by Alejandro that works great. Thank you.
Manthan
@Manthan: My example is for processor without `key()` (very old ones!). Use Dimitre solution and change the `kObjByCol` key's pattern with `match="ObjectGroup[@type='default']/Object"`.
Alejandro