Permalink
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
Cannot retrieve contributors at this time
//! Code for applying CSS styles to the DOM. | |
//! | |
//! This is not very interesting at the moment. It will get much more | |
//! complicated if I add support for compound selectors. | |
use dom::{Node, Element, ElementData}; | |
use css::{Stylesheet, Rule, Selector, Simple, SimpleSelector, Value, Keyword, Specificity}; | |
use std::collections::hashmap::HashMap; | |
/// Map from CSS property names to values. | |
pub type PropertyMap = HashMap<String, Value>; | |
/// A node with associated style data. | |
pub struct StyledNode<'a> { | |
pub node: &'a Node, | |
pub specified_values: PropertyMap, | |
pub children: Vec<StyledNode<'a>>, | |
} | |
#[deriving(PartialEq)] | |
pub enum Display { | |
Inline, | |
Block, | |
None, | |
} | |
impl<'a> StyledNode<'a> { | |
/// Return the specified value of a property if it exists, otherwise `None`. | |
pub fn value(&self, name: &str) -> Option<Value> { | |
self.specified_values.find_equiv(&name).map(|v| v.clone()) | |
} | |
/// Return the specified value of property `name`, or property `fallback_name` if that doesn't | |
/// exist. or value `default` if neither does. | |
pub fn lookup(&self, name: &str, fallback_name: &str, default: &Value) -> Value { | |
self.value(name).unwrap_or_else(|| self.value(fallback_name) | |
.unwrap_or_else(|| default.clone())) | |
} | |
/// The value of the `display` property (defaults to `Block`). | |
pub fn display(&self) -> Display { | |
match self.value("display") { | |
Some(Keyword(s)) => match s.as_slice() { | |
"inline" => Inline, | |
"none" => None, | |
_ => Block | |
}, | |
_ => Block | |
} | |
} | |
} | |
/// Apply a stylesheet to an entire DOM tree, returning a StyledNode tree. | |
/// | |
/// This finds only the specified values at the moment. Eventually it should be extended to find the | |
/// computed values too, including inherited values. | |
pub fn style_tree<'a>(root: &'a Node, stylesheet: &'a Stylesheet) -> StyledNode<'a> { | |
StyledNode { | |
node: root, | |
specified_values: match root.node_type { | |
Element(ref elem) => specified_values(elem, stylesheet), | |
_ => HashMap::new(), | |
}, | |
children: root.children.iter().map(|child| style_tree(child, stylesheet)).collect(), | |
} | |
} | |
/// Apply styles to a single element, returning the specified styles. | |
/// | |
/// To do: Allow multiple UA/author/user stylesheets, and implement the cascade. | |
fn specified_values(elem: &ElementData, stylesheet: &Stylesheet) -> PropertyMap { | |
let mut values = HashMap::new(); | |
let mut rules = matching_rules(elem, stylesheet); | |
// Go through the rules from lowest to highest specificity. | |
rules.sort_by(|&(a, _), &(b, _)| a.cmp(&b)); | |
for &(_, rule) in rules.iter() { | |
for declaration in rule.declarations.iter() { | |
values.insert(declaration.name.clone(), declaration.value.clone()); | |
} | |
} | |
values | |
} | |
/// A single CSS rule and the specificity of its most specific matching selector. | |
type MatchedRule<'a> = (Specificity, &'a Rule); | |
/// Find all CSS rules that match the given element. | |
fn matching_rules<'a>(elem: &ElementData, stylesheet: &'a Stylesheet) -> Vec<MatchedRule<'a>> { | |
// For now, we just do a linear scan of all the rules. For large | |
// documents, it would be more efficient to store the rules in hash tables | |
// based on tag name, id, class, etc. | |
stylesheet.rules.iter().filter_map(|rule| match_rule(elem, rule)).collect() | |
} | |
fn match_rule<'a>(elem: &ElementData, rule: &'a Rule) -> Option<MatchedRule<'a>> { | |
// Find the first (highest-specificity) matching selector. | |
rule.selectors.iter().find(|selector| matches(elem, *selector)) | |
.map(|selector| (selector.specificity(), rule)) | |
} | |
/// Selector matching: | |
fn matches(elem: &ElementData, selector: &Selector) -> bool { | |
match *selector { | |
Simple(ref simple_selector) => matches_simple_selector(elem, simple_selector) | |
} | |
} | |
fn matches_simple_selector(elem: &ElementData, selector: &SimpleSelector) -> bool { | |
// Check type selector | |
if selector.tag_name.iter().any(|name| elem.tag_name != *name) { | |
return false; | |
} | |
// Check ID selector | |
if selector.id.iter().any(|id| elem.id() != Some(id)) { | |
return false; | |
} | |
// Check class selectors | |
let elem_classes = elem.classes(); | |
if selector.class.iter().any(|class| !elem_classes.contains(&class.as_slice())) { | |
return false; | |
} | |
// We didn't find any non-matching selector components. | |
return true; | |
} |