mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Port to the new cssparser.
https://github.com/servo/rust-cssparser/pull/68
This commit is contained in:
parent
ad328fda65
commit
d034a6c6bc
20 changed files with 2018 additions and 2388 deletions
|
@ -8,55 +8,75 @@ use url::Url;
|
|||
|
||||
use encoding::EncodingRef;
|
||||
|
||||
use cssparser::{decode_stylesheet_bytes, tokenize, parse_stylesheet_rules, ToCss};
|
||||
use cssparser::ast::*;
|
||||
use selectors::{mod, ParserContext};
|
||||
use properties;
|
||||
use errors::{ErrorLoggerIterator, log_css_error};
|
||||
use cssparser::{Parser, decode_stylesheet_bytes,
|
||||
QualifiedRuleParser, AtRuleParser, RuleListParser, AtRuleType};
|
||||
use string_cache::Namespace;
|
||||
use selectors::{Selector, parse_selector_list};
|
||||
use parser::ParserContext;
|
||||
use properties::{PropertyDeclarationBlock, parse_property_declaration_list};
|
||||
use namespaces::{NamespaceMap, parse_namespace_rule};
|
||||
use media_queries::{mod, Device, MediaRule};
|
||||
use font_face::{FontFaceRule, Source, parse_font_face_rule, iter_font_face_rules_inner};
|
||||
use selector_matching::StylesheetOrigin;
|
||||
use media_queries::{mod, Device, MediaQueryList, parse_media_query_list};
|
||||
use font_face::{FontFaceRule, Source, parse_font_face_block, iter_font_face_rules_inner};
|
||||
|
||||
|
||||
#[deriving(Clone, PartialEq, Eq, Copy, Show)]
|
||||
pub enum Origin {
|
||||
UserAgent,
|
||||
Author,
|
||||
User,
|
||||
}
|
||||
|
||||
|
||||
#[deriving(Show, PartialEq)]
|
||||
pub struct Stylesheet {
|
||||
/// List of rules in the order they were found (important for
|
||||
/// cascading order)
|
||||
rules: Vec<CSSRule>,
|
||||
pub origin: StylesheetOrigin,
|
||||
pub origin: Origin,
|
||||
}
|
||||
|
||||
|
||||
#[deriving(Show, PartialEq)]
|
||||
pub enum CSSRule {
|
||||
Charset(String),
|
||||
Namespace(Option<String>, Namespace),
|
||||
Style(StyleRule),
|
||||
Media(MediaRule),
|
||||
FontFace(FontFaceRule),
|
||||
}
|
||||
|
||||
#[deriving(Show, PartialEq)]
|
||||
pub struct MediaRule {
|
||||
pub media_queries: MediaQueryList,
|
||||
pub rules: Vec<CSSRule>,
|
||||
}
|
||||
|
||||
|
||||
#[deriving(Show, PartialEq)]
|
||||
pub struct StyleRule {
|
||||
pub selectors: Vec<selectors::Selector>,
|
||||
pub declarations: properties::PropertyDeclarationBlock,
|
||||
pub selectors: Vec<Selector>,
|
||||
pub declarations: PropertyDeclarationBlock,
|
||||
}
|
||||
|
||||
|
||||
impl Stylesheet {
|
||||
pub fn from_bytes_iter<I: Iterator<Vec<u8>>>(
|
||||
mut input: I, base_url: Url, protocol_encoding_label: Option<&str>,
|
||||
environment_encoding: Option<EncodingRef>, origin: StylesheetOrigin) -> Stylesheet {
|
||||
environment_encoding: Option<EncodingRef>, origin: Origin) -> Stylesheet {
|
||||
let mut bytes = vec!();
|
||||
// TODO: incremental decoding and tokenization/parsing
|
||||
for chunk in input {
|
||||
bytes.push_all(chunk.as_slice())
|
||||
}
|
||||
Stylesheet::from_bytes(bytes.as_slice(), base_url, protocol_encoding_label, environment_encoding, origin)
|
||||
Stylesheet::from_bytes(bytes.as_slice(), base_url, protocol_encoding_label,
|
||||
environment_encoding, origin)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8],
|
||||
base_url: Url,
|
||||
protocol_encoding_label: Option<&str>,
|
||||
environment_encoding: Option<EncodingRef>,
|
||||
origin: StylesheetOrigin)
|
||||
origin: Origin)
|
||||
-> Stylesheet {
|
||||
// TODO: bytes.as_slice could be bytes.container_as_bytes()
|
||||
let (string, _) = decode_stylesheet_bytes(
|
||||
|
@ -64,121 +84,139 @@ impl Stylesheet {
|
|||
Stylesheet::from_str(string.as_slice(), base_url, origin)
|
||||
}
|
||||
|
||||
pub fn from_str(css: &str, base_url: Url, origin: StylesheetOrigin) -> Stylesheet {
|
||||
static STATE_CHARSET: uint = 1;
|
||||
static STATE_IMPORTS: uint = 2;
|
||||
static STATE_NAMESPACES: uint = 3;
|
||||
static STATE_BODY: uint = 4;
|
||||
|
||||
let parser_context = ParserContext {
|
||||
origin: origin,
|
||||
pub fn from_str<'i>(css: &'i str, base_url: Url, origin: Origin) -> Stylesheet {
|
||||
let mut context = ParserContext {
|
||||
stylesheet_origin: origin,
|
||||
base_url: &base_url,
|
||||
namespaces: NamespaceMap::new()
|
||||
};
|
||||
|
||||
let mut state: uint = STATE_CHARSET;
|
||||
|
||||
let mut rules = vec!();
|
||||
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 {
|
||||
Rule::QualifiedRule(rule) => {
|
||||
next_state = STATE_BODY;
|
||||
parse_style_rule(&parser_context, rule, &mut rules, &namespaces, &base_url)
|
||||
},
|
||||
Rule::AtRule(rule) => {
|
||||
let lower_name = rule.name.as_slice().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;
|
||||
// TODO: support @import
|
||||
log_css_error(rule.location, "@import is not supported yet")
|
||||
}
|
||||
},
|
||||
"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(&parser_context,
|
||||
lower_name.as_slice(),
|
||||
rule,
|
||||
&mut rules,
|
||||
&namespaces,
|
||||
&base_url)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
state = next_state;
|
||||
}
|
||||
let rule_parser = MainRuleParser {
|
||||
context: &mut context,
|
||||
state: State::Start,
|
||||
};
|
||||
let rules = RuleListParser::new_for_stylesheet(&mut Parser::new(css), rule_parser)
|
||||
.filter_map(|result| result.ok())
|
||||
.collect();
|
||||
Stylesheet {
|
||||
rules: rules,
|
||||
origin: origin,
|
||||
rules: rules,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lower_name is passed explicitly to avoid computing it twice.
|
||||
pub fn parse_nested_at_rule(context: &ParserContext,
|
||||
lower_name: &str,
|
||||
rule: AtRule,
|
||||
parent_rules: &mut Vec<CSSRule>,
|
||||
namespaces: &NamespaceMap,
|
||||
base_url: &Url) {
|
||||
match lower_name {
|
||||
"media" => {
|
||||
media_queries::parse_media_rule(context, rule, parent_rules, namespaces, base_url)
|
||||
|
||||
fn parse_nested_rules(context: &mut ParserContext, input: &mut Parser) -> Vec<CSSRule> {
|
||||
let parser = MainRuleParser {
|
||||
context: context,
|
||||
state: State::Body,
|
||||
};
|
||||
RuleListParser::new_for_nested_rule(input, parser)
|
||||
.filter_map(|result| result.ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
struct MainRuleParser<'a, 'b: 'a> {
|
||||
context: &'a mut ParserContext<'b>,
|
||||
state: State,
|
||||
}
|
||||
|
||||
|
||||
#[deriving(Eq, PartialEq, Ord, PartialOrd)]
|
||||
enum State {
|
||||
Start = 1,
|
||||
Imports = 2,
|
||||
Namespaces = 3,
|
||||
Body = 4,
|
||||
}
|
||||
|
||||
|
||||
enum AtRulePrelude {
|
||||
FontFace,
|
||||
Media(MediaQueryList),
|
||||
}
|
||||
|
||||
|
||||
impl<'a, 'b> AtRuleParser<AtRulePrelude, CSSRule> for MainRuleParser<'a, 'b> {
|
||||
fn parse_prelude(&mut self, name: &str, input: &mut Parser)
|
||||
-> Result<AtRuleType<AtRulePrelude, CSSRule>, ()> {
|
||||
match_ignore_ascii_case! { name:
|
||||
"charset" => {
|
||||
if self.state <= State::Start {
|
||||
// Valid @charset rules are just ignored
|
||||
self.state = State::Imports;
|
||||
let charset = try!(input.expect_string()).into_owned();
|
||||
return Ok(AtRuleType::WithoutBlock(CSSRule::Charset(charset)))
|
||||
} else {
|
||||
return Err(()) // "@charset must be the first rule"
|
||||
}
|
||||
},
|
||||
"import" => {
|
||||
if self.state <= State::Imports {
|
||||
self.state = State::Imports;
|
||||
// TODO: support @import
|
||||
return Err(()) // "@import is not supported yet"
|
||||
} else {
|
||||
return Err(()) // "@import must be before any rule but @charset"
|
||||
}
|
||||
},
|
||||
"namespace" => {
|
||||
if self.state <= State::Namespaces {
|
||||
self.state = State::Namespaces;
|
||||
let (prefix, namespace) = try!(parse_namespace_rule(self.context, input));
|
||||
return Ok(AtRuleType::WithoutBlock(CSSRule::Namespace(prefix, namespace)))
|
||||
} else {
|
||||
return Err(()) // "@namespace must be before any rule but @charset and @import"
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.state = State::Body;
|
||||
|
||||
match_ignore_ascii_case! { name:
|
||||
"media" => {
|
||||
let media_queries = parse_media_query_list(input);
|
||||
Ok(AtRuleType::WithBlock(AtRulePrelude::Media(media_queries)))
|
||||
},
|
||||
"font-face" => {
|
||||
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace))
|
||||
}
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CSSRule, ()> {
|
||||
match prelude {
|
||||
AtRulePrelude::FontFace => {
|
||||
parse_font_face_block(self.context, input).map(CSSRule::FontFace)
|
||||
}
|
||||
AtRulePrelude::Media(media_queries) => {
|
||||
Ok(CSSRule::Media(MediaRule {
|
||||
media_queries: media_queries,
|
||||
rules: parse_nested_rules(self.context, input),
|
||||
}))
|
||||
}
|
||||
}
|
||||
"font-face" => parse_font_face_rule(rule, parent_rules, base_url),
|
||||
_ => log_css_error(rule.location,
|
||||
format!("Unsupported at-rule: @{}", lower_name).as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_style_rule(context: &ParserContext,
|
||||
rule: QualifiedRule,
|
||||
parent_rules: &mut Vec<CSSRule>,
|
||||
namespaces: &NamespaceMap,
|
||||
base_url: &Url) {
|
||||
let QualifiedRule {
|
||||
location,
|
||||
prelude,
|
||||
block
|
||||
} = rule;
|
||||
// FIXME: avoid doing this for valid selectors
|
||||
let serialized = prelude.to_css_string();
|
||||
match selectors::parse_selector_list(context, prelude.into_iter(), namespaces) {
|
||||
Ok(selectors) => parent_rules.push(CSSRule::Style(StyleRule{
|
||||
selectors: selectors,
|
||||
declarations: properties::parse_property_declaration_list(block.into_iter(), base_url)
|
||||
})),
|
||||
Err(()) => log_css_error(location, format!(
|
||||
"Invalid/unsupported selector: {}", serialized).as_slice()),
|
||||
|
||||
impl<'a, 'b> QualifiedRuleParser<Vec<Selector>, CSSRule> for MainRuleParser<'a, 'b> {
|
||||
fn parse_prelude(&mut self, input: &mut Parser) -> Result<Vec<Selector>, ()> {
|
||||
self.state = State::Body;
|
||||
parse_selector_list(self.context, input)
|
||||
}
|
||||
|
||||
fn parse_block(&mut self, prelude: Vec<Selector>, input: &mut Parser) -> Result<CSSRule, ()> {
|
||||
Ok(CSSRule::Style(StyleRule {
|
||||
selectors: prelude,
|
||||
declarations: parse_property_declaration_list(self.context, input)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device,
|
||||
callback: |&StyleRule|) {
|
||||
for rule in rules.iter() {
|
||||
|
@ -187,7 +225,9 @@ pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device,
|
|||
CSSRule::Media(ref rule) => if rule.media_queries.evaluate(device) {
|
||||
iter_style_rules(rule.rules.as_slice(), device, |s| callback(s))
|
||||
},
|
||||
CSSRule::FontFace(_) => {},
|
||||
CSSRule::FontFace(..) |
|
||||
CSSRule::Charset(..) |
|
||||
CSSRule::Namespace(..) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,7 +236,10 @@ pub fn iter_stylesheet_media_rules(stylesheet: &Stylesheet, callback: |&MediaRul
|
|||
for rule in stylesheet.rules.iter() {
|
||||
match *rule {
|
||||
CSSRule::Media(ref rule) => callback(rule),
|
||||
_ => {}
|
||||
CSSRule::Style(..) |
|
||||
CSSRule::FontFace(..) |
|
||||
CSSRule::Charset(..) |
|
||||
CSSRule::Namespace(..) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,3 +256,134 @@ pub fn iter_font_face_rules(stylesheet: &Stylesheet, device: &Device,
|
|||
callback: |family: &str, source: &Source|) {
|
||||
iter_font_face_rules_inner(stylesheet.rules.as_slice(), device, callback)
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_parse_stylesheet() {
|
||||
use std::sync::Arc;
|
||||
use cssparser;
|
||||
use selectors::*;
|
||||
use string_cache::Atom;
|
||||
use properties::{PropertyDeclaration, DeclaredValue, longhands};
|
||||
|
||||
let css = r"
|
||||
@namespace url(http://www.w3.org/1999/xhtml);
|
||||
/* FIXME: only if scripting is enabled */
|
||||
input[type=hidden i] { display: none !important; }
|
||||
html , body /**/ { display: block; }
|
||||
#d1 > .ok { background: blue; }
|
||||
";
|
||||
let url = Url::parse("about::test").unwrap();
|
||||
let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent);
|
||||
assert_eq!(stylesheet, Stylesheet {
|
||||
origin: Origin::UserAgent,
|
||||
rules: vec![
|
||||
CSSRule::Namespace(None, ns!(HTML)),
|
||||
CSSRule::Style(StyleRule {
|
||||
selectors: vec![
|
||||
Selector {
|
||||
compound_selectors: Arc::new(CompoundSelector {
|
||||
simple_selectors: vec![
|
||||
SimpleSelector::Namespace(ns!(HTML)),
|
||||
SimpleSelector::LocalName(LocalName {
|
||||
name: atom!(input),
|
||||
lower_name: atom!(input),
|
||||
}),
|
||||
SimpleSelector::AttrEqual(AttrSelector {
|
||||
name: atom!(type),
|
||||
lower_name: atom!(type),
|
||||
namespace: NamespaceConstraint::Specific(ns!("")),
|
||||
}, "hidden".into_string(), CaseSensitivity::CaseInsensitive)
|
||||
],
|
||||
next: None,
|
||||
}),
|
||||
pseudo_element: None,
|
||||
specificity: (0 << 20) + (1 << 10) + (1 << 0),
|
||||
},
|
||||
],
|
||||
declarations: PropertyDeclarationBlock {
|
||||
normal: Arc::new(vec![]),
|
||||
important: Arc::new(vec![
|
||||
PropertyDeclaration::Display(DeclaredValue::SpecifiedValue(
|
||||
longhands::display::SpecifiedValue::none)),
|
||||
]),
|
||||
},
|
||||
}),
|
||||
CSSRule::Style(StyleRule {
|
||||
selectors: vec![
|
||||
Selector {
|
||||
compound_selectors: Arc::new(CompoundSelector {
|
||||
simple_selectors: vec![
|
||||
SimpleSelector::Namespace(ns!(HTML)),
|
||||
SimpleSelector::LocalName(LocalName {
|
||||
name: atom!(html),
|
||||
lower_name: atom!(html),
|
||||
}),
|
||||
],
|
||||
next: None,
|
||||
}),
|
||||
pseudo_element: None,
|
||||
specificity: (0 << 20) + (0 << 10) + (1 << 0),
|
||||
},
|
||||
Selector {
|
||||
compound_selectors: Arc::new(CompoundSelector {
|
||||
simple_selectors: vec![
|
||||
SimpleSelector::Namespace(ns!(HTML)),
|
||||
SimpleSelector::LocalName(LocalName {
|
||||
name: atom!(body),
|
||||
lower_name: atom!(body),
|
||||
}),
|
||||
],
|
||||
next: None,
|
||||
}),
|
||||
pseudo_element: None,
|
||||
specificity: (0 << 20) + (0 << 10) + (1 << 0),
|
||||
},
|
||||
],
|
||||
declarations: PropertyDeclarationBlock {
|
||||
normal: Arc::new(vec![
|
||||
PropertyDeclaration::Display(DeclaredValue::SpecifiedValue(
|
||||
longhands::display::SpecifiedValue::block)),
|
||||
]),
|
||||
important: Arc::new(vec![]),
|
||||
},
|
||||
}),
|
||||
CSSRule::Style(StyleRule {
|
||||
selectors: vec![
|
||||
Selector {
|
||||
compound_selectors: Arc::new(CompoundSelector {
|
||||
simple_selectors: vec![
|
||||
SimpleSelector::Class(Atom::from_slice("ok")),
|
||||
],
|
||||
next: Some((box CompoundSelector {
|
||||
simple_selectors: vec![
|
||||
SimpleSelector::ID(Atom::from_slice("d1")),
|
||||
],
|
||||
next: None,
|
||||
}, Combinator::Child)),
|
||||
}),
|
||||
pseudo_element: None,
|
||||
specificity: (1 << 20) + (1 << 10) + (0 << 0),
|
||||
},
|
||||
],
|
||||
declarations: PropertyDeclarationBlock {
|
||||
normal: Arc::new(vec![
|
||||
PropertyDeclaration::BackgroundImage(DeclaredValue::Initial),
|
||||
PropertyDeclaration::BackgroundAttachment(DeclaredValue::Initial),
|
||||
PropertyDeclaration::BackgroundRepeat(DeclaredValue::Initial),
|
||||
PropertyDeclaration::BackgroundPosition(DeclaredValue::Initial),
|
||||
PropertyDeclaration::BackgroundColor(DeclaredValue::SpecifiedValue(
|
||||
longhands::background_color::SpecifiedValue {
|
||||
authored: Some("blue".into_string()),
|
||||
parsed: cssparser::Color::RGBA(cssparser::RGBA {
|
||||
red: 0., green: 0., blue: 1., alpha: 1.
|
||||
}),
|
||||
}
|
||||
)),
|
||||
]),
|
||||
important: Arc::new(vec![]),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue