1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.example.android.networkusage;
16 
17 import android.util.Xml;
18 
19 import org.xmlpull.v1.XmlPullParser;
20 import org.xmlpull.v1.XmlPullParserException;
21 
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.List;
26 
27 /**
28  * This class parses XML feeds from stackoverflow.com.
29  * Given an InputStream representation of a feed, it returns a List of entries,
30  * where each list element represents a single entry (post) in the XML feed.
31  */
32 public class StackOverflowXmlParser {
33     private static final String ns = null;
34 
35     // We don't use namespaces
36 
parse(InputStream in)37     public List<Entry> parse(InputStream in) throws XmlPullParserException, IOException {
38         try {
39             XmlPullParser parser = Xml.newPullParser();
40             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
41             parser.setInput(in, null);
42             parser.nextTag();
43             return readFeed(parser);
44         } finally {
45             in.close();
46         }
47     }
48 
readFeed(XmlPullParser parser)49     private List<Entry> readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
50         List<Entry> entries = new ArrayList<Entry>();
51 
52         parser.require(XmlPullParser.START_TAG, ns, "feed");
53         while (parser.next() != XmlPullParser.END_TAG) {
54             if (parser.getEventType() != XmlPullParser.START_TAG) {
55                 continue;
56             }
57             String name = parser.getName();
58             // Starts by looking for the entry tag
59             if (name.equals("entry")) {
60                 entries.add(readEntry(parser));
61             } else {
62                 skip(parser);
63             }
64         }
65         return entries;
66     }
67 
68     // This class represents a single entry (post) in the XML feed.
69     // It includes the data members "title," "link," and "summary."
70     public static class Entry {
71         public final String title;
72         public final String link;
73         public final String summary;
74 
Entry(String title, String summary, String link)75         private Entry(String title, String summary, String link) {
76             this.title = title;
77             this.summary = summary;
78             this.link = link;
79         }
80     }
81 
82     // Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them
83     // off
84     // to their respective &quot;read&quot; methods for processing. Otherwise, skips the tag.
readEntry(XmlPullParser parser)85     private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException {
86         parser.require(XmlPullParser.START_TAG, ns, "entry");
87         String title = null;
88         String summary = null;
89         String link = null;
90         while (parser.next() != XmlPullParser.END_TAG) {
91             if (parser.getEventType() != XmlPullParser.START_TAG) {
92                 continue;
93             }
94             String name = parser.getName();
95             if (name.equals("title")) {
96                 title = readTitle(parser);
97             } else if (name.equals("summary")) {
98                 summary = readSummary(parser);
99             } else if (name.equals("link")) {
100                 link = readLink(parser);
101             } else {
102                 skip(parser);
103             }
104         }
105         return new Entry(title, summary, link);
106     }
107 
108     // Processes title tags in the feed.
readTitle(XmlPullParser parser)109     private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException {
110         parser.require(XmlPullParser.START_TAG, ns, "title");
111         String title = readText(parser);
112         parser.require(XmlPullParser.END_TAG, ns, "title");
113         return title;
114     }
115 
116     // Processes link tags in the feed.
readLink(XmlPullParser parser)117     private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException {
118         String link = "";
119         parser.require(XmlPullParser.START_TAG, ns, "link");
120         String tag = parser.getName();
121         String relType = parser.getAttributeValue(null, "rel");
122         if (tag.equals("link")) {
123             if (relType.equals("alternate")) {
124                 link = parser.getAttributeValue(null, "href");
125                 parser.nextTag();
126             }
127         }
128         parser.require(XmlPullParser.END_TAG, ns, "link");
129         return link;
130     }
131 
132     // Processes summary tags in the feed.
readSummary(XmlPullParser parser)133     private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException {
134         parser.require(XmlPullParser.START_TAG, ns, "summary");
135         String summary = readText(parser);
136         parser.require(XmlPullParser.END_TAG, ns, "summary");
137         return summary;
138     }
139 
140     // For the tags title and summary, extracts their text values.
readText(XmlPullParser parser)141     private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
142         String result = "";
143         if (parser.next() == XmlPullParser.TEXT) {
144             result = parser.getText();
145             parser.nextTag();
146         }
147         return result;
148     }
149 
150     // Skips tags the parser isn't interested in. Uses depth to handle nested tags. i.e.,
151     // if the next tag after a START_TAG isn't a matching END_TAG, it keeps going until it
152     // finds the matching END_TAG (as indicated by the value of "depth" being 0).
skip(XmlPullParser parser)153     private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
154         if (parser.getEventType() != XmlPullParser.START_TAG) {
155             throw new IllegalStateException();
156         }
157         int depth = 1;
158         while (depth != 0) {
159             switch (parser.next()) {
160             case XmlPullParser.END_TAG:
161                     depth--;
162                     break;
163             case XmlPullParser.START_TAG:
164                     depth++;
165                     break;
166             }
167         }
168     }
169 }
170