package de.cologneintelligence.fitgoodies.htmlparser; import de.cologneintelligence.fitgoodies.Counts; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Element; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class FitTable { public static final Pattern ARGUMENT_PATTERN = Pattern.compile("^\\s*([^=]+?)\\s*=\\s*(.*?)\\s*$"); private final Element table; private final String TAG = "tr"; private boolean newStyleTable; private final Counts counts = new Counts(); private boolean hasFeedbackColumn; private Map<Integer, State> errorColumnStates = new HashMap<>(); private Map<Integer, String> errorColumnMessages = new HashMap<>(); private String fixtureClass; private Map<String, String> arguments; private List<FitRow> rows = new LinkedList<>(); private int contentStartPosition; public FitTable(Element table) { this.table = table; if (!checkTable()) { finishExecution(); throw new IllegalArgumentException("Table is not valid"); } parseTable(); } private void parseTable() { Map<String, String> args = new HashMap<>(); Elements trs = table.select("tr"); newStyleTable = table.hasAttr(Constants.ATTR_FIXTURE); if (newStyleTable) { parseNewStyleHeader(args); contentStartPosition = 0; } else { parseOldStyleHeader(trs.first(), args); contentStartPosition = 1; } arguments = Collections.unmodifiableMap(args); for (Element tr : trs.subList(contentStartPosition, trs.size())) { if (!ParserUtils.isIgnored(tr)) { rows.add(new FitRow(this, tr)); } } updateIndices(); } private boolean checkTable() { Elements trs = table.select("tr"); if (trs.size() == 0 || trs.select("td").size() == 0) { exception("Incomplete table definition"); return false; } return true; } private void parseOldStyleHeader(Element tr, Map<String, String> args) { final Elements tds = tr.select("td"); fixtureClass = tds.first().text(); List<Element> subList = tds.subList(1, tds.size()); for (int i = 0; i < subList.size(); i++) { Element element = subList.get(i); Matcher matcher = ARGUMENT_PATTERN.matcher(element.text()); if (matcher.find()) { args.put(matcher.group(1).toLowerCase(), matcher.group(2)); args.put(Integer.toString(i), matcher.group(2)); } else { args.put(Integer.toString(i), element.text()); } } } private void parseNewStyleHeader(Map<String, String> args) { fixtureClass = table.attr(Constants.ATTR_FIXTURE); for (Attribute attribute : table.attributes()) { final String attributeName = attribute.getKey().toLowerCase(); if (attributeName.startsWith(Constants.ATTR_ARGUMENT_PREFIX)) { args.put(attributeName.substring(Constants.ATTR_ARGUMENT_PREFIX.length()), attribute.getValue()); } } } public Element getTable() { return table; } public List<FitRow> rows() { return Collections.unmodifiableList(rows); } public String getFixtureClass() { return fixtureClass; } public Map<String, String> getArguments() { return arguments; } public Counts getCounts() { return counts; } public void finishExecution() { processRowErrors(); for (FitRow row : rows) { for (FitCell cell : row.cells()) { cell.finishExecution(counts); } } if (counts.exceptions > 0) { table.addClass(Constants.CSS_EXCEPTION); } else if (counts.wrong > 0) { table.addClass(Constants.CSS_WRONG); } else { table.addClass(Constants.CSS_RIGHT); } } private void processRowErrors() { if (!errorColumnStates.isEmpty()) { addFeedbackColumn(false); for (Map.Entry<Integer, String> entry : errorColumnMessages.entrySet()) { Element td = rows.get(entry.getKey()).getRow().select("td").first(); State state = errorColumnStates.get(entry.getKey()); td.text(entry.getValue()); td.addClass(state.cssClass); if (state == State.EXCEPTION) { counts.exceptions++; } else { counts.wrong++; } } } } public void exception(Throwable t) { exception(ParserUtils.getHtmlStackTrace(t)); } private void exception(String html) { addFeedbackColumn(true); counts.exceptions++; Element firstTd = table.select("tr").first().select("td").first(); firstTd .html(html) .addClass(Constants.CSS_EXCEPTION); } private void addFeedbackColumn(boolean newRow) { if (hasFeedbackColumn) { return; } hasFeedbackColumn = true; if (table.select("tr").size() == 0 || (newStyleTable && newRow)) { table.prependElement("tr"); } for (Element tr : table.select("tr")) { tr.prependElement("td") .addClass(Constants.CSS_FEEDBACK_COLUMN); } } void exceptionRow(int row, String text) { errorColumnMessages.put(row, text); errorColumnStates.put(row, State.EXCEPTION); } void wrongRow(int row, String text) { if (!errorColumnStates.containsKey(row) || !errorColumnStates.get(row).equals(State.EXCEPTION)) { errorColumnMessages.put(row, text); errorColumnStates.put(row, State.WRONG); } } // FIXME: test! public FitRow appendRow() { return insert(rows.size()); } // FIXME: test! public void remove(int index) { table.select(TAG).get(index + contentStartPosition).remove(); rows.remove(index); updateIndices(); } // FIXME: test! public FitRow insert(int index) { Element tr = new Element(Tag.valueOf(TAG), table.baseUri()); FitRow row = new FitRow(this, tr); Element tbody = table.select("tbody").first(); tbody.insertChildren(index + contentStartPosition, Collections.singleton(tr)); rows.add(index, row); updateIndices(); return row; } private void updateIndices() { for (int i = 0; i < rows.size(); i++) { rows.get(i).updateIndex(i); } } public String pretty() { StringBuilder b = new StringBuilder(); for (Element tr : table.select("tr")) { for (Element td : tr.select("td")) { b.append(td.text()).append(" | "); } b.append("\n"); } return b.toString(); } }