001package org.apache.maven.doxia.module.fo; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.File; 023import java.io.IOException; 024import java.io.PrintWriter; 025import java.io.StringWriter; 026import java.io.Writer; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.LinkedList; 030import java.util.Map; 031import java.util.Set; 032import java.util.Stack; 033import java.util.TreeSet; 034 035import javax.swing.text.MutableAttributeSet; 036import javax.swing.text.html.HTML.Attribute; 037import javax.swing.text.html.HTML.Tag; 038 039import org.apache.maven.doxia.sink.AbstractXmlSink; 040import org.apache.maven.doxia.sink.SinkEventAttributeSet; 041import org.apache.maven.doxia.sink.SinkEventAttributes; 042import org.apache.maven.doxia.sink.SinkUtils; 043import org.apache.maven.doxia.util.DoxiaUtils; 044import org.apache.maven.doxia.util.HtmlTools; 045 046import org.codehaus.plexus.util.StringUtils; 047import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; 048 049/** 050 * A Doxia Sink that produces a FO model. The usage is similar to the following: 051 * 052 * <pre> 053 * FoSink sink = new FoSink( writer ); 054 * sink.beginDocument(); 055 * ... 056 * sink.endDocument(); 057 * </pre> 058 * 059 * @author ltheussl 060 * @version $Id$ 061 * @since 1.1 062 */ 063public class FoSink 064 extends AbstractXmlSink 065 implements FoMarkup 066{ 067 /** For writing the result. */ 068 private final PrintWriter out; 069 070 /** Used to get the current position in numbered lists. */ 071 private final Stack<NumberedListItem> listStack; 072 073 /** Used to get attributes for a given FO element. */ 074 private final FoConfiguration config; 075 076 /** Counts the current section level. */ 077 private int section = 0; 078 079 /** Counts the current subsection level. */ 080 private int subsection = 0; 081 082 /** Counts the current subsubsection level. */ 083 private int subsubsection = 0; 084 085 /** Verbatim flag. */ 086 private boolean verbatim; 087 088 /** figure flag. */ 089 private boolean inFigure; 090 091 private final String encoding; 092 093 private final String languageId; 094 095 /** Stack of drawing borders on table cells. */ 096 private final LinkedList<Boolean> tableGridStack; 097 098 /** Stack of alignment int[] of table cells. */ 099 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 this( writer, "UTF-8" ); 130 } 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 this( writer, encoding, null ); 142 } 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 { 156 if ( writer == null ) 157 { 158 throw new NullPointerException( "Null writer in FO Sink!" ); 159 } 160 161 this.out = new PrintWriter( writer ); 162 this.encoding = encoding; 163 this.languageId = languageId; 164 this.config = new FoConfiguration(); 165 166 this.listStack = new Stack<NumberedListItem>(); 167 this.tableGridStack = new LinkedList<Boolean>(); 168 this.cellJustifStack = new LinkedList<int[]>(); 169 this.isCellJustifStack = new LinkedList<Boolean>(); 170 this.cellCountStack = new LinkedList<Integer>(); 171 this.tableContentWriterStack = new LinkedList<StringWriter>(); 172 this.tableCaptionWriterStack = new LinkedList<StringWriter>(); 173 this.tableCaptionXMLWriterStack = new LinkedList<PrettyPrintXMLWriter>(); 174 this.tableCaptionStack = new LinkedList<String>(); 175 176 setNameSpace( "fo" ); 177 } 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 config.load( configFile ); 195 } 196 197 /** {@inheritDoc} */ 198 public void head( SinkEventAttributes attributes ) 199 { 200 init(); 201 202 startPageSequence( "0", null, null ); 203 } 204 205 /** {@inheritDoc} */ 206 public void head() 207 { 208 head( null ); 209 } 210 211 /** {@inheritDoc} */ 212 public void head_() 213 { 214 writeEOL(); 215 } 216 217 /** {@inheritDoc} */ 218 public void title( SinkEventAttributes attributes ) 219 { 220 writeStartTag( BLOCK_TAG, "doc.header.title" ); 221 } 222 223 /** {@inheritDoc} */ 224 public void title() 225 { 226 title( null ); 227 } 228 229 /** {@inheritDoc} */ 230 public void title_() 231 { 232 writeEndTag( BLOCK_TAG ); 233 writeEOL(); 234 } 235 236 /** {@inheritDoc} */ 237 public void author( SinkEventAttributes attributes ) 238 { 239 writeStartTag( BLOCK_TAG, "doc.header.author" ); 240 } 241 242 /** {@inheritDoc} */ 243 public void author() 244 { 245 author( null ); 246 } 247 248 /** {@inheritDoc} */ 249 public void author_() 250 { 251 writeEndTag( BLOCK_TAG ); 252 writeEOL(); 253 } 254 255 /** {@inheritDoc} */ 256 public void date( SinkEventAttributes attributes ) 257 { 258 writeStartTag( BLOCK_TAG, "doc.header.date" ); 259 } 260 261 /** {@inheritDoc} */ 262 public void date() 263 { 264 date( null ); 265 } 266 267 /** {@inheritDoc} */ 268 public void date_() 269 { 270 writeEndTag( BLOCK_TAG ); 271 writeEOL(); 272 } 273 274 /** {@inheritDoc} */ 275 public void body( SinkEventAttributes attributes ) 276 { 277 // noop 278 } 279 280 /** {@inheritDoc} */ 281 public void body() 282 { 283 body( null ); 284 } 285 286 /** {@inheritDoc} */ 287 public void body_() 288 { 289 writeEOL(); 290 writeEndTag( FLOW_TAG ); 291 writeEOL(); 292 writeEndTag( PAGE_SEQUENCE_TAG ); 293 writeEOL(); 294 endDocument(); 295 } 296 297 // ----------------------------------------------------------------------- 298 // 299 // ----------------------------------------------------------------------- 300 301 /** {@inheritDoc} */ 302 public void sectionTitle() 303 { 304 // nop 305 } 306 307 /** {@inheritDoc} */ 308 public void sectionTitle_() 309 { 310 // nop 311 } 312 313 /** {@inheritDoc} */ 314 public void section( int level, SinkEventAttributes attributes ) 315 { 316 if ( level == SECTION_LEVEL_1 ) 317 { 318 section++; 319 subsection = 0; 320 subsubsection = 0; 321 } 322 else if ( level == SECTION_LEVEL_2 ) 323 { 324 subsection++; 325 subsubsection = 0; 326 } 327 else if ( level == SECTION_LEVEL_3 ) 328 { 329 subsubsection++; 330 } 331 332 onSection(); 333 } 334 335 /** {@inheritDoc} */ 336 public void section_( int level ) 337 { 338 onSection_(); 339 } 340 341 /** {@inheritDoc} */ 342 public void sectionTitle( int level, SinkEventAttributes attributes ) 343 { 344 onSectionTitle( level ); 345 } 346 347 /** {@inheritDoc} */ 348 public void sectionTitle_( int level ) 349 { 350 onSectionTitle_(); 351 } 352 353 /** {@inheritDoc} */ 354 public void section1() 355 { 356 section( SECTION_LEVEL_1, null ); 357 } 358 359 /** {@inheritDoc} */ 360 public void sectionTitle1() 361 { 362 sectionTitle( SECTION_LEVEL_1, null ); 363 } 364 365 /** {@inheritDoc} */ 366 public void sectionTitle1_() 367 { 368 sectionTitle_( SECTION_LEVEL_1 ); 369 } 370 371 /** {@inheritDoc} */ 372 public void section1_() 373 { 374 section_( SECTION_LEVEL_1 ); 375 } 376 377 /** {@inheritDoc} */ 378 public void section2() 379 { 380 section( SECTION_LEVEL_2, null ); 381 } 382 383 /** {@inheritDoc} */ 384 public void sectionTitle2() 385 { 386 sectionTitle( SECTION_LEVEL_2, null ); 387 } 388 389 /** {@inheritDoc} */ 390 public void sectionTitle2_() 391 { 392 sectionTitle_( SECTION_LEVEL_2 ); 393 } 394 395 /** {@inheritDoc} */ 396 public void section2_() 397 { 398 section_( SECTION_LEVEL_2 ); 399 } 400 401 /** {@inheritDoc} */ 402 public void section3() 403 { 404 section( SECTION_LEVEL_3, null ); 405 } 406 407 /** {@inheritDoc} */ 408 public void sectionTitle3() 409 { 410 sectionTitle( SECTION_LEVEL_3, null ); 411 } 412 413 /** {@inheritDoc} */ 414 public void sectionTitle3_() 415 { 416 sectionTitle_( SECTION_LEVEL_3 ); 417 } 418 419 /** {@inheritDoc} */ 420 public void section3_() 421 { 422 section_( SECTION_LEVEL_3 ); 423 } 424 425 /** {@inheritDoc} */ 426 public void section4() 427 { 428 section( SECTION_LEVEL_4, null ); 429 } 430 431 /** {@inheritDoc} */ 432 public void sectionTitle4() 433 { 434 sectionTitle( SECTION_LEVEL_4, null ); 435 } 436 437 /** {@inheritDoc} */ 438 public void sectionTitle4_() 439 { 440 sectionTitle_( SECTION_LEVEL_4 ); 441 } 442 443 /** {@inheritDoc} */ 444 public void section4_() 445 { 446 section_( SECTION_LEVEL_4 ); 447 } 448 449 /** {@inheritDoc} */ 450 public void section5() 451 { 452 section( SECTION_LEVEL_5, null ); 453 } 454 455 /** {@inheritDoc} */ 456 public void sectionTitle5() 457 { 458 sectionTitle( SECTION_LEVEL_5, null ); 459 } 460 461 /** {@inheritDoc} */ 462 public void sectionTitle5_() 463 { 464 sectionTitle_( SECTION_LEVEL_5 ); 465 } 466 467 /** {@inheritDoc} */ 468 public void section5_() 469 { 470 section_( SECTION_LEVEL_5 ); 471 } 472 473 /** Starts a section/subsection. */ 474 private void onSection() 475 { 476 writeEOL(); 477 writeStartTag( BLOCK_TAG, "body.text" ); 478 } 479 480 /** 481 * Starts a section title. 482 * 483 * @param depth The section level. 484 */ 485 private void onSectionTitle( int depth ) 486 { 487 StringBuilder title = new StringBuilder( 16 ); 488 489 title.append( getChapterString() ); 490 491 writeEOL(); 492 if ( depth == SECTION_LEVEL_1 ) 493 { 494 writeStartTag( BLOCK_TAG, "body.h1" ); 495 title.append( section ).append( " " ); 496 } 497 else if ( depth == SECTION_LEVEL_2 ) 498 { 499 writeStartTag( BLOCK_TAG, "body.h2" ); 500 title.append( section ).append( "." ); 501 title.append( subsection ).append( " " ); 502 } 503 else if ( depth == SECTION_LEVEL_3 ) 504 { 505 writeStartTag( BLOCK_TAG, "body.h3" ); 506 title.append( section ).append( "." ); 507 title.append( subsection ).append( "." ); 508 title.append( subsubsection ).append( " " ); 509 } 510 else if ( depth == SECTION_LEVEL_4 ) 511 { 512 writeStartTag( BLOCK_TAG, "body.h4" ); 513 } 514 else 515 { 516 writeStartTag( BLOCK_TAG, "body.h5" ); 517 } 518 519 write( title.toString() ); 520 } 521 522 /** Ends a section title. */ 523 private void onSectionTitle_() 524 { 525 writeEndTag( BLOCK_TAG ); 526 writeEOL(); 527 } 528 529 /** Ends a section/subsection. */ 530 private void onSection_() 531 { 532 writeEndTag( BLOCK_TAG ); 533 writeEOL(); 534 } 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 this.section = 0; 543 } 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 return ""; 554 } 555 556 // ----------------------------------------------------------------------- 557 // 558 // ----------------------------------------------------------------------- 559 560 /** {@inheritDoc} */ 561 public void list( SinkEventAttributes attributes ) 562 { 563 writeEOL(); 564 writeStartTag( LIST_BLOCK_TAG, "list" ); 565 } 566 567 /** {@inheritDoc} */ 568 public void list() 569 { 570 list( null ); 571 } 572 573 /** {@inheritDoc} */ 574 public void list_() 575 { 576 writeEndTag( LIST_BLOCK_TAG ); 577 writeEOL(); 578 } 579 580 /** {@inheritDoc} */ 581 public void listItem( SinkEventAttributes attributes ) 582 { 583 writeStartTag( LIST_ITEM_TAG, "list.item" ); 584 writeStartTag( LIST_ITEM_LABEL_TAG ); 585 writeStartTag( BLOCK_TAG ); 586 write( "•" ); // TODO customize? 587 writeEndTag( BLOCK_TAG ); 588 writeEndTag( LIST_ITEM_LABEL_TAG ); 589 writeEOL(); 590 writeStartTag( LIST_ITEM_BODY_TAG, "list.item" ); 591 writeEOL(); 592 writeStartTag( BLOCK_TAG ); 593 } 594 595 /** {@inheritDoc} */ 596 public void listItem() 597 { 598 listItem( null ); 599 } 600 601 /** {@inheritDoc} */ 602 public void listItem_() 603 { 604 writeEndTag( BLOCK_TAG ); 605 writeEOL(); 606 writeEndTag( LIST_ITEM_BODY_TAG ); 607 writeEOL(); 608 writeEndTag( LIST_ITEM_TAG ); 609 writeEOL(); 610 } 611 612 /** {@inheritDoc} */ 613 public void numberedList( int numbering, SinkEventAttributes attributes ) 614 { 615 this.listStack.push( new NumberedListItem( numbering ) ); 616 writeEOL(); 617 writeStartTag( LIST_BLOCK_TAG, "list" ); 618 } 619 620 /** {@inheritDoc} */ 621 public void numberedList( int numbering ) 622 { 623 numberedList( numbering, null ); 624 } 625 626 /** {@inheritDoc} */ 627 public void numberedList_() 628 { 629 this.listStack.pop(); 630 writeEndTag( LIST_BLOCK_TAG ); 631 writeEOL(); 632 } 633 634 /** {@inheritDoc} */ 635 public void numberedListItem( SinkEventAttributes attributes ) 636 { 637 NumberedListItem current = this.listStack.peek(); 638 current.next(); 639 640 writeStartTag( LIST_ITEM_TAG, "list.item" ); 641 642 writeEOL(); 643 writeStartTag( LIST_ITEM_LABEL_TAG ); 644 writeEOL(); 645 writeStartTag( BLOCK_TAG ); 646 write( current.getListItemSymbol() ); 647 writeEndTag( BLOCK_TAG ); 648 writeEOL(); 649 writeEndTag( LIST_ITEM_LABEL_TAG ); 650 writeEOL(); 651 652 writeStartTag( LIST_ITEM_BODY_TAG, "list.item" ); 653 writeEOL(); 654 writeStartTag( BLOCK_TAG ); 655 } 656 657 /** {@inheritDoc} */ 658 public void numberedListItem() 659 { 660 numberedListItem( null ); 661 } 662 663 /** {@inheritDoc} */ 664 public void numberedListItem_() 665 { 666 writeEndTag( BLOCK_TAG ); 667 writeEOL(); 668 writeEndTag( LIST_ITEM_BODY_TAG ); 669 writeEOL(); 670 writeEndTag( LIST_ITEM_TAG ); 671 writeEOL(); 672 } 673 674 /** {@inheritDoc} */ 675 public void definitionList( SinkEventAttributes attributes ) 676 { 677 writeEOL(); 678 writeStartTag( BLOCK_TAG, "dl" ); 679 } 680 681 /** {@inheritDoc} */ 682 public void definitionList() 683 { 684 definitionList( null ); 685 } 686 687 /** {@inheritDoc} */ 688 public void definitionList_() 689 { 690 writeEndTag( BLOCK_TAG ); 691 writeEOL(); 692 } 693 694 /** {@inheritDoc} */ 695 public void definitionListItem( SinkEventAttributes attributes ) 696 { 697 // nop 698 } 699 700 /** {@inheritDoc} */ 701 public void definitionListItem() 702 { 703 definitionListItem( null ); 704 } 705 706 /** {@inheritDoc} */ 707 public void definitionListItem_() 708 { 709 // nop 710 } 711 712 /** {@inheritDoc} */ 713 public void definedTerm( SinkEventAttributes attributes ) 714 { 715 writeStartTag( BLOCK_TAG, "dt" ); 716 } 717 718 /** {@inheritDoc} */ 719 public void definedTerm() 720 { 721 definedTerm( null ); 722 } 723 724 /** {@inheritDoc} */ 725 public void definedTerm_() 726 { 727 writeEndTag( BLOCK_TAG ); 728 writeEOL(); 729 } 730 731 /** {@inheritDoc} */ 732 public void definition( SinkEventAttributes attributes ) 733 { 734 writeEOL(); 735 writeStartTag( BLOCK_TAG, "dd" ); 736 } 737 738 /** {@inheritDoc} */ 739 public void definition() 740 { 741 definition( null ); 742 } 743 744 /** {@inheritDoc} */ 745 public void definition_() 746 { 747 writeEndTag( BLOCK_TAG ); 748 writeEOL(); 749 } 750 751 /** {@inheritDoc} */ 752 public void figure( SinkEventAttributes attributes ) 753 { 754 this.inFigure = true; 755 writeEOL(); 756 writeStartTag( BLOCK_TAG, "figure.display" ); 757 } 758 759 /** {@inheritDoc} */ 760 public void figure() 761 { 762 figure( null ); 763 } 764 765 /** {@inheritDoc} */ 766 public void figure_() 767 { 768 this.inFigure = false; 769 writeEndTag( BLOCK_TAG ); 770 writeEOL(); 771 } 772 773 /** {@inheritDoc} */ 774 public void figureGraphics( String name ) 775 { 776 figureGraphics( name, null ); 777 } 778 779 /** {@inheritDoc} */ 780 public void figureGraphics( String src, SinkEventAttributes attributes ) 781 { 782 MutableAttributeSet atts = config.getAttributeSet( "figure.graphics" ); 783 atts.addAttribute( Attribute.SRC.toString(), src ); 784 785 // http://xmlgraphics.apache.org/fop/graphics.html#resolution 786 787 final String[] valids = new String[] {"content-height", "content-width", "height", "width"}; 788 final MutableAttributeSet filtered = SinkUtils.filterAttributes( attributes, valids ); 789 790 if ( filtered != null ) 791 { 792 atts.addAttributes( filtered ); 793 } 794 795 writeln( "<fo:external-graphic" + SinkUtils.getAttributeString( atts ) + "/>" ); 796 } 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 return this.inFigure; 806 } 807 808 /** {@inheritDoc} */ 809 public void figureCaption( SinkEventAttributes attributes ) 810 { 811 writeStartTag( BLOCK_TAG, "figure.caption" ); 812 } 813 814 /** {@inheritDoc} */ 815 public void figureCaption() 816 { 817 figureCaption( null ); 818 } 819 820 /** {@inheritDoc} */ 821 public void figureCaption_() 822 { 823 writeEndTag( BLOCK_TAG ); 824 writeEOL(); 825 } 826 827 /** {@inheritDoc} */ 828 public void paragraph() 829 { 830 paragraph( null ); 831 } 832 833 /** {@inheritDoc} */ 834 public void paragraph( SinkEventAttributes attributes ) 835 { 836 MutableAttributeSet atts = config.getAttributeSet( "normal.paragraph" ); 837 838 if ( attributes != null && attributes.isDefined( SinkEventAttributes.ALIGN ) ) 839 { 840 atts.addAttribute( "text-align", attributes.getAttribute( SinkEventAttributes.ALIGN ) ); 841 } 842 843 writeEOL(); 844 writeStartTag( BLOCK_TAG, atts ); 845 } 846 847 /** {@inheritDoc} */ 848 public void paragraph_() 849 { 850 writeEndTag( BLOCK_TAG ); 851 writeEOL(); 852 } 853 854 /** {@inheritDoc} */ 855 public void verbatim( SinkEventAttributes attributes ) 856 { 857 this.verbatim = true; 858 859 boolean boxed = false; 860 861 if ( attributes != null && attributes.isDefined( SinkEventAttributes.DECORATION ) ) 862 { 863 boxed = 864 "boxed".equals( attributes.getAttribute( SinkEventAttributes.DECORATION ).toString() ); 865 } 866 867 if ( boxed ) 868 { 869 writeStartTag( BLOCK_TAG, "body.source" ); 870 } 871 else 872 { 873 writeStartTag( BLOCK_TAG, "body.pre" ); 874 } 875 } 876 877 /** {@inheritDoc} */ 878 public void verbatim( boolean boxed ) 879 { 880 verbatim( boxed ? SinkEventAttributeSet.BOXED : null ); 881 } 882 883 /** {@inheritDoc} */ 884 public void verbatim_() 885 { 886 this.verbatim = false; 887 writeEndTag( BLOCK_TAG ); 888 writeEOL(); 889 } 890 891 /** {@inheritDoc} */ 892 public void horizontalRule( SinkEventAttributes attributes ) 893 { 894 writeEOL(); 895 writeEOL(); 896 writeStartTag( BLOCK_TAG ); 897 writeEmptyTag( LEADER_TAG, "body.rule" ); 898 writeEndTag( BLOCK_TAG ); 899 writeEOL(); 900 } 901 902 /** {@inheritDoc} */ 903 public void horizontalRule() 904 { 905 horizontalRule( null ); 906 } 907 908 /** {@inheritDoc} */ 909 public void pageBreak() 910 { 911 writeEmptyTag( BLOCK_TAG, "break-before", "page" ); 912 writeEOL(); 913 } 914 915 /** {@inheritDoc} */ 916 public void table( SinkEventAttributes attributes ) 917 { 918 writeEOL(); 919 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 this.tableContentWriterStack.addLast( new StringWriter() ); 925 writeStartTag( TABLE_TAG, "table.layout" ); 926 } 927 928 /** {@inheritDoc} */ 929 public void table() 930 { 931 table( null ); 932 } 933 934 /** {@inheritDoc} */ 935 public void table_() 936 { 937 String content = this.tableContentWriterStack.removeLast().toString(); 938 939 StringBuilder sb = new StringBuilder(); 940 int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() ); 941 for ( int i = 0; i < cellCount; i++ ) 942 { 943 sb.append( "<fo:table-column column-width=\"proportional-column-width(1)\"/>" ); 944 sb.append( EOL ); 945 } 946 947 int index = content.indexOf( ">" ) + 1; 948 writeln( content.substring( 0, index ) ); 949 write( sb.toString() ); 950 write( content.substring( index ) ); 951 952 writeEndTag( TABLE_TAG ); 953 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 writeEndTag( BLOCK_TAG ); 959 writeEOL(); 960 961 if ( !this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null ) 962 { 963 paragraph( SinkEventAttributeSet.CENTER ); 964 write( this.tableCaptionStack.removeLast().toString() ); 965 paragraph_(); 966 } 967 } 968 969 /** {@inheritDoc} */ 970 public void tableRows( int[] justification, boolean grid ) 971 { 972 this.tableGridStack.addLast( Boolean.valueOf( grid ) ); 973 this.cellJustifStack.addLast( justification ); 974 this.isCellJustifStack.addLast( Boolean.valueOf( true ) ); 975 this.cellCountStack.addLast( Integer.valueOf( 0 ) ); 976 writeEOL(); 977 writeStartTag( TABLE_BODY_TAG ); 978 } 979 980 /** {@inheritDoc} */ 981 public void tableRows_() 982 { 983 this.tableGridStack.removeLast(); 984 this.cellJustifStack.removeLast(); 985 this.isCellJustifStack.removeLast(); 986 writeEndTag( TABLE_BODY_TAG ); 987 writeEOL(); 988 } 989 990 /** {@inheritDoc} */ 991 public void tableRow( SinkEventAttributes attributes ) 992 { 993 // TODO spacer rows 994 writeStartTag( TABLE_ROW_TAG, "table.body.row" ); 995 this.cellCountStack.removeLast(); 996 this.cellCountStack.addLast( Integer.valueOf( 0 ) ); 997 } 998 999 /** {@inheritDoc} */ 1000 public void tableRow() 1001 { 1002 tableRow( null ); 1003 } 1004 1005 /** {@inheritDoc} */ 1006 public void tableRow_() 1007 { 1008 writeEndTag( TABLE_ROW_TAG ); 1009 writeEOL(); 1010 } 1011 1012 /** {@inheritDoc} */ 1013 public void tableCell( SinkEventAttributes attributes ) 1014 { 1015 tableCell( false, attributes ); 1016 } 1017 1018 /** {@inheritDoc} */ 1019 public void tableCell() 1020 { 1021 tableCell( (SinkEventAttributes) null ); 1022 } 1023 1024 /** {@inheritDoc} */ 1025 public void tableCell( String width ) 1026 { 1027 // TODO: fop can't handle cell width 1028 tableCell(); 1029 } 1030 1031 /** {@inheritDoc} */ 1032 public void tableHeaderCell( SinkEventAttributes attributes ) 1033 { 1034 tableCell( true, attributes ); 1035 } 1036 1037 /** {@inheritDoc} */ 1038 public void tableHeaderCell() 1039 { 1040 tableHeaderCell( (SinkEventAttributes) null ); 1041 } 1042 1043 /** {@inheritDoc} */ 1044 public void tableHeaderCell( String width ) 1045 { 1046 // TODO: fop can't handle cell width 1047 tableHeaderCell(); 1048 } 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 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 int cellCount = Integer.parseInt( this.cellCountStack.getLast().toString() ); 1064 cellAtts.addAttribute( "column-number", String.valueOf( cellCount + 1 ) ); 1065 1066 if ( this.tableGridStack.getLast().equals( Boolean.TRUE ) ) 1067 { 1068 cellAtts.addAttributes( config.getAttributeSet( "table.body.cell.grid" ) ); 1069 } 1070 1071 MutableAttributeSet blockAtts = headerRow 1072 ? config.getAttributeSet( "table.heading.block" ) 1073 : config.getAttributeSet( "table.body.block" ); 1074 1075 String justif = null; 1076 if ( attributes == null ) 1077 { 1078 attributes = new SinkEventAttributeSet( 0 ); 1079 } 1080 1081 if ( attributes.isDefined( Attribute.ALIGN.toString() ) ) 1082 { 1083 justif = attributes.getAttribute( Attribute.ALIGN.toString() ).toString(); 1084 } 1085 1086 int[] cellJustif = this.cellJustifStack.getLast(); 1087 if ( justif == null && cellJustif != null && cellJustif.length > 0 1088 && this.isCellJustifStack.getLast().equals( Boolean.TRUE ) ) 1089 { 1090 switch ( cellJustif[Math.min( cellCount, cellJustif.length - 1 )] ) 1091 { 1092 case JUSTIFY_LEFT: 1093 justif = "left"; 1094 break; 1095 case JUSTIFY_RIGHT: 1096 justif = "right"; 1097 break; 1098 case JUSTIFY_CENTER: 1099 default: 1100 justif = "center"; 1101 } 1102 } 1103 1104 if ( justif != null ) 1105 { 1106 blockAtts.addAttribute( "text-align", justif ); 1107 } 1108 1109 writeStartTag( TABLE_CELL_TAG, cellAtts ); 1110 writeEOL(); 1111 writeStartTag( BLOCK_TAG, blockAtts ); 1112 writeEOL(); 1113 } 1114 1115 /** {@inheritDoc} */ 1116 public void tableCell_() 1117 { 1118 writeEndTag( BLOCK_TAG ); 1119 writeEOL(); 1120 writeEndTag( TABLE_CELL_TAG ); 1121 writeEOL(); 1122 1123 if ( this.isCellJustifStack.getLast().equals( Boolean.TRUE ) ) 1124 { 1125 int cellCount = Integer.parseInt( this.cellCountStack.removeLast().toString() ); 1126 this.cellCountStack.addLast( Integer.valueOf( ++cellCount ) ); 1127 } 1128 } 1129 1130 /** {@inheritDoc} */ 1131 public void tableHeaderCell_() 1132 { 1133 tableCell_(); 1134 } 1135 1136 /** {@inheritDoc} */ 1137 public void tableCaption( SinkEventAttributes attributes ) 1138 { 1139 StringWriter sw = new StringWriter(); 1140 this.tableCaptionWriterStack.addLast( sw ); 1141 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 } 1149 1150 /** {@inheritDoc} */ 1151 public void tableCaption() 1152 { 1153 tableCaption( null ); 1154 } 1155 1156 /** {@inheritDoc} */ 1157 public void tableCaption_() 1158 { 1159 if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null ) 1160 { 1161 this.tableCaptionStack.addLast( this.tableCaptionWriterStack.removeLast().toString() ); 1162 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 } 1167 1168 /** {@inheritDoc} */ 1169 public void anchor( String name, SinkEventAttributes attributes ) 1170 { 1171 if ( name == null ) 1172 { 1173 throw new NullPointerException( "Anchor name cannot be null!" ); 1174 } 1175 1176 String anchor = name; 1177 1178 if ( !DoxiaUtils.isValidId( anchor ) ) 1179 { 1180 anchor = DoxiaUtils.encodeId( name, true ); 1181 1182 String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'"; 1183 logMessage( "modifiedLink", msg ); 1184 } 1185 1186 anchor = "#" + name; 1187 1188 writeStartTag( INLINE_TAG, "id", anchor ); 1189 } 1190 1191 /** {@inheritDoc} */ 1192 public void anchor( String name ) 1193 { 1194 anchor( name, null ); 1195 } 1196 1197 /** {@inheritDoc} */ 1198 public void anchor_() 1199 { 1200 writeEndTag( INLINE_TAG ); 1201 } 1202 1203 /** {@inheritDoc} */ 1204 public void link( String name, SinkEventAttributes attributes ) 1205 { 1206 if ( name == null ) 1207 { 1208 throw new NullPointerException( "Link name cannot be null!" ); 1209 } 1210 1211 if ( DoxiaUtils.isExternalLink( name ) ) 1212 { 1213 writeStartTag( BASIC_LINK_TAG, "external-destination", HtmlTools.escapeHTML( name ) ); 1214 writeStartTag( INLINE_TAG, "href.external" ); 1215 } 1216 else if ( DoxiaUtils.isInternalLink( name ) ) 1217 { 1218 String anchor = name.substring( 1 ); 1219 1220 if ( !DoxiaUtils.isValidId( anchor ) ) 1221 { 1222 anchor = DoxiaUtils.encodeId( anchor, true ); 1223 1224 String msg = "Modified invalid anchor name: '" + name + "' to '" + anchor + "'"; 1225 logMessage( "modifiedLink", msg ); 1226 } 1227 1228 anchor = "#" + anchor; 1229 1230 writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) ); 1231 writeStartTag( INLINE_TAG, "href.internal" ); 1232 } 1233 else 1234 { 1235 // treat everything else as is 1236 String anchor = name; 1237 1238 writeStartTag( BASIC_LINK_TAG, "internal-destination", HtmlTools.escapeHTML( anchor ) ); 1239 writeStartTag( INLINE_TAG, "href.internal" ); 1240 } 1241 } 1242 1243 /** {@inheritDoc} */ 1244 public void link( String name ) 1245 { 1246 link( name, null ); 1247 } 1248 1249 /** {@inheritDoc} */ 1250 public void link_() 1251 { 1252 writeEndTag( INLINE_TAG ); 1253 writeEndTag( BASIC_LINK_TAG ); 1254 } 1255 1256 /** {@inheritDoc} */ 1257 public void italic() 1258 { 1259 writeStartTag( INLINE_TAG, "italic" ); 1260 } 1261 1262 /** {@inheritDoc} */ 1263 public void italic_() 1264 { 1265 writeEndTag( INLINE_TAG ); 1266 } 1267 1268 /** {@inheritDoc} */ 1269 public void bold() 1270 { 1271 writeStartTag( INLINE_TAG, "bold" ); 1272 } 1273 1274 /** {@inheritDoc} */ 1275 public void bold_() 1276 { 1277 writeEndTag( INLINE_TAG ); 1278 } 1279 1280 /** {@inheritDoc} */ 1281 public void monospaced() 1282 { 1283 writeStartTag( INLINE_TAG, "monospace" ); 1284 } 1285 1286 /** {@inheritDoc} */ 1287 public void monospaced_() 1288 { 1289 writeEndTag( INLINE_TAG ); 1290 } 1291 1292 /** {@inheritDoc} */ 1293 public void lineBreak( SinkEventAttributes attributes ) 1294 { 1295 writeEOL(); 1296 writeEOL(); 1297 writeSimpleTag( BLOCK_TAG ); 1298 } 1299 1300 /** {@inheritDoc} */ 1301 public void lineBreak() 1302 { 1303 lineBreak( null ); 1304 } 1305 1306 /** {@inheritDoc} */ 1307 public void nonBreakingSpace() 1308 { 1309 write( " " ); 1310 } 1311 1312 /** {@inheritDoc} */ 1313 public void text( String text, SinkEventAttributes attributes ) 1314 { 1315 content( text ); 1316 } 1317 1318 /** {@inheritDoc} */ 1319 public void text( String text ) 1320 { 1321 text( text, null ); 1322 } 1323 1324 /** {@inheritDoc} */ 1325 public void rawText( String text ) 1326 { 1327 write( text ); 1328 } 1329 1330 /** {@inheritDoc} */ 1331 public void flush() 1332 { 1333 out.flush(); 1334 } 1335 1336 /** {@inheritDoc} */ 1337 public void close() 1338 { 1339 out.close(); 1340 1341 if ( getLog().isWarnEnabled() && this.warnMessages != null ) 1342 { 1343 for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() ) 1344 { 1345 for ( String msg : entry.getValue() ) 1346 { 1347 getLog().warn( msg ); 1348 } 1349 } 1350 1351 this.warnMessages = null; 1352 } 1353 1354 init(); 1355 } 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 String msg = "Unknown Sink event: '" + name + "', ignoring!"; 1366 logMessage( "unknownEvent", msg ); 1367 } 1368 1369 /** {@inheritDoc} */ 1370 public void comment( String comment ) 1371 { 1372 1373 if ( StringUtils.isNotEmpty( comment ) && comment.indexOf( "--" ) != -1 ) 1374 { 1375 String originalComment = comment; 1376 // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments 1377 while ( comment.indexOf( "--" ) != -1 ) 1378 { 1379 comment = StringUtils.replace( comment, "--", "- -" ); 1380 } 1381 1382 String msg = "Modified invalid comment: '" + originalComment + "' to '" + comment + "'"; 1383 logMessage( "modifyComment", msg ); 1384 } 1385 1386 StringBuilder buf = new StringBuilder( comment.length() + 9 ); 1387 1388 buf.append( LESS_THAN ).append( BANG ).append( MINUS ).append( MINUS ).append( SPACE ); 1389 buf.append( comment ); 1390 buf.append( SPACE ).append( MINUS ).append( MINUS ).append( GREATER_THAN ); 1391 1392 write( buf.toString() ); 1393 } 1394 1395 /** 1396 * Writes the beginning of a FO document. 1397 */ 1398 public void beginDocument() 1399 { 1400 write( "<?xml version=\"1.0\"" ); 1401 if ( encoding != null ) 1402 { 1403 write( " encoding=\"" + encoding + "\"" ); 1404 } 1405 write( "?>" ); 1406 writeEOL(); 1407 1408 MutableAttributeSet atts = new SinkEventAttributeSet(); 1409 atts.addAttribute( "xmlns:" + getNameSpace(), FO_NAMESPACE ); 1410 1411 if ( languageId != null ) 1412 { 1413 atts.addAttribute( "language", languageId ); 1414 } 1415 1416 writeStartTag( ROOT_TAG, atts ); 1417 1418 writeStartTag( LAYOUT_MASTER_SET_TAG ); 1419 1420 writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.cover-page" ); 1421 writeEmptyTag( REGION_BODY_TAG, "layout.master.set.cover-page.region-body" ); 1422 writeEndTag( SIMPLE_PAGE_MASTER_TAG ); 1423 writeEOL(); 1424 1425 writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.toc" ); 1426 writeEmptyTag( REGION_BODY_TAG, "layout.master.set.toc.region-body" ); 1427 writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.toc.region-before" ); 1428 writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.toc.region-after" ); 1429 writeEndTag( SIMPLE_PAGE_MASTER_TAG ); 1430 writeEOL(); 1431 1432 writeStartTag( SIMPLE_PAGE_MASTER_TAG, "layout.master.set.body" ); 1433 writeEmptyTag( REGION_BODY_TAG, "layout.master.set.body.region-body" ); 1434 writeEmptyTag( REGION_BEFORE_TAG, "layout.master.set.body.region-before" ); 1435 writeEmptyTag( REGION_AFTER_TAG, "layout.master.set.body.region-after" ); 1436 writeEndTag( SIMPLE_PAGE_MASTER_TAG ); 1437 writeEOL(); 1438 1439 writeEndTag( LAYOUT_MASTER_SET_TAG ); 1440 writeEOL(); 1441 1442 pdfBookmarks(); 1443 } 1444 1445 /** 1446 * Writes the end of a FO document, flushes and closes the stream. 1447 */ 1448 public void endDocument() 1449 { 1450 writeEndTag( ROOT_TAG ); 1451 writeEOL(); 1452 1453 flush(); 1454 close(); 1455 } 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 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 writeEOL(); 1480 writeStartTag( tag, config.getAttributeSet( attributeId ) ); 1481 } 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 writeEOL(); 1493 MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} ); 1494 1495 writeStartTag( tag, att ); 1496 } 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 MutableAttributeSet att = config.getAttributeSet( attributeId ); 1509 1510 // make sure we don't add it twice 1511 if ( att.isDefined( id ) ) 1512 { 1513 att.removeAttribute( id ); 1514 } 1515 1516 att.addAttribute( id, name ); 1517 1518 writeEOL(); 1519 writeStartTag( tag, att ); 1520 } 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 MutableAttributeSet att = new SinkEventAttributeSet( new String[] {id, name} ); 1532 1533 writeEOL(); 1534 writeSimpleTag( tag, att ); 1535 } 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 writeEOL(); 1546 writeSimpleTag( tag, config.getAttributeSet( attributeId ) ); 1547 } 1548 1549 /** 1550 * {@inheritDoc} 1551 * 1552 * Writes a text, swallowing any exceptions. 1553 */ 1554 protected void write( String text ) 1555 { 1556 if ( !this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null ) 1557 { 1558 this.tableCaptionXMLWriterStack.getLast().writeText( unifyEOLs( text ) ); 1559 } 1560 else if ( !this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null ) 1561 { 1562 this.tableContentWriterStack.getLast().write( unifyEOLs( text ) ); 1563 } 1564 else 1565 { 1566 out.write( unifyEOLs( text ) ); 1567 } 1568 } 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 write( text ); 1578 writeEOL(); 1579 } 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 write( escaped( text, verbatim ) ); 1589 } 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 int length = text.length(); 1601 StringBuilder buffer = new StringBuilder( length ); 1602 1603 for ( int i = 0; i < length; ++i ) 1604 { 1605 char c = text.charAt( i ); 1606 switch ( c ) 1607 { 1608 case ' ': 1609 if ( verb ) 1610 { 1611 buffer.append( " " ); 1612 } 1613 else 1614 { 1615 buffer.append( c ); 1616 } 1617 break; 1618 case '<': 1619 buffer.append( "<" ); 1620 break; 1621 case '>': 1622 buffer.append( ">" ); 1623 break; 1624 case '&': 1625 buffer.append( "&" ); 1626 break; 1627 case '\n': 1628 buffer.append( EOL ); 1629 if ( verb ) 1630 { 1631 buffer.append( "<fo:block/>" + EOL ); 1632 } 1633 break; 1634 default: 1635 if ( needsSymbolFont( c ) ) 1636 { 1637 // TODO: make font configurable? 1638 buffer.append( "<fo:inline font-family=\"Symbol\">" ).append( c ).append( "</fo:inline>" ); 1639 } 1640 else 1641 { 1642 buffer.append( c ); 1643 } 1644 } 1645 } 1646 1647 return buffer.toString(); 1648 } 1649 1650 /** {@inheritDoc} */ 1651 protected void writeStartTag( Tag t, MutableAttributeSet att, boolean isSimpleTag ) 1652 { 1653 if ( this.tableCaptionXMLWriterStack.isEmpty() ) 1654 { 1655 super.writeStartTag ( t, att, isSimpleTag ); 1656 } 1657 else 1658 { 1659 String tag = ( getNameSpace() != null ? getNameSpace() + ":" : "" ) + t.toString(); 1660 this.tableCaptionXMLWriterStack.getLast().startElement( tag ); 1661 1662 if ( att != null ) 1663 { 1664 Enumeration<?> names = att.getAttributeNames(); 1665 while ( names.hasMoreElements() ) 1666 { 1667 Object key = names.nextElement(); 1668 Object value = att.getAttribute( key ); 1669 1670 this.tableCaptionXMLWriterStack.getLast().addAttribute( key.toString(), value.toString() ); 1671 } 1672 } 1673 1674 if ( isSimpleTag ) 1675 { 1676 this.tableCaptionXMLWriterStack.getLast().endElement(); 1677 } 1678 } 1679 } 1680 1681 /** {@inheritDoc} */ 1682 protected void writeEndTag( Tag t ) 1683 { 1684 if ( this.tableCaptionXMLWriterStack.isEmpty() ) 1685 { 1686 super.writeEndTag( t ); 1687 } 1688 else 1689 { 1690 this.tableCaptionXMLWriterStack.getLast().endElement(); 1691 } 1692 } 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 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 writeln( "<fo:page-sequence initial-page-number=\"" + initPageNumber + "\" master-reference=\"body\">" ); 1724 regionBefore( headerText ); 1725 regionAfter( footerText ); 1726 writeln( "<fo:flow flow-name=\"xsl-region-body\">" ); 1727 chapterHeading( null, true ); 1728 } 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 } 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 } 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 } 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 } 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 msg = "[FO Sink] " + msg; 1780 if ( getLog().isDebugEnabled() ) 1781 { 1782 getLog().debug( msg ); 1783 1784 return; 1785 } 1786 1787 if ( warnMessages == null ) 1788 { 1789 warnMessages = new HashMap<String, Set<String>>(); 1790 } 1791 1792 Set<String> set = warnMessages.get( key ); 1793 if ( set == null ) 1794 { 1795 set = new TreeSet<String>(); 1796 } 1797 set.add( msg ); 1798 warnMessages.put( key, set ); 1799 } 1800 1801 /** {@inheritDoc} */ 1802 protected void init() 1803 { 1804 super.init(); 1805 1806 this.listStack.clear(); 1807 this.tableGridStack.clear(); 1808 this.cellJustifStack.clear(); 1809 this.isCellJustifStack.clear(); 1810 this.cellCountStack.clear(); 1811 this.tableContentWriterStack.clear(); 1812 this.tableCaptionWriterStack.clear(); 1813 this.tableCaptionXMLWriterStack.clear(); 1814 this.tableCaptionStack.clear(); 1815 1816 this.section = 0; 1817 this.subsection = 0; 1818 this.subsubsection = 0; 1819 this.verbatim = false; 1820 this.inFigure = false; 1821 this.warnMessages = null; 1822 } 1823}