diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java b/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java new file mode 100644 index 0000000000000..e87698cb7ed11 --- /dev/null +++ b/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2016, 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 android.net.wifi.hotspot2.omadm; + +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class represent a node in an XML tree. Each node is an XML element. + * Used by {@link XMLParser} for parsing/converting each XML element to XMLNode. + * + * @hide + */ +public class XMLNode { + private final String mTag; + private final List mChildren; + private final XMLNode mParent; + private StringBuilder mTextBuilder; + private String mText; + + public XMLNode(XMLNode parent, String tag) { + mTag = tag; + mParent = parent; + mChildren = new ArrayList<>(); + mTextBuilder = new StringBuilder(); + mText = null; + } + + /** + * Adding a text to this node. Invoked by {@link XMLParser#characters}. + * + * @param text String to be added + */ + public void addText(String text) { + mTextBuilder.append(text); + } + + /** + * Adding a child node to this node. Invoked by {@link XMLParser#startElement}. + * + * @param child XMLNode to be added + */ + public void addChild(XMLNode child) { + mChildren.add(child); + } + + /** + * Invoked when the end of the XML element is detected. Used for further processing + * of the text enclosed within this XML element. Invoked by {@link XMLParser#endElement}. + */ + public void close() { + // Remove the leading and the trailing whitespaces. + mText = mTextBuilder.toString().trim(); + mTextBuilder = null; + } + + public String getTag() { + return mTag; + } + + public XMLNode getParent() { + return mParent; + } + + public String getText() { + return mText; + } + + public List getChildren() { + return mChildren; + } + + @Override + public boolean equals(Object thatObject) { + if (this == thatObject) { + return true; + } + if (!(thatObject instanceof XMLNode)) { + return false; + } + XMLNode that = (XMLNode) thatObject; + + return TextUtils.equals(mTag, that.mTag) && + TextUtils.equals(mText, that.mText) && + mChildren.equals(that.mChildren); + } +} diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/XMLParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/XMLParser.java new file mode 100644 index 0000000000000..948052c9bf8a6 --- /dev/null +++ b/wifi/java/android/net/wifi/hotspot2/omadm/XMLParser.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 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 android.net.wifi.hotspot2.omadm; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import android.text.TextUtils; + +import java.io.IOException; +import java.io.StringReader; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * Class for parsing an XML string to an XML tree represented by {@link XMLNode}. + * + * The original XML string: + * + * text1 + * + * text3 + * + * + * + * The XML tree representation: + * [root] + * | + * | + * [tag1, text1]-----|-----[tag2] + * | + * | + * [tag3, text3] + * + * @hide + */ +public class XMLParser extends DefaultHandler { + private XMLNode mRoot = null; + private XMLNode mCurrent = null; + + public XMLNode parse(String text) throws IOException, SAXException { + if (TextUtils.isEmpty(text)) { + throw new IOException("XML string not provided"); + } + + // Reset pointers. + mRoot = null; + mCurrent = null; + + try { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.parse(new InputSource(new StringReader(text)), this); + return mRoot; + } catch (ParserConfigurationException pce) { + throw new SAXException(pce); + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + XMLNode parent = mCurrent; + + mCurrent = new XMLNode(parent, qName); + + if (mRoot == null) { + mRoot = mCurrent; + } else if (parent == null) { + throw new SAXException("More than one root nodes"); + } else { + parent.addChild(mCurrent); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (!qName.equals(mCurrent.getTag())) { + throw new SAXException("End tag '" + qName + "' doesn't match current node: " + + mCurrent); + } + + mCurrent.close(); + mCurrent = mCurrent.getParent(); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + mCurrent.addText(new String(ch, start, length)); + } +} diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/XMLParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/XMLParserTest.java new file mode 100644 index 0000000000000..c2dcec693b83e --- /dev/null +++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/XMLParserTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 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 android.net.wifi.hotspot2.omadm; + +import static org.junit.Assert.assertTrue; + +import android.net.wifi.hotspot2.omadm.XMLNode; +import android.net.wifi.hotspot2.omadm.XMLParser; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.SAXException; + +import java.io.IOException; + +/** + * Unit tests for {@link android.net.wifi.hotspot2.omadm.XMLParser}. + */ +@SmallTest +public class XMLParserTest { + XMLParser mParser; + + private static XMLNode createNode(XMLNode parent, String tag, String text) { + XMLNode node = new XMLNode(parent, tag); + node.addText(text); + if (parent != null) + parent.addChild(node); + node.close(); + return node; + } + + /** + * Setup before tests. + */ + @Before + public void setUp() throws Exception { + mParser = new XMLParser(); + } + + @Test(expected = IOException.class) + public void parseNullXML() throws Exception { + mParser.parse(null); + } + + @Test(expected = IOException.class) + public void parseEmptyXML() throws Exception { + mParser.parse(new String()); + } + + @Test(expected = SAXException.class) + public void parseMalformedXML() throws Exception { + String malformedXmlTree = "test1"; + mParser.parse(malformedXmlTree); + } + + @Test + public void parseValidXMLTree() throws Exception { + String xmlTree = "test1test2"; + + // Construct the expected XML tree. + XMLNode expectedRoot = createNode(null, "root", ""); + createNode(expectedRoot, "child1", "test1"); + createNode(expectedRoot, "child2", "test2"); + + XMLNode actualRoot = mParser.parse(xmlTree); + assertTrue(actualRoot.equals(expectedRoot)); + } +}