org.apache.maven.doxia.module.itext.ITextSink.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.maven.doxia.module.itext.ITextSink.java

Source

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

/*
 * 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 com.lowagie.text.BadElementException;
import com.lowagie.text.ElementTags;
import com.lowagie.text.Image;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;

import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.sink.impl.AbstractXmlSink;
import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
import org.apache.maven.doxia.util.DoxiaUtils;
import org.apache.maven.doxia.util.HtmlTools;

import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
import org.codehaus.plexus.util.xml.XMLWriter;

/**
 * <p>A doxia Sink which produces an XML Front End document for <code>iText</code> framework.</p>
 * Known limitations:
 * <ul>
 * <li>Roman lists are not supported.</li>
 * <li>Horizontal rule is not supported with 1.3.
 * See <a href="http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg10323.html">
 * http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg10323.html</a></li>
 * <li>iText has some problems with <code>ElementTags.TABLE</code> and <code>ElementTags.TABLEFITSPAGE</code>.
 * See <a href="http://sourceforge.net/tracker/index.php?func=detail&aid=786427&group_id=15255&atid=115255">
 * SourceForce Tracker</a>.</li>
 * <li>Images could be on another page and next text on the last one.</li>
 * </ul>
 *
 * @see <a href="http://www.lowagie.com/iText/tutorial/ch07.html">http://www.lowagie.com/iText/tutorial/ch07.html</a>
 *
 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
 * @version $Id$
 */
public class ITextSink extends AbstractXmlSink {
    /** This is the place where the iText DTD is located. IMPORTANT: this DTD is not uptodate! */
    public static final String DTD = "http://itext.sourceforge.net/itext.dtd";

    /** This is the reference to the DTD. */
    public static final String DOCTYPE = "ITEXT SYSTEM \"" + DTD + "\"";

    /** This is the default leading for chapter title */
    public static final String DEFAULT_CHAPTER_TITLE_LEADING = "36.0";

    /** This is the default leading for section title */
    public static final String DEFAULT_SECTION_TITLE_LEADING = "24.0";

    /** The ClassLoader used */
    private ClassLoader currentClassLoader;

    /** The action context */
    private SinkActionContext actionContext;

    /** The Writer used */
    private Writer writer;

    /** The XML Writer used */
    private final XMLWriter xmlWriter;

    private boolean writeStart;

    /** The Header object */
    private ITextHeader header;

    /** The font object */
    private ITextFont font;

    private int numberDepth = 1;

    private int depth = 0;

    private StringWriter tableCaptionWriter = null;

    private XMLWriter tableCaptionXMLWriter = null;

    /** Flag to know if an anchor is defined or not. Used as workaround for iText which needs a defined local
     * destination. */
    private boolean anchorDefined = false;

    /** Flag to know if an figure event is called. */
    private boolean figureDefined = false;

    /** Keep track of the closing tags for inline events. */
    protected Stack<List<String>> inlineStack = new Stack<>();

    /** Map of warn messages with a String as key to describe the error type and a Set as value.
     * Using to reduce warn messages. */
    private Map<String, Set<String>> warnMessages;

    /**
     * <p>Constructor for ITextSink.</p>
     *
     * @param writer the writer.
     */
    protected ITextSink(Writer writer) {
        this(writer, "UTF-8");
    }

    /**
     * <p>Constructor for ITextSink.</p>
     *
     * @param writer the writer.
     * @param encoding the encoding.
     * @since 1.1
     */
    protected ITextSink(Writer writer, String encoding) {
        // No doctype since itext doctype is not up to date!
        this(new PrettyPrintXMLWriter(writer, encoding, null));

        this.writer = writer;
        this.writeStart = true;
    }

    /**
     * <p>Constructor for ITextSink.</p>
     *
     * @param xmlWriter a pretty-printing xml writer.
     */
    protected ITextSink(PrettyPrintXMLWriter xmlWriter) {
        this.xmlWriter = xmlWriter;

        this.writeStart = false;

        init();
    }

    /**
     * Get the current classLoader
     *
     * @return the current class loader
     */
    public ClassLoader getClassLoader() {
        return currentClassLoader;
    }

    /**
     * Set a new class loader
     *
     * @param cl the class loader.
     */
    public void setClassLoader(ClassLoader cl) {
        currentClassLoader = cl;
    }

    // ----------------------------------------------------------------------
    // Document
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void close() {
        IOUtil.close(writer);

        init();
    }

    /** {@inheritDoc} */
    public void flush() {
        if (getLog().isWarnEnabled() && this.warnMessages != null) {
            for (Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet()) {
                for (String msg : entry.getValue()) {
                    getLog().warn(msg);
                }
            }
        }

        this.warnMessages = null;
    }

    // ----------------------------------------------------------------------
    // Header
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void head_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void head() {
        //init(); // why? this causes DOXIA-413

        actionContext.setAction(SinkActionContext.HEAD);
    }

    /** {@inheritDoc} */
    public void author_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void author() {
        actionContext.setAction(SinkActionContext.AUTHOR);
    }

    /** {@inheritDoc} */
    public void date_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void date() {
        actionContext.setAction(SinkActionContext.DATE);
    }

    /** {@inheritDoc} */
    public void title_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void title() {
        actionContext.setAction(SinkActionContext.TITLE);
    }

    // ----------------------------------------------------------------------
    // Body
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void body_() {
        if (writeStart) {
            writeEndElement(); // ElementTags.CHAPTER

            writeEndElement(); // ElementTags.ITEXT
        }

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void body() {
        if (writeStart) {
            writeStartElement(ElementTags.ITEXT);
            writeAddAttribute(ElementTags.TITLE, header.getTitle());
            writeAddAttribute(ElementTags.AUTHOR, header.getAuthors());
            writeAddAttribute(ElementTags.CREATIONDATE, header.getDate());
            writeAddAttribute(ElementTags.SUBJECT, header.getTitle());
            writeAddAttribute(ElementTags.KEYWORDS, "");
            writeAddAttribute(ElementTags.PRODUCER, "Generated with Doxia by " + System.getProperty("user.name"));
            writeAddAttribute(ElementTags.PAGE_SIZE, ITextUtil.getPageSize(ITextUtil.getDefaultPageSize()));

            writeStartElement(ElementTags.CHAPTER);
            writeAddAttribute(ElementTags.NUMBERDEPTH, numberDepth);
            writeAddAttribute(ElementTags.DEPTH, depth);
            writeAddAttribute(ElementTags.INDENT, "0.0");

            writeStartElement(ElementTags.TITLE);
            writeAddAttribute(ElementTags.LEADING, DEFAULT_CHAPTER_TITLE_LEADING);
            writeAddAttribute(ElementTags.FONT, ITextFont.DEFAULT_FONT_NAME);
            writeAddAttribute(ElementTags.SIZE, ITextFont.getSectionFontSize(0));
            writeAddAttribute(ElementTags.STYLE, ITextFont.BOLD);
            writeAddAttribute(ElementTags.BLUE, ITextFont.DEFAULT_FONT_COLOR_BLUE);
            writeAddAttribute(ElementTags.GREEN, ITextFont.DEFAULT_FONT_COLOR_GREEN);
            writeAddAttribute(ElementTags.RED, ITextFont.DEFAULT_FONT_COLOR_RED);
            writeAddAttribute(ElementTags.ALIGN, ElementTags.ALIGN_CENTER);

            //            startChunk( ITextFont.DEFAULT_FONT_NAME, ITextFont.getSectionFontSize( 0 ),
            //                    ITextFont.BOLD, ITextFont.DEFAULT_FONT_COLOR_BLUE, ITextFont.DEFAULT_FONT_COLOR_GREEN,
            //                    ITextFont.DEFAULT_FONT_COLOR_RED, "top" );

            writeStartElement(ElementTags.CHUNK);
            writeAddAttribute(ElementTags.FONT, ITextFont.DEFAULT_FONT_NAME);
            writeAddAttribute(ElementTags.SIZE, ITextFont.getSectionFontSize(0));
            writeAddAttribute(ElementTags.STYLE, ITextFont.BOLD);
            writeAddAttribute(ElementTags.BLUE, ITextFont.DEFAULT_FONT_COLOR_BLUE);
            writeAddAttribute(ElementTags.GREEN, ITextFont.DEFAULT_FONT_COLOR_GREEN);
            writeAddAttribute(ElementTags.RED, ITextFont.DEFAULT_FONT_COLOR_RED);
            //            writeAddAttribute( ElementTags.LOCALDESTINATION, "top" );

            write(header.getTitle());

            writeEndElement(); // ElementTags.CHUNK

            writeEndElement(); // ElementTags.TITLE
        }

        actionContext.setAction(SinkActionContext.BODY);
    }

    // ----------------------------------------------------------------------
    // Sections
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void sectionTitle() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void sectionTitle_() {
        actionContext.setAction(SinkActionContext.SECTION_TITLE);
    }

    /** {@inheritDoc} */
    public void section1_() {
        writeEndElement(); // ElementTags.SECTION

        numberDepth--;
        depth = 0;

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void section1() {
        numberDepth++;
        depth = 1;

        writeStartElement(ElementTags.SECTION);
        writeAddAttribute(ElementTags.NUMBERDEPTH, numberDepth);
        writeAddAttribute(ElementTags.DEPTH, depth);
        writeAddAttribute(ElementTags.INDENT, "0.0");

        lineBreak();

        actionContext.setAction(SinkActionContext.SECTION_1);
    }

    /** {@inheritDoc} */
    public void sectionTitle1_() {
        writeEndElement(); // ElementTags.TITLE

        font.setSize(ITextFont.DEFAULT_FONT_SIZE);
        bold_();

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void sectionTitle1() {
        font.setSize(ITextFont.getSectionFontSize(1));
        font.setColor(Color.BLACK);
        bold();

        writeStartElement(ElementTags.TITLE);
        writeAddAttribute(ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());
        //        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve

        actionContext.setAction(SinkActionContext.SECTION_TITLE_1);
    }

    /** {@inheritDoc} */
    public void section2_() {
        writeEndElement(); // ElementTags.SECTION

        numberDepth--;
        depth = 0;

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void section2() {
        numberDepth++;
        depth = 1;

        writeStartElement(ElementTags.SECTION);
        writeAddAttribute(ElementTags.NUMBERDEPTH, numberDepth);
        writeAddAttribute(ElementTags.DEPTH, depth);
        writeAddAttribute(ElementTags.INDENT, "0.0");

        lineBreak();

        actionContext.setAction(SinkActionContext.SECTION_2);
    }

    /** {@inheritDoc} */
    public void sectionTitle2_() {
        writeEndElement(); // ElementTags.TITLE

        font.setSize(ITextFont.DEFAULT_FONT_SIZE);
        bold_();

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void sectionTitle2() {
        font.setSize(ITextFont.getSectionFontSize(2));
        font.setColor(Color.BLACK);
        bold();

        writeStartElement(ElementTags.TITLE);
        writeAddAttribute(ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());
        //        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve

        actionContext.setAction(SinkActionContext.SECTION_TITLE_2);
    }

    /** {@inheritDoc} */
    public void section3_() {
        writeEndElement(); // ElementTags.SECTION

        numberDepth--;
        depth = 1;

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void section3() {
        numberDepth++;
        depth = 1;

        writeStartElement(ElementTags.SECTION);
        writeAddAttribute(ElementTags.NUMBERDEPTH, numberDepth);
        writeAddAttribute(ElementTags.DEPTH, depth);
        writeAddAttribute(ElementTags.INDENT, "0.0");

        lineBreak();

        actionContext.setAction(SinkActionContext.SECTION_3);
    }

    /** {@inheritDoc} */
    public void sectionTitle3_() {
        writeEndElement(); // ElementTags.TITLE

        font.setSize(ITextFont.DEFAULT_FONT_SIZE);
        bold_();

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void sectionTitle3() {
        font.setSize(ITextFont.getSectionFontSize(3));
        font.setColor(Color.BLACK);
        bold();

        writeStartElement(ElementTags.TITLE);
        writeAddAttribute(ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());
        //        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve

        actionContext.setAction(SinkActionContext.SECTION_TITLE_3);
    }

    /** {@inheritDoc} */
    public void section4_() {
        writeEndElement(); // ElementTags.SECTION

        numberDepth--;
        depth = 1;

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void section4() {
        numberDepth++;
        depth = 1;

        writeStartElement(ElementTags.SECTION);
        writeAddAttribute(ElementTags.NUMBERDEPTH, numberDepth);
        writeAddAttribute(ElementTags.DEPTH, depth);
        writeAddAttribute(ElementTags.INDENT, "0.0");

        lineBreak();

        actionContext.setAction(SinkActionContext.SECTION_4);
    }

    /** {@inheritDoc} */
    public void sectionTitle4_() {
        writeEndElement(); // ElementTags.TITLE

        font.setSize(ITextFont.DEFAULT_FONT_SIZE);
        bold_();

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void sectionTitle4() {
        font.setSize(ITextFont.getSectionFontSize(4));
        font.setColor(Color.BLACK);
        bold();

        writeStartElement(ElementTags.TITLE);
        writeAddAttribute(ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());
        //        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve

        actionContext.setAction(SinkActionContext.SECTION_TITLE_4);
    }

    /** {@inheritDoc} */
    public void section5_() {
        writeEndElement(); // ElementTags.SECTION

        numberDepth--;
        depth = 1;

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void section5() {
        numberDepth++;
        depth = 1;

        writeStartElement(ElementTags.SECTION);
        writeAddAttribute(ElementTags.NUMBERDEPTH, numberDepth);
        writeAddAttribute(ElementTags.DEPTH, depth);
        writeAddAttribute(ElementTags.INDENT, "0.0");

        lineBreak();

        actionContext.setAction(SinkActionContext.SECTION_5);
    }

    /** {@inheritDoc} */
    public void sectionTitle5_() {
        writeEndElement(); // ElementTags.TITLE

        font.setSize(ITextFont.DEFAULT_FONT_SIZE);
        bold_();

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void sectionTitle5() {
        font.setSize(ITextFont.getSectionFontSize(5));
        font.setColor(Color.BLACK);
        bold();

        writeStartElement(ElementTags.TITLE);
        writeAddAttribute(ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());
        //        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve

        actionContext.setAction(SinkActionContext.SECTION_TITLE_5);
    }

    // ----------------------------------------------------------------------
    // Paragraph
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void paragraph_() {
        // Special case
        if ((actionContext.getCurrentAction() == SinkActionContext.LIST_ITEM)
                || (actionContext.getCurrentAction() == SinkActionContext.NUMBERED_LIST_ITEM)
                || (actionContext.getCurrentAction() == SinkActionContext.DEFINITION)) {
            return;
        }

        writeEndElement(); // ElementTags.PARAGRAPH

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void paragraph() {
        // Special case
        if ((actionContext.getCurrentAction() == SinkActionContext.LIST_ITEM)
                || (actionContext.getCurrentAction() == SinkActionContext.NUMBERED_LIST_ITEM)
                || (actionContext.getCurrentAction() == SinkActionContext.DEFINITION)) {
            return;
        }

        writeStartElement(ElementTags.PARAGRAPH);
        writeStartElement(ElementTags.NEWLINE);
        writeEndElement();

        actionContext.setAction(SinkActionContext.PARAGRAPH);
    }

    // ----------------------------------------------------------------------
    // Lists
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void list_() {
        writeEndElement(); // ElementTags.LIST

        writeEndElement(); // ElementTags.CHUNK

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void list() {
        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        writeStartElement(ElementTags.LIST);
        writeAddAttribute(ElementTags.NUMBERED, Boolean.FALSE.toString());
        writeAddAttribute(ElementTags.SYMBOLINDENT, "15");

        actionContext.setAction(SinkActionContext.LIST);
    }

    /** {@inheritDoc} */
    public void listItem_() {
        writeEndElement(); // ElementTags.LISTITEM

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void listItem() {
        writeStartElement(ElementTags.LISTITEM);
        writeAddAttribute(ElementTags.INDENTATIONLEFT, "20.0");

        actionContext.setAction(SinkActionContext.LIST_ITEM);
    }

    /** {@inheritDoc} */
    public void numberedList_() {
        writeEndElement(); // ElementTags.LIST

        writeEndElement(); // ElementTags.CHUNK

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void numberedList(int numbering) {
        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        writeStartElement(ElementTags.LIST);
        writeAddAttribute(ElementTags.NUMBERED, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.SYMBOLINDENT, "20");

        switch (numbering) {
        case Sink.NUMBERING_UPPER_ALPHA:
            writeAddAttribute(ElementTags.LETTERED, Boolean.TRUE.toString());
            writeAddAttribute(ElementTags.FIRST, 'A');
            break;

        case Sink.NUMBERING_LOWER_ALPHA:
            writeAddAttribute(ElementTags.LETTERED, Boolean.TRUE.toString());
            writeAddAttribute(ElementTags.FIRST, 'a');
            break;

        // TODO Doesn't work
        case Sink.NUMBERING_UPPER_ROMAN:
            writeAddAttribute(ElementTags.LETTERED, Boolean.TRUE.toString());
            writeAddAttribute(ElementTags.FIRST, 'I');
            break;

        case Sink.NUMBERING_LOWER_ROMAN:
            writeAddAttribute(ElementTags.LETTERED, Boolean.TRUE.toString());
            writeAddAttribute(ElementTags.FIRST, 'i');
            break;

        case Sink.NUMBERING_DECIMAL:
        default:
            writeAddAttribute(ElementTags.LETTERED, Boolean.FALSE.toString());
        }

        actionContext.setAction(SinkActionContext.NUMBERED_LIST);
    }

    /** {@inheritDoc} */
    public void numberedListItem_() {
        writeEndElement(); // ElementTags.LISTITEM

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void numberedListItem() {
        writeStartElement(ElementTags.LISTITEM);
        writeAddAttribute(ElementTags.INDENTATIONLEFT, "20");

        actionContext.setAction(SinkActionContext.NUMBERED_LIST_ITEM);
    }

    /** {@inheritDoc} */
    public void definitionList_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void definitionList() {
        lineBreak();

        actionContext.setAction(SinkActionContext.DEFINITION_LIST);
    }

    /** {@inheritDoc} */
    public void definedTerm_() {
        font.setSize(ITextFont.DEFAULT_FONT_SIZE);
        bold_();

        writeEndElement(); // ElementTags.CHUNK

        actionContext.release();

        lineBreak();
    }

    /** {@inheritDoc} */
    public void definedTerm() {
        font.setSize(ITextFont.DEFAULT_FONT_SIZE + 2);
        bold();

        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        actionContext.setAction(SinkActionContext.DEFINED_TERM);
    }

    /** {@inheritDoc} */
    public void definition_() {
        writeEndElement(); // ElementTags.CHUNK

        actionContext.release();

        lineBreak();
    }

    /** {@inheritDoc} */
    public void definition() {
        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        // We need to add a non break space first to display empty string
        write("\u00A0" + StringUtils.repeat(" ", 16), false, false);

        writeEndElement(); // ElementTags.CHUNK

        actionContext.setAction(SinkActionContext.DEFINITION);
    }

    /** {@inheritDoc} */
    public void definitionListItem_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void definitionListItem() {
        actionContext.setAction(SinkActionContext.DEFINITION_LIST_ITEM);
    }

    // ----------------------------------------------------------------------
    //  Tables
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void table_() {
        if (tableCaptionXMLWriter != null) {
            tableCaptionXMLWriter = null;

            writeEndElement(); // ElementTags.TABLE

            writeEndElement(); // ElementTags.CHUNK

            writeStartElement(ElementTags.PARAGRAPH);
            writeAddAttribute(ElementTags.ALIGN, ElementTags.ALIGN_CENTER);

            write(tableCaptionWriter.toString(), true);

            writeEndElement(); // ElementTags.PARAGRAPH

            tableCaptionWriter = null;
        } else {
            writeEndElement(); // ElementTags.TABLE

            writeEndElement(); // ElementTags.CHUNK
        }
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void table() {
        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        writeStartElement(ElementTags.TABLE);
        writeAddAttribute(ElementTags.LEFT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.RIGHT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.TOP, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.BOTTOM, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.ALIGN, ElementTags.ALIGN_CENTER);
        writeAddAttribute(ElementTags.WIDTH, "100.0%");
        writeAddAttribute(ElementTags.TABLEFITSPAGE, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.CELLSFITPAGE, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.CELLPADDING, "10");
        //writeAddAttribute( ElementTags.COLUMNS, "2" );

        actionContext.setAction(SinkActionContext.TABLE);
    }

    /** {@inheritDoc} */
    public void tableCaption_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void tableCaption() {
        tableCaptionWriter = new StringWriter();
        tableCaptionXMLWriter = new PrettyPrintXMLWriter(tableCaptionWriter);
        actionContext.setAction(SinkActionContext.TABLE_CAPTION);
    }

    /** {@inheritDoc} */
    public void tableCell_() {
        writeEndElement(); // ElementTags.CELL

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void tableCell() {
        writeStartElement(ElementTags.CELL);
        writeAddAttribute(ElementTags.LEFT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.RIGHT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.TOP, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.BOTTOM, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.HORIZONTALALIGN, ElementTags.ALIGN_LEFT);

        actionContext.setAction(SinkActionContext.TABLE_CELL);
    }

    /** {@inheritDoc} */
    public void tableCell(String width) {
        actionContext.setAction(SinkActionContext.TABLE_CELL);
    }

    /** {@inheritDoc} */
    public void tableHeaderCell_() {
        writeEndElement(); // ElementTags.CELL

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void tableHeaderCell() {
        writeStartElement(ElementTags.CELL);
        writeAddAttribute(ElementTags.LEFT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.RIGHT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.TOP, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.BOTTOM, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.HEADER, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.BGRED, Color.GRAY.getRed());
        writeAddAttribute(ElementTags.BGBLUE, Color.GRAY.getBlue());
        writeAddAttribute(ElementTags.BGGREEN, Color.GRAY.getGreen());
        writeAddAttribute(ElementTags.HORIZONTALALIGN, ElementTags.ALIGN_CENTER);

        actionContext.setAction(SinkActionContext.TABLE_HEADER_CELL);
    }

    /** {@inheritDoc} */
    public void tableHeaderCell(String width) {
        actionContext.setAction(SinkActionContext.TABLE_HEADER_CELL);
    }

    /** {@inheritDoc} */
    public void tableRow_() {
        writeEndElement(); // ElementTags.ROW

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void tableRow() {
        writeStartElement(ElementTags.ROW);

        actionContext.setAction(SinkActionContext.TABLE_ROW);
    }

    /** {@inheritDoc} */
    public void tableRows_() {
        //writeEndElement(); // ElementTags.TABLE

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void tableRows(int[] justification, boolean grid) {
        // ElementTags.TABLE
        writeAddAttribute(ElementTags.COLUMNS, justification.length);

        actionContext.setAction(SinkActionContext.TABLE_ROWS);
    }

    // ----------------------------------------------------------------------
    // Verbatim
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void verbatim_() {
        writeEndElement(); // ElementTags.CELL

        writeEndElement(); // ElementTags.ROW

        writeEndElement(); // ElementTags.TABLE

        writeEndElement(); // ElementTags.CHUNK

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void verbatim(boolean boxed) {
        // Always boxed
        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        writeStartElement(ElementTags.TABLE);
        writeAddAttribute(ElementTags.COLUMNS, "1");
        writeAddAttribute(ElementTags.LEFT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.RIGHT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.TOP, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.BOTTOM, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.ALIGN, ElementTags.ALIGN_CENTER);
        writeAddAttribute(ElementTags.TABLEFITSPAGE, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.CELLSFITPAGE, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.CELLPADDING, "10");
        writeAddAttribute(ElementTags.WIDTH, "100.0%");

        writeStartElement(ElementTags.ROW);

        writeStartElement(ElementTags.CELL);
        writeAddAttribute(ElementTags.LEFT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.RIGHT, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.TOP, Boolean.TRUE.toString());
        writeAddAttribute(ElementTags.BOTTOM, Boolean.TRUE.toString());

        actionContext.setAction(SinkActionContext.VERBATIM);
    }

    // ----------------------------------------------------------------------
    // Figures
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void figure_() {
        writeEndElement(); // ElementTags.IMAGE

        writeEndElement(); // ElementTags.CHUNK

        actionContext.release();

        figureDefined = false;
    }

    /** {@inheritDoc} */
    public void figure() {
        figureDefined = true;

        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        writeStartElement(ElementTags.IMAGE);

        actionContext.setAction(SinkActionContext.FIGURE);
    }

    /** {@inheritDoc} */
    public void figureCaption_() {
        actionContext.release();
    }

    /** {@inheritDoc} */
    public void figureCaption() {
        actionContext.setAction(SinkActionContext.FIGURE_CAPTION);
    }

    /**
     * If the <code>name</code> is a relative link, the internal link will used a System property
     * <code>itext.basedir</code>, or the class loader.
     * {@inheritDoc}
     */
    public void figureGraphics(String name) {
        String urlName = null;
        File nameFile = null;
        if ((name.toLowerCase(Locale.ENGLISH).startsWith("http://"))
                || (name.toLowerCase(Locale.ENGLISH).startsWith("https://"))) {
            urlName = name;
        } else {
            if (System.getProperty("itext.basedir") != null) {
                try {
                    nameFile = new File(System.getProperty("itext.basedir"), name);
                    urlName = nameFile.toURI().toURL().toString();
                } catch (MalformedURLException e) {
                    getLog().error("MalformedURLException: " + e.getMessage(), e);
                }
            } else {
                if (getClassLoader() != null) {
                    if (getClassLoader().getResource(name) != null) {
                        urlName = getClassLoader().getResource(name).toString();
                    }
                } else {
                    if (ITextSink.class.getClassLoader().getResource(name) != null) {
                        urlName = ITextSink.class.getClassLoader().getResource(name).toString();
                    }
                }
            }
        }

        if (urlName == null) {
            String msg = "No image '" + name
                    + "' found in the class loader. Try to call setClassLoader(ClassLoader) before.";
            logMessage("imageNotFound", msg);

            return;
        }

        if (nameFile != null && !nameFile.exists()) {
            String msg = "No image '" + nameFile + "' found in your system, check the path.";
            logMessage("imageNotFound", msg);

            return;
        }

        boolean figureCalled = figureDefined;
        if (!figureCalled) {
            figure();
        }

        float width = 0;
        float height = 0;
        try {
            Image image = Image.getInstance(new URL(urlName));
            image.scaleToFit(ITextUtil.getDefaultPageSize().width() / 2,
                    ITextUtil.getDefaultPageSize().height() / 2);
            width = image.plainWidth();
            height = image.plainHeight();
        } catch (BadElementException e) {
            getLog().error("BadElementException: " + e.getMessage(), e);
        } catch (MalformedURLException e) {
            getLog().error("MalformedURLException: " + e.getMessage(), e);
        } catch (IOException e) {
            getLog().error("IOException: " + e.getMessage(), e);
        }

        writeAddAttribute(ElementTags.URL, urlName);
        writeAddAttribute(ElementTags.ALIGN, ElementTags.ALIGN_MIDDLE);
        writeAddAttribute(ElementTags.PLAINWIDTH, String.valueOf(width));
        writeAddAttribute(ElementTags.PLAINHEIGHT, String.valueOf(height));

        actionContext.setAction(SinkActionContext.FIGURE_GRAPHICS);

        if (!figureCalled) {
            figure_();
        }
    }

    // ----------------------------------------------------------------------
    // Fonts
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void inline() {
        inline(null);
    }

    /** {@inheritDoc} */
    public void inline(SinkEventAttributes attributes) {
        List<String> tags = new ArrayList<>();

        if (attributes != null) {

            if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "italic")) {
                font.addItalic();
                tags.add(0, "italic");
            }

            if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "bold")) {
                font.addBold();
                tags.add(0, "bold");
            }

            if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "code")) {
                font.setMonoSpaced(true);
                tags.add(0, "code");
            }

        }

        inlineStack.push(tags);
    }

    /** {@inheritDoc} */
    public void inline_() {
        for (String tag : inlineStack.pop()) {
            if ("italic".equals(tag)) {
                font.removeItalic();
            } else if ("bold".equals(tag)) {
                font.removeBold();
            } else if ("code".equals(tag)) {
                font.setMonoSpaced(false);
            }
        }
    }

    /** {@inheritDoc} */
    public void bold_() {
        inline_();
    }

    /** {@inheritDoc} */
    public void bold() {
        inline(SinkEventAttributeSet.Semantics.BOLD);
    }

    /** {@inheritDoc} */
    public void italic_() {
        inline_();
    }

    /** {@inheritDoc} */
    public void italic() {
        inline(SinkEventAttributeSet.Semantics.ITALIC);
    }

    /** {@inheritDoc} */
    public void monospaced_() {
        inline_();
    }

    /** {@inheritDoc} */
    public void monospaced() {
        inline(SinkEventAttributeSet.Semantics.CODE);
    }

    // ----------------------------------------------------------------------
    // Links
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void link_() {
        writeEndElement(); // ElementTags.ANCHOR

        font.setColor(Color.BLACK);
        font.removeUnderlined();

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void link(String name) {
        if (name == null) {
            throw new NullPointerException("Link name cannot be null!");
        }

        font.setColor(Color.BLUE);
        font.addUnderlined();

        writeStartElement(ElementTags.ANCHOR);
        if (StringUtils.isNotEmpty(name) && name.startsWith("#") && StringUtils.isNotEmpty(header.getTitle())) {
            name = "#" + DoxiaUtils.encodeId(header.getTitle(), true) + "_" + name.substring(1);
        }
        writeAddAttribute(ElementTags.REFERENCE, HtmlTools.escapeHTML(name));
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        actionContext.setAction(SinkActionContext.LINK);
    }

    /** {@inheritDoc} */
    public void anchor_() {
        if (!anchorDefined) {
            // itext needs a defined local destination, we put an invisible text
            writeAddAttribute(ElementTags.BLUE, "255");
            writeAddAttribute(ElementTags.GREEN, "255");
            writeAddAttribute(ElementTags.RED, "255");

            write("_");
        }

        anchorDefined = false;

        writeEndElement(); // ElementTags.ANCHOR

        actionContext.release();
    }

    /** {@inheritDoc} */
    public void anchor(String name) {
        if (name == null) {
            throw new NullPointerException("Anchor name cannot be null!");
        }

        if (StringUtils.isNotEmpty(header.getTitle())) {
            name = header.getTitle() + "_" + name;
        }
        String id = name;

        if (!DoxiaUtils.isValidId(id)) {
            id = DoxiaUtils.encodeId(name, true);

            String msg = "Modified invalid link: '" + name + "' to '" + id + "'";
            logMessage("modifiedLink", msg);
        }

        writeStartElement(ElementTags.ANCHOR);
        writeAddAttribute(ElementTags.NAME, id);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());

        actionContext.setAction(SinkActionContext.ANCHOR);
    }

    // ----------------------------------------------------------------------
    // Misc
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void lineBreak() {
        // Special case for the header
        if ((actionContext.getCurrentAction() == SinkActionContext.AUTHOR)
                || (actionContext.getCurrentAction() == SinkActionContext.DATE)
                || (actionContext.getCurrentAction() == SinkActionContext.TITLE)) {
            return;
        }

        writeStartElement(ElementTags.NEWLINE);
        writeEndElement();
    }

    /** {@inheritDoc} */
    public void nonBreakingSpace() {
        write(" ");
    }

    /** {@inheritDoc} */
    public void pageBreak() {
        writeStartElement(ElementTags.NEWPAGE);
        writeEndElement();
    }

    /** {@inheritDoc} */
    public void horizontalRule() {
        writeStartElement(ElementTags.PARAGRAPH);
        writeAddAttribute(ElementTags.BLUE, "255");
        writeAddAttribute(ElementTags.GREEN, "255");
        writeAddAttribute(ElementTags.RED, "255");
        write("_");
        writeEndElement();

        writeStartElement(ElementTags.PARAGRAPH);
        writeStartElement(ElementTags.HORIZONTALRULE);
        writeEndElement();
        writeEndElement();
    }

    // ----------------------------------------------------------------------
    // Text
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public void rawText(String text) {
        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, font.getFontName());
        writeAddAttribute(ElementTags.SIZE, font.getFontSize());
        writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
        writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
        writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
        writeAddAttribute(ElementTags.RED, font.getFontColorRed());

        write(text, false);

        writeEndElement(); // ElementTags.CHUNK
    }

    /** {@inheritDoc} */
    public void text(String text) {
        if (StringUtils.isEmpty(text)) {
            return;
        }

        switch (actionContext.getCurrentAction()) {
        case SinkActionContext.AUTHOR:
            header.addAuthor(text);
            break;

        case SinkActionContext.DATE:
            header.setDate(text);
            break;

        case SinkActionContext.TITLE:
            header.setTitle(text);
            break;

        case SinkActionContext.TABLE_CAPTION:
            this.tableCaptionXMLWriter.writeText(text);
            break;

        case SinkActionContext.VERBATIM:
            // Used to preserve indentation and formating
            LineNumberReader lnr = new LineNumberReader(new StringReader(text));
            String line;
            try {
                while ((line = lnr.readLine()) != null) {
                    writeStartElement(ElementTags.CHUNK);
                    writeAddAttribute(ElementTags.FONT, font.getFontName());
                    writeAddAttribute(ElementTags.SIZE, font.getFontSize());
                    writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
                    writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
                    writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
                    writeAddAttribute(ElementTags.RED, font.getFontColorRed());

                    write("<![CDATA[", true);
                    // Special case
                    line = StringUtils.replace(line, "<![CDATA[", "< ![CDATA[");
                    line = StringUtils.replace(line, "]]>", "]] >");
                    write(line, true, false);
                    write("]]>", true);

                    writeEndElement();
                    lineBreak();
                }
            } catch (IOException e) {
                throw new RuntimeException("IOException: ", e);
            }
            break;

        case SinkActionContext.FIGURE_CAPTION:
            writeAddAttribute(ElementTags.ALT, text);
            break;

        case SinkActionContext.SECTION_TITLE:
        case SinkActionContext.SECTION_1:
        case SinkActionContext.SECTION_2:
        case SinkActionContext.SECTION_3:
        case SinkActionContext.SECTION_4:
        case SinkActionContext.SECTION_5:
        case SinkActionContext.FIGURE:
        case SinkActionContext.FIGURE_GRAPHICS:
        case SinkActionContext.TABLE_ROW:
        case SinkActionContext.TABLE:
        case SinkActionContext.HEAD:
        case SinkActionContext.UNDEFINED:
            break;

        case SinkActionContext.ANCHOR:
            anchorDefined = true;
        case SinkActionContext.PARAGRAPH:
        case SinkActionContext.LINK:
        case SinkActionContext.TABLE_CELL:
        case SinkActionContext.TABLE_HEADER_CELL:
        case SinkActionContext.DEFINITION:
        case SinkActionContext.DEFINED_TERM:
        case SinkActionContext.NUMBERED_LIST_ITEM:
        case SinkActionContext.LIST_ITEM:
        case SinkActionContext.SECTION_TITLE_5:
        case SinkActionContext.SECTION_TITLE_4:
        case SinkActionContext.SECTION_TITLE_3:
        case SinkActionContext.SECTION_TITLE_2:
        case SinkActionContext.SECTION_TITLE_1:
        default:
            writeStartElement(ElementTags.CHUNK);
            writeAddAttribute(ElementTags.FONT, font.getFontName());
            writeAddAttribute(ElementTags.SIZE, font.getFontSize());
            writeAddAttribute(ElementTags.STYLE, font.getFontStyle());
            writeAddAttribute(ElementTags.BLUE, font.getFontColorBlue());
            writeAddAttribute(ElementTags.GREEN, font.getFontColorGreen());
            writeAddAttribute(ElementTags.RED, font.getFontColorRed());

            write(text);

            writeEndElement(); // ElementTags.CHUNK
        }
    }

    /**
     * {@inheritDoc}
     *
     * Unkown events just log a warning message but are ignored otherwise.
     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
     */
    public void unknown(String name, Object[] requiredParams, SinkEventAttributes attributes) {
        String msg = "Unknown Sink event: '" + name + "', ignoring!";
        logMessage("unknownEvent", msg);
    }

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

        this.actionContext = new SinkActionContext();
        this.font = new ITextFont();
        this.header = new ITextHeader();

        this.numberDepth = 1;
        this.depth = 0;
        this.tableCaptionWriter = null;
        this.tableCaptionXMLWriter = null;
        this.anchorDefined = false;
        this.figureDefined = false;
        this.warnMessages = null;
    }

    /**
     * Convenience method to write a starting element.
     *
     * @param tag the name of the tag
     */
    private void writeStartElement(String tag) {
        if (tableCaptionXMLWriter == null) {
            xmlWriter.startElement(tag);
        } else {
            tableCaptionXMLWriter.startElement(tag);
        }
    }

    /**
     * Convenience method to write a key-value pair.
     *
     * @param key the name of an attribute
     * @param value the value of an attribute
     */
    private void writeAddAttribute(String key, String value) {
        if (tableCaptionXMLWriter == null) {
            xmlWriter.addAttribute(key, value);
        } else {
            tableCaptionXMLWriter.addAttribute(key, value);
        }
    }

    /**
     * Convenience method to write a key-value pair.
     *
     * @param key the name of an attribute
     * @param value the value of an attribute
     */
    private void writeAddAttribute(String key, int value) {
        if (tableCaptionXMLWriter == null) {
            xmlWriter.addAttribute(key, String.valueOf(value));
        } else {
            tableCaptionXMLWriter.addAttribute(key, String.valueOf(value));
        }
    }

    /**
     * Convenience method to write an end element.
     */
    private void writeEndElement() {
        if (tableCaptionXMLWriter == null) {
            xmlWriter.endElement();
        } else {
            tableCaptionXMLWriter.endElement();
        }
    }

    /**
     * Convenience method to write a String
     *
     * @param aString
     */
    protected void write(String aString) {
        write(aString, false);
    }

    /**
     * Convenience method to write a String depending the escapeHtml flag
     *
     * @param aString
     * @param escapeHtml
     */
    private void write(String aString, boolean escapeHtml) {
        write(aString, escapeHtml, true);
    }

    /**
     * Convenience method to write a String depending the escapeHtml flag
     *
     * @param aString
     * @param escapeHtml
     * @param trim
     */
    private void write(String aString, boolean escapeHtml, boolean trim) {
        if (aString == null) {
            return;
        }

        if (trim) {
            aString = StringUtils.replace(aString, "\n", "");

            LineNumberReader lnr = new LineNumberReader(new StringReader(aString));
            StringBuilder sb = new StringBuilder();
            String line;
            try {
                while ((line = lnr.readLine()) != null) {
                    sb.append(beautifyPhrase(line.trim()));
                    sb.append(" ");
                }

                aString = sb.toString();
            } catch (IOException e) {
                // nop
            }
            if (aString.trim().length() == 0) {
                return;
            }
        }
        if (escapeHtml) {
            if (tableCaptionXMLWriter == null) {
                xmlWriter.writeMarkup(aString);
            } else {
                tableCaptionXMLWriter.writeMarkup(aString);
            }
        } else {
            if (tableCaptionXMLWriter == null) {
                xmlWriter.writeText(aString);
            } else {
                tableCaptionXMLWriter.writeText(aString);
            }
        }
    }

    /**
     * Convenience method to return a beautify phrase, i.e. one space between words.
     *
     * @param aString
     * @return a String with only one space between words
     */
    private static String beautifyPhrase(String aString) {
        String[] strings = StringUtils.split(aString, " ");
        StringBuilder sb = new StringBuilder();
        for (String string : strings) {
            if (string.trim().length() != 0) {
                sb.append(string.trim());
                sb.append(" ");
            }
        }

        return sb.toString().trim();
    }

    private void startChunk(String fontName, int fontSize, String fontStyle, int fontColorBlue, int fontColorGreen,
            int fontColorRed, String localDestination) {
        writeStartElement(ElementTags.CHUNK);
        writeAddAttribute(ElementTags.FONT, fontName);
        writeAddAttribute(ElementTags.SIZE, fontSize);
        writeAddAttribute(ElementTags.STYLE, fontStyle);
        writeAddAttribute(ElementTags.BLUE, fontColorBlue);
        writeAddAttribute(ElementTags.GREEN, fontColorGreen);
        writeAddAttribute(ElementTags.RED, fontColorRed);
        //        writeAddAttribute( ElementTags.LOCALDESTINATION, localDestination );
    }

    /**
     * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
     *
     * @param key not null
     * @param msg not null
     * @see #close()
     * @since 1.1.1
     */
    private void logMessage(String key, String msg) {
        msg = "[iText Sink] " + msg;
        if (getLog().isDebugEnabled()) {
            getLog().debug(msg);

            return;
        }

        if (warnMessages == null) {
            warnMessages = new HashMap<>();
        }

        Set<String> set = warnMessages.get(key);
        if (set == null) {
            set = new TreeSet<>();
        }
        set.add(msg);
        warnMessages.put(key, set);
    }
}