Skip to main content

ssb_parser/parsers/
ssb.rs

1// Imports
2use crate::{
3    state::{
4        error::ParseError,
5        ssb_state::Section
6    },
7    objects::ssb_objects::{View,Event,EventTrigger,FontFace,FontStyle,FontData,TextureId,TextureDataVariant},
8    utils::{
9        pattern::*,
10        functions::convert::parse_timestamp
11    }
12};
13use std::{
14    collections::HashMap,
15    io::BufRead,
16    convert::TryFrom
17};
18
19
20/// Raw SSB data, representing original input one-by-one (except empty lines and comments).
21#[derive(Debug, PartialEq, Clone)]
22#[cfg_attr(feature = "serialization", derive(serde::Serialize,serde::Deserialize))]
23pub struct Ssb {
24    // Info section
25    pub info_title: Option<String>,
26    pub info_author: Option<String>,
27    pub info_description: Option<String>,
28    pub info_version: Option<String>,
29    pub info_custom: HashMap<String, String>,
30    // Target section
31    pub target_width: Option<u16>,
32    pub target_height: Option<u16>,
33    pub target_depth: u16,
34    pub target_view: View,
35    // Macros section
36    pub macros: HashMap<String, String>,
37    // Events section
38    pub events: Vec<Event>,
39    // Resources section
40    pub fonts: HashMap<FontFace, FontData>,
41    pub textures: HashMap<TextureId, TextureDataVariant>
42}
43impl Default for Ssb {
44    fn default() -> Self {
45        Self {
46            info_title: None,
47            info_author: None,
48            info_description: None,
49            info_version: None,
50            info_custom: HashMap::default(),
51            target_width: None,
52            target_height: None,
53            target_depth: 1000,
54            target_view: View::Perspective,
55            macros: HashMap::default(),
56            events: Vec::default(),
57            fonts: HashMap::default(),
58            textures: HashMap::default()
59        }
60    }
61}
62impl Ssb {
63    /// Parse SSB input and fill structure (which it owns and returns modified).
64    pub fn parse_owned<R>(mut self, reader: R) -> Result<Self, ParseError>
65        where R: BufRead {
66        self.parse(reader)?;
67        Ok(self)
68    }
69    /// Parse SSB input and fill structure (which it borrows and returns as reference).
70    pub fn parse<R>(&mut self, reader: R) -> Result<&mut Self, ParseError>
71        where R: BufRead {
72        // Initial state
73        let mut section: Option<Section> = None;
74        // Iterate through text lines
75        for (line_index, line) in reader.lines().enumerate() {
76            // Check for valid UTF-8 and remove carriage return (leftover of windows-ending)
77            let mut line = line?;
78            if line.ends_with('\r') {line.pop();}
79            // Ignore empty lines & comments
80            if !(line.is_empty() || line.starts_with("//")) {
81                // Switch or handle section
82                if let Ok(parsed_section) = Section::try_from(line.as_ref()) {
83                    section = Some(parsed_section);
84                } else {
85                    match section {
86                        // Info section
87                        Some(Section::Info) => {
88                            // Title
89                            if line.starts_with(INFO_TITLE_KEY) {
90                                self.info_title = Some(line[INFO_TITLE_KEY.len()..].to_owned());
91                            }
92                            // Author
93                            else if line.starts_with(INFO_AUTHOR_KEY) {
94                                self.info_author = Some(line[INFO_AUTHOR_KEY.len()..].to_owned());
95                            }
96                            // Description
97                            else if line.starts_with(INFO_DESCRIPTION_KEY) {
98                                self.info_description = Some(line[INFO_DESCRIPTION_KEY.len()..].to_owned());
99                            }
100                            // Version
101                            else if line.starts_with(INFO_VERSION_KEY) {
102                                self.info_version = Some(line[INFO_VERSION_KEY.len()..].to_owned());
103                            }
104                            // Custom
105                            else if let Some(separator_pos) = line.find(KEY_SUFFIX).filter(|pos| *pos > 0) {
106                                self.info_custom.insert(
107                                    line[..separator_pos].to_owned(),
108                                    line[separator_pos + KEY_SUFFIX.len()..].to_owned()
109                                );
110                            }
111                            // Invalid entry
112                            else {
113                                return Err(ParseError::new_with_pos("Invalid info entry!", (line_index, 0)));
114                            }
115                        }
116                        // Target section
117                        Some(Section::Target) => {
118                            // Width
119                            if line.starts_with(TARGET_WIDTH_KEY) {
120                                self.target_width = Some(
121                                    line[TARGET_WIDTH_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target width value!", (line_index, TARGET_WIDTH_KEY.len())) )?
122                                );
123                            }
124                            // Height
125                            else if line.starts_with(TARGET_HEIGHT_KEY) {
126                                self.target_height = Some(
127                                    line[TARGET_HEIGHT_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target height value!", (line_index, TARGET_HEIGHT_KEY.len())) )?
128                                );
129                            }
130                            // Depth
131                            else if line.starts_with(TARGET_DEPTH_KEY) {
132                                self.target_depth = line[TARGET_DEPTH_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target depth value!", (line_index, TARGET_DEPTH_KEY.len())) )?;
133                            }
134                            // View
135                            else if line.starts_with(TARGET_VIEW_KEY) {
136                                self.target_view = View::try_from(&line[TARGET_VIEW_KEY.len()..]).map_err(|_| ParseError::new_with_pos("Invalid target view value!", (line_index, TARGET_VIEW_KEY.len())) )?;
137                            }
138                            // Invalid entry
139                            else {
140                                return Err(ParseError::new_with_pos("Invalid target entry!", (line_index, 0)));
141                            }
142                        }
143                        // Macros section
144                        Some(Section::Macros) => {
145                            // Macro
146                            if let Some(separator_pos) = line.find(KEY_SUFFIX).filter(|pos| *pos > 0) {
147                                self.macros.insert(
148                                    line[..separator_pos].to_owned(),
149                                    line[separator_pos + KEY_SUFFIX.len()..].to_owned()
150                                );
151                            }
152                            // Invalid entry
153                            else {
154                                return Err(ParseError::new_with_pos("Invalid macros entry!", (line_index, 0)));
155                            }
156                        }
157                        // Events section
158                        Some(Section::Events) => {
159                            let mut event_tokens = line.splitn(4, EVENT_SEPARATOR);
160                            if let (Some(trigger), Some(macro_name), Some(note), Some(data)) = (event_tokens.next(), event_tokens.next(), event_tokens.next(), event_tokens.next()) {
161                                // Save event
162                                self.events.push(
163                                    Event {
164                                        trigger: {
165                                            // Tag
166                                            if trigger.starts_with('\'') && trigger.len() >= 2 && trigger.ends_with('\'') {
167                                                EventTrigger::Id(trigger[1..trigger.len()-1].to_owned())
168                                            // Time
169                                            } else if let Some(seperator_pos) = trigger.find(TRIGGER_SEPARATOR) {
170                                                let start_time = parse_timestamp(&trigger[..seperator_pos]).map_err(|_| ParseError::new_with_pos("Start timestamp invalid!", (line_index, 0)) )?;
171                                                let end_time = parse_timestamp(&trigger[seperator_pos + 1 /* TRIGGER_SEPARATOR */..]).map_err(|_| ParseError::new_with_pos("End timestamp invalid!", (line_index, seperator_pos + 1 /* TRIGGER_SEPARATOR */) ))?;
172                                                if start_time > end_time {
173                                                    return Err(ParseError::new_with_pos("Start time greater than end time!", (line_index, 0)));
174                                                }
175                                                EventTrigger::Time((start_time, end_time))
176                                            // Invalid
177                                            } else {
178                                                return Err(ParseError::new_with_pos("Invalid trigger format!", (line_index, 0)));
179                                            }
180                                        },
181                                        macro_name: Some(macro_name.to_owned()).filter(|s| !s.is_empty()),
182                                        note: Some(note.to_owned()).filter(|s| !s.is_empty()),
183                                        data: data.to_owned(),
184                                        data_location: (line_index, trigger.len() + macro_name.len() + note.len() + 3 /* 3x EVENT_SEPARATOR */)
185                                    }
186                                );
187                            }
188                            // Invalid entry
189                            else {
190                                return Err(ParseError::new_with_pos("Invalid events entry!", (line_index, 0)));
191                            }
192                        }
193                        // Resources section
194                        Some(Section::Resources) => {
195                            // Font
196                            if line.starts_with(RESOURCES_FONT_KEY) {
197                                // Parse tokens
198                                let mut font_tokens = line[RESOURCES_FONT_KEY.len()..].splitn(3, VALUE_SEPARATOR);
199                                if let (Some(family), Some(style), Some(data)) = (font_tokens.next(), font_tokens.next(), font_tokens.next()) {
200                                    // Save font
201                                    self.fonts.insert(
202                                        FontFace {
203                                            family: family.to_owned(),
204                                            style: FontStyle::try_from(style).map_err(|_| ParseError::new_with_pos("Font style invalid!", (line_index, RESOURCES_FONT_KEY.len() + family.len() + 1 /* VALUE_SEPARATOR */) ))?
205                                        },
206                                        base64::decode(data).map_err(|_| ParseError::new_with_pos("Font data not in base64 format!", (line_index, RESOURCES_FONT_KEY.len() + family.len() + style.len() + (1 /* VALUE_SEPARATOR */ << 1))) )?
207                                    );
208                                } else {
209                                    return Err(ParseError::new_with_pos("Font family, style and data expected!", (line_index, RESOURCES_FONT_KEY.len())));
210                                }
211                            }
212                            // Texture
213                            else if line.starts_with(RESOURCES_TEXTURE_KEY) {
214                                // Parse tokens
215                                let mut texture_tokens = line[RESOURCES_TEXTURE_KEY.len()..].splitn(3, VALUE_SEPARATOR);
216                                if let (Some(id), Some(data_type), Some(data)) = (texture_tokens.next(), texture_tokens.next(), texture_tokens.next()) {
217                                    // Save texture
218                                    self.textures.insert(
219                                        id.to_owned(),
220                                        match data_type {
221                                            // Raw data
222                                            "data" => TextureDataVariant::Raw(
223                                                base64::decode(data).map_err(|_| ParseError::new_with_pos("Texture data not in base64 format!", (line_index, RESOURCES_TEXTURE_KEY.len() + id.len() + data_type.len() + (1 /* VALUE_SEPARATOR */ << 1))) )?
224                                            ),
225                                            // Data by url
226                                            "url" => TextureDataVariant::Url(
227                                                data.to_owned()
228                                            ),
229                                            _ => return Err(ParseError::new_with_pos("Texture data type invalid!", (line_index, RESOURCES_TEXTURE_KEY.len() + id.len() + 1 /* VALUE_SEPARATOR */)))
230                                        }
231                                    );
232                                } else {
233                                    return Err(ParseError::new_with_pos("Texture id, data type and data expected!", (line_index, RESOURCES_TEXTURE_KEY.len())));
234                                }
235                            }
236                            // Invalid entry
237                            else {
238                                return Err(ParseError::new_with_pos("Invalid resources entry!", (line_index, 0)));
239                            }
240                        }
241                        // Unset section
242                        None => return Err(ParseError::new_with_pos("No section set!", (line_index, 0)))
243                    }
244                }
245            }
246        }
247        // Return self for chaining calls
248        Ok(self)
249    }
250}