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