/* * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.doclint; import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.lang.model.element.Name; import com.sun.tools.javac.util.StringUtils; import static com.sun.tools.doclint.HtmlTag.Attr.*; /** * Enum representing HTML tags. * * The intent of this class is to embody the semantics of W3C HTML 4.01 * to the extent supported/used by javadoc. * In time, we may wish to transition javadoc and doclint to using HTML 5. * * This is derivative of com.sun.tools.doclets.formats.html.markup.HtmlTag. * Eventually, these two should be merged back together, and possibly made * public. * * @see <a href="http://www.w3.org/TR/REC-html40/">HTML 4.01 Specification</a> * @see <a href="http://www.w3.org/TR/html5/">HTML 5 Specification</a> * @see <a href="http://www.w3.org/TR/wai-aria/ ">WAI-ARIA Specification</a> * @see <a href="http://www.w3.org/TR/aria-in-html/#recommendations-table">WAI-ARIA Recommendations Table</a> * @author Bhavesh Patel * @author Jonathan Gibbons (revised) */ public enum HtmlTag { A(BlockType.INLINE, EndKind.REQUIRED, attrs(AttrKind.ALL, HREF, TARGET, ID), attrs(AttrKind.HTML4, REV, CHARSET, SHAPE, COORDS, NAME)), ABBR(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), ACRONYM(HtmlVersion.HTML4, BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), ADDRESS(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), ARTICLE(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), ASIDE(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), B(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), BDI(HtmlVersion.HTML5, BlockType.INLINE, EndKind.REQUIRED), BIG(HtmlVersion.HTML4, BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT)), BLOCKQUOTE(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), BODY(BlockType.OTHER, EndKind.REQUIRED), BR(BlockType.INLINE, EndKind.NONE, attrs(AttrKind.USE_CSS, CLEAR)), CAPTION(BlockType.TABLE_ITEM, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT), attrs(AttrKind.USE_CSS, ALIGN)), CENTER(HtmlVersion.HTML4, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), CITE(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), CODE(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), COL(BlockType.TABLE_ITEM, EndKind.NONE, attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF, VALIGN, WIDTH)), COLGROUP(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF, VALIGN, WIDTH)) { @Override public boolean accepts(HtmlTag t) { return (t == COL); } }, DD(BlockType.LIST_ITEM, EndKind.OPTIONAL, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)), DEL(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST), attrs(AttrKind.ALL, Attr.CITE, Attr.DATETIME)), DFN(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), DIV(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE), attrs(AttrKind.USE_CSS, ALIGN)), DL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.USE_CSS, COMPACT)) { @Override public boolean accepts(HtmlTag t) { return (t == DT) || (t == DD); } }, DT(BlockType.LIST_ITEM, EndKind.OPTIONAL, EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)), EM(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.NO_NEST)), FONT(HtmlVersion.HTML4, BlockType.INLINE, EndKind.REQUIRED, // tag itself is deprecated EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.USE_CSS, SIZE, COLOR, FACE)), FOOTER(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)) { @Override public boolean accepts(HtmlTag t) { switch (t) { case HEADER: case FOOTER: case MAIN: return false; default: return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE); } } }, FIGURE(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), FIGCAPTION(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED), FRAME(HtmlVersion.HTML4, BlockType.OTHER, EndKind.NONE), FRAMESET(HtmlVersion.HTML4, BlockType.OTHER, EndKind.REQUIRED), H1(BlockType.BLOCK, EndKind.REQUIRED, attrs(AttrKind.USE_CSS, ALIGN)), H2(BlockType.BLOCK, EndKind.REQUIRED, attrs(AttrKind.USE_CSS, ALIGN)), H3(BlockType.BLOCK, EndKind.REQUIRED, attrs(AttrKind.USE_CSS, ALIGN)), H4(BlockType.BLOCK, EndKind.REQUIRED, attrs(AttrKind.USE_CSS, ALIGN)), H5(BlockType.BLOCK, EndKind.REQUIRED, attrs(AttrKind.USE_CSS, ALIGN)), H6(BlockType.BLOCK, EndKind.REQUIRED, attrs(AttrKind.USE_CSS, ALIGN)), HEAD(BlockType.OTHER, EndKind.REQUIRED), HEADER(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)) { @Override public boolean accepts(HtmlTag t) { switch (t) { case HEADER: case FOOTER: case MAIN: return false; default: return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE); } } }, HR(BlockType.BLOCK, EndKind.NONE, attrs(AttrKind.HTML4, WIDTH), attrs(AttrKind.USE_CSS, ALIGN, NOSHADE, SIZE)), HTML(BlockType.OTHER, EndKind.REQUIRED), I(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), IFRAME(BlockType.OTHER, EndKind.REQUIRED), IMG(BlockType.INLINE, EndKind.NONE, attrs(AttrKind.ALL, SRC, ALT, HEIGHT, WIDTH), attrs(AttrKind.HTML5, CROSSORIGIN), attrs(AttrKind.OBSOLETE, NAME), attrs(AttrKind.USE_CSS, ALIGN, HSPACE, VSPACE, BORDER)), INS(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST), attrs(AttrKind.ALL, Attr.CITE, Attr.DATETIME)), KBD(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), LI(BlockType.LIST_ITEM, EndKind.OPTIONAL, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE), attrs(AttrKind.ALL, VALUE), attrs(AttrKind.USE_CSS, TYPE)), LINK(BlockType.OTHER, EndKind.NONE), MAIN(HtmlVersion.HTML5, BlockType.OTHER, EndKind.REQUIRED), MARK(HtmlVersion.HTML5, BlockType.INLINE, EndKind.REQUIRED), MENU(BlockType.BLOCK, EndKind.REQUIRED) { @Override public boolean accepts(HtmlTag t) { return (t == LI); } }, META(BlockType.OTHER, EndKind.NONE), NAV(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), NOFRAMES(HtmlVersion.HTML4, BlockType.OTHER, EndKind.REQUIRED), NOSCRIPT(BlockType.BLOCK, EndKind.REQUIRED), OL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.ALL, START, TYPE), attrs(AttrKind.HTML5, REVERSED), attrs(AttrKind.USE_CSS, COMPACT)) { @Override public boolean accepts(HtmlTag t) { return (t == LI); } }, P(BlockType.BLOCK, EndKind.OPTIONAL, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.USE_CSS, ALIGN)), PRE(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.USE_CSS, WIDTH)) { @Override public boolean accepts(HtmlTag t) { switch (t) { case IMG: case BIG: case SMALL: case SUB: case SUP: return false; default: return (t.blockType == BlockType.INLINE); } } }, Q(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), S(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), SAMP(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), SCRIPT(BlockType.OTHER, EndKind.REQUIRED), SECTION(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), SMALL(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT)), SPAN(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT)), STRIKE(HtmlVersion.HTML4, BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT)), STRONG(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT)), SUB(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), SUP(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), TABLE(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.ALL, BORDER), attrs(AttrKind.HTML4, SUMMARY, CELLPADDING, CELLSPACING, Attr.FRAME, RULES, WIDTH), attrs(AttrKind.USE_CSS, ALIGN, BGCOLOR)) { @Override public boolean accepts(HtmlTag t) { switch (t) { case CAPTION: case COLGROUP: case THEAD: case TBODY: case TFOOT: case TR: // HTML 3.2 return true; default: return false; } } }, TBODY(BlockType.TABLE_ITEM, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.ALL, VALIGN), attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF)) { @Override public boolean accepts(HtmlTag t) { return (t == TR); } }, TD(BlockType.TABLE_ITEM, EndKind.OPTIONAL, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE), attrs(AttrKind.ALL, COLSPAN, ROWSPAN, HEADERS, VALIGN), attrs(AttrKind.HTML4, AXIS, Attr.ABBR, SCOPE, ALIGN, CHAR, CHAROFF), attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)), TEMPLATE(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), TFOOT(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.ALL, VALIGN), attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF)) { @Override public boolean accepts(HtmlTag t) { return (t == TR); } }, TH(BlockType.TABLE_ITEM, EndKind.OPTIONAL, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE), attrs(AttrKind.ALL, COLSPAN, ROWSPAN, HEADERS, SCOPE, Attr.ABBR, VALIGN), attrs(AttrKind.HTML4, AXIS, ALIGN, CHAR, CHAROFF), attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)), THEAD(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.ALL, VALIGN), attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF)) { @Override public boolean accepts(HtmlTag t) { return (t == TR); } }, TIME(HtmlVersion.HTML5, BlockType.INLINE, EndKind.REQUIRED), TITLE(BlockType.OTHER, EndKind.REQUIRED), TR(BlockType.TABLE_ITEM, EndKind.OPTIONAL, attrs(AttrKind.ALL, VALIGN), attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF), attrs(AttrKind.USE_CSS, BGCOLOR)) { @Override public boolean accepts(HtmlTag t) { return (t == TH) || (t == TD); } }, TT(HtmlVersion.HTML4, BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), U(BlockType.INLINE, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), UL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.HTML4, COMPACT, TYPE)) { @Override public boolean accepts(HtmlTag t) { return (t == LI); } }, WBR(HtmlVersion.HTML5, BlockType.INLINE, EndKind.REQUIRED), VAR(BlockType.INLINE, EndKind.REQUIRED); /** * Enum representing the type of HTML element. */ public static enum BlockType { BLOCK, INLINE, LIST_ITEM, TABLE_ITEM, OTHER } /** * Enum representing HTML end tag requirement. */ public static enum EndKind { NONE, OPTIONAL, REQUIRED } public static enum Flag { ACCEPTS_BLOCK, ACCEPTS_INLINE, EXPECT_CONTENT, NO_NEST } public static enum Attr { ABBR, ALIGN, ALINK, ALT, ARIA_ACTIVEDESCENDANT, ARIA_CONTROLS, ARIA_DESCRIBEDBY, ARIA_EXPANDED, ARIA_LABEL, ARIA_LABELLEDBY, ARIA_LEVEL, ARIA_MULTISELECTABLE, ARIA_OWNS, ARIA_POSINSET, ARIA_SETSIZE, ARIA_READONLY, ARIA_REQUIRED, ARIA_SELECTED, ARIA_SORT, AXIS, BACKGROUND, BGCOLOR, BORDER, CELLSPACING, CELLPADDING, CHAR, CHAROFF, CHARSET, CITE, CLEAR, CLASS, COLOR, COLSPAN, COMPACT, COORDS, CROSSORIGIN, DATETIME, FACE, FRAME, FRAMEBORDER, HEADERS, HEIGHT, HREF, HSPACE, ID, LINK, LONGDESC, MARGINHEIGHT, MARGINWIDTH, NAME, NOSHADE, NOWRAP, PROFILE, REV, REVERSED, ROLE, ROWSPAN, RULES, SCHEME, SCOPE, SCROLLING, SHAPE, SIZE, SPACE, SRC, START, STYLE, SUMMARY, TARGET, TEXT, TYPE, VALIGN, VALUE, VERSION, VLINK, VSPACE, WIDTH; private final String name; Attr() { name = StringUtils.toLowerCase(name().replace("_", "-")); } public String getText() { return name; } static final Map<String,Attr> index = new HashMap<>(); static { for (Attr t: values()) { index.put(t.getText(), t); } } } public static enum AttrKind { HTML4, HTML5, INVALID, OBSOLETE, USE_CSS, ALL } // This class exists to avoid warnings from using parameterized vararg type // Map<Attr,AttrKind> in signature of HtmlTag constructor. private static class AttrMap extends EnumMap<Attr,AttrKind> { private static final long serialVersionUID = 0; AttrMap() { super(Attr.class); } } public final HtmlVersion allowedVersion; public final BlockType blockType; public final EndKind endKind; public final Set<Flag> flags; private final Map<Attr,AttrKind> attrs; HtmlTag(BlockType blockType, EndKind endKind, AttrMap... attrMaps) { this(HtmlVersion.ALL, blockType, endKind, Collections.<Flag>emptySet(), attrMaps); } HtmlTag(HtmlVersion allowedVersion, BlockType blockType, EndKind endKind, AttrMap... attrMaps) { this(allowedVersion, blockType, endKind, Collections.<Flag>emptySet(), attrMaps); } HtmlTag(BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps) { this(HtmlVersion.ALL, blockType, endKind, flags, attrMaps); } HtmlTag(HtmlVersion allowedVersion, BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps) { this.allowedVersion = allowedVersion; this.blockType = blockType; this.endKind = endKind; this.flags = flags; this.attrs = new EnumMap<>(Attr.class); for (Map<Attr,AttrKind> m: attrMaps) this.attrs.putAll(m); attrs.put(Attr.CLASS, AttrKind.ALL); attrs.put(Attr.ID, AttrKind.ALL); attrs.put(Attr.STYLE, AttrKind.ALL); attrs.put(Attr.ROLE, AttrKind.HTML5); // for now, assume that all ARIA attributes are allowed on all tags. attrs.put(Attr.ARIA_ACTIVEDESCENDANT, AttrKind.HTML5); attrs.put(Attr.ARIA_CONTROLS, AttrKind.HTML5); attrs.put(Attr.ARIA_DESCRIBEDBY, AttrKind.HTML5); attrs.put(Attr.ARIA_EXPANDED, AttrKind.HTML5); attrs.put(Attr.ARIA_LABEL, AttrKind.HTML5); attrs.put(Attr.ARIA_LABELLEDBY, AttrKind.HTML5); attrs.put(Attr.ARIA_LEVEL, AttrKind.HTML5); attrs.put(Attr.ARIA_MULTISELECTABLE, AttrKind.HTML5); attrs.put(Attr.ARIA_OWNS, AttrKind.HTML5); attrs.put(Attr.ARIA_POSINSET, AttrKind.HTML5); attrs.put(Attr.ARIA_READONLY, AttrKind.HTML5); attrs.put(Attr.ARIA_REQUIRED, AttrKind.HTML5); attrs.put(Attr.ARIA_SELECTED, AttrKind.HTML5); attrs.put(Attr.ARIA_SETSIZE, AttrKind.HTML5); attrs.put(Attr.ARIA_SORT, AttrKind.HTML5); } public boolean accepts(HtmlTag t) { if (flags.contains(Flag.ACCEPTS_BLOCK) && flags.contains(Flag.ACCEPTS_INLINE)) { return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE); } else if (flags.contains(Flag.ACCEPTS_BLOCK)) { return (t.blockType == BlockType.BLOCK); } else if (flags.contains(Flag.ACCEPTS_INLINE)) { return (t.blockType == BlockType.INLINE); } else switch (blockType) { case BLOCK: case INLINE: return (t.blockType == BlockType.INLINE); case OTHER: // OTHER tags are invalid in doc comments, and will be // reported separately, so silently accept/ignore any content return true; default: // any combination which could otherwise arrive here // ought to have been handled in an overriding method throw new AssertionError(this + ":" + t); } } public boolean acceptsText() { // generally, anywhere we can put text we can also put inline tag // so check if a typical inline tag is allowed return accepts(B); } public String getText() { return StringUtils.toLowerCase(name()); } public Attr getAttr(Name attrName) { return Attr.index.get(StringUtils.toLowerCase(attrName.toString())); } public AttrKind getAttrKind(Name attrName) { AttrKind k = attrs.get(getAttr(attrName)); // null-safe return (k == null) ? AttrKind.INVALID : k; } private static AttrMap attrs(AttrKind k, Attr... attrs) { AttrMap map = new AttrMap(); for (Attr a: attrs) map.put(a, k); return map; } private static final Map<String, HtmlTag> index = new HashMap<>(); static { for (HtmlTag t: values()) { index.put(t.getText(), t); } } public static HtmlTag get(Name tagName) { return index.get(StringUtils.toLowerCase(tagName.toString())); } }