Add PropertyDeclaration, refactor property parsing to use a Mako template.

This commit is contained in:
Simon Sapin 2013-08-22 15:37:49 +01:00
parent 8fec26174b
commit 622bc5705c
8 changed files with 647 additions and 727 deletions

2
.gitignore vendored
View file

@ -4,5 +4,7 @@
*.dylib *.dylib
*.dll *.dll
*.dummy *.dummy
*.pyc
*-test *-test
Makefile Makefile
properties/mod.rs

View file

@ -9,7 +9,8 @@ RUSTC ?= rustc
RUSTFLAGS ?= -L $(CSSPARSER_DIR) RUSTFLAGS ?= -L $(CSSPARSER_DIR)
RUST_SRC=$(shell find $(VPATH)/. -type f -name '*.rs') $(CSSPARSER_DIR)/libcssparser.dummy PROPERTIES_RS=$(VPATH)/properties/mod.rs
RUST_SRC=$(shell find $(VPATH)/. -type f -name '*.rs') $(CSSPARSER_DIR)/libcssparser.dummy $(PROPERTIES_RS)
.PHONY: all .PHONY: all
all: libservo-style.dummy all: libservo-style.dummy
@ -32,3 +33,7 @@ check-debug: servo-style-tests
.PHONY: clean .PHONY: clean
clean: clean:
rm -f *.o *.a *.so *.dylib *.dll *.dummy *-test rm -f *.o *.a *.so *.dylib *.dll *.dummy *-test
$(PROPERTIES_RS): $(PROPERTIES_RS).mako
PYTHONPATH=$(VPATH)/Mako-0.8.1.zip python -c "from mako.template import Template; print(Template(filename='$<').render())" > $@

BIN
Mako-0.8.1.zip Normal file

Binary file not shown.

View file

@ -3,9 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::*;
use parsing_utils::*;
pub type Float = f64; pub type Float = f64;
pub type Integer = i64; pub type Integer = i64;
@ -140,30 +137,3 @@ pub mod computed {
} }
} }
} }
pub enum BorderStyle {
BorderStyleSolid,
// Uncomment when supported
// BorderStyleDotted,
// BorderStyleDashed,
// BorderStyleDouble,
// BorderStyleGroove,
// BorderStyleRidge,
// BorderStyleInset,
// BorderStyleOutset,
// BorderStyleHidden,
BorderStyleNone,
}
impl BorderStyle {
pub fn parse(input: &ComponentValue) -> Option<BorderStyle> {
do get_ident_lower(input).chain |keyword| {
match keyword.as_slice() {
"solid" => Some(BorderStyleSolid),
"none" => Some(BorderStyleNone),
_ => None,
}
}
}
}

View file

@ -1,362 +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;
pub use std::iterator;
pub use std::option;
pub use cssparser::*;
pub use CSSColor = cssparser::Color;
pub use parsing_utils::*;
pub use super::common_types::*;
macro_rules! single_keyword(
($property_name: ident, $( $lower_case_keyword_string: pat => $variant: ident ),+ ) => {
pub mod $property_name {
use super::*;
pub enum SpecifiedValue {
$( $variant ),+
}
pub fn parse(input: &[ComponentValue]) -> option::Option<SpecifiedValue> {
one_component_value(input).chain(from_component_value)
}
pub fn from_component_value(v: &ComponentValue) -> option::Option<SpecifiedValue> {
do get_ident_lower(v).chain |keyword| {
match keyword.as_slice() {
$( $lower_case_keyword_string => option::Some($variant) ),+ ,
_ => option::None,
}
}
}
}
};
)
macro_rules! single_type(
($property_name: ident, $type_: ident) => {
single_type!($property_name, $type_, $type_::parse)
};
($property_name: ident, $type_: ty, $parse_function: expr) => {
pub mod $property_name {
use super::*;
pub type SpecifiedValue = $type_;
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain($parse_function)
}
}
};
)
// CSS 2.1, Section 8 - Box model
single_type!(margin_top, specified::LengthOrPercentageOrAuto,
specified::LengthOrPercentageOrAuto::parse)
single_type!(margin_right, specified::LengthOrPercentageOrAuto,
specified::LengthOrPercentageOrAuto::parse)
single_type!(margin_bottom, specified::LengthOrPercentageOrAuto,
specified::LengthOrPercentageOrAuto::parse)
single_type!(margin_left, specified::LengthOrPercentageOrAuto,
specified::LengthOrPercentageOrAuto::parse)
single_type!(padding_top, specified::LengthOrPercentage,
specified::LengthOrPercentage::parse_non_negative)
single_type!(padding_right, specified::LengthOrPercentage,
specified::LengthOrPercentage::parse_non_negative)
single_type!(padding_bottom, specified::LengthOrPercentage,
specified::LengthOrPercentage::parse_non_negative)
single_type!(padding_left, specified::LengthOrPercentage,
specified::LengthOrPercentage::parse_non_negative)
single_type!(border_top_color, CSSColor)
single_type!(border_right_color, CSSColor)
single_type!(border_bottom_color, CSSColor)
single_type!(border_left_color, CSSColor)
single_type!(border_top_style, BorderStyle)
single_type!(border_right_style, BorderStyle)
single_type!(border_bottom_style, BorderStyle)
single_type!(border_left_style, BorderStyle)
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)
}
}
single_type!(border_top_width, specified::Length, parse_border_width)
single_type!(border_right_width, specified::Length, parse_border_width)
single_type!(border_bottom_width, specified::Length, parse_border_width)
single_type!(border_left_width, specified::Length, parse_border_width)
// CSS 2.1, Section 9 - Visual formatting model
// TODO: dont parse values we dont support
single_keyword!(display,
"inline" => Inline,
"block" => Block,
"list-item" => ListItem,
"inline-block" => InlineBlock,
"table" => Table,
"inline-table" => InlineTable,
"table-row-group" => TableRowGroup,
"table-header-group" => TableHeaderGroup,
"table-footer-group" => TableFooterGroup,
"table-row" => TableRow,
"table-column-group" => TableColumnGroup,
"table-column" => TableColumn,
"table-cell" => TableCell,
"table-caption" => TableCaption,
"none" => None
)
single_keyword!(position,
"static" => Static, "absolute" => Absolute, "relative" => Relative, "fixed" => Fixed)
single_keyword!(float, "left" => Left, "right" => Right, "none" => None)
single_keyword!(clear, "left" => Left, "right" => Right, "none" => None, "both" => Both)
// CSS 2.1, Section 10 - Visual formatting model details
single_type!(width, specified::LengthOrPercentageOrAuto,
specified::LengthOrPercentageOrAuto::parse_non_negative)
single_type!(height, specified::LengthOrPercentageOrAuto,
specified::LengthOrPercentageOrAuto::parse_non_negative)
pub mod line_height {
use super::*;
pub enum SpecifiedValue {
Normal,
Length(specified::Length),
Percentage(Float),
Number(Float),
}
/// normal | <number> | <length> | <percentage>
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain(from_component_value)
}
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
match input {
&ast::Number(ref value) if value.value >= 0.
=> Some(Number(value.value)),
&ast::Percentage(ref value) if value.value >= 0.
=> Some(Percentage(value.value)),
&Dimension(ref value, ref unit) if value.value >= 0.
=> specified::Length::parse_dimension(value.value, unit.as_slice()).map_move(Length),
&Ident(ref value) if value.eq_ignore_ascii_case("auto")
=> Some(Normal),
_ => None,
}
}
}
// 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
single_type!(background_color, CSSColor)
single_type!(color, CSSColor)
// CSS 2.1, Section 15 - Fonts
pub mod font_family {
use super::*;
enum FontFamily {
FamilyName(~str),
// Generic
Serif,
SansSerif,
Cursive,
Fantasy,
Monospace,
}
pub type SpecifiedValue = ~[FontFamily];
/// <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)
}
}
single_keyword!(font_style, "normal" => Normal, "italic" => Italic, "oblique" => Oblique)
single_keyword!(font_variant,
// Uncomment when supported
//"small-caps" => SmallCaps,
"normal" => Normal
)
pub mod font_weight {
use super::*;
pub enum SpecifiedValue {
Bolder,
Lighther,
Weight100,
Weight200,
Weight300,
Weight400,
Weight500,
Weight600,
Weight700,
Weight800,
Weight900,
}
/// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain(from_component_value)
}
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
match input {
&Ident(ref value) => match value.to_ascii_lower().as_slice() {
"bold" => Some(Weight700),
"normal" => Some(Weight400),
"bolder" => Some(Bolder),
"lighter" => Some(Lighther),
_ => None,
},
&Number(ref value) => match value.int_value {
Some(100) => Some(Weight100),
Some(200) => Some(Weight200),
Some(300) => Some(Weight300),
Some(400) => Some(Weight400),
Some(500) => Some(Weight500),
Some(600) => Some(Weight600),
Some(700) => Some(Weight700),
Some(800) => Some(Weight800),
Some(900) => Some(Weight900),
_ => None,
},
_ => None
}
}
}
pub mod font_size {
use super::*;
pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
/// <length> | <percentage>
/// TODO: support <absolute-size> and <relative-size>
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain(from_component_value)
}
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
do specified::LengthOrPercentage::parse_non_negative(input).map_move |value| {
match value {
specified::Length(value) => value,
specified::Percentage(value) => specified::Em(value),
}
}
}
}
// CSS 2.1, Section 16 - Text
single_keyword!(text_align, "left" => Left, "right" => Right,
"center" => Center, "justify" => Justify)
pub mod text_decoration {
use super::*;
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.
}
/// none | [ underline || overline || line-through || blink ]
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 }
}
}
// CSS 2.1, Section 17 - Tables
// CSS 2.1, Section 18 - User interface

View file

@ -1,48 +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 errors::{ErrorLoggerIterator, log_css_error};
pub mod common_types;
pub mod longhands;
pub mod shorthands;
pub struct PropertyDeclarationBlock {
important: ~[PropertyDeclaration],
normal: ~[PropertyDeclaration],
}
pub struct PropertyDeclaration; // TODO
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 !parse_one_property_declaration(n.to_ascii_lower(), v, list) {
log_css_error(l, "Invalid property declaration")
}
}
}
}
PropertyDeclarationBlock { important: important, normal: normal }
}
fn parse_one_property_declaration(name: &str, value: ~[ComponentValue],
result_list: &mut ~[PropertyDeclaration]) -> bool {
let _ = name;
let _ = value;
let _ = result_list;
false
}

639
properties/mod.rs.mako Normal file
View file

@ -0,0 +1,639 @@
/* 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 errors::{ErrorLoggerIterator, log_css_error};
pub use std::iterator;
pub use cssparser::*;
pub use CSSColor = cssparser::Color;
pub use 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 = []
%>
pub mod longhands {
pub use super::*;
<%def name="longhand(name)">
<%
property = Longhand(name)
longhands.append(property)
%>
pub mod ${property.ident} {
use super::*;
${caller.body()}
}
</%def>
<%def name="single_component_value(name)">
<%self:longhand name="${name}">
${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)">
<%self:single_component_value name="${name}">
pub enum SpecifiedValue {
% for value in values.split():
${to_rust_ident(value)},
% endfor
}
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_function(name, result_type, function)">
<%self:longhand name="${name}">
pub type SpecifiedValue = ${result_type};
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain(${function})
}
</%self:longhand>
</%def>
<%def name="predefined_type(name, type)">
${predefined_function(name, type, type + "::parse")}
</%def>
// CSS 2.1, Section 8 - Box model
${predefined_function("margin-top", "specified::LengthOrPercentageOrAuto",
"specified::LengthOrPercentageOrAuto::parse")}
${predefined_function("margin-right", "specified::LengthOrPercentageOrAuto",
"specified::LengthOrPercentageOrAuto::parse")}
${predefined_function("margin-bottom", "specified::LengthOrPercentageOrAuto",
"specified::LengthOrPercentageOrAuto::parse")}
${predefined_function("margin-left", "specified::LengthOrPercentageOrAuto",
"specified::LengthOrPercentageOrAuto::parse")}
${predefined_function("padding-top",
"specified::LengthOrPercentage",
"specified::LengthOrPercentage::parse_non_negative")}
${predefined_function("padding-right",
"specified::LengthOrPercentage",
"specified::LengthOrPercentage::parse_non_negative")}
${predefined_function("padding-bottom",
"specified::LengthOrPercentage",
"specified::LengthOrPercentage::parse_non_negative")}
${predefined_function("padding-left",
"specified::LengthOrPercentage",
"specified::LengthOrPercentage::parse_non_negative")}
${predefined_type("border-top-color", "CSSColor")}
${predefined_type("border-right-color", "CSSColor")}
${predefined_type("border-bottom-color", "CSSColor")}
${predefined_type("border-left-color", "CSSColor")}
pub enum BorderStyle {
BorderStyleSolid,
// Uncomment when supported
// BorderStyleDotted,
// BorderStyleDashed,
// BorderStyleDouble,
// BorderStyleGroove,
// BorderStyleRidge,
// BorderStyleInset,
// BorderStyleOutset,
// BorderStyleHidden,
BorderStyleNone,
}
impl BorderStyle {
pub fn parse(input: &ComponentValue) -> Option<BorderStyle> {
do get_ident_lower(input).chain |keyword| {
match keyword.as_slice() {
"solid" => Some(BorderStyleSolid),
"none" => Some(BorderStyleNone),
_ => None,
}
}
}
}
${predefined_type("border-top-style", "BorderStyle")}
${predefined_type("border-right-style", "BorderStyle")}
${predefined_type("border-bottom-style", "BorderStyle")}
${predefined_type("border-left-style", "BorderStyle")}
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)
}
}
${predefined_function("border-top-width", "specified::Length", "parse_border_width")}
${predefined_function("border-right-width", "specified::Length", "parse_border_width")}
${predefined_function("border-bottom-width", "specified::Length", "parse_border_width")}
${predefined_function("border-left-width", "specified::Length", "parse_border_width")}
// 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", "left right none")}
${single_keyword("clear", "left right none both")}
// CSS 2.1, Section 10 - Visual formatting model details
${predefined_function("width",
"specified::LengthOrPercentageOrAuto",
"specified::LengthOrPercentageOrAuto::parse_non_negative")}
${predefined_function("height",
"specified::LengthOrPercentageOrAuto",
"specified::LengthOrPercentageOrAuto::parse_non_negative")}
<%self:single_component_value name="line-height">
pub enum SpecifiedValue {
Normal,
Length(specified::Length),
Percentage(Float),
Number(Float),
}
/// normal | <number> | <length> | <percentage>
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
match input {
&ast::Number(ref value) if value.value >= 0.
=> Some(Number(value.value)),
&ast::Percentage(ref value) if value.value >= 0.
=> Some(Percentage(value.value)),
&Dimension(ref value, ref unit) if value.value >= 0.
=> specified::Length::parse_dimension(value.value, unit.as_slice())
.map_move(Length),
&Ident(ref value) if value.eq_ignore_ascii_case("auto")
=> Some(Normal),
_ => None,
}
}
</%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")}
${predefined_type("color", "CSSColor")}
// CSS 2.1, Section 15 - Fonts
<%self:longhand name="font-family">
enum FontFamily {
FamilyName(~str),
// Generic
// Serif,
// SansSerif,
// Cursive,
// Fantasy,
// Monospace,
}
pub type SpecifiedValue = ~[FontFamily];
/// <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")}
${single_keyword("font-variant", "normal")} // Add small-caps when supported
<%self:single_component_value name="font-weight">
pub enum SpecifiedValue {
Bolder,
Lighther,
Weight100,
Weight200,
Weight300,
Weight400,
Weight500,
Weight600,
Weight700,
Weight800,
Weight900,
}
/// 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(Weight700),
"normal" => Some(Weight400),
"bolder" => Some(Bolder),
"lighter" => Some(Lighther),
_ => None,
},
&Number(ref value) => match value.int_value {
Some(100) => Some(Weight100),
Some(200) => Some(Weight200),
Some(300) => Some(Weight300),
Some(400) => Some(Weight400),
Some(500) => Some(Weight500),
Some(600) => Some(Weight600),
Some(700) => Some(Weight700),
Some(800) => Some(Weight800),
Some(900) => Some(Weight900),
_ => None,
},
_ => None
}
}
</%self:single_component_value>
<%self:single_component_value name="font-size">
pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
/// <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::Length(value) => value,
specified::Percentage(value) => specified::Em(value),
}
}
}
</%self:single_component_value>
// CSS 2.1, Section 16 - Text
${single_keyword("text-align", "left right center justify")}
<%self:longhand name="text-decoration">
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.
}
/// 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(CSSColor::parse).map_move |color| {
Longhands { background_color: Some(color) }
}
</%self:shorthand>
${four_sides_shorthand("border-color", "border-%s-color", "CSSColor::parse")}
${four_sides_shorthand("border-style", "border-%s-style", "BorderStyle::parse")}
${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")}
pub fn parse_border(input: &[ComponentValue]) -> Option<(Option<CSSColor>, Option<BorderStyle>,
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 CSSColor::parse(component_value) {
Some(c) => { color = Some(c); any = true; loop },
None => ()
}
}
if style.is_none() {
match BorderStyle::parse(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 }
}
<%def name="border_side(side)">
<%self:shorthand name="border-${side}" sub_properties="border-${side}-color
border-${side}-style
border-${side}-width">
do parse_border(input).map_move |(color, style, width)| {
Longhands { border_${side}_color: color, border_${side}_style: style,
border_${side}_width: width }
}
</%self:shorthand>
</%def>
${border_side("top")}
${border_side("right")}
${border_side("bottom")}
${border_side("left")}
<%self:shorthand name="border" sub_properties="
border-top-color
border-top-width
border-top-style
border-right-color
border-right-width
border-right-style
border-bottom-color
border-bottom-width
border-bottom-style
border-left-color
border-left-width
border-left-style
">
do parse_border(input).map_move |(color, style, width)| {
Longhands {
border_top_color: color, border_top_style: style, border_top_width: width,
border_right_color: color, border_right_style: style, border_right_width: width,
border_bottom_color: color, border_bottom_style: style, border_bottom_width: width,
border_left_color: color, border_left_style: style, border_left_width: width,
}
}
</%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

@ -1,286 +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 use super::longhands::*;
macro_rules! shorthand(
($name: ident[$($longhand: ident),+] |input| $parser: expr) => {
pub mod $name {
use super::*;
struct Longhands {
$( $longhand: Option<$longhand::SpecifiedValue> ),+
}
fn parse(input: &[ComponentValue]) -> Option<Longhands> { $parser }
}
};
)
// The value of each longhand is the same as the value of the shorthand
macro_rules! duplicating_shorthand(
($name: ident, $parser_function: expr, $($longhand: ident),+) => {
shorthand!($name [$($longhand),+] |input| {
do $parser_function(input).map_move |value| {
Longhands { $( $longhand: Some(value) ),+ }
}
})
};
)
macro_rules! four_side_shorthand(
($name: ident, $parser_function: expr,
$top: ident, $right: ident, $bottom: ident, $left: ident) => {
shorthand!($name [$top, $right, $bottom, $left] |input| {
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 { $top: top, $right: right, $bottom: bottom, $left: left })
} else {
None
}
})
};
)
// TODO: other background-* properties
shorthand!(background [
background_color
] |input| {
do one_component_value(input).chain(CSSColor::parse).map_move |color| {
Longhands { background_color: Some(color) }
}
})
duplicating_shorthand!(border_color, border_top_color::parse,
border_top_color,
border_right_color,
border_bottom_color,
border_left_color
)
duplicating_shorthand!(border_width, border_top_width::parse,
border_top_width,
border_right_width,
border_bottom_width,
border_left_width
)
duplicating_shorthand!(border_style, border_top_style::parse,
border_top_style,
border_right_style,
border_bottom_style,
border_left_style
)
pub fn parse_border(input: &[ComponentValue]) -> Option<(Option<CSSColor>, Option<BorderStyle>,
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 CSSColor::parse(component_value) {
Some(c) => { color = Some(c); any = true; loop },
None => ()
}
}
if style.is_none() {
match BorderStyle::parse(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 }
}
shorthand!(border_top [
border_top_color,
border_top_width,
border_top_style
] |input| {
do parse_border(input).map_move |(color, style, width)| {
Longhands { border_top_color: color, border_top_style: style,
border_top_width: width }
}
})
shorthand!(border_right [
border_right_color,
border_right_width,
border_right_style
] |input| {
do parse_border(input).map_move |(color, style, width)| {
Longhands { border_right_color: color, border_right_style: style,
border_right_width: width }
}
})
shorthand!(border_bottom [
border_bottom_color,
border_bottom_width,
border_bottom_style
] |input| {
do parse_border(input).map_move |(color, style, width)| {
Longhands { border_bottom_color: color, border_bottom_style: style,
border_bottom_width: width }
}
})
shorthand!(border_left [
border_left_color,
border_left_width,
border_left_style
] |input| {
do parse_border(input).map_move |(color, style, width)| {
Longhands { border_left_color: color, border_left_style: style,
border_left_width: width }
}
})
shorthand!(border [
border_top_color,
border_top_width,
border_top_style,
border_right_color,
border_right_width,
border_right_style,
border_bottom_color,
border_bottom_width,
border_bottom_style,
border_left_color,
border_left_width,
border_left_style
] |input| {
do parse_border(input).map_move |(color, style, width)| {
Longhands {
border_top_color: color, border_top_style: style, border_top_width: width,
border_right_color: color, border_right_style: style, border_right_width: width,
border_bottom_color: color, border_bottom_style: style, border_bottom_width: width,
border_left_color: color, border_left_style: style, border_left_width: width,
}
}
})
// TODO: system fonts
shorthand!(font [
font_style,
font_variant,
font_weight,
font_size,
line_height,
font_family
] |input| {
let mut iter = input.skip_whitespace();
let mut nb_normals = 0u;
let mut style = None;
let mut variant = None;
let mut weight = None;
let mut size = None;
let mut line_height = None;
for component_value in iter {
// Special-case 'normal' because it is valid in each of
// font-style, font-weight and font-variant.
// Leaves the values to None, 'normal' is the initial value for each of them.
if get_ident_lower(component_value).filtered(
|v| v.eq_ignore_ascii_case("normal")).is_some() {
nb_normals += 1;
loop;
}
if style.is_none() {
match font_style::from_component_value(component_value) {
Some(s) => { style = Some(s); loop },
None => ()
}
}
if weight.is_none() {
match font_weight::from_component_value(component_value) {
Some(w) => { weight = Some(w); loop },
None => ()
}
}
if variant.is_none() {
match font_variant::from_component_value(component_value) {
Some(v) => { variant = Some(v); loop },
None => ()
}
}
match font_size::from_component_value(component_value) {
Some(s) => { size = Some(s); break },
None => return None
}
}
#[inline]
fn count<T>(opt: &Option<T>) -> uint {
match opt {
&Some(_) => 1,
&None => 0,
}
}
if size.is_none() || (count(&style) + count(&weight) + count(&variant) + nb_normals) > 3 {
return None
}
let mut copied_iter = iter.clone();
match copied_iter.next() {
Some(&Delim('/')) => {
iter = copied_iter;
line_height = match iter.next() {
Some(v) => line_height::from_component_value(v),
_ => return None,
};
if line_height.is_none() { return None }
}
_ => ()
}
let family = font_family::from_iter(iter);
if family.is_none() { return None }
Some(Longhands{
font_style: style,
font_variant: variant,
font_weight: weight,
font_size: size,
line_height: line_height,
font_family: family
})
})
four_side_shorthand!(margin, specified::LengthOrPercentageOrAuto::parse,
margin_top,
margin_right,
margin_bottom,
margin_left
)
four_side_shorthand!(padding, specified::LengthOrPercentage::parse,
padding_top,
padding_right,
padding_bottom,
padding_left
)