1/* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
2// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
3
4package org.xmlpull.v1.sax2;
5
6import java.io.InputStream;
7import java.io.IOException;
8import java.io.Reader;
9
10// not J2ME classes -- remove if you want to run in MIDP devices
11import java.net.URL;
12import java.net.MalformedURLException;
13
14
15// not J2ME classes
16import java.io.FileInputStream;
17import java.io.FileNotFoundException;
18
19import org.xml.sax.Attributes;
20import org.xml.sax.DTDHandler;
21import org.xml.sax.ContentHandler;
22import org.xml.sax.EntityResolver;
23import org.xml.sax.ErrorHandler;
24import org.xml.sax.InputSource;
25import org.xml.sax.Locator;
26import org.xml.sax.SAXException;
27import org.xml.sax.SAXParseException;
28import org.xml.sax.SAXNotRecognizedException;
29import org.xml.sax.SAXNotSupportedException;
30import org.xml.sax.XMLReader;
31import org.xml.sax.helpers.DefaultHandler;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35import org.xmlpull.v1.XmlPullParserFactory;
36
37/**
38 * SAX2 Driver that pulls events from XmlPullParser
39 * and comverts them into SAX2 callbacks.
40 *
41 * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
42 */
43
44public class Driver implements Locator, XMLReader, Attributes
45{
46
47    protected static final String DECLARATION_HANDLER_PROPERTY =
48        "http://xml.org/sax/properties/declaration-handler";
49
50    protected static final String LEXICAL_HANDLER_PROPERTY =
51        "http://xml.org/sax/properties/lexical-handler";
52
53    protected static final String NAMESPACES_FEATURE =
54        "http://xml.org/sax/features/namespaces";
55
56    protected static final String NAMESPACE_PREFIXES_FEATURE =
57        "http://xml.org/sax/features/namespace-prefixes";
58
59    protected static final String VALIDATION_FEATURE =
60        "http://xml.org/sax/features/validation";
61
62    protected static final String APACHE_SCHEMA_VALIDATION_FEATURE =
63        "http://apache.org/xml/features/validation/schema";
64
65    protected static final String APACHE_DYNAMIC_VALIDATION_FEATURE =
66        "http://apache.org/xml/features/validation/dynamic";
67
68    protected ContentHandler contentHandler = new DefaultHandler();
69    protected ErrorHandler errorHandler = new DefaultHandler();;
70
71    protected String systemId;
72
73    protected XmlPullParser pp;
74
75    //private final static boolean DEBUG = false;
76
77    /**
78     */
79    public Driver() throws XmlPullParserException {
80        final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
81        factory.setNamespaceAware(true);
82        pp = factory.newPullParser();
83    }
84
85    public Driver(XmlPullParser pp) throws XmlPullParserException {
86        this.pp = pp;
87    }
88
89    // -- Attributes interface
90
91    public int getLength() { return pp.getAttributeCount(); }
92    public String getURI(int index) { return pp.getAttributeNamespace(index); }
93    public String getLocalName(int index) { return pp.getAttributeName(index); }
94    public String getQName(int index) {
95        final String prefix = pp.getAttributePrefix(index);
96        if(prefix != null) {
97            return prefix+':'+pp.getAttributeName(index);
98        } else {
99            return pp.getAttributeName(index);
100        }
101    }
102    public String getType(int index) { return pp.getAttributeType(index); }
103    public String getValue(int index) { return pp.getAttributeValue(index); }
104
105    public int getIndex(String uri, String localName) {
106        for (int i = 0; i < pp.getAttributeCount(); i++)
107        {
108            if(pp.getAttributeNamespace(i).equals(uri)
109               && pp.getAttributeName(i).equals(localName))
110            {
111                return i;
112            }
113
114        }
115        return -1;
116    }
117
118    public int getIndex(String qName) {
119        for (int i = 0; i < pp.getAttributeCount(); i++)
120        {
121            if(pp.getAttributeName(i).equals(qName))
122            {
123                return i;
124            }
125
126        }
127        return -1;
128    }
129
130    public String getType(String uri, String localName) {
131        for (int i = 0; i < pp.getAttributeCount(); i++)
132        {
133            if(pp.getAttributeNamespace(i).equals(uri)
134               && pp.getAttributeName(i).equals(localName))
135            {
136                return pp.getAttributeType(i);
137            }
138
139        }
140        return null;
141    }
142    public String getType(String qName) {
143        for (int i = 0; i < pp.getAttributeCount(); i++)
144        {
145            if(pp.getAttributeName(i).equals(qName))
146            {
147                return pp.getAttributeType(i);
148            }
149
150        }
151        return null;
152    }
153    public String getValue(String uri, String localName) {
154        return pp.getAttributeValue(uri, localName);
155    }
156    public String getValue(String qName) {
157        return pp.getAttributeValue(null, qName);
158    }
159
160    // -- Locator interface
161
162    public String getPublicId() { return null; }
163    public String getSystemId() { return systemId; }
164    public int getLineNumber() { return pp.getLineNumber(); }
165    public int getColumnNumber() { return pp.getColumnNumber(); }
166
167    // --- XMLReader interface
168
169    public boolean getFeature(String name)
170        throws SAXNotRecognizedException, SAXNotSupportedException
171    {
172        if(NAMESPACES_FEATURE.equals(name)) {
173            return pp.getFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES);
174        } else if(NAMESPACE_PREFIXES_FEATURE.equals(name)) {
175            return pp.getFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES);
176        } else if(VALIDATION_FEATURE.equals(name)) {
177            return pp.getFeature(XmlPullParser.FEATURE_VALIDATION);
178            //        } else if(APACHE_SCHEMA_VALIDATION_FEATURE.equals(name)) {
179            //            return false;  //TODO
180            //        } else if(APACHE_DYNAMIC_VALIDATION_FEATURE.equals(name)) {
181            //            return false; //TODO
182        } else {
183            return pp.getFeature(name);
184            //throw new SAXNotRecognizedException("unrecognized feature "+name);
185        }
186    }
187
188    public void setFeature (String name, boolean value)
189        throws SAXNotRecognizedException, SAXNotSupportedException
190    {
191        try {
192            if(NAMESPACES_FEATURE.equals(name)) {
193                pp.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, value);
194            } else if(NAMESPACE_PREFIXES_FEATURE.equals(name)) {
195                if(pp.getFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES) != value) {
196                    pp.setFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES, value);
197                }
198            } else if(VALIDATION_FEATURE.equals(name)) {
199                pp.setFeature(XmlPullParser.FEATURE_VALIDATION, value);
200                //          } else if(APACHE_SCHEMA_VALIDATION_FEATURE.equals(name)) {
201                //              // can ignore as validation must be false ...
202                //              //              if(true == value) {
203                //              //                  throw new SAXNotSupportedException("schema validation is not supported");
204                //              //              }
205                //          } else if(APACHE_DYNAMIC_VALIDATION_FEATURE.equals(name)) {
206                //              if(true == value) {
207                //                  throw new SAXNotSupportedException("dynamic validation is not supported");
208                //              }
209            } else {
210                pp.setFeature(name, value);
211                //throw new SAXNotRecognizedException("unrecognized feature "+name);
212            }
213        } catch(XmlPullParserException ex) {
214           // throw new SAXNotSupportedException("problem with setting feature "+name+": "+ex);
215        }
216    }
217
218    public Object getProperty (String name)
219        throws SAXNotRecognizedException, SAXNotSupportedException
220    {
221        if(DECLARATION_HANDLER_PROPERTY.equals(name)) {
222            return null;
223        } else if(LEXICAL_HANDLER_PROPERTY.equals(name)) {
224            return null;
225        } else {
226            return pp.getProperty(name);
227            //throw new SAXNotRecognizedException("not recognized get property "+name);
228        }
229    }
230
231    public void setProperty (String name, Object value)
232        throws SAXNotRecognizedException, SAXNotSupportedException
233    {
234        //
235        if(DECLARATION_HANDLER_PROPERTY.equals(name)) {
236            throw new SAXNotSupportedException("not supported setting property "+name);//+" to "+value);
237        } else if(LEXICAL_HANDLER_PROPERTY.equals(name)) {
238            throw new SAXNotSupportedException("not supported setting property "+name);//+" to "+value);
239        } else {
240            try {
241                pp.setProperty(name, value);
242            } catch(XmlPullParserException ex) {
243                throw new SAXNotSupportedException("not supported set property "+name+": "+ ex);
244            }
245            //throw new SAXNotRecognizedException("not recognized set property "+name);
246        }
247    }
248
249    public void setEntityResolver (EntityResolver resolver) {}
250
251    public EntityResolver getEntityResolver () { return null; }
252
253    public void setDTDHandler (DTDHandler handler) {}
254
255    public DTDHandler getDTDHandler () { return null; }
256
257    public void setContentHandler (ContentHandler handler)
258    {
259        this.contentHandler = handler;
260    }
261
262    public ContentHandler getContentHandler() { return contentHandler; }
263
264    public void setErrorHandler(ErrorHandler handler) {
265        this.errorHandler = handler;
266    }
267
268    public ErrorHandler getErrorHandler() { return errorHandler; }
269
270    public void parse(InputSource source) throws SAXException, IOException
271    {
272
273        systemId = source.getSystemId();
274        contentHandler.setDocumentLocator(this);
275
276        final Reader reader = source.getCharacterStream();
277        try {
278            if (reader == null) {
279                InputStream stream = source.getByteStream();
280                final String encoding = source.getEncoding();
281
282                if (stream == null) {
283                    systemId = source.getSystemId();
284                    if(systemId == null) {
285                        SAXParseException saxException = new SAXParseException(
286                            "null source systemId" , this);
287                        errorHandler.fatalError(saxException);
288                        return;
289                    }
290                    // NOTE: replace with Connection to run in J2ME environment
291                    try {
292                        final URL url = new URL(systemId);
293                        stream = url.openStream();
294                    } catch (MalformedURLException nue) {
295                        try {
296                            stream = new FileInputStream(systemId);
297                        } catch (FileNotFoundException fnfe) {
298                            final SAXParseException saxException = new SAXParseException(
299                                "could not open file with systemId "+systemId, this, fnfe);
300                            errorHandler.fatalError(saxException);
301                            return;
302                        }
303                    }
304                }
305                pp.setInput(stream, encoding);
306            } else {
307                pp.setInput(reader);
308            }
309        } catch (XmlPullParserException ex)  {
310            final SAXParseException saxException = new SAXParseException(
311                "parsing initialization error: "+ex, this, ex);
312            //if(DEBUG) ex.printStackTrace();
313            errorHandler.fatalError(saxException);
314            return;
315        }
316
317        // start parsing - move to first start tag
318        try {
319            contentHandler.startDocument();
320            // get first event
321            pp.next();
322            // it should be start tag...
323            if(pp.getEventType() != XmlPullParser.START_TAG) {
324                final SAXParseException saxException = new SAXParseException(
325                    "expected start tag not"+pp.getPositionDescription(), this);
326                //throw saxException;
327                errorHandler.fatalError(saxException);
328                return;
329            }
330        } catch (XmlPullParserException ex)  {
331            final SAXParseException saxException = new SAXParseException(
332                "parsing initialization error: "+ex, this, ex);
333            //ex.printStackTrace();
334            errorHandler.fatalError(saxException);
335            return;
336        }
337
338        // now real parsing can start!
339
340        parseSubTree(pp);
341
342        // and finished ...
343
344        contentHandler.endDocument();
345    }
346
347    public void parse(String systemId) throws SAXException, IOException {
348        parse(new InputSource(systemId));
349    }
350
351
352    public void parseSubTree(XmlPullParser pp) throws SAXException, IOException {
353        this.pp = pp;
354        final boolean namespaceAware = pp.getFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES);
355        try {
356            if(pp.getEventType() != XmlPullParser.START_TAG) {
357                throw new SAXException(
358                    "start tag must be read before skiping subtree"+pp.getPositionDescription());
359            }
360            final int[] holderForStartAndLength = new int[2];
361            final StringBuilder rawName = new StringBuilder(16);
362            String prefix = null;
363            String name = null;
364            int level = pp.getDepth() - 1;
365            int type = XmlPullParser.START_TAG;
366
367            LOOP:
368            do {
369                switch(type) {
370                    case XmlPullParser.START_TAG:
371                        if(namespaceAware) {
372                            final int depth = pp.getDepth() - 1;
373                            final int countPrev =
374                                (level > depth) ? pp.getNamespaceCount(depth) : 0;
375                            //int countPrev = pp.getNamespaceCount(pp.getDepth() - 1);
376                            final int count = pp.getNamespaceCount(depth + 1);
377                            for (int i = countPrev; i < count; i++)
378                            {
379                                contentHandler.startPrefixMapping(
380                                    pp.getNamespacePrefix(i),
381                                    pp.getNamespaceUri(i)
382                                );
383                            }
384                            name = pp.getName();
385                            prefix = pp.getPrefix();
386                            if(prefix != null) {
387                                rawName.setLength(0);
388                                rawName.append(prefix);
389                                rawName.append(':');
390                                rawName.append(name);
391                            }
392                            startElement(pp.getNamespace(),
393                                         name,
394                                         // TODO Fixed this. Was "not equals".
395                                         prefix == null ? name : rawName.toString());
396                        } else {
397                            startElement(pp.getNamespace(),
398                                         pp.getName(),
399                                         pp.getName());
400                        }
401                        //++level;
402
403                        break;
404                    case XmlPullParser.TEXT:
405                        final char[] chars = pp.getTextCharacters(holderForStartAndLength);
406                        contentHandler.characters(chars,
407                                                  holderForStartAndLength[0], //start
408                                                  holderForStartAndLength[1] //len
409                                                 );
410                        break;
411                    case XmlPullParser.END_TAG:
412                        //--level;
413                        if(namespaceAware) {
414                            name = pp.getName();
415                            prefix = pp.getPrefix();
416                            if(prefix != null) {
417                                rawName.setLength(0);
418                                rawName.append(prefix);
419                                rawName.append(':');
420                                rawName.append(name);
421                            }
422                            contentHandler.endElement(pp.getNamespace(),
423                                                      name,
424                                                      prefix != null ? name : rawName.toString()
425                                                     );
426                            // when entering show prefixes for all levels!!!!
427                            final int depth = pp.getDepth();
428                            final int countPrev =
429                                (level > depth) ? pp.getNamespaceCount(pp.getDepth()) : 0;
430                            int count = pp.getNamespaceCount(pp.getDepth() - 1);
431                            // undeclare them in reverse order
432                            for (int i = count - 1; i >= countPrev; i--)
433                            {
434                                contentHandler.endPrefixMapping(
435                                    pp.getNamespacePrefix(i)
436                                );
437                            }
438                        } else {
439                            contentHandler.endElement(pp.getNamespace(),
440                                                      pp.getName(),
441                                                      pp.getName()
442                                                     );
443
444                        }
445                        break;
446                    case XmlPullParser.END_DOCUMENT:
447                        break LOOP;
448                }
449                type = pp.next();
450            } while(pp.getDepth() > level);
451        } catch (XmlPullParserException ex)  {
452            final SAXParseException saxException = new SAXParseException("parsing error: "+ex, this, ex);
453            ex.printStackTrace();
454            errorHandler.fatalError(saxException);
455        }
456    }
457
458    /**
459     * Calls {@link ContentHandler#startElement(String, String, String, Attributes) startElement}
460     * on the <code>ContentHandler</code> with <code>this</code> driver object as the
461     * {@link Attributes} implementation. In default implementation
462     * {@link Attributes} object is valid only during this method call and may not
463     * be stored. Sub-classes can overwrite this method to cache attributes.
464     */
465    protected void startElement(String namespace, String localName, String qName) throws SAXException {
466        contentHandler.startElement(namespace, localName, qName, this);
467    }
468
469}
470