#include "webvttparser.h"
#include <ctype.h>
#include <climits>
#include <cstddef>
namespace libwebvtt {
enum {
kNUL = '\x00',
kSPACE = ' ',
kTAB = '\x09',
kLF = '\x0A',
kCR = '\x0D'
};
Reader::~Reader() {}
LineReader::~LineReader() {}
int LineReader::GetLine(std::string* line_ptr) {
if (line_ptr == NULL)
return -1;
std::string& ln = *line_ptr;
ln.clear();
for (;;) {
char c;
const int e = GetChar(&c);
if (e < 0) return e;
if (e > 0) return (ln.empty()) ? 1 : 0;
if (c == kLF)
return 0;
if (c == kCR)
break;
if (c == '\xFE' || c == '\xFF') return -1;
enum { kMaxLineLength = 10000 };
if (ln.length() >= kMaxLineLength)
return -1;
ln.push_back(c);
}
char c;
const int e = GetChar(&c);
if (e < 0) return e;
if (e > 0) return 0;
if (c != kLF)
UngetChar(c);
return 0;
}
Parser::Parser(Reader* r) : reader_(r), unget_(-1) {}
Parser::~Parser() {}
int Parser::Init() {
int e = ParseBOM();
if (e < 0) return e;
if (e > 0) return -1;
const char kId[] = "WEBVTT";
for (const char* p = kId; *p; ++p) {
char c;
e = GetChar(&c);
if (e < 0) return e;
if (e > 0) return -1;
if (c != *p)
return -1;
}
std::string line;
e = GetLine(&line);
if (e < 0) return e;
if (e > 0) return 0;
if (!line.empty()) {
const char c = line[0];
if (c != kSPACE && c != kTAB)
return -1;
}
e = GetLine(&line);
if (e < 0) return e;
if (e > 0) return 0;
if (!line.empty())
return -1;
return 0; }
int Parser::Parse(Cue* cue) {
if (cue == NULL)
return -1;
std::string line;
int e;
for (;;) {
e = GetLine(&line);
if (e) return e;
if (!line.empty())
break;
}
const char kArrow[] = "-->";
std::string::size_type arrow_pos = line.find(kArrow);
if (arrow_pos != std::string::npos) {
cue->identifier.clear();
} else {
cue->identifier.swap(line);
e = GetLine(&line);
if (e < 0) return e;
if (e > 0) return -1;
arrow_pos = line.find(kArrow);
if (arrow_pos == std::string::npos) return -1;
}
e = ParseTimingsLine(&line, arrow_pos, &cue->start_time, &cue->stop_time,
&cue->settings);
if (e) return e;
Cue::payload_t& p = cue->payload;
p.clear();
for (;;) {
e = GetLine(&line);
if (e < 0) return e;
if (line.empty())
break;
p.push_back(line);
}
if (p.empty())
return -1;
return 0; }
int Parser::GetChar(char* c) {
if (unget_ >= 0) {
*c = static_cast<char>(unget_);
unget_ = -1;
return 0;
}
return reader_->GetChar(c);
}
void Parser::UngetChar(char c) { unget_ = static_cast<unsigned char>(c); }
int Parser::ParseBOM() {
static const char BOM[] = "\xEF\xBB\xBF";
for (int i = 0; i < 3; ++i) {
char c;
int e = GetChar(&c);
if (e < 0) return e;
if (e > 0) return 1;
if (c != BOM[i]) {
if (i == 0) { UngetChar(c);
return 0; }
return -1; }
}
return 0; }
int Parser::ParseTimingsLine(std::string* line_ptr,
std::string::size_type arrow_pos, Time* start_time,
Time* stop_time, Cue::settings_t* settings) {
if (line_ptr == NULL)
return -1;
std::string& line = *line_ptr;
if (arrow_pos == std::string::npos || arrow_pos >= line.length())
return -1;
line[arrow_pos] = kNUL;
std::string::size_type idx = 0;
int e = ParseTime(line, &idx, start_time);
if (e) return e;
while (char c = line[idx]) {
if (c != kSPACE && c != kTAB)
return -1;
++idx;
}
line.push_back(kNUL);
idx = arrow_pos + 3;
e = ParseTime(line, &idx, stop_time);
if (e) return e;
e = ParseSettings(line, idx, settings);
if (e) return e;
return 0; }
int Parser::ParseTime(const std::string& line, std::string::size_type* idx_ptr,
Time* time) {
if (idx_ptr == NULL)
return -1;
std::string::size_type& idx = *idx_ptr;
if (idx == std::string::npos || idx >= line.length())
return -1;
if (time == NULL)
return -1;
while (char c = line[idx]) {
if (c != kSPACE && c != kTAB)
break;
++idx;
}
int val = ParseNumber(line, &idx);
if (val < 0) return val;
Time& t = *time;
if (line[idx] == ':') {
const int first_val = val;
++idx;
val = ParseNumber(line, &idx);
if (val < 0)
return val;
if (val >= 60) return -1;
if (line[idx] == ':') {
t.hours = first_val;
t.minutes = val;
++idx;
val = ParseNumber(line, &idx);
if (val < 0)
return val;
if (val >= 60) return -1;
t.seconds = val;
} else {
if (first_val >= 60) return -1;
t.hours = 0;
t.minutes = first_val;
t.seconds = val; }
} else {
t.seconds = val;
t.minutes = t.seconds / 60;
t.seconds -= t.minutes * 60;
t.hours = t.minutes / 60;
t.minutes -= t.hours * 60;
}
char c = line[idx];
const bool have_milliseconds = (c == '.');
if (!have_milliseconds) {
t.milliseconds = 0;
} else {
++idx;
val = ParseNumber(line, &idx);
if (val < 0)
return val;
if (val >= 1000)
return -1;
if (val < 10)
t.milliseconds = val * 100;
else if (val < 100)
t.milliseconds = val * 10;
else
t.milliseconds = val;
}
c = line[idx];
if (c != kNUL && c != kSPACE && c != kTAB)
return -1;
return 0; }
int Parser::ParseSettings(const std::string& line, std::string::size_type idx,
Cue::settings_t* settings) {
settings->clear();
if (idx == std::string::npos || idx >= line.length())
return -1;
for (;;) {
for (;;) {
const char c = line[idx];
if (c == kNUL) return 0;
if (c != kSPACE && c != kTAB)
break;
++idx; }
settings->push_back(Setting());
Setting& s = settings->back();
for (;;) {
const char c = line[idx];
if (c == ':') break;
if (c == kNUL || c == kSPACE || c == kTAB)
return -1;
s.name.push_back(c);
++idx;
}
if (s.name.empty())
return -1;
++idx;
for (;;) {
const char c = line[idx];
if (c == kNUL || c == kSPACE || c == kTAB)
break;
if (c == ':') return -1;
s.value.push_back(c);
++idx;
}
if (s.value.empty())
return -1;
}
}
int Parser::ParseNumber(const std::string& line,
std::string::size_type* idx_ptr) {
if (idx_ptr == NULL)
return -1;
std::string::size_type& idx = *idx_ptr;
if (idx == std::string::npos || idx >= line.length())
return -1;
if (!isdigit(line[idx]))
return -1;
int result = 0;
while (isdigit(line[idx])) {
const char c = line[idx];
const int i = c - '0';
if (result > INT_MAX / 10)
return -1;
result *= 10;
if (result > INT_MAX - i)
return -1;
result += i;
++idx;
}
return result;
}
bool Time::operator==(const Time& rhs) const {
if (hours != rhs.hours)
return false;
if (minutes != rhs.minutes)
return false;
if (seconds != rhs.seconds)
return false;
return (milliseconds == rhs.milliseconds);
}
bool Time::operator<(const Time& rhs) const {
if (hours < rhs.hours)
return true;
if (hours > rhs.hours)
return false;
if (minutes < rhs.minutes)
return true;
if (minutes > rhs.minutes)
return false;
if (seconds < rhs.seconds)
return true;
if (seconds > rhs.seconds)
return false;
return (milliseconds < rhs.milliseconds);
}
bool Time::operator>(const Time& rhs) const { return rhs.operator<(*this); }
bool Time::operator<=(const Time& rhs) const { return !this->operator>(rhs); }
bool Time::operator>=(const Time& rhs) const { return !this->operator<(rhs); }
presentation_t Time::presentation() const {
const presentation_t h = 1000LL * 3600LL * presentation_t(hours);
const presentation_t m = 1000LL * 60LL * presentation_t(minutes);
const presentation_t s = 1000LL * presentation_t(seconds);
const presentation_t result = h + m + s + milliseconds;
return result;
}
Time& Time::presentation(presentation_t d) {
if (d < 0) { hours = 0;
minutes = 0;
seconds = 0;
milliseconds = 0;
return *this;
}
seconds = static_cast<int>(d / 1000);
milliseconds = static_cast<int>(d - 1000 * seconds);
minutes = seconds / 60;
seconds -= 60 * minutes;
hours = minutes / 60;
minutes -= 60 * hours;
return *this;
}
Time& Time::operator+=(presentation_t rhs) {
const presentation_t d = this->presentation();
const presentation_t dd = d + rhs;
this->presentation(dd);
return *this;
}
Time Time::operator+(presentation_t d) const {
Time t(*this);
t += d;
return t;
}
Time& Time::operator-=(presentation_t d) { return this->operator+=(-d); }
presentation_t Time::operator-(const Time& t) const {
const presentation_t rhs = t.presentation();
const presentation_t lhs = this->presentation();
const presentation_t result = lhs - rhs;
return result;
}
}