XSPF parser with test.

This commit is contained in:
John Maguire 2010-03-09 17:17:32 +00:00
parent 4eb51bf6d9
commit 11330d0177
8 changed files with 182 additions and 2 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

113
src/xspfparser.cpp Normal file
View File

@ -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;
}

28
src/xspfparser.h Normal file
View File

@ -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

View File

@ -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)

34
tests/xspfparser_test.cpp Normal file
View File

@ -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());
}