/*
* Copyright (c) 2013-2017 Mozilla Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package nu.validator.client;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.regex.Pattern;
import nu.validator.htmlparser.sax.XmlSerializer;
import nu.validator.io.SystemIdIOException;
import nu.validator.messages.GnuMessageEmitter;
import nu.validator.messages.JsonMessageEmitter;
import nu.validator.messages.MessageEmitterAdapter;
import nu.validator.messages.TextMessageEmitter;
import nu.validator.messages.XmlMessageEmitter;
import nu.validator.servlet.imagereview.ImageCollector;
import nu.validator.source.SourceCode;
import nu.validator.validation.SimpleDocumentValidator;
import nu.validator.validation.SimpleDocumentValidator.SchemaReadException;
import nu.validator.xml.SystemErrErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
*
* Simple command-line validator for HTML/XHTML files.
*/
public class SimpleCommandLineValidator {
private static Package pkg = SimpleCommandLineValidator.class.getPackage();
private static String version = pkg.getImplementationVersion();
private static SimpleDocumentValidator validator;
private static OutputStream out;
private static Pattern filterPattern;
private static MessageEmitterAdapter errorHandler;
private static boolean verbose;
private static boolean errorsOnly;
private static boolean exitZeroAlways;
private static boolean loadEntities;
private static boolean noLangDetect;
private static boolean noStream;
private static boolean skipNonHTML;
private static boolean forceHTML;
private static boolean asciiQuotes;
private static int lineOffset;
private static enum OutputFormat {
HTML, XHTML, TEXT, XML, JSON, RELAXED, SOAP, UNICORN, GNU
}
private static OutputFormat outputFormat;
private static String schemaUrl;
private static boolean hasSchemaOption;
public static void main(String[] args) throws SAXException, Exception {
out = System.err;
System.setProperty("nu.validator.datatype.warn", "true");
errorsOnly = false;
skipNonHTML = false;
forceHTML = false;
loadEntities = false;
exitZeroAlways = false;
noLangDetect = false;
noStream = false;
lineOffset = 0;
asciiQuotes = false;
verbose = false;
filterPattern = null;
String filterString = "";
String outFormat = null;
schemaUrl = null;
hasSchemaOption = false;
boolean hasFileArgs = false;
boolean readFromStdIn = false;
int fileArgsStart = 0;
if (args.length == 0) {
usage();
System.exit(1);
}
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-")) {
readFromStdIn = true;
break;
} else if (!args[i].startsWith("--")) {
hasFileArgs = true;
fileArgsStart = i;
break;
} else {
if ("--verbose".equals(args[i])) {
verbose = true;
} else if ("--errors-only".equals(args[i])) {
errorsOnly = true;
System.setProperty("nu.validator.datatype.warn", "false");
} else if ("--exit-zero-always".equals(args[i])) {
exitZeroAlways = true;
} else if ("--asciiquotes".equals(args[i])) {
asciiQuotes = true;
} else if ("--filterfile".equals(args[i])) {
File filterFile = new File(args[++i]);
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = //
new BufferedReader(new FileReader(filterFile))) {
String line;
String pipe = "";
while ((line = reader.readLine()) != null) {
if (line.startsWith("#")) {
continue;
}
sb.append(pipe);
sb.append(line);
pipe = "|";
}
if (sb.length() != 0) {
if ("".equals(filterString)) {
filterString = sb.toString();
} else {
filterString += "|" + sb.toString();
}
}
} catch (FileNotFoundException e) {
System.err.println("error: File not found: "
+ filterFile.getPath());
System.exit(1);
} catch (IOException e) {
e.printStackTrace();
}
} else if ("--filterpattern".equals(args[i])) {
if ("".equals(filterString)) {
filterString = args[++i];
} else {
filterString += "|" + args[++i];
}
} else if ("--format".equals(args[i])) {
outFormat = args[++i];
} else if ("--version".equals(args[i])) {
if (version != null) {
System.out.println(version);
} else {
System.out.println("[unknown version]");
}
System.exit(0);
} else if ("--help".equals(args[i])) {
help();
System.exit(0);
} else if ("--skip-non-html".equals(args[i])) {
skipNonHTML = true;
} else if ("--html".equals(args[i])) {
forceHTML = true;
} else if ("--entities".equals(args[i])) {
loadEntities = true;
} else if ("--no-langdetect".equals(args[i])) {
noLangDetect = true;
} else if ("--no-stream".equals(args[i])) {
noStream = true;
} else if ("--schema".equals(args[i])) {
hasSchemaOption = true;
schemaUrl = args[++i];
if (!schemaUrl.startsWith("http:")) {
System.err.println("error: The \"--schema\" option"
+ " requires a URL for a schema.");
System.exit(1);
}
}
}
}
if (!"".equals(filterString)) {
filterPattern = Pattern.compile(filterString);
}
if (schemaUrl == null) {
schemaUrl = "http://s.validator.nu/html5-rdfalite.rnc";
}
if (outFormat == null) {
outputFormat = OutputFormat.GNU;
} else {
if ("text".equals(outFormat)) {
outputFormat = OutputFormat.TEXT;
} else if ("gnu".equals(outFormat)) {
outputFormat = OutputFormat.GNU;
} else if ("xml".equals(outFormat)) {
outputFormat = OutputFormat.XML;
} else if ("json".equals(outFormat)) {
outputFormat = OutputFormat.JSON;
} else {
System.err.printf("Error: Unsupported output format \"%s\"."
+ " Must be \"gnu\", \"xml\", \"json\","
+ " or \"text\".\n", outFormat);
System.exit(1);
}
}
if (readFromStdIn) {
InputSource is = new InputSource(System.in);
if (noLangDetect) {
validator = new SimpleDocumentValidator(true, false, false);
} else {
validator = new SimpleDocumentValidator();
}
setup(schemaUrl);
validator.checkHtmlInputSource(is);
end();
} else if (hasFileArgs) {
if (noLangDetect) {
validator = new SimpleDocumentValidator(true, false, false);
} else {
validator = new SimpleDocumentValidator(true, false, true);
}
setup(schemaUrl);
checkFiles(args, fileArgsStart);
end();
} else {
System.err.printf("\nError: No documents specified.\n");
usage();
System.exit(1);
}
}
private static void setup(String schemaUrl) throws SAXException, Exception {
setErrorHandler();
errorHandler.setHtml(true);
errorHandler.start(null);
try {
validator.setUpMainSchema(schemaUrl, new SystemErrErrorHandler());
} catch (SchemaReadException e) {
System.out.println(e.getMessage() + " Terminating.");
System.exit(1);
} catch (StackOverflowError e) {
System.out.println("StackOverflowError"
+ " while evaluating HTML schema.");
System.out.println("The checker requires a java thread stack size"
+ " of at least 512k.");
System.out.println("Consider invoking java with the -Xss"
+ " option. For example:");
System.out.println("\n java -Xss512k -jar ~/vnu.jar FILE.html");
System.exit(1);
}
validator.setUpValidatorAndParsers(errorHandler, noStream, loadEntities);
}
private static void end() throws SAXException {
errorHandler.end("Document checking completed. No errors found.",
"Document checking completed.", "");
if (errorHandler.getErrors() > 0 || errorHandler.getFatalErrors() > 0) {
System.exit(exitZeroAlways ? 0 : 1);
}
}
private static void checkFiles(String[] args, int fileArgsStart)
throws IOException, Exception, SAXException {
for (int i = fileArgsStart; i < args.length; i++) {
if (args[i].startsWith("http://") || args[i].startsWith("https://")) {
emitFilename(args[i]);
try {
validator.checkHttpURL(args[i], errorHandler);
} catch (IOException e) {
errorHandler.fatalError(new SAXParseException(e.getMessage(),
null, args[i], -1, -1,
new SystemIdIOException(args[i], e.getMessage())));
}
} else {
File file = new File(args[i]);
if (file.isDirectory()) {
recurseDirectory(file);
} else {
checkHtmlFile(file);
}
}
}
}
private static void recurseDirectory(File directory)
throws IOException, Exception {
if (directory.canRead()) {
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
recurseDirectory(file);
} else {
checkHtmlFile(file);
}
}
}
}
private static void checkHtmlFile(File file) throws IOException, Exception {
try {
String path = file.getPath();
if (!file.exists()) {
if (verbose) {
errorHandler.warning(new SAXParseException(
"File not found.", null,
file.toURI().toURL().toString(), -1, -1));
}
return;
} else if (isXhtml(file)) {
emitFilename(path);
if (forceHTML) {
validator.checkHtmlFile(file, true);
} else {
if (!"http://s.validator.nu/xhtml5-rdfalite.rnc".equals(
schemaUrl) && !hasSchemaOption) {
setup("http://s.validator.nu/xhtml5-rdfalite.rnc");
}
validator.checkXmlFile(file);
}
} else if (isHtml(file)) {
emitFilename(path);
if (!"http://s.validator.nu/html5-rdfalite.rnc".equals(
schemaUrl) && !hasSchemaOption) {
setup("http://s.validator.nu/html5-rdfalite.rnc");
}
validator.checkHtmlFile(file, true);
} else {
if (verbose) {
errorHandler.warning(new SAXParseException(
"File was not checked. Files must have .html,"
+ " .xhtml, .htm, or .xht extensions.",
null, file.toURI().toURL().toString(), -1, -1));
}
}
} catch (SAXException e) {
if (!errorsOnly) {
System.err.printf("\"%s\":-1:-1: warning: %s\n",
file.toURI().toURL().toString(), e.getMessage());
}
}
}
private static boolean isXhtml(File file) {
String name = file.getName();
return (name.endsWith(".xhtml") || name.endsWith(".xht"));
}
private static boolean isHtml(File file) {
String name = file.getName();
return (name.endsWith(".html") || name.endsWith(".htm") || !skipNonHTML);
}
private static void emitFilename(String name) {
if (verbose) {
System.out.println(name);
}
}
private static void setErrorHandler() {
SourceCode sourceCode = validator.getSourceCode();
ImageCollector imageCollector = new ImageCollector(sourceCode);
boolean showSource = false;
if (outputFormat == OutputFormat.TEXT) {
errorHandler = new MessageEmitterAdapter(filterPattern, sourceCode,
showSource, imageCollector, lineOffset, true,
new TextMessageEmitter(out, asciiQuotes));
} else if (outputFormat == OutputFormat.GNU) {
errorHandler = new MessageEmitterAdapter(filterPattern, sourceCode,
showSource, imageCollector, lineOffset, true,
new GnuMessageEmitter(out, asciiQuotes));
} else if (outputFormat == OutputFormat.XML) {
errorHandler = new MessageEmitterAdapter(filterPattern, sourceCode,
showSource, imageCollector, lineOffset, true,
new XmlMessageEmitter(new XmlSerializer(out)));
} else if (outputFormat == OutputFormat.JSON) {
String callback = null;
errorHandler = new MessageEmitterAdapter(filterPattern, sourceCode,
showSource, imageCollector, lineOffset, true,
new JsonMessageEmitter(
new nu.validator.json.Serializer(out), callback));
} else {
throw new RuntimeException("Bug. Should be unreachable.");
}
errorHandler.setErrorsOnly(errorsOnly);
}
private static void usage() {
System.out.println("Usage:");
System.out.println("");
System.out.println(" java -jar vnu.jar [--errors-only] [--exit-zero-always]");
System.out.println(" [--asciiquotes] [--no-stream] [--format gnu|xml|json|text]");
System.out.println(" [--filterfile FILENAME] [--filterpattern PATTERN]");
System.out.println(" [--html] [--skip-non-html] [--no-langdetect]");
System.out.println(" [--help] [--verbose] [--version] FILES");
System.out.println("");
System.out.println(" java -cp vnu.jar nu.validator.servlet.Main 8888");
System.out.println("");
System.out.println(" java -cp vnu.jar nu.validator.client.HttpClient FILES");
System.out.println("");
System.out.println("For detailed usage information, use \"java -jar vnu.jar --help\" or see:");
System.out.println("");
System.out.println(" http://validator.github.io/");
System.out.println("");
System.out.println("To read from stdin, use \"-\" as the filename, like this: \"java -jar vnu.jar - \".");
}
private static void help() {
try (InputStream help = SimpleCommandLineValidator.class.getClassLoader().getResourceAsStream(
"nu/validator/localentities/files/cli-help")) {
System.out.println("");
for (int b = help.read(); b != -1; b = help.read()) {
System.out.write(b);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}