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
26package build.tools.dtdbuilder;
27
28import javax.swing.text.html.parser.*;
29import java.net.URL;
30import java.io.IOException;
31import java.io.InputStream;
32import java.util.Enumeration;
33import java.util.Vector;
34import java.util.Hashtable;
35import java.util.BitSet;
36import 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 */
48final
49class 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}
928