// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.omaha; import android.text.TextUtils; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; /** * Breaks XML down into its constituent elements and attributes. */ public class XMLParser extends DefaultHandler { static final class Node { public final String tag; public final Map<String, String> attributes; public final List<Node> children; public Node(String tagName) { tag = tagName; attributes = new HashMap<String, String>(); children = new ArrayList<Node>(); } } private final Node mRootNode; private final Stack<Node> mTagStack; public XMLParser(String serverResponse) throws RequestFailureException { mRootNode = new Node(null); mTagStack = new Stack<Node>(); mTagStack.push(mRootNode); try { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); saxParser.parse(new InputSource(new StringReader(serverResponse)), this); } catch (IOException e) { throw new RequestFailureException( "Hit IOException", e, RequestFailureException.ERROR_MALFORMED_XML); } catch (ParserConfigurationException e) { throw new RequestFailureException("Hit ParserConfigurationException", e, RequestFailureException.ERROR_MALFORMED_XML); } catch (SAXParseException e) { throw new RequestFailureException( "Hit SAXParseException", e, RequestFailureException.ERROR_MALFORMED_XML); } catch (SAXException e) { throw new RequestFailureException( "Hit SAXException", e, RequestFailureException.ERROR_MALFORMED_XML); } if (mTagStack.peek() != mRootNode) { throw new RequestFailureException( "XML was malformed.", RequestFailureException.ERROR_MALFORMED_XML); } } public Node getRootNode() { return mRootNode; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (mTagStack.empty()) throw new SAXException("Tag stack is empty when it shouldn't be."); Node currentNode = new Node(qName); mTagStack.peek().children.add(currentNode); mTagStack.push(currentNode); for (int i = 0; i < attributes.getLength(); ++i) { String attributeName = attributes.getLocalName(i); String attributeValue = attributes.getValue(attributeName); currentNode.attributes.put(attributeName, attributeValue); } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (mTagStack.empty()) { throw new SAXException("Tried closing empty stack with " + qName); } else if (!TextUtils.equals(qName, mTagStack.peek().tag)) { throw new SAXException("Tried closing " + mTagStack.peek().tag + " with " + qName); } mTagStack.pop(); } }