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