lewp_css/domain/expressions/
attr_expression.rs

1// This file is part of css. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
2// Copyright © 2017 The developers of css. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT.
3
4use {
5    super::{Expression, TypeOrUnit},
6    crate::{
7        domain::units::{
8            conversions::{
9                AttributeConversion,
10                CssVariableConversion,
11                FontRelativeLengthConversion,
12                PercentageConversion,
13                ViewportPercentageLengthConversion,
14            },
15            Unit,
16        },
17        parsers::ParserContext,
18        CustomParseError,
19    },
20    cssparser::{serialize_identifier, ParseError, Parser, ToCss},
21    std::fmt,
22};
23
24#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
25pub struct AttrExpression {
26    pub attribute_lower_case_name: String,
27
28    pub type_or_unit: TypeOrUnit,
29
30    pub default_value_css: Option<String>,
31
32    pub is_not_in_page_rule: bool,
33}
34
35impl ToCss for AttrExpression {
36    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
37        serialize_identifier(&self.attribute_lower_case_name, dest)?;
38
39        if self.type_or_unit != TypeOrUnit::string {
40            dest.write_char(' ')?;
41            self.type_or_unit.to_css(dest)?;
42        }
43
44        if let Some(ref default_value_css) = self.default_value_css {
45            dest.write_char(',')?;
46            dest.write_str(default_value_css)?;
47        }
48
49        Ok(())
50    }
51}
52
53impl<U: Unit> Expression<U> for AttrExpression {
54    /// Division by zero is handled by returning the maximum possible f32 value
55    /// Subtractions for UnsignedCssNumber that are negative are handled by returning 0.0
56    #[inline(always)]
57    fn evaluate<
58        Conversion: FontRelativeLengthConversion<U::Number>
59            + ViewportPercentageLengthConversion<U::Number>
60            + PercentageConversion<U::Number>
61            + AttributeConversion<U>
62            + CssVariableConversion,
63    >(
64        &self,
65        conversion: &Conversion,
66    ) -> Option<U::Number> {
67        self.to_unit(conversion).map(|unit| unit.to_CssNumber())
68    }
69}
70
71impl AttrExpression {
72    pub fn to_unit<U: Unit, Conversion: AttributeConversion<U>>(
73        &self,
74        conversion: &Conversion,
75    ) -> Option<U> {
76        let (possibleValue, propertyDefaultOrIfNoPropertyDefaultTheUnitDefault) =
77            conversion.attributeValue(&self.attribute_lower_case_name);
78        if let Some(value) = possibleValue {
79            if let Ok(ref value_css) = self.type_or_unit.value_to_css(value)
80            {
81                U::from_raw_css_for_var_expression_evaluation(
82                    value_css,
83                    self.is_not_in_page_rule,
84                )
85            } else if let Some(ref value_css) = self.default_value_css {
86                U::from_raw_css_for_var_expression_evaluation(
87                    value_css,
88                    self.is_not_in_page_rule,
89                )
90            } else {
91                Some(propertyDefaultOrIfNoPropertyDefaultTheUnitDefault)
92            }
93        } else if let Some(ref value_css) = self.default_value_css {
94            U::from_raw_css_for_var_expression_evaluation(
95                value_css,
96                self.is_not_in_page_rule,
97            )
98        } else {
99            Some(propertyDefaultOrIfNoPropertyDefaultTheUnitDefault)
100        }
101    }
102
103    #[inline(always)]
104    pub(crate) fn parse<'i, 't>(
105        context: &ParserContext,
106        input: &mut Parser<'i, 't>,
107    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
108        input.parse_nested_block(|input| {
109            let attribute_lower_case_name = {
110                let attribute = input.expect_ident()?;
111                attribute.to_ascii_lowercase()
112            };
113
114            input.skip_whitespace();
115
116            if input.is_exhausted() {
117                Ok(Self {
118                    attribute_lower_case_name,
119                    type_or_unit: TypeOrUnit::default(),
120                    default_value_css: None,
121                    is_not_in_page_rule: context.isNotInPageRule(),
122                })
123            } else {
124                let type_or_unit = if let Ok(type_or_unit) = input.r#try(TypeOrUnit::parse)
125                {
126                    type_or_unit
127                } else {
128                    TypeOrUnit::default()
129                };
130
131                let result = input.r#try(|input| {
132                    input.skip_whitespace();
133                    input.expect_comma()?;
134                    input.skip_whitespace();
135
136                    let startPosition = input.position();
137                    let result: Result<_, ParseError<CustomParseError>> = input
138                        .parse_entirely(|input| {
139                            Ok(input.slice_from(startPosition).to_owned())
140                        });
141                    result
142                });
143
144                let default_value_css = if let Ok(default_value_css) = result {
145                    if default_value_css.is_empty() {
146                        None
147                    } else {
148                        Some(default_value_css)
149                    }
150                } else {
151                    None
152                };
153
154                Ok(Self {
155                    attribute_lower_case_name,
156                    type_or_unit,
157                    default_value_css,
158                    is_not_in_page_rule: context.isNotInPageRule(),
159                })
160            }
161        })
162    }
163}