XSPF parser with test.
This commit is contained in:
parent
4eb51bf6d9
commit
11330d0177
|
@ -55,6 +55,7 @@ set(CLEMENTINE-SOURCES
|
||||||
m3uparser.cpp
|
m3uparser.cpp
|
||||||
playlistmanager.cpp
|
playlistmanager.cpp
|
||||||
playlistsequence.cpp
|
playlistsequence.cpp
|
||||||
|
xspfparser.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Header files that have Q_OBJECT in
|
# Header files that have Q_OBJECT in
|
||||||
|
@ -104,6 +105,7 @@ set(CLEMENTINE-MOC-HEADERS
|
||||||
m3uparser.h
|
m3uparser.h
|
||||||
playlistmanager.h
|
playlistmanager.h
|
||||||
playlistsequence.h
|
playlistsequence.h
|
||||||
|
xspfparser.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# UI files
|
# UI files
|
||||||
|
|
|
@ -30,6 +30,7 @@ const QList<Song>& M3UParser::Parse() {
|
||||||
Song song;
|
Song song;
|
||||||
song.Init(current_metadata_.title,
|
song.Init(current_metadata_.title,
|
||||||
current_metadata_.artist,
|
current_metadata_.artist,
|
||||||
|
QString(), // Unknown album.
|
||||||
current_metadata_.length);
|
current_metadata_.length);
|
||||||
// Track location.
|
// Track location.
|
||||||
QString location;
|
QString location;
|
||||||
|
|
|
@ -103,10 +103,11 @@ Song::Song(FileRefFactory* factory)
|
||||||
factory_(factory) {
|
factory_(factory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Song::Init(const QString& title, const QString& artist, int length) {
|
void Song::Init(const QString& title, const QString& artist, const QString& album, int length) {
|
||||||
d->valid_ = true;
|
d->valid_ = true;
|
||||||
d->title_ = title;
|
d->title_ = title;
|
||||||
d->artist_ = artist;
|
d->artist_ = artist;
|
||||||
|
d->album_ = album;
|
||||||
d->length_ = length;
|
d->length_ = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ class Song {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
void Init(const QString& title, const QString& artist, int length);
|
void Init(const QString& title, const QString& artist, const QString& album, int length);
|
||||||
void InitFromFile(const QString& filename, int directory_id);
|
void InitFromFile(const QString& filename, int directory_id);
|
||||||
void InitFromQuery(const QSqlQuery& query);
|
void InitFromQuery(const QSqlQuery& query);
|
||||||
void InitFromLastFM(const lastfm::Track& track);
|
void InitFromLastFM(const lastfm::Track& track);
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
#include "xspfparser.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
|
XSPFParser::XSPFParser(QIODevice* device, QObject* parent)
|
||||||
|
: QObject(parent),
|
||||||
|
device_(device) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const SongList& XSPFParser::Parse() {
|
||||||
|
QXmlStreamReader reader(device_);
|
||||||
|
if (!ParseUntilElement(&reader, "playlist") ||
|
||||||
|
!ParseUntilElement(&reader, "trackList")) {
|
||||||
|
return songs_;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!reader.atEnd() && ParseUntilElement(&reader, "track")) {
|
||||||
|
Song song = ParseTrack(&reader);
|
||||||
|
if (song.is_valid()) {
|
||||||
|
songs_ << song;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return songs_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XSPFParser::ParseUntilElement(QXmlStreamReader* reader, const QString& name) const {
|
||||||
|
while (!reader->atEnd()) {
|
||||||
|
QXmlStreamReader::TokenType type = reader->readNext();
|
||||||
|
switch (type) {
|
||||||
|
case QXmlStreamReader::StartElement:
|
||||||
|
if (reader->name() == name) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
IgnoreElement(reader);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XSPFParser::IgnoreElement(QXmlStreamReader* reader) const {
|
||||||
|
int level = 1;
|
||||||
|
while (level != 0 && !reader->atEnd()) {
|
||||||
|
QXmlStreamReader::TokenType type = reader->readNext();
|
||||||
|
switch (type) {
|
||||||
|
case QXmlStreamReader::StartElement:
|
||||||
|
++level;
|
||||||
|
break;
|
||||||
|
case QXmlStreamReader::EndElement:
|
||||||
|
--level;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Song XSPFParser::ParseTrack(QXmlStreamReader* reader) const {
|
||||||
|
Song song;
|
||||||
|
QString title, artist, album;
|
||||||
|
int length = -1;
|
||||||
|
while (!reader->atEnd()) {
|
||||||
|
QXmlStreamReader::TokenType type = reader->readNext();
|
||||||
|
switch (type) {
|
||||||
|
case QXmlStreamReader::StartElement: {
|
||||||
|
QStringRef name = reader->name();
|
||||||
|
if (name == "location") {
|
||||||
|
QUrl url(reader->readElementText());
|
||||||
|
if (url.scheme() == "file") {
|
||||||
|
QString filename = url.toLocalFile();
|
||||||
|
if (!QFile::exists(filename)) {
|
||||||
|
return Song();
|
||||||
|
}
|
||||||
|
song.InitFromFile(filename, -1);
|
||||||
|
return song;
|
||||||
|
} else {
|
||||||
|
song.set_filename(url.toString());
|
||||||
|
song.set_filetype(Song::Type_Stream);
|
||||||
|
}
|
||||||
|
} else if (name == "title") {
|
||||||
|
title = reader->readElementText();
|
||||||
|
} else if (name == "creator") {
|
||||||
|
artist = reader->readElementText();
|
||||||
|
} else if (name == "album") {
|
||||||
|
album = reader->readElementText();
|
||||||
|
} else if (name == "duration") { // in milliseconds.
|
||||||
|
const QString& duration = reader->readElementText();
|
||||||
|
bool ok = false;
|
||||||
|
length = duration.toInt(&ok) / 1000;
|
||||||
|
if (!ok) {
|
||||||
|
return Song();
|
||||||
|
}
|
||||||
|
song.set_length(length);
|
||||||
|
} else if (name == "image") {
|
||||||
|
// TODO: Fetch album covers.
|
||||||
|
} else if (name == "info") {
|
||||||
|
// TODO: Do something with extra info?
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
song.Init(title, artist, album, length);
|
||||||
|
return song;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef XSPFPARSER_H
|
||||||
|
#define XSPFPARSER_H
|
||||||
|
|
||||||
|
#include "song.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class QIODevice;
|
||||||
|
class QXmlStreamReader;
|
||||||
|
|
||||||
|
class XSPFParser : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
XSPFParser(QIODevice* device, QObject* parent = 0);
|
||||||
|
virtual ~XSPFParser() {}
|
||||||
|
|
||||||
|
const SongList& Parse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ParseUntilElement(QXmlStreamReader* reader, const QString& element) const;
|
||||||
|
void IgnoreElement(QXmlStreamReader* reader) const;
|
||||||
|
Song ParseTrack(QXmlStreamReader* reader) const;
|
||||||
|
|
||||||
|
QIODevice* device_;
|
||||||
|
SongList songs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -65,3 +65,4 @@ add_test_file(m3uparser_test.cpp)
|
||||||
add_test_file(song_test.cpp)
|
add_test_file(song_test.cpp)
|
||||||
add_test_file(librarybackend_test.cpp)
|
add_test_file(librarybackend_test.cpp)
|
||||||
add_test_file(albumcoverfetcher_test.cpp)
|
add_test_file(albumcoverfetcher_test.cpp)
|
||||||
|
add_test_file(xspfparser_test.cpp)
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
#include "test_utils.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "xspfparser.h"
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
|
class XSPFParserTest : public ::testing::Test {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(XSPFParserTest, ParsesOneTrackFromXML) {
|
||||||
|
QByteArray data =
|
||||||
|
"<playlist><trackList><track>"
|
||||||
|
"<location>http://example.com/foo.mp3</location>"
|
||||||
|
"<title>Foo</title>"
|
||||||
|
"<creator>Bar</creator>"
|
||||||
|
"<album>Baz</album>"
|
||||||
|
"<duration>60000</duration>"
|
||||||
|
"<image>http://example.com/albumcover.jpg</image>"
|
||||||
|
"<info>http://example.com</info>"
|
||||||
|
"</track></trackList></playlist>";
|
||||||
|
QBuffer buffer(&data);
|
||||||
|
buffer.open(QIODevice::ReadOnly);
|
||||||
|
XSPFParser parser(&buffer);
|
||||||
|
const SongList& songs = parser.Parse();
|
||||||
|
ASSERT_EQ(1, songs.length());
|
||||||
|
const Song& song = songs[0];
|
||||||
|
EXPECT_EQ("Foo", song.title());
|
||||||
|
EXPECT_EQ("Bar", song.artist());
|
||||||
|
EXPECT_EQ("Baz", song.album());
|
||||||
|
EXPECT_EQ("http://example.com/foo.mp3", song.filename());
|
||||||
|
EXPECT_TRUE(song.is_valid());
|
||||||
|
}
|
Loading…
Reference in New Issue