1 /*
2  * Copyright (c) 2010, 2021, 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 jdk.javadoc.internal.doclint;
27 
28 import java.util.Collections;
29 import java.util.EnumMap;
30 import java.util.EnumSet;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.Set;
34 import javax.lang.model.element.Name;
35 
36 import com.sun.tools.javac.util.StringUtils;
37 
38 import static jdk.javadoc.internal.doclint.HtmlTag.Attr.*;
39 
40 /**
41  * Enum representing HTML tags.
42  *
43  * The intent of this class is to embody the semantics of the current HTML standard,
44  * to the extent supported/used by javadoc.
45  *
46  * This class is derivative of {@link jdk.javadoc.internal.doclets.formats.html.markup.TagName}.
47  * Eventually, these two should be merged back together, and possibly made
48  * public.
49  *
50  * @see <a href="https://html.spec.whatwg.org/multipage/">HTML Living Standard</a>
51  * @see <a href="http://www.w3.org/TR/html5/">HTML 5 Specification</a>
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/wai-aria/">WAI-ARIA Specification</a>
54  * @see <a href="http://www.w3.org/TR/aria-in-html/#recommendations-table">WAI-ARIA Recommendations Table</a>
55  */
56 public enum HtmlTag {
57     A(BlockType.INLINE, EndKind.REQUIRED,
58             attrs(AttrKind.OK, HREF, TARGET, ID),
59             attrs(AttrKind.HTML4, REV, CHARSET, SHAPE, COORDS, NAME)),
60 
61     ABBR(BlockType.INLINE, EndKind.REQUIRED,
62             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
63 
64     ACRONYM(ElemKind.HTML4, BlockType.INLINE, EndKind.REQUIRED,
65             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
66 
67     ADDRESS(BlockType.INLINE, EndKind.REQUIRED,
68             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
69 
70     ARTICLE(BlockType.BLOCK, EndKind.REQUIRED,
71             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
72 
73     ASIDE(BlockType.BLOCK, EndKind.REQUIRED,
74             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
75 
76     B(BlockType.INLINE, EndKind.REQUIRED,
77             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
78 
79     BDI(BlockType.INLINE, EndKind.REQUIRED),
80 
81     BIG(ElemKind.HTML4, BlockType.INLINE, EndKind.REQUIRED,
82             EnumSet.of(Flag.EXPECT_CONTENT)),
83 
84     BLOCKQUOTE(BlockType.BLOCK, EndKind.REQUIRED,
85             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
86 
87     BODY(BlockType.OTHER, EndKind.REQUIRED),
88 
89     BR(BlockType.INLINE, EndKind.NONE,
90             attrs(AttrKind.HTML4, CLEAR)),
91 
92     CAPTION(BlockType.TABLE_ITEM, EndKind.REQUIRED,
93             EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT),
94             attrs(AttrKind.HTML4, ALIGN)),
95 
96     CENTER(ElemKind.HTML4, BlockType.BLOCK, EndKind.REQUIRED,
97             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
98 
99     CITE(BlockType.INLINE, EndKind.REQUIRED,
100             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
101 
102     CODE(BlockType.INLINE, EndKind.REQUIRED,
103             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
104 
105     COL(BlockType.TABLE_ITEM, EndKind.NONE,
106             attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF, VALIGN, WIDTH)),
107 
COLGROUP(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF, VALIGN, WIDTH))108     COLGROUP(BlockType.TABLE_ITEM, EndKind.REQUIRED,
109             attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF, VALIGN, WIDTH)) {
110         @Override
111         public boolean accepts(HtmlTag t) {
112             return (t == COL);
113         }
114     },
115 
116     DD(BlockType.LIST_ITEM, EndKind.OPTIONAL,
117             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)),
118 
119     DEL(BlockType.INLINE, EndKind.REQUIRED,
120             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST),
121             attrs(AttrKind.OK, Attr.CITE, Attr.DATETIME)),
122 
123     DFN(BlockType.INLINE, EndKind.REQUIRED,
124             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
125 
126     DIV(BlockType.BLOCK, EndKind.REQUIRED,
127             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
128             attrs(AttrKind.HTML4, ALIGN)),
129 
DL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.HTML4, COMPACT))130     DL(BlockType.BLOCK, EndKind.REQUIRED,
131             EnumSet.of(Flag.EXPECT_CONTENT),
132             attrs(AttrKind.HTML4, COMPACT)) {
133         @Override
134         public boolean accepts(HtmlTag t) {
135             return (t == DT) || (t == DD);
136         }
137     },
138 
139     DT(BlockType.LIST_ITEM, EndKind.OPTIONAL,
140             EnumSet.of(Flag.ACCEPTS_INLINE, Flag.EXPECT_CONTENT)),
141 
142     EM(BlockType.INLINE, EndKind.REQUIRED,
143             EnumSet.of(Flag.NO_NEST)),
144 
145     FONT(ElemKind.HTML4, BlockType.INLINE, EndKind.REQUIRED, // tag itself is deprecated
146             EnumSet.of(Flag.EXPECT_CONTENT),
147             attrs(AttrKind.HTML4, SIZE, COLOR, FACE)),
148 
FOOTER(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE))149     FOOTER(BlockType.BLOCK, EndKind.REQUIRED,
150             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)) {
151         @Override
152         public boolean accepts(HtmlTag t) {
153             switch (t) {
154                 case HEADER: case FOOTER: case MAIN:
155                     return false;
156                 default:
157                     return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE);
158             }
159         }
160     },
161 
162     FIGURE(BlockType.BLOCK, EndKind.REQUIRED,
163             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
164 
165     FIGCAPTION(BlockType.BLOCK, EndKind.REQUIRED),
166 
167     FRAME(ElemKind.HTML4, BlockType.OTHER, EndKind.NONE),
168 
169     FRAMESET(ElemKind.HTML4, BlockType.OTHER, EndKind.REQUIRED),
170 
171     H1(BlockType.BLOCK, EndKind.REQUIRED,
172             attrs(AttrKind.HTML4, ALIGN)),
173     H2(BlockType.BLOCK, EndKind.REQUIRED,
174             attrs(AttrKind.HTML4, ALIGN)),
175     H3(BlockType.BLOCK, EndKind.REQUIRED,
176             attrs(AttrKind.HTML4, ALIGN)),
177     H4(BlockType.BLOCK, EndKind.REQUIRED,
178             attrs(AttrKind.HTML4, ALIGN)),
179     H5(BlockType.BLOCK, EndKind.REQUIRED,
180             attrs(AttrKind.HTML4, ALIGN)),
181     H6(BlockType.BLOCK, EndKind.REQUIRED,
182             attrs(AttrKind.HTML4, ALIGN)),
183 
184     HEAD(BlockType.OTHER, EndKind.REQUIRED),
185 
HEADER(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE))186     HEADER(BlockType.BLOCK, EndKind.REQUIRED,
187             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)) {
188         @Override
189         public boolean accepts(HtmlTag t) {
190             switch (t) {
191                 case HEADER: case FOOTER: case MAIN:
192                     return false;
193                 default:
194                     return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE);
195             }
196         }
197     },
198 
199     HR(BlockType.BLOCK, EndKind.NONE,
200             attrs(AttrKind.HTML4, WIDTH, ALIGN, NOSHADE, SIZE)),
201 
202     HTML(BlockType.OTHER, EndKind.REQUIRED),
203 
204     I(BlockType.INLINE, EndKind.REQUIRED,
205             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
206 
207     IFRAME(BlockType.OTHER, EndKind.REQUIRED),
208 
209     IMG(BlockType.INLINE, EndKind.NONE,
210             attrs(AttrKind.OK, SRC, ALT, HEIGHT, WIDTH, CROSSORIGIN),
211             attrs(AttrKind.HTML4, NAME, ALIGN, HSPACE, VSPACE, BORDER)),
212 
213     INS(BlockType.INLINE, EndKind.REQUIRED,
214             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST),
215             attrs(AttrKind.OK, Attr.CITE, Attr.DATETIME)),
216 
217     KBD(BlockType.INLINE, EndKind.REQUIRED,
218             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
219 
220     LI(BlockType.LIST_ITEM, EndKind.OPTIONAL,
221             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
222             attrs(AttrKind.OK, VALUE),
223             attrs(AttrKind.HTML4, TYPE)),
224 
225     LINK(BlockType.OTHER, EndKind.NONE),
226 
227     MAIN(BlockType.OTHER, EndKind.REQUIRED),
228 
229     MARK(BlockType.INLINE, EndKind.REQUIRED),
230 
MENU(BlockType.BLOCK, EndKind.REQUIRED)231     MENU(BlockType.BLOCK, EndKind.REQUIRED) {
232         @Override
233         public boolean accepts(HtmlTag t) {
234             return (t == LI);
235         }
236     },
237 
238     META(BlockType.OTHER, EndKind.NONE),
239 
240     NAV(BlockType.BLOCK, EndKind.REQUIRED,
241             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
242 
243     NOFRAMES(ElemKind.HTML4, BlockType.OTHER, EndKind.REQUIRED),
244 
245     NOSCRIPT(BlockType.BLOCK, EndKind.REQUIRED),
246 
OL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.OK, START, TYPE, REVERSED), attrs(AttrKind.HTML4, COMPACT))247     OL(BlockType.BLOCK, EndKind.REQUIRED,
248             EnumSet.of(Flag.EXPECT_CONTENT),
249             attrs(AttrKind.OK, START, TYPE, REVERSED),
250             attrs(AttrKind.HTML4, COMPACT)) {
251         @Override
252         public boolean accepts(HtmlTag t) {
253             return (t == LI);
254         }
255     },
256 
257     P(BlockType.BLOCK, EndKind.OPTIONAL,
258             EnumSet.of(Flag.EXPECT_CONTENT),
259             attrs(AttrKind.HTML4, ALIGN)),
260 
PRE(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.HTML4, WIDTH))261     PRE(BlockType.BLOCK, EndKind.REQUIRED,
262             EnumSet.of(Flag.EXPECT_CONTENT),
263             attrs(AttrKind.HTML4, WIDTH)) {
264         @Override
265         public boolean accepts(HtmlTag t) {
266             switch (t) {
267                 case IMG: case BIG: case SMALL: case SUB: case SUP:
268                     return false;
269                 default:
270                     return (t.blockType == BlockType.INLINE);
271             }
272         }
273     },
274 
275     Q(BlockType.INLINE, EndKind.REQUIRED,
276             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
277 
278     S(BlockType.INLINE, EndKind.REQUIRED,
279             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
280 
281     SAMP(BlockType.INLINE, EndKind.REQUIRED,
282             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
283 
284     SCRIPT(BlockType.OTHER, EndKind.REQUIRED,
285             attrs(AttrKind.OK, SRC)),
286 
287     SECTION(BlockType.BLOCK, EndKind.REQUIRED,
288             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
289 
290     SMALL(BlockType.INLINE, EndKind.REQUIRED,
291             EnumSet.of(Flag.EXPECT_CONTENT)),
292 
293     SPAN(BlockType.INLINE, EndKind.REQUIRED,
294             EnumSet.of(Flag.EXPECT_CONTENT)),
295 
296     STRIKE(ElemKind.HTML4, BlockType.INLINE, EndKind.REQUIRED,
297             EnumSet.of(Flag.EXPECT_CONTENT)),
298 
299     STRONG(BlockType.INLINE, EndKind.REQUIRED,
300             EnumSet.of(Flag.EXPECT_CONTENT)),
301 
302     STYLE(BlockType.OTHER, EndKind.REQUIRED),
303 
304     SUB(BlockType.INLINE, EndKind.REQUIRED,
305             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
306 
307     SUP(BlockType.INLINE, EndKind.REQUIRED,
308             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
309 
TABLE(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.OK, BORDER), attrs(AttrKind.HTML4, SUMMARY, CELLPADDING, CELLSPACING, Attr.FRAME, RULES, WIDTH, ALIGN, BGCOLOR))310     TABLE(BlockType.BLOCK, EndKind.REQUIRED,
311             EnumSet.of(Flag.EXPECT_CONTENT),
312             attrs(AttrKind.OK, BORDER),
313             attrs(AttrKind.HTML4, SUMMARY, CELLPADDING, CELLSPACING,
314                     Attr.FRAME, RULES, WIDTH, ALIGN, BGCOLOR)) {
315         @Override
316         public boolean accepts(HtmlTag t) {
317             switch (t) {
318                 case CAPTION:
319                 case COLGROUP:
320                 case THEAD: case TBODY: case TFOOT:
321                 case TR: // HTML 3.2
322                     return true;
323                 default:
324                     return false;
325             }
326         }
327     },
328 
TBODY(BlockType.TABLE_ITEM, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.HTML4, ALIGN, VALIGN, CHAR, CHAROFF))329     TBODY(BlockType.TABLE_ITEM, EndKind.REQUIRED,
330             EnumSet.of(Flag.EXPECT_CONTENT),
331             attrs(AttrKind.HTML4, ALIGN, VALIGN, CHAR, CHAROFF)) {
332         @Override
333         public boolean accepts(HtmlTag t) {
334             return (t == TR);
335         }
336     },
337 
338     TD(BlockType.TABLE_ITEM, EndKind.OPTIONAL,
339             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
340             attrs(AttrKind.OK, COLSPAN, ROWSPAN, HEADERS),
341             attrs(AttrKind.HTML4, AXIS, Attr.ABBR, SCOPE, ALIGN, VALIGN, CHAR, CHAROFF,
342                     WIDTH, BGCOLOR, HEIGHT, NOWRAP)),
343 
344     TEMPLATE(BlockType.BLOCK, EndKind.REQUIRED,
345             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
346 
TFOOT(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.HTML4, ALIGN, VALIGN, CHAR, CHAROFF))347     TFOOT(BlockType.TABLE_ITEM, EndKind.REQUIRED,
348             attrs(AttrKind.HTML4, ALIGN, VALIGN, CHAR, CHAROFF)) {
349         @Override
350         public boolean accepts(HtmlTag t) {
351             return (t == TR);
352         }
353     },
354 
355     TH(BlockType.TABLE_ITEM, EndKind.OPTIONAL,
356             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE),
357             attrs(AttrKind.OK, COLSPAN, ROWSPAN, HEADERS, SCOPE, Attr.ABBR),
358             attrs(AttrKind.HTML4, WIDTH, BGCOLOR, HEIGHT, NOWRAP, AXIS, ALIGN, CHAR, CHAROFF, VALIGN)),
359 
THEAD(BlockType.TABLE_ITEM, EndKind.REQUIRED, attrs(AttrKind.HTML4, ALIGN, VALIGN, CHAR, CHAROFF))360     THEAD(BlockType.TABLE_ITEM, EndKind.REQUIRED,
361             attrs(AttrKind.HTML4, ALIGN, VALIGN, CHAR, CHAROFF)) {
362         @Override
363         public boolean accepts(HtmlTag t) {
364             return (t == TR);
365         }
366     },
367 
368     TIME(BlockType.INLINE, EndKind.REQUIRED),
369 
370     TITLE(BlockType.OTHER, EndKind.REQUIRED),
371 
TR(BlockType.TABLE_ITEM, EndKind.OPTIONAL, attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF, BGCOLOR, VALIGN))372     TR(BlockType.TABLE_ITEM, EndKind.OPTIONAL,
373             attrs(AttrKind.HTML4, ALIGN, CHAR, CHAROFF, BGCOLOR, VALIGN)) {
374         @Override
375         public boolean accepts(HtmlTag t) {
376             return (t == TH) || (t == TD);
377         }
378     },
379 
380     TT(ElemKind.HTML4, BlockType.INLINE, EndKind.REQUIRED,
381             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
382 
383     U(BlockType.INLINE, EndKind.REQUIRED,
384             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
385 
UL(BlockType.BLOCK, EndKind.REQUIRED, EnumSet.of(Flag.EXPECT_CONTENT), attrs(AttrKind.HTML4, COMPACT, TYPE))386     UL(BlockType.BLOCK, EndKind.REQUIRED,
387             EnumSet.of(Flag.EXPECT_CONTENT),
388             attrs(AttrKind.HTML4, COMPACT, TYPE)) {
389         @Override
390         public boolean accepts(HtmlTag t) {
391             return (t == LI);
392         }
393     },
394 
395     WBR(BlockType.INLINE, EndKind.NONE),
396 
397     VAR(BlockType.INLINE, EndKind.REQUIRED);
398 
399     /**
400      * Enum representing the supportability of HTML element.
401      */
402     public enum ElemKind {
403         OK,
404         INVALID,
405         OBSOLETE,
406         HTML4
407     }
408 
409     /**
410      * Enum representing the type of HTML element.
411      */
412     public enum BlockType {
413         BLOCK,
414         INLINE,
415         LIST_ITEM,
416         TABLE_ITEM,
417         OTHER
418     }
419 
420     /**
421      * Enum representing HTML end tag requirement.
422      */
423     public enum EndKind {
424         NONE,
425         OPTIONAL,
426         REQUIRED
427     }
428 
429     public enum Flag {
430         ACCEPTS_BLOCK,
431         ACCEPTS_INLINE,
432         EXPECT_CONTENT,
433         NO_NEST
434     }
435 
436     public enum Attr {
437         ABBR,
438         ALIGN,
439         ALINK,
440         ALT,
441         ARIA_ACTIVEDESCENDANT,
442         ARIA_CONTROLS,
443         ARIA_DESCRIBEDBY,
444         ARIA_EXPANDED,
445         ARIA_LABEL,
446         ARIA_LABELLEDBY,
447         ARIA_LEVEL,
448         ARIA_MULTISELECTABLE,
449         ARIA_OWNS,
450         ARIA_POSINSET,
451         ARIA_SETSIZE,
452         ARIA_READONLY,
453         ARIA_REQUIRED,
454         ARIA_SELECTED,
455         ARIA_SORT,
456         AXIS,
457         BACKGROUND,
458         BGCOLOR,
459         BORDER,
460         CELLSPACING,
461         CELLPADDING,
462         CHAR,
463         CHAROFF,
464         CHARSET,
465         CITE,
466         CLEAR,
467         CLASS,
468         COLOR,
469         COLSPAN,
470         COMPACT,
471         COORDS,
472         CROSSORIGIN,
473         DATETIME,
474         FACE,
475         FRAME,
476         FRAMEBORDER,
477         HEADERS,
478         HEIGHT,
479         HREF,
480         HSPACE,
481         ID,
482         LINK,
483         LONGDESC,
484         MARGINHEIGHT,
485         MARGINWIDTH,
486         NAME,
487         NOSHADE,
488         NOWRAP,
489         PROFILE,
490         REV,
491         REVERSED,
492         ROLE,
493         ROWSPAN,
494         RULES,
495         SCHEME,
496         SCOPE,
497         SCROLLING,
498         SHAPE,
499         SIZE,
500         SPACE,
501         SRC,
502         START,
503         STYLE,
504         SUMMARY,
505         TARGET,
506         TEXT,
507         TYPE,
508         VALIGN,
509         VALUE,
510         VERSION,
511         VLINK,
512         VSPACE,
513         WIDTH;
514 
515         private final String name;
516 
Attr()517         Attr() {
518             name = StringUtils.toLowerCase(name().replace("_", "-"));
519         }
520 
getText()521         public String getText() {
522             return name;
523         }
524 
525         static final Map<String,Attr> index = new HashMap<>();
526         static {
527             for (Attr t: values()) {
t.getText()528                 index.put(t.getText(), t);
529             }
530         }
531     }
532 
533     public enum AttrKind {
534         OK,
535         INVALID,
536         OBSOLETE,
537         HTML4
538     }
539 
540     // This class exists to avoid warnings from using parameterized vararg type
541     // Map<Attr,AttrKind> in signature of HtmlTag constructor.
542     private static class AttrMap extends EnumMap<Attr,AttrKind>  {
543         private static final long serialVersionUID = 0;
AttrMap()544         AttrMap() {
545             super(Attr.class);
546         }
547     }
548 
549 
550     public final ElemKind elemKind;
551     public final BlockType blockType;
552     public final EndKind endKind;
553     public final Set<Flag> flags;
554     private final Map<Attr,AttrKind> attrs;
555 
HtmlTag(BlockType blockType, EndKind endKind, AttrMap... attrMaps)556     HtmlTag(BlockType blockType, EndKind endKind, AttrMap... attrMaps) {
557         this(ElemKind.OK, blockType, endKind, Collections.emptySet(), attrMaps);
558     }
559 
HtmlTag(ElemKind elemKind, BlockType blockType, EndKind endKind, AttrMap... attrMaps)560     HtmlTag(ElemKind elemKind, BlockType blockType, EndKind endKind, AttrMap... attrMaps) {
561         this(elemKind, blockType, endKind, Collections.emptySet(), attrMaps);
562     }
563 
HtmlTag(BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps)564     HtmlTag(BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps) {
565         this(ElemKind.OK, blockType, endKind, flags, attrMaps);
566     }
567 
HtmlTag(ElemKind elemKind, BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps)568     HtmlTag(ElemKind elemKind, BlockType blockType, EndKind endKind, Set<Flag> flags, AttrMap... attrMaps) {
569         this.elemKind = elemKind;
570         this.blockType = blockType;
571         this.endKind = endKind;
572         this.flags = flags;
573         this.attrs = new EnumMap<>(Attr.class);
574         for (Map<Attr,AttrKind> m: attrMaps)
575             this.attrs.putAll(m);
576         attrs.put(Attr.CLASS, AttrKind.OK);
577         attrs.put(Attr.ID, AttrKind.OK);
578         attrs.put(Attr.STYLE, AttrKind.OK);
579         attrs.put(Attr.ROLE, AttrKind.OK);
580         // for now, assume that all ARIA attributes are allowed on all tags.
581         attrs.put(Attr.ARIA_ACTIVEDESCENDANT, AttrKind.OK);
582         attrs.put(Attr.ARIA_CONTROLS, AttrKind.OK);
583         attrs.put(Attr.ARIA_DESCRIBEDBY, AttrKind.OK);
584         attrs.put(Attr.ARIA_EXPANDED, AttrKind.OK);
585         attrs.put(Attr.ARIA_LABEL, AttrKind.OK);
586         attrs.put(Attr.ARIA_LABELLEDBY, AttrKind.OK);
587         attrs.put(Attr.ARIA_LEVEL, AttrKind.OK);
588         attrs.put(Attr.ARIA_MULTISELECTABLE, AttrKind.OK);
589         attrs.put(Attr.ARIA_OWNS, AttrKind.OK);
590         attrs.put(Attr.ARIA_POSINSET, AttrKind.OK);
591         attrs.put(Attr.ARIA_READONLY, AttrKind.OK);
592         attrs.put(Attr.ARIA_REQUIRED, AttrKind.OK);
593         attrs.put(Attr.ARIA_SELECTED, AttrKind.OK);
594         attrs.put(Attr.ARIA_SETSIZE, AttrKind.OK);
595         attrs.put(Attr.ARIA_SORT, AttrKind.OK);
596     }
597 
accepts(HtmlTag t)598     public boolean accepts(HtmlTag t) {
599         if (flags.contains(Flag.ACCEPTS_BLOCK) && flags.contains(Flag.ACCEPTS_INLINE)) {
600             return (t.blockType == BlockType.BLOCK) || (t.blockType == BlockType.INLINE);
601         } else if (flags.contains(Flag.ACCEPTS_BLOCK)) {
602             return (t.blockType == BlockType.BLOCK);
603         } else if (flags.contains(Flag.ACCEPTS_INLINE)) {
604             return (t.blockType == BlockType.INLINE);
605         } else
606             switch (blockType) {
607                 case BLOCK:
608                 case INLINE:
609                     return (t.blockType == BlockType.INLINE);
610                 case OTHER:
611                     // OTHER tags are invalid in doc comments, and will be
612                     // reported separately, so silently accept/ignore any content
613                     return true;
614                 default:
615                     // any combination which could otherwise arrive here
616                     // ought to have been handled in an overriding method
617                     throw new AssertionError(this + ":" + t);
618             }
619     }
620 
acceptsText()621     public boolean acceptsText() {
622         // generally, anywhere we can put text we can also put inline tag
623         // so check if a typical inline tag is allowed
624         return accepts(B);
625     }
626 
getText()627     public String getText() {
628         return StringUtils.toLowerCase(name());
629     }
630 
getAttr(Name attrName)631     public Attr getAttr(Name attrName) {
632         return Attr.index.get(StringUtils.toLowerCase(attrName.toString()));
633     }
634 
getAttrKind(Name attrName)635     public AttrKind getAttrKind(Name attrName) {
636         AttrKind k = attrs.get(getAttr(attrName)); // null-safe
637         return (k == null) ? AttrKind.INVALID : k;
638     }
639 
attrs(AttrKind k, Attr... attrs)640     private static AttrMap attrs(AttrKind k, Attr... attrs) {
641         AttrMap map = new AttrMap();
642         for (Attr a: attrs) map.put(a, k);
643         return map;
644     }
645 
646     private static final Map<String, HtmlTag> index = new HashMap<>();
647     static {
648         for (HtmlTag t: values()) {
t.getText()649             index.put(t.getText(), t);
650         }
651     }
652 
get(Name tagName)653     public static HtmlTag get(Name tagName) {
654         return index.get(StringUtils.toLowerCase(tagName.toString()));
655     }
656 }
657