diff --git a/errors.rs b/errors.rs new file mode 100644 index 00000000000..7a64d0192dd --- /dev/null +++ b/errors.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use cssparser::{SyntaxError, SourceLocation}; + + +pub struct ErrorLoggerIterator(I); + +impl>> Iterator for ErrorLoggerIterator { + pub fn next(&mut self) -> Option { + for result in **self { + match result { + Ok(v) => return Some(v), + Err(error) => log_css_error(error.location, fmt!("%?", error.reason)) + } + } + None + } +} + + +pub fn log_css_error(location: SourceLocation, message: &str) { + // TODO eventually this will got into a "web console" or something. + info!("%u:%u %s", location.line, location.column, message) +} diff --git a/namespaces.rs b/namespaces.rs new file mode 100644 index 00000000000..e9105abc7dc --- /dev/null +++ b/namespaces.rs @@ -0,0 +1,63 @@ +/* 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/. */ + +use std::hashmap::HashMap; +use cssparser::*; +use errors::log_css_error; + +pub struct NamespaceMap { + default: Option<~str>, // Optional URL + prefix_map: HashMap<~str, ~str>, // prefix -> URL +} + + +impl NamespaceMap { + pub fn new() -> NamespaceMap { + NamespaceMap { default: None, prefix_map: HashMap::new() } + } +} + + +pub fn parse_namespace_rule(rule: AtRule, namespaces: &mut NamespaceMap) { + let location = rule.location; + macro_rules! syntax_error( + () => {{ + log_css_error(location, "Invalid @namespace rule"); + return + }}; + ); + if rule.block.is_some() { syntax_error!() } + 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() { syntax_error!() } + prefix = Some(value); + }, + URL(value) | String(value) => { + if url.is_some() { syntax_error!() } + url = Some(value); + break + }, + _ => syntax_error!(), + } + } + if iter.next().is_some() { syntax_error!() } + 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); + }, + _ => syntax_error!() + } +} diff --git a/selectors.rs b/selectors.rs index ac7b859cc99..2f6e86d5492 100644 --- a/selectors.rs +++ b/selectors.rs @@ -5,7 +5,7 @@ use std::{vec, iterator}; use std::ascii::to_ascii_lower; use cssparser::*; -use stylesheets::NamespaceMap; +use namespaces::NamespaceMap; pub struct Selector { @@ -68,7 +68,7 @@ pub struct AttrSelector { } -type Iter = iterator::PeekableIterator>; +type Iter = iterator::Peekable>; // None means invalid selector diff --git a/servo-style.rc b/servo-style.rc index c05eb1211c5..8c229f35c8c 100644 --- a/servo-style.rc +++ b/servo-style.rc @@ -9,5 +9,7 @@ extern mod extra; extern mod cssparser; pub mod stylesheets; +pub mod errors; pub mod selectors; pub mod properties; +pub mod namespaces; diff --git a/stylesheets.rs b/stylesheets.rs index fa04fc2843d..0ed968c3b02 100644 --- a/stylesheets.rs +++ b/stylesheets.rs @@ -3,29 +3,31 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::iterator::Iterator; -use std::hashmap::HashMap; use std::ascii::to_ascii_lower; use cssparser::*; use selectors; use properties; +use errors::{ErrorLoggerIterator, log_css_error}; +use namespaces::{NamespaceMap, parse_namespace_rule}; pub struct Stylesheet { - style_rules: ~[StyleRule], + style_rules: ~[CSSRule], namespaces: NamespaceMap, } +pub enum CSSRule { + CSSStyleRule(StyleRule), +// CSSMediaRule(MediaRule), +} + + 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; @@ -35,113 +37,68 @@ fn parse_stylesheet(css: &str) -> Stylesheet { let mut state: uint = STATE_CHARSET; let mut rules = ~[]; - let mut namespaces = NamespaceMap { default: None, prefix_map: HashMap::new() }; + let mut namespaces = NamespaceMap::new(); - for rule in ErrorLogger(parse_stylesheet_rules(tokenize(css))) { + for rule in ErrorLoggerIterator(parse_stylesheet_rules(tokenize(css))) { + let next_state; // Unitialized to force each branch to set it. match rule { AtRule(rule) => { - 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") + let name: &str = to_ascii_lower(rule.name); + match name { + "charset" => { + if state > STATE_CHARSET { + log_css_error(rule.location, "@charset must be the first rule") } - } - loop; + // Valid @charset rules are just ignored + next_state = state; + }, + "import" => { + if state > STATE_IMPORTS { + next_state = state; + log_css_error(rule.location, + "@import must be before any rule but @charset") + } else { + next_state = STATE_IMPORTS; + log_css_error(rule.location, "@import is not supported yet") // TODO + } + }, + "namespace" => { + if state > STATE_NAMESPACES { + next_state = state; + log_css_error( + rule.location, + "@namespace must be before any rule but @charset and @import" + ) + } else { + next_state = STATE_NAMESPACES; + parse_namespace_rule(rule, &mut namespaces) + } + }, + _ => { + next_state = STATE_BODY; + log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", name)) + }, } - state = STATE_BODY; - log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", name)) }, - QualifiedRule(QualifiedRule{location: location, prelude: prelude, block: block}) => { - state = STATE_BODY; - match selectors::parse_selector_list(prelude, &namespaces) { - Some(selectors) => rules.push(StyleRule{ - selectors: selectors, - declarations: properties::parse_property_declaration_list(block) - }), - None => log_css_error(location, "Unsupported CSS selector."), - } + QualifiedRule(rule) => { + next_state = STATE_BODY; + parse_style_rule(rule, &mut rules, &namespaces) }, } + state = next_state; } 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 { - fn next(&mut self) -> Option { - for result in **self { - match result { - Ok(v) => return Some(v), - Err(error) => log_css_error(error.location, fmt!("%?", error.reason)) - } - } - None +fn parse_style_rule(rule: QualifiedRule, rule_list: &mut ~[CSSRule], + namespaces: &NamespaceMap) { + let QualifiedRule{location: location, prelude: prelude, block: block} = rule; + match selectors::parse_selector_list(prelude, namespaces) { + Some(selectors) => rule_list.push(CSSStyleRule(StyleRule{ + selectors: selectors, + declarations: properties::parse_property_declaration_list(block) + })), + None => log_css_error(location, "Unsupported CSS selector."), } } - - -fn log_css_error(location: SourceLocation, message: &str) { - // TODO eventually this will got into a "web console" or something. - info!("%u:%u %s", location.line, location.column, message) -}