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