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