devxlogo

Getting Tidy with Eclipse

Getting Tidy with Eclipse

n my last article, “Extending Eclipse with Helpful Views,” I explained how easy it was to create new functionality in Eclipse. This article continues in the same vein by also creating a new view. However, instead of focusing on the mechanics of writing a view, the focus of this article is on integration. The Java world is rich with freely available libraries that provide all sorts of useful functions. Coupling those libraries with Eclipse can provide a very powerful combination indeed.

In this article, I will show how to make use of JTidy with Eclipse to help clean up HTML files automatically. The JTidy site describes the library as follows.

“JTidy is a Java port of HTML Tidy, a HTML syntax checker and pretty printer. Like its non-Java cousin, JTidy can be used as a tool for cleaning up malformed and faulty HTML.”

The combination of JTidy and Eclipse isn’t a new idea and in fact, an open source project named EclipseTidy already exists to provide just that. I would highly recommend checking out that project if you are in need of JTidy’s functionality in Eclipse. The result of this article?an Eclipse plugin?will provide similar functionality in that both plugins can clean up HTML, but it is not the result that matters; it is getting there.

Figure 1. Tidy Up: The split pane view created in this article will allow you to read in an HTML on the left, to execute JTidy against it in the background, and have the result show up on the right. It’s just one example of innovative ideas for creating views in Eclipse.

I’ve used JTidy quite a bit and I’ve often been faced with the same problem: How do you know that the changes JTidy makes to the HTML won’t affect the way it renders? We’d all like to believe that fixing malformed HTML will result in more standardized code that will render more consistently in browsers. However, that is far from the case. Thus, when dealing with existing HTML you need to be concerned not only with reforming your code but in retaining the original look and feel.

If I wanted to compare my starting HTML with the result of JTidy I might use a diff utility, but that would only show me the differences in the HTML; not the differences in how it will render. Sure I could take each of the HTML files and load them in a browser to compare the result, but all that takes time. What I want is a plugin that can do it for me. In order to produce such a plugin I need two things: the ability to execute JTidy against an HTML file and the ability to render the HTML as a browser would.

To begin I’ll show how to execute JTidy against an HTML file. Next, I’ll show how to render HTML inside an Eclipse view. Finally, I’ll put the two together, creating a split-pane view with one side showing how the HTML renders before and the other side showing the code after JTidy is applied (see Figure 1).

Cleaning up HTML with JTidy
Taking existing HTML and executing JTidy against it is a very simple process. I create a FileInputStream to read in the file and a FileOutputStream to write the result of JTidy. With an instance of Tidy, I simply call the parse method with my FileInputStream and FileOutputStream. Finally, I close both streams. Here is the code:

Tidy tidy = new Tidy();BufferedInputStream in = new BufferedInputStream(newFileInputStream("foo.html"));BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("bar.html"));tidy.parse(in, out);in.close();out.close();

The generated output can vary significantly depending on the HTML in your input file. If the HTML is good there will likely be little change. However, if the HTML is bad JTidy might even throw an exception. Either way, pay close attention to the messages JTidy writes for information on what it’s changing and why. You’ll often find that it issues several warnings for things that it considers wrong, but can’t fix automatically.

Rendering HTML with Eclipse
In the past, whenever I wanted to render HTML in a Java application it was a real pain. The built-in HTML renderer wasn’t very good and really didn’t reflect what the HTML might look like in a mainstream browser. With Eclipse this is incredibly easy thanks to SWT. If you aren’t aware of SWT, it is the widget toolkit provided by Eclipse that performs much the same function as Swing. However, unlike Swing, SWT is just a thin Java wrapper on top of native UI widgets. I mention this because SWT’s browser widget follows suit by providing a thin wrapper on top of a native browser. This approach has many obvious benefits not the least of which is that the browser widget will render exactly the same as the native browser.

Making use of the SWT browser to render HTML takes only two lines of code. First, I instantiate an instance of the browser. Then, I ask the browser to render a URL. The code is as follows:

Browser browser = new Browser(parent, SWT.NONE);browser.setUrl("http://www.devx.com");

The constructor for the browser widget takes two parameters. The first parameter is the parent composite in which the browser widget will be contained. The second parameter is any style bits you want to provide.

Splitting It All Together
Now that I have shown how to make use of JTidy and the SWT browser, I can integrate the two into a split-pane view. I start by creating a class named BrowserView, which extends ViewPart. All Eclipse views must extend ViewPart and all parts must implement the createPartControl and setFocus methods. The createPartControl method is where the initial UI of the view is created.

My view is going to consist of several controls. I want a button that when clicked will show the user a dialog box for browsing the file system. This will be used to select the input HTML file. Next I want a button that will execute JTidy against the input and then render the results. I will make use of a SashForm to create the split-pane. Finally, I will use a GridLayout to arrange the various controls. To make it easier I am also going to have BrowserView implement SelectionListener, which will provide callbacks when either of the two buttons is clicked. That means I will also need to implement the widgetSelected and widgetDefaultSelected methods. All of the imports and the shell class are shown below:

import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import org.eclipse.swt.SWT;import org.eclipse.swt.browser.Browser;import org.eclipse.swt.custom.SashForm;import org.eclipse.swt.events.SelectionEvent;import org.eclipse.swt.events.SelectionListener;import org.eclipse.swt.layout.GridData;import org.eclipse.swt.layout.GridLayout;import org.eclipse.swt.widgets.Button;import org.eclipse.swt.widgets.Composite;import org.eclipse.swt.widgets.FileDialog;import org.eclipse.ui.part.ViewPart;import org.w3c.tidy.Tidy;public class BrowserView extends ViewPart implements SelectionListener {     public void createPartControl(Composite parent) {          }     public void setFocus() {          }     public void widgetSelected(SelectionEvent e) {          }     public void widgetDefaultSelected(SelectionEvent e) {                    }}

Next, I will create several fields to hold references to the various objects with which I need to interact. The declarations are as follows:

Tidy tidy = null;Browser left = null;Browser right = null;Button browse = null;Button render = null;String selectedFile = null;

The first field, tidy, will hold a reference to my Tidy instance. The left and right fields will hold references to the Browser widgets for each pane. The browse and render fields provide references to my buttons. And last, the selectedFile is a string that will hold the path to the file selected by the user as input.

Next I create a constructor to instantiate Tidy:

public BrowserView() {     tidy = new Tidy();}

With all of that complete I am ready to implement the various methods. There is no need to implement setFocus or widgetDefaultSelected in this case, so those will remain empty.

public void createPartControl(Composite parent) {     GridLayout gridLayout = new GridLayout();     gridLayout.numColumns = 2;     parent.setLayout(gridLayout);          browse = new Button(parent, SWT.PUSH);     browse.setText("File Browse");     browse.addSelectionListener(this);     render = new Button(parent, SWT.PUSH);     render.setText("Render");     render.addSelectionListener(this);          GridData gridData = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL);     gridData.horizontalSpan = 2;     SashForm sashForm = new SashForm(parent, SWT.HORIZONTAL);     sashForm.setLayoutData(gridData);     left = new Browser(sashForm, SWT.NONE);     right = new Browser(sashForm, SWT.NONE);          parent.pack();}

The createPartControl method is implemented above. The first step is to assign a GridLayout with two columns to the parent composite. Next, I create my buttons and add a SelectionListener to each one ensuring that my class will be notified when the buttons are pressed.

Next, I create a SashForm to implement my split-pane. Because I want the SashForm to span the entire width of my view I use a GridData object to indicate that. Finally, I create instances of each of my browsers and then pack the parent Composite to have everything lay out correctly.

That leaves me with the widgetSelected method. This method will be called each time one of the buttons is clicked. If the browse button is clicked then I want to present a dialog box to browse the file system for a HTML file. If the render button is clicked then I want to execute Tidy and render the results. The method’s code is shown below:

public void widgetSelected(SelectionEvent e) {     if(e.getSource().equals(browse)) {          FileDialog dialog = new FileDialog(getSite().getShell());          selectedFile = dialog.open();     }     else if(e.getSource().equals(render)) {          if(selectedFile != null || selectedFile.length() > 0) {               try {                    BufferedInputStream in = new BufferedInputStream(new FileInputStream(selectedFile));                    BufferedOutputStream out = new BufferedOutputStream(new 
FileOutputStream(selectedFile + "-tidy.html")); tidy.parse(in, out); in.close(); out.close(); left.setUrl(new File(selectedFile).toURL().toString()); right.setUrl(new File(selectedFile + "-tidy.html").toURL().toString()); } catch (Exception ex) { ex.printStackTrace(); } } }}

By calling the getSource method on the SelectionEvent I can determine which button was pressed. In the case of the browse button, I create an instance of FileDialog and then call its open method. The open method will take care of actually presenting the dialog to the user and will return a string indicating his or her selection, which I assign to the selectedFile field for later use.

In this case of the render button, I need to do several things. First, I need to make sure that a file has actually been selected. After that, I can create a FileInputStream against the selected file as well as a FileOutputStream to write the results from Tidy. I then call Tidy’s parse method with my streams and then close them. With the streams closed I just need to render them, which I do by turning the respective files into URLs using the File object.

Possible Problems
When I first attempted to use JTidy with Eclipse I ran into some problems. It seems that with certain platforms, the binary version of JTidy will not work. The solution is to recompile JTidy itself. Because of this, I created a JTidy plugin for Eclipse that can be referenced as a dependent plugin instead of adding the library directly to your classpath. I would recommend using this plugin instead of the JTidy binary, so that any deployment issues on other platforms are avoided.

The downloadable code provided as part of this article contains both the Tidy plugin and the example split-pane browser plugin. These plugins should work together out of the box. Also included is the HTML from my previous Eclipse article that can be used as input to Tidy.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist