blob: 2ea92af9ac08e6003f863c3cea208da9c01e2a37 [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, 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.Set;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.lang.model.element.Name;
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>
* @author Bhavesh Patel
* @author Jonathan Gibbons (revised)
*/
public enum HtmlTag {
A(BlockType.INLINE, EndKind.REQUIRED,
attrs(AttrKind.OK, HREF, TARGET, NAME)),
B(BlockType.INLINE, EndKind.REQUIRED,
EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
BIG(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)),
CENTER(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)),
DD(BlockType.LIST_ITEM, EndKind.OPTIONAL,
EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)),
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)),
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(BlockType.INLINE, EndKind.REQUIRED, // tag itself is deprecated
EnumSet.of(Flag.EXPECT_CONTENT),
attrs(AttrKind.USE_CSS, SIZE, COLOR, FACE)),
FRAME(BlockType.OTHER, EndKind.NONE),
FRAMESET(BlockType.OTHER, EndKind.REQUIRED),
H1(BlockType.BLOCK, EndKind.REQUIRED),
H2(BlockType.BLOCK, EndKind.REQUIRED),
H3(BlockType.BLOCK, EndKind.REQUIRED),
H4(BlockType.BLOCK, EndKind.REQUIRED),
H5(BlockType.BLOCK, EndKind.REQUIRED),
H6(BlockType.BLOCK, EndKind.REQUIRED),
HEAD(BlockType.OTHER, EndKind.REQUIRED),
HR(BlockType.BLOCK, EndKind.NONE,
attrs(AttrKind.OK, WIDTH)), // OK in 4.01; not allowed in 5
HTML(BlockType.OTHER, EndKind.REQUIRED),
I(BlockType.INLINE, EndKind.REQUIRED,
EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
IMG(BlockType.INLINE, EndKind.NONE,
attrs(AttrKind.OK, SRC, ALT, HEIGHT, WIDTH),
attrs(AttrKind.OBSOLETE, NAME),
attrs(AttrKind.USE_CSS, ALIGN, HSPACE, VSPACE, BORDER)),
LI(BlockType.LIST_ITEM, EndKind.OPTIONAL,
EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
attrs(AttrKind.OK, VALUE)),
LINK(BlockType.OTHER, EndKind.NONE),
MENU(BlockType.BLOCK, EndKind.REQUIRED) {
@Override
public boolean accepts(HtmlTag t) {
return (t == LI);
}
},
META(BlockType.OTHER, EndKind.NONE),
NOFRAMES(BlockType.OTHER, EndKind.REQUIRED),
NOSCRIPT(BlockType.BLOCK, EndKind.REQUIRED),
OL(BlockType.BLOCK, EndKind.REQUIRED,
EnumSet.of(Flag.EXPECT_CONTENT),
attrs(AttrKind.OK, START, TYPE)) {
@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)) {
@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);
}
}
},
SCRIPT(BlockType.OTHER, EndKind.REQUIRED),
SMALL(BlockType.INLINE, EndKind.REQUIRED,
EnumSet.of(Flag.EXPECT_CONTENT)),
SPAN(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.OK, SUMMARY, Attr.FRAME, RULES, BORDER,
CELLPADDING, CELLSPACING, WIDTH), // width OK in 4.01; not allowed in 5
attrs(AttrKind.USE_CSS, ALIGN, BGCOLOR)) {
@Override
public boolean accepts(HtmlTag t) {
switch (t) {
case CAPTION:
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.OK, ALIGN, CHAR, CHAROFF, VALIGN)) {
@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.OK, COLSPAN, ROWSPAN, HEADERS, SCOPE, ABBR, AXIS,
ALIGN, CHAR, CHAROFF, VALIGN),
attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)),
TFOOT(BlockType.TABLE_ITEM, EndKind.REQUIRED,
attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) {
@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.OK, COLSPAN, ROWSPAN, HEADERS, SCOPE, ABBR, AXIS,
ALIGN, CHAR, CHAROFF, VALIGN),
attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)),
THEAD(BlockType.TABLE_ITEM, EndKind.REQUIRED,
attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) {
@Override
public boolean accepts(HtmlTag t) {
return (t == TR);
}
},
TITLE(BlockType.OTHER, EndKind.REQUIRED),
TR(BlockType.TABLE_ITEM, EndKind.OPTIONAL,
attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN),
attrs(AttrKind.USE_CSS, BGCOLOR)) {
@Override
public boolean accepts(HtmlTag t) {
return (t == TH) || (t == TD);
}
},
TT(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.OK, COMPACT, TYPE)) { // OK in 4.01; not allowed in 5
@Override
public boolean accepts(HtmlTag t) {
return (t == LI);
}
},
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,
ALT,
AXIS,
BGCOLOR,
BORDER,
CELLSPACING,
CELLPADDING,
CHAR,
CHAROFF,
CLEAR,
CLASS,
COLOR,
COLSPAN,
COMPACT,
FACE,
FRAME,
HEADERS,
HEIGHT,
HREF,
HSPACE,
ID,
NAME,
NOWRAP,
REVERSED,
ROWSPAN,
RULES,
SCOPE,
SIZE,
SPACE,
SRC,
START,
STYLE,
SUMMARY,
TARGET,
TYPE,
VALIGN,
VALUE,
VSPACE,
WIDTH;
public String getText() {
return toLowerCase(name());
}
static final Map<String,Attr> index = new HashMap<String,Attr>();
static {
for (Attr t: values()) {
index.put(t.getText(), t);
}
}
}
public static enum AttrKind {
INVALID,
OBSOLETE,
USE_CSS,
OK
}
// 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 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(blockType, endKind, Collections.<Flag>emptySet(), attrMaps);
}
HtmlTag(BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps) {
this.blockType = blockType;
this.endKind = endKind;
this.flags = flags;
this.attrs = new EnumMap<Attr,AttrKind>(Attr.class);
for (Map<Attr,AttrKind> m: attrMaps)
this.attrs.putAll(m);
attrs.put(Attr.CLASS, AttrKind.OK);
attrs.put(Attr.ID, AttrKind.OK);
attrs.put(Attr.STYLE, AttrKind.OK);
}
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 toLowerCase(name());
}
public Attr getAttr(Name attrName) {
return Attr.index.get(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<String,HtmlTag>();
static {
for (HtmlTag t: values()) {
index.put(t.getText(), t);
}
}
static HtmlTag get(Name tagName) {
return index.get(toLowerCase(tagName.toString()));
}
private static String toLowerCase(String s) {
return s.toLowerCase(Locale.US);
}
}