diff options
Diffstat (limited to 'renderer/src')
| -rw-r--r-- | renderer/src/html.rs | 393 | ||||
| -rw-r--r-- | renderer/src/html/tests.rs | 275 | ||||
| -rw-r--r-- | renderer/src/lib.rs | 21 |
3 files changed, 689 insertions, 0 deletions
diff --git a/renderer/src/html.rs b/renderer/src/html.rs new file mode 100644 index 0000000..73b994d --- /dev/null +++ b/renderer/src/html.rs @@ -0,0 +1,393 @@ +#[cfg(test)] +pub mod tests; + +use std::io::Write; + +use failure::Error; + +// use crate::url::Url; +use document_tree::{ + Document,Element,HasChildren,ExtraAttributes, + elements as e, + element_categories as c, +}; + + +// static FOOTNOTE_SYMBOLS: [char; 10] = ['*', '†', '‡', '§', '¶', '#', '♠', '♥', '♦', '♣']; + +pub fn render_html<W>(document: &Document, stream: W, standalone: bool) -> Result<(), Error> where W: Write { + let mut renderer = HTMLRenderer { stream, level: 0 }; + if standalone { + document.render_html(&mut renderer) + } else { + for c in document.children() { + (*c).render_html(&mut renderer)?; + writeln!(renderer.stream)?; + } + Ok(()) + } +} + +fn escape_html(text: &str) -> String { + text.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) +} + +struct HTMLRenderer<W> where W: Write { + stream: W, + level: u8, +} + +trait HTMLRender { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write; +} + +macro_rules! impl_html_render_cat {($cat:ident { $($member:ident),+ }) => { + impl HTMLRender for c::$cat { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + match self {$( + c::$cat::$member(elem) => (**elem).render_html(renderer), + )+} + } + } +}} + +macro_rules! impl_html_render_simple { + ( + $type1:ident => $tag1:ident $( [$($post1:tt)+] )?, + $( $type:ident => $tag:ident $( [$($post:tt)+] )? ),+ + ) => { + impl_html_render_simple!($type1 => $tag1 $([$($post1)+])?); + $( impl_html_render_simple!($type => $tag $([$($post)+])?); )+ + }; + ( $type:ident => $tag:ident ) => { + impl_html_render_simple!($type => $tag[""]); + }; + ( $type:ident => $tag:ident [ $post:expr ] ) => { + impl HTMLRender for e::$type { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + let multiple_children = self.children().len() > 1; + write!(renderer.stream, "<{}>", stringify!($tag))?; + if multiple_children { write!(renderer.stream, $post)?; } + for c in self.children() { + (*c).render_html(renderer)?; + if multiple_children { write!(renderer.stream, $post)?; } + } + write!(renderer.stream, "</{}>", stringify!($tag))?; + Ok(()) + } + } + }; +} + +macro_rules! impl_html_render_simple_nochildren {( $($type:ident => $tag:ident),+ ) => { $( + impl HTMLRender for e::$type { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + write!(renderer.stream, "<{0}></{0}>", stringify!($tag))?; + Ok(()) + } + } +)+ }} + +// Impl + +impl HTMLRender for Document { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + writeln!(renderer.stream, "<!doctype html><html>")?; + for c in self.children() { + (*c).render_html(renderer)?; + writeln!(renderer.stream)?; + } + writeln!(renderer.stream, "</html>")?; + Ok(()) + } +} + +impl_html_render_cat!(StructuralSubElement { Title, Subtitle, Decoration, Docinfo, SubStructure }); +impl_html_render_simple!(Subtitle => h2); + +impl HTMLRender for e::Title { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + let level = if renderer.level > 6 { 6 } else { renderer.level }; + write!(renderer.stream, "<h{0}>", level)?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "</h{0}>", level)?; + Ok(()) + } +} + +impl HTMLRender for e::Docinfo { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Like “YAML frontmatter” in Markdown + unimplemented!(); + } +} + +impl HTMLRender for e::Decoration { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Header or footer + unimplemented!(); + } +} + +impl_html_render_cat!(SubStructure { Topic, Sidebar, Transition, Section, BodyElement }); +impl_html_render_simple!(Sidebar => aside); + +impl HTMLRender for e::Section { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + renderer.level += 1; + writeln!(renderer.stream, "<section id=\"{0}\">", self.ids()[0].0)?; + for c in self.children() { + (*c).render_html(renderer)?; + writeln!(renderer.stream)?; + } + write!(renderer.stream, "</section>")?; + Ok(()) + } +} + +impl HTMLRender for e::Transition { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + write!(renderer.stream, "<hr/>")?; + Ok(()) + } +} + +impl HTMLRender for e::Topic { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // A mini section with title + unimplemented!(); + } +} + +impl_html_render_cat!(BodyElement { Paragraph, LiteralBlock, DoctestBlock, MathBlock, Rubric, SubstitutionDefinition, Comment, Pending, Target, Raw, Image, Compound, Container, BulletList, EnumeratedList, DefinitionList, FieldList, OptionList, LineBlock, BlockQuote, Admonition, Attention, Hint, Note, Caution, Danger, Error, Important, Tip, Warning, Footnote, Citation, SystemMessage, Figure, Table }); +impl_html_render_simple!(Paragraph => p, LiteralBlock => pre, MathBlock => math, Rubric => a, Compound => p, Container => div, BulletList => ul["\n"], EnumeratedList => ol["\n"], DefinitionList => dl["\n"], FieldList => dl["\n"], OptionList => pre, LineBlock => div["\n"], BlockQuote => blockquote, Admonition => aside, Attention => aside, Hint => aside, Note => aside, Caution => aside, Danger => aside, Error => aside, Important => aside, Tip => aside, Warning => aside, Figure => figure); +impl_html_render_simple_nochildren!(Table => table); //TODO: after implementing the table, move it to elems with children + +//impl<I> HTMLRender for I where I: e::Element + a::ExtraAttributes<a::Image> +macro_rules! impl_render_html_image { ($t:ty) => { impl HTMLRender for $t { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + let extra = self.extra(); + if let Some(ref target) = extra.target { + write!(renderer.stream, "<a href=\"{}\">", escape_html(target.as_str()))?; + } + write!(renderer.stream, "<img")?; + if let Some(ref alt) = extra.alt { + write!(renderer.stream, " alt=\"{}\"", escape_html(alt))?; + } + // TODO: align: Option<AlignHV> + // TODO: height: Option<Measure> + // TODO: width: Option<Measure> + // TODO: scale: Option<u8> + write!(renderer.stream, " src=\"{}\" />", escape_html(extra.uri.as_str()))?; + if extra.target.is_some() { + write!(renderer.stream, "</a>")?; + } + Ok(()) + } +}}} +impl_render_html_image!(e::Image); +impl_render_html_image!(e::ImageInline); + +impl HTMLRender for e::DoctestBlock { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // TODO + unimplemented!(); + } +} + +impl HTMLRender for e::SubstitutionDefinition { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // TODO: Should those be removed after resolving them + Ok(()) + } +} + +impl HTMLRender for e::Comment { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + write!(renderer.stream, "<!--")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "-->")?; + Ok(()) + } +} + +impl HTMLRender for e::Pending { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Will those be resolved by the time we get here? + unimplemented!(); + } +} + +impl HTMLRender for e::Target { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Should be resolved by now + Ok(()) + } +} + +impl HTMLRender for e::Raw { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + for c in self.children() { + write!(renderer.stream, "{}", c)?; + } + Ok(()) + } +} + +impl HTMLRender for e::Footnote { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + unimplemented!(); + } +} + +impl HTMLRender for e::Citation { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + unimplemented!(); + } +} + +impl HTMLRender for e::SystemMessage { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + write!(renderer.stream, "<figure><caption>System Message</caption>")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "</figure>")?; + Ok(()) + } +} + +impl_html_render_cat!(TextOrInlineElement { String, Emphasis, Strong, Literal, Reference, FootnoteReference, CitationReference, SubstitutionReference, TitleReference, Abbreviation, Acronym, Superscript, Subscript, Inline, Problematic, Generated, Math, TargetInline, RawInline, ImageInline }); +impl_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); + +impl HTMLRender for String { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + write!(renderer.stream, "{}", escape_html(self))?; + Ok(()) + } +} + +impl HTMLRender for e::Reference { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + let extra = self.extra(); + write!(renderer.stream, "<a")?; + if let Some(ref target) = extra.refuri { + write!(renderer.stream, " href=\"{}\"", escape_html(target.as_str()))?; + } + /* + if let Some(ref name) = extra.name { + write!(renderer.stream, " title=\"{}\"", escape_html(&name.0))?; + } + */ + write!(renderer.stream, ">")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "</a>")?; + Ok(()) + } +} + +impl HTMLRender for e::SubstitutionReference { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Will those be resolved by the time we get here? + unimplemented!(); + } +} + +impl HTMLRender for e::Problematic { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Broken inline markup leads to insertion of this in docutils + unimplemented!(); + } +} + +impl HTMLRender for e::Generated { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Section numbers and so on + unimplemented!(); + } +} + +impl HTMLRender for e::RawInline { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + for c in self.children() { + write!(renderer.stream, "{}", c)?; + } + Ok(()) + } +} + + +//--------------\\ +//Content Models\\ +//--------------\\ + +impl_html_render_cat!(SubTopic { Title, BodyElement }); +impl_html_render_cat!(SubSidebar { Topic, Title, Subtitle, BodyElement }); +impl_html_render_simple!(ListItem => li); + +impl HTMLRender for e::DefinitionListItem { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // Term→dt, Definition→dd, Classifier→??? + unimplemented!(); + } +} + +impl HTMLRender for e::Field { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // FieldName→dt, FieldBody→dd + unimplemented!(); + } +} + +impl HTMLRender for e::OptionListItem { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + // OptionGroup→dt(s), Description→dd + unimplemented!(); + } +} + +impl_html_render_cat!(SubLineBlock { LineBlock, Line }); + +impl HTMLRender for e::Line { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "<br>")?; + Ok(()) + } +} + +impl_html_render_cat!(SubBlockQuote { Attribution, BodyElement }); +impl_html_render_simple!(Attribution => cite); //TODO: correct? + +impl_html_render_cat!(SubFigure { Caption, Legend, BodyElement }); +impl_html_render_simple!(Caption => caption); + +impl HTMLRender for e::Legend { + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + unimplemented!(); + } +} + +//------------\\ +//Things to do\\ +//------------\\ + +//TODO: prettyprint option list +//TODO: render admonitions: Admonition, Attention, Hint, Note, Caution, Danger, Error, Important, Tip, Warning +//TODO: properly render tables + +//TODO: add reference target: FootnoteReference, CitationReference, TitleReference +//TODO: add title: Abbr, Acronym +//TODO: convert math, set display attr +//TODO: add id: Rubric, Target, TargetInline diff --git a/renderer/src/html/tests.rs b/renderer/src/html/tests.rs new file mode 100644 index 0000000..8477699 --- /dev/null +++ b/renderer/src/html/tests.rs @@ -0,0 +1,275 @@ +use pretty_assertions::assert_eq; + +use rst_parser::parse; + +use crate::html::render_html; + +fn check_renders_to(rst: &str, expected: &str) { + println!("Rendering:\n{}\n---", rst); + let doc = parse(rst).expect("Cannot parse"); + let mut result_data: Vec<u8> = vec![]; + render_html(&doc, &mut result_data, false).expect("Render error"); + let result = String::from_utf8(result_data).expect("Could not decode"); + assert_eq!(result.as_str().trim(), expected); +} + +#[test] +fn simple_string() { + check_renders_to( + "Simple String", + "<p>Simple String</p>", + ); +} + +#[test] +fn simple_string_with_markup() { + check_renders_to( + "Simple String with *emph* and **strong**", + "<p>Simple String with <em>emph</em> and <strong>strong</strong></p>", + ); +} + +#[test] +fn inline_literal() { + check_renders_to( + "Simple String with an even simpler ``inline literal``", + "<p>Simple String with an even simpler <code>inline literal</code></p>", + ); +} + +/* +#[test] +fn test_reference_anonymous() { + check_renders_to("\ +A simple `anonymous reference`__ + +__ http://www.test.com/test_url +", "\ +<p>A simple <a href=\"http://www.test.com/test_url\">anonymous reference</a></p>\ +"); +} +*/ + +#[test] +fn two_paragraphs() { + check_renders_to( + "One paragraph.\n\nTwo paragraphs.", + "<p>One paragraph.</p>\n<p>Two paragraphs.</p>", + ); +} + +#[test] +fn named_reference() { + check_renders_to("\ +A simple `named reference`_ with stuff in between the +reference and the target. + +.. _`named reference`: http://www.test.com/test_url +", "\ +<p>A simple <a href=\"http://www.test.com/test_url\">named reference</a> with stuff in between the \ +reference and the target.</p>\ +"); +} + +#[test] +fn substitution() { + check_renders_to("\ +A |subst|. + +.. |subst| replace:: text substitution +", "<p>A text substitution.</p>"); +} + +/* +#[test] +fn test_section_hierarchy() { + check_renders_to("\ ++++++ +Title ++++++ + +Subtitle +======== + +Some stuff + +Section +------- + +Some more stuff + +Another Section +............... + +And even more stuff +", "\ +<p>Some stuff</p> +<section id=\"section\"> +<h1>Section</h1> +<p>Some more stuff</p> +<section id=\"another-section\"> +<h2>Another Section</h2> +<p>And even more stuff</p> +</section> +</section>\ +"); +} + +#[test] +fn test_docinfo_title() { + check_renders_to("\ ++++++ +Title ++++++ + +:author: me + +Some stuff +", "\ +<main id=\"title\"> +<h1 class=\"title\">Title</h1> +<dl class=\"docinfo simple\"> +<dt class=\"author\">Author</dt> +<dd class=\"author\"><p>me</p></dd> +</dl> +<p>Some stuff</p> +</main>\ +"); +} +*/ + +#[test] +fn section_hierarchy() { + check_renders_to("\ ++++++ +Title ++++++ + +Not A Subtitle +============== + +Some stuff + +Section +------- + +Some more stuff + +Another Section +............... + +And even more stuff +", "\ +<section id=\"title\"> +<h1>Title</h1> +<section id=\"not-a-subtitle\"> +<h2>Not A Subtitle</h2> +<p>Some stuff</p> +<section id=\"section\"> +<h3>Section</h3> +<p>Some more stuff</p> +<section id=\"another-section\"> +<h4>Another Section</h4> +<p>And even more stuff</p> +</section> +</section> +</section> +</section>\ +"); +} + +#[test] +fn bullet_list() { + check_renders_to("\ +* bullet +* list +", "\ +<ul> +<li><p>bullet</p></li> +<li><p>list</p></li> +</ul>\ +"); +} + +/* +#[test] +fn test_table() { + check_renders_to("\ +.. table:: + :align: right + + +-----+-----+ + | 1 | 2 | + +-----+-----+ + | 3 | 4 | + +-----+-----+ +", "\ +<table class=\"align-right\"> +<colgroup> +<col style=\"width: 50%%\" /> +<col style=\"width: 50%%\" /> +</colgroup> +<tbody> +<tr><td><p>1</p></td> +<td><p>2</p></td> +</tr> +<tr><td><p>3</p></td> +<td><p>4</p></td> +</tr> +</tbody> +</table>\ +"); +} +*/ + +/* +#[test] +fn test_field_list() { + check_renders_to("\ +Not a docinfo. + +:This: .. _target: + + is +:a: +:simple: +:field: list +", "\ +<p>Not a docinfo.</p> +<dl class=\"field-list\"> +<dt>This</dt> +<dd><p id=\"target\">is</p> +</dd> +<dt>a</dt> +<dd><p></p></dd> +<dt>simple</dt> +<dd><p></p></dd> +<dt>field</dt> +<dd><p>list</p> +</dd> +</dl>\ +"); +} +*/ + +/* +#[test] +fn test_field_list_long() { + check_renders_to("\ +Not a docinfo. + +:This is: a +:simple field list with loooong field: names +", "\ +<p>Not a docinfo.</p> +<dl class=\"field-list\"> +<dt>This is</dt> +<dd><p>a</p> +</dd> +<dt>simple field list with loooong field</dt> +<dd><p>names</p> +</dd> +</dl>\ +"); +} +*/ diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs new file mode 100644 index 0000000..4d6bfdb --- /dev/null +++ b/renderer/src/lib.rs @@ -0,0 +1,21 @@ +mod html; + + +use std::io::Write; + +use failure::Error; + +use document_tree::Document; + + +pub fn render_json<W>(document: &Document, stream: W) -> Result<(), Error> where W: Write { + serde_json::to_writer(stream, &document)?; + Ok(()) +} + +pub fn render_xml<W>(document: &Document, stream: W) -> Result<(), Error> where W: Write { + serde_xml_rs::to_writer(stream, &document).map_err(failure::SyncFailure::new)?; + Ok(()) +} + +pub use html::render_html; |
