Skip to main content

ferroui_markup/markup/parsers/
selector_parser.rs

1//! Port of `Markup/Parsers/SelectorParser.cs`: creates a selector from its
2//! text at run time.
3
4use 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/// What [`SelectorParser::parse`] fails with: the exceptions the managed
12/// original throws.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum SelectorParserError {
15    /// The text is not a selector (`ExpressionParseException`).
16    Parse(ExpressionParseException),
17    /// The selector names a type, a property or a value that does not exist
18    /// or does not apply (`InvalidOperationException`).
19    InvalidOperation(String),
20    /// The selector cannot be created from this syntax
21    /// (`NotSupportedException`).
22    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
44/// Resolves the type a selector names: the namespace prefix (empty for
45/// none) and the type name.
46pub type SelectorTypeResolver = dyn Fn(&str, &str) -> Option<&'static TypeInfo>;
47
48/// Parses a [`Selector`] from text.
49pub struct SelectorParser {
50    type_resolver: Box<SelectorTypeResolver>,
51}
52
53impl SelectorParser {
54    /// Creates a parser.
55    ///
56    /// `type_resolver` resolves the types the selector names: it is given
57    /// the namespace prefix and the type name.
58    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    /// Parses a [`Selector`] from text. `None` for an empty selector.
63    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                    // As in the managed original, a type that does not resolve is reported
119                    // by `resolve`, before the check of the owner type.
120                    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                    // The managed original dereferences the selector of the argument: an
157                    // empty argument is a null reference there.
158                    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
206/// `TypeUtilities.TryConvert(property.PropertyType, value, ..)`: the text of
207/// a property selector as a value of the type of the property.
208fn 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}