/* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2 . 0 . If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#[ cfg(feature =
"bench" )]
extern crate test;
use serde_json::{json, Map, Value};
#[ cfg(feature =
"bench" )]
use self ::test::Bencher;
use super ::{
parse_important, parse_nth, parse_one_declaration, parse_one_rule, stylesheet_encodi
ng,
AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, DeclarationParser, Delimiter,
EncodingSupport, ParseError, ParseErrorKind, Parser, ParserInput, ParserState,
QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, StyleSheetParser,
ToCss, Token, TokenSerializationType, UnicodeRange,
};
macro_rules! JArray {
($($e: expr,)*) => { JArray![ $( $e ),* ] };
($($e: expr),*) => { Value::Array(vec!( $( $e.to_json() ),* )) }
}
fn almost_equals(a: &Value, b: &Value) -> bool {
let var_name = match (a, b) {
(Value::Number(a), Value::Number(b)) => {
let a = a.as_f64().unwrap();
let b = b.as_f64().unwrap();
(a - b).abs() <= a.abs() * 1 e-6
}
(&Value::Bool(a), &Value::Bool(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Array(a), Value::Array(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| almost_equals(a, b))
}
(&Value::Object(_), &Value::Object(_)) => panic!("Not implemented" ),
(&Value::Null, &Value::Null) => true ,
_ => false ,
};
var_name
}
fn normalize(json: &mut Value) {
match *json {
Value::Array(ref mut list) => {
for item in list.iter_mut() {
normalize(item)
}
}
Value::String(ref mut s) => {
if *s == "extra-input" || *s == "empty" {
*s = "invalid" .to_string()
}
}
_ => {}
}
}
fn assert_json_eq(results: Value, mut expected: Value, message: &str) {
normalize(&mut expected);
if !almost_equals(&results, &expected) {
println!(
"{}" ,
::difference::Changeset::new(
&serde_json::to_string_pretty(&results).unwrap(),
&serde_json::to_string_pretty(&expected).unwrap(),
"\n" ,
)
);
panic!("{}" , message)
}
}
fn run_raw_json_tests<F: Fn (Value, Value)>(json_data: &str, run: F) {
let items = match serde_json::from_str(json_data) {
Ok(Value::Array(items)) => items,
other => panic!("Invalid JSON: {:?}" , other),
};
assert!(items.len() % 2 == 0 );
let mut input = None;
for item in items.into_iter() {
match (&input, item) {
(&None, json_obj) => input = Some(json_obj),
(&Some(_), expected) => {
let input = input.take().unwrap();
run(input, expected)
}
};
}
}
fn run_json_tests<F: Fn (&mut Parser) -> Value>(json_data: &str, parse: F) {
run_raw_json_tests(json_data, |input, expected| match input {
Value::String(input) => {
let mut parse_input = ParserInput::new(&input);
let result = parse(&mut Parser::new(&mut parse_input));
assert_json_eq(result, expected, &input);
}
_ => panic!("Unexpected JSON" ),
});
}
#[ test]
fn component_value_list() {
run_json_tests(
include_str!("css-parsing-tests/component_value_list.json" ),
|input| Value::Array(component_values_to_json(input)),
);
}
#[ test]
fn one_component_value() {
run_json_tests(
include_str!("css-parsing-tests/one_component_value.json" ),
|input| {
let result: Result<Value, ParseError<()>> = input.parse_entirely(|input| {
Ok(one_component_value_to_json(input.next()?.clone(), input))
});
result.unwrap_or(JArray!["error" , "invalid" ])
},
);
}
#[ test]
fn declaration_list() {
run_json_tests(
include_str!("css-parsing-tests/declaration_list.json" ),
|input| {
Value::Array(
RuleBodyParser::new(input, &mut JsonParser)
.map(|result| result.unwrap_or(JArray!["error" , "invalid" ]))
.collect(),
)
},
);
}
#[ test]
fn one_declaration() {
run_json_tests(
include_str!("css-parsing-tests/one_declaration.json" ),
|input| {
parse_one_declaration(input, &mut JsonParser).unwrap_or(JArray!["error" , "invalid" ])
},
);
}
#[ test]
fn rule_list() {
run_json_tests(include_str!("css-parsing-tests/rule_list.json" ), |input| {
Value::Array(
RuleBodyParser::new(input, &mut JsonParser)
.map(|result| result.unwrap_or(JArray!["error" , "invalid" ]))
.collect(),
)
});
}
#[ test]
fn stylesheet() {
run_json_tests(include_str!("css-parsing-tests/stylesheet.json" ), |input| {
Value::Array(
StyleSheetParser::new(input, &mut JsonParser)
.map(|result| result.unwrap_or(JArray!["error" , "invalid" ]))
.collect(),
)
});
}
#[ test]
fn one_rule() {
run_json_tests(include_str!("css-parsing-tests/one_rule.json" ), |input| {
parse_one_rule(input, &mut JsonParser).unwrap_or(JArray!["error" , "invalid" ])
});
}
#[ test]
fn stylesheet_from_bytes() {
pub struct EncodingRs;
impl EncodingSupport for EncodingRs {
type Encoding = &'static encoding_rs::Encoding;
fn utf8() -> Self ::Encoding {
encoding_rs::UTF_8
}
fn is_utf16_be_or_le(encoding: &Self ::Encoding) -> bool {
*encoding == encoding_rs::UTF_16LE || *encoding == encoding_rs::UTF_16BE
}
fn from_label(ascii_label: &[u8]) -> Option<Self ::Encoding> {
encoding_rs::Encoding::for_label(ascii_label)
}
}
run_raw_json_tests(
include_str!("css-parsing-tests/stylesheet_bytes.json" ),
|input, expected| {
let map = match input {
Value::Object(map) => map,
_ => panic!("Unexpected JSON" ),
};
let result = {
let css = get_string(&map, "css_bytes" )
.unwrap()
.chars()
.map(|c| {
assert!(c as u32 <= 0 xFF);
c as u8
})
.collect::<Vec<u8>>();
let protocol_encoding_label =
get_string(&map, "protocol_encoding" ).map(|s| s.as_bytes());
let environment_encoding = get_string(&map, "environment_encoding" )
.map(|s| s.as_bytes())
.and_then(EncodingRs::from_label);
let encoding = stylesheet_encoding::<EncodingRs>(
&css,
protocol_encoding_label,
environment_encoding,
);
let (css_unicode, used_encoding, _) = encoding.decode(&css);
let mut input = ParserInput::new(&css_unicode);
let input = &mut Parser::new(&mut input);
let rules = StyleSheetParser::new(input, &mut JsonParser)
.map(|result| result.unwrap_or(JArray!["error" , "invalid" ]))
.collect::<Vec<_>>();
JArray![rules, used_encoding.name().to_lowercase()]
};
assert_json_eq(result, expected, &Value::Object(map).to_string());
},
);
fn get_string<'a>(map: &' a Map<String, Value>, key: &str) -> Option<&'a str> {
match map.get(key) {
Some(Value::String(s)) => Some(s),
Some(&Value::Null) => None,
None => None,
_ => panic!("Unexpected JSON" ),
}
}
}
#[ test]
fn expect_no_error_token() {
let mut input = ParserInput::new("foo 4px ( / { !bar }" );
assert!(Parser::new(&mut input).expect_no_error_token().is_ok());
let mut input = ParserInput::new(")" );
assert!(Parser::new(&mut input).expect_no_error_token().is_err());
let mut input = ParserInput::new("}" );
assert!(Parser::new(&mut input).expect_no_error_token().is_err());
let mut input = ParserInput::new("(a){]" );
assert!(Parser::new(&mut input).expect_no_error_token().is_err());
let mut input = ParserInput::new("'\n'" );
assert!(Parser::new(&mut input).expect_no_error_token().is_err());
let mut input = ParserInput::new("url('\n'" );
assert!(Parser::new(&mut input).expect_no_error_token().is_err());
let mut input = ParserInput::new("url(a b)" );
assert!(Parser::new(&mut input).expect_no_error_token().is_err());
let mut input = ParserInput::new("url(\u{7F}))" );
assert!(Parser::new(&mut input).expect_no_error_token().is_err());
}
/// https://github.com/servo/rust-cssparser/issues/71
#[ test]
fn outer_block_end_consumed() {
let mut input = ParserInput::new("(calc(true))" );
let mut input = Parser::new(&mut input);
assert!(input.expect_parenthesis_block().is_ok());
assert!(input
.parse_nested_block(|input| input
.expect_function_matching("calc" )
.map_err(Into::<ParseError<()>>::into))
.is_ok());
println!("{:?}" , input.position());
assert!(input.next().is_err());
}
/// https://github.com/servo/rust-cssparser/issues/174
#[ test]
fn bad_url_slice_out_of_bounds() {
let mut input = ParserInput::new("url(\u{1}\\" );
let mut parser = Parser::new(&mut input);
let result = parser.next_including_whitespace_and_comments(); // This used to panic
assert_eq!(result, Ok(&Token::BadUrl("\u{1}\\" .into())));
}
/// https://bugzilla.mozilla.org/show_bug.cgi?id=1383975
#[ test]
fn bad_url_slice_not_at_char_boundary() {
let mut input = ParserInput::new("url(9\n۰" );
let mut parser = Parser::new(&mut input);
let result = parser.next_including_whitespace_and_comments(); // This used to panic
assert_eq!(result, Ok(&Token::BadUrl("9\n۰" .into())));
}
#[ test]
fn unquoted_url_escaping() {
let token = Token::UnquotedUrl(
"\
\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\
\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \
!\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]\
^_`abcdefghijklmnopqrstuvwxyz{|}~\x7fé\
"
.into(),
);
let serialized = token.to_css_string();
assert_eq!(
serialized,
"\
url(\
\\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\a \\b \\c \\d \\e \\f \\10 \
\\11 \\12 \\13 \\14 \\15 \\16 \\17 \\18 \\19 \\1 a \\1 b \\1 c \\1 d \\1 e \\1 f \\20 \
!\\\"#$%&\\'\\(\\)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]\
^_`abcdefghijklmnopqrstuvwxyz{|}~\\7 f é\
)\
"
);
let mut input = ParserInput::new(&serialized);
assert_eq!(Parser::new(&mut input).next(), Ok(&token));
}
#[ test]
fn test_expect_url() {
fn parse<'a>(s: &mut ParserInput<' a>) -> Result<CowRcStr<'a>, BasicParseError<' a>> {
Parser::new(s).expect_url()
}
let mut input = ParserInput::new("url()" );
assert_eq!(parse(&mut input).unwrap(), "" );
let mut input = ParserInput::new("url( " );
assert_eq!(parse(&mut input).unwrap(), "" );
let mut input = ParserInput::new("url( abc" );
assert_eq!(parse(&mut input).unwrap(), "abc" );
let mut input = ParserInput::new("url( abc \t)" );
assert_eq!(parse(&mut input).unwrap(), "abc" );
let mut input = ParserInput::new("url( 'abc' \t)" );
assert_eq!(parse(&mut input).unwrap(), "abc" );
let mut input = ParserInput::new("url(abc more stuff)" );
assert!(parse(&mut input).is_err());
// The grammar at https://drafts.csswg.org/css-values/#urls plans for `<url-modifier>*`
// at the position of "more stuff", but no such modifier is defined yet.
let mut input = ParserInput::new("url('abc' more stuff)" );
assert!(parse(&mut input).is_err());
}
#[ test]
fn nth() {
run_json_tests(include_str!("css-parsing-tests/An+B.json" ), |input| {
input
.parse_entirely(|i| {
let result: Result<_, ParseError<()>> = parse_nth(i).map_err(Into::into);
result
})
.ok()
.map(|(v0, v1)| json!([v0, v1]))
.unwrap_or(Value::Null)
});
}
#[ test]
fn parse_comma_separated_ignoring_errors() {
let input = "red, green something, yellow, whatever, blue" ;
let mut input = ParserInput::new(input);
let mut input = Parser::new(&mut input);
let result = input.parse_comma_separated_ignoring_errors(|input| {
let loc = input.current_source_location();
let ident = input.expect_ident()?;
crate ::color::parse_named_color(ident).map_err(|()| {
loc.new_unexpected_token_error::<ParseError<()>>(Token::Ident(ident.clone()))
})
});
assert_eq!(result.len(), 3 );
assert_eq!(result[0 ], (255 , 0 , 0 ));
assert_eq!(result[1 ], (255 , 255 , 0 ));
assert_eq!(result[2 ], (0 , 0 , 255 ));
}
#[ test]
fn unicode_range() {
run_json_tests(include_str!("css-parsing-tests/urange.json" ), |input| {
let result: Result<_, ParseError<()>> = input.parse_comma_separated(|input| {
let result = UnicodeRange::parse(input).ok().map(|r| (r.start, r.end));
if input.is_exhausted() {
Ok(result)
} else {
while input.next().is_ok() {}
Ok(None)
}
});
result
.unwrap()
.iter()
.map(|v| {
if let Some((v0, v1)) = v {
json!([v0, v1])
} else {
Value::Null
}
})
.collect::<Vec<_>>()
.to_json()
});
}
#[ test]
fn serializer_not_preserving_comments() {
serializer(false )
}
#[ test]
fn serializer_preserving_comments() {
serializer(true )
}
fn serializer(preserve_comments: bool) {
run_json_tests(
include_str!("css-parsing-tests/component_value_list.json" ),
|input| {
fn write_to(
mut previous_token: TokenSerializationType,
input: &mut Parser,
string: &mut String,
preserve_comments: bool,
) {
while let Ok(token) = if preserve_comments {
input.next_including_whitespace_and_comments().cloned()
} else {
input.next_including_whitespace().cloned()
} {
let token_type = token.serialization_type();
if !preserve_comments && previous_token.needs_separator_when_before(token_type)
{
string.push_str("/**/")
}
previous_token = token_type;
token.to_css(string).unwrap();
let closing_token = match token {
Token::Function(_) | Token::ParenthesisBlock => {
Some(Token::CloseParenthesis)
}
Token::SquareBracketBlock => Some(Token::CloseSquareBracket),
Token::CurlyBracketBlock => Some(Token::CloseCurlyBracket),
_ => None,
};
if let Some(closing_token) = closing_token {
let result: Result<_, ParseError<()>> = input.parse_nested_block(|input| {
write_to(previous_token, input, string, preserve_comments);
Ok(())
});
result.unwrap();
closing_token.to_css(string).unwrap();
}
}
}
let mut serialized = String::new();
write_to(
TokenSerializationType::Nothing,
input,
&mut serialized,
preserve_comments,
);
let mut input = ParserInput::new(&serialized);
let parser = &mut Parser::new(&mut input);
Value::Array(component_values_to_json(parser))
},
);
}
#[ test]
fn serialize_bad_tokens() {
let mut input = ParserInput::new("url(foo\\) b\\)ar)'ba\\'\" z\n4");
let mut parser = Parser::new(&mut input);
let token = parser.next().unwrap().clone();
assert!(matches!(token, Token::BadUrl(_)));
assert_eq!(token.to_css_string(), "url(foo\\) b\\)ar)" );
let token = parser.next().unwrap().clone();
assert!(matches!(token, Token::BadString(_)));
assert_eq!(token.to_css_string(), "\" ba'\\\"z");
let token = parser.next().unwrap().clone();
assert!(matches!(token, Token::Number { .. }));
assert_eq!(token.to_css_string(), "4" );
assert!(parser.next().is_err());
}
#[ test]
fn line_numbers() {
let mut input = ParserInput::new(concat!(
"fo\\30\r\n" ,
"0o bar/*\n",
"*/baz\r\n" ,
"\n" ,
"url(\r\n" ,
" u \r\n" ,
")\" a\\\r\n",
"b\" "
));
let mut input = Parser::new(&mut input);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 0 , column: 1 }
);
assert_eq!(
input.next_including_whitespace(),
Ok(&Token::Ident("fo00o" .into()))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 1 , column: 3 }
);
assert_eq!(
input.next_including_whitespace(),
Ok(&Token::WhiteSpace(" " ))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 1 , column: 4 }
);
assert_eq!(
input.next_including_whitespace(),
Ok(&Token::Ident("bar" .into()))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 1 , column: 7 }
);
assert_eq!(
input.next_including_whitespace_and_comments(),
Ok(&Token::Comment("\n" ))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 2 , column: 3 }
);
assert_eq!(
input.next_including_whitespace(),
Ok(&Token::Ident("baz" .into()))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 2 , column: 6 }
);
let state = input.state();
assert_eq!(
input.next_including_whitespace(),
Ok(&Token::WhiteSpace("\r\n\n" ))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 4 , column: 1 }
);
assert_eq!(
state.source_location(),
SourceLocation { line: 2 , column: 6 }
);
assert_eq!(
input.next_including_whitespace(),
Ok(&Token::UnquotedUrl("u" .into()))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 6 , column: 2 }
);
assert_eq!(
input.next_including_whitespace(),
Ok(&Token::QuotedString("ab" .into()))
);
assert_eq!(
input.current_source_location(),
SourceLocation { line: 7 , column: 3 }
);
assert!(input.next_including_whitespace().is_err());
}
#[ test]
fn overflow() {
let css = r"
2147483646
2147483647
2147483648
10000000000000
1000000000000000000000000000000000000000
1 {309 zeros}
-2147483647
-2147483648
-2147483649
-10000000000000
-1000000000000000000000000000000000000000
-1 {309 zeros}
3 .30282347 e+38
3 .40282347 e+38
3 .402824 e+38
-3 .30282347 e+38
-3 .40282347 e+38
-3 .402824 e+38
"
.replace("{309 zeros}" , &"0" .repeat(309 ));
let mut input = ParserInput::new(&css);
let mut input = Parser::new(&mut input);
assert_eq!(input.expect_integer(), Ok(2147483646 ));
assert_eq!(input.expect_integer(), Ok(2147483647 ));
assert_eq!(input.expect_integer(), Ok(2147483647 )); // Clamp on overflow
assert_eq!(input.expect_integer(), Ok(2147483647 ));
assert_eq!(input.expect_integer(), Ok(2147483647 ));
assert_eq!(input.expect_integer(), Ok(2147483647 ));
assert_eq!(input.expect_integer(), Ok(-2147483647 ));
assert_eq!(input.expect_integer(), Ok(-2147483648 ));
assert_eq!(input.expect_integer(), Ok(-2147483648 )); // Clamp on overflow
assert_eq!(input.expect_integer(), Ok(-2147483648 ));
assert_eq!(input.expect_integer(), Ok(-2147483648 ));
assert_eq!(input.expect_integer(), Ok(-2147483648 ));
assert_eq!(input.expect_number(), Ok(3 .302 _823 _5 e38));
assert_eq!(input.expect_number(), Ok(f32::MAX));
assert_eq!(input.expect_number(), Ok(f32::INFINITY));
assert_eq!(input.expect_number(), Ok(-3 .302 _823 _5 e38));
assert_eq!(input.expect_number(), Ok(f32::MIN));
assert_eq!(input.expect_number(), Ok(f32::NEG_INFINITY));
}
#[ test]
fn line_delimited() {
let mut input = ParserInput::new(" { foo ; bar } baz;," );
let mut input = Parser::new(&mut input);
assert_eq!(input.next(), Ok(&Token::CurlyBracketBlock));
assert!({
let result: Result<_, ParseError<()>> =
input.parse_until_after(Delimiter::Semicolon, |_| Ok(42 ));
result
}
.is_err());
assert_eq!(input.next(), Ok(&Token::Comma));
assert!(input.next().is_err());
}
#[ test]
fn identifier_serialization() {
// Null bytes
assert_eq!(Token::Ident("\0" .into()).to_css_string(), "\u{FFFD}" );
assert_eq!(Token::Ident("a\0" .into()).to_css_string(), "a\u{FFFD}" );
assert_eq!(Token::Ident("\0b" .into()).to_css_string(), "\u{FFFD}b" );
assert_eq!(Token::Ident("a\0b" .into()).to_css_string(), "a\u{FFFD}b" );
// Replacement character
assert_eq!(Token::Ident("\u{FFFD}" .into()).to_css_string(), "\u{FFFD}" );
assert_eq!(
Token::Ident("a\u{FFFD}" .into()).to_css_string(),
"a\u{FFFD}"
);
assert_eq!(
Token::Ident("\u{FFFD}b" .into()).to_css_string(),
"\u{FFFD}b"
);
assert_eq!(
Token::Ident("a\u{FFFD}b" .into()).to_css_string(),
"a\u{FFFD}b"
);
// Number prefix
assert_eq!(Token::Ident("0a" .into()).to_css_string(), "\\30 a" );
assert_eq!(Token::Ident("1a" .into()).to_css_string(), "\\31 a" );
assert_eq!(Token::Ident("2a" .into()).to_css_string(), "\\32 a" );
assert_eq!(Token::Ident("3a" .into()).to_css_string(), "\\33 a" );
assert_eq!(Token::Ident("4a" .into()).to_css_string(), "\\34 a" );
assert_eq!(Token::Ident("5a" .into()).to_css_string(), "\\35 a" );
assert_eq!(Token::Ident("6a" .into()).to_css_string(), "\\36 a" );
assert_eq!(Token::Ident("7a" .into()).to_css_string(), "\\37 a" );
assert_eq!(Token::Ident("8a" .into()).to_css_string(), "\\38 a" );
assert_eq!(Token::Ident("9a" .into()).to_css_string(), "\\39 a" );
// Letter number prefix
assert_eq!(Token::Ident("a0b" .into()).to_css_string(), "a0b" );
assert_eq!(Token::Ident("a1b" .into()).to_css_string(), "a1b" );
assert_eq!(Token::Ident("a2b" .into()).to_css_string(), "a2b" );
assert_eq!(Token::Ident("a3b" .into()).to_css_string(), "a3b" );
assert_eq!(Token::Ident("a4b" .into()).to_css_string(), "a4b" );
assert_eq!(Token::Ident("a5b" .into()).to_css_string(), "a5b" );
assert_eq!(Token::Ident("a6b" .into()).to_css_string(), "a6b" );
assert_eq!(Token::Ident("a7b" .into()).to_css_string(), "a7b" );
assert_eq!(Token::Ident("a8b" .into()).to_css_string(), "a8b" );
assert_eq!(Token::Ident("a9b" .into()).to_css_string(), "a9b" );
// Dash number prefix
assert_eq!(Token::Ident("-0a" .into()).to_css_string(), "-\\30 a" );
assert_eq!(Token::Ident("-1a" .into()).to_css_string(), "-\\31 a" );
assert_eq!(Token::Ident("-2a" .into()).to_css_string(), "-\\32 a" );
assert_eq!(Token::Ident("-3a" .into()).to_css_string(), "-\\33 a" );
assert_eq!(Token::Ident("-4a" .into()).to_css_string(), "-\\34 a" );
assert_eq!(Token::Ident("-5a" .into()).to_css_string(), "-\\35 a" );
assert_eq!(Token::Ident("-6a" .into()).to_css_string(), "-\\36 a" );
assert_eq!(Token::Ident("-7a" .into()).to_css_string(), "-\\37 a" );
assert_eq!(Token::Ident("-8a" .into()).to_css_string(), "-\\38 a" );
assert_eq!(Token::Ident("-9a" .into()).to_css_string(), "-\\39 a" );
// Double dash prefix
assert_eq!(Token::Ident("--a" .into()).to_css_string(), "--a" );
// Various tests
assert_eq!(
Token::Ident("\x01\x02\x1E\x1F" .into()).to_css_string(),
"\\1 \\2 \\1e \\1f "
);
assert_eq!(
Token::Ident("\u{0080}\x2D\x5F\u{00A9}" .into()).to_css_string(),
"\u{0080}\x2D\x5F\u{00A9}"
);
assert_eq!(Token::Ident("\x7F\u{0080}\u{0081}\u{0082}\u{0083}\u{0084}\u{0085}\u{0086}\u{0087}\u{0088}\u{0089}\
\u{008 A}\u{008 B}\u{008 C}\u{008 D}\u{008 E}\u{008 F}\u{0090 }\u{0091 }\u{0092 }\u{0093 }\u{0094 }\u{0095 }\u{0096 }\
\u{0097 }\u{0098 }\u{0099 }\u{009 A}\u{009 B}\u{009 C}\u{009 D}\u{009 E}\u{009 F}".into()).to_css_string(),
"\\7f \u{0080}\u{0081}\u{0082}\u{0083}\u{0084}\u{0085}\u{0086}\u{0087}\u{0088}\u{0089}\u{008A}\u{008B}\u{008C}\
\u{008 D}\u{008 E}\u{008 F}\u{0090 }\u{0091 }\u{0092 }\u{0093 }\u{0094 }\u{0095 }\u{0096 }\u{0097 }\u{0098 }\u{0099 }\
\u{009 A}\u{009 B}\u{009 C}\u{009 D}\u{009 E}\u{009 F}");
assert_eq!(
Token::Ident("\u{00A0}\u{00A1}\u{00A2}" .into()).to_css_string(),
"\u{00A0}\u{00A1}\u{00A2}"
);
assert_eq!(
Token::Ident("a0123456789b" .into()).to_css_string(),
"a0123456789b"
);
assert_eq!(
Token::Ident("abcdefghijklmnopqrstuvwxyz" .into()).to_css_string(),
"abcdefghijklmnopqrstuvwxyz"
);
assert_eq!(
Token::Ident("ABCDEFGHIJKLMNOPQRSTUVWXYZ" .into()).to_css_string(),
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
);
assert_eq!(
Token::Ident("\x20\x21\x78\x79" .into()).to_css_string(),
"\\ \\!xy"
);
// astral symbol (U+1D306 TETRAGRAM FOR CENTRE)
assert_eq!(
Token::Ident("\u{1D306}" .into()).to_css_string(),
"\u{1D306}"
);
}
trait ToJson {
fn to_json(&self ) -> Value;
}
impl <T> ToJson for T
where
T: Clone,
Value: From<T>,
{
fn to_json(&self ) -> Value {
Value::from(self .clone())
}
}
impl <'a> ToJson for CowRcStr<' a> {
fn to_json(&self ) -> Value {
let s: &str = self ;
s.to_json()
}
}
#[ bench]
#[ cfg(feature = "bench" )]
fn delimiter_from_byte(b: &mut Bencher) {
use crate ::Delimiters;
b.iter(|| {
for _ in 0 ..1000 {
for i in 0 ..256 {
std::hint::black_box(Delimiters::from_byte(Some(i as u8)));
}
}
})
}
#[ cfg(feature = "bench" )]
const BACKGROUND_IMAGE: &'static str = include_str!("big-data-url.css");
#[ cfg(feature = "bench" )]
#[ bench]
fn unquoted_url(b: &mut Bencher) {
b.iter(|| {
let mut input = ParserInput::new(BACKGROUND_IMAGE);
let mut input = Parser::new(&mut input);
input.look_for_var_or_env_functions();
let result = input.try_parse(|input| input.expect_url());
assert!(result.is_ok());
input.seen_var_or_env_functions();
(result.is_ok(), input.seen_var_or_env_functions())
})
}
#[ cfg_attr(all(miri, feature = "skip_long_tests" ), ignore)]
#[ cfg(feature = "bench" )]
#[ bench]
fn numeric(b: &mut Bencher) {
b.iter(|| {
for _ in 0 ..1000000 {
let mut input = ParserInput::new("10px" );
let mut input = Parser::new(&mut input);
let _ = test::black_box(input.next());
}
})
}
struct JsonParser;
#[ cfg_attr(all(miri, feature = "skip_long_tests" ), ignore)]
#[ test]
fn no_stack_overflow_multiple_nested_blocks() {
let mut input: String = "{{" .into();
for _ in 0 ..20 {
let dup = input.clone();
input.push_str(&dup);
}
let mut input = ParserInput::new(&input);
let mut input = Parser::new(&mut input);
while input.next().is_ok() {}
}
impl <'i> DeclarationParser<' i> for JsonParser {
type Declaration = Value;
type Error = ();
fn parse_value<'t>(
&mut self ,
name: CowRcStr<'i>,
input: &mut Parser<'i, ' t>,
) -> Result<Value, ParseError<'i, ()>> {
let mut value = vec![];
let mut important = false ;
loop {
let start = input.state();
if let Ok(mut token) = input.next_including_whitespace().cloned() {
// Hack to deal with css-parsing-tests assuming that
// `!important` in the middle of a declaration value is OK.
// This can never happen per spec
// (even CSS Variables forbid top-level `!`)
if token == Token::Delim('!' ) {
input.reset(&start);
if parse_important(input).is_ok() && input.is_exhausted() {
important = true ;
break ;
}
input.reset(&start);
token = input.next_including_whitespace().unwrap().clone();
}
value.push(one_component_value_to_json(token, input));
} else {
break ;
}
}
Ok(JArray!["declaration" , name, value, important,])
}
}
impl <'i> AtRuleParser<' i> for JsonParser {
type Prelude = Vec<Value>;
type AtRule = Value;
type Error = ();
fn parse_prelude<'t>(
&mut self ,
name: CowRcStr<'i>,
input: &mut Parser<'i, ' t>,
) -> Result<Vec<Value>, ParseError<'i, ()>> {
let prelude = vec![
"at-rule" .to_json(),
name.to_json(),
Value::Array(component_values_to_json(input)),
];
match_ignore_ascii_case! { &*name,
"charset" => {
Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())))
},
_ => Ok(prelude),
}
}
fn rule_without_block(
&mut self ,
mut prelude: Vec<Value>,
_: &ParserState,
) -> Result<Value, ()> {
prelude.push(Value::Null);
Ok(Value::Array(prelude))
}
fn parse_block<'t>(
&mut self ,
mut prelude: Vec<Value>,
_: &ParserState,
input: &mut Parser<'i, ' t>,
) -> Result<Value, ParseError<'i, ()>> {
prelude.push(Value::Array(component_values_to_json(input)));
Ok(Value::Array(prelude))
}
}
impl <'i> QualifiedRuleParser<' i> for JsonParser {
type Prelude = Vec<Value>;
type QualifiedRule = Value;
type Error = ();
fn parse_prelude<'t>(
&mut self ,
input: &mut Parser<'i, ' t>,
) -> Result<Vec<Value>, ParseError<'i, ()>> {
Ok(component_values_to_json(input))
}
fn parse_block<'t>(
&mut self ,
prelude: Vec<Value>,
_: &ParserState,
input: &mut Parser<'i, ' t>,
) -> Result<Value, ParseError<'i, ()>> {
Ok(JArray![
"qualified rule" ,
prelude,
component_values_to_json(input),
])
}
}
impl <'i> RuleBodyItemParser<' i, Value, ()> for JsonParser {
fn parse_qualified(&self ) -> bool {
true
}
fn parse_declarations(&self ) -> bool {
true
}
}
fn component_values_to_json(input: &mut Parser) -> Vec<Value> {
let mut values = vec![];
while let Ok(token) = input.next_including_whitespace().cloned() {
values.push(one_component_value_to_json(token, input));
}
values
}
fn one_component_value_to_json(token: Token, input: &mut Parser) -> Value {
fn numeric(value: f32, int_value: Option<i32>, has_sign: bool) -> Vec<Value> {
vec![
Token::Number {
value,
int_value,
has_sign,
}
.to_css_string()
.to_json(),
match int_value {
Some(i) => i.to_json(),
None => value.to_json(),
},
match int_value {
Some(_) => "integer" ,
None => "number" ,
}
.to_json(),
]
}
fn nested(input: &mut Parser) -> Vec<Value> {
let result: Result<_, ParseError<()>> =
input.parse_nested_block(|input| Ok(component_values_to_json(input)));
result.unwrap()
}
match token {
Token::Ident(value) => JArray!["ident" , value],
Token::AtKeyword(value) => JArray!["at-keyword" , value],
Token::Hash(value) => JArray!["hash" , value, "unrestricted" ],
Token::IDHash(value) => JArray!["hash" , value, "id" ],
Token::QuotedString(value) => JArray!["string" , value],
Token::UnquotedUrl(value) => JArray!["url" , value],
Token::Delim('\\' ) => "\\" .to_json(),
Token::Delim(value) => value.to_string().to_json(),
Token::Number {
value,
int_value,
has_sign,
} => Value::Array({
let mut v = vec!["number" .to_json()];
v.extend(numeric(value, int_value, has_sign));
v
}),
Token::Percentage {
unit_value,
int_value,
has_sign,
} => Value::Array({
let mut v = vec!["percentage" .to_json()];
v.extend(numeric(unit_value * 100 ., int_value, has_sign));
v
}),
Token::Dimension {
value,
int_value,
has_sign,
unit,
} => Value::Array({
let mut v = vec!["dimension" .to_json()];
v.extend(numeric(value, int_value, has_sign));
v.push(unit.to_json());
v
}),
Token::WhiteSpace(_) => " " .to_json(),
Token::Comment(_) => "/**/".to_json(),
Token::Colon => ":" .to_json(),
Token::Semicolon => ";" .to_json(),
Token::Comma => "," .to_json(),
Token::IncludeMatch => "~=" .to_json(),
Token::DashMatch => "|=" .to_json(),
Token::PrefixMatch => "^=" .to_json(),
Token::SuffixMatch => "$=" .to_json(),
Token::SubstringMatch => "*=" .to_json(),
Token::CDO => "<!--" .to_json(),
Token::CDC => "-->" .to_json(),
Token::Function(name) => Value::Array({
let mut v = vec!["function" .to_json(), name.to_json()];
v.extend(nested(input));
v
}),
Token::ParenthesisBlock => Value::Array({
let mut v = vec!["()" .to_json()];
v.extend(nested(input));
v
}),
Token::SquareBracketBlock => Value::Array({
let mut v = vec!["[]" .to_json()];
v.extend(nested(input));
v
}),
Token::CurlyBracketBlock => Value::Array({
let mut v = vec!["{}" .to_json()];
v.extend(nested(input));
v
}),
Token::BadUrl(_) => JArray!["error" , "bad-url" ],
Token::BadString(_) => JArray!["error" , "bad-string" ],
Token::CloseParenthesis => JArray!["error" , ")" ],
Token::CloseSquareBracket => JArray!["error" , "]" ],
Token::CloseCurlyBracket => JArray!["error" , "}" ],
}
}
/// A previous version of procedural-masquerade had a bug where it
/// would normalize consecutive whitespace to a single space,
/// including in string literals.
#[ test]
fn procedural_masquerade_whitespace() {
ascii_case_insensitive_phf_map! {
map -> () = {
" \t\n" => ()
}
}
assert_eq!(map::get(" \t\n" ), Some(&()));
assert_eq!(map::get(" " ), None);
match_ignore_ascii_case! { " \t\n" ,
" " => panic!("1" ),
" \t\n" => {},
_ => panic!("2" ),
}
match_ignore_ascii_case! { " " ,
" \t\n" => panic!("3" ),
" " => {},
_ => panic!("4" ),
}
}
#[ test]
fn parse_until_before_stops_at_delimiter_or_end_of_input() {
// For all j and k, inputs[i].1[j] should parse the same as inputs[i].1[k]
// when we use delimiters inputs[i].0.
let inputs = vec![
(
Delimiter::Bang | Delimiter::Semicolon,
// Note that the ';extra' is fine, because the ';' acts the same as
// the end of input.
vec!["token stream;extra" , "token stream!" , "token stream" ],
),
(Delimiter::Bang | Delimiter::Semicolon, vec![";" , "!" , "" ]),
];
for equivalent in inputs {
for (j, x) in equivalent.1 .iter().enumerate() {
for y in equivalent.1 [j + 1 ..].iter() {
let mut ix = ParserInput::new(x);
let mut ix = Parser::new(&mut ix);
let mut iy = ParserInput::new(y);
let mut iy = Parser::new(&mut iy);
let _ = ix.parse_until_before::<_, _, ()>(equivalent.0 , |ix| {
iy.parse_until_before::<_, _, ()>(equivalent.0 , |iy| {
loop {
let ox = ix.next();
let oy = iy.next();
assert_eq!(ox, oy);
if ox.is_err() {
break ;
}
}
Ok(())
})
});
}
}
}
}
#[ test]
fn parser_maintains_current_line() {
let mut input = ParserInput::new("ident ident;\nident ident ident;\nident" );
let mut parser = Parser::new(&mut input);
assert_eq!(parser.current_line(), "ident ident;" );
assert_eq!(parser.next(), Ok(&Token::Ident("ident" .into())));
assert_eq!(parser.next(), Ok(&Token::Ident("ident" .into())));
assert_eq!(parser.next(), Ok(&Token::Semicolon));
assert_eq!(parser.next(), Ok(&Token::Ident("ident" .into())));
assert_eq!(parser.current_line(), "ident ident ident;" );
assert_eq!(parser.next(), Ok(&Token::Ident("ident" .into())));
assert_eq!(parser.next(), Ok(&Token::Ident("ident" .into())));
assert_eq!(parser.next(), Ok(&Token::Semicolon));
assert_eq!(parser.next(), Ok(&Token::Ident("ident" .into())));
assert_eq!(parser.current_line(), "ident" );
}
#[ test]
fn cdc_regression_test() {
let mut input = ParserInput::new("-->x" );
let mut parser = Parser::new(&mut input);
parser.skip_cdc_and_cdo();
assert_eq!(parser.next(), Ok(&Token::Ident("x" .into())));
assert_eq!(
parser.next(),
Err(BasicParseError {
kind: BasicParseErrorKind::EndOfInput,
location: SourceLocation { line: 0 , column: 5 }
})
);
}
#[ test]
fn parse_entirely_reports_first_error() {
#[ derive(PartialEq, Debug)]
enum E {
Foo,
}
let mut input = ParserInput::new("ident" );
let mut parser = Parser::new(&mut input);
let result: Result<(), _> = parser.parse_entirely(|p| Err(p.new_custom_error(E::Foo)));
assert_eq!(
result,
Err(ParseError {
kind: ParseErrorKind::Custom(E::Foo),
location: SourceLocation { line: 0 , column: 1 },
})
);
}
#[ test]
fn parse_sourcemapping_comments() {
let tests = vec![
("/*# sourceMappingURL=here*/", Some("here")),
("/*# sourceMappingURL=here */", Some("here")),
("/*@ sourceMappingURL=here*/", Some("here")),
(
"/*@ sourceMappingURL=there*/ /*# sourceMappingURL=here*/",
Some("here" ),
),
("/*# sourceMappingURL=here there */", Some("here")),
("/*# sourceMappingURL= here */", Some("")),
("/*# sourceMappingURL=*/", Some("")),
("/*# sourceMappingUR=here */", None),
("/*! sourceMappingURL=here */", None),
("/*# sourceMappingURL = here */", None),
("/* # sourceMappingURL=here */", None),
];
for test in tests {
let mut input = ParserInput::new(test.0 );
let mut parser = Parser::new(&mut input);
while parser.next_including_whitespace().is_ok() {}
assert_eq!(parser.current_source_map_url(), test.1 );
}
}
#[ test]
fn parse_sourceurl_comments() {
let tests = vec![
("/*# sourceURL=here*/", Some("here")),
("/*# sourceURL=here */", Some("here")),
("/*@ sourceURL=here*/", Some("here")),
("/*@ sourceURL=there*/ /*# sourceURL=here*/", Some("here")),
("/*# sourceURL=here there */", Some("here")),
("/*# sourceURL= here */", Some("")),
("/*# sourceURL=*/", Some("")),
("/*# sourceMappingUR=here */", None),
("/*! sourceURL=here */", None),
("/*# sourceURL = here */", None),
("/* # sourceURL=here */", None),
];
for test in tests {
let mut input = ParserInput::new(test.0 );
let mut parser = Parser::new(&mut input);
while parser.next_including_whitespace().is_ok() {}
assert_eq!(parser.current_source_url(), test.1 );
}
}
#[ cfg_attr(all(miri, feature = "skip_long_tests" ), ignore)]
#[ test]
fn roundtrip_percentage_token() {
fn test_roundtrip(value: &str) {
let mut input = ParserInput::new(value);
let mut parser = Parser::new(&mut input);
let token = parser.next().unwrap();
assert_eq!(token.to_css_string(), value);
}
// Test simple number serialization
for i in 0 ..101 {
test_roundtrip(&format!("{}%" , i));
for j in 0 ..10 {
if j != 0 {
test_roundtrip(&format!("{}.{}%" , i, j));
}
for k in 1 ..10 {
test_roundtrip(&format!("{}.{}{}%" , i, j, k));
}
}
}
}
#[ test]
fn utf16_columns() {
// This particular test serves two purposes. First, it checks
// that the column number computations are correct. Second, it
// checks that tokenizer code paths correctly differentiate
// between the different UTF-8 encoding bytes. In particular
// different leader bytes and continuation bytes are treated
// differently, so we make sure to include all lengths in the
// tests, using the string "QΡ✈". Also, remember that because
// the column is in units of UTF-16, the 4-byte sequence results
// in two columns.
let tests = vec![
("" , 1 ),
("ascii" , 6 ),
("/*QΡ✈*/", 10),
("'QΡ✈*'" , 9 ),
("\" \\\"'QΡ✈*'" , 12 ),
("\\Q\\Ρ\\✈\\" , 10 ),
("QΡ✈" , 6 ),
("QΡ✈\\Q\\Ρ\\✈\\" , 15 ),
("newline\r\nQΡ✈" , 6 ),
("url(QΡ✈\\Q\\Ρ\\✈\\)" , 20 ),
("url(QΡ✈)" , 11 ),
("url(\r\nQΡ✈\\Q\\Ρ\\✈\\)" , 16 ),
("url(\r\nQΡ✈\\Q\\Ρ\\✈\\" , 15 ),
("url(\r\nQΡ✈\\Q\\Ρ\\✈\\ x" , 17 ),
("QΡ✈()" , 8 ),
// Test that under/over-flow of current_line_start_position is
// handled properly; see the special case in consume_4byte_intro.
("" , 3 ),
];
for test in tests {
let mut input = ParserInput::new(test.0 );
let mut parser = Parser::new(&mut input);
// Read all tokens.
loop {
match parser.next() {
Err(BasicParseError {
kind: BasicParseErrorKind::EndOfInput,
..
}) => {
break ;
}
Err(_) => {
// should this be an explicit panic instead?
unreachable!();
}
Ok(_) => {}
};
}
// Check the resulting column.
assert_eq!(parser.current_source_location().column, test.1 );
}
}
#[ test]
fn servo_define_css_keyword_enum() {
macro_rules! define_css_keyword_enum {
(pub enum $name:ident { $($variant:ident = $css:pat,)+ }) => {
#[ derive(PartialEq, Debug)]
pub enum $name {
$($variant),+
}
impl $name {
pub fn from_ident(ident: &str) -> Result<$name, ()> {
match_ignore_ascii_case! { ident,
$($css => Ok($name::$variant),)+
_ => Err(())
}
}
}
}
}
define_css_keyword_enum! {
pub enum UserZoom {
Zoom = "zoom" ,
Fixed = "fixed" ,
}
}
assert_eq!(UserZoom::from_ident("fixed" ), Ok(UserZoom::Fixed));
}
Messung V0.5 in Prozent C=95 H=95 G=94
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-06-18)
¤
*© Formatika GbR, Deutschland