001package org.apache.tapestry5.javadoc; 002 003import java.io.BufferedInputStream; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.IOException; 007import java.io.InputStream; 008import java.io.Writer; 009 010import org.apache.tapestry5.commons.util.CollectionFactory; 011import org.apache.tapestry5.commons.util.Stack; 012import org.xml.sax.Attributes; 013import org.xml.sax.ContentHandler; 014import org.xml.sax.InputSource; 015import org.xml.sax.Locator; 016import org.xml.sax.SAXException; 017import org.xml.sax.XMLReader; 018import org.xml.sax.ext.LexicalHandler; 019import org.xml.sax.helpers.XMLReaderFactory; 020 021/** 022 * Reads an XDOC file using SAX and streams its content (with some modifications) to 023 * an output stream. 024 */ 025public class XDocStreamer 026{ 027 final File xdoc; 028 029 final Writer writer; 030 031 private static final Runnable NO_OP = new Runnable() 032 { 033 @Override 034 public void run() 035 { 036 } 037 }; 038 039 private void write(String text) 040 { 041 try 042 { 043 writer.write(text); 044 } 045 catch (IOException ex) 046 { 047 throw new RuntimeException(ex); 048 } 049 } 050 051 private Runnable writeClose(final String elementName) 052 { 053 return new Runnable() 054 { 055 @Override 056 public void run() 057 { 058 write("</"); 059 write(elementName); 060 write(">"); 061 } 062 }; 063 } 064 065 public XDocStreamer(File xdoc, Writer writer) 066 { 067 this.xdoc = xdoc; 068 this.writer = writer; 069 } 070 071 enum ParserState 072 { 073 IGNORING, COPYING, COPYING_CDATA 074 }; 075 076 class SaxHandler implements ContentHandler, LexicalHandler 077 { 078 final Stack<Runnable> endElementHandlers = CollectionFactory.newStack(); 079 080 ParserState state = ParserState.IGNORING; 081 082 @Override 083 public void startDTD(String name, String publicId, String systemId) throws SAXException 084 { 085 } 086 087 @Override 088 public void endDTD() throws SAXException 089 { 090 } 091 092 @Override 093 public void startEntity(String name) throws SAXException 094 { 095 } 096 097 @Override 098 public void endEntity(String name) throws SAXException 099 { 100 } 101 102 @Override 103 public void startCDATA() throws SAXException 104 { 105 if (state == ParserState.IGNORING) 106 { 107 endElementHandlers.push(NO_OP); 108 return; 109 } 110 111 state = ParserState.COPYING_CDATA; 112 113 endElementHandlers.push(new Runnable() 114 { 115 @Override 116 public void run() 117 { 118 state = ParserState.COPYING; 119 } 120 }); 121 } 122 123 @Override 124 public void endCDATA() throws SAXException 125 { 126 endElementHandlers.pop().run(); 127 } 128 129 /** Does nothing; comments are always stripped out. */ 130 @Override 131 public void comment(char[] ch, int start, int length) throws SAXException 132 { 133 } 134 135 @Override 136 public void setDocumentLocator(Locator locator) 137 { 138 } 139 140 @Override 141 public void startDocument() throws SAXException 142 { 143 } 144 145 @Override 146 public void endDocument() throws SAXException 147 { 148 } 149 150 @Override 151 public void startPrefixMapping(String prefix, String uri) throws SAXException 152 { 153 } 154 155 @Override 156 public void endPrefixMapping(String prefix) throws SAXException 157 { 158 } 159 160 @Override 161 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException 162 { 163 if (state == ParserState.IGNORING) 164 { 165 if (localName.equals("body")) 166 { 167 state = ParserState.COPYING; 168 } 169 170 endElementHandlers.push(NO_OP); 171 172 return; 173 } 174 175 if (localName.equals("section")) 176 { 177 178 String name = getAttribute(atts, "name"); 179 180 // More JavaDoc ugliness; this makes sections fit in well with the main 181 // output. 182 183 write(String.format("<dt><h3>%s</h3></dt><dd>", name)); 184 185 endElementHandlers.push(writeClose("dd")); 186 187 return; 188 } 189 190 if (localName.equals("subsection")) 191 { 192 writeSectionHeader(atts, "h3"); 193 return; 194 } 195 196 if (localName.equals("source")) 197 { 198 write("<pre>"); 199 endElementHandlers.push(writeClose("pre")); 200 return; 201 } 202 203 write("<"); 204 write(localName); 205 206 for (int i = 0; i < atts.getLength(); i++) 207 { 208 write(String.format(" %s=\"%s\"", atts.getLocalName(i), atts.getValue(i))); 209 } 210 211 write(">"); 212 213 endElementHandlers.push(writeClose(localName)); 214 } 215 216 private void writeSectionHeader(Attributes atts, String elementName) 217 { 218 String name = getAttribute(atts, "name"); 219 220 write(String.format("<%s>%s</%1$s>", elementName, name)); 221 222 endElementHandlers.push(NO_OP); 223 return; 224 } 225 226 private String getAttribute(Attributes atts, String name) 227 { 228 for (int i = 0; i < atts.getLength(); i++) 229 { 230 if (atts.getLocalName(i).equals(name)) 231 return atts.getValue(i); 232 } 233 234 throw new RuntimeException(String.format("No '%s' attribute present.", name)); 235 } 236 237 @Override 238 public void endElement(String uri, String localName, String qName) throws SAXException 239 { 240 endElementHandlers.pop().run(); 241 } 242 243 @Override 244 public void characters(char[] ch, int start, int length) throws SAXException 245 { 246 try 247 { 248 switch (state) 249 { 250 case IGNORING: 251 break; 252 253 case COPYING: 254 writer.write(ch, start, length); 255 break; 256 257 case COPYING_CDATA: 258 259 for (int i = start; i < start + length; i++) 260 { 261 switch (ch[i]) 262 { 263 case '<': 264 write("<"); 265 break; 266 case '>': 267 write(">"); 268 break; 269 case '&': 270 write("&"); 271 break; 272 default: 273 writer.write(ch[i]); 274 } 275 } 276 277 break; 278 } 279 } 280 catch (IOException ex) 281 { 282 throw new SAXException(ex); 283 } 284 } 285 286 @Override 287 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException 288 { 289 } 290 291 @Override 292 public void processingInstruction(String target, String data) throws SAXException 293 { 294 } 295 296 @Override 297 public void skippedEntity(String name) throws SAXException 298 { 299 } 300 301 } 302 303 /** 304 * Parse the file and write its transformed content to the Writer. 305 * @throws SAXException if unable to parse the xdoc file 306 */ 307 public void writeContent() throws SAXException 308 { 309 SaxHandler handler = new SaxHandler(); 310 311 XMLReader reader = XMLReaderFactory.createXMLReader(); 312 313 reader.setContentHandler(handler); 314 reader.setProperty("http://xml.org/sax/properties/lexical-handler", handler); 315 316 try 317 { 318 InputStream is = new BufferedInputStream(new FileInputStream(xdoc)); 319 320 reader.parse(new InputSource(is)); 321 } 322 catch (IOException ex) 323 { 324 throw new RuntimeException(ex); 325 } 326 } 327}