1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 //! Specified types for counter properties.
6 
7 #[cfg(feature = "servo")]
8 use crate::computed_values::list_style_type::T as ListStyleType;
9 use crate::parser::{Parse, ParserContext};
10 use crate::values::generics::counters as generics;
11 use crate::values::generics::counters::CounterPair;
12 #[cfg(feature = "gecko")]
13 use crate::values::generics::CounterStyle;
14 use crate::values::specified::url::SpecifiedImageUrl;
15 #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))]
16 use crate::values::specified::Attr;
17 use crate::values::specified::Integer;
18 use crate::values::CustomIdent;
19 use cssparser::{Parser, Token};
20 use selectors::parser::SelectorParseErrorKind;
21 use style_traits::{ParseError, StyleParseErrorKind};
22 
23 /// A specified value for the `counter-increment` property.
24 pub type CounterIncrement = generics::GenericCounterIncrement<Integer>;
25 
26 impl Parse for CounterIncrement {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>27     fn parse<'i, 't>(
28         context: &ParserContext,
29         input: &mut Parser<'i, 't>,
30     ) -> Result<Self, ParseError<'i>> {
31         Ok(Self::new(parse_counters(context, input, 1)?))
32     }
33 }
34 
35 /// A specified value for the `counter-set` and `counter-reset` properties.
36 pub type CounterSetOrReset = generics::GenericCounterSetOrReset<Integer>;
37 
38 impl Parse for CounterSetOrReset {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>39     fn parse<'i, 't>(
40         context: &ParserContext,
41         input: &mut Parser<'i, 't>,
42     ) -> Result<Self, ParseError<'i>> {
43         Ok(Self::new(parse_counters(context, input, 0)?))
44     }
45 }
46 
parse_counters<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, default_value: i32, ) -> Result<Vec<CounterPair<Integer>>, ParseError<'i>>47 fn parse_counters<'i, 't>(
48     context: &ParserContext,
49     input: &mut Parser<'i, 't>,
50     default_value: i32,
51 ) -> Result<Vec<CounterPair<Integer>>, ParseError<'i>> {
52     if input
53         .try(|input| input.expect_ident_matching("none"))
54         .is_ok()
55     {
56         return Ok(vec![]);
57     }
58 
59     let mut counters = Vec::new();
60     loop {
61         let location = input.current_source_location();
62         let name = match input.next() {
63             Ok(&Token::Ident(ref ident)) => CustomIdent::from_ident(location, ident, &["none"])?,
64             Ok(t) => {
65                 let t = t.clone();
66                 return Err(location.new_unexpected_token_error(t));
67             },
68             Err(_) => break,
69         };
70 
71         let value = input
72             .try(|input| Integer::parse(context, input))
73             .unwrap_or(Integer::new(default_value));
74         counters.push(CounterPair { name, value });
75     }
76 
77     if !counters.is_empty() {
78         Ok(counters)
79     } else {
80         Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
81     }
82 }
83 
84 /// The specified value for the `content` property.
85 pub type Content = generics::GenericContent<SpecifiedImageUrl>;
86 
87 /// The specified value for a content item in the `content` property.
88 pub type ContentItem = generics::GenericContentItem<SpecifiedImageUrl>;
89 
90 impl Content {
91     #[cfg(feature = "servo")]
parse_counter_style(_: &ParserContext, input: &mut Parser) -> ListStyleType92     fn parse_counter_style(_: &ParserContext, input: &mut Parser) -> ListStyleType {
93         input
94             .try(|input| {
95                 input.expect_comma()?;
96                 ListStyleType::parse(input)
97             })
98             .unwrap_or(ListStyleType::Decimal)
99     }
100 
101     #[cfg(feature = "gecko")]
parse_counter_style(context: &ParserContext, input: &mut Parser) -> CounterStyle102     fn parse_counter_style(context: &ParserContext, input: &mut Parser) -> CounterStyle {
103         input
104             .try(|input| {
105                 input.expect_comma()?;
106                 CounterStyle::parse(context, input)
107             })
108             .unwrap_or(CounterStyle::decimal())
109     }
110 }
111 
112 impl Parse for Content {
113     // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote |
114     // no-close-quote ]+
115     // TODO: <uri>, attr(<identifier>)
116     #[cfg_attr(feature = "servo", allow(unused_mut))]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>117     fn parse<'i, 't>(
118         context: &ParserContext,
119         input: &mut Parser<'i, 't>,
120     ) -> Result<Self, ParseError<'i>> {
121         if input
122             .try(|input| input.expect_ident_matching("normal"))
123             .is_ok()
124         {
125             return Ok(generics::Content::Normal);
126         }
127         if input
128             .try(|input| input.expect_ident_matching("none"))
129             .is_ok()
130         {
131             return Ok(generics::Content::None);
132         }
133 
134         let mut content = vec![];
135         let mut has_alt_content = false;
136         loop {
137             #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))]
138             {
139                 if let Ok(url) = input.try(|i| SpecifiedImageUrl::parse(context, i)) {
140                     content.push(generics::ContentItem::Url(url));
141                     continue;
142                 }
143             }
144             match input.next() {
145                 Ok(&Token::QuotedString(ref value)) => {
146                     content.push(generics::ContentItem::String(
147                         value.as_ref().to_owned().into(),
148                     ));
149                 },
150                 Ok(&Token::Function(ref name)) => {
151                     let result = match_ignore_ascii_case! { &name,
152                         "counter" => input.parse_nested_block(|input| {
153                             let location = input.current_source_location();
154                             let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?;
155                             let style = Content::parse_counter_style(context, input);
156                             Ok(generics::ContentItem::Counter(name, style))
157                         }),
158                         "counters" => input.parse_nested_block(|input| {
159                             let location = input.current_source_location();
160                             let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?;
161                             input.expect_comma()?;
162                             let separator = input.expect_string()?.as_ref().to_owned().into();
163                             let style = Content::parse_counter_style(context, input);
164                             Ok(generics::ContentItem::Counters(name, separator, style))
165                         }),
166                         #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))]
167                         "attr" => input.parse_nested_block(|input| {
168                             Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?))
169                         }),
170                         _ => {
171                             let name = name.clone();
172                             return Err(input.new_custom_error(
173                                 StyleParseErrorKind::UnexpectedFunction(name),
174                             ))
175                         }
176                     }?;
177                     content.push(result);
178                 },
179                 Ok(&Token::Ident(ref ident)) => {
180                     content.push(match_ignore_ascii_case! { &ident,
181                         "open-quote" => generics::ContentItem::OpenQuote,
182                         "close-quote" => generics::ContentItem::CloseQuote,
183                         "no-open-quote" => generics::ContentItem::NoOpenQuote,
184                         "no-close-quote" => generics::ContentItem::NoCloseQuote,
185                         #[cfg(feature = "gecko")]
186                         "-moz-alt-content" => {
187                             has_alt_content = true;
188                             generics::ContentItem::MozAltContent
189                         },
190                         _ =>{
191                             let ident = ident.clone();
192                             return Err(input.new_custom_error(
193                                 SelectorParseErrorKind::UnexpectedIdent(ident)
194                             ));
195                         }
196                     });
197                 },
198                 Err(_) => break,
199                 Ok(t) => {
200                     let t = t.clone();
201                     return Err(input.new_unexpected_token_error(t));
202                 },
203             }
204         }
205         // We don't allow to parse `-moz-alt-content in multiple positions.
206         if content.is_empty() || (has_alt_content && content.len() != 1) {
207             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
208         }
209         Ok(generics::Content::Items(content.into()))
210     }
211 }
212