// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved.
// Released under the terms of the CPL Common Public License version 1.0.
package fitnesse.slimTables;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.htmlparser.Node;
import org.htmlparser.Tag;
import org.htmlparser.nodes.TextNode;
import org.htmlparser.tags.CompositeTag;
import org.htmlparser.tags.TableColumn;
import org.htmlparser.tags.TableHeader;
import org.htmlparser.tags.TableRow;
import org.htmlparser.tags.TableTag;
import org.htmlparser.util.NodeList;
import fitnesse.wikitext.Utils;
public class HtmlTable implements Table {
private static final Random RANDOM_GENERATOR = new SecureRandom();
private static Pattern coloredCellPattern = Pattern.compile("<span class=\"(\\w*)\">(.*)(</span>)");
private List<Row> rows = new ArrayList<Row>();
private TableTag tableNode;
public HtmlTable(TableTag tableNode) {
this.tableNode = tableNode;
NodeList nodeList = tableNode.getChildren();
for (int i = 0; i < nodeList.size(); i++) {
Node node = nodeList.elementAt(i);
if (node instanceof TableRow || node instanceof TableHeader) {
rows.add(new Row((CompositeTag) node));
}
}
}
public TableTag getTableNode() {
return tableNode;
}
public void setName(String tableName) {
tableNode.setAttribute("table_name", tableName);
}
public String getCellContents(int columnIndex, int rowIndex) {
return rows.get(rowIndex).getColumn(columnIndex).getContent();
}
public String getCellResult(int col, int row) {
return rows.get(row).getColumn(col).getResult();
}
public String getUnescapedCellContents(int col, int row) {
return Utils.unescapeHTML(getCellContents(col, row));
}
public void appendToCell(int col, int row, String message) {
Cell cell = rows.get(row).getColumn(col);
cell.setContent(cell.getEscapedContent() + message);
}
public int getRowCount() {
return rows.size();
}
public int getColumnCountInRow(int rowIndex) {
return rows.get(rowIndex).getColumnCount();
}
public void setCell(int col, int row, String contents) {
rows.get(row).getColumn(col).setContent(contents);
}
public List<List<String>> asList() {
List<List<String>> list = new ArrayList<List<String>>();
for (Row row : rows)
list.add(row.asList());
return list;
}
public String toString() {
return asList().toString();
}
public String toHtml() {
return tableNode.toHtml();
}
public int addRow(List<String> list) throws Exception {
Row row = new Row();
rows.add(row);
tableNode.getChildren().add(row.getRowNode());
for (String s : list)
row.appendCell(s == null ? "" : s);
return rows.size() - 1;
}
public void appendCellToRow(int rowIndex, String contents) throws Exception {
Row row = rows.get(rowIndex);
row.appendCell(contents);
}
/**
* Scenario tables (mainly) are added on the next row. A bit of javascript allows for collapsing and
* expanding.
*
* @see fitnesse.slimTables.Table#appendChildTable(int, fitnesse.slimTables.Table)
*/
public void appendChildTable(int rowIndex, Table childTable) {
Row row = rows.get(rowIndex);
row.rowNode.setAttribute("class", "scenario closed", '"');
Row childRow = new Row();
TableColumn column = (TableColumn) newTag(TableColumn.class);
column.setChildren(new NodeList(((HtmlTable) childTable).getTableNode()));
column.setAttribute("colspan", "" + colspan(row), '"');
childRow.appendCell(new Cell(column));
childRow.rowNode.setAttribute("class", "scenario-detail", '"');
insertRowAfter(row, childRow);
}
private int colspan(Row row) {
NodeList rowNodes = row.rowNode.getChildren();
int colspan = 0;
for (int i = 0; i < rowNodes.size(); i++) {
if (rowNodes.elementAt(i) instanceof TableColumn) {
String s = ((TableColumn)rowNodes.elementAt(i)).getAttribute("colspan");
if (s != null) {
colspan += Integer.parseInt(s);
} else {
colspan++;
}
}
}
return colspan;
}
// It's a bit of work to insert a node with the htmlparser module.
private void insertRowAfter(Row existingRow, Row childRow) {
NodeList rowNodes = tableNode.getChildren();
int index = rowNodes.indexOf(existingRow.rowNode);
Stack<Node> tempStack = new Stack<Node>();
while (rowNodes.size() - 1 > index) {
tempStack.push(rowNodes.elementAt(tableNode.getChildren().size() - 1));
rowNodes.remove(rowNodes.size() - 1);
}
rowNodes.add(childRow.rowNode);
while (tempStack.size() > 0) {
rowNodes.add(tempStack.pop());
}
}
public void setTestStatusOnRow(int rowIndex, boolean testStatus) {
Row row = rows.get(rowIndex);
row.setTestStatus(testStatus);
}
public String error(String s) {
return String.format("<span class=\"error\">%s</span>", s);
}
public String pass(String s) {
return String.format("<span class=\"pass\">%s</span>", s);
}
public String fail(String s) {
return String.format("<span class=\"fail\">%s</span>", s);
}
public String ignore(String s) {
return String.format("<span class=\"ignore\">%s</span>", s);
}
private Tag newTag(Class<? extends Tag> klass) {
Tag tag = null;
try {
tag = klass.newInstance();
tag.setTagName(tag.getTagName().toLowerCase());
Tag endTag = klass.newInstance();
endTag.setTagName("/" + tag.getTagName().toLowerCase());
endTag.setParent(tag);
tag.setEndTag(endTag);
} catch (Exception e) {
e.printStackTrace();
}
return tag;
}
// This terrible algorithm is an example of either my hatred, or my ignorance, of regular expressions.
public static String colorize(String content) {
while (true) {
int firstMatchEnd = content.indexOf("</span>");
if (firstMatchEnd != -1) {
firstMatchEnd += "</span>".length();
Matcher matcher = coloredCellPattern.matcher(content);
matcher.region(0, firstMatchEnd);
if (matcher.find()) {
String color = matcher.group(1);
String coloredString = matcher.group(2);
content = content.replace(matcher.group(), String.format("%s(%s)", color, coloredString));
} else
break;
} else {
break;
}
}
return content;
}
class Row {
private List<Cell> cells = new ArrayList<Cell>();
private CompositeTag rowNode;
public Row(CompositeTag rowNode) {
this.rowNode = rowNode;
NodeList nodeList = rowNode.getChildren();
for (int i = 0; i < nodeList.size(); i++) {
Node node = nodeList.elementAt(i);
if (node instanceof TableColumn)
cells.add(new Cell((TableColumn) node));
}
}
public Row() {
rowNode = (TableRow) newTag(TableRow.class);
rowNode.setChildren(new NodeList());
Tag endNode = new TableRow();
endNode.setTagName("/" + rowNode.getTagName().toLowerCase());
rowNode.setEndTag(endNode);
}
public int getColumnCount() {
return cells.size();
}
public Cell getColumn(int columnIndex) {
return cells.get(columnIndex);
}
public void appendCell(String contents) {
Cell newCell = new Cell(contents);
appendCell(newCell);
}
private void appendCell(Cell newCell) {
rowNode.getChildren().add(newCell.getColumnNode());
cells.add(newCell);
}
public CompositeTag getRowNode() {
return rowNode;
}
public List<String> asList() {
List<String> list = new ArrayList<String>();
for (Cell cell : cells) {
list.add(colorize(cell.getContent()));
}
return list;
}
public void setTestStatus(boolean testStatus) {
NodeList cells = rowNode.getChildren();
for (int i = 0; i < cells.size(); i++) {
Node cell = cells.elementAt(i);
if (cell instanceof Tag) {
Tag tag = (Tag) cell;
tag.setAttribute("class", testStatus ? "\"pass\"" : "\"fail\"");
}
}
}
private Tag findById(Node node, String id) {
if (hasId(node, id))
return (Tag) node;
return findChildMatchingId(node, id);
}
private Tag findChildMatchingId(Node node, String id) {
NodeList children = node.getChildren();
if (children != null) {
for (int i = 0; i < children.size(); i++) {
Node child = children.elementAt(i);
Tag found = findById(child, id);
if (found != null)
return found;
}
}
return null;
}
private boolean hasId(Node node, String id) {
if (node instanceof Tag) {
Tag t = (Tag) node;
if (id.equals(t.getAttribute("id")))
return true;
}
return false;
}
}
class Cell {
private TableColumn columnNode;
public Cell(TableColumn tableColumn) {
columnNode = tableColumn;
}
public Cell(String contents) {
if (contents == null)
contents = "";
TextNode text = new TextNode(contents);
text.setChildren(new NodeList());
columnNode = (TableColumn) newTag(TableColumn.class);
columnNode.setChildren(new NodeList(text));
}
public Cell(Node node) {
columnNode = (TableColumn) newTag(TableColumn.class);
columnNode.setChildren(new NodeList(node));
}
public String getResult() {
String result = columnNode.getAttribute("class");
if (result == null) {
Node child = columnNode.getFirstChild();
if (child != null)
return child.getText();
} else if (result.equals("pass") || result.equals("fail") || result.equals("error") || result.equals("ignore")) {
return result;
}
return "Unkown Result";
}
public String getContent() {
return getEscapedContent();
}
public String getEscapedContent() {
String unescaped = columnNode.getChildrenHTML();
//Some browsers need inside an empty table cell, so we remove it here.
return " ".equals(unescaped) ? "" : unescaped;
}
public void setContent(String s) {
TextNode textNode = new TextNode(s);
NodeList nodeList = new NodeList(textNode);
columnNode.setChildren(nodeList);
}
public TableColumn getColumnNode() {
return columnNode;
}
}
}