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: }