    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}