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

View file

@ -77,6 +77,19 @@ impl HtmlHandler<Error> for DefaultHtmlHandler {
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::ListItem(_) => write!(w, "<li>")?,
Element::Paragraph { .. } => write!(w, "<p>")?,

View file

@ -51,6 +51,19 @@ impl OrgHandler<Error> for DefaultOrgHandler {
}
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::Section => (),
Element::Strike => write!(w, "+")?,

View file

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

View file

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