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