servo: Merge #831 - Build the new (yet unused) style system as part of libscript, to avoid bitrot (from SimonSapin:newnewcss); r=metajack

I started this in a [separate repository](https://github.com/SimonSapin/servo-style), and imported it with [git-subtree](https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt) into `servo/src/components/script/style` after some Rust minor upgrades.

I move this into the script crate because it’s gonna both need stuff there (the content tree, for selector matching) and be needed by stuff there (the HTML parser calls the CSS parser). As far as I know, we can not have circular dependencies between crates.

Source-Repo: https://github.com/servo/servo
Source-Revision: df2906fc29b4b22be753ae18349ec0ee08e7c0e6
This commit is contained in:
Simon Sapin 2013-09-04 10:00:52 -07:00
parent 32f90658c9
commit c2c7e0ad80
19 changed files with 1791 additions and 19 deletions

View File

@ -226,7 +226,10 @@ DEPS_gfx = $(CRATE_gfx) $(SRC_gfx) $(DONE_SUBMODULES) $(DONE_util) $(DONE_net) $
RFLAGS_script = $(strip $(CFG_RUSTC_FLAGS)) $(addprefix -L $(B)src/,$(DEPS_SUBMODULES)) -L $(B)src/components/util -L $(B)src/components/net -L $(B)src/components/gfx -L $(B)src/components/msg
WEBIDL_script = $(call rwildcard,$(S)src/components/script/,*.webidl)
AUTOGEN_SRC_script = $(patsubst %.webidl, %Binding.rs, $(WEBIDL_script))
SRC_script = $(call rwildcard,$(S)src/components/script/,*.rs) $(AUTOGEN_SRC_script)
MAKO_ZIP = $(S)src/components/script/style/properties/Mako-0.8.1.zip
MAKO_script = $(S)src/components/script/style/properties/mod.rs
MAKO_SRC_script = $(MAKO_script).mako
SRC_script = $(call rwildcard,$(S)src/components/script/,*.rs) $(AUTOGEN_SRC_script) $(MAKO_script)
CRATE_script = $(S)src/components/script/script.rc
DONE_script = $(B)src/components/script/libscript.dummy
@ -287,6 +290,10 @@ $(AUTOGEN_SRC_script): %Binding.rs: $(bindinggen_dependencies) \
globalgen_dependencies := $(addprefix $(BINDINGS_SRC)/, GlobalGen.py Bindings.conf Configuration.py CodegenRust.py parser/WebIDL.py) $(CACHE_DIR)/.done
$(MAKO_script): $(MAKO_SRC_script)
PYTHONPATH=$(MAKO_ZIP) python -c "from mako.template import Template; print(Template(filename='$<').render())" > $@
$(CACHE_DIR)/.done:
mkdir -p $(CACHE_DIR)
@touch $@

View File

@ -1,4 +1,4 @@
# If this file is modified, then rust will be forcibly cleaned and then rebuilt.
# The actual contents of this file do not matter, but to trigger a change on the
# build bots then the contents should be changed so git updates the mtime.
2013-08-30
2013-09-03

View File

@ -86,8 +86,8 @@ impl ShapedGlyphData {
fn byte_offset_of_glyph(&self, i: uint) -> uint {
assert!(i < self.count);
let glyph_info_i = ptr::offset(self.glyph_infos, i as int);
unsafe {
let glyph_info_i = ptr::offset(self.glyph_infos, i as int);
(*glyph_info_i).cluster as uint
}
}

View File

@ -303,7 +303,7 @@ impl LayoutTask {
ctx: &layout_ctx,
};
let display_list = ~Cell::new(DisplayList::new::<AbstractNode<()>>());
let display_list = ~Cell::new(DisplayList::<AbstractNode<()>>::new());
// TODO: Set options on the builder before building.
// TODO: Be smarter about what needs painting.

View File

@ -1247,10 +1247,10 @@ for (uint32_t i = 0; i < length; ++i) {
dataLoc = "${declName}"
#XXXjdm conversionBehavior should be used
template = (
"match JSValConvertible::from_jsval::<%s>(${val}) {\n"
"match JSValConvertible::from_jsval(${val}) {\n"
" None => return 0,\n"
" Some(v) => %s = v\n"
"}" % (typeName, dataLoc))
"}" % (dataLoc,))
declType = CGGeneric(typeName)
if (defaultValue is not None and
# We already handled IDLNullValue, so just deal with the other ones

View File

@ -17,6 +17,7 @@ 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");
@ -140,3 +141,6 @@ 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

@ -0,0 +1,6 @@
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

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

@ -0,0 +1,124 @@
/* 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

@ -0,0 +1,11 @@
/* 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 mod stylesheets;
pub mod errors;
pub mod selectors;
pub mod properties;
pub mod namespaces;
pub mod media_queries;
pub mod parsing_utils;

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

@ -0,0 +1,21 @@
/* 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

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

View File

@ -0,0 +1,182 @@
/* 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;
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)
}
}
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)
}
}
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 = std::util::id;
use super::*;
use super::super::longhands::font_weight;
pub struct Context {
current_color: cssparser::RGBA,
has_border_top: bool,
has_border_right: bool,
has_border_bottom: bool,
has_border_left: bool,
font_size: Length,
font_weight: font_weight::ComputedValue,
// TODO, as needed: root font size, viewport size, etc.
}
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)
},
}
}
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),
}
}
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

@ -0,0 +1,687 @@
/* 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;
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):
self.name = name
self.ident = to_rust_ident(name)
class Shorthand(object):
def __init__(self, name, sub_properties):
self.name = name
self.ident = to_rust_ident(name)
self.sub_properties = [Longhand(s) for s in sub_properties]
LONGHANDS = []
SHORTHANDS = []
INHERITED = set()
%>
pub mod longhands {
pub use super::*;
pub use std;
<%def name="longhand(name, inherited=False, no_super=False)">
<%
property = Longhand(name)
LONGHANDS.append(property)
if inherited:
INHERITED.add(name)
%>
pub mod ${property.ident} {
% if not no_super:
use super::*;
% endif
${caller.body()}
}
</%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 = std::util::id;
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
% for side in ["top", "right", "bottom", "left"]:
${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
"computed::LPA_Length(computed::Length(0))")}
% endfor
% for side in ["top", "right", "bottom", "left"]:
${predefined_type("padding-" + side, "LengthOrPercentage",
"computed::LP_Length(computed::Length(0))",
"parse_non_negative")}
% endfor
% for side in ["top", "right", "bottom", "left"]:
${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
% endfor
// dotted dashed double groove ridge insed outset hidden
${single_keyword("border-top-style", "none solid")}
% 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
// 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">
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,
}
}
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
${predefined_type("background-color", "CSSColor",
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
${predefined_type("color", "CSSColor",
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 1. }) /* black */",
inherited=True)}
// CSS 2.1, Section 15 - Fonts
<%self:longhand name="font-family" inherited="True">
pub use to_computed_value = std::util::id;
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">
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
}
}
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
// 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 = std::util::id;
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}) => {
let list = if i { &mut important } else { &mut normal };
if !PropertyDeclaration::parse(n, v, list) {
log_css_error(l, "Invalid property declaration")
}
}
}
}
PropertyDeclarationBlock { important: important, normal: normal }
}
pub enum CSSWideKeyword {
Initial,
Inherit,
Unset,
}
impl CSSWideKeyword {
pub fn parse(input: &[ComponentValue]) -> Option<CSSWideKeyword> {
do one_component_value(input).chain(get_ident_lower).chain |keyword| {
match keyword.as_slice() {
"initial" => Some(Initial),
"inherit" => Some(Inherit),
"unset" => Some(Unset),
_ => None
}
}
}
}
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 CSSWideKeyword::parse(value) {
Some(keyword) => CSSWideKeyword(keyword),
None => match longhands::${property.ident}::parse(value) {
Some(value) => SpecifiedValue(value),
None => return false,
}
}
)),
% endfor
% for shorthand in SHORTHANDS:
"${shorthand.name}" => match CSSWideKeyword::parse(value) {
Some(keyword) => {
% for sub_property in shorthand.sub_properties:
result_list.push(${sub_property.ident}_declaration(
CSSWideKeyword(keyword)
));
% 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
}
}

View File

@ -0,0 +1,482 @@
/* 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;
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{lowercase_name: ~str, cased_name: ~str},
NamespaceSelector(~str),
// Attribute selectors
AttrExists(AttrSelector), // [foo]
AttrEqual(AttrSelector, ~str), // [foo=bar]
AttrIncludes(AttrSelector, ~str), // [foo~=bar]
AttrDashMatch(AttrSelector, ~str), // [foo|=bar]
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 {
lowercase_name: ~str,
cased_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;
}
}
}
let selector = Selector{
specificity: compute_specificity(&compound, &pseudo_element),
compound_selectors: compound,
pseudo_element: pseudo_element,
};
Some(selector)
}
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{
lowercase_name: name.to_ascii_lower(),
cased_name: name,
}),
None => (),
}
Some(Some(simple_selectors))
}
}
}
// Parse a simple selector other than a type selector
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>)>> {
match namespaces.default {
None => Some(Some((None, local_name))),
Some(ref url) => Some(Some((Some(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('|')));
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('|')) => default_namespace(namespaces, Some(value)),
_ => {
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))
},
}
},
Some(&Delim('*')) => {
iter.next(); // Consume '*'
match iter.peek() {
Some(&Delim('|')) => {
if allow_universal { default_namespace(namespaces, None) }
else { None }
},
_ => explicit_namespace(iter, allow_universal, 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,
lowercase_name: local_name.to_ascii_lower(),
cased_name: local_name,
},
};
skip_whitespace(iter);
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) => AttrDashMatch(attr, get_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

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

View File

@ -9,13 +9,19 @@
// and use its default methods.
macro_rules! get(
($node:expr, $fun:ident) => (
TreeNodeRef::$fun::<Node,Self>($node)
{
let val: Option<Self> = TreeNodeRef::<Node>::$fun($node);
val
}
)
)
macro_rules! set(
($node:expr, $fun:ident, $val:expr) => (
TreeNodeRef::$fun::<Node,Self>($node, $val)
{
let val: Option<Self> = $val;
TreeNodeRef::<Node>::$fun($node, val)
}
)
)
@ -31,7 +37,7 @@ impl<Node, Ref: TreeNodeRef<Node>> Iterator<Ref> for ChildIterator<Ref> {
// FIXME: Do we need two clones here?
let x = self.current.get_ref().clone();
self.current = x.with_base(|n| TreeNodeRef::next_sibling::<Node, Ref>(n));
self.current = x.with_base(|n| TreeNodeRef::<Node>::next_sibling(n));
Some(x.clone())
}
}
@ -246,51 +252,51 @@ fn gather<Node, Ref: TreeNodeRef<Node>>(cur: &Ref, refs: &mut ~[Ref],
pub trait TreeNode<Ref: TreeNodeRef<Self>> {
/// Returns the parent of this node.
fn parent_node(&self) -> Option<Ref> {
TreeNodeRef::parent_node::<Self,Ref>(self)
TreeNodeRef::<Self>::parent_node(self)
}
/// Returns the first child of this node.
fn first_child(&self) -> Option<Ref> {
TreeNodeRef::first_child::<Self,Ref>(self)
TreeNodeRef::<Self>::first_child(self)
}
/// Returns the last child of this node.
fn last_child(&self) -> Option<Ref> {
TreeNodeRef::last_child::<Self,Ref>(self)
TreeNodeRef::<Self>::last_child(self)
}
/// Returns the previous sibling of this node.
fn prev_sibling(&self) -> Option<Ref> {
TreeNodeRef::prev_sibling::<Self,Ref>(self)
TreeNodeRef::<Self>::prev_sibling(self)
}
/// Returns the next sibling of this node.
fn next_sibling(&self) -> Option<Ref> {
TreeNodeRef::next_sibling::<Self,Ref>(self)
TreeNodeRef::<Self>::next_sibling(self)
}
/// Sets the parent of this node.
fn set_parent_node(&mut self, new_parent: Option<Ref>) {
TreeNodeRef::set_parent_node::<Self,Ref>(self, new_parent)
TreeNodeRef::<Self>::set_parent_node(self, new_parent)
}
/// Sets the first child of this node.
fn set_first_child(&mut self, new_first_child: Option<Ref>) {
TreeNodeRef::set_first_child::<Self,Ref>(self, new_first_child)
TreeNodeRef::<Self>::set_first_child(self, new_first_child)
}
/// Sets the last child of this node.
fn set_last_child(&mut self, new_last_child: Option<Ref>) {
TreeNodeRef::set_last_child::<Self,Ref>(self, new_last_child)
TreeNodeRef::<Self>::set_last_child(self, new_last_child)
}
/// Sets the previous sibling of this node.
fn set_prev_sibling(&mut self, new_prev_sibling: Option<Ref>) {
TreeNodeRef::set_prev_sibling::<Self,Ref>(self, new_prev_sibling)
TreeNodeRef::<Self>::set_prev_sibling(self, new_prev_sibling)
}
/// Sets the next sibling of this node.
fn set_next_sibling(&mut self, new_next_sibling: Option<Ref>) {
TreeNodeRef::set_next_sibling::<Self,Ref>(self, new_next_sibling)
TreeNodeRef::<Self>::set_next_sibling(self, new_next_sibling)
}
}