XSPF parser with test.
This commit is contained in:
parent
4eb51bf6d9
commit
11330d0177
|
@ -55,6 +55,7 @@ set(CLEMENTINE-SOURCES
|
|||
m3uparser.cpp
|
||||
playlistmanager.cpp
|
||||
playlistsequence.cpp
|
||||
xspfparser.cpp
|
||||
)
|
||||
|
||||
# Header files that have Q_OBJECT in
|
||||
|
@ -104,6 +105,7 @@ set(CLEMENTINE-MOC-HEADERS
|
|||
m3uparser.h
|
||||
playlistmanager.h
|
||||
playlistsequence.h
|
||||
xspfparser.h
|
||||
)
|
||||
|
||||
# UI files
|
||||
|
|
|
@ -30,6 +30,7 @@ const QList<Song>& M3UParser::Parse() {
|
|||
Song song;
|
||||
song.Init(current_metadata_.title,
|
||||
current_metadata_.artist,
|
||||
QString(), // Unknown album.
|
||||
current_metadata_.length);
|
||||
// Track location.
|
||||
QString location;
|
||||
|
|
|
@ -103,10 +103,11 @@ Song::Song(FileRefFactory* 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->title_ = title;
|
||||
d->artist_ = artist;
|
||||
d->album_ = album;
|
||||
d->length_ = length;
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class Song {
|
|||
};
|
||||
|
||||
// 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 InitFromQuery(const QSqlQuery& query);
|
||||
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(librarybackend_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