package org.apache.tapestry5.javadoc; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.util.Stack; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.XMLReaderFactory; /** * Reads an XDOC file using SAX and streams its content (with some modifications) to * an output stream. */ public class XDocStreamer { final File xdoc; final Writer writer; private static final Runnable NO_OP = new Runnable() { @Override public void run() { } }; private void write(String text) { try { writer.write(text); } catch (IOException ex) { throw new RuntimeException(ex); } } private Runnable writeClose(final String elementName) { return new Runnable() { @Override public void run() { write("</"); write(elementName); write(">"); } }; } public XDocStreamer(File xdoc, Writer writer) { this.xdoc = xdoc; this.writer = writer; } enum ParserState { IGNORING, COPYING, COPYING_CDATA }; class SaxHandler implements ContentHandler, LexicalHandler { final Stack<Runnable> endElementHandlers = CollectionFactory.newStack(); ParserState state = ParserState.IGNORING; @Override public void startDTD(String name, String publicId, String systemId) throws SAXException { } @Override public void endDTD() throws SAXException { } @Override public void startEntity(String name) throws SAXException { } @Override public void endEntity(String name) throws SAXException { } @Override public void startCDATA() throws SAXException { if (state == ParserState.IGNORING) { endElementHandlers.push(NO_OP); return; } state = ParserState.COPYING_CDATA; endElementHandlers.push(new Runnable() { @Override public void run() { state = ParserState.COPYING; } }); } @Override public void endCDATA() throws SAXException { endElementHandlers.pop().run(); } /** Does nothing; comments are always stripped out. */ @Override public void comment(char[] ch, int start, int length) throws SAXException { } @Override public void setDocumentLocator(Locator locator) { } @Override public void startDocument() throws SAXException { } @Override public void endDocument() throws SAXException { } @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { } @Override public void endPrefixMapping(String prefix) throws SAXException { } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { if (state == ParserState.IGNORING) { if (localName.equals("body")) { state = ParserState.COPYING; } endElementHandlers.push(NO_OP); return; } if (localName.equals("section")) { String name = getAttribute(atts, "name"); // More JavaDoc ugliness; this makes sections fit in well with the main // output. write(String.format("<dt><h3>%s</h3></dt><dd>", name)); endElementHandlers.push(writeClose("dd")); return; } if (localName.equals("subsection")) { writeSectionHeader(atts, "h3"); return; } if (localName.equals("source")) { write("<pre>"); endElementHandlers.push(writeClose("pre")); return; } write("<"); write(localName); for (int i = 0; i < atts.getLength(); i++) { write(String.format(" %s=\"%s\"", atts.getLocalName(i), atts.getValue(i))); } write(">"); endElementHandlers.push(writeClose(localName)); } private void writeSectionHeader(Attributes atts, String elementName) { String name = getAttribute(atts, "name"); write(String.format("<%s>%s</%1$s>", elementName, name)); endElementHandlers.push(NO_OP); return; } private String getAttribute(Attributes atts, String name) { for (int i = 0; i < atts.getLength(); i++) { if (atts.getLocalName(i).equals(name)) return atts.getValue(i); } throw new RuntimeException(String.format("No '%s' attribute present.", name)); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { endElementHandlers.pop().run(); } @Override public void characters(char[] ch, int start, int length) throws SAXException { try { switch (state) { case IGNORING: break; case COPYING: writer.write(ch, start, length); break; case COPYING_CDATA: for (int i = start; i < start + length; i++) { switch (ch[i]) { case '<': write("<"); break; case '>': write(">"); break; case '&': write("&"); break; default: writer.write(ch[i]); } } break; } } catch (IOException ex) { throw new SAXException(ex); } } @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { } @Override public void processingInstruction(String target, String data) throws SAXException { } @Override public void skippedEntity(String name) throws SAXException { } } /** Parse the file and write its transformed content to the Writer. */ public void writeContent() throws SAXException { SaxHandler handler = new SaxHandler(); XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setContentHandler(handler); reader.setProperty("http://xml.org/sax/properties/lexical-handler", handler); try { InputStream is = new BufferedInputStream(new FileInputStream(xdoc)); reader.parse(new InputSource(is)); } catch (IOException ex) { throw new RuntimeException(ex); } } }