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