1 /****************************************************************************************
2  * Copyright (c) 2010-2012 Leo Franchi <lfranchi@kde.org>                               *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #include "Parsing_p.h"
18 
19 #include "Artist.h"
20 #include "CatalogItem_p.h"
21 #include "Util.h"
22 #include "Genre.h"
23 #include "Catalog.h"
24 #include "CatalogArtist.h"
25 #include "qjsonwrapper/Json.h"
26 
27 #include <QtNetwork/QNetworkReply>
28 #include <QDateTime>
29 #include <QStringBuilder>
30 
checkForErrors(QNetworkReply * reply)31 void Echonest::Parser::checkForErrors( QNetworkReply* reply ) throw( Echonest::ParseError )
32 {
33     if( !reply )
34         throw ParseError( Echonest::UnknownError );
35 
36     // TODO sometimes this returns false when it shouldn't be? what's going on..
37 //     if( !reply->isFinished() )
38 //         throw ParseError( Echonest::UnfinishedQuery );
39 //
40     if( reply->error() != QNetworkReply::NoError && reply->error() != QNetworkReply::UnknownContentError ) {    // let UnknownContentError through so we parse it in readStatus with the proper error message
41         qDebug() << reply->errorString();
42         ParseError err( Echonest::NetworkError );
43         err.setNetworkError( reply->error() );
44 
45         throw err;
46     }
47 }
48 
readStatus(QXmlStreamReader & xml)49 void Echonest::Parser::readStatus( QXmlStreamReader& xml ) throw( Echonest::ParseError )
50 {
51     if( xml.readNextStartElement() ) {
52         // sanity checks
53         if( xml.atEnd() || xml.name() !=  QLatin1String( "response" ) )
54             throw ParseError( UnknownParseError );
55 
56         if( xml.readNextStartElement() ) {
57             if( xml.atEnd() || xml.name() != QLatin1String( "status" ) )
58                 throw ParseError( UnknownParseError );
59 
60             xml.readNextStartElement();
61             double version = xml.readElementText().toDouble();
62             // TODO use version for something?
63             Q_UNUSED(version);
64             xml.readNextStartElement();
65             Echonest::ErrorType code = static_cast< Echonest::ErrorType >( xml.readElementText().toInt() );
66             xml.readNextStartElement();
67             QString msg = xml.readElementText();
68             xml.readNextStartElement();
69 
70             if( code != Echonest::NoError ) {
71                 qDebug() << "Parse Error:" << code << msg;
72                 throw ParseError( code, msg );
73             }
74 
75             xml.readNext();
76         }
77 
78     } else {
79         throw ParseError( UnknownParseError );
80     }
81 }
82 
parseSongList(QXmlStreamReader & xml)83 QVector< Echonest::Song > Echonest::Parser::parseSongList( QXmlStreamReader& xml ) throw( Echonest::ParseError )
84 {
85     QVector< Echonest::Song > songs;
86 
87     xml.readNext();
88     while( !( xml.name() == QLatin1String( "songs" ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
89         // parse a song
90         songs.append( parseSong( xml ) );
91     }
92     return songs;
93 }
94 
parseSong(QXmlStreamReader & xml)95 Echonest::Song Echonest::Parser::parseSong( QXmlStreamReader& xml ) throw( Echonest::ParseError )
96 {
97     if( xml.atEnd() || xml.name() != QLatin1String( "song" ) )
98         throw ParseError( Echonest::UnknownParseError );
99 
100     Echonest::Song song;
101     while( !( xml.name() == QLatin1String( "song" ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
102         if( xml.name() == QLatin1String( "id" ) && xml.isStartElement() )
103             song.setId( xml.readElementText().toLatin1() );
104         else if( xml.name() == QLatin1String( "title" ) && xml.isStartElement() )
105             song.setTitle( xml.readElementText() );
106         else if( xml.name() == QLatin1String( "artist_id" ) && xml.isStartElement() )
107             song.setArtistId( xml.readElementText().toLatin1() );
108         else if( xml.name() == QLatin1String( "artist_name" ) && xml.isStartElement() )
109             song.setArtistName( xml.readElementText() );
110         else if( xml.name() == QLatin1String( "release" ) && xml.isStartElement() )
111             song.setRelease( xml.readElementText() );
112         else if( xml.name() == QLatin1String( "song_hotttnesss" ) && xml.isStartElement() )
113             song.setHotttnesss( xml.readElementText().toDouble() );
114         else if( xml.name() == QLatin1String( "artist_hotttnesss" ) && xml.isStartElement() )
115             song.setArtistHotttnesss( xml.readElementText().toDouble() );
116         else if( xml.name() == QLatin1String( "artist_familiarity" ) && xml.isStartElement() )
117             song.setArtistFamiliarity( xml.readElementText().toDouble() );
118         else if( xml.name() == QLatin1String( "tracks" ) && xml.isStartElement() ) {
119             song.setTracks( parseSongTrackBucket( xml ) );
120         } else if( xml.name() == QLatin1String( "artist_location" ) && xml.isStartElement() ) {
121             song.setArtistLocation( parseSongArtistLocation( xml ) );
122         } else if( xml.name() == QLatin1String( "audio_summary" ) && xml.isStartElement() ) {
123             song.setAudioSummary( parseAudioSummary( xml ) );
124         } else if( xml.name() == QLatin1String( "song_type" ) && xml.isStartElement() ) {
125             song.addSongType( xml.readElementText() );
126         }
127         xml.readNext();
128     }
129     xml.readNext(); // skip past the last </song>
130 
131     return song;
132 }
133 
134 
parseSongArtistLocation(QXmlStreamReader & xml)135 Echonest::ArtistLocation Echonest::Parser::parseSongArtistLocation( QXmlStreamReader& xml ) throw( Echonest::ParseError )
136 {
137     if( xml.atEnd() || xml.name() != QLatin1String( "artist_location" ) ) {
138         throw ParseError( Echonest::UnknownParseError );
139     }
140     /**
141      * while( !( xml.name() ==  "location" && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
142      x ml.readNex*tStartElement();
143      if( xml.name() == "location" )
144          song.setArtistLocation( xml.readElementText() );
145 }
146 xml.readNext();
147 **/
148     Echonest::ArtistLocation location;
149     while( !( xml.name() == QLatin1String( "artist_location" ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
150         if( xml.name() == QLatin1String( "latitude" ) && xml.isStartElement() ) {
151             location.latitude = xml.readElementText().toDouble();
152         } else if( xml.name() == QLatin1String( "longitude" ) && xml.isStartElement() ) {
153             location.longitude = xml.readElementText().toDouble();
154         } else if( xml.name() == QLatin1String( "location" ) && xml.isStartElement() ) {
155             location.location = xml.readElementText();
156         }
157         xml.readNext();
158     }
159 
160     return location;
161 }
162 
parseSongTrackBucket(QXmlStreamReader & xml)163 Echonest::Tracks Echonest::Parser::parseSongTrackBucket( QXmlStreamReader& xml ) throw( Echonest::ParseError )
164 {
165     if( xml.atEnd() || xml.name() != QLatin1String( "tracks" ) ) {
166         throw ParseError( Echonest::UnknownParseError );
167     }
168 
169     Echonest::Tracks tracks;
170     while( !( xml.name() == QLatin1String( "tracks" ) && xml.tokenType() == QXmlStreamReader::EndElement ) && ( xml.name() != QLatin1String( "track" ) || !xml.isEndElement() ) ) {
171         if( xml.name() == QLatin1String( "track" ) && xml.isStartElement() ) {
172             Echonest::Track track = parseTrack( xml );
173             tracks.append( track );
174         } else
175             xml.readNext();
176     }
177 
178     return tracks;
179 }
180 
181 
parseCatalogSongTracks(QXmlStreamReader & xml)182 Echonest::Tracks Echonest::Parser::parseCatalogSongTracks( QXmlStreamReader& xml ) throw( Echonest::ParseError )
183 {
184     if( xml.atEnd() || xml.name() != QLatin1String( "tracks" ) ) {
185         throw ParseError( Echonest::UnknownParseError );
186     }
187 
188     Echonest::Tracks tracks;
189     while( !( xml.name() == QLatin1String( "tracks" ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
190         if( xml.name() == QLatin1String( "track" ) && xml.isStartElement() ) {
191             tracks.append( Echonest::Track( xml.readElementText().toLatin1() ) );
192         }
193         xml.readNext();
194     }
195 
196     return tracks;
197 }
198 
parseTrack(QXmlStreamReader & xml)199 Echonest::Track Echonest::Parser::parseTrack( QXmlStreamReader& xml ) throw( Echonest::ParseError )
200 {
201     if( xml.atEnd() || xml.name() != QLatin1String( "track" ) ) {
202         throw ParseError( Echonest::UnknownParseError );
203     }
204     Echonest::Track track;
205     while( !( xml.name() == QLatin1String( "track" ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
206         if( xml.name() == QLatin1String( "id" ) && xml.isStartElement() )
207             track.setId( xml.readElementText().toLatin1() );
208         else if( xml.name() == QLatin1String( "title" ) && xml.isStartElement() )
209             track.setTitle( xml.readElementText() );
210         else if( xml.name() == QLatin1String( "artist" ) && xml.isStartElement() )
211             track.setArtist( xml.readElementText() );
212         else if( xml.name() == QLatin1String( "status" ) && xml.isStartElement() )
213             track.setStatus( Echonest::statusToEnum( xml.readElementText() ) );
214         else if( xml.name() == QLatin1String( "analyzer_version" ) && xml.isStartElement() )
215             track.setAnalyzerVersion( xml.readElementText() );
216         else if( xml.name() == QLatin1String( "release" ) && xml.isStartElement() )
217             track.setRelease( xml.readElementText() );
218         else if( xml.name() == QLatin1String( "song_id" ) && xml.isStartElement() )
219             track.setSong( Echonest::Song( xml.readElementText().toLatin1() ) );
220         else if( xml.name() == QLatin1String( "audio_md5" ) && xml.isStartElement() )
221             track.setAudioMD5( xml.readElementText().toLatin1() );
222         else if( xml.name() == QLatin1String( "bitrate" ) && xml.isStartElement() )
223             track.setBitrate( xml.readElementText().toInt() );
224         else if( xml.name() == QLatin1String( "samplerate" ) && xml.isStartElement() )
225             track.setSamplerate( xml.readElementText().toInt() );
226         else if( xml.name() == QLatin1String( "md5" ) && xml.isStartElement() )
227             track.setMD5( xml.readElementText().toLatin1() );
228         else if( xml.name() == QLatin1String( "catalog" ) && xml.isStartElement() )
229             track.setCatalog( xml.readElementText() );
230         else if( xml.name() == QLatin1String( "foreign_id" ) && xml.isStartElement() )
231             track.setForeignId( xml.readElementText().toLatin1() );
232         else if( xml.name() == QLatin1String( "release_image" ) && xml.isStartElement() )
233             track.setReleaseImage( QUrl( xml.readElementText(), QUrl::TolerantMode ) );
234         else if( xml.name() == QLatin1String( "preview_url" ) && xml.isStartElement() )
235             track.setPreviewUrl( QUrl( xml.readElementText(), QUrl::TolerantMode ) );
236         else if( xml.name() == QLatin1String( "audio_summary" ) && xml.isStartElement() ) {
237             track.setAudioSummary( parseAudioSummary( xml ) );
238             continue;
239         }
240         xml.readNext();
241     }
242     xml.readNext(); // skip past the last
243     return track;
244 }
245 
246 
parseAudioSummary(QXmlStreamReader & xml)247 Echonest::AudioSummary Echonest::Parser::parseAudioSummary( QXmlStreamReader& xml ) throw( Echonest::ParseError )
248 {
249     if( xml.atEnd() || xml.name() != QLatin1String( "audio_summary" ) ) {
250         throw ParseError( Echonest::UnknownParseError );
251     }
252 
253     Echonest::AudioSummary summary;
254     while( !( xml.name() == QLatin1String( "audio_summary" ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
255         if( xml.name() == QLatin1String( "key" ) && xml.isStartElement() )
256             summary.setKey( xml.readElementText().toInt() );
257         else if( xml.name() == QLatin1String( "analysis_url" ) && xml.isStartElement() )
258             summary.setAnalysisUrl( QUrl::fromEncoded( xml.readElementText().toUtf8(), QUrl::TolerantMode ) );
259         else if( xml.name() == QLatin1String( "tempo" ) && xml.isStartElement() )
260             summary.setTempo( xml.readElementText().toDouble() );
261         else if( xml.name() == QLatin1String( "mode" ) && xml.isStartElement() )
262             summary.setMode( xml.readElementText().toInt() );
263         else if( xml.name() == QLatin1String( "time_signature" ) && xml.isStartElement() )
264             summary.setTimeSignature( xml.readElementText().toInt() );
265         else if( xml.name() == QLatin1String( "duration" ) && xml.isStartElement() )
266             summary.setDuration( xml.readElementText().toDouble() );
267         else if( xml.name() == QLatin1String( "loudness" ) && xml.isStartElement() )
268             summary.setLoudness( xml.readElementText().toDouble() );
269         else if( xml.name() == QLatin1String( "danceability" ) && xml.isStartElement() )
270             summary.setDanceability( xml.readElementText().toDouble() );
271         else if( xml.name() == QLatin1String( "energy" ) && xml.isStartElement() )
272             summary.setEnergy( xml.readElementText().toDouble() );
273         else if( xml.name() == QLatin1String( "acousticness" ) && xml.isStartElement() )
274             summary.setAcousticness( xml.readElementText().toDouble() );
275         else if( xml.name() == QLatin1String( "speechiness" ) && xml.isStartElement() )
276             summary.setSpeechiness( xml.readElementText().toDouble() );
277         else if( xml.name() == QLatin1String( "liveness" ) && xml.isStartElement() )
278             summary.setLiveness( xml.readElementText().toDouble() );
279         else if( xml.name() == QLatin1String( "valence" ) && xml.isStartElement() )
280             summary.setValence( xml.readElementText().toDouble() );
281 
282         xml.readNext();
283     }
284 
285     return summary;
286 }
287 
288 // extract confidence, duration, start out of a list of them. same code for bars, beats, sections, tatums
289 template< class T >
extractTripleTuple(const QVariantList & list)290 inline QVector< T > extractTripleTuple( const QVariantList& list ) {
291     QVector< T > tList;
292     tList.reserve( list.size() );
293     for( QVariantList::const_iterator iter = list.constBegin(); iter != list.constEnd(); ++iter ) {
294         T t;
295         QVariantMap tMap = iter->toMap();
296         t.confidence = tMap.value( QLatin1String( "confidence" ), -1 ).toReal();
297         t.duration = tMap.value( QLatin1String( "duration" ), -1 ).toReal();
298         t.start = tMap.value( QLatin1String( "start" ), -1 ).toReal();
299 
300         tList.append( t );
301     }
302 //     qDebug() << "Parsed simple list:" << tList.size();
303     return tList;
304 }
305 
parseDetailedAudioSummary(QNetworkReply * reply,Echonest::AudioSummary & summary)306 void Echonest::Parser::parseDetailedAudioSummary( QNetworkReply* reply, Echonest::AudioSummary& summary ) throw( ParseError )
307 {
308    bool ok;
309    QByteArray jsonData = reply->readAll();
310    QVariant data = QJsonWrapper::parseJson( jsonData, &ok );
311    if( !ok ) {
312        qWarning() << "Failed to parse JSON data!" << jsonData;
313        throw ParseError( Echonest::UnknownParseError );
314     }
315     QVariantMap mainMap = data.toMap();
316     if( mainMap.contains( QLatin1String( "meta" ) ) ) {
317         QVariantMap metaMap = mainMap.value( QLatin1String( "meta" ) ).toMap();
318         summary.setAnalysisTime( metaMap.value( QLatin1String( "analysis_time" ), -1 ).toReal() );
319         summary.setAnalysisStatus( metaMap.value( QLatin1String( "status_code" ) ).toInt() );
320         summary.setDetailedStatus( metaMap.value( QLatin1String( "detailed_status" ) ).toString() );
321         summary.setAnalyzerVersion( metaMap.value( QLatin1String( "analyzer_version" ) ).toString() );
322         summary.setTimestamp( metaMap.value( QLatin1String( "analysis_time" ), -1 ).toReal() );
323     }
324     if( mainMap.contains( QLatin1String( "bars" ) ) ) {
325         QVariantList barList = mainMap.value( QLatin1String( "bars" ) ).toList();
326         summary.setBars( extractTripleTuple<Echonest::Bar>( barList ) );
327     }
328     if( mainMap.contains( QLatin1String( "beats" ) ) ) {
329         QVariantList beatList = mainMap.value( QLatin1String( "beats" ) ).toList();
330         summary.setBeats( extractTripleTuple<Echonest::Beat>( beatList ) );
331     }
332     if( mainMap.contains( QLatin1String( "sections" ) ) ) {
333         QVariantList sectionList = mainMap.value( QLatin1String( "sections" ) ).toList();
334         summary.setSections( extractTripleTuple<Echonest::Section>( sectionList ) );
335     }
336     if( mainMap.contains( QLatin1String( "segments" ) ) ) {
337         QVariantList segmentList = mainMap.value( QLatin1String( "segments" ) ).toList();
338         Echonest::SegmentList segments;
339         segments.reserve( segmentList.size() );
340         for( QVariantList::const_iterator iter = segmentList.constBegin(); iter != segmentList.constEnd(); ++iter ) {
341             Echonest::Segment segment;
342             QVariantMap segmentMap = iter->toMap();
343             segment.confidence = segmentMap.value( QLatin1String( "confidence" ), -1 ).toReal();
344             segment.duration = segmentMap.value( QLatin1String( "duration" ), -1 ).toReal();
345             segment.loudness_max = segmentMap.value( QLatin1String( "loudness_max" ), -1 ).toReal();
346             segment.loudness_max_time = segmentMap.value( QLatin1String( "loudness_max_time" ), -1 ).toReal();
347             segment.loudness_start = segmentMap.value( QLatin1String( "loudness_start" ), -1 ).toReal();
348             // pitches
349             QVariantList pitchesList = segmentMap.value( QLatin1String( "pitches" ) ).toList();
350             QVector< qreal > pitches;
351             pitches.reserve( pitchesList.size() );
352             for( QVariantList::const_iterator piter = pitchesList.constBegin(); piter != pitchesList.constEnd(); ++piter )
353                 pitches.append( piter->toReal() );
354             segment.pitches = pitches;
355             segment.start = segmentMap.value( QLatin1String( "start" ), -1 ).toReal();
356             // timbre
357             QVariantList timbreList = segmentMap.value( QLatin1String( "timbre" ) ).toList();
358             QVector< qreal > timbres;
359             timbres.reserve( timbreList.size() );
360             for( QVariantList::const_iterator titer = timbreList.constBegin(); titer != timbreList.constEnd(); ++titer )
361                 timbres.append( titer->toReal() );
362             segment.timbre = timbres;
363             segments.append( segment );
364         }
365         summary.setSegments( segments );
366     }
367     if( mainMap.contains( QLatin1String( "tatums" ) ) ) {
368         QVariantList tatumList = mainMap.value( QLatin1String( "tatums" ) ).toList();
369         summary.setTatums( extractTripleTuple<Echonest::Tatum>( tatumList ) );
370     }
371     if( mainMap.contains( QLatin1String( "track" ) ) ) {
372         QVariantMap trackMap = mainMap.value( QLatin1String( "track" ) ).toMap();
373         summary.setSampleRate( trackMap.value( QLatin1String( "analysis_sample_rate" ), -1 ).toReal() );
374         summary.setEndOfFadeIn( trackMap.value( QLatin1String( "end_of_fade_in" ), -1 ).toReal() );
375         summary.setKeyConfidence( trackMap.value( QLatin1String( "key_confidence" ), -1 ).toReal() );
376         summary.setModeConfidence( trackMap.value( QLatin1String( "mode_confidence" ), -1 ).toReal() );
377         summary.setNumSamples( trackMap.value( QLatin1String( "num_samples" ), -1 ).toLongLong() );
378         summary.setSampleMD5( trackMap.value( QLatin1String( "sample_md5" ) ).toString() );
379         summary.setStartOfFadeOut( trackMap.value( QLatin1String( "start_of_fade_out" ), -1 ).toReal() );
380         summary.setTempoConfidence( trackMap.value( QLatin1String( "tempo_confidence" ), -1 ).toReal() );
381         summary.setTimeSignatureConfidence( trackMap.value( QLatin1String( "time_signature_confidence" ), -1 ).toReal() );
382     }
383 }
384 
385 
parseArtists(QXmlStreamReader & xml)386 Echonest::Artists Echonest::Parser::parseArtists( QXmlStreamReader& xml ) throw( Echonest::ParseError )
387 {
388     // we expect to be in an <artists> start element
389     if( xml.atEnd() || xml.name() != QLatin1String( "artists" ) || !xml.isStartElement() )
390         throw ParseError( Echonest::UnknownParseError );
391 
392     xml.readNextStartElement();
393 
394     Echonest::Artists artists;
395     while( !xml.atEnd() && ( xml.name() != QLatin1String( "artists" ) || !xml.isEndElement() ) ) {
396         if( xml.atEnd() || xml.name() != QLatin1String( "artist" ) || !xml.isStartElement() )
397             throw Echonest::ParseError( Echonest::UnknownParseError );
398         Echonest::Artist artist;
399         while( !xml.atEnd() && ( xml.name() != QLatin1String( "artist" ) || !xml.isEndElement() ) ) {
400             parseArtistInfo( xml, artist );
401             xml.readNextStartElement();
402         }
403         artists.append( artist );
404 
405         xml.readNext();
406     }
407     return artists;
408 }
409 
parseArtistInfoOrProfile(QXmlStreamReader & xml,Echonest::Artist & artist)410 int Echonest::Parser::parseArtistInfoOrProfile( QXmlStreamReader& xml , Echonest::Artist& artist  ) throw( Echonest::ParseError )
411 {
412     if( xml.name() == QLatin1String( "start" ) ) { // this is an individual info query, so lets read it
413         xml.readNextStartElement();
414         xml.readNext();
415 
416         int results = -1;
417         if( xml.name() == QLatin1String( "total" ) ) {
418             results = xml.readElementText().toInt();
419             xml.readNextStartElement();
420         }
421 
422         parseArtistInfo( xml, artist );
423 
424         return results;
425     } else if( xml.name() == QLatin1String( "songs" ) ) {
426         parseArtistSong( xml, artist );
427     } else if( xml.name() == QLatin1String( "urls" ) ) { // urls also has no start/total
428         parseUrls( xml, artist );
429     } else { // this is either a profile query, or a familiarity or hotttness query, so save all the data we find
430         while( !( xml.name() == QLatin1String( "artist" ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
431             parseArtistInfo( xml, artist );
432             xml.readNextStartElement();
433         }
434     }
435 
436     return 0;
437 }
438 
parseArtistInfo(QXmlStreamReader & xml,Echonest::Artist & artist)439 void Echonest::Parser::parseArtistInfo( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
440 {
441     // parse each sort of artist information
442     if( xml.name() == QLatin1String( "audio" ) ) {
443         parseAudio( xml, artist );
444     } else if( xml.name() == QLatin1String( "biographies" ) ) {
445         parseBiographies( xml, artist );
446     } else if( xml.name() == QLatin1String( "familiarity" ) ) {
447         artist.setFamiliarity( xml.readElementText().toDouble() );
448     }  else if( xml.name() == QLatin1String( "hotttnesss" ) ) {
449         artist.setHotttnesss( xml.readElementText().toDouble() );
450     }  else if( xml.name() == QLatin1String( "images" ) ) {
451         parseImages( xml, artist );
452     }  else if( xml.name() == QLatin1String( "news" ) && xml.isStartElement() ) {
453         parseNewsOrBlogs( xml, artist, true );
454     }  else if( xml.name() == QLatin1String( "blogs" ) ) {
455         parseNewsOrBlogs( xml, artist, false );
456     }  else if( xml.name() == QLatin1String( "reviews" ) ) {
457         parseReviews( xml, artist );
458     }  else if( xml.name() == QLatin1String( "terms" ) ) {
459         parseTerms( xml, artist );
460     }  else if( xml.name() == QLatin1String( "urls" ) ) {
461         parseUrls( xml, artist );
462     }  else if( xml.name() == QLatin1String( "songs" ) ) {
463         parseArtistSong( xml, artist );
464     }  else if( xml.name() == QLatin1String( "video" ) ) {
465         parseVideos( xml, artist );
466     }  else if( xml.name() == QLatin1String( "foreign_ids" ) ) {
467         parseForeignArtistIds( xml, artist );
468     }  else if( xml.name() == QLatin1String( "name" ) ) {
469         artist.setName( xml.readElementText() );
470     }  else if( xml.name() == QLatin1String( "id" ) ) {
471         artist.setId( xml.readElementText().toLatin1() );
472     } else if( xml.name() == QLatin1String( "genres" ) ) {
473         parseArtistGenres( xml, artist );
474     } else if ( xml.name() == QLatin1String( "twitter" ) ) {
475         artist.setTwitter( xml.readElementText() );
476     }
477 }
478 
479 
480 // parse each type of artist attribute
481 
parseAudio(QXmlStreamReader & xml,Echonest::Artist & artist)482 void Echonest::Parser::parseAudio( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
483 {
484     if( xml.atEnd() || xml.name() != QLatin1String( "audio" ) || xml.tokenType() != QXmlStreamReader::StartElement )
485         throw Echonest::ParseError( Echonest::UnknownParseError );
486 
487     xml.readNextStartElement();
488     Echonest::AudioList audioList;
489     while( !xml.atEnd() && ( xml.name() != QLatin1String( "audio" ) || xml.tokenType() != QXmlStreamReader::EndElement ) ) {
490         Echonest::AudioFile audio;
491         do {
492             xml.readNext();
493             if( xml.name() == QLatin1String( "title" ) )
494                 audio.setTitle( xml.readElementText() );
495             else if( xml.name() == QLatin1String( "url" ) )
496                 audio.setUrl( QUrl( xml.readElementText() ) );
497             else if( xml.name() == QLatin1String( "artist" ) )
498                 audio.setArtist(  xml.readElementText() );
499             else if( xml.name() == QLatin1String( "date" ) )
500                 audio.setDate( QDateTime::fromString( xml.readElementText(), Qt::ISODate ) );
501             else if( xml.name() == QLatin1String( "length" ) )
502                 audio.setLength( xml.readElementText().toDouble() );
503             else if( xml.name() == QLatin1String( "link" ) )
504                 audio.setLink( QUrl( xml.readElementText() ) );
505             else if( xml.name() == QLatin1String( "release" ) )
506                 audio.setRelease( xml.readElementText() );
507             else if( xml.name() == QLatin1String( "id" ) )
508                 audio.setId( xml.readElementText().toLatin1() );
509 
510         } while( !xml.atEnd() && ( xml.name() != QLatin1String( "audio" ) || xml.tokenType() != QXmlStreamReader::EndElement ) );
511         audioList.append( audio );
512         xml.readNext();
513     }
514     artist.setAudio( audioList );
515 }
516 
parseBiographies(QXmlStreamReader & xml,Echonest::Artist & artist)517 void Echonest::Parser::parseBiographies( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
518 {
519     if( xml.atEnd() || xml.name() != QLatin1String( "biographies" ) || xml.tokenType() != QXmlStreamReader::StartElement )
520         throw Echonest::ParseError( Echonest::UnknownParseError );
521 
522     xml.readNextStartElement();
523     Echonest::BiographyList bios;
524     while( !xml.atEnd() && ( xml.name() != QLatin1String( "biographies" ) || xml.tokenType() != QXmlStreamReader::EndElement ) ) {
525         Echonest::Biography bio;
526         do {
527             xml.readNext();
528 
529             if( xml.name() == QLatin1String( "text" ) )
530                 bio.setText( xml.readElementText() );
531             else if( xml.name() == QLatin1String( "site" ) )
532                 bio.setSite( xml.readElementText() );
533             else if( xml.name() == QLatin1String( "url" ) )
534                 bio.setUrl( QUrl( xml.readElementText() ) );
535             else if( xml.name() == QLatin1String( "license" ) )
536                 bio.setLicense( parseLicense( xml) );
537 
538         } while( !xml.atEnd() && ( xml.name() != QLatin1String( "biography" ) || xml.tokenType() != QXmlStreamReader::EndElement ) );
539         bios.append( bio );
540         xml.readNext();
541     }
542     artist.setBiographies( bios );
543 }
544 
545 
parseImages(QXmlStreamReader & xml,Echonest::Artist & artist)546 void Echonest::Parser::parseImages( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
547 {
548     if( xml.atEnd() || xml.name() != QLatin1String( "images" ) || xml.tokenType() != QXmlStreamReader::StartElement )
549         throw Echonest::ParseError( Echonest::UnknownParseError );
550 
551     xml.readNextStartElement();
552     Echonest::ArtistImageList imgs;
553     while( !xml.atEnd() && ( xml.name() != QLatin1String( "images" ) || xml.tokenType() != QXmlStreamReader::EndElement ) ) {
554         Echonest::ArtistImage img;
555         do {
556             xml.readNext();
557 
558             if( xml.name() == QLatin1String( "url" ) )
559                 img.setUrl( QUrl( xml.readElementText() ) );
560             else if( xml.name() == QLatin1String( "license" ) )
561                 img.setLicense( parseLicense( xml) );
562 
563         } while( !xml.atEnd() && ( xml.name() != QLatin1String( "image" ) || xml.tokenType() != QXmlStreamReader::EndElement ) );
564         imgs.append( img );
565         xml.readNext();
566     }
567     artist.setImages( imgs );
568 }
569 
parseNewsOrBlogs(QXmlStreamReader & xml,Echonest::Artist & artist,bool news)570 void Echonest::Parser::parseNewsOrBlogs( QXmlStreamReader& xml, Echonest::Artist& artist, bool news  ) throw( Echonest::ParseError )
571 {
572     if( news && ( xml.atEnd() || xml.name() != QLatin1String( "news" ) || xml.tokenType() != QXmlStreamReader::StartElement ) )
573         throw Echonest::ParseError( Echonest::UnknownParseError );
574     else if( !news && ( xml.atEnd() || xml.name() != QLatin1String( "blogs" ) || xml.tokenType() != QXmlStreamReader::StartElement ) )
575         throw Echonest::ParseError( Echonest::UnknownParseError );
576 
577     xml.readNextStartElement();
578     Echonest::NewsList newsList;
579     while( !( ( xml.name() == QLatin1String( "news" ) || xml.name() == QLatin1String( "blogs" ) ) && xml.tokenType() == QXmlStreamReader::EndElement ) ) {
580         Echonest::NewsArticle news;
581         do {
582             xml.readNextStartElement();
583 
584             if( xml.name() == QLatin1String( "name" ) )
585                 news.setName( xml.readElementText() );
586             else if( xml.name() == QLatin1String( "url" ) )
587                 news.setUrl( QUrl( xml.readElementText() ) );
588             else if( xml.name() == QLatin1String( "summary" ) )
589                 news.setSummary(  xml.readElementText() );
590             else if( xml.name() == QLatin1String( "date_found" ) )
591                 news.setDateFound( QDateTime::fromString( xml.readElementText(), Qt::ISODate ) );
592             else if( xml.name() == QLatin1String( "id" ) )
593                 news.setId( xml.readElementText().toLatin1() );
594             else if( xml.name() == QLatin1String( "date_posted" ) )
595                 news.setDatePosted( QDateTime::fromString( xml.readElementText(), Qt::ISODate ) );
596         } while( !( ( xml.name() == QLatin1String( "news" ) || xml.name() == QLatin1String( "blog" ) ) && xml.tokenType() == QXmlStreamReader::EndElement ) );
597         newsList.append( news );
598         xml.readNext();
599     }
600     if( news )
601         artist.setNews( newsList );
602     else
603         artist.setBlogs( newsList );
604 }
605 
parseReviews(QXmlStreamReader & xml,Echonest::Artist & artist)606 void Echonest::Parser::parseReviews( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
607 {
608     if( xml.atEnd() || xml.name() != QLatin1String( "reviews" ) || xml.tokenType() != QXmlStreamReader::StartElement )
609         throw Echonest::ParseError( Echonest::UnknownParseError );
610 
611     xml.readNextStartElement();
612     Echonest::ReviewList reviews;
613     while( !xml.atEnd() && ( xml.name() != QLatin1String( "reviews" ) || xml.tokenType() != QXmlStreamReader::EndElement ) ) {
614         Echonest::Review review;
615         do {
616             xml.readNextStartElement();
617 
618             if( xml.name() == QLatin1String( "url" ) )
619                 review.setUrl( QUrl( xml.readElementText() ) );
620             else if( xml.name() == QLatin1String( "name" ) )
621                 review.setName( xml.readElementText() );
622             else if( xml.name() == QLatin1String( "summary" ) )
623                 review.setSummary( xml.readElementText() );
624             else if( xml.name() == QLatin1String( "date_found" ) )
625                 review.setDateFound( QDateTime::fromString( xml.readElementText(), Qt::ISODate ) );
626             else if( xml.name() == QLatin1String( "image" ) )
627                 review.setImageUrl( QUrl( xml.readElementText() ) );
628             else if( xml.name() == QLatin1String( "release" ) )
629                 review.setRelease( xml.readElementText() );
630             else if( xml.name() == QLatin1String( "id" ) )
631                 review.setId( xml.readElementText().toLatin1() );
632 
633         } while( !xml.atEnd() && ( xml.name() != QLatin1String( "review" ) || xml.tokenType() != QXmlStreamReader::EndElement ) );
634         reviews.append( review );
635         xml.readNext();
636     }
637     artist.setReviews( reviews );
638 }
639 
parseArtistSong(QXmlStreamReader & xml,Echonest::Artist & artist)640 void Echonest::Parser::parseArtistSong( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
641 {
642     if( xml.atEnd() || xml.name() != QLatin1String( "songs" ) || xml.tokenType() != QXmlStreamReader::StartElement )
643         throw Echonest::ParseError( Echonest::UnknownParseError );
644 
645     xml.readNextStartElement();
646     Echonest::SongList songs;
647     while( !xml.atEnd() && ( xml.name() != QLatin1String( "songs" ) || xml.tokenType() != QXmlStreamReader::EndElement ) ) {
648         if( xml.name() == QLatin1String( "song" ) && xml.isStartElement() )
649         {
650             Echonest::Song song;
651             while( !xml.atEnd() && ( xml.name() != QLatin1String( "song" ) || !xml.isEndElement() ) ) {
652                 if( xml.name() == QLatin1String( "id" ) && xml.isStartElement() )
653                     song.setId( xml.readElementText().toLatin1() );
654                 else if( xml.name() == QLatin1String( "title" ) && xml.isStartElement() )
655                     song.setTitle( xml.readElementText() );
656                 xml.readNextStartElement();
657             }
658             songs.append( song );
659         }
660         xml.readNext();
661     }
662     artist.setSongs( songs );
663 }
664 
parseTerms(QXmlStreamReader & xml,Echonest::Artist & artist)665 void Echonest::Parser::parseTerms( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
666 {
667     if( xml.atEnd() || xml.name() != QLatin1String( "terms" ) || xml.tokenType() != QXmlStreamReader::StartElement )
668         throw Echonest::ParseError( Echonest::UnknownParseError );
669     artist.setTerms( parseTopTermList( xml ) );
670 }
671 
parseArtistSuggestList(QXmlStreamReader & xml)672 Echonest::Artists Echonest::Parser::parseArtistSuggestList( QXmlStreamReader& xml ) throw( ParseError )
673 {
674     if( xml.atEnd() || xml.name() != QLatin1String( "artists" ) || xml.tokenType() != QXmlStreamReader::StartElement )
675         throw Echonest::ParseError( Echonest::UnknownParseError );
676 
677     Echonest::Artists artists;
678 
679     xml.readNextStartElement();
680     while( xml.name() != QLatin1String( "artists" ) || !xml.isEndElement() ) {
681         QString name;
682         QByteArray id;
683         while( xml.name() != QLatin1String( "artist" ) || !xml.isEndElement() ) {
684             if( xml.name() == QLatin1String( "name" ) && xml.isStartElement() )
685                 name = xml.readElementText();
686             else if( xml.name() == QLatin1String( "id" ) && xml.isStartElement() )
687                 id = xml.readElementText().toLatin1();
688             xml.readNext();
689         }
690         artists << Echonest::Artist( id, name );
691         xml.readNext();
692     }
693     return artists;
694 }
695 
696 
parseUrls(QXmlStreamReader & xml,Echonest::Artist & artist)697 void Echonest::Parser::parseUrls( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
698 {
699     if( xml.atEnd() || xml.name() != QLatin1String( "urls" ) || xml.tokenType() != QXmlStreamReader::StartElement )
700         throw Echonest::ParseError( Echonest::UnknownParseError );
701     xml.readNextStartElement();
702 //     xml.readNextStartElement();
703 
704     while( !xml.atEnd() && ( xml.name() != QLatin1String( "urls" ) || !xml.isEndElement() ) ) {
705         if( xml.name() == QLatin1String( "lastfm_url" ) )
706             artist.setLastFmUrl( QUrl( xml.readElementText() ) );
707         else if( xml.name() == QLatin1String( "aolmusic_url" ) )
708             artist.setAolMusicUrl( QUrl( xml.readElementText() ) );
709         else if( xml.name() == QLatin1String( "myspace_url" ) )
710             artist.setMyspaceUrl( QUrl( xml.readElementText() ) );
711         else if( xml.name() == QLatin1String( "amazon_url" ) )
712             artist.setAmazonUrl( QUrl( xml.readElementText() ) );
713         else if( xml.name() == QLatin1String( "itunes_url" ) )
714             artist.setItunesUrl( QUrl( xml.readElementText() ) );
715         else if( xml.name() == QLatin1String( "mb_url" ) )
716             artist.setMusicbrainzUrl( QUrl( xml.readElementText() ) );
717 
718         xml.readNextStartElement();
719     }
720     xml.readNextStartElement();
721 }
722 
parseVideos(QXmlStreamReader & xml,Echonest::Artist & artist)723 void Echonest::Parser::parseVideos( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
724 {
725     if( xml.atEnd() || xml.name() != QLatin1String( "video" )|| xml.tokenType() != QXmlStreamReader::StartElement )
726         throw Echonest::ParseError( Echonest::UnknownParseError );
727 
728     Echonest::VideoList videos;
729     while( xml.name() == QLatin1String( "video" ) && xml.isStartElement() ) {
730 
731         Echonest::Video video;
732 
733         while( !xml.atEnd() && ( xml.name() != QLatin1String( "video" ) || !xml.isEndElement() ) ) {
734             if( xml.name() == QLatin1String( "title" ) )
735                 video.setTitle( xml.readElementText() );
736             else if( xml.name() == QLatin1String( "url" ) )
737                 video.setUrl( QUrl( xml.readElementText() ) );
738             else if( xml.name() == QLatin1String( "site" ) )
739                 video.setSite( xml.readElementText() );
740             else if( xml.name() == QLatin1String( "date_found" ) )
741                 video.setDateFound( QDateTime::fromString( xml.readElementText(), Qt::ISODate ) );
742             else if( xml.name() == QLatin1String( "image_url" ) )
743                 video.setImageUrl( QUrl( xml.readElementText() ) );
744             else if( xml.name() == QLatin1String( "id" ) )
745                 video.setId( xml.readElementText().toLatin1() );
746 
747             xml.readNextStartElement();
748         }
749         videos.append( video );
750 
751         xml.readNextStartElement();
752     }
753     artist.setVideos( videos );
754 }
755 
parseTopTermList(QXmlStreamReader & xml)756 Echonest::TermList Echonest::Parser::parseTopTermList( QXmlStreamReader& xml ) throw( Echonest::ParseError )
757 {
758     if( xml.atEnd() || xml.name() != QLatin1String( "terms" ) || xml.tokenType() != QXmlStreamReader::StartElement )
759         throw Echonest::ParseError( Echonest::UnknownParseError );
760 
761     Echonest::TermList terms;
762     while( xml.name() == QLatin1String( "terms" ) && xml.isStartElement() ) {
763         Echonest::Term term;
764 //         qDebug() << "Parsing term outer item:" << xml.name() << xml.isStartElement();
765         while( !xml.atEnd() && ( xml.name() != QLatin1String( "terms" ) || !xml.isEndElement() ) ) {
766             if( xml.name() == QLatin1String( "frequency" ) )
767                 term.setFrequency( xml.readElementText().toDouble() );
768             else if( xml.name() == QLatin1String( "name" ) )
769                 term.setName( xml.readElementText() );
770             else if( xml.name() == QLatin1String( "weight" ) )
771                 term.setWeight( xml.readElementText().toDouble() );
772 
773             xml.readNextStartElement();
774         }
775         terms.append( term );
776 //         qDebug() << "Parsing exernal term item:" << xml.name() << xml.isStartElement();
777 
778         xml.readNext();
779     }
780 //     qDebug() << " done Parsing terms:" << xml.name() << xml.isStartElement();
781 
782     return terms;
783 }
784 
785 
parseTermList(QXmlStreamReader & xml)786 QVector< QString > Echonest::Parser::parseTermList( QXmlStreamReader& xml ) throw( Echonest::ParseError )
787 {
788     if( xml.atEnd() || xml.name() != QLatin1String( "terms" ) || xml.tokenType() != QXmlStreamReader::StartElement )
789         throw Echonest::ParseError( Echonest::UnknownParseError );
790 
791     QVector< QString > terms;
792     while( xml.name() != QLatin1String( "response" ) || !xml.isEndElement() ) {
793         if( xml.name() == QLatin1String( "name" ) && xml.isStartElement() )
794             terms.append( xml.readElementText() );
795         xml.readNextStartElement();
796     }
797 
798     return terms;
799 }
800 
parseGenreListStrings(QXmlStreamReader & xml)801 QVector< QString > Echonest::Parser::parseGenreListStrings( QXmlStreamReader& xml ) throw( Echonest::ParseError )
802 {
803     xml.readNextStartElement();
804     xml.readNextStartElement();
805 
806     if( xml.atEnd() || xml.name() != QLatin1String( "genres" ) || xml.tokenType() != QXmlStreamReader::StartElement )
807         throw Echonest::ParseError( Echonest::UnknownParseError );
808 
809     QVector< QString > genres;
810     while( xml.name() != QLatin1String( "response" ) || !xml.isEndElement() ) {
811         if( xml.name() == QLatin1String( "name" ) && xml.isStartElement() )
812             genres.append( xml.readElementText() );
813         xml.readNextStartElement();
814     }
815 
816     return genres;
817 }
818 
parseGenres(QXmlStreamReader & xml)819 Echonest::Genres Echonest::Parser::parseGenres( QXmlStreamReader& xml ) throw( Echonest::ParseError )
820 {
821     if ( xml.name() == QLatin1String( "start" ) )
822         xml.readNextStartElement();
823     if ( xml.name() == QLatin1String( "start" ) )
824         xml.readNextStartElement(); // skip <start>
825 
826     if( xml.atEnd() || xml.name() != QLatin1String( "genres" ) || xml.tokenType() != QXmlStreamReader::StartElement )
827         throw Echonest::ParseError( Echonest::UnknownParseError );
828 
829     Genres genres;
830     while( !(xml.isEndElement() && xml.name() == QLatin1String( "genres" ) ) /*xml.name() != QLatin1String( "response" ) || !xml.isEndElement() ||*/  ){
831         if (!xml.isStartElement())
832             xml.readNextStartElement();
833         if( xml.name() == QLatin1String( "genre" ) && xml.isStartElement() ) {
834             Genre g = parseGenre( xml );
835             genres.append( g );
836         }
837         xml.readNext();
838     }
839     return genres;
840 }
841 
parseGenre(QXmlStreamReader & xml)842 Echonest::Genre Echonest::Parser::parseGenre( QXmlStreamReader& xml ) throw( Echonest::ParseError )
843 {
844     Genre g;
845     while ( !( xml.isEndElement() && xml.name() == QLatin1String( "genre" ) ) ) {
846         if ( xml.name() == QLatin1String( "name" ) && xml.isStartElement() )
847             g.setName( xml.readElementText() );
848         else if ( xml.name() == QLatin1String( "description" ) )
849             g.setDescription( xml.readElementText() );
850         else if ( xml.name() == QLatin1String( "urls" ) ) {
851             xml.readNextStartElement();
852             if ( xml.name() == QLatin1String( "urls" ) ) {
853                 xml.readNextStartElement();
854                 if ( xml.name() == QLatin1String( "wikipedia_url" ) )
855                     g.setWikipediaUrl( xml.readElementText() );
856             }
857         }
858         xml.readNextStartElement();
859     }
860     return g;
861 }
862 
parseArtistGenres(QXmlStreamReader & xml,Echonest::Artist & artist)863 void Echonest::Parser::parseArtistGenres( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
864 {
865     Genres genres = parseGenres( xml );
866     artist.setGenres( genres );
867 }
868 
parseForeignArtistIds(QXmlStreamReader & xml,Echonest::Artist & artist)869 void Echonest::Parser::parseForeignArtistIds( QXmlStreamReader& xml, Echonest::Artist& artist ) throw( Echonest::ParseError )
870 {
871     if( xml.atEnd() || xml.name() != QLatin1String( "foreign_ids" ) || xml.tokenType() != QXmlStreamReader::StartElement )
872         throw Echonest::ParseError( Echonest::UnknownParseError );
873 
874     Echonest::ForeignIds ids;
875     while( xml.name() != QLatin1String( "foreign_ids" ) || !xml.isEndElement() ) {
876         xml.readNext();
877         xml.readNext(); // get past the enclosing <foreign_id>, or else we'll think it's the internal one.
878         Echonest::ForeignId id;
879         while( xml.name() != QLatin1String( "foreign_id" ) || !xml.isEndElement() ) {
880             if( xml.name() == QLatin1String( "catalog" ) && xml.isStartElement() )
881                 id.catalog = xml.readElementText();
882             else if( xml.name() == QLatin1String( "foreign_id" ) && xml.isStartElement() )
883                 id.foreign_id = xml.readElementText();
884 
885             xml.readNext();
886         }
887         ids.append( id );
888         xml.readNext();
889     }
890     artist.setForeignIds( ids );
891 }
892 
parseLicense(QXmlStreamReader & xml)893 Echonest::License Echonest::Parser::parseLicense( QXmlStreamReader& xml ) throw( Echonest::ParseError )
894 {
895     if( xml.atEnd() || xml.name() != QLatin1String( "license" ) || xml.tokenType() != QXmlStreamReader::StartElement )
896         throw Echonest::ParseError( Echonest::UnknownParseError );
897 
898     Echonest::License license;
899     while( !xml.atEnd() && ( xml.name() != QLatin1String( "license" ) || xml.tokenType() != QXmlStreamReader::EndElement ) ) {
900         if( xml.name() == QLatin1String( "type" ) )
901             license.type = xml.readElementText();
902         else if( xml.name() == QLatin1String( "attribution" ) )
903             license.attribution = xml.readElementText();
904         else if( xml.name() == QLatin1String( "url" ) )
905             license.url = QUrl( xml.readElementText() );
906 
907         xml.readNext();
908     }
909 
910     xml.readNextStartElement();
911     return license;
912 }
913 
parsePlaylistSessionId(QXmlStreamReader & xml)914 QByteArray Echonest::Parser::parsePlaylistSessionId( QXmlStreamReader& xml ) throw( Echonest::ParseError )
915 {
916     if( xml.atEnd() || xml.name() != QLatin1String( "session_id" ) || xml.tokenType() != QXmlStreamReader::StartElement )
917         throw Echonest::ParseError( Echonest::UnknownParseError );
918 
919     QByteArray sessionId = xml.readElementText().toLatin1();
920     xml.readNext(); //read to next start element
921     return sessionId;
922 }
923 
parseDynamicLookahead(QXmlStreamReader & xml)924 Echonest::SongList Echonest::Parser::parseDynamicLookahead( QXmlStreamReader& xml ) throw( Echonest::ParseError )
925 {
926     if( xml.atEnd() || xml.tokenType() != QXmlStreamReader::StartElement )
927         throw Echonest::ParseError( Echonest::UnknownParseError );
928 
929     Echonest::SongList lookahead;
930 
931     // Might not be any
932     if ( xml.name() != QLatin1String( "lookahead" ) )
933         return lookahead;
934 
935     while( !xml.atEnd() && ( xml.name() == QLatin1String( "lookahead" ) && xml.tokenType() == QXmlStreamReader::StartElement ) ) {
936         // Read each lookahead track
937         Echonest::Song song;
938         while( !xml.atEnd() && ( xml.name() != QLatin1String( "lookahead" ) || xml.tokenType() != QXmlStreamReader::EndElement ) ) {
939             if( xml.name() == QLatin1String( "id" ) && xml.isStartElement() )
940                 song.setId( xml.readElementText().toLatin1() );
941             else if( xml.name() == QLatin1String( "title" ) && xml.isStartElement() )
942                 song.setTitle( xml.readElementText() );
943             else if( xml.name() == QLatin1String( "artist_id" ) && xml.isStartElement() )
944                 song.setArtistId( xml.readElementText().toLatin1() );
945             else if( xml.name() == QLatin1String( "artist_name" ) && xml.isStartElement() )
946                 song.setArtistName( xml.readElementText() );
947 
948             xml.readNext();
949         }
950         if ( !( song.id().isEmpty() && song.title().isEmpty() && song.artistId().isEmpty() && song.artistName().isEmpty() ) )
951             lookahead.append(song);
952 
953         xml.readNext();
954     }
955 
956     return lookahead;
957 }
958 
959 // Catalogs parseCatalogList( QXmlStreamReader& xml ) throw( ParseError );
parseCatalogList(QXmlStreamReader & xml)960 Echonest::Catalogs Echonest::Parser::parseCatalogList( QXmlStreamReader& xml ) throw( Echonest::ParseError )
961 {
962     if( xml.atEnd() || xml.tokenType() != QXmlStreamReader::StartElement )
963         throw Echonest::ParseError( Echonest::UnknownParseError );
964 
965     int total = -1;
966     while( xml.name() != QLatin1String( "response" ) && ( xml.name() != QLatin1String( "catalogs" ) || !xml.isStartElement() ) ) {
967         if( xml.name() == QLatin1String( "total" ) && xml.isStartElement() )
968             total = xml.readElementText().toInt();
969         xml.readNextStartElement();
970     }
971 
972 
973     Echonest::Catalogs catalogs;
974 
975     if( xml.name() != QLatin1String( "catalogs" ) ) { // none
976         return catalogs;
977     }
978 
979     catalogs.reserve( total );
980     // now we're pointing at the first catalog
981     while( xml.name() != QLatin1String( "response" ) || !xml.isEndElement() )
982         catalogs.append( Echonest::Parser::parseCatalog( xml ) );
983 
984     return catalogs;
985 }
986 
parseCatalog(QXmlStreamReader & xml,bool justOne)987 Echonest::Catalog Echonest::Parser::parseCatalog( QXmlStreamReader& xml, bool justOne ) throw( Echonest::ParseError )
988 {
989     QString cName = justOne ? QLatin1String( "catalog" ) : QLatin1String( "catalogs" );
990     if( xml.atEnd() || xml.name() != cName || !xml.isStartElement() )
991         throw Echonest::ParseError( Echonest::UnknownParseError );
992     xml.readNextStartElement();
993 
994     Echonest::Catalog catalog;
995     while( xml.name() != cName || !xml.isEndElement() ) {
996         if( xml.name() == QLatin1String( "total" ) && xml.isStartElement() )
997             catalog.setTotal( xml.readElementText().toInt() );
998         else if( xml.name() == QLatin1String( "type" ) && xml.isStartElement() )
999             catalog.setType( Echonest::literalToCatalogType( xml.readElementText().toLatin1() ) );
1000         else if( xml.name() == QLatin1String( "id" ) && xml.isStartElement() )
1001             catalog.setId( xml.readElementText().toLatin1() );
1002         else if( xml.name() == QLatin1String( "name" ) && xml.isStartElement() )
1003             catalog.setName( xml.readElementText() );
1004         else if( xml.name() == QLatin1String( "items" ) && xml.isStartElement() ) {
1005             QList<Echonest::CatalogItem*> items = parseCatalogItems( xml );
1006             if( items.isEmpty() ) {
1007                 xml.readNextStartElement();
1008                 continue;
1009             }
1010             if( items[ 0 ]->type() == Echonest::CatalogTypes::Artist ) {
1011                 saveArtistList( catalog, items );
1012             } else if( items[ 0 ]->type() == Echonest::CatalogTypes::Song ) {
1013                 saveSongList( catalog, items );
1014             }
1015         }
1016         xml.readNextStartElement();
1017     }
1018     xml.readNext();
1019 
1020     return catalog;
1021 }
1022 
parseCatalogItems(QXmlStreamReader & xml)1023 QList<Echonest::CatalogItem*> Echonest::Parser::parseCatalogItems( QXmlStreamReader& xml ) throw( Echonest::ParseError )
1024 {
1025     if( xml.atEnd() || xml.name() != QLatin1String( "items" ) || xml.tokenType() != QXmlStreamReader::StartElement )
1026         throw Echonest::ParseError( Echonest::UnknownParseError );
1027 
1028     QList<Echonest::CatalogItem*> items;
1029     while( xml.name() == QLatin1String( "items" ) && xml.isStartElement() ) {
1030         // OK, the mixture of the crappy Catalog API and strongly typed c++ makes this ugly. We don't know if this is an artist or song until we reach the artist_id or song_id.
1031         //  so, we'll keep two copies till the end, where we throw away one. :(
1032         Echonest::CatalogArtist* artist = new Echonest::CatalogArtist;
1033         Echonest::CatalogSong* song = new CatalogSong;
1034         while( xml.name() != QLatin1String( "items" ) || !xml.isEndElement() ) {
1035             // OK, we have to check for all possible song+artist types :'(
1036             if( xml.name() == QLatin1String( "rating" ) && xml.isStartElement() ) { /// mixed and artist items
1037                 artist->setRating( xml.readElementText().toInt() );
1038                 song->setRating( artist->rating() );
1039             } else if( xml.name() == QLatin1String( "request" ) && xml.isStartElement() ) {
1040                 parseCatalogRequestItem( xml, *artist, *song );
1041             } else if( xml.name() == QLatin1String( "artist_name" ) && xml.isStartElement() ) {
1042                 artist->setName( xml.readElementText() );
1043                 song->setArtistName( artist->name() );
1044             } else if( xml.name() == QLatin1String( "reviews" ) && xml.isStartElement() ) {
1045                 parseReviews( xml, *artist );
1046             } else if( xml.name() == QLatin1String( "terms" ) && xml.isStartElement() ) {
1047                 parseTerms( xml, *artist );
1048                 continue;
1049             } else if( xml.name() == QLatin1String( "biographies" ) && xml.isStartElement() ) {
1050                 parseBiographies( xml, *artist );
1051             } else if( xml.name() == QLatin1String( "familiarity" ) && xml.isStartElement() ) {
1052                 artist->setFamiliarity( xml.readElementText().toDouble() );
1053                 song->setArtistFamiliarity( artist->familiarity() );
1054             } else if( xml.name() == QLatin1String( "blogs" ) && xml.isStartElement() ) {
1055                 parseNewsOrBlogs( xml, *artist, false );
1056             } else if( xml.name() == QLatin1String( "hotttnesss" ) && xml.isStartElement() ) {
1057                 artist->setHotttnesss( xml.readElementText().toDouble() );
1058                 song->setArtistHotttnesss( artist->hotttnesss() );
1059             } else if( xml.name() == QLatin1String( "video" ) && xml.isStartElement() ) {
1060                 parseVideos( xml, *artist );
1061             } else if( xml.name() == QLatin1String( "urls" ) && xml.isStartElement() ) {
1062                 parseUrls( xml, *artist );
1063             } else if( xml.name() == QLatin1String( "news" ) && xml.isStartElement() ) {
1064                 parseNewsOrBlogs( xml, *artist );
1065             } else if( xml.name() == QLatin1String( "images" ) && xml.isStartElement() ) {
1066                 parseImages( xml, *artist );
1067             } else if( xml.name() == QLatin1String( "date_added" ) && xml.isStartElement() ) {
1068                 artist->setDateAdded( QDateTime::fromString( xml.readElementText(), Qt::ISODate ) );
1069                 song->setDateAdded( artist->dateAdded() );
1070             } else if( xml.name() == QLatin1String( "artist_id" ) && xml.isStartElement() ) {
1071                 artist->setId( xml.readElementText().toLatin1() );
1072                 song->setArtistId( artist->id() );
1073             } else if( xml.name() == QLatin1String( "audio" ) && xml.isStartElement() ) {
1074                 parseAudio( xml, *artist );
1075             } else if( xml.name() == QLatin1String( "foreign_id" ) && xml.isStartElement() ) {
1076                 artist->setForeignId( xml.readElementText().toLatin1()  );
1077                 song->setForeignId( artist->foreignId() );
1078             } else if( xml.name() == QLatin1String( "song_id" ) && xml.isStartElement() ) { /// song-specific entries
1079                 song->setId( xml.readElementText().toLatin1() );
1080             } else if( xml.name() == QLatin1String( "song_name" ) && xml.isStartElement() ) {
1081                 song->setTitle( xml.readElementText() );
1082             } else if( xml.name() == QLatin1String( "tracks" ) && xml.isStartElement() ) {
1083                 song->setTracks( parseCatalogSongTracks( xml ) );
1084             } else if( xml.name() == QLatin1String( "play_count" ) && xml.isStartElement() ) {
1085                static_cast<Echonest::CatalogSong*>(song)->setPlayCount( xml.readElementText().toInt() );
1086             } else if( xml.name() == QLatin1String( "artist_hotttnesss" ) && xml.isStartElement() ) {
1087                 song->setArtistHotttnesss( xml.readElementText().toDouble() );
1088             } else if( xml.name() == QLatin1String( "artist_location" ) && xml.isStartElement() ) {
1089                 // TODO
1090             } else if( xml.name() == QLatin1String( "song_hotttnesss" ) && xml.isStartElement() ) {
1091                 song->setHotttnesss( xml.readElementText().toDouble() );
1092             } else if( xml.name() == QLatin1String( "artist_familiarity" ) && xml.isStartElement() ) {
1093                 song->setArtistFamiliarity( xml.readElementText().toDouble() );
1094             } else if( xml.name() == QLatin1String( "audio_summary" ) && xml.isStartElement() ) {
1095                 song->setAudioSummary( parseAudioSummary( xml ) );
1096             }
1097             xml.readNextStartElement();
1098         }
1099         if( !song->id().isEmpty() ) { // No song id, so it's an artist.
1100 //             qDebug() << "Adding a song";
1101             items << song;
1102             delete artist;
1103         } else if( !artist->id().isEmpty() ) {
1104 //             qDebug() << "Adding an artist";
1105             items << artist;
1106             delete song;
1107         } else { // dunno what this is really. lets use the song one for now
1108 //             qDebug() << "Adding an EMPTY";
1109             items << song;
1110             delete artist;
1111         }
1112         xml.readNext();
1113     }
1114 
1115     return items;
1116 }
1117 
parseCatalogRequestItem(QXmlStreamReader & xml,Echonest::CatalogArtist & artist,Echonest::CatalogSong & song)1118 void Echonest::Parser::parseCatalogRequestItem( QXmlStreamReader& xml, Echonest::CatalogArtist& artist, Echonest::CatalogSong& song) throw( Echonest::ParseError )
1119 {
1120     if( xml.atEnd() || xml.name() != QLatin1String( "request" ) || xml.tokenType() != QXmlStreamReader::StartElement )
1121         throw Echonest::ParseError( Echonest::UnknownParseError );
1122 
1123     Echonest::CatalogUpdateEntry request;
1124     while( xml.name() != QLatin1String( "request" ) || !xml.isEndElement() ) {
1125         if( xml.name() == QLatin1String( "item_id" ) ) {
1126             request.setItemId( xml.readElementText().toLatin1() );
1127         } else if( xml.name() == QLatin1String( "artist_name" ) ) {
1128             request.setArtistName( xml.readElementText() );
1129         } else if( xml.name() == QLatin1String( "song_name" ) ) {
1130             request.setSongName( xml.readElementText() );
1131         } else if( xml.name() == QLatin1String( "fp_code" ) ) {
1132             request.setFingerprint( xml.readElementText().toLatin1() );
1133         } else if( xml.name() == QLatin1String( "song_id" ) ) {
1134             request.setSongId( xml.readElementText().toLatin1() );
1135         } else if( xml.name() == QLatin1String( "artist_id" ) ) {
1136             request.setArtistId( xml.readElementText().toLatin1() );
1137         } else if( xml.name() == QLatin1String( "release" ) ) {
1138             request.setRelease( xml.readElementText() );
1139         } else if( xml.name() == QLatin1String( "genre" ) ) {
1140             request.setGenre( xml.readElementText() );
1141         }
1142         xml.readNext();
1143     }
1144     artist.setRequest( request );
1145     song.setRequest( request );
1146 }
1147 
saveArtistList(Echonest::Catalog & catalog,QList<Echonest::CatalogItem * > & artists)1148 void Echonest::Parser::saveArtistList( Echonest::Catalog& catalog, QList<Echonest::CatalogItem*>& artists)
1149 {
1150     // will copy artists into the catalog, and delete the origin
1151     Echonest::CatalogArtists ca;
1152     foreach( const Echonest::CatalogItem* item, artists ) {
1153         ca.append( CatalogArtist( *static_cast<const CatalogArtist*>( item ) ) );
1154     }
1155     qDeleteAll( artists );
1156     catalog.setArtists( ca );
1157 }
1158 
saveSongList(Echonest::Catalog & catalog,QList<Echonest::CatalogItem * > & songs)1159 void Echonest::Parser::saveSongList( Echonest::Catalog& catalog, QList<Echonest::CatalogItem*>& songs)
1160 {
1161     // will copy songs into the catalog, and delete the origin
1162     Echonest::CatalogSongs ca;
1163     foreach( const Echonest::CatalogItem* item, songs ) {
1164         ca.append( CatalogSong( *static_cast<const CatalogSong*>( item ) ) );
1165     }
1166     qDeleteAll( songs );
1167     catalog.setSongs( ca );
1168 }
1169 
1170 
parseCatalogStatus(QXmlStreamReader & xml)1171 Echonest::CatalogStatus Echonest::Parser::parseCatalogStatus( QXmlStreamReader& xml ) throw( Echonest::ParseError )
1172 {
1173     Echonest::CatalogStatus status;
1174 
1175     while( xml.name() != QLatin1String( "response" ) || !xml.isEndElement() ) {
1176         if( xml.name() == QLatin1String( "ticket_status" ) && xml.isStartElement() )
1177             status.status = Echonest::literalToCatalogStatus( xml.readElementText().toLatin1() );
1178         else if( xml.name() == QLatin1String( "details" ) && xml.isStartElement() )
1179             status.details = xml.readElementText();
1180         else if( xml.name() == QLatin1String( "items_updated" ) && xml.isStartElement() )
1181             status.items_updated = xml.readElementText().toInt();
1182         else if( xml.name() == QLatin1String( "update_info" ) && xml.isStartElement() )
1183             status.items = parseTicketUpdateInfo( xml );
1184 //         else if( xml.name() == "percent_complete" && xml.isStartElement() )
1185 //             status.percent_complete = xml.readElementText().toInt();
1186 
1187         xml.readNext();
1188     }
1189 
1190     return status;
1191 }
1192 
parseTicketUpdateInfo(QXmlStreamReader & xml)1193 Echonest::CatalogStatusItem Echonest::Parser::parseTicketUpdateInfo( QXmlStreamReader& xml ) throw( Echonest::ParseError )
1194 {
1195 //     if( xml.atEnd() || xml.name() != "ticket_status" || xml.tokenType() != QXmlStreamReader::StartElement )
1196 //         throw Echonest::ParseError( Echonest::UnknownParseError );
1197         // TODO
1198         return Echonest::CatalogStatusItem();
1199 }
1200 
parseCatalogTicket(QXmlStreamReader & xml)1201 QByteArray Echonest::Parser::parseCatalogTicket( QXmlStreamReader& xml ) throw( Echonest::ParseError )
1202 {
1203     if( xml.atEnd() || xml.name() != QLatin1String( "ticket" ) || xml.tokenType() != QXmlStreamReader::StartElement )
1204         throw Echonest::ParseError( Echonest::UnknownParseError );
1205 
1206     QByteArray ticket= xml.readElementText().toLatin1();
1207     return ticket;
1208 }
1209 
parseNewCatalog(QXmlStreamReader & xml)1210 Echonest::Catalog Echonest::Parser::parseNewCatalog( QXmlStreamReader& xml ) throw( Echonest::ParseError )
1211 {
1212     if( xml.atEnd() || xml.tokenType() != QXmlStreamReader::StartElement )
1213         throw Echonest::ParseError( Echonest::UnknownParseError );
1214 
1215     QString name;
1216     QByteArray id;
1217     Echonest::CatalogTypes::Type type = Echonest::CatalogTypes::Artist;
1218 
1219     qDebug() << "Parsing new catalog...";
1220     while( xml.name() != QLatin1String( "response" ) || !xml.isEndElement() ) {
1221         qDebug() << "Parsing at:" << xml.name().toString();
1222         if( xml.name() == QLatin1String( "name" ) && xml.isStartElement() )
1223             name = xml.readElementText();
1224         else if( xml.name() == QLatin1String( "id" ) && xml.isStartElement() )
1225             id = xml.readElementText().toLatin1();
1226         else if( xml.name() == QLatin1String( "type" ) && xml.isStartElement() )
1227             type = Echonest::literalToCatalogType( xml.readElementText().toLatin1() );
1228 
1229         xml.readNextStartElement();
1230         qDebug() << "Parsing next at:" << xml.name().toString();
1231     }
1232     Echonest::Catalog c = Echonest::Catalog( id );
1233     c.setName( name );
1234     c.setType( type );
1235 
1236     return c;
1237 }
1238 
parseSessionInfo(QXmlStreamReader & xml)1239 Echonest::SessionInfo Echonest::Parser::parseSessionInfo( QXmlStreamReader& xml ) throw( Echonest::ParseError )
1240 {
1241     Echonest::SessionInfo info;
1242 
1243     while( xml.name() != QLatin1String( "response" ) || !xml.isEndElement() ) {
1244 //         qDebug() << "Parsing part of session info:" << xml.name() << xml.isStartElement();
1245         // TODO
1246     /** Sample output:
1247      http://files.lfranchi.com/echonest_dynamic_info.xml
1248 
1249 
1250         if( xml.name() == "terms" && xml.isStartElement() ) {
1251             info.terms = parseTopTermList( xml );
1252             continue;
1253         } else if( xml.name() == "rules" && xml.isStartElement() ) {
1254             info.rules = parseRulesList( xml );
1255             continue;
1256         } else if( xml.name() == "session_id" && xml.isStartElement() )
1257             info.session_id = xml.readElementText().toLatin1();
1258         else if( xml.name() == "seeds" && xml.isStartElement() )
1259             info.seeds.append( Artist( xml.readElementText().toLatin1() ) );
1260         else if( xml.name() == "banned_artists" && xml.isStartElement() )
1261             info.banned_artists.append( Artist( xml.readElementText().toLatin1() ) );
1262         else if( xml.name() == "seed_songs" && xml.isStartElement() )
1263             info.seed_songs.append( Song( xml.readElementText().toLatin1() ) );
1264         else if( xml.name() == "seed_catalog" && xml.isStartElement() )
1265             info.seed_catalogs.append( Catalog( xml.readElementText().toLatin1() ) );
1266         else if( xml.name() == "playlist_type" && xml.isStartElement() )
1267             info.playlist_type = xml.readElementText();
1268         else if( xml.name() == "skipped_songs" && xml.isStartElement() ) {
1269             info.skipped_songs = parseSessionSongItem( xml, QLatin1String( "skipped_songs" ) );
1270             continue;
1271         } else if( xml.name() == "banned_songs" && xml.isStartElement() ) {
1272             info.banned_songs = parseSessionSongItem( xml, QLatin1String( "banned_songs" ) );
1273             continue;
1274         } else if( xml.name() == "rated_songs" && xml.isStartElement() ) {
1275             info.rated_songs = parseSessionSongItem( xml, QLatin1String( "rated_songs" ) );
1276             continue;
1277         } else if( xml.name() == "history" && xml.isStartElement() ) {
1278             info.history = parseSessionSongItem( xml, QLatin1String( "history" ) );
1279             continue;
1280         }*/
1281         xml.readNext();
1282     }
1283     return info;
1284 }
1285 
1286 
parseRulesList(QXmlStreamReader & xml)1287 QVector< QString > Echonest::Parser::parseRulesList( QXmlStreamReader& xml ) throw( Echonest::ParseError )
1288 {
1289     if( xml.atEnd() || xml.name() != QLatin1String( "rules" ) || xml.tokenType() != QXmlStreamReader::StartElement )
1290         throw Echonest::ParseError( Echonest::UnknownParseError );
1291 
1292     QVector< QString > rules;
1293     while( xml.name() == QLatin1String( "rules" ) && xml.isStartElement() ) {
1294 //         qDebug() << "Parsing start of rules:" << xml.name() << xml.isStartElement();
1295         xml.readNextStartElement();
1296         rules.append( xml.readElementText() );
1297         xml.readNext();
1298         xml.readNext();
1299 //         qDebug() << "Parsing end of rules:" << xml.name() << xml.isStartElement();
1300     }
1301     return rules;
1302 }
1303 /*
1304  * TODO port to API v2
1305 QVector< Echonest::SessionItem > Echonest::Parser::parseSessionSongItem( QXmlStreamReader& xml, const QString& type ) throw( Echonest::ParseError )
1306 {
1307     if( xml.atEnd() || xml.name() != type || xml.tokenType() != QXmlStreamReader::StartElement )
1308         throw Echonest::ParseError( Echonest::UnknownParseError );
1309 
1310     QVector< Echonest::SessionItem > items;
1311     while( xml.name() == type && xml.isStartElement() ) {
1312 //         qDebug() << "Parsing exernal item:" << xml.name() << xml.isStartElement();
1313 
1314         Echonest::SessionItem item;
1315 
1316         while( !xml.atEnd() && ( xml.name() != type || !xml.isEndElement() ) ) {
1317 //             qDebug() << "Parsing internal item:" << xml.name() << xml.isStartElement();
1318             if( xml.name() == "served_time" )
1319                 item.served_time = xml.readElementText().toDouble();
1320             else if( xml.name() == "artist_id" )
1321                 item.artist_id = xml.readElementText().toLatin1();
1322             else if( xml.name() == "id" )
1323                 item.id = xml.readElementText().toLatin1();
1324             else if( xml.name() == "artist_name" )
1325                 item.artist_name= xml.readElementText();
1326             else if( xml.name() == "title" )
1327                 item.title = xml.readElementText();
1328             else if( xml.name() == "rating" )
1329                 item.rating = xml.readElementText().toInt();
1330 
1331             xml.readNextStartElement();
1332         }
1333         items.append( item );
1334 
1335         xml.readNext();
1336     }
1337     return items;
1338 }*/
1339 
1340