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