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