as3_parser/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    FontFace(CssFontFace),
34    MediaQuery(CssMediaQuery),
35    NamespaceDefinition(CssNamespaceDefinition),
36    Rule(CssRule),
37}
38
39impl CssDirective {
40    pub fn location(&self) -> Location {
41        match self {
42            Self::Invalidated(v) => v.location.clone(),
43            Self::FontFace(v) => v.location.clone(),
44            Self::MediaQuery(v) => v.location.clone(),
45            Self::NamespaceDefinition(v) => v.location.clone(),
46            Self::Rule(v) => v.location.clone(),
47        }
48    }
49}
50
51#[derive(Clone, Serialize, Deserialize)]
52pub enum CssPropertyValue {
53    Invalidated(InvalidatedNode),
54    /// Example: `yellow, #fff`
55    Array(CssArrayPropertyValue),
56    /// Example: `1px solid red`
57    MultiValue(CssMultiValuePropertyValue),
58    /// Example: `yellow`, `#fff`
59    Color(CssColorPropertyValue),
60    /// Example: `10, 10.0, 10pt`
61    Number(CssNumberPropertyValue),
62    /// Example: `rgb(10% 10% 10%)`, `rgb(10%, 10%, 10%)`
63    RgbColor(CssRgbColorPropertyValue),
64    /// Example: `"string"`
65    String(CssStringPropertyValue),
66    /// Example: `solid`, `_serif`
67    Identifier(CssIdentifierPropertyValue),
68    /// `ClassReference(...)`
69    ClassReference(CssClassReferencePropertyValue),
70    /// `PropertyReference(...)`
71    PropertyReference(CssPropertyReferencePropertyValue),
72    //// `url(...) [format(...)]`
73    Url(CssUrlPropertyValue),
74    /// `local(...)`
75    Local(CssLocalPropertyValue),
76    /// `Embed(...)`
77    Embed(CssEmbedPropertyValue),
78}
79
80impl CssPropertyValue {
81    pub fn location(&self) -> Location {
82        match self {
83            Self::Invalidated(v) => v.location.clone(),
84            Self::Array(v) => v.location.clone(),
85            Self::MultiValue(v) => v.location.clone(),
86            Self::Color(v) => v.location.clone(),
87            Self::Number(v) => v.location.clone(),
88            Self::RgbColor(v) => v.location.clone(),
89            Self::String(v) => v.location.clone(),
90            Self::Identifier(v) => v.location.clone(),
91            Self::ClassReference(v) => v.location.clone(),
92            Self::PropertyReference(v) => v.location.clone(),
93            Self::Url(v) => v.location.clone(),
94            Self::Local(v) => v.location.clone(),
95            Self::Embed(v) => v.location.clone(),
96        }
97    }
98
99    pub fn as_array(&self) -> Option<&CssArrayPropertyValue> {
100        let Self::Array(v) = self else { return None; };
101        Some(v)
102    }
103}
104
105#[derive(Clone, Serialize, Deserialize)]
106pub enum CssSelector {
107    Invalidated(InvalidatedNode),
108    Base(CssBaseSelector),
109    Combinator(CssCombinatorSelector),
110}
111
112impl CssSelector {
113    pub fn location(&self) -> Location {
114        match self {
115            Self::Invalidated(v) => v.location.clone(),
116            Self::Base(v) => v.location.clone(),
117            Self::Combinator(v) => v.location.clone(),
118        }
119    }
120}
121
122/// Array property values are comma-separated values in CSS properties.
123///
124/// For example:
125///
126/// ```css
127/// fillColors: #FFFFFF, #CCCCCC, #FFFFFF, #EEEEEE;
128/// ```
129#[derive(Clone, Serialize, Deserialize)]
130pub struct CssArrayPropertyValue {
131    pub location: Location,
132    pub elements: Vec<Rc<CssPropertyValue>>,
133}
134
135/// Multi-value property values are space-separated values in CSS properties.
136///
137/// For example:
138///
139/// ```css
140/// 1px solid blue
141/// ```
142#[derive(Clone, Serialize, Deserialize)]
143pub struct CssMultiValuePropertyValue {
144    pub location: Location,
145    pub values: Vec<Rc<CssPropertyValue>>,
146}
147
148/// A CSS base selector.
149#[derive(Clone, Serialize, Deserialize)]
150pub struct CssBaseSelector {
151    pub location: Location,
152    pub namespace_prefix: Option<(String, Location)>,
153    pub element_name: Option<(String, Location)>,
154    pub conditions: Vec<Rc<CssSelectorCondition>>,
155}
156
157/// Supported condition types for [`CssSelectorCondition`].
158#[derive(Clone, Serialize, Deserialize)]
159pub enum CssSelectorCondition {
160    Invalidated(InvalidatedNode),
161    /// For example: `s|Label.className`
162    Class((String, Location)),
163    /// For example: `s|Label#idValue`
164    Id((String, Location)),
165    /// For example: `s|Label:loadingState`
166    Pseudo((String, Location)),
167    /// For example: `s|Label::loadingState`
168    PseudoElement((String, Location)),
169    /// For example: `s|Panel:not(:first-child)`
170    Not {
171        location: Location,
172        condition: Rc<CssSelectorCondition>,
173    },
174    /// For example: `s|Label[loadingState]`
175    Attribute {
176        location: Location,
177        name: (String, Location),
178        operator: Option<CssAttributeOperator>,
179        value: Option<(String, Location)>,
180    },
181}
182
183impl CssSelectorCondition {
184    pub fn location(&self) -> Location {
185        match self {
186            Self::Invalidated(v) => v.location.clone(),
187            Self::Class((_, l)) => l.clone(),
188            Self::Id((_, l)) => l.clone(),
189            Self::Pseudo((_, l)) => l.clone(),
190            Self::PseudoElement((_, l)) => l.clone(),
191            Self::Not { location, .. } => location.clone(),
192            Self::Attribute { location, .. } => location.clone(),
193        }
194    }
195}
196
197#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
198pub enum CssAttributeOperator {
199    Equals,
200    BeginsWith,
201    EndsWith,
202    Contains,
203    ListMatch,
204    HreflangMatch,
205}
206
207impl ToString for CssAttributeOperator {
208    fn to_string(&self) -> String {
209        match self {
210            Self::Equals => "=".into(),
211            Self::BeginsWith => "^=".into(),
212            Self::EndsWith => "$=".into(),
213            Self::Contains => "*=".into(),
214            Self::ListMatch => "~=".into(),
215            Self::HreflangMatch => "|=".into(),
216        }
217    }
218}
219
220#[derive(Clone, Serialize, Deserialize)]
221pub struct CssColorPropertyValue {
222    pub location: Location,
223    pub color_int: u32,
224}
225
226impl CssColorPropertyValue {
227    pub fn from_hex(location: Location, token_text: &str) -> Result<Self, ParserError> {
228        let mut token_text = if token_text.starts_with('#') { token_text.to_owned() } else {
229            "#".to_owned() + token_text
230        };
231        if token_text.len() == 4 {
232            let mut six = String::new();
233            let chars: Vec<_> = token_text.chars().collect();
234            six.push('#');
235            six.push(chars[1]);
236            six.push(chars[1]);
237            six.push(chars[2]);
238            six.push(chars[2]);
239            six.push(chars[3]);
240            six.push(chars[3]);
241            token_text = six;
242        }
243        Ok(Self {
244            location,
245            color_int: u32::from_str_radix(&token_text[1..], 16).map_err(|_| ParserError::Common)?.clamp(0x000000, 0xFFFFFF),
246        })
247    }
248
249    pub fn text(&self) -> String {
250        self.location.text()
251    }
252}
253
254#[derive(Clone, Serialize, Deserialize)]
255pub struct CssNumberPropertyValue {
256    pub location: Location,
257    pub value: f64,
258    pub unit: Option<String>,
259}
260
261#[derive(Clone, Serialize, Deserialize)]
262pub struct CssRgbColorPropertyValue {
263    pub location: Location,
264    pub color_int: u32,
265}
266
267impl CssRgbColorPropertyValue {
268    pub fn from_raw_arguments(location: &Location, raw_arguments: &[String]) -> Result<Self, ParserError> {
269        Ok(CssRgbColorPropertyValue {
270            location: location.clone(),
271            color_int: (Self::parse_component(&raw_arguments[0])? << 16)
272                    |  (Self::parse_component(&raw_arguments[1])? << 8)
273                    |   Self::parse_component(&raw_arguments[2])?,
274        })
275    }
276
277    fn parse_component(input: &str) -> Result<u32, ParserError> {
278        let i = input.find('%');
279        let v: u32;
280        if let Some(i) = i {
281            let percent = f64::from_str(&input[..i]).map_err(|_| ParserError::Common)?.clamp(0.0, 100.0);
282            v = (255.0 * (percent / 100.0)).round().to_u32().ok_or(ParserError::Common)?;
283        } else if input.contains('.') {
284            let ratio = f64::from_str(input).map_err(|_| ParserError::Common)?.clamp(0.0, 1.0);
285            v = (255.0 * ratio).round().to_u32().ok_or(ParserError::Common)?;
286        } else {
287            v = u32::from_str(input).map_err(|_| ParserError::Common)?;
288        }
289        Ok(v.clamp(0, 255))
290    }
291}
292
293/// A CSS text is a string value written without quotes.
294#[derive(Clone, Serialize, Deserialize)]
295pub struct CssStringPropertyValue {
296    pub location: Location,
297    pub value: String,
298}
299
300#[derive(Clone, Serialize, Deserialize)]
301pub struct CssIdentifierPropertyValue {
302    pub location: Location,
303    pub value: String,
304}
305
306#[derive(Clone, Serialize, Deserialize)]
307pub struct CssClassReferencePropertyValue {
308    pub location: Location,
309    /// Name or "null".
310    pub name: (String, Location),
311}
312
313#[derive(Clone, Serialize, Deserialize)]
314pub struct CssPropertyReferencePropertyValue {
315    pub location: Location,
316    pub name: (String, Location),
317}
318
319#[derive(Clone, Serialize, Deserialize)]
320pub struct CssUrlPropertyValue {
321    pub location: Location,
322    pub url: (String, Location),
323    pub format: Option<(String, Location)>,
324}
325
326#[derive(Clone, Serialize, Deserialize)]
327pub struct CssLocalPropertyValue {
328    pub location: Location,
329    pub name: (String, Location),
330}
331
332#[derive(Clone, Serialize, Deserialize)]
333pub struct CssEmbedPropertyValue {
334    pub location: Location,
335    pub entries: Vec<Rc<CssEmbedEntry>>,
336}
337
338/// Represents a key-value entry for an `Embed` function call property value.
339/// It may be a keyless entry.
340#[derive(Clone, Serialize, Deserialize)]
341pub struct CssEmbedEntry {
342    pub location: Location,
343    pub key: Option<(String, Location)>,
344    pub value: (String, Location),
345}
346
347/// A CSS selector containing a combinator.
348#[derive(Clone, Serialize, Deserialize)]
349pub struct CssCombinatorSelector {
350    pub location: Location,
351    pub left: Rc<CssSelector>,
352    pub right: Rc<CssSelector>,
353    pub combinator_type: CssCombinatorType,
354}
355
356/// The root object of a CSS DOM. The CSS3 DOM objects serve not only IDE
357/// features in code model, but also CSS compilation.
358#[derive(Clone, Serialize, Deserialize)]
359pub struct CssDocument {
360    pub location: Location,
361    pub directives: Vec<Rc<CssDirective>>,
362}
363
364/// CSS DOM for an `@font-face` statement.
365#[derive(Clone, Serialize, Deserialize)]
366pub struct CssFontFace {
367    pub location: Location,
368    pub properties: Vec<Rc<CssProperty>>,
369}
370
371#[derive(Clone, Serialize, Deserialize)]
372pub struct CssProperty {
373    pub location: Location,
374    pub name: (String, Location),
375    pub value: Rc<CssPropertyValue>,
376    #[serde(skip)]
377    _phantom: PhantomData<()>,
378}
379
380impl CssProperty {
381    pub fn new(location: Location, name: (String, Location), value: Rc<CssPropertyValue>) -> Self {
382        Self {
383            location,
384            name: (Self::normalize(&name.0), name.1),
385            value,
386            _phantom: PhantomData::default(),
387        }
388    }
389
390    /// Normalize CSS property names to camel-case style names. Names already in
391    /// camel-case will be returned as-is.
392    fn normalize(name: &str) -> String {
393        let mut split = name.split('-').map(|s| s.to_owned()).collect::<Vec<_>>();
394        let mut v = split[0].chars();
395        let mut v1 = String::new();
396        if let Some(ch) = v.next() {
397            v1.push_str(&ch.to_lowercase().to_string());
398            for ch in v {
399                v1.push(ch);
400            }
401        }
402        split[0] = v1;
403        for i in 1..split.len() {
404            let mut v = split[i].chars();
405            let mut v1 = String::new();
406            if let Some(ch) = v.next() {
407                v1.push_str(&ch.to_uppercase().to_string());
408                for ch in v {
409                    v1.push(ch);
410                }
411            }
412            split[i] = v1;
413        }
414        split.join("")
415    }
416}
417
418#[derive(Clone, Serialize, Deserialize)]
419pub struct CssMediaQuery {
420    pub location: Location,
421    pub conditions: Vec<Rc<CssMediaQueryCondition>>,
422    pub rules: Vec<Rc<CssRule>>,
423}
424
425#[derive(Clone, Serialize, Deserialize)]
426pub enum CssMediaQueryCondition {
427    Invalidated(InvalidatedNode),
428    /// Identifier. Example: "screen".
429    Id((String, Location)),
430    /// The `only` keyword followed by an identifier.
431    /// Example: "only screen".
432    OnlyId {
433        location: Location,
434        id: (String, Location),
435    },
436    /// A parenthesized property, such as
437    /// `(application-dpi: 240)`.
438    ParenProperty((Rc<CssProperty>, Location)),
439    /// A `condition1 and condition2` expression.
440    And {
441        location: Location,
442        left: Rc<CssMediaQueryCondition>,
443        right: Rc<CssMediaQueryCondition>,
444    },
445}
446
447impl CssMediaQueryCondition {
448    pub fn location(&self) -> Location {
449        match self {
450            Self::Invalidated(v) => v.location.clone(),
451            Self::Id((_, l)) => l.clone(),
452            Self::OnlyId { location, .. } => location.clone(),
453            Self::ParenProperty((_, l)) => l.clone(),
454            Self::And { location, .. } => location.clone(),
455        }
456    }
457}
458
459#[derive(Clone, Serialize, Deserialize)]
460pub struct CssRule {
461    pub location: Location,
462    pub selectors: Vec<Rc<CssSelector>>,
463    pub properties: Vec<Rc<CssProperty>>,
464}
465
466#[derive(Clone, Serialize, Deserialize)]
467pub struct CssNamespaceDefinition {
468    pub location: Location,
469    pub prefix: (String, Location),
470    pub uri: (String, Location),
471}