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