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