001package org.apache.maven.doxia.module.itext;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import com.lowagie.text.BadElementException;
023import com.lowagie.text.ElementTags;
024import com.lowagie.text.Image;
025
026import java.awt.Color;
027import java.io.File;
028import java.io.IOException;
029import java.io.LineNumberReader;
030import java.io.StringReader;
031import java.io.StringWriter;
032import java.io.Writer;
033import java.net.MalformedURLException;
034import java.net.URL;
035import java.util.HashMap;
036import java.util.Locale;
037import java.util.Map;
038import java.util.Set;
039import java.util.TreeSet;
040
041import org.apache.maven.doxia.sink.Sink;
042import org.apache.maven.doxia.sink.SinkEventAttributes;
043import org.apache.maven.doxia.sink.impl.AbstractXmlSink;
044import org.apache.maven.doxia.util.DoxiaUtils;
045import org.apache.maven.doxia.util.HtmlTools;
046
047import org.codehaus.plexus.util.IOUtil;
048import org.codehaus.plexus.util.StringUtils;
049import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
050import org.codehaus.plexus.util.xml.XMLWriter;
051
052/**
053 * <p>A doxia Sink which produces an XML Front End document for <code>iText</code> framework.</p>
054 * Known limitations:
055 * <ul>
056 * <li>Roman lists are not supported.</li>
057 * <li>Horizontal rule is not supported with 1.3.
058 * See <a href="http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg10323.html">
059 * http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg10323.html</a></li>
060 * <li>iText has some problems with <code>ElementTags.TABLE</code> and <code>ElementTags.TABLEFITSPAGE</code>.
061 * See http://sourceforge.net/tracker/index.php?func=detail&aid=786427&group_id=15255&atid=115255.</li>
062 * <li>Images could be on another page and next text on the last one.</li>
063 * </ul>
064 *
065 * @see <a href="http://www.lowagie.com/iText/tutorial/ch07.html">http://www.lowagie.com/iText/tutorial/ch07.html</a>
066 *
067 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
068 * @version $Id: ITextSink.html 979316 2016-02-02 21:51:43Z hboutemy $
069 */
070public class ITextSink
071    extends AbstractXmlSink
072{
073    /** This is the place where the iText DTD is located. IMPORTANT: this DTD is not uptodate! */
074    public static final String DTD = "http://itext.sourceforge.net/itext.dtd";
075
076    /** This is the reference to the DTD. */
077    public static final String DOCTYPE = "ITEXT SYSTEM \"" + DTD + "\"";
078
079    /** This is the default leading for chapter title */
080    public static final String DEFAULT_CHAPTER_TITLE_LEADING = "36.0";
081
082    /** This is the default leading for section title */
083    public static final String DEFAULT_SECTION_TITLE_LEADING = "24.0";
084
085    /** The ClassLoader used */
086    private ClassLoader currentClassLoader;
087
088    /** The action context */
089    private SinkActionContext actionContext;
090
091    /** The Writer used */
092    private Writer writer;
093
094    /** The XML Writer used */
095    private final XMLWriter xmlWriter;
096
097    private boolean writeStart;
098
099    /** The Header object */
100    private ITextHeader header;
101
102    /** The font object */
103    private ITextFont font;
104
105    private int numberDepth = 1;
106
107    private int depth = 0;
108
109    private StringWriter tableCaptionWriter = null;
110
111    private XMLWriter tableCaptionXMLWriter = null;
112
113    /** Flag to know if an anchor is defined or not. Used as workaround for iText which needs a defined local
114     * destination. */
115    private boolean anchorDefined = false;
116
117    /** Flag to know if an figure event is called. */
118    private boolean figureDefined = false;
119
120    /** Map of warn messages with a String as key to describe the error type and a Set as value.
121     * Using to reduce warn messages. */
122    private Map<String, Set<String>> warnMessages;
123
124    /**
125     * <p>Constructor for ITextSink.</p>
126     *
127     * @param writer the writer.
128     */
129    protected ITextSink( Writer writer )
130    {
131        this( writer, "UTF-8" );
132    }
133
134    /**
135     * <p>Constructor for ITextSink.</p>
136     *
137     * @param writer the writer.
138     * @param encoding the encoding.
139     * @since 1.1
140     */
141    protected ITextSink( Writer writer, String encoding )
142    {
143        // No doctype since itext doctype is not up to date!
144        this( new PrettyPrintXMLWriter( writer, encoding, null ) );
145
146        this.writer = writer;
147        this.writeStart = true;
148    }
149
150    /**
151     * <p>Constructor for ITextSink.</p>
152     *
153     * @param xmlWriter a pretty-printing xml writer.
154     */
155    protected ITextSink( PrettyPrintXMLWriter xmlWriter )
156    {
157        this.xmlWriter = xmlWriter;
158
159        this.writeStart = false;
160
161        init();
162    }
163
164    /**
165     * Get the current classLoader
166     *
167     * @return the current class loader
168     */
169    public ClassLoader getClassLoader()
170    {
171        return currentClassLoader;
172    }
173
174    /**
175     * Set a new class loader
176     *
177     * @param cl the class loader.
178     */
179    public void setClassLoader( ClassLoader cl )
180    {
181        currentClassLoader = cl;
182    }
183
184    // ----------------------------------------------------------------------
185    // Document
186    // ----------------------------------------------------------------------
187
188    /** {@inheritDoc} */
189    public void close()
190    {
191        IOUtil.close( writer );
192
193        init();
194    }
195
196    /** {@inheritDoc} */
197    public void flush()
198    {
199        if ( getLog().isWarnEnabled() && this.warnMessages != null )
200        {
201            for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
202            {
203                for ( String msg : entry.getValue() )
204                {
205                    getLog().warn( msg );
206                }
207            }
208        }
209
210        this.warnMessages = null;
211    }
212
213    // ----------------------------------------------------------------------
214    // Header
215    // ----------------------------------------------------------------------
216
217    /** {@inheritDoc} */
218    public void head_()
219    {
220        actionContext.release();
221    }
222
223    /** {@inheritDoc} */
224    public void head()
225    {
226        //init(); // why? this causes DOXIA-413
227
228        actionContext.setAction( SinkActionContext.HEAD );
229    }
230
231    /** {@inheritDoc} */
232    public void author_()
233    {
234        actionContext.release();
235    }
236
237    /** {@inheritDoc} */
238    public void author()
239    {
240        actionContext.setAction( SinkActionContext.AUTHOR );
241    }
242
243    /** {@inheritDoc} */
244    public void date_()
245    {
246        actionContext.release();
247    }
248
249    /** {@inheritDoc} */
250    public void date()
251    {
252        actionContext.setAction( SinkActionContext.DATE );
253    }
254
255    /** {@inheritDoc} */
256    public void title_()
257    {
258        actionContext.release();
259    }
260
261    /** {@inheritDoc} */
262    public void title()
263    {
264        actionContext.setAction( SinkActionContext.TITLE );
265    }
266
267    // ----------------------------------------------------------------------
268    // Body
269    // ----------------------------------------------------------------------
270
271    /** {@inheritDoc} */
272    public void body_()
273    {
274        if ( writeStart )
275        {
276            writeEndElement(); // ElementTags.CHAPTER
277
278            writeEndElement(); // ElementTags.ITEXT
279        }
280
281        actionContext.release();
282    }
283
284    /** {@inheritDoc} */
285    public void body()
286    {
287        if ( writeStart )
288        {
289            writeStartElement( ElementTags.ITEXT );
290            writeAddAttribute( ElementTags.TITLE, header.getTitle() );
291            writeAddAttribute( ElementTags.AUTHOR, header.getAuthors() );
292            writeAddAttribute( ElementTags.CREATIONDATE, header.getDate() );
293            writeAddAttribute( ElementTags.SUBJECT, header.getTitle() );
294            writeAddAttribute( ElementTags.KEYWORDS, "" );
295            writeAddAttribute( ElementTags.PRODUCER, "Generated with Doxia by " + System.getProperty( "user.name" ) );
296            writeAddAttribute( ElementTags.PAGE_SIZE, ITextUtil.getPageSize( ITextUtil.getDefaultPageSize() ) );
297
298            writeStartElement( ElementTags.CHAPTER );
299            writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
300            writeAddAttribute( ElementTags.DEPTH, depth );
301            writeAddAttribute( ElementTags.INDENT, "0.0" );
302
303            writeStartElement( ElementTags.TITLE );
304            writeAddAttribute( ElementTags.LEADING, DEFAULT_CHAPTER_TITLE_LEADING );
305            writeAddAttribute( ElementTags.FONT, ITextFont.DEFAULT_FONT_NAME );
306            writeAddAttribute( ElementTags.SIZE, ITextFont.getSectionFontSize( 0 ) );
307            writeAddAttribute( ElementTags.STYLE, ITextFont.BOLD );
308            writeAddAttribute( ElementTags.BLUE, ITextFont.DEFAULT_FONT_COLOR_BLUE );
309            writeAddAttribute( ElementTags.GREEN, ITextFont.DEFAULT_FONT_COLOR_GREEN );
310            writeAddAttribute( ElementTags.RED, ITextFont.DEFAULT_FONT_COLOR_RED );
311            writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
312
313//            startChunk( ITextFont.DEFAULT_FONT_NAME, ITextFont.getSectionFontSize( 0 ),
314//                    ITextFont.BOLD, ITextFont.DEFAULT_FONT_COLOR_BLUE, ITextFont.DEFAULT_FONT_COLOR_GREEN,
315//                    ITextFont.DEFAULT_FONT_COLOR_RED, "top" );
316
317            writeStartElement( ElementTags.CHUNK );
318            writeAddAttribute( ElementTags.FONT, ITextFont.DEFAULT_FONT_NAME );
319            writeAddAttribute( ElementTags.SIZE, ITextFont.getSectionFontSize( 0 ) );
320            writeAddAttribute( ElementTags.STYLE, ITextFont.BOLD );
321            writeAddAttribute( ElementTags.BLUE, ITextFont.DEFAULT_FONT_COLOR_BLUE );
322            writeAddAttribute( ElementTags.GREEN, ITextFont.DEFAULT_FONT_COLOR_GREEN );
323            writeAddAttribute( ElementTags.RED, ITextFont.DEFAULT_FONT_COLOR_RED );
324//            writeAddAttribute( ElementTags.LOCALDESTINATION, "top" );
325
326            write( header.getTitle() );
327
328            writeEndElement(); // ElementTags.CHUNK
329
330            writeEndElement(); // ElementTags.TITLE
331        }
332
333        actionContext.setAction( SinkActionContext.BODY );
334    }
335
336    // ----------------------------------------------------------------------
337    // Sections
338    // ----------------------------------------------------------------------
339
340    /** {@inheritDoc} */
341    public void sectionTitle()
342    {
343        actionContext.release();
344    }
345
346    /** {@inheritDoc} */
347    public void sectionTitle_()
348    {
349        actionContext.setAction( SinkActionContext.SECTION_TITLE );
350    }
351
352    /** {@inheritDoc} */
353    public void section1_()
354    {
355        writeEndElement(); // ElementTags.SECTION
356
357        numberDepth--;
358        depth = 0;
359
360        actionContext.release();
361    }
362
363    /** {@inheritDoc} */
364    public void section1()
365    {
366        numberDepth++;
367        depth = 1;
368
369        writeStartElement( ElementTags.SECTION );
370        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
371        writeAddAttribute( ElementTags.DEPTH, depth );
372        writeAddAttribute( ElementTags.INDENT, "0.0" );
373
374        lineBreak();
375
376        actionContext.setAction( SinkActionContext.SECTION_1 );
377    }
378
379    /** {@inheritDoc} */
380    public void sectionTitle1_()
381    {
382        writeEndElement(); // ElementTags.TITLE
383
384        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
385        bold_();
386
387        actionContext.release();
388    }
389
390    /** {@inheritDoc} */
391    public void sectionTitle1()
392    {
393        font.setSize( ITextFont.getSectionFontSize( 1 ) );
394        font.setColor( Color.BLACK );
395        bold();
396
397        writeStartElement( ElementTags.TITLE );
398        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
399        writeAddAttribute( ElementTags.FONT, font.getFontName() );
400        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
401        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
402        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
403        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
404        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
405//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
406
407        actionContext.setAction( SinkActionContext.SECTION_TITLE_1 );
408    }
409
410    /** {@inheritDoc} */
411    public void section2_()
412    {
413        writeEndElement(); // ElementTags.SECTION
414
415        numberDepth--;
416        depth = 0;
417
418        actionContext.release();
419    }
420
421    /** {@inheritDoc} */
422    public void section2()
423    {
424        numberDepth++;
425        depth = 1;
426
427        writeStartElement( ElementTags.SECTION );
428        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
429        writeAddAttribute( ElementTags.DEPTH, depth );
430        writeAddAttribute( ElementTags.INDENT, "0.0" );
431
432        lineBreak();
433
434        actionContext.setAction( SinkActionContext.SECTION_2 );
435    }
436
437    /** {@inheritDoc} */
438    public void sectionTitle2_()
439    {
440        writeEndElement(); // ElementTags.TITLE
441
442        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
443        bold_();
444
445        actionContext.release();
446    }
447
448    /** {@inheritDoc} */
449    public void sectionTitle2()
450    {
451        font.setSize( ITextFont.getSectionFontSize( 2 ) );
452        font.setColor( Color.BLACK );
453        bold();
454
455        writeStartElement( ElementTags.TITLE );
456        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
457        writeAddAttribute( ElementTags.FONT, font.getFontName() );
458        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
459        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
460        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
461        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
462        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
463//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
464
465        actionContext.setAction( SinkActionContext.SECTION_TITLE_2 );
466    }
467
468    /** {@inheritDoc} */
469    public void section3_()
470    {
471        writeEndElement(); // ElementTags.SECTION
472
473        numberDepth--;
474        depth = 1;
475
476        actionContext.release();
477    }
478
479    /** {@inheritDoc} */
480    public void section3()
481    {
482        numberDepth++;
483        depth = 1;
484
485        writeStartElement( ElementTags.SECTION );
486        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
487        writeAddAttribute( ElementTags.DEPTH, depth );
488        writeAddAttribute( ElementTags.INDENT, "0.0" );
489
490        lineBreak();
491
492        actionContext.setAction( SinkActionContext.SECTION_3 );
493    }
494
495    /** {@inheritDoc} */
496    public void sectionTitle3_()
497    {
498        writeEndElement(); // ElementTags.TITLE
499
500        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
501        bold_();
502
503        actionContext.release();
504    }
505
506    /** {@inheritDoc} */
507    public void sectionTitle3()
508    {
509        font.setSize( ITextFont.getSectionFontSize( 3 ) );
510        font.setColor( Color.BLACK );
511        bold();
512
513        writeStartElement( ElementTags.TITLE );
514        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
515        writeAddAttribute( ElementTags.FONT, font.getFontName() );
516        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
517        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
518        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
519        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
520        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
521//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
522
523        actionContext.setAction( SinkActionContext.SECTION_TITLE_3 );
524    }
525
526    /** {@inheritDoc} */
527    public void section4_()
528    {
529        writeEndElement(); // ElementTags.SECTION
530
531        numberDepth--;
532        depth = 1;
533
534        actionContext.release();
535    }
536
537    /** {@inheritDoc} */
538    public void section4()
539    {
540        numberDepth++;
541        depth = 1;
542
543        writeStartElement( ElementTags.SECTION );
544        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
545        writeAddAttribute( ElementTags.DEPTH, depth );
546        writeAddAttribute( ElementTags.INDENT, "0.0" );
547
548        lineBreak();
549
550        actionContext.setAction( SinkActionContext.SECTION_4 );
551    }
552
553    /** {@inheritDoc} */
554    public void sectionTitle4_()
555    {
556        writeEndElement(); // ElementTags.TITLE
557
558        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
559        bold_();
560
561        actionContext.release();
562    }
563
564    /** {@inheritDoc} */
565    public void sectionTitle4()
566    {
567        font.setSize( ITextFont.getSectionFontSize( 4 ) );
568        font.setColor( Color.BLACK );
569        bold();
570
571        writeStartElement( ElementTags.TITLE );
572        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
573        writeAddAttribute( ElementTags.FONT, font.getFontName() );
574        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
575        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
576        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
577        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
578        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
579//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
580
581        actionContext.setAction( SinkActionContext.SECTION_TITLE_4 );
582    }
583
584    /** {@inheritDoc} */
585    public void section5_()
586    {
587        writeEndElement(); // ElementTags.SECTION
588
589        numberDepth--;
590        depth = 1;
591
592        actionContext.release();
593    }
594
595    /** {@inheritDoc} */
596    public void section5()
597    {
598        numberDepth++;
599        depth = 1;
600
601        writeStartElement( ElementTags.SECTION );
602        writeAddAttribute( ElementTags.NUMBERDEPTH, numberDepth );
603        writeAddAttribute( ElementTags.DEPTH, depth );
604        writeAddAttribute( ElementTags.INDENT, "0.0" );
605
606        lineBreak();
607
608        actionContext.setAction( SinkActionContext.SECTION_5 );
609    }
610
611    /** {@inheritDoc} */
612    public void sectionTitle5_()
613    {
614        writeEndElement(); // ElementTags.TITLE
615
616        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
617        bold_();
618
619        actionContext.release();
620    }
621
622    /** {@inheritDoc} */
623    public void sectionTitle5()
624    {
625        font.setSize( ITextFont.getSectionFontSize( 5 ) );
626        font.setColor( Color.BLACK );
627        bold();
628
629        writeStartElement( ElementTags.TITLE );
630        writeAddAttribute( ElementTags.LEADING, DEFAULT_SECTION_TITLE_LEADING );
631        writeAddAttribute( ElementTags.FONT, font.getFontName() );
632        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
633        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
634        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
635        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
636        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
637//        writeAddAttribute( ElementTags.LOCALDESTINATION, "top" ); // trygve
638
639        actionContext.setAction( SinkActionContext.SECTION_TITLE_5 );
640    }
641
642    // ----------------------------------------------------------------------
643    // Paragraph
644    // ----------------------------------------------------------------------
645
646    /** {@inheritDoc} */
647    public void paragraph_()
648    {
649        // Special case
650        if ( ( actionContext.getCurrentAction() == SinkActionContext.LIST_ITEM )
651            || ( actionContext.getCurrentAction() == SinkActionContext.NUMBERED_LIST_ITEM )
652            || ( actionContext.getCurrentAction() == SinkActionContext.DEFINITION ) )
653        {
654            return;
655        }
656
657        writeEndElement(); // ElementTags.PARAGRAPH
658
659        actionContext.release();
660    }
661
662    /** {@inheritDoc} */
663    public void paragraph()
664    {
665        // Special case
666        if ( ( actionContext.getCurrentAction() == SinkActionContext.LIST_ITEM )
667            || ( actionContext.getCurrentAction() == SinkActionContext.NUMBERED_LIST_ITEM )
668            || ( actionContext.getCurrentAction() == SinkActionContext.DEFINITION ) )
669        {
670            return;
671        }
672
673        writeStartElement( ElementTags.PARAGRAPH );
674        writeStartElement( ElementTags.NEWLINE );
675        writeEndElement();
676
677        actionContext.setAction( SinkActionContext.PARAGRAPH );
678    }
679
680    // ----------------------------------------------------------------------
681    // Lists
682    // ----------------------------------------------------------------------
683
684    /** {@inheritDoc} */
685    public void list_()
686    {
687        writeEndElement(); // ElementTags.LIST
688
689        writeEndElement(); // ElementTags.CHUNK
690
691        actionContext.release();
692    }
693
694    /** {@inheritDoc} */
695    public void list()
696    {
697        writeStartElement( ElementTags.CHUNK );
698        writeAddAttribute( ElementTags.FONT, font.getFontName() );
699        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
700        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
701        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
702        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
703        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
704
705        writeStartElement( ElementTags.LIST );
706        writeAddAttribute( ElementTags.NUMBERED, Boolean.FALSE.toString() );
707        writeAddAttribute( ElementTags.SYMBOLINDENT, "15" );
708
709        actionContext.setAction( SinkActionContext.LIST );
710    }
711
712    /** {@inheritDoc} */
713    public void listItem_()
714    {
715        writeEndElement(); // ElementTags.LISTITEM
716
717        actionContext.release();
718    }
719
720    /** {@inheritDoc} */
721    public void listItem()
722    {
723        writeStartElement( ElementTags.LISTITEM );
724        writeAddAttribute( ElementTags.INDENTATIONLEFT, "20.0" );
725
726        actionContext.setAction( SinkActionContext.LIST_ITEM );
727    }
728
729    /** {@inheritDoc} */
730    public void numberedList_()
731    {
732        writeEndElement(); // ElementTags.LIST
733
734        writeEndElement(); // ElementTags.CHUNK
735
736        actionContext.release();
737    }
738
739    /** {@inheritDoc} */
740    public void numberedList( int numbering )
741    {
742        writeStartElement( ElementTags.CHUNK );
743        writeAddAttribute( ElementTags.FONT, font.getFontName() );
744        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
745        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
746        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
747        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
748        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
749
750        writeStartElement( ElementTags.LIST );
751        writeAddAttribute( ElementTags.NUMBERED, Boolean.TRUE.toString() );
752        writeAddAttribute( ElementTags.SYMBOLINDENT, "20" );
753
754        switch ( numbering )
755        {
756            case Sink.NUMBERING_UPPER_ALPHA:
757                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
758                writeAddAttribute( ElementTags.FIRST, 'A' );
759                break;
760
761            case Sink.NUMBERING_LOWER_ALPHA:
762                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
763                writeAddAttribute( ElementTags.FIRST, 'a' );
764                break;
765
766            // TODO Doesn't work
767            case Sink.NUMBERING_UPPER_ROMAN:
768                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
769                writeAddAttribute( ElementTags.FIRST, 'I' );
770                break;
771
772            case Sink.NUMBERING_LOWER_ROMAN:
773                writeAddAttribute( ElementTags.LETTERED, Boolean.TRUE.toString() );
774                writeAddAttribute( ElementTags.FIRST, 'i' );
775                break;
776
777            case Sink.NUMBERING_DECIMAL:
778            default:
779                writeAddAttribute( ElementTags.LETTERED, Boolean.FALSE.toString() );
780        }
781
782        actionContext.setAction( SinkActionContext.NUMBERED_LIST );
783    }
784
785    /** {@inheritDoc} */
786    public void numberedListItem_()
787    {
788        writeEndElement(); // ElementTags.LISTITEM
789
790        actionContext.release();
791    }
792
793    /** {@inheritDoc} */
794    public void numberedListItem()
795    {
796        writeStartElement( ElementTags.LISTITEM );
797        writeAddAttribute( ElementTags.INDENTATIONLEFT, "20" );
798
799        actionContext.setAction( SinkActionContext.NUMBERED_LIST_ITEM );
800    }
801
802    /** {@inheritDoc} */
803    public void definitionList_()
804    {
805        actionContext.release();
806    }
807
808    /** {@inheritDoc} */
809    public void definitionList()
810    {
811        lineBreak();
812
813        actionContext.setAction( SinkActionContext.DEFINITION_LIST );
814    }
815
816    /** {@inheritDoc} */
817    public void definedTerm_()
818    {
819        font.setSize( ITextFont.DEFAULT_FONT_SIZE );
820        bold_();
821
822        writeEndElement(); // ElementTags.CHUNK
823
824        actionContext.release();
825
826        lineBreak();
827    }
828
829    /** {@inheritDoc} */
830    public void definedTerm()
831    {
832        font.setSize( ITextFont.DEFAULT_FONT_SIZE + 2 );
833        bold();
834
835        writeStartElement( ElementTags.CHUNK );
836        writeAddAttribute( ElementTags.FONT, font.getFontName() );
837        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
838        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
839        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
840        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
841        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
842
843        actionContext.setAction( SinkActionContext.DEFINED_TERM );
844    }
845
846    /** {@inheritDoc} */
847    public void definition_()
848    {
849        writeEndElement(); // ElementTags.CHUNK
850
851        actionContext.release();
852
853        lineBreak();
854    }
855
856    /** {@inheritDoc} */
857    public void definition()
858    {
859        writeStartElement( ElementTags.CHUNK );
860        writeAddAttribute( ElementTags.FONT, font.getFontName() );
861        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
862        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
863        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
864        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
865        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
866
867
868        writeStartElement( ElementTags.CHUNK );
869        writeAddAttribute( ElementTags.FONT, font.getFontName() );
870        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
871        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
872        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
873        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
874        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
875
876        // We need to add a non break space first to display empty string
877        write( "\u00A0" + StringUtils.repeat( " ", 16 ), false, false );
878
879        writeEndElement(); // ElementTags.CHUNK
880
881        actionContext.setAction( SinkActionContext.DEFINITION );
882    }
883
884    /** {@inheritDoc} */
885    public void definitionListItem_()
886    {
887        actionContext.release();
888    }
889
890    /** {@inheritDoc} */
891    public void definitionListItem()
892    {
893        actionContext.setAction( SinkActionContext.DEFINITION_LIST_ITEM );
894    }
895
896    // ----------------------------------------------------------------------
897    //  Tables
898    // ----------------------------------------------------------------------
899
900    /** {@inheritDoc} */
901    public void table_()
902    {
903        if ( tableCaptionXMLWriter != null )
904        {
905            tableCaptionXMLWriter = null;
906
907            writeEndElement(); // ElementTags.TABLE
908
909            writeEndElement(); // ElementTags.CHUNK
910
911            writeStartElement( ElementTags.PARAGRAPH );
912            writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
913
914            write( tableCaptionWriter.toString(), true );
915
916            writeEndElement(); // ElementTags.PARAGRAPH
917
918            tableCaptionWriter = null;
919        }
920        else
921        {
922            writeEndElement(); // ElementTags.TABLE
923
924            writeEndElement(); // ElementTags.CHUNK
925        }
926        actionContext.release();
927    }
928
929    /** {@inheritDoc} */
930    public void table()
931    {
932        writeStartElement( ElementTags.CHUNK );
933        writeAddAttribute( ElementTags.FONT, font.getFontName() );
934        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
935        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
936        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
937        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
938        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
939
940        writeStartElement( ElementTags.TABLE );
941        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
942        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
943        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
944        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
945        writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
946        writeAddAttribute( ElementTags.WIDTH, "100.0%" );
947        writeAddAttribute( ElementTags.TABLEFITSPAGE, Boolean.TRUE.toString() );
948        writeAddAttribute( ElementTags.CELLSFITPAGE, Boolean.TRUE.toString() );
949        writeAddAttribute( ElementTags.CELLPADDING, "10" );
950        //writeAddAttribute( ElementTags.COLUMNS, "2" );
951
952        actionContext.setAction( SinkActionContext.TABLE );
953    }
954
955    /** {@inheritDoc} */
956    public void tableCaption_()
957    {
958        actionContext.release();
959    }
960
961    /** {@inheritDoc} */
962    public void tableCaption()
963    {
964        tableCaptionWriter = new StringWriter();
965        tableCaptionXMLWriter = new PrettyPrintXMLWriter( tableCaptionWriter );
966        actionContext.setAction( SinkActionContext.TABLE_CAPTION );
967    }
968
969    /** {@inheritDoc} */
970    public void tableCell_()
971    {
972        writeEndElement(); // ElementTags.CELL
973
974        actionContext.release();
975    }
976
977    /** {@inheritDoc} */
978    public void tableCell()
979    {
980        writeStartElement( ElementTags.CELL );
981        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
982        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
983        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
984        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
985        writeAddAttribute( ElementTags.HORIZONTALALIGN, ElementTags.ALIGN_LEFT );
986
987        actionContext.setAction( SinkActionContext.TABLE_CELL );
988    }
989
990    /** {@inheritDoc} */
991    public void tableCell( String width )
992    {
993        actionContext.setAction( SinkActionContext.TABLE_CELL );
994    }
995
996    /** {@inheritDoc} */
997    public void tableHeaderCell_()
998    {
999        writeEndElement(); // ElementTags.CELL
1000
1001        actionContext.release();
1002    }
1003
1004    /** {@inheritDoc} */
1005    public void tableHeaderCell()
1006    {
1007        writeStartElement( ElementTags.CELL );
1008        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1009        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1010        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1011        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1012        writeAddAttribute( ElementTags.HEADER, Boolean.TRUE.toString() );
1013        writeAddAttribute( ElementTags.BGRED, Color.GRAY.getRed() );
1014        writeAddAttribute( ElementTags.BGBLUE, Color.GRAY.getBlue() );
1015        writeAddAttribute( ElementTags.BGGREEN, Color.GRAY.getGreen() );
1016        writeAddAttribute( ElementTags.HORIZONTALALIGN, ElementTags.ALIGN_CENTER );
1017
1018        actionContext.setAction( SinkActionContext.TABLE_HEADER_CELL );
1019    }
1020
1021    /** {@inheritDoc} */
1022    public void tableHeaderCell( String width )
1023    {
1024        actionContext.setAction( SinkActionContext.TABLE_HEADER_CELL );
1025    }
1026
1027    /** {@inheritDoc} */
1028    public void tableRow_()
1029    {
1030        writeEndElement(); // ElementTags.ROW
1031
1032        actionContext.release();
1033    }
1034
1035    /** {@inheritDoc} */
1036    public void tableRow()
1037    {
1038        writeStartElement( ElementTags.ROW );
1039
1040        actionContext.setAction( SinkActionContext.TABLE_ROW );
1041    }
1042
1043    /** {@inheritDoc} */
1044    public void tableRows_()
1045    {
1046        //writeEndElement(); // ElementTags.TABLE
1047
1048        actionContext.release();
1049    }
1050
1051    /** {@inheritDoc} */
1052    public void tableRows( int[] justification, boolean grid )
1053    {
1054        // ElementTags.TABLE
1055        writeAddAttribute( ElementTags.COLUMNS, justification.length );
1056
1057        actionContext.setAction( SinkActionContext.TABLE_ROWS );
1058    }
1059
1060    // ----------------------------------------------------------------------
1061    // Verbatim
1062    // ----------------------------------------------------------------------
1063
1064    /** {@inheritDoc} */
1065    public void verbatim_()
1066    {
1067        writeEndElement(); // ElementTags.CELL
1068
1069        writeEndElement(); // ElementTags.ROW
1070
1071        writeEndElement(); // ElementTags.TABLE
1072
1073        writeEndElement(); // ElementTags.CHUNK
1074
1075        actionContext.release();
1076    }
1077
1078    /** {@inheritDoc} */
1079    public void verbatim( boolean boxed )
1080    {
1081        // Always boxed
1082        writeStartElement( ElementTags.CHUNK );
1083        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1084        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1085        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1086        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1087        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1088        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1089
1090        writeStartElement( ElementTags.TABLE );
1091        writeAddAttribute( ElementTags.COLUMNS, "1" );
1092        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1093        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1094        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1095        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1096        writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_CENTER );
1097        writeAddAttribute( ElementTags.TABLEFITSPAGE, Boolean.TRUE.toString() );
1098        writeAddAttribute( ElementTags.CELLSFITPAGE, Boolean.TRUE.toString() );
1099        writeAddAttribute( ElementTags.CELLPADDING, "10" );
1100        writeAddAttribute( ElementTags.WIDTH, "100.0%" );
1101
1102        writeStartElement( ElementTags.ROW );
1103
1104        writeStartElement( ElementTags.CELL );
1105        writeAddAttribute( ElementTags.LEFT, Boolean.TRUE.toString() );
1106        writeAddAttribute( ElementTags.RIGHT, Boolean.TRUE.toString() );
1107        writeAddAttribute( ElementTags.TOP, Boolean.TRUE.toString() );
1108        writeAddAttribute( ElementTags.BOTTOM, Boolean.TRUE.toString() );
1109
1110        actionContext.setAction( SinkActionContext.VERBATIM );
1111    }
1112
1113    // ----------------------------------------------------------------------
1114    // Figures
1115    // ----------------------------------------------------------------------
1116
1117    /** {@inheritDoc} */
1118    public void figure_()
1119    {
1120        writeEndElement(); // ElementTags.IMAGE
1121
1122        writeEndElement(); // ElementTags.CHUNK
1123
1124        actionContext.release();
1125
1126        figureDefined = false;
1127    }
1128
1129    /** {@inheritDoc} */
1130    public void figure()
1131    {
1132        figureDefined = true;
1133
1134        writeStartElement( ElementTags.CHUNK );
1135        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1136        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1137        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1138        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1139        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1140        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1141
1142        writeStartElement( ElementTags.IMAGE );
1143
1144        actionContext.setAction( SinkActionContext.FIGURE );
1145    }
1146
1147    /** {@inheritDoc} */
1148    public void figureCaption_()
1149    {
1150        actionContext.release();
1151    }
1152
1153    /** {@inheritDoc} */
1154    public void figureCaption()
1155    {
1156        actionContext.setAction( SinkActionContext.FIGURE_CAPTION );
1157    }
1158
1159    /**
1160     * If the <code>name</code> is a relative link, the internal link will used a System property
1161     * <code>itext.basedir</code>, or the class loader.
1162     * {@inheritDoc}
1163     */
1164    public void figureGraphics( String name )
1165    {
1166        String urlName = null;
1167        File nameFile = null;
1168        if ( ( name.toLowerCase( Locale.ENGLISH ).startsWith( "http://" ) )
1169            || ( name.toLowerCase( Locale.ENGLISH ).startsWith( "https://" ) ) )
1170        {
1171            urlName = name;
1172        }
1173        else
1174        {
1175            if ( System.getProperty( "itext.basedir" ) != null )
1176            {
1177                try
1178                {
1179                    nameFile = new File( System.getProperty( "itext.basedir" ), name );
1180                    urlName = nameFile.toURI().toURL().toString();
1181                }
1182                catch ( MalformedURLException e )
1183                {
1184                    getLog().error( "MalformedURLException: " + e.getMessage(), e );
1185                }
1186            }
1187            else
1188            {
1189                if ( getClassLoader() != null )
1190                {
1191                    if ( getClassLoader().getResource( name ) != null )
1192                    {
1193                        urlName = getClassLoader().getResource( name ).toString();
1194                    }
1195                }
1196                else
1197                {
1198                    if ( ITextSink.class.getClassLoader().getResource( name ) != null )
1199                    {
1200                        urlName = ITextSink.class.getClassLoader().getResource( name ).toString();
1201                    }
1202                }
1203            }
1204        }
1205
1206        if ( urlName == null )
1207        {
1208            String msg =
1209                "No image '" + name
1210                    + "' found in the class loader. Try to call setClassLoader(ClassLoader) before.";
1211            logMessage( "imageNotFound", msg );
1212
1213            return;
1214        }
1215
1216        if ( nameFile != null && !nameFile.exists() )
1217        {
1218            String msg = "No image '" + nameFile + "' found in your system, check the path.";
1219            logMessage( "imageNotFound", msg );
1220
1221            return;
1222        }
1223
1224        boolean figureCalled = figureDefined;
1225        if ( !figureCalled )
1226        {
1227            figure();
1228        }
1229
1230        float width = 0;
1231        float height = 0;
1232        try
1233        {
1234            Image image = Image.getInstance( new URL( urlName ) );
1235            image.scaleToFit( ITextUtil.getDefaultPageSize().width() / 2, ITextUtil.getDefaultPageSize().height() / 2 );
1236            width = image.plainWidth();
1237            height = image.plainHeight();
1238        }
1239        catch ( BadElementException e )
1240        {
1241            getLog().error( "BadElementException: " + e.getMessage(), e );
1242        }
1243        catch ( MalformedURLException e )
1244        {
1245            getLog().error( "MalformedURLException: " + e.getMessage(), e );
1246        }
1247        catch ( IOException e )
1248        {
1249            getLog().error( "IOException: " + e.getMessage(), e );
1250        }
1251
1252        writeAddAttribute( ElementTags.URL, urlName );
1253        writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_MIDDLE );
1254        writeAddAttribute( ElementTags.PLAINWIDTH, String.valueOf( width ) );
1255        writeAddAttribute( ElementTags.PLAINHEIGHT, String.valueOf( height ) );
1256
1257        actionContext.setAction( SinkActionContext.FIGURE_GRAPHICS );
1258
1259        if ( !figureCalled )
1260        {
1261            figure_();
1262        }
1263    }
1264
1265    // ----------------------------------------------------------------------
1266    // Fonts
1267    // ----------------------------------------------------------------------
1268
1269    /** {@inheritDoc} */
1270    public void bold_()
1271    {
1272        font.removeBold();
1273    }
1274
1275    /** {@inheritDoc} */
1276    public void bold()
1277    {
1278        font.addBold();
1279    }
1280
1281    /** {@inheritDoc} */
1282    public void italic_()
1283    {
1284        font.removeItalic();
1285    }
1286
1287    /** {@inheritDoc} */
1288    public void italic()
1289    {
1290        font.addItalic();
1291    }
1292
1293    /** {@inheritDoc} */
1294    public void monospaced_()
1295    {
1296        font.setMonoSpaced( false );
1297    }
1298
1299    /** {@inheritDoc} */
1300    public void monospaced()
1301    {
1302        font.setMonoSpaced( true );
1303    }
1304
1305    // ----------------------------------------------------------------------
1306    // Links
1307    // ----------------------------------------------------------------------
1308
1309    /** {@inheritDoc} */
1310    public void link_()
1311    {
1312        writeEndElement(); // ElementTags.ANCHOR
1313
1314        font.setColor( Color.BLACK );
1315        font.removeUnderlined();
1316
1317        actionContext.release();
1318    }
1319
1320    /** {@inheritDoc} */
1321    public void link( String name )
1322    {
1323        if ( name == null )
1324        {
1325            throw new NullPointerException( "Link name cannot be null!" );
1326        }
1327
1328        font.setColor( Color.BLUE );
1329        font.addUnderlined();
1330
1331        writeStartElement( ElementTags.ANCHOR );
1332        if ( StringUtils.isNotEmpty( name ) && name.startsWith( "#" ) && StringUtils.isNotEmpty( header.getTitle() ) )
1333        {
1334            name = "#" + DoxiaUtils.encodeId( header.getTitle(), true ) + "_" + name.substring( 1 );
1335        }
1336        writeAddAttribute( ElementTags.REFERENCE, HtmlTools.escapeHTML( name ) );
1337        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1338        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1339        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1340        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1341        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1342        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1343
1344        actionContext.setAction( SinkActionContext.LINK );
1345    }
1346
1347    /** {@inheritDoc} */
1348    public void anchor_()
1349    {
1350        if ( !anchorDefined )
1351        {
1352            // itext needs a defined local destination, we put an invisible text
1353            writeAddAttribute( ElementTags.BLUE, "255" );
1354            writeAddAttribute( ElementTags.GREEN, "255" );
1355            writeAddAttribute( ElementTags.RED, "255" );
1356
1357            write( "_" );
1358        }
1359
1360        anchorDefined = false;
1361
1362        writeEndElement(); // ElementTags.ANCHOR
1363
1364        actionContext.release();
1365    }
1366
1367    /** {@inheritDoc} */
1368    public void anchor( String name )
1369    {
1370        if ( name == null )
1371        {
1372            throw new NullPointerException( "Anchor name cannot be null!" );
1373        }
1374
1375        if ( StringUtils.isNotEmpty( header.getTitle() ) )
1376        {
1377            name = header.getTitle() + "_" + name;
1378        }
1379        String id = name;
1380
1381        if ( !DoxiaUtils.isValidId( id ) )
1382        {
1383            id = DoxiaUtils.encodeId( name, true );
1384
1385            String msg = "Modified invalid link: '" + name + "' to '" + id + "'";
1386            logMessage( "modifiedLink", msg );
1387        }
1388
1389        writeStartElement( ElementTags.ANCHOR );
1390        writeAddAttribute( ElementTags.NAME, id );
1391        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1392        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1393        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1394
1395        actionContext.setAction( SinkActionContext.ANCHOR );
1396    }
1397
1398    // ----------------------------------------------------------------------
1399    // Misc
1400    // ----------------------------------------------------------------------
1401
1402    /** {@inheritDoc} */
1403    public void lineBreak()
1404    {
1405        // Special case for the header
1406        if ( ( actionContext.getCurrentAction() == SinkActionContext.AUTHOR )
1407            || ( actionContext.getCurrentAction() == SinkActionContext.DATE )
1408            || ( actionContext.getCurrentAction() == SinkActionContext.TITLE ) )
1409        {
1410            return;
1411        }
1412
1413        writeStartElement( ElementTags.NEWLINE );
1414        writeEndElement();
1415    }
1416
1417    /** {@inheritDoc} */
1418    public void nonBreakingSpace()
1419    {
1420        write( " " );
1421    }
1422
1423    /** {@inheritDoc} */
1424    public void pageBreak()
1425    {
1426        writeStartElement( ElementTags.NEWPAGE );
1427        writeEndElement();
1428    }
1429
1430    /** {@inheritDoc} */
1431    public void horizontalRule()
1432    {
1433        writeStartElement( ElementTags.PARAGRAPH );
1434        writeAddAttribute( ElementTags.BLUE, "255" );
1435        writeAddAttribute( ElementTags.GREEN, "255" );
1436        writeAddAttribute( ElementTags.RED, "255" );
1437        write( "_" );
1438        writeEndElement();
1439
1440        writeStartElement( ElementTags.PARAGRAPH );
1441        writeStartElement( ElementTags.HORIZONTALRULE );
1442        writeEndElement();
1443        writeEndElement();
1444    }
1445
1446    // ----------------------------------------------------------------------
1447    // Text
1448    // ----------------------------------------------------------------------
1449
1450    /** {@inheritDoc} */
1451    public void rawText( String text )
1452    {
1453        writeStartElement( ElementTags.CHUNK );
1454        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1455        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1456        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1457        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1458        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1459        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1460
1461        write( text, false );
1462
1463        writeEndElement(); // ElementTags.CHUNK
1464    }
1465
1466    /** {@inheritDoc} */
1467    public void text( String text )
1468    {
1469        if ( StringUtils.isEmpty( text ) )
1470        {
1471            return;
1472        }
1473
1474        switch ( actionContext.getCurrentAction() )
1475        {
1476            case SinkActionContext.AUTHOR:
1477                header.addAuthor( text );
1478                break;
1479
1480            case SinkActionContext.DATE:
1481                header.setDate( text );
1482                break;
1483
1484            case SinkActionContext.TITLE:
1485                header.setTitle( text );
1486                break;
1487
1488            case SinkActionContext.TABLE_CAPTION:
1489                this.tableCaptionXMLWriter.writeText( text );
1490                break;
1491
1492            case SinkActionContext.VERBATIM:
1493                // Used to preserve indentation and formating
1494                LineNumberReader lnr = new LineNumberReader( new StringReader( text ) );
1495                String line;
1496                try
1497                {
1498                    while ( ( line = lnr.readLine() ) != null )
1499                    {
1500                        writeStartElement( ElementTags.CHUNK );
1501                        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1502                        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1503                        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1504                        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1505                        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1506                        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1507
1508                        write( "<![CDATA[", true );
1509                        // Special case
1510                        line = StringUtils.replace( line, "<![CDATA[", "< ![CDATA[" );
1511                        line = StringUtils.replace( line, "]]>", "]] >" );
1512                        write( line, true, false );
1513                        write( "]]>", true );
1514
1515                        writeEndElement();
1516                        lineBreak();
1517                    }
1518                }
1519                catch ( IOException e )
1520                {
1521                    throw new RuntimeException( "IOException: ", e );
1522                }
1523                break;
1524
1525            case SinkActionContext.FIGURE_CAPTION:
1526                writeAddAttribute( ElementTags.ALT, text );
1527                break;
1528
1529            case SinkActionContext.SECTION_TITLE:
1530            case SinkActionContext.SECTION_1:
1531            case SinkActionContext.SECTION_2:
1532            case SinkActionContext.SECTION_3:
1533            case SinkActionContext.SECTION_4:
1534            case SinkActionContext.SECTION_5:
1535            case SinkActionContext.FIGURE:
1536            case SinkActionContext.FIGURE_GRAPHICS:
1537            case SinkActionContext.TABLE_ROW:
1538            case SinkActionContext.TABLE:
1539            case SinkActionContext.HEAD:
1540            case SinkActionContext.UNDEFINED:
1541                break;
1542
1543            case SinkActionContext.ANCHOR:
1544                anchorDefined = true;
1545            case SinkActionContext.PARAGRAPH:
1546            case SinkActionContext.LINK:
1547            case SinkActionContext.TABLE_CELL:
1548            case SinkActionContext.TABLE_HEADER_CELL:
1549            case SinkActionContext.DEFINITION:
1550            case SinkActionContext.DEFINED_TERM:
1551            case SinkActionContext.NUMBERED_LIST_ITEM:
1552            case SinkActionContext.LIST_ITEM:
1553            case SinkActionContext.SECTION_TITLE_5:
1554            case SinkActionContext.SECTION_TITLE_4:
1555            case SinkActionContext.SECTION_TITLE_3:
1556            case SinkActionContext.SECTION_TITLE_2:
1557            case SinkActionContext.SECTION_TITLE_1:
1558            default:
1559                writeStartElement( ElementTags.CHUNK );
1560                writeAddAttribute( ElementTags.FONT, font.getFontName() );
1561                writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1562                writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1563                writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1564                writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1565                writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1566
1567                write( text );
1568
1569                writeEndElement(); // ElementTags.CHUNK
1570        }
1571    }
1572
1573    /**
1574     * {@inheritDoc}
1575     *
1576     * Unkown events just log a warning message but are ignored otherwise.
1577     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
1578     */
1579    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1580    {
1581        String msg = "Unknown Sink event: '" + name + "', ignoring!";
1582        logMessage( "unknownEvent", msg );
1583    }
1584
1585    /** {@inheritDoc} */
1586    protected void init()
1587    {
1588        super.init();
1589
1590        this.actionContext = new SinkActionContext();
1591        this.font = new ITextFont();
1592        this.header = new ITextHeader();
1593
1594        this.numberDepth = 1;
1595        this.depth = 0;
1596        this.tableCaptionWriter = null;
1597        this.tableCaptionXMLWriter = null;
1598        this.anchorDefined = false;
1599        this.figureDefined = false;
1600        this.warnMessages = null;
1601    }
1602
1603    /**
1604     * Convenience method to write a starting element.
1605     *
1606     * @param tag the name of the tag
1607     */
1608    private void writeStartElement( String tag )
1609    {
1610        if ( tableCaptionXMLWriter == null )
1611        {
1612            xmlWriter.startElement( tag );
1613        }
1614        else
1615        {
1616            tableCaptionXMLWriter.startElement( tag );
1617        }
1618    }
1619
1620    /**
1621     * Convenience method to write a key-value pair.
1622     *
1623     * @param key the name of an attribute
1624     * @param value the value of an attribute
1625     */
1626    private void writeAddAttribute( String key, String value )
1627    {
1628        if ( tableCaptionXMLWriter == null )
1629        {
1630            xmlWriter.addAttribute( key, value );
1631        }
1632        else
1633        {
1634            tableCaptionXMLWriter.addAttribute( key, value );
1635        }
1636    }
1637
1638    /**
1639     * Convenience method to write a key-value pair.
1640     *
1641     * @param key the name of an attribute
1642     * @param value the value of an attribute
1643     */
1644    private void writeAddAttribute( String key, int value )
1645    {
1646        if ( tableCaptionXMLWriter == null )
1647        {
1648            xmlWriter.addAttribute( key, String.valueOf( value ) );
1649        }
1650        else
1651        {
1652            tableCaptionXMLWriter.addAttribute( key, String.valueOf( value ) );
1653        }
1654    }
1655
1656    /**
1657     * Convenience method to write an end element.
1658     */
1659    private void writeEndElement()
1660    {
1661        if ( tableCaptionXMLWriter == null )
1662        {
1663            xmlWriter.endElement();
1664        }
1665        else
1666        {
1667            tableCaptionXMLWriter.endElement();
1668        }
1669    }
1670
1671    /**
1672     * Convenience method to write a String
1673     *
1674     * @param aString
1675     */
1676    protected void write( String aString )
1677    {
1678        write( aString, false );
1679    }
1680
1681    /**
1682     * Convenience method to write a String depending the escapeHtml flag
1683     *
1684     * @param aString
1685     * @param escapeHtml
1686     */
1687    private void write( String aString, boolean escapeHtml )
1688    {
1689        write( aString, escapeHtml, true );
1690    }
1691
1692    /**
1693     * Convenience method to write a String depending the escapeHtml flag
1694     *
1695     * @param aString
1696     * @param escapeHtml
1697     * @param trim
1698     */
1699    private void write( String aString, boolean escapeHtml, boolean trim )
1700    {
1701        if ( aString == null )
1702        {
1703            return;
1704        }
1705
1706        if ( trim )
1707        {
1708            aString = StringUtils.replace( aString, "\n", "" );
1709
1710            LineNumberReader lnr = new LineNumberReader( new StringReader( aString ) );
1711            StringBuilder sb = new StringBuilder();
1712            String line;
1713            try
1714            {
1715                while ( ( line = lnr.readLine() ) != null )
1716                {
1717                    sb.append( beautifyPhrase( line.trim() ) );
1718                    sb.append( " " );
1719                }
1720
1721                aString = sb.toString();
1722            }
1723            catch ( IOException e )
1724            {
1725                // nop
1726            }
1727            if ( aString.trim().length() == 0 )
1728            {
1729                return;
1730            }
1731        }
1732        if ( escapeHtml )
1733        {
1734            if ( tableCaptionXMLWriter == null )
1735            {
1736                xmlWriter.writeMarkup( aString );
1737            }
1738            else
1739            {
1740                tableCaptionXMLWriter.writeMarkup( aString );
1741            }
1742        }
1743        else
1744        {
1745            if ( tableCaptionXMLWriter == null )
1746            {
1747                xmlWriter.writeText( aString );
1748            }
1749            else
1750            {
1751                tableCaptionXMLWriter.writeText( aString );
1752            }
1753        }
1754    }
1755
1756    /**
1757     * Convenience method to return a beautify phrase, i.e. one space between words.
1758     *
1759     * @param aString
1760     * @return a String with only one space between words
1761     */
1762    private static String beautifyPhrase( String aString )
1763    {
1764        String[] strings = StringUtils.split( aString, " " );
1765        StringBuilder sb = new StringBuilder();
1766        for ( int i = 0; i < strings.length; i++ )
1767        {
1768            if ( strings[i].trim().length() != 0 )
1769            {
1770                sb.append( strings[i].trim() );
1771                sb.append( " " );
1772            }
1773        }
1774
1775        return sb.toString().trim();
1776    }
1777
1778    private void startChunk( String fontName, int fontSize, String fontStyle, int fontColorBlue, int fontColorGreen,
1779                             int fontColorRed, String localDestination )
1780    {
1781        writeStartElement( ElementTags.CHUNK );
1782        writeAddAttribute( ElementTags.FONT, fontName );
1783        writeAddAttribute( ElementTags.SIZE, fontSize );
1784        writeAddAttribute( ElementTags.STYLE, fontStyle );
1785        writeAddAttribute( ElementTags.BLUE, fontColorBlue );
1786        writeAddAttribute( ElementTags.GREEN, fontColorGreen );
1787        writeAddAttribute( ElementTags.RED, fontColorRed );
1788//        writeAddAttribute( ElementTags.LOCALDESTINATION, localDestination );
1789    }
1790
1791    /**
1792     * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
1793     *
1794     * @param key not null
1795     * @param msg not null
1796     * @see #close()
1797     * @since 1.1.1
1798     */
1799    private void logMessage( String key, String msg )
1800    {
1801        msg = "[iText Sink] " + msg;
1802        if ( getLog().isDebugEnabled() )
1803        {
1804            getLog().debug( msg );
1805
1806            return;
1807        }
1808
1809        if ( warnMessages == null )
1810        {
1811            warnMessages = new HashMap<String, Set<String>>();
1812        }
1813
1814        Set<String> set = warnMessages.get( key );
1815        if ( set == null )
1816        {
1817            set = new TreeSet<String>();
1818        }
1819        set.add( msg );
1820        warnMessages.put( key, set );
1821    }
1822}