mxmlextrema_as3parser/tree/
css.rs

1use std::{marker::PhantomData, str::FromStr};
2
3use crate::ns::*;
4use num_traits::ToPrimitive;
5use serde::{Serialize, Deserialize};
6
7/// CSS3 selector combinators.
8/// 
9/// See also: [CSS3 selectors: combinators](http://www.w3.org/TR/css3-selectors/#combinators).
10#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum CssCombinatorType {
12    Descendant,
13    Child,
14    Preceded,
15    Sibling,
16}
17
18impl ToString for CssCombinatorType {
19    /// Symbol that represents the combinator type.
20    fn to_string(&self) -> String {
21        match self {
22            Self::Descendant => " ".into(),
23            Self::Child => ">".into(),
24            Self::Preceded => "+".into(),
25            Self::Sibling => "~".into(),
26        }
27    }
28}
29
30#[derive(Clone, Serialize, Deserialize)]
31pub enum CssDirective {
32    Invalidated(InvalidatedNode),
33    Import(CssImport),
34    FontFace(CssFontFace),
35    MediaQuery(CssMediaQuery),
36    NamespaceDefinition(CssNamespaceDefinition),
37    Rule(CssRule),
38}
39
40impl CssDirective {
41    pub fn location(&self) -> Location {
42        match self {
43            Self::Invalidated(v) => v.location.clone(),
44            Self::Import(v) => v.location.clone(),
45            Self::FontFace(v) => v.location.clone(),
46            Self::MediaQuery(v) => v.location.clone(),
47            Self::NamespaceDefinition(v) => v.location.clone(),
48            Self::Rule(v) => v.location.clone(),
49        }
50    }
51}
52
53#[derive(Clone, Serialize, Deserialize)]
54pub enum CssPropertyValue {
55    Invalidated(InvalidatedNode),
56    /// Example: `yellow, #fff`
57    Array(CssArrayPropertyValue),
58    /// Example: `1px solid red`
59    MultiValue(CssMultiValuePropertyValue),
60    /// Example: `yellow`, `#fff`
61    Color(CssColorPropertyValue),
62    /// Example: `10, 10.0, 10pt`
63    Number(CssNumberPropertyValue),
64    /// Example: `rgb(10% 10% 10%)`, `rgb(10%, 10%, 10%)`
65    RgbColor(CssRgbColorPropertyValue),
66    /// Example: `"string"`
67    String(CssStringPropertyValue),
68    /// Example: `solid`, `_serif`
69    Identifier(CssIdentifierPropertyValue),
70    /// `ClassReference(...)`
71    ClassReference(CssClassReferencePropertyValue),
72    /// `PropertyReference(...)`
73    PropertyReference(CssPropertyReferencePropertyValue),
74    //// `url(...) [format(...)]`
75    Url(CssUrlPropertyValue),
76    /// `local(...)`
77    Local(CssLocalPropertyValue),
78    /// `Embed(...)`
79    Embed(CssEmbedPropertyValue),
80}
81
82impl CssPropertyValue {
83    pub fn location(&self) -> Location {
84        match self {
85            Self::Invalidated(v) => v.location.clone(),
86            Self::Array(v) => v.location.clone(),
87            Self::MultiValue(v) => v.location.clone(),
88            Self::Color(v) => v.location.clone(),
89            Self::Number(v) => v.location.clone(),
90            Self::RgbColor(v) => v.location.clone(),
91            Self::String(v) => v.location.clone(),
92            Self::Identifier(v) => v.location.clone(),
93            Self::ClassReference(v) => v.location.clone(),
94            Self::PropertyReference(v) => v.location.clone(),
95            Self::Url(v) => v.location.clone(),
96            Self::Local(v) => v.location.clone(),
97            Self::Embed(v) => v.location.clone(),
98        }
99    }
100
101    pub fn as_array(&self) -> Option<&CssArrayPropertyValue> {
102        let Self::Array(v) = self else { return None; };
103        Some(v)
104    }
105}
106
107#[derive(Clone, Serialize, Deserialize)]
108pub enum CssSelector {
109    Invalidated(InvalidatedNode),
110    Base(CssBaseSelector),
111    Combinator(CssCombinatorSelector),
112}
113
114impl CssSelector {
115    pub fn location(&self) -> Location {
116        match self {
117            Self::Invalidated(v) => v.location.clone(),
118            Self::Base(v) => v.location.clone(),
119            Self::Combinator(v) => v.location.clone(),
120        }
121    }
122}
123
124impl Eq for CssSelector {}
125
126impl PartialEq for CssSelector {
127    fn eq(&self, other: &Self) -> bool {
128        match self {
129            Self::Invalidated(_) => matches!(other, Self::Invalidated(_)),
130            Self::Base(v1) => {
131                if let Self::Base(v2) = other {
132                    v1 == v2
133                } else {
134                    false
135                }
136            },
137            Self::Combinator(v1) => {
138                if let Self::Combinator(v2) = other {
139                    v1 == v2
140                } else {
141                    false
142                }
143            },
144        }
145    }
146}
147
148/// Array property values are comma-separated values in CSS properties.
149///
150/// For example:
151///
152/// ```css
153/// fillColors: #FFFFFF, #CCCCCC, #FFFFFF, #EEEEEE;
154/// ```
155#[derive(Clone, Serialize, Deserialize)]
156pub struct CssArrayPropertyValue {
157    pub location: Location,
158    pub elements: Vec<Rc<CssPropertyValue>>,
159}
160
161/// Multi-value property values are space-separated values in CSS properties.
162///
163/// For example:
164///
165/// ```css
166/// 1px solid blue
167/// ```
168#[derive(Clone, Serialize, Deserialize)]
169pub struct CssMultiValuePropertyValue {
170    pub location: Location,
171    pub values: Vec<Rc<CssPropertyValue>>,
172}
173
174/// A CSS base selector.
175#[derive(Clone, Serialize, Deserialize)]
176pub struct CssBaseSelector {
177    pub location: Location,
178    pub namespace_prefix: Option<(String, Location)>,
179    pub element_name: Option<(String, Location)>,
180    pub conditions: Vec<Rc<CssSelectorCondition>>,
181}
182
183impl Eq for CssBaseSelector {}
184
185impl PartialEq for CssBaseSelector {
186    fn eq(&self, other: &Self) -> bool {
187        self.namespace_prefix.as_ref().map(|p| &p.0) == other.namespace_prefix.as_ref().map(|p| &p.0) &&
188        self.element_name.as_ref().map(|n| &n.0) == other.element_name.as_ref().map(|n| &n.0) &&
189        self.conditions == other.conditions
190    }
191}
192
193/// Supported condition types for [`CssSelectorCondition`].
194#[derive(Clone, Serialize, Deserialize)]
195pub enum CssSelectorCondition {
196    Invalidated(InvalidatedNode),
197    /// For example: `s|Label.className`
198    Class((String, Location)),
199    /// For example: `s|Label#idValue`
200    Id((String, Location)),
201    /// For example: `s|Label:loadingState`
202    Pseudo((String, Location)),
203    /// For example: `s|Label::loadingState`
204    PseudoElement((String, Location)),
205    /// For example: `s|Label:nth-child(odd)`. This is not
206    /// a "condition", but a selector.
207    NthChild((CssNthChildKind, Location)),
208    /// For example: `s|Panel:not(:first-child)`
209    Not {
210        location: Location,
211        condition: Rc<CssSelectorCondition>,
212    },
213    /// For example: `s|Label[loadingState]`
214    Attribute {
215        location: Location,
216        name: (String, Location),
217        operator: Option<CssAttributeOperator>,
218        value: Option<(String, Location)>,
219    },
220}
221
222impl Eq for CssSelectorCondition {}
223
224impl PartialEq for CssSelectorCondition {
225    fn eq(&self, other: &Self) -> bool {
226        match self {
227            Self::Invalidated(_) => matches!(other, Self::Invalidated(_)),
228            Self::Class((v1, _)) => {
229                if let Self::Class((v2, _)) = other {
230                    v1 == v2
231                } else {
232                    false
233                }
234            },
235            Self::Id((v1, _)) => {
236                if let Self::Id((v2, _)) = other {
237                    v1 == v2
238                } else {
239                    false
240                }
241            },
242            Self::Pseudo((v1, _)) => {
243                if let Self::Pseudo((v2, _)) = other {
244                    v1 == v2
245                } else {
246                    false
247                }
248            },
249            Self::PseudoElement((v1, _)) => {
250                if let Self::PseudoElement((v2, _)) = other {
251                    v1 == v2
252                } else {
253                    false
254                }
255            },
256            Self::NthChild((v1, _)) => {
257                if let Self::NthChild((v2, _)) = other {
258                    v1 == v2
259                } else {
260                    false
261                }
262            },
263            Self::Not { condition: c1, .. } => {
264                if let Self::Not { condition: c2, ..} = other {
265                    c1 == c2
266                } else {
267                    false
268                }
269            },
270            Self::Attribute { name: (name1, _), operator: op1, value: value1, .. } => {
271                let value1 = value1.as_ref().map(|(v, _)| v);
272                if let Self::Attribute { name: (name2, _), operator: op2, value: value2, .. } = other {
273                    let value2 = value2.as_ref().map(|(v, _)| v);
274                    name1 == name2 && op1 == op2 && value1 == value2
275                } else {
276                    false
277                }
278            },
279        }
280    }
281}
282
283#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
284pub enum CssNthChildKind {
285    Invalidated,
286    Odd,
287    Even,
288    Number(u32),
289}
290
291impl CssSelectorCondition {
292    pub fn location(&self) -> Location {
293        match self {
294            Self::Invalidated(v) => v.location.clone(),
295            Self::Class((_, l)) => l.clone(),
296            Self::Id((_, l)) => l.clone(),
297            Self::NthChild((_, l)) => l.clone(),
298            Self::Pseudo((_, l)) => l.clone(),
299            Self::PseudoElement((_, l)) => l.clone(),
300            Self::Not { location, .. } => location.clone(),
301            Self::Attribute { location, .. } => location.clone(),
302        }
303    }
304}
305
306#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
307pub enum CssAttributeOperator {
308    Equals,
309    BeginsWith,
310    EndsWith,
311    Contains,
312    ListMatch,
313    HreflangMatch,
314}
315
316impl ToString for CssAttributeOperator {
317    fn to_string(&self) -> String {
318        match self {
319            Self::Equals => "=".into(),
320            Self::BeginsWith => "^=".into(),
321            Self::EndsWith => "$=".into(),
322            Self::Contains => "*=".into(),
323            Self::ListMatch => "~=".into(),
324            Self::HreflangMatch => "|=".into(),
325        }
326    }
327}
328
329#[derive(Clone, Serialize, Deserialize)]
330pub struct CssColorPropertyValue {
331    pub location: Location,
332    pub color_int: u32,
333}
334
335impl CssColorPropertyValue {
336    pub fn from_hex(location: Location, token_text: &str) -> Result<Self, ParserError> {
337        let mut token_text = if token_text.starts_with('#') { token_text.to_owned() } else {
338            "#".to_owned() + token_text
339        };
340        if token_text.len() == 4 {
341            let mut six = String::new();
342            let chars: Vec<_> = token_text.chars().collect();
343            six.push('#');
344            six.push(chars[1]);
345            six.push(chars[1]);
346            six.push(chars[2]);
347            six.push(chars[2]);
348            six.push(chars[3]);
349            six.push(chars[3]);
350            token_text = six;
351        }
352        Ok(Self {
353            location,
354            color_int: u32::from_str_radix(&token_text[1..], 16).map_err(|_| ParserError::Common)?.clamp(0x000000, 0xFFFFFF),
355        })
356    }
357
358    pub fn text(&self) -> String {
359        self.location.text()
360    }
361}
362
363#[derive(Clone, Serialize, Deserialize)]
364pub struct CssNumberPropertyValue {
365    pub location: Location,
366    pub value: f64,
367    pub unit: Option<String>,
368}
369
370#[derive(Clone, Serialize, Deserialize)]
371pub struct CssRgbColorPropertyValue {
372    pub location: Location,
373    pub color_int: u32,
374}
375
376impl CssRgbColorPropertyValue {
377    pub fn from_raw_arguments(location: &Location, raw_arguments: &[String]) -> Result<Self, ParserError> {
378        Ok(CssRgbColorPropertyValue {
379            location: location.clone(),
380            color_int: (Self::parse_component(&raw_arguments[0])? << 16)
381                    |  (Self::parse_component(&raw_arguments[1])? << 8)
382                    |   Self::parse_component(&raw_arguments[2])?,
383        })
384    }
385
386    fn parse_component(input: &str) -> Result<u32, ParserError> {
387        let i = input.find('%');
388        let v: u32;
389        if let Some(i) = i {
390            let percent = f64::from_str(&input[..i]).map_err(|_| ParserError::Common)?.clamp(0.0, 100.0);
391            v = (255.0 * (percent / 100.0)).round().to_u32().ok_or(ParserError::Common)?;
392        } else if input.contains('.') {
393            let ratio = f64::from_str(input).map_err(|_| ParserError::Common)?.clamp(0.0, 1.0);
394            v = (255.0 * ratio).round().to_u32().ok_or(ParserError::Common)?;
395        } else {
396            v = u32::from_str(input).map_err(|_| ParserError::Common)?;
397        }
398        Ok(v.clamp(0, 255))
399    }
400}
401
402/// A CSS text is a string value written without quotes.
403#[derive(Clone, Serialize, Deserialize)]
404pub struct CssStringPropertyValue {
405    pub location: Location,
406    pub value: String,
407}
408
409#[derive(Clone, Serialize, Deserialize)]
410pub struct CssIdentifierPropertyValue {
411    pub location: Location,
412    pub value: String,
413}
414
415#[derive(Clone, Serialize, Deserialize)]
416pub struct CssClassReferencePropertyValue {
417    pub location: Location,
418    /// Name or "null".
419    pub name: (String, Location),
420}
421
422#[derive(Clone, Serialize, Deserialize)]
423pub struct CssPropertyReferencePropertyValue {
424    pub location: Location,
425    pub name: (String, Location),
426}
427
428#[derive(Clone, Serialize, Deserialize)]
429pub struct CssUrlPropertyValue {
430    pub location: Location,
431    pub url: (String, Location),
432    pub format: Option<(String, Location)>,
433}
434
435#[derive(Clone, Serialize, Deserialize)]
436pub struct CssLocalPropertyValue {
437    pub location: Location,
438    pub name: (String, Location),
439}
440
441#[derive(Clone, Serialize, Deserialize)]
442pub struct CssEmbedPropertyValue {
443    pub location: Location,
444    pub entries: Vec<Rc<CssEmbedEntry>>,
445}
446
447/// Represents a key-value entry for an `Embed` function call property value.
448/// It may be a keyless entry.
449#[derive(Clone, Serialize, Deserialize)]
450pub struct CssEmbedEntry {
451    pub location: Location,
452    pub key: Option<(String, Location)>,
453    pub value: (String, Location),
454}
455
456/// A CSS selector containing a combinator.
457#[derive(Clone, Serialize, Deserialize)]
458pub struct CssCombinatorSelector {
459    pub location: Location,
460    pub left: Rc<CssSelector>,
461    pub right: Rc<CssSelector>,
462    pub combinator_type: CssCombinatorType,
463}
464
465impl Eq for CssCombinatorSelector {}
466
467impl PartialEq for CssCombinatorSelector {
468    fn eq(&self, other: &Self) -> bool {
469        self.left == other.left &&
470        self.right == other.right &&
471        self.combinator_type == other.combinator_type
472    }
473}
474
475/// The root object of a CSS DOM. The CSS3 DOM objects serve not only IDE
476/// features in code model, but also CSS compilation.
477#[derive(Clone, Serialize, Deserialize)]
478pub struct CssDocument {
479    pub location: Location,
480    pub directives: Vec<Rc<CssDirective>>,
481}
482
483/// CSS DOM for an `@font-face` statement.
484#[derive(Clone, Serialize, Deserialize)]
485pub struct CssFontFace {
486    pub location: Location,
487    pub properties: Vec<Rc<CssProperty>>,
488}
489
490/// CSS DOM for an `@import` statement.
491#[derive(Clone, Serialize, Deserialize)]
492pub struct CssImport {
493    pub location: Location,
494    /// If invalidated, holds `None`.
495    pub url: Option<String>,
496}
497
498#[derive(Clone, Serialize, Deserialize)]
499pub struct CssProperty {
500    pub location: Location,
501    pub name: (String, Location),
502    pub value: Rc<CssPropertyValue>,
503    #[serde(skip)]
504    _phantom: PhantomData<()>,
505}
506
507impl CssProperty {
508    pub fn new(location: Location, name: (String, Location), value: Rc<CssPropertyValue>) -> Self {
509        Self {
510            location,
511            name: (Self::normalize(&name.0), name.1),
512            value,
513            _phantom: PhantomData::default(),
514        }
515    }
516
517    /// Normalize CSS property names to camel-case style names. Names already in
518    /// camel-case will be returned as-is.
519    fn normalize(name: &str) -> String {
520        let mut split = name.split('-').map(|s| s.to_owned()).collect::<Vec<_>>();
521        let mut v = split[0].chars();
522        let mut v1 = String::new();
523        if let Some(ch) = v.next() {
524            v1.push_str(&ch.to_lowercase().to_string());
525            for ch in v {
526                v1.push(ch);
527            }
528        }
529        split[0] = v1;
530        for i in 1..split.len() {
531            let mut v = split[i].chars();
532            let mut v1 = String::new();
533            if let Some(ch) = v.next() {
534                v1.push_str(&ch.to_uppercase().to_string());
535                for ch in v {
536                    v1.push(ch);
537                }
538            }
539            split[i] = v1;
540        }
541        split.join("")
542    }
543}
544
545#[derive(Clone, Serialize, Deserialize)]
546pub struct CssMediaQuery {
547    pub location: Location,
548    pub conditions: Vec<Rc<CssMediaQueryCondition>>,
549    pub rules: Vec<Rc<CssRule>>,
550}
551
552#[derive(Clone, Serialize, Deserialize)]
553pub enum CssMediaQueryCondition {
554    Invalidated(InvalidatedNode),
555    /// Identifier. Example: "screen".
556    Id((String, Location)),
557    /// The `only` keyword followed by an identifier.
558    /// Example: "only screen".
559    OnlyId {
560        location: Location,
561        id: (String, Location),
562    },
563    /// A parenthesized property, such as
564    /// `(application-dpi: 240)`.
565    ParenProperty((Rc<CssProperty>, Location)),
566    /// A `condition1 and condition2` expression.
567    And {
568        location: Location,
569        left: Rc<CssMediaQueryCondition>,
570        right: Rc<CssMediaQueryCondition>,
571    },
572}
573
574impl CssMediaQueryCondition {
575    pub fn location(&self) -> Location {
576        match self {
577            Self::Invalidated(v) => v.location.clone(),
578            Self::Id((_, l)) => l.clone(),
579            Self::OnlyId { location, .. } => location.clone(),
580            Self::ParenProperty((_, l)) => l.clone(),
581            Self::And { location, .. } => location.clone(),
582        }
583    }
584}
585
586#[derive(Clone, Serialize, Deserialize)]
587pub struct CssRule {
588    pub location: Location,
589    pub selectors: Vec<Rc<CssSelector>>,
590    pub properties: Vec<Rc<CssProperty>>,
591}
592
593#[derive(Clone, Serialize, Deserialize)]
594pub struct CssNamespaceDefinition {
595    pub location: Location,
596    pub prefix: (String, Location),
597    pub uri: (String, Location),
598}