use {
crate::{domain::Atom, parsers::ParserContext, CustomParseError},
cssparser::{Delimiter, ParseError, Parser, ToCss, TokenSerializationType},
std::{borrow::Cow, collections::HashSet, fmt},
};
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct SpecifiedValue {
pub originalCss: String,
}
impl ToCss for SpecifiedValue {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
dest.write_str(&self.originalCss)
}
}
impl SpecifiedValue {
pub(crate) fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
let mut references = Some(HashSet::new());
let (_first, css, _last) =
Self::parse_self_contained_declaration_value(
input,
&mut references,
)?;
Ok(SpecifiedValue {
originalCss: css.into_owned(),
})
}
fn parse_self_contained_declaration_value<'i, 't>(
input: &mut Parser<'i, 't>,
references: &mut Option<HashSet<Atom>>,
) -> Result<
(TokenSerializationType, Cow<'i, str>, TokenSerializationType),
ParseError<'i, CustomParseError<'i>>,
> {
let start_position = input.position();
let mut missing_closing_characters = String::new();
let (first, last) = Self::parse_declaration_value(
input,
references,
&mut missing_closing_characters,
)?;
let mut css: Cow<str> = input.slice_from(start_position).into();
if !missing_closing_characters.is_empty() {
if css.ends_with('\\') {
let first = missing_closing_characters.as_bytes()[0];
if first == b'"' || first == b'\'' {
css.to_mut().pop();
}
}
css.to_mut().push_str(&missing_closing_characters);
}
Ok((first, css, last))
}
fn parse_declaration_value<'i, 't>(
input: &mut Parser<'i, 't>,
references: &mut Option<HashSet<Atom>>,
missing_closing_characters: &mut String,
) -> Result<
(TokenSerializationType, TokenSerializationType),
ParseError<'i, CustomParseError<'i>>,
> {
input.parse_until_before(
Delimiter::Bang | Delimiter::Semicolon,
|input| {
let start = input.state();
input.next_including_whitespace()?;
input.reset(&start);
Self::parse_declaration_value_block(
input,
references,
missing_closing_characters,
)
},
)
}
fn parse_declaration_value_block<'i, 't>(
input: &mut Parser<'i, 't>,
references: &mut Option<HashSet<Atom>>,
missing_closing_characters: &mut String,
) -> Result<
(TokenSerializationType, TokenSerializationType),
ParseError<'i, CustomParseError<'i>>,
> {
let mut token_start = input.position();
let mut token = match input.next_including_whitespace_and_comments() {
Ok(token) => token.clone(),
Err(_) => {
return Ok((
TokenSerializationType::nothing(),
TokenSerializationType::nothing(),
))
}
};
let first_token_type = token.serialization_type();
loop {
macro_rules! nested {
() => {
input.parse_nested_block(|input| {
Self::parse_declaration_value_block(
input,
references,
missing_closing_characters,
)
})?
};
}
macro_rules! check_closed {
($closing: expr) => {
if !input.slice_from(token_start).ends_with($closing) {
missing_closing_characters.push_str($closing)
}
};
}
use cssparser::Token::*;
let last_token_type = match token {
Comment(_) => {
let token_slice = input.slice_from(token_start);
if !token_slice.ends_with("*/") {
missing_closing_characters.push_str(if token_slice.ends_with('*') {
"/"
} else {
"*/"
})
}
token.serialization_type()
}
BadUrl(url) => {
return Err(ParseError::from(
CustomParseError::BadUrlInDeclarationValueBlock(url),
))
}
BadString(string) => {
return Err(ParseError::from(
CustomParseError::BadStringInDeclarationValueBlock(string),
))
}
CloseParenthesis => {
return Err(ParseError::from(
CustomParseError::UnbalancedCloseParenthesisInDeclarationValueBlock,
))
}
CloseSquareBracket => {
return Err(ParseError::from(
CustomParseError::UnbalancedCloseSquareBracketInDeclarationValueBlock,
))
}
CloseCurlyBracket => {
return Err(ParseError::from(
CustomParseError::UnbalancedCloseCurlyBracketInDeclarationValueBlock,
))
}
Function(ref name) => {
if name.eq_ignore_ascii_case("var") {
let args_start = input.state();
input.parse_nested_block(|input| {
Self::parse_var_function(input, references)
})?;
input.reset(&args_start);
}
nested!();
check_closed!(")");
CloseParenthesis.serialization_type()
}
ParenthesisBlock => {
nested!();
check_closed!(")");
CloseParenthesis.serialization_type()
}
CurlyBracketBlock => {
nested!();
check_closed!("}");
CloseCurlyBracket.serialization_type()
}
SquareBracketBlock => {
nested!();
check_closed!("]");
CloseSquareBracket.serialization_type()
}
QuotedString(_) => {
let token_slice = input.slice_from(token_start);
let quote = &token_slice[..1];
if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
missing_closing_characters.push_str(quote)
}
token.serialization_type()
}
Ident(ref value)
| AtKeyword(ref value)
| Hash(ref value)
| IDHash(ref value)
| UnquotedUrl(ref value)
| Dimension {
unit: ref value, ..
} => {
if value.ends_with('�') && input.slice_from(token_start).ends_with('\\') {
missing_closing_characters.push('�')
}
match token {
UnquotedUrl(_) => check_closed!(")"),
_ => {}
}
token.serialization_type()
}
_ => token.serialization_type(),
};
token_start = input.position();
token = match input.next_including_whitespace_and_comments() {
Ok(token) => token.clone(),
Err(..) => return Ok((first_token_type, last_token_type)),
};
}
}
fn parse_var_function<'i, 't>(
input: &mut Parser<'i, 't>,
references: &mut Option<HashSet<Atom>>,
) -> Result<(), ParseError<'i, CustomParseError<'i>>> {
let name = input.expect_ident_cloned()?;
if input.r#try(|input| input.expect_comma()).is_ok() {
input.parse_until_before(
Delimiter::Bang | Delimiter::Semicolon,
|input| {
input.next_including_whitespace()?;
while input.next_including_whitespace_and_comments().is_ok()
{
}
Ok(())
},
)?;
}
if let Some(ref mut refs) = *references {
refs.insert(Atom::from(name));
}
Ok(())
}
}