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