package org.apache.maven.doxia.module.docbook;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Stack;

import org.apache.maven.doxia.macro.MacroExecutionException;
import org.apache.maven.doxia.markup.HtmlMarkup;
import org.apache.maven.doxia.parser.AbstractXmlParser;
import org.apache.maven.doxia.parser.Parser;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Parse a <a href="http://www.docbook.org/schemas/simplified"><code>Simplified DocBook</code></a> document
 * and emit events into the specified doxia Sink.
 *
 * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
 * @version $Id: DocBookParser.java 1726411 2016-01-23 16:34:09Z hboutemy $
 * @since 1.0
 */
@Component( role = Parser.class, hint = "docbook" )
public class DocBookParser
    extends AbstractXmlParser
    implements DocbookMarkup, SimplifiedDocbookMarkup
{
    /**
     * Level counter for calculating the section level.
     */
    private int level;

    /**
     * Used to distinguish italic from bold.
     */
    private boolean isBold;

    private boolean inHead;

    private boolean ignore;

    private boolean simpleTag;

    private char trademark;

    /**
     * A selective stack of parent elements
     */
    private final Stack<String> parent = new Stack<String>();

    /**
     * The list of DocBook elements that introduce a new level of hierarchy.
     */
    private static final Collection<String> HIER_ELEMENTS = new HashSet<String>();

    /**
     * Simplified DocBook elements that are direct children of &lt;article&gt;
     * and that should be emitted into the Sink's head.
     */
    private static final Collection<String> META_ELEMENTS = new HashSet<String>();

    /**
     * Simplified DocBook elements that occur within &lt;articleinfo&gt;
     * and that are currently recognized by the parser.
     */
    private static final Collection<String> ARTICLEINFO_ELEMENTS = new HashSet<String>();

    /**
     * The list of DocBook elements that will be rendered verbatim
     */
    private static final Collection<String> VERBATIM_ELEMENTS = new HashSet<String>();

    /**
     * The list of DocBook elements that will be rendered inline and bold
     */
    private static final Collection<String> BOLD_ELEMENTS = new HashSet<String>();

    /**
     * The list of DocBook elements that will be rendered inline and italic
     */
    private static final Collection<String> ITALIC_ELEMENTS = new HashSet<String>();

    /**
     * The list of DocBook elements that will be rendered inline and monospace
     */
    private static final Collection<String> MONOSPACE_ELEMENTS = new HashSet<String>();

    /**
     * The list of DocBook elements that may be ignored, either because they don't
     * require any special processing or because they are not yet implemented.
     */
    private static final Collection<String> IGNORABLE_ELEMENTS = new HashSet<String>();
    static
    {
        META_ELEMENTS.add( SimplifiedDocbookMarkup.ARTICLEINFO_TAG.toString() );
        META_ELEMENTS.add( SimplifiedDocbookMarkup.AUTHORBLURB_TAG.toString() );
        META_ELEMENTS.add( SimplifiedDocbookMarkup.SUBTITLE_TAG.toString() );
        META_ELEMENTS.add( SimplifiedDocbookMarkup.TITLE_TAG.toString() );
        META_ELEMENTS.add( SimplifiedDocbookMarkup.TITLEABBREV_TAG.toString() );

        ARTICLEINFO_ELEMENTS.add( SimplifiedDocbookMarkup.TITLE_TAG.toString() );
        ARTICLEINFO_ELEMENTS.add( SimplifiedDocbookMarkup.CORPAUTHOR_TAG.toString() );
        ARTICLEINFO_ELEMENTS.add( SimplifiedDocbookMarkup.DATE_TAG.toString() );

        HIER_ELEMENTS.add( SimplifiedDocbookMarkup.SECTION_TAG.toString() );
        HIER_ELEMENTS.add( SimplifiedDocbookMarkup.APPENDIX_TAG.toString() );
        HIER_ELEMENTS.add( SimplifiedDocbookMarkup.BIBLIOGRAPHY_TAG.toString() );
        HIER_ELEMENTS.add( SimplifiedDocbookMarkup.BIBLIODIV_TAG.toString() );

        VERBATIM_ELEMENTS.add( SimplifiedDocbookMarkup.PROGRAMLISTING_TAG.toString() );
        VERBATIM_ELEMENTS.add( SimplifiedDocbookMarkup.LITERALLAYOUT_TAG.toString() );

        BOLD_ELEMENTS.add( SimplifiedDocbookMarkup.COMMAND_TAG.toString() );
        BOLD_ELEMENTS.add( SimplifiedDocbookMarkup.USERINPUT_TAG.toString() );

        ITALIC_ELEMENTS.add( SimplifiedDocbookMarkup.REPLACEABLE_TAG.toString() );
        ITALIC_ELEMENTS.add( SimplifiedDocbookMarkup.SYSTEMITEM_TAG.toString() );
        ITALIC_ELEMENTS.add( SimplifiedDocbookMarkup.CITETITLE_TAG.toString() );
        ITALIC_ELEMENTS.add( SimplifiedDocbookMarkup.EMPHASIS_TAG.toString() );
        ITALIC_ELEMENTS.add( SimplifiedDocbookMarkup.ATTRIBUTION_TAG.toString() );
        ITALIC_ELEMENTS.add( SimplifiedDocbookMarkup.LINEANNOTATION_TAG.toString() );

        MONOSPACE_ELEMENTS.add( SimplifiedDocbookMarkup.COMPUTEROUTPUT_TAG.toString() );
        MONOSPACE_ELEMENTS.add( SimplifiedDocbookMarkup.REPLACEABLE_TAG.toString() );
        MONOSPACE_ELEMENTS.add( SimplifiedDocbookMarkup.LITERAL_TAG.toString() );
        MONOSPACE_ELEMENTS.add( SimplifiedDocbookMarkup.OPTION_TAG.toString() );
        MONOSPACE_ELEMENTS.add( SimplifiedDocbookMarkup.SYSTEMITEM_TAG.toString() );
        MONOSPACE_ELEMENTS.add( SimplifiedDocbookMarkup.USERINPUT_TAG.toString() );
        MONOSPACE_ELEMENTS.add( SimplifiedDocbookMarkup.FILENAME_TAG.toString() );

        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.ABBREV_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.ABSTRACT_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.BIBLIOMIXED_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.BIBLIOMSET_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.COLSPEC_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.EPIGRAPH_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.EXAMPLE_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.FOOTNOTEREF_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.IMAGEOBJECT_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.INLINEMEDIAOBJECT_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.ISSUENUM_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.PHRASE_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.PUBDATE_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.PUBLISHERNAME_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.SPANSPEC_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.TEXTOBJECT_TAG.toString() );
        IGNORABLE_ELEMENTS.add( SimplifiedDocbookMarkup.VOLUMENUM_TAG.toString() );
    }

    /** {@inheritDoc} */
    protected void init()
    {
        super.init();

        this.parent.clear();
        this.trademark = 0;
        this.level = 0;
        this.isBold = false;
        this.inHead = false;
        this.ignore = false;
        this.simpleTag = false;
    }

    // ----------------------------------------------------------------------
    //
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    protected void handleStartTag( XmlPullParser parser, Sink sink )
        throws XmlPullParserException, MacroExecutionException
    {
        if ( inHead && !META_ELEMENTS.contains( parser.getName() )
                && isParent( SimplifiedDocbookMarkup.ARTICLE_TAG.toString() ) )
        {
            sink.head_();
            inHead = false;

            // assume any element that is not meta starts the body
            sink.body();
        }

        final SinkEventAttributeSet attribs = getAttributesFromParser( parser );
        simpleTag = parser.isEmptyElementTag();

        if ( parser.getName().equals( SimplifiedDocbookMarkup.ARTICLE_TAG.toString() ) )
        {
            handleArticleStart( sink, attribs );
        }
        else if ( isParent( SimplifiedDocbookMarkup.ARTICLEINFO_TAG.toString() ) )
        {
            handleArticleInfoStartTags( parser.getName(), sink, attribs );
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.ARTICLEINFO_TAG.toString() ) )
        {
            parent.push( SimplifiedDocbookMarkup.ARTICLEINFO_TAG.toString() );
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.FOOTNOTE_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.SECTIONINFO_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.VIDEOOBJECT_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.AUDIOOBJECT_TAG.toString() ) )
        {
            parent.push( parser.getName() );
            ignore = true;
        }
        else if ( isParent( ( SimplifiedDocbookMarkup.FOOTNOTE_TAG.toString() ) )
                || isParent( SimplifiedDocbookMarkup.AUDIOOBJECT_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.VIDEOOBJECT_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.SECTIONINFO_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.ENTRYTBL_TAG.toString() ) )
        {
            return; // TODO: implement footnotes, entrytbl
        }
        else if ( HIER_ELEMENTS.contains( parser.getName() ) )
        {
            handleSectionElements( sink, parser.getName(), attribs );
        }
        else if ( listStartTags ( parser.getName(), sink, attribs ) )
        {
            return;
        }
        else if ( mediaStartTag( parser.getName(), sink, attribs ) )
        {
            return;
        }
        else if ( tableStartTags( parser.getName(), sink, attribs ) )
        {
            return;
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.PARA_TAG.toString() ) )
        {
            handleParaStart( sink, attribs );
        }
        else if ( styleStartTags( parser.getName(), sink, attribs ) )
        {
            return;
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TITLE_TAG.toString() ) )
        {
            handleTitleStart( sink, attribs );
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.EMAIL_TAG.toString() ) )
        {
            handleEmailStart( parser, sink, attribs );
        }
        else if ( linkStartTag( parser.getName(), sink, attribs ) )
        {
            return;
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.QUOTE_TAG.toString() ) )
        {
            sink.text( "\"", null );
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TRADEMARK_TAG.toString() ) )
        {
            trademark = '\u2122';
            final Object trade = attribs.getAttribute( "class" );

            if ( trade != null )
            {
                trademark = DocbookUtils.trademarkFromClass( trade.toString() );
            }
        }
        else
        {
            if ( !ignorable( parser.getName() ) )
            {
                if ( simpleTag )
                {
                    handleUnknown( parser, sink, HtmlMarkup.TAG_TYPE_SIMPLE );
                }
                else
                {
                    handleUnknown( parser, sink, HtmlMarkup.TAG_TYPE_START );
                }
            }
        }
    }

    /** {@inheritDoc} */
    protected void handleEndTag( XmlPullParser parser, Sink sink )
        throws XmlPullParserException, MacroExecutionException
    {
        if ( parser.getName().equals( SimplifiedDocbookMarkup.ARTICLE_TAG.toString() ) )
        {
            sink.body_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.ARTICLEINFO_TAG.toString() ) )
        {
            parent.pop();
        }
        else if ( isParent( SimplifiedDocbookMarkup.ARTICLEINFO_TAG.toString() ) )
        {
             handleArticleInfoEndTags( parser.getName(), sink );
        }
        else if ( HIER_ELEMENTS.contains( parser.getName() ) )
        {
            sink.section_( level );

            //decrease the nesting level
            level--;
            parent.pop();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.FOOTNOTE_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.AUDIOOBJECT_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.VIDEOOBJECT_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.SECTIONINFO_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.ENTRYTBL_TAG.toString() ) )
        {
            parent.pop();
            ignore = false;
        }
        else if ( isParent( ( SimplifiedDocbookMarkup.FOOTNOTE_TAG.toString() ) )
                || isParent( SimplifiedDocbookMarkup.AUDIOOBJECT_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.VIDEOOBJECT_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.SECTIONINFO_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.ENTRYTBL_TAG.toString() ) )
        {
            return;
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.ITEMIZEDLIST_TAG.toString() ) )
        {
            sink.list_();
            parent.pop();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.ORDEREDLIST_TAG.toString() ) )
        {
            sink.numberedList_();
            parent.pop();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.LISTITEM_TAG.toString() ) )
        {
            parent.pop();

            if ( isParent( SimplifiedDocbookMarkup.VARIABLELIST_TAG.toString() ) )
            {
                sink.definition_();
            }
            else if ( isParent( SimplifiedDocbookMarkup.ORDEREDLIST_TAG.toString() ) )
            {
                sink.numberedListItem_();
            }
            else
            {
                sink.listItem_();
            }
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.VARIABLELIST_TAG.toString() ) )
        {
            sink.definitionList_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.VARLISTENTRY_TAG.toString() ) )
        {
            sink.definitionListItem_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TERM_TAG.toString() ) )
        {
            sink.definedTerm_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.MEDIAOBJECT_TAG.toString() ) )
        {
            sink.figure_();
            parent.pop();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.IMAGEOBJECT_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.FIGURE_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.THEAD_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.TFOOT_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.TBODY_TAG.toString() ) )
        {
            parent.pop();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.CAPTION_TAG.toString() ) )
        {
            handleCaptionEnd( sink );
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TABLE_TAG.toString() )
            || parser.getName().equals( SimplifiedDocbookMarkup.INFORMALTABLE_TAG.toString() ) )
        {
            sink.table_();

            parent.pop();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TR_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.ROW_TAG.toString() ) )
        {
            sink.tableRow_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TGROUP_TAG.toString() ) )
        {
            sink.tableRows_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.ENTRY_TAG.toString() )
                && isParent( SimplifiedDocbookMarkup.THEAD_TAG.toString() )
            || parser.getName().equals( TH_TAG.toString() ) )
        {
            sink.tableHeaderCell_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.ENTRY_TAG.toString() ) )
        {
            sink.tableCell_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.PARA_TAG.toString() ) )
        {
            handleParaEnd( sink );
        }
        else if ( VERBATIM_ELEMENTS.contains( parser.getName() ) )
        {
            sink.verbatim_();
        }
        else if ( BOLD_ELEMENTS.contains( parser.getName() )
            && MONOSPACE_ELEMENTS.contains( parser.getName() ) )
        {
            sink.monospaced_();
            sink.bold_();
        }
        else if ( ITALIC_ELEMENTS.contains( parser.getName() )
            && MONOSPACE_ELEMENTS.contains( parser.getName() ) )
        {
            sink.monospaced_();
            sink.italic_();
        }
        else if ( BOLD_ELEMENTS.contains( parser.getName() ) )
        {
            sink.bold_();
        }
        else if ( ITALIC_ELEMENTS.contains( parser.getName() ) )
        {
            if ( isBold )
            {
                sink.bold_();

                isBold = false;
            }
            else
            {
                sink.italic_();
            }
        }
        else if ( MONOSPACE_ELEMENTS.contains( parser.getName() ) )
        {
            sink.monospaced_();
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TITLE_TAG.toString() ) )
        {
            handleTitleEnd( sink );
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.ULINK_TAG.toString() )
                || parser.getName().equals( SimplifiedDocbookMarkup.LINK_TAG.toString() ) )
        {
            if ( isParent( parser.getName() ) )
            {
                parent.pop();
                sink.link_();
            }
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.QUOTE_TAG.toString() ) )
        {
            sink.text( "\"", null );
        }
        else if ( parser.getName().equals( SimplifiedDocbookMarkup.TRADEMARK_TAG.toString() ) )
        {
            sink.text( Character.toString( trademark ), null );
        }
        else if ( !simpleTag && !ignorable( parser.getName() ) )
        {
            handleUnknown( parser, sink, HtmlMarkup.TAG_TYPE_END );
        }
    }

    /** {@inheritDoc} */
    protected void handleComment( XmlPullParser parser, Sink sink )
        throws XmlPullParserException
    {
        final String text = parser.getText();

        if ( "PB".equals( text.trim() ) )
        {
            sink.pageBreak();
        }
        else if ( "HR".equals( text.trim() ) )
        {
            sink.horizontalRule();
        }
        else if ( "LB".equals( text.trim() ) )
        {
            sink.lineBreak();
        }
        else if ( "anchor_end".equals( text.trim() ) )
        {
            sink.anchor_();
        }
        else
        {
            if ( isEmitComments() )
            {
                sink.comment( text );
            }
        }
    }

    /** {@inheritDoc} */
    protected void handleCdsect( XmlPullParser parser, Sink sink )
            throws XmlPullParserException
    {
        if ( !ignore )
        {
            super.handleCdsect( parser, sink );
        }
    }

    /** {@inheritDoc} */
    protected void handleEntity( XmlPullParser parser, Sink sink )
            throws XmlPullParserException
    {
        if ( !ignore )
        {
            super.handleEntity( parser, sink );
        }
    }

    /** {@inheritDoc} */
    protected void handleText( XmlPullParser parser, Sink sink )
            throws XmlPullParserException
    {
        if ( !ignore )
        {
            super.handleText( parser, sink );
        }
    }

    // ----------------------------------------------------------------------
    //
    // ----------------------------------------------------------------------

    private void handleArticleInfoStartTags( String name, Sink sink, SinkEventAttributeSet attribs )
    {
        if ( !ARTICLEINFO_ELEMENTS.contains( name ) )
        {
            ignore = true;
            return; // TODO: other meta data are ignored, implement!
        }

        if ( name.equals( SimplifiedDocbookMarkup.TITLE_TAG.toString() ) )
        {
            sink.title( attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.CORPAUTHOR_TAG.toString() ) )
        {
            sink.author( attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.DATE_TAG.toString() ) )
        {
            sink.date( attribs );
        }
    }

    private void handleArticleInfoEndTags( String name, Sink sink )
    {
        if ( !ARTICLEINFO_ELEMENTS.contains( name ) )
        {
            ignore = false;
            return; // TODO: other meta data are ignored, implement!
        }

        if ( name.equals( SimplifiedDocbookMarkup.TITLE_TAG.toString() ) )
        {
            sink.title_();
        }
        else if ( name.equals( SimplifiedDocbookMarkup.CORPAUTHOR_TAG.toString() ) )
        {
            sink.author_();
        }
        else if ( name.equals( SimplifiedDocbookMarkup.DATE_TAG.toString() ) )
        {
            sink.date_();
        }
    }

    private void handleCaptionStart( Sink sink, SinkEventAttributeSet attribs )
    {
        if ( isParent( SimplifiedDocbookMarkup.MEDIAOBJECT_TAG.toString() ) )
        {
            sink.figureCaption( attribs );
        }
        else if ( isParent( SimplifiedDocbookMarkup.INFORMALTABLE_TAG.toString() )
            || isParent( SimplifiedDocbookMarkup.TABLE_TAG.toString() ) )
        {
            sink.tableCaption( attribs );
        }

        parent.push( SimplifiedDocbookMarkup.CAPTION_TAG.toString() );
    }

    private void handleCaptionEnd( Sink sink )
    {
        parent.pop();

        if ( isParent( SimplifiedDocbookMarkup.MEDIAOBJECT_TAG.toString() ) )
        {
            sink.figureCaption_();
        }
        else if ( isParent( SimplifiedDocbookMarkup.INFORMALTABLE_TAG.toString() )
            || isParent( SimplifiedDocbookMarkup.TABLE_TAG.toString() ) )
        {
            sink.tableCaption_();
        }
    }

    private void handleEmailStart( XmlPullParser parser, Sink sink, SinkEventAttributeSet attribs )
            throws XmlPullParserException
    {
        try
        {
            final String mailto = parser.nextText();
            sink.link( "mailto:" + mailto, attribs );
            sink.monospaced();
            sink.text( mailto, null );
            sink.monospaced_();
            sink.link_();
        }
        catch ( IOException e )
        {
            throw new XmlPullParserException( "IOException: " + e.getMessage(), parser, e );
        }
    }

    private void handleFigureStart( Sink sink, SinkEventAttributeSet attribs )
    {
        sink.figure( attribs );
        parent.push( SimplifiedDocbookMarkup.MEDIAOBJECT_TAG.toString() );
    }

    private void handleArticleStart( Sink sink, SinkEventAttributeSet attribs )
    {
        sink.head( attribs );
        inHead = true;

        parent.push( SimplifiedDocbookMarkup.ARTICLE_TAG.toString() );
    }

    //If the element introduces a new level of hierarchy, raise the stack
    private void handleSectionElements( Sink sink, String name, SinkEventAttributeSet attribs )
    {
        //increase the nesting level
        level++;

        sink.section( level, attribs );

        parent.push( name );
    }

    private void handleAnchorStart( Sink sink, SinkEventAttributeSet attribs  )
    {
        final Object id = attribs.getAttribute( SimplifiedDocbookMarkup.ID_ATTRIBUTE );

        if ( id != null )
        {
            sink.anchor( id.toString(), attribs );
        }
    }

    private void handleImageDataStart( Sink sink, SinkEventAttributeSet attribs )
            throws XmlPullParserException
    {
        final Object fileref = attribs.getAttribute( SimplifiedDocbookMarkup.FILEREF_ATTRIBUTE );

        if ( fileref == null )
        {
            throw new XmlPullParserException( "Missing fileref attribute in imagedata!" );
        }

        sink.figureGraphics( fileref.toString(), attribs );
    }

    private void handleItemizedListStart( Sink sink, SinkEventAttributeSet attribs )
    {
        sink.list( attribs );
        //for itemizedlists in variablelists
        parent.push( SimplifiedDocbookMarkup.ITEMIZEDLIST_TAG.toString() );
    }

    private void handleLinkStart( Sink sink, SinkEventAttributeSet attribs )
            throws XmlPullParserException
    {
        final Object linkend = attribs.getAttribute( SimplifiedDocbookMarkup.LINKEND_ATTRIBUTE );

        if ( linkend == null )
        {
            throw new XmlPullParserException( "Missing linkend attribute in link!" );
        }

        parent.push( SimplifiedDocbookMarkup.LINK_TAG.toString() );
        sink.link( "#" + linkend.toString(), attribs );
    }

    private void handleListItemStart( Sink sink, SinkEventAttributeSet attribs )
    {
        if ( isParent( SimplifiedDocbookMarkup.VARIABLELIST_TAG.toString() ) )
        {
            sink.definition( attribs );
        }
        else if ( isParent( SimplifiedDocbookMarkup.ORDEREDLIST_TAG.toString() ) )
        {
            sink.numberedListItem( attribs );
        }
        else
        {
            sink.listItem( attribs );
        }

        parent.push( SimplifiedDocbookMarkup.LISTITEM_TAG.toString() );
    }

    private void handleOrderedListStart( Sink sink, SinkEventAttributeSet attribs )
    {
        //default enumeration style is decimal
        int numeration = Sink.NUMBERING_DECIMAL;

        final Object num = attribs.getAttribute( SimplifiedDocbookMarkup.NUMERATION_ATTRIBUTE );

        if ( num != null )
        {
            numeration = DocbookUtils.doxiaListNumbering( num.toString() );
        }

        sink.numberedList( numeration, attribs );
        parent.push( SimplifiedDocbookMarkup.ORDEREDLIST_TAG.toString() );
    }

    private void handleParaEnd( Sink sink )
    {
        if ( !isParent( SimplifiedDocbookMarkup.CAPTION_TAG.toString() )
                && ! isParent( SimplifiedDocbookMarkup.LISTITEM_TAG.toString() ) )
        {
            sink.paragraph_();
        }
    }

    private void handleParaStart( Sink sink, SinkEventAttributeSet attribs )
    {
        if ( !isParent( SimplifiedDocbookMarkup.CAPTION_TAG.toString() )
                && ! isParent( SimplifiedDocbookMarkup.LISTITEM_TAG.toString() ) )
        {
            sink.paragraph( attribs );
        }
    }

    private void handleTableStart( Sink sink, SinkEventAttributeSet attribs )
    {
        final Object frame = attribs.getAttribute( SimplifiedDocbookMarkup.FRAME_ATTRIBUTE );
        if ( frame != null )
        {
            attribs.addAttribute( SimplifiedDocbookMarkup.FRAME_ATTRIBUTE,
                    DocbookUtils.doxiaTableFrameAttribute( frame.toString() ) );
        }

        sink.table( attribs );

        parent.push( SimplifiedDocbookMarkup.TABLE_TAG.toString() );
    }

    private void handleTitleStart( Sink sink, SinkEventAttributeSet attribs )
    {
        if ( isParent( SimplifiedDocbookMarkup.TABLE_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.INFORMALTABLE_TAG.toString() ) )
        {
            sink.tableCaption( attribs );
        }
        else if ( isParent( SimplifiedDocbookMarkup.ARTICLE_TAG.toString() ) )
        {
            sink.title( attribs );
        }
        else if ( isParent( SimplifiedDocbookMarkup.SECTION_TAG.toString() ) )
        {
            sink.sectionTitle( level, attribs );
        }
        else
        {
            sink.bold();
        }
    }

    private void handleTitleEnd( Sink sink )
    {
        if ( isParent( SimplifiedDocbookMarkup.TABLE_TAG.toString() )
                || isParent( SimplifiedDocbookMarkup.INFORMALTABLE_TAG.toString() ) )
        {
            sink.tableCaption_();
        }
        else if ( isParent( SimplifiedDocbookMarkup.SECTION_TAG.toString() ) )
        {
            sink.sectionTitle_( level );
        }
        else if ( isParent( SimplifiedDocbookMarkup.ARTICLE_TAG.toString() ) )
        {
            sink.title_();
        }
        else
        {
            sink.bold_();
        }
    }

    private void handleUlinkStart( Sink sink, SinkEventAttributeSet attribs )
            throws XmlPullParserException
    {
        final Object url = attribs.getAttribute( SimplifiedDocbookMarkup.URL_ATTRIBUTE );

        if ( url == null )
        {
            throw new XmlPullParserException( "Missing url attribute in ulink!" );
        }

        parent.push( SimplifiedDocbookMarkup.ULINK_TAG.toString() );
        sink.link( url.toString(), attribs );
    }

    private void handleVariableListStart( Sink sink, SinkEventAttributeSet attribs )
    {
        sink.definitionList( attribs );
        parent.push( SimplifiedDocbookMarkup.VARIABLELIST_TAG.toString() );
    }

    private void handleXrefStart( Sink sink, SinkEventAttributeSet attribs )
            throws XmlPullParserException
    {
        final Object linkend = attribs.getAttribute( SimplifiedDocbookMarkup.LINKEND_ATTRIBUTE );

        if ( linkend == null )
        {
            throw new XmlPullParserException( "Missing linkend attribute in xref!" );
        }

        sink.link( "#" + linkend.toString(), attribs );
        sink.text( "Link" ); //TODO: determine text of link target
        sink.link_();
    }

    private boolean ignorable( String name )
    {
        return IGNORABLE_ELEMENTS.contains( name );
    }

    /**
     * Determines if the given element is a parent element.
     *
     * @param element the element to determine.
     * @return true if the given element is a parent element.
     */
    private boolean isParent( String element )
    {
        if ( parent.size() > 0 )
        {
            return parent.peek().equals( element );
        }

        return false;
    }

    private boolean linkStartTag( String name, Sink sink, SinkEventAttributeSet attribs )
            throws XmlPullParserException
    {
        if ( name.equals( SimplifiedDocbookMarkup.ULINK_TAG.toString() ) )
        {
            handleUlinkStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.LINK_TAG.toString() ) )
        {
            handleLinkStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.XREF_TAG.toString() ) )
        {
            handleXrefStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.ANCHOR_TAG.toString() ) )
        {
            handleAnchorStart( sink, attribs );
        }
        else
        {
            return false;
        }

        return true;
    }

    private boolean listStartTags( String name, Sink sink, SinkEventAttributeSet attribs )
    {
        if ( name.equals( SimplifiedDocbookMarkup.ITEMIZEDLIST_TAG.toString() ) )
        {
            handleItemizedListStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.ORDEREDLIST_TAG.toString() ) )
        {
            handleOrderedListStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.LISTITEM_TAG.toString() ) )
        {
            handleListItemStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.VARIABLELIST_TAG.toString() ) )
        {
            handleVariableListStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.VARLISTENTRY_TAG.toString() ) )
        {
            sink.definitionListItem( attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.TERM_TAG.toString() ) )
        {
            sink.definedTerm( attribs );
        }
        else
        {
            return false;
        }

        return true;
    }

    private boolean mediaStartTag( String name, Sink sink, SinkEventAttributeSet attribs )
            throws XmlPullParserException
    {
        if ( name.equals( SimplifiedDocbookMarkup.MEDIAOBJECT_TAG.toString() ) )
        {
            handleFigureStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.IMAGEOBJECT_TAG.toString() )
                || name.equals( SimplifiedDocbookMarkup.FIGURE_TAG.toString() ) )
        {
            parent.push( name );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.IMAGEDATA_TAG.toString() ) )
        {
            handleImageDataStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.CAPTION_TAG.toString() ) )
        {
            handleCaptionStart( sink, attribs );
        }
        else
        {
            return false;
        }

        return true;
    }

    private boolean styleStartTags( String name, Sink sink, SinkEventAttributeSet attribs )
    {
        if ( VERBATIM_ELEMENTS.contains( name ) )
        {
            sink.verbatim( SinkEventAttributeSet.BOXED );
        }
        else if ( BOLD_ELEMENTS.contains( name ) && MONOSPACE_ELEMENTS.contains( name ) )
        {
            sink.bold();
            sink.monospaced();
        }
        else if ( ITALIC_ELEMENTS.contains( name ) && MONOSPACE_ELEMENTS.contains( name ) )
        {
            sink.italic();
            sink.monospaced();
        }
        else if ( BOLD_ELEMENTS.contains( name ) )
        {
            sink.bold();
        }
        else if ( ITALIC_ELEMENTS.contains( name ) && "bold".equals( attribs.getAttribute( "role" ) ) )
        {
            sink.bold();
            isBold = true;
        }
        else if ( ITALIC_ELEMENTS.contains( name ) )
        {
            sink.italic();
        }
        else if ( MONOSPACE_ELEMENTS.contains( name ) )
        {
            sink.monospaced();
        }
        else
        {
            return false;
        }

        return true;
    }

    private boolean tableStartTags( String name, Sink sink, SinkEventAttributeSet attribs )
    {
        if ( name.equals( SimplifiedDocbookMarkup.ENTRYTBL_TAG.toString() ) )
        {
            parent.push( name );
            ignore = true;
            // insert empty table cell instead
            sink.tableCell( (SinkEventAttributeSet) null );
            sink.tableCell_();
        }
        else if ( name.equals( SimplifiedDocbookMarkup.TABLE_TAG.toString() )
            || name.equals( SimplifiedDocbookMarkup.INFORMALTABLE_TAG.toString() ) )
        {
            handleTableStart( sink, attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.THEAD_TAG.toString() )
                || name.equals( SimplifiedDocbookMarkup.TFOOT_TAG.toString() )
                || name.equals( SimplifiedDocbookMarkup.TBODY_TAG.toString() ) )
        {
            parent.push( name );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.TGROUP_TAG.toString() ) )
        {
            // this is required by the DTD
            final int cols = Integer.parseInt( (String) attribs.getAttribute( "cols" ) );
            int[] justification = new int[cols];
            int justif = Sink.JUSTIFY_LEFT;

            final Object align = attribs.getAttribute( SinkEventAttributeSet.ALIGN );

            if ( align != null )
            {
                final String al = align.toString();

                if ( "right".equals( al ) )
                {
                    justif = Sink.JUSTIFY_RIGHT;
                }
                else if ( "center".equals( al ) )
                {
                    justif = Sink.JUSTIFY_CENTER;
                }
            }

            for ( int i = 0; i < justification.length; i++ )
            {
                justification[i] = justif;
            }

            boolean grid = false;
            final Object rowsep = attribs.getAttribute( "rowsep" );

            if ( rowsep != null && Integer.parseInt( (String) rowsep ) == 1 )
            {
                grid = true;
            }

            final Object colsep = attribs.getAttribute( "colsep" );

            if ( colsep != null && Integer.parseInt( (String) colsep ) == 1 )
            {
                grid = true;
            }

            sink.tableRows( justification, grid );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.TR_TAG.toString() )
                || name.equals( SimplifiedDocbookMarkup.ROW_TAG.toString() ) )
        {
            sink.tableRow( attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.ENTRY_TAG.toString() )
                && isParent( SimplifiedDocbookMarkup.THEAD_TAG.toString() )
                || name.equals( SimplifiedDocbookMarkup.TH_TAG.toString() ) )
        {
            sink.tableHeaderCell( attribs );
        }
        else if ( name.equals( SimplifiedDocbookMarkup.ENTRY_TAG.toString() ) )
        {
            sink.tableCell( attribs );
        }
        else
        {
            return false;
        }

        return true;
    }
}
