yarte_dom/
dom_fmt.rs

1use markup5ever::{local_name, namespace_url, ns};
2use quote::quote;
3use syn::parse2;
4
5use yarte_hir::{Each as HEach, IfElse as HIfElse, Struct, HIR};
6use yarte_html::{
7    interface::{QualName, YName},
8    serializer::SerializerOpt,
9    utils::MARK,
10    y_name,
11};
12
13use crate::{
14    serialize::serialize,
15    sink::{parse_document, parse_fragment, ParseAttribute, ParseElement, ParseResult, Sink},
16};
17
18pub struct DOMFmt(pub Vec<HIR>);
19
20// TODO: to try from
21impl From<Vec<HIR>> for DOMFmt {
22    fn from(ir: Vec<HIR>) -> Self {
23        DOMFmt(to_domfmt_init(ir).expect("correct html"))
24    }
25}
26
27const HASH: &str = "0x00000000";
28
29fn get_html(ir: &[HIR]) -> String {
30    let mut html = String::new();
31    for x in ir {
32        match x {
33            HIR::Lit(x) => html.push_str(x),
34            _ => {
35                html.push_str(MARK);
36                html.push_str(HASH);
37            }
38        }
39    }
40
41    html
42}
43
44pub fn to_wasmfmt(mut ir: Vec<HIR>, s: &Struct) -> ParseResult<Vec<HIR>> {
45    let html = get_html(&ir);
46    let sink = match parse_document(&html) {
47        Ok(mut sink) => {
48            add_scripts(s, &mut sink, &mut ir);
49            sink
50        }
51        Err(_) => parse_fragment(&html)?,
52    };
53
54    serialize_domfmt(sink, ir, SerializerOpt { wasm: true })
55}
56
57fn add_scripts(s: &Struct, sink: &mut Sink, ir: &mut Vec<HIR>) {
58    let mut body: Option<usize> = None;
59    use ParseElement::*;
60    match sink.nodes.values().next() {
61        Some(Document(children)) => {
62            if let Some(Node { name, children, .. }) = sink.nodes.get(&children[0]) {
63                if let y_name!("html") = name.local {
64                    for i in children {
65                        if let Some(Node { name, .. }) = sink.nodes.get(i) {
66                            if let y_name!("body") = name.local {
67                                body = Some(*i);
68                            }
69                        }
70                    }
71                }
72            }
73        }
74        _ => panic!("Need <!doctype html>"),
75    }
76
77    let mut last = *sink.nodes.keys().last().unwrap() + 1;
78    let get_state = format!(
79        "function get_state(){{return JSON.stringify({}{});}}",
80        MARK, HASH
81    );
82
83    ir.push(HIR::Safe(Box::new(
84        parse2(quote!(yarte::Json(&self))).unwrap(),
85    )));
86
87    let state = Node {
88        name: QualName {
89            prefix: None,
90            ns: ns!(html),
91            local: y_name!("script"),
92        },
93        attrs: vec![],
94        children: vec![last],
95        parent: None,
96    };
97    sink.nodes.insert(last, Text(get_state));
98    last += 1;
99    sink.nodes.insert(last, state);
100    let state = last;
101    last += 1;
102
103    let init_s = format!(
104        "import init from '{}';async function run(){{await init()}}run()",
105        s.script.as_ref().expect("Need `script` attribute")
106    );
107    let init = Node {
108        name: QualName {
109            prefix: None,
110            ns: ns!(html),
111            local: y_name!("script"),
112        },
113        attrs: vec![ParseAttribute {
114            name: QualName {
115                prefix: None,
116                ns: ns!(),
117                local: y_name!("type"),
118            },
119            value: "module".to_string(),
120        }],
121        children: vec![last],
122        parent: None,
123    };
124    sink.nodes.insert(last, Text(init_s));
125    last += 1;
126    sink.nodes.insert(last, init);
127    match sink.nodes.get_mut(&body.expect("body defined")).unwrap() {
128        Node { children, .. } => {
129            children.push(state);
130            children.push(last);
131        }
132        _ => unreachable!(),
133    }
134}
135
136fn to_domfmt_init(ir: Vec<HIR>) -> ParseResult<Vec<HIR>> {
137    let html = get_html(&ir);
138    let sink = match parse_document(&html) {
139        Ok(a) => a,
140        Err(_) => parse_fragment(&html)?,
141    };
142
143    serialize_domfmt(sink, ir, Default::default())
144}
145
146fn to_domfmt(ir: Vec<HIR>, opts: SerializerOpt) -> ParseResult<Vec<HIR>> {
147    let html = get_html(&ir);
148    serialize_domfmt(parse_fragment(&html)?, ir, opts)
149}
150
151fn serialize_domfmt(sink: Sink, mut ir: Vec<HIR>, opts: SerializerOpt) -> ParseResult<Vec<HIR>> {
152    let mut writer = Vec::new();
153    serialize(&mut writer, sink.into(), opts).expect("some serialize node");
154
155    let html = String::from_utf8(writer).expect("");
156    let mut chunks = html.split(MARK).peekable();
157
158    if let Some(first) = chunks.peek() {
159        if first.is_empty() {
160            chunks.next();
161        }
162    }
163    let mut ir = ir.drain(..).filter(|x| !matches!(x, HIR::Lit(_)));
164
165    let mut buff = vec![];
166    for chunk in chunks {
167        if chunk.is_empty() {
168            panic!("chunk empty")
169        } else if let Some(cut) = chunk.strip_prefix(HASH) {
170            resolve_node(ir.next().expect("Some HIR expression"), &mut buff, opts)?;
171            if !cut.is_empty() {
172                buff.push(HIR::Lit(cut.into()));
173            }
174        } else {
175            buff.push(HIR::Lit(chunk.into()));
176        }
177    }
178
179    // Standard or empty case (with only comments,...)
180    assert!(ir.next().is_none());
181
182    Ok(buff)
183}
184
185fn resolve_node(ir: HIR, buff: &mut Vec<HIR>, opts: SerializerOpt) -> ParseResult<()> {
186    match ir {
187        HIR::Each(each) => {
188            let HEach { args, body, expr } = *each;
189            buff.push(HIR::Each(Box::new(HEach {
190                args,
191                expr,
192                body: to_domfmt(body, opts)?,
193            })))
194        }
195        HIR::IfElse(if_else) => {
196            let HIfElse { ifs, if_else, els } = *if_else;
197            let mut buf_if_else = vec![];
198            for (expr, body) in if_else {
199                buf_if_else.push((expr, to_domfmt(body, opts)?));
200            }
201            let els = if let Some(els) = els {
202                Some(to_domfmt(els, opts)?)
203            } else {
204                None
205            };
206            buff.push(HIR::IfElse(Box::new(HIfElse {
207                ifs: (ifs.0, to_domfmt(ifs.1, opts)?),
208                if_else: buf_if_else,
209                els,
210            })));
211        }
212        HIR::Lit(_) => panic!("Need some node"),
213        ir => buff.push(ir),
214    }
215    Ok(())
216}