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