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