feat: parse Latex environment
This commit is contained in:
parent
e009e1c199
commit
d19b95523a
195
src/elements/latex.rs
Normal file
195
src/elements/latex.rs
Normal 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);
|
||||||
|
}
|
|
@ -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()),
|
||||||
|
|
|
@ -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>")?,
|
||||||
|
|
|
@ -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, "+")?,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(_) => (),
|
||||||
|
|
Loading…
Reference in a new issue