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
15package com.example.android.networkusage;
16
17import android.util.Xml;
18
19import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import java.io.IOException;
23import java.io.InputStream;
24import java.util.ArrayList;
25import 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 */
32public class StackOverflowXmlParser {
33    private static final String ns = null;
34
35    // We don't use namespaces
36
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
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
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.
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.
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.
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.
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.
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).
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