rst_renderer/html/
elems_cats.rs

1use std::io::Write;
2
3use anyhow::{Error, bail};
4
5// use crate::url::Url;
6use super::{HTMLRender, HTMLRenderer, escape_html};
7use document_tree::{
8    Element, ExtraAttributes, HasChildren, attribute_types as at, element_categories as c,
9    elements as e, extra_attributes as a,
10};
11
12// static FOOTNOTE_SYMBOLS: [char; 10] = ['*', '†', '‡', '§', '¶', '#', '♠', '♥', '♦', '♣'];
13
14macro_rules! impl_html_render_cat {($cat:ident { $($member:ident),+ }) => {
15    impl HTMLRender for c::$cat {
16        fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write {
17            match self {$(
18                c::$cat::$member(elem) => elem.render_html(renderer),
19            )+}
20        }
21    }
22}}
23
24macro_rules! impl_html_render_simple {
25    (
26        $type1:ident => $tag1:ident,
27        $( $type:ident => $tag:ident ),+
28    ) => {
29        impl_html_render_simple!($type1 => $tag1);
30        $( impl_html_render_simple!($type => $tag); )+
31    };
32    ( $type:ident => $tag:ident ) => {
33        impl HTMLRender for e::$type {
34            fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write {
35                write!(renderer.stream, "<{}", stringify!($tag))?;
36                if self.classes().len() > 0 {
37                    write!(renderer.stream, " class=\"{}\"", self.classes().join(" "))?;
38                }
39                write!(renderer.stream, ">")?;
40                self.children().render_html(renderer)?;
41                write!(renderer.stream, "</{}>", stringify!($tag))?;
42                Ok(())
43            }
44        }
45    };
46}
47
48macro_rules! impl_html_render_simple_nochildren {( $($type:ident => $tag:ident),+ ) => { $(
49    impl HTMLRender for e::$type {
50        fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write {
51            write!(renderer.stream, "<{0}></{0}>", stringify!($tag))?;
52            Ok(())
53        }
54    }
55)+ }}
56
57// Impl
58
59impl_html_render_cat!(StructuralSubElement {
60    Title,
61    Subtitle,
62    Decoration,
63    Docinfo,
64    SubStructure
65});
66impl_html_render_simple!(Subtitle => h2);
67
68impl HTMLRender for e::Title {
69    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
70    where
71        W: Write,
72    {
73        let level = if renderer.level > 6 {
74            6
75        } else {
76            renderer.level
77        };
78        write!(renderer.stream, "<h{level}>")?;
79        self.children().render_html(renderer)?;
80        write!(renderer.stream, "</h{level}>")?;
81        Ok(())
82    }
83}
84
85impl HTMLRender for e::Docinfo {
86    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
87    where
88        W: Write,
89    {
90        // Like “YAML frontmatter” in Markdown
91        unimplemented!();
92    }
93}
94
95impl HTMLRender for e::Decoration {
96    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
97    where
98        W: Write,
99    {
100        // Header or footer
101        unimplemented!();
102    }
103}
104
105impl_html_render_cat!(SubStructure {
106    Topic,
107    Sidebar,
108    Transition,
109    Section,
110    BodyElement
111});
112impl_html_render_simple!(Sidebar => aside);
113
114impl HTMLRender for e::Section {
115    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
116    where
117        W: Write,
118    {
119        renderer.level += 1;
120        write!(renderer.stream, "<section id=\"{0}\">", self.ids()[0].0)?;
121        self.children().render_html(renderer)?;
122        write!(renderer.stream, "</section>")?;
123        renderer.level -= 1;
124        Ok(())
125    }
126}
127
128impl HTMLRender for e::Transition {
129    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
130    where
131        W: Write,
132    {
133        write!(renderer.stream, "<hr/>")?;
134        Ok(())
135    }
136}
137
138impl HTMLRender for e::Topic {
139    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
140    where
141        W: Write,
142    {
143        // A mini section with title
144        unimplemented!();
145    }
146}
147
148impl_html_render_cat!(BodyElement {
149    Paragraph,
150    LiteralBlock,
151    DoctestBlock,
152    MathBlock,
153    Rubric,
154    SubstitutionDefinition,
155    Comment,
156    Pending,
157    Target,
158    Raw,
159    Image,
160    Compound,
161    Container,
162    BulletList,
163    EnumeratedList,
164    DefinitionList,
165    FieldList,
166    OptionList,
167    LineBlock,
168    BlockQuote,
169    Admonition,
170    Attention,
171    Hint,
172    Note,
173    Caution,
174    Danger,
175    Error,
176    Important,
177    Tip,
178    Warning,
179    Footnote,
180    Citation,
181    SystemMessage,
182    Figure,
183    Table
184});
185impl_html_render_simple!(Paragraph => p, MathBlock => math, Rubric => a, Compound => p, Container => div, BulletList => ul, EnumeratedList => ol, DefinitionList => dl, FieldList => dl, OptionList => pre, LineBlock => div, BlockQuote => blockquote, Admonition => aside, Attention => aside, Hint => aside, Note => aside, Caution => aside, Danger => aside, Error => aside, Important => aside, Tip => aside, Warning => aside, Figure => figure);
186impl_html_render_simple_nochildren!(Table => table); //TODO: after implementing the table, move it to elems with children
187
188// circumvent E0119
189trait IMark {}
190impl IMark for e::Image {}
191impl IMark for e::ImageInline {}
192impl<I> HTMLRender for I
193where
194    I: e::Element + a::ExtraAttributes<a::Image> + IMark,
195{
196    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
197    where
198        W: Write,
199    {
200        let extra = self.extra();
201        if let Some(target) = extra.target.as_ref() {
202            write!(
203                renderer.stream,
204                "<a href=\"{}\">",
205                escape_html(target.as_str())
206            )?;
207        }
208        write!(renderer.stream, "<img")?;
209        if let Some(alt) = extra.alt.as_ref() {
210            write!(renderer.stream, " alt=\"{}\"", escape_html(alt))?;
211        }
212        // TODO: align: Option<AlignHV>
213        // TODO: height: Option<Measure>
214        // TODO: width: Option<Measure>
215        // TODO: scale: Option<u8>
216        write!(
217            renderer.stream,
218            " src=\"{}\" />",
219            escape_html(extra.uri.as_str())
220        )?;
221        if extra.target.is_some() {
222            write!(renderer.stream, "</a>")?;
223        }
224        Ok(())
225    }
226}
227
228impl HTMLRender for e::LiteralBlock {
229    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
230    where
231        W: Write,
232    {
233        let mut cls_iter = self.classes().iter();
234        let is_code = cls_iter.next() == Some(&"code".to_owned());
235        write!(renderer.stream, "<pre>")?;
236        if is_code {
237            // TODO: support those classes not being at the start
238            if let Some(lang) = cls_iter.next() {
239                write!(renderer.stream, "<code class=\"language-{lang}\">")?;
240            } else {
241                write!(renderer.stream, "<code>")?;
242            }
243        }
244        self.children().render_html(renderer)?;
245        if is_code {
246            write!(renderer.stream, "</code>")?;
247        }
248        write!(renderer.stream, "</pre>")?;
249        Ok(())
250    }
251}
252
253impl HTMLRender for e::DoctestBlock {
254    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
255    where
256        W: Write,
257    {
258        // TODO
259        unimplemented!();
260    }
261}
262
263impl HTMLRender for e::SubstitutionDefinition {
264    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
265    where
266        W: Write,
267    {
268        // TODO: Should those be removed after resolving them
269        Ok(())
270    }
271}
272
273impl HTMLRender for e::Comment {
274    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
275    where
276        W: Write,
277    {
278        write!(renderer.stream, "<!--")?;
279        self.children().render_html(renderer)?;
280        write!(renderer.stream, "-->")?;
281        Ok(())
282    }
283}
284
285impl HTMLRender for e::Pending {
286    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
287    where
288        W: Write,
289    {
290        // Will those be resolved by the time we get here?
291        unimplemented!();
292    }
293}
294
295impl HTMLRender for e::Target {
296    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
297    where
298        W: Write,
299    {
300        // Should be resolved by now
301        Ok(())
302    }
303}
304
305impl HTMLRender for e::Raw {
306    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
307    where
308        W: Write,
309    {
310        let extra = self.extra();
311        if extra.format.contains(&at::NameToken("html".to_owned())) {
312            for c in self.children() {
313                write!(renderer.stream, "{c}")?;
314            }
315        }
316        Ok(())
317    }
318}
319
320impl HTMLRender for e::Footnote {
321    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
322    where
323        W: Write,
324    {
325        use c::SubFootnote::BodyElement;
326
327        let mut children = self.children().iter();
328        match (self.get_label(), self.extra().auto) {
329            (Ok(label), Some(at::AutoFootnoteType::Symbol)) => {
330                children.next(); // skip over the label
331                write!(renderer.stream, "<li value=\"{label}\" class=\"symbol\">")?;
332            }
333            (Ok(label), _) => {
334                children.next(); // skip over the label
335                write!(renderer.stream, "<li value=\"{label}\">")?;
336            }
337            (Err(_), _) => {
338                write!(renderer.stream, "<li>")?;
339            }
340        }
341        for child in children {
342            let BodyElement(child) = child else {
343                bail!("Cannot have a footnote label anywhere but as first child node");
344            };
345            child.render_html(renderer)?;
346        }
347        write!(renderer.stream, "</li>")?;
348        Ok(())
349    }
350}
351
352impl HTMLRender for e::Citation {
353    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
354    where
355        W: Write,
356    {
357        unimplemented!();
358    }
359}
360
361impl HTMLRender for e::SystemMessage {
362    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
363    where
364        W: Write,
365    {
366        write!(renderer.stream, "<figure><caption>System Message</caption>")?;
367        self.children().render_html(renderer)?;
368        write!(renderer.stream, "</figure>")?;
369        Ok(())
370    }
371}
372
373impl_html_render_cat!(TextOrInlineElement {
374    String,
375    Emphasis,
376    Strong,
377    Literal,
378    Reference,
379    FootnoteReference,
380    CitationReference,
381    SubstitutionReference,
382    TitleReference,
383    Abbreviation,
384    Acronym,
385    Superscript,
386    Subscript,
387    Inline,
388    Problematic,
389    Generated,
390    Math,
391    TargetInline,
392    RawInline,
393    ImageInline
394});
395impl_html_render_simple!(Emphasis => em, Strong => strong, Literal => code, FootnoteReference => a, CitationReference => a, TitleReference => a, Abbreviation => abbr, Acronym => acronym, Superscript => sup, Subscript => sub, Inline => span, Math => math, TargetInline => a);
396
397impl HTMLRender for String {
398    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
399    where
400        W: Write,
401    {
402        write!(renderer.stream, "{}", escape_html(self))?;
403        Ok(())
404    }
405}
406
407impl HTMLRender for e::Reference {
408    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
409    where
410        W: Write,
411    {
412        let extra = self.extra();
413        write!(renderer.stream, "<a")?;
414        if let Some(target) = extra.refuri.as_ref() {
415            write!(
416                renderer.stream,
417                " href=\"{}\"",
418                escape_html(target.as_str())
419            )?;
420        }
421        /*
422        if let Some(name) = extra.name.as_ref() {
423            write!(renderer.stream, " title=\"{}\"", escape_html(&name.0))?;
424        }
425        */
426        write!(renderer.stream, ">")?;
427        self.children().render_html(renderer)?;
428        write!(renderer.stream, "</a>")?;
429        Ok(())
430    }
431}
432
433impl HTMLRender for e::SubstitutionReference {
434    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
435    where
436        W: Write,
437    {
438        // Will those be resolved by the time we get here?
439        unimplemented!();
440    }
441}
442
443impl HTMLRender for e::Problematic {
444    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
445    where
446        W: Write,
447    {
448        // Broken inline markup leads to insertion of this in docutils
449        unimplemented!();
450    }
451}
452
453impl HTMLRender for e::Generated {
454    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
455    where
456        W: Write,
457    {
458        // Section numbers and so on
459        unimplemented!();
460    }
461}
462
463impl HTMLRender for e::RawInline {
464    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
465    where
466        W: Write,
467    {
468        self.children().render_html(renderer)
469    }
470}
471
472//--------------\\
473//Content Models\\
474//--------------\\
475
476impl_html_render_cat!(SubTopic { Title, BodyElement });
477impl_html_render_cat!(SubSidebar {
478    Topic,
479    Title,
480    Subtitle,
481    BodyElement
482});
483impl_html_render_simple!(ListItem => li);
484
485impl HTMLRender for e::DefinitionListItem {
486    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
487    where
488        W: Write,
489    {
490        // Term→dt, Definition→dd, Classifier→???
491        unimplemented!();
492    }
493}
494
495impl HTMLRender for e::Field {
496    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
497    where
498        W: Write,
499    {
500        // FieldName→dt, FieldBody→dd
501        unimplemented!();
502    }
503}
504
505impl HTMLRender for e::OptionListItem {
506    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
507    where
508        W: Write,
509    {
510        // OptionGroup→dt(s), Description→dd
511        unimplemented!();
512    }
513}
514
515impl_html_render_cat!(SubLineBlock { LineBlock, Line });
516
517impl HTMLRender for e::Line {
518    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
519    where
520        W: Write,
521    {
522        self.children().render_html(renderer)?;
523        write!(renderer.stream, "<br>")?;
524        Ok(())
525    }
526}
527
528impl_html_render_cat!(SubBlockQuote {
529    Attribution,
530    BodyElement
531});
532impl_html_render_simple!(Attribution => cite); //TODO: correct?
533
534impl_html_render_cat!(SubFigure {
535    Caption,
536    Legend,
537    BodyElement
538});
539impl_html_render_simple!(Caption => caption);
540
541impl HTMLRender for e::Legend {
542    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
543    where
544        W: Write,
545    {
546        unimplemented!();
547    }
548}