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