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.AbstractXmlSink;
042import org.apache.maven.doxia.sink.Sink;
043import org.apache.maven.doxia.sink.SinkEventAttributes;
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 905940 2014-04-12 16:27:29Z 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 <code>itext.basedir</code>,
1161     * or the class loader.
1162     * {@inheritDoc} */
1163    public void figureGraphics( String name )
1164    {
1165        String urlName = null;
1166        File nameFile = null;
1167        if ( ( name.toLowerCase( Locale.ENGLISH ).startsWith( "http://" ) )
1168            || ( name.toLowerCase( Locale.ENGLISH ).startsWith( "https://" ) ) )
1169        {
1170            urlName = name;
1171        }
1172        else
1173        {
1174            if ( System.getProperty( "itext.basedir" ) != null )
1175            {
1176                try
1177                {
1178                    nameFile = new File( System.getProperty( "itext.basedir" ), name );
1179                    urlName = nameFile.toURI().toURL().toString();
1180                }
1181                catch ( MalformedURLException e )
1182                {
1183                    getLog().error( "MalformedURLException: " + e.getMessage(), e );
1184                }
1185            }
1186            else
1187            {
1188                if ( getClassLoader() != null )
1189                {
1190                    if ( getClassLoader().getResource( name ) != null )
1191                    {
1192                        urlName = getClassLoader().getResource( name ).toString();
1193                    }
1194                }
1195                else
1196                {
1197                    if ( ITextSink.class.getClassLoader().getResource( name ) != null )
1198                    {
1199                        urlName = ITextSink.class.getClassLoader().getResource( name ).toString();
1200                    }
1201                }
1202            }
1203        }
1204
1205        if ( urlName == null )
1206        {
1207            String msg =
1208                "No image '" + name
1209                    + "' found in the class loader. Try to call setClassLoader(ClassLoader) before.";
1210            logMessage( "imageNotFound", msg );
1211
1212            return;
1213        }
1214
1215        if ( nameFile != null && !nameFile.exists() )
1216        {
1217            String msg = "No image '" + nameFile + "' found in your system, check the path.";
1218            logMessage( "imageNotFound", msg );
1219
1220            return;
1221        }
1222
1223        boolean figureCalled = figureDefined;
1224        if ( !figureCalled )
1225        {
1226            figure();
1227        }
1228
1229        float width = 0;
1230        float height = 0;
1231        try
1232        {
1233            Image image = Image.getInstance( new URL( urlName ) );
1234            image.scaleToFit( ITextUtil.getDefaultPageSize().width() / 2, ITextUtil.getDefaultPageSize().height() / 2 );
1235            width = image.plainWidth();
1236            height = image.plainHeight();
1237        }
1238        catch ( BadElementException e )
1239        {
1240            getLog().error( "BadElementException: " + e.getMessage(), e );
1241        }
1242        catch ( MalformedURLException e )
1243        {
1244            getLog().error( "MalformedURLException: " + e.getMessage(), e );
1245        }
1246        catch ( IOException e )
1247        {
1248            getLog().error( "IOException: " + e.getMessage(), e );
1249        }
1250
1251        writeAddAttribute( ElementTags.URL, urlName );
1252        writeAddAttribute( ElementTags.ALIGN, ElementTags.ALIGN_MIDDLE );
1253        writeAddAttribute( ElementTags.PLAINWIDTH, String.valueOf( width ) );
1254        writeAddAttribute( ElementTags.PLAINHEIGHT, String.valueOf( height ) );
1255
1256        actionContext.setAction( SinkActionContext.FIGURE_GRAPHICS );
1257
1258        if ( !figureCalled )
1259        {
1260            figure_();
1261        }
1262    }
1263
1264    // ----------------------------------------------------------------------
1265    // Fonts
1266    // ----------------------------------------------------------------------
1267
1268    /** {@inheritDoc} */
1269    public void bold_()
1270    {
1271        font.removeBold();
1272    }
1273
1274    /** {@inheritDoc} */
1275    public void bold()
1276    {
1277        font.addBold();
1278    }
1279
1280    /** {@inheritDoc} */
1281    public void italic_()
1282    {
1283        font.removeItalic();
1284    }
1285
1286    /** {@inheritDoc} */
1287    public void italic()
1288    {
1289        font.addItalic();
1290    }
1291
1292    /** {@inheritDoc} */
1293    public void monospaced_()
1294    {
1295        font.setMonoSpaced( false );
1296    }
1297
1298    /** {@inheritDoc} */
1299    public void monospaced()
1300    {
1301        font.setMonoSpaced( true );
1302    }
1303
1304    // ----------------------------------------------------------------------
1305    // Links
1306    // ----------------------------------------------------------------------
1307
1308    /** {@inheritDoc} */
1309    public void link_()
1310    {
1311        writeEndElement(); // ElementTags.ANCHOR
1312
1313        font.setColor( Color.BLACK );
1314        font.removeUnderlined();
1315
1316        actionContext.release();
1317    }
1318
1319    /** {@inheritDoc} */
1320    public void link( String name )
1321    {
1322        if ( name == null )
1323        {
1324            throw new NullPointerException( "Link name cannot be null!" );
1325        }
1326
1327        font.setColor( Color.BLUE );
1328        font.addUnderlined();
1329
1330        writeStartElement( ElementTags.ANCHOR );
1331        if ( StringUtils.isNotEmpty( name )&& name.startsWith( "#" ) && StringUtils.isNotEmpty( header.getTitle() ))
1332        {
1333            name = "#" + DoxiaUtils.encodeId( header.getTitle(), true ) + "_" + name.substring( 1 );
1334        }
1335        writeAddAttribute( ElementTags.REFERENCE, HtmlTools.escapeHTML( name ) );
1336        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1337        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1338        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1339        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1340        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1341        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1342
1343        actionContext.setAction( SinkActionContext.LINK );
1344    }
1345
1346    /** {@inheritDoc} */
1347    public void anchor_()
1348    {
1349        if ( !anchorDefined )
1350        {
1351            // itext needs a defined local destination, we put an invisible text
1352            writeAddAttribute( ElementTags.BLUE, "255" );
1353            writeAddAttribute( ElementTags.GREEN, "255" );
1354            writeAddAttribute( ElementTags.RED, "255" );
1355
1356            write( "_" );
1357        }
1358
1359        anchorDefined = false;
1360
1361        writeEndElement(); // ElementTags.ANCHOR
1362
1363        actionContext.release();
1364    }
1365
1366    /** {@inheritDoc} */
1367    public void anchor( String name )
1368    {
1369        if ( name == null )
1370        {
1371            throw new NullPointerException( "Anchor name cannot be null!" );
1372        }
1373
1374        if ( StringUtils.isNotEmpty( header.getTitle() ) )
1375        {
1376            name = header.getTitle() + "_" + name;
1377        }
1378        String id = name;
1379
1380        if ( !DoxiaUtils.isValidId( id ) )
1381        {
1382            id = DoxiaUtils.encodeId( name, true );
1383
1384            String msg = "Modified invalid link: '" + name + "' to '" + id + "'";
1385            logMessage( "modifiedLink", msg );
1386        }
1387
1388        writeStartElement( ElementTags.ANCHOR );
1389        writeAddAttribute( ElementTags.NAME, id );
1390        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1391        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1392        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1393
1394        actionContext.setAction( SinkActionContext.ANCHOR );
1395    }
1396
1397    // ----------------------------------------------------------------------
1398    // Misc
1399    // ----------------------------------------------------------------------
1400
1401    /** {@inheritDoc} */
1402    public void lineBreak()
1403    {
1404        // Special case for the header
1405        if ( ( actionContext.getCurrentAction() == SinkActionContext.AUTHOR )
1406            || ( actionContext.getCurrentAction() == SinkActionContext.DATE )
1407            || ( actionContext.getCurrentAction() == SinkActionContext.TITLE ) )
1408        {
1409            return;
1410        }
1411
1412        writeStartElement( ElementTags.NEWLINE );
1413        writeEndElement();
1414    }
1415
1416    /** {@inheritDoc} */
1417    public void nonBreakingSpace()
1418    {
1419        write( " " );
1420    }
1421
1422    /** {@inheritDoc} */
1423    public void pageBreak()
1424    {
1425        writeStartElement( ElementTags.NEWPAGE );
1426        writeEndElement();
1427    }
1428
1429    /** {@inheritDoc} */
1430    public void horizontalRule()
1431    {
1432        writeStartElement( ElementTags.PARAGRAPH );
1433        writeAddAttribute( ElementTags.BLUE, "255" );
1434        writeAddAttribute( ElementTags.GREEN, "255" );
1435        writeAddAttribute( ElementTags.RED, "255" );
1436        write( "_" );
1437        writeEndElement();
1438
1439        writeStartElement( ElementTags.PARAGRAPH );
1440        writeStartElement( ElementTags.HORIZONTALRULE );
1441        writeEndElement();
1442        writeEndElement();
1443    }
1444
1445    // ----------------------------------------------------------------------
1446    // Text
1447    // ----------------------------------------------------------------------
1448
1449    /** {@inheritDoc} */
1450    public void rawText( String text )
1451    {
1452        writeStartElement( ElementTags.CHUNK );
1453        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1454        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1455        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1456        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1457        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1458        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1459
1460        write( text, false );
1461
1462        writeEndElement(); // ElementTags.CHUNK
1463    }
1464
1465    /** {@inheritDoc} */
1466    public void text( String text )
1467    {
1468        if ( StringUtils.isEmpty( text ) )
1469        {
1470            return;
1471        }
1472
1473        switch ( actionContext.getCurrentAction() )
1474        {
1475            case SinkActionContext.AUTHOR:
1476                header.addAuthor( text );
1477                break;
1478
1479            case SinkActionContext.DATE:
1480                header.setDate( text );
1481                break;
1482
1483            case SinkActionContext.TITLE:
1484                header.setTitle( text );
1485                break;
1486
1487            case SinkActionContext.TABLE_CAPTION:
1488                this.tableCaptionXMLWriter.writeText( text );
1489                break;
1490
1491            case SinkActionContext.VERBATIM:
1492                // Used to preserve indentation and formating
1493                LineNumberReader lnr = new LineNumberReader( new StringReader( text ) );
1494                String line;
1495                try
1496                {
1497                    while ( ( line = lnr.readLine() ) != null )
1498                    {
1499                        writeStartElement( ElementTags.CHUNK );
1500                        writeAddAttribute( ElementTags.FONT, font.getFontName() );
1501                        writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1502                        writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1503                        writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1504                        writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1505                        writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1506
1507                        write( "<![CDATA[", true );
1508                        // Special case
1509                        line = StringUtils.replace( line, "<![CDATA[", "< ![CDATA[" );
1510                        line = StringUtils.replace( line, "]]>", "]] >" );
1511                        write( line, true, false );
1512                        write( "]]>", true );
1513
1514                        writeEndElement();
1515                        lineBreak();
1516                    }
1517                }
1518                catch ( IOException e )
1519                {
1520                    throw new RuntimeException( "IOException: ", e );
1521                }
1522                break;
1523
1524            case SinkActionContext.FIGURE_CAPTION:
1525                writeAddAttribute( ElementTags.ALT, text );
1526                break;
1527
1528            case SinkActionContext.SECTION_TITLE:
1529            case SinkActionContext.SECTION_1:
1530            case SinkActionContext.SECTION_2:
1531            case SinkActionContext.SECTION_3:
1532            case SinkActionContext.SECTION_4:
1533            case SinkActionContext.SECTION_5:
1534            case SinkActionContext.FIGURE:
1535            case SinkActionContext.FIGURE_GRAPHICS:
1536            case SinkActionContext.TABLE_ROW:
1537            case SinkActionContext.TABLE:
1538            case SinkActionContext.HEAD:
1539            case SinkActionContext.UNDEFINED:
1540                break;
1541
1542            case SinkActionContext.ANCHOR:
1543                anchorDefined = true;
1544            case SinkActionContext.PARAGRAPH:
1545            case SinkActionContext.LINK:
1546            case SinkActionContext.TABLE_CELL:
1547            case SinkActionContext.TABLE_HEADER_CELL:
1548            case SinkActionContext.DEFINITION:
1549            case SinkActionContext.DEFINED_TERM:
1550            case SinkActionContext.NUMBERED_LIST_ITEM:
1551            case SinkActionContext.LIST_ITEM:
1552            case SinkActionContext.SECTION_TITLE_5:
1553            case SinkActionContext.SECTION_TITLE_4:
1554            case SinkActionContext.SECTION_TITLE_3:
1555            case SinkActionContext.SECTION_TITLE_2:
1556            case SinkActionContext.SECTION_TITLE_1:
1557            default:
1558                writeStartElement( ElementTags.CHUNK );
1559                writeAddAttribute( ElementTags.FONT, font.getFontName() );
1560                writeAddAttribute( ElementTags.SIZE, font.getFontSize() );
1561                writeAddAttribute( ElementTags.STYLE, font.getFontStyle() );
1562                writeAddAttribute( ElementTags.BLUE, font.getFontColorBlue() );
1563                writeAddAttribute( ElementTags.GREEN, font.getFontColorGreen() );
1564                writeAddAttribute( ElementTags.RED, font.getFontColorRed() );
1565
1566                write( text );
1567
1568                writeEndElement(); // ElementTags.CHUNK
1569        }
1570    }
1571
1572    /**
1573     * {@inheritDoc}
1574     *
1575     * Unkown events just log a warning message but are ignored otherwise.
1576     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
1577     */
1578    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1579    {
1580        String msg = "Unknown Sink event: '" + name + "', ignoring!";
1581        logMessage( "unknownEvent", msg );
1582    }
1583
1584    /** {@inheritDoc} */
1585    protected void init()
1586    {
1587        super.init();
1588
1589        this.actionContext = new SinkActionContext();
1590        this.font = new ITextFont();
1591        this.header = new ITextHeader();
1592
1593        this.numberDepth = 1;
1594        this.depth = 0;
1595        this.tableCaptionWriter = null;
1596        this.tableCaptionXMLWriter = null;
1597        this.anchorDefined = false;
1598        this.figureDefined = false;
1599        this.warnMessages = null;
1600    }
1601
1602    /**
1603     * Convenience method to write a starting element.
1604     *
1605     * @param tag the name of the tag
1606     */
1607    private void writeStartElement( String tag )
1608    {
1609        if ( tableCaptionXMLWriter == null )
1610        {
1611            xmlWriter.startElement( tag );
1612        }
1613        else
1614        {
1615            tableCaptionXMLWriter.startElement( tag );
1616        }
1617    }
1618
1619    /**
1620     * Convenience method to write a key-value pair.
1621     *
1622     * @param key the name of an attribute
1623     * @param value the value of an attribute
1624     */
1625    private void writeAddAttribute( String key, String value )
1626    {
1627        if ( tableCaptionXMLWriter == null )
1628        {
1629            xmlWriter.addAttribute( key, value );
1630        }
1631        else
1632        {
1633            tableCaptionXMLWriter.addAttribute( key, value );
1634        }
1635    }
1636
1637    /**
1638     * Convenience method to write a key-value pair.
1639     *
1640     * @param key the name of an attribute
1641     * @param value the value of an attribute
1642     */
1643    private void writeAddAttribute( String key, int value )
1644    {
1645        if ( tableCaptionXMLWriter == null )
1646        {
1647            xmlWriter.addAttribute( key, String.valueOf( value ) );
1648        }
1649        else
1650        {
1651            tableCaptionXMLWriter.addAttribute( key, String.valueOf( value ) );
1652        }
1653    }
1654
1655    /**
1656     * Convenience method to write an end element.
1657     */
1658    private void writeEndElement()
1659    {
1660        if ( tableCaptionXMLWriter == null )
1661        {
1662            xmlWriter.endElement();
1663        }
1664        else
1665        {
1666            tableCaptionXMLWriter.endElement();
1667        }
1668    }
1669
1670    /**
1671     * Convenience method to write a String
1672     *
1673     * @param aString
1674     */
1675    protected void write( String aString )
1676    {
1677        write( aString, false );
1678    }
1679
1680    /**
1681     * Convenience method to write a String depending the escapeHtml flag
1682     *
1683     * @param aString
1684     * @param escapeHtml
1685     */
1686    private void write( String aString, boolean escapeHtml )
1687    {
1688        write( aString, escapeHtml, true );
1689    }
1690
1691    /**
1692     * Convenience method to write a String depending the escapeHtml flag
1693     *
1694     * @param aString
1695     * @param escapeHtml
1696     * @param trim
1697     */
1698    private void write( String aString, boolean escapeHtml, boolean trim )
1699    {
1700        if ( aString == null )
1701        {
1702            return;
1703        }
1704
1705        if ( trim )
1706        {
1707            aString = StringUtils.replace( aString, "\n", "" );
1708
1709            LineNumberReader lnr = new LineNumberReader( new StringReader( aString ) );
1710            StringBuilder sb = new StringBuilder();
1711            String line;
1712            try
1713            {
1714                while ( ( line = lnr.readLine() ) != null )
1715                {
1716                    sb.append( beautifyPhrase( line.trim() ) );
1717                    sb.append( " " );
1718                }
1719
1720                aString = sb.toString();
1721            }
1722            catch ( IOException e )
1723            {
1724                // nop
1725            }
1726            if ( aString.trim().length() == 0 )
1727            {
1728                return;
1729            }
1730        }
1731        if ( escapeHtml )
1732        {
1733            if ( tableCaptionXMLWriter == null )
1734            {
1735                xmlWriter.writeMarkup( aString );
1736            }
1737            else
1738            {
1739                tableCaptionXMLWriter.writeMarkup( aString );
1740            }
1741        }
1742        else
1743        {
1744            if ( tableCaptionXMLWriter == null )
1745            {
1746                xmlWriter.writeText( aString );
1747            }
1748            else
1749            {
1750                tableCaptionXMLWriter.writeText( aString );
1751            }
1752        }
1753    }
1754
1755    /**
1756     * Convenience method to return a beautify phrase, i.e. one space between words.
1757     *
1758     * @param aString
1759     * @return a String with only one space between words
1760     */
1761    private static String beautifyPhrase( String aString )
1762    {
1763        String[] strings = StringUtils.split( aString, " " );
1764        StringBuilder sb = new StringBuilder();
1765        for ( int i = 0; i < strings.length; i++ )
1766        {
1767            if ( strings[i].trim().length() != 0 )
1768            {
1769                sb.append( strings[i].trim() );
1770                sb.append( " " );
1771            }
1772        }
1773
1774        return sb.toString().trim();
1775    }
1776
1777    private void startChunk( String fontName, int fontSize, String fontStyle, int fontColorBlue, int fontColorGreen,
1778                             int fontColorRed, String localDestination )
1779    {
1780        writeStartElement( ElementTags.CHUNK );
1781        writeAddAttribute( ElementTags.FONT, fontName );
1782        writeAddAttribute( ElementTags.SIZE, fontSize );
1783        writeAddAttribute( ElementTags.STYLE, fontStyle );
1784        writeAddAttribute( ElementTags.BLUE, fontColorBlue );
1785        writeAddAttribute( ElementTags.GREEN, fontColorGreen );
1786        writeAddAttribute( ElementTags.RED, fontColorRed );
1787//        writeAddAttribute( ElementTags.LOCALDESTINATION, localDestination );
1788    }
1789
1790    /**
1791     * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
1792     *
1793     * @param key not null
1794     * @param msg not null
1795     * @see #close()
1796     * @since 1.1.1
1797     */
1798    private void logMessage( String key, String msg )
1799    {
1800        msg = "[iText Sink] " + msg;
1801        if ( getLog().isDebugEnabled() )
1802        {
1803            getLog().debug( msg );
1804
1805            return;
1806        }
1807
1808        if ( warnMessages == null )
1809        {
1810            warnMessages = new HashMap<String, Set<String>>();
1811        }
1812
1813        Set<String> set = warnMessages.get( key );
1814        if ( set == null )
1815        {
1816            set = new TreeSet<String>();
1817        }
1818        set.add( msg );
1819        warnMessages.put( key, set );
1820    }
1821}