avm2: support CSS comments and fix whitespace issues (#17502)
This commit is contained in:
parent
1a853af7e1
commit
6d2644ed48
|
@ -27,11 +27,13 @@ pub struct CssStream<'a> {
|
|||
input: &'a WStr,
|
||||
}
|
||||
|
||||
const ASTERISK: u16 = '*' as u16;
|
||||
const OPEN_BLOCK: u16 = '{' as u16;
|
||||
const CLOSE_BLOCK: u16 = '}' as u16;
|
||||
const COMMA: u16 = ',' as u16;
|
||||
const COLON: u16 = ':' as u16;
|
||||
const SEMI_COLON: u16 = ';' as u16;
|
||||
const SLASH: u16 = '/' as u16;
|
||||
const SPACE: u16 = ' ' as u16;
|
||||
const NEWLINE: u16 = '\n' as u16;
|
||||
const RETURN: u16 = '\r' as u16;
|
||||
|
@ -44,18 +46,37 @@ impl<'a> CssStream<'a> {
|
|||
Self { pos: 0, input }
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) -> bool {
|
||||
fn skip_whitespace_and_comments(&mut self) -> bool {
|
||||
let mut found = false;
|
||||
while self.consume_any(&ANY_VALID_WHITESPACE).is_some() {
|
||||
while self.skip_comment() || self.consume_any(&ANY_VALID_WHITESPACE).is_some() {
|
||||
found = true;
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
fn skip_comment(&mut self) -> bool {
|
||||
if self.peek2() == Some((SLASH, ASTERISK)) {
|
||||
self.pos += 2; // Skip the `/*`
|
||||
while self.peek2() != Some((ASTERISK, SLASH)) {
|
||||
self.pos += 1;
|
||||
if self.peek().is_none() {
|
||||
return false; // EOF without closing comment
|
||||
}
|
||||
}
|
||||
self.pos += 2; // Skip the `*/`
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<u16> {
|
||||
self.input.get(self.pos)
|
||||
}
|
||||
|
||||
fn peek2(&self) -> Option<(u16, u16)> {
|
||||
Some((self.input.get(self.pos)?, self.input.get(self.pos + 1)?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn pos(&self) -> usize {
|
||||
self.pos
|
||||
|
@ -95,7 +116,7 @@ impl<'a> CssStream<'a> {
|
|||
let mut result = FnvHashMap::default();
|
||||
|
||||
loop {
|
||||
self.skip_whitespace();
|
||||
self.skip_whitespace_and_comments();
|
||||
if self.peek().is_none() {
|
||||
return Ok(result);
|
||||
}
|
||||
|
@ -114,12 +135,12 @@ impl<'a> CssStream<'a> {
|
|||
let mut result = Vec::new();
|
||||
|
||||
loop {
|
||||
self.skip_whitespace();
|
||||
self.skip_whitespace_and_comments();
|
||||
let name = self.consume_until_any(&[OPEN_BLOCK, COMMA, SPACE, NEWLINE, RETURN, TAB]);
|
||||
if !name.is_empty() {
|
||||
result.push(name);
|
||||
}
|
||||
self.skip_whitespace();
|
||||
self.skip_whitespace_and_comments();
|
||||
|
||||
match self.peek() {
|
||||
Some(OPEN_BLOCK) => {
|
||||
|
@ -146,13 +167,13 @@ impl<'a> CssStream<'a> {
|
|||
pub(crate) fn parse_properties(&mut self) -> Result<CssProperties<'a>, CssError> {
|
||||
let mut result = CssProperties::default();
|
||||
|
||||
loop {
|
||||
'main: loop {
|
||||
// [NA] This is a bit awkward:
|
||||
// - spaces at the start of a name are skipped
|
||||
// - spaces in the middle of a name are errors
|
||||
// - spaces at the end of the name are kept
|
||||
|
||||
self.skip_whitespace();
|
||||
self.skip_whitespace_and_comments();
|
||||
|
||||
match self.peek() {
|
||||
Some(CLOSE_BLOCK) => {
|
||||
|
@ -168,7 +189,7 @@ impl<'a> CssStream<'a> {
|
|||
let name_start = self.pos;
|
||||
let mut name = self.consume_until_any(&[COLON, SPACE, NEWLINE, RETURN, TAB]);
|
||||
|
||||
if self.skip_whitespace() {
|
||||
if self.skip_whitespace_and_comments() {
|
||||
let next = self.peek();
|
||||
if next == Some(COLON) {
|
||||
// Expand to contain the spaces at the end of a name, this is valid
|
||||
|
@ -190,17 +211,43 @@ impl<'a> CssStream<'a> {
|
|||
}
|
||||
|
||||
// Okay, we've got the name sorted, now it's the value!
|
||||
self.skip_whitespace();
|
||||
let value = self.consume_until_any(&[SEMI_COLON, CLOSE_BLOCK]);
|
||||
result.insert(name, value);
|
||||
self.skip_whitespace_and_comments();
|
||||
|
||||
let value_start = self.pos;
|
||||
let mut value = self.consume_until_any(&[SEMI_COLON, COLON, CLOSE_BLOCK]);
|
||||
loop {
|
||||
match self.peek() {
|
||||
Some(COLON) => {
|
||||
self.pos = value_start;
|
||||
let possible_value = self.consume_until_any(&[NEWLINE, RETURN]);
|
||||
match self.peek() {
|
||||
Some(NEWLINE) | Some(RETURN) => {
|
||||
self.pos += 1;
|
||||
result.insert(name, possible_value);
|
||||
continue 'main;
|
||||
}
|
||||
_ => {
|
||||
self.pos = value_start;
|
||||
value = self.consume_until_any(&[SEMI_COLON, CLOSE_BLOCK]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(SEMI_COLON) => {
|
||||
self.pos += 1;
|
||||
continue;
|
||||
result.insert(name, value);
|
||||
continue 'main;
|
||||
}
|
||||
Some(CLOSE_BLOCK) => {
|
||||
self.pos += 1;
|
||||
let mut end_value = value.len();
|
||||
for (index, ch) in value.iter().enumerate() {
|
||||
if [NEWLINE, RETURN].contains(&ch) {
|
||||
end_value = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.insert(name, &value[..end_value]);
|
||||
return Ok(result);
|
||||
}
|
||||
None => return Err(CssError::PropertyValueMissing),
|
||||
|
@ -208,6 +255,7 @@ impl<'a> CssStream<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_dashes_to_camel_case(input: &WStr) -> Cow<WStr> {
|
||||
|
@ -251,6 +299,43 @@ mod tests {
|
|||
assert_eq!(stream.pos(), 6)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_selectors_with_comment() {
|
||||
let mut stream = CssStream::new(WStr::from_units(b"name /* comment */ {"));
|
||||
assert_eq!(
|
||||
stream.parse_selectors(),
|
||||
Ok(vec![WStr::from_units(b"name")])
|
||||
);
|
||||
assert_eq!(stream.pos(), 20)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_property_without_semicolon() {
|
||||
let mut stream = CssStream::new(WStr::from_units(b"a { key: value \r\nkey2:v }"));
|
||||
let mut result = FnvHashMap::default();
|
||||
result.insert(WStr::from_units(b"a"), {
|
||||
let mut properties = FnvHashMap::default();
|
||||
properties.insert(WStr::from_units(b"key"), WStr::from_units(b"value "));
|
||||
properties.insert(WStr::from_units(b"key2"), WStr::from_units(b"v "));
|
||||
properties
|
||||
});
|
||||
assert_eq!(stream.parse(), Ok(result));
|
||||
assert_eq!(stream.pos(), 25)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_last_property_without_semicolon() {
|
||||
let mut stream = CssStream::new(WStr::from_units(b"a { key: value \r\n }"));
|
||||
let mut result = FnvHashMap::default();
|
||||
result.insert(WStr::from_units(b"a"), {
|
||||
let mut properties = FnvHashMap::default();
|
||||
properties.insert(WStr::from_units(b"key"), WStr::from_units(b"value "));
|
||||
properties
|
||||
});
|
||||
assert_eq!(stream.parse(), Ok(result));
|
||||
assert_eq!(stream.pos(), 19)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_selectors_multiple() {
|
||||
let mut stream = CssStream::new(WStr::from_units(b"one,two, three ,\n\tfour {"));
|
||||
|
|
|
@ -196,6 +196,21 @@
|
|||
"}");
|
||||
});
|
||||
|
||||
test("parseCSS: last property without semicolon trimmed correctly", styleSheet, function() {
|
||||
styleSheet.clear();
|
||||
styleSheet.parseCSS("a{key1: value; key2: value \r\n} b{key3: value \n}");
|
||||
});
|
||||
|
||||
test("parseCSS: comment", styleSheet, function() {
|
||||
styleSheet.clear();
|
||||
styleSheet.parseCSS("/* comment */ a{key: value;}");
|
||||
});
|
||||
|
||||
test("parseCSS: unclosed comment", styleSheet, function() {
|
||||
styleSheet.clear();
|
||||
styleSheet.parseCSS("/* comment a{key: value;}");
|
||||
});
|
||||
|
||||
styleSheet = new AlwaysRedStyleSheet();
|
||||
test("transform", styleSheet, function() {
|
||||
styleSheet.setStyle("anything", "blue");
|
||||
|
|
|
@ -197,6 +197,25 @@ styleSheet.getStyle("key") = {
|
|||
}
|
||||
styleSheet.getStyle("nonexistant") = {}
|
||||
|
||||
/// parseCSS: last property without semicolon trimmed correctly
|
||||
styleSheet.getStyle("a") = {
|
||||
"key1" = string "value"
|
||||
"key2" = string "value "
|
||||
}
|
||||
styleSheet.getStyle("b") = {
|
||||
"key3" = string "value "
|
||||
}
|
||||
styleSheet.getStyle("nonexistant") = {}
|
||||
|
||||
/// parseCSS: comment
|
||||
styleSheet.getStyle("a") = {
|
||||
"key" = string "value"
|
||||
}
|
||||
styleSheet.getStyle("nonexistant") = {}
|
||||
|
||||
/// parseCSS: unclosed comment
|
||||
styleSheet.getStyle("nonexistant") = {}
|
||||
|
||||
/// transform
|
||||
styleSheet.getStyle("anything") = {}
|
||||
styleSheet.getStyle("nonexistant") = {}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue