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