mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Implemented a parser for a subset of css and added a hack for guessing when .css files exist.
This commit is contained in:
parent
7f8573243b
commit
6f90054a1e
8 changed files with 367 additions and 33 deletions
|
@ -3,6 +3,7 @@ export content;
|
|||
|
||||
import result::extensions;
|
||||
import dom::rcu::writer_methods;
|
||||
import dom::style;
|
||||
import dom=dom::base;
|
||||
import layout::layout;
|
||||
import js::rust::methods;
|
||||
|
@ -38,11 +39,36 @@ fn content(to_layout: chan<layout::msg>) -> chan<msg> {
|
|||
parse(filename) {
|
||||
#debug["content: Received filename `%s` to parse", filename];
|
||||
|
||||
// TODO actually parse where the css sheet should be
|
||||
// Replace .html with .css and try to open a stylesheet
|
||||
assert filename.ends_with(".html");
|
||||
let new_file = filename.substr(0u, filename.len() - 5u)
|
||||
+ ".css";
|
||||
|
||||
// Send off a task to parse the stylesheet
|
||||
let css_port = comm::port();
|
||||
let css_chan = comm::chan(css_port);
|
||||
task::spawn {||
|
||||
let css_stream = parser::lexer::
|
||||
spawn_css_parser_task(new_file);
|
||||
let css_rules = parser::css_builder::
|
||||
build_stylesheet(css_stream);
|
||||
css_chan.send(css_rules);
|
||||
};
|
||||
|
||||
// Note: we can parse the next document in parallel
|
||||
// with any previous documents.
|
||||
let stream = lexer::spawn_html_parser_task(filename);
|
||||
let stream = parser::lexer::spawn_html_parser_task(filename);
|
||||
let root = parser::html_builder::build_dom(scope, stream);
|
||||
|
||||
// Collect the css stylesheet
|
||||
let css_rules = comm::recv(css_port);
|
||||
|
||||
// Apply the css rules to the dom tree:
|
||||
// TODO
|
||||
#debug["%s",style::print_sheet(css_rules)];
|
||||
|
||||
|
||||
// Now, join the layout so that they will see the latest
|
||||
// changes we have made.
|
||||
join_layout(scope, to_layout);
|
||||
|
|
|
@ -7,7 +7,7 @@ enum display_type{
|
|||
}
|
||||
|
||||
enum style_decl{
|
||||
font_size(uint),
|
||||
font_size(uint), // Currently assumes format '# pt'
|
||||
display(display_type),
|
||||
text_color(uint),
|
||||
background_color(uint)
|
||||
|
@ -27,10 +27,9 @@ enum selector{
|
|||
sibling(~selector, ~selector)
|
||||
}
|
||||
|
||||
type rule = (selector, [style_decl]);
|
||||
|
||||
type stylesheet = [rule];
|
||||
type rule = ([~selector], [style_decl]);
|
||||
|
||||
type stylesheet = [~rule];
|
||||
|
||||
fn print_list<T>(list : [T], print : fn(T) -> str) -> str {
|
||||
let l = vec::len(list);
|
||||
|
@ -48,6 +47,23 @@ fn print_list<T>(list : [T], print : fn(T) -> str) -> str {
|
|||
ret res;
|
||||
}
|
||||
|
||||
fn print_list_vert<T>(list : [T], print : fn(T) -> str) -> str {
|
||||
let l = vec::len(list);
|
||||
if l == 0u { ret "" }
|
||||
|
||||
let mut res = "-";
|
||||
res += print(list[0]);
|
||||
let mut i = 1u;
|
||||
|
||||
while i < l {
|
||||
res += "\n-";
|
||||
res += print(list[i]);
|
||||
i += 1u;
|
||||
}
|
||||
|
||||
ret res;
|
||||
}
|
||||
|
||||
fn print_display(dis_ty : display_type) -> str {
|
||||
alt dis_ty {
|
||||
block { "block" }
|
||||
|
@ -57,7 +73,7 @@ fn print_display(dis_ty : display_type) -> str {
|
|||
|
||||
fn print_style(decl : style_decl) -> str{
|
||||
alt decl {
|
||||
font_size(s) { #fmt("Font size = %u px", s) }
|
||||
font_size(s) { #fmt("Font size = %u pt", s) }
|
||||
display(dis_ty) { #fmt("Display style = %s", print_display(dis_ty)) }
|
||||
text_color(c) { #fmt("Text color = 0x%06x", c) }
|
||||
background_color(c) { #fmt("Background color = 0x%06x", c) }
|
||||
|
@ -73,7 +89,7 @@ fn print_attr(attribute : attr) -> str {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_selector(select : ~selector) -> str {
|
||||
fn print_selector(&&select : ~selector) -> str {
|
||||
alt *select {
|
||||
element(s, attrs) { #fmt("Element %s with attributes: %s", s,
|
||||
print_list(attrs, print_attr)) }
|
||||
|
@ -86,39 +102,39 @@ fn print_selector(select : ~selector) -> str {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_rule(rule : rule) -> str {
|
||||
alt rule {
|
||||
(sel, styles) {
|
||||
let sel_str = print_selector(~(copy sel));
|
||||
fn print_rule(&&rule : ~rule) -> str {
|
||||
alt *rule {
|
||||
(sels, styles) {
|
||||
let sel_str = print_list(sels, print_selector);
|
||||
let sty_str = print_list(styles, print_style);
|
||||
|
||||
#fmt("Selector: %s, Style: {%s}", sel_str, sty_str)
|
||||
#fmt("Selectors: %s; Style: {%s}", sel_str, sty_str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_sheet(sheet : stylesheet) -> str {
|
||||
#fmt("CSS Rules: %s", print_list(sheet, print_rule))
|
||||
#fmt("CSS Rules:\n%s", print_list_vert(sheet, print_rule))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print() {
|
||||
let test1 = [(element("p", []), [font_size(32u)])];
|
||||
let test1 = [~([~element("p", [])], [font_size(32u)])];
|
||||
let actual1 = print_sheet(test1);
|
||||
let expected1 = "CSS Rules: Selector: Element p with attributes: ," +
|
||||
" Style: {Font size = 32 px}";
|
||||
let expected1 = "CSS Rules:\n-Selectors: Element p with attributes: ;" +
|
||||
" Style: {Font size = 32 pt}";
|
||||
|
||||
assert(actual1 == expected1);
|
||||
|
||||
let elmt1 = ~element("*", []);
|
||||
let elmt2 = ~element("body", [exact("class", "2")]);
|
||||
|
||||
let test2 = [(descendant(elmt1, elmt2),
|
||||
let test2 = [~([~descendant(elmt1, elmt2)],
|
||||
[display(block), text_color(0u)])];
|
||||
|
||||
let actual2 = print_sheet(test2);
|
||||
let expected2 = "CSS Rules: Selector: (Element * with attributes: ) " +
|
||||
"(Element body with attributes: [class = 2]), " +
|
||||
let expected2 = "CSS Rules:\n-Selectors: (Element * with attributes: ) "
|
||||
+ "(Element body with attributes: [class = 2]); " +
|
||||
"Style: {Display style = block, Text color = 0x000000}";
|
||||
|
||||
assert(actual2 == expected2);
|
||||
|
|
|
@ -165,7 +165,7 @@ mod test {
|
|||
for tree::each_child(btree, root) {|c|
|
||||
r += flat_bounds(c);
|
||||
}
|
||||
ret r + [root.bounds];
|
||||
ret r + [copy root.bounds];
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
262
src/servo/parser/css_builder.rs
Normal file
262
src/servo/parser/css_builder.rs
Normal file
|
@ -0,0 +1,262 @@
|
|||
#[doc="Constructs a list of style rules from a token stream"]
|
||||
|
||||
// TODO: fail according to the css spec instead of failing when things
|
||||
// are not as expected
|
||||
|
||||
import dom::style::*;
|
||||
import parser::lexer::css::{token, to_start_desc, to_end_desc,
|
||||
to_descendant, to_child, to_sibling,
|
||||
to_comma, to_elmt, to_attr, to_desc,
|
||||
to_eof};
|
||||
import comm::recv;
|
||||
|
||||
type token_reader = {stream : port<token>, mut lookahead : option<token>};
|
||||
|
||||
impl methods for token_reader {
|
||||
fn get() -> token {
|
||||
alt self.lookahead {
|
||||
some(tok) { self.lookahead = none; tok }
|
||||
none { recv(self.stream) }
|
||||
}
|
||||
}
|
||||
|
||||
fn unget(tok : token) {
|
||||
self.lookahead = some(tok);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_element(reader : token_reader) -> option<~selector> {
|
||||
// Get the current element type
|
||||
let elmt_name = alt reader.get() {
|
||||
to_elmt(tag) { tag }
|
||||
to_eof { ret none; }
|
||||
_ { fail "Expected an element" }
|
||||
};
|
||||
|
||||
let mut attr_list = [];
|
||||
|
||||
// Get the attributes associated with that element
|
||||
loop {
|
||||
let tok = reader.get();
|
||||
alt tok {
|
||||
to_attr(attr) { attr_list += [attr]; }
|
||||
to_start_desc | to_descendant | to_child | to_sibling
|
||||
| to_comma {
|
||||
reader.unget(tok);
|
||||
break;
|
||||
}
|
||||
to_eof { ret none; }
|
||||
to_elmt(_) { fail "Unexpected second element without " +
|
||||
"relation to first element"; }
|
||||
to_end_desc { fail "Unexpected '}'"; }
|
||||
to_desc(_, _) { fail "Unexpected description"; }
|
||||
}
|
||||
}
|
||||
|
||||
ret some(~element(elmt_name, attr_list));
|
||||
}
|
||||
|
||||
// Currently colors are supported in rgb(a,b,c) form and also by
|
||||
// keywords for several common colors.
|
||||
// TODO: extend this
|
||||
fn parse_color(color : str) -> uint {
|
||||
let blue_unit = 1u;
|
||||
let green_unit = 256u;
|
||||
let red_unit = 256u * 256u;
|
||||
|
||||
let result_color = if color.starts_with("rgb(") {
|
||||
let color_vec = str::bytes(color);
|
||||
let mut i = 4u;
|
||||
let mut red_vec = [];
|
||||
let mut green_vec = [];
|
||||
let mut blue_vec = [];
|
||||
|
||||
while i < color_vec.len() && color_vec[i] != ',' as u8 {
|
||||
red_vec += [color_vec[i]];
|
||||
i += 1u;
|
||||
}
|
||||
|
||||
i += 1u;
|
||||
|
||||
while i < color_vec.len() && color_vec[i] != ',' as u8 {
|
||||
green_vec += [color_vec[i]];
|
||||
i += 1u;
|
||||
}
|
||||
|
||||
i += 1u;
|
||||
|
||||
while i < color_vec.len() && color_vec[i] != ',' as u8 {
|
||||
blue_vec += [color_vec[i]];
|
||||
i += 1u;
|
||||
}
|
||||
|
||||
// TODO, fail by ignoring the rule instead of setting the
|
||||
// color to black
|
||||
|
||||
let blue_intense = alt uint::from_str(str::from_bytes(blue_vec)) {
|
||||
some(c) { c }
|
||||
none { 0u }
|
||||
};
|
||||
|
||||
let green_intense = alt uint::from_str(str::from_bytes(green_vec)) {
|
||||
some(c) { c }
|
||||
none { 0u }
|
||||
};
|
||||
|
||||
let red_intense = alt uint::from_str(str::from_bytes(red_vec)) {
|
||||
some(c) { c }
|
||||
none { 0u }
|
||||
};
|
||||
|
||||
|
||||
blue_unit * blue_intense + green_intense * green_unit
|
||||
+ red_intense * red_unit
|
||||
} else {
|
||||
alt color {
|
||||
"red" { red_unit * 255u }
|
||||
"blue" { blue_unit * 255u }
|
||||
"green" { green_unit * 255u}
|
||||
"white" { red_unit * 256u - 1u }
|
||||
"black" { 0u }
|
||||
// TODO, fail by ignoring the rule instead of setting the
|
||||
// color to black
|
||||
_ { #debug["Unrecognized color %s", color]; 0u }
|
||||
}
|
||||
};
|
||||
|
||||
ret result_color;
|
||||
}
|
||||
|
||||
fn parse_rule(reader : token_reader) -> option<~rule> {
|
||||
let mut sel_list = [];
|
||||
let mut desc_list = [];
|
||||
|
||||
// Collect all the selectors that this rule applies to
|
||||
loop {
|
||||
let mut cur_sel;
|
||||
|
||||
alt parse_element(reader) {
|
||||
some(elmt) { cur_sel <- elmt; }
|
||||
none { ret none; } // we hit an eof in the middle of a rule
|
||||
}
|
||||
|
||||
loop {
|
||||
let tok = reader.get();
|
||||
alt tok {
|
||||
to_descendant {
|
||||
alt parse_element(reader) {
|
||||
some(elmt) {
|
||||
let built_sel <- cur_sel;
|
||||
let new_sel <- elmt;
|
||||
cur_sel <- ~descendant(built_sel, new_sel)
|
||||
}
|
||||
none { ret none; }
|
||||
}
|
||||
}
|
||||
to_child {
|
||||
alt parse_element(reader) {
|
||||
some(elmt) {
|
||||
let built_sel <- cur_sel;
|
||||
let new_sel <- elmt;
|
||||
cur_sel <- ~child(built_sel, new_sel)
|
||||
}
|
||||
none { ret none; }
|
||||
}
|
||||
}
|
||||
to_sibling {
|
||||
alt parse_element(reader) {
|
||||
some(elmt) {
|
||||
let built_sel <- cur_sel;
|
||||
let new_sel <- elmt;
|
||||
cur_sel <- ~sibling(built_sel, new_sel)
|
||||
}
|
||||
none { ret none; }
|
||||
}
|
||||
}
|
||||
to_start_desc {
|
||||
let built_sel <- cur_sel;
|
||||
sel_list += [built_sel];
|
||||
reader.unget(to_start_desc);
|
||||
break;
|
||||
}
|
||||
to_comma {
|
||||
let built_sel <- cur_sel;
|
||||
sel_list += [built_sel];
|
||||
reader.unget(to_comma);
|
||||
break;
|
||||
}
|
||||
to_attr(_) | to_end_desc | to_elmt(_) | to_desc(_, _) {
|
||||
fail #fmt["Unexpected token %? in elements", tok];
|
||||
}
|
||||
to_eof { ret none; }
|
||||
}
|
||||
}
|
||||
|
||||
// check if we should break out of the nesting loop as well
|
||||
let tok = reader.get();
|
||||
alt tok {
|
||||
to_start_desc { break; }
|
||||
to_comma { }
|
||||
_ { reader.unget(tok); }
|
||||
}
|
||||
}
|
||||
|
||||
// Get the description to be applied to the selector
|
||||
loop {
|
||||
let tok = reader.get();
|
||||
alt tok {
|
||||
to_end_desc { break; }
|
||||
to_desc(prop, val) {
|
||||
alt prop {
|
||||
"font-size" {
|
||||
// TODO, support more ways to declare a font size than # pt
|
||||
assert val.ends_with("pt");
|
||||
let num = val.substr(0u, val.len() - 2u);
|
||||
|
||||
alt uint::from_str(num) {
|
||||
some(n) { desc_list += [font_size(n)]; }
|
||||
none { fail "Nonnumber provided as font size"; }
|
||||
}
|
||||
}
|
||||
"display" {
|
||||
alt val {
|
||||
"inline" { desc_list += [display(inline)]; }
|
||||
"block" { desc_list += [display(block)]; }
|
||||
_ { #debug["Recieved unknown display value '%s'",
|
||||
val]; }
|
||||
}
|
||||
}
|
||||
"color" {
|
||||
desc_list += [text_color(parse_color(val))];
|
||||
}
|
||||
"background-color" {
|
||||
desc_list += [background_color(parse_color(val))];
|
||||
}
|
||||
_ { #debug["Recieved unknown style property '%s'",
|
||||
val]; }
|
||||
}
|
||||
}
|
||||
to_eof { ret none; }
|
||||
to_start_desc | to_descendant | to_child | to_sibling
|
||||
| to_comma | to_elmt(_) | to_attr(_) {
|
||||
fail #fmt["Unexpected token %? in description", tok];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret some(~(sel_list, desc_list));
|
||||
}
|
||||
|
||||
fn build_stylesheet(stream : port<token>) -> [~rule] {
|
||||
let mut rule_list = [];
|
||||
let reader = {stream : stream, mut lookahead : none};
|
||||
|
||||
loop {
|
||||
alt parse_rule(reader) {
|
||||
some(rule) { let r <- rule; rule_list += [r]; }
|
||||
none { break; }
|
||||
}
|
||||
}
|
||||
|
||||
ret rule_list;
|
||||
}
|
|
@ -307,7 +307,7 @@ mod css {
|
|||
'>' as u8 { to_child }
|
||||
'+' as u8 { to_sibling }
|
||||
',' as u8 { to_comma }
|
||||
_ { to_descendant }
|
||||
_ { self.unget(c); to_descendant }
|
||||
};
|
||||
|
||||
self.eat_whitespace();
|
||||
|
@ -316,14 +316,22 @@ mod css {
|
|||
}
|
||||
|
||||
fn parse_css_element(c : u8) -> token {
|
||||
/* Check for special attributes with an implied element.*/
|
||||
assert self.lookahead.is_none();
|
||||
|
||||
/* Check for special attributes with an implied element,
|
||||
or a wildcard which is not a alphabet character.*/
|
||||
if c == '.' as u8 || c == '#' as u8 {
|
||||
self.state = ps_css_attribute;
|
||||
self.unget(c);
|
||||
ret to_elmt("*");
|
||||
} else if c == '*' as u8 {
|
||||
self.state = ps_css_attribute;
|
||||
ret to_elmt("*");
|
||||
}
|
||||
|
||||
self.unget(c);
|
||||
let element = self.parse_ident();
|
||||
|
||||
self.state = ps_css_attribute;
|
||||
|
||||
ret to_elmt(element);
|
||||
|
@ -389,7 +397,11 @@ mod css {
|
|||
fn parse_css_description(c: u8) -> token {
|
||||
let mut ch = c;
|
||||
|
||||
if ch.is_whitespace() {
|
||||
if ch == '}' as u8 {
|
||||
self.state = ps_css_elmt;
|
||||
self.eat_whitespace();
|
||||
ret to_end_desc;
|
||||
} else if ch.is_whitespace() {
|
||||
self.eat_whitespace();
|
||||
|
||||
alt self.get() {
|
||||
|
@ -439,7 +451,7 @@ mod css {
|
|||
if desc_val.len() == 0u {
|
||||
fail "Expected descriptor value";
|
||||
} else {
|
||||
self.state = ps_css_elmt;
|
||||
self.unget('}' as u8);
|
||||
break;
|
||||
}
|
||||
} else if ch == ';' as u8 {
|
||||
|
@ -485,16 +497,29 @@ fn spawn_css_parser_task(filename: str) -> port<css::token> {
|
|||
let result_port = port();
|
||||
let result_chan = chan(result_port);
|
||||
task::spawn {||
|
||||
let file_data = io::read_whole_file(filename).get();
|
||||
let reader = io::bytes_reader(file_data);
|
||||
|
||||
assert filename.ends_with(".css");
|
||||
let parser : parser = parser(reader, ps_css_elmt);
|
||||
|
||||
loop {
|
||||
let token = parser.parse_css();
|
||||
result_chan.send(token);
|
||||
if token == css::to_eof { break; }
|
||||
let file_try = io::read_whole_file(filename);
|
||||
|
||||
// Check if the given css file existed, if it does, parse it,
|
||||
// otherwise just send an eof. This is a hack to allow
|
||||
// guessing that if foo.html exists, foo.css is the
|
||||
// corresponding stylesheet.
|
||||
if file_try.is_success() {
|
||||
#debug["Lexing css sheet %s", filename];
|
||||
let file_data = file_try.get();
|
||||
let reader = io::bytes_reader(file_data);
|
||||
|
||||
let parser : parser = parser(reader, ps_css_elmt);
|
||||
|
||||
loop {
|
||||
let token = parser.parse_css();
|
||||
result_chan.send(token);
|
||||
if token == css::to_eof { break; }
|
||||
}
|
||||
} else {
|
||||
#debug["Failed to open css sheet %s", filename];
|
||||
result_chan.send(css::to_eof);
|
||||
}
|
||||
};
|
||||
ret result_port;
|
||||
|
|
|
@ -51,6 +51,7 @@ mod layout {
|
|||
mod parser {
|
||||
mod lexer;
|
||||
mod html_builder;
|
||||
mod css_builder;
|
||||
}
|
||||
|
||||
mod platform {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import comm::*;
|
||||
import parser::lexer;
|
||||
//import parser::lexer::util_methods;
|
||||
import result::extensions;
|
||||
import gfx::renderer;
|
||||
import platform::osmain;
|
||||
|
|
5
src/test/test.css
Normal file
5
src/test/test.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
p {font-size : 12pt}
|
||||
p img {color : rgb(0,255,0); display : block }
|
||||
p.blue > p.green + p.red { background-color : blue ;color : green }
|
||||
img[class] .pastoral *[lang|=en] { display:inline}
|
||||
.book > #novel + *[type=novella] p { color : blue; color : white }
|
Loading…
Add table
Add a link
Reference in a new issue