ferroui_markup/markup/parsers/
selector_parser.rs1use super::{SelectorGrammar, SelectorSyntax};
5use ferroui_base::data::core::{ExpressionParseException, ValueType, ValueTypes};
6use ferroui_base::styling::{Selector, Selectors};
7use ferroui_base::{BoxedValue, FerroProperty, FerroPropertyRegistry, TypeInfo};
8use std::fmt;
9use std::rc::Rc;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum SelectorParserError {
15 Parse(ExpressionParseException),
17 InvalidOperation(String),
20 NotSupported(String),
23}
24
25impl fmt::Display for SelectorParserError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 SelectorParserError::Parse(error) => error.fmt(f),
29 SelectorParserError::InvalidOperation(message) | SelectorParserError::NotSupported(message) => {
30 f.write_str(message)
31 }
32 }
33 }
34}
35
36impl std::error::Error for SelectorParserError {}
37
38impl From<ExpressionParseException> for SelectorParserError {
39 fn from(error: ExpressionParseException) -> Self {
40 SelectorParserError::Parse(error)
41 }
42}
43
44pub type SelectorTypeResolver = dyn Fn(&str, &str) -> Option<&'static TypeInfo>;
47
48pub struct SelectorParser {
50 type_resolver: Box<SelectorTypeResolver>,
51}
52
53impl SelectorParser {
54 pub fn new(type_resolver: impl Fn(&str, &str) -> Option<&'static TypeInfo> + 'static) -> Self {
59 Self { type_resolver: Box::new(type_resolver) }
60 }
61
62 pub fn parse(&self, s: &str) -> Result<Option<Selector>, SelectorParserError> {
64 let syntax = SelectorGrammar::parse(s)?;
65 self.create(&syntax)
66 }
67
68 fn create(&self, syntax: &[SelectorSyntax]) -> Result<Option<Selector>, SelectorParserError> {
69 let mut result: Option<Selector> = None;
70 let mut results: Option<Vec<Selector>> = None;
71
72 for i in syntax {
73 match i {
74 SelectorSyntax::OfType { type_name, xmlns } => {
75 result = Some(Selectors::of_type_info(result, self.resolve(xmlns, type_name)?));
76 }
77 SelectorSyntax::Is { type_name, xmlns } => {
78 result = Some(Selectors::is_type_info(result, self.resolve(xmlns, type_name)?));
79 }
80 SelectorSyntax::Class { class } => {
81 result = Some(Selectors::class(result, class));
82 }
83 SelectorSyntax::Name { name } => {
84 result = Some(Selectors::name(result, name));
85 }
86 SelectorSyntax::Property { property, value } => {
87 let Some(type_) = result.as_ref().and_then(Selector::target_type) else {
88 return Err(SelectorParserError::InvalidOperation(
89 "Property selectors must be applied to a type.".to_string(),
90 ));
91 };
92
93 let Some(target_property) = FerroPropertyRegistry::instance().find_registered(type_, property)
94 else {
95 return Err(SelectorParserError::InvalidOperation(format!(
96 "Cannot find '{property}' on '{type_}"
97 )));
98 };
99
100 match try_convert(target_property, value) {
101 Some(typed_value) => {
102 result = Some(Selectors::property_equals_untyped(result, target_property, typed_value));
103 }
104 None => {
105 return Err(SelectorParserError::InvalidOperation(format!(
106 "Could not convert '{value}' to '{}",
107 target_property.property_type_name()
108 )));
109 }
110 }
111 }
112 SelectorSyntax::AttachedProperty { xmlns, type_name, property, value } => {
113 let Some(target_type) = result.as_ref().and_then(Selector::target_type) else {
114 return Err(SelectorParserError::InvalidOperation(
115 "Attached Property selectors must be applied to a type.".to_string(),
116 ));
117 };
118 let attached_property_owner_type = self.resolve(xmlns, type_name)?;
121
122 let registered = FerroPropertyRegistry::instance().get_registered_attached(target_type);
123 let target_attached_property = registered
124 .iter()
125 .copied()
126 .find(|ap| std::ptr::eq(ap.owner_type(), attached_property_owner_type) && ap.name() == property);
127 let Some(target_attached_property) = target_attached_property else {
128 return Err(SelectorParserError::InvalidOperation(format!(
129 "Cannot find '{property}' on '{attached_property_owner_type}"
130 )));
131 };
132
133 match try_convert(target_attached_property, value) {
134 Some(typed_value) => {
135 result =
136 Some(Selectors::property_equals_untyped(result, target_attached_property, typed_value));
137 }
138 None => {
139 return Err(SelectorParserError::InvalidOperation(format!(
140 "Could not convert '{value}' to '{}",
141 target_attached_property.property_type_name()
142 )));
143 }
144 }
145 }
146 SelectorSyntax::Child => {
147 result = Some(Selectors::child(result));
148 }
149 SelectorSyntax::Descendant => {
150 result = Some(Selectors::descendant(result));
151 }
152 SelectorSyntax::Template => {
153 result = Some(Selectors::template(result));
154 }
155 SelectorSyntax::Not { argument } => {
156 let argument = self
159 .create(argument)?
160 .ok_or_else(|| SelectorParserError::NotSupported("Invalid selector!".to_string()))?;
161 result = Some(Selectors::not(result, argument));
162 }
163 SelectorSyntax::NthChild { offset, step } => {
164 result = Some(Selectors::nth_child(result, *step, *offset));
165 }
166 SelectorSyntax::NthLastChild { offset, step } => {
167 result = Some(Selectors::nth_last_child(result, *step, *offset));
168 }
169 SelectorSyntax::Comma => {
170 let selector = result
171 .take()
172 .ok_or_else(|| SelectorParserError::NotSupported("Invalid selector!".to_string()))?;
173 results.get_or_insert_with(Vec::new).push(selector);
174 }
175 SelectorSyntax::Nesting => {
176 return Err(SelectorParserError::NotSupported(
177 "Unsupported selector grammar 'NestingSyntax'.".to_string(),
178 ));
179 }
180 }
181 }
182
183 if let Some(mut results) = results {
184 if let Some(result) = result.take() {
185 results.push(result);
186 }
187
188 result = if results.len() > 1 { Some(Selectors::or(results)) } else { results.into_iter().next() };
189 }
190
191 Ok(result)
192 }
193
194 fn resolve(&self, xmlns: &str, type_name: &str) -> Result<&'static TypeInfo, SelectorParserError> {
195 match (self.type_resolver)(xmlns, type_name) {
196 Some(result) => Ok(result),
197 None => {
198 let type_ =
199 if xmlns.trim().is_empty() { type_name.to_string() } else { format!("{xmlns}:{type_name}") };
200 Err(SelectorParserError::InvalidOperation(format!("Could not resolve type '{type_}'")))
201 }
202 }
203 }
204}
205
206fn try_convert(property: &'static FerroProperty, value: &str) -> Option<BoxedValue> {
209 let text: BoxedValue = Rc::new(value.to_string());
210 let target = ValueType::new(property.property_type(), property.property_type_name());
211 ValueTypes::try_convert(Some(&text), target).flatten()
212}