use super::prelude::*;
use crate::EncodingLabel;
use css_lexer::{QuoteStyle, Whitespace};
#[node]
#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.charset"))]
#[derive(csskit_derives::NodeWithMetadata)]
#[metadata(node_kinds = AtRule, used_at_rules = Charset)]
pub struct CharsetRule {
#[atom(CssAtomSet::Charset)]
pub at_keyword: T![AtKeyword],
pub space: T![' '],
pub string: T![String],
#[semantic_eq(skip)]
pub semicolon: Option<T![;]>,
}
impl CharsetRule {
pub fn is_byte_sequence(&self) -> bool {
let at_keyword: Cursor = self.at_keyword.into();
let space: Cursor = self.space.into();
let string: Cursor = self.string.into();
at_keyword.token().is_lower_case()
&& !at_keyword.token().contains_escape_chars()
&& space.token().len() == 1
&& space.token() == Whitespace::Space
&& string == QuoteStyle::Double
&& !string.token().contains_escape_chars()
&& self.semicolon.is_some()
}
pub fn label<'a>(&self, source_text: &'a str) -> &'a str {
let c: Cursor = self.string.into();
let source = c.str_slice(source_text);
&source[c.token().leading_len() as usize..source.len() - c.token().trailing_len() as usize]
}
pub fn encoding(&self, source_text: &str) -> EncodingLabel {
if !self.is_byte_sequence() {
return EncodingLabel::Unknown;
}
EncodingLabel::from_label(self.label(source_text)).compact()
}
}
impl<'a> Parse<'a> for CharsetRule {
fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
where
I: Iterator<Item = Cursor> + Clone,
{
let at_keyword = p.parse::<T![AtKeyword]>()?;
let c: Cursor = at_keyword.into();
if !p.equals_atom(c, &CssAtomSet::Charset) {
Err(Diagnostic::new(c, Diagnostic::unexpected))?;
}
let space = p.parse::<T![' ']>()?;
let string = p.parse::<T![String]>()?;
let semicolon = p.parse::<T![;]>().ok();
Ok(Self { at_keyword, space, string, semicolon })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CssAtomSet;
use css_parse::{Parser, assert_parse};
fn is_byte_sequence(source_text: &str) -> bool {
let alloc = css_parse::Arena::default();
let lexer = css_lexer::Lexer::new(&CssAtomSet::ATOMS, source_text);
let mut parser = Parser::new(&alloc, source_text, lexer);
parser.parse_entirely::<CharsetRule>().output.expect("did not parse").is_byte_sequence()
}
#[test]
fn test_writes() {
assert_parse!(CssAtomSet::ATOMS, CharsetRule, "@charset \"utf-8\";");
assert_parse!(CssAtomSet::ATOMS, CharsetRule, "@charset \"UTF-8\";");
}
#[test]
fn test_is_byte_sequence() {
assert!(is_byte_sequence("@charset \"utf-8\";"));
assert!(is_byte_sequence("@charset \"UTF-8\";"));
}
#[test]
fn test_is_not_byte_sequence() {
assert!(!is_byte_sequence("@CHARSET \"utf-8\";"));
assert!(!is_byte_sequence("@charset 'utf-8';"));
assert!(!is_byte_sequence("@charset \"\\75 tf-8\";"));
assert!(!is_byte_sequence("@charset\t\"utf-8\";"));
assert!(!is_byte_sequence("@charset \"utf-8\";"));
assert!(!is_byte_sequence("@charset \"utf-8\""));
}
}