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