aoqi@0: /* aoqi@0: * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.xml.internal.dtdparser; aoqi@0: aoqi@0: import org.xml.sax.EntityResolver; aoqi@0: import org.xml.sax.InputSource; aoqi@0: import org.xml.sax.Locator; aoqi@0: import org.xml.sax.SAXException; aoqi@0: import org.xml.sax.SAXParseException; aoqi@0: aoqi@0: import java.io.IOException; aoqi@0: import java.util.ArrayList; aoqi@0: import java.util.Enumeration; aoqi@0: import java.util.Hashtable; aoqi@0: import java.util.Locale; aoqi@0: import java.util.Set; aoqi@0: import java.util.Vector; aoqi@0: aoqi@0: /** aoqi@0: * This implements parsing of XML 1.0 DTDs. aoqi@0: *

aoqi@0: * This conforms to the portion of the XML 1.0 specification related aoqi@0: * to the external DTD subset. aoqi@0: *

aoqi@0: * For multi-language applications (such as web servers using XML aoqi@0: * processing to create dynamic content), a method supports choosing aoqi@0: * a locale for parser diagnostics which is both understood by the aoqi@0: * message recipient and supported by the parser. aoqi@0: *

aoqi@0: * This parser produces a stream of parse events. It supports some aoqi@0: * features (exposing comments, CDATA sections, and entity references) aoqi@0: * which are not required to be reported by conformant XML processors. aoqi@0: * aoqi@0: * @author David Brownell aoqi@0: * @author Janet Koenig aoqi@0: * @author Kohsuke KAWAGUCHI aoqi@0: * @version $Id: DTDParser.java,v 1.2 2009/04/16 15:25:49 snajper Exp $ aoqi@0: */ aoqi@0: public class DTDParser { aoqi@0: public final static String TYPE_CDATA = "CDATA"; aoqi@0: public final static String TYPE_ID = "ID"; aoqi@0: public final static String TYPE_IDREF = "IDREF"; aoqi@0: public final static String TYPE_IDREFS = "IDREFS"; aoqi@0: public final static String TYPE_ENTITY = "ENTITY"; aoqi@0: public final static String TYPE_ENTITIES = "ENTITIES"; aoqi@0: public final static String TYPE_NMTOKEN = "NMTOKEN"; aoqi@0: public final static String TYPE_NMTOKENS = "NMTOKENS"; aoqi@0: public final static String TYPE_NOTATION = "NOTATION"; aoqi@0: public final static String TYPE_ENUMERATION = "ENUMERATION"; aoqi@0: aoqi@0: aoqi@0: // stack of input entities being merged aoqi@0: private InputEntity in; aoqi@0: aoqi@0: // temporaries reused during parsing aoqi@0: private StringBuffer strTmp; aoqi@0: private char nameTmp []; aoqi@0: private NameCache nameCache; aoqi@0: private char charTmp [] = new char[2]; aoqi@0: aoqi@0: // temporary DTD parsing state aoqi@0: private boolean doLexicalPE; aoqi@0: aoqi@0: // DTD state, used during parsing aoqi@0: // private SimpleHashtable elements = new SimpleHashtable (47); aoqi@0: protected final Set declaredElements = new java.util.HashSet(); aoqi@0: private SimpleHashtable params = new SimpleHashtable(7); aoqi@0: aoqi@0: // exposed to package-private subclass aoqi@0: Hashtable notations = new Hashtable(7); aoqi@0: SimpleHashtable entities = new SimpleHashtable(17); aoqi@0: aoqi@0: private SimpleHashtable ids = new SimpleHashtable(); aoqi@0: aoqi@0: // listeners for DTD parsing events aoqi@0: private DTDEventListener dtdHandler; aoqi@0: aoqi@0: private EntityResolver resolver; aoqi@0: private Locale locale; aoqi@0: aoqi@0: // string constants -- use these copies so "==" works aoqi@0: // package private aoqi@0: static final String strANY = "ANY"; aoqi@0: static final String strEMPTY = "EMPTY"; aoqi@0: aoqi@0: /** aoqi@0: * Used by applications to request locale for diagnostics. aoqi@0: * aoqi@0: * @param l The locale to use, or null to use system defaults aoqi@0: * (which may include only message IDs). aoqi@0: */ aoqi@0: public void setLocale(Locale l) throws SAXException { aoqi@0: aoqi@0: if (l != null && !messages.isLocaleSupported(l.toString())) { aoqi@0: throw new SAXException(messages.getMessage(locale, aoqi@0: "P-078", new Object[]{l})); aoqi@0: } aoqi@0: locale = l; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns the diagnostic locale. aoqi@0: */ aoqi@0: public Locale getLocale() { aoqi@0: return locale; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Chooses a client locale to use for diagnostics, using the first aoqi@0: * language specified in the list that is supported by this parser. aoqi@0: * That locale is then set using aoqi@0: * setLocale(). Such a list could be provided by a variety of user aoqi@0: * preference mechanisms, including the HTTP Accept-Language aoqi@0: * header field. aoqi@0: * aoqi@0: * @param languages Array of language specifiers, ordered with the most aoqi@0: * preferable one at the front. For example, "en-ca" then "fr-ca", aoqi@0: * followed by "zh_CN". Both RFC 1766 and Java styles are supported. aoqi@0: * @return The chosen locale, or null. aoqi@0: * @see MessageCatalog aoqi@0: */ aoqi@0: public Locale chooseLocale(String languages []) aoqi@0: throws SAXException { aoqi@0: aoqi@0: Locale l = messages.chooseLocale(languages); aoqi@0: aoqi@0: if (l != null) { aoqi@0: setLocale(l); aoqi@0: } aoqi@0: return l; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Lets applications control entity resolution. aoqi@0: */ aoqi@0: public void setEntityResolver(EntityResolver r) { aoqi@0: aoqi@0: resolver = r; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns the object used to resolve entities aoqi@0: */ aoqi@0: public EntityResolver getEntityResolver() { aoqi@0: aoqi@0: return resolver; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Used by applications to set handling of DTD parsing events. aoqi@0: */ aoqi@0: public void setDtdHandler(DTDEventListener handler) { aoqi@0: dtdHandler = handler; aoqi@0: if (handler != null) aoqi@0: handler.setDocumentLocator(new Locator() { aoqi@0: public String getPublicId() { aoqi@0: return DTDParser.this.getPublicId(); aoqi@0: } aoqi@0: aoqi@0: public String getSystemId() { aoqi@0: return DTDParser.this.getSystemId(); aoqi@0: } aoqi@0: aoqi@0: public int getLineNumber() { aoqi@0: return DTDParser.this.getLineNumber(); aoqi@0: } aoqi@0: aoqi@0: public int getColumnNumber() { aoqi@0: return DTDParser.this.getColumnNumber(); aoqi@0: } aoqi@0: }); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns the handler used to for DTD parsing events. aoqi@0: */ aoqi@0: public DTDEventListener getDtdHandler() { aoqi@0: return dtdHandler; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Parse a DTD. aoqi@0: */ aoqi@0: public void parse(InputSource in) aoqi@0: throws IOException, SAXException { aoqi@0: init(); aoqi@0: parseInternal(in); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Parse a DTD. aoqi@0: */ aoqi@0: public void parse(String uri) aoqi@0: throws IOException, SAXException { aoqi@0: InputSource in; aoqi@0: aoqi@0: init(); aoqi@0: // System.out.println ("parse (\"" + uri + "\")"); aoqi@0: in = resolver.resolveEntity(null, uri); aoqi@0: aoqi@0: // If custom resolver punts resolution to parser, handle it ... aoqi@0: if (in == null) { aoqi@0: in = Resolver.createInputSource(new java.net.URL(uri), false); aoqi@0: aoqi@0: // ... or if custom resolver doesn't correctly construct the aoqi@0: // input entity, patch it up enough so relative URIs work, and aoqi@0: // issue a warning to minimize later confusion. aoqi@0: } else if (in.getSystemId() == null) { aoqi@0: warning("P-065", null); aoqi@0: in.setSystemId(uri); aoqi@0: } aoqi@0: aoqi@0: parseInternal(in); aoqi@0: } aoqi@0: aoqi@0: // makes sure the parser is reset to "before a document" aoqi@0: private void init() { aoqi@0: in = null; aoqi@0: aoqi@0: // alloc temporary data used in parsing aoqi@0: strTmp = new StringBuffer(); aoqi@0: nameTmp = new char[20]; aoqi@0: nameCache = new NameCache(); aoqi@0: aoqi@0: // reset doc info aoqi@0: // isInAttribute = false; aoqi@0: aoqi@0: doLexicalPE = false; aoqi@0: aoqi@0: entities.clear(); aoqi@0: notations.clear(); aoqi@0: params.clear(); aoqi@0: // elements.clear (); aoqi@0: declaredElements.clear(); aoqi@0: aoqi@0: // initialize predefined references ... re-interpreted later aoqi@0: builtin("amp", "&"); aoqi@0: builtin("lt", "<"); aoqi@0: builtin("gt", ">"); aoqi@0: builtin("quot", "\""); aoqi@0: builtin("apos", "'"); aoqi@0: aoqi@0: if (locale == null) aoqi@0: locale = Locale.getDefault(); aoqi@0: if (resolver == null) aoqi@0: resolver = new Resolver(); aoqi@0: if (dtdHandler == null) aoqi@0: dtdHandler = new DTDHandlerBase(); aoqi@0: } aoqi@0: aoqi@0: private void builtin(String entityName, String entityValue) { aoqi@0: InternalEntity entity; aoqi@0: entity = new InternalEntity(entityName, entityValue.toCharArray()); aoqi@0: entities.put(entityName, entity); aoqi@0: } aoqi@0: aoqi@0: aoqi@0: //////////////////////////////////////////////////////////////// aoqi@0: // aoqi@0: // parsing is by recursive descent, code roughly aoqi@0: // following the BNF rules except tweaked for simple aoqi@0: // lookahead. rules are more or less in numeric order, aoqi@0: // except where code sharing suggests other structures. aoqi@0: // aoqi@0: // a classic benefit of recursive descent parsers: it's aoqi@0: // relatively easy to get diagnostics that make sense. aoqi@0: // aoqi@0: //////////////////////////////////////////////////////////////// aoqi@0: aoqi@0: aoqi@0: private void parseInternal(InputSource input) aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: if (input == null) aoqi@0: fatal("P-000"); aoqi@0: aoqi@0: try { aoqi@0: in = InputEntity.getInputEntity(dtdHandler, locale); aoqi@0: in.init(input, null, null, false); aoqi@0: aoqi@0: dtdHandler.startDTD(in); aoqi@0: aoqi@0: // [30] extSubset ::= TextDecl? extSubsetDecl aoqi@0: // [31] extSubsetDecl ::= ( markupdecl | conditionalSect aoqi@0: // | PEReference | S )* aoqi@0: // ... same as [79] extPE, which is where the code is aoqi@0: aoqi@0: ExternalEntity externalSubset = new ExternalEntity(in); aoqi@0: externalParameterEntity(externalSubset); aoqi@0: aoqi@0: if (!in.isEOF()) { aoqi@0: fatal("P-001", new Object[] aoqi@0: {Integer.toHexString(((int) getc()))}); aoqi@0: } aoqi@0: afterRoot(); aoqi@0: dtdHandler.endDTD(); aoqi@0: aoqi@0: } catch (EndOfInputException e) { aoqi@0: if (!in.isDocument()) { aoqi@0: String name = in.getName(); aoqi@0: do { // force a relevant URI and line number aoqi@0: in = in.pop(); aoqi@0: } while (in.isInternal()); aoqi@0: fatal("P-002", new Object[]{name}); aoqi@0: } else { aoqi@0: fatal("P-003", null); aoqi@0: } aoqi@0: } catch (RuntimeException e) { aoqi@0: // Don't discard location that triggered the exception aoqi@0: // ## Should properly wrap exception aoqi@0: System.err.print("Internal DTD parser error: "); // ## aoqi@0: e.printStackTrace(); aoqi@0: throw new SAXParseException(e.getMessage() != null aoqi@0: ? e.getMessage() : e.getClass().getName(), aoqi@0: getPublicId(), getSystemId(), aoqi@0: getLineNumber(), getColumnNumber()); aoqi@0: aoqi@0: } finally { aoqi@0: // recycle temporary data used during parsing aoqi@0: strTmp = null; aoqi@0: nameTmp = null; aoqi@0: nameCache = null; aoqi@0: aoqi@0: // ditto input sources etc aoqi@0: if (in != null) { aoqi@0: in.close(); aoqi@0: in = null; aoqi@0: } aoqi@0: aoqi@0: // get rid of all DTD info ... some of it would be aoqi@0: // useful for editors etc, investigate later. aoqi@0: aoqi@0: params.clear(); aoqi@0: entities.clear(); aoqi@0: notations.clear(); aoqi@0: declaredElements.clear(); aoqi@0: // elements.clear(); aoqi@0: ids.clear(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void afterRoot() throws SAXException { aoqi@0: // Make sure all IDREFs match declared ID attributes. We scan aoqi@0: // after the document element is parsed, since XML allows forward aoqi@0: // references, and only now can we know if they're all resolved. aoqi@0: aoqi@0: for (Enumeration e = ids.keys(); aoqi@0: e.hasMoreElements(); aoqi@0: ) { aoqi@0: String id = (String) e.nextElement(); aoqi@0: Boolean value = (Boolean) ids.get(id); aoqi@0: if (Boolean.FALSE == value) aoqi@0: error("V-024", new Object[]{id}); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: aoqi@0: // role is for diagnostics aoqi@0: private void whitespace(String roleId) aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: // [3] S ::= (#x20 | #x9 | #xd | #xa)+ aoqi@0: if (!maybeWhitespace()) { aoqi@0: fatal("P-004", new Object[] aoqi@0: {messages.getMessage(locale, roleId)}); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // S? aoqi@0: private boolean maybeWhitespace() aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: if (!doLexicalPE) aoqi@0: return in.maybeWhitespace(); aoqi@0: aoqi@0: // see getc() for the PE logic -- this lets us splice aoqi@0: // expansions of PEs in "anywhere". getc() has smarts, aoqi@0: // so for external PEs we don't bypass it. aoqi@0: aoqi@0: // XXX we can marginally speed PE handling, and certainly aoqi@0: // be cleaner (hence potentially more correct), by using aoqi@0: // the observations that expanded PEs only start and stop aoqi@0: // where whitespace is allowed. getc wouldn't need any aoqi@0: // "lexical" PE expansion logic, and no other method needs aoqi@0: // to handle termination of PEs. (parsing of literals would aoqi@0: // still need to pop entities, but not parsing of references aoqi@0: // in content.) aoqi@0: aoqi@0: char c = getc(); aoqi@0: boolean saw = false; aoqi@0: aoqi@0: while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { aoqi@0: saw = true; aoqi@0: aoqi@0: // this gracefully ends things when we stop playing aoqi@0: // with internal parameters. caller should have a aoqi@0: // grammar rule allowing whitespace at end of entity. aoqi@0: if (in.isEOF() && !in.isInternal()) aoqi@0: return saw; aoqi@0: c = getc(); aoqi@0: } aoqi@0: ungetc(); aoqi@0: return saw; aoqi@0: } aoqi@0: aoqi@0: private String maybeGetName() aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: NameCacheEntry entry = maybeGetNameCacheEntry(); aoqi@0: return (entry == null) ? null : entry.name; aoqi@0: } aoqi@0: aoqi@0: private NameCacheEntry maybeGetNameCacheEntry() aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: // [5] Name ::= (Letter|'_'|':') (Namechar)* aoqi@0: char c = getc(); aoqi@0: aoqi@0: if (!XmlChars.isLetter(c) && c != ':' && c != '_') { aoqi@0: ungetc(); aoqi@0: return null; aoqi@0: } aoqi@0: return nameCharString(c); aoqi@0: } aoqi@0: aoqi@0: // Used when parsing enumerations aoqi@0: private String getNmtoken() aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: // [7] Nmtoken ::= (Namechar)+ aoqi@0: char c = getc(); aoqi@0: if (!XmlChars.isNameChar(c)) aoqi@0: fatal("P-006", new Object[]{new Character(c)}); aoqi@0: return nameCharString(c).name; aoqi@0: } aoqi@0: aoqi@0: // n.b. this gets used when parsing attribute values (for aoqi@0: // internal references) so we can't use strTmp; it's also aoqi@0: // a hotspot for CPU and memory in the parser (called at least aoqi@0: // once for each element) so this has been optimized a bit. aoqi@0: aoqi@0: private NameCacheEntry nameCharString(char c) aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: int i = 1; aoqi@0: aoqi@0: nameTmp[0] = c; aoqi@0: for (; ;) { aoqi@0: if ((c = in.getNameChar()) == 0) aoqi@0: break; aoqi@0: if (i >= nameTmp.length) { aoqi@0: char tmp [] = new char[nameTmp.length + 10]; aoqi@0: System.arraycopy(nameTmp, 0, tmp, 0, nameTmp.length); aoqi@0: nameTmp = tmp; aoqi@0: } aoqi@0: nameTmp[i++] = c; aoqi@0: } aoqi@0: return nameCache.lookupEntry(nameTmp, i); aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: // much similarity between parsing entity values in DTD aoqi@0: // and attribute values (in DTD or content) ... both follow aoqi@0: // literal parsing rules, newline canonicalization, etc aoqi@0: // aoqi@0: // leaves value in 'strTmp' ... either a "replacement text" (4.5), aoqi@0: // or else partially normalized attribute value (the first bit aoqi@0: // of 3.3.3's spec, without the "if not CDATA" bits). aoqi@0: // aoqi@0: private void parseLiteral(boolean isEntityValue) aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: // [9] EntityValue ::= aoqi@0: // '"' ([^"&%] | Reference | PEReference)* '"' aoqi@0: // | "'" ([^'&%] | Reference | PEReference)* "'" aoqi@0: // [10] AttValue ::= aoqi@0: // '"' ([^"&] | Reference )* '"' aoqi@0: // | "'" ([^'&] | Reference )* "'" aoqi@0: char quote = getc(); aoqi@0: char c; aoqi@0: InputEntity source = in; aoqi@0: aoqi@0: if (quote != '\'' && quote != '"') { aoqi@0: fatal("P-007"); aoqi@0: } aoqi@0: aoqi@0: // don't report entity expansions within attributes, aoqi@0: // they're reported "fully expanded" via SAX aoqi@0: // isInAttribute = !isEntityValue; aoqi@0: aoqi@0: // get value into strTmp aoqi@0: strTmp = new StringBuffer(); aoqi@0: aoqi@0: // scan, allowing entity push/pop wherever ... aoqi@0: // expanded entities can't terminate the literal! aoqi@0: for (; ;) { aoqi@0: if (in != source && in.isEOF()) { aoqi@0: // we don't report end of parsed entities aoqi@0: // within attributes (no SAX hooks) aoqi@0: in = in.pop(); aoqi@0: continue; aoqi@0: } aoqi@0: if ((c = getc()) == quote && in == source) { aoqi@0: break; aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: // Basically the "reference in attribute value" aoqi@0: // row of the chart in section 4.4 of the spec aoqi@0: // aoqi@0: if (c == '&') { aoqi@0: String entityName = maybeGetName(); aoqi@0: aoqi@0: if (entityName != null) { aoqi@0: nextChar(';', "F-020", entityName); aoqi@0: aoqi@0: // 4.4 says: bypass these here ... we'll catch aoqi@0: // forbidden refs to unparsed entities on use aoqi@0: if (isEntityValue) { aoqi@0: strTmp.append('&'); aoqi@0: strTmp.append(entityName); aoqi@0: strTmp.append(';'); aoqi@0: continue; aoqi@0: } aoqi@0: expandEntityInLiteral(entityName, entities, isEntityValue); aoqi@0: aoqi@0: aoqi@0: // character references are always included immediately aoqi@0: } else if ((c = getc()) == '#') { aoqi@0: int tmp = parseCharNumber(); aoqi@0: aoqi@0: if (tmp > 0xffff) { aoqi@0: tmp = surrogatesToCharTmp(tmp); aoqi@0: strTmp.append(charTmp[0]); aoqi@0: if (tmp == 2) aoqi@0: strTmp.append(charTmp[1]); aoqi@0: } else aoqi@0: strTmp.append((char) tmp); aoqi@0: } else aoqi@0: fatal("P-009"); aoqi@0: continue; aoqi@0: aoqi@0: } aoqi@0: aoqi@0: // expand parameter entities only within entity value literals aoqi@0: if (c == '%' && isEntityValue) { aoqi@0: String entityName = maybeGetName(); aoqi@0: aoqi@0: if (entityName != null) { aoqi@0: nextChar(';', "F-021", entityName); aoqi@0: expandEntityInLiteral(entityName, params, isEntityValue); aoqi@0: continue; aoqi@0: } else aoqi@0: fatal("P-011"); aoqi@0: } aoqi@0: aoqi@0: // For attribute values ... aoqi@0: if (!isEntityValue) { aoqi@0: // 3.3.3 says whitespace normalizes to space... aoqi@0: if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { aoqi@0: strTmp.append(' '); aoqi@0: continue; aoqi@0: } aoqi@0: aoqi@0: // "<" not legal in parsed literals ... aoqi@0: if (c == '<') aoqi@0: fatal("P-012"); aoqi@0: } aoqi@0: aoqi@0: strTmp.append(c); aoqi@0: } aoqi@0: // isInAttribute = false; aoqi@0: } aoqi@0: aoqi@0: // does a SINGLE expansion of the entity (often reparsed later) aoqi@0: private void expandEntityInLiteral(String name, SimpleHashtable table, aoqi@0: boolean isEntityValue) aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: Object entity = table.get(name); aoqi@0: aoqi@0: if (entity instanceof InternalEntity) { aoqi@0: InternalEntity value = (InternalEntity) entity; aoqi@0: pushReader(value.buf, name, !value.isPE); aoqi@0: aoqi@0: } else if (entity instanceof ExternalEntity) { aoqi@0: if (!isEntityValue) // must be a PE ... aoqi@0: fatal("P-013", new Object[]{name}); aoqi@0: // XXX if this returns false ... aoqi@0: pushReader((ExternalEntity) entity); aoqi@0: aoqi@0: } else if (entity == null) { aoqi@0: // aoqi@0: // Note: much confusion about whether spec requires such aoqi@0: // errors to be fatal in many cases, but none about whether aoqi@0: // it allows "normal" errors to be unrecoverable! aoqi@0: // aoqi@0: fatal((table == params) ? "V-022" : "P-014", aoqi@0: new Object[]{name}); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") aoqi@0: // for PUBLIC and SYSTEM literals, also "' aoqi@0: aoqi@0: // NOTE: XML spec should explicitly say that PE ref syntax is aoqi@0: // ignored in PIs, comments, SystemLiterals, and Pubid Literal aoqi@0: // values ... can't process the XML spec's own DTD without doing aoqi@0: // that for comments. aoqi@0: aoqi@0: private String getQuotedString(String type, String extra) aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: // use in.getc to bypass PE processing aoqi@0: char quote = in.getc(); aoqi@0: aoqi@0: if (quote != '\'' && quote != '"') aoqi@0: fatal("P-015", new Object[]{ aoqi@0: messages.getMessage(locale, type, new Object[]{extra}) aoqi@0: }); aoqi@0: aoqi@0: char c; aoqi@0: aoqi@0: strTmp = new StringBuffer(); aoqi@0: while ((c = in.getc()) != quote) aoqi@0: strTmp.append((char) c); aoqi@0: return strTmp.toString(); aoqi@0: } aoqi@0: aoqi@0: aoqi@0: private String parsePublicId() throws IOException, SAXException { aoqi@0: aoqi@0: // [12] PubidLiteral ::= ('"' PubidChar* '"') | ("'" PubidChar* "'") aoqi@0: // [13] PubidChar ::= #x20|#xd|#xa|[a-zA-Z0-9]|[-'()+,./:=?;!*#@$_%] aoqi@0: String retval = getQuotedString("F-033", null); aoqi@0: for (int i = 0; i < retval.length(); i++) { aoqi@0: char c = retval.charAt(i); aoqi@0: if (" \r\n-'()+,./:=?;!*#@$_%0123456789".indexOf(c) == -1 aoqi@0: && !(c >= 'A' && c <= 'Z') aoqi@0: && !(c >= 'a' && c <= 'z')) aoqi@0: fatal("P-016", new Object[]{new Character(c)}); aoqi@0: } aoqi@0: strTmp = new StringBuffer(); aoqi@0: strTmp.append(retval); aoqi@0: return normalize(false); aoqi@0: } aoqi@0: aoqi@0: // [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) aoqi@0: // handled by: InputEntity.parsedContent() aoqi@0: aoqi@0: private boolean maybeComment(boolean skipStart) aoqi@0: throws IOException, SAXException { aoqi@0: aoqi@0: // [15] Comment ::= '' aoqi@0: if (!in.peek(skipStart ? "!--" : "