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