• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package android.sax;
18 
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;
37 
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 
43 import com.android.frameworks.saxtests.R;
44 
45 public class SafeSaxTest extends AndroidTestCase {
46 
47     private static final String TAG = SafeSaxTest.class.getName();
48 
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";
53 
54     private static class ElementCounter implements ElementListener {
55         int starts = 0;
56         int ends = 0;
57 
start(Attributes attributes)58         public void start(Attributes attributes) {
59             starts++;
60         }
61 
end()62         public void end() {
63             ends++;
64         }
65     }
66 
67     private static class TextElementCounter implements TextElementListener {
68         int starts = 0;
69         String bodies = "";
70 
start(Attributes attributes)71         public void start(Attributes attributes) {
72             starts++;
73         }
74 
end(String body)75         public void end(String body) {
76             this.bodies += body;
77         }
78     }
79 
80     @SmallTest
testListener()81     public void testListener() throws Exception {
82         String xml = "<feed xmlns='http://www.w3.org/2005/Atom'>\n"
83                 + "<entry>\n"
84                 + "<id>a</id>\n"
85                 + "</entry>\n"
86                 + "<entry>\n"
87                 + "<id>b</id>\n"
88                 + "</entry>\n"
89                 + "</feed>\n";
90 
91         RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
92         Element entry = root.requireChild(ATOM_NAMESPACE, "entry");
93         Element id = entry.requireChild(ATOM_NAMESPACE, "id");
94 
95         ElementCounter rootCounter = new ElementCounter();
96         ElementCounter entryCounter = new ElementCounter();
97         TextElementCounter idCounter = new TextElementCounter();
98 
99         root.setElementListener(rootCounter);
100         entry.setElementListener(entryCounter);
101         id.setTextElementListener(idCounter);
102 
103         Xml.parse(xml, root.getContentHandler());
104 
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);
111     }
112 
113     @SmallTest
testMissingRequiredChild()114     public void testMissingRequiredChild() throws Exception {
115         String xml = "<feed></feed>";
116         RootElement root = new RootElement("feed");
117         root.requireChild("entry");
118 
119         try {
120             Xml.parse(xml, root.getContentHandler());
121             fail("expected exception not thrown");
122         } catch (SAXException e) {
123             // Expected.
124         }
125     }
126 
127     @SmallTest
testMixedContent()128     public void testMixedContent() throws Exception {
129         String xml = "<feed><entry></entry></feed>";
130 
131         RootElement root = new RootElement("feed");
132         root.setEndTextElementListener(new EndTextElementListener() {
133             public void end(String body) {
134             }
135         });
136 
137         try {
138             Xml.parse(xml, root.getContentHandler());
139             fail("expected exception not thrown");
140         } catch (SAXException e) {
141             // Expected.
142         }
143     }
144 
145     @LargeTest
testPerformance()146     public void testPerformance() throws Exception {
147         InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
148         byte[] xmlBytes;
149         try {
150             ByteArrayOutputStream out = new ByteArrayOutputStream();
151             byte[] buffer = new byte[1024];
152             int length;
153             while ((length = in.read(buffer)) != -1) {
154                 out.write(buffer, 0, length);
155             }
156             xmlBytes = out.toByteArray();
157         } finally {
158             in.close();
159         }
160 
161         Log.i("***", "File size: " + (xmlBytes.length / 1024) + "k");
162 
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);
169         }
170     }
171 
pureSaxTest(InputStream inputStream)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");
178     }
179 
saxyModelTest(InputStream inputStream)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");
186     }
187 
saxyModelTest(InputStream inputStream, ContentHandler contentHandler)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");
194     }
195 
196     private static class VideoAdapter {
addVideo(YouTubeVideo video)197         public void addVideo(YouTubeVideo video) {
198         }
199     }
200 
newContentHandler(VideoAdapter videoAdapter)201     private static ContentHandler newContentHandler(VideoAdapter videoAdapter) {
202         return new HandlerFactory().newContentHandler(videoAdapter);
203     }
204 
205     private static class HandlerFactory {
206         YouTubeVideo video;
207 
newContentHandler(VideoAdapter videoAdapter)208         public ContentHandler newContentHandler(VideoAdapter videoAdapter) {
209             RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
210 
211             final VideoListener videoListener = new VideoListener(videoAdapter);
212 
213             Element entry = root.getChild(ATOM_NAMESPACE, "entry");
214 
215             entry.setElementListener(videoListener);
216 
217             entry.getChild(ATOM_NAMESPACE, "id")
218                     .setEndTextElementListener(new EndTextElementListener() {
219                         public void end(String body) {
220                             video.videoId = body;
221                         }
222                     });
223 
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);
230                         }
231                     });
232 
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;
238                         }
239                     });
240 
241             Element mediaGroup = entry.getChild(MEDIA_NAMESPACE, "group");
242 
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;
249                             }
250                         }
251                     });
252 
253             mediaGroup.getChild(MEDIA_NAMESPACE, "content")
254                     .setStartElementListener(new StartElementListener() {
255                         public void start(Attributes attributes) {
256                             String url = attributes.getValue("", "url");
257                             if (url != null) {
258                                 video.videoUrl = url;
259                             }
260                         }
261                     });
262 
263             mediaGroup.getChild(MEDIA_NAMESPACE, "player")
264                     .setStartElementListener(new StartElementListener() {
265                         public void start(Attributes attributes) {
266                             String url = attributes.getValue("", "url");
267                             if (url != null) {
268                                 video.playbackUrl = url;
269                             }
270                         }
271                     });
272 
273             mediaGroup.getChild(MEDIA_NAMESPACE, "title")
274                     .setEndTextElementListener(new EndTextElementListener() {
275                         public void end(String body) {
276                             video.title = body;
277                         }
278                     });
279 
280             mediaGroup.getChild(MEDIA_NAMESPACE, "category")
281                     .setEndTextElementListener(new EndTextElementListener() {
282                         public void end(String body) {
283                             video.category = body;
284                         }
285                     });
286 
287             mediaGroup.getChild(MEDIA_NAMESPACE, "description")
288                     .setEndTextElementListener(new EndTextElementListener() {
289                         public void end(String body) {
290                             video.description = body;
291                         }
292                     });
293 
294             mediaGroup.getChild(MEDIA_NAMESPACE, "keywords")
295                     .setEndTextElementListener(new EndTextElementListener() {
296                         public void end(String body) {
297                             video.tags = body;
298                         }
299                     });
300 
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);
307                         }
308                     });
309 
310             mediaGroup.getChild(YOUTUBE_NAMESPACE, "statistics")
311                     .setStartElementListener(new StartElementListener() {
312                         public void start(Attributes attributes) {
313                             String viewCount = attributes.getValue("", "viewCount");
314                             video.viewCount
315                                     = XmlUtils.convertValueToInt(viewCount, 0);
316                         }
317                     });
318 
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);
325                         }
326                     });
327 
328             return root.getContentHandler();
329         }
330 
331         class VideoListener implements ElementListener {
332 
333             final VideoAdapter videoAdapter;
334 
VideoListener(VideoAdapter videoAdapter)335             public VideoListener(VideoAdapter videoAdapter) {
336                 this.videoAdapter = videoAdapter;
337             }
338 
start(Attributes attributes)339             public void start(Attributes attributes) {
340                 video = new YouTubeVideo();
341             }
342 
end()343             public void end() {
344                 videoAdapter.addVideo(video);
345                 video = null;
346             }
347         }
348     }
349 
350     private static class YouTubeContentHandler extends DefaultHandler {
351 
352         final VideoAdapter videoAdapter;
353 
354         YouTubeVideo video = null;
355         StringBuilder builder = null;
356 
YouTubeContentHandler(VideoAdapter videoAdapter)357         public YouTubeContentHandler(VideoAdapter videoAdapter) {
358             this.videoAdapter = videoAdapter;
359         }
360 
361         @Override
startElement(String uri, String localName, String qName, Attributes attributes)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();
367                     return;
368                 }
369 
370                 if (video == null) {
371                     return;
372                 }
373 
374                 if (!localName.equals("id")
375                         && !localName.equals("published")
376                         && !localName.equals("name")) {
377                     return;
378                 }
379                 this.builder = new StringBuilder();
380                 return;
381 
382             }
383 
384             if (video == null) {
385                 return;
386             }
387 
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;
393                     }
394                     return;
395                 }
396 
397                 if (localName.equals("content")) {
398                     String url = attributes.getValue("", "url");
399                     if (url != null) {
400                         video.videoUrl = url;
401                     }
402                     return;
403                 }
404 
405                 if (localName.equals("player")) {
406                     String url = attributes.getValue("", "url");
407                     if (url != null) {
408                         video.playbackUrl = url;
409                     }
410                     return;
411                 }
412 
413                 if (localName.equals("title")
414                         || localName.equals("category")
415                         || localName.equals("description")
416                         || localName.equals("keywords")) {
417                     this.builder = new StringBuilder();
418                     return;
419                 }
420 
421                 return;
422             }
423 
424             if (uri.equals(YOUTUBE_NAMESPACE)) {
425                 if (localName.equals("duration")) {
426                     video.lengthInSeconds = XmlUtils.convertValueToInt(
427                             attributes.getValue("", "seconds"), 0);
428                     return;
429                 }
430 
431                 if (localName.equals("statistics")) {
432                     video.viewCount = XmlUtils.convertValueToInt(
433                             attributes.getValue("", "viewCount"), 0);
434                     return;
435                 }
436 
437                 return;
438             }
439 
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);
445                 }
446             }
447         }
448 
449         @Override
characters(char text[], int start, int length)450         public void characters(char text[], int start, int length)
451                 throws SAXException {
452             if (builder != null) {
453                 builder.append(text, start, length);
454             }
455         }
456 
takeText()457         String takeText() {
458             try {
459                 return builder.toString();
460             } finally {
461                 builder = null;
462             }
463         }
464 
465         @Override
endElement(String uri, String localName, String qName)466         public void endElement(String uri, String localName, String qName)
467                 throws SAXException {
468             if (video == null) {
469                 return;
470             }
471 
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());
477                     return;
478                 }
479 
480                 if (localName.equals("name")) {
481                     video.authorName = takeText();
482                     return;
483                 }
484 
485                 if (localName.equals("id")) {
486                     video.videoId = takeText();
487                     return;
488                 }
489 
490                 if (localName.equals("entry")) {
491                     // Add the video!
492                     videoAdapter.addVideo(video);
493                     video = null;
494                     return;
495                 }
496 
497                 return;
498             }
499 
500             if (uri.equals(MEDIA_NAMESPACE)) {
501                 if (localName.equals("description")) {
502                     video.description = takeText();
503                     return;
504                 }
505 
506                 if (localName.equals("keywords")) {
507                     video.tags = takeText();
508                     return;
509                 }
510 
511                 if (localName.equals("category")) {
512                     video.category = takeText();
513                     return;
514                 }
515 
516                 if (localName.equals("title")) {
517                     video.title = takeText();
518                 }
519             }
520         }
521     }
522 
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
528         public String title;
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;
537         public String tags;
538         public String description;
539     }
540 }
541 
542