From 9512d13cbba00173d3e36b9c0b16e4167d354024 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 8 Aug 2013 18:08:14 +0100 Subject: [PATCH] Parse @namespace rules. --- selectors.rs | 18 +++++---- stylesheets.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 106 insertions(+), 17 deletions(-) diff --git a/selectors.rs b/selectors.rs index 8407d61eb20..b13bd30e5db 100644 --- a/selectors.rs +++ b/selectors.rs @@ -1,4 +1,5 @@ use cssparser::*; +use stylesheets::NamespaceMap; pub struct Selector { @@ -63,9 +64,10 @@ pub struct AttrSelector { } -pub fn parse_selector_list(input: &[ComponentValue]) -> Option<~[Selector]> { +pub fn parse_selector_list(input: &[ComponentValue], namespaces: &NamespaceMap) + -> Option<~[Selector]> { let len = input.len(); - let (first, pos) = match parse_selector(input, 0) { + let (first, pos) = match parse_selector(input, 0, namespaces) { None => return None, Some(result) => result }; @@ -77,7 +79,7 @@ pub fn parse_selector_list(input: &[ComponentValue]) -> Option<~[Selector]> { if pos >= len { break } // EOF if input[pos] != Comma { return None } pos = skip_whitespace(input, pos); - match parse_selector(input, pos) { + match parse_selector(input, pos, namespaces) { None => return None, Some((selector, next_pos)) => { results.push(selector); @@ -89,9 +91,10 @@ pub fn parse_selector_list(input: &[ComponentValue]) -> Option<~[Selector]> { } -fn parse_selector(input: &[ComponentValue], pos: uint) -> Option<(Selector, uint)> { +fn parse_selector(input: &[ComponentValue], pos: uint, namespaces: &NamespaceMap) + -> Option<(Selector, uint)> { let len = input.len(); - let (first, pos) = match parse_simple_selectors(input, pos) { + let (first, pos) = match parse_simple_selectors(input, pos, namespaces) { None => return None, Some(result) => result }; @@ -112,7 +115,7 @@ fn parse_selector(input: &[ComponentValue], pos: uint) -> Option<(Selector, uint } }; pos = skip_whitespace(input, pos); - match parse_simple_selectors(input, pos) { + match parse_simple_selectors(input, pos, namespaces) { None => return None, Some((simple_selectors, next_pos)) => { compound = CompoundSelector { @@ -132,10 +135,11 @@ fn parse_selector(input: &[ComponentValue], pos: uint) -> Option<(Selector, uint } -fn parse_simple_selectors(input: &[ComponentValue], pos: uint) +fn parse_simple_selectors(input: &[ComponentValue], pos: uint, namespaces: &NamespaceMap) -> Option<(~[SimpleSelector], uint)> { let _ = input; let _ = pos; + let _ = namespaces; None // TODO } diff --git a/stylesheets.rs b/stylesheets.rs index 7f27be155bd..fefc4acff3b 100644 --- a/stylesheets.rs +++ b/stylesheets.rs @@ -1,50 +1,135 @@ use std::iterator::Iterator; +use std::hashmap::HashMap; +use std::ascii::to_ascii_lower; use cssparser::*; use selectors; use properties; -struct Stylesheet { +pub struct Stylesheet { style_rules: ~[StyleRule], + namespaces: NamespaceMap, } -struct StyleRule { +pub struct StyleRule { selectors: ~[selectors::Selector], declarations: ~[properties::PropertyDeclaration], } +pub struct NamespaceMap { + default: Option<~str>, // Optional URL + prefix_map: HashMap<~str, ~str>, // prefix -> URL +} + fn parse_stylesheet(css: &str) -> Stylesheet { + static STATE_CHARSET: uint = 1; + static STATE_IMPORTS: uint = 2; + static STATE_NAMESPACES: uint = 3; + static STATE_BODY: uint = 4; + let mut state: uint = STATE_CHARSET; + let mut rules = ~[]; + let mut namespaces = NamespaceMap { default: None, prefix_map: HashMap::new() }; + for rule in ErrorLogger(parse_stylesheet_rules(tokenize(css))) { match rule { AtRule(rule) => { - log_css_error(fmt!("Unsupported at-rule: @%s", rule.name)) + let name = to_ascii_lower(rule.name); + if "charset" == name { + if state > STATE_CHARSET { log_css_error(rule.location, + "@charset must be the first rule") } + // Valid @charset rules are just ignored + loop; + } + if "import" == name { + if state > STATE_IMPORTS { log_css_error( + rule.location, "@import must be before any rule but @charset") } + else { + state = STATE_IMPORTS; + log_css_error(rule.location, "@import is not supported yet") // TODO + } + loop; + } + if "namespace" == name { + if state > STATE_NAMESPACES { log_css_error(rule.location, + "@namespace must be before any rule but @charset and @import") } + else { + state = STATE_NAMESPACES; + let location = rule.location; + if !parse_namespace_rule(rule, &mut namespaces) { + log_css_error(location, "Invalid @namespace rule") + } + } + loop; + } + state = STATE_BODY; + log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", name)) }, QualifiedRule(rule) => { - match selectors::parse_selector_list(rule.prelude) { + state = STATE_BODY; + match selectors::parse_selector_list(rule.prelude, &namespaces) { Some(selectors) => rules.push(StyleRule{ selectors: selectors, declarations: properties::parse_property_declaration_list(rule.block) }), - None => log_css_error("Unsupported CSS selector."), + None => log_css_error(rule.location, "Unsupported CSS selector."), } }, } } - Stylesheet{ style_rules: rules } + Stylesheet{ style_rules: rules, namespaces: namespaces } +} + + +fn parse_namespace_rule(rule: AtRule, namespaces: &mut NamespaceMap) -> bool { + if rule.block.is_some() { return false } + let location = rule.location; + let mut prefix: Option<~str> = None; + let mut url: Option<~str> = None; + let mut iter = rule.prelude.consume_skip_whitespace(); + for component_value in iter { + match component_value { + Ident(value) => { + if prefix.is_some() { return false } + prefix = Some(value); + }, + URL(value) | String(value) => { + if url.is_some() { return false } + url = Some(value); + break + }, + _ => return false, + } + } + if iter.next().is_some() { return false } + match (prefix, url) { + (Some(prefix), Some(url)) => { + if namespaces.prefix_map.swap(prefix, url).is_some() { + log_css_error(location, "Duplicate @namespace rule"); + } + }, + (None, Some(url)) => { + if namespaces.default.is_some() { + log_css_error(location, "Duplicate @namespace rule"); + } + namespaces.default = Some(url); + }, + _ => return false + } + return true } struct ErrorLogger(I); -impl>> Iterator for ErrorLogger { +impl>> Iterator for ErrorLogger { fn next(&mut self) -> Option { for result in **self { match result { Ok(v) => return Some(v), - Err(e) => log_css_error(fmt!("%?", e)) + Err(error) => log_css_error(error.location, fmt!("%?", error.reason)) } } None @@ -52,7 +137,7 @@ impl>> Iterator for ErrorLogger { } -fn log_css_error(message: &str) { +fn log_css_error(location: SourceLocation, message: &str) { // TODO eventually this will got into a "web console" or something. - info!(message) + info!("%u:%u %s", location.line, location.column, message) }