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