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