use crate::prelude::*; use anyhow::{Context, Result}; use assetpath::AssetPath; use quick_xml::{events::Event, Reader}; use std::borrow::Cow; use std::str::from_utf8; use std::str::FromStr; use std::{convert::TryFrom, io::BufRead, sync::Arc}; use super::buttoninfo::ButtonInfo; use super::gridinfo::GridInfo; use super::iconinfo::IconInfo; use super::labelinfo::LabelInfo; use super::multi_line_labelinfo::MultiLineLabelInfo; use super::multi_line_text_field_info::MultiLineTextFieldInfo; use super::progressbar_info::ProgressBarInfo; use super::rootinfo::RootInfo; use super::textfieldinfo::TextFieldInfo; use super::uiinfoelement::UiInfoElement; pub struct Validator { reference_width: Option, reference_height: Option, layer: Option, root: Root, } pub struct Root { pub children: Vec>, } enum StartResult { Grid(GridInfo), Button(ButtonInfo), Label(LabelInfo), MultiLineLabel(MultiLineLabelInfo), MultiLineTextField(MultiLineTextFieldInfo), Icon(IconInfo), ProgressBar(ProgressBarInfo), TextField(TextFieldInfo), Root(RootInfo), } enum CurrentElement { Grid(Arc), Button(Arc), Label(Arc), MultiLineLabel(Arc), MultiLineTextField(Arc), Icon(Arc), ProgressBar(Arc), TextField(Arc), Root, None, } impl Validator { pub fn from_str(gui_handler: &Arc, s: &str) -> Result { Self::_new(gui_handler, Reader::from_str(s)) } pub fn new(gui_handler: &Arc, path: &AssetPath) -> Result { Self::_new( gui_handler, Reader::from_file(path.full_path()).with_context(|| path.full_path())?, ) } #[inline] fn _new(gui_handler: &Arc, mut reader: Reader) -> Result { // removes white spaces in texts reader.trim_text(true); // into reader.expand_empty_elements(true); // validate closing tags reader.check_end_names(true); let mut buf = Vec::new(); let mut reference_width = None; let mut reference_height = None; let mut layer = None; let mut root = Root { children: Vec::new(), }; let mut current_element = CurrentElement::None; 'outer: loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { let start_result = Self::process_start(gui_handler, e, ¤t_element)?; match start_result { StartResult::Root(root_info) => { reference_width = root_info.reference_width; reference_height = root_info.reference_height; layer = root_info.ui_layer; current_element = CurrentElement::Root; } StartResult::Grid(mut grid_info) => { if let CurrentElement::Grid(ref current_info) = current_element { grid_info.parent = Some(Arc::downgrade(current_info)); } let arc_info = Arc::new(grid_info); match current_element { CurrentElement::Grid(ref current_info) => current_info .children .write() .unwrap() .push(UiInfoElement::Grid(arc_info.clone())), CurrentElement::Root => root.children.push(arc_info.clone()), _ => return Err(anyhow::Error::msg("Wrong element in parser")), } current_element = CurrentElement::Grid(arc_info); } StartResult::Button(button_info) => { let arc_info = Arc::new(button_info); match current_element { CurrentElement::Grid(ref grid) => { grid.children .write() .unwrap() .push(UiInfoElement::Button(arc_info.clone())); } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }; current_element = CurrentElement::Button(arc_info); } StartResult::Label(label_info) => { let arc_info = Arc::new(label_info); match current_element { CurrentElement::Grid(ref grid) => { grid.children .write() .unwrap() .push(UiInfoElement::Label(arc_info.clone())); } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }; current_element = CurrentElement::Label(arc_info); } StartResult::MultiLineLabel(multi_line_label_info) => { let arc_info = Arc::new(multi_line_label_info); match current_element { CurrentElement::Grid(ref grid) => { grid.children .write() .unwrap() .push(UiInfoElement::MultiLineLabel(arc_info.clone())); } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }; current_element = CurrentElement::MultiLineLabel(arc_info); } StartResult::MultiLineTextField(multi_line_text_field_info) => { let arc_info = Arc::new(multi_line_text_field_info); match current_element { CurrentElement::Grid(ref grid) => { grid.children .write() .unwrap() .push(UiInfoElement::MultiLineTextField(arc_info.clone())); } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }; current_element = CurrentElement::MultiLineTextField(arc_info); } StartResult::Icon(icon_info) => { let arc_info = Arc::new(icon_info); match current_element { CurrentElement::Grid(ref grid) => { grid.children .write() .unwrap() .push(UiInfoElement::Icon(arc_info.clone())); } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }; current_element = CurrentElement::Icon(arc_info); } StartResult::ProgressBar(progress_bar) => { let arc_info = Arc::new(progress_bar); match current_element { CurrentElement::Grid(ref grid) => { grid.children .write() .unwrap() .push(UiInfoElement::ProgressBar(arc_info.clone())); } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }; current_element = CurrentElement::ProgressBar(arc_info); } StartResult::TextField(text_field) => { let arc_info = Arc::new(text_field); match current_element { CurrentElement::Grid(ref grid) => { grid.children .write() .unwrap() .push(UiInfoElement::TextField(arc_info.clone())); } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }; current_element = CurrentElement::TextField(arc_info); } } } Ok(Event::End(ref e)) => { let next_element = match current_element { CurrentElement::Root => CurrentElement::None, CurrentElement::None => { return Err(anyhow::Error::msg(format!( "Unexpected tag: {}", from_utf8(e.name().into_inner())? ))); } CurrentElement::Grid(ref grid) => match grid.parent { Some(ref weak_parent) => match weak_parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), }, None => CurrentElement::Root, }, CurrentElement::Button(ref button) => match button.parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), }, CurrentElement::Label(ref label) => match label.parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), }, CurrentElement::MultiLineLabel(ref multi_line_label) => { match multi_line_label.parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), } } CurrentElement::MultiLineTextField(ref multi_line_text_field) => { match multi_line_text_field.parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), } } CurrentElement::Icon(ref icon) => match icon.parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), }, CurrentElement::ProgressBar(ref progress_bar) => { match progress_bar.parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), } } CurrentElement::TextField(ref text_field) => { match text_field.parent.upgrade() { Some(arc_parent) => CurrentElement::Grid(arc_parent.clone()), None => return Err(anyhow::Error::msg("Invalid parent reference")), } } }; current_element = next_element; } Ok(Event::Text(ref e)) => match current_element { CurrentElement::Root => { return Err(anyhow::Error::msg("Text inside root not allowed")) } CurrentElement::None => { return Err(anyhow::Error::msg("Text outside root not allowed")) } CurrentElement::Grid(_) => { return Err(anyhow::Error::msg("Text inside grid not allowed")) } CurrentElement::Icon(ref icon) => { *icon.text.write().unwrap() = e.unescape()?.to_string(); } CurrentElement::Button(ref button) => { *button.text.write().unwrap() = e.unescape()?.to_string(); } CurrentElement::Label(ref label) => { *label.text.write().unwrap() = e.unescape()?.to_string(); } CurrentElement::MultiLineLabel(ref multi_line_label) => { *multi_line_label.text.write().unwrap() = e.unescape()?.to_string(); } CurrentElement::MultiLineTextField(ref multi_line_text_field) => { *multi_line_text_field.text.write().unwrap() = e.unescape()?.to_string(); } CurrentElement::ProgressBar(ref progress_bar) => { *progress_bar.text.write().unwrap() = e.unescape()?.to_string(); } CurrentElement::TextField(ref text_field) => { *text_field.text.write().unwrap() = e.unescape()?.to_string(); } }, Ok(Event::Eof) => match current_element { CurrentElement::None => break 'outer, _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, Err(e) => return Err(anyhow::Error::new(e)), _ => (), } buf.clear(); } Ok(Validator { reference_width, reference_height, layer, root, }) } fn process_start<'a>( gui_handler: &Arc, element: &quick_xml::events::BytesStart<'a>, handle: &CurrentElement, ) -> Result { Ok(match element.name().into_inner() { b"grid" => match handle { CurrentElement::None => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::Button(_) => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::Label(_) => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::MultiLineLabel(_) => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::MultiLineTextField(_) => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::Icon(_) => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::ProgressBar(_) => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::TextField(_) => { return Err(anyhow::Error::msg("Wrong element in parser")); } CurrentElement::Root => match GridInfo::new(element.attributes()) { Ok(grid) => StartResult::Grid(grid), Err(msg) => return Err(msg), }, CurrentElement::Grid(_) => match GridInfo::new(element.attributes()) { Ok(grid) => StartResult::Grid(grid), Err(msg) => return Err(msg), }, }, b"button" => match handle { CurrentElement::Grid(grid) => { match ButtonInfo::new(gui_handler, element.attributes(), grid) { Ok(button) => StartResult::Button(button), Err(msg) => return Err(msg), } } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, b"label" => match handle { CurrentElement::Grid(grid) => match LabelInfo::new(element.attributes(), grid) { Ok(label) => StartResult::Label(label), Err(msg) => return Err(msg), }, _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, b"multi_line_label" => match handle { CurrentElement::Grid(grid) => { match MultiLineLabelInfo::new(element.attributes(), grid) { Ok(label) => StartResult::MultiLineLabel(label), Err(msg) => return Err(msg), } } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, b"multi_line_textfield" => match handle { CurrentElement::Grid(grid) => { match MultiLineTextFieldInfo::new(element.attributes(), grid) { Ok(text_field) => StartResult::MultiLineTextField(text_field), Err(msg) => return Err(msg), } } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, b"root" => match handle { CurrentElement::None => match RootInfo::new(element.attributes()) { Ok(root) => StartResult::Root(root), Err(msg) => return Err(msg), }, _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, b"icon" => match handle { CurrentElement::Grid(grid) => match IconInfo::new(element.attributes(), grid) { Ok(icon) => StartResult::Icon(icon), Err(msg) => return Err(msg), }, _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, b"progressbar" => match handle { CurrentElement::Grid(grid) => { match ProgressBarInfo::new(element.attributes(), grid) { Ok(progress_bar) => StartResult::ProgressBar(progress_bar), Err(msg) => return Err(msg), } } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, b"textfield" => match handle { CurrentElement::Grid(grid) => { match TextFieldInfo::new(element.attributes(), grid) { Ok(text_field) => StartResult::TextField(text_field), Err(msg) => return Err(msg), } } _ => return Err(anyhow::Error::msg("Wrong element in parser")), }, _ => { return Err(anyhow::Error::msg(format!( "Unexpected tag: {}", from_utf8(element.name().into_inner())? ))) } }) } pub fn root(&self) -> &Root { &self.root } pub fn dimensions(&self) -> (Option, Option) { (self.reference_width, self.reference_height) } pub fn layer(&self) -> Option { self.layer } } pub fn str_into<'a, T>(cow: Cow<'a, [u8]>) -> Result where T: FromStr, { let string = cow_to_str(cow); match string.parse::() { Ok(number) => Ok(number), Err(_) => Err(anyhow::Error::msg(format!( "failed parsing value: {}", string ))), } } pub fn cow_to_fill_type<'a>(cow: Cow<'a, [u8]>) -> FillTypeInfo { let text = cow_to_str(cow); match Color::try_from(text.as_str()) { Ok(color) => FillTypeInfo::Color(color), Err(_) => FillTypeInfo::Image(AssetPath::from(text)), } } pub fn cow_to_button_select_mode<'a>(cow: Cow<'a, [u8]>) -> Result { let text = cow_to_str(cow); match text.as_str() { "none" => Ok(ButtonSelectMode::None), "bigger" => Ok(ButtonSelectMode::Bigger), _ => Err(anyhow::Error::msg(format!( "failed parsing value: {}", text ))), } } pub fn cow_to_text_alignment<'a>(cow: Cow<'a, [u8]>) -> Result { let text = cow_to_str(cow); TextAlignment::try_from(text) } pub fn str_to_vert_align<'a>(cow: Cow<'a, [u8]>) -> Result { let string = cow_to_str(cow); match string.as_str() { "top" => Ok(VerticalAlign::Top), "middle" => Ok(VerticalAlign::Middle), "bottom" => Ok(VerticalAlign::Bottom), _ => Err(anyhow::Error::msg(format!( "failed parsing value: {}", string ))), } } pub fn str_to_hori_align<'a>(cow: Cow<'a, [u8]>) -> Result { let string = cow_to_str(cow); match string.as_str() { "left" => Ok(HorizontalAlign::Left), "middle" => Ok(HorizontalAlign::Middle), "right" => Ok(HorizontalAlign::Right), _ => Err(anyhow::Error::msg(format!( "failed parsing value: {}", string ))), } } pub fn handle_function_suffix(fname: &str) -> String { if fname.ends_with("()") { str::replace(fname, "()", "") } else { fname.to_string() } } pub fn cow_to_str<'a>(cow: Cow<'a, [u8]>) -> String { let u8s: &[u8] = &cow.to_owned(); from_utf8(u8s).unwrap().to_string() } pub fn cow_to_path<'a>(cow: Cow<'a, [u8]>) -> AssetPath { AssetPath::from(cow_to_str(cow)) }