jjg@1455: /* jjg@1506: * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. jjg@1455: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@1455: * jjg@1455: * This code is free software; you can redistribute it and/or modify it jjg@1455: * under the terms of the GNU General Public License version 2 only, as jjg@1455: * published by the Free Software Foundation. Oracle designates this jjg@1455: * particular file as subject to the "Classpath" exception as provided jjg@1455: * by Oracle in the LICENSE file that accompanied this code. jjg@1455: * jjg@1455: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@1455: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@1455: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@1455: * version 2 for more details (a copy is included in the LICENSE file that jjg@1455: * accompanied this code). jjg@1455: * jjg@1455: * You should have received a copy of the GNU General Public License version jjg@1455: * 2 along with this work; if not, write to the Free Software Foundation, jjg@1455: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@1455: * jjg@1455: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jjg@1455: * or visit www.oracle.com if you need additional information or have any jjg@1455: * questions. jjg@1455: */ jjg@1455: jjg@1455: package com.sun.tools.doclint; jjg@1455: jjg@1455: import java.util.Set; jjg@1455: import java.util.Collections; jjg@1455: import java.util.EnumMap; jjg@1455: import java.util.EnumSet; jjg@1455: import java.util.HashMap; jjg@1964: import java.util.Locale; jjg@1455: import java.util.Map; jjg@1455: jjg@1455: import javax.lang.model.element.Name; jjg@1455: jjg@1455: import static com.sun.tools.doclint.HtmlTag.Attr.*; jjg@1455: jjg@1455: /** jjg@1455: * Enum representing HTML tags. jjg@1455: * jjg@1455: * The intent of this class is to embody the semantics of W3C HTML 4.01 jjg@1455: * to the extent supported/used by javadoc. jjg@1976: * In time, we may wish to transition javadoc and doclint to using HTML 5. jjg@1455: * jjg@1455: * This is derivative of com.sun.tools.doclets.formats.html.markup.HtmlTag. jjg@1455: * Eventually, these two should be merged back together, and possibly made jjg@1455: * public. jjg@1455: * jjg@1455: * @see HTML 4.01 Specification jjg@1976: * @see HTML 5 Specification jjg@1455: * @author Bhavesh Patel jjg@1455: * @author Jonathan Gibbons (revised) jjg@1455: */ jjg@1455: public enum HtmlTag { jjg@1455: A(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: attrs(AttrKind.OK, HREF, TARGET, NAME)), jjg@1455: jjg@1455: B(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1507: BIG(BlockType.INLINE, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT)), jjg@1507: jjg@1507: BLOCKQUOTE(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), jjg@1455: jjg@1455: BODY(BlockType.OTHER, EndKind.REQUIRED), jjg@1455: jjg@1455: BR(BlockType.INLINE, EndKind.NONE, jjg@1455: attrs(AttrKind.USE_CSS, CLEAR)), jjg@1455: jjg@1507: CAPTION(BlockType.TABLE_ITEM, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)), jjg@1455: jjg@1507: CENTER(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), jjg@1455: jjg@1455: CITE(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1455: CODE(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1507: DD(BlockType.LIST_ITEM, EndKind.OPTIONAL, jjg@1507: EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)), jjg@1455: jjg@1507: DIV(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)), jjg@1455: jjg@1455: DL(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT), jjg@1507: attrs(AttrKind.USE_CSS, COMPACT)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == DT) || (t == DD); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1507: DT(BlockType.LIST_ITEM, EndKind.OPTIONAL, jjg@1507: EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)), jjg@1455: jjg@1455: EM(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.NO_NEST)), jjg@1455: jjg@1455: FONT(BlockType.INLINE, EndKind.REQUIRED, // tag itself is deprecated jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT), jjg@1455: attrs(AttrKind.USE_CSS, SIZE, COLOR, FACE)), jjg@1455: jjg@1455: FRAME(BlockType.OTHER, EndKind.NONE), jjg@1455: jjg@1455: FRAMESET(BlockType.OTHER, EndKind.REQUIRED), jjg@1455: jjg@1507: H1(BlockType.BLOCK, EndKind.REQUIRED), jjg@1507: H2(BlockType.BLOCK, EndKind.REQUIRED), jjg@1507: H3(BlockType.BLOCK, EndKind.REQUIRED), jjg@1507: H4(BlockType.BLOCK, EndKind.REQUIRED), jjg@1507: H5(BlockType.BLOCK, EndKind.REQUIRED), jjg@1507: H6(BlockType.BLOCK, EndKind.REQUIRED), jjg@1455: jjg@1455: HEAD(BlockType.OTHER, EndKind.REQUIRED), jjg@1455: jjg@1976: HR(BlockType.BLOCK, EndKind.NONE, jjg@1976: attrs(AttrKind.OK, WIDTH)), // OK in 4.01; not allowed in 5 jjg@1455: jjg@1455: HTML(BlockType.OTHER, EndKind.REQUIRED), jjg@1455: jjg@1455: I(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1455: IMG(BlockType.INLINE, EndKind.NONE, jjg@1455: attrs(AttrKind.OK, SRC, ALT, HEIGHT, WIDTH), jjg@1455: attrs(AttrKind.OBSOLETE, NAME), jjg@1455: attrs(AttrKind.USE_CSS, ALIGN, HSPACE, VSPACE, BORDER)), jjg@1455: jjg@1507: LI(BlockType.LIST_ITEM, EndKind.OPTIONAL, jjg@1793: EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE), jjg@1793: attrs(AttrKind.OK, VALUE)), jjg@1455: jjg@1455: LINK(BlockType.OTHER, EndKind.NONE), jjg@1455: jjg@1507: MENU(BlockType.BLOCK, EndKind.REQUIRED) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == LI); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1455: META(BlockType.OTHER, EndKind.NONE), jjg@1455: jjg@1455: NOFRAMES(BlockType.OTHER, EndKind.REQUIRED), jjg@1455: jjg@1507: NOSCRIPT(BlockType.BLOCK, EndKind.REQUIRED), jjg@1455: jjg@1455: OL(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT), jjg@1976: attrs(AttrKind.OK, START, TYPE)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == LI); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1455: P(BlockType.BLOCK, EndKind.OPTIONAL, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT), jjg@1455: attrs(AttrKind.USE_CSS, ALIGN)), jjg@1455: jjg@1507: PRE(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: switch (t) { jjg@1507: case IMG: case BIG: case SMALL: case SUB: case SUP: jjg@1507: return false; jjg@1507: default: jjg@1507: return (t.blockType == BlockType.INLINE); jjg@1507: } jjg@1507: } jjg@1507: }, jjg@1455: jjg@1455: SCRIPT(BlockType.OTHER, EndKind.REQUIRED), jjg@1455: jjg@1507: SMALL(BlockType.INLINE, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT)), jjg@1455: jjg@1455: SPAN(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT)), jjg@1455: jjg@1455: STRONG(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT)), jjg@1455: jjg@1455: SUB(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1455: SUP(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1455: TABLE(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT), jjg@1455: attrs(AttrKind.OK, SUMMARY, Attr.FRAME, RULES, BORDER, jjg@1976: CELLPADDING, CELLSPACING, WIDTH), // width OK in 4.01; not allowed in 5 jjg@1976: attrs(AttrKind.USE_CSS, ALIGN, BGCOLOR)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: switch (t) { jjg@1507: case CAPTION: jjg@1507: case THEAD: case TBODY: case TFOOT: jjg@1507: case TR: // HTML 3.2 jjg@1507: return true; jjg@1507: default: jjg@1507: return false; jjg@1507: } jjg@1507: } jjg@1507: }, jjg@1455: jjg@1507: TBODY(BlockType.TABLE_ITEM, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT), jjg@1507: attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == TR); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1507: TD(BlockType.TABLE_ITEM, EndKind.OPTIONAL, jjg@1507: EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE), jjg@1455: attrs(AttrKind.OK, COLSPAN, ROWSPAN, HEADERS, SCOPE, ABBR, AXIS, jjg@1455: ALIGN, CHAR, CHAROFF, VALIGN), jjg@1455: attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)), jjg@1455: jjg@1507: TFOOT(BlockType.TABLE_ITEM, EndKind.REQUIRED, jjg@1507: attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == TR); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1507: TH(BlockType.TABLE_ITEM, EndKind.OPTIONAL, jjg@1507: EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE), jjg@1455: attrs(AttrKind.OK, COLSPAN, ROWSPAN, HEADERS, SCOPE, ABBR, AXIS, jjg@1455: ALIGN, CHAR, CHAROFF, VALIGN), jjg@1455: attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)), jjg@1455: jjg@1507: THEAD(BlockType.TABLE_ITEM, EndKind.REQUIRED, jjg@1507: attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == TR); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1455: TITLE(BlockType.OTHER, EndKind.REQUIRED), jjg@1455: jjg@1507: TR(BlockType.TABLE_ITEM, EndKind.OPTIONAL, jjg@1455: attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN), jjg@1507: attrs(AttrKind.USE_CSS, BGCOLOR)) { jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == TH) || (t == TD); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1455: TT(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1455: U(BlockType.INLINE, EndKind.REQUIRED, jjg@1455: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)), jjg@1455: jjg@1455: UL(BlockType.BLOCK, EndKind.REQUIRED, jjg@1507: EnumSet.of(Flag.EXPECT_CONTENT), jjg@1976: attrs(AttrKind.OK, COMPACT, TYPE)) { // OK in 4.01; not allowed in 5 jjg@1507: @Override jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: return (t == LI); jjg@1507: } jjg@1507: }, jjg@1455: jjg@1455: VAR(BlockType.INLINE, EndKind.REQUIRED); jjg@1455: jjg@1455: /** jjg@1455: * Enum representing the type of HTML element. jjg@1455: */ jjg@1455: public static enum BlockType { jjg@1455: BLOCK, jjg@1455: INLINE, jjg@1507: LIST_ITEM, jjg@1507: TABLE_ITEM, jjg@1455: OTHER; jjg@1455: } jjg@1455: jjg@1455: /** jjg@1455: * Enum representing HTML end tag requirement. jjg@1455: */ jjg@1455: public static enum EndKind { jjg@1455: NONE, jjg@1455: OPTIONAL, jjg@1455: REQUIRED; jjg@1455: } jjg@1455: jjg@1455: public static enum Flag { jjg@1507: ACCEPTS_BLOCK, jjg@1507: ACCEPTS_INLINE, jjg@1455: EXPECT_CONTENT, jjg@1507: NO_NEST jjg@1455: } jjg@1455: jjg@1455: public static enum Attr { jjg@1455: ABBR, jjg@1455: ALIGN, jjg@1455: ALT, jjg@1455: AXIS, jjg@1455: BGCOLOR, jjg@1455: BORDER, jjg@1455: CELLSPACING, jjg@1455: CELLPADDING, jjg@1455: CHAR, jjg@1455: CHAROFF, jjg@1455: CLEAR, jjg@1455: CLASS, jjg@1455: COLOR, jjg@1455: COLSPAN, jjg@1455: COMPACT, jjg@1455: FACE, jjg@1455: FRAME, jjg@1455: HEADERS, jjg@1455: HEIGHT, jjg@1455: HREF, jjg@1455: HSPACE, jjg@1455: ID, jjg@1455: NAME, jjg@1455: NOWRAP, jjg@1455: REVERSED, jjg@1455: ROWSPAN, jjg@1455: RULES, jjg@1455: SCOPE, jjg@1455: SIZE, jjg@1455: SPACE, jjg@1455: SRC, jjg@1455: START, jjg@1455: STYLE, jjg@1455: SUMMARY, jjg@1455: TARGET, jjg@1455: TYPE, jjg@1455: VALIGN, jjg@1793: VALUE, jjg@1455: VSPACE, jjg@1455: WIDTH; jjg@1455: jjg@1455: public String getText() { jjg@1964: return toLowerCase(name()); jjg@1455: } jjg@1455: jjg@1455: static final Map index = new HashMap(); jjg@1455: static { jjg@1455: for (Attr t: values()) { jjg@1506: index.put(t.getText(), t); jjg@1455: } jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: public static enum AttrKind { jjg@1455: INVALID, jjg@1455: OBSOLETE, jjg@1455: USE_CSS, jjg@1455: OK jjg@1455: } jjg@1455: jjg@1455: // This class exists to avoid warnings from using parameterized vararg type jjg@1455: // Map in signature of HtmlTag constructor. jjg@1455: private static class AttrMap extends EnumMap { jjg@1455: private static final long serialVersionUID = 0; jjg@1455: AttrMap() { jjg@1455: super(Attr.class); jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: jjg@1455: public final BlockType blockType; jjg@1455: public final EndKind endKind; jjg@1455: public final Set flags; jjg@1455: private final Map attrs; jjg@1455: jjg@1455: HtmlTag(BlockType blockType, EndKind endKind, AttrMap... attrMaps) { jjg@1455: this(blockType, endKind, Collections.emptySet(), attrMaps); jjg@1455: } jjg@1455: jjg@1455: HtmlTag(BlockType blockType, EndKind endKind, Set flags, AttrMap... attrMaps) { jjg@1455: this.blockType = blockType; jjg@1507: this.endKind = endKind; jjg@1507: this.flags = flags; jjg@1455: this.attrs = new EnumMap(Attr.class); jjg@1455: for (Map m: attrMaps) jjg@1455: this.attrs.putAll(m); jjg@1455: attrs.put(Attr.CLASS, AttrKind.OK); jjg@1455: attrs.put(Attr.ID, AttrKind.OK); jjg@1455: attrs.put(Attr.STYLE, AttrKind.OK); jjg@1455: } jjg@1455: jjg@1507: public boolean accepts(HtmlTag t) { jjg@1507: if (flags.contains(Flag.ACCEPTS_BLOCK) && flags.contains(Flag.ACCEPTS_INLINE)) { jjg@1507: return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE); jjg@1507: } else if (flags.contains(Flag.ACCEPTS_BLOCK)) { jjg@1507: return (t.blockType == BlockType.BLOCK); jjg@1507: } else if (flags.contains(Flag.ACCEPTS_INLINE)) { jjg@1507: return (t.blockType == BlockType.INLINE); jjg@1507: } else jjg@1507: switch (blockType) { jjg@1507: case BLOCK: jjg@1507: case INLINE: jjg@1507: return (t.blockType == BlockType.INLINE); jjg@1507: case OTHER: jjg@1507: // OTHER tags are invalid in doc comments, and will be jjg@1507: // reported separately, so silently accept/ignore any content jjg@1507: return true; jjg@1507: default: jjg@1507: // any combination which could otherwise arrive here jjg@1507: // ought to have been handled in an overriding method jjg@1507: throw new AssertionError(this + ":" + t); jjg@1507: } jjg@1507: } jjg@1507: jjg@1507: public boolean acceptsText() { jjg@1507: // generally, anywhere we can put text we can also put inline tag jjg@1507: // so check if a typical inline tag is allowed jjg@1507: return accepts(B); jjg@1507: } jjg@1507: jjg@1455: public String getText() { jjg@1964: return toLowerCase(name()); jjg@1455: } jjg@1455: jjg@1455: public Attr getAttr(Name attrName) { jjg@1964: return Attr.index.get(toLowerCase(attrName.toString())); jjg@1455: } jjg@1455: jjg@1455: public AttrKind getAttrKind(Name attrName) { jjg@1455: AttrKind k = attrs.get(getAttr(attrName)); // null-safe jjg@1455: return (k == null) ? AttrKind.INVALID : k; jjg@1455: } jjg@1455: jjg@1455: private static AttrMap attrs(AttrKind k, Attr... attrs) { jjg@1455: AttrMap map = new AttrMap(); jjg@1455: for (Attr a: attrs) map.put(a, k); jjg@1455: return map; jjg@1455: } jjg@1455: jjg@1455: private static final Map index = new HashMap(); jjg@1455: static { jjg@1455: for (HtmlTag t: values()) { jjg@1506: index.put(t.getText(), t); jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: static HtmlTag get(Name tagName) { jjg@1964: return index.get(toLowerCase(tagName.toString())); jjg@1964: } jjg@1964: jjg@1964: private static String toLowerCase(String s) { jjg@1964: return s.toLowerCase(Locale.US); jjg@1455: } jjg@1455: }