001package org.apache.maven.doxia.sink.impl;
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.sink.SinkEventAttributes;
039import org.apache.maven.doxia.util.DoxiaUtils;
040import org.apache.maven.doxia.util.HtmlTools;
041
042import org.codehaus.plexus.util.StringUtils;
043import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
044
045/**
046 * Abstract base xhtml sink implementation.
047 *
048 * @author Jason van Zyl
049 * @author ltheussl
050 * @version $Id$
051 * @since 1.1
052 */
053public class XhtmlBaseSink
054    extends AbstractXmlSink
055    implements HtmlMarkup
056{
057    // ----------------------------------------------------------------------
058    // Instance fields
059    // ----------------------------------------------------------------------
060
061    /** The PrintWriter to write the result. */
062    private final PrintWriter writer;
063
064    /** Used to collect text events mainly for the head events. */
065    private StringBuffer textBuffer = new StringBuffer();
066
067    /** An indication on if we're inside a head. */
068    private boolean headFlag;
069
070    /** An indication on if we're inside an image caption flag. */
071    private boolean figureCaptionFlag;
072
073    /** An indication on if we're inside a paragraph flag. */
074    private boolean paragraphFlag;
075
076    /** An indication on if we're in verbatim mode. */
077    private boolean verbatimFlag;
078
079    /** Stack of alignment int[] of table cells. */
080    private final LinkedList<int[]> cellJustifStack;
081
082    /** Stack of justification of table cells. */
083    private final LinkedList<Boolean> isCellJustifStack;
084
085    /** Stack of current table cell. */
086    private final LinkedList<Integer> cellCountStack;
087
088    /** Used to style successive table rows differently. */
089    private boolean evenTableRow = true;
090
091    /** The stack of StringWriter to write the table result temporary, so we could play with the output DOXIA-177. */
092    private final LinkedList<StringWriter> tableContentWriterStack;
093
094    private final LinkedList<StringWriter> tableCaptionWriterStack;
095
096    private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
097
098    /** The stack of table caption */
099    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}