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 //! The [`@counter-style`][counter-style] at-rule.
6 //!
7 //! [counter-style]: https://drafts.csswg.org/css-counter-styles/
8 
9 use crate::error_reporting::ContextualParseError;
10 use crate::parser::{Parse, ParserContext};
11 use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
12 use crate::str::CssStringWriter;
13 use crate::values::specified::Integer;
14 use crate::values::CustomIdent;
15 use crate::Atom;
16 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser};
17 use cssparser::{CowRcStr, Parser, SourceLocation, Token};
18 use selectors::parser::SelectorParseErrorKind;
19 use std::fmt::{self, Write};
20 use std::mem;
21 use std::num::Wrapping;
22 use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError};
23 use style_traits::{StyleParseErrorKind, ToCss};
24 
25 /// Parse a counter style name reference.
26 ///
27 /// This allows the reserved counter style names "decimal" and "disc".
parse_counter_style_name<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result<CustomIdent, ParseError<'i>>28 pub fn parse_counter_style_name<'i, 't>(
29     input: &mut Parser<'i, 't>,
30 ) -> Result<CustomIdent, ParseError<'i>> {
31     macro_rules! predefined {
32         ($($name: expr,)+) => {
33             {
34                 ascii_case_insensitive_phf_map! {
35                     // FIXME: use static atoms https://github.com/rust-lang/rust/issues/33156
36                     predefined -> &'static str = {
37                         $(
38                             $name => $name,
39                         )+
40                     }
41                 }
42 
43                 let location = input.current_source_location();
44                 let ident = input.expect_ident()?;
45                 if let Some(&lower_cased) = predefined(&ident) {
46                     Ok(CustomIdent(Atom::from(lower_cased)))
47                 } else {
48                     // none is always an invalid <counter-style> value.
49                     CustomIdent::from_ident(location, ident, &["none"])
50                 }
51             }
52         }
53     }
54     include!("predefined.rs")
55 }
56 
is_valid_name_definition(ident: &CustomIdent) -> bool57 fn is_valid_name_definition(ident: &CustomIdent) -> bool {
58     ident.0 != atom!("decimal")
59         && ident.0 != atom!("disc")
60         && ident.0 != atom!("circle")
61         && ident.0 != atom!("square")
62         && ident.0 != atom!("disclosure-closed")
63         && ident.0 != atom!("disclosure-open")
64 }
65 
66 /// Parse the prelude of an @counter-style rule
parse_counter_style_name_definition<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result<CustomIdent, ParseError<'i>>67 pub fn parse_counter_style_name_definition<'i, 't>(
68     input: &mut Parser<'i, 't>,
69 ) -> Result<CustomIdent, ParseError<'i>> {
70     parse_counter_style_name(input).and_then(|ident| {
71         if !is_valid_name_definition(&ident) {
72             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
73         } else {
74             Ok(ident)
75         }
76     })
77 }
78 
79 /// Parse the body (inside `{}`) of an @counter-style rule
parse_counter_style_body<'i, 't>( name: CustomIdent, context: &ParserContext, input: &mut Parser<'i, 't>, location: SourceLocation, ) -> Result<CounterStyleRuleData, ParseError<'i>>80 pub fn parse_counter_style_body<'i, 't>(
81     name: CustomIdent,
82     context: &ParserContext,
83     input: &mut Parser<'i, 't>,
84     location: SourceLocation,
85 ) -> Result<CounterStyleRuleData, ParseError<'i>> {
86     let start = input.current_source_location();
87     let mut rule = CounterStyleRuleData::empty(name, location);
88     {
89         let parser = CounterStyleRuleParser {
90             context: context,
91             rule: &mut rule,
92         };
93         let mut iter = DeclarationListParser::new(input, parser);
94         while let Some(declaration) = iter.next() {
95             if let Err((error, slice)) = declaration {
96                 let location = error.location;
97                 let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(
98                     slice, error,
99                 );
100                 context.log_css_error(location, error)
101             }
102         }
103     }
104     let error = match *rule.resolved_system() {
105         ref system @ System::Cyclic |
106         ref system @ System::Fixed { .. } |
107         ref system @ System::Symbolic |
108         ref system @ System::Alphabetic |
109         ref system @ System::Numeric
110             if rule.symbols.is_none() =>
111         {
112             let system = system.to_css_string();
113             Some(ContextualParseError::InvalidCounterStyleWithoutSymbols(
114                 system,
115             ))
116         }
117         ref system @ System::Alphabetic | ref system @ System::Numeric
118             if rule.symbols().unwrap().0.len() < 2 =>
119         {
120             let system = system.to_css_string();
121             Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols(
122                 system,
123             ))
124         }
125         System::Additive if rule.additive_symbols.is_none() => {
126             Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols)
127         },
128         System::Extends(_) if rule.symbols.is_some() => {
129             Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols)
130         },
131         System::Extends(_) if rule.additive_symbols.is_some() => {
132             Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols)
133         },
134         _ => None,
135     };
136     if let Some(error) = error {
137         context.log_css_error(start, error);
138         Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
139     } else {
140         Ok(rule)
141     }
142 }
143 
144 struct CounterStyleRuleParser<'a, 'b: 'a> {
145     context: &'a ParserContext<'b>,
146     rule: &'a mut CounterStyleRuleData,
147 }
148 
149 /// Default methods reject all at rules.
150 impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
151     type PreludeNoBlock = ();
152     type PreludeBlock = ();
153     type AtRule = ();
154     type Error = StyleParseErrorKind<'i>;
155 }
156 
157 macro_rules! checker {
158     ($self:ident._($value:ident)) => {};
159     ($self:ident. $checker:ident($value:ident)) => {
160         if !$self.$checker(&$value) {
161             return false;
162         }
163     };
164 }
165 
166 macro_rules! counter_style_descriptors {
167     (
168         $( #[$doc: meta] $name: tt $ident: ident / $setter: ident [$checker: tt]: $ty: ty, )+
169     ) => {
170         /// An @counter-style rule
171         #[derive(Clone, Debug, ToShmem)]
172         pub struct CounterStyleRuleData {
173             name: CustomIdent,
174             generation: Wrapping<u32>,
175             $(
176                 #[$doc]
177                 $ident: Option<$ty>,
178             )+
179             /// Line and column of the @counter-style rule source code.
180             pub source_location: SourceLocation,
181         }
182 
183         impl CounterStyleRuleData {
184             fn empty(name: CustomIdent, source_location: SourceLocation) -> Self {
185                 CounterStyleRuleData {
186                     name: name,
187                     generation: Wrapping(0),
188                     $(
189                         $ident: None,
190                     )+
191                     source_location,
192                 }
193             }
194 
195             $(
196                 #[$doc]
197                 pub fn $ident(&self) -> Option<&$ty> {
198                     self.$ident.as_ref()
199                 }
200             )+
201 
202             $(
203                 #[$doc]
204                 pub fn $setter(&mut self, value: $ty) -> bool {
205                     checker!(self.$checker(value));
206                     self.$ident = Some(value);
207                     self.generation += Wrapping(1);
208                     true
209                 }
210             )+
211         }
212 
213         impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> {
214             type Declaration = ();
215             type Error = StyleParseErrorKind<'i>;
216 
217             fn parse_value<'t>(&mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>)
218                                -> Result<(), ParseError<'i>> {
219                 match_ignore_ascii_case! { &*name,
220                     $(
221                         $name => {
222                             // DeclarationParser also calls parse_entirely
223                             // so we’d normally not need to,
224                             // but in this case we do because we set the value as a side effect
225                             // rather than returning it.
226                             let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
227                             self.rule.$ident = Some(value)
228                         },
229                     )*
230                     _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
231                 }
232                 Ok(())
233             }
234         }
235 
236         impl ToCssWithGuard for CounterStyleRuleData {
237             fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
238                 dest.write_str("@counter-style ")?;
239                 self.name.to_css(&mut CssWriter::new(dest))?;
240                 dest.write_str(" { ")?;
241                 $(
242                     if let Some(ref value) = self.$ident {
243                         dest.write_str(concat!($name, ": "))?;
244                         ToCss::to_css(value, &mut CssWriter::new(dest))?;
245                         dest.write_str("; ")?;
246                     }
247                 )+
248                 dest.write_str("}")
249             }
250         }
251     }
252 }
253 
254 counter_style_descriptors! {
255     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-system>
256     "system" system / set_system [check_system]: System,
257 
258     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative>
259     "negative" negative / set_negative [_]: Negative,
260 
261     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-prefix>
262     "prefix" prefix / set_prefix [_]: Symbol,
263 
264     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-suffix>
265     "suffix" suffix / set_suffix [_]: Symbol,
266 
267     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
268     "range" range / set_range [_]: CounterRanges,
269 
270     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad>
271     "pad" pad / set_pad [_]: Pad,
272 
273     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback>
274     "fallback" fallback / set_fallback [_]: Fallback,
275 
276     /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols>
277     "symbols" symbols / set_symbols [check_symbols]: Symbols,
278 
279     /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols>
280     "additive-symbols" additive_symbols /
281         set_additive_symbols [check_additive_symbols]: AdditiveSymbols,
282 
283     /// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as>
284     "speak-as" speak_as / set_speak_as [_]: SpeakAs,
285 }
286 
287 // Implements the special checkers for some setters.
288 // See <https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface>
289 impl CounterStyleRuleData {
290     /// Check that the system is effectively not changed. Only params
291     /// of system descriptor is changeable.
check_system(&self, value: &System) -> bool292     fn check_system(&self, value: &System) -> bool {
293         mem::discriminant(self.resolved_system()) == mem::discriminant(value)
294     }
295 
check_symbols(&self, value: &Symbols) -> bool296     fn check_symbols(&self, value: &Symbols) -> bool {
297         match *self.resolved_system() {
298             // These two systems require at least two symbols.
299             System::Numeric | System::Alphabetic => value.0.len() >= 2,
300             // No symbols should be set for extends system.
301             System::Extends(_) => false,
302             _ => true,
303         }
304     }
305 
check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool306     fn check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool {
307         match *self.resolved_system() {
308             // No additive symbols should be set for extends system.
309             System::Extends(_) => false,
310             _ => true,
311         }
312     }
313 }
314 
315 impl CounterStyleRuleData {
316     /// Get the name of the counter style rule.
name(&self) -> &CustomIdent317     pub fn name(&self) -> &CustomIdent {
318         &self.name
319     }
320 
321     /// Set the name of the counter style rule. Caller must ensure that
322     /// the name is valid.
set_name(&mut self, name: CustomIdent)323     pub fn set_name(&mut self, name: CustomIdent) {
324         debug_assert!(is_valid_name_definition(&name));
325         self.name = name;
326     }
327 
328     /// Get the current generation of the counter style rule.
generation(&self) -> u32329     pub fn generation(&self) -> u32 {
330         self.generation.0
331     }
332 
333     /// Get the system of this counter style rule, default to
334     /// `symbolic` if not specified.
resolved_system(&self) -> &System335     pub fn resolved_system(&self) -> &System {
336         match self.system {
337             Some(ref system) => system,
338             None => &System::Symbolic,
339         }
340     }
341 }
342 
343 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-system>
344 #[derive(Clone, Debug, ToShmem)]
345 pub enum System {
346     /// 'cyclic'
347     Cyclic,
348     /// 'numeric'
349     Numeric,
350     /// 'alphabetic'
351     Alphabetic,
352     /// 'symbolic'
353     Symbolic,
354     /// 'additive'
355     Additive,
356     /// 'fixed <integer>?'
357     Fixed {
358         /// '<integer>?'
359         first_symbol_value: Option<Integer>,
360     },
361     /// 'extends <counter-style-name>'
362     Extends(CustomIdent),
363 }
364 
365 impl Parse for System {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>366     fn parse<'i, 't>(
367         context: &ParserContext,
368         input: &mut Parser<'i, 't>,
369     ) -> Result<Self, ParseError<'i>> {
370         try_match_ident_ignore_ascii_case! { input,
371             "cyclic" => Ok(System::Cyclic),
372             "numeric" => Ok(System::Numeric),
373             "alphabetic" => Ok(System::Alphabetic),
374             "symbolic" => Ok(System::Symbolic),
375             "additive" => Ok(System::Additive),
376             "fixed" => {
377                 let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok();
378                 Ok(System::Fixed { first_symbol_value })
379             },
380             "extends" => {
381                 let other = parse_counter_style_name(input)?;
382                 Ok(System::Extends(other))
383             },
384         }
385     }
386 }
387 
388 impl ToCss for System {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,389     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
390     where
391         W: Write,
392     {
393         match *self {
394             System::Cyclic => dest.write_str("cyclic"),
395             System::Numeric => dest.write_str("numeric"),
396             System::Alphabetic => dest.write_str("alphabetic"),
397             System::Symbolic => dest.write_str("symbolic"),
398             System::Additive => dest.write_str("additive"),
399             System::Fixed { first_symbol_value } => {
400                 if let Some(value) = first_symbol_value {
401                     dest.write_str("fixed ")?;
402                     value.to_css(dest)
403                 } else {
404                     dest.write_str("fixed")
405                 }
406             },
407             System::Extends(ref other) => {
408                 dest.write_str("extends ")?;
409                 other.to_css(dest)
410             },
411         }
412     }
413 }
414 
415 /// <https://drafts.csswg.org/css-counter-styles/#typedef-symbol>
416 #[derive(
417     Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
418 )]
419 #[repr(u8)]
420 pub enum Symbol {
421     /// <string>
422     String(crate::OwnedStr),
423     /// <custom-ident>
424     Ident(CustomIdent),
425     // Not implemented:
426     // /// <image>
427     // Image(Image),
428 }
429 
430 impl Parse for Symbol {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>431     fn parse<'i, 't>(
432         _context: &ParserContext,
433         input: &mut Parser<'i, 't>,
434     ) -> Result<Self, ParseError<'i>> {
435         let location = input.current_source_location();
436         match *input.next()? {
437             Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())),
438             Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)),
439             ref t => Err(location.new_unexpected_token_error(t.clone())),
440         }
441     }
442 }
443 
444 impl Symbol {
445     /// Returns whether this symbol is allowed in symbols() function.
is_allowed_in_symbols(&self) -> bool446     pub fn is_allowed_in_symbols(&self) -> bool {
447         match self {
448             // Identifier is not allowed.
449             &Symbol::Ident(_) => false,
450             _ => true,
451         }
452     }
453 }
454 
455 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative>
456 #[derive(Clone, Debug, ToCss, ToShmem)]
457 pub struct Negative(pub Symbol, pub Option<Symbol>);
458 
459 impl Parse for Negative {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>460     fn parse<'i, 't>(
461         context: &ParserContext,
462         input: &mut Parser<'i, 't>,
463     ) -> Result<Self, ParseError<'i>> {
464         Ok(Negative(
465             Symbol::parse(context, input)?,
466             input.try_parse(|input| Symbol::parse(context, input)).ok(),
467         ))
468     }
469 }
470 
471 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
472 #[derive(Clone, Debug, ToCss, ToShmem)]
473 pub struct CounterRange {
474     /// The start of the range.
475     pub start: CounterBound,
476     /// The end of the range.
477     pub end: CounterBound,
478 }
479 
480 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
481 ///
482 /// Empty represents 'auto'
483 #[derive(Clone, Debug, ToCss, ToShmem)]
484 #[css(comma)]
485 pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>);
486 
487 /// A bound found in `CounterRanges`.
488 #[derive(Clone, Copy, Debug, ToCss, ToShmem)]
489 pub enum CounterBound {
490     /// An integer bound.
491     Integer(Integer),
492     /// The infinite bound.
493     Infinite,
494 }
495 
496 impl Parse for CounterRanges {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>497     fn parse<'i, 't>(
498         context: &ParserContext,
499         input: &mut Parser<'i, 't>,
500     ) -> Result<Self, ParseError<'i>> {
501         if input
502             .try_parse(|input| input.expect_ident_matching("auto"))
503             .is_ok()
504         {
505             return Ok(CounterRanges(Default::default()));
506         }
507 
508         let ranges = input.parse_comma_separated(|input| {
509             let start = parse_bound(context, input)?;
510             let end = parse_bound(context, input)?;
511             if let (CounterBound::Integer(start), CounterBound::Integer(end)) = (start, end) {
512                 if start > end {
513                     return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
514                 }
515             }
516             Ok(CounterRange { start, end })
517         })?;
518 
519         Ok(CounterRanges(ranges.into()))
520     }
521 }
522 
parse_bound<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<CounterBound, ParseError<'i>>523 fn parse_bound<'i, 't>(
524     context: &ParserContext,
525     input: &mut Parser<'i, 't>,
526 ) -> Result<CounterBound, ParseError<'i>> {
527     if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) {
528         return Ok(CounterBound::Integer(integer));
529     }
530     input.expect_ident_matching("infinite")?;
531     Ok(CounterBound::Infinite)
532 }
533 
534 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad>
535 #[derive(Clone, Debug, ToCss, ToShmem)]
536 pub struct Pad(pub Integer, pub Symbol);
537 
538 impl Parse for Pad {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>539     fn parse<'i, 't>(
540         context: &ParserContext,
541         input: &mut Parser<'i, 't>,
542     ) -> Result<Self, ParseError<'i>> {
543         let pad_with = input.try_parse(|input| Symbol::parse(context, input));
544         let min_length = Integer::parse_non_negative(context, input)?;
545         let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?;
546         Ok(Pad(min_length, pad_with))
547     }
548 }
549 
550 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback>
551 #[derive(Clone, Debug, ToCss, ToShmem)]
552 pub struct Fallback(pub CustomIdent);
553 
554 impl Parse for Fallback {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>555     fn parse<'i, 't>(
556         _context: &ParserContext,
557         input: &mut Parser<'i, 't>,
558     ) -> Result<Self, ParseError<'i>> {
559         Ok(Fallback(parse_counter_style_name(input)?))
560     }
561 }
562 
563 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols>
564 #[derive(
565     Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
566 )]
567 #[repr(C)]
568 pub struct Symbols(#[css(iterable)] pub crate::OwnedSlice<Symbol>);
569 
570 impl Parse for Symbols {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>571     fn parse<'i, 't>(
572         context: &ParserContext,
573         input: &mut Parser<'i, 't>,
574     ) -> Result<Self, ParseError<'i>> {
575         let mut symbols = Vec::new();
576         while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) {
577             symbols.push(s);
578         }
579         if symbols.is_empty() {
580             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
581         }
582         Ok(Symbols(symbols.into()))
583     }
584 }
585 
586 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols>
587 #[derive(Clone, Debug, ToCss, ToShmem)]
588 #[css(comma)]
589 pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>);
590 
591 impl Parse for AdditiveSymbols {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>592     fn parse<'i, 't>(
593         context: &ParserContext,
594         input: &mut Parser<'i, 't>,
595     ) -> Result<Self, ParseError<'i>> {
596         let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
597         // FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220
598         if tuples
599             .windows(2)
600             .any(|window| window[0].weight <= window[1].weight)
601         {
602             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
603         }
604         Ok(AdditiveSymbols(tuples.into()))
605     }
606 }
607 
608 /// <integer> && <symbol>
609 #[derive(Clone, Debug, ToCss, ToShmem)]
610 pub struct AdditiveTuple {
611     /// <integer>
612     pub weight: Integer,
613     /// <symbol>
614     pub symbol: Symbol,
615 }
616 
617 impl OneOrMoreSeparated for AdditiveTuple {
618     type S = Comma;
619 }
620 
621 impl Parse for AdditiveTuple {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>622     fn parse<'i, 't>(
623         context: &ParserContext,
624         input: &mut Parser<'i, 't>,
625     ) -> Result<Self, ParseError<'i>> {
626         let symbol = input.try_parse(|input| Symbol::parse(context, input));
627         let weight = Integer::parse_non_negative(context, input)?;
628         let symbol = symbol.or_else(|_| Symbol::parse(context, input))?;
629         Ok(Self { weight, symbol })
630     }
631 }
632 
633 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as>
634 #[derive(Clone, Debug, ToCss, ToShmem)]
635 pub enum SpeakAs {
636     /// auto
637     Auto,
638     /// bullets
639     Bullets,
640     /// numbers
641     Numbers,
642     /// words
643     Words,
644     // /// spell-out, not supported, see bug 1024178
645     // SpellOut,
646     /// <counter-style-name>
647     Other(CustomIdent),
648 }
649 
650 impl Parse for SpeakAs {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>651     fn parse<'i, 't>(
652         _context: &ParserContext,
653         input: &mut Parser<'i, 't>,
654     ) -> Result<Self, ParseError<'i>> {
655         let mut is_spell_out = false;
656         let result = input.try_parse(|input| {
657             let ident = input.expect_ident().map_err(|_| ())?;
658             match_ignore_ascii_case! { &*ident,
659                 "auto" => Ok(SpeakAs::Auto),
660                 "bullets" => Ok(SpeakAs::Bullets),
661                 "numbers" => Ok(SpeakAs::Numbers),
662                 "words" => Ok(SpeakAs::Words),
663                 "spell-out" => {
664                     is_spell_out = true;
665                     Err(())
666                 },
667                 _ => Err(()),
668             }
669         });
670         if is_spell_out {
671             // spell-out is not supported, but don’t parse it as a <counter-style-name>.
672             // See bug 1024178.
673             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
674         }
675         result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?)))
676     }
677 }
678