-2

I have an XML file that I export from my DB which is structured as below, '

<output>
    <row>
        <Month>October</Month>
        <Location>kansas</Location>
        <bus_name>bus1</bus_name>
        <bus_type>volvo</bus_type>
        <bus_colour>red</bus_colour>
        <bus_count>10</bus_count>
    </row>
    <row>
        <Month>October</Month>
        <Location>kansas</Location>
        <bus_name>bus1</bus_name>
        <bus_type>Volvo</bus_type>
        <bus_colour>green</bus_colour>
        <bus_count>11</bus_count>
    </row>
        <Month>October</Month>
        <Location>kansas</Location>
        <bus_name>bus1</bus_name>
        <bus_type>Merc</bus_type>
        <bus_colour>blue</bus_colour>
        <bus_count>5</bus_count>
    </row>
So on...
</output>

I need the table to look like the image attached below. The XSL and XML file will be refreshed periodically.The cells will have similar color's based on bus type.

I'm new to XSL thus having a really hard time coming up with a solution. Any help would be appreciated.

Table Output

Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
  • Do you have a *specific* question? – michael.hor257k Oct 26 '14 at 12:40
  • I need an XSL which will help me derive the table from the XML i have.The main challenge is ordering the data as per the table since, the XML and table doesnt have a straight forward mapping. – Ravindranath Barathy Oct 26 '14 at 12:45
  • Read an XSLT tutorial. –  Oct 26 '14 at 12:47
  • "*the XML and table doesnt have a straight forward mapping.*" I don't quite see that in your example. Perhaps you should explain more about this "main challenge". – michael.hor257k Oct 26 '14 at 12:53
  • I'm doing the same as we speak. but,i don't seem to reaching anywhere. I was able to create a one to one mapping table but,now the table structure has changed and that has left me puzzled. – Ravindranath Barathy Oct 26 '14 at 12:53
  • @michael.hor257k: The first three node has to be mapped with the first row and also i need to make sure if the bus_type for each color is same as I have to paint them in same color – Ravindranath Barathy Oct 26 '14 at 12:58

2 Answers2

1

Just as a different approach and also handling the colours for the same bus_types.
Demo

<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<table>
    <tr>
        <td colspan="9">ACME BUS SERVICE</td>
    </tr>
    <tr>
        <td colspan="9">
            Month: <xsl:value-of select="//Month"/>
        </td>
    </tr>
    <tr>
        <td>Season</td>
        <td>Location</td>
        <td>Bus Name</td>
        <td colspan="2">RED</td>
        <td colspan="2">GREEN</td>
        <td colspan="2">BLUE</td>
    </tr>
    <tr>
        <td></td>
        <td></td>
        <td></td>
        <td>Bus Type</td>
        <td>Bus Count</td>
        <td>Bus Type</td>
        <td>Bus Count</td>
        <td>Bus Type</td>
        <td>Bus Count</td>
    </tr>
    <xsl:for-each select="//row[Location[not(preceding::Location/. = .)]]" >
        <xsl:variable name="currentLocation" select="./Location"/>
        <tr>
            <xsl:attribute name="class">
              <xsl:value-of select="$currentLocation"/>
             </xsl:attribute>
            <td>
                <xsl:if test="position()=1">Winter</xsl:if>
            </td>
            <td>
                <xsl:value-of select="$currentLocation"/>
            </td>
            <td>
                <xsl:value-of select="./bus_name"/>
            </td>
            <td>
                <xsl:if test="count(//row[Location= $currentLocation]
                                         [bus_type = //row[Location= $currentLocation]
                                         [bus_colour = 'red']/bus_type]) > 1">
                    <xsl:attribute name="class">color</xsl:attribute>
                </xsl:if>
                <xsl:value-of select="//row[Location= $currentLocation]
                                           [bus_colour = 'red']/bus_type"/>
            </td>
            <td>
                <xsl:value-of select="//row[Location= $currentLocation]
                                           [bus_colour = 'red']/bus_count"/>
            </td>
            <td>
                <xsl:if test="count(//row[Location= $currentLocation]
                                         [bus_type = //row[Location=$currentLocation]  
                                         [bus_colour = 'green']/bus_type]) > 1">
                    <xsl:attribute name="class">color</xsl:attribute>
                </xsl:if>
                <xsl:value-of select="//row[Location= $currentLocation]
                                           [bus_colour = 'green']/bus_type"/>
            </td>
            <td>
                <xsl:value-of select="//row[Location= $currentLocation]
                                           [bus_colour = 'green']/bus_count"/>
            </td>
            <td>
                <xsl:if test="count(//row[Location= $currentLocation]
                                         [bus_type = //row[Location=$currentLocation]  
                                         [bus_colour = 'blue']/bus_type]) > 1">
                    <xsl:attribute name="class">color</xsl:attribute>
                </xsl:if>
                <xsl:value-of select="//row[Location= $currentLocation]
                                           [bus_colour = 'blue']/bus_type"/>
            </td>
            <td>
                <xsl:value-of select="//row[Location= $currentLocation]
                                           [bus_colour = 'blue']/bus_count"/>
            </td>
        </tr>
    </xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>

For every location, the location is set as classname to the <tr>, e.g. <tr class="kansas">. Every td with a bus type that is used more than once at a location gets the class="color". So to display the table with different colors, you can just add CSS like e.g. .kansas .color { background-color: blue; }. In case you want to display the same color based on the bus_type, just adjust the classname "color" in the xslt to the current bus_type.

Note: In the linked example I've only added one row for Texas to show that the XSLT displays multiple locations, only sets the season for the first one, and will also work in case not all colors are provided for a location. And the output is not valid HTML (no html-, head-, body-tags etc provided). As you mentioned you'd like to get HTML ouput, you probably already have an XSLT generating valid HTML where you can adjust/include the part you need for the table.

Update for the question in the comments: To set the class name for a <tr> to the name of the bus_type (in case a bus_type is used more than once at a location) instead of the location:

Change this in above XSLT:

<tr>
<xsl:attribute name="class">
   <xsl:value-of select="$currentLocation"/>
</xsl:attribute>

into

<tr>
<xsl:if test="count(//row[Location=$currentLocation]) >  
              count(//row[Location=$currentLocation]/
                      bus_type[not(. = preceding::bus_type)])">  
    <xsl:attribute name="class">
       <xsl:value-of select="//row[Location=$currentLocation]/
                               bus_type[ . = preceding::bus_type]"/>
    </xsl:attribute>
</xsl:if>

Updated Demo 2 for this.

Additional notes and questions: One adjustment to the OP XML was to change the lowercase "volvo" to "Volvo". In case the original export from DB really mixes upper- and lowercase names, this can be handled in the XSLT to lowercase all bus_names (to get the unique values) and uppercase the first letter for the value in the <td>. Also it would be good to know if you use XSLT 2.0 or XSLT 1.0 as XSLT 2.0 provides functionality to simplify some tasks - e.g. 2.0 provides a lower-case() function where in 1.0 the same can be achieved using translate() - as reference for this as you mentioned you're new to XSL: How can I convert a string to upper- or lower-case with XSLT?
Further question is - as the XML example is only a part of the DB export - if there will be only one row for each location or if it is possible that there are various rows, e.g. kansas bus1, kansas bus2 etc.

Update 2 for the second question in the comments: I can add an (almost) line by line explanation and will drop a comment when done. I assume it's not necessary to cover the HTML part but only the XSLT. In the meantime, as you mentioned you're new to XSLT, maybe the following can be of use:
for <xsl:template match="/"> - https://stackoverflow.com/questions/3127108/xsl-xsltemplate-match
for XPath axes - http://www.xmlplease.com/axis
for some basics, e.g. - In what order do templates in an XSLT document execute, and do they match on the source XML or the buffered output?

Note that it should be avoided at SO to have extended comments - when there are too many comments below a post, an automated message will be displayed that suggests to move to chat. Because you need a reputation of 20 to chat (https://stackoverflow.com/help/privileges), this won't be possible at the moment.

Community
  • 1
  • 1
matthias_h
  • 11,356
  • 9
  • 22
  • 40
  • GENIUS!..Works like a charm! – Ravindranath Barathy Oct 26 '14 at 19:26
  • I would like to apply different colour to different row of matching bus type,Is that going to be too tough? – Ravindranath Barathy Oct 26 '14 at 19:27
  • @Curious Glad I was able to be of help. Re diff. colour: it depends. In case you only want to colour bus types that are used more than once in a single row, it's no problem because you can set diff. colours depending on the parent tr (e.g. .kansas .volvo is blue, .texas .volvo is red, if you would like to change the class name to the bus type; but this would also work using .color for the td). In case you want to color e.g. a volvo in texas when it's only used once there, it'd be a different approach as currently only bus types are colored when they're used more than once at a single location. – matthias_h Oct 26 '14 at 19:35
  • :I just found that if I add a new node eg: 2 Red bus nodes,followed by green and blue it doesn't create a new row. Whereas if I add a new node with different location it creates a new row,I understand that you have taken Location as the classname,but I would like to have bus name as the classname what would be the update? – Ravindranath Barathy Oct 26 '14 at 20:15
  • @Curious You mean in case volvo is used more than once in kansas, the `` for kansas should have class `volvo` instead of `kansas` (and the volvo ``s keep the class `color`)? – matthias_h Oct 26 '14 at 20:23
  • Yes,Thats right, a bus name can repeat,how do I go about the change? – Ravindranath Barathy Oct 26 '14 at 20:41
  • matthias_h:I know its asking for too much,can you please share a line by line explanation of the code.This code is going to be my bible for the next couple of days. I may have to break it down and update it in the future.Thanks for your help! – Ravindranath Barathy Oct 26 '14 at 20:56
  • matthias_h:It doesnt seem to be working.I updated the XSL with the changes you metioned. Its still not creating a new row for the new bus type I entered. – Ravindranath Barathy Oct 27 '14 at 12:14
  • @Curious It would be easier to check this issue if you just fork/update the demo example and share the link. In addition, it would be helpful if you update your question for the questions in my answer re the db xml, e.g. if there's only one row for a location or if there are more. – matthias_h Oct 27 '14 at 21:43
0

The first three node has to be mapped with the first row

Now that is a specific question. Assuming your input is arranged so that each group of 3 consecutive <row> elements maps to a single table row (with internal group positions matching the column positions), try it this way:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/output">
    <table border="1">
        <tr>
            <!-- build your header here -->
        </tr>
        <xsl:for-each select="row[position() mod 3 = 1]" >
            <tr>
                <td><xsl:value-of select="Location"/></td>
                <td><xsl:value-of select="bus_name"/></td>
                <xsl:for-each select=". | following-sibling::row[position() &lt; 3]">
                    <td><xsl:value-of select="bus_type"/></td>
                    <td><xsl:value-of select="bus_colour"/></td>
                    <td><xsl:value-of select="bus_count"/></td>
                </xsl:for-each>
            </tr>
        </xsl:for-each>
    </table>
</xsl:template>

</xsl:stylesheet>

I suggest you ask a separate question regarding the coloring. Make sure we understand exactly what is known beforehand (e.g. a list of known bus types?) and what is the required output (post it as code).

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thanks Mike for the response,I tried out the XSL you provided,but t Im getting only Kansas and Bus 1 as the first row,rest is empty and im only getting one row, The header is Location Bus Name RED GREEN BLUE – Ravindranath Barathy Oct 26 '14 at 13:54
  • @Curious http://www.xsltcake.com/slices/9ys6fT -- P.S. make sure your input is well-formed; what you posted here isn't. – michael.hor257k Oct 26 '14 at 14:12