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