Home | History | Annotate | Download | only in sax
      1 /*
      2  * Copyright (C) 2007 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 android.sax;
     18 
     19 import org.xml.sax.helpers.DefaultHandler;
     20 import org.xml.sax.Attributes;
     21 import org.xml.sax.SAXException;
     22 import org.xml.sax.ContentHandler;
     23 import org.xml.sax.Locator;
     24 
     25 /**
     26  * The root XML element. The entry point for this API. Not safe for concurrent
     27  * use.
     28  *
     29  * <p>For example, passing this XML:
     30  *
     31  * <pre>
     32  * &lt;feed xmlns='http://www.w3.org/2005/Atom'>
     33  *   &lt;entry>
     34  *     &lt;id>bob&lt;/id>
     35  *   &lt;/entry>
     36  * &lt;/feed>
     37  * </pre>
     38  *
     39  * to this code:
     40  *
     41  * <pre>
     42  * static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
     43  *
     44  * ...
     45  *
     46  * RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
     47  * Element entry = root.getChild(ATOM_NAMESPACE, "entry");
     48  * entry.getChild(ATOM_NAMESPACE, "id").setEndTextElementListener(
     49  *   new EndTextElementListener() {
     50  *     public void end(String body) {
     51  *       System.out.println("Entry ID: " + body);
     52  *     }
     53  *   });
     54  *
     55  * XMLReader reader = ...;
     56  * reader.setContentHandler(root.getContentHandler());
     57  * reader.parse(...);
     58  * </pre>
     59  *
     60  * would output:
     61  *
     62  * <pre>
     63  * Entry ID: bob
     64  * </pre>
     65  */
     66 public class RootElement extends Element {
     67 
     68     final Handler handler = new Handler();
     69 
     70     /**
     71      * Constructs a new root element with the given name.
     72      *
     73      * @param uri the namespace
     74      * @param localName the local name
     75      */
     76     public RootElement(String uri, String localName) {
     77         super(null, uri, localName, 0);
     78     }
     79 
     80     /**
     81      * Constructs a new root element with the given name. Uses an empty string
     82      * as the namespace.
     83      *
     84      * @param localName the local name
     85      */
     86     public RootElement(String localName) {
     87         this("", localName);
     88     }
     89 
     90     /**
     91      * Gets the SAX {@code ContentHandler}. Pass this to your SAX parser.
     92      */
     93     public ContentHandler getContentHandler() {
     94         return this.handler;
     95     }
     96 
     97     class Handler extends DefaultHandler {
     98 
     99         Locator locator;
    100         int depth = -1;
    101         Element current = null;
    102         StringBuilder bodyBuilder = null;
    103 
    104         @Override
    105         public void setDocumentLocator(Locator locator) {
    106             this.locator = locator;
    107         }
    108 
    109         @Override
    110         public void startElement(String uri, String localName, String qName,
    111                 Attributes attributes) throws SAXException {
    112             int depth = ++this.depth;
    113 
    114             if (depth == 0) {
    115                 // This is the root element.
    116                 startRoot(uri, localName, attributes);
    117                 return;
    118             }
    119 
    120             // Prohibit mixed text and elements.
    121             if (bodyBuilder != null) {
    122                 throw new BadXmlException("Encountered mixed content"
    123                         + " within text element named " + current + ".",
    124                         locator);
    125             }
    126 
    127             // If we're one level below the current element.
    128             if (depth == current.depth + 1) {
    129                 // Look for a child to push onto the stack.
    130                 Children children = current.children;
    131                 if (children != null) {
    132                     Element child = children.get(uri, localName);
    133                     if (child != null) {
    134                         start(child, attributes);
    135                     }
    136                 }
    137             }
    138         }
    139 
    140         void startRoot(String uri, String localName, Attributes attributes)
    141                 throws SAXException {
    142             Element root = RootElement.this;
    143             if (root.uri.compareTo(uri) != 0
    144                     || root.localName.compareTo(localName) != 0) {
    145                 throw new BadXmlException("Root element name does"
    146                         + " not match. Expected: " + root + ", Got: "
    147                         + Element.toString(uri, localName), locator);
    148             }
    149 
    150             start(root, attributes);
    151         }
    152 
    153         void start(Element e, Attributes attributes) {
    154             // Push element onto the stack.
    155             this.current = e;
    156 
    157             if (e.startElementListener != null) {
    158                 e.startElementListener.start(attributes);
    159             }
    160 
    161             if (e.endTextElementListener != null) {
    162                 this.bodyBuilder = new StringBuilder();
    163             }
    164 
    165             e.resetRequiredChildren();
    166             e.visited = true;
    167         }
    168 
    169         @Override
    170         public void characters(char[] buffer, int start, int length)
    171                 throws SAXException {
    172             if (bodyBuilder != null) {
    173                 bodyBuilder.append(buffer, start, length);
    174             }
    175         }
    176 
    177         @Override
    178         public void endElement(String uri, String localName, String qName)
    179                 throws SAXException {
    180             Element current = this.current;
    181 
    182             // If we've ended the current element...
    183             if (depth == current.depth) {
    184                 current.checkRequiredChildren(locator);
    185 
    186                 // Invoke end element listener.
    187                 if (current.endElementListener != null) {
    188                     current.endElementListener.end();
    189                 }
    190 
    191                 // Invoke end text element listener.
    192                 if (bodyBuilder != null) {
    193                     String body = bodyBuilder.toString();
    194                     bodyBuilder = null;
    195 
    196                     // We can assume that this listener is present.
    197                     current.endTextElementListener.end(body);
    198                 }
    199 
    200                 // Pop element off the stack.
    201                 this.current = current.parent;
    202             }
    203 
    204             depth--;
    205         }
    206     }
    207 }
    208