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