/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.llamacorp.equate.unit; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Locale; /** * This class parses XML feeds from * http://finance.yahoo.com/webservice/v1/symbols/allcurrencies/quote */ public class YahooXmlParser { // We don't use namespaces private static final String ns = null; public HashMap<String, Entry> parse(InputStream in) throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(in, null); parser.nextTag(); return findResources(parser); } finally { in.close(); } } /** * Iterates over the top level XML branch called list, looking for the first * "resources" tag. Note that we only expect to find one. * * @param parser XML parser from Yahoo * @return a HashMap of entries * @throws XmlPullParserException * @throws IOException */ private HashMap<String, Entry> findResources(XmlPullParser parser) throws XmlPullParserException, IOException { HashMap<String, Entry> entries = new HashMap<>(); parser.require(XmlPullParser.START_TAG, ns, "list"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG){ continue; } String name = parser.getName(); // Starts by looking for the resources tag if (name.equals("resources")){ entries = readListOfResources(parser); } else { skip(parser); } } return entries; } /** * Iterates through each "resource" tag in the "resources" parent. Each * currency should have it's own "resource" tag. */ private HashMap<String, Entry> readListOfResources(XmlPullParser parser) throws XmlPullParserException, IOException { HashMap<String, Entry> entries = new HashMap<>(); parser.require(XmlPullParser.START_TAG, ns, "resources"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG){ continue; } String name = parser.getName(); // Starts by looking for the resource tag if (name.equals("resource")){ if (parser.isEmptyElementTag()){ skip(parser); } Entry ent = readResource(parser); if (ent != null){ entries.put(ent.symbol, ent); } } else { skip(parser); } } return entries; } /** * Parses the contents of a currency entry. If it encounters a price, * summary, or symbol tag, hands them off to their respective methods for * processing. Otherwise, skips the tag. * * @throws XmlPullParserException * @throws IOException */ private Entry readResource(XmlPullParser parser) throws XmlPullParserException, IOException { parser.require(XmlPullParser.START_TAG, ns, "resource"); String price = null; String symbol = null; Date date = null; while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG){ continue; } //read in name attribute (ie in <field name="symbol">XAG=X</field> //this will be "symbol") String name = parser.getAttributeValue(null, "name"); switch (name) { case "price": price = readPrice(parser); break; case "symbol": symbol = readSymbol(parser); break; case "utctime": SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); try { date = sdf.parse(readSymbol(parser)); } catch (ParseException e) { e.printStackTrace(); } break; default: skip(parser); break; } } if (price == null || symbol == null || date == null){ return null; } return new Entry(Double.parseDouble(price), symbol, date); } // Processes price tags in the feed. private String readPrice(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, ns, "field"); String price = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "field"); return price; } // Processes summary tags in the feed. private String readSymbol(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, ns, "field"); String symbol = readText(parser); //symbol seems to have "=X" after, cut this off symbol = symbol.replace("=X", ""); parser.require(XmlPullParser.END_TAG, ns, "field"); return symbol; } // For the tags price and summary, extracts their text values. private String readText(XmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT){ result = parser.getText(); parser.nextTag(); } return result; } // Skips tags the parser isn't interested in. Uses depth to handle nested tags. i.e., // if the next tag after a START_TAG isn't a matching END_TAG, it keeps going until it // finds the matching END_TAG (as indicated by the value of "depth" being 0). private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { if (parser.getEventType() != XmlPullParser.START_TAG){ throw new IllegalStateException(); } int depth = 1; while (depth != 0) { switch (parser.next()) { case XmlPullParser.END_TAG: depth--; break; case XmlPullParser.START_TAG: depth++; break; } } } static class Entry { final double price; final String symbol; final Date date; private Entry(double price, String symbol, Date date) { this.price = price; this.symbol = symbol; this.date = date; } } }