From a0e3c53758d526bb418c068bce1c99fa5a597ed3 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Thu, 26 Dec 2019 23:01:00 +0100 Subject: Split into smaller crates --- renderer/src/html.rs | 393 +++++++++++++++++++++++++++++++++++++++++++++ renderer/src/html/tests.rs | 275 +++++++++++++++++++++++++++++++ renderer/src/lib.rs | 21 +++ 3 files changed, 689 insertions(+) create mode 100644 renderer/src/html.rs create mode 100644 renderer/src/html/tests.rs create mode 100644 renderer/src/lib.rs (limited to 'renderer/src') 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(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 where W: Write { + stream: W, + level: u8, +} + +trait HTMLRender { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write; +} + +macro_rules! impl_html_render_cat {($cat:ident { $($member:ident),+ }) => { + impl HTMLRender for c::$cat { + fn render_html(&self, renderer: &mut HTMLRenderer) -> 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(&self, renderer: &mut HTMLRenderer) -> 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(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + write!(renderer.stream, "<{0}>", stringify!($tag))?; + Ok(()) + } + } +)+ }} + +// Impl + +impl HTMLRender for Document { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + writeln!(renderer.stream, "")?; + for c in self.children() { + (*c).render_html(renderer)?; + writeln!(renderer.stream)?; + } + writeln!(renderer.stream, "")?; + 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(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + let level = if renderer.level > 6 { 6 } else { renderer.level }; + write!(renderer.stream, "", level)?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "", level)?; + Ok(()) + } +} + +impl HTMLRender for e::Docinfo { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // Like “YAML frontmatter” in Markdown + unimplemented!(); + } +} + +impl HTMLRender for e::Decoration { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> 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(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + renderer.level += 1; + writeln!(renderer.stream, "
", self.ids()[0].0)?; + for c in self.children() { + (*c).render_html(renderer)?; + writeln!(renderer.stream)?; + } + write!(renderer.stream, "
")?; + Ok(()) + } +} + +impl HTMLRender for e::Transition { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + write!(renderer.stream, "
")?; + Ok(()) + } +} + +impl HTMLRender for e::Topic { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> 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 HTMLRender for I where I: e::Element + a::ExtraAttributes +macro_rules! impl_render_html_image { ($t:ty) => { impl HTMLRender for $t { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + let extra = self.extra(); + if let Some(ref target) = extra.target { + write!(renderer.stream, "", escape_html(target.as_str()))?; + } + write!(renderer.stream, " + // TODO: height: Option + // TODO: width: Option + // TODO: scale: Option + write!(renderer.stream, " src=\"{}\" />", escape_html(extra.uri.as_str()))?; + if extra.target.is_some() { + write!(renderer.stream, "")?; + } + Ok(()) + } +}}} +impl_render_html_image!(e::Image); +impl_render_html_image!(e::ImageInline); + +impl HTMLRender for e::DoctestBlock { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // TODO + unimplemented!(); + } +} + +impl HTMLRender for e::SubstitutionDefinition { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // TODO: Should those be removed after resolving them + Ok(()) + } +} + +impl HTMLRender for e::Comment { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + write!(renderer.stream, "")?; + Ok(()) + } +} + +impl HTMLRender for e::Pending { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // Will those be resolved by the time we get here? + unimplemented!(); + } +} + +impl HTMLRender for e::Target { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // Should be resolved by now + Ok(()) + } +} + +impl HTMLRender for e::Raw { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + for c in self.children() { + write!(renderer.stream, "{}", c)?; + } + Ok(()) + } +} + +impl HTMLRender for e::Footnote { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + unimplemented!(); + } +} + +impl HTMLRender for e::Citation { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + unimplemented!(); + } +} + +impl HTMLRender for e::SystemMessage { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + write!(renderer.stream, "
System Message")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "
")?; + 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(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + write!(renderer.stream, "{}", escape_html(self))?; + Ok(()) + } +} + +impl HTMLRender for e::Reference { + fn render_html(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + let extra = self.extra(); + write!(renderer.stream, "")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "")?; + Ok(()) + } +} + +impl HTMLRender for e::SubstitutionReference { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // Will those be resolved by the time we get here? + unimplemented!(); + } +} + +impl HTMLRender for e::Problematic { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // Broken inline markup leads to insertion of this in docutils + unimplemented!(); + } +} + +impl HTMLRender for e::Generated { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // Section numbers and so on + unimplemented!(); + } +} + +impl HTMLRender for e::RawInline { + fn render_html(&self, renderer: &mut HTMLRenderer) -> 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(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // Term→dt, Definition→dd, Classifier→??? + unimplemented!(); + } +} + +impl HTMLRender for e::Field { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + // FieldName→dt, FieldBody→dd + unimplemented!(); + } +} + +impl HTMLRender for e::OptionListItem { + fn render_html(&self, _renderer: &mut HTMLRenderer) -> 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(&self, renderer: &mut HTMLRenderer) -> Result<(), Error> where W: Write { + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "
")?; + 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(&self, _renderer: &mut HTMLRenderer) -> 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 = 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", + "

Simple String

", + ); +} + +#[test] +fn simple_string_with_markup() { + check_renders_to( + "Simple String with *emph* and **strong**", + "

Simple String with emph and strong

", + ); +} + +#[test] +fn inline_literal() { + check_renders_to( + "Simple String with an even simpler ``inline literal``", + "

Simple String with an even simpler inline literal

", + ); +} + +/* +#[test] +fn test_reference_anonymous() { + check_renders_to("\ +A simple `anonymous reference`__ + +__ http://www.test.com/test_url +", "\ +

A simple anonymous reference

\ +"); +} +*/ + +#[test] +fn two_paragraphs() { + check_renders_to( + "One paragraph.\n\nTwo paragraphs.", + "

One paragraph.

\n

Two paragraphs.

", + ); +} + +#[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 +", "\ +

A simple named reference with stuff in between the \ +reference and the target.

\ +"); +} + +#[test] +fn substitution() { + check_renders_to("\ +A |subst|. + +.. |subst| replace:: text substitution +", "

A text substitution.

"); +} + +/* +#[test] +fn test_section_hierarchy() { + check_renders_to("\ ++++++ +Title ++++++ + +Subtitle +======== + +Some stuff + +Section +------- + +Some more stuff + +Another Section +............... + +And even more stuff +", "\ +

Some stuff

+
+

Section

+

Some more stuff

+
+

Another Section

+

And even more stuff

+
+
\ +"); +} + +#[test] +fn test_docinfo_title() { + check_renders_to("\ ++++++ +Title ++++++ + +:author: me + +Some stuff +", "\ +
+

Title

+
+
Author
+

me

+
+

Some stuff

+
\ +"); +} +*/ + +#[test] +fn section_hierarchy() { + check_renders_to("\ ++++++ +Title ++++++ + +Not A Subtitle +============== + +Some stuff + +Section +------- + +Some more stuff + +Another Section +............... + +And even more stuff +", "\ +
+

Title

+
+

Not A Subtitle

+

Some stuff

+
+

Section

+

Some more stuff

+
+

Another Section

+

And even more stuff

+
+
+
+
\ +"); +} + +#[test] +fn bullet_list() { + check_renders_to("\ +* bullet +* list +", "\ +
    +
  • bullet

  • +
  • list

  • +
\ +"); +} + +/* +#[test] +fn test_table() { + check_renders_to("\ +.. table:: + :align: right + + +-----+-----+ + | 1 | 2 | + +-----+-----+ + | 3 | 4 | + +-----+-----+ +", "\ + ++++ + + + + + + + + +

1

2

3

4

\ +"); +} +*/ + +/* +#[test] +fn test_field_list() { + check_renders_to("\ +Not a docinfo. + +:This: .. _target: + + is +:a: +:simple: +:field: list +", "\ +

Not a docinfo.

+
+
This
+

is

+
+
a
+

+
simple
+

+
field
+

list

+
+
\ +"); +} +*/ + +/* +#[test] +fn test_field_list_long() { + check_renders_to("\ +Not a docinfo. + +:This is: a +:simple field list with loooong field: names +", "\ +

Not a docinfo.

+
+
This is
+

a

+
+
simple field list with loooong field
+

names

+
+
\ +"); +} +*/ 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(document: &Document, stream: W) -> Result<(), Error> where W: Write { + serde_json::to_writer(stream, &document)?; + Ok(()) +} + +pub fn render_xml(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; -- cgit v1.2.3