Move the content of 'script/style' into the new 'style' crate.

This commit is contained in:
Simon Sapin 2013-10-14 18:17:44 +01:00
parent c0a5e8f6eb
commit dc882b8ecf
18 changed files with 49 additions and 52 deletions

View file

@ -17,7 +17,6 @@ extern mod hubbub;
extern mod js;
extern mod netsurfcss;
extern mod newcss (name = "css");
extern mod cssparser;
extern mod servo_net (name = "net");
extern mod servo_util (name = "util");
extern mod servo_msg (name = "msg");
@ -143,7 +142,3 @@ pub mod html {
pub mod layout_interface;
pub mod script_task;
// "New" (as of 2013-08) style system, not used yet but included to avoid bitrot.
mod style;

View file

@ -1,6 +0,0 @@
servo-style
===========
Prototype replacement style system for Servo,
based on [rust-cssparser](https://github.com/mozilla-servo/rust-cssparser)
instead of [NetSurfs libcss](https://github.com/mozilla-servo/libcss).

View file

@ -1,26 +0,0 @@
/* 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> {
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)
}

View file

@ -1,124 +0,0 @@
/* 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::ascii::StrAsciiExt;
use cssparser::*;
use style::errors::{ErrorLoggerIterator, log_css_error};
use style::stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule};
use style::namespaces::NamespaceMap;
pub struct MediaRule {
media_queries: MediaQueryList,
rules: ~[CSSRule],
}
pub struct MediaQueryList {
// "not all" is omitted from the list.
// An empty list never matches.
media_queries: ~[MediaQuery]
}
// For now, this is a "Level 2 MQ", ie. a media type.
struct MediaQuery {
media_type: MediaQueryType,
// TODO: Level 3 MQ expressions
}
enum MediaQueryType {
All, // Always true
MediaType(MediaType),
}
#[deriving(Eq)]
pub enum MediaType {
Screen,
Print,
}
pub struct Device {
media_type: MediaType,
// TODO: Level 3 MQ data: viewport size, etc.
}
pub fn parse_media_rule(rule: AtRule, parent_rules: &mut ~[CSSRule],
namespaces: &NamespaceMap) {
let media_queries = parse_media_query_list(rule.prelude);
let block = match rule.block {
Some(block) => block,
None => {
log_css_error(rule.location, "Invalid @media rule");
return
}
};
let mut rules = ~[];
for rule in ErrorLoggerIterator(parse_rule_list(block.move_iter())) {
match rule {
QualifiedRule(rule) => parse_style_rule(rule, &mut rules, namespaces),
AtRule(rule) => parse_nested_at_rule(
rule.name.to_ascii_lower(), rule, &mut rules, namespaces),
}
}
parent_rules.push(CSSMediaRule(MediaRule {
media_queries: media_queries,
rules: rules,
}))
}
pub fn parse_media_query_list(input: &[ComponentValue]) -> MediaQueryList {
let iter = &mut input.skip_whitespace();
let mut next = iter.next();
if next.is_none() {
return MediaQueryList{ media_queries: ~[MediaQuery{media_type: All}] }
}
let mut queries = ~[];
loop {
let mq = match next {
Some(&Ident(ref value)) => {
match value.to_ascii_lower().as_slice() {
"screen" => Some(MediaQuery{ media_type: MediaType(Screen) }),
"print" => Some(MediaQuery{ media_type: MediaType(Print) }),
"all" => Some(MediaQuery{ media_type: All }),
_ => None
}
},
_ => None
};
match iter.next() {
None => {
mq.map_move(|mq| queries.push(mq));
return MediaQueryList{ media_queries: queries }
},
Some(&Comma) => {
mq.map_move(|mq| queries.push(mq));
},
// Ingnore this comma-separated part
_ => loop {
match iter.next() {
Some(&Comma) => break,
None => return MediaQueryList{ media_queries: queries },
_ => (),
}
},
}
next = iter.next();
}
}
impl MediaQueryList {
pub fn evaluate(&self, device: &Device) -> bool {
do self.media_queries.iter().any |mq| {
match mq.media_type {
MediaType(media_type) => media_type == device.media_type,
All => true,
}
// TODO: match Level 3 expressions
}
}
}

View file

@ -1,20 +0,0 @@
/* 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/. */
// The "real" public API
pub use self::selector_matching::{Stylist, StylesheetOrigin};
// Things that need to be public to make the compiler happy
pub mod stylesheets;
pub mod errors;
pub mod selectors;
pub mod selector_matching;
pub mod properties;
pub mod namespaces;
pub mod media_queries;
pub mod parsing_utils;
#[cfg(test)]
mod tests;

View file

@ -1,63 +0,0 @@
/* 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 style::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.move_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

@ -1,21 +0,0 @@
/* 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::ascii::StrAsciiExt;
use cssparser::*;
pub fn one_component_value<'a>(input: &'a [ComponentValue]) -> Option<&'a ComponentValue> {
let mut iter = input.skip_whitespace();
iter.next().filtered(|_| iter.next().is_none())
}
pub fn get_ident_lower(component_value: &ComponentValue) -> Option<~str> {
match component_value {
&Ident(ref value) => Some(value.to_ascii_lower()),
_ => None,
}
}

View file

@ -1 +0,0 @@
mod.rs

View file

@ -1,191 +0,0 @@
/* 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/. */
pub type Float = f64;
pub type Integer = i64;
pub mod specified {
use std::ascii::StrAsciiExt;
use cssparser::*;
use super::{Integer, Float};
pub use CSSColor = cssparser::Color;
#[deriving(Clone)]
pub enum Length {
Au(Integer), // application units
Em(Float),
Ex(Float),
// XXX uncomment when supported:
// Ch(Float),
// Rem(Float),
// Vw(Float),
// Vh(Float),
// Vmin(Float),
// Vmax(Float),
}
static AU_PER_PX: Float = 60.;
static AU_PER_IN: Float = AU_PER_PX * 96.;
static AU_PER_CM: Float = AU_PER_IN / 2.54;
static AU_PER_MM: Float = AU_PER_IN / 25.4;
static AU_PER_PT: Float = AU_PER_IN / 72.;
static AU_PER_PC: Float = AU_PER_PT * 12.;
impl Length {
#[inline]
fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Option<Length> {
match input {
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
=> Length::parse_dimension(value.value, unit.as_slice()),
&Number(ref value) if value.value == 0. => Some(Au(0)),
_ => None
}
}
pub fn parse(input: &ComponentValue) -> Option<Length> {
Length::parse_internal(input, /* negative_ok = */ true)
}
pub fn parse_non_negative(input: &ComponentValue) -> Option<Length> {
Length::parse_internal(input, /* negative_ok = */ false)
}
pub fn parse_dimension(value: Float, unit: &str) -> Option<Length> {
match unit.to_ascii_lower().as_slice() {
"px" => Some(Length::from_px(value)),
"in" => Some(Au((value * AU_PER_IN) as Integer)),
"cm" => Some(Au((value * AU_PER_CM) as Integer)),
"mm" => Some(Au((value * AU_PER_MM) as Integer)),
"pt" => Some(Au((value * AU_PER_PT) as Integer)),
"pc" => Some(Au((value * AU_PER_PC) as Integer)),
"em" => Some(Em(value)),
"ex" => Some(Ex(value)),
_ => None
}
}
#[inline]
pub fn from_px(px_value: Float) -> Length {
Au((px_value * AU_PER_PX) as Integer)
}
}
#[deriving(Clone)]
pub enum LengthOrPercentage {
LP_Length(Length),
LP_Percentage(Float),
}
impl LengthOrPercentage {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Option<LengthOrPercentage> {
match input {
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
=> Length::parse_dimension(value.value, unit.as_slice()).map_move(LP_Length),
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
=> Some(LP_Percentage(value.value)),
&Number(ref value) if value.value == 0. => Some(LP_Length(Au(0))),
_ => None
}
}
#[inline]
pub fn parse(input: &ComponentValue) -> Option<LengthOrPercentage> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentage> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
}
}
#[deriving(Clone)]
pub enum LengthOrPercentageOrAuto {
LPA_Length(Length),
LPA_Percentage(Float),
LPA_Auto,
}
impl LengthOrPercentageOrAuto {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Option<LengthOrPercentageOrAuto> {
match input {
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
=> Length::parse_dimension(value.value, unit.as_slice()).map_move(LPA_Length),
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
=> Some(LPA_Percentage(value.value)),
&Number(ref value) if value.value == 0. => Some(LPA_Length(Au(0))),
&Ident(ref value) if value.eq_ignore_ascii_case("auto") => Some(LPA_Auto),
_ => None
}
}
#[inline]
pub fn parse(input: &ComponentValue) -> Option<LengthOrPercentageOrAuto> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentageOrAuto> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
}
}
}
pub mod computed {
use cssparser;
pub use CSSColor = cssparser::Color;
pub use compute_CSSColor = super::super::longhands::computed_as_specified;
use super::*;
use super::super::longhands;
pub struct Context {
current_color: cssparser::RGBA,
font_size: Length,
font_weight: longhands::font_weight::ComputedValue,
position: longhands::position::SpecifiedValue,
float: longhands::float::SpecifiedValue,
is_root_element: bool,
has_border_top: bool,
has_border_right: bool,
has_border_bottom: bool,
has_border_left: bool,
// TODO, as needed: root font size, viewport size, etc.
}
#[deriving(Clone)]
pub struct Length(Integer); // in application units
impl Length {
pub fn times(self, factor: Float) -> Length {
Length(((*self as Float) * factor) as Integer)
}
}
pub fn compute_Length(value: specified::Length, context: &Context) -> Length {
match value {
specified::Au(value) => Length(value),
specified::Em(value) => context.font_size.times(value),
specified::Ex(value) => {
let x_height = 0.5; // TODO: find that from the font
context.font_size.times(value * x_height)
},
}
}
#[deriving(Clone)]
pub enum LengthOrPercentage {
LP_Length(Length),
LP_Percentage(Float),
}
pub fn compute_LengthOrPercentage(value: specified::LengthOrPercentage, context: &Context)
-> LengthOrPercentage {
match value {
specified::LP_Length(value) => LP_Length(compute_Length(value, context)),
specified::LP_Percentage(value) => LP_Percentage(value),
}
}
#[deriving(Clone)]
pub enum LengthOrPercentageOrAuto {
LPA_Length(Length),
LPA_Percentage(Float),
LPA_Auto,
}
pub fn compute_LengthOrPercentageOrAuto(value: specified::LengthOrPercentageOrAuto,
context: &Context) -> LengthOrPercentageOrAuto {
match value {
specified::LPA_Length(value) => LPA_Length(compute_Length(value, context)),
specified::LPA_Percentage(value) => LPA_Percentage(value),
specified::LPA_Auto => LPA_Auto,
}
}
}

View file

@ -1,892 +0,0 @@
/* 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/. */
// This file is a Mako template: http://www.makotemplates.org/
use std::ascii::StrAsciiExt;
use std::at_vec;
pub use std::iterator;
pub use cssparser::*;
pub use style::errors::{ErrorLoggerIterator, log_css_error};
pub use style::parsing_utils::*;
pub use self::common_types::*;
pub mod common_types;
<%!
def to_rust_ident(name):
name = name.replace("-", "_")
if name in ["static"]: # Rust keywords
name += "_"
return name
class Longhand(object):
def __init__(self, name, is_inherited):
self.name = name
self.ident = to_rust_ident(name)
self.is_inherited = is_inherited
class Shorthand(object):
def __init__(self, name, sub_properties):
self.name = name
self.ident = to_rust_ident(name)
self.sub_properties = [LONGHANDS_BY_NAME[s] for s in sub_properties]
LONGHANDS_PER_STYLE_STRUCT = []
THIS_STYLE_STRUCT_LONGHANDS = None
LONGHANDS = []
LONGHANDS_BY_NAME = {}
SHORTHANDS = []
def new_style_struct(name):
longhands = []
LONGHANDS_PER_STYLE_STRUCT.append((name, longhands))
global THIS_STYLE_STRUCT_LONGHANDS
THIS_STYLE_STRUCT_LONGHANDS = longhands
return ""
%>
pub mod longhands {
pub use super::*;
pub use std;
pub fn computed_as_specified<T>(value: T, _context: &computed::Context) -> T { value }
<%def name="raw_longhand(name, inherited=False, no_super=False)">
<%
property = Longhand(name, inherited)
THIS_STYLE_STRUCT_LONGHANDS.append(property)
LONGHANDS.append(property)
LONGHANDS_BY_NAME[name] = property
%>
pub mod ${property.ident} {
% if not no_super:
use super::*;
% endif
${caller.body()}
pub fn parse_declared(input: &[ComponentValue])
-> Option<DeclaredValue<SpecifiedValue>> {
match CSSWideKeyword::parse(input) {
Some(Left(keyword)) => Some(CSSWideKeyword(keyword)),
Some(Right(Unset)) => Some(CSSWideKeyword(${
"Inherit" if inherited else "Initial"})),
None => parse_specified(input),
}
}
}
</%def>
<%def name="longhand(name, inherited=False, no_super=False)">
<%self:raw_longhand name="${name}" inherited="${inherited}">
${caller.body()}
pub fn parse_specified(input: &[ComponentValue])
-> Option<DeclaredValue<SpecifiedValue>> {
parse(input).map_move(super::SpecifiedValue)
}
</%self:raw_longhand>
</%def>
<%def name="single_component_value(name, inherited=False)">
<%self:longhand name="${name}" inherited="${inherited}">
${caller.body()}
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain(from_component_value)
}
</%self:longhand>
</%def>
<%def name="single_keyword(name, values, inherited=False)">
<%self:single_component_value name="${name}" inherited="${inherited}">
// The computed value is the same as the specified value.
pub use to_computed_value = super::computed_as_specified;
#[deriving(Clone)]
pub enum SpecifiedValue {
% for value in values.split():
${to_rust_ident(value)},
% endfor
}
pub type ComputedValue = SpecifiedValue;
#[inline] pub fn get_initial_value() -> ComputedValue {
${to_rust_ident(values.split()[0])}
}
pub fn from_component_value(v: &ComponentValue) -> Option<SpecifiedValue> {
do get_ident_lower(v).chain |keyword| {
match keyword.as_slice() {
% for value in values.split():
"${value}" => Some(${to_rust_ident(value)}),
% endfor
_ => None,
}
}
}
</%self:single_component_value>
</%def>
<%def name="predefined_type(name, type, initial_value, parse_method='parse', inherited=False)">
<%self:longhand name="${name}" inherited="${inherited}">
pub use to_computed_value = super::super::common_types::computed::compute_${type};
pub type SpecifiedValue = specified::${type};
pub type ComputedValue = computed::${type};
#[inline] pub fn get_initial_value() -> ComputedValue { ${initial_value} }
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain(specified::${type}::${parse_method})
}
</%self:longhand>
</%def>
// CSS 2.1, Section 8 - Box model
${new_style_struct("Margin")}
% for side in ["top", "right", "bottom", "left"]:
${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
"computed::LPA_Length(computed::Length(0))")}
% endfor
${new_style_struct("Padding")}
% for side in ["top", "right", "bottom", "left"]:
${predefined_type("padding-" + side, "LengthOrPercentage",
"computed::LP_Length(computed::Length(0))",
"parse_non_negative")}
% endfor
${new_style_struct("Border")}
% for side in ["top", "right", "bottom", "left"]:
${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
% endfor
// dotted dashed double groove ridge insed outset
${single_keyword("border-top-style", "none solid hidden")}
% for side in ["right", "bottom", "left"]:
<%self:longhand name="border-${side}-style", no_super="True">
pub use super::border_top_style::*;
pub type SpecifiedValue = super::border_top_style::SpecifiedValue;
pub type ComputedValue = super::border_top_style::ComputedValue;
</%self:longhand>
% endfor
pub fn parse_border_width(component_value: &ComponentValue) -> Option<specified::Length> {
match component_value {
&Ident(ref value) => match value.to_ascii_lower().as_slice() {
"thin" => Some(specified::Length::from_px(1.)),
"medium" => Some(specified::Length::from_px(3.)),
"thick" => Some(specified::Length::from_px(5.)),
_ => None
},
_ => specified::Length::parse_non_negative(component_value)
}
}
% for side in ["top", "right", "bottom", "left"]:
<%self:longhand name="border-${side}-width">
pub type SpecifiedValue = specified::Length;
pub type ComputedValue = computed::Length;
#[inline] pub fn get_initial_value() -> ComputedValue {
computed::Length(3 * 60) // medium
}
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain(parse_border_width)
}
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
-> ComputedValue {
if context.has_border_${side} { computed::compute_Length(value, context) }
else { computed::Length(0) }
}
</%self:longhand>
% endfor
// CSS 2.1, Section 9 - Visual formatting model
${new_style_struct("Box")}
// TODO: don't parse values we don't support
${single_keyword("display",
"inline block list-item inline-block none "
)}
// "table inline-table table-row-group table-header-group table-footer-group "
// "table-row table-column-group table-column table-cell table-caption"
${single_keyword("position", "static absolute relative fixed")}
${single_keyword("float", "none left right")}
${single_keyword("clear", "none left right both")}
// CSS 2.1, Section 10 - Visual formatting model details
${predefined_type("width", "LengthOrPercentageOrAuto",
"computed::LPA_Auto",
"parse_non_negative")}
${predefined_type("height", "LengthOrPercentageOrAuto",
"computed::LPA_Auto",
"parse_non_negative")}
<%self:single_component_value name="line-height">
#[deriving(Clone)]
pub enum SpecifiedValue {
SpecifiedNormal,
SpecifiedLength(specified::Length),
SpecifiedNumber(Float),
// percentage are the same as em.
}
/// normal | <number> | <length> | <percentage>
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
match input {
&ast::Number(ref value) if value.value >= 0.
=> Some(SpecifiedNumber(value.value)),
&ast::Percentage(ref value) if value.value >= 0.
=> Some(SpecifiedLength(specified::Em(value.value))),
&Dimension(ref value, ref unit) if value.value >= 0.
=> specified::Length::parse_dimension(value.value, unit.as_slice())
.map_move(SpecifiedLength),
&Ident(ref value) if value.eq_ignore_ascii_case("normal")
=> Some(SpecifiedNormal),
_ => None,
}
}
#[deriving(Clone)]
pub enum ComputedValue {
Normal,
Length(computed::Length),
Number(Float),
}
#[inline] pub fn get_initial_value() -> ComputedValue { Normal }
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
-> ComputedValue {
match value {
SpecifiedNormal => Normal,
SpecifiedLength(value) => Length(computed::compute_Length(value, context)),
SpecifiedNumber(value) => Number(value),
}
}
</%self:single_component_value>
// CSS 2.1, Section 11 - Visual effects
// CSS 2.1, Section 12 - Generated content, automatic numbering, and lists
// CSS 2.1, Section 13 - Paged media
// CSS 2.1, Section 14 - Colors and Backgrounds
${new_style_struct("Background")}
${predefined_type("background-color", "CSSColor",
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
${new_style_struct("Color")}
<%self:raw_longhand name="color" inherited="True">
pub use to_computed_value = super::computed_as_specified;
pub type SpecifiedValue = RGBA;
pub type ComputedValue = SpecifiedValue;
#[inline] pub fn get_initial_value() -> ComputedValue {
RGBA { red: 0., green: 0., blue: 0., alpha: 1. } /* black */
}
pub fn parse_specified(input: &[ComponentValue]) -> Option<DeclaredValue<SpecifiedValue>> {
match one_component_value(input).chain(Color::parse) {
Some(RGBA(rgba)) => Some(SpecifiedValue(rgba)),
Some(CurrentColor) => Some(CSSWideKeyword(Inherit)),
None => None,
}
}
</%self:raw_longhand>
// CSS 2.1, Section 15 - Fonts
${new_style_struct("Font")}
<%self:longhand name="font-family" inherited="True">
pub use to_computed_value = super::computed_as_specified;
#[deriving(Clone)]
enum FontFamily {
FamilyName(~str),
// Generic
// Serif,
// SansSerif,
// Cursive,
// Fantasy,
// Monospace,
}
pub type SpecifiedValue = ~[FontFamily];
pub type ComputedValue = SpecifiedValue;
#[inline] pub fn get_initial_value() -> ComputedValue { ~[FamilyName(~"serif")] }
/// <familiy-name>#
/// <familiy-name> = <string> | [ <ident>+ ]
/// TODO: <generic-familiy>
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
from_iter(input.skip_whitespace())
}
pub fn from_iter<'a>(mut iter: SkipWhitespaceIterator<'a>) -> Option<SpecifiedValue> {
let mut result = ~[];
macro_rules! add(
($value: expr) => {
{
result.push($value);
match iter.next() {
Some(&Comma) => (),
None => break 'outer,
_ => return None,
}
}
}
)
'outer: loop {
match iter.next() {
// TODO: avoid copying strings?
Some(&String(ref value)) => add!(FamilyName(value.to_owned())),
Some(&Ident(ref value)) => {
let value = value.as_slice();
match value.to_ascii_lower().as_slice() {
// "serif" => add!(Serif),
// "sans-serif" => add!(SansSerif),
// "cursive" => add!(Cursive),
// "fantasy" => add!(Fantasy),
// "monospace" => add!(Monospace),
_ => {
let mut idents = ~[value];
loop {
match iter.next() {
Some(&Ident(ref value)) => idents.push(value.as_slice()),
Some(&Comma) => {
result.push(FamilyName(idents.connect(" ")));
break
},
None => {
result.push(FamilyName(idents.connect(" ")));
break 'outer
},
_ => return None,
}
}
}
}
}
_ => return None,
}
}
Some(result)
}
</%self:longhand>
${single_keyword("font-style", "normal italic oblique", inherited=True)}
${single_keyword("font-variant", "normal", inherited=True)} // Add small-caps when supported
<%self:single_component_value name="font-weight" inherited="True">
#[deriving(Clone)]
pub enum SpecifiedValue {
Bolder,
Lighther,
% for weight in range(100, 901, 100):
SpecifiedWeight${weight},
% endfor
}
/// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
match input {
&Ident(ref value) => match value.to_ascii_lower().as_slice() {
"bold" => Some(SpecifiedWeight700),
"normal" => Some(SpecifiedWeight400),
"bolder" => Some(Bolder),
"lighter" => Some(Lighther),
_ => None,
},
&Number(ref value) => match value.int_value {
Some(100) => Some(SpecifiedWeight100),
Some(200) => Some(SpecifiedWeight200),
Some(300) => Some(SpecifiedWeight300),
Some(400) => Some(SpecifiedWeight400),
Some(500) => Some(SpecifiedWeight500),
Some(600) => Some(SpecifiedWeight600),
Some(700) => Some(SpecifiedWeight700),
Some(800) => Some(SpecifiedWeight800),
Some(900) => Some(SpecifiedWeight900),
_ => None,
},
_ => None
}
}
#[deriving(Clone)]
pub enum ComputedValue {
% for weight in range(100, 901, 100):
Weight${weight},
% endfor
}
#[inline] pub fn get_initial_value() -> ComputedValue { Weight400 } // normal
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
-> ComputedValue {
match value {
% for weight in range(100, 901, 100):
SpecifiedWeight${weight} => Weight${weight},
% endfor
Bolder => match context.font_weight {
Weight100 => Weight400,
Weight200 => Weight400,
Weight300 => Weight400,
Weight400 => Weight700,
Weight500 => Weight700,
Weight600 => Weight900,
Weight700 => Weight900,
Weight800 => Weight900,
Weight900 => Weight900,
},
Lighther => match context.font_weight {
Weight100 => Weight100,
Weight200 => Weight100,
Weight300 => Weight100,
Weight400 => Weight100,
Weight500 => Weight100,
Weight600 => Weight400,
Weight700 => Weight400,
Weight800 => Weight700,
Weight900 => Weight700,
},
}
}
</%self:single_component_value>
<%self:single_component_value name="font-size" inherited="True">
pub use to_computed_value = super::super::common_types::computed::compute_Length;
pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
pub type ComputedValue = computed::Length;
#[inline] pub fn get_initial_value() -> ComputedValue {
computed::Length(16 * 60) // medium
}
/// <length> | <percentage>
/// TODO: support <absolute-size> and <relative-size>
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
do specified::LengthOrPercentage::parse_non_negative(input).map_move |value| {
match value {
specified::LP_Length(value) => value,
specified::LP_Percentage(value) => specified::Em(value),
}
}
}
</%self:single_component_value>
// CSS 2.1, Section 16 - Text
${new_style_struct("Text")}
// TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.)
${single_keyword("text-align", "left right center justify", inherited=True)}
<%self:longhand name="text-decoration">
pub use to_computed_value = super::computed_as_specified;
#[deriving(Clone)]
pub struct SpecifiedValue {
underline: bool,
overline: bool,
line_through: bool,
// 'blink' is accepted in the parser but ignored.
// Just not blinking the text is a conforming implementation per CSS 2.1.
}
pub type ComputedValue = SpecifiedValue;
#[inline] pub fn get_initial_value() -> ComputedValue {
SpecifiedValue { underline: false, overline: false, line_through: false } // none
}
/// none | [ underline || overline || line-through || blink ]
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
let mut result = SpecifiedValue {
underline: false, overline: false, line_through: false,
};
let mut blink = false;
let mut empty = true;
for component_value in input.skip_whitespace() {
match get_ident_lower(component_value) {
None => return None,
Some(keyword) => match keyword.as_slice() {
"underline" => if result.underline { return None }
else { empty = false; result.underline = true },
"overline" => if result.overline { return None }
else { empty = false; result.overline = true },
"line-through" => if result.line_through { return None }
else { empty = false; result.line_through = true },
"blink" => if blink { return None }
else { empty = false; blink = true },
"none" => return if empty { Some(result) } else { None },
_ => return None,
}
}
}
if !empty { Some(result) } else { None }
}
</%self:longhand>
// CSS 2.1, Section 17 - Tables
// CSS 2.1, Section 18 - User interface
}
pub mod shorthands {
pub use super::*;
pub use super::longhands::*;
<%def name="shorthand(name, sub_properties)">
<%
shorthand = Shorthand(name, sub_properties.split())
SHORTHANDS.append(shorthand)
%>
pub mod ${shorthand.ident} {
use super::*;
struct Longhands {
% for sub_property in shorthand.sub_properties:
${sub_property.ident}: Option<${sub_property.ident}::SpecifiedValue>,
% endfor
}
pub fn parse(input: &[ComponentValue]) -> Option<Longhands> {
${caller.body()}
}
}
</%def>
<%def name="four_sides_shorthand(name, sub_property_pattern, parser_function)">
<%self:shorthand name="${name}" sub_properties="${
' '.join(sub_property_pattern % side
for side in ['top', 'right', 'bottom', 'left'])}">
let mut iter = input.skip_whitespace().map(${parser_function});
// zero or more than four values is invalid.
// one value sets them all
// two values set (top, bottom) and (left, right)
// three values set top, (left, right) and bottom
// four values set them in order
let top = iter.next().unwrap_or_default(None);
let right = iter.next().unwrap_or_default(top);
let bottom = iter.next().unwrap_or_default(top);
let left = iter.next().unwrap_or_default(right);
if top.is_some() && right.is_some() && bottom.is_some() && left.is_some()
&& iter.next().is_none() {
Some(Longhands {
% for side in ["top", "right", "bottom", "left"]:
${to_rust_ident(sub_property_pattern % side)}: ${side},
% endfor
})
} else {
None
}
</%self:shorthand>
</%def>
// TODO: other background-* properties
<%self:shorthand name="background" sub_properties="background-color">
do one_component_value(input).chain(specified::CSSColor::parse).map_move |color| {
Longhands { background_color: Some(color) }
}
</%self:shorthand>
${four_sides_shorthand("border-color", "border-%s-color", "specified::CSSColor::parse")}
${four_sides_shorthand("border-style", "border-%s-style",
"border_top_style::from_component_value")}
${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")}
pub fn parse_border(input: &[ComponentValue])
-> Option<(Option<specified::CSSColor>,
Option<border_top_style::SpecifiedValue>,
Option<specified::Length>)> {
let mut color = None;
let mut style = None;
let mut width = None;
let mut any = false;
for component_value in input.skip_whitespace() {
if color.is_none() {
match specified::CSSColor::parse(component_value) {
Some(c) => { color = Some(c); any = true; loop },
None => ()
}
}
if style.is_none() {
match border_top_style::from_component_value(component_value) {
Some(s) => { style = Some(s); any = true; loop },
None => ()
}
}
if width.is_none() {
match parse_border_width(component_value) {
Some(w) => { width = Some(w); any = true; loop },
None => ()
}
}
return None
}
if any { Some((color, style, width)) } else { None }
}
% for side in ["top", "right", "bottom", "left"]:
<%self:shorthand name="border-${side}" sub_properties="${' '.join(
'border-%s-%s' % (side, prop)
for prop in ['color', 'style', 'width']
)}">
do parse_border(input).map_move |(color, style, width)| {
Longhands {
% for prop in ["color", "style", "width"]:
${"border_%s_%s: %s," % (side, prop, prop)}
% endfor
}
}
</%self:shorthand>
% endfor
<%self:shorthand name="border" sub_properties="${' '.join(
'border-%s-%s' % (side, prop)
for side in ['top', 'right', 'bottom', 'left']
for prop in ['color', 'style', 'width']
)}">
do parse_border(input).map_move |(color, style, width)| {
Longhands {
% for side in ["top", "right", "bottom", "left"]:
% for prop in ["color", "style", "width"]:
${"border_%s_%s: %s," % (side, prop, prop)}
% endfor
% endfor
}
}
</%self:shorthand>
}
pub struct PropertyDeclarationBlock {
important: @[PropertyDeclaration],
normal: @[PropertyDeclaration],
}
pub fn parse_property_declaration_list(input: ~[Node]) -> PropertyDeclarationBlock {
let mut important = ~[];
let mut normal = ~[];
for item in ErrorLoggerIterator(parse_declaration_list(input.move_iter())) {
match item {
Decl_AtRule(rule) => log_css_error(
rule.location, fmt!("Unsupported at-rule in declaration list: @%s", rule.name)),
Declaration(Declaration{ location: l, name: n, value: v, important: i}) => {
// TODO: only keep the last valid declaration for a given name.
let list = if i { &mut important } else { &mut normal };
if !PropertyDeclaration::parse(n, v, list) {
log_css_error(l, "Invalid property declaration")
}
}
}
}
PropertyDeclarationBlock {
// TODO avoid copying?
important: at_vec::to_managed_move(important),
normal: at_vec::to_managed_move(normal),
}
}
#[deriving(Clone)]
pub enum CSSWideKeyword {
Initial,
Inherit,
}
struct Unset;
impl CSSWideKeyword {
pub fn parse(input: &[ComponentValue]) -> Option<Either<CSSWideKeyword, Unset>> {
do one_component_value(input).chain(get_ident_lower).chain |keyword| {
match keyword.as_slice() {
"initial" => Some(Left(Initial)),
"inherit" => Some(Left(Inherit)),
"unset" => Some(Right(Unset)),
_ => None
}
}
}
}
#[deriving(Clone)]
pub enum DeclaredValue<T> {
SpecifiedValue(T),
CSSWideKeyword(CSSWideKeyword),
}
pub enum PropertyDeclaration {
% for property in LONGHANDS:
${property.ident}_declaration(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
% endfor
}
impl PropertyDeclaration {
pub fn parse(name: &str, value: &[ComponentValue],
result_list: &mut ~[PropertyDeclaration]) -> bool {
match name.to_ascii_lower().as_slice() {
% for property in LONGHANDS:
"${property.name}" => result_list.push(${property.ident}_declaration(
match longhands::${property.ident}::parse_declared(value) {
Some(value) => value,
None => return false,
}
)),
% endfor
% for shorthand in SHORTHANDS:
"${shorthand.name}" => match CSSWideKeyword::parse(value) {
Some(Left(keyword)) => {
% for sub_property in shorthand.sub_properties:
result_list.push(${sub_property.ident}_declaration(
CSSWideKeyword(keyword)
));
% endfor
},
Some(Right(Unset)) => {
% for sub_property in shorthand.sub_properties:
result_list.push(${sub_property.ident}_declaration(
CSSWideKeyword(${
"Inherit" if sub_property.is_inherited else "Initial"})
));
% endfor
},
None => match shorthands::${shorthand.ident}::parse(value) {
Some(result) => {
% for sub_property in shorthand.sub_properties:
result_list.push(${sub_property.ident}_declaration(
match result.${sub_property.ident} {
Some(value) => SpecifiedValue(value),
None => CSSWideKeyword(Initial),
}
));
% endfor
},
None => return false,
}
},
% endfor
_ => return false, // Unknown property
}
true
}
}
pub mod style_structs {
use super::longhands;
% for name, longhands in LONGHANDS_PER_STYLE_STRUCT:
pub struct ${name} {
% for longhand in longhands:
${longhand.ident}: longhands::${longhand.ident}::ComputedValue,
% endfor
}
% endfor
}
pub struct ComputedValues {
% for name, longhands in LONGHANDS_PER_STYLE_STRUCT:
${name}: style_structs::${name},
% endfor
}
#[inline]
fn get_initial_values() -> ComputedValues {
ComputedValues {
% for style_struct, longhands in LONGHANDS_PER_STYLE_STRUCT:
${style_struct}: style_structs::${style_struct} {
% for longhand in longhands:
${longhand.ident}: longhands::${longhand.ident}::get_initial_value(),
% endfor
},
% endfor
}
}
// Most specific/important declarations last
pub fn cascade(applicable_declarations: &[@[PropertyDeclaration]],
parent_style: Option< &ComputedValues>)
-> ComputedValues {
let initial_keep_alive;
let (parent_style, is_root_element) = match parent_style {
Some(s) => (s, false),
None => {
initial_keep_alive = ~get_initial_values();
(&*initial_keep_alive, true)
}
};
struct AllDeclaredValues {
% for property in LONGHANDS:
${property.ident}: DeclaredValue<longhands::${property.ident}::SpecifiedValue>,
% endfor
}
let mut specified = AllDeclaredValues {
% for property in LONGHANDS:
${property.ident}: CSSWideKeyword(${
"Inherit" if property.is_inherited else "Initial"}),
% endfor
};
for sub_list in applicable_declarations.iter() {
for declaration in sub_list.iter() {
match declaration {
% for property in LONGHANDS:
&${property.ident}_declaration(ref value) => {
// Overwrite earlier declarations.
// TODO: can we avoid a copy?
specified.${property.ident} = (*value).clone()
}
% endfor
}
}
}
// This assumes that the computed and specified values have the same Rust type.
macro_rules! get_specified(
($style_struct: ident, $property: ident) => {
match specified.$property {
SpecifiedValue(value) => value,
CSSWideKeyword(Initial) => longhands::$property::get_initial_value(),
CSSWideKeyword(Inherit) => parent_style.$style_struct.$property.clone(),
}
};
)
macro_rules! has_border(
($property: ident) => {
match get_specified!(Border, $property) {
longhands::border_top_style::none
| longhands::border_top_style::hidden => false,
_ => true,
}
};
)
let context = &mut computed::Context {
current_color: get_specified!(Color, color),
font_size: parent_style.Font.font_size,
font_weight: parent_style.Font.font_weight,
position: get_specified!(Box, position),
float: get_specified!(Box, float),
is_root_element: is_root_element,
has_border_top: has_border!(border_top_style),
has_border_right: has_border!(border_right_style),
has_border_bottom: has_border!(border_bottom_style),
has_border_left: has_border!(border_left_style),
};
macro_rules! get_computed(
($style_struct: ident, $property: ident) => {
match specified.$property {
SpecifiedValue(ref value)
// TODO: avoid a copy?
=> longhands::$property::to_computed_value(value.clone(), context),
CSSWideKeyword(Initial) => longhands::$property::get_initial_value(),
CSSWideKeyword(Inherit) => parent_style.$style_struct.$property.clone(),
}
};
)
context.font_size = get_computed!(Font, font_size);
ComputedValues {
% for style_struct, longhands in LONGHANDS_PER_STYLE_STRUCT:
${style_struct}: style_structs::${style_struct} {
% for longhand in longhands:
${longhand.ident}: get_computed!(${style_struct}, ${longhand.ident}),
% endfor
},
% endfor
}
}

View file

@ -1,245 +0,0 @@
/* 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::ascii::StrAsciiExt;
use extra::sort::tim_sort;
use style::selectors::*;
use style::stylesheets::parse_stylesheet;
use style::media_queries::{Device, Screen};
use style::properties::{ComputedValues, cascade, PropertyDeclaration};
use dom::node::{AbstractNode, ScriptView};
use dom::element::Element;
pub enum StylesheetOrigin {
UserAgentOrigin,
AuthorOrigin,
UserOrigin,
}
pub struct Stylist {
priv ua_rules: PerOriginRules,
priv author_rules: PerOriginRules,
priv user_rules: PerOriginRules,
}
impl Stylist {
#[inline]
pub fn new() -> Stylist {
Stylist {
ua_rules: PerOriginRules::new(),
author_rules: PerOriginRules::new(),
user_rules: PerOriginRules::new(),
}
}
pub fn add_stylesheet(&mut self, css_source: &str, origin: StylesheetOrigin) {
let stylesheet = parse_stylesheet(css_source);
let rules = match origin {
UserAgentOrigin => &mut self.ua_rules,
AuthorOrigin => &mut self.author_rules,
UserOrigin => &mut self.user_rules,
};
let mut added_normal_declarations = false;
let mut added_important_declarations = false;
macro_rules! append(
($priority: ident, $flag: ident) => {
if style_rule.declarations.$priority.len() > 0 {
$flag = true;
for selector in style_rule.selectors.iter() {
rules.$priority.push(Rule {
selector: *selector,
declarations: style_rule.declarations.$priority,
})
}
}
};
)
let device = &Device { media_type: Screen }; // TODO, use Print when printing
for style_rule in stylesheet.iter_style_rules(device) {
append!(normal, added_normal_declarations);
append!(important, added_important_declarations);
}
// These sorts need to be stable
// Do not sort already-sorted unchanged vectors
if added_normal_declarations {
tim_sort(rules.normal)
}
if added_important_declarations {
tim_sort(rules.important)
}
}
pub fn get_computed_style(&self, element: AbstractNode<ScriptView>,
parent_style: Option<&ComputedValues>,
pseudo_element: Option<PseudoElement>)
-> ComputedValues {
assert!(element.is_element())
// Only the root does not inherit.
// The root has no parent or a non-element parent.
assert_eq!(
parent_style.is_none(),
match element.parent_node() {
None => true,
Some(ref node) => !node.is_element()
}
);
let mut applicable_declarations = ~[]; // TODO: use an iterator?
macro_rules! append(
($rules: expr) => {
for rule in $rules.iter() {
if matches_selector(rule.selector, element, pseudo_element) {
applicable_declarations.push(rule.declarations)
}
}
};
);
// In cascading order
append!(self.ua_rules.normal);
append!(self.user_rules.normal);
append!(self.author_rules.normal);
// TODO add style attribute
append!(self.author_rules.important);
append!(self.user_rules.important);
append!(self.ua_rules.important);
cascade(applicable_declarations, parent_style)
}
}
struct PerOriginRules {
normal: ~[Rule],
important: ~[Rule],
}
impl PerOriginRules {
#[inline]
fn new() -> PerOriginRules {
PerOriginRules { normal: ~[], important: ~[] }
}
}
#[deriving(Clone)]
struct Rule {
selector: @Selector,
declarations: @[PropertyDeclaration],
}
impl Ord for Rule {
#[inline]
fn lt(&self, other: &Rule) -> bool {
self.selector.specificity < other.selector.specificity
}
}
#[inline]
fn matches_selector(selector: &Selector, element: AbstractNode<ScriptView>,
pseudo_element: Option<PseudoElement>) -> bool {
selector.pseudo_element == pseudo_element &&
matches_compound_selector(&selector.compound_selectors, element)
}
fn matches_compound_selector(selector: &CompoundSelector,
element: AbstractNode<ScriptView>) -> bool {
if do element.with_imm_element |element| {
!do selector.simple_selectors.iter().all |simple_selector| {
matches_simple_selector(simple_selector, element)
}
} {
return false
}
match selector.next {
None => true,
Some((ref next_selector, combinator)) => {
let (siblings, just_one) = match combinator {
Child => (false, true),
Descendant => (false, false),
NextSibling => (true, true),
LaterSibling => (true, false),
};
let mut node = element;
loop {
match if siblings { node.prev_sibling() } else { node.parent_node() } {
None => return false,
Some(next_node) => node = next_node,
}
if node.is_element() {
if matches_compound_selector(&**next_selector, node) {
return true
} else if just_one {
return false
}
}
}
}
}
}
#[inline]
fn matches_simple_selector(selector: &SimpleSelector, element: &Element) -> bool {
static WHITESPACE: &'static [char] = &'static [' ', '\t', '\n', '\r', '\x0C'];
match *selector {
// TODO: case-sensitivity depends on the document type
LocalNameSelector(ref name) => element.tag_name.eq_ignore_ascii_case(name.as_slice()),
NamespaceSelector(_) => false, // TODO, when the DOM supports namespaces on elements.
// TODO: case-sensitivity depends on the document type and quirks mode
// TODO: cache and intern IDs on elements.
IDSelector(ref id) => element.get_attr("id") == Some(id.as_slice()),
// TODO: cache and intern classe names on elements.
ClassSelector(ref class) => match element.get_attr("class") {
None => false,
// TODO: case-sensitivity depends on the document type and quirks mode
Some(ref class_attr)
=> class_attr.split_iter(WHITESPACE).any(|c| c == class.as_slice()),
},
AttrExists(ref attr) => match_attribute(attr, element, |_| true),
AttrEqual(ref attr, ref value) => match_attribute(attr, element, |v| v == value.as_slice()),
AttrIncludes(ref attr, ref value) => do match_attribute(attr, element) |attr_value| {
attr_value.split_iter(WHITESPACE).any(|v| v == value.as_slice())
},
AttrDashMatch(ref attr, ref value, ref dashing_value)
=> do match_attribute(attr, element) |attr_value| {
attr_value == value.as_slice() || attr_value.starts_with(dashing_value.as_slice())
},
AttrPrefixMatch(ref attr, ref value) => do match_attribute(attr, element) |attr_value| {
attr_value.starts_with(value.as_slice())
},
AttrSubstringMatch(ref attr, ref value) => do match_attribute(attr, element) |attr_value| {
attr_value.contains(value.as_slice())
},
AttrSuffixMatch(ref attr, ref value) => do match_attribute(attr, element) |attr_value| {
attr_value.ends_with(value.as_slice())
},
Negation(ref negated) => {
!negated.iter().all(|s| matches_simple_selector(s, element))
},
}
}
#[inline]
fn match_attribute(attr: &AttrSelector, element: &Element, f: &fn(&str)-> bool) -> bool {
match attr.namespace {
Some(_) => false, // TODO, when the DOM supports namespaces on attributes
None => match element.get_attr(attr.name) {
None => false,
Some(ref value) => f(value.as_slice())
}
}
}

View file

@ -1,485 +0,0 @@
/* 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::{vec, iterator};
use std::ascii::StrAsciiExt;
use cssparser::*;
use style::namespaces::NamespaceMap;
pub struct Selector {
compound_selectors: CompoundSelector,
pseudo_element: Option<PseudoElement>,
specificity: u32,
}
pub static STYLE_ATTRIBUTE_SPECIFICITY: u32 = 1 << 31;
#[deriving(Eq)]
pub enum PseudoElement {
Before,
After,
FirstLine,
FirstLetter,
}
pub struct CompoundSelector {
simple_selectors: ~[SimpleSelector],
next: Option<(~CompoundSelector, Combinator)>, // c.next is left of c
}
pub enum Combinator {
Child, // >
Descendant, // space
NextSibling, // +
LaterSibling, // ~
}
pub enum SimpleSelector {
IDSelector(~str),
ClassSelector(~str),
LocalNameSelector(~str),
NamespaceSelector(~str),
// Attribute selectors
AttrExists(AttrSelector), // [foo]
AttrEqual(AttrSelector, ~str), // [foo=bar]
AttrIncludes(AttrSelector, ~str), // [foo~=bar]
AttrDashMatch(AttrSelector, ~str, ~str), // [foo|=bar] Second string is the first + "-"
AttrPrefixMatch(AttrSelector, ~str), // [foo^=bar]
AttrSubstringMatch(AttrSelector, ~str), // [foo*=bar]
AttrSuffixMatch(AttrSelector, ~str), // [foo$=bar]
// Pseudo-classes
// Empty,
// Root,
// Lang(~str),
// NthChild(i32, i32),
Negation(~[SimpleSelector]),
// ...
}
pub struct AttrSelector {
name: ~str,
namespace: Option<~str>,
}
type Iter = iterator::Peekable<ComponentValue, vec::MoveIterator<ComponentValue>>;
// None means invalid selector
pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap)
-> Option<~[@Selector]> {
let iter = &mut input.move_iter().peekable();
let first = match parse_selector(iter, namespaces) {
None => return None,
Some(result) => result
};
let mut results = ~[first];
loop {
skip_whitespace(iter);
match iter.peek() {
None => break, // EOF
Some(&Comma) => (),
_ => return None,
}
match parse_selector(iter, namespaces) {
Some(selector) => results.push(selector),
None => return None,
}
}
Some(results)
}
// None means invalid selector
fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap)
-> Option<@Selector> {
let (first, pseudo_element) = match parse_simple_selectors(iter, namespaces) {
None => return None,
Some(result) => result
};
let mut compound = CompoundSelector{ simple_selectors: first, next: None };
let mut pseudo_element = pseudo_element;
while pseudo_element.is_none() {
let any_whitespace = skip_whitespace(iter);
let combinator = match iter.peek() {
None => break, // EOF
Some(&Delim('>')) => { iter.next(); Child },
Some(&Delim('+')) => { iter.next(); NextSibling },
Some(&Delim('~')) => { iter.next(); LaterSibling },
Some(_) => {
if any_whitespace { Descendant }
else { return None }
}
};
match parse_simple_selectors(iter, namespaces) {
None => return None,
Some((simple_selectors, pseudo)) => {
compound = CompoundSelector {
simple_selectors: simple_selectors,
next: Some((~compound, combinator))
};
pseudo_element = pseudo;
}
}
}
Some(@Selector {
specificity: compute_specificity(&compound, &pseudo_element),
compound_selectors: compound,
pseudo_element: pseudo_element,
})
}
fn compute_specificity(mut selector: &CompoundSelector,
pseudo_element: &Option<PseudoElement>) -> u32 {
struct Specificity {
id_selectors: u32,
class_like_selectors: u32,
element_selectors: u32,
}
let mut specificity = Specificity {
id_selectors: 0,
class_like_selectors: 0,
element_selectors: 0,
};
if pseudo_element.is_some() { specificity.element_selectors += 1 }
simple_selectors_specificity(selector.simple_selectors, &mut specificity);
loop {
match selector.next {
None => break,
Some((ref next_selector, _)) => {
selector = &**next_selector;
simple_selectors_specificity(selector.simple_selectors, &mut specificity)
}
}
}
fn simple_selectors_specificity(simple_selectors: &[SimpleSelector],
specificity: &mut Specificity) {
for simple_selector in simple_selectors.iter() {
match simple_selector {
&LocalNameSelector(*) => specificity.element_selectors += 1,
&IDSelector(*) => specificity.id_selectors += 1,
&ClassSelector(*)
| &AttrExists(*) | &AttrEqual(*) | &AttrIncludes(*) | &AttrDashMatch(*)
| &AttrPrefixMatch(*) | &AttrSubstringMatch(*) | &AttrSuffixMatch(*)
// | &Empty | &Root | &Lang(*) | &NthChild(*)
=> specificity.class_like_selectors += 1,
&NamespaceSelector(*) => (),
&Negation(ref negated)
=> simple_selectors_specificity(negated.as_slice(), specificity),
}
}
}
static MAX_10BIT: u32 = (1u32 << 10) - 1;
specificity.id_selectors.min(&MAX_10BIT) << 20
| specificity.class_like_selectors.min(&MAX_10BIT) << 10
| specificity.id_selectors.min(&MAX_10BIT)
}
// None means invalid selector
fn parse_simple_selectors(iter: &mut Iter, namespaces: &NamespaceMap)
-> Option<(~[SimpleSelector], Option<PseudoElement>)> {
let mut empty = true;
let mut simple_selectors = match parse_type_selector(iter, namespaces) {
None => return None, // invalid selector
Some(None) => ~[],
Some(Some(s)) => { empty = false; s }
};
let mut pseudo_element = None;
loop {
match parse_one_simple_selector(iter, namespaces, /* inside_negation = */ false) {
None => return None, // invalid selector
Some(None) => break,
Some(Some(Left(s))) => simple_selectors.push(s),
Some(Some(Right(p))) => { pseudo_element = Some(p); break },
}
}
if empty { None } // An empty selector is invalid
else { Some((simple_selectors, pseudo_element)) }
}
// None means invalid selector
// Some(None) means no type selector.
// Some(Some(~[...])) is a type selector. Might be empty for *|*
fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
-> Option<Option<~[SimpleSelector]>> {
skip_whitespace(iter);
match parse_qualified_name(iter, /* allow_universal = */ true, namespaces) {
None => None, // invalid selector
Some(None) => Some(None),
Some(Some((namespace, local_name))) => {
let mut simple_selectors = ~[];
match namespace {
Some(url) => simple_selectors.push(NamespaceSelector(url)),
None => (),
}
match local_name {
Some(name) => simple_selectors.push(LocalNameSelector(name)),
None => (),
}
Some(Some(simple_selectors))
}
}
}
// Parse a simple selector other than a type selector
// None means invalid selector
// Some(None) means not a simple name
// Some(Some(Left(s)) is a simple selector
// Some(Some(Right(p)) is a pseudo-element
fn parse_one_simple_selector(iter: &mut Iter, namespaces: &NamespaceMap, inside_negation: bool)
-> Option<Option<Either<SimpleSelector, PseudoElement>>> {
match iter.peek() {
Some(&IDHash(_)) => match iter.next() {
Some(IDHash(id)) => Some(Some(Left(IDSelector(id)))),
_ => fail!("Implementation error, this should not happen."),
},
Some(&Delim('.')) => {
iter.next();
match iter.next() {
Some(Ident(class)) => Some(Some(Left(ClassSelector(class)))),
_ => None, // invalid selector
}
}
Some(&SquareBracketBlock(_)) => match iter.next() {
Some(SquareBracketBlock(content))
=> match parse_attribute_selector(content, namespaces) {
None => None,
Some(simple_selector) => Some(Some(Left(simple_selector))),
},
_ => fail!("Implementation error, this should not happen."),
},
Some(&Delim(':')) => {
iter.next();
match iter.next() {
Some(Ident(name)) => match parse_simple_pseudo_class(name) {
None => None,
Some(result) => Some(Some(result)),
},
Some(Function(name, arguments)) => match parse_functional_pseudo_class(
name, arguments, namespaces, inside_negation) {
None => None,
Some(simple_selector) => Some(Some(Left(simple_selector))),
},
Some(Delim(':')) => {
match iter.next() {
Some(Ident(name)) => match parse_pseudo_element(name) {
Some(pseudo_element) => Some(Some(Right(pseudo_element))),
_ => None,
},
_ => None,
}
}
_ => None,
}
}
_ => Some(None),
}
}
// None means invalid selector
// Some(None) means not a qualified name
// Some(Some((None, None)) means *|*
// Some(Some((Some(url), None)) means prefix|*
// Some(Some((None, Some(name)) means *|name
// Some(Some((Some(url), Some(name))) means prefix|name
// ... or equivalent
fn parse_qualified_name(iter: &mut Iter, allow_universal: bool, namespaces: &NamespaceMap)
-> Option<Option<(Option<~str>, Option<~str>)>> {
#[inline]
fn default_namespace(namespaces: &NamespaceMap, local_name: Option<~str>)
-> Option<Option<(Option<~str>, Option<~str>)>> {
Some(Some((namespaces.default.map(|url| url.to_owned()), local_name)))
}
#[inline]
fn explicit_namespace(iter: &mut Iter, allow_universal: bool, namespace_url: Option<~str>)
-> Option<Option<(Option<~str>, Option<~str>)>> {
assert!(iter.next() == Some(Delim('|')),
"Implementation error, this should not happen.");
match iter.peek() {
Some(&Delim('*')) if allow_universal => {
iter.next();
Some(Some((namespace_url, None)))
},
Some(&Ident(_)) => {
let local_name = get_next_ident(iter);
Some(Some((namespace_url, Some(local_name))))
},
_ => None, // invalid selector
}
}
match iter.peek() {
Some(&Ident(_)) => {
let value = get_next_ident(iter);
match iter.peek() {
Some(&Delim('|')) => {
let namespace_url = match namespaces.prefix_map.find(&value) {
None => return None, // Undeclared namespace prefix: invalid selector
Some(ref url) => url.to_owned(),
};
explicit_namespace(iter, allow_universal, Some(namespace_url))
},
_ => default_namespace(namespaces, Some(value)),
}
},
Some(&Delim('*')) => {
iter.next(); // Consume '*'
match iter.peek() {
Some(&Delim('|')) => explicit_namespace(iter, allow_universal, None),
_ => {
if allow_universal { default_namespace(namespaces, None) }
else { None }
},
}
},
Some(&Delim('|')) => explicit_namespace(iter, allow_universal, Some(~"")),
_ => return None,
}
}
fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMap)
-> Option<SimpleSelector> {
let iter = &mut content.move_iter().peekable();
let attr = match parse_qualified_name(iter, /* allow_universal = */ false, namespaces) {
None => return None, // invalid selector
Some(None) => return None,
Some(Some((_, None))) => fail!("Implementation error, this should not happen."),
Some(Some((namespace, Some(local_name)))) => AttrSelector {
namespace: namespace,
name: local_name,
},
};
skip_whitespace(iter);
// TODO: deal with empty value or value containing whitespace (see spec)
macro_rules! get_value( () => {{
skip_whitespace(iter);
match iter.next() {
Some(Ident(value)) | Some(String(value)) => value,
_ => return None,
}
}};)
let result = match iter.next() {
None => AttrExists(attr), // [foo]
Some(Delim('=')) => AttrEqual(attr, get_value!()), // [foo=bar]
Some(IncludeMatch) => AttrIncludes(attr, get_value!()), // [foo~=bar]
Some(DashMatch) => {
let value = get_value!();
let dashing_value = value + "-";
AttrDashMatch(attr, value, dashing_value) // [foo|=bar]
},
Some(PrefixMatch) => AttrPrefixMatch(attr, get_value!()), // [foo^=bar]
Some(SubstringMatch) => AttrSubstringMatch(attr, get_value!()), // [foo*=bar]
Some(SuffixMatch) => AttrSuffixMatch(attr, get_value!()), // [foo$=bar]
_ => return None
};
skip_whitespace(iter);
if iter.next().is_none() { Some(result) } else { None }
}
fn parse_simple_pseudo_class(name: ~str) -> Option<Either<SimpleSelector, PseudoElement>> {
match name.to_ascii_lower().as_slice() {
// "root" => Some(Left(Root)),
// "empty" => Some(Left(Empty)),
// Supported CSS 2.1 pseudo-elements only.
"before" => Some(Right(Before)),
"after" => Some(Right(After)),
"first-line" => Some(Right(FirstLine)),
"first-letter" => Some(Right(FirstLetter)),
_ => None
}
}
fn parse_functional_pseudo_class(name: ~str, arguments: ~[ComponentValue],
namespaces: &NamespaceMap, inside_negation: bool)
-> Option<SimpleSelector> {
match name.to_ascii_lower().as_slice() {
// "lang" => parse_lang(arguments),
// "nth-child" => parse_nth(arguments).map(|&(a, b)| NthChild(a, b)),
"not" => if inside_negation { None } else { parse_negation(arguments, namespaces) },
_ => None
}
}
fn parse_pseudo_element(name: ~str) -> Option<PseudoElement> {
match name.to_ascii_lower().as_slice() {
// All supported pseudo-elements
"before" => Some(Before),
"after" => Some(After),
"first-line" => Some(FirstLine),
"first-letter" => Some(FirstLetter),
_ => None
}
}
//fn parse_lang(arguments: ~[ComponentValue]) -> Option<SimpleSelector> {
// let mut iter = arguments.move_skip_whitespace();
// match iter.next() {
// Some(Ident(value)) => {
// if "" == value || iter.next().is_some() { None }
// else { Some(Lang(value)) }
// },
// _ => None,
// }
//}
// Level 3: Parse ONE simple_selector
fn parse_negation(arguments: ~[ComponentValue], namespaces: &NamespaceMap)
-> Option<SimpleSelector> {
let iter = &mut arguments.move_iter().peekable();
Some(Negation(match parse_type_selector(iter, namespaces) {
None => return None, // invalid selector
Some(Some(s)) => s,
Some(None) => {
match parse_one_simple_selector(iter, namespaces, /* inside_negation = */ true) {
Some(Some(Left(s))) => ~[s],
_ => return None
}
},
}))
}
/// Assuming the next token is an ident, consume it and return its value
#[inline]
fn get_next_ident(iter: &mut Iter) -> ~str {
match iter.next() {
Some(Ident(value)) => value,
_ => fail!("Implementation error, this should not happen."),
}
}
#[inline]
fn skip_whitespace(iter: &mut Iter) -> bool {
let mut any_whitespace = false;
loop {
if iter.peek() != Some(&WhiteSpace) { return any_whitespace }
any_whitespace = true;
iter.next();
}
}

View file

@ -1,153 +0,0 @@
/* 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::iterator::Iterator;
use std::ascii::StrAsciiExt;
use cssparser::*;
use style::selectors;
use style::properties;
use style::errors::{ErrorLoggerIterator, log_css_error};
use style::namespaces::{NamespaceMap, parse_namespace_rule};
use style::media_queries::{MediaRule, parse_media_rule};
use style::media_queries;
pub struct Stylesheet {
rules: ~[CSSRule],
namespaces: NamespaceMap,
}
pub enum CSSRule {
CSSStyleRule(StyleRule),
CSSMediaRule(MediaRule),
}
pub struct StyleRule {
selectors: ~[@selectors::Selector],
declarations: properties::PropertyDeclarationBlock,
}
pub 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::new();
for rule in ErrorLoggerIterator(parse_stylesheet_rules(tokenize(css))) {
let next_state; // Unitialized to force each branch to set it.
match rule {
QualifiedRule(rule) => {
next_state = STATE_BODY;
parse_style_rule(rule, &mut rules, &namespaces)
},
AtRule(rule) => {
let lower_name = rule.name.to_ascii_lower();
match lower_name.as_slice() {
"charset" => {
if state > STATE_CHARSET {
log_css_error(rule.location, "@charset must be the first rule")
}
// Valid @charset rules are just ignored
next_state = STATE_IMPORTS;
},
"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;
parse_nested_at_rule(lower_name, rule, &mut rules, &namespaces)
},
}
},
}
state = next_state;
}
Stylesheet{ rules: rules, namespaces: namespaces }
}
pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut ~[CSSRule],
namespaces: &NamespaceMap) {
let QualifiedRule{location: location, prelude: prelude, block: block} = rule;
match selectors::parse_selector_list(prelude, namespaces) {
Some(selectors) => parent_rules.push(CSSStyleRule(StyleRule{
selectors: selectors,
declarations: properties::parse_property_declaration_list(block)
})),
None => log_css_error(location, "Unsupported CSS selector."),
}
}
// lower_name is passed explicitly to avoid computing it twice.
pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule,
parent_rules: &mut ~[CSSRule], namespaces: &NamespaceMap) {
match lower_name {
"media" => parse_media_rule(rule, parent_rules, namespaces),
_ => log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", lower_name))
}
}
impl Stylesheet {
pub fn iter_style_rules<'a>(&'a self, device: &'a media_queries::Device)
-> StyleRuleIterator<'a> {
StyleRuleIterator { device: device, stack: ~[(self.rules.as_slice(), 0)] }
}
}
struct StyleRuleIterator<'self> {
device: &'self media_queries::Device,
// FIXME: I couldnt get this to borrow-check with a stack of VecIterator
stack: ~[(&'self [CSSRule], uint)],
}
impl<'self> Iterator<&'self StyleRule> for StyleRuleIterator<'self> {
fn next(&mut self) -> Option<&'self StyleRule> {
loop {
match self.stack.pop_opt() {
None => return None,
Some((rule_list, i)) => {
if i + 1 < rule_list.len() {
self.stack.push((rule_list, i + 1))
}
match rule_list[i] {
CSSStyleRule(ref rule) => return Some(rule),
CSSMediaRule(ref rule) => {
if rule.media_queries.evaluate(self.device) {
self.stack.push((rule.rules.as_slice(), 0))
}
}
}
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
/* 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 super::stylesheets::parse_stylesheet;
#[test]
fn test_bootstrap() {
// Test that parsing bootstrap does not trigger an assertion or otherwise fail.
let stylesheet = parse_stylesheet(include_str!("bootstrap-v3.0.0.css"));
assert!(stylesheet.rules.len() > 100); // This depends on whet selectors are supported.
}