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