From f0db2d5b9cfcbcefad688be6d83fba9fa212b98f Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 10 Feb 2026 21:35:38 +0100 Subject: Give line number in SubRip parsing exceptions. --- src/exceptions.cc | 5 +++-- src/exceptions.h | 7 ++++++- src/subrip_reader.cc | 16 +++++++++------- src/subrip_reader.h | 2 +- test/subrip_reader_test.cc | 46 +++++++++++++++++++++++----------------------- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/exceptions.cc b/src/exceptions.cc index d09be61..df9ae9a 100644 --- a/src/exceptions.cc +++ b/src/exceptions.cc @@ -31,8 +31,9 @@ ProgrammingError::ProgrammingError (string file, int line) } -SubripError::SubripError (string saw, string expecting, list context) - : runtime_error ("Error in SubRip file: saw " + (saw.empty() ? "an empty string" : saw) + " when expecting " + expecting) +SubripError::SubripError(int line_number, string saw, string expecting, list context) + : runtime_error(String::compose("Error in SubRip file line %1: saw %2 when expecting %3", line_number, saw.empty() ? "an empty string" : saw, expecting).c_str()) + , _line_number(line_number) , _context (context) { diff --git a/src/exceptions.h b/src/exceptions.h index 3b2961d..58659ad 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -55,13 +55,18 @@ public: class SubripError : public std::runtime_error { public: - SubripError (std::string saw, std::string expecting, std::list context); + SubripError(int line_number, std::string saw, std::string expecting, std::list context); std::list context () const { return _context; } + int line_number() const { + return _line_number; + } + private: + int _line_number; std::list _context; }; diff --git a/src/subrip_reader.cc b/src/subrip_reader.cc index 0e948f9..b95e6be 100644 --- a/src/subrip_reader.cc +++ b/src/subrip_reader.cc @@ -79,9 +79,11 @@ SubripReader::read (function ()> get_line) RawSubtitle rs; prepare(rs); + int line_number = 0; while (true) { auto line = get_line (); + ++line_number; if (!line) { break; } @@ -123,19 +125,19 @@ SubripReader::read (function ()> get_line) _context.push_back (*ex); } } - throw SubripError (*line, "a time/position line", _context); + throw SubripError(line_number, *line, "a time/position line", _context); } string expected; auto from = convert_time(p[0], ",", &expected); if (!from) { - throw SubripError(p[0], expected, _context); + throw SubripError(line_number, p[0], expected, _context); } rs.from = *from; auto to = convert_time(p[2], ",", &expected); if (!to) { - throw SubripError(p[2], expected, _context); + throw SubripError(line_number, p[2], expected, _context); } rs.to = *to; @@ -154,7 +156,7 @@ SubripReader::read (function ()> get_line) */ boost::algorithm::split_regex(sub_lines, *line, boost::regex("\xe2\x80\xa8")); for (auto sub_line: sub_lines) { - convert_line(sub_line, rs); + convert_line(line_number, sub_line, rs); rs.vertical_position.line = rs.vertical_position.line.get() + 1; rs.text.clear(); } @@ -221,7 +223,7 @@ SubripReader::convert_time(string t, string milliseconds_separator, string* expe } void -SubripReader::convert_line (string t, RawSubtitle& p) +SubripReader::convert_line(int line_number, string t, RawSubtitle& p) { vector colours; colours.push_back (Colour (1, 1, 1)); @@ -276,7 +278,7 @@ SubripReader::convert_line (string t, RawSubtitle& p) p.colour = Colour::from_rgba_hex(match[1]); colours.push_back(p.colour); } else { - throw SubripError(tag, "a colour in the format #rrggbb #rrggbbaa or rgba(rr,gg,bb,aa)", _context); + throw SubripError(line_number, tag, "a colour in the format #rrggbb #rrggbbaa or rgba(rr,gg,bb,aa)", _context); } } else { re = boost::regex ( @@ -293,7 +295,7 @@ SubripReader::convert_line (string t, RawSubtitle& p) p.colour.b = raw_convert(string(match[3])) / 255.0; colours.push_back (p.colour); } else { - throw SubripError (tag, "a colour in the format #rrggbb #rrggbbaa or rgba(rr,gg,bb,aa)", _context); + throw SubripError(line_number, tag, "a colour in the format #rrggbb #rrggbbaa or rgba(rr,gg,bb,aa)", _context); } } } else if (has_next(t, i, "")) { diff --git a/src/subrip_reader.h b/src/subrip_reader.h index 83fefdf..bf438ed 100644 --- a/src/subrip_reader.h +++ b/src/subrip_reader.h @@ -51,7 +51,7 @@ private: friend struct ::subrip_reader_test6; SubripReader () {} - void convert_line (std::string t, RawSubtitle& p); + void convert_line(int line_number, std::string t, RawSubtitle& p); void maybe_content (RawSubtitle& p); void read (boost::function ()> get_line); diff --git a/test/subrip_reader_test.cc b/test/subrip_reader_test.cc index afd2579..ad48b6e 100644 --- a/test/subrip_reader_test.cc +++ b/test/subrip_reader_test.cc @@ -242,55 +242,55 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) sub::SubripReader r; sub::RawSubtitle rs; - r.convert_line ("Hello world", rs); + r.convert_line(0, "Hello world", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("Hello world", rs); + r.convert_line(0, "Hello world", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); BOOST_CHECK_EQUAL (r._subs.front().bold, true); r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("Hello world", rs); + r.convert_line(0, "Hello world", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); BOOST_CHECK_EQUAL (r._subs.front().italic, true); r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("Hello world", rs); + r.convert_line(0, "Hello world", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); BOOST_CHECK_EQUAL (r._subs.front().underline, true); r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("{b}Hello world{/b}", rs); + r.convert_line(0, "{b}Hello world{/b}", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); BOOST_CHECK_EQUAL (r._subs.front().bold, true); r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("{i}Hello world{/i}", rs); + r.convert_line(0, "{i}Hello world{/i}", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); BOOST_CHECK_EQUAL (r._subs.front().italic, true); r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("{u}Hello world{/u}", rs); + r.convert_line(0, "{u}Hello world{/u}", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); BOOST_CHECK_EQUAL (r._subs.front().underline, true); r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("This is nesting of subtitles", rs); + r.convert_line(0, "This is nesting of subtitles", rs); BOOST_CHECK_EQUAL(r._subs.size(), 3U); auto i = r._subs.begin(); BOOST_CHECK_EQUAL (i->text, "This is "); @@ -308,7 +308,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("This is nesting of subtitles", rs); + r.convert_line(0, "This is nesting of subtitles", rs); BOOST_CHECK_EQUAL(r._subs.size(), 3U); i = r._subs.begin(); BOOST_CHECK_EQUAL (i->text, "This is "); @@ -326,7 +326,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("simple color", rs); + r.convert_line(0, "simple color", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "simple color"); BOOST_CHECK_EQUAL (r._subs.front().bold, false); @@ -337,7 +337,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) /* single quotes are apparently also allowed */ rs = sub::RawSubtitle(); - r.convert_line("simple color", rs); + r.convert_line(0, "simple color", rs); BOOST_CHECK_EQUAL(r._subs.size(), 1U); BOOST_CHECK_EQUAL(r._subs.front().text, "simple color"); BOOST_CHECK_EQUAL(r._subs.front().bold, false); @@ -347,7 +347,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear(); rs = sub::RawSubtitle(); - r.convert_line ("simple color in capitals", rs); + r.convert_line(0, "simple color in capitals", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "simple color in capitals"); BOOST_CHECK_EQUAL (r._subs.front().bold, false); @@ -357,7 +357,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("some red text in bold", rs); + r.convert_line(0, "some red text in bold", rs); BOOST_CHECK_EQUAL (r._subs.size(), 2U); i = r._subs.begin (); BOOST_CHECK_EQUAL (i->text, "some red text "); @@ -374,7 +374,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("some red text in bold", rs); + r.convert_line(0, "some red text in bold", rs); BOOST_CHECK_EQUAL (r._subs.size(), 2U); i = r._subs.begin (); BOOST_CHECK_EQUAL (i->text, "some red text "); @@ -391,7 +391,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line ("some blue text in bold", rs); + r.convert_line(0, "some blue text in bold", rs); BOOST_CHECK_EQUAL (r._subs.size(), 2U); i = r._subs.begin (); BOOST_CHECK_EQUAL (i->text, "some blue text "); @@ -408,7 +408,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear (); rs = sub::RawSubtitle(); - r.convert_line("some blue text with alpha", rs); + r.convert_line(0, "some blue text with alpha", rs); BOOST_CHECK_EQUAL(r._subs.size(), 1U); i = r._subs.begin(); BOOST_CHECK_EQUAL(i->text, "some blue text with alpha"); @@ -419,7 +419,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_convert_line_test) r._subs.clear(); rs = sub::RawSubtitle(); - r.convert_line ("<< angle brackets but no HTML >>", rs); + r.convert_line(0, "<< angle brackets but no HTML >>", rs); BOOST_CHECK_EQUAL (r._subs.size(), 1U); i = r._subs.begin (); BOOST_CHECK_EQUAL (i->text, "<< angle brackets but no HTML >>"); @@ -610,7 +610,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_test5) { sub::RawSubtitle rs; sub::SubripReader r; - r.convert_line ("Foo bar", rs); + r.convert_line(0, "Foo bar", rs); BOOST_REQUIRE_EQUAL(r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Foo bar"); BOOST_CHECK_CLOSE (r._subs.front().colour.r, 255.0 / 255, 0.1); @@ -619,7 +619,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_test5) r._subs.clear (); rs = sub::RawSubtitle (); - r.convert_line ("Foo bar", rs); + r.convert_line(0, "Foo bar", rs); BOOST_REQUIRE_EQUAL(r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Foo bar"); BOOST_CHECK_CLOSE (r._subs.front().colour.r, 1.0 / 255, 0.1); @@ -635,7 +635,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_test6) rs.vertical_position.line = 0; rs.vertical_position.reference = sub::TOP_OF_SUBTITLE; sub::SubripReader r; - r.convert_line ("Hello world", rs); + r.convert_line(0, "Hello world", rs); BOOST_REQUIRE_EQUAL(r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello world"); BOOST_REQUIRE (r._subs.front().vertical_position.line); @@ -646,7 +646,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_test6) rs = sub::RawSubtitle (); rs.vertical_position.line = 0; - r.convert_line ("{\\an1}Hello", rs); + r.convert_line(0, "{\\an1}Hello", rs); BOOST_REQUIRE_EQUAL(r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "Hello"); BOOST_REQUIRE (r._subs.front().vertical_position.line); @@ -659,7 +659,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_test6) rs = sub::RawSubtitle (); rs.vertical_position.line = 0; - r.convert_line ("{\\an2}to", rs); + r.convert_line(0, "{\\an2}to", rs); BOOST_REQUIRE_EQUAL(r._subs.size(), 1U); BOOST_CHECK_EQUAL (r._subs.front().text, "to"); BOOST_REQUIRE (r._subs.front().vertical_position.line); @@ -672,7 +672,7 @@ BOOST_AUTO_TEST_CASE (subrip_reader_test6) rs = sub::RawSubtitle (); rs.vertical_position.line = 0; - r.convert_line ("{\\an3}you", rs); + r.convert_line(0, "{\\an3}you", rs); BOOST_CHECK_EQUAL (r._subs.front().text, "you"); BOOST_REQUIRE (r._subs.front().vertical_position.line); BOOST_CHECK_EQUAL (r._subs.front().vertical_position.line.get(), 0); -- cgit v1.2.3