2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 import android.graphics.Bitmap;
20 import android.sax.Element;
21 import android.sax.ElementListener;
22 import android.sax.EndTextElementListener;
23 import android.sax.RootElement;
24 import android.sax.StartElementListener;
25 import android.sax.TextElementListener;
26 import android.test.AndroidTestCase;
27 import android.test.suitebuilder.annotation.LargeTest;
28 import android.test.suitebuilder.annotation.SmallTest;
29 import android.text.format.Time;
30 import android.util.Log;
31 import android.util.Xml;
32 import com.android.internal.util.XmlUtils;
33 import org.xml.sax.Attributes;
34 import org.xml.sax.ContentHandler;
35 import org.xml.sax.SAXException;
36 import org.xml.sax.helpers.DefaultHandler;
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
43 import com.android.frameworks.saxtests.R;
45 public class SafeSaxTest extends AndroidTestCase {
47 private static final String TAG = SafeSaxTest.class.getName();
49 private static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
50 private static final String MEDIA_NAMESPACE = "http://search.yahoo.com/mrss/";
51 private static final String YOUTUBE_NAMESPACE = "http://gdata.youtube.com/schemas/2007";
52 private static final String GDATA_NAMESPACE = "http://schemas.google.com/g/2005";
54 private static class ElementCounter implements ElementListener {
58 public void start(Attributes attributes) {
67 private static class TextElementCounter implements TextElementListener {
71 public void start(Attributes attributes) {
75 public void end(String body) {
81 public void testListener() throws Exception {
82 String xml = "<feed xmlns='http://www.w3.org/2005/Atom'>\n"
91 RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
92 Element entry = root.requireChild(ATOM_NAMESPACE, "entry");
93 Element id = entry.requireChild(ATOM_NAMESPACE, "id");
95 ElementCounter rootCounter = new ElementCounter();
96 ElementCounter entryCounter = new ElementCounter();
97 TextElementCounter idCounter = new TextElementCounter();
99 root.setElementListener(rootCounter);
100 entry.setElementListener(entryCounter);
101 id.setTextElementListener(idCounter);
103 Xml.parse(xml, root.getContentHandler());
105 assertEquals(1, rootCounter.starts);
106 assertEquals(1, rootCounter.ends);
107 assertEquals(2, entryCounter.starts);
108 assertEquals(2, entryCounter.ends);
109 assertEquals(2, idCounter.starts);
110 assertEquals("ab", idCounter.bodies);
114 public void testMissingRequiredChild() throws Exception {
115 String xml = "<feed></feed>";
116 RootElement root = new RootElement("feed");
117 root.requireChild("entry");
120 Xml.parse(xml, root.getContentHandler());
121 fail("expected exception not thrown");
122 } catch (SAXException e) {
128 public void testMixedContent() throws Exception {
129 String xml = "<feed><entry></entry></feed>";
131 RootElement root = new RootElement("feed");
132 root.setEndTextElementListener(new EndTextElementListener() {
133 public void end(String body) {
138 Xml.parse(xml, root.getContentHandler());
139 fail("expected exception not thrown");
140 } catch (SAXException e) {
146 public void testPerformance() throws Exception {
147 InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
150 ByteArrayOutputStream out = new ByteArrayOutputStream();
151 byte[] buffer = new byte[1024];
153 while ((length = in.read(buffer)) != -1) {
154 out.write(buffer, 0, length);
156 xmlBytes = out.toByteArray();
161 Log.i("***", "File size: " + (xmlBytes.length / 1024) + "k");
163 VideoAdapter videoAdapter = new VideoAdapter();
164 ContentHandler handler = newContentHandler(videoAdapter);
165 for (int i = 0; i < 2; i++) {
166 pureSaxTest(new ByteArrayInputStream(xmlBytes));
167 saxyModelTest(new ByteArrayInputStream(xmlBytes));
168 saxyModelTest(new ByteArrayInputStream(xmlBytes), handler);
172 private static void pureSaxTest(InputStream inputStream) throws IOException, SAXException {
173 long start = System.currentTimeMillis();
174 VideoAdapter videoAdapter = new VideoAdapter();
175 Xml.parse(inputStream, Xml.Encoding.UTF_8, new YouTubeContentHandler(videoAdapter));
176 long elapsed = System.currentTimeMillis() - start;
177 Log.i(TAG, "pure SAX: " + elapsed + "ms");
180 private static void saxyModelTest(InputStream inputStream) throws IOException, SAXException {
181 long start = System.currentTimeMillis();
182 VideoAdapter videoAdapter = new VideoAdapter();
183 Xml.parse(inputStream, Xml.Encoding.UTF_8, newContentHandler(videoAdapter));
184 long elapsed = System.currentTimeMillis() - start;
185 Log.i(TAG, "Saxy Model: " + elapsed + "ms");
188 private static void saxyModelTest(InputStream inputStream, ContentHandler contentHandler)
189 throws IOException, SAXException {
190 long start = System.currentTimeMillis();
191 Xml.parse(inputStream, Xml.Encoding.UTF_8, contentHandler);
192 long elapsed = System.currentTimeMillis() - start;
193 Log.i(TAG, "Saxy Model (preloaded): " + elapsed + "ms");
196 private static class VideoAdapter {
197 public void addVideo(YouTubeVideo video) {
201 private static ContentHandler newContentHandler(VideoAdapter videoAdapter) {
202 return new HandlerFactory().newContentHandler(videoAdapter);
205 private static class HandlerFactory {
208 public ContentHandler newContentHandler(VideoAdapter videoAdapter) {
209 RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
211 final VideoListener videoListener = new VideoListener(videoAdapter);
213 Element entry = root.getChild(ATOM_NAMESPACE, "entry");
215 entry.setElementListener(videoListener);
217 entry.getChild(ATOM_NAMESPACE, "id")
218 .setEndTextElementListener(new EndTextElementListener() {
219 public void end(String body) {
220 video.videoId = body;
224 entry.getChild(ATOM_NAMESPACE, "published")
225 .setEndTextElementListener(new EndTextElementListener() {
226 public void end(String body) {
227 // TODO(tomtaylor): programmatically get the timezone
228 video.dateAdded = new Time(Time.TIMEZONE_UTC);
229 video.dateAdded.parse3339(body);
233 Element author = entry.getChild(ATOM_NAMESPACE, "author");
234 author.getChild(ATOM_NAMESPACE, "name")
235 .setEndTextElementListener(new EndTextElementListener() {
236 public void end(String body) {
237 video.authorName = body;
241 Element mediaGroup = entry.getChild(MEDIA_NAMESPACE, "group");
243 mediaGroup.getChild(MEDIA_NAMESPACE, "thumbnail")
244 .setStartElementListener(new StartElementListener() {
245 public void start(Attributes attributes) {
246 String url = attributes.getValue("", "url");
247 if (video.thumbnailUrl == null && url.length() > 0) {
248 video.thumbnailUrl = url;
253 mediaGroup.getChild(MEDIA_NAMESPACE, "content")
254 .setStartElementListener(new StartElementListener() {
255 public void start(Attributes attributes) {
256 String url = attributes.getValue("", "url");
258 video.videoUrl = url;
263 mediaGroup.getChild(MEDIA_NAMESPACE, "player")
264 .setStartElementListener(new StartElementListener() {
265 public void start(Attributes attributes) {
266 String url = attributes.getValue("", "url");
268 video.playbackUrl = url;
273 mediaGroup.getChild(MEDIA_NAMESPACE, "title")
274 .setEndTextElementListener(new EndTextElementListener() {
275 public void end(String body) {
280 mediaGroup.getChild(MEDIA_NAMESPACE, "category")
281 .setEndTextElementListener(new EndTextElementListener() {
282 public void end(String body) {
283 video.category = body;
287 mediaGroup.getChild(MEDIA_NAMESPACE, "description")
288 .setEndTextElementListener(new EndTextElementListener() {
289 public void end(String body) {
290 video.description = body;
294 mediaGroup.getChild(MEDIA_NAMESPACE, "keywords")
295 .setEndTextElementListener(new EndTextElementListener() {
296 public void end(String body) {
301 mediaGroup.getChild(YOUTUBE_NAMESPACE, "duration")
302 .setStartElementListener(new StartElementListener() {
303 public void start(Attributes attributes) {
304 String seconds = attributes.getValue("", "seconds");
305 video.lengthInSeconds
306 = XmlUtils.convertValueToInt(seconds, 0);
310 mediaGroup.getChild(YOUTUBE_NAMESPACE, "statistics")
311 .setStartElementListener(new StartElementListener() {
312 public void start(Attributes attributes) {
313 String viewCount = attributes.getValue("", "viewCount");
315 = XmlUtils.convertValueToInt(viewCount, 0);
319 entry.getChild(GDATA_NAMESPACE, "rating")
320 .setStartElementListener(new StartElementListener() {
321 public void start(Attributes attributes) {
322 String average = attributes.getValue("", "average");
323 video.rating = average == null
324 ? 0.0f : Float.parseFloat(average);
328 return root.getContentHandler();
331 class VideoListener implements ElementListener {
333 final VideoAdapter videoAdapter;
335 public VideoListener(VideoAdapter videoAdapter) {
336 this.videoAdapter = videoAdapter;
339 public void start(Attributes attributes) {
340 video = new YouTubeVideo();
344 videoAdapter.addVideo(video);
350 private static class YouTubeContentHandler extends DefaultHandler {
352 final VideoAdapter videoAdapter;
354 YouTubeVideo video = null;
355 StringBuilder builder = null;
357 public YouTubeContentHandler(VideoAdapter videoAdapter) {
358 this.videoAdapter = videoAdapter;
362 public void startElement(String uri, String localName, String qName,
363 Attributes attributes) throws SAXException {
364 if (uri.equals(ATOM_NAMESPACE)) {
365 if (localName.equals("entry")) {
366 video = new YouTubeVideo();
374 if (!localName.equals("id")
375 && !localName.equals("published")
376 && !localName.equals("name")) {
379 this.builder = new StringBuilder();
388 if (uri.equals(MEDIA_NAMESPACE)) {
389 if (localName.equals("thumbnail")) {
390 String url = attributes.getValue("", "url");
391 if (video.thumbnailUrl == null && url.length() > 0) {
392 video.thumbnailUrl = url;
397 if (localName.equals("content")) {
398 String url = attributes.getValue("", "url");
400 video.videoUrl = url;
405 if (localName.equals("player")) {
406 String url = attributes.getValue("", "url");
408 video.playbackUrl = url;
413 if (localName.equals("title")
414 || localName.equals("category")
415 || localName.equals("description")
416 || localName.equals("keywords")) {
417 this.builder = new StringBuilder();
424 if (uri.equals(YOUTUBE_NAMESPACE)) {
425 if (localName.equals("duration")) {
426 video.lengthInSeconds = XmlUtils.convertValueToInt(
427 attributes.getValue("", "seconds"), 0);
431 if (localName.equals("statistics")) {
432 video.viewCount = XmlUtils.convertValueToInt(
433 attributes.getValue("", "viewCount"), 0);
440 if (uri.equals(GDATA_NAMESPACE)) {
441 if (localName.equals("rating")) {
442 String average = attributes.getValue("", "average");
443 video.rating = average == null
444 ? 0.0f : Float.parseFloat(average);
450 public void characters(char text[], int start, int length)
451 throws SAXException {
452 if (builder != null) {
453 builder.append(text, start, length);
459 return builder.toString();
466 public void endElement(String uri, String localName, String qName)
467 throws SAXException {
472 if (uri.equals(ATOM_NAMESPACE)) {
473 if (localName.equals("published")) {
474 // TODO(tomtaylor): programmatically get the timezone
475 video.dateAdded = new Time(Time.TIMEZONE_UTC);
476 video.dateAdded.parse3339(takeText());
480 if (localName.equals("name")) {
481 video.authorName = takeText();
485 if (localName.equals("id")) {
486 video.videoId = takeText();
490 if (localName.equals("entry")) {
492 videoAdapter.addVideo(video);
500 if (uri.equals(MEDIA_NAMESPACE)) {
501 if (localName.equals("description")) {
502 video.description = takeText();
506 if (localName.equals("keywords")) {
507 video.tags = takeText();
511 if (localName.equals("category")) {
512 video.category = takeText();
516 if (localName.equals("title")) {
517 video.title = takeText();
523 private static class YouTubeVideo {
524 public String videoId; // the id used to lookup on YouTube
525 public String videoUrl; // the url to play the video
526 public String playbackUrl; // the url to share for users to play video
527 public String thumbnailUrl; // the url of the thumbnail image
529 public Bitmap bitmap; // cached bitmap of the thumbnail
530 public int lengthInSeconds;
531 public int viewCount; // number of times the video has been viewed
532 public float rating; // ranges from 0.0 to 5.0
533 public Boolean triedToLoadThumbnail;
534 public String authorName;
535 public Time dateAdded;
536 public String category;
538 public String description;