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("&lt;");
265                                    break;
266                                case '>':
267                                    write("&gt;");
268                                    break;
269                                case '&':
270                                    write("&amp;");
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}