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