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