Java: Write DTD Valid XHTML
Sunday, 16 October 2011 22:45

Here's an example of how to write a DTD validated XHTML file from Java.

Notes:

  • The files below were copied locally to avoid timeouts while reading from the www.w3.org during validation.
    • xhtml1-strict.dtd
    • xhtml-lat1.ent
    • xhtml-special.ent
    • xhtml-symbol.ent
  • The XML is written to a 'StringWriter' first to allow validation before anything is written to disc.
  • This technique can only be used for DTD validation.
  • This IBM developer works article 'JAXP validation' is a fairly good read.

MyMain.java
import java.io.ByteArrayInputStream;
import java.io.FileWriter;
import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class MyMain {

	public static void main(String[] args) {
		try {
			//
			// 1.) Write XML as DOM Document
			//
			String filename = "./src/junk.xhtml";

			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(true);
			factory.setNamespaceAware(false);
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document myXML = builder.newDocument();
			Element html = myXML.createElement("html");
			myXML.appendChild(html);
			Element head = myXML.createElement("head");
			html.appendChild(head);
			Element title = myXML.createElement("title");
			title.setTextContent("My Homepage");
			head.appendChild(title);
			Element body = myXML.createElement("body");
			html.appendChild(body);

			//
			// 2.) Write DOM Document to string
			//
			DOMSource domSource = new DOMSource(myXML);
			TransformerFactory transformFactory = TransformerFactory
					.newInstance();

			StringWriter out = new StringWriter();
			Transformer tf = transformFactory.newTransformer();
			tf.setOutputProperty(OutputKeys.INDENT, "yes");
			tf.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
					"-//W3C//DTD XHTML 1.0 Strict//EN");
			// Note: Point DOCTYPE_SYSTEM at the local file system. Accessing the 
			//       www.w3.org DTD seems to frequently time outs etc.
			tf.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,".//DTD//xhtml1-strict.dtd");
			tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
			tf.setOutputProperty(OutputKeys.VERSION, "1.0");
			tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
			tf.setOutputProperty(OutputKeys.METHOD, "xml");
			tf.transform(domSource, new StreamResult(out));

			//
			// 3.) Validate the XML File that was just written
			//
			ByteArrayInputStream string2Input = new ByteArrayInputStream(out
					.toString().getBytes());
			XHTMLErrorHandler xhtmlErrors = new XHTMLErrorHandler();
			builder.setErrorHandler(xhtmlErrors);
			builder.parse(string2Input);
			if (xhtmlErrors.getProblemCount() != 0) {
				return;
			}

			//
			// 4.) Write the XML File to disc
			//
			FileWriter fileout = new FileWriter(filename);
			tf.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
				"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
			tf.transform(domSource, new StreamResult(fileout));
			System.out.println("Successfully generated '"+filename+"'");

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}
XHTMLErrorHandler.java
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class XHTMLErrorHandler implements ErrorHandler {

	private int problemCount;

	public XHTMLErrorHandler() {
		problemCount = 0;
	}

	public int getProblemCount() {
		return problemCount;
	}

	@Override
	public void error(SAXParseException exception) throws SAXException {
		problemCount += 1;
		System.out.println("*** Error (line:" + exception.getLineNumber()
				+ ", column:" + exception.getColumnNumber() + "): "
				+ exception.getMessage());
	}

	@Override
	public void fatalError(SAXParseException exception) throws SAXException {
		problemCount += 1;
		System.out.println("*** Fatal Error (line:" + exception.getLineNumber()
				+ ", column:" + exception.getColumnNumber() + "): "
				+ exception.getMessage());
	}

	@Override
	public void warning(SAXParseException exception) throws SAXException {
		problemCount += 1;
		System.out.println("Warning (line:" + exception.getLineNumber()
				+ ", column:" + exception.getColumnNumber() + "): "
				+ exception.getMessage());
	}

};