-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathyattag.rs
135 lines (114 loc) · 3.44 KB
/
yattag.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
* Copyright 2021 Miklos Vajna
*
* SPDX-License-Identifier: MIT
*/
#![deny(warnings)]
#![warn(clippy::all)]
#![warn(missing_docs)]
//! Generate HTML with Rust.
//!
//! This is more or less a Rust port of <https://www.yattag.org/>, mostly because
//! <https://crates.io/crates/html-builder> would require you to manually escape attribute values.
use std::cell::RefCell;
use std::fmt::Write as _;
use std::rc::Rc;
/// Generates xml/html documents.
#[derive(Clone)]
pub struct Doc {
value: Rc<RefCell<String>>,
}
impl Doc {
/// Creates an empty Doc.
pub fn new() -> Doc {
Doc {
value: Rc::new(RefCell::new(String::from(""))),
}
}
/// Factory of yattag.Doc from a string.
pub fn from_text(text: &str) -> Self {
let doc = Doc::new();
doc.text(text);
doc
}
/// Gets the escaped value.
pub fn get_value(&self) -> String {
self.value.borrow().to_string()
}
/// Appends escaped content to the value.
pub fn append_value(&self, value: String) {
self.value.borrow_mut().push_str(&value)
}
/// Starts a new tag.
pub fn tag(&self, name: &str, attrs: &[(&str, &str)]) -> Tag {
Tag::new(&self.value, name, attrs)
}
/// Starts a new tag and closes it as well.
pub fn stag(&self, name: &str) {
self.append_value(format!("<{name} />"));
}
/// Appends unescaped content to the document.
pub fn text(&self, text: &str) {
let encoded = html_escape::encode_safe(text).to_string();
self.append_value(encoded);
}
}
/// HtmlTable is a matrix (rows, then cols) of Doc instances.
pub type HtmlTable = Vec<Vec<Doc>>;
impl Default for Doc {
fn default() -> Self {
Self::new()
}
}
/// Starts a tag, which is closed automatically.
pub struct Tag {
value: Rc<RefCell<String>>,
name: String,
}
impl Tag {
fn new(value: &Rc<RefCell<String>>, name: &str, attrs: &[(&str, &str)]) -> Tag {
let mut guard = value.borrow_mut();
guard.push_str(&format!("<{name}"));
for attr in attrs {
let key = attr.0;
let val = html_escape::encode_double_quoted_attribute(&attr.1);
guard.push_str(&format!(" {key}=\"{val}\""));
}
guard.push('>');
let value = value.clone();
Tag {
value,
name: name.to_string(),
}
}
/// Appends unescaped content inside a tag.
pub fn text(&self, text: &str) {
let encoded = html_escape::encode_safe(text).to_string();
self.value.borrow_mut().push_str(&encoded)
}
/// Starts a new tag inside a tag.
pub fn tag(&self, name: &str, attrs: &[(&str, &str)]) -> Tag {
Tag::new(&self.value, name, attrs)
}
/// Starts a new tag and closes it as well, inside a tag.
pub fn stag(&self, name: &str, attrs: &[(&str, &str)]) {
self.append_value(format!("<{name}"));
for attr in attrs {
let key = attr.0;
let value = html_escape::encode_double_quoted_attribute(&attr.1);
self.append_value(format!(" {key}=\"{value}\""));
}
self.append_value(String::from("/>"))
}
/// Appends escaped content inside a tag.
pub fn append_value(&self, value: String) {
self.value.borrow_mut().push_str(&value)
}
}
impl Drop for Tag {
fn drop(&mut self) {
let _ = write!(self.value.borrow_mut(), "</{}>", self.name);
}
}
#[cfg(test)]
mod tests;