feat: parse Latex environment

This commit is contained in:
Jader Brasil 2021-03-30 22:48:59 -03:00
parent e009e1c199
commit d19b95523a
6 changed files with 237 additions and 17 deletions

195
src/elements/latex.rs Normal file
View file

@ -0,0 +1,195 @@
use nom::{
bytes::complete::{tag, take_until},
sequence::delimited,
IResult,
};
use std::borrow::Cow;
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug, Clone)]
pub struct LatexEnvironment<'a> {
pub contents: Cow<'a, str>,
pub argument: Cow<'a, str>,
pub inline: bool,
}
impl<'a> LatexEnvironment<'a> {
#[inline]
pub fn parse(input: &str) -> Option<(&str, LatexEnvironment)> {
let bytes = input.as_bytes();
match (bytes[0], bytes[1]) {
(b'\\', b'[') => parse_internal_delimited(input, "\\[", "\\]", false).ok(),
(b'\\', b'(') => parse_internal_delimited(input, "\\(", "\\)", true).ok(),
(b'\\', _) => parse_internal_environment(input).ok(),
(b'$', b'$') => parse_internal_delimited(input, "$$", "$$", false).ok(),
(b'$', _) => parse_internal_delimited(input, "$", "$", true).ok(),
_ => None,
}
}
pub fn into_owned(self) -> LatexEnvironment<'static> {
LatexEnvironment {
contents: self.contents.into_owned().into(),
argument: self.argument.into_owned().into(),
inline: self.inline,
}
}
}
fn parse_internal_delimited<'a>(
input: &'a str,
starts: &str,
ends: &str,
inline: bool,
) -> IResult<&'a str, LatexEnvironment<'a>, ()> {
let (input, contents) = delimited(tag(starts), take_until(ends), tag(ends))(input)?;
Ok((
input,
LatexEnvironment {
contents: contents.trim().into(),
argument: "".into(),
inline: inline,
},
))
}
fn parse_internal_environment(input: &str) -> IResult<&str, LatexEnvironment, ()> {
let (input, argument) = delimited(tag("\\begin{"), take_until("}"), tag("}"))(input)?;
let end = &format!("\\end{{{}}}", argument)[..];
let (input, contents) = take_until(end)(input)?;
let (input, _) = tag(end)(input)?;
Ok((
input,
LatexEnvironment {
contents: contents.trim().into(),
argument: argument.into(),
inline: false,
},
))
}
#[test]
fn parse_environment() {
assert_eq!(
LatexEnvironment::parse("\\[\n\\frac{1}{3}\n\n\\]"),
Some((
"",
LatexEnvironment {
contents: "\\frac{1}{3}".into(),
argument: "".into(),
inline: false,
}
))
);
assert_eq!(
LatexEnvironment::parse("$$\n42!\n\n$$"),
Some((
"",
LatexEnvironment {
contents: "42!".into(),
argument: "".into(),
inline: false,
}
))
);
assert_eq!(
LatexEnvironment::parse(
r#"\begin{equation}
\int^{a}_{b}\,f(x)\,dx
\end{equation}"#
),
Some((
"",
LatexEnvironment {
contents: "\\int^{a}_{b}\\,f(x)\\,dx".into(),
argument: "equation".into(),
inline: false,
}
))
);
assert_eq!(
LatexEnvironment::parse(
r#"\begin{equation}
\int^{a}_{b}\,f(x)\,dx
\end{align}"#
),
None
);
}
#[test]
fn parse_inline() {
assert_eq!(
LatexEnvironment::parse("$\\frac{1}{3}$"),
Some((
"",
LatexEnvironment {
contents: "\\frac{1}{3}".into(),
argument: "".into(),
inline: true,
}
))
);
assert_eq!(
LatexEnvironment::parse("\\(\\frac{1}{3}\\)"),
Some((
"",
LatexEnvironment {
contents: "\\frac{1}{3}".into(),
argument: "".into(),
inline: true,
}
))
);
assert_eq!(
LatexEnvironment::parse("$ text with spaces $"),
Some((
"",
LatexEnvironment {
contents: "text with spaces".into(),
argument: "".into(),
inline: true,
}
))
);
assert_eq!(
LatexEnvironment::parse("\\( text with spaces \\)"),
Some((
"",
LatexEnvironment {
contents: "text with spaces".into(),
argument: "".into(),
inline: true,
}
))
);
assert_eq!(
LatexEnvironment::parse("$ LaTeXxxx$"),
Some((
"",
LatexEnvironment {
contents: "LaTeXxxx".into(),
argument: "".into(),
inline: true,
}
))
);
assert_eq!(
LatexEnvironment::parse("\\(b\nol\nd\\)"),
Some((
"",
LatexEnvironment {
contents: "b\nol\nd".into(),
argument: "".into(),
inline: true,
}
))
);
assert_eq!(LatexEnvironment::parse("$$b\nol\nd*"), None);
assert_eq!(LatexEnvironment::parse("$b\nol\nd*"), None);
}

View file

@ -13,6 +13,7 @@ pub(crate) mod fn_ref;
pub(crate) mod inline_call; pub(crate) mod inline_call;
pub(crate) mod inline_src; pub(crate) mod inline_src;
pub(crate) mod keyword; pub(crate) mod keyword;
pub(crate) mod latex;
pub(crate) mod link; pub(crate) mod link;
pub(crate) mod list; pub(crate) mod list;
pub(crate) mod macros; pub(crate) mod macros;
@ -41,6 +42,7 @@ pub use self::{
inline_call::InlineCall, inline_call::InlineCall,
inline_src::InlineSrc, inline_src::InlineSrc,
keyword::{BabelCall, Keyword}, keyword::{BabelCall, Keyword},
latex::LatexEnvironment,
link::Link, link::Link,
list::{List, ListItem}, list::{List, ListItem},
macros::Macros, macros::Macros,
@ -85,6 +87,7 @@ pub enum Element<'a> {
Link(Link<'a>), Link(Link<'a>),
List(List), List(List),
ListItem(ListItem<'a>), ListItem(ListItem<'a>),
LatexEnvironment(LatexEnvironment<'a>),
Macros(Macros<'a>), Macros(Macros<'a>),
Snippet(Snippet<'a>), Snippet(Snippet<'a>),
Text { value: Cow<'a, str> }, Text { value: Cow<'a, str> },
@ -137,6 +140,7 @@ impl Element<'_> {
use Element::*; use Element::*;
match self { match self {
LatexEnvironment(e) => LatexEnvironment(e.into_owned()),
SpecialBlock(e) => SpecialBlock(e.into_owned()), SpecialBlock(e) => SpecialBlock(e.into_owned()),
QuoteBlock(e) => QuoteBlock(e.into_owned()), QuoteBlock(e) => QuoteBlock(e.into_owned()),
CenterBlock(e) => CenterBlock(e.into_owned()), CenterBlock(e) => CenterBlock(e.into_owned()),

View file

@ -77,6 +77,19 @@ impl HtmlHandler<Error> for DefaultHtmlHandler {
write!(w, "<ul>")?; write!(w, "<ul>")?;
} }
} }
Element::LatexEnvironment(latex_inline) => {
if latex_inline.inline {
write!(&mut w, "\\({}\\)", latex_inline.contents)?
} else if latex_inline.argument == "" {
write!(&mut w, "\n\\[{}\\]\n", latex_inline.contents)?
} else {
write!(
&mut w,
"\n\\begin{{{0}}}{1}\\end{{{0}}}\n",
latex_inline.argument, latex_inline.contents
)?
}
}
Element::Italic => write!(w, "<i>")?, Element::Italic => write!(w, "<i>")?,
Element::ListItem(_) => write!(w, "<li>")?, Element::ListItem(_) => write!(w, "<li>")?,
Element::Paragraph { .. } => write!(w, "<p>")?, Element::Paragraph { .. } => write!(w, "<p>")?,

View file

@ -51,6 +51,19 @@ impl OrgHandler<Error> for DefaultOrgHandler {
} }
write!(&mut w, "{}", list_item.bullet)?; write!(&mut w, "{}", list_item.bullet)?;
} }
Element::LatexEnvironment(latex_inline) => {
if latex_inline.inline {
write!(&mut w, "${}$", latex_inline.contents)?
} else if latex_inline.argument == "" {
write!(&mut w, "\\[{}\\]", latex_inline.contents)?
} else {
write!(
&mut w,
"\\begin{{{0}}}{1}\\end{{{0}}}",
latex_inline.argument, latex_inline.contents
)?
}
}
Element::Paragraph { .. } => (), Element::Paragraph { .. } => (),
Element::Section => (), Element::Section => (),
Element::Strike => write!(w, "+")?, Element::Strike => write!(w, "+")?,

View file

@ -10,8 +10,8 @@ use crate::config::ParseConfig;
use crate::elements::{ use crate::elements::{
block::RawBlock, emphasis::Emphasis, keyword::RawKeyword, radio_target::parse_radio_target, block::RawBlock, emphasis::Emphasis, keyword::RawKeyword, radio_target::parse_radio_target,
Clock, Comment, Cookie, Drawer, DynBlock, Element, FixedWidth, FnDef, FnRef, InlineCall, Clock, Comment, Cookie, Drawer, DynBlock, Element, FixedWidth, FnDef, FnRef, InlineCall,
InlineSrc, Link, List, ListItem, Macros, Rule, Snippet, Table, TableCell, TableRow, Target, InlineSrc, LatexEnvironment, Link, List, ListItem, Macros, Rule, Snippet, Table, TableCell,
Timestamp, Title, TableRow, Target, Timestamp, Title,
}; };
use crate::parse::combinators::lines_while; use crate::parse::combinators::lines_while;
@ -287,10 +287,7 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
arena.append(clock, parent); arena.append(clock, parent);
Some(tail) Some(tail)
} }
b'\'' => { b'\'' => None,
// TODO: LaTeX environment
None
}
b'-' => { b'-' => {
if let Some((tail, rule)) = Rule::parse(contents) { if let Some((tail, rule)) = Rule::parse(contents) {
arena.append(rule, parent); arena.append(rule, parent);
@ -486,6 +483,11 @@ pub fn parse_inline<'a, T: ElementArena<'a>>(
Some(tail) Some(tail)
} }
} }
b'$' | b'\\' => {
let (tail, latex) = LatexEnvironment::parse(contents)?;
arena.append(Element::LatexEnvironment(latex), parent);
Some(tail)
}
b'*' | b'+' | b'/' | b'_' | b'=' | b'~' => { b'*' | b'+' | b'/' | b'_' | b'=' | b'~' => {
let (tail, emphasis) = Emphasis::parse(contents, byte)?; let (tail, emphasis) = Emphasis::parse(contents, byte)?;
let (element, content) = emphasis.into_element(); let (element, content) = emphasis.into_element();

View file

@ -83,11 +83,7 @@ impl Org<'_> {
} }
for child in children { for child in children {
expect_element!( expect_element!(child, "Headline", Element::Headline { .. });
child,
"Headline",
Element::Headline { .. }
);
} }
} }
Element::Headline { .. } => { Element::Headline { .. } => {
@ -107,11 +103,7 @@ impl Org<'_> {
} }
for child in children { for child in children {
expect_element!( expect_element!(child, "Headline", Element::Headline { .. });
child,
"Headline",
Element::Headline { .. }
);
} }
} }
Element::Title(title) => { Element::Title(title) => {
@ -191,7 +183,8 @@ impl Org<'_> {
| Element::DynBlock(_) => { | Element::DynBlock(_) => {
expect_children!(node_id); expect_children!(node_id);
} }
Element::ListItem(_) Element::LatexEnvironment(_)
| Element::ListItem(_)
| Element::Drawer(_) | Element::Drawer(_)
| Element::TableCell(_) | Element::TableCell(_)
| Element::Table(_) => (), | Element::Table(_) => (),