| /* XMLParser.java -- | |
| Copyright (C) 2005 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. | |
| Partly derived from code which carried the following notice: | |
| Copyright (c) 1997, 1998 by Microstar Software Ltd. | |
| AElfred is free for both commercial and non-commercial use and | |
| redistribution, provided that Microstar's copyright and disclaimer are | |
| retained intact. You are free to modify AElfred for your own use and | |
| to redistribute AElfred with your modifications, provided that the | |
| modifications are clearly documented. | |
| This program is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| merchantability or fitness for a particular purpose. Please use it AT | |
| YOUR OWN RISK. | |
| */ | |
| package gnu.xml.stream; | |
| import java.io.BufferedInputStream; | |
| import java.io.EOFException; | |
| import java.io.File; | |
| import java.io.FileOutputStream; | |
| import java.io.FileWriter; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.IOException; | |
| import java.io.Reader; | |
| import java.io.StringReader; | |
| import java.io.UnsupportedEncodingException; | |
| import java.net.MalformedURLException; | |
| import java.net.URL; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.HashSet; | |
| import java.util.Iterator; | |
| import java.util.LinkedHashMap; | |
| import java.util.LinkedList; | |
| import java.util.Map; | |
| import java.util.NoSuchElementException; | |
| import java.util.StringTokenizer; | |
| import javax.xml.XMLConstants; | |
| import javax.xml.namespace.NamespaceContext; | |
| import javax.xml.namespace.QName; | |
| import javax.xml.stream.Location; | |
| import javax.xml.stream.XMLInputFactory; | |
| import javax.xml.stream.XMLReporter; | |
| import javax.xml.stream.XMLResolver; | |
| import javax.xml.stream.XMLStreamConstants; | |
| import javax.xml.stream.XMLStreamException; | |
| import javax.xml.stream.XMLStreamReader; | |
| import gnu.java.net.CRLFInputStream; | |
| import gnu.classpath.debug.TeeInputStream; | |
| import gnu.classpath.debug.TeeReader; | |
| /** | |
| * An XML parser. | |
| * This parser supports the following additional StAX properties: | |
| * <table> | |
| * <tr><td>gnu.xml.stream.stringInterning</td> | |
| * <td>Boolean</td> | |
| * <td>Indicates whether markup strings will be interned</td></tr> | |
| * <tr><td>gnu.xml.stream.xmlBase</td> | |
| * <td>Boolean</td> | |
| * <td>Indicates whether XML Base processing will be performed</td></tr> | |
| * <tr><td>gnu.xml.stream.baseURI</td> | |
| * <td>String</td> | |
| * <td>Returns the base URI of the current event</td></tr> | |
| * </table> | |
| * | |
| * @see http://www.w3.org/TR/REC-xml/ | |
| * @see http://www.w3.org/TR/xml11/ | |
| * @see http://www.w3.org/TR/REC-xml-names | |
| * @see http://www.w3.org/TR/xml-names11 | |
| * @see http://www.w3.org/TR/xmlbase/ | |
| * | |
| * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> | |
| */ | |
| public class XMLParser | |
| implements XMLStreamReader, NamespaceContext | |
| { | |
| // -- parser state machine states -- | |
| private static final int INIT = 0; // start state | |
| private static final int PROLOG = 1; // in prolog | |
| private static final int CONTENT = 2; // in content | |
| private static final int EMPTY_ELEMENT = 3; // empty element state | |
| private static final int MISC = 4; // in Misc (after root element) | |
| // -- parameters for parsing literals -- | |
| private final static int LIT_ENTITY_REF = 2; | |
| private final static int LIT_NORMALIZE = 4; | |
| private final static int LIT_ATTRIBUTE = 8; | |
| private final static int LIT_DISABLE_PE = 16; | |
| private final static int LIT_DISABLE_CREF = 32; | |
| private final static int LIT_DISABLE_EREF = 64; | |
| private final static int LIT_PUBID = 256; | |
| // -- types of attribute values -- | |
| final static int ATTRIBUTE_DEFAULT_UNDECLARED = 30; | |
| final static int ATTRIBUTE_DEFAULT_SPECIFIED = 31; | |
| final static int ATTRIBUTE_DEFAULT_IMPLIED = 32; | |
| final static int ATTRIBUTE_DEFAULT_REQUIRED = 33; | |
| final static int ATTRIBUTE_DEFAULT_FIXED = 34; | |
| // -- additional event types -- | |
| final static int START_ENTITY = 50; | |
| final static int END_ENTITY = 51; | |
| /** | |
| * The current input. | |
| */ | |
| private Input input; | |
| /** | |
| * Stack of inputs representing XML general entities. | |
| * The input representing the XML input stream or reader is always the | |
| * first element in this stack. | |
| */ | |
| private LinkedList inputStack = new LinkedList(); | |
| /** | |
| * Stack of start-entity events to be reported. | |
| */ | |
| private LinkedList startEntityStack = new LinkedList(); | |
| /** | |
| * Stack of end-entity events to be reported. | |
| */ | |
| private LinkedList endEntityStack = new LinkedList(); | |
| /** | |
| * Current parser state within the main state machine. | |
| */ | |
| private int state = INIT; | |
| /** | |
| * The (type of the) current event. | |
| */ | |
| private int event; | |
| /** | |
| * Whether we are looking ahead. Used by hasNext. | |
| */ | |
| private boolean lookahead; | |
| /** | |
| * The element name stack. The first element in this stack will be the | |
| * root element. | |
| */ | |
| private LinkedList stack = new LinkedList(); | |
| /** | |
| * Stack of namespace contexts. These are maps specifying prefix-to-URI | |
| * mappings. The first element in this stack is the most recent namespace | |
| * context (i.e. the other way around from the element name stack). | |
| */ | |
| private LinkedList namespaces = new LinkedList(); | |
| /** | |
| * The base-URI stack. This holds the base URI context for each element. | |
| * The first element in this stack is the most recent context (i.e. the | |
| * other way around from the element name stack). | |
| */ | |
| private LinkedList bases = new LinkedList(); | |
| /** | |
| * The list of attributes for the current element, in the order defined in | |
| * the XML stream. | |
| */ | |
| private ArrayList attrs = new ArrayList(); | |
| /** | |
| * Buffer for text and character data. | |
| */ | |
| private StringBuffer buf = new StringBuffer(); | |
| /** | |
| * Buffer for NMTOKEN strings (markup). | |
| */ | |
| private StringBuffer nmtokenBuf = new StringBuffer(); | |
| /** | |
| * Buffer for string literals. (e.g. attribute values) | |
| */ | |
| private StringBuffer literalBuf = new StringBuffer(); | |
| /** | |
| * Temporary Unicode character buffer used during character data reads. | |
| */ | |
| private int[] tmpBuf = new int[1024]; | |
| /** | |
| * The element content model for the current element. | |
| */ | |
| private ContentModel currentContentModel; | |
| /** | |
| * The validation stack. This holds lists of the elements seen for each | |
| * element, in order to determine whether the names and order of these | |
| * elements match the content model for the element. The last entry in | |
| * this stack represents the current element. | |
| */ | |
| private LinkedList validationStack; | |
| /** | |
| * These sets contain the IDs and the IDREFs seen in the document, to | |
| * ensure that IDs are unique and that each IDREF refers to an ID in the | |
| * document. | |
| */ | |
| private HashSet ids, idrefs; | |
| /** | |
| * The target and data associated with the current processing instruction | |
| * event. | |
| */ | |
| private String piTarget, piData; | |
| /** | |
| * The XML version declared in the XML declaration. | |
| */ | |
| private String xmlVersion; | |
| /** | |
| * The encoding declared in the XML declaration. | |
| */ | |
| private String xmlEncoding; | |
| /** | |
| * The standalone value declared in the XML declaration. | |
| */ | |
| private Boolean xmlStandalone; | |
| /** | |
| * The document type definition. | |
| */ | |
| Doctype doctype; | |
| /** | |
| * State variables for determining parameter-entity expansion. | |
| */ | |
| private boolean expandPE, peIsError; | |
| /** | |
| * Whether this is a validating parser. | |
| */ | |
| private final boolean validating; | |
| /** | |
| * Whether strings representing markup will be interned. | |
| */ | |
| private final boolean stringInterning; | |
| /** | |
| * If true, CDATA sections will be merged with adjacent text nodes into a | |
| * single event. | |
| */ | |
| private final boolean coalescing; | |
| /** | |
| * Whether to replace general entity references with their replacement | |
| * text automatically during parsing. | |
| * Otherwise entity-reference events will be issued. | |
| */ | |
| private final boolean replaceERefs; | |
| /** | |
| * Whether to support external entities. | |
| */ | |
| private final boolean externalEntities; | |
| /** | |
| * Whether to support DTDs. | |
| */ | |
| private final boolean supportDTD; | |
| /** | |
| * Whether to support XML namespaces. If true, namespace information will | |
| * be available. Otherwise namespaces will simply be reported as ordinary | |
| * attributes. | |
| */ | |
| private final boolean namespaceAware; | |
| /** | |
| * Whether to support XML Base. If true, URIs specified in xml:base | |
| * attributes will be honoured when resolving external entities. | |
| */ | |
| private final boolean baseAware; | |
| /** | |
| * Whether to report extended event types (START_ENTITY and END_ENTITY) | |
| * in addition to the standard event types. Used by the SAX parser. | |
| */ | |
| private final boolean extendedEventTypes; | |
| /** | |
| * The reporter to receive parsing warnings. | |
| */ | |
| final XMLReporter reporter; | |
| /** | |
| * Callback interface for resolving external entities. | |
| */ | |
| final XMLResolver resolver; | |
| // -- Constants for testing the next kind of markup event -- | |
| private static final String TEST_START_ELEMENT = "<"; | |
| private static final String TEST_END_ELEMENT = "</"; | |
| private static final String TEST_COMMENT = "<!--"; | |
| private static final String TEST_PI = "<?"; | |
| private static final String TEST_CDATA = "<![CDATA["; | |
| private static final String TEST_XML_DECL = "<?xml"; | |
| private static final String TEST_DOCTYPE_DECL = "<!DOCTYPE"; | |
| private static final String TEST_ELEMENT_DECL = "<!ELEMENT"; | |
| private static final String TEST_ATTLIST_DECL = "<!ATTLIST"; | |
| private static final String TEST_ENTITY_DECL = "<!ENTITY"; | |
| private static final String TEST_NOTATION_DECL = "<!NOTATION"; | |
| private static final String TEST_KET = ">"; | |
| private static final String TEST_END_COMMENT = "--"; | |
| private static final String TEST_END_PI = "?>"; | |
| private static final String TEST_END_CDATA = "]]>"; | |
| /** | |
| * The general entities predefined by the XML specification. | |
| */ | |
| private static final LinkedHashMap PREDEFINED_ENTITIES = new LinkedHashMap(); | |
| static | |
| { | |
| PREDEFINED_ENTITIES.put("amp", "&"); | |
| PREDEFINED_ENTITIES.put("lt", "<"); | |
| PREDEFINED_ENTITIES.put("gt", ">"); | |
| PREDEFINED_ENTITIES.put("apos", "'"); | |
| PREDEFINED_ENTITIES.put("quot", "\""); | |
| } | |
| /** | |
| * Creates a new XML parser for the given input stream. | |
| * This constructor should be used where possible, as it allows the | |
| * encoding of the XML data to be correctly determined from the stream. | |
| * @param in the input stream | |
| * @param systemId the URL from which the input stream was retrieved | |
| * (necessary if there are external entities to be resolved) | |
| * @param validating if the parser is to be a validating parser | |
| * @param namespaceAware if the parser should support XML Namespaces | |
| * @param coalescing if CDATA sections should be merged into adjacent text | |
| * nodes | |
| * @param replaceERefs if entity references should be automatically | |
| * replaced by their replacement text (otherwise they will be reported as | |
| * entity-reference events) | |
| * @param externalEntities if external entities should be loaded | |
| * @param supportDTD if support for the XML DTD should be enabled | |
| * @param baseAware if the parser should support XML Base to resolve | |
| * external entities | |
| * @param stringInterning whether strings will be interned during parsing | |
| * @param reporter the reporter to receive warnings during processing | |
| * @param resolver the callback interface used to resolve external | |
| * entities | |
| */ | |
| public XMLParser(InputStream in, String systemId, | |
| boolean validating, | |
| boolean namespaceAware, | |
| boolean coalescing, | |
| boolean replaceERefs, | |
| boolean externalEntities, | |
| boolean supportDTD, | |
| boolean baseAware, | |
| boolean stringInterning, | |
| boolean extendedEventTypes, | |
| XMLReporter reporter, | |
| XMLResolver resolver) | |
| { | |
| this.validating = validating; | |
| this.namespaceAware = namespaceAware; | |
| this.coalescing = coalescing; | |
| this.replaceERefs = replaceERefs; | |
| this.externalEntities = externalEntities; | |
| this.supportDTD = supportDTD; | |
| this.baseAware = baseAware; | |
| this.stringInterning = stringInterning; | |
| this.extendedEventTypes = extendedEventTypes; | |
| this.reporter = reporter; | |
| this.resolver = resolver; | |
| if (validating) | |
| { | |
| validationStack = new LinkedList(); | |
| ids = new HashSet(); | |
| idrefs = new HashSet(); | |
| } | |
| String debug = System.getProperty("gnu.xml.debug.input"); | |
| if (debug != null) | |
| { | |
| try | |
| { | |
| File file = File.createTempFile(debug, ".xml"); | |
| in = new TeeInputStream(in, new FileOutputStream(file)); | |
| } | |
| catch (IOException e) | |
| { | |
| RuntimeException e2 = new RuntimeException(); | |
| e2.initCause(e); | |
| throw e2; | |
| } | |
| } | |
| pushInput(new Input(in, null, null, systemId, null, null, false, true)); | |
| } | |
| /** | |
| * Creates a new XML parser for the given character stream. | |
| * This constructor is only available for compatibility with the JAXP | |
| * APIs, which permit XML to be parsed from a character stream. Because | |
| * the encoding specified by the character stream may conflict with that | |
| * specified in the XML declaration, this method should be avoided where | |
| * possible. | |
| * @param in the input stream | |
| * @param systemId the URL from which the input stream was retrieved | |
| * (necessary if there are external entities to be resolved) | |
| * @param validating if the parser is to be a validating parser | |
| * @param namespaceAware if the parser should support XML Namespaces | |
| * @param coalescing if CDATA sections should be merged into adjacent text | |
| * nodes | |
| * @param replaceERefs if entity references should be automatically | |
| * replaced by their replacement text (otherwise they will be reported as | |
| * entity-reference events) | |
| * @param externalEntities if external entities should be loaded | |
| * @param supportDTD if support for the XML DTD should be enabled | |
| * @param baseAware if the parser should support XML Base to resolve | |
| * external entities | |
| * @param stringInterning whether strings will be interned during parsing | |
| * @param reporter the reporter to receive warnings during processing | |
| * @param resolver the callback interface used to resolve external | |
| * entities | |
| */ | |
| public XMLParser(Reader reader, String systemId, | |
| boolean validating, | |
| boolean namespaceAware, | |
| boolean coalescing, | |
| boolean replaceERefs, | |
| boolean externalEntities, | |
| boolean supportDTD, | |
| boolean baseAware, | |
| boolean stringInterning, | |
| boolean extendedEventTypes, | |
| XMLReporter reporter, | |
| XMLResolver resolver) | |
| { | |
| this.validating = validating; | |
| this.namespaceAware = namespaceAware; | |
| this.coalescing = coalescing; | |
| this.replaceERefs = replaceERefs; | |
| this.externalEntities = externalEntities; | |
| this.supportDTD = supportDTD; | |
| this.baseAware = baseAware; | |
| this.stringInterning = stringInterning; | |
| this.extendedEventTypes = extendedEventTypes; | |
| this.reporter = reporter; | |
| this.resolver = resolver; | |
| if (validating) | |
| { | |
| validationStack = new LinkedList(); | |
| ids = new HashSet(); | |
| idrefs = new HashSet(); | |
| } | |
| String debug = System.getProperty("gnu.xml.debug.input"); | |
| if (debug != null) | |
| { | |
| try | |
| { | |
| File file = File.createTempFile(debug, ".xml"); | |
| reader = new TeeReader(reader, new FileWriter(file)); | |
| } | |
| catch (IOException e) | |
| { | |
| RuntimeException e2 = new RuntimeException(); | |
| e2.initCause(e); | |
| throw e2; | |
| } | |
| } | |
| pushInput(new Input(null, reader, null, systemId, null, null, false, true)); | |
| } | |
| // -- NamespaceContext -- | |
| public String getNamespaceURI(String prefix) | |
| { | |
| if (XMLConstants.XML_NS_PREFIX.equals(prefix)) | |
| return XMLConstants.XML_NS_URI; | |
| if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) | |
| return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; | |
| for (Iterator i = namespaces.iterator(); i.hasNext(); ) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) i.next(); | |
| String namespaceURI = (String) ctx.get(prefix); | |
| if (namespaceURI != null) | |
| return namespaceURI; | |
| } | |
| return null; | |
| } | |
| public String getPrefix(String namespaceURI) | |
| { | |
| if (XMLConstants.XML_NS_URI.equals(namespaceURI)) | |
| return XMLConstants.XML_NS_PREFIX; | |
| if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI)) | |
| return XMLConstants.XMLNS_ATTRIBUTE; | |
| for (Iterator i = namespaces.iterator(); i.hasNext(); ) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) i.next(); | |
| if (ctx.containsValue(namespaceURI)) | |
| { | |
| for (Iterator j = ctx.entrySet().iterator(); j.hasNext(); ) | |
| { | |
| Map.Entry entry = (Map.Entry) i.next(); | |
| String uri = (String) entry.getValue(); | |
| if (uri.equals(namespaceURI)) | |
| return (String) entry.getKey(); | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| public Iterator getPrefixes(String namespaceURI) | |
| { | |
| if (XMLConstants.XML_NS_URI.equals(namespaceURI)) | |
| return Collections.singleton(XMLConstants.XML_NS_PREFIX).iterator(); | |
| if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI)) | |
| return Collections.singleton(XMLConstants.XMLNS_ATTRIBUTE).iterator(); | |
| LinkedList acc = new LinkedList(); | |
| for (Iterator i = namespaces.iterator(); i.hasNext(); ) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) i.next(); | |
| if (ctx.containsValue(namespaceURI)) | |
| { | |
| for (Iterator j = ctx.entrySet().iterator(); j.hasNext(); ) | |
| { | |
| Map.Entry entry = (Map.Entry) i.next(); | |
| String uri = (String) entry.getValue(); | |
| if (uri.equals(namespaceURI)) | |
| acc.add(entry.getKey()); | |
| } | |
| } | |
| } | |
| return acc.iterator(); | |
| } | |
| // -- XMLStreamReader -- | |
| public void close() | |
| throws XMLStreamException | |
| { | |
| stack = null; | |
| namespaces = null; | |
| bases = null; | |
| buf = null; | |
| attrs = null; | |
| doctype = null; | |
| inputStack = null; | |
| validationStack = null; | |
| ids = null; | |
| idrefs = null; | |
| } | |
| public NamespaceContext getNamespaceContext() | |
| { | |
| return this; | |
| } | |
| public int getAttributeCount() | |
| { | |
| return attrs.size(); | |
| } | |
| public String getAttributeLocalName(int index) | |
| { | |
| Attribute a = (Attribute) attrs.get(index); | |
| return a.localName; | |
| } | |
| public String getAttributeNamespace(int index) | |
| { | |
| String prefix = getAttributePrefix(index); | |
| return getNamespaceURI(prefix); | |
| } | |
| public String getAttributePrefix(int index) | |
| { | |
| Attribute a = (Attribute) attrs.get(index); | |
| return a.prefix; | |
| } | |
| public QName getAttributeName(int index) | |
| { | |
| Attribute a = (Attribute) attrs.get(index); | |
| String namespaceURI = getNamespaceURI(a.prefix); | |
| return new QName(namespaceURI, a.localName, a.prefix); | |
| } | |
| public String getAttributeType(int index) | |
| { | |
| Attribute a = (Attribute) attrs.get(index); | |
| return a.type; | |
| } | |
| private String getAttributeType(String elementName, String attName) | |
| { | |
| if (doctype != null) | |
| { | |
| AttributeDecl att = doctype.getAttributeDecl(elementName, attName); | |
| if (att != null) | |
| return att.type; | |
| } | |
| return "CDATA"; | |
| } | |
| public String getAttributeValue(int index) | |
| { | |
| Attribute a = (Attribute) attrs.get(index); | |
| return a.value; | |
| } | |
| public String getAttributeValue(String namespaceURI, String localName) | |
| { | |
| for (Iterator i = attrs.iterator(); i.hasNext(); ) | |
| { | |
| Attribute a = (Attribute) i.next(); | |
| if (a.localName.equals(localName)) | |
| { | |
| String uri = getNamespaceURI(a.prefix); | |
| if ((uri == null && namespaceURI == null) || | |
| (uri != null && uri.equals(namespaceURI))) | |
| return a.value; | |
| } | |
| } | |
| return null; | |
| } | |
| boolean isAttributeDeclared(int index) | |
| { | |
| if (doctype == null) | |
| return false; | |
| Attribute a = (Attribute) attrs.get(index); | |
| String qn = ("".equals(a.prefix)) ? a.localName : | |
| a.prefix + ":" + a.localName; | |
| String elementName = buf.toString(); | |
| return doctype.isAttributeDeclared(elementName, qn); | |
| } | |
| public String getCharacterEncodingScheme() | |
| { | |
| return xmlEncoding; | |
| } | |
| public String getElementText() | |
| throws XMLStreamException | |
| { | |
| if (event != XMLStreamConstants.START_ELEMENT) | |
| throw new XMLStreamException("current event must be START_ELEMENT"); | |
| StringBuffer elementText = new StringBuffer(); | |
| int depth = stack.size(); | |
| while (event != XMLStreamConstants.END_ELEMENT || stack.size() > depth) | |
| { | |
| switch (next()) | |
| { | |
| case XMLStreamConstants.CHARACTERS: | |
| case XMLStreamConstants.SPACE: | |
| elementText.append(buf.toString()); | |
| } | |
| } | |
| return elementText.toString(); | |
| } | |
| public String getEncoding() | |
| { | |
| return (input.inputEncoding == null) ? "UTF-8" : input.inputEncoding; | |
| } | |
| public int getEventType() | |
| { | |
| return event; | |
| } | |
| public String getLocalName() | |
| { | |
| switch (event) | |
| { | |
| case XMLStreamConstants.START_ELEMENT: | |
| case XMLStreamConstants.END_ELEMENT: | |
| String qName = buf.toString(); | |
| int ci = qName.indexOf(':'); | |
| return (ci == -1) ? qName : qName.substring(ci + 1); | |
| default: | |
| return null; | |
| } | |
| } | |
| public Location getLocation() | |
| { | |
| return input; | |
| } | |
| public QName getName() | |
| { | |
| switch (event) | |
| { | |
| case XMLStreamConstants.START_ELEMENT: | |
| case XMLStreamConstants.END_ELEMENT: | |
| String qName = buf.toString(); | |
| int ci = qName.indexOf(':'); | |
| String localName = (ci == -1) ? qName : qName.substring(ci + 1); | |
| String prefix = (ci == -1) ? | |
| (namespaceAware ? XMLConstants.DEFAULT_NS_PREFIX : null) : | |
| qName.substring(0, ci); | |
| String namespaceURI = getNamespaceURI(prefix); | |
| return new QName(namespaceURI, localName, prefix); | |
| default: | |
| return null; | |
| } | |
| } | |
| public int getNamespaceCount() | |
| { | |
| if (!namespaceAware || namespaces.isEmpty()) | |
| return 0; | |
| switch (event) | |
| { | |
| case XMLStreamConstants.START_ELEMENT: | |
| case XMLStreamConstants.END_ELEMENT: | |
| LinkedHashMap ctx = (LinkedHashMap) namespaces.getFirst(); | |
| return ctx.size(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| public String getNamespacePrefix(int index) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) namespaces.getFirst(); | |
| int count = 0; | |
| for (Iterator i = ctx.keySet().iterator(); i.hasNext(); ) | |
| { | |
| String prefix = (String) i.next(); | |
| if (count++ == index) | |
| return prefix; | |
| } | |
| return null; | |
| } | |
| public String getNamespaceURI() | |
| { | |
| switch (event) | |
| { | |
| case XMLStreamConstants.START_ELEMENT: | |
| case XMLStreamConstants.END_ELEMENT: | |
| String qName = buf.toString(); | |
| int ci = qName.indexOf(':'); | |
| if (ci == -1) | |
| return null; | |
| String prefix = qName.substring(0, ci); | |
| return getNamespaceURI(prefix); | |
| default: | |
| return null; | |
| } | |
| } | |
| public String getNamespaceURI(int index) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) namespaces.getFirst(); | |
| int count = 0; | |
| for (Iterator i = ctx.values().iterator(); i.hasNext(); ) | |
| { | |
| String uri = (String) i.next(); | |
| if (count++ == index) | |
| return uri; | |
| } | |
| return null; | |
| } | |
| public String getPIData() | |
| { | |
| return piData; | |
| } | |
| public String getPITarget() | |
| { | |
| return piTarget; | |
| } | |
| public String getPrefix() | |
| { | |
| switch (event) | |
| { | |
| case XMLStreamConstants.START_ELEMENT: | |
| case XMLStreamConstants.END_ELEMENT: | |
| String qName = buf.toString(); | |
| int ci = qName.indexOf(':'); | |
| return (ci == -1) ? | |
| (namespaceAware ? XMLConstants.DEFAULT_NS_PREFIX : null) : | |
| qName.substring(0, ci); | |
| default: | |
| return null; | |
| } | |
| } | |
| public Object getProperty(String name) | |
| throws IllegalArgumentException | |
| { | |
| if (name == null) | |
| throw new IllegalArgumentException("name is null"); | |
| if (XMLInputFactory.ALLOCATOR.equals(name)) | |
| return null; | |
| if (XMLInputFactory.IS_COALESCING.equals(name)) | |
| return coalescing ? Boolean.TRUE : Boolean.FALSE; | |
| if (XMLInputFactory.IS_NAMESPACE_AWARE.equals(name)) | |
| return namespaceAware ? Boolean.TRUE : Boolean.FALSE; | |
| if (XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES.equals(name)) | |
| return replaceERefs ? Boolean.TRUE : Boolean.FALSE; | |
| if (XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES.equals(name)) | |
| return externalEntities ? Boolean.TRUE : Boolean.FALSE; | |
| if (XMLInputFactory.IS_VALIDATING.equals(name)) | |
| return Boolean.FALSE; | |
| if (XMLInputFactory.REPORTER.equals(name)) | |
| return reporter; | |
| if (XMLInputFactory.RESOLVER.equals(name)) | |
| return resolver; | |
| if (XMLInputFactory.SUPPORT_DTD.equals(name)) | |
| return supportDTD ? Boolean.TRUE : Boolean.FALSE; | |
| if ("gnu.xml.stream.stringInterning".equals(name)) | |
| return stringInterning ? Boolean.TRUE : Boolean.FALSE; | |
| if ("gnu.xml.stream.xmlBase".equals(name)) | |
| return baseAware ? Boolean.TRUE : Boolean.FALSE; | |
| if ("gnu.xml.stream.baseURI".equals(name)) | |
| return getXMLBase(); | |
| return null; | |
| } | |
| public String getText() | |
| { | |
| return buf.toString(); | |
| } | |
| public char[] getTextCharacters() | |
| { | |
| return buf.toString().toCharArray(); | |
| } | |
| public int getTextCharacters(int sourceStart, char[] target, | |
| int targetStart, int length) | |
| throws XMLStreamException | |
| { | |
| length = Math.min(sourceStart + buf.length(), length); | |
| int sourceEnd = sourceStart + length; | |
| buf.getChars(sourceStart, sourceEnd, target, targetStart); | |
| return length; | |
| } | |
| public int getTextLength() | |
| { | |
| return buf.length(); | |
| } | |
| public int getTextStart() | |
| { | |
| return 0; | |
| } | |
| public String getVersion() | |
| { | |
| return (xmlVersion == null) ? "1.0" : xmlVersion; | |
| } | |
| public boolean hasName() | |
| { | |
| switch (event) | |
| { | |
| case XMLStreamConstants.START_ELEMENT: | |
| case XMLStreamConstants.END_ELEMENT: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| public boolean hasText() | |
| { | |
| switch (event) | |
| { | |
| case XMLStreamConstants.CHARACTERS: | |
| case XMLStreamConstants.SPACE: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| public boolean isAttributeSpecified(int index) | |
| { | |
| Attribute a = (Attribute) attrs.get(index); | |
| return a.specified; | |
| } | |
| public boolean isCharacters() | |
| { | |
| return (event == XMLStreamConstants.CHARACTERS); | |
| } | |
| public boolean isEndElement() | |
| { | |
| return (event == XMLStreamConstants.END_ELEMENT); | |
| } | |
| public boolean isStandalone() | |
| { | |
| return Boolean.TRUE.equals(xmlStandalone); | |
| } | |
| public boolean isStartElement() | |
| { | |
| return (event == XMLStreamConstants.START_ELEMENT); | |
| } | |
| public boolean isWhiteSpace() | |
| { | |
| return (event == XMLStreamConstants.SPACE); | |
| } | |
| public int nextTag() | |
| throws XMLStreamException | |
| { | |
| do | |
| { | |
| switch (next()) | |
| { | |
| case XMLStreamConstants.START_ELEMENT: | |
| case XMLStreamConstants.END_ELEMENT: | |
| case XMLStreamConstants.CHARACTERS: | |
| case XMLStreamConstants.SPACE: | |
| case XMLStreamConstants.COMMENT: | |
| case XMLStreamConstants.PROCESSING_INSTRUCTION: | |
| break; | |
| default: | |
| throw new XMLStreamException("Unexpected event type: " + event); | |
| } | |
| } | |
| while (event != XMLStreamConstants.START_ELEMENT && | |
| event != XMLStreamConstants.END_ELEMENT); | |
| return event; | |
| } | |
| public void require(int type, String namespaceURI, String localName) | |
| throws XMLStreamException | |
| { | |
| if (event != type) | |
| throw new XMLStreamException("Current event type is " + event); | |
| if (event == XMLStreamConstants.START_ELEMENT || | |
| event == XMLStreamConstants.END_ELEMENT) | |
| { | |
| String ln = getLocalName(); | |
| if (!ln.equals(localName)) | |
| throw new XMLStreamException("Current local-name is " + ln); | |
| String uri = getNamespaceURI(); | |
| if ((uri == null && namespaceURI != null) || | |
| (uri != null && !uri.equals(namespaceURI))) | |
| throw new XMLStreamException("Current namespace URI is " + uri); | |
| } | |
| } | |
| public boolean standaloneSet() | |
| { | |
| return (xmlStandalone != null); | |
| } | |
| public boolean hasNext() | |
| throws XMLStreamException | |
| { | |
| if (event == XMLStreamConstants.END_DOCUMENT) | |
| return false; | |
| if (!lookahead) | |
| { | |
| next(); | |
| lookahead = true; | |
| } | |
| return event != -1; | |
| } | |
| public int next() | |
| throws XMLStreamException | |
| { | |
| if (lookahead) | |
| { | |
| lookahead = false; | |
| return event; | |
| } | |
| if (event == XMLStreamConstants.END_ELEMENT) | |
| { | |
| // Pop namespace context | |
| if (namespaceAware && !namespaces.isEmpty()) | |
| namespaces.removeFirst(); | |
| // Pop base context | |
| if (baseAware && !bases.isEmpty()) | |
| bases.removeFirst(); | |
| } | |
| if (!startEntityStack.isEmpty()) | |
| { | |
| String entityName = (String) startEntityStack.removeFirst(); | |
| buf.setLength(0); | |
| buf.append(entityName); | |
| event = START_ENTITY; | |
| return extendedEventTypes ? event : next(); | |
| } | |
| else if (!endEntityStack.isEmpty()) | |
| { | |
| String entityName = (String) endEntityStack.removeFirst(); | |
| buf.setLength(0); | |
| buf.append(entityName); | |
| event = END_ENTITY; | |
| return extendedEventTypes ? event : next(); | |
| } | |
| try | |
| { | |
| if (!input.initialized) | |
| input.init(); | |
| switch (state) | |
| { | |
| case CONTENT: | |
| if (tryRead(TEST_END_ELEMENT)) | |
| { | |
| readEndElement(); | |
| if (stack.isEmpty()) | |
| state = MISC; | |
| event = XMLStreamConstants.END_ELEMENT; | |
| } | |
| else if (tryRead(TEST_COMMENT)) | |
| { | |
| readComment(false); | |
| event = XMLStreamConstants.COMMENT; | |
| } | |
| else if (tryRead(TEST_PI)) | |
| { | |
| readPI(false); | |
| event = XMLStreamConstants.PROCESSING_INSTRUCTION; | |
| } | |
| else if (tryRead(TEST_CDATA)) | |
| { | |
| readCDSect(); | |
| event = XMLStreamConstants.CDATA; | |
| } | |
| else if (tryRead(TEST_START_ELEMENT)) | |
| { | |
| state = readStartElement(); | |
| event = XMLStreamConstants.START_ELEMENT; | |
| } | |
| else | |
| { | |
| // Check for character reference or predefined entity | |
| mark(8); | |
| int c = readCh(); | |
| if (c == 0x26) // '&' | |
| { | |
| c = readCh(); | |
| if (c == 0x23) // '#' | |
| { | |
| reset(); | |
| event = readCharData(null); | |
| } | |
| else | |
| { | |
| // entity reference | |
| reset(); | |
| readCh(); // & | |
| readReference(); | |
| String ref = buf.toString(); | |
| String text = (String) PREDEFINED_ENTITIES.get(ref); | |
| if (text != null) | |
| { | |
| event = readCharData(text); | |
| } | |
| else if (replaceERefs && !isUnparsedEntity(ref)) | |
| { | |
| // this will report a start-entity event | |
| boolean external = false; | |
| if (doctype != null) | |
| { | |
| Object entity = doctype.getEntity(ref); | |
| if (entity instanceof ExternalIds) | |
| external = true; | |
| } | |
| expandEntity(ref, false, external); | |
| event = next(); | |
| } | |
| else | |
| { | |
| event = XMLStreamConstants.ENTITY_REFERENCE; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| reset(); | |
| event = readCharData(null); | |
| if (validating && doctype != null) | |
| validatePCData(buf.toString()); | |
| } | |
| } | |
| break; | |
| case EMPTY_ELEMENT: | |
| String elementName = (String) stack.removeLast(); | |
| buf.setLength(0); | |
| buf.append(elementName); | |
| state = stack.isEmpty() ? MISC : CONTENT; | |
| event = XMLStreamConstants.END_ELEMENT; | |
| if (validating && doctype != null) | |
| endElementValidationHook(); | |
| break; | |
| case INIT: // XMLDecl? | |
| if (tryRead(TEST_XML_DECL)) | |
| readXMLDecl(); | |
| input.finalizeEncoding(); | |
| event = XMLStreamConstants.START_DOCUMENT; | |
| state = PROLOG; | |
| break; | |
| case PROLOG: // Misc* (doctypedecl Misc*)? | |
| skipWhitespace(); | |
| if (doctype == null && tryRead(TEST_DOCTYPE_DECL)) | |
| { | |
| readDoctypeDecl(); | |
| event = XMLStreamConstants.DTD; | |
| } | |
| else if (tryRead(TEST_COMMENT)) | |
| { | |
| readComment(false); | |
| event = XMLStreamConstants.COMMENT; | |
| } | |
| else if (tryRead(TEST_PI)) | |
| { | |
| readPI(false); | |
| event = XMLStreamConstants.PROCESSING_INSTRUCTION; | |
| } | |
| else if (tryRead(TEST_START_ELEMENT)) | |
| { | |
| state = readStartElement(); | |
| event = XMLStreamConstants.START_ELEMENT; | |
| } | |
| else | |
| { | |
| int c = readCh(); | |
| error("no root element: U+" + Integer.toHexString(c)); | |
| } | |
| break; | |
| case MISC: // Comment | PI | S | |
| skipWhitespace(); | |
| if (tryRead(TEST_COMMENT)) | |
| { | |
| readComment(false); | |
| event = XMLStreamConstants.COMMENT; | |
| } | |
| else if (tryRead(TEST_PI)) | |
| { | |
| readPI(false); | |
| event = XMLStreamConstants.PROCESSING_INSTRUCTION; | |
| } | |
| else | |
| { | |
| if (event == XMLStreamConstants.END_DOCUMENT) | |
| throw new NoSuchElementException(); | |
| int c = readCh(); | |
| if (c != -1) | |
| error("Only comments and PIs may appear after " + | |
| "the root element"); | |
| event = XMLStreamConstants.END_DOCUMENT; | |
| } | |
| break; | |
| default: | |
| event = -1; | |
| } | |
| return event; | |
| } | |
| catch (IOException e) | |
| { | |
| XMLStreamException e2 = new XMLStreamException(); | |
| e2.initCause(e); | |
| throw e2; | |
| } | |
| } | |
| // package private | |
| /** | |
| * Returns the current element name. | |
| */ | |
| String getCurrentElement() | |
| { | |
| return (String) stack.getLast(); | |
| } | |
| // private | |
| private void mark(int limit) | |
| throws IOException | |
| { | |
| input.mark(limit); | |
| } | |
| private void reset() | |
| throws IOException | |
| { | |
| input.reset(); | |
| } | |
| private int read() | |
| throws IOException | |
| { | |
| return input.read(); | |
| } | |
| private int read(int[] b, int off, int len) | |
| throws IOException | |
| { | |
| return input.read(b, off, len); | |
| } | |
| /** | |
| * Parsed character read. | |
| */ | |
| private int readCh() | |
| throws IOException, XMLStreamException | |
| { | |
| int c = read(); | |
| if (expandPE && c == 0x25) // '%' | |
| { | |
| if (peIsError) | |
| error("PE reference within decl in internal subset."); | |
| expandPEReference(); | |
| return readCh(); | |
| } | |
| return c; | |
| } | |
| /** | |
| * Reads the next character, ensuring it is the character specified. | |
| * @param delim the character to match | |
| * @exception XMLStreamException if the next character is not the | |
| * specified one | |
| */ | |
| private void require(char delim) | |
| throws IOException, XMLStreamException | |
| { | |
| mark(1); | |
| int c = readCh(); | |
| if (delim != c) | |
| { | |
| reset(); | |
| error("required character (got U+" + Integer.toHexString(c) + ")", | |
| new Character(delim)); | |
| } | |
| } | |
| /** | |
| * Reads the next few characters, ensuring they match the string specified. | |
| * @param delim the string to match | |
| * @exception XMLStreamException if the next characters do not match the | |
| * specified string | |
| */ | |
| private void require(String delim) | |
| throws IOException, XMLStreamException | |
| { | |
| char[] chars = delim.toCharArray(); | |
| int len = chars.length; | |
| mark(len); | |
| int off = 0; | |
| do | |
| { | |
| int l2 = read(tmpBuf, off, len - off); | |
| if (l2 == -1) | |
| { | |
| reset(); | |
| error("EOF before required string", delim); | |
| } | |
| off += l2; | |
| } | |
| while (off < len); | |
| for (int i = 0; i < chars.length; i++) | |
| { | |
| if (chars[i] != tmpBuf[i]) | |
| { | |
| reset(); | |
| error("required string", delim); | |
| } | |
| } | |
| } | |
| /** | |
| * Try to read a single character. On failure, reset the stream. | |
| * @param delim the character to test | |
| * @return true if the character matched delim, false otherwise. | |
| */ | |
| private boolean tryRead(char delim) | |
| throws IOException, XMLStreamException | |
| { | |
| mark(1); | |
| int c = readCh(); | |
| if (delim != c) | |
| { | |
| reset(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Tries to read the specified characters. | |
| * If successful, the stream is positioned after the last character, | |
| * otherwise it is reset. | |
| * @param test the string to test | |
| * @return true if the characters matched the test string, false otherwise. | |
| */ | |
| private boolean tryRead(String test) | |
| throws IOException | |
| { | |
| char[] chars = test.toCharArray(); | |
| int len = chars.length; | |
| mark(len); | |
| int count = 0; | |
| int l2 = read(tmpBuf, 0, len); | |
| if (l2 == -1) | |
| { | |
| reset(); | |
| return false; | |
| } | |
| count += l2; | |
| // check the characters we received first before doing additional reads | |
| for (int i = 0; i < count; i++) | |
| { | |
| if (chars[i] != tmpBuf[i]) | |
| { | |
| reset(); | |
| return false; | |
| } | |
| } | |
| while (count < len) | |
| { | |
| // force read | |
| int c = read(); | |
| if (c == -1) | |
| { | |
| reset(); | |
| return false; | |
| } | |
| tmpBuf[count] = (char) c; | |
| // check each character as it is read | |
| if (chars[count] != tmpBuf[count]) | |
| { | |
| reset(); | |
| return false; | |
| } | |
| count++; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Reads characters until the specified test string is encountered. | |
| * @param delim the string delimiting the end of the characters | |
| */ | |
| private void readUntil(String delim) | |
| throws IOException, XMLStreamException | |
| { | |
| int startLine = input.line; | |
| try | |
| { | |
| while (!tryRead(delim)) | |
| { | |
| int c = readCh(); | |
| if (c == -1) | |
| throw new EOFException(); | |
| else if (input.xml11) | |
| { | |
| if (!isXML11Char(c) || isXML11RestrictedChar(c)) | |
| error("illegal XML 1.1 character", | |
| "U+" + Integer.toHexString(c)); | |
| } | |
| else if (!isChar(c)) | |
| error("illegal XML character", | |
| "U+" + Integer.toHexString(c)); | |
| buf.append(Character.toChars(c)); | |
| } | |
| } | |
| catch (EOFException e) | |
| { | |
| error("end of input while looking for delimiter "+ | |
| "(started on line " + startLine + ')', delim); | |
| } | |
| } | |
| /** | |
| * Reads any whitespace characters. | |
| * @return true if whitespace characters were read, false otherwise | |
| */ | |
| private boolean tryWhitespace() | |
| throws IOException, XMLStreamException | |
| { | |
| boolean white; | |
| boolean ret = false; | |
| do | |
| { | |
| mark(1); | |
| int c = readCh(); | |
| while (c == -1 && inputStack.size() > 1) | |
| { | |
| popInput(); | |
| c = readCh(); | |
| } | |
| white = (c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d); | |
| if (white) | |
| ret = true; | |
| } | |
| while (white); | |
| reset(); | |
| return ret; | |
| } | |
| /** | |
| * Skip over any whitespace characters. | |
| */ | |
| private void skipWhitespace() | |
| throws IOException, XMLStreamException | |
| { | |
| boolean white; | |
| do | |
| { | |
| mark(1); | |
| int c = readCh(); | |
| while (c == -1 && inputStack.size() > 1) | |
| { | |
| popInput(); | |
| c = readCh(); | |
| } | |
| white = (c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d); | |
| } | |
| while (white); | |
| reset(); | |
| } | |
| /** | |
| * Try to read as many whitespace characters as are available. | |
| * @exception XMLStreamException if no whitespace characters were seen | |
| */ | |
| private void requireWhitespace() | |
| throws IOException, XMLStreamException | |
| { | |
| if (!tryWhitespace()) | |
| error("whitespace required"); | |
| } | |
| /** | |
| * Returns the current base URI for resolving external entities. | |
| */ | |
| String getXMLBase() | |
| { | |
| if (baseAware) | |
| { | |
| for (Iterator i = bases.iterator(); i.hasNext(); ) | |
| { | |
| String base = (String) i.next(); | |
| if (base != null) | |
| return base; | |
| } | |
| } | |
| return input.systemId; | |
| } | |
| /** | |
| * Push the specified text input source. | |
| */ | |
| private void pushInput(String name, String text, boolean report, | |
| boolean normalize) | |
| throws IOException, XMLStreamException | |
| { | |
| // Check for recursion | |
| if (name != null && !"".equals(name)) | |
| { | |
| for (Iterator i = inputStack.iterator(); i.hasNext(); ) | |
| { | |
| Input ctx = (Input) i.next(); | |
| if (name.equals(ctx.name)) | |
| error("entities may not be self-recursive", name); | |
| } | |
| } | |
| else | |
| report = false; | |
| pushInput(new Input(null, new StringReader(text), input.publicId, | |
| input.systemId, name, input.inputEncoding, report, | |
| normalize)); | |
| } | |
| /** | |
| * Push the specified external input source. | |
| */ | |
| private void pushInput(String name, ExternalIds ids, boolean report, | |
| boolean normalize) | |
| throws IOException, XMLStreamException | |
| { | |
| if (!externalEntities) | |
| return; | |
| String url = absolutize(input.systemId, ids.systemId); | |
| // Check for recursion | |
| for (Iterator i = inputStack.iterator(); i.hasNext(); ) | |
| { | |
| Input ctx = (Input) i.next(); | |
| if (url.equals(ctx.systemId)) | |
| error("entities may not be self-recursive", url); | |
| if (name != null && !"".equals(name) && name.equals(ctx.name)) | |
| error("entities may not be self-recursive", name); | |
| } | |
| if (name == null || "".equals(name)) | |
| report = false; | |
| InputStream in = null; | |
| if (resolver != null) | |
| { | |
| Object obj = resolver.resolveEntity(ids.publicId, url, getXMLBase(), | |
| null); | |
| if (obj instanceof InputStream) | |
| in = (InputStream) obj; | |
| } | |
| if (in == null) | |
| in = resolve(url); | |
| if (in == null) | |
| error("unable to resolve external entity", | |
| (ids.systemId != null) ? ids.systemId : ids.publicId); | |
| pushInput(new Input(in, null, ids.publicId, url, name, null, report, | |
| normalize)); | |
| input.init(); | |
| if (tryRead(TEST_XML_DECL)) | |
| readTextDecl(); | |
| input.finalizeEncoding(); | |
| } | |
| /** | |
| * Push the specified input source (general entity) onto the input stack. | |
| */ | |
| private void pushInput(Input input) | |
| { | |
| if (input.report) | |
| startEntityStack.addFirst(input.name); | |
| inputStack.addLast(input); | |
| if (this.input != null) | |
| input.xml11 = this.input.xml11; | |
| this.input = input; | |
| } | |
| /** | |
| * "Absolutize" a URL. This resolves a relative URL into an absolute one. | |
| * @param base the current base URL | |
| * @param href the (absolute or relative) URL to resolve | |
| */ | |
| public static String absolutize(String base, String href) | |
| throws MalformedURLException | |
| { | |
| if (href == null) | |
| return null; | |
| int ci = href.indexOf(':'); | |
| if (ci > 1 && isURLScheme(href.substring(0, ci))) | |
| { | |
| // href is absolute already | |
| return href; | |
| } | |
| if (base == null) | |
| base = ""; | |
| else | |
| { | |
| int i = base.lastIndexOf('/'); | |
| if (i != -1) | |
| base = base.substring(0, i + 1); | |
| else | |
| base = ""; | |
| } | |
| if ("".equals(base)) | |
| { | |
| // assume file URL relative to current directory | |
| base = System.getProperty("user.dir"); | |
| if (base.charAt(0) == '/') | |
| base = base.substring(1); | |
| base = "file:///" + base.replace(File.separatorChar, '/'); | |
| if (!base.endsWith("/")) | |
| base += "/"; | |
| } | |
| return new URL(new URL(base), href).toString(); | |
| } | |
| /** | |
| * Indicates whether the specified characters match the scheme portion of | |
| * a URL. | |
| * @see RFC 1738 section 2.1 | |
| */ | |
| private static boolean isURLScheme(String text) | |
| { | |
| int len = text.length(); | |
| for (int i = 0; i < len; i++) | |
| { | |
| char c = text.charAt(i); | |
| if (c == '+' || c == '.' || c == '-') | |
| continue; | |
| if (c < 65 || (c > 90 && c < 97) || c > 122) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Returns an input stream for the given URL. | |
| */ | |
| static InputStream resolve(String url) | |
| throws IOException | |
| { | |
| try | |
| { | |
| return new URL(url).openStream(); | |
| } | |
| catch (MalformedURLException e) | |
| { | |
| return null; | |
| } | |
| catch (IOException e) | |
| { | |
| IOException e2 = new IOException("error resolving " + url); | |
| e2.initCause(e); | |
| throw e2; | |
| } | |
| } | |
| /** | |
| * Pops the current input source (general entity) off the stack. | |
| */ | |
| private void popInput() | |
| { | |
| Input old = (Input) inputStack.removeLast(); | |
| if (old.report) | |
| endEntityStack.addFirst(old.name); | |
| input = (Input) inputStack.getLast(); | |
| } | |
| /** | |
| * Parse an entity text declaration. | |
| */ | |
| private void readTextDecl() | |
| throws IOException, XMLStreamException | |
| { | |
| final int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; | |
| requireWhitespace(); | |
| if (tryRead("version")) | |
| { | |
| readEq(); | |
| String v = readLiteral(flags, false); | |
| if ("1.0".equals(v)) | |
| input.xml11 = false; | |
| else if ("1.1".equals(v)) | |
| { | |
| Input i1 = (Input) inputStack.getFirst(); | |
| if (!i1.xml11) | |
| error("external entity specifies later version number"); | |
| input.xml11 = true; | |
| } | |
| else | |
| throw new XMLStreamException("illegal XML version: " + v); | |
| requireWhitespace(); | |
| } | |
| require("encoding"); | |
| readEq(); | |
| String enc = readLiteral(flags, false); | |
| skipWhitespace(); | |
| require("?>"); | |
| input.setInputEncoding(enc); | |
| } | |
| /** | |
| * Parse the XML declaration. | |
| */ | |
| private void readXMLDecl() | |
| throws IOException, XMLStreamException | |
| { | |
| final int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; | |
| requireWhitespace(); | |
| require("version"); | |
| readEq(); | |
| xmlVersion = readLiteral(flags, false); | |
| if ("1.0".equals(xmlVersion)) | |
| input.xml11 = false; | |
| else if ("1.1".equals(xmlVersion)) | |
| input.xml11 = true; | |
| else | |
| throw new XMLStreamException("illegal XML version: " + xmlVersion); | |
| boolean white = tryWhitespace(); | |
| if (tryRead("encoding")) | |
| { | |
| if (!white) | |
| error("whitespace required before 'encoding='"); | |
| readEq(); | |
| xmlEncoding = readLiteral(flags, false); | |
| white = tryWhitespace(); | |
| } | |
| if (tryRead("standalone")) | |
| { | |
| if (!white) | |
| error("whitespace required before 'standalone='"); | |
| readEq(); | |
| String standalone = readLiteral(flags, false); | |
| if ("yes".equals(standalone)) | |
| xmlStandalone = Boolean.TRUE; | |
| else if ("no".equals(standalone)) | |
| xmlStandalone = Boolean.FALSE; | |
| else | |
| error("standalone flag must be 'yes' or 'no'", standalone); | |
| } | |
| skipWhitespace(); | |
| require("?>"); | |
| if (xmlEncoding != null) | |
| input.setInputEncoding(xmlEncoding); | |
| } | |
| /** | |
| * Parse the DOCTYPE declaration. | |
| */ | |
| private void readDoctypeDecl() | |
| throws IOException, XMLStreamException | |
| { | |
| if (!supportDTD) | |
| error("parser was configured not to support DTDs"); | |
| requireWhitespace(); | |
| String rootName = readNmtoken(true); | |
| skipWhitespace(); | |
| ExternalIds ids = readExternalIds(false, true); | |
| doctype = | |
| this.new Doctype(rootName, ids.publicId, ids.systemId); | |
| // Parse internal subset first | |
| skipWhitespace(); | |
| if (tryRead('[')) | |
| { | |
| while (true) | |
| { | |
| expandPE = true; | |
| skipWhitespace(); | |
| expandPE = false; | |
| if (tryRead(']')) | |
| break; | |
| else | |
| readMarkupdecl(false); | |
| } | |
| } | |
| skipWhitespace(); | |
| require('>'); | |
| // Parse external subset | |
| if (ids.systemId != null && externalEntities) | |
| { | |
| pushInput("", ">", false, false); | |
| pushInput("[dtd]", ids, true, true); | |
| // loop until we get back to ">" | |
| while (true) | |
| { | |
| expandPE = true; | |
| skipWhitespace(); | |
| expandPE = false; | |
| mark(1); | |
| int c = readCh(); | |
| if (c == 0x3e) // '>' | |
| break; | |
| else if (c == -1) | |
| popInput(); | |
| else | |
| { | |
| reset(); | |
| expandPE = true; | |
| readMarkupdecl(true); | |
| expandPE = true; | |
| } | |
| } | |
| if (inputStack.size() != 2) | |
| error("external subset has unmatched '>'"); | |
| popInput(); | |
| } | |
| checkDoctype(); | |
| if (validating) | |
| validateDoctype(); | |
| // Make rootName available for reading | |
| buf.setLength(0); | |
| buf.append(rootName); | |
| } | |
| /** | |
| * Checks the well-formedness of the DTD. | |
| */ | |
| private void checkDoctype() | |
| throws XMLStreamException | |
| { | |
| // TODO check entity recursion | |
| } | |
| /** | |
| * Parse the markupdecl production. | |
| */ | |
| private void readMarkupdecl(boolean inExternalSubset) | |
| throws IOException, XMLStreamException | |
| { | |
| boolean saved = expandPE; | |
| mark(1); | |
| require('<'); | |
| reset(); | |
| expandPE = false; | |
| if (tryRead(TEST_ELEMENT_DECL)) | |
| { | |
| expandPE = saved; | |
| readElementDecl(); | |
| } | |
| else if (tryRead(TEST_ATTLIST_DECL)) | |
| { | |
| expandPE = saved; | |
| readAttlistDecl(); | |
| } | |
| else if (tryRead(TEST_ENTITY_DECL)) | |
| { | |
| expandPE = saved; | |
| readEntityDecl(inExternalSubset); | |
| } | |
| else if (tryRead(TEST_NOTATION_DECL)) | |
| { | |
| expandPE = saved; | |
| readNotationDecl(inExternalSubset); | |
| } | |
| else if (tryRead(TEST_PI)) | |
| { | |
| readPI(true); | |
| expandPE = saved; | |
| } | |
| else if (tryRead(TEST_COMMENT)) | |
| { | |
| readComment(true); | |
| expandPE = saved; | |
| } | |
| else if (tryRead("<![")) | |
| { | |
| // conditional section | |
| expandPE = saved; | |
| if (inputStack.size() < 2) | |
| error("conditional sections illegal in internal subset"); | |
| skipWhitespace(); | |
| if (tryRead("INCLUDE")) | |
| { | |
| skipWhitespace(); | |
| require('['); | |
| skipWhitespace(); | |
| while (!tryRead("]]>")) | |
| { | |
| readMarkupdecl(inExternalSubset); | |
| skipWhitespace(); | |
| } | |
| } | |
| else if (tryRead("IGNORE")) | |
| { | |
| skipWhitespace(); | |
| require('['); | |
| expandPE = false; | |
| for (int nesting = 1; nesting > 0; ) | |
| { | |
| int c = readCh(); | |
| switch (c) | |
| { | |
| case 0x3c: // '<' | |
| if (tryRead("![")) | |
| nesting++; | |
| break; | |
| case 0x5d: // ']' | |
| if (tryRead("]>")) | |
| nesting--; | |
| break; | |
| case -1: | |
| throw new EOFException(); | |
| } | |
| } | |
| expandPE = saved; | |
| } | |
| else | |
| error("conditional section must begin with INCLUDE or IGNORE"); | |
| } | |
| else | |
| error("expected markup declaration"); | |
| } | |
| /** | |
| * Parse the elementdecl production. | |
| */ | |
| private void readElementDecl() | |
| throws IOException, XMLStreamException | |
| { | |
| requireWhitespace(); | |
| boolean saved = expandPE; | |
| expandPE = (inputStack.size() > 1); | |
| String name = readNmtoken(true); | |
| expandPE = saved; | |
| requireWhitespace(); | |
| readContentspec(name); | |
| skipWhitespace(); | |
| require('>'); | |
| } | |
| /** | |
| * Parse the contentspec production. | |
| */ | |
| private void readContentspec(String elementName) | |
| throws IOException, XMLStreamException | |
| { | |
| if (tryRead("EMPTY")) | |
| doctype.addElementDecl(elementName, "EMPTY", new EmptyContentModel()); | |
| else if (tryRead("ANY")) | |
| doctype.addElementDecl(elementName, "ANY", new AnyContentModel()); | |
| else | |
| { | |
| ContentModel model; | |
| StringBuffer acc = new StringBuffer(); | |
| require('('); | |
| acc.append('('); | |
| skipWhitespace(); | |
| if (tryRead("#PCDATA")) | |
| { | |
| // mixed content | |
| acc.append("#PCDATA"); | |
| MixedContentModel mm = new MixedContentModel(); | |
| model = mm; | |
| skipWhitespace(); | |
| if (tryRead(')')) | |
| { | |
| acc.append(")"); | |
| if (tryRead('*')) | |
| { | |
| mm.min = 0; | |
| mm.max = -1; | |
| } | |
| } | |
| else | |
| { | |
| while (!tryRead(")")) | |
| { | |
| require('|'); | |
| acc.append('|'); | |
| skipWhitespace(); | |
| String name = readNmtoken(true); | |
| acc.append(name); | |
| mm.addName(name); | |
| skipWhitespace(); | |
| } | |
| require('*'); | |
| acc.append(")*"); | |
| mm.min = 0; | |
| mm.max = -1; | |
| } | |
| } | |
| else | |
| model = readElements(acc); | |
| doctype.addElementDecl(elementName, acc.toString(), model); | |
| } | |
| } | |
| /** | |
| * Parses an element content model. | |
| */ | |
| private ElementContentModel readElements(StringBuffer acc) | |
| throws IOException, XMLStreamException | |
| { | |
| int separator; | |
| ElementContentModel model = new ElementContentModel(); | |
| // Parse first content particle | |
| skipWhitespace(); | |
| model.addContentParticle(readContentParticle(acc)); | |
| // End or separator | |
| skipWhitespace(); | |
| int c = readCh(); | |
| switch (c) | |
| { | |
| case 0x29: // ')' | |
| acc.append(')'); | |
| mark(1); | |
| c = readCh(); | |
| switch (c) | |
| { | |
| case 0x3f: // '?' | |
| acc.append('?'); | |
| model.min = 0; | |
| model.max = 1; | |
| break; | |
| case 0x2a: // '*' | |
| acc.append('*'); | |
| model.min = 0; | |
| model.max = -1; | |
| break; | |
| case 0x2b: // '+' | |
| acc.append('+'); | |
| model.min = 1; | |
| model.max = -1; | |
| break; | |
| default: | |
| reset(); | |
| } | |
| return model; // done | |
| case 0x7c: // '|' | |
| model.or = true; | |
| // fall through | |
| case 0x2c: // ',' | |
| separator = c; | |
| acc.append(Character.toChars(c)); | |
| break; | |
| default: | |
| error("bad separator in content model", | |
| "U+" + Integer.toHexString(c)); | |
| return model; | |
| } | |
| // Parse subsequent content particles | |
| while (true) | |
| { | |
| skipWhitespace(); | |
| model.addContentParticle(readContentParticle(acc)); | |
| skipWhitespace(); | |
| c = readCh(); | |
| if (c == 0x29) // ')' | |
| { | |
| acc.append(')'); | |
| break; | |
| } | |
| else if (c != separator) | |
| { | |
| error("bad separator in content model", | |
| "U+" + Integer.toHexString(c)); | |
| return model; | |
| } | |
| else | |
| acc.append(c); | |
| } | |
| // Check for occurrence indicator | |
| mark(1); | |
| c = readCh(); | |
| switch (c) | |
| { | |
| case 0x3f: // '?' | |
| acc.append('?'); | |
| model.min = 0; | |
| model.max = 1; | |
| break; | |
| case 0x2a: // '*' | |
| acc.append('*'); | |
| model.min = 0; | |
| model.max = -1; | |
| break; | |
| case 0x2b: // '+' | |
| acc.append('+'); | |
| model.min = 1; | |
| model.max = -1; | |
| break; | |
| default: | |
| reset(); | |
| } | |
| return model; | |
| } | |
| /** | |
| * Parse a cp production. | |
| */ | |
| private ContentParticle readContentParticle(StringBuffer acc) | |
| throws IOException, XMLStreamException | |
| { | |
| ContentParticle cp = new ContentParticle(); | |
| if (tryRead('(')) | |
| { | |
| acc.append('('); | |
| cp.content = readElements(acc); | |
| } | |
| else | |
| { | |
| String name = readNmtoken(true); | |
| acc.append(name); | |
| cp.content = name; | |
| mark(1); | |
| int c = readCh(); | |
| switch (c) | |
| { | |
| case 0x3f: // '?' | |
| acc.append('?'); | |
| cp.min = 0; | |
| cp.max = 1; | |
| break; | |
| case 0x2a: // '*' | |
| acc.append('*'); | |
| cp.min = 0; | |
| cp.max = -1; | |
| break; | |
| case 0x2b: // '+' | |
| acc.append('+'); | |
| cp.min = 1; | |
| cp.max = -1; | |
| break; | |
| default: | |
| reset(); | |
| } | |
| } | |
| return cp; | |
| } | |
| /** | |
| * Parse an attribute-list definition. | |
| */ | |
| private void readAttlistDecl() | |
| throws IOException, XMLStreamException | |
| { | |
| requireWhitespace(); | |
| boolean saved = expandPE; | |
| expandPE = (inputStack.size() > 1); | |
| String elementName = readNmtoken(true); | |
| expandPE = saved; | |
| boolean white = tryWhitespace(); | |
| while (!tryRead('>')) | |
| { | |
| if (!white) | |
| error("whitespace required before attribute definition"); | |
| readAttDef(elementName); | |
| white = tryWhitespace(); | |
| } | |
| } | |
| /** | |
| * Parse a single attribute definition. | |
| */ | |
| private void readAttDef(String elementName) | |
| throws IOException, XMLStreamException | |
| { | |
| String name = readNmtoken(true); | |
| requireWhitespace(); | |
| StringBuffer acc = new StringBuffer(); | |
| HashSet values = new HashSet(); | |
| String type = readAttType(acc, values); | |
| if (validating) | |
| { | |
| if ("ID".equals(type)) | |
| { | |
| // VC: One ID per Element Type | |
| for (Iterator i = doctype.attlistIterator(elementName); | |
| i.hasNext(); ) | |
| { | |
| Map.Entry entry = (Map.Entry) i.next(); | |
| AttributeDecl decl = (AttributeDecl) entry.getValue(); | |
| if ("ID".equals(decl.type)) | |
| error("element types must not have more than one ID " + | |
| "attribute"); | |
| } | |
| } | |
| else if ("NOTATION".equals(type)) | |
| { | |
| // VC: One Notation Per Element Type | |
| for (Iterator i = doctype.attlistIterator(elementName); | |
| i.hasNext(); ) | |
| { | |
| Map.Entry entry = (Map.Entry) i.next(); | |
| AttributeDecl decl = (AttributeDecl) entry.getValue(); | |
| if ("NOTATION".equals(decl.type)) | |
| error("element types must not have more than one NOTATION " + | |
| "attribute"); | |
| } | |
| // VC: No Notation on Empty Element | |
| ContentModel model = doctype.getElementModel(elementName); | |
| if (model != null && model.type == ContentModel.EMPTY) | |
| error("attributes of type NOTATION must not be declared on an " + | |
| "element declared EMPTY"); | |
| } | |
| } | |
| String enumer = null; | |
| if ("ENUMERATION".equals(type) || "NOTATION".equals(type)) | |
| enumer = acc.toString(); | |
| else | |
| values = null; | |
| requireWhitespace(); | |
| readDefault(elementName, name, type, enumer, values); | |
| } | |
| /** | |
| * Parse an attribute type. | |
| */ | |
| private String readAttType(StringBuffer acc, HashSet values) | |
| throws IOException, XMLStreamException | |
| { | |
| if (tryRead('(')) | |
| { | |
| readEnumeration(false, acc, values); | |
| return "ENUMERATION"; | |
| } | |
| else | |
| { | |
| String typeString = readNmtoken(true); | |
| if ("NOTATION".equals(typeString)) | |
| { | |
| readNotationType(acc, values); | |
| return typeString; | |
| } | |
| else if ("CDATA".equals(typeString) || | |
| "ID".equals(typeString) || | |
| "IDREF".equals(typeString) || | |
| "IDREFS".equals(typeString) || | |
| "ENTITY".equals(typeString) || | |
| "ENTITIES".equals(typeString) || | |
| "NMTOKEN".equals(typeString) || | |
| "NMTOKENS".equals(typeString)) | |
| return typeString; | |
| else | |
| { | |
| error("illegal attribute type", typeString); | |
| return null; | |
| } | |
| } | |
| } | |
| /** | |
| * Parse an enumeration. | |
| */ | |
| private void readEnumeration(boolean isNames, StringBuffer acc, | |
| HashSet values) | |
| throws IOException, XMLStreamException | |
| { | |
| acc.append('('); | |
| // first token | |
| skipWhitespace(); | |
| String token = readNmtoken(isNames); | |
| acc.append(token); | |
| values.add(token); | |
| // subsequent tokens | |
| skipWhitespace(); | |
| while (!tryRead(')')) | |
| { | |
| require('|'); | |
| acc.append('|'); | |
| skipWhitespace(); | |
| token = readNmtoken(isNames); | |
| // VC: No Duplicate Tokens | |
| if (validating && values.contains(token)) | |
| error("duplicate token", token); | |
| acc.append(token); | |
| values.add(token); | |
| skipWhitespace(); | |
| } | |
| acc.append(')'); | |
| } | |
| /** | |
| * Parse a notation type for an attribute. | |
| */ | |
| private void readNotationType(StringBuffer acc, HashSet values) | |
| throws IOException, XMLStreamException | |
| { | |
| requireWhitespace(); | |
| require('('); | |
| readEnumeration(true, acc, values); | |
| } | |
| /** | |
| * Parse the default value for an attribute. | |
| */ | |
| private void readDefault(String elementName, String name, | |
| String type, String enumeration, HashSet values) | |
| throws IOException, XMLStreamException | |
| { | |
| int valueType = ATTRIBUTE_DEFAULT_SPECIFIED; | |
| int flags = LIT_ATTRIBUTE; | |
| String value = null, defaultType = null; | |
| boolean saved = expandPE; | |
| if (!"CDATA".equals(type)) | |
| flags |= LIT_NORMALIZE; | |
| expandPE = false; | |
| if (tryRead('#')) | |
| { | |
| if (tryRead("FIXED")) | |
| { | |
| defaultType = "#FIXED"; | |
| valueType = ATTRIBUTE_DEFAULT_FIXED; | |
| requireWhitespace(); | |
| value = readLiteral(flags, false); | |
| } | |
| else if (tryRead("REQUIRED")) | |
| { | |
| defaultType = "#REQUIRED"; | |
| valueType = ATTRIBUTE_DEFAULT_REQUIRED; | |
| } | |
| else if (tryRead("IMPLIED")) | |
| { | |
| defaultType = "#IMPLIED"; | |
| valueType = ATTRIBUTE_DEFAULT_IMPLIED; | |
| } | |
| else | |
| error("illegal keyword for attribute default value"); | |
| } | |
| else | |
| value = readLiteral(flags, false); | |
| expandPE = saved; | |
| if (validating) | |
| { | |
| if ("ID".equals(type)) | |
| { | |
| // VC: Attribute Default Value Syntactically Correct | |
| if (value != null && !isNmtoken(value, true)) | |
| error("default value must match Name production", value); | |
| // VC: ID Attribute Default | |
| if (valueType != ATTRIBUTE_DEFAULT_REQUIRED && | |
| valueType != ATTRIBUTE_DEFAULT_IMPLIED) | |
| error("ID attributes must have a declared default of " + | |
| "#IMPLIED or #REQUIRED"); | |
| } | |
| else if (value != null) | |
| { | |
| // VC: Attribute Default Value Syntactically Correct | |
| if ("IDREF".equals(type) || "ENTITY".equals(type)) | |
| { | |
| if (!isNmtoken(value, true)) | |
| error("default value must match Name production", value); | |
| } | |
| else if ("IDREFS".equals(type) || "ENTITIES".equals(type)) | |
| { | |
| StringTokenizer st = new StringTokenizer(value); | |
| while (st.hasMoreTokens()) | |
| { | |
| String token = st.nextToken(); | |
| if (!isNmtoken(token, true)) | |
| error("default value must match Name production", token); | |
| } | |
| } | |
| else if ("NMTOKEN".equals(type) || "ENUMERATION".equals(type)) | |
| { | |
| if (!isNmtoken(value, false)) | |
| error("default value must match Nmtoken production", value); | |
| } | |
| else if ("NMTOKENS".equals(type)) | |
| { | |
| StringTokenizer st = new StringTokenizer(value); | |
| while (st.hasMoreTokens()) | |
| { | |
| String token = st.nextToken(); | |
| if (!isNmtoken(token, false)) | |
| error("default value must match Nmtoken production", | |
| token); | |
| } | |
| } | |
| } | |
| } | |
| // Register attribute def | |
| AttributeDecl attribute = | |
| new AttributeDecl(type, value, valueType, enumeration, values, | |
| inputStack.size() != 1); | |
| doctype.addAttributeDecl(elementName, name, attribute); | |
| } | |
| /** | |
| * Parse the EntityDecl production. | |
| */ | |
| private void readEntityDecl(boolean inExternalSubset) | |
| throws IOException, XMLStreamException | |
| { | |
| int flags = 0; | |
| // Check if parameter entity | |
| boolean peFlag = false; | |
| expandPE = false; | |
| requireWhitespace(); | |
| if (tryRead('%')) | |
| { | |
| peFlag = true; | |
| requireWhitespace(); | |
| } | |
| expandPE = true; | |
| // Read entity name | |
| String name = readNmtoken(true); | |
| if (name.indexOf(':') != -1) | |
| error("illegal character ':' in entity name", name); | |
| if (peFlag) | |
| name = "%" + name; | |
| requireWhitespace(); | |
| mark(1); | |
| int c = readCh(); | |
| reset(); | |
| if (c == 0x22 || c == 0x27) // " | ' | |
| { | |
| // Internal entity replacement text | |
| String value = readLiteral(flags | LIT_DISABLE_EREF, true); | |
| int ai = value.indexOf('&'); | |
| while (ai != -1) | |
| { | |
| int sci = value.indexOf(';', ai); | |
| if (sci == -1) | |
| error("malformed reference in entity value", value); | |
| String ref = value.substring(ai + 1, sci); | |
| int[] cp = UnicodeReader.toCodePointArray(ref); | |
| if (cp.length == 0) | |
| error("malformed reference in entity value", value); | |
| if (cp[0] == 0x23) // # | |
| { | |
| if (cp.length == 1) | |
| error("malformed reference in entity value", value); | |
| if (cp[1] == 0x78) // 'x' | |
| { | |
| if (cp.length == 2) | |
| error("malformed reference in entity value", value); | |
| for (int i = 2; i < cp.length; i++) | |
| { | |
| int x = cp[i]; | |
| if (x < 0x30 || | |
| (x > 0x39 && x < 0x41) || | |
| (x > 0x46 && x < 0x61) || | |
| x > 0x66) | |
| error("malformed character reference in entity value", | |
| value); | |
| } | |
| } | |
| else | |
| { | |
| for (int i = 1; i < cp.length; i++) | |
| { | |
| int x = cp[i]; | |
| if (x < 0x30 || x > 0x39) | |
| error("malformed character reference in entity value", | |
| value); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| if (!isNameStartCharacter(cp[0], input.xml11)) | |
| error("malformed reference in entity value", value); | |
| for (int i = 1; i < cp.length; i++) | |
| { | |
| if (!isNameCharacter(cp[i], input.xml11)) | |
| error("malformed reference in entity value", value); | |
| } | |
| } | |
| ai = value.indexOf('&', sci); | |
| } | |
| doctype.addEntityDecl(name, value, inExternalSubset); | |
| } | |
| else | |
| { | |
| ExternalIds ids = readExternalIds(false, false); | |
| // Check for NDATA | |
| boolean white = tryWhitespace(); | |
| if (!peFlag && tryRead("NDATA")) | |
| { | |
| if (!white) | |
| error("whitespace required before NDATA"); | |
| requireWhitespace(); | |
| ids.notationName = readNmtoken(true); | |
| } | |
| doctype.addEntityDecl(name, ids, inExternalSubset); | |
| } | |
| // finish | |
| skipWhitespace(); | |
| require('>'); | |
| } | |
| /** | |
| * Parse the NotationDecl production. | |
| */ | |
| private void readNotationDecl(boolean inExternalSubset) | |
| throws IOException, XMLStreamException | |
| { | |
| requireWhitespace(); | |
| String notationName = readNmtoken(true); | |
| if (notationName.indexOf(':') != -1) | |
| error("illegal character ':' in notation name", notationName); | |
| if (validating) | |
| { | |
| // VC: Unique Notation Name | |
| ExternalIds notation = doctype.getNotation(notationName); | |
| if (notation != null) | |
| error("duplicate notation name", notationName); | |
| } | |
| requireWhitespace(); | |
| ExternalIds ids = readExternalIds(true, false); | |
| ids.notationName = notationName; | |
| doctype.addNotationDecl(notationName, ids, inExternalSubset); | |
| skipWhitespace(); | |
| require('>'); | |
| } | |
| /** | |
| * Returns a tuple {publicId, systemId}. | |
| */ | |
| private ExternalIds readExternalIds(boolean inNotation, boolean isSubset) | |
| throws IOException, XMLStreamException | |
| { | |
| int c; | |
| int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; | |
| ExternalIds ids = new ExternalIds(); | |
| if (tryRead("PUBLIC")) | |
| { | |
| requireWhitespace(); | |
| ids.publicId = readLiteral(LIT_NORMALIZE | LIT_PUBID | flags, false); | |
| if (inNotation) | |
| { | |
| skipWhitespace(); | |
| mark(1); | |
| c = readCh(); | |
| reset(); | |
| if (c == 0x22 || c == 0x27) // " | ' | |
| { | |
| String href = readLiteral(flags, false); | |
| ids.systemId = absolutize(input.systemId, href); | |
| } | |
| } | |
| else | |
| { | |
| requireWhitespace(); | |
| String href = readLiteral(flags, false); | |
| ids.systemId = absolutize(input.systemId, href); | |
| } | |
| // Check valid URI characters | |
| for (int i = 0; i < ids.publicId.length(); i++) | |
| { | |
| char d = ids.publicId.charAt(i); | |
| if (d >= 'a' && d <= 'z') | |
| continue; | |
| if (d >= 'A' && d <= 'Z') | |
| continue; | |
| if (" \r\n0123456789-' ()+,./:=?;!*#@$_%".indexOf(d) != -1) | |
| continue; | |
| error("illegal PUBLIC id character", | |
| "U+" + Integer.toHexString(d)); | |
| } | |
| } | |
| else if (tryRead("SYSTEM")) | |
| { | |
| requireWhitespace(); | |
| String href = readLiteral(flags, false); | |
| ids.systemId = absolutize(input.systemId, href); | |
| } | |
| else if (!isSubset) | |
| { | |
| error("missing SYSTEM or PUBLIC keyword"); | |
| } | |
| if (ids.systemId != null && !inNotation) | |
| { | |
| if (ids.systemId.indexOf('#') != -1) | |
| error("SYSTEM id has a URI fragment", ids.systemId); | |
| } | |
| return ids; | |
| } | |
| /** | |
| * Parse the start of an element. | |
| * @return the state of the parser afterwards (EMPTY_ELEMENT or CONTENT) | |
| */ | |
| private int readStartElement() | |
| throws IOException, XMLStreamException | |
| { | |
| // Read element name | |
| String elementName = readNmtoken(true); | |
| attrs.clear(); | |
| // Push namespace context | |
| if (namespaceAware) | |
| { | |
| if (elementName.charAt(0) == ':' || | |
| elementName.charAt(elementName.length() - 1) == ':') | |
| error("not a QName", elementName); | |
| namespaces.addFirst(new LinkedHashMap()); | |
| } | |
| // Read element content | |
| boolean white = tryWhitespace(); | |
| mark(1); | |
| int c = readCh(); | |
| while (c != 0x2f && c != 0x3e) // '/' | '>' | |
| { | |
| // Read attribute | |
| reset(); | |
| if (!white) | |
| error("need whitespace between attributes"); | |
| readAttribute(elementName); | |
| white = tryWhitespace(); | |
| mark(1); | |
| c = readCh(); | |
| } | |
| // supply defaulted attributes | |
| if (doctype != null) | |
| { | |
| for (Iterator i = doctype.attlistIterator(elementName); i.hasNext(); ) | |
| { | |
| Map.Entry entry = (Map.Entry) i.next(); | |
| String attName = (String) entry.getKey(); | |
| AttributeDecl decl = (AttributeDecl) entry.getValue(); | |
| if (validating) | |
| { | |
| switch (decl.valueType) | |
| { | |
| case ATTRIBUTE_DEFAULT_REQUIRED: | |
| // VC: Required Attribute | |
| if (decl.value == null && !attributeSpecified(attName)) | |
| error("value for " + attName + " attribute is required"); | |
| break; | |
| case ATTRIBUTE_DEFAULT_FIXED: | |
| // VC: Fixed Attribute Default | |
| for (Iterator j = attrs.iterator(); j.hasNext(); ) | |
| { | |
| Attribute a = (Attribute) j.next(); | |
| if (attName.equals(a.name) && | |
| !decl.value.equals(a.value)) | |
| error("value for " + attName + " attribute must be " + | |
| decl.value); | |
| } | |
| break; | |
| } | |
| } | |
| if (namespaceAware && attName.equals("xmlns")) | |
| { | |
| LinkedHashMap ctx = | |
| (LinkedHashMap) namespaces.getFirst(); | |
| if (ctx.containsKey(XMLConstants.DEFAULT_NS_PREFIX)) | |
| continue; // namespace was specified | |
| } | |
| else if (namespaceAware && attName.startsWith("xmlns:")) | |
| { | |
| LinkedHashMap ctx = | |
| (LinkedHashMap) namespaces.getFirst(); | |
| if (ctx.containsKey(attName.substring(6))) | |
| continue; // namespace was specified | |
| } | |
| else if (attributeSpecified(attName)) | |
| continue; | |
| if (decl.value == null) | |
| continue; | |
| // VC: Standalone Document Declaration | |
| if (validating && decl.external && xmlStandalone == Boolean.TRUE) | |
| error("standalone must be 'no' if attributes inherit values " + | |
| "from externally declared markup declarations"); | |
| Attribute attr = | |
| new Attribute(attName, decl.type, false, decl.value); | |
| if (namespaceAware) | |
| { | |
| if (!addNamespace(attr)) | |
| attrs.add(attr); | |
| } | |
| else | |
| attrs.add(attr); | |
| } | |
| } | |
| if (baseAware) | |
| { | |
| String uri = getAttributeValue(XMLConstants.XML_NS_URI, "base"); | |
| String base = getXMLBase(); | |
| bases.addFirst(absolutize(base, uri)); | |
| } | |
| if (namespaceAware) | |
| { | |
| // check prefix bindings | |
| int ci = elementName.indexOf(':'); | |
| if (ci != -1) | |
| { | |
| String prefix = elementName.substring(0, ci); | |
| String uri = getNamespaceURI(prefix); | |
| if (uri == null) | |
| error("unbound element prefix", prefix); | |
| else if (input.xml11 && "".equals(uri)) | |
| error("XML 1.1 unbound element prefix", prefix); | |
| } | |
| for (Iterator i = attrs.iterator(); i.hasNext(); ) | |
| { | |
| Attribute attr = (Attribute) i.next(); | |
| if (attr.prefix != null && | |
| !XMLConstants.XMLNS_ATTRIBUTE.equals(attr.prefix)) | |
| { | |
| String uri = getNamespaceURI(attr.prefix); | |
| if (uri == null) | |
| error("unbound attribute prefix", attr.prefix); | |
| else if (input.xml11 && "".equals(uri)) | |
| error("XML 1.1 unbound attribute prefix", attr.prefix); | |
| } | |
| } | |
| } | |
| if (validating && doctype != null) | |
| { | |
| validateStartElement(elementName); | |
| currentContentModel = doctype.getElementModel(elementName); | |
| if (currentContentModel == null) | |
| error("no element declaration", elementName); | |
| validationStack.add(new LinkedList()); | |
| } | |
| // make element name available for read | |
| buf.setLength(0); | |
| buf.append(elementName); | |
| // push element onto stack | |
| stack.addLast(elementName); | |
| switch (c) | |
| { | |
| case 0x3e: // '>' | |
| return CONTENT; | |
| case 0x2f: // '/' | |
| require('>'); | |
| return EMPTY_ELEMENT; | |
| } | |
| return -1; // to satisfy compiler | |
| } | |
| /** | |
| * Indicates whether the specified attribute name was specified for the | |
| * current element. | |
| */ | |
| private boolean attributeSpecified(String attName) | |
| { | |
| for (Iterator j = attrs.iterator(); j.hasNext(); ) | |
| { | |
| Attribute a = (Attribute) j.next(); | |
| if (attName.equals(a.name)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Parse an attribute. | |
| */ | |
| private void readAttribute(String elementName) | |
| throws IOException, XMLStreamException | |
| { | |
| // Read attribute name | |
| String attributeName = readNmtoken(true); | |
| String type = getAttributeType(elementName, attributeName); | |
| readEq(); | |
| // Read literal | |
| final int flags = LIT_ATTRIBUTE | LIT_ENTITY_REF; | |
| String value = (type == null || "CDATA".equals(type)) ? | |
| readLiteral(flags, false) : readLiteral(flags | LIT_NORMALIZE, false); | |
| // add attribute event | |
| Attribute attr = this.new Attribute(attributeName, type, true, value); | |
| if (namespaceAware) | |
| { | |
| if (attributeName.charAt(0) == ':' || | |
| attributeName.charAt(attributeName.length() - 1) == ':') | |
| error("not a QName", attributeName); | |
| else if (attributeName.equals("xmlns")) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) namespaces.getFirst(); | |
| if (ctx.containsKey(XMLConstants.DEFAULT_NS_PREFIX)) | |
| error("duplicate default namespace"); | |
| } | |
| else if (attributeName.startsWith("xmlns:")) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) namespaces.getFirst(); | |
| if (ctx.containsKey(attributeName.substring(6))) | |
| error("duplicate namespace", attributeName.substring(6)); | |
| } | |
| else if (attrs.contains(attr)) | |
| error("duplicate attribute", attributeName); | |
| } | |
| else if (attrs.contains(attr)) | |
| error("duplicate attribute", attributeName); | |
| if (validating && doctype != null) | |
| { | |
| // VC: Attribute Value Type | |
| AttributeDecl decl = | |
| doctype.getAttributeDecl(elementName, attributeName); | |
| if (decl == null) | |
| error("attribute must be declared", attributeName); | |
| if ("ENUMERATION".equals(decl.type)) | |
| { | |
| // VC: Enumeration | |
| if (!decl.values.contains(value)) | |
| error("value does not match enumeration " + decl.enumeration, | |
| value); | |
| } | |
| else if ("ID".equals(decl.type)) | |
| { | |
| // VC: ID | |
| if (!isNmtoken(value, true)) | |
| error("ID values must match the Name production"); | |
| if (ids.contains(value)) | |
| error("Duplicate ID", value); | |
| ids.add(value); | |
| } | |
| else if ("IDREF".equals(decl.type) || "IDREFS".equals(decl.type)) | |
| { | |
| StringTokenizer st = new StringTokenizer(value); | |
| while (st.hasMoreTokens()) | |
| { | |
| String token = st.nextToken(); | |
| // VC: IDREF | |
| if (!isNmtoken(token, true)) | |
| error("IDREF values must match the Name production"); | |
| idrefs.add(token); | |
| } | |
| } | |
| else if ("NMTOKEN".equals(decl.type) || "NMTOKENS".equals(decl.type)) | |
| { | |
| StringTokenizer st = new StringTokenizer(value); | |
| while (st.hasMoreTokens()) | |
| { | |
| String token = st.nextToken(); | |
| // VC: Name Token | |
| if (!isNmtoken(token, false)) | |
| error("NMTOKEN values must match the Nmtoken production"); | |
| } | |
| } | |
| else if ("ENTITY".equals(decl.type)) | |
| { | |
| // VC: Entity Name | |
| if (!isNmtoken(value, true)) | |
| error("ENTITY values must match the Name production"); | |
| Object entity = doctype.getEntity(value); | |
| if (entity == null || !(entity instanceof ExternalIds) || | |
| ((ExternalIds) entity).notationName == null) | |
| error("ENTITY values must match the name of an unparsed " + | |
| "entity declared in the DTD"); | |
| } | |
| else if ("NOTATION".equals(decl.type)) | |
| { | |
| if (!decl.values.contains(value)) | |
| error("NOTATION values must match a declared notation name", | |
| value); | |
| // VC: Notation Attributes | |
| ExternalIds notation = doctype.getNotation(value); | |
| if (notation == null) | |
| error("NOTATION values must match the name of a notation " + | |
| "declared in the DTD", value); | |
| } | |
| } | |
| if (namespaceAware) | |
| { | |
| if (!addNamespace(attr)) | |
| attrs.add(attr); | |
| } | |
| else | |
| attrs.add(attr); | |
| } | |
| /** | |
| * Determines whether the specified attribute is a namespace declaration, | |
| * and adds it to the current namespace context if so. Returns false if | |
| * the attribute is an ordinary attribute. | |
| */ | |
| private boolean addNamespace(Attribute attr) | |
| throws XMLStreamException | |
| { | |
| if ("xmlns".equals(attr.name)) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) namespaces.getFirst(); | |
| if (ctx.get(XMLConstants.DEFAULT_NS_PREFIX) != null) | |
| error("Duplicate default namespace declaration"); | |
| if (XMLConstants.XML_NS_URI.equals(attr.value)) | |
| error("can't bind XML namespace"); | |
| ctx.put(XMLConstants.DEFAULT_NS_PREFIX, attr.value); | |
| return true; | |
| } | |
| else if ("xmlns".equals(attr.prefix)) | |
| { | |
| LinkedHashMap ctx = (LinkedHashMap) namespaces.getFirst(); | |
| if (ctx.get(attr.localName) != null) | |
| error("Duplicate namespace declaration for prefix", | |
| attr.localName); | |
| if (XMLConstants.XML_NS_PREFIX.equals(attr.localName)) | |
| { | |
| if (!XMLConstants.XML_NS_URI.equals(attr.value)) | |
| error("can't redeclare xml prefix"); | |
| else | |
| return false; // treat as attribute | |
| } | |
| if (XMLConstants.XML_NS_URI.equals(attr.value)) | |
| error("can't bind non-xml prefix to XML namespace"); | |
| if (XMLConstants.XMLNS_ATTRIBUTE.equals(attr.localName)) | |
| error("can't redeclare xmlns prefix"); | |
| if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attr.value)) | |
| error("can't bind non-xmlns prefix to XML Namespace namespace"); | |
| if ("".equals(attr.value) && !input.xml11) | |
| error("illegal use of 1.1-style prefix unbinding in 1.0 document"); | |
| ctx.put(attr.localName, attr.value); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Parse a closing tag. | |
| */ | |
| private void readEndElement() | |
| throws IOException, XMLStreamException | |
| { | |
| // pop element off stack | |
| String expected = (String) stack.removeLast(); | |
| require(expected); | |
| skipWhitespace(); | |
| require('>'); | |
| // Make element name available | |
| buf.setLength(0); | |
| buf.append(expected); | |
| if (validating && doctype != null) | |
| endElementValidationHook(); | |
| } | |
| /** | |
| * Validate the end of an element. | |
| * Called on an end-element or empty element if validating. | |
| */ | |
| private void endElementValidationHook() | |
| throws XMLStreamException | |
| { | |
| validateEndElement(); | |
| validationStack.removeLast(); | |
| if (stack.isEmpty()) | |
| currentContentModel = null; | |
| else | |
| { | |
| String parent = (String) stack.getLast(); | |
| currentContentModel = doctype.getElementModel(parent); | |
| } | |
| } | |
| /** | |
| * Parse a comment. | |
| */ | |
| private void readComment(boolean inDTD) | |
| throws IOException, XMLStreamException | |
| { | |
| boolean saved = expandPE; | |
| expandPE = false; | |
| buf.setLength(0); | |
| readUntil(TEST_END_COMMENT); | |
| require('>'); | |
| expandPE = saved; | |
| if (inDTD) | |
| doctype.addComment(buf.toString()); | |
| } | |
| /** | |
| * Parse a processing instruction. | |
| */ | |
| private void readPI(boolean inDTD) | |
| throws IOException, XMLStreamException | |
| { | |
| boolean saved = expandPE; | |
| expandPE = false; | |
| piTarget = readNmtoken(true); | |
| if (piTarget.indexOf(':') != -1) | |
| error("illegal character in PI target", new Character(':')); | |
| if ("xml".equalsIgnoreCase(piTarget)) | |
| error("illegal PI target", piTarget); | |
| if (tryRead(TEST_END_PI)) | |
| piData = null; | |
| else | |
| { | |
| if (!tryWhitespace()) | |
| error("whitespace required between PI target and data"); | |
| buf.setLength(0); | |
| readUntil(TEST_END_PI); | |
| piData = buf.toString(); | |
| } | |
| expandPE = saved; | |
| if (inDTD) | |
| doctype.addPI(piTarget, piData); | |
| } | |
| /** | |
| * Parse an entity reference. | |
| */ | |
| private void readReference() | |
| throws IOException, XMLStreamException | |
| { | |
| buf.setLength(0); | |
| String entityName = readNmtoken(true); | |
| require(';'); | |
| buf.setLength(0); | |
| buf.append(entityName); | |
| } | |
| /** | |
| * Read an CDATA section. | |
| */ | |
| private void readCDSect() | |
| throws IOException, XMLStreamException | |
| { | |
| buf.setLength(0); | |
| readUntil(TEST_END_CDATA); | |
| } | |
| /** | |
| * Read character data. | |
| * @return the type of text read (CHARACTERS or SPACE) | |
| */ | |
| private int readCharData(String prefix) | |
| throws IOException, XMLStreamException | |
| { | |
| boolean white = true; | |
| buf.setLength(0); | |
| if (prefix != null) | |
| buf.append(prefix); | |
| boolean done = false; | |
| boolean entities = false; | |
| while (!done) | |
| { | |
| // Block read | |
| mark(tmpBuf.length); | |
| int len = read(tmpBuf, 0, tmpBuf.length); | |
| if (len == -1) | |
| { | |
| if (inputStack.size() > 1) | |
| { | |
| popInput(); | |
| // report end-entity | |
| done = true; | |
| } | |
| else | |
| throw new EOFException(); | |
| } | |
| for (int i = 0; i < len && !done; i++) | |
| { | |
| int c = tmpBuf[i]; | |
| switch (c) | |
| { | |
| case 0x20: | |
| case 0x09: | |
| case 0x0a: | |
| case 0x0d: | |
| buf.append(Character.toChars(c)); | |
| break; // whitespace | |
| case 0x26: // '&' | |
| reset(); | |
| read(tmpBuf, 0, i); | |
| // character reference? | |
| mark(3); | |
| c = readCh(); // & | |
| c = readCh(); | |
| if (c == 0x23) // '#' | |
| { | |
| mark(1); | |
| c = readCh(); | |
| boolean hex = (c == 0x78); // 'x' | |
| if (!hex) | |
| reset(); | |
| char[] ch = readCharacterRef(hex ? 16 : 10); | |
| buf.append(ch, 0, ch.length); | |
| for (int j = 0; j < ch.length; j++) | |
| { | |
| switch (ch[j]) | |
| { | |
| case 0x20: | |
| case 0x09: | |
| case 0x0a: | |
| case 0x0d: | |
| break; // whitespace | |
| default: | |
| white = false; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // entity reference | |
| reset(); | |
| c = readCh(); // & | |
| String entityName = readNmtoken(true); | |
| require(';'); | |
| String text = | |
| (String) PREDEFINED_ENTITIES.get(entityName); | |
| if (text != null) | |
| buf.append(text); | |
| else | |
| { | |
| pushInput("", "&" + entityName + ";", false, false); | |
| done = true; | |
| break; | |
| } | |
| } | |
| // continue processing | |
| i = -1; | |
| mark(tmpBuf.length); | |
| len = read(tmpBuf, 0, tmpBuf.length); | |
| if (len == -1) | |
| { | |
| if (inputStack.size() > 1) | |
| { | |
| popInput(); | |
| done = true; | |
| } | |
| else | |
| throw new EOFException(); | |
| } | |
| entities = true; | |
| break; // end of text sequence | |
| case 0x3e: // '>' | |
| int l = buf.length(); | |
| if (l > 1 && | |
| buf.charAt(l - 1) == ']' && | |
| buf.charAt(l - 2) == ']') | |
| error("Character data may not contain unescaped ']]>'"); | |
| buf.append(Character.toChars(c)); | |
| break; | |
| case 0x3c: // '<' | |
| reset(); | |
| // read i characters | |
| int count = 0, remaining = i; | |
| do | |
| { | |
| int r = read(tmpBuf, 0, remaining); | |
| count += r; | |
| remaining -= r; | |
| } | |
| while (count < i); | |
| i = len; | |
| if (coalescing && tryRead(TEST_CDATA)) | |
| readUntil(TEST_END_CDATA); // read CDATA section into buf | |
| else | |
| done = true; // end of text sequence | |
| break; | |
| default: | |
| if (input.xml11) | |
| { | |
| if (!isXML11Char(c) || isXML11RestrictedChar(c)) | |
| error("illegal XML 1.1 character", | |
| "U+" + Integer.toHexString(c)); | |
| } | |
| else if (!isChar(c)) | |
| error("illegal XML character", | |
| "U+" + Integer.toHexString(c)); | |
| white = false; | |
| buf.append(Character.toChars(c)); | |
| } | |
| } | |
| // if text buffer >= 2MB, return it as a chunk | |
| // to avoid excessive memory use | |
| if (buf.length() >= 2097152) | |
| done = true; | |
| } | |
| if (entities) | |
| normalizeCRLF(buf); | |
| return white ? XMLStreamConstants.SPACE : XMLStreamConstants.CHARACTERS; | |
| } | |
| /** | |
| * Expands the specified entity. | |
| */ | |
| private void expandEntity(String name, boolean inAttr, boolean normalize) | |
| throws IOException, XMLStreamException | |
| { | |
| if (doctype != null) | |
| { | |
| Object value = doctype.getEntity(name); | |
| if (value != null) | |
| { | |
| if (xmlStandalone == Boolean.TRUE) | |
| { | |
| // VC: Standalone Document Declaration | |
| if (doctype.isEntityExternal(name)) | |
| error("reference to external entity in standalone document"); | |
| else if (value instanceof ExternalIds) | |
| { | |
| ExternalIds ids = (ExternalIds) value; | |
| if (ids.notationName != null && | |
| doctype.isNotationExternal(ids.notationName)) | |
| error("reference to external notation in " + | |
| "standalone document"); | |
| } | |
| } | |
| if (value instanceof String) | |
| { | |
| String text = (String) value; | |
| if (inAttr && text.indexOf('<') != -1) | |
| error("< in attribute value"); | |
| pushInput(name, text, !inAttr, normalize); | |
| } | |
| else if (inAttr) | |
| error("reference to external entity in attribute value", name); | |
| else | |
| pushInput(name, (ExternalIds) value, !inAttr, normalize); | |
| return; | |
| } | |
| } | |
| error("reference to undeclared entity", name); | |
| } | |
| /** | |
| * Indicates whether the specified entity is unparsed. | |
| */ | |
| private boolean isUnparsedEntity(String name) | |
| { | |
| if (doctype != null) | |
| { | |
| Object value = doctype.getEntity(name); | |
| if (value != null && value instanceof ExternalIds) | |
| return ((ExternalIds) value).notationName != null; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Read an equals sign. | |
| */ | |
| private void readEq() | |
| throws IOException, XMLStreamException | |
| { | |
| skipWhitespace(); | |
| require('='); | |
| skipWhitespace(); | |
| } | |
| /** | |
| * Character read for reading literals. | |
| * @param recognizePEs whether to recognize parameter-entity references | |
| */ | |
| private int literalReadCh(boolean recognizePEs) | |
| throws IOException, XMLStreamException | |
| { | |
| int c = recognizePEs ? readCh() : read(); | |
| while (c == -1) | |
| { | |
| if (inputStack.size() > 1) | |
| { | |
| inputStack.removeLast(); | |
| input = (Input) inputStack.getLast(); | |
| // Don't issue end-entity | |
| c = recognizePEs ? readCh() : read(); | |
| } | |
| else | |
| throw new EOFException(); | |
| } | |
| return c; | |
| } | |
| /** | |
| * Read a string literal. | |
| */ | |
| private String readLiteral(int flags, boolean recognizePEs) | |
| throws IOException, XMLStreamException | |
| { | |
| boolean saved = expandPE; | |
| int delim = readCh(); | |
| if (delim != 0x27 && delim != 0x22) | |
| error("expected '\"' or \"'\"", "U+" + Integer.toHexString(delim)); | |
| literalBuf.setLength(0); | |
| if ((flags & LIT_DISABLE_PE) != 0) | |
| expandPE = false; | |
| boolean entities = false; | |
| int inputStackSize = inputStack.size(); | |
| do | |
| { | |
| int c = literalReadCh(recognizePEs); | |
| if (c == delim && inputStackSize == inputStack.size()) | |
| break; | |
| switch (c) | |
| { | |
| case 0x0a: | |
| case 0x0d: | |
| if ((flags & (LIT_ATTRIBUTE | LIT_PUBID)) != 0) | |
| c = 0x20; // normalize to space | |
| break; | |
| case 0x09: | |
| if ((flags & LIT_ATTRIBUTE) != 0) | |
| c = 0x20; // normalize to space | |
| break; | |
| case 0x26: // '&' | |
| mark(2); | |
| c = readCh(); | |
| if (c == 0x23) // '#' | |
| { | |
| if ((flags & LIT_DISABLE_CREF) != 0) | |
| { | |
| reset(); | |
| c = 0x26; // '&' | |
| } | |
| else | |
| { | |
| mark(1); | |
| c = readCh(); | |
| boolean hex = (c == 0x78); // 'x' | |
| if (!hex) | |
| reset(); | |
| char[] ref = readCharacterRef(hex ? 16 : 10); | |
| for (int i = 0; i < ref.length; i++) | |
| literalBuf.append(ref[i]); | |
| entities = true; | |
| continue; | |
| } | |
| } | |
| else | |
| { | |
| if ((flags & LIT_DISABLE_EREF) != 0) | |
| { | |
| reset(); | |
| c = 0x26; // '&' | |
| } | |
| else | |
| { | |
| reset(); | |
| String entityName = readNmtoken(true); | |
| require(';'); | |
| String text = | |
| (String) PREDEFINED_ENTITIES.get(entityName); | |
| if (text != null) | |
| literalBuf.append(text); | |
| else | |
| expandEntity(entityName, | |
| (flags & LIT_ATTRIBUTE) != 0, | |
| true); | |
| entities = true; | |
| continue; | |
| } | |
| } | |
| break; | |
| case 0x3c: // '<' | |
| if ((flags & LIT_ATTRIBUTE) != 0) | |
| error("attribute values may not contain '<'"); | |
| break; | |
| case -1: | |
| if (inputStack.size() > 1) | |
| { | |
| popInput(); | |
| continue; | |
| } | |
| throw new EOFException(); | |
| default: | |
| if ((c < 0x0020 || c > 0xfffd) || | |
| (c >= 0xd800 && c < 0xdc00) || | |
| (input.xml11 && (c >= 0x007f) && | |
| (c <= 0x009f) && (c != 0x0085))) | |
| error("illegal character", "U+" + Integer.toHexString(c)); | |
| } | |
| literalBuf.append(Character.toChars(c)); | |
| } | |
| while (true); | |
| expandPE = saved; | |
| if (entities) | |
| normalizeCRLF(literalBuf); | |
| if ((flags & LIT_NORMALIZE) > 0) | |
| literalBuf = normalize(literalBuf); | |
| return literalBuf.toString(); | |
| } | |
| /** | |
| * Performs attribute-value normalization of the text buffer. | |
| * This discards leading and trailing whitespace, and replaces sequences | |
| * of whitespace with a single space. | |
| */ | |
| private StringBuffer normalize(StringBuffer buf) | |
| { | |
| StringBuffer acc = new StringBuffer(); | |
| int len = buf.length(); | |
| int avState = 0; | |
| for (int i = 0; i < len; i++) | |
| { | |
| char c = buf.charAt(i); | |
| if (c == ' ') | |
| avState = (avState == 0) ? 0 : 1; | |
| else | |
| { | |
| if (avState == 1) | |
| acc.append(' '); | |
| acc.append(c); | |
| avState = 2; | |
| } | |
| } | |
| return acc; | |
| } | |
| /** | |
| * Replace any CR/LF pairs in the buffer with LF. | |
| * This may be necessary if combinations of CR or LF were declared as | |
| * (character) entity references in the input. | |
| */ | |
| private void normalizeCRLF(StringBuffer buf) | |
| { | |
| int len = buf.length() - 1; | |
| for (int i = 0; i < len; i++) | |
| { | |
| char c = buf.charAt(i); | |
| if (c == '\r' && buf.charAt(i + 1) == '\n') | |
| { | |
| buf.deleteCharAt(i--); | |
| len--; | |
| } | |
| } | |
| } | |
| /** | |
| * Parse and expand a parameter entity reference. | |
| */ | |
| private void expandPEReference() | |
| throws IOException, XMLStreamException | |
| { | |
| String name = readNmtoken(true, new StringBuffer()); | |
| require(';'); | |
| mark(1); // ensure we don't reset to before the semicolon | |
| if (doctype != null) | |
| { | |
| String entityName = "%" + name; | |
| Object entity = doctype.getEntity(entityName); | |
| if (entity != null) | |
| { | |
| if (xmlStandalone == Boolean.TRUE) | |
| { | |
| if (doctype.isEntityExternal(entityName)) | |
| error("reference to external parameter entity in " + | |
| "standalone document"); | |
| } | |
| if (entity instanceof String) | |
| { | |
| pushInput(name, (String) entity, false, input.normalize); | |
| //pushInput(name, " " + (String) entity + " "); | |
| } | |
| else | |
| { | |
| //pushInput("", " "); | |
| pushInput(name, (ExternalIds) entity, false, input.normalize); | |
| //pushInput("", " "); | |
| } | |
| } | |
| else | |
| error("reference to undeclared parameter entity", name); | |
| } | |
| else | |
| error("reference to parameter entity without doctype", name); | |
| } | |
| /** | |
| * Parse the digits in a character reference. | |
| * @param base the base of the digits (10 or 16) | |
| */ | |
| private char[] readCharacterRef(int base) | |
| throws IOException, XMLStreamException | |
| { | |
| StringBuffer b = new StringBuffer(); | |
| for (int c = readCh(); c != 0x3b && c != -1; c = readCh()) | |
| b.append(Character.toChars(c)); | |
| try | |
| { | |
| int ord = Integer.parseInt(b.toString(), base); | |
| if (input.xml11) | |
| { | |
| if (!isXML11Char(ord)) | |
| error("illegal XML 1.1 character reference " + | |
| "U+" + Integer.toHexString(ord)); | |
| } | |
| else | |
| { | |
| if ((ord < 0x20 && !(ord == 0x0a || ord == 0x09 || ord == 0x0d)) | |
| || (ord >= 0xd800 && ord <= 0xdfff) | |
| || ord == 0xfffe || ord == 0xffff | |
| || ord > 0x0010ffff) | |
| error("illegal XML character reference " + | |
| "U+" + Integer.toHexString(ord)); | |
| } | |
| return Character.toChars(ord); | |
| } | |
| catch (NumberFormatException e) | |
| { | |
| error("illegal characters in character reference", b.toString()); | |
| return null; | |
| } | |
| } | |
| /** | |
| * Parses an NMTOKEN or Name production. | |
| * @param isName if a Name, otherwise an NMTOKEN | |
| */ | |
| private String readNmtoken(boolean isName) | |
| throws IOException, XMLStreamException | |
| { | |
| return readNmtoken(isName, nmtokenBuf); | |
| } | |
| /** | |
| * Parses an NMTOKEN or Name production using the specified buffer. | |
| * @param isName if a Name, otherwise an NMTOKEN | |
| * @param buf the character buffer to use | |
| */ | |
| private String readNmtoken(boolean isName, StringBuffer buf) | |
| throws IOException, XMLStreamException | |
| { | |
| buf.setLength(0); | |
| int c = readCh(); | |
| if (isName) | |
| { | |
| if (!isNameStartCharacter(c, input.xml11)) | |
| error("not a name start character", | |
| "U+" + Integer.toHexString(c)); | |
| } | |
| else | |
| { | |
| if (!isNameCharacter(c, input.xml11)) | |
| error("not a name character", | |
| "U+" + Integer.toHexString(c)); | |
| } | |
| buf.append(Character.toChars(c)); | |
| do | |
| { | |
| mark(1); | |
| c = readCh(); | |
| switch (c) | |
| { | |
| case 0x25: // '%' | |
| case 0x3c: // '<' | |
| case 0x3e: // '>' | |
| case 0x26: // '&' | |
| case 0x2c: // ',' | |
| case 0x7c: // '|' | |
| case 0x2a: // '*' | |
| case 0x2b: // '+' | |
| case 0x3f: // '?' | |
| case 0x29: // ')' | |
| case 0x3d: // '=' | |
| case 0x27: // '\'' | |
| case 0x22: // '"' | |
| case 0x5b: // '[' | |
| case 0x20: // ' ' | |
| case 0x09: // '\t' | |
| case 0x0a: // '\n' | |
| case 0x0d: // '\r' | |
| case 0x3b: // ';' | |
| case 0x2f: // '/' | |
| case -1: | |
| reset(); | |
| return intern(buf.toString()); | |
| default: | |
| if (!isNameCharacter(c, input.xml11)) | |
| error("not a name character", | |
| "U+" + Integer.toHexString(c)); | |
| else | |
| buf.append(Character.toChars(c)); | |
| } | |
| } | |
| while (true); | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character is an XML 1.1 Char. | |
| */ | |
| public static boolean isXML11Char(int c) | |
| { | |
| return ((c >= 0x0001 && c <= 0xD7FF) || | |
| (c >= 0xE000 && c < 0xFFFE) || | |
| (c >= 0x10000 && c <= 0x10FFFF)); | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character is an XML 1.1 | |
| * RestrictedChar. | |
| */ | |
| public static boolean isXML11RestrictedChar(int c) | |
| { | |
| return ((c >= 0x0001 && c <= 0x0008) || | |
| (c >= 0x000B && c <= 0x000C) || | |
| (c >= 0x000E && c <= 0x001F) || | |
| (c >= 0x007F && c <= 0x0084) || | |
| (c >= 0x0086 && c <= 0x009F)); | |
| } | |
| /** | |
| * Indicates whether the specified text matches the Name or Nmtoken | |
| * production. | |
| */ | |
| private boolean isNmtoken(String text, boolean isName) | |
| { | |
| try | |
| { | |
| int[] cp = UnicodeReader.toCodePointArray(text); | |
| if (cp.length == 0) | |
| return false; | |
| if (isName) | |
| { | |
| if (!isNameStartCharacter(cp[0], input.xml11)) | |
| return false; | |
| } | |
| else | |
| { | |
| if (!isNameCharacter(cp[0], input.xml11)) | |
| return false; | |
| } | |
| for (int i = 1; i < cp.length; i++) | |
| { | |
| if (!isNameCharacter(cp[i], input.xml11)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| catch (IOException e) | |
| { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character is a Name start | |
| * character. | |
| */ | |
| public static boolean isNameStartCharacter(int c, boolean xml11) | |
| { | |
| if (xml11) | |
| return ((c >= 0x0041 && c <= 0x005a) || | |
| (c >= 0x0061 && c <= 0x007a) || | |
| c == 0x3a | | |
| c == 0x5f | | |
| (c >= 0xC0 && c <= 0xD6) || | |
| (c >= 0xD8 && c <= 0xF6) || | |
| (c >= 0xF8 && c <= 0x2FF) || | |
| (c >= 0x370 && c <= 0x37D) || | |
| (c >= 0x37F && c <= 0x1FFF) || | |
| (c >= 0x200C && c <= 0x200D) || | |
| (c >= 0x2070 && c <= 0x218F) || | |
| (c >= 0x2C00 && c <= 0x2FEF) || | |
| (c >= 0x3001 && c <= 0xD7FF) || | |
| (c >= 0xF900 && c <= 0xFDCF) || | |
| (c >= 0xFDF0 && c <= 0xFFFD) || | |
| (c >= 0x10000 && c <= 0xEFFFF)); | |
| else | |
| return (c == 0x5f || c == 0x3a || isLetter(c)); | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character is a Name non-initial | |
| * character. | |
| */ | |
| public static boolean isNameCharacter(int c, boolean xml11) | |
| { | |
| if (xml11) | |
| return ((c >= 0x0041 && c <= 0x005a) || | |
| (c >= 0x0061 && c <= 0x007a) || | |
| (c >= 0x0030 && c <= 0x0039) || | |
| c == 0x3a | | |
| c == 0x5f | | |
| c == 0x2d | | |
| c == 0x2e | | |
| c == 0xB7 | | |
| (c >= 0xC0 && c <= 0xD6) || | |
| (c >= 0xD8 && c <= 0xF6) || | |
| (c >= 0xF8 && c <= 0x2FF) || | |
| (c >= 0x300 && c <= 0x37D) || | |
| (c >= 0x37F && c <= 0x1FFF) || | |
| (c >= 0x200C && c <= 0x200D) || | |
| (c >= 0x203F && c <= 0x2040) || | |
| (c >= 0x2070 && c <= 0x218F) || | |
| (c >= 0x2C00 && c <= 0x2FEF) || | |
| (c >= 0x3001 && c <= 0xD7FF) || | |
| (c >= 0xF900 && c <= 0xFDCF) || | |
| (c >= 0xFDF0 && c <= 0xFFFD) || | |
| (c >= 0x10000 && c <= 0xEFFFF)); | |
| else | |
| return (c == 0x2e || c == 0x2d || c == 0x5f || c == 0x3a || | |
| isLetter(c) || isDigit(c) || | |
| isCombiningChar(c) || isExtender(c)); | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character matches the Letter | |
| * production. | |
| */ | |
| public static boolean isLetter(int c) | |
| { | |
| if ((c >= 0x0041 && c <= 0x005A) || | |
| (c >= 0x0061 && c <= 0x007A) || | |
| (c >= 0x00C0 && c <= 0x00D6) || | |
| (c >= 0x00D8 && c <= 0x00F6) || | |
| (c >= 0x00F8 && c <= 0x00FF) || | |
| (c >= 0x0100 && c <= 0x0131) || | |
| (c >= 0x0134 && c <= 0x013E) || | |
| (c >= 0x0141 && c <= 0x0148) || | |
| (c >= 0x014A && c <= 0x017E) || | |
| (c >= 0x0180 && c <= 0x01C3) || | |
| (c >= 0x01CD && c <= 0x01F0) || | |
| (c >= 0x01F4 && c <= 0x01F5) || | |
| (c >= 0x01FA && c <= 0x0217) || | |
| (c >= 0x0250 && c <= 0x02A8) || | |
| (c >= 0x02BB && c <= 0x02C1) || | |
| c == 0x0386 || | |
| (c >= 0x0388 && c <= 0x038A) || | |
| c == 0x038C || | |
| (c >= 0x038E && c <= 0x03A1) || | |
| (c >= 0x03A3 && c <= 0x03CE) || | |
| (c >= 0x03D0 && c <= 0x03D6) || | |
| c == 0x03DA || | |
| c == 0x03DC || | |
| c == 0x03DE || | |
| c == 0x03E0 || | |
| (c >= 0x03E2 && c <= 0x03F3) || | |
| (c >= 0x0401 && c <= 0x040C) || | |
| (c >= 0x040E && c <= 0x044F) || | |
| (c >= 0x0451 && c <= 0x045C) || | |
| (c >= 0x045E && c <= 0x0481) || | |
| (c >= 0x0490 && c <= 0x04C4) || | |
| (c >= 0x04C7 && c <= 0x04C8) || | |
| (c >= 0x04CB && c <= 0x04CC) || | |
| (c >= 0x04D0 && c <= 0x04EB) || | |
| (c >= 0x04EE && c <= 0x04F5) || | |
| (c >= 0x04F8 && c <= 0x04F9) || | |
| (c >= 0x0531 && c <= 0x0556) || | |
| c == 0x0559 || | |
| (c >= 0x0561 && c <= 0x0586) || | |
| (c >= 0x05D0 && c <= 0x05EA) || | |
| (c >= 0x05F0 && c <= 0x05F2) || | |
| (c >= 0x0621 && c <= 0x063A) || | |
| (c >= 0x0641 && c <= 0x064A) || | |
| (c >= 0x0671 && c <= 0x06B7) || | |
| (c >= 0x06BA && c <= 0x06BE) || | |
| (c >= 0x06C0 && c <= 0x06CE) || | |
| (c >= 0x06D0 && c <= 0x06D3) || | |
| c == 0x06D5 || | |
| (c >= 0x06E5 && c <= 0x06E6) || | |
| (c >= 0x0905 && c <= 0x0939) || | |
| c == 0x093D || | |
| (c >= 0x0958 && c <= 0x0961) || | |
| (c >= 0x0985 && c <= 0x098C) || | |
| (c >= 0x098F && c <= 0x0990) || | |
| (c >= 0x0993 && c <= 0x09A8) || | |
| (c >= 0x09AA && c <= 0x09B0) || | |
| c == 0x09B2 || | |
| (c >= 0x09B6 && c <= 0x09B9) || | |
| (c >= 0x09DC && c <= 0x09DD) || | |
| (c >= 0x09DF && c <= 0x09E1) || | |
| (c >= 0x09F0 && c <= 0x09F1) || | |
| (c >= 0x0A05 && c <= 0x0A0A) || | |
| (c >= 0x0A0F && c <= 0x0A10) || | |
| (c >= 0x0A13 && c <= 0x0A28) || | |
| (c >= 0x0A2A && c <= 0x0A30) || | |
| (c >= 0x0A32 && c <= 0x0A33) || | |
| (c >= 0x0A35 && c <= 0x0A36) || | |
| (c >= 0x0A38 && c <= 0x0A39) || | |
| (c >= 0x0A59 && c <= 0x0A5C) || | |
| c == 0x0A5E || | |
| (c >= 0x0A72 && c <= 0x0A74) || | |
| (c >= 0x0A85 && c <= 0x0A8B) || | |
| c == 0x0A8D || | |
| (c >= 0x0A8F && c <= 0x0A91) || | |
| (c >= 0x0A93 && c <= 0x0AA8) || | |
| (c >= 0x0AAA && c <= 0x0AB0) || | |
| (c >= 0x0AB2 && c <= 0x0AB3) || | |
| (c >= 0x0AB5 && c <= 0x0AB9) || | |
| c == 0x0ABD || | |
| c == 0x0AE0 || | |
| (c >= 0x0B05 && c <= 0x0B0C) || | |
| (c >= 0x0B0F && c <= 0x0B10) || | |
| (c >= 0x0B13 && c <= 0x0B28) || | |
| (c >= 0x0B2A && c <= 0x0B30) || | |
| (c >= 0x0B32 && c <= 0x0B33) || | |
| (c >= 0x0B36 && c <= 0x0B39) || | |
| c == 0x0B3D || | |
| (c >= 0x0B5C && c <= 0x0B5D) || | |
| (c >= 0x0B5F && c <= 0x0B61) || | |
| (c >= 0x0B85 && c <= 0x0B8A) || | |
| (c >= 0x0B8E && c <= 0x0B90) || | |
| (c >= 0x0B92 && c <= 0x0B95) || | |
| (c >= 0x0B99 && c <= 0x0B9A) || | |
| c == 0x0B9C || | |
| (c >= 0x0B9E && c <= 0x0B9F) || | |
| (c >= 0x0BA3 && c <= 0x0BA4) || | |
| (c >= 0x0BA8 && c <= 0x0BAA) || | |
| (c >= 0x0BAE && c <= 0x0BB5) || | |
| (c >= 0x0BB7 && c <= 0x0BB9) || | |
| (c >= 0x0C05 && c <= 0x0C0C) || | |
| (c >= 0x0C0E && c <= 0x0C10) || | |
| (c >= 0x0C12 && c <= 0x0C28) || | |
| (c >= 0x0C2A && c <= 0x0C33) || | |
| (c >= 0x0C35 && c <= 0x0C39) || | |
| (c >= 0x0C60 && c <= 0x0C61) || | |
| (c >= 0x0C85 && c <= 0x0C8C) || | |
| (c >= 0x0C8E && c <= 0x0C90) || | |
| (c >= 0x0C92 && c <= 0x0CA8) || | |
| (c >= 0x0CAA && c <= 0x0CB3) || | |
| (c >= 0x0CB5 && c <= 0x0CB9) || | |
| c == 0x0CDE || | |
| (c >= 0x0CE0 && c <= 0x0CE1) || | |
| (c >= 0x0D05 && c <= 0x0D0C) || | |
| (c >= 0x0D0E && c <= 0x0D10) || | |
| (c >= 0x0D12 && c <= 0x0D28) || | |
| (c >= 0x0D2A && c <= 0x0D39) || | |
| (c >= 0x0D60 && c <= 0x0D61) || | |
| (c >= 0x0E01 && c <= 0x0E2E) || | |
| c == 0x0E30 || | |
| (c >= 0x0E32 && c <= 0x0E33) || | |
| (c >= 0x0E40 && c <= 0x0E45) || | |
| (c >= 0x0E81 && c <= 0x0E82) || | |
| c == 0x0E84 || | |
| (c >= 0x0E87 && c <= 0x0E88) || | |
| c == 0x0E8A || | |
| c == 0x0E8D || | |
| (c >= 0x0E94 && c <= 0x0E97) || | |
| (c >= 0x0E99 && c <= 0x0E9F) || | |
| (c >= 0x0EA1 && c <= 0x0EA3) || | |
| c == 0x0EA5 || | |
| c == 0x0EA7 || | |
| (c >= 0x0EAA && c <= 0x0EAB) || | |
| (c >= 0x0EAD && c <= 0x0EAE) || | |
| c == 0x0EB0 || | |
| (c >= 0x0EB2 && c <= 0x0EB3) || | |
| c == 0x0EBD || | |
| (c >= 0x0EC0 && c <= 0x0EC4) || | |
| (c >= 0x0F40 && c <= 0x0F47) || | |
| (c >= 0x0F49 && c <= 0x0F69) || | |
| (c >= 0x10A0 && c <= 0x10C5) || | |
| (c >= 0x10D0 && c <= 0x10F6) || | |
| c == 0x1100 || | |
| (c >= 0x1102 && c <= 0x1103) || | |
| (c >= 0x1105 && c <= 0x1107) || | |
| c == 0x1109 || | |
| (c >= 0x110B && c <= 0x110C) || | |
| (c >= 0x110E && c <= 0x1112) || | |
| c == 0x113C || | |
| c == 0x113E || | |
| c == 0x1140 || | |
| c == 0x114C || | |
| c == 0x114E || | |
| c == 0x1150 || | |
| (c >= 0x1154 && c <= 0x1155) || | |
| c == 0x1159 || | |
| (c >= 0x115F && c <= 0x1161) || | |
| c == 0x1163 || | |
| c == 0x1165 || | |
| c == 0x1167 || | |
| c == 0x1169 || | |
| (c >= 0x116D && c <= 0x116E) || | |
| (c >= 0x1172 && c <= 0x1173) || | |
| c == 0x1175 || | |
| c == 0x119E || | |
| c == 0x11A8 || | |
| c == 0x11AB || | |
| (c >= 0x11AE && c <= 0x11AF) || | |
| (c >= 0x11B7 && c <= 0x11B8) || | |
| c == 0x11BA || | |
| (c >= 0x11BC && c <= 0x11C2) || | |
| c == 0x11EB || | |
| c == 0x11F0 || | |
| c == 0x11F9 || | |
| (c >= 0x1E00 && c <= 0x1E9B) || | |
| (c >= 0x1EA0 && c <= 0x1EF9) || | |
| (c >= 0x1F00 && c <= 0x1F15) || | |
| (c >= 0x1F18 && c <= 0x1F1D) || | |
| (c >= 0x1F20 && c <= 0x1F45) || | |
| (c >= 0x1F48 && c <= 0x1F4D) || | |
| (c >= 0x1F50 && c <= 0x1F57) || | |
| c == 0x1F59 || | |
| c == 0x1F5B || | |
| c == 0x1F5D || | |
| (c >= 0x1F5F && c <= 0x1F7D) || | |
| (c >= 0x1F80 && c <= 0x1FB4) || | |
| (c >= 0x1FB6 && c <= 0x1FBC) || | |
| c == 0x1FBE || | |
| (c >= 0x1FC2 && c <= 0x1FC4) || | |
| (c >= 0x1FC6 && c <= 0x1FCC) || | |
| (c >= 0x1FD0 && c <= 0x1FD3) || | |
| (c >= 0x1FD6 && c <= 0x1FDB) || | |
| (c >= 0x1FE0 && c <= 0x1FEC) || | |
| (c >= 0x1FF2 && c <= 0x1FF4) || | |
| (c >= 0x1FF6 && c <= 0x1FFC) || | |
| c == 0x2126 || | |
| (c >= 0x212A && c <= 0x212B) || | |
| c == 0x212E || | |
| (c >= 0x2180 && c <= 0x2182) || | |
| (c >= 0x3041 && c <= 0x3094) || | |
| (c >= 0x30A1 && c <= 0x30FA) || | |
| (c >= 0x3105 && c <= 0x312C) || | |
| (c >= 0xAC00 && c <= 0xD7A3)) | |
| return true; // BaseChar | |
| if ((c >= 0x4e00 && c <= 0x9fa5) || | |
| c == 0x3007 || | |
| (c >= 0x3021 && c <= 0x3029)) | |
| return true; // Ideographic | |
| return false; | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character matches the Digit | |
| * production. | |
| */ | |
| public static boolean isDigit(int c) | |
| { | |
| return ((c >= 0x0030 && c <= 0x0039) || | |
| (c >= 0x0660 && c <= 0x0669) || | |
| (c >= 0x06F0 && c <= 0x06F9) || | |
| (c >= 0x0966 && c <= 0x096F) || | |
| (c >= 0x09E6 && c <= 0x09EF) || | |
| (c >= 0x0A66 && c <= 0x0A6F) || | |
| (c >= 0x0AE6 && c <= 0x0AEF) || | |
| (c >= 0x0B66 && c <= 0x0B6F) || | |
| (c >= 0x0BE7 && c <= 0x0BEF) || | |
| (c >= 0x0C66 && c <= 0x0C6F) || | |
| (c >= 0x0CE6 && c <= 0x0CEF) || | |
| (c >= 0x0D66 && c <= 0x0D6F) || | |
| (c >= 0x0E50 && c <= 0x0E59) || | |
| (c >= 0x0ED0 && c <= 0x0ED9) || | |
| (c >= 0x0F20 && c <= 0x0F29)); | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character matches the | |
| * CombiningChar production. | |
| */ | |
| public static boolean isCombiningChar(int c) | |
| { | |
| return ((c >= 0x0300 && c <= 0x0345) || | |
| (c >= 0x0360 && c <= 0x0361) || | |
| (c >= 0x0483 && c <= 0x0486) || | |
| (c >= 0x0591 && c <= 0x05A1) || | |
| (c >= 0x05A3 && c <= 0x05B9) || | |
| (c >= 0x05BB && c <= 0x05BD) || | |
| c == 0x05BF || | |
| (c >= 0x05C1 && c <= 0x05C2) || | |
| c == 0x05C4 || | |
| (c >= 0x064B && c <= 0x0652) || | |
| c == 0x0670 || | |
| (c >= 0x06D6 && c <= 0x06DC) || | |
| (c >= 0x06DD && c <= 0x06DF) || | |
| (c >= 0x06E0 && c <= 0x06E4) || | |
| (c >= 0x06E7 && c <= 0x06E8) || | |
| (c >= 0x06EA && c <= 0x06ED) || | |
| (c >= 0x0901 && c <= 0x0903) || | |
| c == 0x093C || | |
| (c >= 0x093E && c <= 0x094C) || | |
| c == 0x094D || | |
| (c >= 0x0951 && c <= 0x0954) || | |
| (c >= 0x0962 && c <= 0x0963) || | |
| (c >= 0x0981 && c <= 0x0983) || | |
| c == 0x09BC || | |
| c == 0x09BE || | |
| c == 0x09BF || | |
| (c >= 0x09C0 && c <= 0x09C4) || | |
| (c >= 0x09C7 && c <= 0x09C8) || | |
| (c >= 0x09CB && c <= 0x09CD) || | |
| c == 0x09D7 || | |
| (c >= 0x09E2 && c <= 0x09E3) || | |
| c == 0x0A02 || | |
| c == 0x0A3C || | |
| c == 0x0A3E || | |
| c == 0x0A3F || | |
| (c >= 0x0A40 && c <= 0x0A42) || | |
| (c >= 0x0A47 && c <= 0x0A48) || | |
| (c >= 0x0A4B && c <= 0x0A4D) || | |
| (c >= 0x0A70 && c <= 0x0A71) || | |
| (c >= 0x0A81 && c <= 0x0A83) || | |
| c == 0x0ABC || | |
| (c >= 0x0ABE && c <= 0x0AC5) || | |
| (c >= 0x0AC7 && c <= 0x0AC9) || | |
| (c >= 0x0ACB && c <= 0x0ACD) || | |
| (c >= 0x0B01 && c <= 0x0B03) || | |
| c == 0x0B3C || | |
| (c >= 0x0B3E && c <= 0x0B43) || | |
| (c >= 0x0B47 && c <= 0x0B48) || | |
| (c >= 0x0B4B && c <= 0x0B4D) || | |
| (c >= 0x0B56 && c <= 0x0B57) || | |
| (c >= 0x0B82 && c <= 0x0B83) || | |
| (c >= 0x0BBE && c <= 0x0BC2) || | |
| (c >= 0x0BC6 && c <= 0x0BC8) || | |
| (c >= 0x0BCA && c <= 0x0BCD) || | |
| c == 0x0BD7 || | |
| (c >= 0x0C01 && c <= 0x0C03) || | |
| (c >= 0x0C3E && c <= 0x0C44) || | |
| (c >= 0x0C46 && c <= 0x0C48) || | |
| (c >= 0x0C4A && c <= 0x0C4D) || | |
| (c >= 0x0C55 && c <= 0x0C56) || | |
| (c >= 0x0C82 && c <= 0x0C83) || | |
| (c >= 0x0CBE && c <= 0x0CC4) || | |
| (c >= 0x0CC6 && c <= 0x0CC8) || | |
| (c >= 0x0CCA && c <= 0x0CCD) || | |
| (c >= 0x0CD5 && c <= 0x0CD6) || | |
| (c >= 0x0D02 && c <= 0x0D03) || | |
| (c >= 0x0D3E && c <= 0x0D43) || | |
| (c >= 0x0D46 && c <= 0x0D48) || | |
| (c >= 0x0D4A && c <= 0x0D4D) || | |
| c == 0x0D57 || | |
| c == 0x0E31 || | |
| (c >= 0x0E34 && c <= 0x0E3A) || | |
| (c >= 0x0E47 && c <= 0x0E4E) || | |
| c == 0x0EB1 || | |
| (c >= 0x0EB4 && c <= 0x0EB9) || | |
| (c >= 0x0EBB && c <= 0x0EBC) || | |
| (c >= 0x0EC8 && c <= 0x0ECD) || | |
| (c >= 0x0F18 && c <= 0x0F19) || | |
| c == 0x0F35 || | |
| c == 0x0F37 || | |
| c == 0x0F39 || | |
| c == 0x0F3E || | |
| c == 0x0F3F || | |
| (c >= 0x0F71 && c <= 0x0F84) || | |
| (c >= 0x0F86 && c <= 0x0F8B) || | |
| (c >= 0x0F90 && c <= 0x0F95) || | |
| c == 0x0F97 || | |
| (c >= 0x0F99 && c <= 0x0FAD) || | |
| (c >= 0x0FB1 && c <= 0x0FB7) || | |
| c == 0x0FB9 || | |
| (c >= 0x20D0 && c <= 0x20DC) || | |
| c == 0x20E1 || | |
| (c >= 0x302A && c <= 0x302F) || | |
| c == 0x3099 || | |
| c == 0x309A); | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character matches the Extender | |
| * production. | |
| */ | |
| public static boolean isExtender(int c) | |
| { | |
| return (c == 0x00B7 || | |
| c == 0x02D0 || | |
| c == 0x02D1 || | |
| c == 0x0387 || | |
| c == 0x0640 || | |
| c == 0x0E46 || | |
| c == 0x0EC6 || | |
| c == 0x3005 || | |
| (c >= 0x3031 && c <= 0x3035) || | |
| (c >= 0x309D && c <= 0x309E) || | |
| (c >= 0x30FC && c <= 0x30FE)); | |
| } | |
| /** | |
| * Indicates whether the specified Unicode character matches the Char | |
| * production. | |
| */ | |
| public static boolean isChar(int c) | |
| { | |
| return (c >= 0x20 && c < 0xd800) || | |
| (c >= 0xe00 && c < 0xfffe) || | |
| (c >= 0x10000 && c < 0x110000) || | |
| c == 0xa || c == 0x9 || c == 0xd; | |
| } | |
| /** | |
| * Interns the specified text or not, depending on the value of | |
| * stringInterning. | |
| */ | |
| private String intern(String text) | |
| { | |
| return stringInterning ? text.intern() : text; | |
| } | |
| /** | |
| * Report a parsing error. | |
| */ | |
| private void error(String message) | |
| throws XMLStreamException | |
| { | |
| error(message, null); | |
| } | |
| /** | |
| * Report a parsing error. | |
| */ | |
| private void error(String message, Object info) | |
| throws XMLStreamException | |
| { | |
| if (info != null) | |
| { | |
| if (info instanceof String) | |
| message += ": \"" + ((String) info) + "\""; | |
| else if (info instanceof Character) | |
| message += ": '" + ((Character) info) + "'"; | |
| } | |
| throw new XMLStreamException(message); | |
| } | |
| /** | |
| * Perform validation of a start-element event. | |
| */ | |
| private void validateStartElement(String elementName) | |
| throws XMLStreamException | |
| { | |
| if (currentContentModel == null) | |
| { | |
| // root element | |
| // VC: Root Element Type | |
| if (!elementName.equals(doctype.rootName)) | |
| error("root element name must match name in DTD"); | |
| return; | |
| } | |
| // VC: Element Valid | |
| switch (currentContentModel.type) | |
| { | |
| case ContentModel.EMPTY: | |
| error("child element found in empty element", elementName); | |
| break; | |
| case ContentModel.ELEMENT: | |
| LinkedList ctx = (LinkedList) validationStack.getLast(); | |
| ctx.add(elementName); | |
| break; | |
| case ContentModel.MIXED: | |
| MixedContentModel mm = (MixedContentModel) currentContentModel; | |
| if (!mm.containsName(elementName)) | |
| error("illegal element for content model", elementName); | |
| break; | |
| } | |
| } | |
| /** | |
| * Perform validation of an end-element event. | |
| */ | |
| private void validateEndElement() | |
| throws XMLStreamException | |
| { | |
| if (currentContentModel == null) | |
| { | |
| // root element | |
| // VC: IDREF | |
| if (!idrefs.containsAll(ids)) | |
| error("IDREF values must match the value of some ID attribute"); | |
| return; | |
| } | |
| // VC: Element Valid | |
| switch (currentContentModel.type) | |
| { | |
| case ContentModel.ELEMENT: | |
| LinkedList ctx = (LinkedList) validationStack.getLast(); | |
| ElementContentModel ecm = (ElementContentModel) currentContentModel; | |
| validateElementContent(ecm, ctx); | |
| break; | |
| } | |
| } | |
| /** | |
| * Perform validation of character data. | |
| */ | |
| private void validatePCData(String text) | |
| throws XMLStreamException | |
| { | |
| // VC: Element Valid | |
| switch (currentContentModel.type) | |
| { | |
| case ContentModel.EMPTY: | |
| error("character data found in empty element", text); | |
| break; | |
| case ContentModel.ELEMENT: | |
| boolean white = true; | |
| int len = text.length(); | |
| for (int i = 0; i < len; i++) | |
| { | |
| char c = text.charAt(i); | |
| if (c != ' ' && c != '\t' && c != '\n' && c != '\r') | |
| { | |
| white = false; | |
| break; | |
| } | |
| } | |
| if (!white) | |
| error("character data found in element with element content", text); | |
| else if (xmlStandalone == Boolean.TRUE && currentContentModel.external) | |
| // VC: Standalone Document Declaration | |
| error("whitespace in element content of externally declared " + | |
| "element in standalone document"); | |
| break; | |
| } | |
| } | |
| /** | |
| * Validates the specified validation context (list of child elements) | |
| * against the element content model for the current element. | |
| */ | |
| private void validateElementContent(ElementContentModel model, | |
| LinkedList children) | |
| throws XMLStreamException | |
| { | |
| // Use regular expression | |
| StringBuffer buf = new StringBuffer(); | |
| for (Iterator i = children.iterator(); i.hasNext(); ) | |
| { | |
| buf.append((String) i.next()); | |
| buf.append(' '); | |
| } | |
| String c = buf.toString(); | |
| String regex = createRegularExpression(model); | |
| if (!c.matches(regex)) | |
| error("element content "+model.text+" does not match expression "+regex, c); | |
| } | |
| /** | |
| * Creates the regular expression used to validate an element content | |
| * model. | |
| */ | |
| private String createRegularExpression(ElementContentModel model) | |
| { | |
| if (model.regex == null) | |
| { | |
| StringBuffer buf = new StringBuffer(); | |
| buf.append('('); | |
| for (Iterator i = model.contentParticles.iterator(); i.hasNext(); ) | |
| { | |
| ContentParticle cp = (ContentParticle) i.next(); | |
| if (cp.content instanceof String) | |
| { | |
| buf.append('('); | |
| buf.append((String) cp.content); | |
| buf.append(' '); | |
| buf.append(')'); | |
| if (cp.max == -1) | |
| { | |
| if (cp.min == 0) | |
| buf.append('*'); | |
| else | |
| buf.append('+'); | |
| } | |
| else if (cp.min == 0) | |
| buf.append('?'); | |
| } | |
| else | |
| { | |
| ElementContentModel ecm = (ElementContentModel) cp.content; | |
| buf.append(createRegularExpression(ecm)); | |
| } | |
| if (model.or && i.hasNext()) | |
| buf.append('|'); | |
| } | |
| buf.append(')'); | |
| if (model.max == -1) | |
| { | |
| if (model.min == 0) | |
| buf.append('*'); | |
| else | |
| buf.append('+'); | |
| } | |
| else if (model.min == 0) | |
| buf.append('?'); | |
| model.regex = buf.toString(); | |
| } | |
| return model.regex; | |
| } | |
| /** | |
| * Performs validation of a document type declaration event. | |
| */ | |
| void validateDoctype() | |
| throws XMLStreamException | |
| { | |
| for (Iterator i = doctype.entityIterator(); i.hasNext(); ) | |
| { | |
| Map.Entry entry = (Map.Entry) i.next(); | |
| Object entity = entry.getValue(); | |
| if (entity instanceof ExternalIds) | |
| { | |
| ExternalIds ids = (ExternalIds) entity; | |
| if (ids.notationName != null) | |
| { | |
| // VC: Notation Declared | |
| ExternalIds notation = doctype.getNotation(ids.notationName); | |
| if (notation == null) | |
| error("Notation name must match the declared name of a " + | |
| "notation", ids.notationName); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Simple test harness for reading an XML file. | |
| * args[0] is the filename of the XML file | |
| * If args[1] is "-x", enable XInclude processing | |
| */ | |
| public static void main(String[] args) | |
| throws Exception | |
| { | |
| boolean validating = false; | |
| boolean namespaceAware = false; | |
| boolean xIncludeAware = false; | |
| int pos = 0; | |
| while (pos < args.length && args[pos].startsWith("-")) | |
| { | |
| if ("-x".equals(args[pos])) | |
| xIncludeAware = true; | |
| else if ("-v".equals(args[pos])) | |
| validating = true; | |
| else if ("-n".equals(args[pos])) | |
| namespaceAware = true; | |
| pos++; | |
| } | |
| if (pos >= args.length) | |
| { | |
| System.out.println("Syntax: XMLParser [-n] [-v] [-x] <file> [<file2> [...]]"); | |
| System.out.println("\t-n: use namespace aware mode"); | |
| System.out.println("\t-v: use validating parser"); | |
| System.out.println("\t-x: use XInclude aware mode"); | |
| System.exit(2); | |
| } | |
| while (pos < args.length) | |
| { | |
| XMLParser p = new XMLParser(new java.io.FileInputStream(args[pos]), | |
| absolutize(null, args[pos]), | |
| validating, // validating | |
| namespaceAware, // namespaceAware | |
| true, // coalescing, | |
| true, // replaceERefs | |
| true, // externalEntities | |
| true, // supportDTD | |
| true, // baseAware | |
| true, // stringInterning | |
| true, // extendedEventTypes | |
| null, | |
| null); | |
| XMLStreamReader reader = p; | |
| if (xIncludeAware) | |
| reader = new XIncludeFilter(p, args[pos], true, true, true); | |
| try | |
| { | |
| int event; | |
| //do | |
| while (reader.hasNext()) | |
| { | |
| event = reader.next(); | |
| Location loc = reader.getLocation(); | |
| System.out.print(loc.getLineNumber() + ":" + | |
| loc.getColumnNumber() + " "); | |
| switch (event) | |
| { | |
| case XMLStreamConstants.START_DOCUMENT: | |
| System.out.println("START_DOCUMENT version=" + | |
| reader.getVersion() + | |
| " encoding=" + | |
| reader.getEncoding()); | |
| break; | |
| case XMLStreamConstants.END_DOCUMENT: | |
| System.out.println("END_DOCUMENT"); | |
| break; | |
| case XMLStreamConstants.START_ELEMENT: | |
| System.out.println("START_ELEMENT " + | |
| reader.getName()); | |
| int l = reader.getNamespaceCount(); | |
| for (int i = 0; i < l; i++) | |
| System.out.println("\tnamespace " + | |
| reader.getNamespacePrefix(i) + "='" + | |
| reader.getNamespaceURI(i)+"'"); | |
| l = reader.getAttributeCount(); | |
| for (int i = 0; i < l; i++) | |
| System.out.println("\tattribute " + | |
| reader.getAttributeName(i) + "='" + | |
| reader.getAttributeValue(i) + "'"); | |
| break; | |
| case XMLStreamConstants.END_ELEMENT: | |
| System.out.println("END_ELEMENT " + reader.getName()); | |
| break; | |
| case XMLStreamConstants.CHARACTERS: | |
| System.out.println("CHARACTERS '" + | |
| encodeText(reader.getText()) + "'"); | |
| break; | |
| case XMLStreamConstants.CDATA: | |
| System.out.println("CDATA '" + | |
| encodeText(reader.getText()) + "'"); | |
| break; | |
| case XMLStreamConstants.SPACE: | |
| System.out.println("SPACE '" + | |
| encodeText(reader.getText()) + "'"); | |
| break; | |
| case XMLStreamConstants.DTD: | |
| System.out.println("DTD " + reader.getText()); | |
| break; | |
| case XMLStreamConstants.ENTITY_REFERENCE: | |
| System.out.println("ENTITY_REFERENCE " + reader.getText()); | |
| break; | |
| case XMLStreamConstants.COMMENT: | |
| System.out.println("COMMENT '" + | |
| encodeText(reader.getText()) + "'"); | |
| break; | |
| case XMLStreamConstants.PROCESSING_INSTRUCTION: | |
| System.out.println("PROCESSING_INSTRUCTION " + | |
| reader.getPITarget() + " " + | |
| reader.getPIData()); | |
| break; | |
| case START_ENTITY: | |
| System.out.println("START_ENTITY " + reader.getText()); | |
| break; | |
| case END_ENTITY: | |
| System.out.println("END_ENTITY " + reader.getText()); | |
| break; | |
| default: | |
| System.out.println("Unknown event: " + event); | |
| } | |
| } | |
| } | |
| catch (XMLStreamException e) | |
| { | |
| Location l = reader.getLocation(); | |
| System.out.println("At line "+l.getLineNumber()+ | |
| ", column "+l.getColumnNumber()+ | |
| " of "+l.getSystemId()); | |
| throw e; | |
| } | |
| pos++; | |
| } | |
| } | |
| /** | |
| * Escapes control characters in the specified text. For debugging. | |
| */ | |
| private static String encodeText(String text) | |
| { | |
| StringBuffer b = new StringBuffer(); | |
| int len = text.length(); | |
| for (int i = 0; i < len; i++) | |
| { | |
| char c = text.charAt(i); | |
| switch (c) | |
| { | |
| case '\t': | |
| b.append("\\t"); | |
| break; | |
| case '\n': | |
| b.append("\\n"); | |
| break; | |
| case '\r': | |
| b.append("\\r"); | |
| break; | |
| default: | |
| b.append(c); | |
| } | |
| } | |
| return b.toString(); | |
| } | |
| /** | |
| * An attribute instance. | |
| */ | |
| class Attribute | |
| { | |
| /** | |
| * Attribute name. | |
| */ | |
| final String name; | |
| /** | |
| * Attribute type as declared in the DTD, or CDATA otherwise. | |
| */ | |
| final String type; | |
| /** | |
| * Whether the attribute was specified or defaulted. | |
| */ | |
| final boolean specified; | |
| /** | |
| * The attribute value. | |
| */ | |
| final String value; | |
| /** | |
| * The namespace prefix. | |
| */ | |
| final String prefix; | |
| /** | |
| * The namespace local-name. | |
| */ | |
| final String localName; | |
| Attribute(String name, String type, boolean specified, String value) | |
| { | |
| this.name = name; | |
| this.type = type; | |
| this.specified = specified; | |
| this.value = value; | |
| int ci = name.indexOf(':'); | |
| if (ci == -1) | |
| { | |
| prefix = null; | |
| localName = intern(name); | |
| } | |
| else | |
| { | |
| prefix = intern(name.substring(0, ci)); | |
| localName = intern(name.substring(ci + 1)); | |
| } | |
| } | |
| public boolean equals(Object other) | |
| { | |
| if (other instanceof Attribute) | |
| { | |
| Attribute a = (Attribute) other; | |
| if (namespaceAware) | |
| { | |
| if (!a.localName.equals(localName)) | |
| return false; | |
| String auri = getNamespaceURI(a.prefix); | |
| String uri = getNamespaceURI(prefix); | |
| if (uri == null && (auri == null || | |
| (input.xml11 && "".equals(auri)))) | |
| return true; | |
| if (uri != null) | |
| { | |
| if ("".equals(uri) && input.xml11 && "".equals(auri)) | |
| return true; | |
| return uri.equals(auri); | |
| } | |
| return false; | |
| } | |
| else | |
| return a.name.equals(name); | |
| } | |
| return false; | |
| } | |
| } | |
| /** | |
| * Representation of a DTD. | |
| */ | |
| class Doctype | |
| { | |
| /** | |
| * Name of the root element. | |
| */ | |
| final String rootName; | |
| /** | |
| * Public ID, if any, of external subset. | |
| */ | |
| final String publicId; | |
| /** | |
| * System ID (URL), if any, of external subset. | |
| */ | |
| final String systemId; | |
| /** | |
| * Map of element names to content models. | |
| */ | |
| private final LinkedHashMap elements = new LinkedHashMap(); | |
| /** | |
| * Map of element names to maps of attribute declarations. | |
| */ | |
| private final LinkedHashMap attlists = new LinkedHashMap(); | |
| /** | |
| * Map of entity names to entities (String or ExternalIds). | |
| */ | |
| private final LinkedHashMap entities = new LinkedHashMap(); | |
| /** | |
| * Map of notation names to ExternalIds. | |
| */ | |
| private final LinkedHashMap notations = new LinkedHashMap(); | |
| /** | |
| * Map of anonymous keys to comments. | |
| */ | |
| private final LinkedHashMap comments = new LinkedHashMap(); | |
| /** | |
| * Map of anonymous keys to processing instructions (String[2] | |
| * containing {target, data}). | |
| */ | |
| private final LinkedHashMap pis = new LinkedHashMap(); | |
| /** | |
| * List of keys to all markup entries in the DTD. | |
| */ | |
| private final LinkedList entries = new LinkedList(); | |
| /** | |
| * Set of the entities defined in the external subset. | |
| */ | |
| private final HashSet externalEntities = new HashSet(); | |
| /** | |
| * Set of the notations defined in the external subset. | |
| */ | |
| private final HashSet externalNotations = new HashSet(); | |
| /** | |
| * Counter for making anonymous keys. | |
| */ | |
| private int anon = 1; | |
| /** | |
| * Constructor. | |
| */ | |
| Doctype(String rootName, String publicId, String systemId) | |
| { | |
| this.rootName = rootName; | |
| this.publicId = publicId; | |
| this.systemId = systemId; | |
| } | |
| /** | |
| * Adds an element declaration. | |
| * @param name the element name | |
| * @param text the content model text | |
| * @param model the parsed content model | |
| */ | |
| void addElementDecl(String name, String text, ContentModel model) | |
| { | |
| if (elements.containsKey(name)) | |
| return; | |
| model.text = text; | |
| model.external = (inputStack.size() != 1); | |
| elements.put(name, model); | |
| entries.add("E" + name); | |
| } | |
| /** | |
| * Adds an attribute declaration. | |
| * @param ename the element name | |
| * @param aname the attribute name | |
| * @param decl the attribute declaration details | |
| */ | |
| void addAttributeDecl(String ename, String aname, AttributeDecl decl) | |
| { | |
| LinkedHashMap attlist = (LinkedHashMap) attlists.get(ename); | |
| if (attlist == null) | |
| { | |
| attlist = new LinkedHashMap(); | |
| attlists.put(ename, attlist); | |
| } | |
| else if (attlist.containsKey(aname)) | |
| return; | |
| attlist.put(aname, decl); | |
| String key = "A" + ename; | |
| if (!entries.contains(key)) | |
| entries.add(key); | |
| } | |
| /** | |
| * Adds an entity declaration. | |
| * @param name the entity name | |
| * @param text the entity replacement text | |
| * @param inExternalSubset if we are in the exernal subset | |
| */ | |
| void addEntityDecl(String name, String text, boolean inExternalSubset) | |
| { | |
| if (entities.containsKey(name)) | |
| return; | |
| entities.put(name, text); | |
| entries.add("e" + name); | |
| if (inExternalSubset) | |
| externalEntities.add(name); | |
| } | |
| /** | |
| * Adds an entity declaration. | |
| * @param name the entity name | |
| * @param ids the external IDs | |
| * @param inExternalSubset if we are in the exernal subset | |
| */ | |
| void addEntityDecl(String name, ExternalIds ids, boolean inExternalSubset) | |
| { | |
| if (entities.containsKey(name)) | |
| return; | |
| entities.put(name, ids); | |
| entries.add("e" + name); | |
| if (inExternalSubset) | |
| externalEntities.add(name); | |
| } | |
| /** | |
| * Adds a notation declaration. | |
| * @param name the notation name | |
| * @param ids the external IDs | |
| * @param inExternalSubset if we are in the exernal subset | |
| */ | |
| void addNotationDecl(String name, ExternalIds ids, boolean inExternalSubset) | |
| { | |
| if (notations.containsKey(name)) | |
| return; | |
| notations.put(name, ids); | |
| entries.add("n" + name); | |
| if (inExternalSubset) | |
| externalNotations.add(name); | |
| } | |
| /** | |
| * Adds a comment. | |
| */ | |
| void addComment(String text) | |
| { | |
| String key = Integer.toString(anon++); | |
| comments.put(key, text); | |
| entries.add("c" + key); | |
| } | |
| /** | |
| * Adds a processing instruction. | |
| */ | |
| void addPI(String target, String data) | |
| { | |
| String key = Integer.toString(anon++); | |
| pis.put(key, new String[] {target, data}); | |
| entries.add("p" + key); | |
| } | |
| /** | |
| * Returns the content model for the specified element. | |
| * @param name the element name | |
| */ | |
| ContentModel getElementModel(String name) | |
| { | |
| return (ContentModel) elements.get(name); | |
| } | |
| /** | |
| * Returns the attribute definition for the given attribute | |
| * @param ename the element name | |
| * @param aname the attribute name | |
| */ | |
| AttributeDecl getAttributeDecl(String ename, String aname) | |
| { | |
| LinkedHashMap attlist = (LinkedHashMap) attlists.get(ename); | |
| return (attlist == null) ? null : (AttributeDecl) attlist.get(aname); | |
| } | |
| /** | |
| * Indicates whether the specified attribute was declared in the DTD. | |
| * @param ename the element name | |
| * @param aname the attribute name | |
| */ | |
| boolean isAttributeDeclared(String ename, String aname) | |
| { | |
| LinkedHashMap attlist = (LinkedHashMap) attlists.get(ename); | |
| return (attlist == null) ? false : attlist.containsKey(aname); | |
| } | |
| /** | |
| * Returns an iterator over the entries in the attribute list for the | |
| * given element. | |
| * @param ename the element name | |
| */ | |
| Iterator attlistIterator(String ename) | |
| { | |
| LinkedHashMap attlist = (LinkedHashMap) attlists.get(ename); | |
| return (attlist == null) ? Collections.EMPTY_LIST.iterator() : | |
| attlist.entrySet().iterator(); | |
| } | |
| /** | |
| * Returns the entity (String or ExternalIds) for the given entity name. | |
| */ | |
| Object getEntity(String name) | |
| { | |
| return entities.get(name); | |
| } | |
| /** | |
| * Indicates whether the specified entity was declared in the external | |
| * subset. | |
| */ | |
| boolean isEntityExternal(String name) | |
| { | |
| return externalEntities.contains(name); | |
| } | |
| /** | |
| * Returns an iterator over the entity map entries. | |
| */ | |
| Iterator entityIterator() | |
| { | |
| return entities.entrySet().iterator(); | |
| } | |
| /** | |
| * Returns the notation IDs for the given notation name. | |
| */ | |
| ExternalIds getNotation(String name) | |
| { | |
| return (ExternalIds) notations.get(name); | |
| } | |
| /** | |
| * Indicates whether the specified notation was declared in the external | |
| * subset. | |
| */ | |
| boolean isNotationExternal(String name) | |
| { | |
| return externalNotations.contains(name); | |
| } | |
| /** | |
| * Returns the comment associated with the specified (anonymous) key. | |
| */ | |
| String getComment(String key) | |
| { | |
| return (String) comments.get(key); | |
| } | |
| /** | |
| * Returns the processing instruction associated with the specified | |
| * (anonymous) key. | |
| */ | |
| String[] getPI(String key) | |
| { | |
| return (String[]) pis.get(key); | |
| } | |
| /** | |
| * Returns an iterator over the keys of the markup entries in this DTD, | |
| * in the order declared. | |
| */ | |
| Iterator entryIterator() | |
| { | |
| return entries.iterator(); | |
| } | |
| } | |
| /** | |
| * Combination of an ExternalID and an optional NDataDecl. | |
| */ | |
| class ExternalIds | |
| { | |
| /** | |
| * The public ID. | |
| */ | |
| String publicId; | |
| /** | |
| * The system ID. | |
| */ | |
| String systemId; | |
| /** | |
| * The notation name declared with the NDATA keyword. | |
| */ | |
| String notationName; | |
| } | |
| /** | |
| * A content model. | |
| */ | |
| abstract class ContentModel | |
| { | |
| static final int EMPTY = 0; | |
| static final int ANY = 1; | |
| static final int ELEMENT = 2; | |
| static final int MIXED = 3; | |
| int min; | |
| int max; | |
| final int type; | |
| String text; | |
| boolean external; | |
| ContentModel(int type) | |
| { | |
| this.type = type; | |
| min = 1; | |
| max = 1; | |
| } | |
| } | |
| /** | |
| * The EMPTY content model. | |
| */ | |
| class EmptyContentModel | |
| extends ContentModel | |
| { | |
| EmptyContentModel() | |
| { | |
| super(ContentModel.EMPTY); | |
| min = 0; | |
| max = 0; | |
| } | |
| } | |
| /** | |
| * The ANY content model. | |
| */ | |
| class AnyContentModel | |
| extends ContentModel | |
| { | |
| AnyContentModel() | |
| { | |
| super(ContentModel.ANY); | |
| min = 0; | |
| max = -1; | |
| } | |
| } | |
| /** | |
| * An element content model. | |
| */ | |
| class ElementContentModel | |
| extends ContentModel | |
| { | |
| LinkedList contentParticles; | |
| boolean or; | |
| String regex; // regular expression cache | |
| ElementContentModel() | |
| { | |
| super(ContentModel.ELEMENT); | |
| contentParticles = new LinkedList(); | |
| } | |
| void addContentParticle(ContentParticle cp) | |
| { | |
| contentParticles.add(cp); | |
| } | |
| } | |
| class ContentParticle | |
| { | |
| int min = 1; | |
| int max = 1; | |
| Object content; // Name (String) or ElementContentModel | |
| } | |
| /** | |
| * A mixed content model. | |
| */ | |
| class MixedContentModel | |
| extends ContentModel | |
| { | |
| private HashSet names; | |
| MixedContentModel() | |
| { | |
| super(ContentModel.MIXED); | |
| names = new HashSet(); | |
| } | |
| void addName(String name) | |
| { | |
| names.add(name); | |
| } | |
| boolean containsName(String name) | |
| { | |
| return names.contains(name); | |
| } | |
| } | |
| /** | |
| * An attribute definition. | |
| */ | |
| class AttributeDecl | |
| { | |
| /** | |
| * The attribute type (CDATA, ID, etc). | |
| */ | |
| final String type; | |
| /** | |
| * The default value. | |
| */ | |
| final String value; | |
| /** | |
| * The value type (#FIXED, #IMPLIED, etc). | |
| */ | |
| final int valueType; | |
| /** | |
| * The enumeration text. | |
| */ | |
| final String enumeration; | |
| /** | |
| * The enumeration tokens. | |
| */ | |
| final HashSet values; | |
| /** | |
| * Whether this attribute declaration occurred in the external subset. | |
| */ | |
| final boolean external; | |
| AttributeDecl(String type, String value, | |
| int valueType, String enumeration, | |
| HashSet values, boolean external) | |
| { | |
| this.type = type; | |
| this.value = value; | |
| this.valueType = valueType; | |
| this.enumeration = enumeration; | |
| this.values = values; | |
| this.external = external; | |
| } | |
| } | |
| /** | |
| * An XML input source. | |
| */ | |
| static class Input | |
| implements Location | |
| { | |
| int line = 1, markLine; | |
| int column, markColumn; | |
| int offset, markOffset; | |
| final String publicId, systemId, name; | |
| final boolean report; // report start- and end-entity | |
| final boolean normalize; // normalize CR, etc to LF | |
| InputStream in; | |
| Reader reader; | |
| UnicodeReader unicodeReader; | |
| boolean initialized; | |
| boolean encodingDetected; | |
| String inputEncoding; | |
| boolean xml11; | |
| Input(InputStream in, Reader reader, String publicId, String systemId, | |
| String name, String inputEncoding, boolean report, | |
| boolean normalize) | |
| { | |
| if (inputEncoding == null) | |
| inputEncoding = "UTF-8"; | |
| this.inputEncoding = inputEncoding; | |
| this.publicId = publicId; | |
| this.systemId = systemId; | |
| this.name = name; | |
| this.report = report; | |
| this.normalize = normalize; | |
| if (in != null) | |
| { | |
| if (reader != null) | |
| throw new IllegalStateException("both byte and char streams "+ | |
| "specified"); | |
| if (normalize) | |
| in = new CRLFInputStream(in); | |
| in = new BufferedInputStream(in); | |
| this.in = in; | |
| } | |
| else | |
| { | |
| this.reader = normalize ? new CRLFReader(reader) : reader; | |
| unicodeReader = new UnicodeReader(this.reader); | |
| } | |
| initialized = false; | |
| } | |
| // -- Location -- | |
| public int getCharacterOffset() | |
| { | |
| return offset; | |
| } | |
| public int getColumnNumber() | |
| { | |
| return column; | |
| } | |
| public int getLineNumber() | |
| { | |
| return line; | |
| } | |
| public String getPublicId() | |
| { | |
| return publicId; | |
| } | |
| public String getSystemId() | |
| { | |
| return systemId; | |
| } | |
| void init() | |
| throws IOException | |
| { | |
| if (initialized) | |
| return; | |
| if (in != null) | |
| detectEncoding(); | |
| initialized = true; | |
| } | |
| void mark(int len) | |
| throws IOException | |
| { | |
| markOffset = offset; | |
| markLine = line; | |
| markColumn = column; | |
| if (unicodeReader != null) | |
| unicodeReader.mark(len); | |
| else | |
| in.mark(len); | |
| } | |
| /** | |
| * Character read. | |
| */ | |
| int read() | |
| throws IOException | |
| { | |
| offset++; | |
| int ret = (unicodeReader != null) ? unicodeReader.read() : in.read(); | |
| if (normalize && | |
| (ret == 0x0d || (xml11 && (ret == 0x85 || ret == 0x2028)))) | |
| { | |
| // Normalize CR etc to LF | |
| ret = 0x0a; | |
| } | |
| // Locator handling | |
| if (ret == 0x0a) | |
| { | |
| line++; | |
| column = 0; | |
| } | |
| else | |
| column++; | |
| return ret; | |
| } | |
| /** | |
| * Block read. | |
| */ | |
| int read(int[] b, int off, int len) | |
| throws IOException | |
| { | |
| int ret; | |
| if (unicodeReader != null) | |
| { | |
| ret = unicodeReader.read(b, off, len); | |
| } | |
| else | |
| { | |
| byte[] b2 = new byte[len]; | |
| ret = in.read(b2, 0, len); | |
| if (ret != -1) | |
| { | |
| String s = new String(b2, 0, ret, inputEncoding); | |
| int[] c = UnicodeReader.toCodePointArray(s); | |
| ret = c.length; | |
| System.arraycopy(c, 0, b, off, ret); | |
| } | |
| } | |
| if (ret != -1) | |
| { | |
| // Locator handling | |
| for (int i = 0; i < ret; i++) | |
| { | |
| int c = b[off + i]; | |
| if (normalize && | |
| (c == 0x0d || (xml11 && (c == 0x85 || c == 0x2028)))) | |
| { | |
| // Normalize CR etc to LF | |
| c = 0x0a; | |
| b[off + i] = c; | |
| } | |
| if (c == 0x0a) | |
| { | |
| line++; | |
| column = 0; | |
| } | |
| else | |
| column++; | |
| } | |
| } | |
| return ret; | |
| } | |
| void reset() | |
| throws IOException | |
| { | |
| if (unicodeReader != null) | |
| unicodeReader.reset(); | |
| else | |
| in.reset(); | |
| offset = markOffset; | |
| line = markLine; | |
| column = markColumn; | |
| } | |
| // Detection of input encoding | |
| private static final int[] SIGNATURE_UCS_4_1234 = | |
| new int[] { 0x00, 0x00, 0x00, 0x3c }; | |
| private static final int[] SIGNATURE_UCS_4_4321 = | |
| new int[] { 0x3c, 0x00, 0x00, 0x00 }; | |
| private static final int[] SIGNATURE_UCS_4_2143 = | |
| new int[] { 0x00, 0x00, 0x3c, 0x00 }; | |
| private static final int[] SIGNATURE_UCS_4_3412 = | |
| new int[] { 0x00, 0x3c, 0x00, 0x00 }; | |
| private static final int[] SIGNATURE_UCS_2_12 = | |
| new int[] { 0xfe, 0xff }; | |
| private static final int[] SIGNATURE_UCS_2_21 = | |
| new int[] { 0xff, 0xfe }; | |
| private static final int[] SIGNATURE_UCS_2_12_NOBOM = | |
| new int[] { 0x00, 0x3c, 0x00, 0x3f }; | |
| private static final int[] SIGNATURE_UCS_2_21_NOBOM = | |
| new int[] { 0x3c, 0x00, 0x3f, 0x00 }; | |
| private static final int[] SIGNATURE_UTF_8 = | |
| new int[] { 0x3c, 0x3f, 0x78, 0x6d }; | |
| private static final int[] SIGNATURE_UTF_8_BOM = | |
| new int[] { 0xef, 0xbb, 0xbf }; | |
| /** | |
| * Detect the input encoding. | |
| */ | |
| private void detectEncoding() | |
| throws IOException | |
| { | |
| int[] signature = new int[4]; | |
| in.mark(4); | |
| for (int i = 0; i < 4; i++) | |
| signature[i] = in.read(); | |
| in.reset(); | |
| // 4-byte encodings | |
| if (equals(SIGNATURE_UCS_4_1234, signature)) | |
| { | |
| in.read(); | |
| in.read(); | |
| in.read(); | |
| in.read(); | |
| setInputEncoding("UTF-32BE"); | |
| encodingDetected = true; | |
| } | |
| else if (equals(SIGNATURE_UCS_4_4321, signature)) | |
| { | |
| in.read(); | |
| in.read(); | |
| in.read(); | |
| in.read(); | |
| setInputEncoding("UTF-32LE"); | |
| encodingDetected = true; | |
| } | |
| else if (equals(SIGNATURE_UCS_4_2143, signature) || | |
| equals(SIGNATURE_UCS_4_3412, signature)) | |
| throw new UnsupportedEncodingException("unsupported UCS-4 byte ordering"); | |
| // 2-byte encodings | |
| else if (equals(SIGNATURE_UCS_2_12, signature)) | |
| { | |
| in.read(); | |
| in.read(); | |
| setInputEncoding("UTF-16BE"); | |
| encodingDetected = true; | |
| } | |
| else if (equals(SIGNATURE_UCS_2_21, signature)) | |
| { | |
| in.read(); | |
| in.read(); | |
| setInputEncoding("UTF-16LE"); | |
| encodingDetected = true; | |
| } | |
| else if (equals(SIGNATURE_UCS_2_12_NOBOM, signature)) | |
| { | |
| //setInputEncoding("UTF-16BE"); | |
| throw new UnsupportedEncodingException("no byte-order mark for UCS-2 entity"); | |
| } | |
| else if (equals(SIGNATURE_UCS_2_21_NOBOM, signature)) | |
| { | |
| //setInputEncoding("UTF-16LE"); | |
| throw new UnsupportedEncodingException("no byte-order mark for UCS-2 entity"); | |
| } | |
| // ASCII-derived encodings | |
| else if (equals(SIGNATURE_UTF_8, signature)) | |
| { | |
| // UTF-8 input encoding implied, TextDecl | |
| } | |
| else if (equals(SIGNATURE_UTF_8_BOM, signature)) | |
| { | |
| in.read(); | |
| in.read(); | |
| in.read(); | |
| setInputEncoding("UTF-8"); | |
| encodingDetected = true; | |
| } | |
| } | |
| private static boolean equals(int[] b1, int[] b2) | |
| { | |
| for (int i = 0; i < b1.length; i++) | |
| { | |
| if (b1[i] != b2[i]) | |
| return false; | |
| } | |
| return true; | |
| } | |
| void setInputEncoding(String encoding) | |
| throws IOException | |
| { | |
| if (encoding.equals(inputEncoding)) | |
| return; | |
| if ("UTF-16".equalsIgnoreCase(encoding) && | |
| inputEncoding.startsWith("UTF-16")) | |
| return; | |
| if (encodingDetected) | |
| throw new UnsupportedEncodingException("document is not in its " + | |
| "declared encoding " + | |
| inputEncoding + | |
| ": " + encoding); | |
| inputEncoding = encoding; | |
| finalizeEncoding(); | |
| } | |
| void finalizeEncoding() | |
| throws IOException | |
| { | |
| if (reader != null) | |
| return; | |
| reader = new BufferedReader(new InputStreamReader(in, inputEncoding)); | |
| unicodeReader = new UnicodeReader(reader); | |
| mark(1); | |
| } | |
| } | |
| } | |