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