/*
* UltraStar Deluxe - Karaoke Game
*
* UltraStar Deluxe is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include <algorithm>
#include <exception>
#include <sstream>
#include <string>
#include <boost/algorithm/string.hpp>
#include "songloading_strategy_txt.hpp"
#include "utils/locale_independent_float.hpp"
namespace usdx
{
log4cpp::Category& SongloadingStrategyTxt::log =
log4cpp::Category::getInstance(
"usdx.base.songloading.SongloadingStrategyTxt");
SongloadingStrategyTxt::SongloadingStrategyTxt(void)
{
}
SongloadingStrategyTxt::~SongloadingStrategyTxt(void)
{
}
std::pair<std::string, std::string> SongloadingStrategyTxt::split_header_field(std::string &line)
{
std::size_t pos = line.find(':');
if (line[0] != '#' || pos == std::string::npos) {
log << log4cpp::Priority::INFO <<
"Tried to parse invalid header line: '" << line << "'";
throw "Invalid header!";
}
std::pair<std::string, std::string> result;
// copy the substring until ':', without # to result.first and
// transform to upper case
result.first.resize(pos - 1);
std::transform(line.begin() + 1, line.begin() + pos,
result.first.begin(), toupper);
// line is already ltrimmed
boost::trim_right(result.first);
result.second = line.substr(pos + 1);
// line is already rtrimmed
boost::trim_left(result.second);
log << log4cpp::Priority::INFO << "Found header: '" <<
result.first << "' with value '" << result.second << "'";
return result;
}
Song* SongloadingStrategyTxt::load_song(Song *song)
{
log << log4cpp::Priority::INFO <<
"Starting loading song from file: " << song->get_filename();
TextFile file(song->get_filename());
int line_number = 0;
while (file.stream().good() && parse_line(song, file, ++line_number));
// fill song
return song;
}
bool SongloadingStrategyTxt::parse_line(Song* song, TextFile& file, const int line_number)
{
try {
std::string line;
std::getline(file.stream(), line);
char type;
std::istringstream linestream(line);
linestream >> std::skipws >> type;
if (type == '#') {
// ignore, header already read
}
else if (type == 'E') {
// song end
if (file.stream().eof()) {
log << log4cpp::Priority::WARN <<
"End marker found in line " << line_number <<
" before end of file: '" << song->get_filename() <<
"'.";
}
return false;
}
else if (type == '-') {
parse_newline(song, linestream, line_number);
}
else if (type == 'B') {
parse_bpm(song, linestream, line_number);
}
else if (type == ':' || type == 'F' || type == '*') {
parse_note(song, type, linestream, line_number);
}
else {
log << log4cpp::Priority::WARN <<
"Unknown line in song: '" << line <<
"' in file: " << song->get_filename() <<
" at line " << line_number;
}
}
catch (std::exception &e) {
log << log4cpp::Priority::WARN <<
"Error in song file at line " << line_number << ": " <<
e.what();
}
return true;
}
void SongloadingStrategyTxt::parse_newline(Song *song, std::istringstream& linestream, const int line_number)
{
// line break
int line_out, line_in = -1;
linestream >> line_out;
if (linestream.good()) {
linestream >> line_in;
log << log4cpp::Priority::INFO << "Found newline in line " <<
line_number << " with out of last line with " <<
line_out << " and in of next line " << line_in;
}
else {
log << log4cpp::Priority::INFO << "Found newline in line " <<
line_number << " with out of last line with " << line_out;
}
song->new_line(line_out, line_in);
}
void SongloadingStrategyTxt::parse_bpm(Song *song, std::istringstream& linestream, const int line_number)
{
// new bpm
int beat;
LocaleIndependentFloat new_bpm;
linestream >> beat >> new_bpm;
log << log4cpp::Priority::INFO << "Found new bpm in line " <<
line_number << " starting at beat: " << beat <<
" and new bpm of " << new_bpm.get_value();
song->new_bpm(beat, new_bpm.get_value());
}
void SongloadingStrategyTxt::parse_note(Song *song, char type, std::istringstream& linestream, const int line_number)
{
// normal line
int beat, length, height;
std::string lyric;
linestream >> beat >> length >> height >> std::noskipws;
linestream.ignore();
getline(linestream, lyric);
boost::trim_right_if(lyric, boost::is_cntrl());
log << log4cpp::Priority::INFO << "Found lyric: '" << lyric <<
"' at line: " << line_number << " at beat: " << beat <<
" with length: " << length << " at height: " << height;
song->new_note(type, beat, length, height, lyric);
}
Song* SongloadingStrategyTxt::load_header(const boost::filesystem::wpath& filename)
{
TextFile file(filename);
std::string line;
std::map<std::string, std::string> header_fields;
bool header = true, notes_found = false;
while (file.stream().good()) {
std::getline(file.stream(), line);
boost::trim(line);
boost::trim_if(line, boost::is_cntrl());
log << log4cpp::Priority::INFO << "Line: " << line;
if (header && line[0] == '#') {
// header
header_fields.insert(split_header_field(line));
}
else {
if (header) {
// end of header
header = false;
}
if (line[0] == ':' || line[0] == '*' || line[0] == 'F') {
notes_found = true;
break;
}
}
}
if (! notes_found) {
log << log4cpp::Priority::WARN << "Song: '" << filename <<
"' has no notes. Ignoring!";
throw "No notes.";
}
// fill song
return new Song(filename, header_fields);
}
}