src/share/classes/com/sun/tools/javadoc/JavaScriptScanner.java

Wed, 23 Nov 2016 00:35:47 -0800

author
asaha
date
Wed, 23 Nov 2016 00:35:47 -0800
changeset 3343
b634abfcd98f
parent 3315
6f0746b6de9f
child 3845
735048c9f2d6
permissions
-rw-r--r--

8170268: 8u121 L10n resource file update - msgdrop 20
Reviewed-by: coffeys
Contributed-by: li.jiang@oracle.com

     1 /*
     2  * Copyright (c) 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  */
    26 package com.sun.tools.javadoc;
    28 import java.util.Arrays;
    29 import java.util.HashMap;
    30 import java.util.HashSet;
    31 import java.util.Locale;
    32 import java.util.Map;
    33 import java.util.Set;
    35 import com.sun.tools.javadoc.JavaScriptScanner.TagParser.Kind;
    37 import static com.sun.tools.javac.util.LayoutCharacters.EOI;
    39 /**
    40  * Parser to detect use of JavaScript in documentation comments.
    41  */
    42 @Deprecated
    43 public class JavaScriptScanner {
    44     public static interface Reporter {
    45         void report();
    46     }
    48     static class ParseException extends Exception {
    49         private static final long serialVersionUID = 0;
    50         ParseException(String key) {
    51             super(key);
    52         }
    53     }
    55     private Reporter reporter;
    57     /** The input buffer, index of most recent character read,
    58      *  index of one past last character in buffer.
    59      */
    60     protected char[] buf;
    61     protected int bp;
    62     protected int buflen;
    64     /** The current character.
    65      */
    66     protected char ch;
    68     private boolean newline = true;
    70     Map<String, TagParser> tagParsers;
    71     Set<String> eventAttrs;
    72     Set<String> uriAttrs;
    74     public JavaScriptScanner() {
    75         initTagParsers();
    76         initEventAttrs();
    77         initURIAttrs();
    78     }
    80     public void parse(String comment, Reporter r) {
    81         reporter = r;
    82         String c = comment;
    83         buf = new char[c.length() + 1];
    84         c.getChars(0, c.length(), buf, 0);
    85         buf[buf.length - 1] = EOI;
    86         buflen = buf.length - 1;
    87         bp = -1;
    88         newline = true;
    89         nextChar();
    91         blockContent();
    92         blockTags();
    93     }
    95     private void checkHtmlTag(String tag) {
    96         if (tag.equalsIgnoreCase("script")) {
    97             reporter.report();
    98         }
    99     }
   101     private void checkHtmlAttr(String name, String value) {
   102         String n = name.toLowerCase(Locale.ENGLISH);
   103         if (eventAttrs.contains(n)
   104                 || uriAttrs.contains(n)
   105                     && value != null && value.toLowerCase(Locale.ENGLISH).trim().startsWith("javascript:")) {
   106             reporter.report();
   107         }
   108     }
   110     void nextChar() {
   111         ch = buf[bp < buflen ? ++bp : buflen];
   112         switch (ch) {
   113             case '\f': case '\n': case '\r':
   114                 newline = true;
   115         }
   116     }
   118     /**
   119      * Read block content, consisting of text, html and inline tags.
   120      * Terminated by the end of input, or the beginning of the next block tag:
   121      * i.e. @ as the first non-whitespace character on a line.
   122      */
   123     @SuppressWarnings("fallthrough")
   124     protected void blockContent() {
   126         loop:
   127         while (bp < buflen) {
   128             switch (ch) {
   129                 case '\n': case '\r': case '\f':
   130                     newline = true;
   131                     // fallthrough
   133                 case ' ': case '\t':
   134                     nextChar();
   135                     break;
   137                 case '&':
   138                     entity(null);
   139                     break;
   141                 case '<':
   142                     html();
   143                     break;
   145                 case '>':
   146                     newline = false;
   147                     nextChar();
   148                     break;
   150                 case '{':
   151                     inlineTag(null);
   152                     break;
   154                 case '@':
   155                     if (newline) {
   156                         break loop;
   157                     }
   158                     // fallthrough
   160                 default:
   161                     newline = false;
   162                     nextChar();
   163             }
   164         }
   165     }
   167     /**
   168      * Read a series of block tags, including their content.
   169      * Standard tags parse their content appropriately.
   170      * Non-standard tags are represented by {@link UnknownBlockTag}.
   171      */
   172     protected void blockTags() {
   173         while (ch == '@')
   174             blockTag();
   175     }
   177     /**
   178      * Read a single block tag, including its content.
   179      * Standard tags parse their content appropriately.
   180      * Non-standard tags are represented by {@link UnknownBlockTag}.
   181      */
   182     protected void blockTag() {
   183         int p = bp;
   184         try {
   185             nextChar();
   186             if (isIdentifierStart(ch)) {
   187                 String name = readTagName();
   188                 TagParser tp = tagParsers.get(name);
   189                 if (tp == null) {
   190                     blockContent();
   191                 } else {
   192                     switch (tp.getKind()) {
   193                         case BLOCK:
   194                             tp.parse(p);
   195                             return;
   196                         case INLINE:
   197                             return;
   198                     }
   199                 }
   200             }
   201             blockContent();
   202         } catch (ParseException e) {
   203             blockContent();
   204         }
   205     }
   207     protected void inlineTag(Void list) {
   208         newline = false;
   209         nextChar();
   210         if (ch == '@') {
   211             inlineTag();
   212         }
   213     }
   215     /**
   216      * Read a single inline tag, including its content.
   217      * Standard tags parse their content appropriately.
   218      * Non-standard tags are represented by {@link UnknownBlockTag}.
   219      * Malformed tags may be returned as {@link Erroneous}.
   220      */
   221     protected void inlineTag() {
   222         int p = bp - 1;
   223         try {
   224             nextChar();
   225             if (isIdentifierStart(ch)) {
   226                 String name = readTagName();
   227                 TagParser tp = tagParsers.get(name);
   229                 if (tp == null) {
   230                     skipWhitespace();
   231                     inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
   232                     nextChar();
   233                 } else {
   234                     skipWhitespace();
   235                     if (tp.getKind() == TagParser.Kind.INLINE) {
   236                         tp.parse(p);
   237                     } else { // handle block tags (ex: @see) in inline content
   238                         inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
   239                         nextChar();
   240                     }
   241                 }
   242             }
   243         } catch (ParseException e) {
   244         }
   245     }
   247     private static enum WhitespaceRetentionPolicy {
   248         RETAIN_ALL,
   249         REMOVE_FIRST_SPACE,
   250         REMOVE_ALL
   251     }
   253     /**
   254      * Read plain text content of an inline tag.
   255      * Matching pairs of { } are skipped; the text is terminated by the first
   256      * unmatched }. It is an error if the beginning of the next tag is detected.
   257      */
   258     private void inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws ParseException {
   259         switch (whitespacePolicy) {
   260             case REMOVE_ALL:
   261                 skipWhitespace();
   262                 break;
   263             case REMOVE_FIRST_SPACE:
   264                 if (ch == ' ')
   265                     nextChar();
   266                 break;
   267             case RETAIN_ALL:
   268             default:
   269                 // do nothing
   270                 break;
   272         }
   273         int pos = bp;
   274         int depth = 1;
   276         loop:
   277         while (bp < buflen) {
   278             switch (ch) {
   279                 case '\n': case '\r': case '\f':
   280                     newline = true;
   281                     break;
   283                 case ' ': case '\t':
   284                     break;
   286                 case '{':
   287                     newline = false;
   288                     depth++;
   289                     break;
   291                 case '}':
   292                     if (--depth == 0) {
   293                         return;
   294                     }
   295                     newline = false;
   296                     break;
   298                 case '@':
   299                     if (newline)
   300                         break loop;
   301                     newline = false;
   302                     break;
   304                 default:
   305                     newline = false;
   306                     break;
   307             }
   308             nextChar();
   309         }
   310         throw new ParseException("dc.unterminated.inline.tag");
   311     }
   313     /**
   314      * Read Java class name, possibly followed by member
   315      * Matching pairs of {@literal < >} are skipped. The text is terminated by the first
   316      * unmatched }. It is an error if the beginning of the next tag is detected.
   317      */
   318     // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
   319     // TODO: improve quality of parse to forbid bad constructions.
   320     // TODO: update to use ReferenceParser
   321     @SuppressWarnings("fallthrough")
   322     protected void reference(boolean allowMember) throws ParseException {
   323         int pos = bp;
   324         int depth = 0;
   326         // scan to find the end of the signature, by looking for the first
   327         // whitespace not enclosed in () or <>, or the end of the tag
   328         loop:
   329         while (bp < buflen) {
   330             switch (ch) {
   331                 case '\n': case '\r': case '\f':
   332                     newline = true;
   333                     // fallthrough
   335                 case ' ': case '\t':
   336                     if (depth == 0)
   337                         break loop;
   338                     break;
   340                 case '(':
   341                 case '<':
   342                     newline = false;
   343                     depth++;
   344                     break;
   346                 case ')':
   347                 case '>':
   348                     newline = false;
   349                     --depth;
   350                     break;
   352                 case '}':
   353                     if (bp == pos)
   354                         return;
   355                     newline = false;
   356                     break loop;
   358                 case '@':
   359                     if (newline)
   360                         break loop;
   361                     // fallthrough
   363                 default:
   364                     newline = false;
   366             }
   367             nextChar();
   368         }
   370         if (depth != 0)
   371             throw new ParseException("dc.unterminated.signature");
   372     }
   374     /**
   375      * Read Java identifier
   376      * Matching pairs of { } are skipped; the text is terminated by the first
   377      * unmatched }. It is an error if the beginning of the next tag is detected.
   378      */
   379     @SuppressWarnings("fallthrough")
   380     protected void identifier() throws ParseException {
   381         skipWhitespace();
   382         int pos = bp;
   384         if (isJavaIdentifierStart(ch)) {
   385             readJavaIdentifier();
   386             return;
   387         }
   389         throw new ParseException("dc.identifier.expected");
   390     }
   392     /**
   393      * Read a quoted string.
   394      * It is an error if the beginning of the next tag is detected.
   395      */
   396     @SuppressWarnings("fallthrough")
   397     protected void quotedString() {
   398         int pos = bp;
   399         nextChar();
   401         loop:
   402         while (bp < buflen) {
   403             switch (ch) {
   404                 case '\n': case '\r': case '\f':
   405                     newline = true;
   406                     break;
   408                 case ' ': case '\t':
   409                     break;
   411                 case '"':
   412                     nextChar();
   413                     // trim trailing white-space?
   414                     return;
   416                 case '@':
   417                     if (newline)
   418                         break loop;
   420             }
   421             nextChar();
   422         }
   423     }
   425     /**
   426      * Read a term ie. one word.
   427      * It is an error if the beginning of the next tag is detected.
   428      */
   429     @SuppressWarnings("fallthrough")
   430     protected void inlineWord() {
   431         int pos = bp;
   432         int depth = 0;
   433         loop:
   434         while (bp < buflen) {
   435             switch (ch) {
   436                 case '\n':
   437                     newline = true;
   438                     // fallthrough
   440                 case '\r': case '\f': case ' ': case '\t':
   441                     return;
   443                 case '@':
   444                     if (newline)
   445                         break loop;
   447                 case '{':
   448                     depth++;
   449                     break;
   451                 case '}':
   452                     if (depth == 0 || --depth == 0)
   453                         return;
   454                     break;
   455             }
   456             newline = false;
   457             nextChar();
   458         }
   459     }
   461     /**
   462      * Read general text content of an inline tag, including HTML entities and elements.
   463      * Matching pairs of { } are skipped; the text is terminated by the first
   464      * unmatched }. It is an error if the beginning of the next tag is detected.
   465      */
   466     @SuppressWarnings("fallthrough")
   467     private void inlineContent() {
   469         skipWhitespace();
   470         int pos = bp;
   471         int depth = 1;
   473         loop:
   474         while (bp < buflen) {
   476             switch (ch) {
   477                 case '\n': case '\r': case '\f':
   478                     newline = true;
   479                     // fall through
   481                 case ' ': case '\t':
   482                     nextChar();
   483                     break;
   485                 case '&':
   486                     entity(null);
   487                     break;
   489                 case '<':
   490                     newline = false;
   491                     html();
   492                     break;
   494                 case '{':
   495                     newline = false;
   496                     depth++;
   497                     nextChar();
   498                     break;
   500                 case '}':
   501                     newline = false;
   502                     if (--depth == 0) {
   503                         nextChar();
   504                         return;
   505                     }
   506                     nextChar();
   507                     break;
   509                 case '@':
   510                     if (newline)
   511                         break loop;
   512                     // fallthrough
   514                 default:
   515                     nextChar();
   516                     break;
   517             }
   518         }
   520     }
   522     protected void entity(Void list) {
   523         newline = false;
   524         entity();
   525     }
   527     /**
   528      * Read an HTML entity.
   529      * {@literal &identifier; } or {@literal &#digits; } or {@literal &#xhex-digits; }
   530      */
   531     protected void entity() {
   532         nextChar();
   533         String name = null;
   534         if (ch == '#') {
   535             int namep = bp;
   536             nextChar();
   537             if (isDecimalDigit(ch)) {
   538                 nextChar();
   539                 while (isDecimalDigit(ch))
   540                     nextChar();
   541                 name = new String(buf, namep, bp - namep);
   542             } else if (ch == 'x' || ch == 'X') {
   543                 nextChar();
   544                 if (isHexDigit(ch)) {
   545                     nextChar();
   546                     while (isHexDigit(ch))
   547                         nextChar();
   548                     name = new String(buf, namep, bp - namep);
   549                 }
   550             }
   551         } else if (isIdentifierStart(ch)) {
   552             name = readIdentifier();
   553         }
   555         if (name != null) {
   556             if (ch != ';')
   557                 return;
   558             nextChar();
   559         }
   560     }
   562     /**
   563      * Read the start or end of an HTML tag, or an HTML comment
   564      * {@literal <identifier attrs> } or {@literal </identifier> }
   565      */
   566     protected void html() {
   567         int p = bp;
   568         nextChar();
   569         if (isIdentifierStart(ch)) {
   570             String name = readIdentifier();
   571             checkHtmlTag(name);
   572             htmlAttrs();
   573             if (ch == '/') {
   574                 nextChar();
   575             }
   576             if (ch == '>') {
   577                 nextChar();
   578                 return;
   579             }
   580         } else if (ch == '/') {
   581             nextChar();
   582             if (isIdentifierStart(ch)) {
   583                 readIdentifier();
   584                 skipWhitespace();
   585                 if (ch == '>') {
   586                     nextChar();
   587                     return;
   588                 }
   589             }
   590         } else if (ch == '!') {
   591             nextChar();
   592             if (ch == '-') {
   593                 nextChar();
   594                 if (ch == '-') {
   595                     nextChar();
   596                     while (bp < buflen) {
   597                         int dash = 0;
   598                         while (ch == '-') {
   599                             dash++;
   600                             nextChar();
   601                         }
   602                         // Strictly speaking, a comment should not contain "--"
   603                         // so dash > 2 is an error, dash == 2 implies ch == '>'
   604                         // See http://www.w3.org/TR/html-markup/syntax.html#syntax-comments
   605                         // for more details.
   606                         if (dash >= 2 && ch == '>') {
   607                             nextChar();
   608                             return;
   609                         }
   611                         nextChar();
   612                     }
   613                 }
   614             }
   615         }
   617         bp = p + 1;
   618         ch = buf[bp];
   619     }
   621     /**
   622      * Read a series of HTML attributes, terminated by {@literal > }.
   623      * Each attribute is of the form {@literal identifier[=value] }.
   624      * "value" may be unquoted, single-quoted, or double-quoted.
   625      */
   626     protected void htmlAttrs() {
   627         skipWhitespace();
   629         loop:
   630         while (isIdentifierStart(ch)) {
   631             int namePos = bp;
   632             String name = readAttributeName();
   633             skipWhitespace();
   634             StringBuilder value = new StringBuilder();
   635             if (ch == '=') {
   636                 nextChar();
   637                 skipWhitespace();
   638                 if (ch == '\'' || ch == '"') {
   639                     char quote = ch;
   640                     nextChar();
   641                     while (bp < buflen && ch != quote) {
   642                         if (newline && ch == '@') {
   643                             // No point trying to read more.
   644                             // In fact, all attrs get discarded by the caller
   645                             // and superseded by a malformed.html node because
   646                             // the html tag itself is not terminated correctly.
   647                             break loop;
   648                         }
   649                         value.append(ch);
   650                         nextChar();
   651                     }
   652                     nextChar();
   653                 } else {
   654                     while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
   655                         value.append(ch);
   656                         nextChar();
   657                     }
   658                 }
   659                 skipWhitespace();
   660             }
   661             checkHtmlAttr(name, value.toString());
   662         }
   663     }
   665     protected void attrValueChar(Void list) {
   666         switch (ch) {
   667             case '&':
   668                 entity(list);
   669                 break;
   671             case '{':
   672                 inlineTag(list);
   673                 break;
   675             default:
   676                 nextChar();
   677         }
   678     }
   680     protected boolean isIdentifierStart(char ch) {
   681         return Character.isUnicodeIdentifierStart(ch);
   682     }
   684     protected String readIdentifier() {
   685         int start = bp;
   686         nextChar();
   687         while (bp < buflen && Character.isUnicodeIdentifierPart(ch))
   688             nextChar();
   689         return new String(buf, start, bp - start);
   690     }
   692     protected String readAttributeName() {
   693         int start = bp;
   694         nextChar();
   695         while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '-'))
   696             nextChar();
   697         return new String(buf, start, bp - start);
   698     }
   700     protected String readTagName() {
   701         int start = bp;
   702         nextChar();
   703         while (bp < buflen
   704                 && (Character.isUnicodeIdentifierPart(ch) || ch == '.'
   705                 || ch == '-' || ch == ':')) {
   706             nextChar();
   707         }
   708         return new String(buf, start, bp - start);
   709     }
   711     protected boolean isJavaIdentifierStart(char ch) {
   712         return Character.isJavaIdentifierStart(ch);
   713     }
   715     protected String readJavaIdentifier() {
   716         int start = bp;
   717         nextChar();
   718         while (bp < buflen && Character.isJavaIdentifierPart(ch))
   719             nextChar();
   720         return new String(buf, start, bp - start);
   721     }
   723     protected boolean isDecimalDigit(char ch) {
   724         return ('0' <= ch && ch <= '9');
   725     }
   727     protected boolean isHexDigit(char ch) {
   728         return ('0' <= ch && ch <= '9')
   729                 || ('a' <= ch && ch <= 'f')
   730                 || ('A' <= ch && ch <= 'F');
   731     }
   733     protected boolean isUnquotedAttrValueTerminator(char ch) {
   734         switch (ch) {
   735             case '\f': case '\n': case '\r': case '\t':
   736             case ' ':
   737             case '"': case '\'': case '`':
   738             case '=': case '<': case '>':
   739                 return true;
   740             default:
   741                 return false;
   742         }
   743     }
   745     protected boolean isWhitespace(char ch) {
   746         return Character.isWhitespace(ch);
   747     }
   749     protected void skipWhitespace() {
   750         while (isWhitespace(ch)) {
   751             nextChar();
   752         }
   753     }
   755     /**
   756      * @param start position of first character of string
   757      * @param end position of character beyond last character to be included
   758      */
   759     String newString(int start, int end) {
   760         return new String(buf, start, end - start);
   761     }
   763     static abstract class TagParser {
   764         enum Kind { INLINE, BLOCK }
   766         final Kind kind;
   767         final String name;
   770         TagParser(Kind k, String tk) {
   771             kind = k;
   772             name = tk;
   773         }
   775         TagParser(Kind k, String tk, boolean retainWhiteSpace) {
   776             this(k, tk);
   777         }
   779         Kind getKind() {
   780             return kind;
   781         }
   783         String getName() {
   784             return name;
   785         }
   787         abstract void parse(int pos) throws ParseException;
   788     }
   790     /**
   791      * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#javadoctags">Javadoc Tags</a>
   792      */
   793     @SuppressWarnings("deprecation")
   794     private void initTagParsers() {
   795         TagParser[] parsers = {
   796             // @author name-text
   797             new TagParser(Kind.BLOCK, "author") {
   798                 @Override
   799                 public void parse(int pos) {
   800                     blockContent();
   801                 }
   802             },
   804             // {@code text}
   805             new TagParser(Kind.INLINE, "code", true) {
   806                 @Override
   807                 public void parse(int pos) throws ParseException {
   808                     inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
   809                     nextChar();
   810                 }
   811             },
   813             // @deprecated deprecated-text
   814             new TagParser(Kind.BLOCK, "deprecated") {
   815                 @Override
   816                 public void parse(int pos) {
   817                     blockContent();
   818                 }
   819             },
   821             // {@docRoot}
   822             new TagParser(Kind.INLINE, "docRoot") {
   823                 @Override
   824                 public void parse(int pos) throws ParseException {
   825                     if (ch == '}') {
   826                         nextChar();
   827                         return;
   828                     }
   829                     inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
   830                     nextChar();
   831                     throw new ParseException("dc.unexpected.content");
   832                 }
   833             },
   835             // @exception class-name description
   836             new TagParser(Kind.BLOCK, "exception") {
   837                 @Override
   838                 public void parse(int pos) throws ParseException {
   839                     skipWhitespace();
   840                     reference(false);
   841                     blockContent();
   842                 }
   843             },
   845             // @hidden hidden-text
   846             new TagParser(Kind.BLOCK, "hidden") {
   847                 @Override
   848                 public void parse(int pos) {
   849                     blockContent();
   850                 }
   851             },
   853             // @index search-term options-description
   854             new TagParser(Kind.INLINE, "index") {
   855                 @Override
   856                 public void parse(int pos) throws ParseException {
   857                     skipWhitespace();
   858                     if (ch == '}') {
   859                         throw new ParseException("dc.no.content");
   860                     }
   861                     if (ch == '"') quotedString(); else inlineWord();
   862                     skipWhitespace();
   863                     if (ch != '}') {
   864                         inlineContent();
   865                     } else {
   866                         nextChar();
   867                     }
   868                 }
   869             },
   871             // {@inheritDoc}
   872             new TagParser(Kind.INLINE, "inheritDoc") {
   873                 @Override
   874                 public void parse(int pos) throws ParseException {
   875                     if (ch == '}') {
   876                         nextChar();
   877                         return;
   878                     }
   879                     inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
   880                     nextChar();
   881                     throw new ParseException("dc.unexpected.content");
   882                 }
   883             },
   885             // {@link package.class#member label}
   886             new TagParser(Kind.INLINE, "link") {
   887                 @Override
   888                 public void parse(int pos) throws ParseException {
   889                     reference(true);
   890                     inlineContent();
   891                 }
   892             },
   894             // {@linkplain package.class#member label}
   895             new TagParser(Kind.INLINE, "linkplain") {
   896                 @Override
   897                 public void parse(int pos) throws ParseException {
   898                     reference(true);
   899                     inlineContent();
   900                 }
   901             },
   903             // {@literal text}
   904             new TagParser(Kind.INLINE, "literal", true) {
   905                 @Override
   906                 public void parse(int pos) throws ParseException {
   907                     inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
   908                     nextChar();
   909                 }
   910             },
   912             // @param parameter-name description
   913             new TagParser(Kind.BLOCK, "param") {
   914                 @Override
   915                 public void parse(int pos) throws ParseException {
   916                     skipWhitespace();
   918                     boolean typaram = false;
   919                     if (ch == '<') {
   920                         typaram = true;
   921                         nextChar();
   922                     }
   924                     identifier();
   926                     if (typaram) {
   927                         if (ch != '>')
   928                             throw new ParseException("dc.gt.expected");
   929                         nextChar();
   930                     }
   932                     skipWhitespace();
   933                     blockContent();
   934                 }
   935             },
   937             // @return description
   938             new TagParser(Kind.BLOCK, "return") {
   939                 @Override
   940                 public void parse(int pos) {
   941                     blockContent();
   942                 }
   943             },
   945             // @see reference | quoted-string | HTML
   946             new TagParser(Kind.BLOCK, "see") {
   947                 @Override
   948                 public void parse(int pos) throws ParseException {
   949                     skipWhitespace();
   950                     switch (ch) {
   951                         case '"':
   952                             quotedString();
   953                             skipWhitespace();
   954                             if (ch == '@'
   955                                     || ch == EOI && bp == buf.length - 1) {
   956                                 return;
   957                             }
   958                             break;
   960                         case '<':
   961                             blockContent();
   962                             return;
   964                         case '@':
   965                             if (newline)
   966                                 throw new ParseException("dc.no.content");
   967                             break;
   969                         case EOI:
   970                             if (bp == buf.length - 1)
   971                                 throw new ParseException("dc.no.content");
   972                             break;
   974                         default:
   975                             if (isJavaIdentifierStart(ch) || ch == '#') {
   976                                 reference(true);
   977                                 blockContent();
   978                             }
   979                     }
   980                     throw new ParseException("dc.unexpected.content");
   981                 }
   982             },
   984             // @serialData data-description
   985             new TagParser(Kind.BLOCK, "@serialData") {
   986                 @Override
   987                 public void parse(int pos) {
   988                     blockContent();
   989                 }
   990             },
   992             // @serialField field-name field-type description
   993             new TagParser(Kind.BLOCK, "serialField") {
   994                 @Override
   995                 public void parse(int pos) throws ParseException {
   996                     skipWhitespace();
   997                     identifier();
   998                     skipWhitespace();
   999                     reference(false);
  1000                     if (isWhitespace(ch)) {
  1001                         skipWhitespace();
  1002                         blockContent();
  1005             },
  1007             // @serial field-description | include | exclude
  1008             new TagParser(Kind.BLOCK, "serial") {
  1009                 @Override
  1010                 public void parse(int pos) {
  1011                     blockContent();
  1013             },
  1015             // @since since-text
  1016             new TagParser(Kind.BLOCK, "since") {
  1017                 @Override
  1018                 public void parse(int pos) {
  1019                     blockContent();
  1021             },
  1023             // @throws class-name description
  1024             new TagParser(Kind.BLOCK, "throws") {
  1025                 @Override
  1026                 public void parse(int pos) throws ParseException {
  1027                     skipWhitespace();
  1028                     reference(false);
  1029                     blockContent();
  1031             },
  1033             // {@value package.class#field}
  1034             new TagParser(Kind.INLINE, "value") {
  1035                 @Override
  1036                 public void parse(int pos) throws ParseException {
  1037                     reference(true);
  1038                     skipWhitespace();
  1039                     if (ch == '}') {
  1040                         nextChar();
  1041                         return;
  1043                     nextChar();
  1044                     throw new ParseException("dc.unexpected.content");
  1046             },
  1048             // @version version-text
  1049             new TagParser(Kind.BLOCK, "version") {
  1050                 @Override
  1051                 public void parse(int pos) {
  1052                     blockContent();
  1054             },
  1055         };
  1057         tagParsers = new HashMap<>();
  1058         for (TagParser p: parsers)
  1059             tagParsers.put(p.getName(), p);
  1063     private void initEventAttrs() {
  1064         eventAttrs = new HashSet<>(Arrays.asList(
  1065             // See https://www.w3.org/TR/html-markup/global-attributes.html#common.attrs.event-handler
  1066             "onabort",  "onblur",  "oncanplay",  "oncanplaythrough",
  1067             "onchange",  "onclick",  "oncontextmenu",  "ondblclick",
  1068             "ondrag",  "ondragend",  "ondragenter",  "ondragleave",
  1069             "ondragover",  "ondragstart",  "ondrop",  "ondurationchange",
  1070             "onemptied",  "onended",  "onerror",  "onfocus",  "oninput",
  1071             "oninvalid",  "onkeydown",  "onkeypress",  "onkeyup",
  1072             "onload",  "onloadeddata",  "onloadedmetadata",  "onloadstart",
  1073             "onmousedown",  "onmousemove",  "onmouseout",  "onmouseover",
  1074             "onmouseup",  "onmousewheel",  "onpause",  "onplay",
  1075             "onplaying",  "onprogress",  "onratechange",  "onreadystatechange",
  1076             "onreset",  "onscroll",  "onseeked",  "onseeking",
  1077             "onselect",  "onshow",  "onstalled",  "onsubmit",  "onsuspend",
  1078             "ontimeupdate",  "onvolumechange",  "onwaiting",
  1080             // See https://www.w3.org/TR/html4/sgml/dtd.html
  1081             // Most of the attributes that take a %Script are also defined as event handlers
  1082             // in HTML 5. The one exception is onunload.
  1083             // "onchange",  "onclick",   "ondblclick",  "onfocus",
  1084             // "onkeydown",  "onkeypress",  "onkeyup",  "onload",
  1085             // "onmousedown",  "onmousemove",  "onmouseout",  "onmouseover",
  1086             // "onmouseup",  "onreset",  "onselect",  "onsubmit",
  1087             "onunload"
  1088         ));
  1091     private void initURIAttrs() {
  1092         uriAttrs = new HashSet<>(Arrays.asList(
  1093             // See https://www.w3.org/TR/html4/sgml/dtd.html
  1094             //     https://www.w3.org/TR/html5/
  1095             // These are all the attributes that take a %URI or a valid URL potentially surrounded
  1096             // by spaces
  1097             "action",  "cite",  "classid",  "codebase",  "data",
  1098             "datasrc",  "for",  "href",  "longdesc",  "profile",
  1099             "src",  "usemap"
  1100         ));

mercurial