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