Coverage Report - org.apache.maven.doxia.module.fo.FoSink
 
Classes in this File Line Coverage Branch Coverage Complexity
FoSink
92%
609/655
62%
96/154
1,529
 
 1  
 package org.apache.maven.doxia.module.fo;
 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.File;
 23  
 import java.io.IOException;
 24  
 import java.io.PrintWriter;
 25  
 import java.io.StringWriter;
 26  
 import java.io.Writer;
 27  
 import java.util.Enumeration;
 28  
 import java.util.HashMap;
 29  
 import java.util.LinkedList;
 30  
 import java.util.Map;
 31  
 import java.util.Set;
 32  
 import java.util.Stack;
 33  
 import java.util.TreeSet;
 34  
 
 35  
 import javax.swing.text.MutableAttributeSet;
 36  
 import javax.swing.text.html.HTML.Attribute;
 37  
 import javax.swing.text.html.HTML.Tag;
 38  
 
 39  
 import org.apache.maven.doxia.sink.AbstractXmlSink;
 40  
 import org.apache.maven.doxia.sink.SinkEventAttributeSet;
 41  
 import org.apache.maven.doxia.sink.SinkEventAttributes;
 42  
 import org.apache.maven.doxia.sink.SinkUtils;
 43  
 import org.apache.maven.doxia.util.DoxiaUtils;
 44  
 import org.apache.maven.doxia.util.HtmlTools;
 45  
 
 46  
 import org.codehaus.plexus.util.StringUtils;
 47  
 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
 48  
 
 49  
 /**
 50  
  * A Doxia Sink that produces a FO model. The usage is similar to the following:
 51  
  *
 52  
  * <pre>
 53  
  * FoSink sink = new FoSink( writer );
 54  
  * sink.beginDocument();
 55  
  * ...
 56  
  * sink.endDocument();
 57  
  * </pre>
 58  
  *
 59  
  * @author ltheussl
 60  
  * @version $Id: FoSink.java 1091053 2011-04-11 12:55:07Z ltheussl $
 61  
  * @since 1.1
 62  
  */
 63  
 public class FoSink
 64  
     extends AbstractXmlSink
 65  
     implements FoMarkup
 66  
 {
 67  
     /** For writing the result. */
 68  
     private final PrintWriter out;
 69  
 
 70  
     /** Used to get the current position in numbered lists. */
 71  
     private final Stack<NumberedListItem> listStack;
 72  
 
 73  
     /** Used to get attributes for a given FO element. */
 74  
     private final FoConfiguration config;
 75  
 
 76  
     /** Counts the current section level. */
 77  92
     private int section = 0;
 78  
 
 79  
     /** Counts the current subsection level. */
 80  92
     private int subsection = 0;
 81  
 
 82  
     /** Counts the current subsubsection level. */
 83  92
     private int subsubsection = 0;
 84  
 
 85  
     /** Verbatim flag. */
 86  
     private boolean verbatim;
 87  
 
 88  
     /** figure flag. */
 89  
     private boolean inFigure;
 90  
 
 91  
     private final String encoding;
 92  
 
 93  
     private final String languageId;
 94  
 
 95  
     /** Stack of drawing borders on table cells. */
 96  
     private final LinkedList<Boolean> tableGridStack;
 97  
 
 98  
     /** Stack of alignment int[] of table cells. */
 99  
     private final LinkedList<int[]> cellJustifStack;
 100  
 
 101  
     /** Stack of justification of table cells. */
 102  
     private final LinkedList<Boolean> isCellJustifStack;
 103  
 
 104  
     /** Stack of current table cell. */
 105  
     private final LinkedList<Integer> cellCountStack;
 106  
 
 107  
     /** The stack of StringWriter to write the table result temporary, so we could play with the output and fix fo. */
 108  
     private final LinkedList<StringWriter> tableContentWriterStack;
 109  
 
 110  
     private final LinkedList<StringWriter> tableCaptionWriterStack;
 111  
 
 112  
     private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack;
 113  
 
 114  
     /** The stack of table caption */
 115  
     private final LinkedList<String> tableCaptionStack;
 116  
 
 117  
     /** Map of warn messages with a String as key to describe the error type and a Set as value.
 118  
      * Using to reduce warn messages. */
 119  
     protected Map<String, Set<String>> warnMessages;
 120  
 
 121  
     /**
 122  
      * Constructor, initialize the Writer.
 123  
      *
 124  
      * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
 125  
      * You could use <code>newXmlWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
 126  
      */
 127  
     protected FoSink( Writer writer )
 128  
     {
 129  92
         this( writer, "UTF-8" );
 130  92
     }
 131  
 
 132  
     /**
 133  
      * Constructor, initialize the Writer and tells which encoding is used.
 134  
      *
 135  
      * @param writer not null writer to write the result.
 136  
      * @param encoding the encoding used, that should be written to the generated HTML content
 137  
      * if not <code>null</code>.
 138  
      */
 139  
     protected FoSink( Writer writer, String encoding )
 140  
     {
 141  92
         this( writer, encoding, null );
 142  92
     }
 143  
 
 144  
     /**
 145  
      * Constructor, initialize the Writer and tells which encoding and languageId are used.
 146  
      *
 147  
      * @param writer not null writer to write the result.
 148  
      * @param encoding the encoding used, that should be written to the generated HTML content
 149  
      * if not <code>null</code>.
 150  
      * @param languageId language identifier for the root element as defined by
 151  
      * <a href="ftp://ftp.isi.edu/in-notes/bcp/bcp47.txt">IETF BCP 47</a>, Tags for the Identification of Languages;
 152  
      * in addition, the empty string may be specified.
 153  
      */
 154  
     protected FoSink( Writer writer, String encoding, String languageId )
 155  92
     {
 156  92
         if ( writer == null )
 157  
         {
 158  0
             throw new NullPointerException( "Null writer in FO Sink!" );
 159  
         }
 160  
 
 161  92
         this.out = new PrintWriter( writer );
 162  92
         this.encoding = encoding;
 163  92
         this.languageId = languageId;
 164  92
         this.config = new FoConfiguration();
 165  
 
 166  92
         this.listStack = new Stack<NumberedListItem>();
 167  92
         this.tableGridStack = new LinkedList<Boolean>();
 168  92
         this.cellJustifStack = new LinkedList<int[]>();
 169  92
         this.isCellJustifStack = new LinkedList<Boolean>();
 170  92
         this.cellCountStack = new LinkedList<Integer>();
 171  92
         this.tableContentWriterStack = new LinkedList<StringWriter>();
 172  92
         this.tableCaptionWriterStack = new LinkedList<StringWriter>();
 173  92
         this.tableCaptionXMLWriterStack = new LinkedList<PrettyPrintXMLWriter>();
 174  92
         this.tableCaptionStack = new LinkedList<String>();
 175  
 
 176  92
         setNameSpace( "fo" );
 177  92
     }
 178  
 
 179  
     // TODO add FOP compliance mode?
 180  
 
 181  
     /**
 182  
      * Load configuration parameters from a File.
 183  
      *
 184  
      * @param configFile the configuration file.
 185  
      *
 186  
      * @throws java.io.IOException if the File cannot be read
 187  
      *  or some error occurs when initializing the configuration parameters.
 188  
      *
 189  
      * @since 1.1.1
 190  
      */
 191  
     public void load( File configFile )
 192  
             throws IOException
 193  
     {
 194  0
         config.load( configFile );
 195  0
     }
 196  
 
 197  
     /** {@inheritDoc} */
 198  
     public void head( SinkEventAttributes attributes )
 199  
     {
 200  8
         init();
 201  
 
 202  8
         startPageSequence( "0", null, null );
 203  8
     }
 204  
 
 205  
     /** {@inheritDoc} */
 206  
     public void head()
 207  
     {
 208  8
         head( null );
 209  8
     }
 210  
 
 211  
     /** {@inheritDoc} */
 212  
     public void head_()
 213  
     {
 214  8
         writeEOL();
 215  8
     }
 216  
 
 217  
     /** {@inheritDoc} */
 218  
     public void title( SinkEventAttributes attributes )
 219  
     {
 220  10
         writeStartTag( BLOCK_TAG, "doc.header.title" );
 221  10
     }
 222  
 
 223  
     /** {@inheritDoc} */
 224  
     public void title()
 225  
     {
 226  10
         title( null );
 227  10
     }
 228  
 
 229  
     /** {@inheritDoc} */
 230  
     public void title_()
 231  
     {
 232  10
         writeEndTag( BLOCK_TAG );
 233  10
         writeEOL();
 234  10
     }
 235  
 
 236  
     /** {@inheritDoc} */
 237  
     public void author( SinkEventAttributes attributes )
 238  
     {
 239  10
         writeStartTag( BLOCK_TAG, "doc.header.author" );
 240  10
     }
 241  
 
 242  
     /** {@inheritDoc} */
 243  
     public void author()
 244  
     {
 245  10
         author( null );
 246  10
     }
 247  
 
 248  
     /** {@inheritDoc} */
 249  
     public void author_()
 250  
     {
 251  10
         writeEndTag( BLOCK_TAG );
 252  10
         writeEOL();
 253  10
     }
 254  
 
 255  
     /** {@inheritDoc} */
 256  
     public void date( SinkEventAttributes attributes )
 257  
     {
 258  10
         writeStartTag( BLOCK_TAG, "doc.header.date" );
 259  10
     }
 260  
 
 261  
     /** {@inheritDoc} */
 262  
     public void date()
 263  
     {
 264  10
         date( null );
 265  10
     }
 266  
 
 267  
     /** {@inheritDoc} */
 268  
     public void date_()
 269  
     {
 270  10
         writeEndTag( BLOCK_TAG );
 271  10
         writeEOL();
 272  10
     }
 273  
 
 274  
     /** {@inheritDoc} */
 275  
     public void body( SinkEventAttributes attributes )
 276  
     {
 277  
         // noop
 278  10
     }
 279  
 
 280  
     /** {@inheritDoc} */
 281  
     public void body()
 282  
     {
 283  10
         body( null );
 284  10
     }
 285  
 
 286  
     /** {@inheritDoc} */
 287  
     public void body_()
 288  
     {
 289  10
         writeEOL();
 290  10
         writeEndTag( FLOW_TAG );
 291  10
         writeEOL();
 292  10
         writeEndTag( PAGE_SEQUENCE_TAG );
 293  10
         writeEOL();
 294  10
         endDocument();
 295  10
     }
 296  
 
 297  
     // -----------------------------------------------------------------------
 298  
     //
 299  
     // -----------------------------------------------------------------------
 300  
 
 301  
     /** {@inheritDoc} */
 302  
     public void sectionTitle()
 303  
     {
 304  
         // nop
 305  2
     }
 306  
 
 307  
     /** {@inheritDoc} */
 308  
     public void sectionTitle_()
 309  
     {
 310  
         // nop
 311  2
     }
 312  
 
 313  
     /** {@inheritDoc} */
 314  
     public void section( int level, SinkEventAttributes attributes )
 315  
     {
 316  50
         if ( level == SECTION_LEVEL_1 )
 317  
         {
 318  10
             section++;
 319  10
             subsection = 0;
 320  10
             subsubsection = 0;
 321  
         }
 322  40
         else if ( level == SECTION_LEVEL_2 )
 323  
         {
 324  10
             subsection++;
 325  10
             subsubsection = 0;
 326  
         }
 327  30
         else if ( level == SECTION_LEVEL_3 )
 328  
         {
 329  10
             subsubsection++;
 330  
         }
 331  
 
 332  50
         onSection();
 333  50
     }
 334  
 
 335  
     /** {@inheritDoc} */
 336  
     public void section_( int level )
 337  
     {
 338  50
         onSection_();
 339  50
     }
 340  
 
 341  
     /** {@inheritDoc} */
 342  
     public void sectionTitle( int level, SinkEventAttributes attributes )
 343  
     {
 344  50
         onSectionTitle( level );
 345  50
     }
 346  
 
 347  
     /** {@inheritDoc} */
 348  
     public void sectionTitle_( int level )
 349  
     {
 350  50
         onSectionTitle_();
 351  50
     }
 352  
 
 353  
     /** {@inheritDoc} */
 354  
     public void section1()
 355  
     {
 356  10
         section( SECTION_LEVEL_1, null );
 357  10
     }
 358  
 
 359  
     /** {@inheritDoc} */
 360  
     public void sectionTitle1()
 361  
     {
 362  10
         sectionTitle( SECTION_LEVEL_1, null );
 363  10
     }
 364  
 
 365  
     /** {@inheritDoc} */
 366  
     public void sectionTitle1_()
 367  
     {
 368  10
         sectionTitle_( SECTION_LEVEL_1 );
 369  10
     }
 370  
 
 371  
     /** {@inheritDoc} */
 372  
     public void section1_()
 373  
     {
 374  10
         section_( SECTION_LEVEL_1 );
 375  10
     }
 376  
 
 377  
     /** {@inheritDoc} */
 378  
     public void section2()
 379  
     {
 380  10
         section( SECTION_LEVEL_2, null );
 381  10
     }
 382  
 
 383  
     /** {@inheritDoc} */
 384  
     public void sectionTitle2()
 385  
     {
 386  10
         sectionTitle( SECTION_LEVEL_2, null );
 387  10
     }
 388  
 
 389  
     /** {@inheritDoc} */
 390  
     public void sectionTitle2_()
 391  
     {
 392  10
         sectionTitle_( SECTION_LEVEL_2 );
 393  10
     }
 394  
 
 395  
     /** {@inheritDoc} */
 396  
     public void section2_()
 397  
     {
 398  10
         section_( SECTION_LEVEL_2 );
 399  10
     }
 400  
 
 401  
     /** {@inheritDoc} */
 402  
     public void section3()
 403  
     {
 404  10
         section( SECTION_LEVEL_3, null );
 405  10
     }
 406  
 
 407  
     /** {@inheritDoc} */
 408  
     public void sectionTitle3()
 409  
     {
 410  10
         sectionTitle( SECTION_LEVEL_3, null );
 411  10
     }
 412  
 
 413  
     /** {@inheritDoc} */
 414  
     public void sectionTitle3_()
 415  
     {
 416  10
         sectionTitle_( SECTION_LEVEL_3 );
 417  10
     }
 418  
 
 419  
     /** {@inheritDoc} */
 420  
     public void section3_()
 421  
     {
 422  10
         section_( SECTION_LEVEL_3 );
 423  10
     }
 424  
 
 425  
     /** {@inheritDoc} */
 426  
     public void section4()
 427  
     {
 428  10
         section( SECTION_LEVEL_4, null );
 429  10
     }
 430  
 
 431  
     /** {@inheritDoc} */
 432  
     public void sectionTitle4()
 433  
     {
 434  10
         sectionTitle( SECTION_LEVEL_4, null );
 435  10
     }
 436  
 
 437  
     /** {@inheritDoc} */
 438  
     public void sectionTitle4_()
 439  
     {
 440  10
         sectionTitle_( SECTION_LEVEL_4 );
 441  10
     }
 442  
 
 443  
     /** {@inheritDoc} */
 444  
     public void section4_()
 445  
     {
 446  10
         section_( SECTION_LEVEL_4 );
 447  10
     }
 448  
 
 449  
     /** {@inheritDoc} */
 450  
     public void section5()
 451  
     {
 452  10
         section( SECTION_LEVEL_5, null );
 453  10
     }
 454  
 
 455  
     /** {@inheritDoc} */
 456  
     public void sectionTitle5()
 457  
     {
 458  10
         sectionTitle( SECTION_LEVEL_5, null );
 459  10
     }
 460  
 
 461  
     /** {@inheritDoc} */
 462  
     public void sectionTitle5_()
 463  
     {
 464  10
         sectionTitle_( SECTION_LEVEL_5 );
 465  10
     }
 466  
 
 467  
     /** {@inheritDoc} */
 468  
     public void section5_()
 469  
     {
 470  10
         section_( SECTION_LEVEL_5 );
 471  10
     }
 472  
 
 473  
     /** Starts a section/subsection. */
 474  
     private void onSection()
 475  
     {
 476  50
         writeEOL();
 477  50
         writeStartTag( BLOCK_TAG, "body.text" );
 478  50
     }
 479  
 
 480  
     /**
 481  
      * Starts a section title.
 482  
      *
 483  
      * @param depth The section level.
 484  
      */
 485  
     private void onSectionTitle( int depth )
 486  
     {
 487  50
         StringBuffer title = new StringBuffer( 16 );
 488  
 
 489  50
         title.append( getChapterString() );
 490  
 
 491  50
         writeEOL();
 492  50
         if ( depth == SECTION_LEVEL_1 )
 493  
         {
 494  10
             writeStartTag( BLOCK_TAG, "body.h1" );
 495  10
             title.append( section ).append( "   " );
 496  
         }
 497  40
         else if ( depth == SECTION_LEVEL_2 )
 498  
         {
 499  10
             writeStartTag( BLOCK_TAG, "body.h2" );
 500  10
             title.append( section ).append( "." );
 501  10
             title.append( subsection ).append( "   " );
 502  
         }
 503  30
         else if ( depth == SECTION_LEVEL_3 )
 504  
         {
 505  10
             writeStartTag( BLOCK_TAG, "body.h3" );
 506  10
             title.append( section ).append( "." );
 507  10
             title.append( subsection ).append( "." );
 508  10
             title.append( subsubsection ).append( "   " );
 509  
         }
 510  20
         else if ( depth == SECTION_LEVEL_4 )
 511  
         {
 512  10
             writeStartTag( BLOCK_TAG, "body.h4" );
 513  
         }
 514  
         else
 515  
         {
 516  10
             writeStartTag( BLOCK_TAG, "body.h5" );
 517  
         }
 518  
 
 519  50
         write( title.toString() );
 520  50
     }
 521  
 
 522  
     /** Ends a section title. */
 523  
     private void onSectionTitle_()
 524  
     {
 525  50
         writeEndTag( BLOCK_TAG );
 526  50
         writeEOL();
 527  50
     }
 528  
 
 529  
     /** Ends a section/subsection. */
 530  
     private void onSection_()
 531  
     {
 532  50
         writeEndTag( BLOCK_TAG );
 533  50
         writeEOL();
 534  50
     }
 535  
 
 536  
     /**
 537  
      * Resets the section counter to 0.
 538  
      * Only useful for overriding classes, like AggregateSink, the FoSink puts everything into one chapter.
 539  
      */
 540  
     protected void resetSectionCounter()
 541  
     {
 542  12
         this.section = 0;
 543  12
     }
 544  
 
 545  
     /**
 546  
      * Returns the current chapter number as a string.
 547  
      * By default does nothing, gets overridden by AggregateSink.
 548  
      *
 549  
      * @return an empty String.
 550  
      */
 551  
     protected String getChapterString()
 552  
     {
 553  30
         return "";
 554  
     }
 555  
 
 556  
     // -----------------------------------------------------------------------
 557  
     //
 558  
     // -----------------------------------------------------------------------
 559  
 
 560  
     /** {@inheritDoc} */
 561  
     public void list( SinkEventAttributes attributes )
 562  
     {
 563  18
         writeEOL();
 564  18
         writeStartTag( LIST_BLOCK_TAG, "list" );
 565  18
     }
 566  
 
 567  
     /** {@inheritDoc} */
 568  
     public void list()
 569  
     {
 570  18
         list( null );
 571  18
     }
 572  
 
 573  
     /** {@inheritDoc} */
 574  
     public void list_()
 575  
     {
 576  18
         writeEndTag( LIST_BLOCK_TAG );
 577  18
         writeEOL();
 578  18
     }
 579  
 
 580  
     /** {@inheritDoc} */
 581  
     public void listItem( SinkEventAttributes attributes )
 582  
     {
 583  42
         writeStartTag( LIST_ITEM_TAG, "list.item" );
 584  42
         writeStartTag( LIST_ITEM_LABEL_TAG );
 585  42
         writeStartTag( BLOCK_TAG );
 586  42
         write( "&#8226;" ); // TODO customize?
 587  42
         writeEndTag( BLOCK_TAG );
 588  42
         writeEndTag( LIST_ITEM_LABEL_TAG );
 589  42
         writeEOL();
 590  42
         writeStartTag( LIST_ITEM_BODY_TAG, "list.item" );
 591  42
         writeEOL();
 592  42
         writeStartTag( BLOCK_TAG );
 593  42
     }
 594  
 
 595  
     /** {@inheritDoc} */
 596  
     public void listItem()
 597  
     {
 598  42
         listItem( null );
 599  42
     }
 600  
 
 601  
     /** {@inheritDoc} */
 602  
     public void listItem_()
 603  
     {
 604  42
         writeEndTag( BLOCK_TAG );
 605  42
         writeEOL();
 606  42
         writeEndTag( LIST_ITEM_BODY_TAG );
 607  42
         writeEOL();
 608  42
         writeEndTag( LIST_ITEM_TAG );
 609  42
         writeEOL();
 610  42
     }
 611  
 
 612  
     /** {@inheritDoc} */
 613  
     public void numberedList( int numbering, SinkEventAttributes attributes )
 614  
     {
 615  18
         this.listStack.push( new NumberedListItem( numbering ) );
 616  18
         writeEOL();
 617  18
         writeStartTag( LIST_BLOCK_TAG, "list" );
 618  18
     }
 619  
 
 620  
     /** {@inheritDoc} */
 621  
     public void numberedList( int numbering )
 622  
     {
 623  18
         numberedList( numbering, null );
 624  18
     }
 625  
 
 626  
     /** {@inheritDoc} */
 627  
     public void numberedList_()
 628  
     {
 629  18
         this.listStack.pop();
 630  18
         writeEndTag( LIST_BLOCK_TAG );
 631  18
         writeEOL();
 632  18
     }
 633  
 
 634  
     /** {@inheritDoc} */
 635  
     public void numberedListItem( SinkEventAttributes attributes )
 636  
     {
 637  34
         NumberedListItem current = this.listStack.peek();
 638  34
         current.next();
 639  
 
 640  34
         writeStartTag( LIST_ITEM_TAG, "list.item" );
 641  
 
 642  34
         writeEOL();
 643  34
         writeStartTag( LIST_ITEM_LABEL_TAG );
 644  34
         writeEOL();
 645  34
         writeStartTag( BLOCK_TAG );
 646  34
         write( current.getListItemSymbol() );
 647  34
         writeEndTag( BLOCK_TAG );
 648  34
         writeEOL();
 649  34
         writeEndTag( LIST_ITEM_LABEL_TAG );
 650  34
         writeEOL();
 651  
 
 652  34
         writeStartTag( LIST_ITEM_BODY_TAG, "list.item" );
 653  34
         writeEOL();
 654  34
         writeStartTag( BLOCK_TAG );
 655  34
     }
 656  
 
 657  
     /** {@inheritDoc} */
 658  
     public void numberedListItem()
 659  
     {
 660  34
         numberedListItem( null );
 661  34
     }
 662  
 
 663  
     /** {@inheritDoc} */
 664  
     public void numberedListItem_()
 665  
     {
 666  34
         writeEndTag( BLOCK_TAG );
 667  34
         writeEOL();
 668  34
         writeEndTag( LIST_ITEM_BODY_TAG );
 669  34
         writeEOL();
 670  34
         writeEndTag( LIST_ITEM_TAG );
 671  34
         writeEOL();
 672  34
     }
 673  
 
 674  
     /** {@inheritDoc} */
 675  
     public void definitionList( SinkEventAttributes attributes )
 676  
     {
 677  10
         writeEOL();
 678  10
         writeStartTag( BLOCK_TAG, "dl" );
 679  10
     }
 680  
 
 681  
     /** {@inheritDoc} */
 682  
     public void definitionList()
 683  
     {
 684  10
         definitionList( null );
 685  10
     }
 686  
 
 687  
     /** {@inheritDoc} */
 688  
     public void definitionList_()
 689  
     {
 690  10
         writeEndTag( BLOCK_TAG );
 691  10
         writeEOL();
 692  10
     }
 693  
 
 694  
     /** {@inheritDoc} */
 695  
     public void definitionListItem( SinkEventAttributes attributes )
 696  
     {
 697  
         // nop
 698  18
     }
 699  
 
 700  
     /** {@inheritDoc} */
 701  
     public void definitionListItem()
 702  
     {
 703  18
         definitionListItem( null );
 704  18
     }
 705  
 
 706  
     /** {@inheritDoc} */
 707  
     public void definitionListItem_()
 708  
     {
 709  
         // nop
 710  18
     }
 711  
 
 712  
     /** {@inheritDoc} */
 713  
     public void definedTerm( SinkEventAttributes attributes )
 714  
     {
 715  18
         writeStartTag( BLOCK_TAG, "dt" );
 716  18
     }
 717  
 
 718  
     /** {@inheritDoc} */
 719  
     public void definedTerm()
 720  
     {
 721  18
         definedTerm( null );
 722  18
     }
 723  
 
 724  
     /** {@inheritDoc} */
 725  
     public void definedTerm_()
 726  
     {
 727  18
         writeEndTag( BLOCK_TAG );
 728  18
         writeEOL();
 729  18
     }
 730  
 
 731  
     /** {@inheritDoc} */
 732  
     public void definition( SinkEventAttributes attributes )
 733  
     {
 734  18
         writeEOL();
 735  18
         writeStartTag( BLOCK_TAG, "dd" );
 736  18
     }
 737  
 
 738  
     /** {@inheritDoc} */
 739  
     public void definition()
 740  
     {
 741  18
         definition( null );
 742  18
     }
 743  
 
 744  
     /** {@inheritDoc} */
 745  
     public void definition_()
 746  
     {
 747  18
         writeEndTag( BLOCK_TAG );
 748  18
         writeEOL();
 749  18
     }
 750  
 
 751  
     /** {@inheritDoc} */
 752  
     public void figure( SinkEventAttributes attributes )
 753  
     {
 754  10
         this.inFigure = true;
 755  10
         writeEOL();
 756  10
         writeStartTag( BLOCK_TAG, "figure.display" );
 757  10
     }
 758  
 
 759  
     /** {@inheritDoc} */
 760  
     public void figure()
 761  
     {
 762  2
         figure( null );
 763  2
     }
 764  
 
 765  
     /** {@inheritDoc} */
 766  
     public void figure_()
 767  
     {
 768  10
         this.inFigure = false;
 769  10
         writeEndTag( BLOCK_TAG );
 770  10
         writeEOL();
 771  10
     }
 772  
 
 773  
     /** {@inheritDoc} */
 774  
     public void figureGraphics( String name )
 775  
     {
 776  2
         figureGraphics( name, null );
 777  2
     }
 778  
 
 779  
     /** {@inheritDoc} */
 780  
     public void figureGraphics( String src, SinkEventAttributes attributes )
 781  
     {
 782  12
         MutableAttributeSet atts = config.getAttributeSet( "figure.graphics" );
 783  12
         atts.addAttribute( Attribute.SRC.toString(), src );
 784  
 
 785  
         // http://xmlgraphics.apache.org/fop/graphics.html#resolution
 786  
 
 787  12
         final String[] valids = new String[] {"content-height", "content-width", "height", "width"};
 788  12
         final MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, valids );
 789  
 
 790  12
         if ( filtered != null )
 791  
         {
 792  0
             atts.addAttributes( filtered );
 793  
         }
 794  
 
 795  12
         writeln( "<fo:external-graphic" + SinkUtils.getAttributeString( atts ) + "/>" );
 796  12
     }
 797  
 
 798  
     /**
 799  
      * Flags if we are inside a figure.
 800  
      *
 801  
      * @return True if we are between {@link #figure()} and {@link #figure_()} calls.
 802  
      */
 803  
     protected boolean isFigure()
 804  
     {
 805  0
         return this.inFigure;
 806  
     }
 807  
 
 808  
     /** {@inheritDoc} */
 809  
     public void figureCaption( SinkEventAttributes attributes )
 810  
     {
 811  10
         writeStartTag( BLOCK_TAG, "figure.caption" );
 812  10
     }
 813  
 
 814  
     /** {@inheritDoc} */
 815  
     public void figureCaption()
 816  
     {
 817  2
         figureCaption( null );
 818  2
     }
 819  
 
 820  
     /** {@inheritDoc} */
 821  
     public void figureCaption_()
 822  
     {
 823  10
         writeEndTag( BLOCK_TAG );
 824  10
         writeEOL();
 825  10
     }
 826  
 
 827  
     /** {@inheritDoc} */
 828  
     public void paragraph()
 829  
     {
 830  114
         paragraph( null );
 831  114
     }
 832  
 
 833  
     /** {@inheritDoc} */
 834  
     public void paragraph( SinkEventAttributes attributes )
 835  
     {
 836  132
         MutableAttributeSet atts = config.getAttributeSet( "normal.paragraph" );
 837  
 
 838  132
         if ( attributes != null && attributes.isDefined( SinkEventAttributes.ALIGN ) )
 839  
         {
 840  18
             atts.addAttribute( "text-align", attributes.getAttribute( SinkEventAttributes.ALIGN ) );
 841  
         }
 842  
 
 843  132
         writeEOL();
 844  132
         writeStartTag( BLOCK_TAG, atts );
 845  132
     }
 846  
 
 847  
     /** {@inheritDoc} */
 848  
     public void paragraph_()
 849  
     {
 850  132
         writeEndTag( BLOCK_TAG );
 851  132
         writeEOL();
 852  132
     }
 853  
 
 854  
     /** {@inheritDoc} */
 855  
     public void verbatim( SinkEventAttributes attributes )
 856  
     {
 857  18
         this.verbatim = true;
 858  
 
 859  18
         boolean boxed = false;
 860  
 
 861  18
         if ( attributes != null && attributes.isDefined( SinkEventAttributes.DECORATION ) )
 862  
         {
 863  18
             boxed =
 864  
                 "boxed".equals( attributes.getAttribute( SinkEventAttributes.DECORATION ).toString() );
 865  
         }
 866  
 
 867  18
         if ( boxed )
 868  
         {
 869  18
             writeStartTag( BLOCK_TAG, "body.source" );
 870  
         }
 871  
         else
 872  
         {
 873  0
             writeStartTag( BLOCK_TAG, "body.pre" );
 874  
         }
 875  18
     }
 876  
 
 877  
     /** {@inheritDoc} */
 878  
     public void verbatim( boolean boxed )
 879  
     {
 880  0
         verbatim( boxed ? SinkEventAttributeSet.BOXED : null );
 881  0
     }
 882  
 
 883  
     /** {@inheritDoc} */
 884  
     public void verbatim_()
 885  
     {
 886  18
         this.verbatim = false;
 887  18
         writeEndTag( BLOCK_TAG );
 888  18
         writeEOL();
 889  18
     }
 890  
 
 891  
     /** {@inheritDoc} */
 892  
     public void horizontalRule( SinkEventAttributes attributes )
 893  
     {
 894  10
         writeEOL();
 895  10
         writeEOL();
 896  10
         writeStartTag( BLOCK_TAG );
 897  10
         writeEmptyTag( LEADER_TAG, "body.rule" );
 898  10
         writeEndTag( BLOCK_TAG );
 899  10
         writeEOL();
 900  10
     }
 901  
 
 902  
     /** {@inheritDoc} */
 903  
     public void horizontalRule()
 904  
     {
 905  10
         horizontalRule( null );
 906  10
     }
 907  
 
 908  
     /** {@inheritDoc} */
 909  
     public void pageBreak()
 910  
     {
 911  10
         writeEmptyTag( BLOCK_TAG, "break-before", "page" );
 912  10
         writeEOL();
 913  10
     }
 914  
 
 915  
     /** {@inheritDoc} */
 916  
     public void table( SinkEventAttributes attributes )
 917  
     {
 918  34
         writeEOL();
 919  34
         writeStartTag( BLOCK_TAG, "table.padding" );
 920  
 
 921  
         // <fo:table-and-caption> is XSL-FO 1.0 standard but still not implemented in FOP 0.95
 922  
         //writeStartTag( TABLE_AND_CAPTION_TAG );
 923  
 
 924  34
         this.tableContentWriterStack.addLast( new StringWriter() );
 925  34
         writeStartTag( TABLE_TAG, "table.layout" );
 926  34
     }
 927  
 
 928  
     /** {@inheritDoc} */
 929  
     public void table()
 930  
     {
 931  26
         table( null );
 932  26
     }
 933  
 
 934  
     /** {@inheritDoc} */
 935  
     public void table_()
 936  
     {
 937  34
         String content = this.tableContentWriterStack.removeLast().toString();
 938  
 
 939  34
         StringBuffer sb = new StringBuffer();
 940  34
         int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
 941  104
         for ( int i = 0; i < cellCount; i++ )
 942  
         {
 943  70
             sb.append( "<fo:table-column column-width=\"proportional-column-width(1)\"/>" );
 944  70
             sb.append( EOL );
 945  
         }
 946  
 
 947  34
         int index = content.indexOf( ">" ) + 1;
 948  34
         writeln( content.substring( 0, index ) );
 949  34
         write( sb.toString() );
 950  34
         write( content.substring( index ) );
 951  
 
 952  34
         writeEndTag( TABLE_TAG );
 953  34
         writeEOL();
 954  
 
 955  
         // <fo:table-and-caption> is XSL-FO 1.0 standard but still not implemented in FOP 0.95
 956  
         //writeEndTag( TABLE_AND_CAPTION_TAG );
 957  
 
 958  34
         writeEndTag( BLOCK_TAG );
 959  34
         writeEOL();
 960  
 
 961  34
         if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null )
 962  
         {
 963  18
             paragraph( SinkEventAttributeSet.CENTER );
 964  18
             write( this.tableCaptionStack.removeLast().toString() );
 965  18
             paragraph_();
 966  
         }
 967  34
     }
 968  
 
 969  
     /** {@inheritDoc} */
 970  
     public void tableRows( int[] justification, boolean grid )
 971  
     {
 972  34
         this.tableGridStack.addLast( Boolean.valueOf( grid ) );
 973  34
         this.cellJustifStack.addLast( justification );
 974  34
         this.isCellJustifStack.addLast( Boolean.valueOf( true ) );
 975  34
         this.cellCountStack.addLast( new Integer( 0 ) );
 976  34
         writeEOL();
 977  34
         writeStartTag( TABLE_BODY_TAG );
 978  34
     }
 979  
 
 980  
     /** {@inheritDoc} */
 981  
     public void tableRows_()
 982  
     {
 983  34
         this.tableGridStack.removeLast();
 984  34
         this.cellJustifStack.removeLast();
 985  34
         this.isCellJustifStack.removeLast();
 986  34
         writeEndTag( TABLE_BODY_TAG );
 987  34
         writeEOL();
 988  34
     }
 989  
 
 990  
     /** {@inheritDoc} */
 991  
     public void tableRow( SinkEventAttributes attributes )
 992  
     {
 993  
         // TODO spacer rows
 994  64
         writeStartTag( TABLE_ROW_TAG, "table.body.row" );
 995  64
         this.cellCountStack.removeLast();
 996  64
         this.cellCountStack.addLast( new Integer( 0 ) );
 997  64
     }
 998  
 
 999  
     /** {@inheritDoc} */
 1000  
     public void tableRow()
 1001  
     {
 1002  50
         tableRow( null );
 1003  50
     }
 1004  
 
 1005  
     /** {@inheritDoc} */
 1006  
     public void tableRow_()
 1007  
     {
 1008  64
         writeEndTag( TABLE_ROW_TAG );
 1009  64
         writeEOL();
 1010  64
     }
 1011  
 
 1012  
     /** {@inheritDoc} */
 1013  
     public void tableCell( SinkEventAttributes attributes )
 1014  
     {
 1015  118
         tableCell( false, attributes );
 1016  118
     }
 1017  
 
 1018  
     /** {@inheritDoc} */
 1019  
     public void tableCell()
 1020  
     {
 1021  98
         tableCell( (SinkEventAttributes) null );
 1022  98
     }
 1023  
 
 1024  
     /** {@inheritDoc} */
 1025  
     public void tableCell( String width )
 1026  
     {
 1027  
         // TODO: fop can't handle cell width
 1028  0
         tableCell();
 1029  0
     }
 1030  
 
 1031  
     /** {@inheritDoc} */
 1032  
     public void tableHeaderCell( SinkEventAttributes attributes )
 1033  
     {
 1034  16
         tableCell( true, attributes );
 1035  16
     }
 1036  
 
 1037  
     /** {@inheritDoc} */
 1038  
     public void tableHeaderCell()
 1039  
     {
 1040  16
         tableHeaderCell( (SinkEventAttributes) null );
 1041  16
     }
 1042  
 
 1043  
     /** {@inheritDoc} */
 1044  
     public void tableHeaderCell( String width )
 1045  
     {
 1046  
         // TODO: fop can't handle cell width
 1047  0
         tableHeaderCell();
 1048  0
     }
 1049  
 
 1050  
     /**
 1051  
      * Writes a table cell.
 1052  
      *
 1053  
      * @param headerRow true if this is a header cell.
 1054  
      * @param attributes the cell attributes, could be null.
 1055  
      */
 1056  
     private void tableCell( boolean headerRow, SinkEventAttributes attributes )
 1057  
     {
 1058  134
         MutableAttributeSet cellAtts = headerRow
 1059  
                  ? config.getAttributeSet( "table.heading.cell" )
 1060  
                  : config.getAttributeSet( "table.body.cell" );
 1061  
 
 1062  
         // the column-number is needed for the hack to center the table, see tableRows.
 1063  134
         int cellCount = Integer.parseInt( this.cellCountStack.getLast().toString() );
 1064  134
         cellAtts.addAttribute( "column-number", String.valueOf( cellCount + 1 ) );
 1065  
 
 1066  134
         if ( this.tableGridStack.getLast().equals( Boolean.TRUE ) )
 1067  
         {
 1068  80
             cellAtts.addAttributes( config.getAttributeSet( "table.body.cell.grid" ) );
 1069  
         }
 1070  
 
 1071  134
         MutableAttributeSet blockAtts = headerRow
 1072  
                  ? config.getAttributeSet( "table.heading.block" )
 1073  
                  : config.getAttributeSet( "table.body.block" );
 1074  
 
 1075  134
         String justif = null;
 1076  134
         if ( attributes == null )
 1077  
         {
 1078  114
             attributes = new SinkEventAttributeSet( 0 );
 1079  
         }
 1080  
 
 1081  134
         if ( attributes.isDefined( Attribute.ALIGN.toString() ) )
 1082  
         {
 1083  0
             justif = attributes.getAttribute( Attribute.ALIGN.toString() ).toString();
 1084  
         }
 1085  
 
 1086  134
         int[] cellJustif = this.cellJustifStack.getLast();
 1087  134
         if ( justif == null && cellJustif != null && cellJustif.length > 0
 1088  
             && this.isCellJustifStack.getLast().equals( Boolean.TRUE ) )
 1089  
         {
 1090  134
             switch ( cellJustif[Math.min( cellCount, cellJustif.length - 1 )] )
 1091  
             {
 1092  
                 case JUSTIFY_LEFT:
 1093  36
                     justif = "left";
 1094  36
                     break;
 1095  
                 case JUSTIFY_RIGHT:
 1096  16
                     justif = "right";
 1097  16
                     break;
 1098  
                 case JUSTIFY_CENTER:
 1099  
                 default:
 1100  82
                     justif = "center";
 1101  
             }
 1102  
         }
 1103  
 
 1104  134
         if ( justif != null )
 1105  
         {
 1106  134
             blockAtts.addAttribute( "text-align", justif );
 1107  
         }
 1108  
 
 1109  134
         writeStartTag( TABLE_CELL_TAG, cellAtts );
 1110  134
         writeEOL();
 1111  134
         writeStartTag( BLOCK_TAG, blockAtts );
 1112  134
         writeEOL();
 1113  134
     }
 1114  
 
 1115  
     /** {@inheritDoc} */
 1116  
     public void tableCell_()
 1117  
     {
 1118  134
         writeEndTag( BLOCK_TAG );
 1119  134
         writeEOL();
 1120  134
         writeEndTag( TABLE_CELL_TAG );
 1121  134
         writeEOL();
 1122  
 
 1123  134
         if ( this.isCellJustifStack.getLast().equals( Boolean.TRUE ) )
 1124  
         {
 1125  134
             int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() );
 1126  134
             this.cellCountStack.addLast( new Integer( ++cellCount ) );
 1127  
         }
 1128  134
     }
 1129  
 
 1130  
     /** {@inheritDoc} */
 1131  
     public void tableHeaderCell_()
 1132  
     {
 1133  16
         tableCell_();
 1134  16
     }
 1135  
 
 1136  
     /** {@inheritDoc} */
 1137  
     public void tableCaption( SinkEventAttributes attributes )
 1138  
     {
 1139  18
         StringWriter sw = new StringWriter();
 1140  18
         this.tableCaptionWriterStack.addLast( sw );
 1141  18
         this.tableCaptionXMLWriterStack.addLast( new PrettyPrintXMLWriter( sw ) );
 1142  
 
 1143  
         // <fo:table-caption> is XSL-FO 1.0 standard but not implemented in FOP 0.95
 1144  
         //writeStartTag( TABLE_CAPTION_TAG );
 1145  
 
 1146  
         // TODO: how to implement this otherwise?
 1147  
         // table-footer doesn't work because it has to be declared before table-body.
 1148  18
     }
 1149  
 
 1150  
     /** {@inheritDoc} */
 1151  
     public void tableCaption()
 1152  
     {
 1153  10
         tableCaption( null );
 1154  10
     }
 1155  
 
 1156  
     /** {@inheritDoc} */
 1157  
     public void tableCaption_()
 1158  
     {
 1159  18
         if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
 1160  
         {
 1161  18
             this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() );
 1162  18
             this.tableCaptionXMLWriterStack.removeLast();
 1163  
         }
 1164  
         // <fo:table-caption> is XSL-FO 1.0 standard but not implemented in FOP 0.95
 1165  
         //writeEndTag( TABLE_CAPTION_TAG );
 1166  18
     }
 1167  
 
 1168  
     /** {@inheritDoc} */
 1169  
     public void anchor( String name, SinkEventAttributes attributes )
 1170  
     {
 1171  6
         if ( name == null )
 1172  
         {
 1173  0
             throw new NullPointerException( "Anchor name cannot be null!" );
 1174  
         }
 1175  
 
 1176  6
         String anchor = name;
 1177  
 
 1178  6
         if ( !DoxiaUtils.isValidId( anchor ) )
 1179  
         {
 1180  0
             anchor = DoxiaUtils.encodeId( name, true );
 1181  
 
 1182  0
             String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'";
 1183  0
             logMessage( "modifiedLink", msg );
 1184  
         }
 1185  
 
 1186  6
         anchor = "#" + name;
 1187  
 
 1188  6
         writeStartTag( INLINE_TAG, "id", anchor );
 1189  6
     }
 1190  
 
 1191  
     /** {@inheritDoc} */
 1192  
     public void anchor( String name )
 1193  
     {
 1194  6
         anchor( name, null );
 1195  6
     }
 1196  
 
 1197  
     /** {@inheritDoc} */
 1198  
     public void anchor_()
 1199  
     {
 1200  10
         writeEndTag( INLINE_TAG );
 1201  10
     }
 1202  
 
 1203  
     /** {@inheritDoc} */
 1204  
     public void link( String name, SinkEventAttributes attributes )
 1205  
     {
 1206  18
         if ( name == null )
 1207  
         {
 1208  0
             throw new NullPointerException( "Link name cannot be null!" );
 1209  
         }
 1210  
 
 1211  18
         if ( DoxiaUtils.isExternalLink( name ) )
 1212  
         {
 1213  8
             writeStartTag( BASIC_LINK_TAG, "external-destination", HtmlTools.escapeHTML( name ) );
 1214  8
             writeStartTag( INLINE_TAG, "href.external" );
 1215  
         }
 1216  10
         else if ( DoxiaUtils.isInternalLink( name ) )
 1217  
         {
 1218  10
             String anchor = name.substring( 1 );
 1219  
 
 1220  10
             if ( !DoxiaUtils.isValidId( anchor ) )
 1221  
             {
 1222  0
                 anchor = DoxiaUtils.encodeId( anchor, true );
 1223  
 
 1224  0
                 String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'";
 1225  0
                 logMessage( "modifiedLink", msg );
 1226  
             }
 1227  
 
 1228  10
             anchor = "#" + anchor;
 1229  
 
 1230  10
             writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) );
 1231  10
             writeStartTag( INLINE_TAG, "href.internal" );
 1232  10
         }
 1233  
         else
 1234  
         {
 1235  
             // treat everything else as is
 1236  0
             String anchor = name;
 1237  
 
 1238  0
             writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) );
 1239  0
             writeStartTag( INLINE_TAG, "href.internal" );
 1240  
         }
 1241  18
     }
 1242  
 
 1243  
     /** {@inheritDoc} */
 1244  
     public void link( String name )
 1245  
     {
 1246  18
         link( name, null );
 1247  18
     }
 1248  
 
 1249  
     /** {@inheritDoc} */
 1250  
     public void link_()
 1251  
     {
 1252  50
         writeEndTag( INLINE_TAG );
 1253  50
         writeEndTag( BASIC_LINK_TAG );
 1254  50
     }
 1255  
 
 1256  
     /** {@inheritDoc} */
 1257  
     public void italic()
 1258  
     {
 1259  10
         writeStartTag( INLINE_TAG, "italic" );
 1260  10
     }
 1261  
 
 1262  
     /** {@inheritDoc} */
 1263  
     public void italic_()
 1264  
     {
 1265  10
         writeEndTag( INLINE_TAG );
 1266  10
     }
 1267  
 
 1268  
     /** {@inheritDoc} */
 1269  
     public void bold()
 1270  
     {
 1271  10
         writeStartTag( INLINE_TAG, "bold" );
 1272  10
     }
 1273  
 
 1274  
     /** {@inheritDoc} */
 1275  
     public void bold_()
 1276  
     {
 1277  10
         writeEndTag( INLINE_TAG );
 1278  10
     }
 1279  
 
 1280  
     /** {@inheritDoc} */
 1281  
     public void monospaced()
 1282  
     {
 1283  10
         writeStartTag( INLINE_TAG, "monospace" );
 1284  10
     }
 1285  
 
 1286  
     /** {@inheritDoc} */
 1287  
     public void monospaced_()
 1288  
     {
 1289  10
         writeEndTag( INLINE_TAG );
 1290  10
     }
 1291  
 
 1292  
     /** {@inheritDoc} */
 1293  
     public void lineBreak( SinkEventAttributes attributes )
 1294  
     {
 1295  138
         writeEOL();
 1296  138
         writeEOL();
 1297  138
         writeSimpleTag( BLOCK_TAG );
 1298  138
     }
 1299  
 
 1300  
     /** {@inheritDoc} */
 1301  
     public void lineBreak()
 1302  
     {
 1303  138
         lineBreak( null );
 1304  138
     }
 1305  
 
 1306  
     /** {@inheritDoc} */
 1307  
     public void nonBreakingSpace()
 1308  
     {
 1309  18
         write( "&#160;" );
 1310  18
     }
 1311  
 
 1312  
     /** {@inheritDoc} */
 1313  
     public void text( String text, SinkEventAttributes attributes )
 1314  
     {
 1315  860
         content( text );
 1316  860
     }
 1317  
 
 1318  
     /** {@inheritDoc} */
 1319  
     public void text( String text )
 1320  
     {
 1321  756
         text( text, null );
 1322  756
     }
 1323  
 
 1324  
     /** {@inheritDoc} */
 1325  
     public void rawText( String text )
 1326  
     {
 1327  2
         write( text );
 1328  2
     }
 1329  
 
 1330  
     /** {@inheritDoc} */
 1331  
     public void flush()
 1332  
     {
 1333  84
         out.flush();
 1334  84
     }
 1335  
 
 1336  
     /** {@inheritDoc} */
 1337  
     public void close()
 1338  
     {
 1339  96
         out.close();
 1340  
 
 1341  96
         if ( getLog().isWarnEnabled() && this.warnMessages != null )
 1342  
         {
 1343  4
             for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
 1344  
             {
 1345  4
                 for ( String msg : entry.getValue() )
 1346  
                 {
 1347  4
                     getLog().warn( msg );
 1348  
                 }
 1349  
             }
 1350  
 
 1351  4
             this.warnMessages = null;
 1352  
         }
 1353  
 
 1354  96
         init();
 1355  96
     }
 1356  
 
 1357  
     /**
 1358  
      * {@inheritDoc}
 1359  
      *
 1360  
      * Unkown events just log a warning message but are ignored otherwise.
 1361  
      * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
 1362  
      */
 1363  
     public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
 1364  
     {
 1365  0
         String msg = "Unknown Sink event: '" + name + "', ignoring!";
 1366  0
         logMessage( "unknownEvent", msg );
 1367  0
     }
 1368  
 
 1369  
     /** {@inheritDoc} */
 1370  
     public void comment( String comment )
 1371  
     {
 1372  
 
 1373  10
         if ( StringUtils.isNotEmpty( comment ) && comment.indexOf( "--" ) != -1 )
 1374  
         {
 1375  2
             String originalComment = comment;
 1376  
             // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments
 1377  6
             while ( comment.indexOf( "--" ) != -1 )
 1378  
             {
 1379  4
                 comment = StringUtils.replace( comment, "--", "- -" );
 1380  
             }
 1381  
 
 1382  2
             String msg = "Modified invalid comment: '" + originalComment + "' to '" + comment + "'";
 1383  2
             logMessage( "modifyComment", msg );
 1384  
         }
 1385  
 
 1386  10
         StringBuffer buf = new StringBuffer( comment.length() + 9 );
 1387  
 
 1388  10
         buf.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS ).append( SPACE );
 1389  10
         buf.append( comment );
 1390  10
         buf.append( SPACE ).append( MINUS ).append( MINUS ).append( GREATER_THAN );
 1391  
 
 1392  10
         write( buf.toString() );
 1393  10
     }
 1394  
 
 1395  
     /**
 1396  
      * Writes the beginning of a FO document.
 1397  
      */
 1398  
     public void beginDocument()
 1399  
     {
 1400  8
         write( "<?xml version=\"1.0\"" );
 1401  8
         if ( encoding != null )
 1402  
         {
 1403  8
             write( " encoding=\"" + encoding + "\"" );
 1404  
         }
 1405  8
         write( "?>" );
 1406  8
         writeEOL();
 1407  
 
 1408  8
         MutableAttributeSet atts = new SinkEventAttributeSet();
 1409  8
         atts.addAttribute( "xmlns:" + getNameSpace(), FO_NAMESPACE );
 1410  
 
 1411  8
         if ( languageId != null )
 1412  
         {
 1413  0
             atts.addAttribute( "language", languageId );
 1414  
         }
 1415  
 
 1416  8
         writeStartTag( ROOT_TAG, atts );
 1417  
 
 1418  8
         writeStartTag( LAYOUT_MASTER_SET_TAG );
 1419  
 
 1420  8
         writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.cover-page" );
 1421  8
         writeEmptyTag( REGION_BODY_TAG, "layout.master.set.cover-page.region-body" );
 1422  8
         writeEndTag( SIMPLE_PAGE_MASTER_TAG );
 1423  8
         writeEOL();
 1424  
 
 1425  8
         writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.toc" );
 1426  8
         writeEmptyTag( REGION_BODY_TAG, "layout.master.set.toc.region-body" );
 1427  8
         writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.toc.region-before" );
 1428  8
         writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.toc.region-after" );
 1429  8
         writeEndTag( SIMPLE_PAGE_MASTER_TAG );
 1430  8
         writeEOL();
 1431  
 
 1432  8
         writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.body" );
 1433  8
         writeEmptyTag( REGION_BODY_TAG, "layout.master.set.body.region-body" );
 1434  8
         writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.body.region-before" );
 1435  8
         writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.body.region-after" );
 1436  8
         writeEndTag( SIMPLE_PAGE_MASTER_TAG );
 1437  8
         writeEOL();
 1438  
 
 1439  8
         writeEndTag( LAYOUT_MASTER_SET_TAG );
 1440  8
         writeEOL();
 1441  
 
 1442  8
         pdfBookmarks();
 1443  8
     }
 1444  
 
 1445  
     /**
 1446  
      * Writes the end of a FO document, flushes and closes the stream.
 1447  
      */
 1448  
     public void endDocument()
 1449  
     {
 1450  18
         writeEndTag( ROOT_TAG );
 1451  18
         writeEOL();
 1452  
 
 1453  18
         flush();
 1454  18
         close();
 1455  18
     }
 1456  
 
 1457  
     // ----------------------------------------------------------------------
 1458  
     //
 1459  
     // ----------------------------------------------------------------------
 1460  
 
 1461  
     /**
 1462  
      * Returns the configuration object of this sink.
 1463  
      *
 1464  
      * @return The configuration object of this sink.
 1465  
      */
 1466  
     protected FoConfiguration getFoConfiguration()
 1467  
     {
 1468  50
         return config;
 1469  
     }
 1470  
 
 1471  
     /**
 1472  
      * Writes a start tag, prepending EOL.
 1473  
      *
 1474  
      * @param tag The tag.
 1475  
      * @param attributeId An id identifying the attribute set.
 1476  
      */
 1477  
     protected void writeStartTag( Tag tag, String attributeId )
 1478  
     {
 1479  842
         writeEOL();
 1480  842
         writeStartTag( tag, config.getAttributeSet( attributeId ) );
 1481  842
     }
 1482  
 
 1483  
     /**
 1484  
      * Writes a start tag, prepending EOL.
 1485  
      *
 1486  
      * @param tag The tag.
 1487  
      * @param id An id to add.
 1488  
      * @param name The name (value) of the id.
 1489  
      */
 1490  
     protected void writeStartTag( Tag tag, String id, String name )
 1491  
     {
 1492  164
         writeEOL();
 1493  164
         MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} );
 1494  
 
 1495  164
         writeStartTag( tag, att );
 1496  164
     }
 1497  
 
 1498  
     /**
 1499  
      * Writes a start tag, prepending EOL.
 1500  
      *
 1501  
      * @param tag The tag.
 1502  
      * @param id An id to add.
 1503  
      * @param name The name (value) of the id.
 1504  
      * @param attributeId An id identifying the attribute set.
 1505  
      */
 1506  
     protected void writeStartTag( Tag tag, String id, String name, String attributeId )
 1507  
     {
 1508  10
         MutableAttributeSet att = config.getAttributeSet( attributeId );
 1509  
 
 1510  
         // make sure we don't add it twice
 1511  10
         if ( att.isDefined( id ) )
 1512  
         {
 1513  0
             att.removeAttribute( id );
 1514  
         }
 1515  
 
 1516  10
         att.addAttribute( id, name );
 1517  
 
 1518  10
         writeEOL();
 1519  10
         writeStartTag( tag, att );
 1520  10
     }
 1521  
 
 1522  
     /**
 1523  
      * Writes an empty tag, prepending EOL.
 1524  
      *
 1525  
      * @param tag The tag.
 1526  
      * @param id An id to add.
 1527  
      * @param name The name (value) of the id.
 1528  
      */
 1529  
     protected void writeEmptyTag( Tag tag, String id, String name )
 1530  
     {
 1531  68
         MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} );
 1532  
 
 1533  68
         writeEOL();
 1534  68
         writeSimpleTag( tag, att );
 1535  68
     }
 1536  
 
 1537  
     /**
 1538  
      * Writes a simple tag, appending EOL.
 1539  
      *
 1540  
      * @param tag The tag name.
 1541  
      * @param attributeId An id identifying the attribute set.
 1542  
      */
 1543  
     protected void writeEmptyTag( Tag tag, String attributeId )
 1544  
     {
 1545  104
         writeEOL();
 1546  104
         writeSimpleTag( tag, config.getAttributeSet( attributeId ) );
 1547  104
     }
 1548  
 
 1549  
     /**
 1550  
      * {@inheritDoc}
 1551  
      *
 1552  
      * Writes a text, swallowing any exceptions.
 1553  
      */
 1554  
     protected void write( String text )
 1555  
     {
 1556  8932
         if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null )
 1557  
         {
 1558  18
             this.tableCaptionXMLWriterStack.getLast().writeText( unifyEOLs( text ) );
 1559  
         }
 1560  8914
         else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null )
 1561  
         {
 1562  1894
             this.tableContentWriterStack.getLast().write( unifyEOLs( text ) );
 1563  
         }
 1564  
         else
 1565  
         {
 1566  7020
             out.write( unifyEOLs( text ) );
 1567  
         }
 1568  8932
     }
 1569  
 
 1570  
     /**
 1571  
      * Writes a text, appending EOL.
 1572  
      *
 1573  
      * @param text The text to write.
 1574  
      */
 1575  
     protected void writeln( String text )
 1576  
     {
 1577  140
         write( text );
 1578  140
         writeEOL();
 1579  140
     }
 1580  
 
 1581  
     /**
 1582  
      * Writes content, escaping special characters.
 1583  
      *
 1584  
      * @param text The text to write.
 1585  
      */
 1586  
     protected void content( String text )
 1587  
     {
 1588  848
         write( escaped( text, verbatim ) );
 1589  848
     }
 1590  
 
 1591  
     /**
 1592  
      * Escapes special characters so that the text can be included in a fo file.
 1593  
      *
 1594  
      * @param text The text to process.
 1595  
      * @param verb In verbatim mode, white space and newlines are escaped.
 1596  
      * @return The text with special characters escaped.
 1597  
      */
 1598  
     public static String escaped( String text, boolean verb )
 1599  
     {
 1600  850
         int length = text.length();
 1601  850
         StringBuffer buffer = new StringBuffer( length );
 1602  
 
 1603  10608
         for ( int i = 0; i < length; ++i )
 1604  
         {
 1605  9758
             char c = text.charAt( i );
 1606  9758
             switch ( c )
 1607  
             {
 1608  
                 case ' ':
 1609  1208
                     if ( verb )
 1610  
                     {
 1611  336
                         buffer.append( "&#160;" );
 1612  
                     }
 1613  
                     else
 1614  
                     {
 1615  872
                         buffer.append( c );
 1616  
                     }
 1617  872
                     break;
 1618  
                 case '<':
 1619  12
                     buffer.append( "&lt;" );
 1620  12
                     break;
 1621  
                 case '>':
 1622  12
                     buffer.append( "&gt;" );
 1623  12
                     break;
 1624  
                 case '&':
 1625  0
                     buffer.append( "&amp;" );
 1626  0
                     break;
 1627  
                 case '\n':
 1628  80
                     buffer.append( EOL );
 1629  80
                     if ( verb )
 1630  
                     {
 1631  8
                         buffer.append( "<fo:block/>" + EOL );
 1632  
                     }
 1633  
                     break;
 1634  
                 default:
 1635  8446
                     if ( needsSymbolFont( c ) )
 1636  
                     {
 1637  
                         // TODO: make font configurable?
 1638  0
                         buffer.append( "<fo:inline font-family=\"Symbol\">" ).append( c ).append( "</fo:inline>" );
 1639  
                     }
 1640  
                     else
 1641  
                     {
 1642  8446
                         buffer.append( c );
 1643  
                     }
 1644  
             }
 1645  
         }
 1646  
 
 1647  850
         return buffer.toString();
 1648  
     }
 1649  
 
 1650  
     /** {@inheritDoc} */
 1651  
     protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag )
 1652  
     {
 1653  2060
         if ( this.tableCaptionXMLWriterStack.isEmpty() )
 1654  
         {
 1655  2060
             super.writeStartTag ( t, att, isSimpleTag );
 1656  
         }
 1657  
         else
 1658  
         {
 1659  0
             String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString();
 1660  0
             this.tableCaptionXMLWriterStack.getLast().startElement( tag );
 1661  
 
 1662  0
             if ( att != null )
 1663  
             {
 1664  0
                 Enumeration<?> names = att.getAttributeNames();
 1665  0
                 while ( names.hasMoreElements() )
 1666  
                 {
 1667  0
                     Object key = names.nextElement();
 1668  0
                     Object value = att.getAttribute( key );
 1669  
 
 1670  0
                     this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() );
 1671  0
                 }
 1672  
             }
 1673  
 
 1674  0
             if ( isSimpleTag )
 1675  
             {
 1676  0
                 this.tableCaptionXMLWriterStack.getLast().endElement();
 1677  
             }
 1678  
         }
 1679  2060
     }
 1680  
 
 1681  
     /** {@inheritDoc} */
 1682  
     protected void writeEndTag( Tag t )
 1683  
     {
 1684  1842
         if ( this.tableCaptionXMLWriterStack.isEmpty() )
 1685  
         {
 1686  1842
             super.writeEndTag( t );
 1687  
         }
 1688  
         else
 1689  
         {
 1690  0
             this.tableCaptionXMLWriterStack.getLast().endElement();
 1691  
         }
 1692  1842
     }
 1693  
 
 1694  
     private static final char UPPER_ALPHA = 0x391;
 1695  
     private static final char PIV = 0x3d6;
 1696  
     private static final char OLINE = 0x203e;
 1697  
     private static final char DIAMS = 0x2666;
 1698  
     private static final char EURO = 0x20ac;
 1699  
     private static final char TRADE = 0x2122;
 1700  
     private static final char PRIME = 0x2032;
 1701  
     private static final char PPRIME = 0x2033;
 1702  
 
 1703  
     private static boolean needsSymbolFont( char c )
 1704  
     {
 1705  
         // greek characters and mathematical symbols, except the euro and trade symbols
 1706  
         // symbols I couldn't get to display in any font:
 1707  
         // zwnj (0x200C), zwj (0x200D), lrm (0x200E), rlm (0x200F), oline (0x203E),
 1708  
         // lceil (0x2038), rceil (0x2039), lfloor (0x203A), rfloor (0x203B)
 1709  8446
         return ( c >= UPPER_ALPHA && c <= PIV )
 1710  
                 || ( c == PRIME || c == PPRIME )
 1711  
                 || ( c >= OLINE && c <= DIAMS && c != EURO && c != TRADE );
 1712  
     }
 1713  
 
 1714  
     /**
 1715  
      * Starts a page sequence.
 1716  
      *
 1717  
      * @param initPageNumber The initial page number. Should be either "0" (for the first page) or "auto".
 1718  
      * @param headerText The text to write in the header, if null, nothing is written.
 1719  
      * @param footerText The text to write in the footer, if null, nothing is written.
 1720  
      */
 1721  
     protected void startPageSequence( String initPageNumber, String headerText, String footerText )
 1722  
     {
 1723  20
         writeln( "<fo:page-sequence initial-page-number=\"" + initPageNumber + "\" master-reference=\"body\">" );
 1724  20
         regionBefore( headerText );
 1725  20
         regionAfter( footerText );
 1726  20
         writeln( "<fo:flow flow-name=\"xsl-region-body\">" );
 1727  20
         chapterHeading( null, true );
 1728  20
     }
 1729  
 
 1730  
     /**
 1731  
      * Writes a 'xsl-region-before' block.
 1732  
      *
 1733  
      * @param headerText The text to write in the header, if null, nothing is written.
 1734  
      */
 1735  
     protected void regionBefore( String headerText )
 1736  
     {
 1737  
         // do nothing, overridden by AggregateSink
 1738  8
     }
 1739  
 
 1740  
     /**
 1741  
      * Writes a 'xsl-region-after' block. By default does nothing, gets overridden by AggregateSink.
 1742  
      *
 1743  
      * @param footerText The text to write in the footer, if null, nothing is written.
 1744  
      */
 1745  
     protected void regionAfter( String footerText )
 1746  
     {
 1747  
         // do nothing, overridden by AggregateSink
 1748  8
     }
 1749  
 
 1750  
     /**
 1751  
      * Writes a chapter heading. By default does nothing, gets overridden by AggregateSink.
 1752  
      *
 1753  
      * @param headerText The text to write in the header, if null, the current document title is written.
 1754  
      * @param chapterNumber True if the chapter number should be written in front of the text.
 1755  
      */
 1756  
     protected void chapterHeading( String headerText, boolean chapterNumber )
 1757  
     {
 1758  
         // do nothing, overridden by AggregateSink
 1759  8
     }
 1760  
 
 1761  
     /**
 1762  
      * Writes a fo:bookmark-tree. By default does nothing, gets overridden by AggregateSink.
 1763  
      */
 1764  
     protected void pdfBookmarks()
 1765  
     {
 1766  
         // do nothing, overridden by AggregateSink
 1767  6
     }
 1768  
 
 1769  
     /**
 1770  
      * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
 1771  
      *
 1772  
      * @param key not null
 1773  
      * @param msg not null
 1774  
      * @see #close()
 1775  
      * @since 1.1.1
 1776  
      */
 1777  
     protected void logMessage( String key, String msg )
 1778  
     {
 1779  4
         msg = "[FO Sink] " + msg;
 1780  4
         if ( getLog().isDebugEnabled() )
 1781  
         {
 1782  0
             getLog().debug( msg );
 1783  
 
 1784  0
             return;
 1785  
         }
 1786  
 
 1787  4
         if ( warnMessages == null )
 1788  
         {
 1789  4
             warnMessages = new HashMap<String, Set<String>>();
 1790  
         }
 1791  
 
 1792  4
         Set<String> set = warnMessages.get( key );
 1793  4
         if ( set == null )
 1794  
         {
 1795  4
             set = new TreeSet<String>();
 1796  
         }
 1797  4
         set.add( msg );
 1798  4
         warnMessages.put( key, set );
 1799  4
     }
 1800  
 
 1801  
     /** {@inheritDoc} */
 1802  
     protected void init()
 1803  
     {
 1804  108
         super.init();
 1805  
 
 1806  108
         this.listStack.clear();
 1807  108
         this.tableGridStack.clear();
 1808  108
         this.cellJustifStack.clear();
 1809  108
         this.isCellJustifStack.clear();
 1810  108
         this.cellCountStack.clear();
 1811  108
         this.tableContentWriterStack.clear();
 1812  108
         this.tableCaptionWriterStack.clear();
 1813  108
         this.tableCaptionXMLWriterStack.clear();
 1814  108
         this.tableCaptionStack.clear();
 1815  
 
 1816  108
         this.section = 0;
 1817  108
         this.subsection = 0;
 1818  108
         this.subsubsection = 0;
 1819  108
         this.verbatim = false;
 1820  108
         this.inFigure = false;
 1821  108
         this.warnMessages = null;
 1822  108
     }
 1823  
 }