iteong iteong - 1 month ago 15
HTML Question

XSLT use first child node value in later child nodes

I got a HTML file to transform into a XML file using XSLT, where I need to grab only the first child node's date value to be used in the latter child nodes, since the latter child nodes do not have the data values and the tags will appear empty. I tried to use positioning to do that for the XSLT but to no avail.

The relevant HTML code is:

<tbody>
<tr>
<th rowspan="8" scope="rowgroup">2005DEC</th>
<th scope="row">Blemish on Card</th>

</tr>

<tr>
<th scope="row">Damaged</th>

</tr>

<tr>
<th rowspan="8" scope="rowgroup">2006JAN</th>
<th scope="row">Lost</th>

</tr>

<tr>
<th scope="row">Stolen</th>

</tr>
</tbody>


The relevant XSLT code is:

<xsl:for-each select="html/body/div/div/div/table/tbody/tr">
<Entry>
<xsl:choose>
<xsl:when test="th[@scope='rowgroup']">
<Year>
<xsl:value-of select="substring(th[@scope='rowgroup']/., 1, 4)" />
</Year>
<Month>
<xsl:value-of select="substring(th[@scope='rowgroup']/., 5, 3)" />
</Month>
</xsl:when>
<xsl:otherwise>
<Year>
<xsl:value-of select="substring(tr[1]/th[@scope='rowgroup']/., 1, 4)" />
</Year>
<Month>
<xsl:value-of select="substring(tr[1]/th[@scope='rowgroup']/., 5, 3)" />
</Month>
</xsl:otherwise>
</xsl:choose>
</Entry>
</xsl:for-each>


The XML code that is output is the following, which is not what I want as the second child node does not have the date value of the first child node and just shows up as an empty value tag for year and month:

<Entry>
<Year>2005</Year>
<Month>DEC</Month>
<Reason>Blemish on Card</Reason>
</Entry>
<Entry>
<Year/>
<Month/>
<Reason>Damaged</Reason>
</Entry>
<Entry>
<Year>2006</Year>
<Month>JAN</Month>
<Reason>Lost</Reason>
</Entry>
<Entry>
<Year/>
<Month/>
<Reason>Stolen</Reason>
</Entry>


What I need is:

<Entry>
<Year>2005</Year>
<Month>DEC</Month>
<Reason>Blemish on Card</Reason>
</Entry>
<Entry>
<Year>2005</Year>
<Month>DEC</Month>
<Reason>Damaged</Reason>
</Entry>
<Entry>
<Year>2006</Year>
<Month>JAN</Month>
<Reason>Lost</Reason>
</Entry>
<Entry>
<Year>2006</Year>
<Month>JAN</Month>
<Reason>Stolen</Reason>
</Entry>


How do I do that for my XSLT?

Answer

You have to navigate up first: substring(../tr[1]/th[@scope='rowgroup']/., 1, 4) if you want to select the data from the first row.

Based on your comments that is not what you want, if you want to select the data from the first preceding sibling row with a scope="rowgroup" attribute then one way, continuing with your approach is

<xsl:for-each select="//tbody/tr">
    <Entry>
        <xsl:choose>
            <xsl:when test="th[@scope='rowgroup']">
                <Year>
                    <xsl:value-of select="substring(th[@scope='rowgroup']/., 1, 4)" />
                </Year>
                <Month>
                    <xsl:value-of select="substring(th[@scope='rowgroup']/., 5, 3)" />
                </Month>
            </xsl:when>
            <xsl:otherwise>
                <Year>
                    <xsl:value-of select="substring(preceding-sibling::tr[th[@scope='rowgroup']][1]/th[@scope='rowgroup'], 1, 4)" />
                </Year>
                <Month>
                    <xsl:value-of select="substring(preceding-sibling::tr[th[@scope='rowgroup']][1]/th[@scope='rowgroup'], 5, 3)" />
                </Month>
            </xsl:otherwise>
        </xsl:choose>

        <xsl:for-each select="th[@scope='row']">
            <Reason>
                <xsl:value-of select="." />
            </Reason>
        </xsl:for-each>
    </Entry>
</xsl:for-each>

although I might be easier to use for-each-group group-starting-with="tr[th[@scope = 'rowgroup']]" if you use an XSLT 2.0 processor.

Comments