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.ioc.internal.util.CollectionFactory;
011import org.apache.tapestry5.ioc.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        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><h3>%s</h3></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}