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