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