/ src / markdown_inline_element.rs
markdown_inline_element.rs
  1  use serde::{Deserialize, Serialize};
  2  use std::collections::HashMap;
  3  
  4  #[derive(Debug, Hash, Eq, PartialEq, Clone, Deserialize, Serialize)]
  5  pub enum MarkdownTokenType {
  6      Italics,
  7      Bold,
  8      BoldItalicsStart,
  9      BoldItalicsEnd,
 10      NexusPointerStart,
 11      NexusPointerEnd,
 12      Plain,
 13  }
 14  #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
 15  pub enum MarkdownToken {
 16      Italics(Box<MarkdownToken>),
 17      Bold(Box<MarkdownToken>),
 18      BoldItalics(Box<MarkdownToken>),
 19      Plain(Box<MarkdownToken>),
 20      _UseVariable(Box<MarkdownToken>),
 21      String(String),
 22      NexusPointer(NexusPointer),
 23  }
 24  
 25  impl MarkdownToken {
 26      // New method that creates a MarkdownToken::BoldItalics variant
 27      pub fn new(token_type: MarkdownTokenType, input: &str) -> MarkdownToken {
 28          return match token_type {
 29              MarkdownTokenType::Italics => {
 30                  MarkdownToken::Italics(Box::new(MarkdownToken::String(input.to_string())))
 31              }
 32              MarkdownTokenType::Bold => {
 33                  MarkdownToken::Bold(Box::new(MarkdownToken::String(input.to_string())))
 34              }
 35              MarkdownTokenType::BoldItalicsStart | MarkdownTokenType::BoldItalicsEnd => {
 36                  MarkdownToken::BoldItalics(Box::new(MarkdownToken::String(input.to_string())))
 37              }
 38              MarkdownTokenType::NexusPointerStart | MarkdownTokenType::NexusPointerEnd => {
 39                  MarkdownToken::NexusPointer(NexusPointer::new(&input))
 40              }
 41              // Add other MarkdownTokenType matches here if you want to handle other cases
 42              _ => MarkdownToken::Plain(Box::new(MarkdownToken::String(input.to_string()))),
 43          };
 44      }
 45  
 46      // Method to extract the string from the token
 47      pub fn get_string(&self) -> Option<String> {
 48          match self {
 49              // If the variant is String, return the string
 50              MarkdownToken::String(s) => Some(s.clone()),
 51  
 52              // If the variant is BoldItalics, recursively check the inner token
 53              MarkdownToken::BoldItalics(inner) => inner.get_string(),
 54  
 55              // If the variant is Italics, recursively check the inner token
 56              MarkdownToken::Italics(inner) => inner.get_string(),
 57  
 58              // If the variant is Bold, recursively check the inner token
 59              MarkdownToken::Bold(inner) => inner.get_string(),
 60  
 61              // If the variant is Plain, recursively check the inner token
 62              MarkdownToken::Plain(inner) => inner.get_string(),
 63  
 64              // For other token types, return None (they don't contain a string)
 65              _ => None,
 66          }
 67      }
 68  }
 69  
 70  #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
 71  pub struct NexusPointer {
 72      pub text: String,
 73      pub link_type: String,
 74      pub pointer: String,
 75  }
 76  impl NexusPointer {
 77      pub fn new(input: &str) -> NexusPointer {
 78          println!("input: {}", input);
 79          let first_quote = input.chars().position(|c| c == '"').unwrap();
 80          let string: String = input.chars().take(first_quote).collect();
 81          let colon_index = input.chars().position(|c| c == ':').unwrap();
 82          let link_type: String = input
 83              .chars()
 84              .skip(first_quote + 1)
 85              .take(colon_index - first_quote - 1)
 86              .collect();
 87          let pointer: String = input
 88              .chars()
 89              .skip(colon_index + 1)
 90              .take(input.len() - 1)
 91              .collect();
 92  
 93          return NexusPointer {
 94              text: string,
 95              link_type,
 96              pointer,
 97          };
 98      }
 99  }
100  
101  /// Extracts the positions of markdown symbols in the input string.
102  /// Returns a HashMap where each key is a MarkdownTokenType and the value is a vector of indices.
103  fn extract_positions(input: &str) -> HashMap<MarkdownTokenType, Vec<usize>> {
104      let mut positions: HashMap<MarkdownTokenType, Vec<usize>> = HashMap::new();
105  
106      // Locate special markdown markers in the input.
107      positions.insert(
108          MarkdownTokenType::NexusPointerStart,
109          input.match_indices("${").map(|(i, _)| i).collect(),
110      );
111      positions.insert(
112          MarkdownTokenType::NexusPointerEnd,
113          input.match_indices("}$").map(|(i, _)| i).collect(),
114      );
115      positions.insert(
116          MarkdownTokenType::BoldItalicsStart,
117          input.match_indices("**_").map(|(i, _)| i).collect(),
118      );
119      positions.insert(
120          MarkdownTokenType::BoldItalicsEnd,
121          input.match_indices("_**").map(|(i, _)| i).collect(),
122      );
123  
124      // Filter out ** that are actually part of **_ bold-italics sequences.
125      let bold_indices_raw: Vec<usize> = input.match_indices("**").map(|(i, _)| i).collect();
126      let mut bold_indices: Vec<usize> = vec![];
127      for &bold_index in &bold_indices_raw {
128          if !positions[&MarkdownTokenType::BoldItalicsStart].contains(&bold_index)
129              && !(bold_index > 0
130                  && positions[&MarkdownTokenType::BoldItalicsEnd].contains(&(bold_index - 1)))
131          {
132              bold_indices.push(bold_index);
133          }
134      }
135      positions.insert(MarkdownTokenType::Bold, bold_indices);
136  
137      // Identify valid standalone * for italics (not part of bold or bold-italics sequences).
138      let italics_indices_raw: Vec<usize> = input.match_indices("*").map(|(i, _)| i).collect();
139      let mut italics_indices: Vec<usize> = vec![];
140      for &italics_index in &italics_indices_raw {
141          if !positions[&MarkdownTokenType::Bold].contains(&italics_index)
142              && !(italics_index > 0
143                  && positions[&MarkdownTokenType::Bold].contains(&(italics_index - 1)))
144              && !positions[&MarkdownTokenType::BoldItalicsStart].contains(&italics_index)
145              && !(italics_index > 0
146                  && positions[&MarkdownTokenType::BoldItalicsStart].contains(&(italics_index - 1)))
147              && !(italics_index > 0
148                  && positions[&MarkdownTokenType::BoldItalicsEnd].contains(&(italics_index - 1)))
149              && !(italics_index > 1
150                  && positions[&MarkdownTokenType::BoldItalicsEnd].contains(&(italics_index - 2)))
151          {
152              italics_indices.push(italics_index);
153          }
154      }
155      positions.insert(MarkdownTokenType::Italics, italics_indices);
156  
157      positions
158  }
159  
160  /// Handles opening and closing logic for markdown tokens.
161  fn handle_token_logic(
162      input: &str,
163      lowest_index: usize,
164      current_index: usize,
165      lowest_index_type: &MarkdownTokenType,
166      token_type_stack: &mut Vec<MarkdownTokenType>,
167      tokens: &mut Vec<MarkdownToken>,
168  ) {
169      let parsed_text = input[current_index..lowest_index].to_string();
170      if token_type_stack.is_empty()
171          || !(token_type_stack.last().unwrap() == &MarkdownTokenType::BoldItalicsStart
172              && lowest_index_type == &MarkdownTokenType::BoldItalicsEnd)
173              && !(token_type_stack.last().unwrap() == &MarkdownTokenType::NexusPointerStart
174                  && lowest_index_type == &MarkdownTokenType::NexusPointerEnd)
175              && !(token_type_stack.last().unwrap() == lowest_index_type)
176      {
177          if token_type_stack.is_empty() {
178              tokens.push(MarkdownToken::new(MarkdownTokenType::Plain, &parsed_text));
179          } else {
180              tokens.push(MarkdownToken::new(
181                  token_type_stack.last().unwrap().clone(),
182                  &parsed_text,
183              ));
184          }
185          token_type_stack.push(lowest_index_type.clone());
186      } else {
187          let current_closure = token_type_stack.pop().unwrap();
188          tokens.push(MarkdownToken::new(current_closure, &parsed_text));
189      }
190  }
191  
192  /// Processes the markdown input and updates the token list accordingly.
193  pub fn process_inline_markdown_text(input: &str) -> Vec<MarkdownToken> {
194      let mut token_type_stack: Vec<MarkdownTokenType> = vec![];
195      let mut current_index = 0;
196      let mut tokens: Vec<MarkdownToken> = vec![];
197      // Get the positions of the inline text modifiers
198      let positions: HashMap<MarkdownTokenType, Vec<usize>> = extract_positions(&input);
199  
200      while current_index < input.len() {
201          let mut lowest_index = usize::MAX;
202          let mut lowest_index_type = MarkdownTokenType::Plain;
203  
204          for (token_type, indices) in &positions {
205              if let Some(&index) = indices.iter().find(|&&i| i >= current_index) {
206                  if index < lowest_index {
207                      lowest_index = index;
208                      lowest_index_type = token_type.clone();
209                  }
210              }
211          }
212  
213          if lowest_index == usize::MAX {
214              break;
215          }
216  
217          handle_token_logic(
218              input,
219              lowest_index,
220              current_index,
221              &lowest_index_type,
222              &mut token_type_stack,
223              &mut tokens,
224          );
225  
226          current_index = lowest_index
227              + match lowest_index_type {
228                  MarkdownTokenType::NexusPointerStart
229                  | MarkdownTokenType::BoldItalicsStart
230                  | MarkdownTokenType::BoldItalicsEnd => 3,
231                  MarkdownTokenType::NexusPointerEnd | MarkdownTokenType::Bold => 2,
232                  MarkdownTokenType::Italics => 1,
233                  _ => 0,
234              };
235      }
236  
237      // Add the last bit of the line if its Plain
238      if current_index < input.len() {
239          tokens.push(MarkdownToken::new(
240              MarkdownTokenType::Plain,
241              &input[current_index..].to_string(),
242          ));
243      }
244  
245      return tokens;
246  }