org.htmlunit.html.HtmlTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
/*
* Copyright (c) 2002-2024 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.html;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.htmlunit.ElementNotFoundException;
import org.htmlunit.SgmlPage;
/**
* Wrapper for the HTML element "table".
*
* @author Mike Bowler
* @author David K. Taylor
* @author Christian Sell
* @author Ahmed Ashour
* @author Ronald Brill
* @author Frank Danek
*/
public class HtmlTable extends HtmlElement {
/** The HTML tag represented by this element. */
public static final String TAG_NAME = "table";
/**
* Creates an instance.
*
* @param qualifiedName the qualified name of the element type to instantiate
* @param page the page that contains this element
* @param attributes the initial attributes
*/
HtmlTable(final String qualifiedName, final SgmlPage page,
final Map attributes) {
super(qualifiedName, page, attributes);
}
/**
* Returns the first cell that matches the specified row and column, searching left to right, top to bottom.
* This method returns different values than getRow(rowIndex).getCell(cellIndex) because this takes cellspan
* and rowspan into account.
* This means, a cell with colspan='2' consumes two columns; a cell with rowspan='3' consumes three rows. The
* index is based on the 'background' model of the table; if you have a row like
* <td>cell1</td> <td colspan='2'>cell2</td> then this row is treated as a row with
* three cells.
*
*
* getCellAt(rowIndex, 0).asText() returns "cell1";
* getCellAt(rowIndex, 1).asText() returns "cell2";
* getCellAt(rowIndex, 2).asText() returns "cell2"; and
* getCellAt(rowIndex, 3).asText() returns null;
*
*
*
* @param rowIndex the row index
* @param columnIndex the column index
* @return the HtmlTableCell at that location or null if there are no cells at that location
*/
public final HtmlTableCell getCellAt(final int rowIndex, final int columnIndex) {
final RowIterator rowIterator = getRowIterator();
final HashSet occupied = new HashSet<>();
int row = 0;
for (final HtmlTableRow htmlTableRow : rowIterator) {
final HtmlTableRow.CellIterator cellIterator = htmlTableRow.getCellIterator();
int col = 0;
for (final HtmlTableCell cell : cellIterator) {
while (occupied.contains(new Position(row, col))) {
col++;
}
final int nextRow = row + cell.getRowSpan();
if (row <= rowIndex && nextRow > rowIndex) {
final int nextCol = col + cell.getColumnSpan();
if (col <= columnIndex && nextCol > columnIndex) {
return cell;
}
}
if (cell.getRowSpan() > 1 || cell.getColumnSpan() > 1) {
for (int i = 0; i < cell.getRowSpan(); i++) {
for (int j = 0; j < cell.getColumnSpan(); j++) {
occupied.add(new Position(row + i, col + j));
}
}
}
col++;
}
row++;
}
return null;
}
/**
* @return an iterator over all the HtmlTableRow objects
*/
private RowIterator getRowIterator() {
return new RowIterator();
}
/**
* @return an immutable list containing all the HtmlTableRow objects
* @see #getRowIterator
*/
public List getRows() {
final List result = new ArrayList<>();
for (final HtmlTableRow row : getRowIterator()) {
result.add(row);
}
return Collections.unmodifiableList(result);
}
/**
* @param index the 0-based index of the row
* @return the HtmlTableRow at the given index
* @throws IndexOutOfBoundsException if there is no row at the given index
* @see #getRowIterator
*/
public HtmlTableRow getRow(final int index) throws IndexOutOfBoundsException {
int count = 0;
for (final HtmlTableRow row : getRowIterator()) {
if (count == index) {
return row;
}
count++;
}
throw new IndexOutOfBoundsException("No row found for index " + index + ".");
}
/**
* Computes the number of rows in this table. Note that the count is computed dynamically
* by iterating over all rows.
*
* @return the number of rows in this table
*/
public final int getRowCount() {
int count = 0;
for (final RowIterator iterator = getRowIterator(); iterator.hasNext(); iterator.next()) {
count++;
}
return count;
}
/**
* Finds and return the row with the specified id.
*
* @param id the id of the row
* @return the row with the specified id
* @exception ElementNotFoundException If the row cannot be found.
*/
public final HtmlTableRow getRowById(final String id) throws ElementNotFoundException {
for (final HtmlTableRow row : getRowIterator()) {
if (row.getId().equals(id)) {
return row;
}
}
throw new ElementNotFoundException("tr", DomElement.ID_ATTRIBUTE, id);
}
/**
* Returns the table caption text or an empty string if a caption wasn't specified.
*
* @return the caption text
*/
public String getCaptionText() {
for (final DomElement element : getChildElements()) {
if (element instanceof HtmlCaption) {
return element.asNormalizedText();
}
}
return null;
}
/**
* Returns the table header or null if a header wasn't specified.
*
* @return the table header
*/
public HtmlTableHeader getHeader() {
for (final DomElement element : getChildElements()) {
if (element instanceof HtmlTableHeader) {
return (HtmlTableHeader) element;
}
}
return null;
}
/**
* Returns the table footer or null if a footer wasn't specified.
*
* @return the table footer
*/
public HtmlTableFooter getFooter() {
for (final DomElement element : getChildElements()) {
if (element instanceof HtmlTableFooter) {
return (HtmlTableFooter) element;
}
}
return null;
}
/**
* Returns a list of tables bodies defined in this table. If no bodies were defined
* then an empty list will be returned.
*
* @return a list of {@link HtmlTableBody} objects
*/
public List getBodies() {
final List bodies = new ArrayList<>();
for (final DomElement element : getChildElements()) {
if (element instanceof HtmlTableBody) {
bodies.add((HtmlTableBody) element);
}
}
return bodies;
}
/**
* Returns the value of the attribute {@code summary}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code summary}
* or an empty string if that attribute isn't defined.
*/
public final String getSummaryAttribute() {
return getAttributeDirect("summary");
}
/**
* Returns the value of the attribute {@code width}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code width}
* or an empty string if that attribute isn't defined.
*/
public final String getWidthAttribute() {
return getAttributeDirect("width");
}
/**
* Returns the value of the attribute {@code border}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code border}
* or an empty string if that attribute isn't defined.
*/
public final String getBorderAttribute() {
return getAttributeDirect("border");
}
/**
* Returns the value of the attribute {@code frame}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code frame}
* or an empty string if that attribute isn't defined.
*/
public final String getFrameAttribute() {
return getAttributeDirect("frame");
}
/**
* Returns the value of the attribute {@code rules}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code rules}
* or an empty string if that attribute isn't defined.
*/
public final String getRulesAttribute() {
return getAttributeDirect("rules");
}
/**
* Returns the value of the attribute {@code cellspacing}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code cellspacing}
* or an empty string if that attribute isn't defined.
*/
public final String getCellSpacingAttribute() {
return getAttributeDirect("cellspacing");
}
/**
* Returns the value of the attribute {@code cellpadding}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code cellpadding}
* or an empty string if that attribute isn't defined.
*/
public final String getCellPaddingAttribute() {
return getAttributeDirect("cellpadding");
}
/**
* Returns the value of the attribute {@code align}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code align}
* or an empty string if that attribute isn't defined.
*/
public final String getAlignAttribute() {
return getAttributeDirect("align");
}
/**
* Returns the value of the attribute {@code bgcolor}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code bgcolor}
* or an empty string if that attribute isn't defined.
*/
public final String getBgcolorAttribute() {
return getAttributeDirect("bgcolor");
}
/**
* An iterator that moves over all rows in this table. The iterator will also
* enter into nested row group elements (header, footer and body).
*/
private class RowIterator implements Iterator, Iterable {
private HtmlTableRow nextRow_;
private TableRowGroup currentGroup_;
/** Creates a new instance. */
RowIterator() {
setNextRow(getFirstChild());
}
/**
* @return {@code true} if there are more rows available
*/
@Override
public boolean hasNext() {
return nextRow_ != null;
}
/**
* @return the next row from this iterator
* @throws NoSuchElementException if no more rows are available
*/
@Override
public HtmlTableRow next() throws NoSuchElementException {
return nextRow();
}
/**
* Removes the current row from the underlying table.
*/
@Override
public void remove() {
if (nextRow_ == null) {
throw new IllegalStateException();
}
final DomNode sibling = nextRow_.getPreviousSibling();
if (sibling != null) {
sibling.remove();
}
}
/**
* @return the next row from this iterator
* @throws NoSuchElementException if no more rows are available
*/
public HtmlTableRow nextRow() throws NoSuchElementException {
if (nextRow_ != null) {
final HtmlTableRow result = nextRow_;
setNextRow(nextRow_.getNextSibling());
return result;
}
throw new NoSuchElementException();
}
/**
* Sets the internal position to the next row, starting at the given node.
* @param node the node to mark as the next row; if this is not a row, the
* next reachable row will be marked.
*/
private void setNextRow(final DomNode node) {
nextRow_ = null;
for (DomNode next = node; next != null; next = next.getNextSibling()) {
if (next instanceof HtmlTableRow) {
nextRow_ = (HtmlTableRow) next;
return;
}
else if (currentGroup_ == null && next instanceof TableRowGroup) {
currentGroup_ = (TableRowGroup) next;
setNextRow(next.getFirstChild());
return;
}
}
if (currentGroup_ != null) {
final DomNode group = currentGroup_;
currentGroup_ = null;
setNextRow(group.getNextSibling());
}
}
@Override
public Iterator iterator() {
return this;
}
}
/**
* {@inheritDoc}
* @return {@code true} as browsers ignore self closing table
tags.
*/
@Override
protected boolean isEmptyXmlTagExpanded() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public DisplayStyle getDefaultStyleDisplay() {
return DisplayStyle.TABLE;
}
private static final class Position {
private final int posX_;
private final int posY_;
private Position(final int x, final int y) {
posX_ = x;
posY_ = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + posX_;
result = prime * result + posY_;
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Position other = (Position) obj;
if (posX_ != other.posX_) {
return false;
}
if (posY_ != other.posY_) {
return false;
}
return true;
}
}
}