1
0
Fork 0
firefox/servo/components/style/counter_style/mod.rs
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

863 lines
28 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! The [`@counter-style`][counter-style] at-rule.
//!
//! [counter-style]: https://drafts.csswg.org/css-counter-styles/
use crate::error_reporting::ContextualParseError;
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::values::specified::Integer;
use crate::values::{AtomString, CustomIdent};
use crate::Atom;
use cssparser::{
AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser,
};
use cssparser::{CowRcStr, Parser, ParserState, SourceLocation, Token};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write};
use std::mem;
use std::num::Wrapping;
use style_traits::{
Comma, CssWriter, KeywordsCollectFn, OneOrMoreSeparated, ParseError, SpecifiedValueInfo,
StyleParseErrorKind, ToCss,
};
/// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum SymbolsType {
Cyclic,
Numeric,
Alphabetic,
Symbolic,
Fixed,
}
/// <https://drafts.csswg.org/css-counter-styles/#typedef-counter-style>
///
/// Note that 'none' is not a valid name, but we include this (along with String) for space
/// efficiency when storing list-style-type.
#[derive(
Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
)]
#[repr(u8)]
pub enum CounterStyle {
/// The 'none' value.
None,
/// `<counter-style-name>`
Name(CustomIdent),
/// `symbols()`
#[css(function)]
Symbols {
/// The <symbols-type>, or symbolic if not specified.
#[css(skip_if = "is_symbolic")]
ty: SymbolsType,
/// The actual symbols.
symbols: Symbols,
},
/// A single string value, useful for `<list-style-type>`.
String(AtomString),
}
#[inline]
fn is_symbolic(symbols_type: &SymbolsType) -> bool {
*symbols_type == SymbolsType::Symbolic
}
impl CounterStyle {
/// disc value
pub fn disc() -> Self {
CounterStyle::Name(CustomIdent(atom!("disc")))
}
/// decimal value
pub fn decimal() -> Self {
CounterStyle::Name(CustomIdent(atom!("decimal")))
}
/// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`)
#[inline]
pub fn is_bullet(&self) -> bool {
match self {
CounterStyle::Name(CustomIdent(ref name)) => {
name == &atom!("disc") ||
name == &atom!("circle") ||
name == &atom!("square") ||
name == &atom!("disclosure-closed") ||
name == &atom!("disclosure-open")
},
_ => false,
}
}
}
bitflags! {
#[derive(Clone, Copy)]
/// Flags to control parsing of counter styles.
pub struct CounterStyleParsingFlags: u8 {
/// Whether `none` is allowed.
const ALLOW_NONE = 1 << 0;
/// Whether a bare string is allowed.
const ALLOW_STRING = 1 << 1;
}
}
impl CounterStyle {
/// Parse a counter style, and optionally none|string (for list-style-type).
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
flags: CounterStyleParsingFlags,
) -> Result<Self, ParseError<'i>> {
use self::CounterStyleParsingFlags as Flags;
let location = input.current_source_location();
match input.next()? {
Token::QuotedString(ref string) if flags.intersects(Flags::ALLOW_STRING) => {
Ok(Self::String(AtomString::from(string.as_ref())))
},
Token::Ident(ref ident) => {
if flags.intersects(Flags::ALLOW_NONE) && ident.eq_ignore_ascii_case("none") {
return Ok(Self::None);
}
Ok(Self::Name(counter_style_name_from_ident(ident, location)?))
},
Token::Function(ref name) if name.eq_ignore_ascii_case("symbols") => {
input.parse_nested_block(|input| {
let symbols_type = input
.try_parse(SymbolsType::parse)
.unwrap_or(SymbolsType::Symbolic);
let symbols = Symbols::parse(context, input)?;
// There must be at least two symbols for alphabetic or
// numeric system.
if (symbols_type == SymbolsType::Alphabetic ||
symbols_type == SymbolsType::Numeric) &&
symbols.0.len() < 2
{
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
// Identifier is not allowed in symbols() function.
if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(Self::Symbols {
ty: symbols_type,
symbols,
})
})
},
t => Err(location.new_unexpected_token_error(t.clone())),
}
}
}
impl SpecifiedValueInfo for CounterStyle {
fn collect_completion_keywords(f: KeywordsCollectFn) {
// XXX The best approach for implementing this is probably
// having a CounterStyleName type wrapping CustomIdent, and
// put the predefined list for that type in counter_style mod.
// But that's a non-trivial change itself, so we use a simpler
// approach here.
macro_rules! predefined {
($($name:expr,)+) => {
f(&["symbols", "none", $($name,)+])
}
}
include!("predefined.rs");
}
}
fn parse_counter_style_name<'i>(input: &mut Parser<'i, '_>) -> Result<CustomIdent, ParseError<'i>> {
let location = input.current_source_location();
let ident = input.expect_ident()?;
counter_style_name_from_ident(ident, location)
}
/// This allows the reserved counter style names "decimal" and "disc".
fn counter_style_name_from_ident<'i>(
ident: &CowRcStr<'i>,
location: SourceLocation,
) -> Result<CustomIdent, ParseError<'i>> {
macro_rules! predefined {
($($name: tt,)+) => {{
ascii_case_insensitive_phf_map! {
predefined -> Atom = {
$(
$name => atom!($name),
)+
}
}
// This effectively performs case normalization only on predefined names.
if let Some(lower_case) = predefined::get(&ident) {
Ok(CustomIdent(lower_case.clone()))
} else {
// none is always an invalid <counter-style> value.
CustomIdent::from_ident(location, ident, &["none"])
}
}}
}
include!("predefined.rs")
}
fn is_valid_name_definition(ident: &CustomIdent) -> bool {
ident.0 != atom!("decimal") &&
ident.0 != atom!("disc") &&
ident.0 != atom!("circle") &&
ident.0 != atom!("square") &&
ident.0 != atom!("disclosure-closed") &&
ident.0 != atom!("disclosure-open")
}
/// Parse the prelude of an @counter-style rule
pub fn parse_counter_style_name_definition<'i, 't>(
input: &mut Parser<'i, 't>,
) -> Result<CustomIdent, ParseError<'i>> {
parse_counter_style_name(input).and_then(|ident| {
if !is_valid_name_definition(&ident) {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(ident)
}
})
}
/// Parse the body (inside `{}`) of an @counter-style rule
pub fn parse_counter_style_body<'i, 't>(
name: CustomIdent,
context: &ParserContext,
input: &mut Parser<'i, 't>,
location: SourceLocation,
) -> Result<CounterStyleRuleData, ParseError<'i>> {
let start = input.current_source_location();
let mut rule = CounterStyleRuleData::empty(name, location);
{
let mut parser = CounterStyleRuleParser {
context,
rule: &mut rule,
};
let mut iter = RuleBodyParser::new(input, &mut parser);
while let Some(declaration) = iter.next() {
if let Err((error, slice)) = declaration {
let location = error.location;
let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(
slice, error,
);
context.log_css_error(location, error)
}
}
}
let error = match *rule.resolved_system() {
ref system @ System::Cyclic |
ref system @ System::Fixed { .. } |
ref system @ System::Symbolic |
ref system @ System::Alphabetic |
ref system @ System::Numeric
if rule.symbols.is_none() =>
{
let system = system.to_css_string();
Some(ContextualParseError::InvalidCounterStyleWithoutSymbols(
system,
))
},
ref system @ System::Alphabetic | ref system @ System::Numeric
if rule.symbols().unwrap().0.len() < 2 =>
{
let system = system.to_css_string();
Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols(
system,
))
},
System::Additive if rule.additive_symbols.is_none() => {
Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols)
},
System::Extends(_) if rule.symbols.is_some() => {
Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols)
},
System::Extends(_) if rule.additive_symbols.is_some() => {
Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols)
},
_ => None,
};
if let Some(error) = error {
context.log_css_error(start, error);
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(rule)
}
}
struct CounterStyleRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
rule: &'a mut CounterStyleRuleData,
}
/// Default methods reject all at rules.
impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
type Prelude = ();
type AtRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> QualifiedRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
type Prelude = ();
type QualifiedRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
for CounterStyleRuleParser<'a, 'b>
{
fn parse_qualified(&self) -> bool {
false
}
fn parse_declarations(&self) -> bool {
true
}
}
macro_rules! checker {
($self:ident._($value:ident)) => {};
($self:ident. $checker:ident($value:ident)) => {
if !$self.$checker(&$value) {
return false;
}
};
}
macro_rules! counter_style_descriptors {
(
$( #[$doc: meta] $name: tt $ident: ident / $setter: ident [$checker: tt]: $ty: ty, )+
) => {
/// An @counter-style rule
#[derive(Clone, Debug, ToShmem)]
pub struct CounterStyleRuleData {
name: CustomIdent,
generation: Wrapping<u32>,
$(
#[$doc]
$ident: Option<$ty>,
)+
/// Line and column of the @counter-style rule source code.
pub source_location: SourceLocation,
}
impl CounterStyleRuleData {
fn empty(name: CustomIdent, source_location: SourceLocation) -> Self {
CounterStyleRuleData {
name: name,
generation: Wrapping(0),
$(
$ident: None,
)+
source_location,
}
}
$(
#[$doc]
pub fn $ident(&self) -> Option<&$ty> {
self.$ident.as_ref()
}
)+
$(
#[$doc]
pub fn $setter(&mut self, value: $ty) -> bool {
checker!(self.$checker(value));
self.$ident = Some(value);
self.generation += Wrapping(1);
true
}
)+
}
impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> {
type Declaration = ();
type Error = StyleParseErrorKind<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
_declaration_start: &ParserState,
) -> Result<(), ParseError<'i>> {
match_ignore_ascii_case! { &*name,
$(
$name => {
// DeclarationParser also calls parse_entirely so wed normally not
// need to, but in this case we do because we set the value as a side
// effect rather than returning it.
let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
self.rule.$ident = Some(value)
},
)*
_ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
}
Ok(())
}
}
impl ToCssWithGuard for CounterStyleRuleData {
fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
dest.write_str("@counter-style ")?;
self.name.to_css(&mut CssWriter::new(dest))?;
dest.write_str(" { ")?;
$(
if let Some(ref value) = self.$ident {
dest.write_str(concat!($name, ": "))?;
ToCss::to_css(value, &mut CssWriter::new(dest))?;
dest.write_str("; ")?;
}
)+
dest.write_char('}')
}
}
}
}
counter_style_descriptors! {
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-system>
"system" system / set_system [check_system]: System,
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative>
"negative" negative / set_negative [_]: Negative,
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-prefix>
"prefix" prefix / set_prefix [_]: Symbol,
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-suffix>
"suffix" suffix / set_suffix [_]: Symbol,
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
"range" range / set_range [_]: CounterRanges,
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad>
"pad" pad / set_pad [_]: Pad,
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback>
"fallback" fallback / set_fallback [_]: Fallback,
/// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols>
"symbols" symbols / set_symbols [check_symbols]: Symbols,
/// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols>
"additive-symbols" additive_symbols /
set_additive_symbols [check_additive_symbols]: AdditiveSymbols,
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as>
"speak-as" speak_as / set_speak_as [_]: SpeakAs,
}
// Implements the special checkers for some setters.
// See <https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface>
impl CounterStyleRuleData {
/// Check that the system is effectively not changed. Only params
/// of system descriptor is changeable.
fn check_system(&self, value: &System) -> bool {
mem::discriminant(self.resolved_system()) == mem::discriminant(value)
}
fn check_symbols(&self, value: &Symbols) -> bool {
match *self.resolved_system() {
// These two systems require at least two symbols.
System::Numeric | System::Alphabetic => value.0.len() >= 2,
// No symbols should be set for extends system.
System::Extends(_) => false,
_ => true,
}
}
fn check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool {
match *self.resolved_system() {
// No additive symbols should be set for extends system.
System::Extends(_) => false,
_ => true,
}
}
}
impl CounterStyleRuleData {
/// Get the name of the counter style rule.
pub fn name(&self) -> &CustomIdent {
&self.name
}
/// Set the name of the counter style rule. Caller must ensure that
/// the name is valid.
pub fn set_name(&mut self, name: CustomIdent) {
debug_assert!(is_valid_name_definition(&name));
self.name = name;
}
/// Get the current generation of the counter style rule.
pub fn generation(&self) -> u32 {
self.generation.0
}
/// Get the system of this counter style rule, default to
/// `symbolic` if not specified.
pub fn resolved_system(&self) -> &System {
match self.system {
Some(ref system) => system,
None => &System::Symbolic,
}
}
}
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-system>
#[derive(Clone, Debug, ToShmem)]
pub enum System {
/// 'cyclic'
Cyclic,
/// 'numeric'
Numeric,
/// 'alphabetic'
Alphabetic,
/// 'symbolic'
Symbolic,
/// 'additive'
Additive,
/// 'fixed <integer>?'
Fixed {
/// '<integer>?'
first_symbol_value: Option<Integer>,
},
/// 'extends <counter-style-name>'
Extends(CustomIdent),
}
impl Parse for System {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
try_match_ident_ignore_ascii_case! { input,
"cyclic" => Ok(System::Cyclic),
"numeric" => Ok(System::Numeric),
"alphabetic" => Ok(System::Alphabetic),
"symbolic" => Ok(System::Symbolic),
"additive" => Ok(System::Additive),
"fixed" => {
let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok();
Ok(System::Fixed { first_symbol_value })
},
"extends" => {
let other = parse_counter_style_name(input)?;
Ok(System::Extends(other))
},
}
}
}
impl ToCss for System {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
System::Cyclic => dest.write_str("cyclic"),
System::Numeric => dest.write_str("numeric"),
System::Alphabetic => dest.write_str("alphabetic"),
System::Symbolic => dest.write_str("symbolic"),
System::Additive => dest.write_str("additive"),
System::Fixed { first_symbol_value } => {
if let Some(value) = first_symbol_value {
dest.write_str("fixed ")?;
value.to_css(dest)
} else {
dest.write_str("fixed")
}
},
System::Extends(ref other) => {
dest.write_str("extends ")?;
other.to_css(dest)
},
}
}
}
/// <https://drafts.csswg.org/css-counter-styles/#typedef-symbol>
#[derive(
Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
)]
#[repr(u8)]
pub enum Symbol {
/// <string>
String(crate::OwnedStr),
/// <custom-ident>
Ident(CustomIdent),
// Not implemented:
// /// <image>
// Image(Image),
}
impl Parse for Symbol {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())),
Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)),
ref t => Err(location.new_unexpected_token_error(t.clone())),
}
}
}
impl Symbol {
/// Returns whether this symbol is allowed in symbols() function.
pub fn is_allowed_in_symbols(&self) -> bool {
match self {
// Identifier is not allowed.
&Symbol::Ident(_) => false,
_ => true,
}
}
}
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative>
#[derive(Clone, Debug, ToCss, ToShmem)]
pub struct Negative(pub Symbol, pub Option<Symbol>);
impl Parse for Negative {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(Negative(
Symbol::parse(context, input)?,
input.try_parse(|input| Symbol::parse(context, input)).ok(),
))
}
}
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
#[derive(Clone, Debug, ToCss, ToShmem)]
pub struct CounterRange {
/// The start of the range.
pub start: CounterBound,
/// The end of the range.
pub end: CounterBound,
}
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-range>
///
/// Empty represents 'auto'
#[derive(Clone, Debug, ToCss, ToShmem)]
#[css(comma)]
pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>);
/// A bound found in `CounterRanges`.
#[derive(Clone, Copy, Debug, ToCss, ToShmem)]
pub enum CounterBound {
/// An integer bound.
Integer(Integer),
/// The infinite bound.
Infinite,
}
impl Parse for CounterRanges {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input
.try_parse(|input| input.expect_ident_matching("auto"))
.is_ok()
{
return Ok(CounterRanges(Default::default()));
}
let ranges = input.parse_comma_separated(|input| {
let start = parse_bound(context, input)?;
let end = parse_bound(context, input)?;
if let (CounterBound::Integer(start), CounterBound::Integer(end)) = (start, end) {
if start > end {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
}
Ok(CounterRange { start, end })
})?;
Ok(CounterRanges(ranges.into()))
}
}
fn parse_bound<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<CounterBound, ParseError<'i>> {
if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) {
return Ok(CounterBound::Integer(integer));
}
input.expect_ident_matching("infinite")?;
Ok(CounterBound::Infinite)
}
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad>
#[derive(Clone, Debug, ToCss, ToShmem)]
pub struct Pad(pub Integer, pub Symbol);
impl Parse for Pad {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let pad_with = input.try_parse(|input| Symbol::parse(context, input));
let min_length = Integer::parse_non_negative(context, input)?;
let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?;
Ok(Pad(min_length, pad_with))
}
}
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback>
#[derive(Clone, Debug, ToCss, ToShmem)]
pub struct Fallback(pub CustomIdent);
impl Parse for Fallback {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(Fallback(parse_counter_style_name(input)?))
}
}
/// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols>
#[derive(
Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
)]
#[repr(C)]
pub struct Symbols(
#[css(iterable)]
#[ignore_malloc_size_of = "Arc"]
pub crate::ArcSlice<Symbol>,
);
impl Parse for Symbols {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut symbols = smallvec::SmallVec::<[_; 5]>::new();
while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) {
symbols.push(s);
}
if symbols.is_empty() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(Symbols(crate::ArcSlice::from_iter(symbols.drain(..))))
}
}
/// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols>
#[derive(Clone, Debug, ToCss, ToShmem)]
#[css(comma)]
pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>);
impl Parse for AdditiveSymbols {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
// FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220
if tuples
.windows(2)
.any(|window| window[0].weight <= window[1].weight)
{
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(AdditiveSymbols(tuples.into()))
}
}
/// <integer> && <symbol>
#[derive(Clone, Debug, ToCss, ToShmem)]
pub struct AdditiveTuple {
/// <integer>
pub weight: Integer,
/// <symbol>
pub symbol: Symbol,
}
impl OneOrMoreSeparated for AdditiveTuple {
type S = Comma;
}
impl Parse for AdditiveTuple {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let symbol = input.try_parse(|input| Symbol::parse(context, input));
let weight = Integer::parse_non_negative(context, input)?;
let symbol = symbol.or_else(|_| Symbol::parse(context, input))?;
Ok(Self { weight, symbol })
}
}
/// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as>
#[derive(Clone, Debug, ToCss, ToShmem)]
pub enum SpeakAs {
/// auto
Auto,
/// bullets
Bullets,
/// numbers
Numbers,
/// words
Words,
// /// spell-out, not supported, see bug 1024178
// SpellOut,
/// <counter-style-name>
Other(CustomIdent),
}
impl Parse for SpeakAs {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut is_spell_out = false;
let result = input.try_parse(|input| {
let ident = input.expect_ident().map_err(|_| ())?;
match_ignore_ascii_case! { &*ident,
"auto" => Ok(SpeakAs::Auto),
"bullets" => Ok(SpeakAs::Bullets),
"numbers" => Ok(SpeakAs::Numbers),
"words" => Ok(SpeakAs::Words),
"spell-out" => {
is_spell_out = true;
Err(())
},
_ => Err(()),
}
});
if is_spell_out {
// spell-out is not supported, but dont parse it as a <counter-style-name>.
// See bug 1024178.
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?)))
}
}