android-ibc-forum

Unnamed repository; edit this file 'description' to name the repository.
git clone http://git.code.weiherhei.de/android-ibc-forum.git
Log | Files | Refs

RSSHandler.java (9343B)


      1 /*
      2  * Copyright (C) 2010 A. Horn
      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.mcsoxford.rss;
     18 
     19 /**
     20  * Internal SAX handler to efficiently parse RSS feeds. Only a single thread
     21  * must use this SAX handler.
     22  * 
     23  * @author Mr Horn
     24  */
     25 class RSSHandler extends org.xml.sax.helpers.DefaultHandler {
     26 
     27   /**
     28    * Constant for XML element name which identifies RSS items.
     29    */
     30   private static final String RSS_ITEM = "item";
     31 
     32   /**
     33    * Constant symbol table to ensure efficient treatment of handler states.
     34    */
     35   private final java.util.Map<String, Setter> setters;
     36 
     37   /**
     38    * Reference is never {@code null}. Visibility must be package-private to
     39    * ensure efficiency of inner classes.
     40    */
     41   final RSSFeed feed = new RSSFeed();
     42 
     43   /**
     44    * Reference is {@code null} unless started to parse &lt;item&gt; element.
     45    * Visibility must be package-private to ensure efficiency of inner classes.
     46    */
     47   RSSItem item;
     48 
     49   /**
     50    * If not {@code null}, then buffer the characters inside an XML text element.
     51    */
     52   private StringBuilder buffer;
     53 
     54   /**
     55    * Dispatcher to set either {@link #feed} or {@link #item} fields.
     56    */
     57   private Setter setter;
     58 
     59   /**
     60    * Interface to store information about RSS elements.
     61    */
     62   private static interface Setter {}
     63 
     64   /**
     65    * Closure to change fields in POJOs which store RSS content.
     66    */
     67   private static interface ContentSetter extends Setter {
     68 
     69     /**
     70      * Set the field of an object which represents an RSS element.
     71      */
     72     void set(String value);
     73 
     74   }
     75 
     76   /**
     77    * Closure to change fields in POJOs which store information
     78    * about RSS elements which have only attributes.
     79    */
     80   private static interface AttributeSetter extends Setter {
     81 
     82     /**
     83      * Set the XML attributes.
     84      */
     85     void set(org.xml.sax.Attributes attributes);
     86 
     87   }
     88 
     89   /**
     90    * Setter for RSS &lt;title&gt; elements inside a &lt;channel&gt; or an
     91    * &lt;item&gt; element. The title of the RSS feed is set only if
     92    * {@link #item} is {@code null}. Otherwise, the title of the RSS
     93    * {@link #item} is set.
     94    */
     95   private final Setter SET_TITLE = new ContentSetter() {
     96     @Override
     97     public void set(String title) {
     98       if (item == null) {
     99         feed.setTitle(title);
    100       } else {
    101         item.setTitle(title);
    102       }
    103     }
    104   };
    105 
    106   /**
    107    * Setter for RSS &lt;description&gt; elements inside a &lt;channel&gt; or an
    108    * &lt;item&gt; element. The title of the RSS feed is set only if
    109    * {@link #item} is {@code null}. Otherwise, the title of the RSS
    110    * {@link #item} is set.
    111    */
    112   private final Setter SET_DESCRIPTION = new ContentSetter() {
    113     @Override
    114     public void set(String description) {
    115       if (item == null) {
    116         feed.setDescription(description);
    117       } else {
    118         item.setDescription(description);
    119       }
    120     }
    121   };
    122   
    123   /**
    124    * Setter for an RSS &lt;content:encoded&gt; element inside an &lt;item&gt;
    125    * element.
    126    */
    127   private final Setter SET_CONTENT = new ContentSetter() {
    128     @Override
    129     public void set(String content) {
    130       if (item != null) {
    131         item.setFullContent(content);
    132       }
    133     }
    134   };
    135 
    136   /**
    137    * Setter for RSS &lt;link&gt; elements inside a &lt;channel&gt; or an
    138    * &lt;item&gt; element. The title of the RSS feed is set only if
    139    * {@link #item} is {@code null}. Otherwise, the title of the RSS
    140    * {@link #item} is set.
    141    */
    142   private final Setter SET_LINK = new ContentSetter() {
    143     @Override
    144     public void set(String link) {
    145       final android.net.Uri uri = android.net.Uri.parse(link);
    146       if (item == null) {
    147         feed.setLink(uri);
    148       } else {
    149         item.setLink(uri);
    150       }
    151     }
    152   };
    153 
    154   /**
    155    * Setter for RSS &lt;pubDate&gt; elements inside a &lt;channel&gt; or an
    156    * &lt;item&gt; element. The title of the RSS feed is set only if
    157    * {@link #item} is {@code null}. Otherwise, the title of the RSS
    158    * {@link #item} is set.
    159    */
    160   private final Setter SET_PUBDATE = new ContentSetter() {
    161     @Override
    162     public void set(String pubDate) {
    163       final java.util.Date date = Dates.parseRfc822(pubDate);
    164       if (item == null) {
    165         feed.setPubDate(date);
    166       } else {
    167         item.setPubDate(date);
    168       }
    169     }
    170   };
    171 
    172   /**
    173    * Setter for one or multiple RSS &lt;category&gt; elements inside a
    174    * &lt;channel&gt; or an &lt;item&gt; element. The title of the RSS feed is
    175    * set only if {@link #item} is {@code null}. Otherwise, the title of the RSS
    176    * {@link #item} is set.
    177    */
    178   private final Setter ADD_CATEGORY = new ContentSetter() {
    179 
    180     @Override
    181     public void set(String category) {
    182       if (item == null) {
    183         feed.addCategory(category);
    184       } else {
    185         item.addCategory(category);
    186       }
    187     }
    188   };
    189 
    190   /**
    191    * Setter for one or multiple RSS &lt;media:thumbnail&gt; elements inside an
    192    * &lt;item&gt; element. The thumbnail element has only attributes. Both its
    193    * height and width are optional. Invalid elements are ignored.
    194    */
    195   private final Setter ADD_MEDIA_THUMBNAIL = new AttributeSetter() {
    196 
    197     private static final String MEDIA_THUMBNAIL_HEIGHT = "height";
    198     private static final String MEDIA_THUMBNAIL_WIDTH = "width";
    199     private static final String MEDIA_THUMBNAIL_URL = "url";
    200     private static final int DEFAULT_DIMENSION = -1;
    201 
    202     @Override
    203     public void set(org.xml.sax.Attributes attributes) {
    204       if (item == null) {
    205         // ignore invalid media:thumbnail elements which are not inside item
    206         // elements
    207         return;
    208       }
    209 
    210       final int height = MediaAttributes.intValue(attributes, MEDIA_THUMBNAIL_HEIGHT, DEFAULT_DIMENSION);
    211       final int width = MediaAttributes.intValue(attributes, MEDIA_THUMBNAIL_WIDTH, DEFAULT_DIMENSION);
    212       final String url = MediaAttributes.stringValue(attributes, MEDIA_THUMBNAIL_URL);
    213 
    214       if (url == null) {
    215         // ignore invalid media:thumbnail elements which have no URL.
    216         return;
    217       }
    218 
    219       item.addThumbnail(new MediaThumbnail(android.net.Uri.parse(url), height, width));
    220     }
    221 
    222   };
    223 
    224   /**
    225    * Use configuration to optimize initial capacities of collections
    226    */
    227   private final RSSConfig config;
    228 
    229   /**
    230    * Instantiate a SAX handler which can parse a subset of RSS 2.0 feeds.
    231    * 
    232    * @param config configuration for the initial capacities of collections
    233    */
    234   RSSHandler(RSSConfig config) {
    235     this.config = config;
    236 
    237     // initialize dispatchers to manage the state of the SAX handler
    238     setters = new java.util.HashMap<String, Setter>(/* 2^3 */8);
    239     setters.put("title", SET_TITLE);
    240     setters.put("description", SET_DESCRIPTION);
    241     setters.put("content:encoded", SET_CONTENT);
    242     setters.put("link", SET_LINK);
    243     setters.put("category", ADD_CATEGORY);
    244     setters.put("pubDate", SET_PUBDATE);
    245     setters.put("media:thumbnail", ADD_MEDIA_THUMBNAIL);
    246   }
    247 
    248   /**
    249    * Returns the RSS feed after this SAX handler has processed the XML document.
    250    */
    251   RSSFeed feed() {
    252     return feed;
    253   }
    254 
    255   /**
    256    * Identify the appropriate dispatcher which should be used to store XML data
    257    * in a POJO. Unsupported RSS 2.0 elements are currently ignored.
    258    */
    259   @Override
    260   public void startElement(String nsURI, String localName, String qname,
    261       org.xml.sax.Attributes attributes) {
    262     // Lookup dispatcher in hash table
    263     setter = setters.get(qname);
    264     if (setter == null) {
    265       if (RSS_ITEM.equals(qname)) {
    266         item = new RSSItem(config.categoryAvg, config.thumbnailAvg);
    267       }
    268     } else if (setter instanceof AttributeSetter) {
    269       ((AttributeSetter) setter).set(attributes);
    270     } else {
    271       // Buffer supported RSS content data
    272       buffer = new StringBuilder();
    273     }
    274   }
    275 
    276   @Override
    277   public void endElement(String nsURI, String localName, String qname) {
    278     if (isBuffering()) {
    279       // set field of an RSS feed or RSS item
    280       ((ContentSetter) setter).set(buffer.toString());
    281 
    282       // clear buffer
    283       buffer = null;
    284     } else if (RSS_ITEM.equals(qname)) {
    285       feed.addItem(item);
    286 
    287       // (re)enter <channel> scope
    288       item = null;
    289     }
    290   }
    291 
    292   @Override
    293   public void characters(char ch[], int start, int length) {
    294     if (isBuffering()) {
    295       buffer.append(ch, start, length);
    296     }
    297   }
    298 
    299   /**
    300    * Determines if the SAX parser is ready to receive data inside an XML element
    301    * such as &lt;title&gt; or &lt;description&gt;.
    302    * 
    303    * @return boolean {@code true} if the SAX handler parses data inside an XML
    304    *         element, {@code false} otherwise
    305    */
    306   boolean isBuffering() {
    307     return buffer != null && setter != null;
    308   }
    309 
    310 }
    311