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 }