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