2020-04-26 00:55:38 -04:00
|
|
|
use std::fmt::{self, Display, Write};
|
2020-04-20 03:45:28 -04:00
|
|
|
|
2020-04-12 19:37:12 -04:00
|
|
|
use codemap::Span;
|
2020-04-02 20:59:37 -04:00
|
|
|
|
2020-06-16 20:00:11 -04:00
|
|
|
use crate::{common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident, value::Value};
|
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
use super::{Namespace, QualifiedName};
|
2020-04-02 20:59:37 -04:00
|
|
|
|
2020-06-07 03:06:08 -04:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
2020-04-02 20:59:37 -04:00
|
|
|
pub(crate) struct Attribute {
|
2020-04-26 00:55:38 -04:00
|
|
|
attr: QualifiedName,
|
2020-04-02 21:10:45 -04:00
|
|
|
value: String,
|
|
|
|
modifier: Option<char>,
|
2020-04-26 00:55:38 -04:00
|
|
|
op: AttributeOp,
|
|
|
|
span: Span,
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult<QualifiedName> {
|
|
|
|
let next = parser.toks.peek().ok_or(("Expected identifier.", start))?;
|
2020-04-26 00:55:38 -04:00
|
|
|
if next.kind == '*' {
|
|
|
|
let pos = next.pos;
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.toks.next();
|
|
|
|
if parser.toks.peek().ok_or(("expected \"|\".", pos))?.kind != '|' {
|
2020-04-26 00:55:38 -04:00
|
|
|
return Err(("expected \"|\".", pos).into());
|
|
|
|
}
|
2020-04-26 01:52:43 -04:00
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.span_before = parser.toks.next().unwrap().pos();
|
2020-04-26 01:52:43 -04:00
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
let ident = parser.parse_identifier()?.node;
|
2020-04-26 00:55:38 -04:00
|
|
|
return Ok(QualifiedName {
|
|
|
|
ident,
|
2020-05-31 04:51:41 -04:00
|
|
|
namespace: Namespace::Asterisk,
|
2020-04-26 00:55:38 -04:00
|
|
|
});
|
|
|
|
}
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.span_before = next.pos;
|
|
|
|
let name_or_namespace = parser.parse_identifier()?;
|
|
|
|
match parser.toks.peek() {
|
2020-04-26 00:55:38 -04:00
|
|
|
Some(v) if v.kind != '|' => {
|
|
|
|
return Ok(QualifiedName {
|
|
|
|
ident: name_or_namespace.node,
|
2020-05-31 04:51:41 -04:00
|
|
|
namespace: Namespace::None,
|
2020-04-26 00:55:38 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
Some(..) => {}
|
|
|
|
None => return Err(("expected more input.", name_or_namespace.span).into()),
|
|
|
|
}
|
2020-06-16 19:38:30 -04:00
|
|
|
match parser.toks.peek_forward(1) {
|
2020-04-26 00:55:38 -04:00
|
|
|
Some(v) if v.kind == '=' => {
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.toks.reset_view();
|
2020-04-26 00:55:38 -04:00
|
|
|
return Ok(QualifiedName {
|
|
|
|
ident: name_or_namespace.node,
|
2020-05-31 04:51:41 -04:00
|
|
|
namespace: Namespace::None,
|
2020-04-26 00:55:38 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
Some(..) => {
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.toks.reset_view();
|
2020-04-26 00:55:38 -04:00
|
|
|
}
|
|
|
|
None => return Err(("expected more input.", name_or_namespace.span).into()),
|
|
|
|
}
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.span_before = parser.toks.next().unwrap().pos();
|
|
|
|
let ident = parser.parse_identifier()?.node;
|
2020-04-26 00:55:38 -04:00
|
|
|
Ok(QualifiedName {
|
|
|
|
ident,
|
2020-05-31 04:51:41 -04:00
|
|
|
namespace: Namespace::Other(name_or_namespace.node),
|
2020-04-26 00:55:38 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
fn attribute_operator(parser: &mut Parser<'_>) -> SassResult<AttributeOp> {
|
|
|
|
let start = parser.span_before;
|
|
|
|
let op = match parser.toks.next().ok_or(("Expected \"]\".", start))?.kind {
|
2020-04-26 00:55:38 -04:00
|
|
|
'=' => return Ok(AttributeOp::Equals),
|
|
|
|
'~' => AttributeOp::Include,
|
|
|
|
'|' => AttributeOp::Dash,
|
|
|
|
'^' => AttributeOp::Prefix,
|
|
|
|
'$' => AttributeOp::Suffix,
|
|
|
|
'*' => AttributeOp::Contains,
|
|
|
|
_ => return Err(("Expected \"]\".", start).into()),
|
|
|
|
};
|
2020-06-16 19:38:30 -04:00
|
|
|
if parser.toks.next().ok_or(("expected \"=\".", start))?.kind != '=' {
|
2020-04-26 00:55:38 -04:00
|
|
|
return Err(("expected \"=\".", start).into());
|
|
|
|
}
|
|
|
|
Ok(op)
|
|
|
|
}
|
2020-04-02 20:59:37 -04:00
|
|
|
impl Attribute {
|
2020-06-16 19:38:30 -04:00
|
|
|
pub fn from_tokens(parser: &mut Parser<'_>) -> SassResult<Attribute> {
|
|
|
|
let start = parser.span_before;
|
|
|
|
parser.whitespace();
|
|
|
|
let attr = attribute_name(parser, start)?;
|
|
|
|
parser.whitespace();
|
|
|
|
if parser
|
|
|
|
.toks
|
|
|
|
.peek()
|
|
|
|
.ok_or(("expected more input.", start))?
|
|
|
|
.kind
|
|
|
|
== ']'
|
|
|
|
{
|
|
|
|
parser.toks.next();
|
2020-05-31 04:51:41 -04:00
|
|
|
return Ok(Attribute {
|
2020-04-26 00:55:38 -04:00
|
|
|
attr,
|
|
|
|
value: String::new(),
|
|
|
|
modifier: None,
|
|
|
|
op: AttributeOp::Any,
|
|
|
|
span: start,
|
2020-05-31 04:51:41 -04:00
|
|
|
});
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.span_before = start;
|
|
|
|
let op = attribute_operator(parser)?;
|
|
|
|
parser.whitespace();
|
2020-04-02 20:59:37 -04:00
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
let peek = parser.toks.peek().ok_or(("expected more input.", start))?;
|
|
|
|
parser.span_before = peek.pos;
|
2020-04-26 00:55:38 -04:00
|
|
|
let value = match peek.kind {
|
|
|
|
q @ '\'' | q @ '"' => {
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.toks.next();
|
|
|
|
match parser.parse_quoted_string(q)?.node {
|
2020-05-22 14:35:41 -04:00
|
|
|
Value::String(s, ..) => s,
|
2020-04-26 00:55:38 -04:00
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
2020-06-16 19:38:30 -04:00
|
|
|
_ => parser.parse_identifier()?.node,
|
2020-04-02 20:59:37 -04:00
|
|
|
};
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.whitespace();
|
2020-04-02 20:59:37 -04:00
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
let peek = parser.toks.peek().ok_or(("expected more input.", start))?;
|
2020-04-12 19:37:12 -04:00
|
|
|
|
2020-04-26 00:55:38 -04:00
|
|
|
let modifier = match peek.kind {
|
|
|
|
c if c.is_alphabetic() => Some(c),
|
|
|
|
_ => None,
|
2020-04-02 20:59:37 -04:00
|
|
|
};
|
|
|
|
|
2020-04-26 00:55:38 -04:00
|
|
|
let pos = peek.pos();
|
|
|
|
|
|
|
|
if modifier.is_some() {
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.toks.next();
|
|
|
|
parser.whitespace();
|
2020-04-26 00:55:38 -04:00
|
|
|
}
|
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
if parser.toks.peek().ok_or(("expected \"]\".", pos))?.kind != ']' {
|
2020-04-26 00:55:38 -04:00
|
|
|
return Err(("expected \"]\".", pos).into());
|
|
|
|
}
|
|
|
|
|
2020-06-16 19:38:30 -04:00
|
|
|
parser.toks.next();
|
2020-04-26 01:52:43 -04:00
|
|
|
|
2020-05-31 04:51:41 -04:00
|
|
|
Ok(Attribute {
|
2020-04-26 00:55:38 -04:00
|
|
|
op,
|
2020-04-02 20:59:37 -04:00
|
|
|
attr,
|
|
|
|
value,
|
|
|
|
modifier,
|
2020-04-26 00:55:38 -04:00
|
|
|
span: start,
|
2020-05-31 04:51:41 -04:00
|
|
|
})
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for Attribute {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-04-26 00:55:38 -04:00
|
|
|
f.write_char('[')?;
|
|
|
|
write!(f, "{}", self.attr)?;
|
|
|
|
|
|
|
|
if self.op != AttributeOp::Any {
|
|
|
|
f.write_str(self.op.into())?;
|
|
|
|
if is_ident(&self.value) && !self.value.starts_with("--") {
|
|
|
|
f.write_str(&self.value)?;
|
|
|
|
|
|
|
|
if self.modifier.is_some() {
|
|
|
|
f.write_char(' ')?;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// todo: remove unwrap by not doing this in display
|
|
|
|
// or having special emitter for quoted strings?
|
|
|
|
// (also avoids the clone because we can consume/modify self)
|
|
|
|
f.write_str(
|
2020-05-22 14:35:41 -04:00
|
|
|
&Value::String(self.value.clone(), QuoteKind::Quoted)
|
2020-05-22 14:20:31 -04:00
|
|
|
.to_css_string(self.span)
|
|
|
|
.unwrap(),
|
2020-04-26 00:55:38 -04:00
|
|
|
)?;
|
|
|
|
// todo: this space is not emitted when `compressed` output
|
|
|
|
if self.modifier.is_some() {
|
|
|
|
f.write_char(' ')?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(c) = self.modifier {
|
|
|
|
f.write_char(c)?;
|
|
|
|
}
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
2020-04-26 00:55:38 -04:00
|
|
|
|
|
|
|
f.write_char(']')?;
|
|
|
|
|
|
|
|
Ok(())
|
2020-04-02 20:59:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-07 03:06:08 -04:00
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
2020-04-26 00:55:38 -04:00
|
|
|
enum AttributeOp {
|
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,
|
|
|
|
}
|
2020-04-26 00:55:38 -04:00
|
|
|
|
|
|
|
impl Into<&'static str> for AttributeOp {
|
|
|
|
fn into(self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
Self::Any => "",
|
|
|
|
Self::Equals => "=",
|
|
|
|
Self::Include => "~=",
|
|
|
|
Self::Dash => "|=",
|
|
|
|
Self::Prefix => "^=",
|
|
|
|
Self::Suffix => "$=",
|
|
|
|
Self::Contains => "*=",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|