sbiTunesXMLParser.cpp
Go to the documentation of this file.
1 /*
2  //
3  // BEGIN SONGBIRD GPL
4  //
5  // This file is part of the Songbird web player.
6  //
7  // Copyright(c) 2005-2009 POTI, Inc.
8  // http://songbirdnest.com
9  //
10  // This file may be licensed under the terms of of the
11  // GNU General Public License Version 2 (the "GPL").
12  //
13  // Software distributed under the License is distributed
14  // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
15  // express or implied. See the GPL for the specific language
16  // governing rights and limitations.
17  //
18  // You should have received a copy of the GPL along with this
19  // program. If not, go to http://www.gnu.org/licenses/gpl.html
20  // or write to the Free Software Foundation, Inc.,
21  // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  //
23  // END SONGBIRD GPL
24  //
25  */
26 
27 #include "sbiTunesXMLParser.h"
28 
29 #include <prlog.h>
30 #include <nsComponentManagerUtils.h>
31 #include <nsCRTGlue.h>
32 #include <nsISAXAttributes.h>
33 #include <nsISAXLocator.h>
34 #include <nsIInputStreamPump.h>
35 #include <nsThreadUtils.h>
36 
37 #include <sbIiTunesXMLParserListener.h>
39 
40 #include <sbFileUtils.h>
41 
42 inline
43 nsString BuildErrorMessage(char const * aType,
44  nsISAXLocator * aLocator,
45  nsAString const & aError) {
46  PRInt32 line = 0;
47  PRInt32 column = 0;
48  aLocator->GetLineNumber(&line);
49  aLocator->GetColumnNumber(&column);
50  nsString msg;
51  msg.AppendLiteral(aType);
52  msg.AppendLiteral(" occurred at line ");
53  msg.AppendInt(line, 10);
54  msg.AppendLiteral(" column ");
55  msg.AppendInt(column, 10);
56  msg.Append(aError);
57  return msg;
58 }
59 
60 #ifdef PR_LOGGING
61 static PRLogModuleInfo* giTunesXMLParserLog = nsnull;
62 #define TRACE(args) \
63  PR_BEGIN_MACRO \
64  if (!giTunesXMLParserLog) \
65  giTunesXMLParserLog = PR_NewLogModule("sbiTunesXMLParser"); \
66  PR_LOG(giTunesXMLParserLog, PR_LOG_DEBUG, args); \
67  PR_END_MACRO
68 #define LOG(args) \
69  PR_BEGIN_MACRO \
70  if (!giTunesXMLParserLog) \
71  giTunesXMLParserLog = PR_NewLogModule("sbiTunesXMLParser"); \
72  PR_LOG(giTunesXMLParserLog, PR_LOG_WARN, args); \
73  PR_END_MACRO
74 
78 inline
79 void LogStartElement(const nsAString & aURI,
80  const nsAString & aLocalName,
81  const nsAString & aQName,
82  nsISAXAttributes * aAttributes) {
83  LOG(("StartElement: URI=%s localName=%s qName=%s\n",
84  NS_LossyConvertUTF16toASCII(aURI).get(),
85  NS_LossyConvertUTF16toASCII(aLocalName).get(),
86  NS_LossyConvertUTF16toASCII(aQName).get()));
87  PRInt32 length;
88  aAttributes->GetLength(&length);
89  nsString name;
90  nsString value;
91  for (PRInt32 index = 0; index < length; ++index) {
92  aAttributes->GetLocalName(index, name);
93  aAttributes->GetValue(index, value);
94  LOG((" %s:%s\n",
95  NS_LossyConvertUTF16toASCII(name).get(),
96  NS_LossyConvertUTF16toASCII(value).get()));
97  }
98 }
99 
103 inline
104 void LogError(char const * aType,
105  nsISAXLocator * aLocator,
106  nsAString const & aError) {
107  LOG((NS_LossyConvertUTF16toASCII(BuildErrorMessage(aType, aLocator, aError)).get()));
108 }
109 
110 #else
111 #define TRACE(args) /* nothing */
112 #define LOG(args) /* nothing */
113 inline
114 void LogStartElement(const nsAString & aURI,
115  const nsAString & aLocalName,
116  const nsAString & qName,
117  nsISAXAttributes * aAttributes) {
118 }
119 inline
120 void LogError(char const * aType,
121  nsISAXLocator * aLocator,
122  nsAString const & aError) {
123 }
124 #endif /* PR_LOGGING */
125 
128  nsISAXContentHandler,
129  nsISAXErrorHandler)
130 
131 /* Constants */
132 char const XML_CONTENT_TYPE[] = "text/xml";
133 char const NS_SAXXMLREADER_CONTRACTID[] = "@mozilla.org/saxparser/xmlreader;1";
134 
136  return new sbiTunesXMLParser;
137 }
138 
139 sbiTunesXMLParser::sbiTunesXMLParser() : mState(START), mBytesRead(0) {
140  MOZ_COUNT_CTOR(sbiTunesXMLParser);
141  GetSAXReader();
142 }
143 
145  Finalize();MOZ_COUNT_DTOR(sbiTunesXMLParser);
146 }
147 
148 /* sbIiTunesXMLParser implementation */
149 
150 NS_IMETHODIMP sbiTunesXMLParser::Parse(nsIInputStream * aiTunesXMLStream,
151  sbIiTunesXMLParserListener * aListener) {
152 
153  nsresult rv;
154 
155  NS_ENSURE_ARG_POINTER(aiTunesXMLStream);
156  NS_ENSURE_ARG_POINTER(aListener);
157  /* Update status. */
158 
159  mListener = aListener;
160 
161  rv = InitializeProperties();
162  NS_ENSURE_SUCCESS(rv, rv);
163 
164  nsISAXXMLReaderPtr const & reader = GetSAXReader();
165 
166  rv = reader->SetContentHandler(this);
167  NS_ENSURE_SUCCESS(rv, rv);
168 
169  rv = reader->SetErrorHandler(this);
170  NS_ENSURE_SUCCESS(rv, rv);
171 
172  rv = reader->ParseAsync(nsnull);
173 
174  mPump = do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
175  NS_ENSURE_SUCCESS(rv, rv);
176 
177  rv = mPump->Init(aiTunesXMLStream, -1, -1, 0, 0, PR_TRUE);
178  NS_ENSURE_SUCCESS(rv, rv);
179 
180  nsCOMPtr<nsIStreamListener> streamListener = do_QueryInterface(reader);
181  rv = mPump->AsyncRead(streamListener, nsnull);
182 
183  NS_ENSURE_SUCCESS(rv, rv);
184 
185  return NS_OK;
186 }
187 
188 /* void finalize(); */
189 NS_IMETHODIMP sbiTunesXMLParser::Finalize() {
190  mSAXReader = nsnull;
191  mProperties = nsnull;
192  mListener = nsnull;
193  mTracks.Clear();
194  return NS_OK;
195 }
196 
197 nsISAXXMLReaderPtr const & sbiTunesXMLParser::GetSAXReader() {
198  if (!mSAXReader) {
199  nsresult rv;
200  mSAXReader = do_CreateInstance(NS_SAXXMLREADER_CONTRACTID, &rv);
201  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to create xmlreader");
202  }
203  return mSAXReader;
204 }
205 
206 /* nsISAXContentHandler implementation */
207 
208 /* void startDocument (); */
209 NS_IMETHODIMP
210 sbiTunesXMLParser::StartDocument() {
211  LOG(("StartDocument\n"));
212  // Add bytes in for the <?xml ...
213  // <!DOCTYPE and the <plist
214  mBytesRead = 38 + 112 + 21;
215  mState = START;
216  return NS_OK;
217 }
218 
219 /* void endDocument (); */
220 NS_IMETHODIMP
221 sbiTunesXMLParser::EndDocument() {
222  LOG(("EndDocument\n"));
223  mSAXReader = nsnull;
224  return NS_OK;
225 }
226 
227 /* void startElement (in AString uri, in AString localName, in AString qName, in nsISAXAttributes attributes); */
228 NS_IMETHODIMP
229 sbiTunesXMLParser::StartElement(const nsAString & uri,
230  const nsAString & localName,
231  const nsAString & qName,
232  nsISAXAttributes *attributes) {
233  LogStartElement(uri, localName, qName, attributes);
234 
235  // If we're done then ignore everything else
236  if (mState == DONE) {
237  return NS_OK;
238  }
239  // Is this a key element
240  if (localName.EqualsLiteral("true") || localName.EqualsLiteral("false")) {
241  // Handle boolean values which are just an element
242  if (!mPropertyName.IsEmpty()) {
243  mProperties->Set(mPropertyName, localName);
244  mPropertyName.Truncate();
245  }
246  mCharacters.Truncate();
247  return NS_OK;
248  }
249  mListener->OnProgress(mBytesRead);
250  // Add the local name length and then 2 for the < and >. iTunes doesn't use
251  // name spaces or attributes
252  mBytesRead += localName.Length() + 2;
253 
254  if (localName.EqualsLiteral("dict")) {
255  // Based on the current state, figure out what type of collection we're in
256  switch (mState) {
257  case START: {
258  mState = TOP_LEVEL_PROPERTIES;
259  }
260  break;
261  case TRACKS: {
262  mState = TRACKS_COLLECTION;
263  }
264  break;
265  case TRACKS_COLLECTION: {
266  mState = TRACK;
267  }
268  break;
269  case PLAYLISTS_COLLECTION: {
270  mState = PLAYLIST;
271  }
272  break;
273  case PLAYLIST_ITEMS: {
274  mState = PLAYLIST_ITEM;
275  }
276  break;
277  }
278  }
279  else if (localName.EqualsLiteral("array")) {
280  // If we're in the Playlists section, then enter the
281  // playlist collection state
282  switch (mState) {
283  case PLAYLISTS: {
284  mState = PLAYLISTS_COLLECTION;
285  }
286  break;
287  }
288  }
289  mCharacters.Truncate();
290  return NS_OK;
291 }
292 
293 /* void endElement (in AString uri, in AString localName, in AString qName); */
294 NS_IMETHODIMP sbiTunesXMLParser::EndElement(const nsAString & uri,
295  const nsAString & localName,
296  const nsAString & qName) {
297  LOG(("EndElement: URI=%s localName=%s qName=%s\n",
298  NS_LossyConvertUTF16toASCII(uri).get(),
299  NS_LossyConvertUTF16toASCII(localName).get(),
300  NS_LossyConvertUTF16toASCII(qName).get()));
301  nsresult rv;
302  mListener->OnProgress(mBytesRead);
303  // Add the local name length and the 3 for the </> characters.
304  mBytesRead += localName.Length() + 3;
305 
306  // If we're done then ignore everything else
307  if (mState == DONE) {
308  return NS_OK;
309  }
310  // Save off the characters and clear the member so we don't have to
311  // worry about return paths
312  nsString characters(mCharacters);
313  mCharacters.Truncate();
314 
315  nsString const propertyName(mPropertyName);
316  mPropertyName.Truncate();
317 
318  if (localName.EqualsLiteral("key")) {
319  switch (mState) {
320  case TOP_LEVEL_PROPERTIES: {
321  if (characters.EqualsLiteral("Tracks")) {
322  rv = mListener->OnTopLevelProperties(mProperties);
323  NS_ENSURE_SUCCESS(rv, rv);
324  mProperties->Clear();
325  mState = TRACKS;
326  }
327  else if (characters.EqualsLiteral("Playlists")) {
328  mState = PLAYLISTS;
329  }
330  else {
331  mPropertyName = characters;
332  }
333  }
334  break;
335  case PLAYLIST: {
336  if (characters.EqualsLiteral("Playlist Items")) {
337  mState = PLAYLIST_ITEMS;
338  }
339  else {
340  mPropertyName = characters;
341  }
342  }
343  break;
344  case TRACK:
345  case PLAYLIST_ITEM: {
346  mPropertyName = characters;
347  }
348  break;
349  // Nothing to do here, skip the key which is the track ID, we'll pick it up later
350  case TRACKS_COLLECTION:
351  break;
352  default: {
353  NS_WARNING("Unexpected state in sbiTunesXMLParser::EndElement (dict)");
354  }
355  break;
356  }
357  }
358  // If we're ending a dict element (dictionary collection
359  else if (localName.EqualsLiteral("dict")) {
360  // There's probably work to be done
361  switch (mState) {
362  case TRACKS_COLLECTION:
363  mState = TOP_LEVEL_PROPERTIES;
364  rv = mListener->OnTracksComplete();
365  NS_ENSURE_SUCCESS(rv, rv);
366  break;
367  case TRACK: { // We're leaving a track so notify
368  // Then go back to track collection
369  mState = TRACKS_COLLECTION;
370  LOG(("onTrack\n"));
371 
372  // Don't bother with this item if it has the "Movie" property.
373  nsString isMovieProp;
374  mProperties->Get(NS_LITERAL_STRING("Movie"), isMovieProp);
375  if (isMovieProp.IsEmpty()) {
376  rv = mListener->OnTrack(mProperties);
377  NS_ENSURE_SUCCESS(rv, rv);
378  }
379 
380  mProperties->Clear();
381  }
382  break;
383  case PLAYLIST: { // We're leaving a playlist so notify
384  // Then go back to the playlists collection
385  mState = PLAYLISTS_COLLECTION;
386  LOG(("onPlaylist\n"));
387  rv = mListener->OnPlaylist(mProperties, mTracks.Elements(), mTracks.Length());
388  NS_ENSURE_SUCCESS(rv, rv);
389  mTracks.Clear();
390  mProperties->Clear();
391  }
392  break;
393  case PLAYLIST_ITEM: { // We're leaving a playlist item
394  // Go back to the playlist items
395  mState = PLAYLIST_ITEMS;
396  }
397  break;
398  default: {
399  NS_WARNING("Unexpected state in sbiTunesXMLParser::EndElement (dict)");
400  }
401  break;
402  }
403  }
404  // if We're leaving an array, see if it's the playlist's array of items
405  // and if so, set the state back to the playlist.
406  else if (localName.EqualsLiteral("array")) {
407  switch (mState) {
408  case PLAYLIST_ITEMS: {
409  mState = PLAYLIST;
410  }
411  break;
412  // Leaving the playlsits array, go back to top level properties
413  case PLAYLISTS_COLLECTION: {
414  mState = TOP_LEVEL_PROPERTIES;
415  rv = mListener->OnPlaylistsComplete();
416  NS_ENSURE_SUCCESS(rv, rv);
417  mState = DONE;
418  }
419  break;
420  default: {
421  NS_WARNING("Unexpected state in sbiTunesXMLParser::EndElement (array)");
422  }
423  break;
424  }
425  }
426  else {
427  if (mState == PLAYLIST_ITEM && propertyName.EqualsLiteral("Track ID")) {
428  PRInt32 const trackID = characters.ToInteger(&rv, 10);
429  if (NS_SUCCEEDED(rv)) {
430  PRInt32 const * newTrackID = mTracks.AppendElement(trackID);
431  NS_ENSURE_TRUE(newTrackID, NS_ERROR_OUT_OF_MEMORY);
432  }
433  }
434  else if (!propertyName.IsEmpty()) {
435  mProperties->Set(propertyName, characters);
436  }
437  }
438  return NS_OK;
439 }
440 
441 /* void characters (in AString value); */
442 NS_IMETHODIMP sbiTunesXMLParser::Characters(const nsAString & value) {
443  LOG(("Characters: %s\n", NS_LossyConvertUTF16toASCII(value).get()));
444  // Calculate the bytes in the stream. This isn't perfect, but should
445  // be good enough for progress calculations
446  PRUnichar const * begin;
447  PRUnichar const * end;
448  value.BeginReading(&begin, &end);
449  while (begin != end) {
450  mBytesRead += NS_IsAscii(*begin++) ? 1 : 2;
451  }
452 
453  mCharacters.Append(value);
454  return NS_OK;
455 }
456 
457 /* void processingInstruction (in AString target, in AString data); */
458 NS_IMETHODIMP sbiTunesXMLParser::ProcessingInstruction(const nsAString & target,
459  const nsAString & data) {
460  // Ignore
461  return NS_OK;
462 }
463 
464 /* void ignorableWhitespace (in AString whitespace); */
465 NS_IMETHODIMP sbiTunesXMLParser::IgnorableWhitespace(const nsAString & whitespace) {
466  // Ignore
467  return NS_OK;
468 }
469 
470 /* void startPrefixMapping (in AString prefix, in AString uri); */
471 NS_IMETHODIMP sbiTunesXMLParser::StartPrefixMapping(const nsAString & prefix,
472  const nsAString & uri) {
473  // Ignore
474  return NS_OK;
475 }
476 
477 /* void endPrefixMapping (in AString prefix); */
478 NS_IMETHODIMP sbiTunesXMLParser::EndPrefixMapping(const nsAString & prefix) {
479  // Ignore
480  return NS_OK;
481 }
482 
483 /* nsSAXErrorHandler */
484 
485 /* void error (in nsISAXLocator locator, in AString error); */
486 NS_IMETHODIMP sbiTunesXMLParser::Error(nsISAXLocator *locator,
487  const nsAString & error) {
488  LogError("Error", locator, error);
489  PRBool continueParsing = PR_FALSE;
490  nsresult rv = mListener->OnError(BuildErrorMessage("Error", locator, error), &continueParsing);
491  NS_ENSURE_SUCCESS(rv, rv);
492 
493  return continueParsing ? NS_OK : NS_ERROR_FAILURE;
494 }
495 
496 /* void fatalError (in nsISAXLocator locator, in AString error); */
497 NS_IMETHODIMP sbiTunesXMLParser::FatalError(nsISAXLocator *locator,
498  const nsAString & error) {
499  LogError("Fatal error", locator, error);
500  PRBool continueParsing = PR_FALSE;
501  nsresult rv = mListener->OnError(BuildErrorMessage("Fatal error", locator, error), &continueParsing);
502  NS_ENSURE_SUCCESS(rv, rv);
503 
504  return continueParsing ? NS_OK : NS_ERROR_FAILURE;
505 }
506 
507 /* void ignorableWarning (in nsISAXLocator locator, in AString error); */
508 NS_IMETHODIMP sbiTunesXMLParser::IgnorableWarning(nsISAXLocator *locator,
509  const nsAString & error) {
510  LogError("Warning", locator, error);
511  PRBool continueParsing = PR_FALSE;
512  nsresult rv = mListener->OnError(BuildErrorMessage("Warning", locator, error), &continueParsing);
513  NS_ENSURE_SUCCESS(rv, rv);
514 
515  return continueParsing ? NS_OK : NS_ERROR_FAILURE;
516 }
517 
518 nsresult sbiTunesXMLParser::InitializeProperties() {
519 
520  nsresult rv = NS_OK;
521  if (!mProperties) {
522  mProperties = do_CreateInstance(SB_STRINGMAP_CONTRACTID, &rv);
523  }
524  else {
525  mProperties->Clear();
526  }
527  return rv;
528 }
NS_IMPL_THREADSAFE_ISUPPORTS3(sbiTunesXMLParser, sbIiTunesXMLParser, nsISAXContentHandler, nsISAXErrorHandler) char const XML_CONTENT_TYPE[]
return NS_OK
char const NS_SAXXMLREADER_CONTRACTID[]
tabData attributes[name]
void LogError(char const *aType, nsISAXLocator *aLocator, nsAString const &aError)
#define SB_STRINGMAP_CONTRACTID
void LogStartElement(const nsAString &aURI, const nsAString &aLocalName, const nsAString &qName, nsISAXAttributes *aAttributes)
nsCOMPtr< nsISAXXMLReader > nsISAXXMLReaderPtr
#define LOG(args)
var uri
Definition: FeedWriter.js:1135
countRef value
Definition: FeedWriter.js:1423
nsString BuildErrorMessage(char const *aType, nsISAXLocator *aLocator, nsAString const &aError)
function msg
observe data
Definition: FeedWriter.js:1329