use super::CssParser;
use crate::ast::*;
use crate::lexer::TokenKind;
use crate::whitespace::boundary_split_offset;
use tsv_lang::{Error, Span};
pub(crate) fn parse_attribute_selector<'arena>(
parser: &mut CssParser<'_, 'arena>,
start: usize,
) -> Result<SimpleSelector<'arena>, Error> {
parser.expect(TokenKind::LeftBracket)?;
parser.skip_boundary_whitespace_registering_comments()?;
let namespace_span = if parser.check(TokenKind::Asterisk) {
let prefix = Span {
start: parser.span_pos(parser.current_start()),
end: parser.span_pos(parser.current_end()),
};
parser.advance()?;
parser.register_and_skip_comments()?;
if !parser.check(TokenKind::Pipe) {
return Err(parser.error_expected_after("'|'", "'*' in attribute selector"));
}
parser.advance()?; parser.register_and_skip_comments()?;
Some(prefix)
} else if parser.check(TokenKind::Pipe) {
let prefix = Span {
start: parser.span_pos(parser.current_start()),
end: parser.span_pos(parser.current_start()),
};
parser.advance()?; parser.register_and_skip_comments()?;
Some(prefix)
} else if parser.check(TokenKind::Identifier) {
let maybe_namespace_span = Span {
start: parser.span_pos(parser.current_start()),
end: parser.span_pos(parser.current_end()),
};
parser.advance()?;
let space_before_pipe = parser.skip_boundary_whitespace_registering_comments()?;
if parser.check(TokenKind::Pipe) {
parser.advance()?; parser.register_and_skip_comments()?;
if parser.check(TokenKind::Equals) {
parser.advance()?; parser.skip_boundary_whitespace_registering_comments()?;
let value_span = parse_attribute_value(parser)?;
let flags = parse_attribute_flags(parser)?;
let end = parser.expect_and_capture(TokenKind::RightBracket)?;
return Ok(SimpleSelector::Attribute {
namespace_span: None,
name_span: maybe_namespace_span,
matcher: Some(AttributeMatcher::DashMatch),
value_span,
flags,
span: Span {
start: start as u32,
end,
},
});
}
if space_before_pipe {
return Err(parser.error_msg(
"No whitespace allowed between the components of a namespace-qualified name",
));
}
Some(maybe_namespace_span)
} else {
let (matcher, value_span) = parse_attribute_matcher_and_value(parser)?;
let flags = parse_attribute_flags(parser)?;
let end = parser.expect_and_capture(TokenKind::RightBracket)?;
return Ok(SimpleSelector::Attribute {
namespace_span: None, name_span: maybe_namespace_span,
matcher,
value_span,
flags,
span: Span {
start: start as u32,
end,
},
});
}
} else {
return Err(parser.error_expected("attribute name, '*', or '|'"));
};
if !parser.check(TokenKind::Identifier) {
return Err(parser.error_expected_after("attribute name", "namespace"));
}
let name_span = Span {
start: parser.span_pos(parser.current_start()),
end: parser.span_pos(parser.current_end()),
};
parser.advance()?;
parser.skip_boundary_whitespace_registering_comments()?;
let (matcher, value_span) = parse_attribute_matcher_and_value(parser)?;
let flags = parse_attribute_flags(parser)?;
let end = parser.expect_and_capture(TokenKind::RightBracket)?;
Ok(SimpleSelector::Attribute {
namespace_span,
name_span,
matcher,
value_span,
flags,
span: Span {
start: start as u32,
end,
},
})
}
fn parse_attribute_matcher_and_value(
parser: &mut CssParser<'_, '_>,
) -> Result<(Option<AttributeMatcher>, Option<Span>), Error> {
if parser.check(TokenKind::RightBracket) {
Ok((None, None)) } else {
let matcher = parse_attribute_matcher(parser)?;
parser.skip_boundary_whitespace_registering_comments()?;
let value = parse_attribute_value(parser)?;
Ok((Some(matcher), value))
}
}
fn parse_attribute_flags<'arena>(
parser: &mut CssParser<'_, 'arena>,
) -> Result<Option<&'arena str>, Error> {
if !parser.check(TokenKind::Identifier) {
return Ok(None);
}
let (start, end) = (parser.current_start(), parser.current_end());
let raw = &parser.source()[start..end];
let letters = raw.bytes().take_while(u8::is_ascii_alphabetic).count();
let flag = &raw[..letters];
if !(flag.eq_ignore_ascii_case("i") || flag.eq_ignore_ascii_case("s")) {
return Ok(None);
}
let flag = parser.alloc_str_in(flag);
parser.advance_from(start + letters)?;
parser.skip_boundary_whitespace_registering_comments()?;
Ok(Some(flag)) }
fn parse_attribute_value(parser: &mut CssParser<'_, '_>) -> Result<Option<Span>, Error> {
let (start, mut end) = (parser.current_start(), parser.current_end());
match parser.current_kind() {
TokenKind::Identifier => {
if let Some(split) = boundary_split_offset(&parser.source()[start..end]) {
end = start + split;
}
}
TokenKind::String { .. } => {}
_ => {
return Err(parser.error_expected("attribute value"));
}
}
let span = Span {
start: parser.span_pos(start),
end: parser.span_pos(end),
};
parser.advance_from(end)?;
parser.skip_boundary_whitespace_registering_comments()?;
Ok(Some(span))
}
fn parse_attribute_matcher(parser: &mut CssParser<'_, '_>) -> Result<AttributeMatcher, Error> {
let matcher = match parser.current_kind() {
TokenKind::Equals => AttributeMatcher::Exact, TokenKind::Tilde => {
parser.advance()?;
parser.register_and_skip_comments()?;
if !parser.check(TokenKind::Equals) {
return Err(parser.error_expected_after("'='", "'~'"));
}
parser.advance()?; return Ok(AttributeMatcher::Contains);
}
TokenKind::Pipe => {
parser.advance()?;
parser.register_and_skip_comments()?;
if !parser.check(TokenKind::Equals) {
return Err(parser.error_expected_after("'='", "'|'"));
}
parser.advance()?; return Ok(AttributeMatcher::DashMatch);
}
TokenKind::Caret => {
parser.advance()?;
parser.register_and_skip_comments()?;
if !parser.check(TokenKind::Equals) {
return Err(parser.error_expected_after("'='", "'^'"));
}
parser.advance()?; return Ok(AttributeMatcher::Prefix);
}
TokenKind::Dollar => {
parser.advance()?;
parser.register_and_skip_comments()?;
if !parser.check(TokenKind::Equals) {
return Err(parser.error_expected_after("'='", "'$'"));
}
parser.advance()?; return Ok(AttributeMatcher::Suffix);
}
TokenKind::Asterisk => {
parser.advance()?;
parser.register_and_skip_comments()?;
if !parser.check(TokenKind::Equals) {
return Err(parser.error_expected_after("'='", "'*'"));
}
parser.advance()?; return Ok(AttributeMatcher::Substring);
}
_ => {
return Err(parser.error_expected_found("attribute matcher or ']'"));
}
};
parser.advance()?; Ok(matcher)
}