Implemented a parser for a subset of css and added a hack for guessing when .css files exist.

This commit is contained in:
Margaret Meyerhofer 2012-05-30 14:44:52 -07:00
parent 7f8573243b
commit 6f90054a1e
8 changed files with 367 additions and 33 deletions

View file

@ -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);

View file

@ -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);

View file

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

View 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;
}

View file

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

View file

@ -51,6 +51,7 @@ mod layout {
mod parser {
mod lexer;
mod html_builder;
mod css_builder;
}
mod platform {

View file

@ -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
View 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 }