001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.doxia.sink.impl; 020 021import javax.swing.text.MutableAttributeSet; 022import javax.swing.text.html.HTML.Tag; 023 024import java.io.PrintWriter; 025import java.io.StringWriter; 026import java.io.Writer; 027import java.util.ArrayList; 028import java.util.EmptyStackException; 029import java.util.Enumeration; 030import java.util.HashMap; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034import java.util.Objects; 035import java.util.Stack; 036import java.util.regex.Pattern; 037 038import org.apache.commons.lang3.StringUtils; 039import org.apache.maven.doxia.markup.HtmlMarkup; 040import org.apache.maven.doxia.markup.Markup; 041import org.apache.maven.doxia.sink.Sink; 042import org.apache.maven.doxia.sink.SinkEventAttributes; 043import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink.VerbatimMode; 044import org.apache.maven.doxia.util.DoxiaUtils; 045import org.apache.maven.doxia.util.HtmlTools; 046import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050/** 051 * Abstract base xhtml5 sink implementation. 052 */ 053public class Xhtml5BaseSink extends AbstractXmlSink implements HtmlMarkup { 054 private static final Logger LOGGER = LoggerFactory.getLogger(Xhtml5BaseSink.class); 055 056 // ---------------------------------------------------------------------- 057 // Instance fields 058 // ---------------------------------------------------------------------- 059 060 /** The PrintWriter to write the result. */ 061 private final PrintWriter writer; 062 063 /** Used to identify if a class string contains `hidden` */ 064 private static final Pattern HIDDEN_CLASS_PATTERN = Pattern.compile("(?:.*\\s|^)hidden(?:\\s.*|$)"); 065 066 /** Used to collect text events mainly for the head events. */ 067 private StringBuffer textBuffer = new StringBuffer(); 068 069 /** An indication on if we're inside a head. */ 070 private boolean headFlag; 071 072 /** Keep track of the main and div tags for content events. */ 073 protected Stack<Tag> contentStack = new Stack<>(); 074 075 /** Keep track of the closing tags for inline events. */ 076 protected Stack<List<Tag>> inlineStack = new Stack<>(); 077 078 /** An indication on if we're inside a paragraph flag. */ 079 private boolean paragraphFlag; 080 081 protected enum VerbatimMode { 082 /** not in verbatim mode */ 083 OFF, 084 /** Inside {@code <pre>} */ 085 ON, 086 /** Inside {@code <pre><code>} */ 087 ON_WITH_CODE 088 } 089 /** An indication on if we're in verbatim mode and if so, surrounded by which tags. */ 090 private VerbatimMode verbatimMode; 091 092 /** Stack of alignment int[] of table cells. */ 093 private final LinkedList<int[]> cellJustifStack; 094 095 /** Stack of justification of table cells. */ 096 private final LinkedList<Boolean> isCellJustifStack; 097 098 /** Stack of current table cell. */ 099 private final LinkedList<Integer> cellCountStack; 100 101 /** Used to style successive table rows differently. */ 102 private boolean evenTableRow = true; 103 104 /** The stack of StringWriter to write the table result temporary, so we could play with the output DOXIA-177. */ 105 private final LinkedList<StringWriter> tableContentWriterStack; 106 107 private final LinkedList<StringWriter> tableCaptionWriterStack; 108 109 private final LinkedList<PrettyPrintXMLWriter> tableCaptionXMLWriterStack; 110 111 /** The stack of table caption */ 112 private final LinkedList<String> tableCaptionStack; 113 114 /** used to store attributes passed to table(). */ 115 protected MutableAttributeSet tableAttributes; 116 117 // ---------------------------------------------------------------------- 118 // Constructor 119 // ---------------------------------------------------------------------- 120 121 /** 122 * Constructor, initialize the PrintWriter. 123 * 124 * @param out The writer to write the result. 125 */ 126 public Xhtml5BaseSink(Writer out) { 127 this.writer = new PrintWriter(out); 128 129 this.cellJustifStack = new LinkedList<>(); 130 this.isCellJustifStack = new LinkedList<>(); 131 this.cellCountStack = new LinkedList<>(); 132 this.tableContentWriterStack = new LinkedList<>(); 133 this.tableCaptionWriterStack = new LinkedList<>(); 134 this.tableCaptionXMLWriterStack = new LinkedList<>(); 135 this.tableCaptionStack = new LinkedList<>(); 136 137 init(); 138 } 139 140 // ---------------------------------------------------------------------- 141 // Accessor methods 142 // ---------------------------------------------------------------------- 143 144 /** 145 * To use mainly when playing with the head events. 146 * 147 * @return the current buffer of text events. 148 */ 149 protected StringBuffer getTextBuffer() { 150 return this.textBuffer; 151 } 152 153 /** 154 * <p>Setter for the field <code>headFlag</code>.</p> 155 * 156 * @param headFlag an header flag. 157 */ 158 protected void setHeadFlag(boolean headFlag) { 159 this.headFlag = headFlag; 160 } 161 162 /** 163 * <p>isHeadFlag.</p> 164 * 165 * @return the current headFlag. 166 */ 167 protected boolean isHeadFlag() { 168 return this.headFlag; 169 } 170 171 /** 172 * 173 * @return the current verbatim mode. 174 */ 175 protected VerbatimMode getVerbatimMode() { 176 return this.verbatimMode; 177 } 178 179 /** 180 * <p>Setter for the field <code>verbatimMode</code>.</p> 181 * 182 * @param mode a verbatim mode. 183 */ 184 protected void setVerbatimMode(VerbatimMode mode) { 185 this.verbatimMode = mode; 186 } 187 188 /** 189 * 190 * @return {@code true} if inside verbatim section, {@code false} otherwise 191 */ 192 protected boolean isVerbatim() { 193 return this.verbatimMode != VerbatimMode.OFF; 194 } 195 196 /** 197 * <p>Setter for the field <code>cellJustif</code>.</p> 198 * 199 * @param justif the new cell justification array. 200 */ 201 protected void setCellJustif(int[] justif) { 202 this.cellJustifStack.addLast(justif); 203 this.isCellJustifStack.addLast(Boolean.TRUE); 204 } 205 206 /** 207 * <p>Getter for the field <code>cellJustif</code>.</p> 208 * 209 * @return the current cell justification array. 210 */ 211 protected int[] getCellJustif() { 212 return this.cellJustifStack.getLast(); 213 } 214 215 /** 216 * <p>Setter for the field <code>cellCount</code>.</p> 217 * 218 * @param count the new cell count. 219 */ 220 protected void setCellCount(int count) { 221 this.cellCountStack.addLast(count); 222 } 223 224 /** 225 * <p>Getter for the field <code>cellCount</code>.</p> 226 * 227 * @return the current cell count. 228 */ 229 protected int getCellCount() { 230 return this.cellCountStack.getLast(); 231 } 232 233 /** {@inheritDoc} */ 234 @Override 235 protected void init() { 236 super.init(); 237 238 resetTextBuffer(); 239 240 this.cellJustifStack.clear(); 241 this.isCellJustifStack.clear(); 242 this.cellCountStack.clear(); 243 this.tableContentWriterStack.clear(); 244 this.tableCaptionWriterStack.clear(); 245 this.tableCaptionXMLWriterStack.clear(); 246 this.tableCaptionStack.clear(); 247 this.inlineStack.clear(); 248 249 this.headFlag = false; 250 this.paragraphFlag = false; 251 this.verbatimMode = VerbatimMode.OFF; 252 253 this.evenTableRow = true; 254 this.tableAttributes = null; 255 } 256 257 /** 258 * Reset the text buffer. 259 */ 260 protected void resetTextBuffer() { 261 this.textBuffer = new StringBuffer(); 262 } 263 264 // ---------------------------------------------------------------------- 265 // Sections 266 // ---------------------------------------------------------------------- 267 268 /** {@inheritDoc} */ 269 @Override 270 public void article(SinkEventAttributes attributes) { 271 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 272 273 writeStartTag(HtmlMarkup.ARTICLE, atts); 274 } 275 276 /** {@inheritDoc} */ 277 @Override 278 public void article_() { 279 writeEndTag(HtmlMarkup.ARTICLE); 280 } 281 282 /** {@inheritDoc} */ 283 @Override 284 public void navigation(SinkEventAttributes attributes) { 285 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 286 287 writeStartTag(HtmlMarkup.NAV, atts); 288 } 289 290 /** {@inheritDoc} */ 291 @Override 292 public void navigation_() { 293 writeEndTag(HtmlMarkup.NAV); 294 } 295 296 /** {@inheritDoc} */ 297 @Override 298 public void sidebar(SinkEventAttributes attributes) { 299 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 300 301 writeStartTag(HtmlMarkup.ASIDE, atts); 302 } 303 304 /** {@inheritDoc} */ 305 @Override 306 public void sidebar_() { 307 writeEndTag(HtmlMarkup.ASIDE); 308 } 309 310 /** {@inheritDoc} */ 311 @Override 312 public void section(int level, SinkEventAttributes attributes) { 313 onSection(level, attributes); 314 } 315 316 /** {@inheritDoc} */ 317 @Override 318 public void sectionTitle(int level, SinkEventAttributes attributes) { 319 onSectionTitle(level, attributes); 320 } 321 322 /** {@inheritDoc} */ 323 @Override 324 public void sectionTitle_(int level) { 325 onSectionTitle_(level); 326 } 327 328 /** {@inheritDoc} */ 329 @Override 330 public void section_(int level) { 331 onSection_(level); 332 } 333 334 /** 335 * Starts a section. 336 * 337 * @param depth The level of the section. 338 * @param attributes some attributes. May be null. 339 */ 340 protected void onSection(int depth, SinkEventAttributes attributes) { 341 if (depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_6) { 342 MutableAttributeSet att = new SinkEventAttributeSet(); 343 att.addAttributes(SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES)); 344 345 writeStartTag(HtmlMarkup.SECTION, att); 346 } 347 } 348 349 /** 350 * Ends a section. 351 * 352 * @param depth The level of the section. 353 * @see #SECTION 354 */ 355 protected void onSection_(int depth) { 356 if (depth >= SECTION_LEVEL_1 && depth <= SECTION_LEVEL_6) { 357 writeEndTag(HtmlMarkup.SECTION); 358 } 359 } 360 361 /** 362 * Starts a section title. 363 * 364 * @param depth The level of the section title. 365 * @param attributes some attributes. May be null. 366 * @see #H1 367 * @see #H2 368 * @see #H3 369 * @see #H4 370 * @see #H5 371 * @see #H6 372 */ 373 protected void onSectionTitle(int depth, SinkEventAttributes attributes) { 374 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 375 376 if (depth == SECTION_LEVEL_1) { 377 writeStartTag(HtmlMarkup.H1, atts); 378 } else if (depth == SECTION_LEVEL_2) { 379 writeStartTag(HtmlMarkup.H2, atts); 380 } else if (depth == SECTION_LEVEL_3) { 381 writeStartTag(HtmlMarkup.H3, atts); 382 } else if (depth == SECTION_LEVEL_4) { 383 writeStartTag(HtmlMarkup.H4, atts); 384 } else if (depth == SECTION_LEVEL_5) { 385 writeStartTag(HtmlMarkup.H5, atts); 386 } else if (depth == SECTION_LEVEL_6) { 387 writeStartTag(HtmlMarkup.H6, atts); 388 } 389 } 390 391 /** 392 * Ends a section title. 393 * 394 * @param depth The level of the section title. 395 * @see #H1 396 * @see #H2 397 * @see #H3 398 * @see #H4 399 * @see #H5 400 * @see #H6 401 */ 402 protected void onSectionTitle_(int depth) { 403 if (depth == SECTION_LEVEL_1) { 404 writeEndTag(HtmlMarkup.H1); 405 } else if (depth == SECTION_LEVEL_2) { 406 writeEndTag(HtmlMarkup.H2); 407 } else if (depth == SECTION_LEVEL_3) { 408 writeEndTag(HtmlMarkup.H3); 409 } else if (depth == SECTION_LEVEL_4) { 410 writeEndTag(HtmlMarkup.H4); 411 } else if (depth == SECTION_LEVEL_5) { 412 writeEndTag(HtmlMarkup.H5); 413 } else if (depth == SECTION_LEVEL_6) { 414 writeEndTag(HtmlMarkup.H6); 415 } 416 } 417 418 /** {@inheritDoc} */ 419 @Override 420 public void header(SinkEventAttributes attributes) { 421 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 422 423 writeStartTag(HtmlMarkup.HEADER, atts); 424 } 425 426 /** {@inheritDoc} */ 427 @Override 428 public void header_() { 429 writeEndTag(HtmlMarkup.HEADER); 430 } 431 432 /** {@inheritDoc} */ 433 @Override 434 public void content(SinkEventAttributes attributes) { 435 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 436 437 if (contentStack.empty()) { 438 writeStartTag(contentStack.push(HtmlMarkup.MAIN), atts); 439 } else { 440 if (atts == null) { 441 atts = new SinkEventAttributeSet(1); 442 } 443 444 String divClass = "content"; 445 if (atts.isDefined(SinkEventAttributes.CLASS)) { 446 divClass += " " + atts.getAttribute(SinkEventAttributes.CLASS).toString(); 447 } 448 449 atts.addAttribute(SinkEventAttributes.CLASS, divClass); 450 451 writeStartTag(contentStack.push(HtmlMarkup.DIV), atts); 452 } 453 } 454 455 /** {@inheritDoc} */ 456 @Override 457 public void content_() { 458 try { 459 writeEndTag(contentStack.pop()); 460 } catch (EmptyStackException ese) { 461 /* do nothing if the stack is empty */ 462 } 463 } 464 465 /** {@inheritDoc} */ 466 @Override 467 public void footer(SinkEventAttributes attributes) { 468 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 469 470 writeStartTag(HtmlMarkup.FOOTER, atts); 471 } 472 473 /** {@inheritDoc} */ 474 @Override 475 public void footer_() { 476 writeEndTag(HtmlMarkup.FOOTER); 477 } 478 479 // ----------------------------------------------------------------------- 480 // 481 // ----------------------------------------------------------------------- 482 483 /** 484 * {@inheritDoc} 485 * @see javax.swing.text.html.HTML.Tag#UL 486 */ 487 @Override 488 public void list(SinkEventAttributes attributes) { 489 if (paragraphFlag) { 490 // The content of element type "p" must match 491 // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong| 492 // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)". 493 paragraph_(); 494 } 495 496 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 497 498 writeStartTag(HtmlMarkup.UL, atts); 499 } 500 501 /** 502 * {@inheritDoc} 503 * @see javax.swing.text.html.HTML.Tag#UL 504 */ 505 @Override 506 public void list_() { 507 writeEndTag(HtmlMarkup.UL); 508 } 509 510 /** 511 * {@inheritDoc} 512 * @see javax.swing.text.html.HTML.Tag#LI 513 */ 514 @Override 515 public void listItem(SinkEventAttributes attributes) { 516 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 517 518 writeStartTag(HtmlMarkup.LI, atts); 519 } 520 521 /** 522 * {@inheritDoc} 523 * @see javax.swing.text.html.HTML.Tag#LI 524 */ 525 @Override 526 public void listItem_() { 527 writeEndTag(HtmlMarkup.LI); 528 } 529 530 /** 531 * The default list style depends on the numbering. 532 * 533 * {@inheritDoc} 534 * @see javax.swing.text.html.HTML.Tag#OL 535 */ 536 @Override 537 public void numberedList(int numbering, SinkEventAttributes attributes) { 538 if (paragraphFlag) { 539 // The content of element type "p" must match 540 // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong| 541 // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)". 542 paragraph_(); 543 } 544 545 String olStyle = "list-style-type: "; 546 switch (numbering) { 547 case NUMBERING_UPPER_ALPHA: 548 olStyle += "upper-alpha"; 549 break; 550 case NUMBERING_LOWER_ALPHA: 551 olStyle += "lower-alpha"; 552 break; 553 case NUMBERING_UPPER_ROMAN: 554 olStyle += "upper-roman"; 555 break; 556 case NUMBERING_LOWER_ROMAN: 557 olStyle += "lower-roman"; 558 break; 559 case NUMBERING_DECIMAL: 560 default: 561 olStyle += "decimal"; 562 } 563 olStyle += ";"; 564 565 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 566 567 if (atts == null) { 568 atts = new SinkEventAttributeSet(1); 569 } 570 571 if (atts.isDefined(SinkEventAttributes.STYLE)) { 572 olStyle += " " + atts.getAttribute(SinkEventAttributes.STYLE).toString(); 573 } 574 575 atts.addAttribute(SinkEventAttributes.STYLE, olStyle); 576 577 writeStartTag(HtmlMarkup.OL, atts); 578 } 579 580 /** 581 * {@inheritDoc} 582 * @see javax.swing.text.html.HTML.Tag#OL 583 */ 584 @Override 585 public void numberedList_() { 586 writeEndTag(HtmlMarkup.OL); 587 } 588 589 /** 590 * {@inheritDoc} 591 * @see javax.swing.text.html.HTML.Tag#LI 592 */ 593 @Override 594 public void numberedListItem(SinkEventAttributes attributes) { 595 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 596 597 writeStartTag(HtmlMarkup.LI, atts); 598 } 599 600 /** 601 * {@inheritDoc} 602 * @see javax.swing.text.html.HTML.Tag#LI 603 */ 604 @Override 605 public void numberedListItem_() { 606 writeEndTag(HtmlMarkup.LI); 607 } 608 609 /** 610 * {@inheritDoc} 611 * @see javax.swing.text.html.HTML.Tag#DL 612 */ 613 @Override 614 public void definitionList(SinkEventAttributes attributes) { 615 if (paragraphFlag) { 616 // The content of element type "p" must match 617 // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong| 618 // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)". 619 paragraph_(); 620 } 621 622 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 623 624 writeStartTag(HtmlMarkup.DL, atts); 625 } 626 627 /** 628 * {@inheritDoc} 629 * @see javax.swing.text.html.HTML.Tag#DL 630 */ 631 @Override 632 public void definitionList_() { 633 writeEndTag(HtmlMarkup.DL); 634 } 635 636 /** 637 * {@inheritDoc} 638 * @see javax.swing.text.html.HTML.Tag#DT 639 */ 640 @Override 641 public void definedTerm(SinkEventAttributes attributes) { 642 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 643 644 writeStartTag(HtmlMarkup.DT, atts); 645 } 646 647 /** 648 * {@inheritDoc} 649 * @see javax.swing.text.html.HTML.Tag#DT 650 */ 651 @Override 652 public void definedTerm_() { 653 writeEndTag(HtmlMarkup.DT); 654 } 655 656 /** 657 * {@inheritDoc} 658 * @see javax.swing.text.html.HTML.Tag#DD 659 */ 660 @Override 661 public void definition(SinkEventAttributes attributes) { 662 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 663 664 writeStartTag(HtmlMarkup.DD, atts); 665 } 666 667 /** 668 * {@inheritDoc} 669 * @see javax.swing.text.html.HTML.Tag#DD 670 */ 671 @Override 672 public void definition_() { 673 writeEndTag(HtmlMarkup.DD); 674 } 675 676 /** {@inheritDoc} */ 677 @Override 678 public void figure(SinkEventAttributes attributes) { 679 writeStartTag(HtmlMarkup.FIGURE, attributes); 680 } 681 682 /** {@inheritDoc} */ 683 @Override 684 public void figure_() { 685 writeEndTag(HtmlMarkup.FIGURE); 686 } 687 688 /** {@inheritDoc} */ 689 @Override 690 public void figureGraphics(String src, SinkEventAttributes attributes) { 691 MutableAttributeSet filtered = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_IMG_ATTRIBUTES); 692 if (filtered != null) { 693 filtered.removeAttribute(SinkEventAttributes.SRC.toString()); 694 } 695 696 int count = (attributes == null ? 1 : attributes.getAttributeCount() + 1); 697 698 MutableAttributeSet atts = new SinkEventAttributeSet(count); 699 700 atts.addAttribute(SinkEventAttributes.SRC, HtmlTools.escapeHTML(src, true)); 701 atts.addAttributes(filtered); 702 703 writeStartTag(HtmlMarkup.IMG, atts, true); 704 } 705 706 /** {@inheritDoc} */ 707 @Override 708 public void figureCaption(SinkEventAttributes attributes) { 709 writeStartTag(HtmlMarkup.FIGCAPTION, attributes); 710 } 711 712 /** {@inheritDoc} */ 713 @Override 714 public void figureCaption_() { 715 writeEndTag(HtmlMarkup.FIGCAPTION); 716 } 717 718 /** 719 * {@inheritDoc} 720 * @see javax.swing.text.html.HTML.Tag#P 721 */ 722 @Override 723 public void paragraph(SinkEventAttributes attributes) { 724 paragraphFlag = true; 725 726 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 727 728 writeStartTag(HtmlMarkup.P, atts); 729 } 730 731 /** 732 * {@inheritDoc} 733 * @see javax.swing.text.html.HTML.Tag#P 734 */ 735 @Override 736 public void paragraph_() { 737 if (paragraphFlag) { 738 writeEndTag(HtmlMarkup.P); 739 paragraphFlag = false; 740 } 741 } 742 743 /** {@inheritDoc} */ 744 @Override 745 public void data(String value, SinkEventAttributes attributes) { 746 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 747 748 MutableAttributeSet att = new SinkEventAttributeSet(); 749 if (value != null) { 750 att.addAttribute(SinkEventAttributes.VALUE, value); 751 } 752 att.addAttributes(atts); 753 754 writeStartTag(HtmlMarkup.DATA, att); 755 } 756 757 /** {@inheritDoc} */ 758 @Override 759 public void data_() { 760 writeEndTag(HtmlMarkup.DATA); 761 } 762 763 /** {@inheritDoc} */ 764 @Override 765 public void time(String datetime, SinkEventAttributes attributes) { 766 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 767 768 MutableAttributeSet att = new SinkEventAttributeSet(); 769 if (datetime != null) { 770 att.addAttribute("datetime", datetime); 771 } 772 att.addAttributes(atts); 773 774 writeStartTag(HtmlMarkup.TIME, att); 775 } 776 777 /** {@inheritDoc} */ 778 @Override 779 public void time_() { 780 writeEndTag(HtmlMarkup.TIME); 781 } 782 783 /** 784 * {@inheritDoc} 785 * @see javax.swing.text.html.HTML.Tag#ADDRESS 786 */ 787 @Override 788 public void address(SinkEventAttributes attributes) { 789 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 790 791 writeStartTag(HtmlMarkup.ADDRESS, atts); 792 } 793 794 /** 795 * {@inheritDoc} 796 * @see javax.swing.text.html.HTML.Tag#ADDRESS 797 */ 798 @Override 799 public void address_() { 800 writeEndTag(HtmlMarkup.ADDRESS); 801 } 802 803 /** 804 * {@inheritDoc} 805 * @see javax.swing.text.html.HTML.Tag#BLOCKQUOTE 806 */ 807 @Override 808 public void blockquote(SinkEventAttributes attributes) { 809 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 810 811 writeStartTag(HtmlMarkup.BLOCKQUOTE, atts); 812 } 813 814 /** 815 * {@inheritDoc} 816 * @see javax.swing.text.html.HTML.Tag#BLOCKQUOTE 817 */ 818 @Override 819 public void blockquote_() { 820 writeEndTag(HtmlMarkup.BLOCKQUOTE); 821 } 822 823 /** 824 * {@inheritDoc} 825 * @see javax.swing.text.html.HTML.Tag#DIV 826 */ 827 @Override 828 public void division(SinkEventAttributes attributes) { 829 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 830 831 writeStartTag(HtmlMarkup.DIV, atts); 832 } 833 834 /** 835 * {@inheritDoc} 836 * @see javax.swing.text.html.HTML.Tag#DIV 837 */ 838 @Override 839 public void division_() { 840 writeEndTag(HtmlMarkup.DIV); 841 } 842 843 /** 844 * Depending on whether the decoration attribute is "source" or not, this leads 845 * to either emitting {@code <pre><code>} or just {@code <pre>}. 846 * No default classes are emitted but the given attributes are always added to the {@code pre} element only. 847 * 848 * {@inheritDoc} 849 * @see javax.swing.text.html.HTML.Tag#PRE 850 * @see javax.swing.text.html.HTML.Tag#CODE 851 */ 852 @Override 853 public void verbatim(SinkEventAttributes attributes) { 854 if (paragraphFlag) { 855 // The content of element type "p" must match 856 // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong| 857 // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)". 858 paragraph_(); 859 } 860 861 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES); 862 863 if (atts == null) { 864 atts = new SinkEventAttributeSet(); 865 } 866 867 verbatimMode = VerbatimMode.ON; 868 if (atts.isDefined(SinkEventAttributes.DECORATION)) { 869 if ("source" 870 .equals(atts.getAttribute(SinkEventAttributes.DECORATION).toString())) { 871 verbatimMode = VerbatimMode.ON_WITH_CODE; 872 } 873 } 874 875 atts.removeAttribute(SinkEventAttributes.DECORATION); 876 877 writeStartTag(HtmlMarkup.PRE, atts); 878 if (verbatimMode == VerbatimMode.ON_WITH_CODE) { 879 writeStartTag(HtmlMarkup.CODE); 880 } 881 } 882 883 /** 884 * {@inheritDoc} 885 * @see javax.swing.text.html.HTML.Tag#CODE 886 * @see javax.swing.text.html.HTML.Tag#PRE 887 */ 888 @Override 889 public void verbatim_() { 890 if (verbatimMode == VerbatimMode.ON_WITH_CODE) { 891 writeEndTag(HtmlMarkup.CODE); 892 } 893 writeEndTag(HtmlMarkup.PRE); 894 895 verbatimMode = VerbatimMode.OFF; 896 } 897 898 /** 899 * {@inheritDoc} 900 * @see javax.swing.text.html.HTML.Tag#HR 901 */ 902 @Override 903 public void horizontalRule(SinkEventAttributes attributes) { 904 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_HR_ATTRIBUTES); 905 906 writeSimpleTag(HtmlMarkup.HR, atts); 907 } 908 909 /** {@inheritDoc} */ 910 @Override 911 public void table(SinkEventAttributes attributes) { 912 this.tableContentWriterStack.addLast(new StringWriter()); 913 914 if (paragraphFlag) { 915 // The content of element type "p" must match 916 // "(a|br|span|bdo|object|applet|img|map|iframe|tt|i|b|u|s|strike|big|small|font|basefont|em|strong| 917 // dfn|code|q|samp|kbd|var|cite|abbr|acronym|sub|sup|input|select|textarea|label|button|ins|del|script)". 918 paragraph_(); 919 } 920 921 // start table with tableRows 922 if (attributes == null) { 923 this.tableAttributes = new SinkEventAttributeSet(0); 924 } else { 925 this.tableAttributes = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_TABLE_ATTRIBUTES); 926 } 927 } 928 929 /** 930 * {@inheritDoc} 931 * @see javax.swing.text.html.HTML.Tag#TABLE 932 */ 933 @Override 934 public void table_() { 935 writeEndTag(HtmlMarkup.TABLE); 936 937 if (!this.cellCountStack.isEmpty()) { 938 this.cellCountStack.removeLast().toString(); 939 } 940 941 if (this.tableContentWriterStack.isEmpty()) { 942 LOGGER.warn("{}No table content", getLocationLogPrefix()); 943 return; 944 } 945 946 String tableContent = this.tableContentWriterStack.removeLast().toString(); 947 948 String tableCaption = null; 949 if (!this.tableCaptionStack.isEmpty() && this.tableCaptionStack.getLast() != null) { 950 tableCaption = this.tableCaptionStack.removeLast(); 951 } 952 953 if (tableCaption != null) { 954 // DOXIA-177 955 StringBuilder sb = new StringBuilder(); 956 sb.append(tableContent, 0, tableContent.indexOf(Markup.GREATER_THAN) + 1); 957 sb.append(tableCaption); 958 sb.append(tableContent.substring(tableContent.indexOf(Markup.GREATER_THAN) + 1)); 959 960 write(sb.toString()); 961 } else { 962 write(tableContent); 963 } 964 } 965 966 /** 967 * The default style class is <code>bodyTable</code>. 968 * 969 * @param grid if {@code true} the style class {@code bodyTableBorder} will be added 970 * 971 * {@inheritDoc} 972 * @see javax.swing.text.html.HTML.Tag#TABLE 973 */ 974 @Override 975 public void tableRows(int[] justification, boolean grid) { 976 setCellJustif(justification); 977 978 MutableAttributeSet att = new SinkEventAttributeSet(); 979 980 String tableClass = "bodyTable" + (grid ? " bodyTableBorder" : ""); 981 if (this.tableAttributes.isDefined(SinkEventAttributes.CLASS.toString())) { 982 tableClass += " " 983 + this.tableAttributes 984 .getAttribute(SinkEventAttributes.CLASS) 985 .toString(); 986 } 987 988 att.addAttribute(SinkEventAttributes.CLASS, tableClass); 989 990 att.addAttributes(this.tableAttributes); 991 this.tableAttributes.removeAttributes(this.tableAttributes); 992 993 writeStartTag(HtmlMarkup.TABLE, att); 994 995 this.cellCountStack.addLast(0); 996 } 997 998 /** {@inheritDoc} */ 999 @Override 1000 public void tableRows_() { 1001 if (!this.cellJustifStack.isEmpty()) { 1002 this.cellJustifStack.removeLast(); 1003 } 1004 if (!this.isCellJustifStack.isEmpty()) { 1005 this.isCellJustifStack.removeLast(); 1006 } 1007 1008 this.evenTableRow = true; 1009 } 1010 1011 /** 1012 * Rows are striped with two colors by adding the class <code>a</code> or <code>b</code>. If the provided attributes 1013 * specify the <code>hidden</code> class, the next call to tableRow will set the same striping class as this one. A 1014 * style for <code>hidden</code> or <code>table.bodyTable hidden</code> may need to be provided to actually hide 1015 * such a row. {@inheritDoc} 1016 * 1017 * @see javax.swing.text.html.HTML.Tag#TR 1018 */ 1019 @Override 1020 public void tableRow(SinkEventAttributes attributes) { 1021 MutableAttributeSet attrs = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_TR_ATTRIBUTES); 1022 1023 if (attrs == null) { 1024 attrs = new SinkEventAttributeSet(); 1025 } 1026 1027 String rowClass = evenTableRow ? "a" : "b"; 1028 boolean hidden = false; 1029 if (attrs.isDefined(SinkEventAttributes.CLASS.toString())) { 1030 String givenRowClass = (String) attrs.getAttribute(SinkEventAttributes.CLASS.toString()); 1031 if (HIDDEN_CLASS_PATTERN.matcher(givenRowClass).matches()) { 1032 hidden = true; 1033 } 1034 rowClass += " " + givenRowClass; 1035 } 1036 1037 attrs.addAttribute(SinkEventAttributes.CLASS, rowClass); 1038 1039 writeStartTag(HtmlMarkup.TR, attrs); 1040 1041 if (!hidden) { 1042 evenTableRow = !evenTableRow; 1043 } 1044 1045 if (!this.cellCountStack.isEmpty()) { 1046 this.cellCountStack.removeLast(); 1047 this.cellCountStack.addLast(0); 1048 } 1049 } 1050 1051 /** 1052 * {@inheritDoc} 1053 * @see javax.swing.text.html.HTML.Tag#TR 1054 */ 1055 @Override 1056 public void tableRow_() { 1057 writeEndTag(HtmlMarkup.TR); 1058 } 1059 1060 /** {@inheritDoc} */ 1061 @Override 1062 public void tableCell(SinkEventAttributes attributes) { 1063 tableCell(false, attributes); 1064 } 1065 1066 /** {@inheritDoc} */ 1067 @Override 1068 public void tableHeaderCell(SinkEventAttributes attributes) { 1069 tableCell(true, attributes); 1070 } 1071 1072 /** 1073 * @param headerRow true if it is an header row 1074 * @param attributes the cell attributes 1075 * @see javax.swing.text.html.HTML.Tag#TH 1076 * @see javax.swing.text.html.HTML.Tag#TD 1077 */ 1078 private void tableCell(boolean headerRow, MutableAttributeSet attributes) { 1079 Tag t = (headerRow ? HtmlMarkup.TH : HtmlMarkup.TD); 1080 1081 if (!headerRow 1082 && cellCountStack != null 1083 && !cellCountStack.isEmpty() 1084 && cellJustifStack != null 1085 && !cellJustifStack.isEmpty() 1086 && getCellJustif() != null) { 1087 int cellCount = getCellCount(); 1088 int[] cellJustif = getCellJustif(); 1089 if (cellCount < cellJustif.length) { 1090 if (attributes == null) { 1091 attributes = new SinkEventAttributeSet(); 1092 } 1093 Map<Integer, String> hash = new HashMap<>(); 1094 hash.put(Sink.JUSTIFY_CENTER, "center"); 1095 hash.put(Sink.JUSTIFY_LEFT, "left"); 1096 hash.put(Sink.JUSTIFY_RIGHT, "right"); 1097 1098 String tdStyle = "text-align: " + hash.get(cellJustif[cellCount]) + ";"; 1099 if (attributes.isDefined(SinkEventAttributes.STYLE)) { 1100 tdStyle += " " 1101 + attributes.getAttribute(SinkEventAttributes.STYLE).toString(); 1102 } 1103 1104 attributes.addAttribute(SinkEventAttributes.STYLE, tdStyle); 1105 } 1106 } 1107 1108 if (attributes == null) { 1109 writeStartTag(t, null); 1110 } else { 1111 writeStartTag(t, SinkUtils.filterAttributes(attributes, SinkUtils.SINK_TD_ATTRIBUTES)); 1112 } 1113 } 1114 1115 /** {@inheritDoc} */ 1116 @Override 1117 public void tableCell_() { 1118 tableCell_(false); 1119 } 1120 1121 /** {@inheritDoc} */ 1122 @Override 1123 public void tableHeaderCell_() { 1124 tableCell_(true); 1125 } 1126 1127 /** 1128 * Ends a table cell. 1129 * 1130 * @param headerRow true if it is an header row 1131 * @see javax.swing.text.html.HTML.Tag#TH 1132 * @see javax.swing.text.html.HTML.Tag#TD 1133 */ 1134 private void tableCell_(boolean headerRow) { 1135 Tag t = (headerRow ? HtmlMarkup.TH : HtmlMarkup.TD); 1136 1137 writeEndTag(t); 1138 1139 if (!this.isCellJustifStack.isEmpty() 1140 && this.isCellJustifStack.getLast().equals(Boolean.TRUE) 1141 && !this.cellCountStack.isEmpty()) { 1142 int cellCount = Integer.parseInt(this.cellCountStack.removeLast().toString()); 1143 this.cellCountStack.addLast(++cellCount); 1144 } 1145 } 1146 1147 /** 1148 * {@inheritDoc} 1149 * @see javax.swing.text.html.HTML.Tag#CAPTION 1150 */ 1151 @Override 1152 public void tableCaption(SinkEventAttributes attributes) { 1153 StringWriter sw = new StringWriter(); 1154 this.tableCaptionWriterStack.addLast(sw); 1155 this.tableCaptionXMLWriterStack.addLast(new PrettyPrintXMLWriter(sw)); 1156 1157 // TODO: tableCaption should be written before tableRows (DOXIA-177) 1158 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_SECTION_ATTRIBUTES); 1159 1160 writeStartTag(HtmlMarkup.CAPTION, atts); 1161 } 1162 1163 /** 1164 * {@inheritDoc} 1165 * @see javax.swing.text.html.HTML.Tag#CAPTION 1166 */ 1167 @Override 1168 public void tableCaption_() { 1169 writeEndTag(HtmlMarkup.CAPTION); 1170 1171 if (!this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null) { 1172 this.tableCaptionStack.addLast( 1173 this.tableCaptionWriterStack.removeLast().toString()); 1174 this.tableCaptionXMLWriterStack.removeLast(); 1175 } 1176 } 1177 1178 /** 1179 * {@inheritDoc} 1180 * @see javax.swing.text.html.HTML.Tag#A 1181 */ 1182 @Override 1183 public void anchor(String name, SinkEventAttributes attributes) { 1184 Objects.requireNonNull(name, "name cannot be null"); 1185 1186 if (headFlag) { 1187 return; 1188 } 1189 1190 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BASE_ATTRIBUTES); 1191 1192 String id = name; 1193 1194 if (!DoxiaUtils.isValidId(id)) { 1195 id = DoxiaUtils.encodeId(name); 1196 1197 LOGGER.debug("{}Modified invalid anchor name '{}' to '{}'", getLocationLogPrefix(), name, id); 1198 } 1199 1200 MutableAttributeSet att = new SinkEventAttributeSet(); 1201 att.addAttribute(SinkEventAttributes.ID, id); 1202 att.addAttributes(atts); 1203 1204 writeStartTag(HtmlMarkup.A, att); 1205 } 1206 1207 /** 1208 * {@inheritDoc} 1209 * @see javax.swing.text.html.HTML.Tag#A 1210 */ 1211 @Override 1212 public void anchor_() { 1213 if (!headFlag) { 1214 writeEndTag(HtmlMarkup.A); 1215 } 1216 } 1217 1218 /** 1219 * The default style class for external link is <code>externalLink</code>. 1220 * 1221 * {@inheritDoc} 1222 * @see javax.swing.text.html.HTML.Tag#A 1223 **/ 1224 @Override 1225 public void link(String name, SinkEventAttributes attributes) { 1226 Objects.requireNonNull(name, "name cannot be null"); 1227 1228 if (headFlag) { 1229 return; 1230 } 1231 1232 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_LINK_ATTRIBUTES); 1233 1234 if (atts == null) { 1235 atts = new SinkEventAttributeSet(); 1236 } 1237 1238 if (DoxiaUtils.isExternalLink(name)) { 1239 String linkClass = "externalLink"; 1240 if (atts.isDefined(SinkEventAttributes.CLASS.toString())) { 1241 String givenLinkClass = (String) atts.getAttribute(SinkEventAttributes.CLASS.toString()); 1242 linkClass += " " + givenLinkClass; 1243 } 1244 1245 atts.addAttribute(SinkEventAttributes.CLASS, linkClass); 1246 } 1247 1248 atts.addAttribute(SinkEventAttributes.HREF, HtmlTools.escapeHTML(name)); 1249 1250 writeStartTag(HtmlMarkup.A, atts); 1251 } 1252 1253 /** 1254 * {@inheritDoc} 1255 * @see javax.swing.text.html.HTML.Tag#A 1256 */ 1257 @Override 1258 public void link_() { 1259 if (!headFlag) { 1260 writeEndTag(HtmlMarkup.A); 1261 } 1262 } 1263 1264 private void inlineSemantics(SinkEventAttributes attributes, String semantic, List<Tag> tags, Tag tag) { 1265 if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, semantic)) { 1266 SinkEventAttributes attributesNoSemantics = (SinkEventAttributes) attributes.copyAttributes(); 1267 attributesNoSemantics.removeAttribute(SinkEventAttributes.SEMANTICS); 1268 writeStartTag(tag, attributesNoSemantics); 1269 tags.add(0, tag); 1270 } 1271 } 1272 1273 /** {@inheritDoc} */ 1274 @Override 1275 public void inline(SinkEventAttributes attributes) { 1276 if (!headFlag) { 1277 List<Tag> tags = new ArrayList<>(); 1278 1279 if (attributes != null) { 1280 inlineSemantics(attributes, "emphasis", tags, HtmlMarkup.EM); 1281 inlineSemantics(attributes, "strong", tags, HtmlMarkup.STRONG); 1282 inlineSemantics(attributes, "small", tags, HtmlMarkup.SMALL); 1283 inlineSemantics(attributes, "line-through", tags, HtmlMarkup.S); 1284 inlineSemantics(attributes, "citation", tags, HtmlMarkup.CITE); 1285 inlineSemantics(attributes, "quote", tags, HtmlMarkup.Q); 1286 inlineSemantics(attributes, "definition", tags, HtmlMarkup.DFN); 1287 inlineSemantics(attributes, "abbreviation", tags, HtmlMarkup.ABBR); 1288 inlineSemantics(attributes, "italic", tags, HtmlMarkup.I); 1289 inlineSemantics(attributes, "bold", tags, HtmlMarkup.B); 1290 inlineSemantics(attributes, "code", tags, HtmlMarkup.CODE); 1291 inlineSemantics(attributes, "variable", tags, HtmlMarkup.VAR); 1292 inlineSemantics(attributes, "sample", tags, HtmlMarkup.SAMP); 1293 inlineSemantics(attributes, "keyboard", tags, HtmlMarkup.KBD); 1294 inlineSemantics(attributes, "superscript", tags, HtmlMarkup.SUP); 1295 inlineSemantics(attributes, "subscript", tags, HtmlMarkup.SUB); 1296 inlineSemantics(attributes, "annotation", tags, HtmlMarkup.U); 1297 inlineSemantics(attributes, "highlight", tags, HtmlMarkup.MARK); 1298 inlineSemantics(attributes, "ruby", tags, HtmlMarkup.RUBY); 1299 inlineSemantics(attributes, "rubyBase", tags, HtmlMarkup.RB); 1300 inlineSemantics(attributes, "rubyText", tags, HtmlMarkup.RT); 1301 inlineSemantics(attributes, "rubyTextContainer", tags, HtmlMarkup.RTC); 1302 inlineSemantics(attributes, "rubyParentheses", tags, HtmlMarkup.RP); 1303 inlineSemantics(attributes, "bidirectionalIsolation", tags, HtmlMarkup.BDI); 1304 inlineSemantics(attributes, "bidirectionalOverride", tags, HtmlMarkup.BDO); 1305 inlineSemantics(attributes, "phrase", tags, HtmlMarkup.SPAN); 1306 inlineSemantics(attributes, "insert", tags, HtmlMarkup.INS); 1307 inlineSemantics(attributes, "delete", tags, HtmlMarkup.DEL); 1308 } 1309 1310 inlineStack.push(tags); 1311 } 1312 } 1313 1314 /** {@inheritDoc} */ 1315 @Override 1316 public void inline_() { 1317 if (!headFlag) { 1318 for (Tag tag : inlineStack.pop()) { 1319 writeEndTag(tag); 1320 } 1321 } 1322 } 1323 1324 /** 1325 * {@inheritDoc} 1326 * @see javax.swing.text.html.HTML.Tag#I 1327 */ 1328 @Override 1329 public void italic() { 1330 inline(SinkEventAttributeSet.Semantics.ITALIC); 1331 } 1332 1333 /** 1334 * {@inheritDoc} 1335 * @see javax.swing.text.html.HTML.Tag#I 1336 */ 1337 @Override 1338 public void italic_() { 1339 inline_(); 1340 } 1341 1342 /** 1343 * {@inheritDoc} 1344 * @see javax.swing.text.html.HTML.Tag#B 1345 */ 1346 @Override 1347 public void bold() { 1348 inline(SinkEventAttributeSet.Semantics.BOLD); 1349 } 1350 1351 /** 1352 * {@inheritDoc} 1353 * @see javax.swing.text.html.HTML.Tag#B 1354 */ 1355 @Override 1356 public void bold_() { 1357 inline_(); 1358 } 1359 1360 /** 1361 * {@inheritDoc} 1362 * @see javax.swing.text.html.HTML.Tag#CODE 1363 */ 1364 @Override 1365 public void monospaced() { 1366 inline(SinkEventAttributeSet.Semantics.CODE); 1367 } 1368 1369 /** 1370 * {@inheritDoc} 1371 * @see javax.swing.text.html.HTML.Tag#CODE 1372 */ 1373 @Override 1374 public void monospaced_() { 1375 inline_(); 1376 } 1377 1378 /** 1379 * {@inheritDoc} 1380 * @see javax.swing.text.html.HTML.Tag#BR 1381 */ 1382 @Override 1383 public void lineBreak(SinkEventAttributes attributes) { 1384 if (headFlag || isVerbatim()) { 1385 getTextBuffer().append(EOL); 1386 } else { 1387 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BR_ATTRIBUTES); 1388 1389 writeSimpleTag(HtmlMarkup.BR, atts); 1390 } 1391 } 1392 1393 /** {@inheritDoc} */ 1394 @Override 1395 public void lineBreakOpportunity(SinkEventAttributes attributes) { 1396 if (!headFlag && !isVerbatim()) { 1397 MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_BR_ATTRIBUTES); 1398 1399 writeSimpleTag(HtmlMarkup.WBR, atts); 1400 } 1401 } 1402 1403 /** {@inheritDoc} */ 1404 @Override 1405 public void pageBreak() { 1406 comment(" PB "); 1407 } 1408 1409 /** {@inheritDoc} */ 1410 @Override 1411 public void nonBreakingSpace() { 1412 if (headFlag) { 1413 getTextBuffer().append(' '); 1414 } else { 1415 write(" "); 1416 } 1417 } 1418 1419 /** {@inheritDoc} */ 1420 @Override 1421 public void text(String text, SinkEventAttributes attributes) { 1422 if (attributes != null) { 1423 inline(attributes); 1424 } 1425 if (headFlag) { 1426 getTextBuffer().append(text); 1427 } else if (isVerbatim()) { 1428 verbatimContent(text); 1429 } else { 1430 content(text); 1431 } 1432 if (attributes != null) { 1433 inline_(); 1434 } 1435 } 1436 1437 /** {@inheritDoc} */ 1438 @Override 1439 public void rawText(String text) { 1440 if (headFlag) { 1441 getTextBuffer().append(text); 1442 } else { 1443 write(text); 1444 } 1445 } 1446 1447 /** {@inheritDoc} */ 1448 @Override 1449 public void comment(String comment) { 1450 if (comment != null) { 1451 final String originalComment = comment; 1452 1453 // http://www.w3.org/TR/2000/REC-xml-20001006#sec-comments 1454 while (comment.contains("--")) { 1455 comment = comment.replace("--", "- -"); 1456 } 1457 1458 if (comment.endsWith("-")) { 1459 comment += " "; 1460 } 1461 1462 if (!originalComment.equals(comment)) { 1463 LOGGER.warn( 1464 "{}Modified invalid comment '{}' to '{}'", getLocationLogPrefix(), originalComment, comment); 1465 } 1466 1467 final StringBuilder buffer = new StringBuilder(comment.length() + 7); 1468 1469 buffer.append(LESS_THAN).append(BANG).append(MINUS).append(MINUS); 1470 buffer.append(comment); 1471 buffer.append(MINUS).append(MINUS).append(GREATER_THAN); 1472 1473 write(buffer.toString()); 1474 } 1475 } 1476 1477 /** 1478 * {@inheritDoc} 1479 * 1480 * Add an unknown event. 1481 * This can be used to generate html tags for which no corresponding sink event exists. 1482 * 1483 * <p> 1484 * If {@link org.apache.maven.doxia.util.HtmlTools#getHtmlTag(String) HtmlTools.getHtmlTag(name)} 1485 * does not return null, the corresponding tag will be written. 1486 * </p> 1487 * 1488 * <p>For example, the div block</p> 1489 * 1490 * <pre> 1491 * <div class="detail" style="display:inline">text</div> 1492 * </pre> 1493 * 1494 * <p>can be generated via the following event sequence:</p> 1495 * 1496 * <pre> 1497 * SinkEventAttributeSet atts = new SinkEventAttributeSet(); 1498 * atts.addAttribute(SinkEventAttributes.CLASS, "detail"); 1499 * atts.addAttribute(SinkEventAttributes.STYLE, "display:inline"); 1500 * sink.unknown("div", new Object[]{new Integer(HtmlMarkup.TAG_TYPE_START)}, atts); 1501 * sink.text("text"); 1502 * sink.unknown("div", new Object[]{new Integer(HtmlMarkup.TAG_TYPE_END)}, null); 1503 * </pre> 1504 * 1505 * @param name the name of the event. If this is not a valid xhtml tag name 1506 * as defined in {@link org.apache.maven.doxia.markup.HtmlMarkup} then the event is ignored. 1507 * @param requiredParams If this is null or the first argument is not an Integer then the event is ignored. 1508 * The first argument should indicate the type of the unknown event, its integer value should be one of 1509 * {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_START TAG_TYPE_START}, 1510 * {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_END TAG_TYPE_END}, 1511 * {@link org.apache.maven.doxia.markup.HtmlMarkup#TAG_TYPE_SIMPLE TAG_TYPE_SIMPLE}, 1512 * {@link org.apache.maven.doxia.markup.HtmlMarkup#ENTITY_TYPE ENTITY_TYPE}, or 1513 * {@link org.apache.maven.doxia.markup.HtmlMarkup#CDATA_TYPE CDATA_TYPE}, 1514 * otherwise the event will be ignored. 1515 * @param attributes a set of attributes for the event. May be null. 1516 * The attributes will always be written, no validity check is performed. 1517 */ 1518 @Override 1519 public void unknown(String name, Object[] requiredParams, SinkEventAttributes attributes) { 1520 if (requiredParams == null || !(requiredParams[0] instanceof Integer)) { 1521 LOGGER.warn("{}No type information for unknown event '{}', ignoring!", getLocationLogPrefix(), name); 1522 1523 return; 1524 } 1525 1526 int tagType = (Integer) requiredParams[0]; 1527 1528 if (tagType == ENTITY_TYPE) { 1529 rawText(name); 1530 1531 return; 1532 } 1533 1534 if (tagType == CDATA_TYPE) { 1535 rawText(EOL + "//<![CDATA[" + requiredParams[1] + "]]>" + EOL); 1536 1537 return; 1538 } 1539 1540 Tag tag = HtmlTools.getHtmlTag(name); 1541 1542 if (tag == null) { 1543 LOGGER.warn("[]No HTML tag found for unknown event '{}', ignoring!", getLocationLogPrefix(), name); 1544 } else { 1545 if (tagType == TAG_TYPE_SIMPLE) { 1546 writeSimpleTag(tag, escapeAttributeValues(attributes)); 1547 } else if (tagType == TAG_TYPE_START) { 1548 writeStartTag(tag, escapeAttributeValues(attributes)); 1549 } else if (tagType == TAG_TYPE_END) { 1550 writeEndTag(tag); 1551 } else { 1552 LOGGER.warn("{}No type information for unknown event '{}', ignoring!", getLocationLogPrefix(), name); 1553 } 1554 } 1555 } 1556 1557 private SinkEventAttributes escapeAttributeValues(SinkEventAttributes attributes) { 1558 SinkEventAttributeSet set = new SinkEventAttributeSet(attributes.getAttributeCount()); 1559 1560 Enumeration<?> names = attributes.getAttributeNames(); 1561 1562 while (names.hasMoreElements()) { 1563 Object name = names.nextElement(); 1564 1565 set.addAttribute(name, escapeHTML(attributes.getAttribute(name).toString())); 1566 } 1567 1568 return set; 1569 } 1570 1571 /** {@inheritDoc} */ 1572 @Override 1573 public void flush() { 1574 writer.flush(); 1575 } 1576 1577 /** {@inheritDoc} */ 1578 @Override 1579 public void close() { 1580 writer.close(); 1581 1582 init(); 1583 } 1584 1585 // ---------------------------------------------------------------------- 1586 // 1587 // ---------------------------------------------------------------------- 1588 1589 /** 1590 * Write HTML escaped text to output. 1591 * 1592 * @param text The text to write. 1593 */ 1594 protected void content(String text) { 1595 // small hack due to DOXIA-314 1596 String txt = escapeHTML(text); 1597 txt = StringUtils.replace(txt, "&#", "&#"); 1598 write(txt); 1599 } 1600 1601 /** 1602 * Write HTML escaped text to output. 1603 * 1604 * @param text The text to write. 1605 */ 1606 protected void verbatimContent(String text) { 1607 write(escapeHTML(text)); 1608 } 1609 1610 /** 1611 * Forward to HtmlTools.escapeHTML(text). 1612 * 1613 * @param text the String to escape, may be null 1614 * @return the text escaped, "" if null String input 1615 * @see org.apache.maven.doxia.util.HtmlTools#escapeHTML(String) 1616 */ 1617 protected static String escapeHTML(String text) { 1618 return HtmlTools.escapeHTML(text, false); 1619 } 1620 1621 /** 1622 * Forward to HtmlTools.encodeURL(text). 1623 * 1624 * @param text the String to encode, may be null. 1625 * @return the text encoded, null if null String input. 1626 * @see org.apache.maven.doxia.util.HtmlTools#encodeURL(String) 1627 */ 1628 protected static String encodeURL(String text) { 1629 return HtmlTools.encodeURL(text); 1630 } 1631 1632 /** {@inheritDoc} */ 1633 protected void write(String text) { 1634 if (!this.tableCaptionXMLWriterStack.isEmpty() && this.tableCaptionXMLWriterStack.getLast() != null) { 1635 this.tableCaptionXMLWriterStack.getLast().writeMarkup(unifyEOLs(text)); 1636 } else if (!this.tableContentWriterStack.isEmpty() && this.tableContentWriterStack.getLast() != null) { 1637 this.tableContentWriterStack.getLast().write(unifyEOLs(text)); 1638 } else { 1639 writer.write(unifyEOLs(text)); 1640 } 1641 } 1642 1643 /** {@inheritDoc} */ 1644 @Override 1645 protected void writeStartTag(Tag t, MutableAttributeSet att, boolean isSimpleTag) { 1646 if (this.tableCaptionXMLWriterStack.isEmpty()) { 1647 super.writeStartTag(t, att, isSimpleTag); 1648 } else { 1649 String tag = (getNameSpace() != null ? getNameSpace() + ":" : "") + t.toString(); 1650 this.tableCaptionXMLWriterStack.getLast().startElement(tag); 1651 1652 if (att != null) { 1653 Enumeration<?> names = att.getAttributeNames(); 1654 while (names.hasMoreElements()) { 1655 Object key = names.nextElement(); 1656 Object value = att.getAttribute(key); 1657 1658 this.tableCaptionXMLWriterStack.getLast().addAttribute(key.toString(), value.toString()); 1659 } 1660 } 1661 1662 if (isSimpleTag) { 1663 this.tableCaptionXMLWriterStack.getLast().endElement(); 1664 } 1665 } 1666 } 1667 1668 /** {@inheritDoc} */ 1669 @Override 1670 protected void writeEndTag(Tag t) { 1671 if (this.tableCaptionXMLWriterStack.isEmpty()) { 1672 super.writeEndTag(t); 1673 } else { 1674 this.tableCaptionXMLWriterStack.getLast().endElement(); 1675 } 1676 } 1677}