diff --git a/src/elements.rs b/src/elements.rs index 32edd21..74c0205 100644 --- a/src/elements.rs +++ b/src/elements.rs @@ -106,6 +106,7 @@ pub enum SubText { Striked(StrikedText), Monospace(MonospaceText), Url(Url), + Image(Image), } #[derive(Clone, Debug)] @@ -146,7 +147,8 @@ pub struct Url { #[derive(Clone, Debug)] pub struct Image { - url: Url, + pub(crate) url: Url, + pub(crate) metadata: Option, } // implementations diff --git a/src/parser.rs b/src/parser.rs index f8032a1..15b2c9c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -339,7 +339,6 @@ impl Parser { while let Ok(token) = self.parse_inline() { paragraph.add_element(token); let start_index = self.index; - self.seek_inline_whitespace(); if self.check_special_group(&BLOCK_SPECIAL_CHARS) { self.revert_to(start_index)?; break; @@ -441,6 +440,7 @@ impl Parser { Ok(item) } + /// parses a markdown table fn parse_table(&mut self) -> Result { let header = self.parse_row()?; let start_index = self.index; @@ -531,6 +531,9 @@ impl Parser { if self.check_linebreak() { return Err(ParseError::new(self.index)); } + if let Ok(image) = self.parse_image() { + return Ok(SubText::Image(image)); + } if let Ok(url) = self.parse_url() { return Ok(SubText::Url(url)); } @@ -589,40 +592,61 @@ impl Parser { } } + /// parses an image url + fn parse_image(&mut self) -> Result { + let start_index = self.index; + self.seek_inline_whitespace(); + + if !self.check_special(&IMG_START) || self.next_char() == None { + return Err(self.revert_with_error(start_index)); + } + if let Ok(url) = self.parse_url() { + let metadata = if let Ok(meta) = self.parse_inline_metadata() { + if self.check_special(&META_CLOSE) && self.next_char() == None { + return Err(self.revert_with_error(start_index)); + } + Some(meta) + } else { + None + }; + Ok(Image { url, metadata }) + } else { + Err(self.revert_with_error(start_index)) + } + } + // parses an url fn parse_url(&mut self) -> Result { let start_index = self.index; self.seek_inline_whitespace(); let mut description = String::new(); - if self.check_special(&R_BRACKET) { + if self.check_special(&DESC_OPEN) { while let Some(character) = self.next_char() { - if self.check_special(&L_BRACKET) || self.check_linebreak() { + if self.check_special(&DESC_CLOSE) || self.check_linebreak() { break; } description.push(character); } - if !self.check_special(&L_BRACKET) { + if !self.check_special(&DESC_CLOSE) || self.next_char() == None { // it stopped at a linebreak or EOF return Err(self.revert_with_error(start_index)); } } - if let Some(_) = self.next_char() { - if !self.check_special(&R_PARENTH) { - // the next char isn't the start of the encased url - return Err(self.revert_with_error(start_index)); - } + if !self.check_special(&URL_OPEN) { + // the next char isn't the start of the encased url + return Err(self.revert_with_error(start_index)); } self.seek_inline_whitespace(); let mut url = String::new(); while let Some(character) = self.next_char() { - if self.check_special(&L_PARENTH) || self.check_linebreak() { + if self.check_special(&URL_CLOSE) || self.check_linebreak() { break; } url.push(character); } - if !self.check_special(&L_PARENTH) || url.is_empty() { + if !self.check_special(&URL_CLOSE) || url.is_empty() { return Err(self.revert_with_error(start_index)); } parse_option!(self.next_char(), self.index); @@ -645,7 +669,7 @@ impl Parser { let mut count = 0; loop { if self.check_special_group(&INLINE_SPECIAL_CHARS) - || (count > 0 && self.check_special(&R_BRACKET)) + || (count > 0 && self.check_special_group(&INLINE_SPECIAL_CHARS_SECOND)) { break; } else { diff --git a/src/tokens.rs b/src/tokens.rs index ba4503c..da64b5f 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -17,6 +17,7 @@ pub(crate) const HASH: char = '#'; pub(crate) const O: char = 'o'; pub(crate) const X: char = 'x'; pub(crate) const GT: char = '>'; +pub(crate) const BANG: char = '!'; // aliases @@ -26,12 +27,16 @@ pub(crate) const META_CLOSE: char = L_BRACKET; pub(crate) const QUOTE_START: char = GT; pub(crate) const DESC_OPEN: char = R_BRACKET; pub(crate) const DESC_CLOSE: char = L_BRACKET; +pub(crate) const IMG_START: char = BANG; +pub(crate) const URL_OPEN: char = R_PARENTH; +pub(crate) const URL_CLOSE: char = L_PARENTH; // groups pub(crate) const BLOCK_SPECIAL_CHARS: [char; 6] = [HASH, MINUS, BACKTICK, PIPE, QUOTE_START, META_OPEN]; pub(crate) const INLINE_SPECIAL_CHARS: [char; 6] = [LB, ASTERISK, UNDERSCR, TILDE, PIPE, BACKTICK]; +pub(crate) const INLINE_SPECIAL_CHARS_SECOND: [char; 3] = [DESC_OPEN, IMG_START, URL_OPEN]; pub(crate) const LIST_SPECIAL_CHARS: [char; 4] = [MINUS, PLUS, ASTERISK, O]; @@ -41,4 +46,5 @@ pub(crate) const SQ_CODE_BLOCK: [char; 3] = [BACKTICK, BACKTICK, BACKTICK]; // expressions -pub(crate) const EXPR_URI: &str = r"^(https?://)?\w+\.\w+(.\w+)?$|^([\w, -.]+|\w:)?(/[\w, -.]+)+$"; +pub(crate) const EXPR_URI: &str = + r"^(https?://)?\w+\.\w+(\.\w+|)?(/[\w, -.%&]+)*/?$|^([\w, -.]+|\w:)?(/[\w, -.]+)+$";