use rowan::{NodeOrToken, WalkEvent}; use std::cmp::min; use std::fmt; use std::fmt::Write as _; use super::TraversalContext; use super::Traverser; use crate::ast::*; use crate::syntax::SyntaxToken; use crate::SyntaxKind; /// A wrapper for escaping sensitive characters in html. /// /// ```rust /// use orgize::export::HtmlEscape as Escape; /// /// assert_eq!(format!("{}", Escape("< < <")), "< < <"); /// assert_eq!( /// format!("{}", Escape("")), /// "<script>alert('Hello XSS')</script>" /// ); /// ``` pub struct HtmlEscape>(pub S); impl> fmt::Display for HtmlEscape { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut pos = 0; let content = self.0.as_ref(); let bytes = content.as_bytes(); while let Some(off) = jetscii::bytes!(b'<', b'>', b'&', b'\'', b'"').find(&bytes[pos..]) { write!(f, "{}", &content[pos..pos + off])?; pos += off + 1; match bytes[pos - 1] { b'<' => write!(f, "<")?, b'>' => write!(f, ">")?, b'&' => write!(f, "&")?, b'\'' => write!(f, "'")?, b'"' => write!(f, """)?, _ => {} } } write!(f, "{}", &content[pos..]) } } #[derive(Default)] pub struct HtmlExport { output: String, in_descriptive_list: Vec, table_row: TableRow, } #[derive(Default, PartialEq, Eq)] enum TableRow { #[default] HeaderRule, Header, BodyRule, Body, } impl HtmlExport { pub fn push_str(&mut self, s: impl AsRef) { self.output += s.as_ref(); } pub fn finish(self) -> String { self.output } } impl Traverser for HtmlExport { #[tracing::instrument(skip(self, _ctx))] fn text(&mut self, token: SyntaxToken, _ctx: &mut TraversalContext) { self.output += &HtmlEscape(token.text()).to_string(); } #[tracing::instrument(skip(self, _ctx))] fn document(&mut self, event: WalkEvent<&Document>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "
", WalkEvent::Leave(_) => "
", }; } #[tracing::instrument(skip(self, _ctx))] fn list(&mut self, event: WalkEvent<&List>, _ctx: &mut TraversalContext) { match event { WalkEvent::Enter(list) => { self.output += if list.is_ordered() { self.in_descriptive_list.push(false); "
    " } else if list.is_descriptive() { self.in_descriptive_list.push(true); "
    " } else { self.in_descriptive_list.push(false); "
      " }; } WalkEvent::Leave(list) => { self.output += if list.is_ordered() { "
" } else if let Some(true) = self.in_descriptive_list.last() { "" } else { "" }; self.in_descriptive_list.pop(); } }; } #[tracing::instrument(skip(self, ctx))] fn list_item(&mut self, event: WalkEvent<&ListItem>, ctx: &mut TraversalContext) { if self.in_descriptive_list.last().copied().unwrap_or_default() { match event { WalkEvent::Enter(item) => { self.output += "
"; for elem in item.tag() { match elem { NodeOrToken::Node(n) => self.node(n, ctx), NodeOrToken::Token(t) => self.token(t, ctx), } } self.output += "
"; } WalkEvent::Leave(_) => self.output += "
", }; } else { match event { WalkEvent::Enter(_) => self.output += "
  • ", WalkEvent::Leave(_) => self.output += "
  • ", }; } } #[tracing::instrument(skip(self, _ctx))] fn paragraph(&mut self, event: WalkEvent<&Paragraph>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "

    ", WalkEvent::Leave(_) => "

    ", }; } #[tracing::instrument(skip(self, _ctx))] fn section(&mut self, event: WalkEvent<&Section>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "
    ", WalkEvent::Leave(_) => "
    ", }; } #[tracing::instrument(skip(self, _ctx))] fn fixed_width(&mut self, event: WalkEvent<&FixedWidth>, _ctx: &mut TraversalContext) { if let WalkEvent::Enter(_f) = event { // self.output += f.text(); }; } #[tracing::instrument(skip(self, ctx))] fn snippet(&mut self, event: WalkEvent<&Snippet>, ctx: &mut TraversalContext) { if let WalkEvent::Enter(snippet) = event { if matches!(snippet.name(), Some(name) if name.text().eq_ignore_ascii_case("html")) { if let Some(value) = snippet.value() { self.output += value.text() } } return ctx.skip(); }; } #[tracing::instrument(skip(self, _ctx))] fn italic(&mut self, event: WalkEvent<&Italic>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", WalkEvent::Leave(_) => "", }; } #[tracing::instrument(skip(self, _ctx))] fn bold(&mut self, event: WalkEvent<&Bold>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", WalkEvent::Leave(_) => "", }; } #[tracing::instrument(skip(self, _ctx))] fn strike(&mut self, event: WalkEvent<&Strike>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", WalkEvent::Leave(_) => "", }; } #[tracing::instrument(skip(self, _ctx))] fn underline(&mut self, event: WalkEvent<&Underline>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", WalkEvent::Leave(_) => "", }; } #[tracing::instrument(skip(self, _ctx))] fn verbatim(&mut self, event: WalkEvent<&Verbatim>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", WalkEvent::Leave(_) => "", }; } #[tracing::instrument(skip(self, _ctx))] fn code(&mut self, event: WalkEvent<&Code>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", WalkEvent::Leave(_) => "", }; } #[tracing::instrument(skip(self, ctx))] fn rule(&mut self, event: WalkEvent<&Rule>, ctx: &mut TraversalContext) { if let WalkEvent::Enter(_) = event { self.output += "
    " }; ctx.skip() } #[tracing::instrument(skip(self, ctx))] fn link(&mut self, event: WalkEvent<&Link>, ctx: &mut TraversalContext) { match event { WalkEvent::Enter(link) => { let path = link.path(); let path = path.as_ref().map(|path| path.text()).unwrap_or_default(); if link.is_image() { let _ = write!(&mut self.output, r#""#, HtmlEscape(path)); return ctx.skip(); } let _ = write!(&mut self.output, r#""#, HtmlEscape(path)); if !link.has_description() { let _ = write!(&mut self.output, "{}", HtmlEscape(path)); return ctx.skip(); } } WalkEvent::Leave(_) => { self.output += ""; } } } #[tracing::instrument(skip(self, _ctx))] fn quote_block(&mut self, event: WalkEvent<&QuoteBlock>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "
    ", WalkEvent::Leave(_) => "
    ", }; } #[tracing::instrument(skip(self, _ctx))] fn verse_block(&mut self, event: WalkEvent<&VerseBlock>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "

    ", WalkEvent::Leave(_) => "

    ", }; } #[tracing::instrument(skip(self, _ctx))] fn example_block(&mut self, event: WalkEvent<&ExampleBlock>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "
    ",
                WalkEvent::Leave(_) => "
    ", }; } #[tracing::instrument(skip(self, _ctx))] fn center_block(&mut self, event: WalkEvent<&CenterBlock>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "
    ", WalkEvent::Leave(_) => "
    ", }; } #[tracing::instrument(skip(self, _ctx))] fn org_table(&mut self, event: WalkEvent<&OrgTable>, _ctx: &mut TraversalContext) { match event { WalkEvent::Enter(table) => { self.output += ""; self.table_row = if table.has_header() { TableRow::HeaderRule } else { TableRow::BodyRule } } WalkEvent::Leave(_) => { match self.table_row { TableRow::Body => self.output += "", TableRow::Header => self.output += "", _ => {} } self.output += "
    "; } } } #[tracing::instrument(skip(self, ctx))] fn org_table_row(&mut self, event: WalkEvent<&OrgTableRow>, ctx: &mut TraversalContext) { if match event { WalkEvent::Enter(n) | WalkEvent::Leave(n) => n.is_rule(), } { match self.table_row { TableRow::Body => { self.output += ""; self.table_row = TableRow::BodyRule; } TableRow::Header => { self.output += ""; self.table_row = TableRow::BodyRule; } _ => {} } return ctx.skip(); } match event { WalkEvent::Enter(_) => { match self.table_row { TableRow::HeaderRule => { self.table_row = TableRow::Header; self.output += ""; } TableRow::BodyRule => { self.table_row = TableRow::Body; self.output += ""; } _ => {} } self.output += ""; } WalkEvent::Leave(_) => { self.output += ""; } } } #[tracing::instrument(skip(self, _ctx))] fn org_table_cell(&mut self, event: WalkEvent<&OrgTableCell>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", WalkEvent::Leave(_) => "", }; } #[tracing::instrument(skip(self, _ctx))] fn comment(&mut self, event: WalkEvent<&Comment>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", }; } #[tracing::instrument(skip(self, _ctx))] fn comment_block(&mut self, event: WalkEvent<&CommentBlock>, _ctx: &mut TraversalContext) { self.output += match event { WalkEvent::Enter(_) => "", }; } #[tracing::instrument(skip(self, ctx))] fn headline(&mut self, event: WalkEvent<&Headline>, ctx: &mut TraversalContext) { if let WalkEvent::Enter(headline) = event { let level = min(headline.level(), 6); let _ = write!(&mut self.output, ""); for elem in headline.title() { match elem { NodeOrToken::Node(node) => self.node(node, ctx), NodeOrToken::Token(token) => self.token(token, ctx), } } let _ = write!(&mut self.output, ""); } } #[tracing::instrument(skip(self, ctx))] fn inline_src(&mut self, _event: WalkEvent<&InlineSrc>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn inline_call(&mut self, _event: WalkEvent<&InlineCall>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn special_block(&mut self, _event: WalkEvent<&SpecialBlock>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn export_block(&mut self, _event: WalkEvent<&ExportBlock>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn source_block(&mut self, _event: WalkEvent<&SourceBlock>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn babel_call(&mut self, _event: WalkEvent<&BabelCall>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn clock(&mut self, _event: WalkEvent<&Clock>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn cookie(&mut self, _event: WalkEvent<&Cookie>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn radio_target(&mut self, _event: WalkEvent<&RadioTarget>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn drawer(&mut self, _event: WalkEvent<&Drawer>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn dyn_block(&mut self, _event: WalkEvent<&DynBlock>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn fn_def(&mut self, _event: WalkEvent<&FnDef>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn fn_ref(&mut self, _event: WalkEvent<&FnRef>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn macros(&mut self, _event: WalkEvent<&Macros>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn timestamp(&mut self, event: WalkEvent<&Timestamp>, ctx: &mut TraversalContext) { if let WalkEvent::Enter(t) = event { self.output += r#""#; for e in t.syntax.children_with_tokens() { match e { NodeOrToken::Token(t) if t.kind() == SyntaxKind::MINUS2 => { self.output += "–"; } NodeOrToken::Token(t) => { self.output += t.text(); } _ => {} } } self.output += r#""#; ctx.skip(); } } #[tracing::instrument(skip(self, ctx))] fn target(&mut self, _event: WalkEvent<&Target>, ctx: &mut TraversalContext) { ctx.skip(); } #[tracing::instrument(skip(self, ctx))] fn latex_fragment(&mut self, event: WalkEvent<&LatexFragment>, ctx: &mut TraversalContext) { if let WalkEvent::Enter(l) = event { self.output += &l.syntax.to_string(); ctx.skip(); } } #[tracing::instrument(skip(self, ctx))] fn latex_environment( &mut self, event: WalkEvent<&LatexEnvironment>, ctx: &mut TraversalContext, ) { if let WalkEvent::Enter(l) = event { self.output += &l.syntax.to_string(); ctx.skip(); } } #[tracing::instrument(skip(self, ctx))] fn entity(&mut self, event: WalkEvent<&Entity>, ctx: &mut TraversalContext) { if let WalkEvent::Enter(e) = event { self.output += e.html(); ctx.skip(); } } }