7 个版本

0.1.6 2022年3月26日
0.1.5 2022年3月26日

#79 in #dom

MIT 许可证

21KB
454

Topus

javascript 相比,rust 有更加严谨的语法,编译期能发现 bug 的优点。既然 javascript 能编写 html,那么使用 rust 编写 html 的体验可能会更好。基于这个想法,我编写了 topus

我的想法是

  1. 构建一个简单的 struct DOM,由上至下有 enum Nodeenum Attribute
  2. 实现 Display trait,通过 to_string() 转换成字符串
  3. 创建 html 文件,写入字符串

属性

为了降低学习难度,所有字符串类型均为 String,而不是带有生命周期的 &str

enum Attribute 有两个 attribute 变体。

  1. Boolean(String),代表布尔属性,如 hidden
  2. Normal { key: String, value: Sting },代表普通属性,如 style="display: None"

创建方式

  1. 直接创建
let hidden = Attribute::Boolean("hidden".to_string());
let style = Attribute::Normal {
    key: "style".to_string(),
    value: "display: None".to_string()
};
let http_equiv = Attribute::Normal {
    key: "http-equiv".to_string(),
    value: "X-UA-Compatible".to_string()
};
  1. 宏创建
let macro_hidden = attribute!(hidden);
let macro_style = attribute!(style="display: None");
let macro_http_equiv = attribute!(http-equiv="X-UA-Compatible");

推荐使用宏创建 Attribute,方便快捷。

断言

assert_eq!(hidden, macro_hidden);
assert_eq!(style, macro_style);
assert_eq!(http_equiv, macro_http_equiv);

创建 Vec<Attribute>

使用 attributes 宏可以很方便地创建 Vec

let attributes = attributes!(html style="display:None");
assert_eq!(
    vec![ 
        Attribute::Normal{
            key: "style".to_string(),
            value: "display:None".to_string()
        },
        Attribute::Boolean("html".to_string())],
    attributes);

细心的应该发现问题了,htmlstyle="display:None" 属性是逆向加入 Vec 容器的。

节点

enum Node 有三个变体。

  1. 元素 { node_name: String, attributes: Vec<Attribute>, child_nodes: Vec<Node>},代表element node
  2. Text { node_value: String },代表text node
  3. Comment { node_value: String },代表comment node

创建方式

  1. 直接创建
let text = Node::Text { node_value: "hello world".to_string() };
let comment = Node::Comment { node_value: "comment".to_string()};

let doctype = Node::Element {
    node_name: "!DOCTYPE".to_string(),
    attributes: attributes!(html),
    child_nodes: Vec::<Node>::with_capacity(0)
};
let a = Node::Element {
    node_name: "a".to_string(),
    attributes: attributes!(hidden style="display:None"),
    child_nodes: Vec::<Node>::with_capacity(0)
};
  1. 宏创建
let macro_text = text!("hello world");
let macro_comment = comment!("comment");

let macro_doctype = element!(!DOCTYPE html);
let macro_a = element!(a hidden style="display:None");

断言

assert_eq!(text, macro_text);
assert_eq!(comment, macro_comment);
assert_eq!(doctype, macro_doctype);

assert_eq!(a, macro_a);
assert_eq!("<a hidden style=\"display:None\">".to_string(), macro_a.to_string());

细心地又发现了,macro_a.to_string() 中的 hiddenstyle="display: None" 属性顺序是正向的,因为在实现 Display trait 过程中,通过 attributes.iter().rev() 逆转了 attributes 的显示顺序。

创建 Vec<Node>

使用 elements 宏可以很方便地创建 Vec<Node>

let nodes = nodes!(head body);
assert_eq!(
    vec![ 
        element!(body),
        element!(head),],
    attrs);

同样的,headbody 节点是逆序的。

使用 expression

element! 宏调用中我们也可以传入表达式参数。如

let html = element!(html charset="UTF-8" =>
	element!(head =>
        element!(meta charset="UTF-8"),
        element!(meta http-equiv="X-UA-Compatible" content="IE=edge"),
        element!(meta name="viewport" content="width=device-width, initial-scale=1.0"),
        element!(title => text!(title),),
    ),
    element!(body => text!(""),),
);

AttributeNode 表达式后面要跟着 ,Vec<Attribute>Vec<Node> 表达式后面要跟着 ;

生成 html 文件

通过 build! 宏,生成 html 文件。

build!(html => "index.html");

无运行时依赖