package org.wicketstuff.htmlvalidator; import static org.wicketstuff.htmlvalidator.ValidatorUtils.cssFor; import static org.wicketstuff.htmlvalidator.ValidatorUtils.imgFor; import static org.wicketstuff.htmlvalidator.ValidatorUtils.jsFor; import java.util.ArrayList; import java.util.List; import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.util.lang.Bytes; import org.apache.wicket.util.string.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXParseException; public class ValidationReport implements ErrorHandler { private static final Logger log = LoggerFactory .getLogger(ValidationReport.class); private List<SAXParseException> parseErrors = new ArrayList<SAXParseException>(); private final HtmlValidationConfiguration configuration; private final String doctype; private final String markup; private final String[] lines; private final String page; public ValidationReport(HtmlValidationConfiguration configuration, IRequestablePage page, String markup, DocType doctype) { this.configuration = configuration; if (page != null) this.page = page.getClass().getName(); else this.page = "<unable to determine page>"; this.markup = markup; this.lines = Strings.split(markup, '\n'); this.doctype = doctype.name(); // getIdentifier(); } @Override public void error(SAXParseException exception) { log.error( "Line {} contains error at {}: {}", new Object[] { exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage() }); parseErrors.add(exception); } @Override public void fatalError(SAXParseException exception) { log.error("Line {} contains fatal error at {}: {}", new Object[] { exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage() }); parseErrors.add(exception); } @Override public void warning(SAXParseException exception) { log.warn( "Line {} contains warning at {}: {}", new Object[] { exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage() }); parseErrors.add(exception); } public boolean isShowReportWindow() { for (SAXParseException error : parseErrors) { if (configuration.mustShowWindowForError(error)) return true; } return false; } public boolean isValid() { return parseErrors.isEmpty(); } public String getHeadMarkup() { StringBuilder sb = new StringBuilder(); sb.append("\t" + cssFor("prettify/prettify.css") + "\n"); sb.append("\t" + cssFor("validator.css") + "\n"); sb.append("\t" + jsFor("prettify/prettify.js") + "\n"); sb.append("\t" + "<script>\n"); sb.append("\t\t" + "if(window.addEventListener) {\n"); sb.append("\t\t\twindow.addEventListener('load',function (event) { prettyPrint() },false);\n"); sb.append("\t\t" + "} else {\n"); sb.append("\t\t\twindow.attachEvent('onload',function (event) { prettyPrint() });\n"); sb.append("\t\t" + "}\n"); sb.append("\t" + "</script>\n"); return sb.toString(); } public String getBodyMarkup() { StringBuilder sb = new StringBuilder(); sb.append("<div id=\"validationreportwindow\" class=\"validation-report\"" + (isShowReportWindow() ? "" : " style=\"display:none\"") + ">"); sb.append("<div class=\"validation-header\">\n"); sb.append("<button class=\"close\" onclick=\"document.getElementById('validationreportwindow').style.display='none';\">×</button>\n"); if (isValid()) sb.append("<h3>Generated document is valid " + doctype + "</h3>\n"); else sb.append("<h3>Markup errors found in generated document</h3>\n"); sb.append("</div>\n"); sb.append("<div class=\"validation-content\">\n"); generateSourceView(sb); sb.append("</div>\n"); sb.append("<div class=\"validation-footer\">\n"); generateFooter(sb); sb.append("</div>\n"); sb.append("</div>"); sb.append("<a class=\"validationreportbadge\" href=\"#\" onclick=\"document.getElementById('validationreportwindow').style.display='block';\" title=\"Content is " + (isValid() ? "valid" : "invalid") + " " + doctype + "\">"); sb.append(imgFor("HTML5_Badge_32.png").toString()); sb.append("</a>"); return sb.toString(); } private void generateErrorItem(StringBuilder sb, SAXParseException error) { sb.append("<div class=\"markup-error\">\n"); sb.append("<button class=\"close\" title=\"Remove this message\" onclick=\"this.parentNode.style.display='none'\">×</button>\n"); if (error.getLineNumber() != -1) { sb.append("<a href=\"#LN" + error.getLineNumber() + "\" class=\"position\">"); if (error.getLineNumber() == -1 && error.getColumnNumber() == -1) { sb.append("-"); } else { sb.append(error.getLineNumber()); // sb.append(":"); // sb.append(error.getColumnNumber()); } sb.append("</a> "); } if (error.getMessage().contains(";")) { int pos = error.getLocalizedMessage().indexOf(';'); String msg = error.getLocalizedMessage().substring(0, pos); sb.append(Strings.escapeMarkup(msg)); } else sb.append(Strings.escapeMarkup(error.getLocalizedMessage())); sb.append("</div>\n"); } private void generateSourceView(StringBuilder sb) { sb.append("<table cellpadding='0' cellspacing='0'>"); sb.append("<thead>"); sb.append("<tr>\n"); sb.append("<td colspan=\"2\">\n"); generateHeader(sb); sb.append("</td>\n"); sb.append("</tr>\n"); sb.append("</thead>\n"); sb.append("<tbody>\n"); sb.append("<tr>\n"); sb.append("<td class=\"linenrs\"> </td>\n"); sb.append("<td>\n"); generateErrorList(sb); sb.append("</td>\n"); sb.append("</tr>\n"); sb.append("<tr>"); generateLineNrs(sb); generateLines(sb); sb.append("</tr>\n"); sb.append("</tbody>\n"); sb.append("</table>\n"); } private void generateFooter(StringBuilder sb) { sb.append("<a href=\"#\" onclick=\"this.parentNode.parentNode.style.display='none'\" class=\"btn\">Close</a>\n"); } private void generateHeader(StringBuilder sb) { sb.append("<div class=\"control-group\">\n"); sb.append("<label class=\"control-label\" for=\"validationReportPage\">Validated page:</label>\n"); sb.append("<div class=\"controls\">"); sb.append("<input type=\"text\" class=\"\" readonly id=\"validationReportPage\" value=\"" + page + "\"/>"); sb.append("</div>\n"); sb.append("</div>\n"); sb.append("<div class=\"control-group\">\n"); sb.append("<label class=\"control-label\" for=\"validationReportResult\">Validation result:</label>\n"); sb.append("<div class=\"controls\">"); sb.append("<input type=\"text\" class=\"" + (isValid() ? "validation-success" : "validation-error") + "\" readonly id=\"validationReportResult\" value=\"" + (isValid() ? "Valid" : "Invalid") + " " + doctype + "\"/>"); sb.append("</div>\n"); sb.append("</div>\n"); sb.append("<div class=\"control-group\">\n"); sb.append("<label class=\"control-label\" for=\"validationDocType\">Detected doctype:</label>\n"); sb.append("<div class=\"controls\">"); sb.append("<input type=\"text\" class=\"\" readonly id=\"validationDocType\" value=\"" + doctype + "\"/>"); sb.append("</div>\n"); sb.append("</div>\n"); sb.append("<div class=\"control-group\">\n"); sb.append("<label class=\"control-label\" for=\"validationReportLines\">Lines:</label>\n"); sb.append("<div class=\"controls\">"); sb.append("<input type=\"text\" class=\"\" readonly id=\"validationReportLines\" value=\"" + lines.length + "\"/>"); sb.append("</div>\n"); sb.append("</div>\n"); sb.append("<div class=\"control-group\">\n"); sb.append("<label class=\"control-label\" for=\"validationReportSize\">Size:</label>\n"); sb.append("<div class=\"controls\">"); sb.append("<input type=\"text\" class=\"\" readonly id=\"validationReportSize\" value=\"" + Bytes.bytes(markup.length()).toString() + "\"/>"); sb.append("</div>\n"); sb.append("</div>\n"); } private void generateErrorList(StringBuilder sb) { sb.append("<div class=\"markup-errors\">\n"); for (SAXParseException curError : parseErrors) { generateErrorItem(sb, curError); } sb.append("</div>\n"); } private void generateLineNrs(StringBuilder sb) { sb.append("<td class=\"linenrs\"><pre>"); for (int i = 1; i <= lines.length; i++) { sb.append("<a name=\"LN" + i + "\">"); sb.append(i); sb.append("</a>"); sb.append("\n"); } sb.append("</pre></td>\n"); } private void generateLines(StringBuilder sb) { Tag td = new Tag("td"); Tag pre = new Tag("pre").attr("class", "lines", "prettyprint"); sb.append(td.getOpenTag()); sb.append(pre.getOpenTag()); for (int i = 1; i <= lines.length; i++) { String line = lines[i - 1]; if (hasError(i)) { generateErrorLine(sb, line, i); } else { generateCodeLine(sb, line, i); } } sb.append(pre.getCloseTag()); sb.append(td.getCloseTag()); sb.append("\n"); } private void generateErrorLine(StringBuilder sb, String line, int i) { SAXParseException error = getError(i); Tag code = new Tag("code"); if (hasError(i)) { code.attr("class", "error"); code.attr("title", Strings.escapeMarkup(error.getMessage())); } else { code.attr("class", "language-xml"); } if (Strings.isEmpty(line)) code.setBody(" "); else code.setBody(Strings.escapeMarkup(line)); sb.append(code.toString()); } private void generateCodeLine(StringBuilder sb, String line, int i) { Tag code = new Tag("code"); code.attr("class", "language-xml"); if (Strings.isEmpty(line)) code.setBody(" "); else code.setBody(Strings.escapeMarkup(line)); sb.append(code.toString()); } private SAXParseException getError(int i) { for (SAXParseException error : parseErrors) { if (error.getLineNumber() == i) return error; } return null; } private boolean hasError(int i) { for (SAXParseException error : parseErrors) { if (error.getLineNumber() == i) return true; } return false; } }