Simple xsl:for-each "Pull" Example

This example uses <xsl:for-each> to "pull" selected information out of OXML output and create customized HTML tables.

Although you can easily generate HTML output using DESTINATION FORMAT=HTML on the OMS command, you have very little control over the HTML generated beyond the specific object types included in the HTML file. Using OXML, however, you can create customized tables. This example

  • selects only frequency tables in the OXML file;
  • displays only valid (nonmissing) values;
  • displays only the Frequency and Valid Percent columns;
  • replaces the default column labels with Count and Percent.

The XSLT stylesheet used in this example is oms_simple_frequency_tables.xsl.

Note: This stylesheet is not designed to work with frequency tables generated with layered split-file processing.

Figure 1. XSLT stylesheet: oms_simple_frequency_tables.xsl
<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" xmlns:oms="http://xml.spss.com/spss/oms">
<!--enclose everything in a template, starting at the root node-->
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE>Modified Frequency Tables</TITLE>
</HEAD>
<BODY>
<!--Find all Frequency Tables-->
<xsl:for-each select="//oms:pivotTable[@subType='Frequencies']">
<xsl:for-each select="oms:dimension[@axis='row']">
 <h3>
  <xsl:value-of select="@text"/>
 </h3>
</xsl:for-each>
<!--create the HTML table-->
<table border="1">
 <tbody align="char" char="." charoff="1">
 <tr>
  <!--
  table header row; you could extract headings from 
  the XML but in this example we're using different header text
  -->
  <th>Category</th><th>Count</th><th>Percent</th>
 </tr>
 <!--find the columns of the pivot table-->
 <xsl:for-each select="descendant::oms:dimension[@axis='column']">
   <!--select only valid, skip missing-->
    <xsl:if test="ancestor::oms:group[@text='Valid']">
     <tr>
     <td>
       <xsl:choose>
        <xsl:when test="not((parent::*)[@text='Total'])">
         <xsl:value-of select="parent::*/@text"/>
        </xsl:when>
        <xsl:when test="((parent::*)[@text='Total'])">
         <b><xsl:value-of select="parent::*/@text"/></b>
        </xsl:when>
       </xsl:choose>
     </td>
     <td>
      <xsl:value-of select="oms:category[@text='Frequency']/oms:cell/@text"/>      
     </td>
     <td>
      <xsl:value-of select="oms:category[@text='Valid Percent']/oms:cell/@text"/>
     </td>
    </tr>
  </xsl:if>
  </xsl:for-each>
 </tbody>
</table>
<!--Don't forget possible footnotes for split files-->
<xsl:if test="descendant::*/oms:note">
<p><xsl:value-of select="descendant::*/oms:note/@text"/></p>
</xsl:if>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
  • xmlns:oms="http://xml.spss.com/spss/oms" defines "oms" as the prefix that identifies the namespace, so all element names in XPath expressions need to include the prefix "oms:".
  • The XSLT primarily consists of a series of nested <xsl:for-each> statements, each drilling down to a different element and attribute of the table.
  • <xsl:for-each select="//oms:pivotTable[@subType='Frequencies']"> selects all tables of the subtype 'Frequencies'.
  • <xsl:for-each select="oms:dimension[@axis='row']"> selects the row dimension of each table.
  • <xsl:for-each select="descendant::oms:dimension[@axis='column']"> selects the column elements from each row. OXML represents tables row by row, so column elements are nested within row elements.
  • <xsl:if test="ancestor::oms:group[@text='Valid']"> selects only the section of the table that contains valid, nonmissing values. If there are no missing values reported in the table, this will include the entire table. This is the first of several XSLT specifications in this example that rely on attribute values that differ for different output languages. If you don't need solutions that work for multiple output languages, this is often the simplest, most direct way to select certain elements. Many times, however, there are alternatives that don't rely on localized text strings. See the topic Advanced xsl:for-each "Pull" Example for more information.
  • <xsl:when test="not((parent::*)[@text='Total'])"> selects column elements that aren't in the 'Total' row. Once again, this selection relies on localized text, and the only reason we make the distinction between total and nontotal rows in this example is to make the row label 'Total' bold.
  • <xsl:value-of select="oms:category[@text='Frequency']/oms:cell/@text"/> gets the content of the cell in the 'Frequency' column of each row.
  • <xsl:value-of select="oms:category[@text='Valid Percent']/oms:cell/@text"/> gets the content of the cell in the 'Valid Percent' column of each row. Both this and the previous code for obtaining the value from the 'Frequency' column rely on localized text.