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

Mon, 12 Aug 2019 13:24:23 -0700

author
igerasim
date
Mon, 12 Aug 2019 13:24:23 -0700
changeset 3845
735048c9f2d6
parent 3315
6f0746b6de9f
permissions
-rw-r--r--

8226765: Commentary on Javadoc comments
Reviewed-by: jjg, rhalade, skoivu

aefimov@3315 1 /*
aefimov@3315 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
aefimov@3315 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
aefimov@3315 4 *
aefimov@3315 5 * This code is free software; you can redistribute it and/or modify it
aefimov@3315 6 * under the terms of the GNU General Public License version 2 only, as
aefimov@3315 7 * published by the Free Software Foundation. Oracle designates this
aefimov@3315 8 * particular file as subject to the "Classpath" exception as provided
aefimov@3315 9 * by Oracle in the LICENSE file that accompanied this code.
aefimov@3315 10 *
aefimov@3315 11 * This code is distributed in the hope that it will be useful, but WITHOUT
aefimov@3315 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
aefimov@3315 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
aefimov@3315 14 * version 2 for more details (a copy is included in the LICENSE file that
aefimov@3315 15 * accompanied this code).
aefimov@3315 16 *
aefimov@3315 17 * You should have received a copy of the GNU General Public License version
aefimov@3315 18 * 2 along with this work; if not, write to the Free Software Foundation,
aefimov@3315 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
aefimov@3315 20 *
aefimov@3315 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
aefimov@3315 22 * or visit www.oracle.com if you need additional information or have any
aefimov@3315 23 * questions.
aefimov@3315 24 */
aefimov@3315 25
aefimov@3315 26 package com.sun.tools.javadoc;
aefimov@3315 27
aefimov@3315 28 import java.util.Arrays;
aefimov@3315 29 import java.util.HashMap;
aefimov@3315 30 import java.util.HashSet;
aefimov@3315 31 import java.util.Locale;
aefimov@3315 32 import java.util.Map;
aefimov@3315 33 import java.util.Set;
aefimov@3315 34
aefimov@3315 35 import com.sun.tools.javadoc.JavaScriptScanner.TagParser.Kind;
aefimov@3315 36
aefimov@3315 37 import static com.sun.tools.javac.util.LayoutCharacters.EOI;
aefimov@3315 38
aefimov@3315 39 /**
aefimov@3315 40 * Parser to detect use of JavaScript in documentation comments.
aefimov@3315 41 */
aefimov@3315 42 @Deprecated
aefimov@3315 43 public class JavaScriptScanner {
aefimov@3315 44 public static interface Reporter {
aefimov@3315 45 void report();
aefimov@3315 46 }
aefimov@3315 47
aefimov@3315 48 static class ParseException extends Exception {
aefimov@3315 49 private static final long serialVersionUID = 0;
aefimov@3315 50 ParseException(String key) {
aefimov@3315 51 super(key);
aefimov@3315 52 }
aefimov@3315 53 }
aefimov@3315 54
aefimov@3315 55 private Reporter reporter;
aefimov@3315 56
aefimov@3315 57 /** The input buffer, index of most recent character read,
aefimov@3315 58 * index of one past last character in buffer.
aefimov@3315 59 */
aefimov@3315 60 protected char[] buf;
aefimov@3315 61 protected int bp;
aefimov@3315 62 protected int buflen;
aefimov@3315 63
aefimov@3315 64 /** The current character.
aefimov@3315 65 */
aefimov@3315 66 protected char ch;
aefimov@3315 67
aefimov@3315 68 private boolean newline = true;
aefimov@3315 69
aefimov@3315 70 Map<String, TagParser> tagParsers;
aefimov@3315 71 Set<String> uriAttrs;
aefimov@3315 72
aefimov@3315 73 public JavaScriptScanner() {
aefimov@3315 74 initTagParsers();
aefimov@3315 75 initURIAttrs();
aefimov@3315 76 }
aefimov@3315 77
aefimov@3315 78 public void parse(String comment, Reporter r) {
aefimov@3315 79 reporter = r;
aefimov@3315 80 String c = comment;
aefimov@3315 81 buf = new char[c.length() + 1];
aefimov@3315 82 c.getChars(0, c.length(), buf, 0);
aefimov@3315 83 buf[buf.length - 1] = EOI;
aefimov@3315 84 buflen = buf.length - 1;
aefimov@3315 85 bp = -1;
aefimov@3315 86 newline = true;
aefimov@3315 87 nextChar();
aefimov@3315 88
aefimov@3315 89 blockContent();
aefimov@3315 90 blockTags();
aefimov@3315 91 }
aefimov@3315 92
aefimov@3315 93 private void checkHtmlTag(String tag) {
aefimov@3315 94 if (tag.equalsIgnoreCase("script")) {
aefimov@3315 95 reporter.report();
aefimov@3315 96 }
aefimov@3315 97 }
aefimov@3315 98
aefimov@3315 99 private void checkHtmlAttr(String name, String value) {
aefimov@3315 100 String n = name.toLowerCase(Locale.ENGLISH);
igerasim@3845 101 // https://www.w3.org/TR/html52/fullindex.html#attributes-table
igerasim@3845 102 // See https://www.w3.org/TR/html52/webappapis.html#events-event-handlers
igerasim@3845 103 // An event handler has a name, which always starts with "on" and is followed by
igerasim@3845 104 // the name of the event for which it is intended.
igerasim@3845 105 if (n.startsWith("on")
aefimov@3315 106 || uriAttrs.contains(n)
aefimov@3315 107 && value != null && value.toLowerCase(Locale.ENGLISH).trim().startsWith("javascript:")) {
aefimov@3315 108 reporter.report();
aefimov@3315 109 }
aefimov@3315 110 }
aefimov@3315 111
aefimov@3315 112 void nextChar() {
aefimov@3315 113 ch = buf[bp < buflen ? ++bp : buflen];
aefimov@3315 114 switch (ch) {
aefimov@3315 115 case '\f': case '\n': case '\r':
aefimov@3315 116 newline = true;
aefimov@3315 117 }
aefimov@3315 118 }
aefimov@3315 119
aefimov@3315 120 /**
aefimov@3315 121 * Read block content, consisting of text, html and inline tags.
aefimov@3315 122 * Terminated by the end of input, or the beginning of the next block tag:
aefimov@3315 123 * i.e. @ as the first non-whitespace character on a line.
aefimov@3315 124 */
aefimov@3315 125 @SuppressWarnings("fallthrough")
aefimov@3315 126 protected void blockContent() {
aefimov@3315 127
aefimov@3315 128 loop:
aefimov@3315 129 while (bp < buflen) {
aefimov@3315 130 switch (ch) {
aefimov@3315 131 case '\n': case '\r': case '\f':
aefimov@3315 132 newline = true;
aefimov@3315 133 // fallthrough
aefimov@3315 134
aefimov@3315 135 case ' ': case '\t':
aefimov@3315 136 nextChar();
aefimov@3315 137 break;
aefimov@3315 138
aefimov@3315 139 case '&':
aefimov@3315 140 entity(null);
aefimov@3315 141 break;
aefimov@3315 142
aefimov@3315 143 case '<':
aefimov@3315 144 html();
aefimov@3315 145 break;
aefimov@3315 146
aefimov@3315 147 case '>':
aefimov@3315 148 newline = false;
aefimov@3315 149 nextChar();
aefimov@3315 150 break;
aefimov@3315 151
aefimov@3315 152 case '{':
aefimov@3315 153 inlineTag(null);
aefimov@3315 154 break;
aefimov@3315 155
aefimov@3315 156 case '@':
aefimov@3315 157 if (newline) {
aefimov@3315 158 break loop;
aefimov@3315 159 }
aefimov@3315 160 // fallthrough
aefimov@3315 161
aefimov@3315 162 default:
aefimov@3315 163 newline = false;
aefimov@3315 164 nextChar();
aefimov@3315 165 }
aefimov@3315 166 }
aefimov@3315 167 }
aefimov@3315 168
aefimov@3315 169 /**
aefimov@3315 170 * Read a series of block tags, including their content.
aefimov@3315 171 * Standard tags parse their content appropriately.
aefimov@3315 172 * Non-standard tags are represented by {@link UnknownBlockTag}.
aefimov@3315 173 */
aefimov@3315 174 protected void blockTags() {
aefimov@3315 175 while (ch == '@')
aefimov@3315 176 blockTag();
aefimov@3315 177 }
aefimov@3315 178
aefimov@3315 179 /**
aefimov@3315 180 * Read a single block tag, including its content.
aefimov@3315 181 * Standard tags parse their content appropriately.
aefimov@3315 182 * Non-standard tags are represented by {@link UnknownBlockTag}.
aefimov@3315 183 */
aefimov@3315 184 protected void blockTag() {
aefimov@3315 185 int p = bp;
aefimov@3315 186 try {
aefimov@3315 187 nextChar();
aefimov@3315 188 if (isIdentifierStart(ch)) {
aefimov@3315 189 String name = readTagName();
aefimov@3315 190 TagParser tp = tagParsers.get(name);
aefimov@3315 191 if (tp == null) {
aefimov@3315 192 blockContent();
aefimov@3315 193 } else {
aefimov@3315 194 switch (tp.getKind()) {
aefimov@3315 195 case BLOCK:
aefimov@3315 196 tp.parse(p);
aefimov@3315 197 return;
aefimov@3315 198 case INLINE:
aefimov@3315 199 return;
aefimov@3315 200 }
aefimov@3315 201 }
aefimov@3315 202 }
aefimov@3315 203 blockContent();
aefimov@3315 204 } catch (ParseException e) {
aefimov@3315 205 blockContent();
aefimov@3315 206 }
aefimov@3315 207 }
aefimov@3315 208
aefimov@3315 209 protected void inlineTag(Void list) {
aefimov@3315 210 newline = false;
aefimov@3315 211 nextChar();
aefimov@3315 212 if (ch == '@') {
aefimov@3315 213 inlineTag();
aefimov@3315 214 }
aefimov@3315 215 }
aefimov@3315 216
aefimov@3315 217 /**
aefimov@3315 218 * Read a single inline tag, including its content.
aefimov@3315 219 * Standard tags parse their content appropriately.
aefimov@3315 220 * Non-standard tags are represented by {@link UnknownBlockTag}.
aefimov@3315 221 * Malformed tags may be returned as {@link Erroneous}.
aefimov@3315 222 */
aefimov@3315 223 protected void inlineTag() {
aefimov@3315 224 int p = bp - 1;
aefimov@3315 225 try {
aefimov@3315 226 nextChar();
aefimov@3315 227 if (isIdentifierStart(ch)) {
aefimov@3315 228 String name = readTagName();
aefimov@3315 229 TagParser tp = tagParsers.get(name);
aefimov@3315 230
aefimov@3315 231 if (tp == null) {
aefimov@3315 232 skipWhitespace();
aefimov@3315 233 inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
aefimov@3315 234 nextChar();
aefimov@3315 235 } else {
aefimov@3315 236 skipWhitespace();
aefimov@3315 237 if (tp.getKind() == TagParser.Kind.INLINE) {
aefimov@3315 238 tp.parse(p);
aefimov@3315 239 } else { // handle block tags (ex: @see) in inline content
aefimov@3315 240 inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
aefimov@3315 241 nextChar();
aefimov@3315 242 }
aefimov@3315 243 }
aefimov@3315 244 }
aefimov@3315 245 } catch (ParseException e) {
aefimov@3315 246 }
aefimov@3315 247 }
aefimov@3315 248
aefimov@3315 249 private static enum WhitespaceRetentionPolicy {
aefimov@3315 250 RETAIN_ALL,
aefimov@3315 251 REMOVE_FIRST_SPACE,
aefimov@3315 252 REMOVE_ALL
aefimov@3315 253 }
aefimov@3315 254
aefimov@3315 255 /**
aefimov@3315 256 * Read plain text content of an inline tag.
aefimov@3315 257 * Matching pairs of { } are skipped; the text is terminated by the first
aefimov@3315 258 * unmatched }. It is an error if the beginning of the next tag is detected.
aefimov@3315 259 */
aefimov@3315 260 private void inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws ParseException {
aefimov@3315 261 switch (whitespacePolicy) {
aefimov@3315 262 case REMOVE_ALL:
aefimov@3315 263 skipWhitespace();
aefimov@3315 264 break;
aefimov@3315 265 case REMOVE_FIRST_SPACE:
aefimov@3315 266 if (ch == ' ')
aefimov@3315 267 nextChar();
aefimov@3315 268 break;
aefimov@3315 269 case RETAIN_ALL:
aefimov@3315 270 default:
aefimov@3315 271 // do nothing
aefimov@3315 272 break;
aefimov@3315 273
aefimov@3315 274 }
aefimov@3315 275 int pos = bp;
aefimov@3315 276 int depth = 1;
aefimov@3315 277
aefimov@3315 278 loop:
aefimov@3315 279 while (bp < buflen) {
aefimov@3315 280 switch (ch) {
aefimov@3315 281 case '\n': case '\r': case '\f':
aefimov@3315 282 newline = true;
aefimov@3315 283 break;
aefimov@3315 284
aefimov@3315 285 case ' ': case '\t':
aefimov@3315 286 break;
aefimov@3315 287
aefimov@3315 288 case '{':
aefimov@3315 289 newline = false;
aefimov@3315 290 depth++;
aefimov@3315 291 break;
aefimov@3315 292
aefimov@3315 293 case '}':
aefimov@3315 294 if (--depth == 0) {
aefimov@3315 295 return;
aefimov@3315 296 }
aefimov@3315 297 newline = false;
aefimov@3315 298 break;
aefimov@3315 299
aefimov@3315 300 case '@':
aefimov@3315 301 if (newline)
aefimov@3315 302 break loop;
aefimov@3315 303 newline = false;
aefimov@3315 304 break;
aefimov@3315 305
aefimov@3315 306 default:
aefimov@3315 307 newline = false;
aefimov@3315 308 break;
aefimov@3315 309 }
aefimov@3315 310 nextChar();
aefimov@3315 311 }
aefimov@3315 312 throw new ParseException("dc.unterminated.inline.tag");
aefimov@3315 313 }
aefimov@3315 314
aefimov@3315 315 /**
aefimov@3315 316 * Read Java class name, possibly followed by member
aefimov@3315 317 * Matching pairs of {@literal < >} are skipped. The text is terminated by the first
aefimov@3315 318 * unmatched }. It is an error if the beginning of the next tag is detected.
aefimov@3315 319 */
aefimov@3315 320 // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
aefimov@3315 321 // TODO: improve quality of parse to forbid bad constructions.
aefimov@3315 322 // TODO: update to use ReferenceParser
aefimov@3315 323 @SuppressWarnings("fallthrough")
aefimov@3315 324 protected void reference(boolean allowMember) throws ParseException {
aefimov@3315 325 int pos = bp;
aefimov@3315 326 int depth = 0;
aefimov@3315 327
aefimov@3315 328 // scan to find the end of the signature, by looking for the first
aefimov@3315 329 // whitespace not enclosed in () or <>, or the end of the tag
aefimov@3315 330 loop:
aefimov@3315 331 while (bp < buflen) {
aefimov@3315 332 switch (ch) {
aefimov@3315 333 case '\n': case '\r': case '\f':
aefimov@3315 334 newline = true;
aefimov@3315 335 // fallthrough
aefimov@3315 336
aefimov@3315 337 case ' ': case '\t':
aefimov@3315 338 if (depth == 0)
aefimov@3315 339 break loop;
aefimov@3315 340 break;
aefimov@3315 341
aefimov@3315 342 case '(':
aefimov@3315 343 case '<':
aefimov@3315 344 newline = false;
aefimov@3315 345 depth++;
aefimov@3315 346 break;
aefimov@3315 347
aefimov@3315 348 case ')':
aefimov@3315 349 case '>':
aefimov@3315 350 newline = false;
aefimov@3315 351 --depth;
aefimov@3315 352 break;
aefimov@3315 353
aefimov@3315 354 case '}':
aefimov@3315 355 if (bp == pos)
aefimov@3315 356 return;
aefimov@3315 357 newline = false;
aefimov@3315 358 break loop;
aefimov@3315 359
aefimov@3315 360 case '@':
aefimov@3315 361 if (newline)
aefimov@3315 362 break loop;
aefimov@3315 363 // fallthrough
aefimov@3315 364
aefimov@3315 365 default:
aefimov@3315 366 newline = false;
aefimov@3315 367
aefimov@3315 368 }
aefimov@3315 369 nextChar();
aefimov@3315 370 }
aefimov@3315 371
aefimov@3315 372 if (depth != 0)
aefimov@3315 373 throw new ParseException("dc.unterminated.signature");
aefimov@3315 374 }
aefimov@3315 375
aefimov@3315 376 /**
aefimov@3315 377 * Read Java identifier
aefimov@3315 378 * Matching pairs of { } are skipped; the text is terminated by the first
aefimov@3315 379 * unmatched }. It is an error if the beginning of the next tag is detected.
aefimov@3315 380 */
aefimov@3315 381 @SuppressWarnings("fallthrough")
aefimov@3315 382 protected void identifier() throws ParseException {
aefimov@3315 383 skipWhitespace();
aefimov@3315 384 int pos = bp;
aefimov@3315 385
aefimov@3315 386 if (isJavaIdentifierStart(ch)) {
aefimov@3315 387 readJavaIdentifier();
aefimov@3315 388 return;
aefimov@3315 389 }
aefimov@3315 390
aefimov@3315 391 throw new ParseException("dc.identifier.expected");
aefimov@3315 392 }
aefimov@3315 393
aefimov@3315 394 /**
aefimov@3315 395 * Read a quoted string.
aefimov@3315 396 * It is an error if the beginning of the next tag is detected.
aefimov@3315 397 */
aefimov@3315 398 @SuppressWarnings("fallthrough")
aefimov@3315 399 protected void quotedString() {
aefimov@3315 400 int pos = bp;
aefimov@3315 401 nextChar();
aefimov@3315 402
aefimov@3315 403 loop:
aefimov@3315 404 while (bp < buflen) {
aefimov@3315 405 switch (ch) {
aefimov@3315 406 case '\n': case '\r': case '\f':
aefimov@3315 407 newline = true;
aefimov@3315 408 break;
aefimov@3315 409
aefimov@3315 410 case ' ': case '\t':
aefimov@3315 411 break;
aefimov@3315 412
aefimov@3315 413 case '"':
aefimov@3315 414 nextChar();
aefimov@3315 415 // trim trailing white-space?
aefimov@3315 416 return;
aefimov@3315 417
aefimov@3315 418 case '@':
aefimov@3315 419 if (newline)
aefimov@3315 420 break loop;
aefimov@3315 421
aefimov@3315 422 }
aefimov@3315 423 nextChar();
aefimov@3315 424 }
aefimov@3315 425 }
aefimov@3315 426
aefimov@3315 427 /**
aefimov@3315 428 * Read a term ie. one word.
aefimov@3315 429 * It is an error if the beginning of the next tag is detected.
aefimov@3315 430 */
aefimov@3315 431 @SuppressWarnings("fallthrough")
aefimov@3315 432 protected void inlineWord() {
aefimov@3315 433 int pos = bp;
aefimov@3315 434 int depth = 0;
aefimov@3315 435 loop:
aefimov@3315 436 while (bp < buflen) {
aefimov@3315 437 switch (ch) {
aefimov@3315 438 case '\n':
aefimov@3315 439 newline = true;
aefimov@3315 440 // fallthrough
aefimov@3315 441
aefimov@3315 442 case '\r': case '\f': case ' ': case '\t':
aefimov@3315 443 return;
aefimov@3315 444
aefimov@3315 445 case '@':
aefimov@3315 446 if (newline)
aefimov@3315 447 break loop;
aefimov@3315 448
aefimov@3315 449 case '{':
aefimov@3315 450 depth++;
aefimov@3315 451 break;
aefimov@3315 452
aefimov@3315 453 case '}':
aefimov@3315 454 if (depth == 0 || --depth == 0)
aefimov@3315 455 return;
aefimov@3315 456 break;
aefimov@3315 457 }
aefimov@3315 458 newline = false;
aefimov@3315 459 nextChar();
aefimov@3315 460 }
aefimov@3315 461 }
aefimov@3315 462
aefimov@3315 463 /**
aefimov@3315 464 * Read general text content of an inline tag, including HTML entities and elements.
aefimov@3315 465 * Matching pairs of { } are skipped; the text is terminated by the first
aefimov@3315 466 * unmatched }. It is an error if the beginning of the next tag is detected.
aefimov@3315 467 */
aefimov@3315 468 @SuppressWarnings("fallthrough")
aefimov@3315 469 private void inlineContent() {
aefimov@3315 470
aefimov@3315 471 skipWhitespace();
aefimov@3315 472 int pos = bp;
aefimov@3315 473 int depth = 1;
aefimov@3315 474
aefimov@3315 475 loop:
aefimov@3315 476 while (bp < buflen) {
aefimov@3315 477
aefimov@3315 478 switch (ch) {
aefimov@3315 479 case '\n': case '\r': case '\f':
aefimov@3315 480 newline = true;
aefimov@3315 481 // fall through
aefimov@3315 482
aefimov@3315 483 case ' ': case '\t':
aefimov@3315 484 nextChar();
aefimov@3315 485 break;
aefimov@3315 486
aefimov@3315 487 case '&':
aefimov@3315 488 entity(null);
aefimov@3315 489 break;
aefimov@3315 490
aefimov@3315 491 case '<':
aefimov@3315 492 newline = false;
aefimov@3315 493 html();
aefimov@3315 494 break;
aefimov@3315 495
aefimov@3315 496 case '{':
aefimov@3315 497 newline = false;
aefimov@3315 498 depth++;
aefimov@3315 499 nextChar();
aefimov@3315 500 break;
aefimov@3315 501
aefimov@3315 502 case '}':
aefimov@3315 503 newline = false;
aefimov@3315 504 if (--depth == 0) {
aefimov@3315 505 nextChar();
aefimov@3315 506 return;
aefimov@3315 507 }
aefimov@3315 508 nextChar();
aefimov@3315 509 break;
aefimov@3315 510
aefimov@3315 511 case '@':
aefimov@3315 512 if (newline)
aefimov@3315 513 break loop;
aefimov@3315 514 // fallthrough
aefimov@3315 515
aefimov@3315 516 default:
aefimov@3315 517 nextChar();
aefimov@3315 518 break;
aefimov@3315 519 }
aefimov@3315 520 }
aefimov@3315 521
aefimov@3315 522 }
aefimov@3315 523
aefimov@3315 524 protected void entity(Void list) {
aefimov@3315 525 newline = false;
aefimov@3315 526 entity();
aefimov@3315 527 }
aefimov@3315 528
aefimov@3315 529 /**
aefimov@3315 530 * Read an HTML entity.
aefimov@3315 531 * {@literal &identifier; } or {@literal &#digits; } or {@literal &#xhex-digits; }
aefimov@3315 532 */
aefimov@3315 533 protected void entity() {
aefimov@3315 534 nextChar();
aefimov@3315 535 String name = null;
aefimov@3315 536 if (ch == '#') {
aefimov@3315 537 int namep = bp;
aefimov@3315 538 nextChar();
aefimov@3315 539 if (isDecimalDigit(ch)) {
aefimov@3315 540 nextChar();
aefimov@3315 541 while (isDecimalDigit(ch))
aefimov@3315 542 nextChar();
aefimov@3315 543 name = new String(buf, namep, bp - namep);
aefimov@3315 544 } else if (ch == 'x' || ch == 'X') {
aefimov@3315 545 nextChar();
aefimov@3315 546 if (isHexDigit(ch)) {
aefimov@3315 547 nextChar();
aefimov@3315 548 while (isHexDigit(ch))
aefimov@3315 549 nextChar();
aefimov@3315 550 name = new String(buf, namep, bp - namep);
aefimov@3315 551 }
aefimov@3315 552 }
aefimov@3315 553 } else if (isIdentifierStart(ch)) {
aefimov@3315 554 name = readIdentifier();
aefimov@3315 555 }
aefimov@3315 556
aefimov@3315 557 if (name != null) {
aefimov@3315 558 if (ch != ';')
aefimov@3315 559 return;
aefimov@3315 560 nextChar();
aefimov@3315 561 }
aefimov@3315 562 }
aefimov@3315 563
aefimov@3315 564 /**
aefimov@3315 565 * Read the start or end of an HTML tag, or an HTML comment
aefimov@3315 566 * {@literal <identifier attrs> } or {@literal </identifier> }
aefimov@3315 567 */
aefimov@3315 568 protected void html() {
aefimov@3315 569 int p = bp;
aefimov@3315 570 nextChar();
aefimov@3315 571 if (isIdentifierStart(ch)) {
aefimov@3315 572 String name = readIdentifier();
aefimov@3315 573 checkHtmlTag(name);
aefimov@3315 574 htmlAttrs();
aefimov@3315 575 if (ch == '/') {
aefimov@3315 576 nextChar();
aefimov@3315 577 }
aefimov@3315 578 if (ch == '>') {
aefimov@3315 579 nextChar();
aefimov@3315 580 return;
aefimov@3315 581 }
aefimov@3315 582 } else if (ch == '/') {
aefimov@3315 583 nextChar();
aefimov@3315 584 if (isIdentifierStart(ch)) {
aefimov@3315 585 readIdentifier();
aefimov@3315 586 skipWhitespace();
aefimov@3315 587 if (ch == '>') {
aefimov@3315 588 nextChar();
aefimov@3315 589 return;
aefimov@3315 590 }
aefimov@3315 591 }
aefimov@3315 592 } else if (ch == '!') {
aefimov@3315 593 nextChar();
aefimov@3315 594 if (ch == '-') {
aefimov@3315 595 nextChar();
aefimov@3315 596 if (ch == '-') {
aefimov@3315 597 nextChar();
aefimov@3315 598 while (bp < buflen) {
aefimov@3315 599 int dash = 0;
aefimov@3315 600 while (ch == '-') {
aefimov@3315 601 dash++;
aefimov@3315 602 nextChar();
aefimov@3315 603 }
aefimov@3315 604 // Strictly speaking, a comment should not contain "--"
aefimov@3315 605 // so dash > 2 is an error, dash == 2 implies ch == '>'
aefimov@3315 606 // See http://www.w3.org/TR/html-markup/syntax.html#syntax-comments
aefimov@3315 607 // for more details.
aefimov@3315 608 if (dash >= 2 && ch == '>') {
aefimov@3315 609 nextChar();
aefimov@3315 610 return;
aefimov@3315 611 }
aefimov@3315 612
aefimov@3315 613 nextChar();
aefimov@3315 614 }
aefimov@3315 615 }
aefimov@3315 616 }
aefimov@3315 617 }
aefimov@3315 618
aefimov@3315 619 bp = p + 1;
aefimov@3315 620 ch = buf[bp];
aefimov@3315 621 }
aefimov@3315 622
aefimov@3315 623 /**
aefimov@3315 624 * Read a series of HTML attributes, terminated by {@literal > }.
aefimov@3315 625 * Each attribute is of the form {@literal identifier[=value] }.
aefimov@3315 626 * "value" may be unquoted, single-quoted, or double-quoted.
aefimov@3315 627 */
aefimov@3315 628 protected void htmlAttrs() {
aefimov@3315 629 skipWhitespace();
aefimov@3315 630
aefimov@3315 631 loop:
aefimov@3315 632 while (isIdentifierStart(ch)) {
aefimov@3315 633 int namePos = bp;
aefimov@3315 634 String name = readAttributeName();
aefimov@3315 635 skipWhitespace();
aefimov@3315 636 StringBuilder value = new StringBuilder();
aefimov@3315 637 if (ch == '=') {
aefimov@3315 638 nextChar();
aefimov@3315 639 skipWhitespace();
aefimov@3315 640 if (ch == '\'' || ch == '"') {
aefimov@3315 641 char quote = ch;
aefimov@3315 642 nextChar();
aefimov@3315 643 while (bp < buflen && ch != quote) {
aefimov@3315 644 if (newline && ch == '@') {
aefimov@3315 645 // No point trying to read more.
aefimov@3315 646 // In fact, all attrs get discarded by the caller
aefimov@3315 647 // and superseded by a malformed.html node because
aefimov@3315 648 // the html tag itself is not terminated correctly.
aefimov@3315 649 break loop;
aefimov@3315 650 }
aefimov@3315 651 value.append(ch);
aefimov@3315 652 nextChar();
aefimov@3315 653 }
aefimov@3315 654 nextChar();
aefimov@3315 655 } else {
aefimov@3315 656 while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
aefimov@3315 657 value.append(ch);
aefimov@3315 658 nextChar();
aefimov@3315 659 }
aefimov@3315 660 }
aefimov@3315 661 skipWhitespace();
aefimov@3315 662 }
aefimov@3315 663 checkHtmlAttr(name, value.toString());
aefimov@3315 664 }
aefimov@3315 665 }
aefimov@3315 666
aefimov@3315 667 protected void attrValueChar(Void list) {
aefimov@3315 668 switch (ch) {
aefimov@3315 669 case '&':
aefimov@3315 670 entity(list);
aefimov@3315 671 break;
aefimov@3315 672
aefimov@3315 673 case '{':
aefimov@3315 674 inlineTag(list);
aefimov@3315 675 break;
aefimov@3315 676
aefimov@3315 677 default:
aefimov@3315 678 nextChar();
aefimov@3315 679 }
aefimov@3315 680 }
aefimov@3315 681
aefimov@3315 682 protected boolean isIdentifierStart(char ch) {
aefimov@3315 683 return Character.isUnicodeIdentifierStart(ch);
aefimov@3315 684 }
aefimov@3315 685
aefimov@3315 686 protected String readIdentifier() {
aefimov@3315 687 int start = bp;
aefimov@3315 688 nextChar();
aefimov@3315 689 while (bp < buflen && Character.isUnicodeIdentifierPart(ch))
aefimov@3315 690 nextChar();
aefimov@3315 691 return new String(buf, start, bp - start);
aefimov@3315 692 }
aefimov@3315 693
aefimov@3315 694 protected String readAttributeName() {
aefimov@3315 695 int start = bp;
aefimov@3315 696 nextChar();
aefimov@3315 697 while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '-'))
aefimov@3315 698 nextChar();
aefimov@3315 699 return new String(buf, start, bp - start);
aefimov@3315 700 }
aefimov@3315 701
aefimov@3315 702 protected String readTagName() {
aefimov@3315 703 int start = bp;
aefimov@3315 704 nextChar();
aefimov@3315 705 while (bp < buflen
aefimov@3315 706 && (Character.isUnicodeIdentifierPart(ch) || ch == '.'
aefimov@3315 707 || ch == '-' || ch == ':')) {
aefimov@3315 708 nextChar();
aefimov@3315 709 }
aefimov@3315 710 return new String(buf, start, bp - start);
aefimov@3315 711 }
aefimov@3315 712
aefimov@3315 713 protected boolean isJavaIdentifierStart(char ch) {
aefimov@3315 714 return Character.isJavaIdentifierStart(ch);
aefimov@3315 715 }
aefimov@3315 716
aefimov@3315 717 protected String readJavaIdentifier() {
aefimov@3315 718 int start = bp;
aefimov@3315 719 nextChar();
aefimov@3315 720 while (bp < buflen && Character.isJavaIdentifierPart(ch))
aefimov@3315 721 nextChar();
aefimov@3315 722 return new String(buf, start, bp - start);
aefimov@3315 723 }
aefimov@3315 724
aefimov@3315 725 protected boolean isDecimalDigit(char ch) {
aefimov@3315 726 return ('0' <= ch && ch <= '9');
aefimov@3315 727 }
aefimov@3315 728
aefimov@3315 729 protected boolean isHexDigit(char ch) {
aefimov@3315 730 return ('0' <= ch && ch <= '9')
aefimov@3315 731 || ('a' <= ch && ch <= 'f')
aefimov@3315 732 || ('A' <= ch && ch <= 'F');
aefimov@3315 733 }
aefimov@3315 734
aefimov@3315 735 protected boolean isUnquotedAttrValueTerminator(char ch) {
aefimov@3315 736 switch (ch) {
aefimov@3315 737 case '\f': case '\n': case '\r': case '\t':
aefimov@3315 738 case ' ':
aefimov@3315 739 case '"': case '\'': case '`':
aefimov@3315 740 case '=': case '<': case '>':
aefimov@3315 741 return true;
aefimov@3315 742 default:
aefimov@3315 743 return false;
aefimov@3315 744 }
aefimov@3315 745 }
aefimov@3315 746
aefimov@3315 747 protected boolean isWhitespace(char ch) {
aefimov@3315 748 return Character.isWhitespace(ch);
aefimov@3315 749 }
aefimov@3315 750
aefimov@3315 751 protected void skipWhitespace() {
aefimov@3315 752 while (isWhitespace(ch)) {
aefimov@3315 753 nextChar();
aefimov@3315 754 }
aefimov@3315 755 }
aefimov@3315 756
aefimov@3315 757 /**
aefimov@3315 758 * @param start position of first character of string
aefimov@3315 759 * @param end position of character beyond last character to be included
aefimov@3315 760 */
aefimov@3315 761 String newString(int start, int end) {
aefimov@3315 762 return new String(buf, start, end - start);
aefimov@3315 763 }
aefimov@3315 764
aefimov@3315 765 static abstract class TagParser {
aefimov@3315 766 enum Kind { INLINE, BLOCK }
aefimov@3315 767
aefimov@3315 768 final Kind kind;
aefimov@3315 769 final String name;
aefimov@3315 770
aefimov@3315 771
aefimov@3315 772 TagParser(Kind k, String tk) {
aefimov@3315 773 kind = k;
aefimov@3315 774 name = tk;
aefimov@3315 775 }
aefimov@3315 776
aefimov@3315 777 TagParser(Kind k, String tk, boolean retainWhiteSpace) {
aefimov@3315 778 this(k, tk);
aefimov@3315 779 }
aefimov@3315 780
aefimov@3315 781 Kind getKind() {
aefimov@3315 782 return kind;
aefimov@3315 783 }
aefimov@3315 784
aefimov@3315 785 String getName() {
aefimov@3315 786 return name;
aefimov@3315 787 }
aefimov@3315 788
aefimov@3315 789 abstract void parse(int pos) throws ParseException;
aefimov@3315 790 }
aefimov@3315 791
aefimov@3315 792 /**
aefimov@3315 793 * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#javadoctags">Javadoc Tags</a>
aefimov@3315 794 */
aefimov@3315 795 @SuppressWarnings("deprecation")
aefimov@3315 796 private void initTagParsers() {
aefimov@3315 797 TagParser[] parsers = {
aefimov@3315 798 // @author name-text
aefimov@3315 799 new TagParser(Kind.BLOCK, "author") {
aefimov@3315 800 @Override
aefimov@3315 801 public void parse(int pos) {
aefimov@3315 802 blockContent();
aefimov@3315 803 }
aefimov@3315 804 },
aefimov@3315 805
aefimov@3315 806 // {@code text}
aefimov@3315 807 new TagParser(Kind.INLINE, "code", true) {
aefimov@3315 808 @Override
aefimov@3315 809 public void parse(int pos) throws ParseException {
aefimov@3315 810 inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
aefimov@3315 811 nextChar();
aefimov@3315 812 }
aefimov@3315 813 },
aefimov@3315 814
aefimov@3315 815 // @deprecated deprecated-text
aefimov@3315 816 new TagParser(Kind.BLOCK, "deprecated") {
aefimov@3315 817 @Override
aefimov@3315 818 public void parse(int pos) {
aefimov@3315 819 blockContent();
aefimov@3315 820 }
aefimov@3315 821 },
aefimov@3315 822
aefimov@3315 823 // {@docRoot}
aefimov@3315 824 new TagParser(Kind.INLINE, "docRoot") {
aefimov@3315 825 @Override
aefimov@3315 826 public void parse(int pos) throws ParseException {
aefimov@3315 827 if (ch == '}') {
aefimov@3315 828 nextChar();
aefimov@3315 829 return;
aefimov@3315 830 }
aefimov@3315 831 inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
aefimov@3315 832 nextChar();
aefimov@3315 833 throw new ParseException("dc.unexpected.content");
aefimov@3315 834 }
aefimov@3315 835 },
aefimov@3315 836
aefimov@3315 837 // @exception class-name description
aefimov@3315 838 new TagParser(Kind.BLOCK, "exception") {
aefimov@3315 839 @Override
aefimov@3315 840 public void parse(int pos) throws ParseException {
aefimov@3315 841 skipWhitespace();
aefimov@3315 842 reference(false);
aefimov@3315 843 blockContent();
aefimov@3315 844 }
aefimov@3315 845 },
aefimov@3315 846
aefimov@3315 847 // @hidden hidden-text
aefimov@3315 848 new TagParser(Kind.BLOCK, "hidden") {
aefimov@3315 849 @Override
aefimov@3315 850 public void parse(int pos) {
aefimov@3315 851 blockContent();
aefimov@3315 852 }
aefimov@3315 853 },
aefimov@3315 854
aefimov@3315 855 // @index search-term options-description
aefimov@3315 856 new TagParser(Kind.INLINE, "index") {
aefimov@3315 857 @Override
aefimov@3315 858 public void parse(int pos) throws ParseException {
aefimov@3315 859 skipWhitespace();
aefimov@3315 860 if (ch == '}') {
aefimov@3315 861 throw new ParseException("dc.no.content");
aefimov@3315 862 }
aefimov@3315 863 if (ch == '"') quotedString(); else inlineWord();
aefimov@3315 864 skipWhitespace();
aefimov@3315 865 if (ch != '}') {
aefimov@3315 866 inlineContent();
aefimov@3315 867 } else {
aefimov@3315 868 nextChar();
aefimov@3315 869 }
aefimov@3315 870 }
aefimov@3315 871 },
aefimov@3315 872
aefimov@3315 873 // {@inheritDoc}
aefimov@3315 874 new TagParser(Kind.INLINE, "inheritDoc") {
aefimov@3315 875 @Override
aefimov@3315 876 public void parse(int pos) throws ParseException {
aefimov@3315 877 if (ch == '}') {
aefimov@3315 878 nextChar();
aefimov@3315 879 return;
aefimov@3315 880 }
aefimov@3315 881 inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
aefimov@3315 882 nextChar();
aefimov@3315 883 throw new ParseException("dc.unexpected.content");
aefimov@3315 884 }
aefimov@3315 885 },
aefimov@3315 886
aefimov@3315 887 // {@link package.class#member label}
aefimov@3315 888 new TagParser(Kind.INLINE, "link") {
aefimov@3315 889 @Override
aefimov@3315 890 public void parse(int pos) throws ParseException {
aefimov@3315 891 reference(true);
aefimov@3315 892 inlineContent();
aefimov@3315 893 }
aefimov@3315 894 },
aefimov@3315 895
aefimov@3315 896 // {@linkplain package.class#member label}
aefimov@3315 897 new TagParser(Kind.INLINE, "linkplain") {
aefimov@3315 898 @Override
aefimov@3315 899 public void parse(int pos) throws ParseException {
aefimov@3315 900 reference(true);
aefimov@3315 901 inlineContent();
aefimov@3315 902 }
aefimov@3315 903 },
aefimov@3315 904
aefimov@3315 905 // {@literal text}
aefimov@3315 906 new TagParser(Kind.INLINE, "literal", true) {
aefimov@3315 907 @Override
aefimov@3315 908 public void parse(int pos) throws ParseException {
aefimov@3315 909 inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
aefimov@3315 910 nextChar();
aefimov@3315 911 }
aefimov@3315 912 },
aefimov@3315 913
aefimov@3315 914 // @param parameter-name description
aefimov@3315 915 new TagParser(Kind.BLOCK, "param") {
aefimov@3315 916 @Override
aefimov@3315 917 public void parse(int pos) throws ParseException {
aefimov@3315 918 skipWhitespace();
aefimov@3315 919
aefimov@3315 920 boolean typaram = false;
aefimov@3315 921 if (ch == '<') {
aefimov@3315 922 typaram = true;
aefimov@3315 923 nextChar();
aefimov@3315 924 }
aefimov@3315 925
aefimov@3315 926 identifier();
aefimov@3315 927
aefimov@3315 928 if (typaram) {
aefimov@3315 929 if (ch != '>')
aefimov@3315 930 throw new ParseException("dc.gt.expected");
aefimov@3315 931 nextChar();
aefimov@3315 932 }
aefimov@3315 933
aefimov@3315 934 skipWhitespace();
aefimov@3315 935 blockContent();
aefimov@3315 936 }
aefimov@3315 937 },
aefimov@3315 938
aefimov@3315 939 // @return description
aefimov@3315 940 new TagParser(Kind.BLOCK, "return") {
aefimov@3315 941 @Override
aefimov@3315 942 public void parse(int pos) {
aefimov@3315 943 blockContent();
aefimov@3315 944 }
aefimov@3315 945 },
aefimov@3315 946
aefimov@3315 947 // @see reference | quoted-string | HTML
aefimov@3315 948 new TagParser(Kind.BLOCK, "see") {
aefimov@3315 949 @Override
aefimov@3315 950 public void parse(int pos) throws ParseException {
aefimov@3315 951 skipWhitespace();
aefimov@3315 952 switch (ch) {
aefimov@3315 953 case '"':
aefimov@3315 954 quotedString();
aefimov@3315 955 skipWhitespace();
aefimov@3315 956 if (ch == '@'
aefimov@3315 957 || ch == EOI && bp == buf.length - 1) {
aefimov@3315 958 return;
aefimov@3315 959 }
aefimov@3315 960 break;
aefimov@3315 961
aefimov@3315 962 case '<':
aefimov@3315 963 blockContent();
aefimov@3315 964 return;
aefimov@3315 965
aefimov@3315 966 case '@':
aefimov@3315 967 if (newline)
aefimov@3315 968 throw new ParseException("dc.no.content");
aefimov@3315 969 break;
aefimov@3315 970
aefimov@3315 971 case EOI:
aefimov@3315 972 if (bp == buf.length - 1)
aefimov@3315 973 throw new ParseException("dc.no.content");
aefimov@3315 974 break;
aefimov@3315 975
aefimov@3315 976 default:
aefimov@3315 977 if (isJavaIdentifierStart(ch) || ch == '#') {
aefimov@3315 978 reference(true);
aefimov@3315 979 blockContent();
aefimov@3315 980 }
aefimov@3315 981 }
aefimov@3315 982 throw new ParseException("dc.unexpected.content");
aefimov@3315 983 }
aefimov@3315 984 },
aefimov@3315 985
aefimov@3315 986 // @serialData data-description
aefimov@3315 987 new TagParser(Kind.BLOCK, "@serialData") {
aefimov@3315 988 @Override
aefimov@3315 989 public void parse(int pos) {
aefimov@3315 990 blockContent();
aefimov@3315 991 }
aefimov@3315 992 },
aefimov@3315 993
aefimov@3315 994 // @serialField field-name field-type description
aefimov@3315 995 new TagParser(Kind.BLOCK, "serialField") {
aefimov@3315 996 @Override
aefimov@3315 997 public void parse(int pos) throws ParseException {
aefimov@3315 998 skipWhitespace();
aefimov@3315 999 identifier();
aefimov@3315 1000 skipWhitespace();
aefimov@3315 1001 reference(false);
aefimov@3315 1002 if (isWhitespace(ch)) {
aefimov@3315 1003 skipWhitespace();
aefimov@3315 1004 blockContent();
aefimov@3315 1005 }
aefimov@3315 1006 }
aefimov@3315 1007 },
aefimov@3315 1008
aefimov@3315 1009 // @serial field-description | include | exclude
aefimov@3315 1010 new TagParser(Kind.BLOCK, "serial") {
aefimov@3315 1011 @Override
aefimov@3315 1012 public void parse(int pos) {
aefimov@3315 1013 blockContent();
aefimov@3315 1014 }
aefimov@3315 1015 },
aefimov@3315 1016
aefimov@3315 1017 // @since since-text
aefimov@3315 1018 new TagParser(Kind.BLOCK, "since") {
aefimov@3315 1019 @Override
aefimov@3315 1020 public void parse(int pos) {
aefimov@3315 1021 blockContent();
aefimov@3315 1022 }
aefimov@3315 1023 },
aefimov@3315 1024
aefimov@3315 1025 // @throws class-name description
aefimov@3315 1026 new TagParser(Kind.BLOCK, "throws") {
aefimov@3315 1027 @Override
aefimov@3315 1028 public void parse(int pos) throws ParseException {
aefimov@3315 1029 skipWhitespace();
aefimov@3315 1030 reference(false);
aefimov@3315 1031 blockContent();
aefimov@3315 1032 }
aefimov@3315 1033 },
aefimov@3315 1034
aefimov@3315 1035 // {@value package.class#field}
aefimov@3315 1036 new TagParser(Kind.INLINE, "value") {
aefimov@3315 1037 @Override
aefimov@3315 1038 public void parse(int pos) throws ParseException {
aefimov@3315 1039 reference(true);
aefimov@3315 1040 skipWhitespace();
aefimov@3315 1041 if (ch == '}') {
aefimov@3315 1042 nextChar();
aefimov@3315 1043 return;
aefimov@3315 1044 }
aefimov@3315 1045 nextChar();
aefimov@3315 1046 throw new ParseException("dc.unexpected.content");
aefimov@3315 1047 }
aefimov@3315 1048 },
aefimov@3315 1049
aefimov@3315 1050 // @version version-text
aefimov@3315 1051 new TagParser(Kind.BLOCK, "version") {
aefimov@3315 1052 @Override
aefimov@3315 1053 public void parse(int pos) {
aefimov@3315 1054 blockContent();
aefimov@3315 1055 }
aefimov@3315 1056 },
aefimov@3315 1057 };
aefimov@3315 1058
aefimov@3315 1059 tagParsers = new HashMap<>();
aefimov@3315 1060 for (TagParser p: parsers)
aefimov@3315 1061 tagParsers.put(p.getName(), p);
aefimov@3315 1062
aefimov@3315 1063 }
aefimov@3315 1064
aefimov@3315 1065 private void initURIAttrs() {
aefimov@3315 1066 uriAttrs = new HashSet<>(Arrays.asList(
aefimov@3315 1067 // See https://www.w3.org/TR/html4/sgml/dtd.html
aefimov@3315 1068 // https://www.w3.org/TR/html5/
aefimov@3315 1069 // These are all the attributes that take a %URI or a valid URL potentially surrounded
aefimov@3315 1070 // by spaces
aefimov@3315 1071 "action", "cite", "classid", "codebase", "data",
aefimov@3315 1072 "datasrc", "for", "href", "longdesc", "profile",
aefimov@3315 1073 "src", "usemap"
aefimov@3315 1074 ));
aefimov@3315 1075 }
aefimov@3315 1076
aefimov@3315 1077 }

mercurial