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