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

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

mercurial