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