2020-04-02 20:59:37 -04:00
|
|
|
use std::fmt::{self, Display};
|
|
|
|
use std::iter::Peekable;
|
|
|
|
use std::string::ToString;
|
|
|
|
|
|
|
|
use super::{Selector, SelectorKind};
|
|
|
|
use crate::error::SassResult;
|
|
|
|
use crate::scope::Scope;
|
2020-04-03 13:28:37 -04:00
|
|
|
use crate::utils::{
|
|
|
|
devour_whitespace, eat_ident, is_ident_char, parse_interpolation, parse_quoted_string,
|
|
|
|
};
|
2020-04-02 20:59:37 -04:00
|
|
|
use crate::Token;
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub(crate) struct Attribute {
|
2020-04-02 21:10:45 -04:00
|
|
|
attr: String,
|
|
|
|
value: String,
|
|
|
|
modifier: Option<char>,
|
|
|
|
kind: AttributeKind,
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Attribute {
|
|
|
|
pub fn from_tokens<I: Iterator<Item = Token>>(
|
|
|
|
toks: &mut Peekable<I>,
|
|
|
|
scope: &Scope,
|
|
|
|
super_selector: &Selector,
|
|
|
|
) -> SassResult<SelectorKind> {
|
|
|
|
devour_whitespace(toks);
|
2020-04-02 21:44:26 -04:00
|
|
|
let attr = match toks.peek().ok_or("Expected identifier.")?.kind {
|
2020-04-03 13:28:37 -04:00
|
|
|
c if is_ident_char(c) => eat_ident(toks, scope, super_selector)?,
|
2020-04-02 21:44:26 -04:00
|
|
|
'#' => {
|
|
|
|
toks.next();
|
|
|
|
if toks.next().ok_or("Expected expression.")?.kind == '{' {
|
|
|
|
parse_interpolation(toks, scope, super_selector)?.to_string()
|
|
|
|
} else {
|
|
|
|
return Err("Expected expression.".into());
|
|
|
|
}
|
2020-04-02 21:23:23 -04:00
|
|
|
}
|
|
|
|
_ => return Err("Expected identifier.".into()),
|
2020-04-02 20:59:37 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
devour_whitespace(toks);
|
|
|
|
|
2020-04-02 21:23:23 -04:00
|
|
|
let kind = match toks.next().ok_or("expected \"{\".")?.kind {
|
2020-04-02 21:51:55 -04:00
|
|
|
c if is_ident_char(c) => return Err("Expected \"]\".".into()),
|
2020-04-02 21:23:23 -04:00
|
|
|
']' => {
|
|
|
|
return Ok(SelectorKind::Attribute(Attribute {
|
|
|
|
kind: AttributeKind::Any,
|
|
|
|
attr,
|
|
|
|
value: String::new(),
|
|
|
|
modifier: None,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
'=' => AttributeKind::Equals,
|
|
|
|
'~' => AttributeKind::Include,
|
|
|
|
'|' => AttributeKind::Dash,
|
|
|
|
'^' => AttributeKind::Prefix,
|
|
|
|
'$' => AttributeKind::Suffix,
|
|
|
|
'*' => AttributeKind::Contains,
|
2020-04-02 21:44:26 -04:00
|
|
|
_ => return Err("expected \"]\".".into()),
|
2020-04-02 20:59:37 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
if kind != AttributeKind::Equals {
|
2020-04-02 21:23:23 -04:00
|
|
|
match toks.next().ok_or("expected \"=\".")?.kind {
|
2020-04-02 20:59:37 -04:00
|
|
|
'=' => {}
|
|
|
|
_ => return Err("expected \"=\".".into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
devour_whitespace(toks);
|
|
|
|
|
2020-04-02 21:23:23 -04:00
|
|
|
let value = match toks.next().ok_or("Expected identifier.")?.kind {
|
|
|
|
v @ 'a'..='z' | v @ 'A'..='Z' | v @ '-' | v @ '_' => {
|
|
|
|
format!("{}{}", v, eat_ident(toks, scope, super_selector)?)
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
2020-04-02 22:07:22 -04:00
|
|
|
q @ '"' | q @ '\'' => parse_quoted_string(toks, scope, q, super_selector)?.to_string(),
|
2020-04-02 21:23:23 -04:00
|
|
|
_ => return Err("Expected identifier.".into()),
|
2020-04-02 20:59:37 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
devour_whitespace(toks);
|
|
|
|
|
2020-04-02 21:23:23 -04:00
|
|
|
let modifier = match toks.next().ok_or("expected \"]\".")?.kind {
|
|
|
|
']' => {
|
|
|
|
return Ok(SelectorKind::Attribute(Attribute {
|
|
|
|
kind,
|
|
|
|
attr,
|
|
|
|
value,
|
|
|
|
modifier: None,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
v @ 'a'..='z' | v @ 'A'..='Z' => {
|
|
|
|
match toks.next().ok_or("expected \"]\".")?.kind {
|
|
|
|
']' => {}
|
|
|
|
_ => return Err("expected \"]\".".into()),
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
2020-04-02 21:23:23 -04:00
|
|
|
Some(v)
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
2020-04-02 21:44:26 -04:00
|
|
|
_ => return Err("expected \"]\".".into()),
|
2020-04-02 20:59:37 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(SelectorKind::Attribute(Attribute {
|
|
|
|
kind,
|
|
|
|
attr,
|
|
|
|
value,
|
|
|
|
modifier,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for Attribute {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-04-02 21:10:45 -04:00
|
|
|
let modifier = if let Some(c) = self.modifier {
|
|
|
|
format!(" {}", c)
|
|
|
|
} else {
|
|
|
|
String::new()
|
|
|
|
};
|
2020-04-02 20:59:37 -04:00
|
|
|
match self.kind {
|
2020-04-02 21:10:45 -04:00
|
|
|
AttributeKind::Any => write!(f, "[{}{}]", self.attr, modifier),
|
|
|
|
AttributeKind::Equals => write!(f, "[{}={}{}]", self.attr, self.value, modifier),
|
|
|
|
AttributeKind::Include => write!(f, "[{}~={}{}]", self.attr, self.value, modifier),
|
|
|
|
AttributeKind::Dash => write!(f, "[{}|={}{}]", self.attr, self.value, modifier),
|
|
|
|
AttributeKind::Prefix => write!(f, "[{}^={}{}]", self.attr, self.value, modifier),
|
|
|
|
AttributeKind::Suffix => write!(f, "[{}$={}{}]", self.attr, self.value, modifier),
|
|
|
|
AttributeKind::Contains => write!(f, "[{}*={}{}]", self.attr, self.value, modifier),
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
2020-04-02 21:10:45 -04:00
|
|
|
enum AttributeKind {
|
2020-04-04 19:07:00 -04:00
|
|
|
/// \[attr\]
|
2020-04-02 21:10:45 -04:00
|
|
|
///
|
2020-04-02 20:59:37 -04:00
|
|
|
/// Represents elements with an attribute name of `attr`
|
|
|
|
Any,
|
2020-04-02 21:10:45 -04:00
|
|
|
|
2020-04-02 20:59:37 -04:00
|
|
|
/// [attr=value]
|
2020-04-02 21:10:45 -04:00
|
|
|
///
|
|
|
|
/// Represents elements with an attribute name of `attr`
|
|
|
|
/// whose value is exactly `value`
|
2020-04-02 20:59:37 -04:00
|
|
|
Equals,
|
2020-04-02 21:10:45 -04:00
|
|
|
|
2020-04-02 20:59:37 -04:00
|
|
|
/// [attr~=value]
|
2020-04-02 21:10:45 -04:00
|
|
|
///
|
|
|
|
/// Represents elements with an attribute name of `attr`
|
|
|
|
/// whose value is a whitespace-separated list of words,
|
|
|
|
/// one of which is exactly `value`
|
|
|
|
Include,
|
|
|
|
|
2020-04-02 20:59:37 -04:00
|
|
|
/// [attr|=value]
|
2020-04-02 21:10:45 -04:00
|
|
|
///
|
|
|
|
/// Represents elements with an attribute name of `attr`
|
|
|
|
/// whose value can be exactly value or can begin with
|
|
|
|
/// `value` immediately followed by a hyphen (`-`)
|
|
|
|
Dash,
|
|
|
|
|
2020-04-02 20:59:37 -04:00
|
|
|
/// [attr^=value]
|
2020-04-02 21:10:45 -04:00
|
|
|
Prefix,
|
|
|
|
|
2020-04-02 20:59:37 -04:00
|
|
|
/// [attr$=value]
|
2020-04-02 21:10:45 -04:00
|
|
|
Suffix,
|
|
|
|
|
2020-04-02 20:59:37 -04:00
|
|
|
/// [attr*=value]
|
2020-04-02 21:10:45 -04:00
|
|
|
///
|
|
|
|
/// Represents elements with an attribute name of `attr`
|
|
|
|
/// whose value contains at least one occurrence of
|
|
|
|
/// `value` within the string
|
2020-04-02 20:59:37 -04:00
|
|
|
Contains,
|
|
|
|
}
|