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.SinkEventAttributes;
42  import org.apache.maven.doxia.util.DoxiaUtils;
43  import org.apache.maven.doxia.util.HtmlTools;
44  
45  import org.codehaus.plexus.util.StringUtils;
46  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
47  
48  /**
49   * Abstract base xhtml sink implementation.
50   *
51   * @author Jason van Zyl
52   * @author ltheussl
53   * @version $Id$
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 ( attributes == null )
1541         {
1542             writeStartTag( t, null );
1543         }
1544         else
1545         {
1546             writeStartTag( t,
1547                 SinkUtils.filterAttributes( attributes, SinkUtils.SINK_TD_ATTRIBUTES ) );
1548         }
1549     }
1550 
1551     /** {@inheritDoc} */
1552     @Override
1553     public void tableCell_()
1554     {
1555         tableCell_( false );
1556     }
1557 
1558     /** {@inheritDoc} */
1559     @Override
1560     public void tableHeaderCell_()
1561     {
1562         tableCell_( true );
1563     }
1564 
1565     /**
1566      * Ends a table cell.
1567      *
1568      * @param headerRow true if it is an header row
1569      * @see javax.swing.text.html.HTML.Tag#TH
1570      * @see javax.swing.text.html.HTML.Tag#TD
1571      */
1572     private void tableCell_( boolean headerRow )
1573     {
1574         Tag t = ( headerRow ? HtmlMarkup.TH : HtmlMarkup.TD );
1575 
1576         writeEndTag( t );
1577 
1578         if ( !this.isCellJustifStack.isEmpty() && this.isCellJustifStack.getLast().equals( Boolean.TRUE )
1579             && !this.cellCountStack.isEmpty() )
1580         {
1581             int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
1582             this.cellCountStack.addLast( ++cellCount );
1583         }
1584     }
1585 
1586     /**
1587      * {@inheritDoc}
1588      * @see javax.swing.text.html.HTML.Tag#CAPTION
1589      */
1590     @Override
1591     public void tableCaption()
1592     {
1593         tableCaption( null );
1594     }
1595 
1596     /**
1597      * {@inheritDoc}
1598      * @see javax.swing.text.html.HTML.Tag#CAPTION
1599      */
1600     @Override
1601     public void tableCaption( SinkEventAttributes attributes )
1602     {
1603         StringWriter sw = new StringWriter();
1604         this.tableCaptionWriterStack.addLast( sw );
1605         this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) );
1606 
1607         // TODO: tableCaption should be written before tableRows (DOXIA-177)
1608         MutableAttributeSet atts = SinkUtils.filterAttributes(
1609                 attributes, SinkUtils.SINK_SECTION_ATTRIBUTES  );
1610 
1611         writeStartTag( HtmlMarkup.CAPTION, atts );
1612     }
1613 
1614     /**
1615      * {@inheritDoc}
1616      * @see javax.swing.text.html.HTML.Tag#CAPTION
1617      */
1618     @Override
1619     public void tableCaption_()
1620     {
1621         writeEndTag( HtmlMarkup.CAPTION );
1622 
1623         if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
1624         {
1625             this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() );
1626             this.tableCaptionXMLWriterStack.removeLast();
1627         }
1628     }
1629 
1630     /**
1631      * {@inheritDoc}
1632      * @see javax.swing.text.html.HTML.Tag#A
1633      */
1634     @Override
1635     public void anchor( String name )
1636     {
1637         anchor( name, null );
1638     }
1639 
1640     /**
1641      * {@inheritDoc}
1642      * @see javax.swing.text.html.HTML.Tag#A
1643      */
1644     @Override
1645     public void anchor( String name, SinkEventAttributes attributes )
1646     {
1647         if ( name == null )
1648         {
1649             throw new NullPointerException( "Anchor name cannot be null!" );
1650         }
1651 
1652         if ( headFlag )
1653         {
1654             return;
1655         }
1656 
1657         MutableAttributeSet atts = SinkUtils.filterAttributes(
1658                 attributes, SinkUtils.SINK_BASE_ATTRIBUTES  );
1659 
1660         String id = name;
1661 
1662         if ( !DoxiaUtils.isValidId( id ) )
1663         {
1664             id = DoxiaUtils.encodeId( name, true );
1665 
1666             String msg = "Modified invalid anchor name: '" + name + "' to '" + id + "'";
1667             logMessage( "modifiedLink", msg );
1668         }
1669 
1670         MutableAttributeSet att = new SinkEventAttributeSet();
1671         att.addAttribute( Attribute.NAME, id );
1672         att.addAttributes( atts );
1673 
1674         writeStartTag( HtmlMarkup.A, att );
1675     }
1676 
1677     /**
1678      * {@inheritDoc}
1679      * @see javax.swing.text.html.HTML.Tag#A
1680      */
1681     @Override
1682     public void anchor_()
1683     {
1684         if ( !headFlag )
1685         {
1686             writeEndTag( HtmlMarkup.A );
1687         }
1688     }
1689 
1690     /** {@inheritDoc} */
1691     @Override
1692     public void link( String name )
1693     {
1694         link( name, null );
1695     }
1696 
1697     /** {@inheritDoc} */
1698     @Override
1699     public void link( String name, SinkEventAttributes attributes )
1700     {
1701         if ( attributes == null )
1702         {
1703             link( name, null, null );
1704         }
1705         else
1706         {
1707             String target = (String) attributes.getAttribute( Attribute.TARGET.toString() );
1708             MutableAttributeSet atts = SinkUtils.filterAttributes(
1709                     attributes, SinkUtils.SINK_LINK_ATTRIBUTES  );
1710 
1711             link( name, target, atts );
1712         }
1713     }
1714 
1715     /**
1716      * Adds a link with an optional target.
1717      * The default style class for external link is <code>externalLink</code>.
1718      *
1719      * @param href the link href.
1720      * @param target the link target, may be null.
1721      * @param attributes an AttributeSet, may be null.
1722      *      This is supposed to be filtered already.
1723      * @see javax.swing.text.html.HTML.Tag#A
1724      */
1725     private void link( String href, String target, MutableAttributeSet attributes )
1726     {
1727         if ( href == null )
1728         {
1729             throw new NullPointerException( "Link name cannot be null!" );
1730         }
1731 
1732         if ( headFlag )
1733         {
1734             return;
1735         }
1736 
1737         MutableAttributeSet att = new SinkEventAttributeSet();
1738 
1739         if ( DoxiaUtils.isExternalLink( href  ) )
1740         {
1741             att.addAttribute( Attribute.CLASS, "externalLink" );
1742         }
1743 
1744         att.addAttribute( Attribute.HREF, HtmlTools.escapeHTML( href  ) );
1745 
1746         if ( target != null )
1747         {
1748             att.addAttribute( Attribute.TARGET, target );
1749         }
1750 
1751         if ( attributes != null )
1752         {
1753             attributes.removeAttribute( Attribute.HREF.toString() );
1754             attributes.removeAttribute( Attribute.TARGET.toString() );
1755             att.addAttributes( attributes );
1756         }
1757 
1758         writeStartTag( HtmlMarkup.A, att );
1759     }
1760 
1761     /**
1762      * {@inheritDoc}
1763      * @see javax.swing.text.html.HTML.Tag#A
1764      */
1765     @Override
1766     public void link_()
1767     {
1768         if ( !headFlag )
1769         {
1770             writeEndTag( HtmlMarkup.A );
1771         }
1772     }
1773 
1774     /** {@inheritDoc} */
1775     @Override
1776     public void inline()
1777     {
1778         inline( null );
1779     }
1780 
1781     private void inlineSemantics( SinkEventAttributes attributes, String semantic,
1782             List<Tag> tags, Tag tag )
1783     {
1784         if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, semantic ) )
1785         {
1786             writeStartTag( tag );
1787             tags.add( 0, tag );
1788         }
1789     }
1790 
1791     /** {@inheritDoc} */
1792     @Override
1793     public void inline( SinkEventAttributes attributes )
1794     {
1795         if ( !headFlag )
1796         {
1797             List<Tag> tags = new ArrayList<>();
1798 
1799             if ( attributes != null )
1800             {
1801                 inlineSemantics( attributes, "emphasis", tags, HtmlMarkup.EM );
1802                 inlineSemantics( attributes, "strong", tags, HtmlMarkup.STRONG );
1803                 inlineSemantics( attributes, "small", tags, HtmlMarkup.SMALL );
1804                 inlineSemantics( attributes, "line-through", tags, HtmlMarkup.S );
1805                 inlineSemantics( attributes, "citation", tags, HtmlMarkup.CITE );
1806                 inlineSemantics( attributes, "quote", tags, HtmlMarkup.Q );
1807                 inlineSemantics( attributes, "definition", tags, HtmlMarkup.DFN );
1808                 inlineSemantics( attributes, "abbreviation", tags, HtmlMarkup.ABBR );
1809                 inlineSemantics( attributes, "italic", tags, HtmlMarkup.I );
1810                 inlineSemantics( attributes, "bold", tags, HtmlMarkup.B );
1811                 inlineSemantics( attributes, "monospaced", tags, HtmlMarkup.TT );
1812                 inlineSemantics( attributes, "code", tags, HtmlMarkup.CODE );
1813                 inlineSemantics( attributes, "variable", tags, HtmlMarkup.VAR );
1814                 inlineSemantics( attributes, "sample", tags, HtmlMarkup.SAMP );
1815                 inlineSemantics( attributes, "keyboard", tags, HtmlMarkup.KBD );
1816                 inlineSemantics( attributes, "superscript", tags, HtmlMarkup.SUP );
1817                 inlineSemantics( attributes, "subscript", tags, HtmlMarkup.SUB );
1818                 inlineSemantics( attributes, "annotation", tags, HtmlMarkup.U );
1819                 inlineSemantics( attributes, "bidirectionalOverride", tags, HtmlMarkup.BDO );
1820                 inlineSemantics( attributes, "phrase", tags, HtmlMarkup.SPAN );
1821                 inlineSemantics( attributes, "insert", tags, HtmlMarkup.INS );
1822                 inlineSemantics( attributes, "delete", tags, HtmlMarkup.DEL );
1823             }
1824 
1825             inlineStack.push( tags );
1826         }
1827     }
1828 
1829     /** {@inheritDoc} */
1830     @Override
1831     public void inline_()
1832     {
1833         if ( !headFlag )
1834         {
1835             for ( Tag tag: inlineStack.pop() )
1836             {
1837                 writeEndTag( tag );
1838             }
1839         }
1840     }
1841 
1842     /**
1843      * {@inheritDoc}
1844      * @see javax.swing.text.html.HTML.Tag#I
1845      */
1846     @Override
1847     public void italic()
1848     {
1849         inline( SinkEventAttributeSet.Semantics.ITALIC );
1850     }
1851 
1852     /**
1853      * {@inheritDoc}
1854      * @see javax.swing.text.html.HTML.Tag#I
1855      */
1856     @Override
1857     public void italic_()
1858     {
1859         inline_();
1860     }
1861 
1862     /**
1863      * {@inheritDoc}
1864      * @see javax.swing.text.html.HTML.Tag#B
1865      */
1866     @Override
1867     public void bold()
1868     {
1869         inline( SinkEventAttributeSet.Semantics.BOLD );
1870     }
1871 
1872     /**
1873      * {@inheritDoc}
1874      * @see javax.swing.text.html.HTML.Tag#B
1875      */
1876     @Override
1877     public void bold_()
1878     {
1879         inline_();
1880     }
1881 
1882     /**
1883      * {@inheritDoc}
1884      * @see javax.swing.text.html.HTML.Tag#TT
1885      */
1886     @Override
1887     public void monospaced()
1888     {
1889         inline( SinkEventAttributeSet.Semantics.MONOSPACED );
1890     }
1891 
1892     /**
1893      * {@inheritDoc}
1894      * @see javax.swing.text.html.HTML.Tag#TT
1895      */
1896     @Override
1897     public void monospaced_()
1898     {
1899         inline_();
1900     }
1901 
1902     /**
1903      * {@inheritDoc}
1904      * @see javax.swing.text.html.HTML.Tag#BR
1905      */
1906     @Override
1907     public void lineBreak()
1908     {
1909         lineBreak( null );
1910     }
1911 
1912     /**
1913      * {@inheritDoc}
1914      * @see javax.swing.text.html.HTML.Tag#BR
1915      */
1916     @Override
1917     public void lineBreak( SinkEventAttributes attributes )
1918     {
1919         if ( headFlag || isVerbatimFlag() )
1920         {
1921             getTextBuffer().append( EOL );
1922         }
1923         else
1924         {
1925             MutableAttributeSet atts = SinkUtils.filterAttributes(
1926                 attributes, SinkUtils.SINK_BR_ATTRIBUTES  );
1927 
1928             writeSimpleTag( HtmlMarkup.BR, atts );
1929         }
1930     }
1931 
1932     /** {@inheritDoc} */
1933     @Override
1934     public void pageBreak()
1935     {
1936         comment( " PB " );
1937     }
1938 
1939     /** {@inheritDoc} */
1940     @Override
1941     public void nonBreakingSpace()
1942     {
1943         if ( headFlag )
1944         {
1945             getTextBuffer().append( ' ' );
1946         }
1947         else
1948         {
1949             write( "&#160;" );
1950         }
1951     }
1952 
1953     /** {@inheritDoc} */
1954     @Override
1955     public void text( String text )
1956     {
1957         if ( headFlag )
1958         {
1959             getTextBuffer().append( text );
1960         }
1961         else if ( verbatimFlag )
1962         {
1963             verbatimContent( text );
1964         }
1965         else
1966         {
1967             content( text );
1968         }
1969     }
1970 
1971     /** {@inheritDoc} */
1972     @Override
1973     public void text( String text, SinkEventAttributes attributes )
1974     {
1975         text( text );
1976     }
1977 
1978     /** {@inheritDoc} */
1979     @Override
1980     public void rawText( String text )
1981     {
1982         if ( headFlag )
1983         {
1984             getTextBuffer().append( text );
1985         }
1986         else
1987         {
1988             write( text );
1989         }
1990     }
1991 
1992     /** {@inheritDoc} */
1993     @Override
1994     public void comment( String comment )
1995     {
1996         if ( comment != null )
1997         {
1998             final String originalComment = comment;
1999 
2000             // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments
2001             while ( comment.contains( "--" ) )
2002             {
2003                 comment = comment.replace( "--", "- -" );
2004             }
2005 
2006             if ( comment.endsWith( "-" ) )
2007             {
2008                 comment += " ";
2009             }
2010 
2011             if ( !originalComment.equals( comment ) )
2012             {
2013                 getLog().warn( "[Xhtml Sink] Modified invalid comment '" + originalComment + "' to '" + comment + "'" );
2014             }
2015 
2016             final StringBuilder buffer = new StringBuilder( comment.length() + 7 );
2017 
2018             buffer.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS );
2019             buffer.append( comment );
2020             buffer.append( MINUS ).append( MINUS ).append( GREATER_THAN );
2021 
2022             write( buffer.toString() );
2023         }
2024     }
2025 
2026     /**
2027      * Add an unknown event.
2028      * This can be used to generate html tags for which no corresponding sink event exists.
2029      *
2030      * <p>
2031      * If {@link org.apache.maven.doxia.util.HtmlTools#getHtmlTag(String) HtmlTools.getHtmlTag( name )}
2032      * does not return null, the corresponding tag will be written.
2033      * </p>
2034      *
2035      * <p>For example, the div block</p>
2036      *
2037      * <pre>
2038      *  &lt;div class="detail" style="display:inline"&gt;text&lt;/div&gt;
2039      * </pre>
2040      *
2041      * <p>can be generated via the following event sequence:</p>
2042      *
2043      * <pre>
2044      *  SinkEventAttributeSet atts = new SinkEventAttributeSet();
2045      *  atts.addAttribute( SinkEventAttributes.CLASS, "detail" );
2046      *  atts.addAttribute( SinkEventAttributes.STYLE, "display:inline" );
2047      *  sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_START )}, atts );
2048      *  sink.text( "text" );
2049      *  sink.unknown( "div", new Object[]{new Integer( HtmlMarkup.TAG_TYPE_END )}, null );
2050      * </pre>
2051      *
2052      * @param name the name of the event. If this is not a valid xhtml tag name
2053      *      as defined in {@link org.apache.maven.doxia.markup.HtmlMarkup} then the event is ignored.
2054      * @param requiredParams If this is null or the first argument is not an Integer then the event is ignored.
2055      *      The first argument should indicate the type of the unknown event, its integer value should be one of
2056      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_START TAG_TYPE_START},
2057      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_END TAG_TYPE_END},
2058      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_SIMPLE TAG_TYPE_SIMPLE},
2059      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#ENTITY_TYPE ENTITY_TYPE}, or
2060      *      {@link org.apache.maven.doxia.markup.HtmlMarkup#CDATA_TYPE CDATA_TYPE},
2061      *      otherwise the event will be ignored.
2062      * @param attributes a set of attributes for the event. May be null.
2063      *      The attributes will always be written, no validity check is performed.
2064      */
2065     @Override
2066     public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
2067     {
2068         if ( requiredParams == null || !( requiredParams[0] instanceof Integer ) )
2069         {
2070             String msg = "No type information for unknown event: '" + name + "', ignoring!";
2071             logMessage( "noTypeInfo", msg );
2072 
2073             return;
2074         }
2075 
2076         int tagType = (Integer) requiredParams[0];
2077 
2078         if ( tagType == ENTITY_TYPE )
2079         {
2080             rawText( name );
2081 
2082             return;
2083         }
2084 
2085         if ( tagType == CDATA_TYPE )
2086         {
2087             rawText( EOL + "//<![CDATA[" + requiredParams[1] + "]]>" + EOL );
2088 
2089             return;
2090         }
2091 
2092         Tag tag = HtmlTools.getHtmlTag( name );
2093 
2094         if ( tag == null )
2095         {
2096             String msg = "No HTML tag found for unknown event: '" + name + "', ignoring!";
2097             logMessage( "noHtmlTag", msg );
2098         }
2099         else
2100         {
2101             if ( tagType == TAG_TYPE_SIMPLE )
2102             {
2103                 writeSimpleTag( tag, escapeAttributeValues( attributes ) );
2104             }
2105             else if ( tagType == TAG_TYPE_START )
2106             {
2107                 writeStartTag( tag, escapeAttributeValues( attributes ) );
2108             }
2109             else if ( tagType == TAG_TYPE_END )
2110             {
2111                 writeEndTag( tag );
2112             }
2113             else
2114             {
2115                 String msg = "No type information for unknown event: '" + name + "', ignoring!";
2116                 logMessage( "noTypeInfo", msg );
2117             }
2118         }
2119     }
2120 
2121     private SinkEventAttributes escapeAttributeValues( SinkEventAttributes attributes )
2122     {
2123         SinkEventAttributeSet set = new SinkEventAttributeSet( attributes.getAttributeCount() );
2124 
2125         Enumeration<?> names = attributes.getAttributeNames();
2126 
2127         while ( names.hasMoreElements() )
2128         {
2129             Object name = names.nextElement();
2130 
2131             set.addAttribute( name, escapeHTML( attributes.getAttribute( name ).toString() ) );
2132         }
2133 
2134         return set;
2135     }
2136 
2137     /** {@inheritDoc} */
2138     @Override
2139     public void flush()
2140     {
2141         writer.flush();
2142     }
2143 
2144     /** {@inheritDoc} */
2145     @Override
2146     public void close()
2147     {
2148         writer.close();
2149 
2150         if ( getLog().isWarnEnabled() && this.warnMessages != null )
2151         {
2152             for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
2153             {
2154                 for ( String msg : entry.getValue() )
2155                 {
2156                     getLog().warn( msg );
2157                 }
2158             }
2159 
2160             this.warnMessages = null;
2161         }
2162 
2163         init();
2164     }
2165 
2166     // ----------------------------------------------------------------------
2167     //
2168     // ----------------------------------------------------------------------
2169 
2170     /**
2171      * Write HTML escaped text to output.
2172      *
2173      * @param text The text to write.
2174      */
2175     protected void content( String text )
2176     {
2177         // small hack due to DOXIA-314
2178         String txt = escapeHTML( text );
2179         txt = StringUtils.replace( txt, "&amp;#", "&#" );
2180         write( txt );
2181     }
2182 
2183     /**
2184      * Write HTML escaped text to output.
2185      *
2186      * @param text The text to write.
2187      */
2188     protected void verbatimContent( String text )
2189     {
2190         write( escapeHTML( text ) );
2191     }
2192 
2193     /**
2194      * Forward to HtmlTools.escapeHTML( text ).
2195      *
2196      * @param text the String to escape, may be null
2197      * @return the text escaped, "" if null String input
2198      * @see org.apache.maven.doxia.util.HtmlTools#escapeHTML(String)
2199      */
2200     protected static String escapeHTML( String text )
2201     {
2202         return HtmlTools.escapeHTML( text, false );
2203     }
2204 
2205     /**
2206      * Forward to HtmlTools.encodeURL( text ).
2207      *
2208      * @param text the String to encode, may be null.
2209      * @return the text encoded, null if null String input.
2210      * @see org.apache.maven.doxia.util.HtmlTools#encodeURL(String)
2211      */
2212     protected static String encodeURL( String text )
2213     {
2214         return HtmlTools.encodeURL( text );
2215     }
2216 
2217     /** {@inheritDoc} */
2218     protected void write( String text )
2219     {
2220         if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
2221         {
2222             this.tableCaptionXMLWriterStack.getLast().writeMarkup( unifyEOLs( text ) );
2223         }
2224         else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null )
2225         {
2226             this.tableContentWriterStack.getLast().write( unifyEOLs( text ) );
2227         }
2228         else
2229         {
2230             writer.write( unifyEOLs( text ) );
2231         }
2232     }
2233 
2234     /** {@inheritDoc} */
2235     @Override
2236     protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag )
2237     {
2238         if ( this.tableCaptionXMLWriterStack.isEmpty() )
2239         {
2240             super.writeStartTag ( t, att, isSimpleTag );
2241         }
2242         else
2243         {
2244             String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString();
2245             this.tableCaptionXMLWriterStack.getLast().startElement( tag );
2246 
2247             if ( att != null )
2248             {
2249                 Enumeration<?> names = att.getAttributeNames();
2250                 while ( names.hasMoreElements() )
2251                 {
2252                     Object key = names.nextElement();
2253                     Object value = att.getAttribute( key );
2254 
2255                     this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() );
2256                 }
2257             }
2258 
2259             if ( isSimpleTag )
2260             {
2261                 this.tableCaptionXMLWriterStack.getLast().endElement();
2262             }
2263         }
2264     }
2265 
2266     /** {@inheritDoc} */
2267     @Override
2268     protected void writeEndTag( Tag t )
2269     {
2270         if ( this.tableCaptionXMLWriterStack.isEmpty() )
2271         {
2272             super.writeEndTag( t );
2273         }
2274         else
2275         {
2276             this.tableCaptionXMLWriterStack.getLast().endElement();
2277         }
2278     }
2279 
2280     /**
2281      * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
2282      *
2283      * @param key not null
2284      * @param msg not null
2285      * @see #close()
2286      * @since 1.1.1
2287      */
2288     private void logMessage( String key, String msg )
2289     {
2290         final String mesg = "[XHTML Sink] " + msg;
2291         if ( getLog().isDebugEnabled() )
2292         {
2293             getLog().debug( mesg );
2294 
2295             return;
2296         }
2297 
2298         if ( warnMessages == null )
2299         {
2300             warnMessages = new HashMap<>();
2301         }
2302 
2303         Set<String> set = warnMessages.get( key );
2304         if ( set == null )
2305         {
2306             set = new TreeSet<>();
2307         }
2308         set.add( mesg );
2309         warnMessages.put( key, set );
2310     }
2311 }