feat: handle <thead> in html export
This commit is contained in:
parent
1362624083
commit
db7fb70724
5 changed files with 300 additions and 14 deletions
|
|
@ -30,12 +30,15 @@ impl Traverser for MyHtmlHandler {
|
|||
let level = title.headline().and_then(|h| h.level()).unwrap_or(1);
|
||||
let level = min(level, 6);
|
||||
let raw = title.syntax().to_string();
|
||||
self.0.output += &format!("<h{level}><a id=\"{0}\" href=\"#{0}\">", slugify!(&raw));
|
||||
self.0.push_str(format!(
|
||||
"<h{level}><a id=\"{0}\" href=\"#{0}\">",
|
||||
slugify!(&raw)
|
||||
));
|
||||
}
|
||||
WalkEvent::Leave(title) => {
|
||||
let level = title.headline().and_then(|h| h.level()).unwrap_or(1);
|
||||
let level = min(level, 6);
|
||||
self.0.output += &format!("</a></h{level}>");
|
||||
self.0.push_str(format!("</a></h{level}>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,57 @@
|
|||
use super::OrgTableRow;
|
||||
use rowan::ast::AstNode;
|
||||
|
||||
use super::{OrgTable, OrgTableRow};
|
||||
use crate::syntax::SyntaxKind;
|
||||
|
||||
impl OrgTable {
|
||||
/// Returns `true` if this table has a header
|
||||
///
|
||||
/// A table has a header when it contains at least two row groups.
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::OrgTable};
|
||||
///
|
||||
/// let org = Org::parse(r#"
|
||||
/// | a | b |
|
||||
/// |---+---|
|
||||
/// | c | d |"#);
|
||||
/// let table = org.first_node::<OrgTable>().unwrap();
|
||||
/// assert!(table.has_header());
|
||||
///
|
||||
/// let org = Org::parse(r#"
|
||||
/// | a | b |
|
||||
/// | 0 | 1 |
|
||||
/// |---+---|
|
||||
/// | a | w |"#);
|
||||
/// let table = org.first_node::<OrgTable>().unwrap();
|
||||
/// assert!(table.has_header());
|
||||
///
|
||||
/// let org = Org::parse(r#"
|
||||
/// | a | b |
|
||||
/// | c | d |"#);
|
||||
/// let table = org.first_node::<OrgTable>().unwrap();
|
||||
/// assert!(!table.has_header());
|
||||
///
|
||||
/// let org = Org::parse(r#"
|
||||
/// |---+---|
|
||||
/// | a | b |
|
||||
/// | c | d |
|
||||
/// |---+---|"#);
|
||||
/// let table = org.first_node::<OrgTable>().unwrap();
|
||||
/// assert!(!table.has_header());
|
||||
/// ```
|
||||
pub fn has_header(&self) -> bool {
|
||||
self.syntax
|
||||
.children()
|
||||
.filter_map(OrgTableRow::cast)
|
||||
.skip_while(|row| row.is_rule())
|
||||
.skip_while(|row| row.is_standard())
|
||||
.skip_while(|row| row.is_rule())
|
||||
.next()
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl OrgTableRow {
|
||||
/// Returns `true` if this row is a rule
|
||||
///
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@
|
|||
/// let level = title.headline().and_then(|h| h.level()).unwrap_or(1);
|
||||
/// let level = min(level, 6);
|
||||
/// let raw = title.syntax().to_string();
|
||||
/// self.0.output += &format!("<h{level}><a id=\"{0}\" href=\"#{0}\">", slugify!(&raw));
|
||||
/// self.0.push_str(format!("<h{level}><a id=\"{0}\" href=\"#{0}\">", slugify!(&raw)));
|
||||
/// }
|
||||
/// WalkEvent::Leave(title) => {
|
||||
/// let level = title.headline().and_then(|h| h.level()).unwrap_or(1);
|
||||
/// let level = min(level, 6);
|
||||
/// self.0.output += &format!("</a></h{level}>");
|
||||
/// self.0.push_str(format!("</a></h{level}>"));
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
|
|
|
|||
|
|
@ -47,11 +47,27 @@ impl<S: AsRef<str>> fmt::Display for HtmlEscape<S> {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct HtmlExport {
|
||||
pub output: String,
|
||||
output: String,
|
||||
|
||||
in_descriptive_list: Vec<bool>,
|
||||
|
||||
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<str>) {
|
||||
self.output += s.as_ref();
|
||||
}
|
||||
|
||||
pub fn finish(self) -> String {
|
||||
self.output
|
||||
}
|
||||
|
|
@ -306,10 +322,28 @@ impl Traverser for HtmlExport {
|
|||
|
||||
#[tracing::instrument(skip(self, _ctx))]
|
||||
fn org_table(&mut self, event: WalkEvent<&OrgTable>, _ctx: &mut TraversalContext) {
|
||||
self.output += match event {
|
||||
WalkEvent::Enter(_) => "<table><tbody>",
|
||||
WalkEvent::Leave(_) => "</tbody></table>",
|
||||
};
|
||||
match event {
|
||||
WalkEvent::Enter(table) => {
|
||||
self.output += "<table>";
|
||||
self.table_row = if table.has_header() {
|
||||
TableRow::HeaderRule
|
||||
} else {
|
||||
TableRow::BodyRule
|
||||
}
|
||||
}
|
||||
WalkEvent::Leave(_) => {
|
||||
match self.table_row {
|
||||
TableRow::Body => {
|
||||
self.output += "</tbody>";
|
||||
}
|
||||
TableRow::Header => {
|
||||
self.output += "</thead>";
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.output += "</table>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, ctx))]
|
||||
|
|
@ -317,13 +351,39 @@ impl Traverser for HtmlExport {
|
|||
if match event {
|
||||
WalkEvent::Enter(n) | WalkEvent::Leave(n) => n.is_rule(),
|
||||
} {
|
||||
match self.table_row {
|
||||
TableRow::Body => {
|
||||
self.output += "</tbody>";
|
||||
self.table_row = TableRow::BodyRule;
|
||||
}
|
||||
TableRow::Header => {
|
||||
self.output += "</thead>";
|
||||
self.table_row = TableRow::BodyRule;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return ctx.skip();
|
||||
}
|
||||
|
||||
self.output += match event {
|
||||
WalkEvent::Enter(_) => "<tr>",
|
||||
WalkEvent::Leave(_) => "</tr>",
|
||||
};
|
||||
match event {
|
||||
WalkEvent::Enter(_) => {
|
||||
match self.table_row {
|
||||
TableRow::HeaderRule => {
|
||||
self.table_row = TableRow::Header;
|
||||
self.output += "<thead>";
|
||||
}
|
||||
TableRow::BodyRule => {
|
||||
self.table_row = TableRow::Body;
|
||||
self.output += "<tbody>";
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.output += "<tr>";
|
||||
}
|
||||
WalkEvent::Leave(_) => {
|
||||
self.output += "</tr>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, _ctx))]
|
||||
|
|
|
|||
172
tests/html.rs
Normal file
172
tests/html.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
use orgize::Org;
|
||||
|
||||
#[test]
|
||||
fn emphasis() {
|
||||
insta::assert_snapshot!(
|
||||
Org::parse("*bold*, /italic/,\n_underlined_, =verbatim= and ~code~").to_html(),
|
||||
@r###"
|
||||
<main><section><p><b>bold</b>, <i>italic</i>,
|
||||
<u>underlined</u>, <code>verbatim</code> and <code>code</code></p></section></main>
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link() {
|
||||
insta::assert_snapshot!(
|
||||
Org::parse("Visit[[http://example.com][link1]]or[[http://example.com][link1]].").to_html(),
|
||||
@r###"<main><section><p>Visit<a href="http://example.com">link1</a>or<a href="http://example.com">link1</a>.</p></section></main>"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn section_and_headline() {
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
* title 1
|
||||
section 1
|
||||
** title 2
|
||||
section 2
|
||||
* title 3
|
||||
section 3
|
||||
* title 4
|
||||
section 4
|
||||
"#).to_html(),
|
||||
@r###"
|
||||
<main><h1>title 1</h1><section><p>section 1
|
||||
</p></section><h2>title 2</h2><section><p>section 2
|
||||
</p></section><h1>title 3</h1><section><p>section 3
|
||||
</p></section><h1>title 4</h1><section><p>section 4
|
||||
</p></section></main>
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
+ 1
|
||||
|
||||
+ 2
|
||||
|
||||
- 3
|
||||
|
||||
- 4
|
||||
|
||||
+ 5
|
||||
"#).to_html(),
|
||||
@r###"
|
||||
<main><section><ul><li><p>1
|
||||
|
||||
</p></li><li><p>2
|
||||
|
||||
</p><ul><li><p>3
|
||||
|
||||
</p></li><li><p>4
|
||||
|
||||
</p></li></ul></li><li><p>5
|
||||
</p></li></ul></section></main>
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snippet() {
|
||||
insta::assert_snapshot!(
|
||||
Org::parse("@@html:<del>@@delete this@@html:</del>@@").to_html(),
|
||||
@"<main><section><p><del>delete this</del></p></section></main>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paragraphs() {
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
* title
|
||||
|
||||
paragraph 1
|
||||
|
||||
paragraph 2
|
||||
|
||||
paragraph 3
|
||||
|
||||
paragraph 4
|
||||
"#).to_html(),
|
||||
@r###"
|
||||
<main><h1>title</h1><section><p></p><p>paragraph 1
|
||||
</p><p>paragraph 2
|
||||
</p><p>paragraph 3
|
||||
</p><p>paragraph 4
|
||||
</p></section></main>
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
// don't has table header
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
|-----+-----+-----|
|
||||
| 0 | 1 | 2 |
|
||||
| 4 | 5 | 6 |
|
||||
|-----+-----+-----|
|
||||
"#).to_html(),
|
||||
@"<main><section><table><tbody><tr><td>0</td><td>1</td><td>2</td></tr><tr><td>4</td><td>5</td><td>6</td></tr></tbody></table></section></main>"
|
||||
);
|
||||
|
||||
// has table header
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
| 0 | 1 | 2 |
|
||||
|-----+-----+-----|
|
||||
| 4 | 5 | 6 |
|
||||
|-----+-----+-----|
|
||||
"#).to_html(),
|
||||
@"<main><section><table><thead><tr><td>0</td><td>1</td><td>2</td></tr></thead><tbody><tr><td>4</td><td>5</td><td>6</td></tr></tbody></table></section></main>"
|
||||
);
|
||||
|
||||
// has two table body
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
| 0 | 1 | 2 |
|
||||
|-----+-----+-----|
|
||||
| 4 | 5 | 6 |
|
||||
|-----+-----+-----|
|
||||
| 7 | 8 | 9 |
|
||||
"#).to_html(),
|
||||
@"<main><section><table><thead><tr><td>0</td><td>1</td><td>2</td></tr></thead><tbody><tr><td>4</td><td>5</td><td>6</td></tr></tbody><tbody><tr><td>7</td><td>8</td><td>9</td></tr></tbody></table></section></main>"
|
||||
);
|
||||
|
||||
// multiple row rule
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
| 0 | 1 | 2 |
|
||||
|-----+-----+-----|
|
||||
|-----+-----+-----|
|
||||
| 4 | 5 | 6 |
|
||||
"#).to_html(),
|
||||
@"<main><section><table><thead><tr><td>0</td><td>1</td><td>2</td></tr></thead><tbody><tr><td>4</td><td>5</td><td>6</td></tr></tbody></table></section></main>"
|
||||
);
|
||||
|
||||
// empty
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
|-----+-----+-----|
|
||||
|-----+-----+-----|
|
||||
"#).to_html(),
|
||||
@"<main><section><table></table></section></main>"
|
||||
);
|
||||
|
||||
insta::assert_snapshot!(
|
||||
Org::parse(r#"
|
||||
|
|
||||
|-
|
||||
|
|
||||
|-
|
||||
|
|
||||
"#).to_html(),
|
||||
@"<main><section><table><thead><tr></tr></thead><tbody><tr></tr></tbody><tbody><tr></tr></tbody></table></section></main>"
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue