From 72d0e0a215cfdb53bb922a5a0de4e24d8b517012 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 14 Dec 2020 12:50:54 +0100 Subject: [PATCH] Resolve conflict between glossary and striked text parsing Signed-off-by: trivernis --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/elements/tokens.rs | 3 +- src/format/epub/assets/style.css | 136 +++++++++++++++++++++++++++ src/format/epub/to_epub.rs | 154 +++++++++++++++++++++++++++++++ src/parser/inline.rs | 23 +++-- 6 files changed, 310 insertions(+), 10 deletions(-) create mode 100644 src/format/epub/assets/style.css create mode 100644 src/format/epub/to_epub.rs diff --git a/Cargo.lock b/Cargo.lock index d444440..4c0cc4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,7 +1277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "snekdown" -version = "0.29.1" +version = "0.29.2" dependencies = [ "asciimath-rs 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index fd88df2..c810ff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snekdown" -version = "0.29.1" +version = "0.29.2" authors = ["trivernis "] edition = "2018" license-file = "LICENSE" diff --git a/src/elements/tokens.rs b/src/elements/tokens.rs index 6084fad..d62a64f 100644 --- a/src/elements/tokens.rs +++ b/src/elements/tokens.rs @@ -34,6 +34,7 @@ pub(crate) const PERCENT: char = '%'; pub(crate) const COMMA: char = ','; pub(crate) const MATH: char = '$'; pub(crate) const AMPERSAND: char = '&'; +pub(crate) const QUESTION_MARK: char = '?'; // aliases @@ -135,7 +136,7 @@ pub(crate) const LIST_SPECIAL_CHARS: [char; 14] = [ MINUS, PLUS, ASTERISK, O, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', ]; -pub(crate) const WHITESPACE: [char; 4] = [' ', '\t', '\r', '\n']; +pub(crate) const WHITESPACE: &[char] = &[' ', '\t', '\r', '\n']; pub(crate) const INLINE_WHITESPACE: [char; 3] = [' ', '\t', '\r']; // sequences diff --git a/src/format/epub/assets/style.css b/src/format/epub/assets/style.css new file mode 100644 index 0000000..ef21d15 --- /dev/null +++ b/src/format/epub/assets/style.css @@ -0,0 +1,136 @@ +body { + overflow-x: hidden; + color: #000; + word-break: break-word; +} + +.content { + font-family: "Fira Sans", "Noto Sans", SansSerif, sans-serif; + margin: auto; + background-color: #FFF; +} + +h1 { + font-size: 2.2rem; +} + +h2 { + font-size: 1.8rem; +} + +h3 { + font-size: 1.4rem; +} + +h4 { + font-size: 1rem; +} + +h5 { + font-size: 0.8rem; +} + +h6 { + font-size: 0.4rem; +} + +img { + max-width: 100%; + max-height: 100vh; + height: auto; +} + +code { + color: #000; +} + +code pre { + font-family: "Fira Code", "Mono", monospace; + padding: 0.8em 0.2em; + background-color: #EEE !important; + border-radius: 0.25em; +} + +code.inlineCode { + font-family: "Fira Code", monospace; + border-radius: 0.1em; + background-color: #EEE; + padding: 0 0.1em +} + +.tableWrapper { + overflow-x: auto; + width: 100%; +} + +.tableWrapper > table { + margin: auto; +} + +table { + border-collapse: collapse; +} + +table tr:nth-child(odd) { + background-color: #DDD; +} + +table tr:nth-child(1) { + background-color: white; + font-weight: bold; + border-bottom: 1px solid black; +} + +table td, table th { + border-left: 1px solid black; + padding: 0.2em 0.5em +} + +table tr td:first-child, table tr th:first-child { + border-left: none; +} + +blockquote { + margin-left: 0; + background-color: rgba(0, 0, 0, 0); +} + +.quote { + border-left: 0.3em solid gray; + border-radius: 0.2em; + padding-left: 1em; + margin-left: 0; + background-color: #EEE; +} + +.quote .metadata { + font-style: italic; + padding-left: 0.5em; + color: #444 +} + +.figure { + width: 100%; + display: block; + text-align: center; +} + +.figure .imageDescription { + display: block; + color: #444; + font-style: italic; +} + +.centered { + text-align: center; +} + +.glossaryReference { + text-decoration: none; + color: inherit; + border-bottom: 1px dotted #000; +} + +.arrow { + font-family: "Fira Code", "Mono", monospace; +} \ No newline at end of file diff --git a/src/format/epub/to_epub.rs b/src/format/epub/to_epub.rs new file mode 100644 index 0000000..1d7c751 --- /dev/null +++ b/src/format/epub/to_epub.rs @@ -0,0 +1,154 @@ +use crate::elements::{ + Block, Document, Element, Header, Inline, Line, List, ListItem, Paragraph, Section, +}; +use crate::format::epub::epub_writer::EpubWriter; +use std::io::Result; +use std::mem; + +pub trait ToEpub { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()>; +} + +impl ToEpub for Element { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + match self { + Element::Inline(inline) => inline.to_epub(writer), + Element::Block(block) => block.to_epub(writer), + Element::Line(line) => line.to_epub(writer), + } + } +} + +impl ToEpub for Block { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + match self { + Block::Null => Ok(()), + Block::Section(s) => s.to_epub(writer), + Block::List(l) => l.to_epub(writer), + Block::Paragraph(p) => p.to_epub(writer), + } + } +} + +impl ToEpub for Line { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + unimplemented!() + } +} + +impl ToEpub for Inline { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + unimplemented!() + } +} + +impl ToEpub for Document { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + self.downloads.lock().unwrap().download_all(); + + let style = minify(std::include_str!("assets/style.css")); + writer.stylesheet(style)?; + + for mut stylesheet in self.stylesheets { + let mut sheet = stylesheet.lock().unwrap(); + let data = mem::take(&mut sheet.data); + + if let Some(data) = data { + let sheet_data = String::from_utf8(data)?; + writer.stylesheet(&sheet_data); + } + } + + Ok(()) + } +} + +impl ToEpub for Section { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + writer.section(self.header.size, self.header.plain.clone())?; + writer.content("".to_string()); + writer.content("".to_string()); + self.header.to_epub(writer)?; + for element in &self.elements { + element.to_epub(writer)?; + } + writer.content("".to_string()); + + Ok(()) + } +} + +impl ToEpub for Header { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + writer.content(format!("".to_string()); + self.line.to_epub(writer); + writer.content("".to_string()); + + Ok(()) + } +} + +impl ToEpub for List { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + if self.ordered { + writer.content("
    ".to_string()); + for item in self.items { + item.to_epub(writer); + } + writer.content("
".to_string()); + } else { + writer.content("
    ".to_string()); + for item in self.items { + item.to_epub(writer); + } + writer.content("
".to_string()); + } + + Ok(()) + } +} + +impl ToEpub for ListItem { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + writer.content("
  • ".to_string()); + self.text.to_epub(writer)?; + + if let Some(first) = self.children.first() { + if first.ordered { + writer.content("
      ".to_string()); + for item in &self.children { + item.to_epub(writer)?; + } + writer.content("
    ".to_string()); + } else { + writer.content("
      ".to_string()); + for item in &self.children { + item.to_epub(writer)?; + } + writer.content("
    ".to_string()); + } + } + writer.content("
  • ".to_string()); + + Ok(()) + } +} + +impl ToEpub for Paragraph { + fn to_epub(&self, writer: &mut EpubWriter) -> Result<()> { + writer.content("
    1 { + for element in &self.elements[1..] { + writer.content("
    ".to_string()); + element.to_epub(writer)?; + } + } + + Ok(()) + } +} diff --git a/src/parser/inline.rs b/src/parser/inline.rs index c7da338..d4f3b29 100644 --- a/src/parser/inline.rs +++ b/src/parser/inline.rs @@ -94,12 +94,12 @@ impl ParseInline for Parser { } else if let Ok(mono) = self.parse_monospace() { log::trace!("Inline::Monospace {}", mono.value); Ok(Inline::Monospace(mono)) - } else if let Ok(gloss) = self.parse_glossary_reference() { - log::trace!("Inline::GlossaryReference {}", gloss.lock().unwrap().short); - Ok(Inline::GlossaryReference(gloss)) } else if let Ok(striked) = self.parse_striked() { log::trace!("Inline::Striked"); Ok(Inline::Striked(striked)) + } else if let Ok(gloss) = self.parse_glossary_reference() { + log::trace!("Inline::GlossaryReference {}", gloss.lock().unwrap().short); + Ok(Inline::GlossaryReference(gloss)) } else if let Ok(superscript) = self.parse_superscript() { log::trace!("Inline::Superscript"); Ok(Inline::Superscript(superscript)) @@ -252,6 +252,7 @@ impl ParseInline for Parser { self.ctm.assert_sequence(&STRIKED, Some(start_index))?; self.ctm.seek_one()?; let mut inline = vec![self.parse_inline()?]; + while !self.ctm.check_sequence(&STRIKED) { if let Ok(result) = self.parse_inline() { inline.push(result); @@ -259,7 +260,14 @@ impl ParseInline for Parser { return Err(self.ctm.rewind_with_error(start_index)); } } - self.ctm.seek_one()?; + self.ctm.rewind(self.ctm.get_index() - STRIKED.len()); + if self.ctm.check_any(WHITESPACE) { + return Err(self.ctm.rewind_with_error(start_index)); + } + for _ in 0..(STRIKED.len() + 1) { + self.ctm.seek_one()?; + } + Ok(StrikedText { value: inline }) } @@ -396,8 +404,9 @@ impl ParseInline for Parser { /// Parses a reference to a glossary entry fn parse_glossary_reference(&mut self) -> ParseResult>> { - self.ctm.assert_char(&GLOSSARY_REF_START, None)?; let start_index = self.ctm.get_index(); + self.ctm + .assert_char(&GLOSSARY_REF_START, Some(start_index))?; self.ctm.seek_one()?; let display = if self.ctm.check_char(&GLOSSARY_REF_START) { @@ -409,10 +418,10 @@ impl ParseInline for Parser { let mut key = self.ctm .get_string_until_any_or_rewind(&WHITESPACE, &[TILDE], start_index)?; - if key.len() == 0 { + if key.is_empty() { return Err(self.ctm.rewind_with_error(start_index)); } - if !key.chars().last().unwrap().is_alphabetic() { + while !key.is_empty() && !key.chars().last().unwrap().is_alphabetic() { self.ctm.rewind(self.ctm.get_index() - 1); key = key[..key.len() - 1].to_string(); }