Add parsing of supported shorthand properties.

This commit is contained in:
Simon Sapin 2013-08-13 23:43:42 +01:00
parent 3343de55e6
commit 449683565f
4 changed files with 381 additions and 38 deletions

View file

@ -3,6 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::*;
use parsing_utils::*;
pub type Float = f64;
pub type Integer = i64;
@ -16,6 +19,7 @@ pub mod specified {
Au(Integer), // application units
Em(Float),
Ex(Float),
// XXX uncomment when supported:
// Ch(Float),
// Rem(Float),
// Vw(Float),
@ -136,3 +140,30 @@ 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

@ -4,23 +4,26 @@
pub use std::ascii::{to_ascii_lower, eq_ignore_ascii_case};
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::specified;
pub use super::common_types;
pub use super::common_types::*;
macro_rules! single_keyword(
($property_name: ident, $( $lower_case_keyword_string: pat => $variant: ident ),+ ) => {
mod $property_name {
pub mod $property_name {
use super::*;
enum SpecifiedValue {
pub enum SpecifiedValue {
$( $variant ),+
}
fn parse(input: &[ComponentValue]) -> option::Option<SpecifiedValue> {
do one_component_value(input).chain(get_ident_lower).chain |keyword| {
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,
@ -37,10 +40,10 @@ macro_rules! single_type(
single_type!($property_name, $type_, $type_::parse)
};
($property_name: ident, $type_: ty, $parse_function: expr) => {
mod $property_name {
pub mod $property_name {
use super::*;
type SpecifiedValue = $type_;
fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
pub type SpecifiedValue = $type_;
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
one_component_value(input).chain($parse_function)
}
}
@ -74,6 +77,11 @@ 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 to_ascii_lower(value.as_slice()).as_slice() {
@ -91,6 +99,7 @@ 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
@ -125,24 +134,27 @@ single_type!(width, specified::LengthOrPercentageOrAuto,
single_type!(height, specified::LengthOrPercentageOrAuto,
specified::LengthOrPercentageOrAuto::parse_non_negative)
mod line_height {
pub mod line_height {
use super::*;
enum SpecifiedValue {
pub enum SpecifiedValue {
Normal,
Length(specified::Length),
Percentage(common_types::Float),
Number(common_types::Float),
Percentage(Float),
Number(Float),
}
/// normal | <number> | <length> | <percentage>
fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
match one_component_value(input) {
Some(&ast::Number(ref value)) if value.value >= 0.
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)),
Some(&ast::Percentage(ref value)) if value.value >= 0.
&ast::Percentage(ref value) if value.value >= 0.
=> Some(Percentage(value.value)),
Some(&Dimension(ref value, ref unit)) if value.value >= 0.
&Dimension(ref value, ref unit) if value.value >= 0.
=> specified::Length::parse_dimension(value.value, unit.as_slice()).map_move(Length),
Some(&Ident(ref value)) if eq_ignore_ascii_case(value.as_slice(), "auto")
&Ident(ref value) if eq_ignore_ascii_case(value.as_slice(), "auto")
=> Some(Normal),
_ => None,
}
@ -163,7 +175,7 @@ single_type!(color, CSSColor)
// CSS 2.1, Section 15 - Fonts
mod font_family {
pub mod font_family {
use super::*;
enum FontFamily {
FamilyName(~str),
@ -174,13 +186,17 @@ mod font_family {
Fantasy,
Monospace,
}
type SpecifiedValue = ~[FontFamily];
pub type SpecifiedValue = ~[FontFamily];
/// <familiy-name>#
/// <familiy-name> = <string> | [ <ident>+ ]
/// TODO: <generic-familiy>
fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
// XXX Using peekable() for compat with parsing of the 'font' shorthand.
from_iter(input.skip_whitespace().peekable())
}
type Iter<'self> = iterator::Peekable<&'self ComponentValue, SkipWhitespaceIterator<'self>>;
pub fn from_iter(mut iter: Iter) -> Option<SpecifiedValue> {
let mut result = ~[];
let mut iter = input.skip_whitespace();
macro_rules! add(
($value: expr) => {
{
@ -232,11 +248,15 @@ mod font_family {
}
single_keyword!(font_style, "normal" => Normal, "italic" => Italic, "oblique" => Oblique)
single_keyword!(font_variant, "normal" => Normal, "small-caps" => SmallCaps)
single_keyword!(font_variant,
// Uncomment when supported
//"small-caps" => SmallCaps,
"normal" => Normal
)
mod font_weight {
pub mod font_weight {
use super::*;
enum SpecifiedValue {
pub enum SpecifiedValue {
Bolder,
Lighther,
Weight100,
@ -250,16 +270,19 @@ mod font_weight {
Weight900,
}
/// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
match one_component_value(input) {
Some(&Ident(ref value)) => match to_ascii_lower(value.as_slice()).as_slice() {
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 to_ascii_lower(value.as_slice()).as_slice() {
"bold" => Some(Weight700),
"normal" => Some(Weight400),
"bolder" => Some(Bolder),
"lighter" => Some(Lighther),
_ => None,
},
Some(&Number(ref value)) => match value.int_value {
&Number(ref value) => match value.int_value {
Some(100) => Some(Weight100),
Some(200) => Some(Weight200),
Some(300) => Some(Weight300),
@ -276,14 +299,16 @@ mod font_weight {
}
}
mod font_size {
pub mod font_size {
use super::*;
type SpecifiedValue = specified::Length; // Percentages are the same as em.
pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
/// <length> | <percentage>
/// TODO: support <absolute-size> and <relative-size>
fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
do one_component_value(input).chain(specified::LengthOrPercentage::parse_non_negative)
.map_move |value| {
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),
@ -297,9 +322,9 @@ mod font_size {
single_keyword!(text_align, "left" => Left, "right" => Right,
"center" => Center, "justify" => Justify)
mod text_decoration {
pub mod text_decoration {
use super::*;
struct SpecifiedValue {
pub struct SpecifiedValue {
underline: bool,
overline: bool,
line_through: bool,

View file

@ -6,8 +6,9 @@ use std::ascii::to_ascii_lower;
use cssparser::*;
use errors::{ErrorLoggerIterator, log_css_error};
pub mod longhands;
pub mod common_types;
pub mod longhands;
pub mod shorthands;
pub struct PropertyDeclarationBlock {

286
properties/shorthands.rs Normal file
View file

@ -0,0 +1,286 @@
/* 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| eq_ignore_ascii_case(v.as_slice(), "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 iter = iter.peekable();
match iter.peek() {
Some(& &Delim('/')) => {
iter.next();
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
)