1 /*
2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.doclint;
27 
28 import java.util.Set;
29 import java.util.Collections;
30 import java.util.EnumMap;
31 import java.util.EnumSet;
32 import java.util.HashMap;
33 import java.util.Locale;
34 import java.util.Map;
35 
36 import javax.lang.model.element.Name;
37 
38 import static com.sun.tools.doclint.HtmlTag.Attr.*;
39 import com.sun.tools.javac.util.StringUtils;
40 
41 /**
42  * Enum representing HTML tags.
43  *
44  * The intent of this class is to embody the semantics of W3C HTML 4.01
45  * to the extent supported/used by javadoc.
46  * In time, we may wish to transition javadoc and doclint to using HTML 5.
47  *
48  * This is derivative of com.sun.tools.doclets.formats.html.markup.HtmlTag.
49  * Eventually, these two should be merged back together, and possibly made
50  * public.
51  *
52  * @see <a href="http://www.w3.org/TR/REC-html40/">HTML 4.01 Specification</a>
53  * @see <a href="http://www.w3.org/TR/html5/">HTML 5 Specification</a>
54  * @author Bhavesh Patel
55  * @author Jonathan Gibbons (revised)
56  */
57 public enum HtmlTag {
58     A(BlockType.INLINE, EndKind.REQUIRED,
59             attrs(AttrKind.OK, HREF, TARGET, NAME)),
60 
61     B(BlockType.INLINE, EndKind.REQUIRED,
62             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
63 
64     BIG(BlockType.INLINE, EndKind.REQUIRED,
65             EnumSet.of(Flag.EXPECT_CONTENT)),
66 
67     BLOCKQUOTE(BlockType.BLOCK, EndKind.REQUIRED,
68             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
69 
70     BODY(BlockType.OTHER, EndKind.REQUIRED),
71 
72     BR(BlockType.INLINE, EndKind.NONE,
73             attrs(AttrKind.USE_CSS, CLEAR)),
74 
75     CAPTION(BlockType.TABLE_ITEM, EndKind.REQUIRED,
76             EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)),
77 
78     CENTER(BlockType.BLOCK, EndKind.REQUIRED,
79             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
80 
81     CITE(BlockType.INLINE, EndKind.REQUIRED,
82             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
83 
84     CODE(BlockType.INLINE, EndKind.REQUIRED,
85             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
86 
87     DD(BlockType.LIST_ITEM, EndKind.OPTIONAL,
88             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)),
89 
90     DFN(BlockType.INLINE, EndKind.REQUIRED,
91             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
92 
93     DIV(BlockType.BLOCK, EndKind.REQUIRED,
94             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
95 
DL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.USE_CSS, COMPACT))96     DL(BlockType.BLOCK, EndKind.REQUIRED,
97             EnumSet.of(Flag.EXPECT_CONTENT),
98             attrs(AttrKind.USE_CSS, COMPACT)) {
99         @Override
100         public boolean accepts(HtmlTag t) {
101             return (t == DT) || (t == DD);
102         }
103     },
104 
105     DT(BlockType.LIST_ITEM, EndKind.OPTIONAL,
106             EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)),
107 
108     EM(BlockType.INLINE, EndKind.REQUIRED,
109             EnumSet.of(Flag.NO_NEST)),
110 
111     FONT(BlockType.INLINE, EndKind.REQUIRED, // tag itself is deprecated
112             EnumSet.of(Flag.EXPECT_CONTENT),
113             attrs(AttrKind.USE_CSS, SIZE, COLOR, FACE)),
114 
115     FRAME(BlockType.OTHER, EndKind.NONE),
116 
117     FRAMESET(BlockType.OTHER, EndKind.REQUIRED),
118 
119     H1(BlockType.BLOCK, EndKind.REQUIRED),
120     H2(BlockType.BLOCK, EndKind.REQUIRED),
121     H3(BlockType.BLOCK, EndKind.REQUIRED),
122     H4(BlockType.BLOCK, EndKind.REQUIRED),
123     H5(BlockType.BLOCK, EndKind.REQUIRED),
124     H6(BlockType.BLOCK, EndKind.REQUIRED),
125 
126     HEAD(BlockType.OTHER, EndKind.REQUIRED),
127 
128     HR(BlockType.BLOCK, EndKind.NONE,
129             attrs(AttrKind.OK, WIDTH)), // OK in 4.01; not allowed in 5
130 
131     HTML(BlockType.OTHER, EndKind.REQUIRED),
132 
133     I(BlockType.INLINE, EndKind.REQUIRED,
134             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
135 
136     IMG(BlockType.INLINE, EndKind.NONE,
137             attrs(AttrKind.OK, SRC, ALT, HEIGHT, WIDTH),
138             attrs(AttrKind.OBSOLETE, NAME),
139             attrs(AttrKind.USE_CSS, ALIGN, HSPACE, VSPACE, BORDER)),
140 
141     LI(BlockType.LIST_ITEM, EndKind.OPTIONAL,
142             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
143             attrs(AttrKind.OK, VALUE)),
144 
145     LINK(BlockType.OTHER, EndKind.NONE),
146 
MENU(BlockType.BLOCK, EndKind.REQUIRED)147     MENU(BlockType.BLOCK, EndKind.REQUIRED) {
148         @Override
149         public boolean accepts(HtmlTag t) {
150             return (t == LI);
151         }
152     },
153 
154     META(BlockType.OTHER, EndKind.NONE),
155 
156     NOFRAMES(BlockType.OTHER, EndKind.REQUIRED),
157 
158     NOSCRIPT(BlockType.BLOCK, EndKind.REQUIRED),
159 
OL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.OK, START, TYPE))160     OL(BlockType.BLOCK, EndKind.REQUIRED,
161             EnumSet.of(Flag.EXPECT_CONTENT),
162             attrs(AttrKind.OK, START, TYPE)) {
163         @Override
164         public boolean accepts(HtmlTag t) {
165             return (t == LI);
166         }
167     },
168 
169     P(BlockType.BLOCK, EndKind.OPTIONAL,
170             EnumSet.of(Flag.EXPECT_CONTENT),
171             attrs(AttrKind.USE_CSS, ALIGN)),
172 
PRE(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT))173     PRE(BlockType.BLOCK, EndKind.REQUIRED,
174             EnumSet.of(Flag.EXPECT_CONTENT)) {
175         @Override
176         public boolean accepts(HtmlTag t) {
177             switch (t) {
178                 case IMG: case BIG: case SMALL: case SUB: case SUP:
179                     return false;
180                 default:
181                     return (t.blockType == BlockType.INLINE);
182             }
183         }
184     },
185 
186     SCRIPT(BlockType.OTHER, EndKind.REQUIRED,
187             attrs(AttrKind.OK, SRC)),
188 
189     SMALL(BlockType.INLINE, EndKind.REQUIRED,
190             EnumSet.of(Flag.EXPECT_CONTENT)),
191 
192     SPAN(BlockType.INLINE, EndKind.REQUIRED,
193             EnumSet.of(Flag.EXPECT_CONTENT)),
194 
195     STRONG(BlockType.INLINE, EndKind.REQUIRED,
196             EnumSet.of(Flag.EXPECT_CONTENT)),
197 
198     SUB(BlockType.INLINE, EndKind.REQUIRED,
199             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
200 
201     SUP(BlockType.INLINE, EndKind.REQUIRED,
202             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
203 
TABLE(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.OK, SUMMARY, Attr.FRAME, RULES, BORDER, CELLPADDING, CELLSPACING, WIDTH), attrs(AttrKind.USE_CSS, ALIGN, BGCOLOR))204     TABLE(BlockType.BLOCK, EndKind.REQUIRED,
205             EnumSet.of(Flag.EXPECT_CONTENT),
206             attrs(AttrKind.OK, SUMMARY, Attr.FRAME, RULES, BORDER,
207                 CELLPADDING, CELLSPACING, WIDTH), // width OK in 4.01; not allowed in 5
208             attrs(AttrKind.USE_CSS, ALIGN, BGCOLOR)) {
209         @Override
210         public boolean accepts(HtmlTag t) {
211             switch (t) {
212                 case CAPTION:
213                 case THEAD: case TBODY: case TFOOT:
214                 case TR: // HTML 3.2
215                     return true;
216                 default:
217                     return false;
218             }
219         }
220     },
221 
TBODY(BlockType.TABLE_ITEM, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN))222     TBODY(BlockType.TABLE_ITEM, EndKind.REQUIRED,
223             EnumSet.of(Flag.EXPECT_CONTENT),
224             attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) {
225         @Override
226         public boolean accepts(HtmlTag t) {
227             return (t == TR);
228         }
229     },
230 
231     TD(BlockType.TABLE_ITEM, EndKind.OPTIONAL,
232             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
233             attrs(AttrKind.OK, COLSPAN, ROWSPAN, HEADERS, SCOPE, ABBR, AXIS,
234                 ALIGN, CHAR, CHAROFF, VALIGN),
235             attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)),
236 
TFOOT(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN))237     TFOOT(BlockType.TABLE_ITEM, EndKind.REQUIRED,
238             attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) {
239         @Override
240         public boolean accepts(HtmlTag t) {
241             return (t == TR);
242         }
243     },
244 
245     TH(BlockType.TABLE_ITEM, EndKind.OPTIONAL,
246             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
247             attrs(AttrKind.OK, COLSPAN, ROWSPAN, HEADERS, SCOPE, ABBR, AXIS,
248                 ALIGN, CHAR, CHAROFF, VALIGN),
249             attrs(AttrKind.USE_CSS, WIDTH, BGCOLOR, HEIGHT, NOWRAP)),
250 
THEAD(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN))251     THEAD(BlockType.TABLE_ITEM, EndKind.REQUIRED,
252             attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN)) {
253         @Override
254         public boolean accepts(HtmlTag t) {
255             return (t == TR);
256         }
257     },
258 
259     TITLE(BlockType.OTHER, EndKind.REQUIRED),
260 
TR(BlockType.TABLE_ITEM, EndKind.OPTIONAL, attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN), attrs(AttrKind.USE_CSS, BGCOLOR))261     TR(BlockType.TABLE_ITEM, EndKind.OPTIONAL,
262             attrs(AttrKind.OK, ALIGN, CHAR, CHAROFF, VALIGN),
263             attrs(AttrKind.USE_CSS, BGCOLOR)) {
264         @Override
265         public boolean accepts(HtmlTag t) {
266             return (t == TH) || (t == TD);
267         }
268     },
269 
270     TT(BlockType.INLINE, EndKind.REQUIRED,
271             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
272 
273     U(BlockType.INLINE, EndKind.REQUIRED,
274             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
275 
UL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.OK, COMPACT, TYPE))276     UL(BlockType.BLOCK, EndKind.REQUIRED,
277             EnumSet.of(Flag.EXPECT_CONTENT),
278             attrs(AttrKind.OK, COMPACT, TYPE)) { // OK in 4.01; not allowed in 5
279         @Override
280         public boolean accepts(HtmlTag t) {
281             return (t == LI);
282         }
283     },
284 
285     VAR(BlockType.INLINE, EndKind.REQUIRED);
286 
287     /**
288      * Enum representing the type of HTML element.
289      */
290     public static enum BlockType {
291         BLOCK,
292         INLINE,
293         LIST_ITEM,
294         TABLE_ITEM,
295         OTHER;
296     }
297 
298     /**
299      * Enum representing HTML end tag requirement.
300      */
301     public static enum EndKind {
302         NONE,
303         OPTIONAL,
304         REQUIRED;
305     }
306 
307     public static enum Flag {
308         ACCEPTS_BLOCK,
309         ACCEPTS_INLINE,
310         EXPECT_CONTENT,
311         NO_NEST
312     }
313 
314     public static enum Attr {
315         ABBR,
316         ALIGN,
317         ALT,
318         AXIS,
319         BGCOLOR,
320         BORDER,
321         CELLSPACING,
322         CELLPADDING,
323         CHAR,
324         CHAROFF,
325         CLEAR,
326         CLASS,
327         COLOR,
328         COLSPAN,
329         COMPACT,
330         FACE,
331         FRAME,
332         HEADERS,
333         HEIGHT,
334         HREF,
335         HSPACE,
336         ID,
337         NAME,
338         NOWRAP,
339         REVERSED,
340         ROWSPAN,
341         RULES,
342         SCOPE,
343         SIZE,
344         SPACE,
345         SRC,
346         START,
347         STYLE,
348         SUMMARY,
349         TARGET,
350         TYPE,
351         VALIGN,
352         VALUE,
353         VSPACE,
354         WIDTH;
355 
getText()356         public String getText() {
357             return StringUtils.toLowerCase(name());
358         }
359 
360         static final Map<String,Attr> index = new HashMap<String,Attr>();
361         static {
362             for (Attr t: values()) {
t.getText()363                 index.put(t.getText(), t);
364             }
365         }
366     }
367 
368     public static enum AttrKind {
369         INVALID,
370         OBSOLETE,
371         USE_CSS,
372         OK
373     }
374 
375     // This class exists to avoid warnings from using parameterized vararg type
376     // Map<Attr,AttrKind> in signature of HtmlTag constructor.
377     private static class AttrMap extends EnumMap<Attr,AttrKind>  {
378         private static final long serialVersionUID = 0;
AttrMap()379         AttrMap() {
380             super(Attr.class);
381         }
382     }
383 
384 
385     public final BlockType blockType;
386     public final EndKind endKind;
387     public final Set<Flag> flags;
388     private final Map<Attr,AttrKind> attrs;
389 
HtmlTag(BlockType blockType, EndKind endKind, AttrMap... attrMaps)390     HtmlTag(BlockType blockType, EndKind endKind, AttrMap... attrMaps) {
391         this(blockType, endKind, Collections.<Flag>emptySet(), attrMaps);
392     }
393 
HtmlTag(BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps)394     HtmlTag(BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps) {
395         this.blockType = blockType;
396         this.endKind = endKind;
397         this.flags = flags;
398         this.attrs = new EnumMap<Attr,AttrKind>(Attr.class);
399         for (Map<Attr,AttrKind> m: attrMaps)
400             this.attrs.putAll(m);
401         attrs.put(Attr.CLASS, AttrKind.OK);
402         attrs.put(Attr.ID, AttrKind.OK);
403         attrs.put(Attr.STYLE, AttrKind.OK);
404     }
405 
accepts(HtmlTag t)406     public boolean accepts(HtmlTag t) {
407         if (flags.contains(Flag.ACCEPTS_BLOCK) && flags.contains(Flag.ACCEPTS_INLINE)) {
408             return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE);
409         } else if (flags.contains(Flag.ACCEPTS_BLOCK)) {
410             return (t.blockType == BlockType.BLOCK);
411         } else if (flags.contains(Flag.ACCEPTS_INLINE)) {
412             return (t.blockType == BlockType.INLINE);
413         } else
414             switch (blockType) {
415                 case BLOCK:
416                 case INLINE:
417                     return (t.blockType == BlockType.INLINE);
418                 case OTHER:
419                     // OTHER tags are invalid in doc comments, and will be
420                     // reported separately, so silently accept/ignore any content
421                     return true;
422                 default:
423                     // any combination which could otherwise arrive here
424                     // ought to have been handled in an overriding method
425                     throw new AssertionError(this + ":" + t);
426             }
427     }
428 
acceptsText()429     public boolean acceptsText() {
430         // generally, anywhere we can put text we can also put inline tag
431         // so check if a typical inline tag is allowed
432         return accepts(B);
433     }
434 
getText()435     public String getText() {
436         return StringUtils.toLowerCase(name());
437     }
438 
getAttr(Name attrName)439     public Attr getAttr(Name attrName) {
440         return Attr.index.get(StringUtils.toLowerCase(attrName.toString()));
441     }
442 
getAttrKind(Name attrName)443     public AttrKind getAttrKind(Name attrName) {
444         AttrKind k = attrs.get(getAttr(attrName)); // null-safe
445         return (k == null) ? AttrKind.INVALID : k;
446     }
447 
attrs(AttrKind k, Attr... attrs)448     private static AttrMap attrs(AttrKind k, Attr... attrs) {
449         AttrMap map = new AttrMap();
450         for (Attr a: attrs) map.put(a, k);
451         return map;
452     }
453 
454     private static final Map<String,HtmlTag> index = new HashMap<String,HtmlTag>();
455     static {
456         for (HtmlTag t: values()) {
t.getText()457             index.put(t.getText(), t);
458         }
459     }
460 
get(Name tagName)461     static HtmlTag get(Name tagName) {
462         return index.get(StringUtils.toLowerCase(tagName.toString()));
463     }
464 
465 }
466