Geertjan's Blog

  • June 6, 2012

HTML Tidy in NetBeans IDE (Part 2)

Geertjan Wielenga
Product Manager

This is what I was aiming for in the previous blog entry:

What you can see above (especially if you click to enlarge it) is that I have HTML Tidy integrated into the NetBeans analyzer functionality, which is pluggable from 7.2 onwards. Well, if you set an implementation dependency on "Static Analysis Core", since it's not an official API yet.

Also, the scopes of the analyzer functionality are not pluggable. That means you can 'only' set the analyzer's scope to one or more projects, one or more packages, or one or more files. Not one or more folders, which means you can't have a bunch off HTML files in a folder that you access via the Favorites window and then run the analyzer on that folder (or on multiple folders).

Thus, to try out my new code, I had to put some HTML files into a package inside a Java application. Then I chose that package as the scope of the analyzer. Then I ran all the analyzers (i.e., standard NetBeans Java hints, FindBugs, as well as my HTML Tidy extension) on that package. The screenshot above is the result.

Here's all the code for the above, which is a port of the Action code from the previous blog entry into a new Analyzer implementation:

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.text.Document;
import org.netbeans.api.fileinfo.NonRecursiveFolder;
import org.netbeans.modules.analysis.spi.Analyzer;
import org.netbeans.modules.analysis.spi.Analyzer.AnalyzerFactory;
import org.netbeans.modules.analysis.spi.Analyzer.Context;
import org.netbeans.modules.analysis.spi.Analyzer.CustomizerProvider;
import org.netbeans.modules.analysis.spi.Analyzer.WarningDescription;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
import org.netbeans.spi.editor.hints.Severity;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.util.Exceptions;
import org.openide.util.lookup.ServiceProvider;
import org.w3c.tidy.Tidy;
public class TidyAnalyzer implements Analyzer {
    private final Context ctx;
    private TidyAnalyzer(Context cntxt) {
        this.ctx = cntxt;
    public Iterable<? extends ErrorDescription> analyze() {
        List<ErrorDescription> result = new ArrayList<ErrorDescription>();
        for (NonRecursiveFolder sr : ctx.getScope().getFolders()) {
            FileObject folder = sr.getFolder();
            for (FileObject fo : folder.getChildren()) {
                for (ErrorDescription ed : doRunHTMLTidy(fo)) {
                    if (fo.getMIMEType().equals("text/html")) {
        return result;
    private List<ErrorDescription> doRunHTMLTidy(FileObject sr) {
        final List<ErrorDescription> result = new ArrayList<ErrorDescription>();
        Tidy tidy = new Tidy();
        StringWriter stringWriter = new StringWriter();
        PrintWriter errorWriter = new PrintWriter(stringWriter);
        try {
            Document doc = DataObject.find(sr).getLookup().lookup(EditorCookie.class).openDocument();
            tidy.parse(sr.getInputStream(), System.out);
            String[] split = stringWriter.toString().split("\n");
            for (String string : split) {
                //Bit of ugly string parsing coming up:
                if (string.startsWith("line")) {
                    final int end = string.indexOf(" c");
                    int lineNumber = Integer.parseInt(string.substring(0, end).replace("line ", ""));
                    string = string.substring(string.indexOf(": ")).replace(":", "");
        } catch (IOException ex) {
        return result;
    public boolean cancel() {
        return true;
    @ServiceProvider(service = AnalyzerFactory.class)
    public static final class MyAnalyzerFactory extends AnalyzerFactory {
        public MyAnalyzerFactory() {
            super("htmltidy", "HTML Tidy", "org/jtidy/format_misc.gif");
        public Iterable<? extends WarningDescription> getWarnings() {
            return Collections.EMPTY_LIST;
        public <D, C extends JComponent> CustomizerProvider<D, C> getCustomizerProvider() {
            return null;
        public Analyzer createAnalyzer(Context cntxt) {
            return new TidyAnalyzer(cntxt);

The above only works on packages, not on projects and not on individual files.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.