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