想象一下,通过
Document
的特征实现,动态包含
Header
或
Paragraph
等对象的
Element
在执行时,我希望把手能够渲染文档及其子文档。因此,应该根据模板的类型来选择模板
Element
use std::{cell::RefCell, error::Error, rc::Rc};
use handlebars::Handlebars;
use serde::Serialize;
pub trait Element: std::fmt::Debug + erased_serde::Serialize {
}
erased_serde::serialize_trait_object!(Element);
#[derive(Debug, Serialize)]
pub struct Document {
pub content: Vec<Rc<RefCell<dyn Element>>>
}
impl Element for Document { }
#[derive(Debug, Serialize)]
pub struct Header {
content: String
}
impl Element for Header { }
#[derive(Debug, Serialize)]
pub struct Paragraph {
content: String
}
impl Element for Paragraph { }
fn main() -> Result<(), Box<dyn Error>> {
let mut handlebars = Handlebars::new();
handlebars.register_template_string("Header", "<h1>{{content}}</h1>".to_string())?;
handlebars.register_template_string("Paragraph", "<p>{{content}}</p>".to_string())?;
handlebars.register_template_string("Document", "{{#each content}}{{this}}\n{{/each}}".to_string())?;
let document = Document {
content: vec![
Rc::new(RefCell::new(Header { content: "Title".to_string()})),
Rc::new(RefCell::new(Paragraph { content: "Text".to_string()})),
]
};
println!("{:?}", document);
println!("{}", handlebars.render("Document", &document)?);
Ok(())
}
调试按预期工作
Document { content: [RefCell { value: Header { content: "Title" } }, RefCell { value: Paragraph { content: "Text" } }] }
但是车把只是渲染
[object]
,而不是在该对象上使用模板
[object]
[object]
我想要实现的输出:
<h1>Title</h1>
<p>Text<p>
如何实现这一目标?如何告诉车把根据对象类型/名称自动选择相应的模板?
我自己目前想到的唯一解决方案是在特质
render
级别引入一个Element
函数,最初在文档上调用它并递归地向下渲染它。
不幸的是,这需要我创建另一个数据结构,例如 json 或 btree 映射,这对我来说似乎是一个很大的开销。
use handlebars::{Handlebars, RenderError};
use serde::Serialize;
use serde_json::json;
use std::{cell::RefCell, error::Error, rc::Rc};
pub trait Element: std::fmt::Debug + erased_serde::Serialize {
fn render(&self, handlebars: &Handlebars) -> Result<String, RenderError>;
}
erased_serde::serialize_trait_object!(Element);
#[derive(Debug, Serialize)]
pub struct Div {
pub content: Vec<Rc<RefCell<dyn Element>>>,
}
impl Element for Div {
fn render(&self, handlebars: &Handlebars) -> Result<String, RenderError> {
let mut children = Vec::new();
for child in self.content.iter() {
children.push(child.as_ref().borrow().render(&handlebars)?);
}
handlebars.render("Div", &json!({"content": children}))
}
}
#[derive(Debug, Serialize)]
pub struct Header {
content: String,
}
impl Element for Header {
fn render(&self, handlebars: &Handlebars) -> Result<String, RenderError> {
handlebars.render("Header", &json!({"content": &self.content}))
}
}
#[derive(Debug, Serialize)]
pub struct Paragraph {
content: String,
}
impl Element for Paragraph {
fn render(&self, handlebars: &Handlebars) -> Result<String, RenderError> {
handlebars.render("Paragraph", &json!({"content": &self.content}))
}
}
fn main() -> Result<(), Box<dyn Error>> {
let mut handlebars = Handlebars::new();
handlebars.register_template_string("Header", "<h1>{{{content}}}</h1>".to_string())?;
handlebars.register_template_string("Paragraph", "<p>{{{content}}}</p>".to_string())?;
handlebars.register_template_string(
"Div",
"<div>{{#each content}}{{{this}}}{{/each}}</div>".to_string(),
)?;
let div = Div {
content: vec![
Rc::new(RefCell::new(Header {
content: "Title".to_string(),
})),
Rc::new(RefCell::new(Paragraph {
content: "Paragraph One".to_string(),
})),
Rc::new(RefCell::new(Div {
content: vec![Rc::new(RefCell::new(Paragraph {
content: "Nested Paragraph".to_string(),
}))],
})),
Rc::new(RefCell::new(Paragraph {
content: "Paragraph Two".to_string(),
})),
],
};
println!("{}", div.render(&handlebars).unwrap());
Ok(())
}