1 /*
   2  * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package org.openjdk.buildtools.dtdbuilder;
  27 
  28 import javax.swing.text.html.parser.*;
  29 import java.net.URL;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.util.Enumeration;
  33 import java.util.Vector;
  34 import java.util.Hashtable;
  35 import java.util.BitSet;
  36 import java.text.MessageFormat;
  37 
  38 /**
  39  * A parser for DTDs. This parser roughly corresponds to the
  40  * rules specified in "The SGML Handbook" by Charles F. Goldfarb.
  41  * The end result of parsing the stream is a DTD object.
  42  *
  43  *
  44  * @see DTD
  45  * @see DTDInputStream
  46  * @author Arthur van Hoff
  47  */
  48 final
  49 class DTDParser implements DTDConstants {
  50     DTDBuilder dtd;
  51     DTDInputStream in;
  52     int ch;
  53     char str[] = new char[128];
  54     int strpos = 0;
  55     int nerrors = 0;
  56 
  57     /**
  58      * Report an error.
  59      */
  60     void error(String err, String arg1, String arg2, String arg3) {
  61         nerrors++;
  62 
  63         String msgParams[] = {arg1, arg2, arg3};
  64 
  65         String str = getSubstProp("dtderr." + err, msgParams);
  66         if (str == null) {
  67             str = err + "[" + arg1 + "," + arg2 + "," + arg3 + "]";
  68         }
  69         System.err.println("line " + in.ln + ", dtd " + dtd + ": " + str);
  70     }
  71     void error(String err, String arg1, String arg2) {
  72         error(err, arg1, arg2, "?");
  73     }
  74     void error(String err, String arg1) {
  75         error(err, arg1, "?", "?");
  76     }
  77     void error(String err) {
  78         error(err, "?", "?", "?");
  79     }
  80 
  81     private String getSubstProp(String propName, String args[]) {
  82         String prop = System.getProperty(propName);
  83 
  84         if (prop == null) {
  85             return null;
  86         }
  87 
  88         return MessageFormat.format(prop, (Object[])args);
  89     }
  90 
  91     /**
  92      * Expect a character.
  93      */
  94     boolean expect(int c) throws IOException {
  95         if (ch != c) {
  96             char str[] = {(char)c};
  97             error("expected", "'" + new String(str) + "'");
  98             return false;
  99         }
 100         ch = in.read();
 101         return true;
 102     }
 103 
 104     /**
 105      * Add a char to the string buffer.
 106      */
 107     void addString(int c) {
 108         if (strpos == str.length) {
 109             char newstr[] = new char[str.length * 2];
 110             System.arraycopy(str, 0, newstr, 0, str.length);
 111             str = newstr;
 112         }
 113         str[strpos++] = (char)c;
 114     }
 115 
 116     /**
 117      * Get the string which was accumulated in the buffer.
 118      * Pos is the starting position of the string.
 119      */
 120     String getString(int pos) {
 121         char newstr[] = new char[strpos - pos];
 122         System.arraycopy(str, pos, newstr, 0, strpos - pos);
 123         strpos = pos;
 124         return new String(newstr);
 125     }
 126 
 127     /**
 128      * Get the chars which were accumulated in the buffer.
 129      * Pos is the starting position of the string.
 130      */
 131     char[] getChars(int pos) {
 132         char newstr[] = new char[strpos - pos];
 133         System.arraycopy(str, pos, newstr, 0, strpos - pos);
 134         strpos = pos;
 135         return newstr;
 136     }
 137 
 138     /**
 139      * Skip spaces. [5] 297:23
 140      */
 141     void skipSpace() throws IOException {
 142         while (true) {
 143             switch (ch) {
 144               case '\n':
 145               case ' ':
 146               case '\t':
 147                 ch = in.read();
 148                 break;
 149 
 150               default:
 151                 return;
 152             }
 153         }
 154     }
 155 
 156     /**
 157      * Skip tag spaces (includes comments). [65] 372:1
 158      */
 159     void skipParameterSpace() throws IOException {
 160         while (true) {
 161             switch (ch) {
 162               case '\n':
 163               case ' ':
 164               case '\t':
 165                 ch = in.read();
 166                 break;
 167               case '-':
 168                 if ((ch = in.read()) != '-') {
 169                     in.push(ch);
 170                     ch = '-';
 171                     return;
 172                 }
 173 
 174                 in.replace++;
 175                 while (true) {
 176                     switch (ch = in.read()) {
 177                       case '-':
 178                         if ((ch = in.read()) == '-') {
 179                             ch = in.read();
 180                             in.replace--;
 181                             skipParameterSpace();
 182                             return;
 183                         }
 184                         break;
 185 
 186                       case -1:
 187                         error("eof.arg", "comment");
 188                         in.replace--;
 189                         return;
 190                     }
 191                 }
 192               default:
 193                 return;
 194             }
 195         }
 196     }
 197 
 198     /**
 199      * Parse identifier. Uppercase characters are automatically
 200      * folded to lowercase. Returns falsed if no identifier is found.
 201      */
 202     @SuppressWarnings("fallthrough")
 203     boolean parseIdentifier(boolean lower) throws IOException {
 204         switch (ch) {
 205           case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
 206           case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
 207           case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
 208           case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
 209           case 'Y': case 'Z':
 210             if (lower) {
 211                 ch = 'a' + (ch - 'A');
 212             }
 213             /* fall through */
 214 
 215           case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
 216           case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
 217           case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
 218           case 's': case 't': case 'u': case 'v': case 'w': case 'x':
 219           case 'y': case 'z':
 220             break;
 221 
 222           default:
 223             return false;
 224         }
 225 
 226         addString(ch);
 227         ch = in.read();
 228         parseNameToken(lower);
 229         return true;
 230     }
 231 
 232     /**
 233      * Parses name token. If <code>lower</code> is true, upper case letters
 234      * are folded to lower case. Returns falsed if no token is found.
 235      */
 236     @SuppressWarnings("fallthrough")
 237     boolean parseNameToken(boolean lower) throws IOException {
 238         boolean first = true;
 239 
 240         while (true) {
 241             switch (ch) {
 242               case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
 243               case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
 244               case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
 245               case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
 246               case 'Y': case 'Z':
 247                 if (lower) {
 248                     ch = 'a' + (ch - 'A');
 249                 }
 250                 /* fall through */
 251 
 252               case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
 253               case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
 254               case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
 255               case 's': case 't': case 'u': case 'v': case 'w': case 'x':
 256               case 'y': case 'z':
 257 
 258               case '0': case '1': case '2': case '3': case '4':
 259               case '5': case '6': case '7': case '8': case '9':
 260 
 261               case '.': case '-':
 262                 addString(ch);
 263                 ch = in.read();
 264                 first = false;
 265                 break;
 266 
 267               default:
 268                 return !first;
 269             }
 270         }
 271     }
 272 
 273     /**
 274      * Parse a list of identifiers.
 275      */
 276     Vector<String> parseIdentifierList(boolean lower) throws IOException {
 277         Vector<String> elems = new Vector<>();
 278         skipSpace();
 279         switch (ch) {
 280           case '(':
 281             ch = in.read();
 282             skipParameterSpace();
 283             while (parseNameToken(lower)) {
 284                 elems.addElement(getString(0));
 285                 skipParameterSpace();
 286                 if (ch == '|') {
 287                     ch = in.read();
 288                     skipParameterSpace();
 289                 }
 290             }
 291             expect(')');
 292             skipParameterSpace();
 293             break;
 294 
 295           default:
 296             if (!parseIdentifier(lower)) {
 297                 error("expected", "identifier");
 298                 break;
 299             }
 300             elems.addElement(getString(0));
 301             skipParameterSpace();
 302             break;
 303         }
 304         return elems;
 305     }
 306 
 307     /**
 308      * Parse and Entity reference. Should be called when
 309      * a &amp; is encountered. The data is put in the string buffer.
 310      * [59] 350:17
 311      */
 312     private void parseEntityReference() throws IOException {
 313         int pos = strpos;
 314 
 315         if ((ch = in.read()) == '#') {
 316             int n = 0;
 317             ch = in.read();
 318             if (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) {
 319                 addString('#');
 320             } else {
 321                 while ((ch >= '0') && (ch <= '9')) {
 322                     n = (n * 10) + ch - '0';
 323                     ch = in.read();
 324                 }
 325                 if ((ch == ';') || (ch == '\n')) {
 326                     ch = in.read();
 327                 }
 328                 addString(n);
 329                 return;
 330             }
 331         }
 332 
 333         while (true) {
 334             switch (ch) {
 335               case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
 336               case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
 337               case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
 338               case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
 339               case 'Y': case 'Z':
 340 
 341               case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
 342               case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
 343               case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
 344               case 's': case 't': case 'u': case 'v': case 'w': case 'x':
 345               case 'y': case 'z':
 346 
 347               case '0': case '1': case '2': case '3': case '4':
 348               case '5': case '6': case '7': case '8': case '9':
 349 
 350               case '.': case '-':
 351                 addString(ch);
 352                 ch = in.read();
 353                 break;
 354 
 355               default:
 356                 if (strpos == pos) {
 357                     addString('&');
 358                     return;
 359                 }
 360                 String nm = getString(pos);
 361                 Entity ent = dtd.getEntity(nm);
 362                 if (ent == null) {
 363                     error("undef.entref" + nm);
 364                     return;
 365                 }
 366                 if ((ch == ';') || (ch == '\n')) {
 367                     ch = in.read();
 368                 }
 369                 char data[] = ent.getData();
 370                 for (int i = 0 ; i < data.length ; i++) {
 371                     addString(data[i]);
 372                 }
 373                 return;
 374             }
 375         }
 376     }
 377 
 378     /**
 379      * Parse an entity declaration.
 380      * [101] 394:18
 381      * REMIND: external entity type
 382      */
 383     private void parseEntityDeclaration() throws IOException {
 384         int type = GENERAL;
 385 
 386         skipSpace();
 387         if (ch == '%') {
 388             ch = in.read();
 389             type = PARAMETER;
 390             skipSpace();
 391         }
 392         if (ch == '#') {
 393             addString('#');
 394             ch = in.read();
 395         }
 396         if (!parseIdentifier(false)) {
 397             error("expected", "identifier");
 398             return;
 399         }
 400         String nm = getString(0);
 401         skipParameterSpace();
 402         if (parseIdentifier(false)) {
 403             String tnm = getString(0);
 404             int t = Entity.name2type(tnm);
 405             if (t == 0) {
 406                 error("invalid.arg", "entity type", tnm);
 407             } else {
 408                 type |= t;
 409             }
 410             skipParameterSpace();
 411         }
 412 
 413         if ((ch != '"') && (ch != '\'')) {
 414             error("expected", "entity value");
 415             skipParameterSpace();
 416             if (ch == '>') {
 417                 ch = in.read();
 418             }
 419             return;
 420         }
 421 
 422         int term = ch;
 423         ch = in.read();
 424         while ((ch != -1) && (ch != term)) {
 425             if (ch == '&') {
 426                 parseEntityReference();
 427             } else {
 428                 addString(ch & 0xFF);
 429                 ch = in.read();
 430             }
 431         }
 432         if (ch == term) {
 433             ch = in.read();
 434         }
 435         if (in.replace == 0) {
 436             char data[] = getChars(0);
 437             dtd.defineEntity(nm, type, data);
 438         } else {
 439             strpos = 0;
 440         }
 441         skipParameterSpace();
 442         expect('>');
 443     }
 444 
 445     /**
 446      * Parse content model.
 447      * [126] 410:1
 448      * REMIND: data tag group
 449      */
 450     ContentModel parseContentModel() throws IOException {
 451         ContentModel m = null;
 452 
 453         switch (ch) {
 454           case '(':
 455             ch = in.read();
 456             skipParameterSpace();
 457             ContentModel e = parseContentModel();
 458 
 459             if (ch != ')') {
 460                 m = new ContentModel(ch, e);
 461                 do {
 462                     ch = in.read();
 463                     skipParameterSpace();
 464                     e.next = parseContentModel();
 465                     if (e.next.type == m.type) {
 466                         e.next = (ContentModel)e.next.content;
 467                     }
 468                     for (; e.next != null ; e = e.next);
 469                 } while (ch == m.type);
 470             } else {
 471                 m = new ContentModel(',', e);
 472             }
 473             expect(')');
 474             break;
 475 
 476           case '#':
 477             ch = in.read();
 478             if (parseIdentifier(true)) {
 479                 m = new ContentModel('*', new ContentModel(dtd.getElement("#" + getString(0))));
 480             } else {
 481                 error("invalid", "content model");
 482             }
 483             break;
 484 
 485           default:
 486             if (parseIdentifier(true)) {
 487                 m = new ContentModel(dtd.getElement(getString(0)));
 488             } else {
 489                 error("invalid", "content model");
 490             }
 491             break;
 492         }
 493 
 494         switch (ch) {
 495           case '?':
 496           case '*':
 497           case '+':
 498             m = new ContentModel(ch, m);
 499             ch = in.read();
 500             break;
 501         }
 502         skipParameterSpace();
 503 
 504         return m;
 505     }
 506 
 507     /**
 508      * Parse element declaration.
 509      * [116] 405:6
 510      */
 511     void parseElementDeclaration() throws IOException {
 512         Vector<String> elems = parseIdentifierList(true);
 513         BitSet inclusions = null;
 514         BitSet exclusions = null;
 515         boolean omitStart = false;
 516         boolean omitEnd = false;
 517 
 518         if ((ch == '-') || (ch == 'O')) {
 519             omitStart = ch == 'O';
 520             ch = in.read();
 521             skipParameterSpace();
 522 
 523             if ((ch == '-') || (ch == 'O')) {
 524                 omitEnd = ch == 'O';
 525                 ch = in.read();
 526                 skipParameterSpace();
 527             } else {
 528                 expect('-');
 529             }
 530         }
 531 
 532         int type = MODEL;
 533         ContentModel content = null;
 534         if (parseIdentifier(false)) {
 535             String nm = getString(0);
 536             type = Element.name2type(nm);
 537             if (type == 0) {
 538                 error("invalid.arg", "content type", nm);
 539                 type = EMPTY;
 540             }
 541             skipParameterSpace();
 542         } else {
 543             content = parseContentModel();
 544         }
 545 
 546         if ((type == MODEL) || (type == ANY)) {
 547             if (ch == '-') {
 548                 ch = in.read();
 549                 Vector<String> v = parseIdentifierList(true);
 550                 exclusions = new BitSet();
 551                 for (Enumeration<String> e = v.elements() ; e.hasMoreElements() ;) {
 552                     exclusions.set(dtd.getElement(e.nextElement()).getIndex());
 553                 }
 554             }
 555             if (ch == '+') {
 556                 ch = in.read();
 557                 Vector<String> v = parseIdentifierList(true);
 558                 inclusions = new BitSet();
 559                 for (Enumeration<String> e = v.elements() ; e.hasMoreElements() ;) {
 560                     inclusions.set(dtd.getElement(e.nextElement()).getIndex());
 561                 }
 562             }
 563         }
 564         expect('>');
 565 
 566         if (in.replace == 0) {
 567             for (Enumeration<String> e = elems.elements() ; e.hasMoreElements() ;) {
 568                 dtd.defineElement(e.nextElement(), type, omitStart, omitEnd, content, exclusions, inclusions, null);
 569             }
 570         }
 571     }
 572 
 573     /**
 574      * Parse an attribute declared value.
 575      * [145] 422:6
 576      */
 577     void parseAttributeDeclaredValue(AttributeList atts) throws IOException {
 578         if (ch == '(') {
 579             atts.values = parseIdentifierList(true);
 580             atts.type = NMTOKEN;
 581             return;
 582         }
 583         if (!parseIdentifier(false)) {
 584             error("invalid", "attribute value");
 585             return;
 586         }
 587         atts.type = AttributeList.name2type(getString(0));
 588         skipParameterSpace();
 589         if (atts.type == NOTATION) {
 590             atts.values = parseIdentifierList(true);
 591         }
 592     }
 593 
 594     /**
 595      * Parse an attribute value specification.
 596      * [33] 331:1
 597      */
 598     @SuppressWarnings("fallthrough")
 599     String parseAttributeValueSpecification() throws IOException {
 600         int delim = -1;
 601         switch (ch) {
 602           case '\'':
 603           case '"':
 604             delim = ch;
 605             ch = in.read();
 606         }
 607         while (true) {
 608             switch (ch) {
 609               case -1:
 610                 error("eof.arg", "attribute value");
 611                 return getString(0);
 612 
 613               case '&':
 614                 parseEntityReference();
 615                 break;
 616 
 617               case ' ':
 618               case '\t':
 619               case '\n':
 620                 if (delim == -1) {
 621                     return getString(0);
 622                 }
 623                 addString(' ');
 624                 ch = in.read();
 625                 break;
 626 
 627               case '\'':
 628               case '"':
 629                 if (delim == ch) {
 630                     ch = in.read();
 631                     return getString(0);
 632                 }
 633                 /* fall through */
 634 
 635               default:
 636                 addString(ch & 0xFF);
 637                 ch = in.read();
 638                 break;
 639             }
 640         }
 641     }
 642 
 643     /**
 644      * Parse an attribute default value.
 645      * [147] 425:1
 646      */
 647     void parseAttributeDefaultValue(AttributeList atts) throws IOException {
 648         if (ch == '#') {
 649             ch = in.read();
 650             if (!parseIdentifier(true)) {
 651                 error("invalid", "attribute value");
 652                 return;
 653             }
 654             skipParameterSpace();
 655             atts.modifier = AttributeList.name2type(getString(0));
 656             if (atts.modifier != FIXED) {
 657                 return;
 658             }
 659         }
 660         atts.value = parseAttributeValueSpecification();
 661         skipParameterSpace();
 662     }
 663 
 664     /**
 665      * Parse an attribute definition list declaration.
 666      * [141] 420:15
 667      * REMIND: associated notation name
 668      */
 669     void parseAttlistDeclaration() throws IOException {
 670         Vector<String> elems = parseIdentifierList(true);
 671         AttributeList attlist = null, atts = null;
 672 
 673         while (parseIdentifier(true)) {
 674             if (atts == null) {
 675                 attlist = atts = new AttributeList(getString(0));
 676             } else {
 677                 atts.next = new AttributeList(getString(0));
 678                 atts = atts.next;
 679             }
 680             skipParameterSpace();
 681             parseAttributeDeclaredValue(atts);
 682             parseAttributeDefaultValue(atts);
 683 
 684             if ((atts.modifier == IMPLIED) && (atts.values != null) && (atts.values.size() == 1)) {
 685                 atts.value = (String)atts.values.elementAt(0);
 686             }
 687         }
 688 
 689         expect('>');
 690 
 691         if (in.replace == 0) {
 692             for (Enumeration<String> e = elems.elements() ; e.hasMoreElements() ;) {
 693                 dtd.defineAttributes(e.nextElement(), attlist);
 694             }
 695         }
 696     }
 697 
 698     /**
 699      * Parse an ignored section until ]]> is encountered.
 700      */
 701     void parseIgnoredSection() throws IOException {
 702         int depth = 1;
 703         in.replace++;
 704         while (true) {
 705             switch (ch) {
 706               case '<':
 707                 if ((ch = in.read()) == '!') {
 708                     if ((ch = in.read()) == '[') {
 709                         ch = in.read();
 710                         depth++;
 711                     }
 712                 }
 713                 break;
 714               case ']':
 715                 if ((ch = in.read()) == ']') {
 716                     if ((ch = in.read()) == '>') {
 717                         ch = in.read();
 718                         if (--depth == 0) {
 719                             in.replace--;
 720                             return;
 721                         }
 722                     }
 723                 }
 724                 break;
 725               case -1:
 726                 error("eof");
 727                 in.replace--;
 728                 return;
 729 
 730               default:
 731                 ch = in.read();
 732                 break;
 733             }
 734         }
 735     }
 736 
 737     /**
 738      * Parse a marked section declaration.
 739      * [93] 391:13
 740      * REMIND: deal with all status keywords
 741      */
 742     void parseMarkedSectionDeclaration() throws IOException {
 743         ch = in.read();
 744         skipSpace();
 745         if (!parseIdentifier(true)) {
 746             error("expected", "section status keyword");
 747             return;
 748         }
 749         String str = getString(0);
 750         skipSpace();
 751         expect('[');
 752         if ("ignore".equals(str)) {
 753             parseIgnoredSection();
 754         } else {
 755             if (!"include".equals(str)) {
 756                 error("invalid.arg", "section status keyword", str);
 757             }
 758             parseSection();
 759             expect(']');
 760             expect(']');
 761             expect('>');
 762         }
 763     }
 764 
 765     /**
 766      * Parse an external identifier
 767      * [73] 379:1
 768      */
 769     void parseExternalIdentifier() throws IOException {
 770         if (parseIdentifier(false)) {
 771             String id = getString(0);
 772             skipParameterSpace();
 773 
 774             if (id.equals("PUBLIC")) {
 775                 if ((ch == '\'') || (ch == '"')) {
 776                     parseAttributeValueSpecification();
 777                 } else {
 778                     error("expected", "public identifier");
 779                 }
 780                 skipParameterSpace();
 781             } else if (!id.equals("SYSTEM")) {
 782                 error("invalid", "external identifier");
 783             }
 784             if ((ch == '\'') || (ch == '"')) {
 785                 parseAttributeValueSpecification();
 786             }
 787             skipParameterSpace();
 788         }
 789     }
 790 
 791     /**
 792      * Parse document type declaration.
 793      * [110] 403:1
 794      */
 795     void parseDocumentTypeDeclaration() throws IOException {
 796         skipParameterSpace();
 797         if (!parseIdentifier(true)) {
 798             error("expected", "identifier");
 799         } else {
 800             skipParameterSpace();
 801         }
 802         strpos = 0;
 803         parseExternalIdentifier();
 804 
 805         if (ch == '[') {
 806             ch = in.read();
 807             parseSection();
 808             expect(']');
 809             skipParameterSpace();
 810         }
 811         expect('>');
 812     }
 813 
 814     /**
 815      * Parse a section of the input upto EOF or ']'.
 816      */
 817     @SuppressWarnings("fallthrough")
 818     void parseSection() throws IOException {
 819         while (true) {
 820             switch (ch) {
 821               case ']':
 822                 return;
 823 
 824               case '<':
 825                 switch (ch = in.read()) {
 826                   case '!':
 827                     switch (ch = in.read()) {
 828                       case '[':
 829                         parseMarkedSectionDeclaration();
 830                         break;
 831 
 832                       case '-':
 833                         skipParameterSpace();
 834                         expect('>');
 835                         break;
 836 
 837                       default:
 838                         if (parseIdentifier(true)) {
 839                             String str = getString(0);
 840 
 841                             if (str.equals("element")) {
 842                                 parseElementDeclaration();
 843 
 844                             } else if (str.equals("entity")) {
 845                                 parseEntityDeclaration();
 846 
 847                             } else if (str.equals("attlist")) {
 848                                 parseAttlistDeclaration();
 849 
 850                             } else if (str.equals("doctype")) {
 851                                 parseDocumentTypeDeclaration();
 852 
 853                             } else if (str.equals("usemap")) {
 854                                 error("ignoring", "usemap");
 855                                 while ((ch != -1) && (ch != '>')) {
 856                                     ch = in.read();
 857                                 }
 858                                 expect('>');
 859                             } else if (str.equals("shortref")) {
 860                                 error("ignoring", "shortref");
 861                                 while ((ch != -1) && (ch != '>')) {
 862                                     ch = in.read();
 863                                 }
 864                                 expect('>');
 865                             } else if (str.equals("notation")) {
 866                                 error("ignoring", "notation");
 867                                 while ((ch != -1) && (ch != '>')) {
 868                                     ch = in.read();
 869                                 }
 870                                 expect('>');
 871                             } else {
 872                                 error("markup");
 873                             }
 874                         } else {
 875                             error("markup");
 876                             while ((ch != -1) && (ch != '>')) {
 877                                 ch = in.read();
 878                             }
 879                             expect('>');
 880                         }
 881                     }
 882                 }
 883                 break;
 884 
 885               case -1:
 886                 return;
 887 
 888               default:
 889                 char str[] = {(char)ch};
 890                 error("invalid.arg", "character", "'" + new String(str) + "' / " + ch);
 891                 /* fall through */
 892 
 893               case ' ':
 894               case '\t':
 895               case '\n':
 896                 ch = in.read();
 897                 break;
 898             }
 899         }
 900     }
 901 
 902     /**
 903      * Parse a DTD.
 904      * @return the dtd or null if an error occurred.
 905      */
 906     DTD parse(InputStream in, DTDBuilder dtd) {
 907         try {
 908             this.dtd = dtd;
 909             this.in = new DTDInputStream(in, dtd);
 910 
 911             ch = this.in.read();
 912             parseSection();
 913 
 914             if (ch != -1) {
 915                 error("premature");
 916             }
 917         } catch (IOException e) {
 918             error("ioexception");
 919         } catch (Exception e) {
 920             error("exception", e.getClass().getName(), e.getMessage());
 921             e.printStackTrace();
 922         } catch (ThreadDeath e) {
 923             error("terminated");
 924         }
 925         return (nerrors > 0) ? null : dtd;
 926     }
 927 }