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