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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! The seam between the renderable tree and its markup: [`TagRenderer`] and
//! the [`Children`] answer its `open` gives back.
//!
//! Private module; both items are re-exported from [`crate::render`], and the
//! reasoning that shapes the trait is on the trait itself, where a reader
//! meets it.
use crateTag;
use js;
/// Whether a tag's children are rendered after its opening markup.
///
/// Returned by [`TagRenderer::open`]. [`Html`](super::Html) answers [`Skip`]
/// for a void element, which is upstream's
/// `if (VOID_ELEMENTS.has(name)) return`: the children are dropped, not
/// deferred, and no closing markup follows.
///
/// [`Skip`]: Children::Skip
/// Turns renderable tags into markup.
///
/// Upstream's `renderers/html.ts` is one function that does two things at
/// once: it decides *which nodes* to visit and in what order, and it decides
/// *what bytes* a visited node becomes. Only the second is HTML. This trait is
/// the second decision on its own; the first stays in this crate, in
/// [`render_with`](super::render_with), for a reason that shapes the trait.
///
/// # Why three methods and not one
///
/// The obvious seam hands a host one tag and a callback that renders the
/// children:
///
/// ```text
/// fn render_tag(&self, out: &mut String, tag: &Tag, children: impl FnOnce(&mut String));
/// ```
///
/// That is recursion with the host's frame in the loop, once per level of
/// nesting. Nesting depth comes from the document, which is
/// attacker-controlled, and a stack overflow in Rust aborts the process rather
/// than unwinding into anything a caller could catch. The renderer walks an
/// explicit, heap-allocated stack for exactly this reason, and a callback
/// would hand that stack back to the host one frame at a time, in code this
/// crate cannot see.
///
/// So the trait never asks for children to be rendered. [`open`] writes what
/// precedes them and says whether they follow; the crate walks them;
/// [`close`] writes what comes after. Every method is called for one node,
/// writes, and returns, and the crate never re-enters the renderer. That is
/// the whole of the guarantee, and it is worth being exact about: [`open`]
/// receives the tag with its `children` and `attributes` in plain view, and
/// an implementation that walks either of them itself -- rendering a slot's
/// subtree from inside `open`, say -- has taken the stack back. Return
/// [`Children::Render`] and let the crate do it.
///
/// # What the crate decides, and what the host decides
///
/// The crate owns the shape of the walk: document order; that an array child
/// renders element by element, each reaching the renderer on its own; that
/// `null`, a boolean and an object render as nothing and reach no method at
/// all; and that a tag with no name is a wrapper whose children render in
/// place. None of those reaches an implementation, because none of them is
/// markup. They are upstream's tree semantics, and a host that changed one
/// would be rendering a different tree.
///
/// The host owns everything that is markup: what a tag opens and closes with,
/// which attributes are written and how, which elements are void, and how text
/// is escaped. [`Html`](super::Html) is upstream's answer to each, and three
/// pieces of it are public so that an implementation can keep the parts it
/// wants:
///
/// - [`escape_html_into`](super::escape_html_into) is markdown-it's escaper,
/// the four replacements upstream makes and no others. It does not replace
/// `'`, so it is only safe in text and between double quotes; a renderer
/// that delimits attributes differently needs its own.
/// - [`attribute_value`](super::attribute_value) is ECMAScript's `String(v)`
/// over an attribute, which is what upstream writes between the quotes.
/// - [`is_void_element`](super::is_void_element) is the HTML standard's list.
///
/// Every method writes into `out`, so the output is one `String` appended to
/// from start to finish rather than assembled from pieces.
///
/// # Examples
///
/// A renderer that emits an S-expression instead of HTML:
///
/// ```
/// use accent_proust::render::{Children, TagRenderer, attribute_value, render_with};
/// use accent_proust::renderable::{RenderableTreeNode, Tag};
/// use indexmap::IndexMap;
///
/// struct Sexp;
///
/// impl TagRenderer for Sexp {
/// fn open(&self, out: &mut String, tag: &Tag) -> Children {
/// out.push('(');
/// out.push_str(&tag.name);
/// for (key, value) in &tag.attributes {
/// out.push(' ');
/// out.push_str(key);
/// out.push('=');
/// out.push_str(&attribute_value(value));
/// }
/// Children::Render
/// }
///
/// fn close(&self, out: &mut String, _tag: &Tag) {
/// out.push(')');
/// }
///
/// fn text(&self, out: &mut String, text: &str) {
/// out.push_str(" \"");
/// out.push_str(text);
/// out.push('"');
/// }
/// }
///
/// let heading = Tag::with("h1", IndexMap::new(), vec![RenderableTreeNode::text("hi")]);
/// assert_eq!(render_with(&RenderableTreeNode::tag(heading), &Sexp), r#"(h1 "hi")"#);
/// ```
///
/// [`open`]: TagRenderer::open
/// [`close`]: TagRenderer::close