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