FeedParser.java (10690B)
1 package de.nordgedanken.rpicms.basicsyncadapter.net; 2 3 /** 4 * Created by frank on 13.10.14. 5 */ 6 import android.text.format.Time; 7 import android.util.Xml; 8 9 import org.xmlpull.v1.XmlPullParser; 10 import org.xmlpull.v1.XmlPullParserException; 11 12 import java.io.IOException; 13 import java.io.InputStream; 14 import java.text.ParseException; 15 import java.util.ArrayList; 16 import java.util.List; 17 public class FeedParser { 18 19 20 /** 21 * This class parses generic Atom feeds. 22 * 23 * <p>Given an InputStream representation of a feed, it returns a List of entries, 24 * where each list element represents a single entry (post) in the XML feed. 25 * 26 * <p>An example of an Atom feed can be found at: 27 * http://en.wikipedia.org/w/index.php?title=Atom_(standard)&oldid=560239173#Example_of_an_Atom_1.0_feed 28 */ 29 30 // Constants indicting XML element names that we're interested in 31 private static final int TAG_ID = 1; 32 private static final int TAG_TITLE = 2; 33 private static final int TAG_PUBLISHED = 3; 34 private static final int TAG_LINK = 4; 35 36 // We don't use XML namespaces 37 private static final String ns = null; 38 39 /** Parse an Atom feed, returning a collection of Entry objects. 40 * 41 * @param in Atom feed, as a stream. 42 * @return List of {@link de.nordgedanken.rpicms.basicsyncadapter.net.FeedParser.Entry} objects. 43 * @throws org.xmlpull.v1.XmlPullParserException on error parsing feed. 44 * @throws java.io.IOException on I/O error. 45 */ 46 public List<Entry> parse(InputStream in) 47 throws XmlPullParserException, IOException, ParseException { 48 try { 49 XmlPullParser parser = Xml.newPullParser(); 50 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); 51 parser.setInput(in, null); 52 parser.nextTag(); 53 return readFeed(parser); 54 } finally { 55 in.close(); 56 } 57 } 58 59 /** 60 * Decode a feed attached to an XmlPullParser. 61 * 62 * @param parser Incoming XMl 63 * @return List of {@link de.nordgedanken.rpicms.basicsyncadapter.net.FeedParser.Entry} objects. 64 * @throws org.xmlpull.v1.XmlPullParserException on error parsing feed. 65 * @throws java.io.IOException on I/O error. 66 */ 67 private List<Entry> readFeed(XmlPullParser parser) 68 throws XmlPullParserException, IOException, ParseException { 69 List<Entry> entries = new ArrayList<Entry>(); 70 71 // Search for <feed> tags. These wrap the beginning/end of an Atom document. 72 // 73 // Example: 74 // <?xml version="1.0" encoding="utf-8"?> 75 // <feed xmlns="http://www.w3.org/2005/Atom"> 76 // ... 77 // </feed> 78 parser.require(XmlPullParser.START_TAG, ns, "feed"); 79 while (parser.next() != XmlPullParser.END_TAG) { 80 if (parser.getEventType() != XmlPullParser.START_TAG) { 81 continue; 82 } 83 String name = parser.getName(); 84 // Starts by looking for the <entry> tag. This tag repeates inside of <feed> for each 85 // article in the feed. 86 // 87 // Example: 88 // <entry> 89 // <title>Article title</title> 90 // <link rel="alternate" type="text/html" href="http://example.com/article/1234"/> 91 // <link rel="edit" href="http://example.com/admin/article/1234"/> 92 // <id>urn:uuid:218AC159-7F68-4CC6-873F-22AE6017390D</id> 93 // <published>2003-06-27T12:00:00Z</published> 94 // <updated>2003-06-28T12:00:00Z</updated> 95 // <summary>Article summary goes here.</summary> 96 // <author> 97 // <name>Rick Deckard</name> 98 // <email>deckard@example.com</email> 99 // </author> 100 // </entry> 101 if (name.equals("entry")) { 102 entries.add(readEntry(parser)); 103 } else { 104 skip(parser); 105 } 106 } 107 return entries; 108 } 109 110 /** 111 * Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them 112 * off to their respective "read" methods for processing. Otherwise, skips the tag. 113 */ 114 private Entry readEntry(XmlPullParser parser) 115 throws XmlPullParserException, IOException, ParseException { 116 parser.require(XmlPullParser.START_TAG, ns, "entry"); 117 String id = null; 118 String title = null; 119 String link = null; 120 long publishedOn = 0; 121 122 while (parser.next() != XmlPullParser.END_TAG) { 123 if (parser.getEventType() != XmlPullParser.START_TAG) { 124 continue; 125 } 126 String name = parser.getName(); 127 if (name.equals("id")){ 128 // Example: <id>urn:uuid:218AC159-7F68-4CC6-873F-22AE6017390D</id> 129 id = readTag(parser, TAG_ID); 130 } else if (name.equals("title")) { 131 // Example: <title>Article title</title> 132 title = readTag(parser, TAG_TITLE); 133 } else if (name.equals("link")) { 134 // Example: <link rel="alternate" type="text/html" href="http://example.com/article/1234"/> 135 // 136 // Multiple link types can be included. readAlternateLink() will only return 137 // non-null when reading an "alternate"-type link. Ignore other responses. 138 String tempLink = readTag(parser, TAG_LINK); 139 if (tempLink != null) { 140 link = tempLink; 141 } 142 } else if (name.equals("published")) { 143 // Example: <published>2003-06-27T12:00:00Z</published> 144 Time t = new Time(); 145 t.parse3339(readTag(parser, TAG_PUBLISHED)); 146 publishedOn = t.toMillis(false); 147 } else { 148 skip(parser); 149 } 150 } 151 return new Entry(id, title, link, publishedOn); 152 } 153 154 /** 155 * Process an incoming tag and read the selected value from it. 156 */ 157 private String readTag(XmlPullParser parser, int tagType) 158 throws IOException, XmlPullParserException { 159 String tag = null; 160 String endTag = null; 161 162 switch (tagType) { 163 case TAG_ID: 164 return readBasicTag(parser, "id"); 165 case TAG_TITLE: 166 return readBasicTag(parser, "title"); 167 case TAG_PUBLISHED: 168 return readBasicTag(parser, "published"); 169 case TAG_LINK: 170 return readAlternateLink(parser); 171 default: 172 throw new IllegalArgumentException("Unknown tag type: " + tagType); 173 } 174 } 175 176 /** 177 * Reads the body of a basic XML tag, which is guaranteed not to contain any nested elements. 178 * 179 * <p>You probably want to call readTag(). 180 * 181 * @param parser Current parser object 182 * @param tag XML element tag name to parse 183 * @return Body of the specified tag 184 * @throws java.io.IOException 185 * @throws org.xmlpull.v1.XmlPullParserException 186 */ 187 private String readBasicTag(XmlPullParser parser, String tag) 188 throws IOException, XmlPullParserException { 189 parser.require(XmlPullParser.START_TAG, ns, tag); 190 String result = readText(parser); 191 parser.require(XmlPullParser.END_TAG, ns, tag); 192 return result; 193 } 194 195 /** 196 * Processes link tags in the feed. 197 */ 198 private String readAlternateLink(XmlPullParser parser) 199 throws IOException, XmlPullParserException { 200 String link = null; 201 parser.require(XmlPullParser.START_TAG, ns, "link"); 202 String tag = parser.getName(); 203 String relType = parser.getAttributeValue(null, "rel"); 204 if (relType.equals("alternate")) { 205 link = parser.getAttributeValue(null, "href"); 206 } 207 while (true) { 208 if (parser.nextTag() == XmlPullParser.END_TAG) break; 209 // Intentionally break; consumes any remaining sub-tags. 210 } 211 return link; 212 } 213 214 /** 215 * For the tags title and summary, extracts their text values. 216 */ 217 private String readText(XmlPullParser parser) throws IOException, XmlPullParserException { 218 String result = null; 219 if (parser.next() == XmlPullParser.TEXT) { 220 result = parser.getText(); 221 parser.nextTag(); 222 } 223 return result; 224 } 225 226 /** 227 * Skips tags the parser isn't interested in. Uses depth to handle nested tags. i.e., 228 * if the next tag after a START_TAG isn't a matching END_TAG, it keeps going until it 229 * finds the matching END_TAG (as indicated by the value of "depth" being 0). 230 */ 231 private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { 232 if (parser.getEventType() != XmlPullParser.START_TAG) { 233 throw new IllegalStateException(); 234 } 235 int depth = 1; 236 while (depth != 0) { 237 switch (parser.next()) { 238 case XmlPullParser.END_TAG: 239 depth--; 240 break; 241 case XmlPullParser.START_TAG: 242 depth++; 243 break; 244 } 245 } 246 } 247 248 /** 249 * This class represents a single entry (post) in the XML feed. 250 * 251 * <p>It includes the data members "title," "link," and "summary." 252 */ 253 public static class Entry { 254 public final String id; 255 public final String title; 256 public final String link; 257 public final long published; 258 259 Entry(String id, String title, String link, long published) { 260 this.id = id; 261 this.title = title; 262 this.link = link; 263 this.published = published; 264 } 265 } 266 267 }