blob: 5ac272fbba6a7b4ebbd2e3f34f99ae2fbd2d21e8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.html.validation;
import java.net.URL;
import java.util.*;
import java.util.Iterator;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.modules.html.editor.lib.api.HtmlVersion;
import org.netbeans.modules.html.editor.lib.api.ProblemDescription;
import org.netbeans.modules.html.editor.lib.api.elements.Element;
import org.netbeans.modules.html.editor.lib.api.elements.ElementType;
import org.netbeans.modules.html.editor.lib.api.elements.OpenTag;
import org.netbeans.modules.html.editor.lib.api.validation.ValidationContext;
import org.netbeans.modules.html.editor.lib.api.validation.ValidationException;
import org.netbeans.modules.html.editor.lib.api.validation.ValidationResult;
import org.netbeans.modules.html.editor.lib.api.validation.Validator;
import org.netbeans.modules.web.common.api.LexerUtils;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.util.Exceptions;
import org.openide.util.lookup.ServiceProvider;
import org.xml.sax.SAXException;
/**
*
* @author marekfukala
*/
@ServiceProvider(service=Validator.class, position=10)
public class ValidatorImpl implements Validator {
private static final Pattern TEMPLATING_MARKS_PATTERN = Pattern.compile("@@@"); //NOI18N
private static final String TEMPLATING_MARKS_MASK = " "; //NOI18N
@Override
public ValidationResult validate(ValidationContext context) throws ValidationException {
assert canValidate(context.getVersion());
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
NbValidationTransaction validatorTransaction =
NbValidationTransaction.create(context.getVersion()); //NOI18N
// //simulate the "in body" mode if the code is a fragment
// if(context.getSyntaxAnalyzerResult().getDetectedHtmlVersion() == null) {
// validatorTransaction.setBodyFragmentContextMode(true);
// }
FileObject file = context.getFile();
URL sourceFileURL = file != null ? URLMapper.findURL(file, URLMapper.EXTERNAL) : null;
Set<String> filteredNamespaces = Collections.emptySet();
if(context.isFeatureEnabled("filter.foreign.namespaces")) { //NOI18N
filteredNamespaces = context.getSyntaxAnalyzerResult().getAllDeclaredNamespaces().keySet();
filteredNamespaces.remove("http://www.w3.org/1999/xhtml"); //NOI18N
}
String encoding = file != null ? FileEncodingQuery.getEncoding(file).name() : "UTF-8"; //NOI18N
// StringBuilder content = new StringBuilder();
// try {
// Reader sourceReader = context.getSourceReader();
// char[] b = new char[1024];
// int read;
// while((read = sourceReader.read(b)) > 0) {
// content.append(new String(b, 0, read));
// }
// sourceReader.reset();
// } catch (IOException ex) {
// Exceptions.printStackTrace(ex);
// }
// System.out.println("----------------------------------------");
// System.out.println(content.toString());
// System.out.println("----------------------------------------");
//
// validatorTransaction.validateCode(new StringReader(content.toString()),
validatorTransaction.validateCode(context.getSourceReader(),
sourceFileURL != null ? sourceFileURL.toExternalForm() : null,
filteredNamespaces,
encoding);
Collection<ProblemDescription> problems = new LinkedList<>(validatorTransaction.getFoundProblems(ProblemDescription.WARNING));
if(context.getSyntaxAnalyzerResult().getDetectedHtmlVersion() == null) {
//1. unknown doctype, the HtmlSourceVersionQuery is used
//some of the "missing doctype" errors should be suppressed
//2. the code might be just a fragment of code which usually belongs to the body of the
//complete document. In such case the Error: Required children missing from element "head"
//should be filtered as well
filterCodeFragmentProblems(context, problems);
}
return new ValidationResult(this, context, problems, problems.isEmpty());
} catch (SAXException ex) {
throw new ValidationException(ex);
} finally {
//ensure the thread's context classloader hasn't been changed during the validator code execution
if(Thread.currentThread().getContextClassLoader() != contextClassLoader) {
Logger.getAnonymousLogger().info("Thread's context ClassLoader has been changed during the validation.nu code execution! See issue 195626 for more info"); //NOI18N
//let's recover
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
}
@Override
public String getValidatorName() {
return "validator.nu"; //NOI18N
}
@Override
public boolean canValidate(HtmlVersion version) {
switch(version) {
case HTML32:
case HTML40_FRAMESET:
case HTML40_STRICT:
case HTML40_TRANSATIONAL:
case HTML41_FRAMESET:
case HTML41_STRICT:
case HTML41_TRANSATIONAL:
case XHTML10_FRAMESET:
case XHTML10_TRANSATIONAL:
case XHTML10_STICT:
case HTML5:
case XHTML5:
return true;
default:
return false;
}
}
private void filterCodeFragmentProblems(ValidationContext context, Collection<ProblemDescription> problems) {
for(Iterator<ProblemDescription> itr = problems.iterator(); itr.hasNext();) {
ProblemDescription problem = itr.next();
if(problem.getText().startsWith("Error: Start tag seen without seeing a doctype first.")
|| (problem.getText().startsWith("Error: Element \"head\" is missing a required instance of child element") && !containsHeadElement(context)) ) {
itr.remove();
}
}
}
private boolean containsHeadElement(ValidationContext context) {
Iterator<Element> head = context.getSyntaxAnalyzerResult().getElementsIterator();
//limit the search to the beginning of the file
int limit = 20;
while(head.hasNext()) {
Element se = head.next();
if(limit-- == 0) {
break;
}
if(se.type() == ElementType.OPEN_TAG) {
OpenTag ot = (OpenTag)se;
if(LexerUtils.equals("head", ot.unqualifiedName(), true, true)) { //NOI18N
return true;
}
}
}
return false;
}
static String maskTemplatingMarks(String code) {
return TEMPLATING_MARKS_PATTERN.matcher(code).replaceAll(TEMPLATING_MARKS_MASK);
}
}