1use crate::error_reporting::ContextualParseError;
10use crate::parser::{Parse, ParserContext};
11use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
12use crate::str::CssStringWriter;
13use crate::values::specified::Integer;
14use crate::values::{AtomString, CustomIdent};
15use crate::Atom;
16use cssparser::{
17 AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser,
18};
19use cssparser::{CowRcStr, Parser, ParserState, SourceLocation, Token};
20use selectors::parser::SelectorParseErrorKind;
21use std::fmt::{self, Write};
22use std::mem;
23use std::num::Wrapping;
24use style_traits::{
25 Comma, CssWriter, KeywordsCollectFn, OneOrMoreSeparated, ParseError, SpecifiedValueInfo,
26 StyleParseErrorKind, ToCss,
27};
28
29#[allow(missing_docs)]
31#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
32#[derive(
33 Clone,
34 Copy,
35 Debug,
36 Eq,
37 MallocSizeOf,
38 Parse,
39 PartialEq,
40 ToComputedValue,
41 ToCss,
42 ToResolvedValue,
43 ToShmem,
44)]
45#[repr(u8)]
46pub enum SymbolsType {
47 Cyclic,
48 Numeric,
49 Alphabetic,
50 Symbolic,
51 Fixed,
52}
53
54#[derive(
59 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
60)]
61#[repr(u8)]
62pub enum CounterStyle {
63 None,
65 Name(CustomIdent),
67 #[css(function)]
69 Symbols {
70 #[css(skip_if = "is_symbolic")]
72 ty: SymbolsType,
73 symbols: Symbols,
75 },
76 String(AtomString),
78}
79
80#[inline]
81fn is_symbolic(symbols_type: &SymbolsType) -> bool {
82 *symbols_type == SymbolsType::Symbolic
83}
84
85impl CounterStyle {
86 pub fn disc() -> Self {
88 CounterStyle::Name(CustomIdent(atom!("disc")))
89 }
90
91 pub fn decimal() -> Self {
93 CounterStyle::Name(CustomIdent(atom!("decimal")))
94 }
95
96 #[inline]
98 pub fn is_bullet(&self) -> bool {
99 match self {
100 CounterStyle::Name(CustomIdent(ref name)) => {
101 name == &atom!("disc")
102 || name == &atom!("circle")
103 || name == &atom!("square")
104 || name == &atom!("disclosure-closed")
105 || name == &atom!("disclosure-open")
106 },
107 _ => false,
108 }
109 }
110}
111
112bitflags! {
113 #[derive(Clone, Copy)]
114 pub struct CounterStyleParsingFlags: u8 {
116 const ALLOW_NONE = 1 << 0;
118 const ALLOW_STRING = 1 << 1;
120 }
121}
122
123impl CounterStyle {
124 pub fn parse<'i, 't>(
126 context: &ParserContext,
127 input: &mut Parser<'i, 't>,
128 flags: CounterStyleParsingFlags,
129 ) -> Result<Self, ParseError<'i>> {
130 use self::CounterStyleParsingFlags as Flags;
131 let location = input.current_source_location();
132 match input.next()? {
133 Token::QuotedString(ref string) if flags.intersects(Flags::ALLOW_STRING) => {
134 Ok(Self::String(AtomString::from(string.as_ref())))
135 },
136 Token::Ident(ref ident) => {
137 if flags.intersects(Flags::ALLOW_NONE) && ident.eq_ignore_ascii_case("none") {
138 return Ok(Self::None);
139 }
140 Ok(Self::Name(counter_style_name_from_ident(ident, location)?))
141 },
142 Token::Function(ref name) if name.eq_ignore_ascii_case("symbols") => {
143 input.parse_nested_block(|input| {
144 let symbols_type = input
145 .try_parse(SymbolsType::parse)
146 .unwrap_or(SymbolsType::Symbolic);
147 let symbols = Symbols::parse(context, input)?;
148 if (symbols_type == SymbolsType::Alphabetic
151 || symbols_type == SymbolsType::Numeric)
152 && symbols.0.len() < 2
153 {
154 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
155 }
156 if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) {
158 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
159 }
160 Ok(Self::Symbols {
161 ty: symbols_type,
162 symbols,
163 })
164 })
165 },
166 t => Err(location.new_unexpected_token_error(t.clone())),
167 }
168 }
169}
170
171impl SpecifiedValueInfo for CounterStyle {
172 fn collect_completion_keywords(f: KeywordsCollectFn) {
173 macro_rules! predefined {
179 ($($name:expr,)+) => {
180 f(&["symbols", "none", $($name,)+])
181 }
182 }
183 include!("predefined.rs");
184 }
185}
186
187fn parse_counter_style_name<'i>(input: &mut Parser<'i, '_>) -> Result<CustomIdent, ParseError<'i>> {
188 let location = input.current_source_location();
189 let ident = input.expect_ident()?;
190 counter_style_name_from_ident(ident, location)
191}
192
193fn counter_style_name_from_ident<'i>(
195 ident: &CowRcStr<'i>,
196 location: SourceLocation,
197) -> Result<CustomIdent, ParseError<'i>> {
198 macro_rules! predefined {
199 ($($name: tt,)+) => {{
200 ascii_case_insensitive_phf_map! {
201 predefined -> Atom = {
202 $(
203 $name => atom!($name),
204 )+
205 }
206 }
207
208 if let Some(lower_case) = predefined::get(&ident) {
210 Ok(CustomIdent(lower_case.clone()))
211 } else {
212 CustomIdent::from_ident(location, ident, &["none"])
214 }
215 }}
216 }
217 include!("predefined.rs")
218}
219
220fn is_valid_name_definition(ident: &CustomIdent) -> bool {
221 ident.0 != atom!("decimal")
222 && ident.0 != atom!("disc")
223 && ident.0 != atom!("circle")
224 && ident.0 != atom!("square")
225 && ident.0 != atom!("disclosure-closed")
226 && ident.0 != atom!("disclosure-open")
227}
228
229pub fn parse_counter_style_name_definition<'i, 't>(
231 input: &mut Parser<'i, 't>,
232) -> Result<CustomIdent, ParseError<'i>> {
233 parse_counter_style_name(input).and_then(|ident| {
234 if !is_valid_name_definition(&ident) {
235 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
236 } else {
237 Ok(ident)
238 }
239 })
240}
241
242pub fn parse_counter_style_body<'i, 't>(
244 name: CustomIdent,
245 context: &ParserContext,
246 input: &mut Parser<'i, 't>,
247 location: SourceLocation,
248) -> Result<CounterStyleRuleData, ParseError<'i>> {
249 let start = input.current_source_location();
250 let mut rule = CounterStyleRuleData::empty(name, location);
251 {
252 let mut parser = CounterStyleRuleParser {
253 context,
254 rule: &mut rule,
255 };
256 let mut iter = RuleBodyParser::new(input, &mut parser);
257 while let Some(declaration) = iter.next() {
258 if let Err((error, slice)) = declaration {
259 let location = error.location;
260 let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(
261 slice, error,
262 );
263 context.log_css_error(location, error)
264 }
265 }
266 }
267 let error = match *rule.resolved_system() {
268 ref system @ System::Cyclic
269 | ref system @ System::Fixed { .. }
270 | ref system @ System::Symbolic
271 | ref system @ System::Alphabetic
272 | ref system @ System::Numeric
273 if rule.symbols.is_none() =>
274 {
275 let system = system.to_css_string();
276 Some(ContextualParseError::InvalidCounterStyleWithoutSymbols(
277 system,
278 ))
279 },
280 ref system @ System::Alphabetic | ref system @ System::Numeric
281 if rule.symbols().unwrap().0.len() < 2 =>
282 {
283 let system = system.to_css_string();
284 Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols(
285 system,
286 ))
287 },
288 System::Additive if rule.additive_symbols.is_none() => {
289 Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols)
290 },
291 System::Extends(_) if rule.symbols.is_some() => {
292 Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols)
293 },
294 System::Extends(_) if rule.additive_symbols.is_some() => {
295 Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols)
296 },
297 _ => None,
298 };
299 if let Some(error) = error {
300 context.log_css_error(start, error);
301 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
302 } else {
303 Ok(rule)
304 }
305}
306
307struct CounterStyleRuleParser<'a, 'b: 'a> {
308 context: &'a ParserContext<'b>,
309 rule: &'a mut CounterStyleRuleData,
310}
311
312impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
314 type Prelude = ();
315 type AtRule = ();
316 type Error = StyleParseErrorKind<'i>;
317}
318
319impl<'a, 'b, 'i> QualifiedRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
320 type Prelude = ();
321 type QualifiedRule = ();
322 type Error = StyleParseErrorKind<'i>;
323}
324
325impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
326 for CounterStyleRuleParser<'a, 'b>
327{
328 fn parse_qualified(&self) -> bool {
329 false
330 }
331 fn parse_declarations(&self) -> bool {
332 true
333 }
334}
335
336macro_rules! checker {
337 ($self:ident._($value:ident)) => {};
338 ($self:ident. $checker:ident($value:ident)) => {
339 if !$self.$checker(&$value) {
340 return false;
341 }
342 };
343}
344
345macro_rules! counter_style_descriptors {
346 (
347 $( #[$doc: meta] $name: tt $ident: ident / $setter: ident [$checker: tt]: $ty: ty, )+
348 ) => {
349 #[derive(Clone, Debug, ToShmem)]
351 pub struct CounterStyleRuleData {
352 name: CustomIdent,
353 generation: Wrapping<u32>,
354 $(
355 #[$doc]
356 $ident: Option<$ty>,
357 )+
358 pub source_location: SourceLocation,
360 }
361
362 impl CounterStyleRuleData {
363 fn empty(name: CustomIdent, source_location: SourceLocation) -> Self {
364 CounterStyleRuleData {
365 name: name,
366 generation: Wrapping(0),
367 $(
368 $ident: None,
369 )+
370 source_location,
371 }
372 }
373
374 $(
375 #[$doc]
376 pub fn $ident(&self) -> Option<&$ty> {
377 self.$ident.as_ref()
378 }
379 )+
380
381 $(
382 #[$doc]
383 pub fn $setter(&mut self, value: $ty) -> bool {
384 checker!(self.$checker(value));
385 self.$ident = Some(value);
386 self.generation += Wrapping(1);
387 true
388 }
389 )+
390 }
391
392 impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> {
393 type Declaration = ();
394 type Error = StyleParseErrorKind<'i>;
395
396 fn parse_value<'t>(
397 &mut self,
398 name: CowRcStr<'i>,
399 input: &mut Parser<'i, 't>,
400 _declaration_start: &ParserState,
401 ) -> Result<(), ParseError<'i>> {
402 match_ignore_ascii_case! { &*name,
403 $(
404 $name => {
405 let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
409 self.rule.$ident = Some(value)
410 },
411 )*
412 _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
413 }
414 Ok(())
415 }
416 }
417
418 impl ToCssWithGuard for CounterStyleRuleData {
419 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
420 dest.write_str("@counter-style ")?;
421 self.name.to_css(&mut CssWriter::new(dest))?;
422 dest.write_str(" { ")?;
423 $(
424 if let Some(ref value) = self.$ident {
425 dest.write_str(concat!($name, ": "))?;
426 ToCss::to_css(value, &mut CssWriter::new(dest))?;
427 dest.write_str("; ")?;
428 }
429 )+
430 dest.write_char('}')
431 }
432 }
433 }
434}
435
436counter_style_descriptors! {
437 "system" system / set_system [check_system]: System,
439
440 "negative" negative / set_negative [_]: Negative,
442
443 "prefix" prefix / set_prefix [_]: Symbol,
445
446 "suffix" suffix / set_suffix [_]: Symbol,
448
449 "range" range / set_range [_]: CounterRanges,
451
452 "pad" pad / set_pad [_]: Pad,
454
455 "fallback" fallback / set_fallback [_]: Fallback,
457
458 "symbols" symbols / set_symbols [check_symbols]: Symbols,
460
461 "additive-symbols" additive_symbols /
463 set_additive_symbols [check_additive_symbols]: AdditiveSymbols,
464
465 "speak-as" speak_as / set_speak_as [_]: SpeakAs,
467}
468
469impl CounterStyleRuleData {
472 fn check_system(&self, value: &System) -> bool {
475 mem::discriminant(self.resolved_system()) == mem::discriminant(value)
476 }
477
478 fn check_symbols(&self, value: &Symbols) -> bool {
479 match *self.resolved_system() {
480 System::Numeric | System::Alphabetic => value.0.len() >= 2,
482 System::Extends(_) => false,
484 _ => true,
485 }
486 }
487
488 fn check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool {
489 match *self.resolved_system() {
490 System::Extends(_) => false,
492 _ => true,
493 }
494 }
495}
496
497impl CounterStyleRuleData {
498 pub fn name(&self) -> &CustomIdent {
500 &self.name
501 }
502
503 pub fn set_name(&mut self, name: CustomIdent) {
506 debug_assert!(is_valid_name_definition(&name));
507 self.name = name;
508 }
509
510 pub fn generation(&self) -> u32 {
512 self.generation.0
513 }
514
515 pub fn resolved_system(&self) -> &System {
518 match self.system {
519 Some(ref system) => system,
520 None => &System::Symbolic,
521 }
522 }
523}
524
525#[derive(Clone, Debug, ToShmem)]
527pub enum System {
528 Cyclic,
530 Numeric,
532 Alphabetic,
534 Symbolic,
536 Additive,
538 Fixed {
540 first_symbol_value: Option<Integer>,
542 },
543 Extends(CustomIdent),
545}
546
547impl Parse for System {
548 fn parse<'i, 't>(
549 context: &ParserContext,
550 input: &mut Parser<'i, 't>,
551 ) -> Result<Self, ParseError<'i>> {
552 try_match_ident_ignore_ascii_case! { input,
553 "cyclic" => Ok(System::Cyclic),
554 "numeric" => Ok(System::Numeric),
555 "alphabetic" => Ok(System::Alphabetic),
556 "symbolic" => Ok(System::Symbolic),
557 "additive" => Ok(System::Additive),
558 "fixed" => {
559 let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok();
560 Ok(System::Fixed { first_symbol_value })
561 },
562 "extends" => {
563 let other = parse_counter_style_name(input)?;
564 Ok(System::Extends(other))
565 },
566 }
567 }
568}
569
570impl ToCss for System {
571 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
572 where
573 W: Write,
574 {
575 match *self {
576 System::Cyclic => dest.write_str("cyclic"),
577 System::Numeric => dest.write_str("numeric"),
578 System::Alphabetic => dest.write_str("alphabetic"),
579 System::Symbolic => dest.write_str("symbolic"),
580 System::Additive => dest.write_str("additive"),
581 System::Fixed { first_symbol_value } => {
582 if let Some(value) = first_symbol_value {
583 dest.write_str("fixed ")?;
584 value.to_css(dest)
585 } else {
586 dest.write_str("fixed")
587 }
588 },
589 System::Extends(ref other) => {
590 dest.write_str("extends ")?;
591 other.to_css(dest)
592 },
593 }
594 }
595}
596
597#[derive(
599 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
600)]
601#[repr(u8)]
602pub enum Symbol {
603 String(crate::OwnedStr),
605 Ident(CustomIdent),
607 }
611
612impl Parse for Symbol {
613 fn parse<'i, 't>(
614 _context: &ParserContext,
615 input: &mut Parser<'i, 't>,
616 ) -> Result<Self, ParseError<'i>> {
617 let location = input.current_source_location();
618 match *input.next()? {
619 Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())),
620 Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)),
621 ref t => Err(location.new_unexpected_token_error(t.clone())),
622 }
623 }
624}
625
626impl Symbol {
627 pub fn is_allowed_in_symbols(&self) -> bool {
629 match self {
630 &Symbol::Ident(_) => false,
632 _ => true,
633 }
634 }
635}
636
637#[derive(Clone, Debug, ToCss, ToShmem)]
639pub struct Negative(pub Symbol, pub Option<Symbol>);
640
641impl Parse for Negative {
642 fn parse<'i, 't>(
643 context: &ParserContext,
644 input: &mut Parser<'i, 't>,
645 ) -> Result<Self, ParseError<'i>> {
646 Ok(Negative(
647 Symbol::parse(context, input)?,
648 input.try_parse(|input| Symbol::parse(context, input)).ok(),
649 ))
650 }
651}
652
653#[derive(Clone, Debug, ToCss, ToShmem)]
655pub struct CounterRange {
656 pub start: CounterBound,
658 pub end: CounterBound,
660}
661
662#[derive(Clone, Debug, ToCss, ToShmem)]
666#[css(comma)]
667pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>);
668
669#[derive(Clone, Copy, Debug, ToCss, ToShmem)]
671pub enum CounterBound {
672 Integer(Integer),
674 Infinite,
676}
677
678impl Parse for CounterRanges {
679 fn parse<'i, 't>(
680 context: &ParserContext,
681 input: &mut Parser<'i, 't>,
682 ) -> Result<Self, ParseError<'i>> {
683 if input
684 .try_parse(|input| input.expect_ident_matching("auto"))
685 .is_ok()
686 {
687 return Ok(CounterRanges(Default::default()));
688 }
689
690 let ranges = input.parse_comma_separated(|input| {
691 let start = parse_bound(context, input)?;
692 let end = parse_bound(context, input)?;
693 if let (CounterBound::Integer(start), CounterBound::Integer(end)) = (start, end) {
694 if start > end {
695 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
696 }
697 }
698 Ok(CounterRange { start, end })
699 })?;
700
701 Ok(CounterRanges(ranges.into()))
702 }
703}
704
705fn parse_bound<'i, 't>(
706 context: &ParserContext,
707 input: &mut Parser<'i, 't>,
708) -> Result<CounterBound, ParseError<'i>> {
709 if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) {
710 return Ok(CounterBound::Integer(integer));
711 }
712 input.expect_ident_matching("infinite")?;
713 Ok(CounterBound::Infinite)
714}
715
716#[derive(Clone, Debug, ToCss, ToShmem)]
718pub struct Pad(pub Integer, pub Symbol);
719
720impl Parse for Pad {
721 fn parse<'i, 't>(
722 context: &ParserContext,
723 input: &mut Parser<'i, 't>,
724 ) -> Result<Self, ParseError<'i>> {
725 let pad_with = input.try_parse(|input| Symbol::parse(context, input));
726 let min_length = Integer::parse_non_negative(context, input)?;
727 let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?;
728 Ok(Pad(min_length, pad_with))
729 }
730}
731
732#[derive(Clone, Debug, ToCss, ToShmem)]
734pub struct Fallback(pub CustomIdent);
735
736impl Parse for Fallback {
737 fn parse<'i, 't>(
738 _context: &ParserContext,
739 input: &mut Parser<'i, 't>,
740 ) -> Result<Self, ParseError<'i>> {
741 Ok(Fallback(parse_counter_style_name(input)?))
742 }
743}
744
745#[derive(
747 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
748)]
749#[repr(C)]
750pub struct Symbols(
751 #[css(iterable)]
752 #[ignore_malloc_size_of = "Arc"]
753 pub crate::ArcSlice<Symbol>,
754);
755
756impl Parse for Symbols {
757 fn parse<'i, 't>(
758 context: &ParserContext,
759 input: &mut Parser<'i, 't>,
760 ) -> Result<Self, ParseError<'i>> {
761 let mut symbols = smallvec::SmallVec::<[_; 5]>::new();
762 while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) {
763 symbols.push(s);
764 }
765 if symbols.is_empty() {
766 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
767 }
768 Ok(Symbols(crate::ArcSlice::from_iter(symbols.drain(..))))
769 }
770}
771
772#[derive(Clone, Debug, ToCss, ToShmem)]
774#[css(comma)]
775pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>);
776
777impl Parse for AdditiveSymbols {
778 fn parse<'i, 't>(
779 context: &ParserContext,
780 input: &mut Parser<'i, 't>,
781 ) -> Result<Self, ParseError<'i>> {
782 let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
783 if tuples
785 .windows(2)
786 .any(|window| window[0].weight <= window[1].weight)
787 {
788 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
789 }
790 Ok(AdditiveSymbols(tuples.into()))
791 }
792}
793
794#[derive(Clone, Debug, ToCss, ToShmem)]
796pub struct AdditiveTuple {
797 pub weight: Integer,
799 pub symbol: Symbol,
801}
802
803impl OneOrMoreSeparated for AdditiveTuple {
804 type S = Comma;
805}
806
807impl Parse for AdditiveTuple {
808 fn parse<'i, 't>(
809 context: &ParserContext,
810 input: &mut Parser<'i, 't>,
811 ) -> Result<Self, ParseError<'i>> {
812 let symbol = input.try_parse(|input| Symbol::parse(context, input));
813 let weight = Integer::parse_non_negative(context, input)?;
814 let symbol = symbol.or_else(|_| Symbol::parse(context, input))?;
815 Ok(Self { weight, symbol })
816 }
817}
818
819#[derive(Clone, Debug, ToCss, ToShmem)]
821pub enum SpeakAs {
822 Auto,
824 Bullets,
826 Numbers,
828 Words,
830 Other(CustomIdent),
834}
835
836impl Parse for SpeakAs {
837 fn parse<'i, 't>(
838 _context: &ParserContext,
839 input: &mut Parser<'i, 't>,
840 ) -> Result<Self, ParseError<'i>> {
841 let mut is_spell_out = false;
842 let result = input.try_parse(|input| {
843 let ident = input.expect_ident().map_err(|_| ())?;
844 match_ignore_ascii_case! { &*ident,
845 "auto" => Ok(SpeakAs::Auto),
846 "bullets" => Ok(SpeakAs::Bullets),
847 "numbers" => Ok(SpeakAs::Numbers),
848 "words" => Ok(SpeakAs::Words),
849 "spell-out" => {
850 is_spell_out = true;
851 Err(())
852 },
853 _ => Err(()),
854 }
855 });
856 if is_spell_out {
857 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
860 }
861 result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?)))
862 }
863}