Factor out parsing of various types of rules.

This commit is contained in:
Simon Sapin 2013-08-10 13:02:41 +01:00
parent ff1f4e62ec
commit 9b22acf2f3
5 changed files with 152 additions and 104 deletions

26
errors.rs Normal file
View file

@ -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>(I);
impl<T, I: Iterator<Result<T, SyntaxError>>> Iterator<T> for ErrorLoggerIterator<I> {
pub fn next(&mut self) -> Option<T> {
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)
}

63
namespaces.rs Normal file
View file

@ -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!()
}
}

View file

@ -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<ComponentValue, vec::ConsumeIterator<ComponentValue>>;
type Iter = iterator::Peekable<ComponentValue, vec::ConsumeIterator<ComponentValue>>;
// None means invalid selector

View file

@ -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;

View file

@ -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>(I);
impl<T, I: Iterator<Result<T, SyntaxError>>> Iterator<T> for ErrorLogger<I> {
fn next(&mut self) -> Option<T> {
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)
}