1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.apache.harmony.xml; 18 19 import org.xml.sax.ContentHandler; 20 import org.xml.sax.DTDHandler; 21 import org.xml.sax.EntityResolver; 22 import org.xml.sax.ErrorHandler; 23 import org.xml.sax.InputSource; 24 import org.xml.sax.SAXException; 25 import org.xml.sax.SAXNotRecognizedException; 26 import org.xml.sax.SAXNotSupportedException; 27 import org.xml.sax.XMLReader; 28 import org.xml.sax.ext.LexicalHandler; 29 30 import android.compat.annotation.UnsupportedAppUsage; 31 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.Reader; 35 import libcore.io.IoUtils; 36 37 /** 38 * SAX wrapper around Expat. Interns strings. Does not support validation. 39 * Does not support {@link DTDHandler}. 40 */ 41 public class ExpatReader implements XMLReader { 42 /* 43 * ExpatParser accesses these fields directly during parsing. The user 44 * should be able to safely change them during parsing. 45 */ 46 @UnsupportedAppUsage 47 /*package*/ ContentHandler contentHandler; 48 /*package*/ DTDHandler dtdHandler; 49 /*package*/ EntityResolver entityResolver; 50 /*package*/ ErrorHandler errorHandler; 51 /*package*/ LexicalHandler lexicalHandler; 52 53 private boolean processNamespaces = true; 54 private boolean processNamespacePrefixes = false; 55 56 private static final String LEXICAL_HANDLER_PROPERTY 57 = "http://xml.org/sax/properties/lexical-handler"; 58 59 private static class Feature { 60 private static final String BASE_URI = "http://xml.org/sax/features/"; 61 private static final String VALIDATION = BASE_URI + "validation"; 62 private static final String NAMESPACES = BASE_URI + "namespaces"; 63 private static final String NAMESPACE_PREFIXES = BASE_URI + "namespace-prefixes"; 64 private static final String STRING_INTERNING = BASE_URI + "string-interning"; 65 private static final String EXTERNAL_GENERAL_ENTITIES 66 = BASE_URI + "external-general-entities"; 67 private static final String EXTERNAL_PARAMETER_ENTITIES 68 = BASE_URI + "external-parameter-entities"; 69 } 70 71 @UnsupportedAppUsage ExpatReader()72 public ExpatReader() { 73 } 74 getFeature(String name)75 public boolean getFeature(String name) 76 throws SAXNotRecognizedException, SAXNotSupportedException { 77 if (name == null) { 78 throw new NullPointerException("name == null"); 79 } 80 81 if (name.equals(Feature.VALIDATION) 82 || name.equals(Feature.EXTERNAL_GENERAL_ENTITIES) 83 || name.equals(Feature.EXTERNAL_PARAMETER_ENTITIES)) { 84 return false; 85 } 86 87 if (name.equals(Feature.NAMESPACES)) { 88 return processNamespaces; 89 } 90 91 if (name.equals(Feature.NAMESPACE_PREFIXES)) { 92 return processNamespacePrefixes; 93 } 94 95 if (name.equals(Feature.STRING_INTERNING)) { 96 return true; 97 } 98 99 throw new SAXNotRecognizedException(name); 100 } 101 setFeature(String name, boolean value)102 public void setFeature(String name, boolean value) 103 throws SAXNotRecognizedException, SAXNotSupportedException { 104 if (name == null) { 105 throw new NullPointerException("name == null"); 106 } 107 108 if (name.equals(Feature.VALIDATION) 109 || name.equals(Feature.EXTERNAL_GENERAL_ENTITIES) 110 || name.equals(Feature.EXTERNAL_PARAMETER_ENTITIES)) { 111 if (value) { 112 throw new SAXNotSupportedException("Cannot enable " + name); 113 } else { 114 // Default. 115 return; 116 } 117 } 118 119 if (name.equals(Feature.NAMESPACES)) { 120 processNamespaces = value; 121 return; 122 } 123 124 if (name.equals(Feature.NAMESPACE_PREFIXES)) { 125 processNamespacePrefixes = value; 126 return; 127 } 128 129 if (name.equals(Feature.STRING_INTERNING)) { 130 if (value) { 131 // Default. 132 return; 133 } else { 134 throw new SAXNotSupportedException("Cannot disable " + name); 135 } 136 } 137 138 throw new SAXNotRecognizedException(name); 139 } 140 getProperty(String name)141 public Object getProperty(String name) 142 throws SAXNotRecognizedException, SAXNotSupportedException { 143 if (name == null) { 144 throw new NullPointerException("name == null"); 145 } 146 147 if (name.equals(LEXICAL_HANDLER_PROPERTY)) { 148 return lexicalHandler; 149 } 150 151 throw new SAXNotRecognizedException(name); 152 } 153 setProperty(String name, Object value)154 public void setProperty(String name, Object value) 155 throws SAXNotRecognizedException, SAXNotSupportedException { 156 if (name == null) { 157 throw new NullPointerException("name == null"); 158 } 159 160 if (name.equals(LEXICAL_HANDLER_PROPERTY)) { 161 // The object must implement LexicalHandler 162 if (value instanceof LexicalHandler || value == null) { 163 this.lexicalHandler = (LexicalHandler) value; 164 return; 165 } 166 throw new SAXNotSupportedException("value doesn't implement " + 167 "org.xml.sax.ext.LexicalHandler"); 168 } 169 170 throw new SAXNotRecognizedException(name); 171 } 172 setEntityResolver(EntityResolver resolver)173 public void setEntityResolver(EntityResolver resolver) { 174 this.entityResolver = resolver; 175 } 176 getEntityResolver()177 public EntityResolver getEntityResolver() { 178 return entityResolver; 179 } 180 setDTDHandler(DTDHandler dtdHandler)181 public void setDTDHandler(DTDHandler dtdHandler) { 182 this.dtdHandler = dtdHandler; 183 } 184 getDTDHandler()185 public DTDHandler getDTDHandler() { 186 return dtdHandler; 187 } 188 setContentHandler(ContentHandler handler)189 public void setContentHandler(ContentHandler handler) { 190 this.contentHandler = handler; 191 } 192 getContentHandler()193 public ContentHandler getContentHandler() { 194 return this.contentHandler; 195 } 196 setErrorHandler(ErrorHandler handler)197 public void setErrorHandler(ErrorHandler handler) { 198 this.errorHandler = handler; 199 } 200 getErrorHandler()201 public ErrorHandler getErrorHandler() { 202 return errorHandler; 203 } 204 205 /** 206 * Returns the current lexical handler. 207 * 208 * @return the current lexical handler, or null if none has been registered 209 * @see #setLexicalHandler 210 */ getLexicalHandler()211 public LexicalHandler getLexicalHandler() { 212 return lexicalHandler; 213 } 214 215 /** 216 * Registers a lexical event handler. Supports neither 217 * {@link LexicalHandler#startEntity(String)} nor 218 * {@link LexicalHandler#endEntity(String)}. 219 * 220 * <p>If the application does not register a lexical handler, all 221 * lexical events reported by the SAX parser will be silently 222 * ignored.</p> 223 * 224 * <p>Applications may register a new or different handler in the 225 * middle of a parse, and the SAX parser must begin using the new 226 * handler immediately.</p> 227 * 228 * @param lexicalHandler listens for lexical events 229 * @see #getLexicalHandler() 230 */ setLexicalHandler(LexicalHandler lexicalHandler)231 public void setLexicalHandler(LexicalHandler lexicalHandler) { 232 this.lexicalHandler = lexicalHandler; 233 } 234 235 /** 236 * Returns true if this SAX parser processes namespaces. 237 * 238 * @see #setNamespaceProcessingEnabled(boolean) 239 */ isNamespaceProcessingEnabled()240 public boolean isNamespaceProcessingEnabled() { 241 return processNamespaces; 242 } 243 244 /** 245 * Enables or disables namespace processing. Set to true by default. If you 246 * enable namespace processing, the parser will invoke 247 * {@link ContentHandler#startPrefixMapping(String, String)} and 248 * {@link ContentHandler#endPrefixMapping(String)}, and it will filter 249 * out namespace declarations from element attributes. 250 * 251 * @see #isNamespaceProcessingEnabled() 252 */ setNamespaceProcessingEnabled(boolean processNamespaces)253 public void setNamespaceProcessingEnabled(boolean processNamespaces) { 254 this.processNamespaces = processNamespaces; 255 } 256 parse(InputSource input)257 public void parse(InputSource input) throws IOException, SAXException { 258 if (processNamespacePrefixes && processNamespaces) { 259 /* 260 * Expat has XML_SetReturnNSTriplet, but that still doesn't 261 * include xmlns attributes like this feature requires. We may 262 * have to implement namespace processing ourselves if we want 263 * this (not too difficult). We obviously "support" namespace 264 * prefixes if namespaces are disabled. 265 */ 266 throw new SAXNotSupportedException("The 'namespace-prefix' " + 267 "feature is not supported while the 'namespaces' " + 268 "feature is enabled."); 269 } 270 271 // Try the character stream. 272 Reader reader = input.getCharacterStream(); 273 if (reader != null) { 274 try { 275 parse(reader, input.getPublicId(), input.getSystemId()); 276 } finally { 277 IoUtils.closeQuietly(reader); 278 } 279 return; 280 } 281 282 // Try the byte stream. 283 InputStream in = input.getByteStream(); 284 String encoding = input.getEncoding(); 285 if (in != null) { 286 try { 287 parse(in, encoding, input.getPublicId(), input.getSystemId()); 288 } finally { 289 IoUtils.closeQuietly(in); 290 } 291 return; 292 } 293 294 String systemId = input.getSystemId(); 295 if (systemId == null) { 296 throw new SAXException("No input specified."); 297 } 298 299 // Try the system id. 300 in = ExpatParser.openUrl(systemId); 301 try { 302 parse(in, encoding, input.getPublicId(), systemId); 303 } finally { 304 IoUtils.closeQuietly(in); 305 } 306 } 307 parse(Reader in, String publicId, String systemId)308 private void parse(Reader in, String publicId, String systemId) 309 throws IOException, SAXException { 310 ExpatParser parser = new ExpatParser( 311 ExpatParser.CHARACTER_ENCODING, 312 this, 313 processNamespaces, 314 publicId, 315 systemId 316 ); 317 parser.parseDocument(in); 318 } 319 parse(InputStream in, String charsetName, String publicId, String systemId)320 private void parse(InputStream in, String charsetName, String publicId, String systemId) 321 throws IOException, SAXException { 322 ExpatParser parser = 323 new ExpatParser(charsetName, this, processNamespaces, publicId, systemId); 324 parser.parseDocument(in); 325 } 326 parse(String systemId)327 public void parse(String systemId) throws IOException, SAXException { 328 parse(new InputSource(systemId)); 329 } 330 } 331