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 java.io.IOException;
20import java.io.InputStream;
21import java.io.Reader;
22import libcore.io.IoUtils;
23import org.xml.sax.ContentHandler;
24import org.xml.sax.DTDHandler;
25import org.xml.sax.EntityResolver;
26import org.xml.sax.ErrorHandler;
27import org.xml.sax.InputSource;
28import org.xml.sax.SAXException;
29import org.xml.sax.SAXNotRecognizedException;
30import org.xml.sax.SAXNotSupportedException;
31import org.xml.sax.XMLReader;
32import 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 */
38public 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
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
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
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
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
165    public void setEntityResolver(EntityResolver resolver) {
166        this.entityResolver = resolver;
167    }
168
169    public EntityResolver getEntityResolver() {
170        return entityResolver;
171    }
172
173    public void setDTDHandler(DTDHandler dtdHandler) {
174        this.dtdHandler = dtdHandler;
175    }
176
177    public DTDHandler getDTDHandler() {
178        return dtdHandler;
179    }
180
181    public void setContentHandler(ContentHandler handler) {
182        this.contentHandler = handler;
183    }
184
185    public ContentHandler getContentHandler() {
186        return this.contentHandler;
187    }
188
189    public void setErrorHandler(ErrorHandler handler) {
190        this.errorHandler = handler;
191    }
192
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     */
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     */
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     */
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     */
245    public void setNamespaceProcessingEnabled(boolean processNamespaces) {
246        this.processNamespaces = processNamespaces;
247    }
248
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
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
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
319    public void parse(String systemId) throws IOException, SAXException {
320        parse(new InputSource(systemId));
321    }
322}
323