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