001: package com.meterware.httpunit;
002:
003: /********************************************************************************************************************
004: * $Id: WebTable.java,v 1.31 2006/03/09 01:52:28 russgold Exp $
005: *
006: * Copyright (c) 2000-2005, Russell Gold
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
009: * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
010: * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
011: * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included in all copies or substantial portions
014: * of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
017: * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
019: * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
020: * DEALINGS IN THE SOFTWARE.
021: *
022: *******************************************************************************************************************/
023: import com.meterware.httpunit.scripting.ScriptableDelegate;
024: import org.w3c.dom.Element;
025: import org.w3c.dom.Node;
026:
027: import java.net.URL;
028: import java.util.ArrayList;
029: import java.util.Enumeration;
030: import java.util.Hashtable;
031:
032: /**
033: * This class represents a table in an HTML page.
034: *
035: * @author <a href="mailto:russgold@httpunit.org">Russell Gold</a>
036: * @author <a href="mailto:bx@bigfoot.com">Benoit Xhenseval</a>
037: **/
038: public class WebTable extends HTMLElementBase {
039:
040: /** Predicate to match the complete text of a table's first non-blank cell. **/
041: public final static HTMLElementPredicate MATCH_FIRST_NONBLANK_CELL;
042:
043: /** Predicate to match a prefix of a table's first non-blank cell. **/
044: public final static HTMLElementPredicate MATCH_FIRST_NONBLANK_CELL_PREFIX;
045:
046: /** Predicate to match a table's summary attribute. **/
047: public final static HTMLElementPredicate MATCH_SUMMARY;
048:
049: /** Predicate to match a table's ID. **/
050: public final static HTMLElementPredicate MATCH_ID;
051:
052: /**
053: * Returns the number of rows in the table.
054: **/
055: public int getRowCount() {
056: return getCells().length;
057: }
058:
059: private TableCell[][] getCells() {
060: if (_cells == null)
061: readTable();
062: return _cells;
063:
064: }
065:
066: /**
067: * Returns the number of columns in the table.
068: **/
069: public int getColumnCount() {
070: if (getCells().length == 0)
071: return 0;
072: return getCells()[0].length;
073: }
074:
075: /**
076: * Returns the contents of the specified table cell as text.
077: * The row and column numbers are zero-based.
078: * @throws IndexOutOfBoundsException if the specified cell numbers are not valid
079: **/
080: public String getCellAsText(int row, int column) {
081: TableCell cell = getTableCell(row, column);
082: return (cell == null) ? "" : cell.getText();
083: }
084:
085: /**
086: * Returns the contents of the specified table cell as text.
087: * The row and column numbers are zero-based.
088: * @throws IndexOutOfBoundsException if the specified cell numbers are not valid
089: **/
090: public TableCell getTableCell(int row, int column) {
091: return getCells()[row][column];
092: }
093:
094: /**
095: * Returns the contents of the specified table cell with a given ID
096: * @return TableCell with given ID or null if ID is not found.
097: **/
098: public TableCell getTableCellWithID(String id) {
099: for (int i = 0; i < getRowCount(); i++) {
100: for (int j = 0; j < getColumnCount(); j++) {
101: final TableCell tableCell = getCells()[i][j];
102: if (tableCell != null && tableCell.getID().equals(id))
103: return tableCell;
104: }
105: }
106: return null;
107: }
108:
109: /**
110: * Removes all rows and all columns from this table which have no visible text in them.
111: **/
112: public void purgeEmptyCells() {
113: int numRowsWithText = 0;
114: int numColumnsWithText = 0;
115: boolean rowHasText[] = new boolean[getRowCount()];
116: boolean columnHasText[] = new boolean[getColumnCount()];
117: Hashtable spanningCells = new Hashtable();
118:
119: // look for rows and columns with any text in a non-spanning cell
120: for (int i = 0; i < rowHasText.length; i++) {
121: for (int j = 0; j < columnHasText.length; j++) {
122: if (getCellAsText(i, j).trim().length() == 0)
123: continue;
124: if (getTableCell(i, j).getColSpan() == 1
125: && getTableCell(i, j).getRowSpan() == 1) {
126: if (!rowHasText[i])
127: numRowsWithText++;
128: if (!columnHasText[j])
129: numColumnsWithText++;
130: rowHasText[i] = columnHasText[j] = true;
131: } else if (!spanningCells
132: .containsKey(getTableCell(i, j))) {
133: spanningCells.put(getTableCell(i, j), new int[] {
134: i, j });
135: }
136: }
137: }
138:
139: // look for requirements to keep spanning cells: special processing is needed if either:
140: // none of its rows already have text, or none of its columns already have text.
141: for (Enumeration e = spanningCells.keys(); e.hasMoreElements();) {
142: TableCell cell = (TableCell) e.nextElement();
143: int coords[] = (int[]) spanningCells.get(cell);
144: boolean neededInRow = true;
145: boolean neededInCol = true;
146: for (int i = coords[0]; neededInRow
147: && (i < rowHasText.length)
148: && (i < coords[0] + cell.getRowSpan()); i++) {
149: neededInRow = !rowHasText[i];
150: }
151: for (int j = coords[1]; neededInCol
152: && (j < columnHasText.length)
153: && (j < coords[1] + cell.getColSpan()); j++) {
154: neededInCol = !columnHasText[j];
155: }
156: if (neededInRow) {
157: rowHasText[coords[0]] = true;
158: numRowsWithText++;
159: }
160: if (neededInCol) {
161: columnHasText[coords[1]] = true;
162: numColumnsWithText++;
163: }
164: }
165:
166: TableCell[][] remainingCells = new TableCell[numRowsWithText][numColumnsWithText];
167:
168: int targetRow = 0;
169: for (int i = 0; i < rowHasText.length; i++) {
170: if (!rowHasText[i])
171: continue;
172: int targetColumn = 0;
173: for (int j = 0; j < columnHasText.length; j++) {
174: if (!columnHasText[j])
175: continue;
176: remainingCells[targetRow][targetColumn++] = _cells[i][j];
177: }
178: targetRow++;
179: }
180:
181: _cells = remainingCells;
182:
183: }
184:
185: /**
186: * Returns a rendering of this table with all cells converted to text.
187: **/
188: public String[][] asText() {
189: String[][] result = new String[getRowCount()][getColumnCount()];
190:
191: for (int i = 0; i < result.length; i++) {
192: for (int j = 0; j < result[0].length; j++) {
193: result[i][j] = getCellAsText(i, j);
194: }
195: }
196: return result;
197: }
198:
199: /**
200: * Returns the summary attribute associated with this table.
201: **/
202: public String getSummary() {
203: return NodeUtils.getNodeAttribute(_dom, "summary");
204: }
205:
206: public String toString() {
207: String eol = System.getProperty("line.separator");
208: StringBuffer sb = new StringBuffer(
209: HttpUnitUtils.DEFAULT_TEXT_BUFFER_SIZE).append(
210: "WebTable:").append(eol);
211: for (int i = 0; i < getCells().length; i++) {
212: sb.append("[").append(i).append("]: ");
213: for (int j = 0; j < getCells()[i].length; j++) {
214: sb.append(" [").append(j).append("]=");
215: if (getCells()[i][j] == null) {
216: sb.append("null");
217: } else {
218: sb.append(getCells()[i][j].getText());
219: }
220: }
221: sb.append(eol);
222: }
223: return sb.toString();
224: }
225:
226: protected ScriptableDelegate newScriptable() {
227: return new HTMLElementScriptable(this );
228: }
229:
230: protected ScriptableDelegate getParentDelegate() {
231: return _response.getScriptableObject().getDocument();
232: }
233:
234: //----------------------------------- private members -----------------------------------
235:
236: private Element _dom;
237: private URL _url;
238: private FrameSelector _frameName;
239: private String _baseTarget;
240: private String _characterSet;
241: private WebResponse _response;
242:
243: private TableCell[][] _cells;
244:
245: WebTable(WebResponse response, FrameSelector frame,
246: Node domTreeRoot, URL sourceURL, String baseTarget,
247: String characterSet) {
248: super (domTreeRoot);
249: _response = response;
250: _frameName = frame;
251: _dom = (Element) domTreeRoot;
252: _url = sourceURL;
253: _baseTarget = baseTarget;
254: _characterSet = characterSet;
255: }
256:
257: private void readTable() {
258: TableRow[] rows = getRows();
259: int[] columnsRequired = new int[rows.length];
260:
261: for (int i = 0; i < rows.length; i++) {
262: TableCell[] cells = rows[i].getCells();
263: for (int j = 0; j < cells.length; j++) {
264: int spannedRows = Math.min(columnsRequired.length - i,
265: cells[j].getRowSpan());
266: for (int k = 0; k < spannedRows; k++) {
267: columnsRequired[i + k] += cells[j].getColSpan();
268: }
269: }
270: }
271: int numColumns = 0;
272: for (int i = 0; i < columnsRequired.length; i++) {
273: numColumns = Math.max(numColumns, columnsRequired[i]);
274: }
275:
276: _cells = new TableCell[columnsRequired.length][numColumns];
277:
278: for (int i = 0; i < rows.length; i++) {
279: TableCell[] cells = rows[i].getCells();
280: for (int j = 0; j < cells.length; j++) {
281: int spannedRows = Math.min(columnsRequired.length - i,
282: cells[j].getRowSpan());
283: for (int k = 0; k < spannedRows; k++) {
284: for (int l = 0; l < cells[j].getColSpan(); l++) {
285: placeCell(i + k, j + l, cells[j]);
286: }
287: }
288: }
289: }
290: }
291:
292: private void placeCell(int row, int column, TableCell cell) {
293: while (_cells[row][column] != null)
294: column++;
295: _cells[row][column] = cell;
296: }
297:
298: private ArrayList _rows = new ArrayList();
299:
300: void addRow(TableRow tableRow) {
301: _cells = null;
302: _rows.add(tableRow);
303: }
304:
305: TableRow newTableRow(Element element) {
306: return new TableRow(this , element);
307: }
308:
309: /**
310: * Returns an array of rows for this table.
311: */
312: public TableRow[] getRows() {
313: return (TableRow[]) _rows.toArray(new TableRow[_rows.size()]);
314: }
315:
316: TableCell newTableCell(Element element) {
317: return new TableCell(_response, _frameName, element, _url,
318: _baseTarget, _characterSet);
319: }
320:
321: static {
322: MATCH_FIRST_NONBLANK_CELL = new HTMLElementPredicate() { // XXX find a way to do this w/o purging the table cells
323: public boolean matchesCriteria(Object htmlElement,
324: Object criteria) {
325: WebTable table = ((WebTable) htmlElement);
326: table.purgeEmptyCells();
327: return table.getRowCount() > 0
328: && HttpUnitUtils.matches(table.getCellAsText(0,
329: 0).trim(), (String) criteria);
330: };
331: };
332:
333: MATCH_FIRST_NONBLANK_CELL_PREFIX = new HTMLElementPredicate() { // XXX find a way to do this w/o purging the table cells
334: public boolean matchesCriteria(Object htmlElement,
335: Object criteria) {
336: WebTable table = ((WebTable) htmlElement);
337: table.purgeEmptyCells();
338: return table.getRowCount() > 0
339: && HttpUnitUtils.hasPrefix(table.getCellAsText(
340: 0, 0).toUpperCase().trim(),
341: (String) criteria);
342: };
343: };
344:
345: MATCH_ID = new HTMLElementPredicate() {
346: public boolean matchesCriteria(Object htmlElement,
347: Object criteria) {
348: return HttpUnitUtils.matches(((WebTable) htmlElement)
349: .getID(), (String) criteria);
350: };
351: };
352:
353: MATCH_SUMMARY = new HTMLElementPredicate() {
354: public boolean matchesCriteria(Object htmlElement,
355: Object criteria) {
356: return HttpUnitUtils.matches(((WebTable) htmlElement)
357: .getSummary(), (String) criteria);
358: };
359: };
360:
361: }
362:
363: }
|