ui/src/builder/validator/validator.rs

560 lines
23 KiB
Rust
Raw Normal View History

2023-01-16 09:53:52 +00:00
use crate::prelude::*;
use anyhow::{Context, Result};
use assetpath::AssetPath;
2023-01-16 11:58:59 +00:00
use utilities::prelude::*;
2023-01-16 09:53:52 +00:00
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<u32>,
reference_height: Option<u32>,
layer: Option<i32>,
root: Root,
}
pub struct Root {
pub children: Vec<Arc<GridInfo>>,
}
enum StartResult {
Grid(GridInfo),
Button(ButtonInfo),
Label(LabelInfo),
MultiLineLabel(MultiLineLabelInfo),
MultiLineTextField(MultiLineTextFieldInfo),
Icon(IconInfo),
ProgressBar(ProgressBarInfo),
TextField(TextFieldInfo),
Root(RootInfo),
}
enum CurrentElement {
Grid(Arc<GridInfo>),
Button(Arc<ButtonInfo>),
Label(Arc<LabelInfo>),
MultiLineLabel(Arc<MultiLineLabelInfo>),
MultiLineTextField(Arc<MultiLineTextFieldInfo>),
Icon(Arc<IconInfo>),
ProgressBar(Arc<ProgressBarInfo>),
TextField(Arc<TextFieldInfo>),
Root,
None,
}
impl Validator {
pub fn from_str(gui_handler: &Arc<GuiHandler>, s: &str) -> Result<Validator> {
Self::_new(gui_handler, Reader::from_str(s))
}
pub fn new(gui_handler: &Arc<GuiHandler>, path: &AssetPath) -> Result<Validator> {
Self::_new(
gui_handler,
Reader::from_file(path.full_path()).with_context(|| path.full_path())?,
)
}
#[inline]
fn _new<T: BufRead>(gui_handler: &Arc<GuiHandler>, mut reader: Reader<T>) -> Result<Validator> {
// removes white spaces in texts
reader.trim_text(true);
// <tag/> into <tag></tag>
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, &current_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<GuiHandler>,
element: &quick_xml::events::BytesStart<'a>,
handle: &CurrentElement,
) -> Result<StartResult> {
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<u32>, Option<u32>) {
(self.reference_width, self.reference_height)
}
pub fn layer(&self) -> Option<i32> {
self.layer
}
}
pub fn str_into<'a, T>(cow: Cow<'a, [u8]>) -> Result<T>
where
T: FromStr,
{
let string = cow_to_str(cow);
match string.parse::<T>() {
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<ButtonSelectMode> {
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<TextAlignment> {
let text = cow_to_str(cow);
TextAlignment::try_from(text)
}
pub fn str_to_vert_align<'a>(cow: Cow<'a, [u8]>) -> Result<VerticalAlign> {
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<HorizontalAlign> {
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))
}