View Javadoc

1   package org.apache.maven.doxia.sink;
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 java.io.PrintWriter;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.util.Enumeration;
26  import java.util.HashMap;
27  import java.util.LinkedList;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.TreeSet;
31  
32  import javax.swing.text.MutableAttributeSet;
33  import javax.swing.text.html.HTML.Attribute;
34  import javax.swing.text.html.HTML.Tag;
35  
36  import org.apache.maven.doxia.markup.HtmlMarkup;
37  import org.apache.maven.doxia.markup.Markup;
38  import org.apache.maven.doxia.util.DoxiaUtils;
39  import org.apache.maven.doxia.util.HtmlTools;
40  
41  import org.codehaus.plexus.util.StringUtils;
42  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
43  
44  /**
45   * Abstract base xhtml sink implementation.
46   *
47   * @author Jason van Zyl
48   * @author ltheussl
49   * @version $Id: XhtmlBaseSink.java 1090706 2011-04-09 23:15:28Z hboutemy $
50   * @since 1.1
51   */
52  public class XhtmlBaseSink
53      extends AbstractXmlSink
54      implements HtmlMarkup
55  {
56      // ----------------------------------------------------------------------
57      // Instance fields
58      // ----------------------------------------------------------------------
59  
60      /** The PrintWriter to write the result. */
61      private final PrintWriter writer;
62  
63      /** Used to collect text events mainly for the head events. */
64      private StringBuffer textBuffer = new StringBuffer();
65  
66      /** An indication on if we're inside a head. */
67      private boolean headFlag;
68  
69      /** An indication on if we're inside an image caption flag. */
70      private boolean figureCaptionFlag;
71  
72      /** An indication on if we're inside a paragraph flag. */
73      private boolean paragraphFlag;
74  
75      /** An indication on if we're in verbatim mode. */
76      private boolean verbatimFlag;
77  
78      /** Stack of alignment int[] of table cells. */
79      private final LinkedList<int[]> cellJustifStack;
80  
81      /** Stack of justification of table cells. */
82      private final LinkedList<Boolean> isCellJustifStack;
83  
84      /** Stack of current table cell. */
85      private final LinkedList<Integer> cellCountStack;
86  
87      /** Used to style successive table rows differently. */
88      private boolean evenTableRow = true;
89  
90      /** The stack of StringWriter to write the table result temporary, so we could play with the output DOXIA-177. */
91      private final LinkedList<StringWriter> tableContentWriterStack;
92  
93      private final LinkedList<StringWriter> tableCaptionWriterStack;
94  
95      private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
96  
97      /** The stack of table caption */
98      private final LinkedList<String> tableCaptionStack;
99  
100     /** used to store attributes passed to table(). */
101     protected MutableAttributeSet tableAttributes;
102 
103     /** Used to distinguish old-style figure handling. */
104     private boolean legacyFigure;
105 
106     /** Used to distinguish old-style figure handling. */
107     private boolean legacyFigureCaption;
108 
109     /** Indicates that an image is part of a figure. */
110     private boolean inFigure;
111 
112     /** Flag to know if {@link #tableRows(int[], boolean)} is called or not. It is mainly to be backward compatible
113      * with some plugins (like checkstyle) which uses:
114      * <pre>
115      * sink.table();
116      * sink.tableRow();
117      * </pre>
118      * instead of
119      * <pre>
120      * sink.table();
121      * sink.tableRows( justify, true );
122      * sink.tableRow();
123      * </pre>
124      * */
125     protected boolean tableRows = false;
126 
127     /** Map of warn messages with a String as key to describe the error type and a Set as value.
128      * Using to reduce warn messages. */
129     private Map<String, Set<String>> warnMessages;
130 
131     // ----------------------------------------------------------------------
132     // Constructor
133     // ----------------------------------------------------------------------
134 
135     /**
136      * Constructor, initialize the PrintWriter.
137      *
138      * @param out The writer to write the result.
139      */
140     public XhtmlBaseSink( Writer out )
141     {
142         this.writer = new PrintWriter( out );
143 
144         this.cellJustifStack = new LinkedList<int[]>();
145         this.isCellJustifStack = new LinkedList<Boolean>();
146         this.cellCountStack = new LinkedList<Integer>();
147         this.tableContentWriterStack = new LinkedList<StringWriter>();
148         this.tableCaptionWriterStack = new LinkedList<StringWriter>();
149         this.tableCaptionXMLWriterStack = new LinkedList<PrettyPrintXMLWriter>();
150         this.tableCaptionStack = new LinkedList<String>();
151 
152         init();
153     }
154 
155     // ----------------------------------------------------------------------
156     // Accessor methods
157     // ----------------------------------------------------------------------
158 
159     /**
160      * To use mainly when playing with the head events.
161      *
162      * @return the current buffer of text events.
163      */
164     protected StringBuffer getTextBuffer()
165     {
166         return this.textBuffer;
167     }
168 
169     /**
170      * <p>Setter for the field <code>headFlag</code>.</p>
171      *
172      * @param headFlag an header flag.
173      */
174     protected void setHeadFlag( boolean headFlag )
175     {
176         this.headFlag = headFlag;
177     }
178 
179     /**
180      * <p>isHeadFlag.</p>
181      *
182      * @return the current headFlag.
183      */
184     protected boolean isHeadFlag()
185     {
186         return this.headFlag ;
187     }
188 
189     /**
190      * <p>Setter for the field <code>verbatimFlag</code>.</p>
191      *
192      * @param verb a verbatim flag.
193      */
194     protected void setVerbatimFlag( boolean verb )
195     {
196         this.verbatimFlag = verb;
197     }
198 
199     /**
200      * <p>isVerbatimFlag.</p>
201      *
202      * @return the current verbatim flag.
203      */
204     protected boolean isVerbatimFlag()
205     {
206         return this.verbatimFlag ;
207     }
208 
209     /**
210      * <p>Setter for the field <code>cellJustif</code>.</p>
211      *
212      * @param justif the new cell justification array.
213      */
214     protected void setCellJustif( int[] justif )
215     {
216         this.cellJustifStack.addLast( justif );
217         this.isCellJustifStack.addLast( Boolean.TRUE );
218     }
219 
220     /**
221      * <p>Getter for the field <code>cellJustif</code>.</p>
222      *
223      * @return the current cell justification array.
224      */
225     protected int[] getCellJustif()
226     {
227         return (int[]) this.cellJustifStack.getLast();
228     }
229 
230     /**
231      * <p>Setter for the field <code>cellCount</code>.</p>
232      *
233      * @param count the new cell count.
234      */
235     protected void setCellCount( int count )
236     {
237         this.cellCountStack.addLast( count );
238     }
239 
240     /**
241      * <p>Getter for the field <code>cellCount</code>.</p>
242      *
243      * @return the current cell count.
244      */
245     protected int getCellCount()
246     {
247         return Integer.parseInt( this.cellCountStack.getLast().toString() );
248     }
249 
250     /**
251      * Reset all variables.
252      *
253      * @deprecated since 1.1.2, use {@link #init()} instead of.
254      */
255     protected void resetState()
256     {
257         init();
258     }
259 
260     /** {@inheritDoc} */
261     protected void init()
262     {
263         super.init();
264 
265         resetTextBuffer();
266 
267         this.headFlag = false;
268         this.verbatimFlag = false;
269         this.evenTableRow = true;
270         this.cellJustifStack.clear();
271         this.isCellJustifStack.clear();
272         this.cellCountStack.clear();
273         this.tableContentWriterStack.clear();
274         this.tableCaptionWriterStack.clear();
275         this.tableCaptionXMLWriterStack.clear();
276         this.tableCaptionStack.clear();
277 
278         this.headFlag = false;
279         this.figureCaptionFlag = false;
280         this.paragraphFlag = false;
281         this.verbatimFlag = false;
282         this.evenTableRow = true;
283         this.tableAttributes = null;
284         this.legacyFigure = false;
285         this.legacyFigureCaption = false;
286         this.inFigure = false;
287         this.tableRows = false;
288         this.warnMessages = null;
289     }
290 
291     /**
292      * Reset the text buffer.
293      */
294     protected void resetTextBuffer()
295     {
296         this.textBuffer = new StringBuffer();
297     }
298 
299     // ----------------------------------------------------------------------
300     // Sections
301     // ----------------------------------------------------------------------
302 
303     /** {@inheritDoc} */
304     public void section( int level, SinkEventAttributes attributes )
305     {
306         onSection( level, attributes );
307     }
308 
309     /** {@inheritDoc} */
310     public void sectionTitle( int level, SinkEventAttributes attributes )
311     {
312         onSectionTitle( level, attributes );
313     }
314 
315     /** {@inheritDoc} */
316     public void sectionTitle_( int level )
317     {
318         onSectionTitle_( level );
319     }
320 
321     /** {@inheritDoc} */
322     public void section_( int level )
323     {
324         onSection_( level );
325     }
326 
327     /** {@inheritDoc} */
328     public void section1()
329     {
330         onSection( SECTION_LEVEL_1, null );
331     }
332 
333     /** {@inheritDoc} */
334     public void sectionTitle1()
335     {
336         onSectionTitle( SECTION_LEVEL_1, null );
337     }
338 
339     /** {@inheritDoc} */
340     public void sectionTitle1_()
341     {
342         onSectionTitle_( SECTION_LEVEL_1 );
343     }
344 
345     /** {@inheritDoc} */
346     public void section1_()
347     {
348         onSection_( SECTION_LEVEL_1 );
349     }
350 
351     /** {@inheritDoc} */
352     public void section2()
353     {
354         onSection( SECTION_LEVEL_2, null );
355     }
356 
357     /** {@inheritDoc} */
358     public void sectionTitle2()
359     {
360         onSectionTitle( SECTION_LEVEL_2, null );
361     }
362 
363     /** {@inheritDoc} */
364     public void sectionTitle2_()
365     {
366         onSectionTitle_( SECTION_LEVEL_2 );
367     }
368 
369     /** {@inheritDoc} */
370     public void section2_()
371     {
372         onSection_( SECTION_LEVEL_2 );
373     }
374 
375     /** {@inheritDoc} */
376     public void section3()
377     {
378         onSection( SECTION_LEVEL_3, null );
379     }
380 
381     /** {@inheritDoc} */
382     public void sectionTitle3()
383     {
384         onSectionTitle( SECTION_LEVEL_3, null );
385     }
386 
387     /** {@inheritDoc} */
388     public void sectionTitle3_()
389     {
390         onSectionTitle_( SECTION_LEVEL_3 );
391     }
392 
393     /** {@inheritDoc} */
394     public void section3_()
395     {
396         onSection_( SECTION_LEVEL_3 );
397     }
398 
399     /** {@inheritDoc} */
400     public void section4()
401     {
402         onSection( SECTION_LEVEL_4, null );
403     }
404 
405     /** {@inheritDoc} */
406     public void sectionTitle4()
407     {
408         onSectionTitle( SECTION_LEVEL_4, null );
409     }
410 
411     /** {@inheritDoc} */
412     public void sectionTitle4_()
413     {
414         onSectionTitle_( SECTION_LEVEL_4 );
415     }
416 
417     /** {@inheritDoc} */
418     public void section4_()
419     {
420         onSection_( SECTION_LEVEL_4 );
421     }
422 
423     /** {@inheritDoc} */
424     public void section5()
425     {
426         onSection( SECTION_LEVEL_5, null );
427     }
428 
429     /** {@inheritDoc} */
430     public void sectionTitle5()
431     {
432         onSectionTitle( SECTION_LEVEL_5, null );
433     }
434 
435     /** {@inheritDoc} */
436     public void sectionTitle5_()
437     {
438         onSectionTitle_( SECTION_LEVEL_5 );
439     }
440 
441     /** {@inheritDoc} */
442     public void section5_()
443     {
444         onSection_( SECTION_LEVEL_5 );
445     }
446 
447     /**
448      * Starts a section. The default class style is <code>section</code>.
449      *
450      * @param depth The level of the section.
451      * @param attributes some attributes. May be null.
452      * @see javax.swing.text.html.HTML.Tag#DIV
453      */
454     protected void onSection( int depth, SinkEventAttributes attributes )
455     {
456         if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
457         {
458             MutableAttributeSet att = new SinkEventAttributeSet();
459             att.addAttribute( Attribute.CLASS, "section" );
460             // NOTE: any class entry in attributes will overwrite the above
461             att.addAttributes( SinkUtils.filterAttributes(
462                     attributes, SinkUtils.SINK_BASE_ATTRIBUTES  ) );
463 
464             att.removeAttribute( Attribute.ID.toString() );
465             writeStartTag( HtmlMarkup.DIV, att );
466         }
467     }
468 
469     /**
470      * Ends a section.
471      *
472      * @param depth The level of the section.
473      * @see javax.swing.text.html.HTML.Tag#DIV
474      */
475     protected void onSection_( int depth )
476     {
477         if ( depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_5 )
478         {
479             writeEndTag( HtmlMarkup.DIV );
480         }
481     }
482 
483     /**
484      * Starts a section title.
485      *
486      * @param depth The level of the section title.
487      * @param attributes some attributes. May be null.
488      * @see javax.swing.text.html.HTML.Tag#H2
489      * @see javax.swing.text.html.HTML.Tag#H3
490      * @see javax.swing.text.html.HTML.Tag#H4
491      * @see javax.swing.text.html.HTML.Tag#H5
492      * @see javax.swing.text.html.HTML.Tag#H6
493      */
494     protected void onSectionTitle( int depth, SinkEventAttributes attributes )
495     {
496         MutableAttributeSet atts = SinkUtils.filterAttributes(
497                 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
498 
499         if ( depth == SECTION_LEVEL_1 )
500         {
501             writeStartTag( HtmlMarkup.H2, atts );
502         }
503         else if ( depth == SECTION_LEVEL_2 )
504         {
505             writeStartTag( HtmlMarkup.H3, atts );
506         }
507         else if ( depth == SECTION_LEVEL_3 )
508         {
509             writeStartTag( HtmlMarkup.H4, atts );
510         }
511         else if ( depth == SECTION_LEVEL_4 )
512         {
513             writeStartTag( HtmlMarkup.H5, atts );
514         }
515         else if ( depth == SECTION_LEVEL_5 )
516         {
517             writeStartTag( HtmlMarkup.H6, atts );
518         }
519     }
520 
521     /**
522      * Ends a section title.
523      *
524      * @param depth The level of the section title.
525      * @see javax.swing.text.html.HTML.Tag#H2
526      * @see javax.swing.text.html.HTML.Tag#H3
527      * @see javax.swing.text.html.HTML.Tag#H4
528      * @see javax.swing.text.html.HTML.Tag#H5
529      * @see javax.swing.text.html.HTML.Tag#H6
530      */
531     protected void onSectionTitle_( int depth )
532     {
533         if ( depth == SECTION_LEVEL_1 )
534         {
535             writeEndTag( HtmlMarkup.H2 );
536         }
537         else if ( depth == SECTION_LEVEL_2 )
538         {
539             writeEndTag( HtmlMarkup.H3 );
540         }
541         else if ( depth == SECTION_LEVEL_3 )
542         {
543             writeEndTag( HtmlMarkup.H4 );
544         }
545         else if ( depth == SECTION_LEVEL_4 )
546         {
547             writeEndTag( HtmlMarkup.H5 );
548         }
549         else if ( depth == SECTION_LEVEL_5 )
550         {
551             writeEndTag( HtmlMarkup.H6 );
552         }
553     }
554 
555     // -----------------------------------------------------------------------
556     //
557     // -----------------------------------------------------------------------
558 
559     /**
560      * {@inheritDoc}
561      * @see javax.swing.text.html.HTML.Tag#UL
562      */
563     public void list()
564     {
565         list( null );
566     }
567 
568     /**
569      * {@inheritDoc}
570      * @see javax.swing.text.html.HTML.Tag#UL
571      */
572     public void list( SinkEventAttributes attributes )
573     {
574         if ( paragraphFlag )
575         {
576             // The content of element type "p" must match
577             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
578             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
579             paragraph_();
580         }
581 
582         MutableAttributeSet atts = SinkUtils.filterAttributes(
583                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
584 
585         writeStartTag( HtmlMarkup.UL, atts );
586     }
587 
588     /**
589      * {@inheritDoc}
590      * @see javax.swing.text.html.HTML.Tag#UL
591      */
592     public void list_()
593     {
594         writeEndTag( HtmlMarkup.UL );
595     }
596 
597     /**
598      * {@inheritDoc}
599      * @see javax.swing.text.html.HTML.Tag#LI
600      */
601     public void listItem()
602     {
603         listItem( null );
604     }
605 
606     /**
607      * {@inheritDoc}
608      * @see javax.swing.text.html.HTML.Tag#LI
609      */
610     public void listItem( SinkEventAttributes attributes )
611     {
612         MutableAttributeSet atts = SinkUtils.filterAttributes(
613                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
614 
615         writeStartTag( HtmlMarkup.LI, atts );
616     }
617 
618     /**
619      * {@inheritDoc}
620      * @see javax.swing.text.html.HTML.Tag#LI
621      */
622     public void listItem_()
623     {
624         writeEndTag( HtmlMarkup.LI );
625     }
626 
627     /**
628      * The default list style depends on the numbering.
629      *
630      * {@inheritDoc}
631      * @see javax.swing.text.html.HTML.Tag#OL
632      */
633     public void numberedList( int numbering )
634     {
635         numberedList( numbering, null );
636     }
637 
638     /**
639      * The default list style depends on the numbering.
640      *
641      * {@inheritDoc}
642      * @see javax.swing.text.html.HTML.Tag#OL
643      */
644     public void numberedList( int numbering, SinkEventAttributes attributes )
645     {
646         if ( paragraphFlag )
647         {
648             // The content of element type "p" must match
649             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
650             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
651             paragraph_();
652         }
653 
654         String style;
655         switch ( numbering )
656         {
657             case NUMBERING_UPPER_ALPHA:
658                 style = "upper-alpha";
659                 break;
660             case NUMBERING_LOWER_ALPHA:
661                 style = "lower-alpha";
662                 break;
663             case NUMBERING_UPPER_ROMAN:
664                 style = "upper-roman";
665                 break;
666             case NUMBERING_LOWER_ROMAN:
667                 style = "lower-roman";
668                 break;
669             case NUMBERING_DECIMAL:
670             default:
671                 style = "decimal";
672         }
673 
674         MutableAttributeSet atts = SinkUtils.filterAttributes(
675                 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
676 
677         if ( atts == null )
678         {
679             atts = new SinkEventAttributeSet( 1 );
680         }
681 
682         atts.addAttribute( Attribute.STYLE, "list-style-type: " + style );
683 
684         writeStartTag( HtmlMarkup.OL, atts );
685     }
686 
687     /**
688      * {@inheritDoc}
689      * @see javax.swing.text.html.HTML.Tag#OL
690      */
691     public void numberedList_()
692     {
693         writeEndTag( HtmlMarkup.OL );
694     }
695 
696     /**
697      * {@inheritDoc}
698      * @see javax.swing.text.html.HTML.Tag#LI
699      */
700     public void numberedListItem()
701     {
702         numberedListItem( null );
703     }
704 
705     /**
706      * {@inheritDoc}
707      * @see javax.swing.text.html.HTML.Tag#LI
708      */
709     public void numberedListItem( SinkEventAttributes attributes )
710     {
711         MutableAttributeSet atts = SinkUtils.filterAttributes(
712                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
713 
714         writeStartTag( HtmlMarkup.LI, atts );
715     }
716 
717     /**
718      * {@inheritDoc}
719      * @see javax.swing.text.html.HTML.Tag#LI
720      */
721     public void numberedListItem_()
722     {
723         writeEndTag( HtmlMarkup.LI );
724     }
725 
726     /**
727      * {@inheritDoc}
728      * @see javax.swing.text.html.HTML.Tag#DL
729      */
730     public void definitionList()
731     {
732         definitionList( null );
733     }
734 
735     /**
736      * {@inheritDoc}
737      * @see javax.swing.text.html.HTML.Tag#DL
738      */
739     public void definitionList( SinkEventAttributes attributes )
740     {
741         if ( paragraphFlag )
742         {
743             // The content of element type "p" must match
744             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
745             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
746             paragraph_();
747         }
748 
749         MutableAttributeSet atts = SinkUtils.filterAttributes(
750                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
751 
752         writeStartTag( HtmlMarkup.DL, atts );
753     }
754 
755     /**
756      * {@inheritDoc}
757      * @see javax.swing.text.html.HTML.Tag#DL
758      */
759     public void definitionList_()
760     {
761         writeEndTag( HtmlMarkup.DL );
762     }
763 
764     /**
765      * {@inheritDoc}
766      * @see javax.swing.text.html.HTML.Tag#DT
767      */
768     public void definedTerm( SinkEventAttributes attributes )
769     {
770         MutableAttributeSet atts = SinkUtils.filterAttributes(
771                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
772 
773         writeStartTag( HtmlMarkup.DT, atts );
774     }
775 
776     /**
777      * {@inheritDoc}
778      * @see javax.swing.text.html.HTML.Tag#DT
779      */
780     public void definedTerm()
781     {
782         definedTerm( null );
783     }
784 
785     /**
786      * {@inheritDoc}
787      * @see javax.swing.text.html.HTML.Tag#DT
788      */
789     public void definedTerm_()
790     {
791         writeEndTag( HtmlMarkup.DT );
792     }
793 
794     /**
795      * {@inheritDoc}
796      * @see javax.swing.text.html.HTML.Tag#DD
797      */
798     public void definition()
799     {
800         definition( null );
801     }
802 
803     /**
804      * {@inheritDoc}
805      * @see javax.swing.text.html.HTML.Tag#DD
806      */
807     public void definition( SinkEventAttributes attributes )
808     {
809         MutableAttributeSet atts = SinkUtils.filterAttributes(
810                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
811 
812         writeStartTag( HtmlMarkup.DD, atts );
813     }
814 
815     /**
816      * {@inheritDoc}
817      * @see javax.swing.text.html.HTML.Tag#DD
818      */
819     public void definition_()
820     {
821         writeEndTag( HtmlMarkup.DD );
822     }
823 
824     /**
825      * {@inheritDoc}
826      * @see javax.swing.text.html.HTML.Tag#IMG
827      * @deprecated Use {@link #figure(SinkEventAttributes)}, this method is only kept for
828      * backward compatibility. Note that the behavior is different though, as this method
829      * writes an img tag, while correctly the img tag should be written by  figureGraphics().
830      */
831     public void figure()
832     {
833         write( String.valueOf( LESS_THAN ) + HtmlMarkup.IMG );
834         legacyFigure = true;
835     }
836 
837     /**
838      * {@inheritDoc}
839      * @see javax.swing.text.html.HTML.Tag#IMG
840      */
841     public void figure( SinkEventAttributes attributes )
842     {
843         inFigure = true;
844 
845         MutableAttributeSet atts = SinkUtils.filterAttributes(
846                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
847 
848         if ( atts == null )
849         {
850             atts = new SinkEventAttributeSet( 1 );
851         }
852 
853         if ( !atts.isDefined( SinkEventAttributes.CLASS ) )
854         {
855             atts.addAttribute( SinkEventAttributes.CLASS, "figure" );
856         }
857 
858         writeStartTag( HtmlMarkup.DIV, atts );
859     }
860 
861     /** {@inheritDoc} */
862     public void figure_()
863     {
864         if ( legacyFigure )
865         {
866             if ( !figureCaptionFlag )
867             {
868                 // Attribute "alt" is required and must be specified for element type "img".
869                 write( String.valueOf( SPACE ) + Attribute.ALT + EQUAL + QUOTE + QUOTE );
870             }
871             write( String.valueOf( SPACE ) + SLASH + GREATER_THAN );
872             legacyFigure = false;
873         }
874         else
875         {
876             writeEndTag( HtmlMarkup.DIV );
877             inFigure = false;
878         }
879 
880         figureCaptionFlag = false;
881     }
882 
883     /**
884      * {@inheritDoc}
885      * @deprecated Use {@link #figureGraphics(String,SinkEventAttributes)},
886      * this method is only kept for backward compatibility. Note that the behavior is
887      * different though, as this method does not write the img tag, only the src attribute.
888      */
889     public void figureGraphics( String name )
890     {
891         write( String.valueOf( SPACE ) + Attribute.SRC + EQUAL + QUOTE + escapeHTML( name ) + QUOTE );
892     }
893 
894     /** {@inheritDoc} */
895     public void figureGraphics( String src, SinkEventAttributes attributes )
896     {
897         if ( inFigure )
898         {
899             MutableAttributeSet atts = new SinkEventAttributeSet( 1 );
900             atts.addAttribute( SinkEventAttributes.ALIGN, "center" );
901 
902             writeStartTag( HtmlMarkup.P, atts );
903         }
904 
905         MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, SinkUtils.SINK_IMG_ATTRIBUTES );
906         if ( filtered != null )
907         {
908             filtered.removeAttribute( Attribute.SRC.toString() );
909         }
910 
911         int count = ( attributes == null ? 1 : attributes.getAttributeCount() + 1 );
912 
913         MutableAttributeSet atts = new SinkEventAttributeSet( count );
914 
915         atts.addAttribute( Attribute.SRC, escapeHTML( src ) );
916         atts.addAttributes( filtered );
917 
918         if ( atts.getAttribute( Attribute.ALT.toString() ) == null )
919         {
920             atts.addAttribute( Attribute.ALT.toString(), "" );
921         }
922 
923         writeStartTag( HtmlMarkup.IMG, atts, true );
924 
925         if ( inFigure )
926         {
927             writeEndTag( HtmlMarkup.P );
928         }
929     }
930 
931     /**
932      * {@inheritDoc}
933      * @deprecated Use {@link #figureCaption(SinkEventAttributes)},
934      * this method is only kept for backward compatibility. Note that the behavior is
935      * different though, as this method only writes an alt attribute.
936      */
937     public void figureCaption()
938     {
939         figureCaptionFlag = true;
940         write( String.valueOf( SPACE ) + Attribute.ALT + EQUAL + QUOTE );
941         legacyFigureCaption = true;
942     }
943 
944     /** {@inheritDoc} */
945     public void figureCaption( SinkEventAttributes attributes )
946     {
947         if ( legacyFigureCaption )
948         {
949             write( String.valueOf( SPACE ) + Attribute.ALT + EQUAL + QUOTE );
950             legacyFigureCaption = false;
951             figureCaptionFlag = true;
952         }
953         else
954         {
955             SinkEventAttributeSet atts = new SinkEventAttributeSet( 1 );
956             atts.addAttribute( SinkEventAttributes.ALIGN, "center" );
957             atts.addAttributes( SinkUtils.filterAttributes(
958                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  ) );
959 
960             paragraph( atts );
961             italic();
962         }
963     }
964 
965     /** {@inheritDoc} */
966     public void figureCaption_()
967     {
968         if ( legacyFigureCaption )
969         {
970             write( String.valueOf( QUOTE ) );
971         }
972         else
973         {
974             italic_();
975             paragraph_();
976         }
977     }
978 
979     /**
980      * {@inheritDoc}
981      * @see javax.swing.text.html.HTML.Tag#P
982      */
983     public void paragraph()
984     {
985         paragraph( null );
986     }
987 
988     /**
989      * {@inheritDoc}
990      * @see javax.swing.text.html.HTML.Tag#P
991      */
992     public void paragraph( SinkEventAttributes attributes )
993     {
994         paragraphFlag = true;
995 
996         MutableAttributeSet atts = SinkUtils.filterAttributes(
997                 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
998 
999         writeStartTag( HtmlMarkup.P, atts );
1000     }
1001 
1002     /**
1003      * {@inheritDoc}
1004      * @see javax.swing.text.html.HTML.Tag#P
1005      */
1006     public void paragraph_()
1007     {
1008         if ( paragraphFlag )
1009         {
1010             writeEndTag( HtmlMarkup.P );
1011             paragraphFlag = false;
1012         }
1013     }
1014 
1015     /**
1016      * The default class style for boxed is <code>source</code>.
1017      *
1018      * {@inheritDoc}
1019      * @see javax.swing.text.html.HTML.Tag#DIV
1020      * @see javax.swing.text.html.HTML.Tag#PRE
1021      */
1022     public void verbatim( boolean boxed )
1023     {
1024         if ( boxed )
1025         {
1026             verbatim( SinkEventAttributeSet.BOXED );
1027         }
1028         else
1029         {
1030             verbatim( null );
1031         }
1032     }
1033 
1034     /**
1035      * The default class style for boxed is <code>source</code>.
1036      *
1037      * {@inheritDoc}
1038      * @see javax.swing.text.html.HTML.Tag#DIV
1039      * @see javax.swing.text.html.HTML.Tag#PRE
1040      */
1041     public void verbatim( SinkEventAttributes attributes )
1042     {
1043         if ( paragraphFlag )
1044         {
1045             // The content of element type "p" must match
1046             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
1047             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
1048             paragraph_();
1049         }
1050 
1051         verbatimFlag = true;
1052 
1053         MutableAttributeSet atts = SinkUtils.filterAttributes(
1054                 attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES  );
1055 
1056         if ( atts == null )
1057         {
1058             atts = new SinkEventAttributeSet();
1059         }
1060 
1061         boolean boxed = false;
1062 
1063         if ( atts.isDefined( SinkEventAttributes.DECORATION ) )
1064         {
1065             boxed =
1066                 "boxed".equals( atts.getAttribute( SinkEventAttributes.DECORATION ).toString() );
1067         }
1068 
1069         if ( boxed )
1070         {
1071             atts.addAttribute( Attribute.CLASS, "source" );
1072         }
1073 
1074         atts.removeAttribute( SinkEventAttributes.DECORATION );
1075 
1076         String width = (String) atts.getAttribute( Attribute.WIDTH.toString() );
1077         atts.removeAttribute( Attribute.WIDTH.toString() );
1078 
1079         writeStartTag( HtmlMarkup.DIV, atts );
1080 
1081         if ( width != null )
1082         {
1083             atts.addAttribute( Attribute.WIDTH.toString(), width );
1084         }
1085 
1086         atts.removeAttribute( Attribute.ALIGN.toString() );
1087         atts.removeAttribute( Attribute.CLASS.toString() );
1088 
1089         writeStartTag( HtmlMarkup.PRE, atts );
1090     }
1091 
1092     /**
1093      * {@inheritDoc}
1094      * @see javax.swing.text.html.HTML.Tag#DIV
1095      * @see javax.swing.text.html.HTML.Tag#PRE
1096      */
1097     public void verbatim_()
1098     {
1099         writeEndTag( HtmlMarkup.PRE );
1100         writeEndTag( HtmlMarkup.DIV );
1101 
1102         verbatimFlag = false;
1103 
1104     }
1105 
1106     /**
1107      * {@inheritDoc}
1108      * @see javax.swing.text.html.HTML.Tag#HR
1109      */
1110     public void horizontalRule()
1111     {
1112         horizontalRule( null );
1113     }
1114 
1115     /**
1116      * {@inheritDoc}
1117      * @see javax.swing.text.html.HTML.Tag#HR
1118      */
1119     public void horizontalRule( SinkEventAttributes attributes )
1120     {
1121         MutableAttributeSet atts = SinkUtils.filterAttributes(
1122                 attributes, SinkUtils.SINK_HR_ATTRIBUTES  );
1123 
1124         writeSimpleTag( HtmlMarkup.HR, atts );
1125     }
1126 
1127     /** {@inheritDoc} */
1128     public void table()
1129     {
1130         // start table with tableRows
1131         table( null );
1132     }
1133 
1134     /** {@inheritDoc} */
1135     public void table( SinkEventAttributes attributes )
1136     {
1137         this.tableContentWriterStack.addLast( new StringWriter() );
1138         this.tableRows = false;
1139 
1140         if ( paragraphFlag )
1141         {
1142             // The content of element type "p" must match
1143             // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong|
1144             // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)".
1145             paragraph_();
1146         }
1147 
1148         // start table with tableRows
1149         if ( attributes == null )
1150         {
1151             this.tableAttributes = new SinkEventAttributeSet( 0 );
1152         }
1153         else
1154         {
1155             this.tableAttributes = SinkUtils.filterAttributes(
1156                 attributes, SinkUtils.SINK_TABLE_ATTRIBUTES  );
1157         }
1158     }
1159 
1160     /**
1161      * {@inheritDoc}
1162      * @see javax.swing.text.html.HTML.Tag#TABLE
1163      */
1164     public void table_()
1165     {
1166         this.tableRows = false;
1167 
1168         writeEndTag( HtmlMarkup.TABLE );
1169 
1170         if ( !this.cellCountStack.isEmpty() )
1171         {
1172             this.cellCountStack.removeLast().toString();
1173         }
1174 
1175         if ( this.tableContentWriterStack.isEmpty() )
1176         {
1177             if ( getLog().isWarnEnabled() )
1178             {
1179                 getLog().warn( "No table content." );
1180             }
1181             return;
1182         }
1183 
1184         String tableContent = this.tableContentWriterStack.removeLast().toString();
1185 
1186         String tableCaption = null;
1187         if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null )
1188         {
1189             tableCaption = this.tableCaptionStack.removeLast().toString();
1190         }
1191 
1192         if ( tableCaption != null )
1193         {
1194             // DOXIA-177
1195             StringBuffer sb = new StringBuffer();
1196             sb.append( tableContent.substring( 0, tableContent.indexOf( Markup.GREATER_THAN ) + 1 ) );
1197             sb.append( tableCaption );
1198             sb.append( tableContent.substring( tableContent.indexOf( Markup.GREATER_THAN ) + 1 ) );
1199 
1200             write( sb.toString() );
1201         }
1202         else
1203         {
1204             write( tableContent );
1205         }
1206     }
1207 
1208     /**
1209      * The default class style is <code>bodyTable</code>.
1210      * The default align is <code>center</code>.
1211      *
1212      * {@inheritDoc}
1213      * @see javax.swing.text.html.HTML.Tag#TABLE
1214      */
1215     public void tableRows( int[] justification, boolean grid )
1216     {
1217         this.tableRows = true;
1218 
1219         setCellJustif( justification );
1220 
1221         if ( this.tableAttributes == null )
1222         {
1223             this.tableAttributes = new SinkEventAttributeSet( 0 );
1224         }
1225 
1226         MutableAttributeSet att = new SinkEventAttributeSet();
1227         if ( !this.tableAttributes.isDefined( Attribute.BORDER.toString() ) )
1228         {
1229             att.addAttribute( Attribute.BORDER, ( grid ? "1" : "0" ) );
1230         }
1231 
1232         if ( !this.tableAttributes.isDefined( Attribute.CLASS.toString() ) )
1233         {
1234             att.addAttribute( Attribute.CLASS, "bodyTable" );
1235         }
1236 
1237         att.addAttributes( this.tableAttributes );
1238         this.tableAttributes.removeAttributes( this.tableAttributes );
1239 
1240         writeStartTag( HtmlMarkup.TABLE, att );
1241 
1242         this.cellCountStack.addLast( new Integer( 0 ) );
1243     }
1244 
1245     /** {@inheritDoc} */
1246     public void tableRows_()
1247     {
1248         this.tableRows = false;
1249         if ( !this.cellJustifStack.isEmpty() )
1250         {
1251             this.cellJustifStack.removeLast();
1252         }
1253         if ( !this.isCellJustifStack.isEmpty() )
1254         {
1255             this.isCellJustifStack.removeLast();
1256         }
1257 
1258         this.evenTableRow = true;
1259     }
1260 
1261     /**
1262      * The default class style is <code>a</code> or <code>b</code> depending the row id.
1263      *
1264      * {@inheritDoc}
1265      * @see javax.swing.text.html.HTML.Tag#TR
1266      */
1267     public void tableRow()
1268     {
1269         // To be backward compatible
1270         if ( !this.tableRows )
1271         {
1272             tableRows( null, false );
1273         }
1274         tableRow( null );
1275     }
1276 
1277     /**
1278      * The default class style is <code>a</code> or <code>b</code> depending the row id.
1279      *
1280      * {@inheritDoc}
1281      * @see javax.swing.text.html.HTML.Tag#TR
1282      */
1283     public void tableRow( SinkEventAttributes attributes )
1284     {
1285         MutableAttributeSet att = new SinkEventAttributeSet();
1286 
1287         if ( evenTableRow )
1288         {
1289             att.addAttribute( Attribute.CLASS, "a" );
1290         }
1291         else
1292         {
1293             att.addAttribute( Attribute.CLASS, "b" );
1294         }
1295 
1296         att.addAttributes( SinkUtils.filterAttributes(
1297                 attributes, SinkUtils.SINK_TR_ATTRIBUTES  ) );
1298 
1299         writeStartTag( HtmlMarkup.TR, att );
1300 
1301         evenTableRow = !evenTableRow;
1302 
1303         if ( !this.cellCountStack.isEmpty() )
1304         {
1305             this.cellCountStack.removeLast();
1306             this.cellCountStack.addLast( new Integer( 0 ) );
1307         }
1308     }
1309 
1310     /**
1311      * {@inheritDoc}
1312      * @see javax.swing.text.html.HTML.Tag#TR
1313      */
1314     public void tableRow_()
1315     {
1316         writeEndTag( HtmlMarkup.TR );
1317     }
1318 
1319     /** {@inheritDoc} */
1320     public void tableCell()
1321     {
1322         tableCell( (SinkEventAttributeSet) null );
1323     }
1324 
1325     /** {@inheritDoc} */
1326     public void tableHeaderCell()
1327     {
1328         tableHeaderCell( (SinkEventAttributeSet) null );
1329     }
1330 
1331     /** {@inheritDoc} */
1332     public void tableCell( String width )
1333     {
1334         MutableAttributeSet att = new SinkEventAttributeSet();
1335         att.addAttribute( Attribute.WIDTH, width );
1336 
1337         tableCell( false, att );
1338     }
1339 
1340     /** {@inheritDoc} */
1341     public void tableHeaderCell( String width )
1342     {
1343         MutableAttributeSet att = new SinkEventAttributeSet();
1344         att.addAttribute( Attribute.WIDTH, width );
1345 
1346         tableCell( true, att );
1347     }
1348 
1349     /** {@inheritDoc} */
1350     public void tableCell( SinkEventAttributes attributes )
1351     {
1352         tableCell( false, attributes );
1353     }
1354 
1355     /** {@inheritDoc} */
1356     public void tableHeaderCell( SinkEventAttributes attributes )
1357     {
1358         tableCell( true, attributes );
1359     }
1360 
1361     /**
1362      * @param headerRow true if it is an header row
1363      * @param attributes the cell attributes
1364      * @see javax.swing.text.html.HTML.Tag#TH
1365      * @see javax.swing.text.html.HTML.Tag#TD
1366      */
1367     private void tableCell( boolean headerRow, MutableAttributeSet attributes )
1368     {
1369         Tag t = ( headerRow ? HtmlMarkup.TH : HtmlMarkup.TD );
1370 
1371         MutableAttributeSet att = new SinkEventAttributeSet();
1372 
1373         if ( attributes == null )
1374         {
1375             attributes = new SinkEventAttributeSet( 0 );
1376         }
1377 
1378         att.addAttributes( SinkUtils.filterAttributes(
1379                 attributes, SinkUtils.SINK_TD_ATTRIBUTES  ) );
1380 
1381         writeStartTag( t, att );
1382     }
1383 
1384     /** {@inheritDoc} */
1385     public void tableCell_()
1386     {
1387         tableCell_( false );
1388     }
1389 
1390     /** {@inheritDoc} */
1391     public void tableHeaderCell_()
1392     {
1393         tableCell_( true );
1394     }
1395 
1396     /**
1397      * Ends a table cell.
1398      *
1399      * @param headerRow true if it is an header row
1400      * @see javax.swing.text.html.HTML.Tag#TH
1401      * @see javax.swing.text.html.HTML.Tag#TD
1402      */
1403     private void tableCell_( boolean headerRow )
1404     {
1405         Tag t = ( headerRow ? HtmlMarkup.TH : HtmlMarkup.TD );
1406 
1407         writeEndTag( t );
1408 
1409         if ( !this.isCellJustifStack.isEmpty() && this.isCellJustifStack.getLast().equals( Boolean.TRUE )
1410             && !this.cellCountStack.isEmpty() )
1411         {
1412             int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
1413             this.cellCountStack.addLast( new Integer( ++cellCount ) );
1414         }
1415     }
1416 
1417     /**
1418      * {@inheritDoc}
1419      * @see javax.swing.text.html.HTML.Tag#CAPTION
1420      */
1421     public void tableCaption()
1422     {
1423         tableCaption( null );
1424     }
1425 
1426     /**
1427      * {@inheritDoc}
1428      * @see javax.swing.text.html.HTML.Tag#CAPTION
1429      */
1430     public void tableCaption( SinkEventAttributes attributes )
1431     {
1432         StringWriter sw = new StringWriter();
1433         this.tableCaptionWriterStack.addLast( sw );
1434         this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) );
1435 
1436         // TODO: tableCaption should be written before tableRows (DOXIA-177)
1437         MutableAttributeSet atts = SinkUtils.filterAttributes(
1438                 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
1439 
1440         writeStartTag( HtmlMarkup.CAPTION, atts );
1441     }
1442 
1443     /**
1444      * {@inheritDoc}
1445      * @see javax.swing.text.html.HTML.Tag#CAPTION
1446      */
1447     public void tableCaption_()
1448     {
1449         writeEndTag( HtmlMarkup.CAPTION );
1450 
1451         if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
1452         {
1453             this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() );
1454             this.tableCaptionXMLWriterStack.removeLast();
1455         }
1456     }
1457 
1458     /**
1459      * {@inheritDoc}
1460      * @see javax.swing.text.html.HTML.Tag#A
1461      */
1462     public void anchor( String name )
1463     {
1464         anchor( name, null );
1465     }
1466 
1467     /**
1468      * {@inheritDoc}
1469      * @see javax.swing.text.html.HTML.Tag#A
1470      */
1471     public void anchor( String name, SinkEventAttributes attributes )
1472     {
1473         if ( name == null )
1474         {
1475             throw new NullPointerException( "Anchor name cannot be null!" );
1476         }
1477 
1478         if ( headFlag )
1479         {
1480             return;
1481         }
1482 
1483         MutableAttributeSet atts = SinkUtils.filterAttributes(
1484                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
1485 
1486         String id = name;
1487 
1488         if ( !DoxiaUtils.isValidId( id ) )
1489         {
1490             id = DoxiaUtils.encodeId( name, true );
1491 
1492             String msg = "Modified invalid anchor name: '" + name + "' to '" + id + "'";
1493             logMessage( "modifiedLink", msg );
1494         }
1495 
1496         MutableAttributeSet att = new SinkEventAttributeSet();
1497         att.addAttribute( Attribute.NAME, id );
1498         att.addAttributes( atts );
1499 
1500         writeStartTag( HtmlMarkup.A, att );
1501     }
1502 
1503     /**
1504      * {@inheritDoc}
1505      * @see javax.swing.text.html.HTML.Tag#A
1506      */
1507     public void anchor_()
1508     {
1509         if ( !headFlag )
1510         {
1511             writeEndTag( HtmlMarkup.A );
1512         }
1513     }
1514 
1515     /** {@inheritDoc} */
1516     public void link( String name )
1517     {
1518         link( name, null );
1519     }
1520 
1521     /** {@inheritDoc} */
1522     public void link( String name, SinkEventAttributes attributes )
1523     {
1524         if ( attributes == null )
1525         {
1526             link( name, null, null );
1527         }
1528         else
1529         {
1530             String target = (String) attributes.getAttribute( Attribute.TARGET.toString() );
1531             MutableAttributeSet atts = SinkUtils.filterAttributes(
1532                     attributes, SinkUtils.SINK_LINK_ATTRIBUTES  );
1533 
1534             link( name, target, atts );
1535         }
1536     }
1537 
1538     /**
1539      * Adds a link with an optional target.
1540      * The default style class for external link is <code>externalLink</code>.
1541      *
1542      * @param href the link href.
1543      * @param target the link target, may be null.
1544      * @param attributes an AttributeSet, may be null.
1545      *      This is supposed to be filtered already.
1546      * @see javax.swing.text.html.HTML.Tag#A
1547      */
1548     private void link( String href, String target, MutableAttributeSet attributes )
1549     {
1550         if ( href == null )
1551         {
1552             throw new NullPointerException( "Link name cannot be null!" );
1553         }
1554 
1555         if ( headFlag )
1556         {
1557             return;
1558         }
1559 
1560         MutableAttributeSet att = new SinkEventAttributeSet();
1561 
1562         if ( DoxiaUtils.isExternalLink( href  ) )
1563         {
1564             att.addAttribute( Attribute.CLASS, "externalLink" );
1565         }
1566 
1567         att.addAttribute( Attribute.HREF, HtmlTools.escapeHTML( href  ) );
1568 
1569         if ( target != null )
1570         {
1571             att.addAttribute( Attribute.TARGET, target );
1572         }
1573 
1574         if ( attributes != null )
1575         {
1576             attributes.removeAttribute( Attribute.HREF.toString() );
1577             attributes.removeAttribute( Attribute.TARGET.toString() );
1578             att.addAttributes( attributes );
1579         }
1580 
1581         writeStartTag( HtmlMarkup.A, att );
1582     }
1583 
1584     /**
1585      * {@inheritDoc}
1586      * @see javax.swing.text.html.HTML.Tag#A
1587      */
1588     public void link_()
1589     {
1590         if ( !headFlag )
1591         {
1592             writeEndTag( HtmlMarkup.A );
1593         }
1594     }
1595 
1596     /**
1597      * {@inheritDoc}
1598      * @see javax.swing.text.html.HTML.Tag#I
1599      */
1600     public void italic()
1601     {
1602         if ( !headFlag )
1603         {
1604             writeStartTag( HtmlMarkup.I );
1605         }
1606     }
1607 
1608     /**
1609      * {@inheritDoc}
1610      * @see javax.swing.text.html.HTML.Tag#I
1611      */
1612     public void italic_()
1613     {
1614         if ( !headFlag )
1615         {
1616             writeEndTag( HtmlMarkup.I );
1617         }
1618     }
1619 
1620     /**
1621      * {@inheritDoc}
1622      * @see javax.swing.text.html.HTML.Tag#B
1623      */
1624     public void bold()
1625     {
1626         if ( !headFlag )
1627         {
1628             writeStartTag( HtmlMarkup.B );
1629         }
1630     }
1631 
1632     /**
1633      * {@inheritDoc}
1634      * @see javax.swing.text.html.HTML.Tag#B
1635      */
1636     public void bold_()
1637     {
1638         if ( !headFlag )
1639         {
1640             writeEndTag( HtmlMarkup.B );
1641         }
1642     }
1643 
1644     /**
1645      * {@inheritDoc}
1646      * @see javax.swing.text.html.HTML.Tag#TT
1647      */
1648     public void monospaced()
1649     {
1650         if ( !headFlag )
1651         {
1652             writeStartTag( HtmlMarkup.TT );
1653         }
1654     }
1655 
1656     /**
1657      * {@inheritDoc}
1658      * @see javax.swing.text.html.HTML.Tag#TT
1659      */
1660     public void monospaced_()
1661     {
1662         if ( !headFlag )
1663         {
1664             writeEndTag( HtmlMarkup.TT );
1665         }
1666     }
1667 
1668     /**
1669      * {@inheritDoc}
1670      * @see javax.swing.text.html.HTML.Tag#BR
1671      */
1672     public void lineBreak()
1673     {
1674         lineBreak( null );
1675     }
1676 
1677     /**
1678      * {@inheritDoc}
1679      * @see javax.swing.text.html.HTML.Tag#BR
1680      */
1681     public void lineBreak( SinkEventAttributes attributes )
1682     {
1683         if ( headFlag || isVerbatimFlag() )
1684         {
1685             getTextBuffer().append( EOL );
1686         }
1687         else
1688         {
1689             MutableAttributeSet atts = SinkUtils.filterAttributes(
1690                 attributes, SinkUtils.SINK_BR_ATTRIBUTES  );
1691 
1692             writeSimpleTag( HtmlMarkup.BR, atts );
1693         }
1694     }
1695 
1696     /** {@inheritDoc} */
1697     public void pageBreak()
1698     {
1699         comment( "PB" );
1700     }
1701 
1702     /** {@inheritDoc} */
1703     public void nonBreakingSpace()
1704     {
1705         if ( headFlag )
1706         {
1707             getTextBuffer().append( ' ' );
1708         }
1709         else
1710         {
1711             write( "&#160;" );
1712         }
1713     }
1714 
1715     /** {@inheritDoc} */
1716     public void text( String text )
1717     {
1718         if ( headFlag )
1719         {
1720             getTextBuffer().append( text );
1721         }
1722         else if ( verbatimFlag )
1723         {
1724             verbatimContent( text );
1725         }
1726         else
1727         {
1728             content( text );
1729         }
1730     }
1731 
1732     /** {@inheritDoc} */
1733     public void text( String text, SinkEventAttributes attributes )
1734     {
1735         if ( attributes == null )
1736         {
1737             text( text );
1738         }
1739         else
1740         {
1741             if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "underline" ) )
1742             {
1743                 writeStartTag( HtmlMarkup.U );
1744             }
1745             if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "line-through" ) )
1746             {
1747                 writeStartTag( HtmlMarkup.S );
1748             }
1749             if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sub" ) )
1750             {
1751                 writeStartTag( HtmlMarkup.SUB );
1752             }
1753             if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sup" ) )
1754             {
1755                 writeStartTag( HtmlMarkup.SUP );
1756             }
1757 
1758             text( text );
1759 
1760             if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sup" ) )
1761             {
1762                 writeEndTag( HtmlMarkup.SUP );
1763             }
1764             if ( attributes.containsAttribute( SinkEventAttributes.VALIGN, "sub" ) )
1765             {
1766                 writeEndTag( HtmlMarkup.SUB );
1767             }
1768             if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "line-through" ) )
1769             {
1770                 writeEndTag( HtmlMarkup.S );
1771             }
1772             if ( attributes.containsAttribute( SinkEventAttributes.DECORATION, "underline" ) )
1773             {
1774                 writeEndTag( HtmlMarkup.U );
1775             }
1776         }
1777     }
1778 
1779     /** {@inheritDoc} */
1780     public void rawText( String text )
1781     {
1782         if ( headFlag )
1783         {
1784             getTextBuffer().append( text );
1785         }
1786         else
1787         {
1788             write( text );
1789         }
1790     }
1791 
1792     /** {@inheritDoc} */
1793     public void comment( String comment )
1794     {
1795         if ( StringUtils.isNotEmpty( comment ) && comment.indexOf( "--" ) != -1 )
1796         {
1797             String originalComment = comment;
1798             // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments
1799             while ( comment.indexOf( "--" ) != -1 )
1800             {
1801                 comment = StringUtils.replace( comment, "--", "- -" );
1802             }
1803 
1804             getLog()
1805                     .warn( "[Xhtml Sink] Modified invalid comment: '" + originalComment + "' to '" + comment + "'" );
1806         }
1807 
1808         StringBuffer buf = new StringBuffer( comment.length() + 9 );
1809 
1810         buf.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS ).append( SPACE );
1811         buf.append( comment );
1812         buf.append( SPACE ).append( MINUS ).append( MINUS ).append( GREATER_THAN );
1813 
1814         write( buf.toString() );
1815     }
1816 
1817     /**
1818      * Add an unknown event.
1819      * This can be used to generate html tags for which no corresponding sink event exists.
1820      *
1821      * <p>
1822      * If {@link org.apache.maven.doxia.util.HtmlTools#getHtmlTag(String) HtmlTools.getHtmlTag( name )}
1823      * does not return null, the corresponding tag will be written.
1824      * </p>
1825      *
1826      * <p>For example, the div block</p>
1827      *
1828      * <pre>
1829      *  &lt;div class="detail" style="display:inline"&gt;text&lt;/div&gt;
1830      * </pre>
1831      *
1832      * <p>can be generated via the following event sequence:</p>
1833      *
1834      * <pre>
1835      *  SinkEventAttributeSet atts = new SinkEventAttributeSet();
1836      *  atts.addAttribute( SinkEventAttributes.CLASS, "detail" );
1837      *  atts.addAttribute( SinkEventAttributes.STYLE, "display:inline" );
1838      *  sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
1839      *  sink.text( "text" );
1840      *  sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
1841      * </pre>
1842      *
1843      * @param name the name of the event. If this is not a valid xhtml tag name
1844      *      as defined in {@link org.apache.maven.doxia.markup.HtmlMarkup} then the event is ignored.
1845      * @param requiredParams If this is null or the first argument is not an Integer then the event is ignored.
1846      *      The first argument should indicate the type of the unknown event, its integer value should be one of
1847      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_START TAG_TYPE_START},
1848      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_END TAG_TYPE_END},
1849      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_SIMPLE TAG_TYPE_SIMPLE},
1850      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#ENTITY_TYPE ENTITY_TYPE}, or
1851      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#CDATA_TYPE CDATA_TYPE},
1852      *      otherwise the event will be ignored.
1853      * @param attributes a set of attributes for the event. May be null.
1854      *      The attributes will always be written, no validity check is performed.
1855      */
1856     public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
1857     {
1858         if ( requiredParams == null || !( requiredParams[0] instanceof Integer ) )
1859         {
1860             String msg = "No type information for unknown event: '" + name + "', ignoring!";
1861             logMessage( "noTypeInfo", msg );
1862 
1863             return;
1864         }
1865 
1866         int tagType = ( (Integer) requiredParams[0] ).intValue();
1867 
1868         if ( tagType == ENTITY_TYPE )
1869         {
1870             rawText( name );
1871 
1872             return;
1873         }
1874 
1875         if ( tagType == CDATA_TYPE )
1876         {
1877             rawText( EOL + "//<![CDATA[" + requiredParams[1] + "]]>" + EOL );
1878 
1879             return;
1880         }
1881 
1882         Tag tag = HtmlTools.getHtmlTag( name );
1883 
1884         if ( tag == null )
1885         {
1886             String msg = "No HTML tag found for unknown event: '" + name + "', ignoring!";
1887             logMessage( "noHtmlTag", msg );
1888         }
1889         else
1890         {
1891             if ( tagType == TAG_TYPE_SIMPLE )
1892             {
1893                 writeSimpleTag( tag, escapeAttributeValues( attributes ) );
1894             }
1895             else if ( tagType == TAG_TYPE_START )
1896             {
1897                 writeStartTag( tag, escapeAttributeValues( attributes ) );
1898             }
1899             else if ( tagType == TAG_TYPE_END )
1900             {
1901                 writeEndTag( tag );
1902             }
1903             else
1904             {
1905                 String msg = "No type information for unknown event: '" + name + "', ignoring!";
1906                 logMessage( "noTypeInfo", msg );
1907             }
1908         }
1909     }
1910 
1911     private SinkEventAttributes escapeAttributeValues( SinkEventAttributes attributes )
1912     {
1913         SinkEventAttributeSet set = new SinkEventAttributeSet( attributes.getAttributeCount() );
1914 
1915         Enumeration<?> names = attributes.getAttributeNames();
1916 
1917         while ( names.hasMoreElements() )
1918         {
1919             Object name = names.nextElement();
1920 
1921             set.addAttribute( name, escapeHTML( attributes.getAttribute( name ).toString() ) );
1922         }
1923 
1924         return set;
1925     }
1926 
1927     /** {@inheritDoc} */
1928     public void flush()
1929     {
1930         writer.flush();
1931     }
1932 
1933     /** {@inheritDoc} */
1934     public void close()
1935     {
1936         writer.close();
1937 
1938         if ( getLog().isWarnEnabled() && this.warnMessages != null )
1939         {
1940             for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
1941             {
1942                 for ( String msg : entry.getValue() )
1943                 {
1944                     getLog().warn( msg );
1945                 }
1946             }
1947 
1948             this.warnMessages = null;
1949         }
1950 
1951         init();
1952     }
1953 
1954     // ----------------------------------------------------------------------
1955     //
1956     // ----------------------------------------------------------------------
1957 
1958     /**
1959      * Write HTML escaped text to output.
1960      *
1961      * @param text The text to write.
1962      */
1963     protected void content( String text )
1964     {
1965         // small hack due to DOXIA-314
1966         text = escapeHTML( text );
1967         text = StringUtils.replace( text, "&amp;#", "&#" );
1968         write( text );
1969     }
1970 
1971     /**
1972      * Write HTML escaped text to output.
1973      *
1974      * @param text The text to write.
1975      */
1976     protected void verbatimContent( String text )
1977     {
1978         write( escapeHTML( text ) );
1979     }
1980 
1981     /**
1982      * Forward to HtmlTools.escapeHTML( text ).
1983      *
1984      * @param text the String to escape, may be null
1985      * @return the text escaped, "" if null String input
1986      * @see org.apache.maven.doxia.util.HtmlTools#escapeHTML(String)
1987      */
1988     protected static String escapeHTML( String text )
1989     {
1990         return HtmlTools.escapeHTML( text, false );
1991     }
1992 
1993     /**
1994      * Forward to HtmlTools.encodeURL( text ).
1995      *
1996      * @param text the String to encode, may be null.
1997      * @return the text encoded, null if null String input.
1998      * @see org.apache.maven.doxia.util.HtmlTools#encodeURL(String)
1999      */
2000     protected static String encodeURL( String text )
2001     {
2002         return HtmlTools.encodeURL( text );
2003     }
2004 
2005     /** {@inheritDoc} */
2006     protected void write( String text )
2007     {
2008         if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
2009         {
2010             this.tableCaptionXMLWriterStack.getLast().writeText( unifyEOLs( text ) );
2011         }
2012         else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null )
2013         {
2014             this.tableContentWriterStack.getLast().write( unifyEOLs( text ) );
2015         }
2016         else
2017         {
2018             writer.write( unifyEOLs( text ) );
2019         }
2020     }
2021 
2022     /** {@inheritDoc} */
2023     protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag )
2024     {
2025         if ( this.tableCaptionXMLWriterStack.isEmpty() )
2026         {
2027             super.writeStartTag ( t, att, isSimpleTag );
2028         }
2029         else
2030         {
2031             String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString();
2032             this.tableCaptionXMLWriterStack.getLast().startElement( tag );
2033 
2034             if ( att != null )
2035             {
2036                 Enumeration<?> names = att.getAttributeNames();
2037                 while ( names.hasMoreElements() )
2038                 {
2039                     Object key = names.nextElement();
2040                     Object value = att.getAttribute( key );
2041 
2042                     this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() );
2043                 }
2044             }
2045 
2046             if ( isSimpleTag )
2047             {
2048                 this.tableCaptionXMLWriterStack.getLast().endElement();
2049             }
2050         }
2051     }
2052 
2053     /** {@inheritDoc} */
2054     protected void writeEndTag( Tag t )
2055     {
2056         if ( this.tableCaptionXMLWriterStack.isEmpty() )
2057         {
2058             super.writeEndTag( t );
2059         }
2060         else
2061         {
2062             this.tableCaptionXMLWriterStack.getLast().endElement();
2063         }
2064     }
2065 
2066     /**
2067      * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
2068      *
2069      * @param key not null
2070      * @param msg not null
2071      * @see #close()
2072      * @since 1.1.1
2073      */
2074     private void logMessage( String key, String msg )
2075     {
2076         msg = "[XHTML Sink] " + msg;
2077         if ( getLog().isDebugEnabled() )
2078         {
2079             getLog().debug( msg );
2080 
2081             return;
2082         }
2083 
2084         if ( warnMessages == null )
2085         {
2086             warnMessages = new HashMap<String, Set<String>>();
2087         }
2088 
2089         Set<String> set = warnMessages.get( key );
2090         if ( set == null )
2091         {
2092             set = new TreeSet<String>();
2093         }
2094         set.add( msg );
2095         warnMessages.put( key, set );
2096     }
2097 }