In the “pull” approach, the structure and order of elements in the source XML file may not be relevant for the transformed results. Instead, the source XML is treated like a data repository from which selected pieces of information are extracted, and the structure of the transformed results is defined by the XSLT stylesheet.
The “pull” approach typically uses <xsl:for-each> to select and extract information from the XML.
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 usingDESTINATION FORMAT=HTMLon theOMScommand, 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 9-15
Frequencies pivot tables in Viewer
Figure 9-16
Customized HTML frequency tables
Figure 9-17
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>
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']">
<!–Don't forget possible footnotes for split files–>
<xsl:if test="descendant::*/oms:note">
</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.
For more information, see the topic “Advanced xsl:for-each “Pull” Example” on p. 157.
<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.
Figure 9-18
XPath expressions for selected frequency table elements
Advanced xsl:for-each “Pull” Example
In addition to selecting and displaying only selected parts of each frequency table in HTML format, this example
doesn’t rely on any localized text;
always shows both variable names and labels;
always shows both values and value labels;
rounds decimal values to integers.
The XSLT stylesheet used in this example is customized_frequency_tables.xsl.
Note: This stylesheet is not designed to work with frequency tables generated with layered split-file processing.
Figure 9-19
Customized HTML with value rounded to integers
The simple example contained a single XSLT <template> element. This stylesheet contains multiple templates:
A main template that selects the table elements from the OXML
A template that defines the display of variable names and labels
A template that defines the display of values and value labels
A template that defines the display of cell values as rounded integers The following sections explain the different templates used in the stylesheet.
Main Template for Advanced xsl:for-each Example
Since this XSLT stylesheet produces tables with essentially the same structure as the simple
<xsl:for-each>example, the main template is similar to the one used in the simple example.
Figure 9-20
Main template of customized_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>
<xsl:for-each select="//oms:pivotTable[@subType='Frequencies']">
<xsl:for-each select="oms:dimension[@axis='row']">
<h3>
This template is very similar to the one for the simple example. The main differences are:
<xsl:call-template name="showVarInfo"/>calls another template to determine what to show for the table title instead of simply using the text attribute of the row dimension (oms:dimension[@axis='row']). For more information, see the topic “Controlling Variable and Value Label Display” on p. 160.
<xsl:if test="oms:category[3]">selects only the data in the ‘Valid’ section of the table instead of <xsl:if test="ancestor::oms:group[@text='Valid']">. The positional argument used in this example doesn’t rely on localized text. It relies on the fact that the basic structure of a frequency table is always the same and the fact that OXML does not include elements for
empty cells. Since the ‘Missing’ section of a frequency table contains values only in the first two columns, there are no oms:category[3] column elements in the ‘Missing’ section, so the test condition is not met for the ‘Missing’ rows. For more information, see the topic “XPath Expressions in Multiple Language Environments” on p. 162.
<xsl:when test="parent::*/@varName">selects the nontotal rows instead of <xsl:when test="not((parent::*)[@text='Total'])">. Column elements in the nontotal rows in a frequency table contain a varName attribute that identifies the variable, whereas column elements in total rows do not. So this selects nontotal rows without relying on localized text.
<xsl:call-template name="showValueInfo"/>calls another template to determine what to show for the row labels instead of <xsl:value-of select="parent::*/@text"/>. For more information, see the topic “Controlling Variable and Value Label Display” on p. 160.
<xsl:apply-templates select="oms:category[1]/oms:cell/@number"/>
selects the value in the ‘Frequency’ column instead of <xsl:value-of select="oms:category[@text='Frequency']/oms:cell/@text"/>. A positional
argument is used instead of localized text (the ‘Frequency’ column is always the first column in a frequency table), and a template is applied to determine how to display the value in the cell. Percentage values are handled the same way, using oms:category[3] to select the values from the ‘Valid Percent’ column. For more information, see the topic “Controlling Decimal Display” on p. 161.
Controlling Variable and Value Label Display
The display of variable names and/or labels and values and/or value labels in pivot tables is determined by the current settings forSET TVARSandSET TNUMBERS—the corresponding text attributes in the OXML also reflect those settings. The system default is to display labels when they exist and names or values when they don’t. The settings can be changed to always show names or values and never show labels or always show both.
The XSLT templates showVarInfo and showValueInfo are designed to ignore those settings and always show both names or values and labels (if present).
Figure 9-21
showVarInfo and showValueInfo templates
<!–display both variable names and labels–>
<xsl:template name="showVarInfo">
<!–display both values and value labels–>
<xsl:template name="showValueInfo">
<xsl:choose>
<!–Numeric vars have a number attribute, string vars have a string attribute –>
<xsl:when test="parent::*/@number">
<xsl:text>Variable Name: </xsl:text>and <xsl:value-of select="@varName"/> display the text
“Variable Name:” followed by the variable name.
<xsl:if test="@label">checks to see if the variable has a defined label.
If the variable has a defined label, <xsl:text>Variable Label: </xsl:text> and <xsl:value-of select="@label"/>display the text “Variable Label:” followed by the defined variable label.
Values and value labels are handled in a similar fashion, except instead of a varName attribute, values will have either a number attribute or a string attribute.
Controlling Decimal Display
The text attribute of a <cell> element in OXML displays numeric values with the default number of decimal positions for the particular type of cell value. For most table types, there is little or no control over the default number of decimals displayed in cell values in pivot tables, but OXML can provide some flexibility not available in default pivot table display.
In this example, the cell values are rounded to integers, but we could just as easily display five or six or more decimal positions because the number attribute may contain up to 15 significant digits.
Figure 9-22
Rounding cell values
<!–round decimal cell values to integers–>
<xsl:template match="@number">
<xsl:value-of select="format-number(.,'#')"/>
</xsl:template>
This template is invoked whenever <apply-templates select="..."/> contains a reference to a number attribute.
<xsl:value-of select="format-number(.,'#')"/>specifies that the selected values should be rounded to integers with no decimal positions.