Created
September 5, 2013 11:21
-
-
Save Crydust/6448900 to your computer and use it in GitHub Desktop.
ValidatorFilter uses local validator.nu to validate html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package be.crydust.validator; | |
import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4; | |
import java.io.CharArrayWriter; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.io.PrintWriter; | |
import java.net.URL; | |
import java.net.URLConnection; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.List; | |
import javax.servlet.Filter; | |
import javax.servlet.FilterChain; | |
import javax.servlet.FilterConfig; | |
import javax.servlet.ServletException; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.ServletResponse; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import javax.servlet.http.HttpServletResponseWrapper; | |
import javax.xml.XMLConstants; | |
import javax.xml.namespace.NamespaceContext; | |
import javax.xml.parsers.DocumentBuilder; | |
import javax.xml.parsers.DocumentBuilderFactory; | |
import javax.xml.parsers.ParserConfigurationException; | |
import javax.xml.xpath.XPath; | |
import javax.xml.xpath.XPathConstants; | |
import javax.xml.xpath.XPathExpression; | |
import javax.xml.xpath.XPathExpressionException; | |
import javax.xml.xpath.XPathFactory; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.NamedNodeMap; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.NodeList; | |
import org.xml.sax.SAXException; | |
public final class ValidatorFilter implements Filter { | |
String serviceUrl = "http://validator.nu/"; | |
List<String> ignoreRequestIfUriContains = Collections.emptyList(); | |
List<String> ignoreRequestIfUriEndsWith = Collections.emptyList(); | |
List<String> ignoredMessages = Collections.emptyList(); | |
public void init(FilterConfig filterConfig) throws ServletException { | |
System.out.println("*****************************************************"); | |
System.out.println("ValidatorFilter"); | |
System.out.println(""); | |
if (!isNullOrEmpty(filterConfig.getInitParameter("serviceUrl"))) { | |
serviceUrl = filterConfig.getInitParameter("serviceUrl"); | |
} | |
if (!isNullOrEmpty(filterConfig | |
.getInitParameter("ignoreRequestIfUriContains"))) { | |
ignoreRequestIfUriContains = Arrays.asList(filterConfig | |
.getInitParameter("ignoreRequestIfUriContains").split(";")); | |
} | |
if (!isNullOrEmpty(filterConfig | |
.getInitParameter("ignoreRequestIfUriEndsWith"))) { | |
ignoreRequestIfUriEndsWith = Arrays.asList(filterConfig | |
.getInitParameter("ignoreRequestIfUriEndsWith").split(";")); | |
} | |
if (!isNullOrEmpty(filterConfig.getInitParameter("ignoredMessages"))) { | |
ignoredMessages = Arrays.asList(filterConfig.getInitParameter( | |
"ignoredMessages").split(";")); | |
} | |
System.out.printf("serviceUrl = %s%n", serviceUrl); | |
System.out.printf("ignoreRequestIfUriContains = %n* %s%n", | |
join("\n* ", ignoreRequestIfUriContains)); | |
System.out.printf("ignoreRequestIfUriEndsWith = %n* %s%n", | |
join("\n* ", ignoreRequestIfUriEndsWith)); | |
System.out.printf("ignoredMessages = %n* %s%n", | |
join("\n* ", ignoredMessages)); | |
System.out.println("*****************************************************"); | |
} | |
public void doFilter(ServletRequest request, ServletResponse response, | |
FilterChain chain) throws IOException, ServletException { | |
HttpServletRequest req = (HttpServletRequest) request; | |
HttpServletResponse res = (HttpServletResponse) response; | |
String requestUri = req.getRequestURI(); | |
boolean ignoreRequest = false; | |
for (String s : ignoreRequestIfUriContains) { | |
if (!s.isEmpty() && requestUri.contains(s)) { | |
ignoreRequest = true; | |
} | |
} | |
for (String s : ignoreRequestIfUriEndsWith) { | |
if (!s.isEmpty() && requestUri.endsWith(s)) { | |
ignoreRequest = true; | |
} | |
} | |
if (ignoreRequest) { | |
chain.doFilter(req, res); | |
return; | |
} | |
ResponseBuffer buffer = new ResponseBuffer(res); | |
chain.doFilter(request, buffer); | |
String contents = buffer.toString(); | |
// validate | |
// see http://wiki.whatwg.org/wiki/Validator.nu_Web_Service_Interface | |
StringBuilder html = new StringBuilder(); | |
try { | |
// send page sourcecode to service | |
String url = serviceUrl + "?out=xml&showsource=yes"; | |
URLConnection connection = new URL(url).openConnection(); | |
connection.setDoOutput(true); | |
connection.setRequestProperty("Accept-Charset", "UTF-8"); | |
connection.setRequestProperty("Content-Type", "text/html"); | |
try (OutputStream output = connection.getOutputStream();) { | |
output.write(contents.getBytes("UTF-8")); | |
} | |
// read the reply from the web service | |
final XPathFactory factory = XPathFactory.newInstance(); | |
NamespaceContext ns = new ValidatorNamespaceContext(); | |
Document document = readXmlDocument(connection.getInputStream()); | |
// find relevant xml nodes | |
NodeList infoNodes = getNodesByXPath(factory, ns, document, | |
"/:messages/:info"); | |
NodeList errorNodes = getNodesByXPath(factory, ns, document, | |
"/:messages/:error"); | |
NodeList nonDocumentErrorNodes = getNodesByXPath(factory, ns, | |
document, "/:messages/:non-document-error"); | |
NodeList infoOrErrorNodes = getNodesByXPath(factory, ns, document, | |
"/:messages/:info|/:messages/:error"); | |
String source = getNodesByXPath(factory, ns, document, | |
"/:messages/:source/text()").item(0).getNodeValue(); | |
// the little box top left of the page with onclick | |
// remember where to insert the "bad errors count" | |
html.append("<div") | |
.append(" onclick='(function(){var s = document.getElementById(\"validationFilterMessages\").style; s.visibility = (s.visibility === \"hidden\" ? \"visible\" : \"hidden\");}());'") | |
.append("style='z-index:1000;position:absolute;top:0px;left:0;font-size:12px;color:#000;background:#ccc;width:200px;height:40px;overflow:hidden;'>") | |
.append("<b>bad errors: "); | |
int badErrorsInsertionPoint = html.length(); | |
html.append( | |
String.format( | |
"</b><br /><small>error: %d, info: %d, other: %d</small>", | |
errorNodes.getLength(), infoNodes.getLength(), | |
nonDocumentErrorNodes.getLength())) | |
.append("</div>"); | |
// the big box with messages (hidden for now) | |
// begin #validationFilterMessages | |
html.append( | |
"<div id='validationFilterMessages' style='visibility:hidden;:z-index:1000;position:absolute;top:40px;left:0;font-size:16px;color:#000;background:#fff;width:1024px;height:600px;overflow:scroll;'>") | |
.append("<ol>"); | |
// loop over messages and counting the non-ignored ones | |
int badErrors = 0; | |
for (int i = 0, leni = infoOrErrorNodes.getLength(); i < leni; i++) { | |
Node infoOrErrorNode = infoOrErrorNodes.item(i); | |
boolean isError = "error".equals(infoOrErrorNode.getNodeName()); | |
Node messageNode = getNodesByXPath(factory, ns, | |
infoOrErrorNode, "./:message").item(0); | |
Node extractNode = null; | |
if (isError) { | |
extractNode = getNodesByXPath(factory, ns, infoOrErrorNode, | |
"./:extract").item(0); | |
} | |
String messageText = messageNode.getTextContent(); | |
// line and column number (-1 for the ones not given) | |
int firstLine = Integer.parseInt(getAttributeOrDefault( | |
infoOrErrorNode, "first-line", "-1")); | |
int firstColumn = Integer.parseInt(getAttributeOrDefault( | |
infoOrErrorNode, "first-column", "-1")); | |
int lastLine = Integer.parseInt(getAttributeOrDefault( | |
infoOrErrorNode, "last-line", "-1")); | |
int lastColumn = Integer.parseInt(getAttributeOrDefault( | |
infoOrErrorNode, "last-column", "-1")); | |
boolean isIgnored = ignoredMessages.contains(messageText); | |
if (isError && !isIgnored) { | |
badErrors++; | |
} | |
String backgroundColor = isIgnored ? "#CCC" : (isError ? "#FCC" | |
: "#CFF"); | |
String color = isIgnored ? "#999" : "#000"; | |
html.append("<li style='").append("color:").append(color) | |
.append(";background-color:"); | |
// message | |
html.append(backgroundColor).append(";'><b>") | |
.append(isError ? "Error" : "Info").append(": </b>") | |
.append(escapeHtml4(messageText)); | |
// link to line in sourcecode | |
if (lastLine != -1 && !isIgnored) { | |
html.append("<br />"); | |
if (firstLine != -1) { | |
html.append(String | |
.format("<a href='#sourceLine%d'><small>From line %d, column %d; to line %d, column %d</small></a>", | |
firstLine, firstLine, firstColumn, | |
lastLine, lastColumn)); | |
} else { | |
html.append(String | |
.format("<a href='#sourceLine%d'><small>At line %d, column %d</small></a>", | |
lastLine, lastLine, lastColumn)); | |
} | |
} | |
// extract | |
if (isError && !isIgnored && extractNode != null) { | |
html.append("<br /><pre style='margin:0;'>"); | |
NodeList extractChildNodes = extractNode.getChildNodes(); | |
for (int j = 0, lenj = extractChildNodes.getLength(); j < lenj; j++) { | |
Node extractChildNode = extractChildNodes.item(j); | |
if ("#text".equals(extractChildNode.getNodeName())) { | |
html.append(escapeHtml4(extractChildNode | |
.getNodeValue().replaceAll("\r?\n", | |
"\u21a9"))); | |
} else { | |
html.append("<b style='background:#f66;'>") | |
.append(escapeHtml4(extractChildNode | |
.getFirstChild().getNodeValue() | |
.replaceAll("\r?\n", "\u21a9"))) | |
.append("</b>"); | |
} | |
} | |
html.append("</pre>"); | |
} | |
html.append("</li>"); | |
} | |
html.append("</ol>"); | |
// the non-ignored erros are now counted | |
html.insert(badErrorsInsertionPoint, badErrors); | |
// write sourcecode line by line with anchors | |
String[] sourceLines = source.split("\r?\n"); | |
html.append("<ol>"); | |
int lineNumber = 1; | |
for (String sourceLine : sourceLines) { | |
html.append("<li id='sourceLine").append(lineNumber) | |
.append("'><pre style='margin:0;'>") | |
.append(escapeHtml4(sourceLine)).append("</pre>"); | |
lineNumber++; | |
} | |
html.append("</ol>"); | |
// end #validationFilterMessages | |
html.append("</div>"); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
try { | |
PrintWriter printWriter = res.getWriter(); | |
printWriter.write(contents); | |
printWriter.write(html.toString()); | |
printWriter.close(); | |
} catch (IllegalStateException ex) { | |
OutputStream outputStream = res.getOutputStream(); | |
outputStream.write(contents.getBytes("UTF-8")); | |
outputStream.write(html.toString().getBytes("UTF-8")); | |
outputStream.close(); | |
} | |
} | |
public void destroy() { | |
// NOOP | |
} | |
private static class ValidatorNamespaceContext implements NamespaceContext { | |
public String getPrefix(String namespaceURI) { | |
return null; | |
} | |
@SuppressWarnings("rawtypes") | |
public Iterator getPrefixes(String namespaceURI) { | |
return null; | |
} | |
public String getNamespaceURI(String prefix) { | |
if (prefix == null) | |
throw new NullPointerException("Invalid Namespace Prefix"); | |
else if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) | |
return "http://n.validator.nu/messages/"; | |
else if ("h".equals(prefix)) | |
return "http://www.w3.org/1999/xhtml"; | |
else | |
return XMLConstants.NULL_NS_URI; | |
} | |
} | |
private static NodeList getNodesByXPath(XPathFactory factory, | |
NamespaceContext ns, Object item, String expression) | |
throws XPathExpressionException { | |
XPath xpath = factory.newXPath(); | |
xpath.setNamespaceContext(ns); | |
XPathExpression expr = xpath.compile(expression); | |
Object result = expr.evaluate(item, XPathConstants.NODESET); | |
return (NodeList) result; | |
} | |
private static String getAttributeOrDefault(Node node, String name, | |
String defaultValue) { | |
NamedNodeMap attributes = node.getAttributes(); | |
if (attributes != null) { | |
Node attribute = attributes.getNamedItem(name); | |
if (attribute != null) { | |
return attribute.getNodeValue(); | |
} | |
} | |
return defaultValue; | |
} | |
private static Document readXmlDocument(InputStream is) | |
throws ParserConfigurationException, SAXException, IOException { | |
if (is == null) { | |
return null; | |
} | |
Document document = null; | |
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
factory.setNamespaceAware(true); | |
DocumentBuilder builder = factory.newDocumentBuilder(); | |
document = builder.parse(is); | |
return document; | |
} | |
private static class ResponseBuffer extends HttpServletResponseWrapper { | |
private CharArrayWriter buffer; | |
public String toString() { | |
return buffer.toString(); | |
} | |
public PrintWriter getWriter() { | |
return new PrintWriter(buffer); | |
} | |
public ResponseBuffer(HttpServletResponse response) { | |
super(response); | |
buffer = new CharArrayWriter(); | |
} | |
} | |
private static boolean isNullOrEmpty(String s) { | |
return s == null || s.isEmpty(); | |
} | |
private static String join(String delimiter, List<String> s) { | |
StringBuilder builder = new StringBuilder(); | |
Iterator<?> iter = s.iterator(); | |
while (iter.hasNext()) { | |
builder.append(iter.next()); | |
if (!iter.hasNext()) { | |
break; | |
} | |
builder.append(delimiter); | |
} | |
return builder.toString(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" | |
version="2.4"> | |
<!-- ... --> | |
<filter> | |
<filter-name>ValidatorFilter</filter-name> | |
<filter-class>be.crydust.validator.ValidatorFilter</filter-class> | |
<init-param> | |
<param-name>serviceUrl</param-name> | |
<param-value>http://192.168.56.101:8888/</param-value> | |
</init-param> | |
<init-param> | |
<param-name>ignoredMessages</param-name> | |
<param-value>The cellspacing attribute on the table element is obsolete. Use CSS instead.;The cellpadding attribute on the table element is obsolete. Use CSS instead.</param-value> | |
</init-param> | |
</filter> | |
<!-- ... --> | |
<filter-mapping> | |
<filter-name>ValidatorFilter</filter-name> | |
<url-pattern>*.jsp</url-pattern> | |
</filter-mapping> | |
<!-- ... --> | |
</web-app> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment