1 /*
   2  * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.doclets.formats.html.markup;
  27 
  28 import java.io.IOException;
  29 import java.io.Writer;
  30 import java.util.*;
  31 import java.nio.charset.*;
  32 
  33 import com.sun.tools.doclets.internal.toolkit.Content;
  34 import com.sun.tools.doclets.internal.toolkit.util.*;
  35 import com.sun.tools.doclets.formats.html.markup.HtmlAttr.Role;
  36 
  37 /**
  38  * Class for generating HTML tree for javadoc output.
  39  *
  40  *  <p><b>This is NOT part of any supported API.
  41  *  If you write code that depends on this, you do so at your own risk.
  42  *  This code and its internal interfaces are subject to change or
  43  *  deletion without notice.</b>
  44  *
  45  * @author Bhavesh Patel
  46  */
  47 public class HtmlTree extends Content {
  48 
  49     private HtmlTag htmlTag;
  50     private Map<HtmlAttr,String> attrs = Collections.<HtmlAttr,String>emptyMap();
  51     private List<Content> content = Collections.<Content>emptyList();
  52     public static final Content EMPTY = new StringContent("");
  53 
  54     /**
  55      * Constructor to construct HtmlTree object.
  56      *
  57      * @param tag HTML tag for the HtmlTree object
  58      */
  59     public HtmlTree(HtmlTag tag) {
  60         htmlTag = nullCheck(tag);
  61     }
  62 
  63     /**
  64      * Constructor to construct HtmlTree object.
  65      *
  66      * @param tag HTML tag for the HtmlTree object
  67      * @param contents contents to be added to the tree
  68      */
  69     public HtmlTree(HtmlTag tag, Content... contents) {
  70         this(tag);
  71         for (Content content: contents)
  72             addContent(content);
  73     }
  74 
  75     /**
  76      * Adds an attribute for the HTML tag.
  77      *
  78      * @param attrName name of the attribute
  79      * @param attrValue value of the attribute
  80      */
  81     public void addAttr(HtmlAttr attrName, String attrValue) {
  82         if (attrs.isEmpty())
  83             attrs = new LinkedHashMap<>(3);
  84         attrs.put(nullCheck(attrName), escapeHtmlChars(attrValue));
  85     }
  86 
  87     public void setTitle(Content body) {
  88         addAttr(HtmlAttr.TITLE, stripHtml(body));
  89     }
  90 
  91     public void setRole(Role role) {
  92         addAttr(HtmlAttr.ROLE, role.toString());
  93     }
  94 
  95     /**
  96      * Adds a style for the HTML tag.
  97      *
  98      * @param style style to be added
  99      */
 100     public void addStyle(HtmlStyle style) {
 101         addAttr(HtmlAttr.CLASS, style.toString());
 102     }
 103 
 104     /**
 105      * Adds content for the HTML tag.
 106      *
 107      * @param tagContent tag content to be added
 108      */
 109     public void addContent(Content tagContent) {
 110         if (tagContent instanceof ContentBuilder) {
 111             for (Content content: ((ContentBuilder)tagContent).contents) {
 112                 addContent(content);
 113             }
 114         }
 115         else if (tagContent == HtmlTree.EMPTY || tagContent.isValid()) {
 116             if (content.isEmpty())
 117                 content = new ArrayList<>();
 118             content.add(tagContent);
 119         }
 120     }
 121 
 122     /**
 123      * This method adds a string content to the htmltree. If the last content member
 124      * added is a StringContent, append the string to that StringContent or else
 125      * create a new StringContent and add it to the html tree.
 126      *
 127      * @param stringContent string content that needs to be added
 128      */
 129     public void addContent(String stringContent) {
 130         if (!content.isEmpty()) {
 131             Content lastContent = content.get(content.size() - 1);
 132             if (lastContent instanceof StringContent)
 133                 lastContent.addContent(stringContent);
 134             else
 135                 addContent(new StringContent(stringContent));
 136         }
 137         else
 138             addContent(new StringContent(stringContent));
 139     }
 140 
 141     public int charCount() {
 142         int n = 0;
 143         for (Content c : content)
 144             n += c.charCount();
 145         return n;
 146     }
 147 
 148     /**
 149      * Given a string, escape all special html characters and
 150      * return the result.
 151      *
 152      * @param s The string to check.
 153      * @return the original string with all of the HTML characters escaped.
 154      */
 155     private static String escapeHtmlChars(String s) {
 156         for (int i = 0; i < s.length(); i++) {
 157             char ch = s.charAt(i);
 158             switch (ch) {
 159                 // only start building a new string if we need to
 160                 case '<': case '>': case '&':
 161                     StringBuilder sb = new StringBuilder(s.substring(0, i));
 162                     for ( ; i < s.length(); i++) {
 163                         ch = s.charAt(i);
 164                         switch (ch) {
 165                             case '<': sb.append("&lt;");  break;
 166                             case '>': sb.append("&gt;");  break;
 167                             case '&': sb.append("&amp;"); break;
 168                             default:  sb.append(ch);      break;
 169                         }
 170                     }
 171                     return sb.toString();
 172             }
 173         }
 174         return s;
 175     }
 176 
 177     /**
 178      * A set of ASCII URI characters to be left unencoded.
 179      */
 180     public static final BitSet NONENCODING_CHARS = new BitSet(256);
 181 
 182     static {
 183         // alphabetic characters
 184         for (int i = 'a'; i <= 'z'; i++) {
 185             NONENCODING_CHARS.set(i);
 186         }
 187         for (int i = 'A'; i <= 'Z'; i++) {
 188             NONENCODING_CHARS.set(i);
 189         }
 190         // numeric characters
 191         for (int i = '0'; i <= '9'; i++) {
 192             NONENCODING_CHARS.set(i);
 193         }
 194         // Reserved characters as per RFC 3986. These are set of delimiting characters.
 195         String noEnc = ":/?#[]@!$&'()*+,;=";
 196         // Unreserved characters as per RFC 3986 which should not be percent encoded.
 197         noEnc += "-._~";
 198         for (int i = 0; i < noEnc.length(); i++) {
 199             NONENCODING_CHARS.set(noEnc.charAt(i));
 200         }
 201     }
 202 
 203     private static String encodeURL(String url) {
 204         StringBuilder sb = new StringBuilder();
 205         for (byte c : url.getBytes(Charset.forName("UTF-8"))) {
 206             if (NONENCODING_CHARS.get(c & 0xFF)) {
 207                 sb.append((char) c);
 208             } else {
 209                 sb.append(String.format("%%%02X", c & 0xFF));
 210             }
 211         }
 212         return sb.toString();
 213     }
 214 
 215     /**
 216      * Generates an HTML anchor tag.
 217      *
 218      * @param ref reference url for the anchor tag
 219      * @param body content for the anchor tag
 220      * @return an HtmlTree object
 221      */
 222     public static HtmlTree A(String ref, Content body) {
 223         HtmlTree htmltree = new HtmlTree(HtmlTag.A, nullCheck(body));
 224         htmltree.addAttr(HtmlAttr.HREF, encodeURL(ref));
 225         return htmltree;
 226     }
 227 
 228     /**
 229      * Generates an HTML anchor tag with an id or a name attribute and content.
 230      *
 231      * @param htmlVersion the version of the generated HTML
 232      * @param attr name or id attribute for the anchor tag
 233      * @param body content for the anchor tag
 234      * @return an HtmlTree object
 235      */
 236     public static HtmlTree A(HtmlVersion htmlVersion, String attr, Content body) {
 237         HtmlTree htmltree = new HtmlTree(HtmlTag.A);
 238         htmltree.addAttr((htmlVersion == HtmlVersion.HTML4)
 239                 ? HtmlAttr.NAME
 240                 : HtmlAttr.ID,
 241                 nullCheck(attr));
 242         htmltree.addContent(nullCheck(body));
 243         return htmltree;
 244     }
 245 
 246     /**
 247      * Generates an HTML anchor tag with id attribute and a body.
 248      *
 249      * @param id id for the anchor tag
 250      * @param body body for the anchor tag
 251      * @return an HtmlTree object
 252      */
 253     public static HtmlTree A_ID(String id, Content body) {
 254         HtmlTree htmltree = new HtmlTree(HtmlTag.A);
 255         htmltree.addAttr(HtmlAttr.ID, nullCheck(id));
 256         htmltree.addContent(nullCheck(body));
 257         return htmltree;
 258     }
 259 
 260     /**
 261      * Generates a CAPTION tag with some content.
 262      *
 263      * @param body content for the tag
 264      * @return an HtmlTree object for the CAPTION tag
 265      */
 266     public static HtmlTree CAPTION(Content body) {
 267         HtmlTree htmltree = new HtmlTree(HtmlTag.CAPTION, nullCheck(body));
 268         return htmltree;
 269     }
 270 
 271     /**
 272      * Generates a CODE tag with some content.
 273      *
 274      * @param body content for the tag
 275      * @return an HtmlTree object for the CODE tag
 276      */
 277     public static HtmlTree CODE(Content body) {
 278         HtmlTree htmltree = new HtmlTree(HtmlTag.CODE, nullCheck(body));
 279         return htmltree;
 280     }
 281 
 282     /**
 283      * Generates a DD tag with some content.
 284      *
 285      * @param body content for the tag
 286      * @return an HtmlTree object for the DD tag
 287      */
 288     public static HtmlTree DD(Content body) {
 289         HtmlTree htmltree = new HtmlTree(HtmlTag.DD, nullCheck(body));
 290         return htmltree;
 291     }
 292 
 293     /**
 294      * Generates a DL tag with some content.
 295      *
 296      * @param body content for the tag
 297      * @return an HtmlTree object for the DL tag
 298      */
 299     public static HtmlTree DL(Content body) {
 300         HtmlTree htmltree = new HtmlTree(HtmlTag.DL, nullCheck(body));
 301         return htmltree;
 302     }
 303 
 304     /**
 305      * Generates a DIV tag with the style class attributes. It also encloses
 306      * a content.
 307      *
 308      * @param styleClass stylesheet class for the tag
 309      * @param body content for the tag
 310      * @return an HtmlTree object for the DIV tag
 311      */
 312     public static HtmlTree DIV(HtmlStyle styleClass, Content body) {
 313         HtmlTree htmltree = new HtmlTree(HtmlTag.DIV, nullCheck(body));
 314         if (styleClass != null)
 315             htmltree.addStyle(styleClass);
 316         return htmltree;
 317     }
 318 
 319     /**
 320      * Generates a DIV tag with some content.
 321      *
 322      * @param body content for the tag
 323      * @return an HtmlTree object for the DIV tag
 324      */
 325     public static HtmlTree DIV(Content body) {
 326         return DIV(null, body);
 327     }
 328 
 329     /**
 330      * Generates a DT tag with some content.
 331      *
 332      * @param body content for the tag
 333      * @return an HtmlTree object for the DT tag
 334      */
 335     public static HtmlTree DT(Content body) {
 336         HtmlTree htmltree = new HtmlTree(HtmlTag.DT, nullCheck(body));
 337         return htmltree;
 338     }
 339 
 340     /**
 341      * Generates a FOOTER tag with role attribute.
 342      *
 343      * @return an HtmlTree object for the FOOTER tag
 344      */
 345     public static HtmlTree FOOTER() {
 346         HtmlTree htmltree = new HtmlTree(HtmlTag.FOOTER);
 347         htmltree.setRole(Role.CONTENTINFO);
 348         return htmltree;
 349     }
 350 
 351     /**
 352      * Generates a HEADER tag with role attribute.
 353      *
 354      * @return an HtmlTree object for the HEADER tag
 355      */
 356     public static HtmlTree HEADER() {
 357         HtmlTree htmltree = new HtmlTree(HtmlTag.HEADER);
 358         htmltree.setRole(Role.BANNER);
 359         return htmltree;
 360     }
 361 
 362     /**
 363      * Generates a heading tag (h1 to h6) with the title and style class attributes. It also encloses
 364      * a content.
 365      *
 366      * @param headingTag the heading tag to be generated
 367      * @param printTitle true if title for the tag needs to be printed else false
 368      * @param styleClass stylesheet class for the tag
 369      * @param body content for the tag
 370      * @return an HtmlTree object for the tag
 371      */
 372     public static HtmlTree HEADING(HtmlTag headingTag, boolean printTitle,
 373             HtmlStyle styleClass, Content body) {
 374         HtmlTree htmltree = new HtmlTree(headingTag, nullCheck(body));
 375         if (printTitle)
 376             htmltree.setTitle(body);
 377         if (styleClass != null)
 378             htmltree.addStyle(styleClass);
 379         return htmltree;
 380     }
 381 
 382     /**
 383      * Generates a heading tag (h1 to h6) with style class attribute. It also encloses
 384      * a content.
 385      *
 386      * @param headingTag the heading tag to be generated
 387      * @param styleClass stylesheet class for the tag
 388      * @param body content for the tag
 389      * @return an HtmlTree object for the tag
 390      */
 391     public static HtmlTree HEADING(HtmlTag headingTag, HtmlStyle styleClass, Content body) {
 392         return HEADING(headingTag, false, styleClass, body);
 393     }
 394 
 395     /**
 396      * Generates a heading tag (h1 to h6) with the title attribute. It also encloses
 397      * a content.
 398      *
 399      * @param headingTag the heading tag to be generated
 400      * @param printTitle true if the title for the tag needs to be printed else false
 401      * @param body content for the tag
 402      * @return an HtmlTree object for the tag
 403      */
 404     public static HtmlTree HEADING(HtmlTag headingTag, boolean printTitle, Content body) {
 405         return HEADING(headingTag, printTitle, null, body);
 406     }
 407 
 408     /**
 409      * Generates a heading tag (h1 to h6)  with some content.
 410      *
 411      * @param headingTag the heading tag to be generated
 412      * @param body content for the tag
 413      * @return an HtmlTree object for the tag
 414      */
 415     public static HtmlTree HEADING(HtmlTag headingTag, Content body) {
 416         return HEADING(headingTag, false, null, body);
 417     }
 418 
 419     /**
 420      * Generates an HTML tag with lang attribute. It also adds head and body
 421      * content to the HTML tree.
 422      *
 423      * @param lang language for the HTML document
 424      * @param head head for the HTML tag
 425      * @param body body for the HTML tag
 426      * @return an HtmlTree object for the HTML tag
 427      */
 428     public static HtmlTree HTML(String lang, Content head, Content body) {
 429         HtmlTree htmltree = new HtmlTree(HtmlTag.HTML, nullCheck(head), nullCheck(body));
 430         htmltree.addAttr(HtmlAttr.LANG, nullCheck(lang));
 431         return htmltree;
 432     }
 433 
 434     /**
 435      * Generates a IFRAME tag.
 436      *
 437      * @param src the url of the document to be shown in the frame
 438      * @param name specifies the name of the frame
 439      * @param title the title for the frame
 440      * @return an HtmlTree object for the IFRAME tag
 441      */
 442     public static HtmlTree IFRAME(String src, String name, String title) {
 443         HtmlTree htmltree = new HtmlTree(HtmlTag.IFRAME);
 444         htmltree.addAttr(HtmlAttr.SRC, nullCheck(src));
 445         htmltree.addAttr(HtmlAttr.NAME, nullCheck(name));
 446         htmltree.addAttr(HtmlAttr.TITLE, nullCheck(title));
 447         return htmltree;
 448     }
 449 
 450     /**
 451      * Generates a INPUT tag with some id.
 452      *
 453      * @param type the type of input
 454      * @param id id for the tag
 455      * @return an HtmlTree object for the INPUT tag
 456      */
 457     public static HtmlTree INPUT(String type, String id) {
 458         HtmlTree htmltree = new HtmlTree(HtmlTag.INPUT);
 459         htmltree.addAttr(HtmlAttr.TYPE, nullCheck(type));
 460         htmltree.addAttr(HtmlAttr.ID, nullCheck(id));
 461         htmltree.addAttr(HtmlAttr.VALUE, " ");
 462         htmltree.addAttr(HtmlAttr.DISABLED, "disabled");
 463         return htmltree;
 464     }
 465 
 466     /**
 467      * Generates a LI tag with some content.
 468      *
 469      * @param body content for the tag
 470      * @return an HtmlTree object for the LI tag
 471      */
 472     public static HtmlTree LI(Content body) {
 473         return LI(null, body);
 474     }
 475 
 476     /**
 477      * Generates a LI tag with some content.
 478      *
 479      * @param styleClass style for the tag
 480      * @param body content for the tag
 481      * @return an HtmlTree object for the LI tag
 482      */
 483     public static HtmlTree LI(HtmlStyle styleClass, Content body) {
 484         HtmlTree htmltree = new HtmlTree(HtmlTag.LI, nullCheck(body));
 485         if (styleClass != null)
 486             htmltree.addStyle(styleClass);
 487         return htmltree;
 488     }
 489 
 490     /**
 491      * Generates a LINK tag with the rel, type, href and title attributes.
 492      *
 493      * @param rel relevance of the link
 494      * @param type type of link
 495      * @param href the path for the link
 496      * @param title title for the link
 497      * @return an HtmlTree object for the LINK tag
 498      */
 499     public static HtmlTree LINK(String rel, String type, String href, String title) {
 500         HtmlTree htmltree = new HtmlTree(HtmlTag.LINK);
 501         htmltree.addAttr(HtmlAttr.REL, nullCheck(rel));
 502         htmltree.addAttr(HtmlAttr.TYPE, nullCheck(type));
 503         htmltree.addAttr(HtmlAttr.HREF, nullCheck(href));
 504         htmltree.addAttr(HtmlAttr.TITLE, nullCheck(title));
 505         return htmltree;
 506     }
 507 
 508     /**
 509      * Generates a MAIN tag with role attribute.
 510      *
 511      * @return an HtmlTree object for the MAIN tag
 512      */
 513     public static HtmlTree MAIN() {
 514         HtmlTree htmltree = new HtmlTree(HtmlTag.MAIN);
 515         htmltree.setRole(Role.MAIN);
 516         return htmltree;
 517     }
 518 
 519     /**
 520      * Generates a MAIN tag with role attribute and some content.
 521      *
 522      * @param body content of the MAIN tag
 523      * @return an HtmlTree object for the MAIN tag
 524      */
 525     public static HtmlTree MAIN(Content body) {
 526         HtmlTree htmltree = new HtmlTree(HtmlTag.MAIN, nullCheck(body));
 527         htmltree.setRole(Role.MAIN);
 528         return htmltree;
 529     }
 530 
 531     /**
 532      * Generates a MAIN tag with role attribute, style attribute and some content.
 533      *
 534      * @param styleClass style of the MAIN tag
 535      * @param body content of the MAIN tag
 536      * @return an HtmlTree object for the MAIN tag
 537      */
 538     public static HtmlTree MAIN(HtmlStyle styleClass, Content body) {
 539         HtmlTree htmltree = HtmlTree.MAIN(body);
 540         if (styleClass != null) {
 541             htmltree.addStyle(styleClass);
 542         }
 543         return htmltree;
 544     }
 545 
 546     /**
 547      * Generates a META tag with the http-equiv, content and charset attributes.
 548      *
 549      * @param httpEquiv http equiv attribute for the META tag
 550      * @param content type of content
 551      * @param charSet character set used
 552      * @return an HtmlTree object for the META tag
 553      */
 554     public static HtmlTree META(String httpEquiv, String content, String charSet) {
 555         HtmlTree htmltree = new HtmlTree(HtmlTag.META);
 556         String contentCharset = content + "; charset=" + charSet;
 557         htmltree.addAttr(HtmlAttr.HTTP_EQUIV, nullCheck(httpEquiv));
 558         htmltree.addAttr(HtmlAttr.CONTENT, contentCharset);
 559         return htmltree;
 560     }
 561 
 562     /**
 563      * Generates a META tag with the name and content attributes.
 564      *
 565      * @param name name attribute
 566      * @param content type of content
 567      * @return an HtmlTree object for the META tag
 568      */
 569     public static HtmlTree META(String name, String content) {
 570         HtmlTree htmltree = new HtmlTree(HtmlTag.META);
 571         htmltree.addAttr(HtmlAttr.NAME, nullCheck(name));
 572         htmltree.addAttr(HtmlAttr.CONTENT, nullCheck(content));
 573         return htmltree;
 574     }
 575 
 576     /**
 577      * Generates a NAV tag with the role attribute.
 578      *
 579      * @return an HtmlTree object for the NAV tag
 580      */
 581     public static HtmlTree NAV() {
 582         HtmlTree htmltree = new HtmlTree(HtmlTag.NAV);
 583         htmltree.setRole(Role.NAVIGATION);
 584         return htmltree;
 585     }
 586 
 587     /**
 588      * Generates a NOSCRIPT tag with some content.
 589      *
 590      * @param body content of the noscript tag
 591      * @return an HtmlTree object for the NOSCRIPT tag
 592      */
 593     public static HtmlTree NOSCRIPT(Content body) {
 594         HtmlTree htmltree = new HtmlTree(HtmlTag.NOSCRIPT, nullCheck(body));
 595         return htmltree;
 596     }
 597 
 598     /**
 599      * Generates a P tag with some content.
 600      *
 601      * @param body content of the Paragraph tag
 602      * @return an HtmlTree object for the P tag
 603      */
 604     public static HtmlTree P(Content body) {
 605         return P(null, body);
 606     }
 607 
 608     /**
 609      * Generates a P tag with some content.
 610      *
 611      * @param styleClass style of the Paragraph tag
 612      * @param body content of the Paragraph tag
 613      * @return an HtmlTree object for the P tag
 614      */
 615     public static HtmlTree P(HtmlStyle styleClass, Content body) {
 616         HtmlTree htmltree = new HtmlTree(HtmlTag.P, nullCheck(body));
 617         if (styleClass != null)
 618             htmltree.addStyle(styleClass);
 619         return htmltree;
 620     }
 621 
 622     /**
 623      * Generates a SCRIPT tag with the type and src attributes.
 624      *
 625      * @param type type of link
 626      * @param src the path for the script
 627      * @return an HtmlTree object for the SCRIPT tag
 628      */
 629     public static HtmlTree SCRIPT(String src) {
 630         HtmlTree htmltree = HtmlTree.SCRIPT();
 631         htmltree.addAttr(HtmlAttr.SRC, nullCheck(src));
 632         return htmltree;
 633     }
 634 
 635     /**
 636      * Generates a SCRIPT tag with the type attribute.
 637      *
 638      * @return an HtmlTree object for the SCRIPT tag
 639      */
 640     public static HtmlTree SCRIPT() {
 641         HtmlTree htmltree = new HtmlTree(HtmlTag.SCRIPT);
 642         htmltree.addAttr(HtmlAttr.TYPE, "text/javascript");
 643         return htmltree;
 644     }
 645 
 646     /**
 647      * Generates a SECTION tag with role attribute.
 648      *
 649      * @return an HtmlTree object for the SECTION tag
 650      */
 651     public static HtmlTree SECTION() {
 652         HtmlTree htmltree = new HtmlTree(HtmlTag.SECTION);
 653         htmltree.setRole(Role.REGION);
 654         return htmltree;
 655     }
 656 
 657     /**
 658      * Generates a SECTION tag with role attribute and some content.
 659      *
 660      * @param body content of the section tag
 661      * @return an HtmlTree object for the SECTION tag
 662      */
 663     public static HtmlTree SECTION(Content body) {
 664         HtmlTree htmltree = new HtmlTree(HtmlTag.SECTION, nullCheck(body));
 665         htmltree.setRole(Role.REGION);
 666         return htmltree;
 667     }
 668 
 669     /**
 670      * Generates a SMALL tag with some content.
 671      *
 672      * @param body content for the tag
 673      * @return an HtmlTree object for the SMALL tag
 674      */
 675     public static HtmlTree SMALL(Content body) {
 676         HtmlTree htmltree = new HtmlTree(HtmlTag.SMALL, nullCheck(body));
 677         return htmltree;
 678     }
 679 
 680     /**
 681      * Generates a SPAN tag with some content.
 682      *
 683      * @param body content for the tag
 684      * @return an HtmlTree object for the SPAN tag
 685      */
 686     public static HtmlTree SPAN(Content body) {
 687         return SPAN(null, body);
 688     }
 689 
 690     /**
 691      * Generates a SPAN tag with style class attribute and some content.
 692      *
 693      * @param styleClass style class for the tag
 694      * @param body content for the tag
 695      * @return an HtmlTree object for the SPAN tag
 696      */
 697     public static HtmlTree SPAN(HtmlStyle styleClass, Content body) {
 698         HtmlTree htmltree = new HtmlTree(HtmlTag.SPAN, nullCheck(body));
 699         if (styleClass != null)
 700             htmltree.addStyle(styleClass);
 701         return htmltree;
 702     }
 703 
 704     /**
 705      * Generates a SPAN tag with id and style class attributes. It also encloses
 706      * a content.
 707      *
 708      * @param id the id for the tag
 709      * @param styleClass stylesheet class for the tag
 710      * @param body content for the tag
 711      * @return an HtmlTree object for the SPAN tag
 712      */
 713     public static HtmlTree SPAN(String id, HtmlStyle styleClass, Content body) {
 714         HtmlTree htmltree = new HtmlTree(HtmlTag.SPAN, nullCheck(body));
 715         htmltree.addAttr(HtmlAttr.ID, nullCheck(id));
 716         if (styleClass != null)
 717             htmltree.addStyle(styleClass);
 718         return htmltree;
 719     }
 720 
 721     /**
 722      * Generates a Table tag with style class and summary attributes and some content.
 723      *
 724      * @param styleClass style of the table
 725      * @param summary summary for the table
 726      * @param body content for the table
 727      * @return an HtmlTree object for the TABLE tag
 728      */
 729     public static HtmlTree TABLE(HtmlStyle styleClass, String summary, Content body) {
 730         HtmlTree htmltree = new HtmlTree(HtmlTag.TABLE, nullCheck(body));
 731         if (styleClass != null)
 732             htmltree.addStyle(styleClass);
 733         htmltree.addAttr(HtmlAttr.SUMMARY, nullCheck(summary));
 734         return htmltree;
 735     }
 736 
 737     /**
 738      * Generates a Table tag with style class attribute and some content.
 739      *
 740      * @param styleClass style of the table
 741      * @param body content for the table
 742      * @return an HtmlTree object for the TABLE tag
 743      */
 744     public static HtmlTree TABLE(HtmlStyle styleClass, Content body) {
 745         HtmlTree htmltree = new HtmlTree(HtmlTag.TABLE, nullCheck(body));
 746         if (styleClass != null) {
 747             htmltree.addStyle(styleClass);
 748         }
 749         return htmltree;
 750     }
 751 
 752     /**
 753      * Generates a TD tag with style class attribute and some content.
 754      *
 755      * @param styleClass style for the tag
 756      * @param body content for the tag
 757      * @return an HtmlTree object for the TD tag
 758      */
 759     public static HtmlTree TD(HtmlStyle styleClass, Content body) {
 760         HtmlTree htmltree = new HtmlTree(HtmlTag.TD, nullCheck(body));
 761         if (styleClass != null)
 762             htmltree.addStyle(styleClass);
 763         return htmltree;
 764     }
 765 
 766     /**
 767      * Generates a TD tag for an HTML table with some content.
 768      *
 769      * @param body content for the tag
 770      * @return an HtmlTree object for the TD tag
 771      */
 772     public static HtmlTree TD(Content body) {
 773         return TD(null, body);
 774     }
 775 
 776     /**
 777      * Generates a TH tag with style class and scope attributes and some content.
 778      *
 779      * @param styleClass style for the tag
 780      * @param scope scope of the tag
 781      * @param body content for the tag
 782      * @return an HtmlTree object for the TH tag
 783      */
 784     public static HtmlTree TH(HtmlStyle styleClass, String scope, Content body) {
 785         HtmlTree htmltree = new HtmlTree(HtmlTag.TH, nullCheck(body));
 786         if (styleClass != null)
 787             htmltree.addStyle(styleClass);
 788         htmltree.addAttr(HtmlAttr.SCOPE, nullCheck(scope));
 789         return htmltree;
 790     }
 791 
 792     /**
 793      * Generates a TH tag with scope attribute and some content.
 794      *
 795      * @param scope scope of the tag
 796      * @param body content for the tag
 797      * @return an HtmlTree object for the TH tag
 798      */
 799     public static HtmlTree TH(String scope, Content body) {
 800         return TH(null, scope, body);
 801     }
 802 
 803     /**
 804      * Generates a TITLE tag with some content.
 805      *
 806      * @param body content for the tag
 807      * @return an HtmlTree object for the TITLE tag
 808      */
 809     public static HtmlTree TITLE(Content body) {
 810         HtmlTree htmltree = new HtmlTree(HtmlTag.TITLE, nullCheck(body));
 811         return htmltree;
 812     }
 813 
 814     /**
 815      * Generates a TR tag for an HTML table with some content.
 816      *
 817      * @param body content for the tag
 818      * @return an HtmlTree object for the TR tag
 819      */
 820     public static HtmlTree TR(Content body) {
 821         HtmlTree htmltree = new HtmlTree(HtmlTag.TR, nullCheck(body));
 822         return htmltree;
 823     }
 824 
 825     /**
 826      * Generates a UL tag with the style class attribute and some content.
 827      *
 828      * @param styleClass style for the tag
 829      * @param body content for the tag
 830      * @return an HtmlTree object for the UL tag
 831      */
 832     public static HtmlTree UL(HtmlStyle styleClass, Content body) {
 833         HtmlTree htmltree = new HtmlTree(HtmlTag.UL, nullCheck(body));
 834         htmltree.addStyle(nullCheck(styleClass));
 835         return htmltree;
 836     }
 837 
 838     /**
 839      * {@inheritDoc}
 840      */
 841     public boolean isEmpty() {
 842         return (!hasContent() && !hasAttrs());
 843     }
 844 
 845     /**
 846      * Returns true if the HTML tree has content.
 847      *
 848      * @return true if the HTML tree has content else return false
 849      */
 850     public boolean hasContent() {
 851         return (!content.isEmpty());
 852     }
 853 
 854     /**
 855      * Returns true if the HTML tree has attributes.
 856      *
 857      * @return true if the HTML tree has attributes else return false
 858      */
 859     public boolean hasAttrs() {
 860         return (!attrs.isEmpty());
 861     }
 862 
 863     /**
 864      * Returns true if the HTML tree has a specific attribute.
 865      *
 866      * @param attrName name of the attribute to check within the HTML tree
 867      * @return true if the HTML tree has the specified attribute else return false
 868      */
 869     public boolean hasAttr(HtmlAttr attrName) {
 870         return (attrs.containsKey(attrName));
 871     }
 872 
 873     /**
 874      * Returns true if the HTML tree is valid. This check is more specific to
 875      * standard doclet and not exactly similar to W3C specifications. But it
 876      * ensures HTML validation.
 877      *
 878      * @return true if the HTML tree is valid
 879      */
 880     public boolean isValid() {
 881         switch (htmlTag) {
 882             case A :
 883                 return (hasAttr(HtmlAttr.NAME) || hasAttr(HtmlAttr.ID) || (hasAttr(HtmlAttr.HREF) && hasContent()));
 884             case BR :
 885                 return (!hasContent() && (!hasAttrs() || hasAttr(HtmlAttr.CLEAR)));
 886             case IFRAME :
 887                 return (hasAttr(HtmlAttr.SRC) && !hasContent());
 888             case HR :
 889             case INPUT:
 890                 return (!hasContent());
 891             case IMG :
 892                 return (hasAttr(HtmlAttr.SRC) && hasAttr(HtmlAttr.ALT) && !hasContent());
 893             case LINK :
 894                 return (hasAttr(HtmlAttr.HREF) && !hasContent());
 895             case META :
 896                 return (hasAttr(HtmlAttr.CONTENT) && !hasContent());
 897             case SCRIPT :
 898                 return ((hasAttr(HtmlAttr.TYPE) && hasAttr(HtmlAttr.SRC) && !hasContent()) ||
 899                         (hasAttr(HtmlAttr.TYPE) && hasContent()));
 900             default :
 901                 return hasContent();
 902         }
 903     }
 904 
 905     /**
 906      * Returns true if the element is an inline element.
 907      *
 908      * @return true if the HTML tag is an inline element
 909      */
 910     public boolean isInline() {
 911         return (htmlTag.blockType == HtmlTag.BlockType.INLINE);
 912     }
 913 
 914     /**
 915      * {@inheritDoc}
 916      */
 917     @Override
 918     public boolean write(Writer out, boolean atNewline) throws IOException {
 919         if (!isInline() && !atNewline)
 920             out.write(DocletConstants.NL);
 921         String tagString = htmlTag.toString();
 922         out.write("<");
 923         out.write(tagString);
 924         Iterator<HtmlAttr> iterator = attrs.keySet().iterator();
 925         HtmlAttr key;
 926         String value;
 927         while (iterator.hasNext()) {
 928             key = iterator.next();
 929             value = attrs.get(key);
 930             out.write(" ");
 931             out.write(key.toString());
 932             if (!value.isEmpty()) {
 933                 out.write("=\"");
 934                 out.write(value);
 935                 out.write("\"");
 936             }
 937         }
 938         out.write(">");
 939         boolean nl = false;
 940         for (Content c : content)
 941             nl = c.write(out, nl);
 942         if (htmlTag.endTagRequired()) {
 943             out.write("</");
 944             out.write(tagString);
 945             out.write(">");
 946         }
 947         if (!isInline()) {
 948             out.write(DocletConstants.NL);
 949             return true;
 950         } else {
 951             return false;
 952         }
 953     }
 954 
 955     /**
 956      * Given a Content node, strips all html characters and
 957      * return the result.
 958      *
 959      * @param body The content node to check.
 960      * @return the plain text from the content node
 961      *
 962      */
 963     private static String stripHtml(Content body) {
 964         String rawString = body.toString();
 965         // remove HTML tags
 966         rawString = rawString.replaceAll("\\<.*?>", " ");
 967         // consolidate multiple spaces between a word to a single space
 968         rawString = rawString.replaceAll("\\b\\s{2,}\\b", " ");
 969         // remove extra whitespaces
 970         return rawString.trim();
 971     }
 972 }