1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// found in the LICENSE file.
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * TODO(stoarca): This class has become obsolete except for the shadow table.
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Chop most of it away.
8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @fileoverview A DOM traversal interface for navigating data in tables.
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.provide('cvox.TraverseTable');
12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('cvox.DomPredicates');
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('cvox.DomUtil');
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('cvox.SelectionUtil');
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('cvox.TableUtil');
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('cvox.TraverseUtil');
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * An object that represents an active table cell inside the shadow table.
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @constructor
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)function ShadowTableNode() {}
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Whether or not the active cell is spanned by a preceding cell.
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {boolean}
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.spanned;
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Whether or not this cell is spanned by a rowSpan.
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {?boolean}
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.rowSpan;
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Whether or not this cell is spanned by a colspan
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {?boolean}
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.colSpan;
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The row index of the corresponding active table cell
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {?number}
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.i;
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The column index of the corresponding active table cell
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {?number}
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.j;
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The corresponding <TD> or <TH> node in the active table.
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {?Node}
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.activeCell;
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The cells that are row headers of the corresponding active table cell
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {!Array}
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.rowHeaderCells = [];
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The cells that are column headers of the corresponding active table cell
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {!Array}
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ShadowTableNode.prototype.colHeaderCells = [];
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Initializes the traversal with the provided table node.
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @constructor
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Node} tableNode The table to be traversed.
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable = function(tableNode) {
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The active table <TABLE> node. In this context, "active" means that this is
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * the table the TraverseTable object is navigating.
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {Node}
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.activeTable_ = null;
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * A 2D array "shadow table" that contains pointers to nodes in the active
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * table. More specifically, each cell of the shadow table contains a special
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * object ShadowTableNode that has as one of its member variables the
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * corresponding cell in the active table.
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   *
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The shadow table will allow us efficient navigation of tables with
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * rowspans and colspans without needing to repeatedly scan the table. For
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * example, if someone requests a cell at (1,3), predecessor cells with
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * rowspans/colspans mean the cell you eventually return could actually be
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * one located at (0,2) that spans out to (1,3).
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   *
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * This shadow table will contain a ShadowTableNode with the (0, 2) index at
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * the (1,3) position, eliminating the need to check for predecessor cells
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * with rowspan/colspan every time we traverse the table.
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   *
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {!Array.<Array.<ShadowTableNode>>}
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.shadowTable_ = [];
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * An array of shadow table nodes that have been determined to contain header
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * cells or information about header cells. This array is collected at
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * initialization and then only recalculated if the table changes.
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * This array is used by findHeaderCells() to determine table row headers
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * and column headers.
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {Array.<ShadowTableNode>}
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.candidateHeaders_ = [];
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * An array that associates cell IDs with their corresponding shadow nodes.
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * If there are two shadow nodes for the same cell (i.e. when a cell spans
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * other cells) then the first one will be associated with the ID. This means
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * that shadow nodes that have spanned set to true will not be included in
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * this array.
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {Array.<ShadowTableNode>}
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.idToShadowNode_ = [];
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.initialize(tableNode);
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The cell cursor, represented by an array that stores the row and
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * column location [i, j] of the active cell. These numbers are 0-based.
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * In this context, "active" means that this is the cell the user is
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * currently looking at.
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {Array}
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.currentCellCursor;
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The number of columns in the active table. This is calculated at
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * initialization and then only recalculated if the table changes.
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Please Note: We have chosen to use the number of columns in the shadow
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * table as the canonical column count. This is important for tables that
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * have colspans - the number of columns in the active table will always be
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * less than the true number of columns.
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {?number}
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.colCount = null;
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The number of rows in the active table. This is calculated at
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * initialization and then only recalculated if the table changes.
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {?number}
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.rowCount = null;
177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The row headers in the active table. This is calculated at
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * initialization and then only recalculated if the table changes.
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Please Note:
184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  Row headers are defined here as <TH> or <TD> elements. <TD> elements when
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  serving as header cells must have either:
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  - The scope attribute defined
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  - Their IDs referenced in the header content attribute of another <TD> or
188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  <TH> element.
189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  The HTML5 spec specifies that only header <TH> elements can be row headers
191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  ( http://dev.w3.org/html5/spec/tabular-data.html#row-header ) but the
192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  HTML4 spec says that <TD> elements can act as both
193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  ( http://www.w3.org/TR/html401/struct/tables.html#h-11.2.6 ). In the
194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  interest of providing meaningful header information for all tables, here
195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  we take the position that <TD> elements can act as both.
196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {Array}
198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
199cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.tableRowHeaders = null;
200cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
202cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The column headers in the active table. This is calculated at
204cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * initialization and then only recalculated if the table changes.
205cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Please Note: see comment for tableRowHeaders.
207cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
208cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {Array}
209cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.tableColHeaders = null;
211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// TODO (stoarca): tighten up interface to {!Node}
214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Initializes the class member variables.
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Node} tableNode The table to be traversed.
217cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
218cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.initialize = function(tableNode) {
219cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!tableNode) {
220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (tableNode == this.activeTable_) {
223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.activeTable_ = tableNode;
226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.currentCellCursor = null;
227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.tableRowHeaders = [];
229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.tableColHeaders = [];
230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.buildShadowTable_();
232cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.colCount = this.shadowColCount_();
234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.rowCount = this.countRows_();
235cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
236cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.findHeaderCells_();
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Listen for changes to the active table. If the active table changes,
239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // rebuild the shadow table.
240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // TODO (stoarca): Is this safe? When this object goes away, doesn't the
241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // eventListener stay on the node? Someone with better knowledge of js
242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // please confirm. If so, this is a leak.
243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.activeTable_.addEventListener('DOMSubtreeModified',
244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.bind(function() {
245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.buildShadowTable_();
246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.colCount = this.shadowColCount_();
247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.rowCount = this.countRows_();
248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.tableRowHeaders = [];
250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.tableColHeaders = [];
251cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.findHeaderCells_();
252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (this.colCount == 0 && this.rowCount == 0) {
254cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          return;
255cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
257cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (this.getCell() == null) {
258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          this.attachCursorToNearestCell_();
259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }, this), false);
261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Finds the cell cursor containing the specified node within the table.
266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Returns null if there is no close cell.
267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Node} node The node for which to find the cursor.
268cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {Array.<number>} The table index for the node.
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
270cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.findNearestCursor = function(node) {
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // TODO (stoarca): The current structure for representing the
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // shadow table is not optimal for this query, but it's not urgent
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // since this only gets executed at most once per user action.
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // In case node is in a table but above any individual cell, we go down as
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // deep as we can, being careful to avoid going into nested tables.
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var n = node;
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (n.firstElementChild &&
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)         !(n.firstElementChild.tagName == 'TABLE' ||
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           cvox.AriaUtil.isGrid(n.firstElementChild))) {
282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    n = n.firstElementChild;
283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
284cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (!cvox.DomPredicates.cellPredicate(cvox.DomUtil.getAncestors(n))) {
285cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    n = cvox.DomUtil.directedNextLeafNode(n);
286cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // TODO(stoarca): Ugly logic. Captions should be part of tables.
287cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // There have been a bunch of bugs as a result of
288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // DomUtil.findTableNodeInList excluding captions from tables because
289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // it makes them non-contiguous.
290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!cvox.DomUtil.getContainingTable(n, {allowCaptions: true})) {
291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return null;
292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < this.rowCount; ++i) {
295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var j = 0; j < this.colCount; ++j) {
296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (this.shadowTable_[i][j]) {
297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (cvox.DomUtil.isDescendantOfNode(
298cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            n, this.shadowTable_[i][j].activeCell)) {
299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          return [i, j];
300cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
301cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
302cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
303cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
304cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return null;
305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
306cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
308cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Finds the valid cell nearest to the current cell cursor and moves the cell
309cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * cursor there. To be used when the table has changed and the current cell
310cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * cursor is now invalid (doesn't exist anymore).
311cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
312cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
313cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.attachCursorToNearestCell_ = function() {
314cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.currentCellCursor) {
315cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // We have no idea.  Just go 'somewhere'. Other code paths in this
316cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // function go to the last cell, so let's do that!
317cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.goToLastCell();
318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
319cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
321cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var currentCursor = this.currentCellCursor;
322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Does the current row still exist in the table?
324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var currentRow = this.shadowTable_[currentCursor[0]];
325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (currentRow) {
326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Try last cell of current row
327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.currentCellCursor = [currentCursor[0], (currentRow.length - 1)];
328cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Current row does not exist anymore. Does current column still exist?
330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Try last cell of current column
331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var numRows = this.shadowTable_.length;
332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (numRows == 0) {
333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Table has been deleted!
334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.currentCellCursor = null;
335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return;
336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var aboveCell =
338cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.shadowTable_[numRows - 1][currentCursor[1]];
339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (aboveCell) {
340cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.currentCellCursor = [(numRows - 1), currentCursor[1]];
341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Current column does not exist anymore either.
343cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Move cursor to last cell in table.
344cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.goToLastCell();
345cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
346cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Builds or rebuilds the shadow table by iterating through all of the cells
352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * ( <TD> or <TH> or role='gridcell' nodes) of the active table.
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Array} The shadow table.
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.buildShadowTable_ = function() {
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Clear shadow table
358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.shadowTable_ = [];
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Build shadow table structure. Initialize it as a 2D array.
361cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var allRows = cvox.TableUtil.getChildRows(this.activeTable_);
362cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var currentRowParent = null;
363cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var currentRowGroup = null;
364cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
365cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var colGroups = cvox.TableUtil.getColGroups(this.activeTable_);
366cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var colToColGroup = cvox.TableUtil.determineColGroups(colGroups);
367cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
368cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var ctr = 0; ctr < allRows.length; ctr++) {
369cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.shadowTable_.push([]);
370cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
371cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
372cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Iterate through active table by row
373cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < allRows.length; i++) {
374cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var childCells = cvox.TableUtil.getChildCells(allRows[i]);
375cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
376cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Keep track of position in active table
377cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var activeTableCol = 0;
378cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Keep track of position in shadow table
379cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var shadowTableCol = 0;
380cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
381cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    while (activeTableCol < childCells.length) {
382cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
383cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Check to make sure we haven't already filled this cell.
384cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (this.shadowTable_[i][shadowTableCol] == null) {
385cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
386cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var activeTableCell = childCells[activeTableCol];
387cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
388cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Default value for colspan and rowspan is 1
389cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var colsSpanned = 1;
390cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var rowsSpanned = 1;
391cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
392cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (activeTableCell.hasAttribute('colspan')) {
393cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
394cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          colsSpanned =
395cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              parseInt(activeTableCell.getAttribute('colspan'), 10);
396cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
397cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          if ((isNaN(colsSpanned)) || (colsSpanned <= 0)) {
398cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // The HTML5 spec defines colspan MUST be greater than 0:
399cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // http://dev.w3.org/html5/spec/Overview.html#attr-tdth-colspan
400cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            //
401cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // This is a change from the HTML4 spec:
402cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // http://www.w3.org/TR/html401/struct/tables.html#adef-colspan
403cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            //
404cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // We will degrade gracefully by treating a colspan=0 as
405cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // equivalent to a colspan=1.
406cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // Tested in method testColSpan0 in rowColSpanTable_test.js
407cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            colsSpanned = 1;
408cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
409cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
410cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (activeTableCell.hasAttribute('rowspan')) {
411cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          rowsSpanned =
412cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              parseInt(activeTableCell.getAttribute('rowspan'), 10);
413cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
414cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          if ((isNaN(rowsSpanned)) || (rowsSpanned <= 0)) {
415cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // The HTML5 spec defines that rowspan can be any non-negative
416cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // integer, including 0:
417cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // http://dev.w3.org/html5/spec/Overview.html#attr-tdth-rowspan
418cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            //
419cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // However, Chromium treats rowspan=0 as rowspan=1. This appears
420cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // to be a bug from WebKit:
421cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // https://bugs.webkit.org/show_bug.cgi?id=10300
422cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // Inherited from a bug (since fixed) in KDE:
423cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // http://bugs.kde.org/show_bug.cgi?id=41063
424cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            //
425cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // We will follow Chromium and treat rowspan=0 as equivalent to
426cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // rowspan=1.
427cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            //
428cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // Tested in method testRowSpan0 in rowColSpanTable_test.js
429cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            //
430cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // Filed as a bug in Chromium: http://crbug.com/58223
431cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            rowsSpanned = 1;
432cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
433cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
434cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        for (var r = 0; r < rowsSpanned; r++) {
435cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          for (var c = 0; c < colsSpanned; c++) {
436cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            var shadowNode = new ShadowTableNode();
437cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            if ((r == 0) && (c == 0)) {
438cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              // This position is not spanned.
439cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.spanned = false;
440cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.rowSpan = false;
441cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.colSpan = false;
442cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.i = i;
443cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.j = shadowTableCol;
444cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.activeCell = activeTableCell;
445cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.rowHeaderCells = [];
446cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.colHeaderCells = [];
447cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.isRowHeader = false;
448cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.isColHeader = false;
449cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            } else {
450cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              // This position is spanned.
451cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.spanned = true;
452cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.rowSpan = (rowsSpanned > 1);
453cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.colSpan = (colsSpanned > 1);
454cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.i = i;
455cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.j = shadowTableCol;
456cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.activeCell = activeTableCell;
457cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.rowHeaderCells = [];
458cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.colHeaderCells = [];
459cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.isRowHeader = false;
460cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.isColHeader = false;
461cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            }
462cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // Check this shadowNode to see if it is a candidate header cell
463cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            if (cvox.TableUtil.checkIfHeader(shadowNode.activeCell)) {
464cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              this.candidateHeaders_.push(shadowNode);
465cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            } else if (shadowNode.activeCell.hasAttribute('headers')) {
466cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              // This shadowNode has information about other header cells
467cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              this.candidateHeaders_.push(shadowNode);
468cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            }
469cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
470cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // Check and update row group status.
471cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            if (currentRowParent == null) {
472cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              // This is the first row
473cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              currentRowParent = allRows[i].parentNode;
474cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              currentRowGroup = 0;
475cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            } else {
476cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              if (allRows[i].parentNode != currentRowParent) {
477cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                // We're in a different row group now
478cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                currentRowParent = allRows[i].parentNode;
479cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                currentRowGroup = currentRowGroup + 1;
480cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              }
481cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            }
482cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            shadowNode.rowGroup = currentRowGroup;
483cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
484cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            // Check and update col group status
485cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            if (colToColGroup.length > 0) {
486cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.colGroup = colToColGroup[shadowTableCol];
487cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            } else {
488cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowNode.colGroup = 0;
489cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            }
490cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
491cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            if (! shadowNode.spanned) {
492cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              if (activeTableCell.id != null) {
493cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                this.idToShadowNode_[activeTableCell.id] = shadowNode;
494cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              }
495cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            }
496cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
497cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            this.shadowTable_[i + r][shadowTableCol + c] = shadowNode;
498cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
499cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
500cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        shadowTableCol += colsSpanned;
501cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        activeTableCol++;
502cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      } else {
503cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // This position has already been filled (by a previous cell that has
504cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // a colspan or a rowspan)
505cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        shadowTableCol += 1;
506cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
507cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
508cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
509cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.shadowTable_;
510cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
511cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
512cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
513cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
514cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Finds header cells from the list of candidate headers and classifies them
515cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * in two ways:
516cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * -- Identifies them for the entire table by adding them to
517cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * this.tableRowHeaders and this.tableColHeaders.
518cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * -- Identifies them for each shadow table node by adding them to the node's
519cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * rowHeaderCells or colHeaderCells arrays.
520cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
521cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
522cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
523cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.findHeaderCells_ = function() {
524cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Forming relationships between data cells and header cells:
525cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // http://dev.w3.org/html5/spec/tabular-data.html
526cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // #header-and-data-cell-semantics
527cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
528cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < this.candidateHeaders_.length; i++) {
529cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
530cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var currentShadowNode = this.candidateHeaders_[i];
531cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var currentCell = currentShadowNode.activeCell;
532cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
533cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var assumedScope = null;
534cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var specifiedScope = null;
535cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
536cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (currentShadowNode.spanned) {
537cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
538cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
539cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
540cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if ((currentCell.tagName == 'TH') &&
541cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        !(currentCell.hasAttribute('scope'))) {
542cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // No scope specified - compute scope ourselves.
543cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Go left/right - if there's a header node, then this is a column
544cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // header
545cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (currentShadowNode.j > 0) {
546cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (this.shadowTable_[currentShadowNode.i][currentShadowNode.j - 1].
547cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            activeCell.tagName == 'TH') {
548cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          assumedScope = 'col';
549cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
550cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      } else if (currentShadowNode.j < this.shadowTable_[currentShadowNode.i].
551cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          length - 1) {
552cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (this.shadowTable_[currentShadowNode.i][currentShadowNode.j + 1].
553cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            activeCell.tagName == 'TH') {
554cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          assumedScope = 'col';
555cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
556cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      } else {
557cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // This row has a width of 1 cell, just assume this is a colum header
558cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        assumedScope = 'col';
559cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
560cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
561cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (assumedScope == null) {
562cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Go up/down - if there's a header node, then this is a row header
563cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (currentShadowNode.i > 0) {
564cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          if (this.shadowTable_[currentShadowNode.i - 1][currentShadowNode.j].
565cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              activeCell.tagName == 'TH') {
566cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            assumedScope = 'row';
567cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
568cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        } else if (currentShadowNode.i < this.shadowTable_.length - 1) {
569cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          if (this.shadowTable_[currentShadowNode.i + 1][currentShadowNode.j].
570cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              activeCell.tagName == 'TH') {
571cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            assumedScope = 'row';
572cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
573cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        } else {
574cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          // This column has a height of 1 cell, just assume that this is
575cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          // a row header
576cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          assumedScope = 'row';
577cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
578cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
579cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (currentCell.hasAttribute('scope')) {
580cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      specifiedScope = currentCell.getAttribute('scope');
581cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (currentCell.hasAttribute('role') &&
582cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        (currentCell.getAttribute('role') == 'rowheader')) {
583cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      specifiedScope = 'row';
584cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (currentCell.hasAttribute('role') &&
585cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        (currentCell.getAttribute('role') == 'columnheader')) {
586cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)     specifiedScope = 'col';
587cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
588cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
589cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if ((specifiedScope == 'row') || (assumedScope == 'row')) {
590cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      currentShadowNode.isRowHeader = true;
591cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
592cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Go right until you hit the edge of the table or a data
593cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // cell after another header cell.
594cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Add this cell to each shadowNode.rowHeaderCells attribute as you go.
595cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var rightCtr = currentShadowNode.j;
596cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           rightCtr < this.shadowTable_[currentShadowNode.i].length;
597cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           rightCtr++) {
598cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
599cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var rightShadowNode = this.shadowTable_[currentShadowNode.i][rightCtr];
600cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var rightCell = rightShadowNode.activeCell;
601cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
602cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if ((rightCell.tagName == 'TH') ||
603cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            (rightCell.hasAttribute('scope'))) {
604cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
605cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          if (rightCtr < this.shadowTable_[currentShadowNode.i].length - 1) {
606cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            var checkDataCell =
607cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                this.shadowTable_[currentShadowNode.i][rightCtr + 1];
608cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
609cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
610cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        rightShadowNode.rowHeaderCells.push(currentCell);
611cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
612cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.tableRowHeaders.push(currentCell);
613cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if ((specifiedScope == 'col') || (assumedScope == 'col')) {
614cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      currentShadowNode.isColHeader = true;
615cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
616cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Go down until you hit the edge of the table or a data cell
617cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // after another header cell.
618cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Add this cell to each shadowNode.colHeaders attribute as you go.
619cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
620cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var downCtr = currentShadowNode.i;
621cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           downCtr < this.shadowTable_.length;
622cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           downCtr++) {
623cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
624cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var downShadowNode = this.shadowTable_[downCtr][currentShadowNode.j];
625cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (downShadowNode == null) {
626cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          break;
627cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
628cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var downCell = downShadowNode.activeCell;
629cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
630cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if ((downCell.tagName == 'TH') ||
631cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            (downCell.hasAttribute('scope'))) {
632cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
633cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          if (downCtr < this.shadowTable_.length - 1) {
634cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            var checkDataCell =
635cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                this.shadowTable_[downCtr + 1][currentShadowNode.j];
636cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
637cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
638cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        downShadowNode.colHeaderCells.push(currentCell);
639cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
640cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.tableColHeaders.push(currentCell);
641cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (specifiedScope == 'rowgroup') {
642cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)       currentShadowNode.isRowHeader = true;
643cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
644cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // This cell is a row header for the rest of the cells in this row group.
645cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var currentRowGroup = currentShadowNode.rowGroup;
646cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
647cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Get the rest of the cells in this row first
648cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var cellsInRow = currentShadowNode.j + 1;
649cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           cellsInRow < this.shadowTable_[currentShadowNode.i].length;
650cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           cellsInRow++) {
651cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.shadowTable_[currentShadowNode.i][cellsInRow].
652cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            rowHeaderCells.push(currentCell);
653cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
654cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
655cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Now propagate to rest of row group
656cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var downCtr = currentShadowNode.i + 1;
657cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           downCtr < this.shadowTable_.length;
658cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           downCtr++) {
659cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
660cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (this.shadowTable_[downCtr][0].rowGroup != currentRowGroup) {
661cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          break;
662cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
663cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
664cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        for (var rightCtr = 0;
665cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)             rightCtr < this.shadowTable_[downCtr].length;
666cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)             rightCtr++) {
667cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
668cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          this.shadowTable_[downCtr][rightCtr].
669cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              rowHeaderCells.push(currentCell);
670cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
671cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
672cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.tableRowHeaders.push(currentCell);
673cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
674cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (specifiedScope == 'colgroup') {
675cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      currentShadowNode.isColHeader = true;
676cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
677cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // This cell is a col header for the rest of the cells in this col group.
678cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var currentColGroup = currentShadowNode.colGroup;
679cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
680cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Get the rest of the cells in this colgroup first
681cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var cellsInCol = currentShadowNode.j + 1;
682cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           cellsInCol < this.shadowTable_[currentShadowNode.i].length;
683cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           cellsInCol++) {
684cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (this.shadowTable_[currentShadowNode.i][cellsInCol].colGroup ==
685cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            currentColGroup) {
686cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          this.shadowTable_[currentShadowNode.i][cellsInCol].
687cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              colHeaderCells.push(currentCell);
688cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
689cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
690cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
691cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Now propagate to rest of col group
692cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var downCtr = currentShadowNode.i + 1;
693cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           downCtr < this.shadowTable_.length;
694cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           downCtr++) {
695cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
696cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        for (var rightCtr = 0;
697cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)             rightCtr < this.shadowTable_[downCtr].length;
698cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)             rightCtr++) {
699cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
700cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          if (this.shadowTable_[downCtr][rightCtr].colGroup ==
701cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              currentColGroup) {
702cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            this.shadowTable_[downCtr][rightCtr].
703cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                colHeaderCells.push(currentCell);
704cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          }
705cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
706cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
707cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.tableColHeaders.push(currentCell);
708cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
709cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (currentCell.hasAttribute('headers')) {
710cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.findAttrbHeaders_(currentShadowNode);
711cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
712cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (currentCell.hasAttribute('aria-describedby')) {
713cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.findAttrbDescribedBy_(currentShadowNode);
714cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
715cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
716cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
717cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
718cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
719cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
720cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Finds header cells from the 'headers' attribute of a given shadow node's
721cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * active cell and classifies them in two ways:
722cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * -- Identifies them for the entire table by adding them to
723cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * this.tableRowHeaders and this.tableColHeaders.
724cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * -- Identifies them for the shadow table node by adding them to the node's
725cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * rowHeaderCells or colHeaderCells arrays.
726cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Please note that header cells found through the 'headers' attribute are
727cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * difficult to attribute to being either row or column headers because a
728cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * table cell can declare arbitrary cells as its headers. A guess is made here
729cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * based on which axis the header cell is closest to.
730cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
731cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ShadowTableNode} currentShadowNode A shadow node with an active cell
732cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * that has a 'headers' attribute.
733cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
734cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
735cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
736cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.findAttrbHeaders_ = function(currentShadowNode) {
737cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var activeTableCell = currentShadowNode.activeCell;
738cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
739cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var idList = activeTableCell.getAttribute('headers').split(' ');
740cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var idToken = 0; idToken < idList.length; idToken++) {
741cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Find cell(s) with this ID, add to header list
742cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var idCellArray = cvox.TableUtil.getCellWithID(this.activeTable_,
743cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                                   idList[idToken]);
744cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
745cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var idCtr = 0; idCtr < idCellArray.length; idCtr++) {
746cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (idCellArray[idCtr].id == activeTableCell.id) {
747cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Skip if the ID is the same as the current cell's ID
748cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
749cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
750cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Check if this list of candidate headers contains a
751cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // shadowNode with an active cell with this ID already
752cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var possibleHeaderNode =
753cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          this.idToShadowNode_[idCellArray[idCtr].id];
754cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (! cvox.TableUtil.checkIfHeader(possibleHeaderNode.activeCell)) {
755cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // This listed header cell will not be handled later.
756cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Determine whether this is a row or col header for
757cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // the active table cell
758cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
759cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var iDiff = Math.abs(possibleHeaderNode.i - currentShadowNode.i);
760cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var jDiff = Math.abs(possibleHeaderNode.j - currentShadowNode.j);
761cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if ((iDiff == 0) || (iDiff < jDiff)) {
762cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(currentShadowNode.rowHeaderCells,
763cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
764cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(this.tableRowHeaders,
765cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
766cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        } else {
767cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          // This is a column header
768cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(currentShadowNode.colHeaderCells,
769cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
770cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(this.tableColHeaders,
771cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
772cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
773cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
774cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
775cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
776cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
777cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
778cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
779cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
780cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Finds header cells from the 'aria-describedby' attribute of a given shadow
781cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * node's active cell and classifies them in two ways:
782cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * -- Identifies them for the entire table by adding them to
783cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * this.tableRowHeaders and this.tableColHeaders.
784cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * -- Identifies them for the shadow table node by adding them to the node's
785cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * rowHeaderCells or colHeaderCells arrays.
786cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
787cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Please note that header cells found through the 'aria-describedby' attribute
788cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * must have the role='rowheader' or role='columnheader' attributes in order to
789cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * be considered header cells.
790cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
791cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ShadowTableNode} currentShadowNode A shadow node with an active cell
792cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * that has an 'aria-describedby' attribute.
793cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
794cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
795cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
796cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.findAttrbDescribedBy_ =
797cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    function(currentShadowNode) {
798cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var activeTableCell = currentShadowNode.activeCell;
799cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
800cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var idList = activeTableCell.getAttribute('aria-describedby').split(' ');
801cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var idToken = 0; idToken < idList.length; idToken++) {
802cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Find cell(s) with this ID, add to header list
803cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var idCellArray = cvox.TableUtil.getCellWithID(this.activeTable_,
804cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                                   idList[idToken]);
805cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
806cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var idCtr = 0; idCtr < idCellArray.length; idCtr++) {
807cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (idCellArray[idCtr].id == activeTableCell.id) {
808cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Skip if the ID is the same as the current cell's ID
809cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
810cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
811cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // Check if this list of candidate headers contains a
812cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // shadowNode with an active cell with this ID already
813cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var possibleHeaderNode =
814cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          this.idToShadowNode_[idCellArray[idCtr].id];
815cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (! cvox.TableUtil.checkIfHeader(possibleHeaderNode.activeCell)) {
816cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // This listed header cell will not be handled later.
817cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Determine whether this is a row or col header for
818cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // the active table cell
819cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
820cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (possibleHeaderNode.activeCell.hasAttribute('role') &&
821cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            (possibleHeaderNode.activeCell.getAttribute('role') ==
822cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                'rowheader')) {
823cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(currentShadowNode.rowHeaderCells,
824cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
825cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(this.tableRowHeaders,
826cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
827cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        } else if (possibleHeaderNode.activeCell.hasAttribute('role') &&
828cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            (possibleHeaderNode.activeCell.getAttribute('role') ==
829cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                'columnheader')) {
830cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(currentShadowNode.colHeaderCells,
831cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
832cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          cvox.TableUtil.pushIfNotContained(this.tableColHeaders,
833cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                            possibleHeaderNode.activeCell);
834cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
835cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
836cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
837cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
838cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
839cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
840cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
841cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
842cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the current cell or null if there is no current cell.
843cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {?Node} The cell <TD> or <TH> or role='gridcell' node.
844cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
845cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.getCell = function() {
846cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.currentCellCursor || !this.shadowTable_) {
847cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return null;
848cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
849cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
850cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var shadowEntry =
851cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]];
852cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
853cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return shadowEntry && shadowEntry.activeCell;
854cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
855cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
856cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
857cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
858cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the cell at the specified location.
859cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Array.<number>} index The index <i, j> of the required cell.
860cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {?Node} The cell <TD> or <TH> or role='gridcell' node at the
861cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * specified location. Null if that cell does not exist.
862cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
863cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.getCellAt = function(index) {
864cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (((index[0] < this.rowCount) && (index[0] >= 0)) &&
865cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ((index[1] < this.colCount) && (index[1] >= 0))) {
866cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var shadowEntry = this.shadowTable_[index[0]][index[1]];
867cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (shadowEntry != null) {
868cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return shadowEntry.activeCell;
869cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
870cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
871cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return null;
872cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
873cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
874cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
875cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
876cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the cells that are row headers of the current cell.
877cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Array} The cells that are row headers of the current cell. Empty if
878cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * the current cell does not have row headers.
879cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
880cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.getCellRowHeaders = function() {
881cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var shadowEntry =
882cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]];
883cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
884cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return shadowEntry.rowHeaderCells;
885cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
886cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
887cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
888cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
889cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the cells that are col headers of the current cell.
890cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Array} The cells that are col headers of the current cell. Empty if
891cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * the current cell does not have col headers.
892cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
893cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.getCellColHeaders = function() {
894cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var shadowEntry =
895cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]];
896cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
897cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return shadowEntry.colHeaderCells;
898cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
899cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
900cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
901cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
902cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Whether or not the current cell is spanned by another cell.
903cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Whether or not the current cell is spanned by another cell.
904cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
905cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.isSpanned = function() {
906cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var shadowEntry =
907cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]];
908cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
909cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return shadowEntry.spanned;
910cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
911cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
912cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
913cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
914cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Whether or not the current cell is a row header cell.
915cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Whether or not the current cell is a row header cell.
916cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
917cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.isRowHeader = function() {
918cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var shadowEntry =
919cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]];
920cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
921cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return shadowEntry.isRowHeader;
922cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
923cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
924cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
925cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
926cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Whether or not the current cell is a col header cell.
927cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Whether or not the current cell is a col header cell.
928cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
929cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.isColHeader = function() {
930cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var shadowEntry =
931cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]];
932cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
933cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return shadowEntry.isColHeader;
934cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
935cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
936cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
937cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
938cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the active column, represented as an array of <TH> or <TD> nodes that
939cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * make up a column. In this context, "active" means that this is the column
940cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * that contains the cell the user is currently looking at.
941cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {Array} An array of <TH> or <TD> or role='gridcell' nodes.
942cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
943cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.getCol = function() {
944cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var colArray = [];
945cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < this.shadowTable_.length; i++) {
946cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
947cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (this.shadowTable_[i][this.currentCellCursor[1]]) {
948cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var shadowEntry = this.shadowTable_[i][this.currentCellCursor[1]];
949cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
950cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (shadowEntry.colSpan && shadowEntry.rowSpan) {
951cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Look at the last element in the column cell aray.
952cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var prev = colArray[colArray.length - 1];
953cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (prev !=
954cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            shadowEntry.activeCell) {
955cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          // Watch out for positions spanned by a cell with rowspan and
956cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          // colspan. We don't want the same cell showing up multiple times
957cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          // in per-column cell lists.
958cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          colArray.push(
959cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)              shadowEntry.activeCell);
960cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
961cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      } else if ((shadowEntry.colSpan) || (!shadowEntry.rowSpan)) {
962cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        colArray.push(
963cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            shadowEntry.activeCell);
964cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
965cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
966cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
967cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return colArray;
968cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
969cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
970cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
971cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
972cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the active row <TR> node. In this context, "active" means that this is
973cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * the row that contains the cell the user is currently looking at.
974cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {Node} The active row node.
975cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
976cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.getRow = function() {
977cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var childRows = cvox.TableUtil.getChildRows(this.activeTable_);
978cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return childRows[this.currentCellCursor[0]];
979cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
980cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
981cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
982cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
983cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the table summary text.
984cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
985cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {?string} Either:
986cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     1) The table summary text
987cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     2) Null if the table does not contain a summary attribute.
988cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
989cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.summaryText = function() {
990cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // see http://code.google.com/p/chromium/issues/detail?id=46567
991cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // for information why this is necessary
992cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.activeTable_.hasAttribute('summary')) {
993cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return null;
994cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
995cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.activeTable_.getAttribute('summary');
996cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
997cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
998cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
999cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1000cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Gets the table caption text.
1001cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
1002cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {?string} Either:
1003cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     1) The table caption text
1004cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     2) Null if the table does not include a caption tag.
1005cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1006cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.captionText = function() {
1007cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // If there's more than one outer <caption> element, choose the first one.
1008cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var captionNodes = cvox.XpathUtil.evalXPath('caption\[1]',
1009cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.activeTable_);
1010cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (captionNodes.length > 0) {
1011cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return captionNodes[0].innerHTML;
1012cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1013cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return null;
1014cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1015cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1016cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1017cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1018cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1019cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Calculates the number of columns in the shadow table.
1020cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {number} The number of columns in the shadow table.
1021cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
1022cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1023cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.shadowColCount_ = function() {
1024cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // As the shadow table is a 2D array, the number of columns is the
1025cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // max number of elements in the second-level arrays.
1026cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var max = 0;
1027cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < this.shadowTable_.length; i++) {
1028cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (this.shadowTable_[i].length > max) {
1029cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      max = this.shadowTable_[i].length;
1030cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
1031cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1032cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return max;
1033cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1034cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1035cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1036cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1037cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Calculates the number of rows in the table.
1038cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {number} The number of rows in the table.
1039cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
1040cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1041cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.countRows_ = function() {
1042cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Number of rows in a table is equal to the number of TR elements contained
1043cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // by the (outer) TBODY elements.
1044cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var rowCount = cvox.TableUtil.getChildRows(this.activeTable_);
1045cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return rowCount.length;
1046cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1047cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1048cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1049cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1050cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Calculates the number of columns in the table.
1051cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * This uses the W3C recommended algorithm for calculating number of
1052cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * columns, but it does not take rowspans or colspans into account. This means
1053cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * that the number of columns calculated here might be lower than the actual
1054cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * number of columns in the table if columns are indicated by colspans.
1055cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {number} The number of columns in the table.
1056cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
1057cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1058cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.getW3CColCount_ = function() {
1059cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // See http://www.w3.org/TR/html401/struct/tables.html#h-11.2.4.3
1060cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1061cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var colgroupNodes = cvox.XpathUtil.evalXPath('child::colgroup',
1062cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.activeTable_);
1063cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var colNodes = cvox.XpathUtil.evalXPath('child::col', this.activeTable_);
1064cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1065cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if ((colgroupNodes.length == 0) && (colNodes.length == 0)) {
1066cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var maxcols = 0;
1067cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var outerChildren = cvox.TableUtil.getChildRows(this.activeTable_);
1068cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var i = 0; i < outerChildren.length; i++) {
1069cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var childrenCount = cvox.TableUtil.getChildCells(outerChildren[i]);
1070cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (childrenCount.length > maxcols) {
1071cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        maxcols = childrenCount.length;
1072cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
1073cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
1074cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return maxcols;
1075cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1076cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var sum = 0;
1077cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var i = 0; i < colNodes.length; i++) {
1078cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (colNodes[i].hasAttribute('span')) {
1079cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        sum += colNodes[i].getAttribute('span');
1080cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      } else {
1081cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        sum += 1;
1082cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
1083cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
1084cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (i = 0; i < colgroupNodes.length; i++) {
1085cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var colChildren = cvox.XpathUtil.evalXPath('child::col',
1086cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          colgroupNodes[i]);
1087cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (colChildren.length == 0) {
1088cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (colgroupNodes[i].hasAttribute('span')) {
1089cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          sum += colgroupNodes[i].getAttribute('span');
1090cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        } else {
1091cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          sum += 1;
1092cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
1093cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
1094cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
1095cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1096cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return sum;
1097cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1098cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1099cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the next row in the table. Updates the cell cursor.
1102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
1103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the update has been made.
1105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the end of the table has been reached and the update has not
1106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       happened.
1107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  */
1108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.nextRow = function() {
1109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.currentCellCursor) {
1110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // We have not started moving through the table yet
1111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToRow(0);
1112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToRow(this.currentCellCursor[0] + 1);
1114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the previous row in the table. Updates the cell cursor.
1121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
1122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the update has been made.
1124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the end of the table has been reached and the update has not
1125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       happened.
1126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.prevRow = function() {
1128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.currentCellCursor) {
1129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // We have not started moving through the table yet
1130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToRow(this.rowCount - 1);
1131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToRow(this.currentCellCursor[0] - 1);
1133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the next column in the table. Updates the cell cursor.
1139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
1140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the update has been made.
1142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the end of the table has been reached and the update has not
1143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       happened.
1144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.nextCol = function() {
1146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.currentCellCursor) {
1147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // We have not started moving through the table yet
1148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToCol(0);
1149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToCol(this.currentCellCursor[1] + 1);
1151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the previous column in the table. Updates the cell cursor.
1157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
1158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the update has been made.
1160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the end of the table has been reached and the update has not
1161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       happened.
1162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.prevCol = function() {
1164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.currentCellCursor) {
1165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // We have not started moving through the table yet
1166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToCol(this.shadowColCount_() - 1);
1167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return this.goToCol(this.currentCellCursor[1] - 1);
1169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the row at the specified index in the table. Updates the cell
1175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * cursor.
1176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} index The index of the required row.
1177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the index is valid and the update has been made.
1179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the index is not valid (either less than 0 or greater than
1180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       the number of rows in the table).
1181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.goToRow = function(index) {
1183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.shadowTable_[index] != null) {
1184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (this.currentCellCursor == null) {
1185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // We haven't started moving through the table yet
1186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.currentCellCursor = [index, 0];
1187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
1188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.currentCellCursor = [index, this.currentCellCursor[1]];
1189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
1190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return true;
1191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return false;
1193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the column at the specified index in the table. Updates the cell
1199cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * cursor.
1200cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} index The index of the required column.
1201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1202cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the index is valid and the update has been made.
1203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the index is not valid (either less than 0 or greater than
1204cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       the number of rows in the table).
1205cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.goToCol = function(index) {
1207cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (index < 0 || index >= this.colCount) {
1208cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return false;
1209cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.currentCellCursor == null) {
1211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // We haven't started moving through the table yet
1212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.currentCellCursor = [0, index];
1213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
1214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.currentCellCursor = [this.currentCellCursor[0], index];
1215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return true;
1217cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1218cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1219cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the cell at the specified index <i, j> in the table. Updates the
1222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * cell cursor.
1223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Array.<number>} index The index <i, j> of the required cell.
1224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the index is valid and the update has been made.
1226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the index is not valid (either less than 0, greater than
1227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       the number of rows or columns in the table, or there is no cell
1228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       at that location).
1229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.goToCell = function(index) {
1231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (((index[0] < this.rowCount) && (index[0] >= 0)) &&
1232cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ((index[1] < this.colCount) && (index[1] >= 0))) {
1233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var cell = this.shadowTable_[index[0]][index[1]];
1234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (cell != null) {
1235cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.currentCellCursor = index;
1236cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return true;
1237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
1238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return false;
1240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the cell at the last index in the table. Updates the cell cursor.
1245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the index is valid and the update has been made.
1247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the index is not valid (there is no cell at that location).
1248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.goToLastCell = function() {
1250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var numRows = this.shadowTable_.length;
1251cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (numRows == 0) {
1252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return false;
1253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1254cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var lastRow = this.shadowTable_[numRows - 1];
1255cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var lastIndex = [(numRows - 1), (lastRow.length - 1)];
1256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var cell =
1257cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[lastIndex[0]][lastIndex[1]];
1258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (cell != null) {
1259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.currentCellCursor = lastIndex;
1260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return true;
1261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return false;
1263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the cell at the last index in the current row  of the table. Update
1268cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * the cell cursor.
1269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1270cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the index is valid and the update has been made.
1271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the index is not valid (there is no cell at that location).
1272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.goToRowLastCell = function() {
1274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var currentRow = this.currentCellCursor[0];
1275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var lastIndex = [currentRow, (this.shadowTable_[currentRow].length - 1)];
1276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var cell =
1277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[lastIndex[0]][lastIndex[1]];
1278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (cell != null) {
1279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.currentCellCursor = lastIndex;
1280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return true;
1281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return false;
1283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1284cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1285cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1286cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1287cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Moves to the cell at the last index in the current column  of the table.
1288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Update the cell cursor.
1289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Either:
1290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    1) True if the index is valid and the update has been made.
1291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    2) False if the index is not valid (there is no cell at that location).
1292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.goToColLastCell = function() {
1294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var currentCol = this.getCol();
1295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var lastIndex = [(currentCol.length - 1), this.currentCellCursor[1]];
1296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var cell =
1297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.shadowTable_[lastIndex[0]][lastIndex[1]];
1298cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (cell != null) {
1299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.currentCellCursor = lastIndex;
1300cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return true;
1301cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1302cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return false;
1303cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1304cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1306cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
1307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Resets the table cursors.
1308cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
1309cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
1310cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.TraverseTable.prototype.resetCursor = function() {
1311cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.currentCellCursor = null;
1312cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1313