M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2007 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.sax;
import org.xml.sax.SAXParseException;
import org.xml.sax.Locator;
/**
* An XML parse exception which includes the line number in the message.
*/
class BadXmlException extends SAXParseException {
public BadXmlException(String message, Locator locator) {
super(message, locator);
}
public String getMessage() {
return "Line " + getLineNumber() + ": " + super.getMessage();
}
}
+97
View File
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2007 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.sax;
/**
* Contains element children. Using this class instead of HashMap results in
* measurably better performance.
*/
class Children {
Child[] children = new Child[16];
/**
* Looks up a child by name and creates a new one if necessary.
*/
Element getOrCreate(Element parent, String uri, String localName) {
int hash = uri.hashCode() * 31 + localName.hashCode();
int index = hash & 15;
Child current = children[index];
if (current == null) {
// We have no children in this bucket yet.
current = new Child(parent, uri, localName, parent.depth + 1, hash);
children[index] = current;
return current;
} else {
// Search this bucket.
Child previous;
do {
if (current.hash == hash
&& current.uri.compareTo(uri) == 0
&& current.localName.compareTo(localName) == 0) {
// We already have a child with that name.
return current;
}
previous = current;
current = current.next;
} while (current != null);
// Add a new child to the bucket.
current = new Child(parent, uri, localName, parent.depth + 1, hash);
previous.next = current;
return current;
}
}
/**
* Looks up a child by name.
*/
Element get(String uri, String localName) {
int hash = uri.hashCode() * 31 + localName.hashCode();
int index = hash & 15;
Child current = children[index];
if (current == null) {
return null;
} else {
do {
if (current.hash == hash
&& current.uri.compareTo(uri) == 0
&& current.localName.compareTo(localName) == 0) {
return current;
}
current = current.next;
} while (current != null);
return null;
}
}
static class Child extends Element {
final int hash;
Child next;
Child(Element parent, String uri, String localName, int depth,
int hash) {
super(parent, uri, localName, depth);
this.hash = hash;
}
}
}
+204
View File
@@ -0,0 +1,204 @@
/*
* Copyright (C) 2007 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.sax;
import org.xml.sax.Locator;
import org.xml.sax.SAXParseException;
import java.util.ArrayList;
import android.util.Log;
/**
* An XML element. Provides access to child elements and hooks to listen
* for events related to this element.
*
* @see RootElement
*/
public class Element {
final String uri;
final String localName;
final int depth;
final Element parent;
Children children;
ArrayList<Element> requiredChilden;
boolean visited;
StartElementListener startElementListener;
EndElementListener endElementListener;
EndTextElementListener endTextElementListener;
Element(Element parent, String uri, String localName, int depth) {
this.parent = parent;
this.uri = uri;
this.localName = localName;
this.depth = depth;
}
/**
* Gets the child element with the given name. Uses an empty string as the
* namespace.
*/
public Element getChild(String localName) {
return getChild("", localName);
}
/**
* Gets the child element with the given name.
*/
public Element getChild(String uri, String localName) {
if (endTextElementListener != null) {
throw new IllegalStateException("This element already has an end"
+ " text element listener. It cannot have children.");
}
if (children == null) {
children = new Children();
}
return children.getOrCreate(this, uri, localName);
}
/**
* Gets the child element with the given name. Uses an empty string as the
* namespace. We will throw a {@link org.xml.sax.SAXException} at parsing
* time if the specified child is missing. This helps you ensure that your
* listeners are called.
*/
public Element requireChild(String localName) {
return requireChild("", localName);
}
/**
* Gets the child element with the given name. We will throw a
* {@link org.xml.sax.SAXException} at parsing time if the specified child
* is missing. This helps you ensure that your listeners are called.
*/
public Element requireChild(String uri, String localName) {
Element child = getChild(uri, localName);
if (requiredChilden == null) {
requiredChilden = new ArrayList<Element>();
requiredChilden.add(child);
} else {
if (!requiredChilden.contains(child)) {
requiredChilden.add(child);
}
}
return child;
}
/**
* Sets start and end element listeners at the same time.
*/
public void setElementListener(ElementListener elementListener) {
setStartElementListener(elementListener);
setEndElementListener(elementListener);
}
/**
* Sets start and end text element listeners at the same time.
*/
public void setTextElementListener(TextElementListener elementListener) {
setStartElementListener(elementListener);
setEndTextElementListener(elementListener);
}
/**
* Sets a listener for the start of this element.
*/
public void setStartElementListener(
StartElementListener startElementListener) {
if (this.startElementListener != null) {
throw new IllegalStateException(
"Start element listener has already been set.");
}
this.startElementListener = startElementListener;
}
/**
* Sets a listener for the end of this element.
*/
public void setEndElementListener(EndElementListener endElementListener) {
if (this.endElementListener != null) {
throw new IllegalStateException(
"End element listener has already been set.");
}
this.endElementListener = endElementListener;
}
/**
* Sets a listener for the end of this text element.
*/
public void setEndTextElementListener(
EndTextElementListener endTextElementListener) {
if (this.endTextElementListener != null) {
throw new IllegalStateException(
"End text element listener has already been set.");
}
if (children != null) {
throw new IllegalStateException("This element already has children."
+ " It cannot have an end text element listener.");
}
this.endTextElementListener = endTextElementListener;
}
@Override
public String toString() {
return toString(uri, localName);
}
static String toString(String uri, String localName) {
return "'" + (uri.equals("") ? localName : uri + ":" + localName) + "'";
}
/**
* Clears flags on required children.
*/
void resetRequiredChildren() {
ArrayList<Element> requiredChildren = this.requiredChilden;
if (requiredChildren != null) {
for (int i = requiredChildren.size() - 1; i >= 0; i--) {
requiredChildren.get(i).visited = false;
}
}
}
/**
* Throws an exception if a required child was not present.
*/
void checkRequiredChildren(Locator locator) throws SAXParseException {
ArrayList<Element> requiredChildren = this.requiredChilden;
if (requiredChildren != null) {
for (int i = requiredChildren.size() - 1; i >= 0; i--) {
Element child = requiredChildren.get(i);
if (!child.visited) {
throw new BadXmlException(
"Element named " + this + " is missing required"
+ " child element named "
+ child + ".", locator);
}
}
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2007 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.sax;
/**
* Listens for the beginning and ending of elements.
*/
public interface ElementListener extends StartElementListener,
EndElementListener {}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2007 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.sax;
/**
* Listens for the end of elements.
*/
public interface EndElementListener {
/**
* Invoked at the end of an element.
*/
void end();
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2007 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.sax;
/**
* Listens for the end of text elements.
*/
public interface EndTextElementListener {
/**
* Invoked at the end of a text element with the body of the element.
*
* @param body of the element
*/
void end(String body);
}
+207
View File
@@ -0,0 +1,207 @@
/*
* Copyright (C) 2007 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.sax;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
/**
* The root XML element. The entry point for this API. Not safe for concurrent
* use.
*
* <p>For example, passing this XML:
*
* <pre>
* &lt;feed xmlns='http://www.w3.org/2005/Atom'>
* &lt;entry>
* &lt;id>bob&lt;/id>
* &lt;/entry>
* &lt;/feed>
* </pre>
*
* to this code:
*
* <pre>
* static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
*
* ...
*
* RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
* Element entry = root.getChild(ATOM_NAMESPACE, "entry");
* entry.getChild(ATOM_NAMESPACE, "id").setEndTextElementListener(
* new EndTextElementListener() {
* public void end(String body) {
* System.out.println("Entry ID: " + body);
* }
* });
*
* XMLReader reader = ...;
* reader.setContentHandler(root.getContentHandler());
* reader.parse(...);
* </pre>
*
* would output:
*
* <pre>
* Entry ID: bob
* </pre>
*/
public class RootElement extends Element {
final Handler handler = new Handler();
/**
* Constructs a new root element with the given name.
*
* @param uri the namespace
* @param localName the local name
*/
public RootElement(String uri, String localName) {
super(null, uri, localName, 0);
}
/**
* Constructs a new root element with the given name. Uses an empty string
* as the namespace.
*
* @param localName the local name
*/
public RootElement(String localName) {
this("", localName);
}
/**
* Gets the SAX {@code ContentHandler}. Pass this to your SAX parser.
*/
public ContentHandler getContentHandler() {
return this.handler;
}
class Handler extends DefaultHandler {
Locator locator;
int depth = -1;
Element current = null;
StringBuilder bodyBuilder = null;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
int depth = ++this.depth;
if (depth == 0) {
// This is the root element.
startRoot(uri, localName, attributes);
return;
}
// Prohibit mixed text and elements.
if (bodyBuilder != null) {
throw new BadXmlException("Encountered mixed content"
+ " within text element named " + current + ".",
locator);
}
// If we're one level below the current element.
if (depth == current.depth + 1) {
// Look for a child to push onto the stack.
Children children = current.children;
if (children != null) {
Element child = children.get(uri, localName);
if (child != null) {
start(child, attributes);
}
}
}
}
void startRoot(String uri, String localName, Attributes attributes)
throws SAXException {
Element root = RootElement.this;
if (root.uri.compareTo(uri) != 0
|| root.localName.compareTo(localName) != 0) {
throw new BadXmlException("Root element name does"
+ " not match. Expected: " + root + ", Got: "
+ Element.toString(uri, localName), locator);
}
start(root, attributes);
}
void start(Element e, Attributes attributes) {
// Push element onto the stack.
this.current = e;
if (e.startElementListener != null) {
e.startElementListener.start(attributes);
}
if (e.endTextElementListener != null) {
this.bodyBuilder = new StringBuilder();
}
e.resetRequiredChildren();
e.visited = true;
}
@Override
public void characters(char[] buffer, int start, int length)
throws SAXException {
if (bodyBuilder != null) {
bodyBuilder.append(buffer, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
Element current = this.current;
// If we've ended the current element...
if (depth == current.depth) {
current.checkRequiredChildren(locator);
// Invoke end element listener.
if (current.endElementListener != null) {
current.endElementListener.end();
}
// Invoke end text element listener.
if (bodyBuilder != null) {
String body = bodyBuilder.toString();
bodyBuilder = null;
// We can assume that this listener is present.
current.endTextElementListener.end(body);
}
// Pop element off the stack.
this.current = current.parent;
}
depth--;
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2007 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.sax;
import org.xml.sax.Attributes;
/**
* Listens for the beginning of elements.
*/
public interface StartElementListener {
/**
* Invoked at the beginning of an element.
*
* @param attributes from the element
*/
void start(Attributes attributes);
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2007 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.sax;
/**
* Listens for the beginning and ending of text elements.
*/
public interface TextElementListener extends StartElementListener,
EndTextElementListener {}
+5
View File
@@ -0,0 +1,5 @@
<html>
<body>
A framework that makes it easy to write efficient and robust SAX handlers.
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksSaxTests
include $(BUILD_PACKAGE)
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.saxtests">
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.frameworks.saxtests"
android:label="Frameworks Sax Tests" />
</manifest>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,125 @@
/*
* Copyright (C) 2007 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.sax;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.util.Xml;
import org.kxml2.io.KXmlParser;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import com.android.frameworks.saxtests.R;
public class ExpatPerformanceTest extends AndroidTestCase {
private static final String TAG = ExpatPerformanceTest.class.getSimpleName();
private byte[] mXmlBytes;
@Override
public void setUp() throws Exception {
super.setUp();
InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
mXmlBytes = out.toByteArray();
Log.i("***", "File size: " + (mXmlBytes.length / 1024) + "k");
}
@LargeTest
public void testPerformance() throws Exception {
// try {
// Debug.startMethodTracing("expat3");
// for (int i = 0; i < 1; i++) {
runJavaPullParser();
runSax();
runExpatPullParser();
// }
// } finally {
// Debug.stopMethodTracing();
// }
}
private InputStream newInputStream() {
return new ByteArrayInputStream(mXmlBytes);
}
private void runSax() throws IOException, SAXException {
long start = System.currentTimeMillis();
Xml.parse(newInputStream(), Xml.Encoding.UTF_8, new DefaultHandler());
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "expat SAX: " + elapsed + "ms");
}
private void runExpatPullParser() throws XmlPullParserException, IOException {
long start = System.currentTimeMillis();
XmlPullParser pullParser = Xml.newPullParser();
pullParser.setInput(newInputStream(), "UTF-8");
withPullParser(pullParser);
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "expat pull: " + elapsed + "ms");
}
private void runJavaPullParser() throws XmlPullParserException, IOException {
XmlPullParser pullParser;
long start = System.currentTimeMillis();
pullParser = new KXmlParser();
pullParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
pullParser.setInput(newInputStream(), "UTF-8");
withPullParser(pullParser);
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "java pull parser: " + elapsed + "ms");
}
private static void withPullParser(XmlPullParser pullParser)
throws IOException, XmlPullParserException {
int eventType = pullParser.next();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
pullParser.getName();
// int nattrs = pullParser.getAttributeCount();
// for (int i = 0; i < nattrs; ++i) {
// pullParser.getAttributeName(i);
// pullParser.getAttributeValue(i);
// }
break;
case XmlPullParser.END_TAG:
pullParser.getName();
break;
case XmlPullParser.TEXT:
pullParser.getText();
break;
}
eventType = pullParser.next();
}
}
}
@@ -0,0 +1,541 @@
/*
* Copyright (C) 2007 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.sax;
import android.graphics.Bitmap;
import android.sax.Element;
import android.sax.ElementListener;
import android.sax.EndTextElementListener;
import android.sax.RootElement;
import android.sax.StartElementListener;
import android.sax.TextElementListener;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.format.Time;
import android.util.Log;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import com.android.frameworks.saxtests.R;
public class SafeSaxTest extends AndroidTestCase {
private static final String TAG = SafeSaxTest.class.getName();
private static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
private static final String MEDIA_NAMESPACE = "http://search.yahoo.com/mrss/";
private static final String YOUTUBE_NAMESPACE = "http://gdata.youtube.com/schemas/2007";
private static final String GDATA_NAMESPACE = "http://schemas.google.com/g/2005";
private static class ElementCounter implements ElementListener {
int starts = 0;
int ends = 0;
public void start(Attributes attributes) {
starts++;
}
public void end() {
ends++;
}
}
private static class TextElementCounter implements TextElementListener {
int starts = 0;
String bodies = "";
public void start(Attributes attributes) {
starts++;
}
public void end(String body) {
this.bodies += body;
}
}
@SmallTest
public void testListener() throws Exception {
String xml = "<feed xmlns='http://www.w3.org/2005/Atom'>\n"
+ "<entry>\n"
+ "<id>a</id>\n"
+ "</entry>\n"
+ "<entry>\n"
+ "<id>b</id>\n"
+ "</entry>\n"
+ "</feed>\n";
RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
Element entry = root.requireChild(ATOM_NAMESPACE, "entry");
Element id = entry.requireChild(ATOM_NAMESPACE, "id");
ElementCounter rootCounter = new ElementCounter();
ElementCounter entryCounter = new ElementCounter();
TextElementCounter idCounter = new TextElementCounter();
root.setElementListener(rootCounter);
entry.setElementListener(entryCounter);
id.setTextElementListener(idCounter);
Xml.parse(xml, root.getContentHandler());
assertEquals(1, rootCounter.starts);
assertEquals(1, rootCounter.ends);
assertEquals(2, entryCounter.starts);
assertEquals(2, entryCounter.ends);
assertEquals(2, idCounter.starts);
assertEquals("ab", idCounter.bodies);
}
@SmallTest
public void testMissingRequiredChild() throws Exception {
String xml = "<feed></feed>";
RootElement root = new RootElement("feed");
root.requireChild("entry");
try {
Xml.parse(xml, root.getContentHandler());
fail("expected exception not thrown");
} catch (SAXException e) {
// Expected.
}
}
@SmallTest
public void testMixedContent() throws Exception {
String xml = "<feed><entry></entry></feed>";
RootElement root = new RootElement("feed");
root.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
}
});
try {
Xml.parse(xml, root.getContentHandler());
fail("expected exception not thrown");
} catch (SAXException e) {
// Expected.
}
}
@LargeTest
public void testPerformance() throws Exception {
InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
byte[] xmlBytes;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
xmlBytes = out.toByteArray();
} finally {
in.close();
}
Log.i("***", "File size: " + (xmlBytes.length / 1024) + "k");
VideoAdapter videoAdapter = new VideoAdapter();
ContentHandler handler = newContentHandler(videoAdapter);
for (int i = 0; i < 2; i++) {
pureSaxTest(new ByteArrayInputStream(xmlBytes));
saxyModelTest(new ByteArrayInputStream(xmlBytes));
saxyModelTest(new ByteArrayInputStream(xmlBytes), handler);
}
}
private static void pureSaxTest(InputStream inputStream) throws IOException, SAXException {
long start = System.currentTimeMillis();
VideoAdapter videoAdapter = new VideoAdapter();
Xml.parse(inputStream, Xml.Encoding.UTF_8, new YouTubeContentHandler(videoAdapter));
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "pure SAX: " + elapsed + "ms");
}
private static void saxyModelTest(InputStream inputStream) throws IOException, SAXException {
long start = System.currentTimeMillis();
VideoAdapter videoAdapter = new VideoAdapter();
Xml.parse(inputStream, Xml.Encoding.UTF_8, newContentHandler(videoAdapter));
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "Saxy Model: " + elapsed + "ms");
}
private static void saxyModelTest(InputStream inputStream, ContentHandler contentHandler)
throws IOException, SAXException {
long start = System.currentTimeMillis();
Xml.parse(inputStream, Xml.Encoding.UTF_8, contentHandler);
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "Saxy Model (preloaded): " + elapsed + "ms");
}
private static class VideoAdapter {
public void addVideo(YouTubeVideo video) {
}
}
private static ContentHandler newContentHandler(VideoAdapter videoAdapter) {
return new HandlerFactory().newContentHandler(videoAdapter);
}
private static class HandlerFactory {
YouTubeVideo video;
public ContentHandler newContentHandler(VideoAdapter videoAdapter) {
RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
final VideoListener videoListener = new VideoListener(videoAdapter);
Element entry = root.getChild(ATOM_NAMESPACE, "entry");
entry.setElementListener(videoListener);
entry.getChild(ATOM_NAMESPACE, "id")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.videoId = body;
}
});
entry.getChild(ATOM_NAMESPACE, "published")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
// TODO(tomtaylor): programmatically get the timezone
video.dateAdded = new Time(Time.TIMEZONE_UTC);
video.dateAdded.parse3339(body);
}
});
Element author = entry.getChild(ATOM_NAMESPACE, "author");
author.getChild(ATOM_NAMESPACE, "name")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.authorName = body;
}
});
Element mediaGroup = entry.getChild(MEDIA_NAMESPACE, "group");
mediaGroup.getChild(MEDIA_NAMESPACE, "thumbnail")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String url = attributes.getValue("", "url");
if (video.thumbnailUrl == null && url.length() > 0) {
video.thumbnailUrl = url;
}
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "content")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String url = attributes.getValue("", "url");
if (url != null) {
video.videoUrl = url;
}
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "player")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String url = attributes.getValue("", "url");
if (url != null) {
video.playbackUrl = url;
}
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "title")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.title = body;
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "category")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.category = body;
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "description")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.description = body;
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "keywords")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.tags = body;
}
});
mediaGroup.getChild(YOUTUBE_NAMESPACE, "duration")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String seconds = attributes.getValue("", "seconds");
video.lengthInSeconds
= XmlUtils.convertValueToInt(seconds, 0);
}
});
mediaGroup.getChild(YOUTUBE_NAMESPACE, "statistics")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String viewCount = attributes.getValue("", "viewCount");
video.viewCount
= XmlUtils.convertValueToInt(viewCount, 0);
}
});
entry.getChild(GDATA_NAMESPACE, "rating")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String average = attributes.getValue("", "average");
video.rating = average == null
? 0.0f : Float.parseFloat(average);
}
});
return root.getContentHandler();
}
class VideoListener implements ElementListener {
final VideoAdapter videoAdapter;
public VideoListener(VideoAdapter videoAdapter) {
this.videoAdapter = videoAdapter;
}
public void start(Attributes attributes) {
video = new YouTubeVideo();
}
public void end() {
videoAdapter.addVideo(video);
video = null;
}
}
}
private static class YouTubeContentHandler extends DefaultHandler {
final VideoAdapter videoAdapter;
YouTubeVideo video = null;
StringBuilder builder = null;
public YouTubeContentHandler(VideoAdapter videoAdapter) {
this.videoAdapter = videoAdapter;
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if (uri.equals(ATOM_NAMESPACE)) {
if (localName.equals("entry")) {
video = new YouTubeVideo();
return;
}
if (video == null) {
return;
}
if (!localName.equals("id")
&& !localName.equals("published")
&& !localName.equals("name")) {
return;
}
this.builder = new StringBuilder();
return;
}
if (video == null) {
return;
}
if (uri.equals(MEDIA_NAMESPACE)) {
if (localName.equals("thumbnail")) {
String url = attributes.getValue("", "url");
if (video.thumbnailUrl == null && url.length() > 0) {
video.thumbnailUrl = url;
}
return;
}
if (localName.equals("content")) {
String url = attributes.getValue("", "url");
if (url != null) {
video.videoUrl = url;
}
return;
}
if (localName.equals("player")) {
String url = attributes.getValue("", "url");
if (url != null) {
video.playbackUrl = url;
}
return;
}
if (localName.equals("title")
|| localName.equals("category")
|| localName.equals("description")
|| localName.equals("keywords")) {
this.builder = new StringBuilder();
return;
}
return;
}
if (uri.equals(YOUTUBE_NAMESPACE)) {
if (localName.equals("duration")) {
video.lengthInSeconds = XmlUtils.convertValueToInt(
attributes.getValue("", "seconds"), 0);
return;
}
if (localName.equals("statistics")) {
video.viewCount = XmlUtils.convertValueToInt(
attributes.getValue("", "viewCount"), 0);
return;
}
return;
}
if (uri.equals(GDATA_NAMESPACE)) {
if (localName.equals("rating")) {
String average = attributes.getValue("", "average");
video.rating = average == null
? 0.0f : Float.parseFloat(average);
}
}
}
@Override
public void characters(char text[], int start, int length)
throws SAXException {
if (builder != null) {
builder.append(text, start, length);
}
}
String takeText() {
try {
return builder.toString();
} finally {
builder = null;
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (video == null) {
return;
}
if (uri.equals(ATOM_NAMESPACE)) {
if (localName.equals("published")) {
// TODO(tomtaylor): programmatically get the timezone
video.dateAdded = new Time(Time.TIMEZONE_UTC);
video.dateAdded.parse3339(takeText());
return;
}
if (localName.equals("name")) {
video.authorName = takeText();
return;
}
if (localName.equals("id")) {
video.videoId = takeText();
return;
}
if (localName.equals("entry")) {
// Add the video!
videoAdapter.addVideo(video);
video = null;
return;
}
return;
}
if (uri.equals(MEDIA_NAMESPACE)) {
if (localName.equals("description")) {
video.description = takeText();
return;
}
if (localName.equals("keywords")) {
video.tags = takeText();
return;
}
if (localName.equals("category")) {
video.category = takeText();
return;
}
if (localName.equals("title")) {
video.title = takeText();
}
}
}
}
private static class YouTubeVideo {
public String videoId; // the id used to lookup on YouTube
public String videoUrl; // the url to play the video
public String playbackUrl; // the url to share for users to play video
public String thumbnailUrl; // the url of the thumbnail image
public String title;
public Bitmap bitmap; // cached bitmap of the thumbnail
public int lengthInSeconds;
public int viewCount; // number of times the video has been viewed
public float rating; // ranges from 0.0 to 5.0
public Boolean triedToLoadThumbnail;
public String authorName;
public Time dateAdded;
public String category;
public String tags;
public String description;
}
}