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@2051: DFN(BlockType.INLINE, EndKind.REQUIRED,
jjg@2051: EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
jjg@2051:
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: }