aspasia/substation/common/
parse.rs

1use nom::{
2    branch::alt,
3    bytes::complete::{tag, tag_no_case, take_until},
4    character::complete::{anychar, char, i64, multispace0, space0},
5    combinator::{map, value},
6    multi::{many0, many_till},
7    sequence::{delimited, preceded, separated_pair, terminated, tuple},
8    IResult, Parser,
9};
10
11use crate::{
12    substation::{SubStationFont, SubStationGraphic},
13    Format, Moment,
14};
15
16#[derive(Clone, Debug)]
17pub(crate) enum SubStationSection {
18    ScriptInfo,
19    Styles,
20    Events,
21    Fonts,
22    Graphics,
23}
24
25pub(crate) fn parse_script_info_heading(input: &str) -> IResult<&str, &str> {
26    delimited(char('['), tag_no_case("Script Info"), char(']')).parse(input)
27}
28
29fn parse_script_type(input: &str) -> IResult<&str, Format> {
30    map(
31        preceded(
32            tuple((space0, tag_no_case("ScriptType"), space0, char(':'), space0)),
33            terminated(
34                alt((tag_no_case("v4.00+"), tag_no_case("v4.00"))),
35                multispace0,
36            ),
37        ),
38        |s: &str| match s.to_ascii_lowercase().as_str() {
39            "v4.00" => Format::Ssa,
40            _ => Format::Ass,
41        },
42    )
43    .parse(input)
44}
45
46pub(crate) fn parse_format(input: &str) -> IResult<&str, Format> {
47    map(many_till(anychar, parse_script_type), |(_, format)| format).parse(input)
48}
49
50fn parse_script_info_header(input: &str) -> IResult<&str, SubStationSection> {
51    value(SubStationSection::ScriptInfo, tag_no_case("Script Info")).parse(input)
52}
53
54fn parse_events_header(input: &str) -> IResult<&str, SubStationSection> {
55    value(SubStationSection::Events, tag_no_case("Events")).parse(input)
56}
57
58fn parse_graphics_header(input: &str) -> IResult<&str, SubStationSection> {
59    value(SubStationSection::Graphics, tag_no_case("Graphics")).parse(input)
60}
61
62fn parse_fonts_header(input: &str) -> IResult<&str, SubStationSection> {
63    value(SubStationSection::Fonts, tag_no_case("Fonts")).parse(input)
64}
65
66fn parse_styles_header(input: &str) -> IResult<&str, SubStationSection> {
67    value(
68        SubStationSection::Styles,
69        alt((tag_no_case("V4 Styles"), tag_no_case("V4+ Styles"))),
70    )
71    .parse(input)
72}
73
74pub(crate) fn parse_category_header(input: &str) -> IResult<&str, SubStationSection> {
75    delimited(
76        multispace0,
77        delimited(
78            char('['),
79            alt((
80                parse_events_header,
81                parse_styles_header,
82                parse_fonts_header,
83                parse_graphics_header,
84                parse_script_info_header,
85            )),
86            char(']'),
87        ),
88        multispace0,
89    )
90    .parse(input)
91}
92
93pub(crate) fn parse_reverse_bool(input: &str) -> IResult<&str, bool> {
94    alt((value(true, tag("-1")), value(false, char('0')))).parse(input)
95}
96
97fn parse_fontname(input: &str) -> IResult<&str, &str> {
98    preceded(
99        tuple((multispace0, tag("fontname:"), space0)),
100        take_until("\n"),
101    )
102    .parse(input)
103}
104
105fn parse_font(input: &str) -> IResult<&str, SubStationFont> {
106    map(
107        separated_pair(parse_fontname, multispace0, take_until("\n\n")),
108        |(fontname, data)| SubStationFont {
109            fontname: fontname.to_string(),
110            data: data.to_string(),
111        },
112    )
113    .parse(input)
114}
115
116pub(crate) fn parse_fonts(input: &str) -> IResult<&str, Vec<SubStationFont>> {
117    many0(parse_font).parse(input)
118}
119
120fn parse_filename(input: &str) -> IResult<&str, &str> {
121    preceded(
122        tuple((multispace0, tag("filename:"), space0)),
123        take_until("\n"),
124    )
125    .parse(input)
126}
127
128fn parse_graphic(input: &str) -> IResult<&str, SubStationGraphic> {
129    map(
130        separated_pair(parse_filename, multispace0, take_until("\n\n")),
131        |(filename, data)| SubStationGraphic {
132            filename: filename.to_string(),
133            data: data.to_string(),
134        },
135    )
136    .parse(input)
137}
138
139pub(crate) fn parse_graphics(input: &str) -> IResult<&str, Vec<SubStationGraphic>> {
140    many0(parse_graphic).parse(input)
141}
142
143pub(crate) fn parse_timestamp(input: &str) -> IResult<&str, Moment> {
144    map(
145        delimited(
146            space0,
147            tuple((
148                terminated(i64, char(':')),
149                i64,
150                delimited(char(':'), i64, char('.')),
151                i64,
152            )),
153            space0,
154        ),
155        |(h, m, s, cs)| Moment::from_timestamp(h, m, s, cs * 10),
156    )
157    .parse(input)
158}