Proposal HTP01
Adding Support for Stub Levels in HTML Tables

Stephen Ferg, Bureau of Labor Statistics
Contact Info
Revised: July 24, 2002

For the best results when printing this page, set your browser's font size to SMALLEST.

Introduction

This paper proposes adding four features to the HTML language specification in order to support data tables.

Before plunging into the technical details of this paper, it may be helpful to take a quick look at the last section, Examples, and look at the HTML markup for the example table.

Background

In the following discussion, we will think of a table as a grid of data cells, with a set of column header cells above the grid, and a set of row header cells (called the stub) to the left.

Stub
Header
Column Header Cells
Row Header Cells (Stub)        
       
       
       

We will focus our attention on the row headers in the stub. This is because in most tables of statistical data, there is a fundamental asymmetry in the formatting of the stub and the column headers. If both column and row headers were presented in the same way, as tree-like structures, tables would look like Exhibit A. Row headers, however, are not typically presented this way. Rather, they are usually formatted in an indented, outline-like structure. Often, data cells are associated with middle and upper nodes of the category structure, as well as with leaf nodes (Exhibit B).

Exhibit A
Ruritanian
Population
Survey
By Gender
Males Females
All Regions 7777 12221
By Region North East 1111 2222
West 1111 2222
South East 4444 5555
West 1111 2222
Exhibit B
Ruritanian
Population
Survey
By Gender
Males Females
All Regions 5555 7777
North 1111 2222
East 4444 5555
West 4444 5555
South 4444 5555
East 4444 5555
West 4444 5555

It is this asymmetry that causes the problem. The current HTML standard (4.01) offers satisfactory facilities for dealing with the tree-like formatting that is usually used for table headings. So table headings don't present significant problems. But HTML currently offers no real support for indented, outline-style stubs. It is that limitation that these proposals seek to address.

NOTE: In the following discussion, whatever is said about TH (that is, header) cells applies equally to TD cells that have been made to act like header cells through the use of a SCOPE attribute.

ROWLEVEL

Feature Type

Attribute of TR element

Description

This attribute indicates that a row in a table is at a certain level of nesting in the category structure of the stub. This level of nesting can be used in formatting the stub visually (see the proposed ROWMARGIN attribute), and enabling screen readers to locate nested metadata information.

Valid Values

Unsigned integer in the range 0 .. 255

Note that the count begins with zero, not with one.

Examples

    <TR rowlevel="0" ....>
    <TR rowlevel="1" ....>
    <TR rowlevel="2" ....>
    <TR rowlevel="3" ....>
    <TR rowlevel="4" ....>

Effect

In the following discussion, note that:

For a discussion of "absolute" and "relative" rowlevels, see the proposal for a STOPLEVEL attribute for TR elements.


Effects

When a TR tag has a ROWLEVEL attribute, row header information is associated with data cells in the row using the following algorithm.

  1. The list of row header cells is constructed according to the current HTML specification.
  2. For each row that contains a header cell that was added to the list, if the row has a ROWLEVEL attribute of N, then the TR tags in the table are searched backward, looking for a TR tag with a ROWLEVEL less than N and greater than or equal to the stoplevel of the initial row.
  3. If such a TR element is found, then the header cells in the row are inserted into the list. Note that a row may contain multiple header cells. Only the leftmost header cells (all header cells occuring to the left of any non-header cells) will be inserted. Then N is set to the ROWLEVEL of the row that was found, and the backward search resumes.
  4. The search process is repeated until it is stopped by a TR element with ROWLEVEL less than or equal to the STOPLEVEL, or the beginning of the table, or a </THEAD> or </TFOOT>. If a row with a ROWLEVEL equal to the STOPLEVEL is discovered, that row's header cells are added to the list.

The effect of ROWLEVEL, when used correctly, is to enable a data cell to be associated with all of the ancestor levels of its metadata (its nested stub categories) -- all the way up to level 0 (the top level) if that is desired.

Note that the algorithm that we've described in terms of "searching backward" could be implemented differently and perhaps more efficiently. For instance, as a user with a screen-reader moves the cursor from cell to cell in the table, the screen-reader needs only to maintain a list of the ancestor rows and headers to the current row. As the cursor moves from row to row, this list could be easily updated with relatively small processing overhead.

Benefits of ROWLEVEL

In the current HTML specification, in a table whose stub is formatted in an indented outline style, in order to associate a data cell with multiple generations of stub header cells, the only alternative to rather intricate tricks using ROWSPAN and ABBR is to use the HEADERS attribute. If we want data cells in the table to know about multiple generations of header cells, we must assign all of the header cells unique IDs, and every data cell in the table must have a HEADERS attribute listing all of the IDs of all of its generations of row header cells. And the only way to associate a header cell with its ancestor header cells is via a similar use of the HEADERS attribute.

Such coding is so difficult as to be impossible for a human being to create for any but the tiniest tables. For large tables, although it may be possible to create such markup programmatically, the HEADERS attributes add significantly to the size of the HTML file.

The ROWLEVEL attribute solves these problems.

When a table is marked up with ROWLEVEL attributes, data cells can be associated with all of the levels of their stub header cells, using the algorithm described earlier. Header cells can be associated with their ancestor header cells using a similar algorithm. This should make such tables considerably smaller and easier to create.

In addition, there are benefits for vision-impaired users using screen readers. For example, a user could place the screen reader's cursor on a row header cell, and have the screen reader read the complete "line of descent" for that header.

Perhaps the most important benefit of ROWLEVEL for vision-impaired users is that it opens the possibility of improved table navigation. Currently, some screen readers (e.g. JAWS) allow a user to navigate through a table -- moving up and down across rows, and left and right across columns -- by means of the arrow keys on the keyboard. But users are limited to row-to-row or column-to-column navigation. In contrast, a screen reader that knows about ROWLEVELs should be able to support level-to-level navigation. For instance, a particular key combination (e.g. control + UP arrow) might be used to move from a row at one level to the parent-level row. Other key combinations might be used to move from a row at a given level to its predecessor or successor siblings at the same level. In large tables, being able to navigate this way could enable a vision-impaired user to skip dozens of rows at a time. Tables that can be navigated this way should be significantly easier for a vision-impaired user to navigate in a meaningful way.

ROWMARGIN

Feature Type

Attribute of TABLE element

Description

In conjunction with the ROWLEVEL attribute, this attribute makes it easy to format nested stub categories for sighted users.

This same effect can be achieved with the current HTML specification, using the "margin-left" Cascading Style Sheet attribute. The ROWMARGIN attribute, however, should be much easier to use. More important, it will enable the visual level of indenting of stub headings to be tied automatically to the logical level of the stub heading (as expressed in the ROWLEVEL attribute), eliminating the possibility of a mismatch between the two levels.

Valid Values

Same as the valid values for CSS2 "margins" attribute

Examples

    <TABLE ROWMARGIN="2mm" ....>
    <TABLE ROWMARGIN="10px" ....>

Effect

The value for ROWMARGIN is specified at the TABLE level, and its effects extend to all TR elements with a ROWLEVEL attribute. The left margin of the first TH element in affected rows will be set to a value equal to the ROWMARGIN multiplied by the ROWLEVEL.

Example: Suppose that ROWMARGIN for the table has been set to 5 pixels. Then:

STOPLEVEL attribute of TR

Feature Type

Attribute of TR element

Description

This attribute has an effect only when used in conjunction with a ROWLEVEL attribute. Its purpose is to put a limit on the extent of the search when when a table is being searched during the process of constructing a list of headers that are associated with the data cells in the row.

The intended use of this feature is comparable to the intended use of the ABBR attribute. That is, it provides a way of limiting the amount of information that a screen-reader will speak, when it is not useful to read the total amount of information that is available.

Valid Values

Optionally signed integer in the range -255 .. 255

If a STOPLEVEL attribute is not specified for a TR element, the value of STOPLEVEL defaults to the STOPLEVEL set at the table level. Unless it has been explicitly over-ridden, this value will be "0" (zero).

Note that +0 and -0 are considered different values.

Examples

    <TR STOPLEVEL="0"  ....>
    <TR STOPLEVEL="1"  ....>
    <TR STOPLEVEL="2"  ....>
    <TR STOPLEVEL="-0" ....>
    <TR STOPLEVEL="-1" ....>
    <TR STOPLEVEL="-2" ....>

Effect

In tables with deeply nested stubs, the list of headers associated with data cells may be very long. It may be desirable for a screen-reader to limit the number of headers that it reads. STOPLEVEL provides a means of indicating to a screen-reader the amount of information that it should read. STOPLEVEL is used in the search algorithm for headers, as described in the discussion of the ROWLEVEL attribute.

If the STOPLEVEL is positive or unsigned, it indicates an absolute rowlevel. The topmost rowlevel is "0" (zero).

If the STOPLEVEL is negative, it indicates a relative rowlevel -- that is, a rowlevel relative to the rowlevel of the TR element in which it occurs. Note that this means that a stoplevel value of "-0" (negative zero) is different from a value of "0" (positive or unsigned zero).

The absolute stoplevel for a TR element is calculated by adding the relative stoplevel to the TR's ROWLEVEL. For example, if a TR element has a ROWLEVEL="2", then:

There are two rules for out-of-range values of STOPLEVEL. Out-of-range stoplevels may occur due to HTML coding mistakes, of course, but they can also easily occur in non-error situations.

  1. An absolute stoplevel cannot have a value less than zero. In a situation in which the absolute stoplevel for a TR element is set to a value less than zero, the row's absolute stoplevel will be reset to zero. This situation might occur if the default STOPLEVEL was set to -1 at the table level, and a TR element had a ROWLEVEL="0" and did not over-ride the default stoplevel.
  2. An absolute stoplevel cannot have a value greater than the rowlevel. In a situation in which the absolute rowlevel for a TR element is set to a value greater than the ROWLEVEL of that element, the row's absolute stoplevel will be reset to the ROWLEVEL of the row. This situation might occur if the default STOPLEVEL was set to 3 at the table level, and a TR element had a ROWLEVEL="2" and did not over-ride the default stoplevel.

STOPLEVEL attribute of TABLE

Feature Type

Attribute of TABLE element

Description

The default STOPLEVEL value for all TR elements in a table is "0" (zero). The STOPLEVEL attribute at the table level provides a means of controlling the default STOPLEVEL value for the TR elements in the table.

This feature should reduce the need to code the STOPLEVEL attribute at the row level.

Valid Values

Optionally signed integer in the range -255 .. 255

If a STOPLEVEL attribute is not specified, the value of STOPLEVEL defaults to "0" (zero).

Examples

    <TABLE STOPLEVEL="0"  ....>
    <TABLE STOPLEVEL="-0" ....>
    <TABLE STOPLEVEL="1"  ....>
    <TABLE STOPLEVEL="-1" ....>
    <TABLE STOPLEVEL="2"  ....>
    <TABLE STOPLEVEL="-2" ....>

Effect

A STOPLEVEL attribute of a TABLE element changes the default STOPLEVEL value for all TR elements in the table. That is, the TR elements in the table inherit this default STOPLEVEL value.

Note that the default ROWLEVEL for a table can be set to "-0" (negative zero). Unless it was over-ridden at the row level, this setting would prevent any searches for header information from proceeding past the row that contained the data cell that started the search.

EXAMPLES

A table that would display this way

Ruritanian Mineral Production
Ruritanian
Mineral
Production
1999 2000 2001
All Minerals 9999 9999 9999
Bauxite 9999 9999 9999
Copper 9999 9999 9999
Refined 9999 9999 9999
Unrefined 9999 9999 9999
Iron 9999 9999 9999
Ferrous Iron 9999 9999 9999
Ferric Iron 9999 9999 9999
Ferride Iron 9999 9999 9999
Tin 9999 9999 9999
Refined 9999 9999 9999
Unrefined 9999 9999 9999
Zinc 9999 9999 9999

Could be coded this way:

Bracketed numbers like this [3] are notes, and not part of the HTML.

<table ROWMARGIN="8mm" [1]  STOPLEVEL="1" [2] cellpadding="5" cellspacing="0" border="1">
<caption>Ruritanian Mineral Production</caption>

<tr>
<th class="centered">Ruritanian<br />Mineral<br />Production</th>
<th class="centered">1999</th>
<th class="centered">2000</th>
<th class="centered">2001</th>
</tr>

<tr ROWLEVEL="0"> [3]
<th >All Minerals</th> <td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr ROWLEVEL="1" > [4]
<th>Bauxite</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr ROWLEVEL="1" >
<th>Copper</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr  ROWLEVEL="2" > <th>Refined </th>  <td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr  ROWLEVEL="2" > [5]
<th>Unrefined</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>  </tr>

<tr>
<th ROWLEVEL="1" >Iron</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr>
<th  ROWLEVEL="2" STOPLEVEL="-0">Ferrous Iron</th> [6]

<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr>
<th  ROWLEVEL="2" STOPLEVEL="-0" >Ferric Iron</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>


<tr>
<th  ROWLEVEL="2" STOPLEVEL="-0" >Ferride Iron</th>
<td>9999</td>
<td>9999</td>
<td>9999</td> </tr>

<tr ROWLEVEL="1" >
<th>Tin</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr ROWLEVEL="1" >
<th>Copper</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr  ROWLEVEL="2" > <th>Refined </th>  <td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

<tr  ROWLEVEL="2" >
<th>Unrefined</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>  </tr>

<tr ROWLEVEL="1" >
<th>Zinc</th>
<td>9999</td>
<td>9999</td>
<td>9999</td>
</tr>

</table>

Notes

In these notes, we will be discussing row headers only. We assume that the list of column headers for any given data cell will be constructed according to the current HTML specification, so we won't discuss the column headers here.

A terminology note: As row levels increase, we will say that they get "lower" in the tree. Level 0 (zero) is the root of the tree, which puts it at a "higher" level than all other levels.

[1]
In our HTML table, we start by setting ROWMARGIN to "8mm" at the table level. This causes the indentation to be in increments of 8 millimeters. As you can see, when we get down to the rows where ROWLEVEL="2" (such as the row for "Unrefined" or "Ferrous Iron"), the margin value is 16mm, producing the desired amount of visual indentation.

[2]
Next, we set the default STOPLEVEL for the table to "1" . This insures that, unless the default STOPLEVEL is over-ridden at the row level, stub information for data cells will be read up to headers at level 1. We have done this because this table is constructed in such a way that we can get complete stub header information for every row in the table by finding headers up to level 1. Level 0 ("All Minerals") doesn't add any information, and to read it would be distracting for a user who is browsing the table with a screen reader.

Note that we don't want to set the default stoplevel any lower (e.g. to "2") because in this table there are many rows containing the non-unique headers "Refined" and "Unrefined" at level 2. Just the the fact that they are not unique is enough to prevent them from fully describing their data cells. For such rows, we will need supplemental stub information from higher-level rows.

Now let's look at the row headers that will be associated with the data cells in the rows.

[3]
In the row for "All Minerals", the rowlevel is 0 (zero) and the stoplevel (which is inherited from the table element) is 1. In cases where the stoplevel is greater than the rowlevel, the stoplevel is reset to the rowlevel. In this case, that means that the stoplevel for this row is reset to 0. When the row headers for this row are constructed, they contain only the headers for the row itself.

So the list of row headers associated with any data cell in this row is:

    All Minerals

[4]
In the row for "Bauxite", the rowlevel is 1 and the stoplevel (which is inherited from the table element) is 1. When the row headers for this row are constructed, they contain only the headers for the row itself.

So the list of row headers associated with any data cell in this row is:

    Bauxite

[5]
In the row for "Unrefined", the rowlevel is 2 and the stoplevel (which is inherited from the table element) is 1. The row headers for this row are constructed in the following way.

  1. We start with the data cell and search left, looking for header cells. We find "Unrefined".
  2. The row containing "Unrefined" has ROWLEVEL="2". No STOPLEVEL has been coded at the row level, so we use the STOPLEVEL coded on the TABLE element, 1. We search backward in the table, looking for a row with a ROWLEVEL that is less than 2 and greater than or equal 1.
  3. We find a row with ROWLEVEL="1", and add its header cells ("Copper") to the list.
  4. We terminate the search because the last ROWLEVEL that we found matched our STOPLEVEL.

So the list of row headers associated with any data cell in this row is:

    Copper
    Unrefined

[6]
Note that in the row for "Ferrous Iron", the stub header "Ferrous Iron" (unlike a row header of "Refined" or "Unrefined") is sufficient to completely describe the data. So, for the convenience of users with screen readers, we set the stoplevel of the row to -0 (minus zero). This causes any search for row headers to stop after finding the headers in this row.

So the list of row headers associated with any data cell in this row is:

    Ferrous Iron