iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 6
3

CSS
今天繼續使用 robinson 這個「玩具」來進行講解,第二篇文章講到解析,目前我們已經會處理 DOM 和 HTML 了,接下來就是處理 CSS 的部分。在開始之前,不知道你對 CSS 了解多少,平常有在開發網頁的話應該還算是懂吧,不過我想並沒有真正完全去理解它,因為其實只是開發網頁的話,也不需要懂。

但是既然我們現在要處理 CSS 的部分,有完整的了解當然最好,可以參考 Mozilla 文件 或是 W3C 文件

處理 CSS 又分兩個步驟,有 Parser 和 style,前者是解析原始 CSS,後者則是讓 DOM 有 style。

robinson 這個專案中,CSS 拆成兩個模組,在 robinson/src/css.rsrobinson/src/style.rs。有一點點小長了,因為 CSS 本來就蠻複雜的。對照一下,Servo 的 CSS 部分在 servo/components/style,點進去就會發現它很複雜。CSS 部分已經被拿去在 Firefox 上使用,算是滿完整的,正式的名稱叫做 Stylo,現在只是稍微提一下,之後會再細講。


Parser

今天先針對 parser 來解說, 也就是 css.rs 的部分。(解說都是在程式碼區塊的上方)

定義 css 的規則,所以是規則的向量

pub struct Stylesheet {
    pub rules: Vec<Rule>,
}

一條一條的規則,包含選擇子和宣告

struct Rule {
    selectors: Vec<Selector>,
    declarations: Vec<Declaration>,
}

這邊的 SimpleSelector 只支援單一邏輯,事實上應該還有組合子邏輯,只是在這邊的範例碼中並沒有實現。
舉例來說多重選擇就是像 div > pdiv + p 這樣。

tag 就是 divph1 之類的, id 則是 # 開頭,而 class 就是 . 開頭

pub struct SimpleSelector {
    pub tag_name: Option<String>,
    pub id: Option<String>,
    pub class: Vec<String>,
}

選擇器選到東西之後,會看裡面的內容,也就是宣告了。
例如 padding: 10pxpadding 是名稱,10px 則是他的值。

struct Declaration {
    name: String,
    value: Value,
}

而宣告的值,單位項目都不一樣,必須要有東西來讀他們。例如長度就有可能是 pxem,顏色可以用 white 或是 #000 來表示。這邊就是定義他們。

enum Value {..}
enum Unit {..}
struct Color {..}

簡單的 CSS 選擇器,從左到右讀進去,然後存進 SimpleSelector,這邊並不嚴謹,很多錯誤的輸入會被當成正確的,就像是 *ABC*,但事實上 * 當然不會接在尾巴。一般正式的瀏覽器在解析碰到錯誤的時候,會直接忽視那個錯誤的 CSS,一些奇怪的輸入也會被忽視。所以像是輸入 padding: red 這種完全錯誤的,瀏覽器就不會把他列入 CSS tree 中。

    fn parse_simple_selector(&mut self) -> SimpleSelector {
        let mut selector = SimpleSelector { tag_name: None, id: None, class: Vec::new() };
        while !self.eof() {
            match self.next_char() {
                '#' => {
                    self.consume_char();
                    selector.id = Some(self.parse_identifier());
                }
                '.' => {
                    self.consume_char();
                    selector.class.push(self.parse_identifier());
                }
                '*' => {
                    // universal selector
                    self.consume_char();
                }
                c if valid_identifier_char(c) => {
                    selector.tag_name = Some(self.parse_identifier());
                }
                _ => break
            }
        }
        selector
}

Parser 的細節部分跟昨天的 HTML 都非常接近,像是 parse_declarations 就是抓兩個大括弧中的內容,然後再針對內容有更近一步的拆解,這部分看原始碼就非常好理解了,就不在一一說明。

    /// Parse a list of declarations enclosed in `{ ... }`.
    fn parse_declarations(&mut self) -> Vec<Declaration> {
        assert_eq!(self.consume_char(), '{');
        let mut declarations = Vec::new();
        loop {
            self.consume_whitespace();
            if self.next_char() == '}' {
                self.consume_char();
                break;
            }
            declarations.push(self.parse_declaration());
        }
        declarations
}

最後比較重要的部分是,權重分配。同一個 DOM 可能同時有很多 CSS 定義它,那究竟要選擇哪個當作最後呈現的,這時候就看他們的重要程度啦。一般而言 id > class > tag,所以就可以定義,比方說 id 加權 3,tag 只有加權 1 之類的,最後哪個 CSS 針對這個 DOM 數值最高,就使用它。

impl Selector {
    pub fn specificity(&self) -> Specificity {
        // http://www.w3.org/TR/selectors/#specificity
        let Selector::Simple(ref simple) = *self;
        let a = simple.id.iter().count();
        let b = simple.class.len();
        let c = simple.tag_name.iter().count();
        (a, b, c)
    }
}

希望對大家有幫助,大家明天見!


關於作者

劉安齊

軟體工程師,熱愛寫程式,更喜歡推廣程式讓更多人學會


上一篇
瀏覽器引擎處理 HTML 的簡易版
下一篇
瀏覽器引擎處理 CSS 的簡易版(二)
系列文
來做個網路瀏覽器吧!Let's build a web browser!35

尚未有邦友留言

立即登入留言