1mod line_type;
2pub mod utilities;
3
4use line_type::{block_line_type, HtmlType, LineType};
5
6use std::{
7 collections::HashMap,
8 fs::File,
9 io::{BufRead, BufReader, Error},
10 path::Path,
11};
12
13#[derive(Debug, Clone)]
17struct NodeData {
18 tag: String,
21
22 misc: Option<String>,
24 text: Option<String>,
26
27 contents: Vec<NodeData>,
28}
29
30#[derive(Debug, Clone)]
31struct ReferenceLink {
32 url: String,
33 title: Option<String>,
34}
35
36#[derive(Debug)]
37pub struct Converter {
38 html_void: Vec<String>,
39 references: HashMap<String, ReferenceLink>,
40 indentation: usize,
41}
42
43#[derive(Debug)]
44pub struct FrontMatter {
45 pub date: Option<String>,
46 pub description: Option<String>,
47 pub title: Option<String>,
48 pub slug: Option<String>,
49 pub author: Option<String>,
50 pub draft: bool,
51 pub slides: bool,
52 pub skip_index: bool,
53}
54
55impl Converter {
57 pub fn new(html_void: Vec<String>, indentation: usize) -> Self {
58 Converter {
59 html_void,
60 references: Default::default(),
61 indentation,
62 }
63 }
64
65 pub fn convert_file(&mut self, input: &Path, depth: usize) -> Result<(String, FrontMatter), Error> {
67 let lines = self.file_read(input)?;
69
70 let lines_processed_references = self.reference_link_get(lines);
72
73 let (lines_processed_fm, fm) = self.fm_get(lines_processed_references);
75
76 let blocks = self.block_process(lines_processed_fm)?;
79
80 let processed = self.block_merge(blocks, depth, fm.slides);
82
83 self.references = Default::default();
85
86 Ok((processed, fm))
87 }
88
89 fn file_read(&self, path: &Path) -> Result<Vec<String>, Error> {
91 let input = File::open(path)?;
95 let buffered = BufReader::new(input);
96
97 let lines_all = buffered
98 .lines()
99 .map(|l| {
100 let unwrapped = l.expect("Could not parse line");
101 unwrapped.replace('\u{0000}', "\u{FFFD}")
102 })
103 .collect::<Vec<String>>();
104
105 Ok(lines_all)
106 }
107
108 fn reference_link_get(&mut self, lines: Vec<String>) -> Vec<String> {
109 let mut result: Vec<String> = vec![];
110
111 let mut references: HashMap<String, ReferenceLink> = HashMap::new();
115
116 'outer: for line in lines {
117 if !line.trim_start().starts_with('[') {
119 result.push(line);
120 continue;
121 }
122
123 let characters: Vec<char> = line.trim_start().chars().collect();
127 let mut index_char = 1;
129
130 let mut reference_vec: Vec<char> = vec![];
132 loop {
133 if index_char >= characters.len() {
134 break;
135 }
136
137 let character = characters[index_char];
138
139 match character {
140 ']' => {
141 if characters[index_char - 1] == '\\' {
143 reference_vec.pop();
146 reference_vec.push(character);
147 } else {
148 index_char += 1;
150 break;
151 }
152 }
153 _ => reference_vec.push(character),
154 }
155 index_char += 1;
156 }
157
158 if reference_vec.is_empty() {
160 result.push(line);
161 continue 'outer;
162 }
163
164 if index_char >= characters.len() {
166 result.push(line);
167 continue 'outer;
168 }
169 if characters[index_char] != ':' {
170 result.push(line);
172 continue 'outer;
173 } else {
174 index_char += 1;
176 }
177
178 loop {
180 if index_char >= characters.len() {
181 break;
182 }
183 let character = characters[index_char];
184
185 if character == ' ' || character == '\t' {
186 index_char += 1;
187 } else {
188 break;
189 }
190 }
191
192 let mut url_vec: Vec<char> = vec![];
194 let mut angle_brackets = (false, false);
196 loop {
197 if index_char >= characters.len() {
198 break;
199 }
200 let character = characters[index_char];
201
202 match character {
203 '<' => {
204 if !angle_brackets.0 {
206 angle_brackets = (true, false);
207 } else {
208 url_vec.push(character)
209 }
210 }
211 '>' => {
212 if angle_brackets.0 {
214 if characters[index_char - 1] == '\\' {
218 url_vec.pop();
220 url_vec.push(character);
221 } else {
222 angle_brackets = (true, true);
224
225 index_char += 1;
226 break;
227 }
228 } else {
229 url_vec.push(character);
230 }
231 }
232 ' ' | '\t' => {
233 if angle_brackets.0 {
236 url_vec.push(character)
237 } else {
238 break;
239 }
240 }
241
242 _ => url_vec.push(character),
243 }
244
245 index_char += 1;
246 }
247
248 if url_vec.is_empty() {
249 if !angle_brackets.1 {
251 result.push(line);
252 continue 'outer;
253 }
254 }
255
256 if angle_brackets.0 && !angle_brackets.1 {
258 result.push(line);
259 continue 'outer;
260 }
261
262 let mut whitespace = 0;
264 loop {
265 if index_char >= characters.len() {
266 break;
267 }
268 let character = characters[index_char];
269
270 match character {
271 ' ' | '\t' => {
272 index_char += 1;
273 whitespace += 1;
274 }
275 '\'' | '"' => {
276 if whitespace == 0 {
277 result.push(line);
278 continue 'outer;
279 } else {
280 break;
281 }
282 }
283 _ => break,
284 }
285 }
286
287 let mut title_quote: Option<char> = None;
292 if index_char < characters.len() {
293 let character = characters[index_char];
294 match character {
295 '\'' | '"' => title_quote = Some(character),
296 _ => {}
297 }
298 index_char += 1;
299 }
300
301 let mut title_vec: Vec<char> = vec![];
302
303 if let Some(delimiter) = title_quote {
304 let mut title_closed = false;
306
307 loop {
308 if index_char >= characters.len() {
309 break;
310 }
311 let character = characters[index_char];
312
313 if character == delimiter {
314 if characters[index_char - 1] == '\\' {
317 title_vec.pop();
320 title_vec.push(character);
321 } else {
322 title_closed = true;
323
324 break;
327 }
328 } else {
329 title_vec.push(character)
330 }
331
332 index_char += 1;
333 }
334
335 if !title_closed {
337 title_vec = vec![];
339 }
340 }
341
342 let reference: String = reference_vec.iter().collect();
344 let url: String = url_vec.iter().collect();
345
346 let title: Option<String> = if title_vec.is_empty() { None } else { Some(title_vec.iter().collect()) };
347
348 let reference_data = ReferenceLink {
349 url,
350 title,
351 };
352
353 references.insert(reference, reference_data);
354 }
355
356 self.references = references;
357
358 result
359 }
360
361 fn fm_get(&mut self, lines: Vec<String>) -> (Vec<String>, FrontMatter) {
362 let mut fm = FrontMatter {
363 date: None,
364 description: None,
365 title: None,
366 slug: None,
367 author: None,
368 draft: false,
369 slides: false,
370 skip_index: false,
371 };
372
373 let mut result: Vec<String> = vec![];
374
375 let mut fm_processed = false;
376 let mut fm_in_progress = false;
377
378 let mut closer: Vec<char> = vec![];
380 let mut pre_closer = String::default();
381 for line in lines {
382 if block_line_type(&line, &[]) == LineType::FrontMatter && !fm_in_progress && !fm_processed {
383 fm_in_progress = true;
384 }
385 if !fm_in_progress {
386 result.push(line);
387 continue;
388 }
389
390 if closer.is_empty() {
398 for character in line.trim().chars() {
399 match character {
400 '+' => closer.push(character),
401 _ => {
402 break;
403 }
404 }
405 }
406 pre_closer = closer.iter().collect::<String>().trim().to_string();
407
408 continue;
410 }
411
412 let trimmed = line.trim();
413
414 if trimmed == pre_closer.as_str() {
415 fm_in_progress = false;
416 fm_processed = true;
417 continue;
418 }
419
420 let mut stripped = String::default();
422
423 for substring in ["date", "slug", "title", "description", "author", "draft", "slides", "skip_index"] {
424 if trimmed.starts_with(substring) {
425 stripped = trimmed.replace(substring, "")
426 }
427 }
428
429 let mut characters = vec![];
433 let mut equals = false;
434 for c in stripped.chars() {
435 match c {
436 '=' => {
437 if !equals {
438 equals = true;
439 } else {
440 characters.push(c);
441 }
442 }
443 _ => {
444 if equals {
445 characters.push(c);
446 }
447 }
448 }
449 }
450
451 let tmp = characters.iter().collect::<String>();
452 let mut value_tmp = tmp.trim();
453 if (value_tmp.starts_with('\'') && value_tmp.ends_with('\'')) || (value_tmp.starts_with('"') && value_tmp.ends_with('"')) {
454 value_tmp = &value_tmp[1..value_tmp.len() - 1];
455 }
456 let value = value_tmp.trim().to_owned();
457 let formatted = self.inline_process(value.clone(), 0);
458
459 if trimmed.starts_with("date") {
461 fm.date = Some(formatted);
462 } else if trimmed.starts_with("slug") {
463 fm.slug = Some(value);
464 } else if trimmed.starts_with("title") {
465 fm.title = Some(formatted);
466 } else if trimmed.starts_with("description") {
467 fm.description = Some(formatted);
468 } else if trimmed.starts_with("author") {
469 fm.author = Some(formatted);
470 } else if trimmed.starts_with("draft") && value.to_ascii_lowercase().as_str() == "true" {
471 fm.draft = true;
472 } else if trimmed.starts_with("slides") && value.to_ascii_lowercase().as_str() == "true" {
473 fm.slides = true;
474 } else if trimmed.starts_with("skip_index") && value.to_ascii_lowercase().as_str() == "true" {
475 fm.skip_index = true;
476 };
477 }
478
479 (result, fm)
480 }
481
482 fn block_process(&self, lines: Vec<String>) -> Result<Vec<NodeData>, Error> {
483 let mut node_data: Vec<NodeData> = vec![];
487
488 let mut row: isize = 0;
490
491 let mut block_excluded: Vec<String> = vec![];
493
494 let mut html_end_col = 0;
496
497 loop {
498 if row >= lines.len() as isize {
500 break;
501 }
502
503 let line = if html_end_col != 0 { &lines[row as usize][html_end_col..] } else { lines[row as usize].as_str() };
504
505 let lines_remaining = &lines[(row as usize)..];
509
510 let mut lines_remaining_partial = vec![line.to_string()];
512
513 if ((row as usize) + 1) < lines.len() {
514 let remainder = &lines[((row as usize) + 1)..];
515 lines_remaining_partial.extend(remainder.to_vec());
516 }
517
518 let mut clean_up = false;
519 match block_line_type(line, &block_excluded) {
521 LineType::Header => {
522 node_data.push(self.block_process_headers(line));
523
524 row += 1;
526
527 clean_up = true;
528 }
529 LineType::HorizontalRule => {
530 node_data.push(NodeData {
535 tag: "hr".to_string(),
536 text: None,
537 misc: None,
538 contents: vec![],
539 });
540
541 row += 1;
543
544 clean_up = true;
545 }
546
547 LineType::Paragraph => {
549 if line.is_empty() {
550 row += 1;
551 continue;
552 }
553
554 if let Some((node, offset)) = self.block_process_paragraph(&lines_remaining_partial, 0, &block_excluded) {
555 node_data.push(node);
556
557 row += offset;
558
559 clean_up = true;
560 } else {
561 block_excluded.push("p".to_string());
562 }
563 }
564 LineType::Preformatted => {
565 if let Some((node, offset)) = self.block_process_pre(lines_remaining) {
566 node_data.push(node);
567
568 row += offset + 1;
570
571 clean_up = true;
572 } else {
573 block_excluded.push("pre".to_string());
574 }
575 }
576
577 LineType::FrontMatter => {
578 }
580
581 LineType::BlockQuote => {
582 if let Some((node, offset)) = self.block_process_blockquote(lines_remaining) {
583 node_data.push(node);
584
585 row += offset;
586
587 clean_up = true;
588 } else {
589 block_excluded.push("blockquote".to_string());
590 }
591 }
592
593 LineType::UL(_, _) | LineType::OL(_, _) => {
594 if let Some((node, offset)) = self.block_process_lists(lines_remaining) {
595 node_data.push(node);
596
597 row += offset;
598
599 clean_up = true;
600 } else {
601 block_excluded.push("ul".to_string());
602 }
603 }
604
605 LineType::Html(html_type) => {
606 if let Some((node, offset, col)) = self.block_process_html(&lines_remaining_partial, html_type) {
607 node_data.push(node);
608
609 if col > 0 {
610 row += offset - 1;
612 html_end_col = col;
613 } else {
614 row += offset;
615 html_end_col = 0;
616 }
617
618 block_excluded = vec![];
620 } else {
621 match html_type {
622 HtmlType::Normal => {
623 block_excluded.push("html".to_string());
624 }
625 HtmlType::Comment => {
626 block_excluded.push("html_comment".to_string());
627 }
628 HtmlType::CData => {
629 block_excluded.push("html_cdata".to_string());
630 }
631 }
632 }
633 continue;
635 }
636
637 LineType::Table => {
638 if let Some((node, offset)) = self.block_process_table(lines_remaining) {
639 node_data.push(node);
640
641 row += offset;
642
643 clean_up = true;
644 } else {
645 block_excluded.push("table".to_string());
646 }
647 }
648 }
649
650 if clean_up {
652 block_excluded = vec![];
654 html_end_col = 0;
655
656 continue;
657 }
658
659 row += 1;
661 }
662 Ok(node_data)
663 }
664
665 fn block_process_headers(&self, line: &str) -> NodeData {
666 let mut count = 0;
675 let mut broken = false;
676 let mut content_array: Vec<char> = vec![];
677 for character in line.trim().chars() {
678 match character {
679 '#' => {
680 if !broken && count < 6 {
681 count += 1
682 } else {
683 content_array.push(character);
684 }
685 }
686 _ => {
687 broken = true;
688 content_array.push(character);
689 }
690 }
691 }
692
693 let tag = format!("h{}", &count);
695
696 let content_string = content_array.to_vec().iter().collect::<String>();
698 let text = Some(content_string.as_str().trim().to_string());
699
700 NodeData {
701 tag,
702 text,
703 misc: None,
704 contents: vec![],
705 }
706 }
707
708 fn block_process_pre(&self, lines: &[String]) -> Option<(NodeData, isize)> {
709 let line_first = &lines[0];
720
721 let mut pre_language_tmp: Vec<char> = vec![];
723 let mut closer: Vec<char> = vec![];
724 let mut space = false;
725 for character in line_first.trim().chars() {
726 match character {
727 '`' => {
729 if !space {
730 closer.push(character)
731 }
732
733 continue;
734 }
735 ' ' | '\t' => {
736 if !space {
738 space = true;
739 } else {
740 pre_language_tmp.push(character);
741 }
742 }
743 _ => {
744 pre_language_tmp.push(character);
745 }
746 }
747 }
748
749 let pre_language = if pre_language_tmp.is_empty() {
750 None
751 } else {
752 let language = pre_language_tmp.iter().collect::<String>().trim().to_string();
753 if language.is_empty() {
754 None
755 } else {
756 Some(language)
757 }
758 };
759
760 let pre_closer = closer.iter().collect::<String>().trim().to_string();
762
763 let mut row = 1;
765
766 let mut block_lines = vec![];
767 let mut finished = false;
768 loop {
769 if row >= lines.len() as isize {
770 break;
771 }
772 let line = &lines[row as usize];
773
774 if line.trim() == pre_closer.as_str() {
775 finished = true;
777 break;
778 } else {
779 block_lines.push(line.clone());
781
782 }
784 row += 1;
785 }
786
787 if finished {
790 let text = block_lines.join("\n").replace('<', "<").replace('>', ">");
791 let node = NodeData {
792 tag: "pre".to_string(),
793 text: Some(text),
794 misc: pre_language,
795 contents: vec![],
796 };
797 Some((node, row))
798 } else {
799 None
800 }
801 }
802
803 fn block_process_blockquote(&self, lines: &[String]) -> Option<(NodeData, isize)> {
804 let mut row = 0;
814
815 let mut block_lines = vec![];
816 loop {
817 if row >= lines.len() as isize {
818 break;
819 }
820 let line = &lines[row as usize];
821
822 if line.starts_with('>') && !line.starts_with(">!") {
824 let cleaned = line.replacen('>', "", 1).replacen(' ', "", 1);
825 block_lines.push(cleaned);
826 } else {
827 break;
828 }
829
830 row += 1;
831 }
832
833 if !block_lines.is_empty() {
834 let contents = self
835 .block_process(block_lines)
836 .unwrap_or_default();
838
839 let node = NodeData {
840 tag: "blockquote".to_string(),
841 text: None,
842 misc: None,
843 contents,
844 };
845
846 Some((node, row))
847 } else {
848 None
849 }
850 }
851
852 fn block_process_table(&self, lines: &[String]) -> Option<(NodeData, isize)> {
853 let mut table_rows = vec![];
862 let mut table_alignment: HashMap<i32, String> = HashMap::new();
863 let mut table_header_length = 0;
864
865 let mut row = 0;
866 loop {
867 if row >= lines.len() as isize {
868 break;
869 }
870 let line = &lines[row as usize];
871
872 if !line.trim().starts_with('|') {
874 break;
875 }
876
877 if line.replace(['|', '-', ':'], "").trim() == "" {
879 table_alignment = self.block_process_table_alignment(line);
883
884 row += 1;
885 continue;
886 }
887
888 let mut row_contents: Vec<NodeData> = vec![];
889 let mut content: Vec<char> = vec![];
890 let mut last_character: Option<char> = None;
891 let mut current_col = 0;
892
893 let tag_col = if table_rows.is_empty() { "th".to_string() } else { "td".to_string() };
895
896 let tag_row = "tr".to_string();
897
898 for character in line.trim_start().trim_end_matches('|').chars() {
899 match character {
900 '|' => {
901 if let Some(x) = last_character {
902 if x == '\\' {
903 content.pop();
905 content.push(character);
906 last_character = Some(character);
907 continue;
908 }
909 }
910
911 if current_col > 0 {
912 let text: String = content.iter().collect();
913 let alignment = table_alignment.get(&{ current_col }).cloned();
914
915 row_contents.push(NodeData {
917 tag: tag_col.clone(),
918 misc: alignment,
919 text: Some(text),
920 contents: vec![],
921 });
922 }
923
924 content = vec![];
926
927 current_col += 1;
929
930 if table_rows.is_empty() {
931 table_header_length = current_col;
932 } else if current_col > table_header_length {
933 break;
935 }
936 }
937 _ => content.push(character),
938 }
939 last_character = Some(character);
940 }
941
942 if !content.is_empty() {
944 let text: String = content.iter().collect();
945 let alignment = table_alignment.get(&{ current_col }).cloned();
946
947 row_contents.push(NodeData {
949 tag: tag_col.clone(),
950 misc: alignment,
951 text: Some(text),
952 contents: vec![],
953 });
954 }
955
956 table_rows.push(NodeData {
957 tag: tag_row,
958 misc: None,
959 text: None,
960 contents: row_contents,
961 });
962
963 row += 1;
964 }
965
966 if !table_rows.is_empty() {
967 let table_header = vec![table_rows[0].clone()];
968
969 let table_body = if table_rows.len() > 1 { table_rows[1..].to_vec() } else { vec![] };
970
971 let table = NodeData {
972 tag: "table".to_string(),
973 misc: None,
974 text: None,
975 contents: vec![
976 NodeData {
977 tag: "thead".to_string(),
978 misc: None,
979 text: None,
980 contents: table_header,
981 },
982 NodeData {
983 tag: "tbody".to_string(),
984 misc: None,
985 text: None,
986 contents: table_body,
987 },
988 ],
989 };
990
991 Some((table, row))
992 } else {
993 None
994 }
995 }
996
997 fn block_process_lists(&self, lines: &[String]) -> Option<(NodeData, isize)> {
998 let mut li_active = false;
1013 let mut li_number: Option<String> = None;
1014 let mut li_type = LineType::Paragraph;
1015 let mut li_indent = 0;
1016 let mut li_lines = vec![];
1018 let mut li_finished = false;
1019
1020 let mut li_array = vec![];
1021
1022 let mut row = 0;
1023 loop {
1024 let mut finished = false;
1025
1026 if row >= lines.len() as isize {
1027 break;
1028 }
1029 let line = &lines[row as usize];
1030
1031 let list_type = block_line_type(line, &[]);
1032
1033 if !li_active {
1034 match list_type {
1037 LineType::OL(indent, number) => {
1038 li_indent = indent;
1039 li_number = Some(number.to_string());
1040 }
1041 LineType::UL(indent, _) => {
1042 li_indent = indent;
1043 }
1044 _ => {}
1045 };
1046
1047 let vec_chars: Vec<char> = line.chars().collect();
1048 let trimmed = &vec_chars[li_indent..];
1050 let cleaned = trimmed.iter().collect::<String>();
1051
1052 li_lines.push(cleaned);
1053 li_type = list_type;
1054
1055 li_active = true;
1057 } else {
1058 let leading_spaces = String::from_utf8(vec![b' '; li_indent]).unwrap_or_default();
1061
1062 if line.starts_with(&leading_spaces) {
1063 let trimmed = line.replacen(' ', "", li_indent);
1065 li_lines.push(trimmed);
1066 } else if line.is_empty() {
1067 li_finished = true;
1069 finished = true;
1070 } else if compare_lists(list_type, li_type) {
1071 li_finished = true;
1073
1074 row -= 1;
1076 } else {
1077 li_finished = true;
1080 finished = true;
1081 }
1082 }
1083
1084 if (row + 1) >= lines.len() as isize {
1085 li_finished = true;
1086 }
1087
1088 if li_finished {
1089 if li_lines.len() == 1 {
1090 li_array.push(NodeData {
1091 tag: "li".to_string(),
1092 text: Some(li_lines[0].clone()),
1093 misc: li_number.clone(),
1094 contents: vec![],
1095 });
1096 } else {
1097 let contents = self
1098 .block_process(li_lines.clone())
1099 .unwrap_or_default();
1101
1102 li_array.push(NodeData {
1103 tag: "li".to_string(),
1104 text: None,
1105 misc: li_number.clone(),
1106 contents,
1107 });
1108 }
1109
1110 li_finished = false;
1112 li_active = false;
1113 li_lines = vec![];
1114 }
1115
1116 if finished {
1117 break;
1118 }
1119
1120 row += 1;
1121 }
1122
1123 if !li_array.is_empty() {
1124 let list_type = match li_number {
1125 None => "ul".to_string(),
1126 Some(_) => "ol".to_string(),
1127 };
1128 let node = NodeData {
1129 tag: list_type,
1130 text: None,
1131 misc: None,
1132 contents: li_array,
1133 };
1134
1135 Some((node, row))
1136 } else {
1137 None
1138 }
1139 }
1140
1141 fn block_process_paragraph(&self, lines: &[String], row_start: isize, block_excluded: &[String]) -> Option<(NodeData, isize)> {
1142 let mut block_lines = lines[..(row_start as usize)].to_vec();
1156
1157 let mut row = row_start;
1158 let mut first_run = true;
1159 loop {
1160 if row >= lines.len() as isize {
1161 break;
1162 }
1163
1164 let line = &lines[row as usize];
1165
1166 if line.is_empty() {
1168 break;
1169 }
1170
1171 if block_lines.is_empty() {
1172 let line_to_push = if block_excluded.contains(&"html".to_string()) || block_excluded.contains(&"html_comment".to_string()) || block_excluded.contains(&"html_cdata".to_string()) {
1173 line.replacen('<', ">", 1).to_string()
1175 } else {
1176 line.to_string()
1177 };
1178
1179 block_lines.push(line_to_push);
1181 } else {
1182 match block_line_type(line, &[]) {
1183 LineType::Paragraph => {}
1184 _ => {
1185 if !first_run {
1188 break;
1189 }
1190 }
1191 }
1192
1193 block_lines.push(line.to_string());
1194 }
1195
1196 first_run = false;
1197 row += 1;
1198 }
1199
1200 if !block_lines.is_empty() {
1201 let node = NodeData {
1202 tag: 'p'.to_string(),
1203 text: Some(block_lines.join("\n")),
1204 misc: None,
1205 contents: vec![],
1206 };
1207
1208 Some((node, row))
1209 } else {
1210 None
1211 }
1212 }
1213
1214 fn block_process_html(&self, lines: &[String], html_type: HtmlType) -> Option<(NodeData, isize, usize)> {
1215 let opener;
1233 let closer;
1234 let tag;
1235
1236 if !lines.is_empty() {
1237 if let Some((inner_tag, inner_opener, inner_closer)) = self.block_process_html_tags(&lines[0], html_type) {
1238 tag = inner_tag;
1239 opener = inner_opener;
1240 closer = inner_closer;
1241 } else {
1242 return None;
1243 }
1244 } else {
1245 return None;
1246 };
1247
1248 let mut block_lines = vec![];
1249
1250 let mut html_depth = 0;
1251
1252 let mut row = 0;
1253 let mut index = 0;
1254 let mut html_end_col = 0;
1255 let mut finished = false;
1256 loop {
1257 if row >= lines.len() as isize {
1258 break;
1259 }
1260 let line = &lines[row as usize];
1261
1262 let mut valid_chars_closing = 0;
1266 let mut valid_chars_opening = 0;
1267 index = 0;
1269
1270 let line_chars: Vec<char> = line.chars().collect();
1271
1272 loop {
1273 if closer.is_empty() {
1274 break;
1275 }
1276 if opener.is_empty() {
1277 break;
1278 }
1279 if index >= line_chars.len() {
1280 break;
1281 }
1282
1283 let character = line_chars[index];
1284
1285 index += 1;
1287
1288 if opener[valid_chars_opening] == character {
1289 valid_chars_opening += 1;
1291
1292 if valid_chars_opening == opener.len() {
1293 html_depth += 1;
1294 valid_chars_opening = 0;
1295 }
1296 } else {
1297 valid_chars_opening = 0;
1299 }
1300
1301 if closer[valid_chars_closing] == character {
1302 valid_chars_closing += 1;
1304
1305 if valid_chars_closing == closer.len() {
1306 html_depth -= 1;
1307 if html_depth == 0 {
1308 finished = true;
1309 break;
1310 }
1311 valid_chars_closing = 0;
1313 }
1314 } else {
1315 valid_chars_closing = 0;
1317 }
1318 }
1319
1320 if finished {
1321 break;
1322 } else {
1323 block_lines.push(line.to_string())
1324 }
1325
1326 row += 1;
1327 }
1328
1329 if finished {
1331 let mut partial_line = false;
1332 let mut permitted_in_p = false;
1333 let forbidden_tag = false; let line = &lines[row as usize];
1336
1337 if line[index..].trim() != "" {
1339 partial_line = true;
1340
1341 let allowed_tags_p = vec![
1345 "abbr",
1347 "audio",
1348 "b",
1349 "button",
1350 "canvas",
1351 "cite",
1352 "code",
1353 "data",
1354 "datalist",
1355 "dfn",
1356 "em",
1357 "embed",
1358 "i",
1359 "iframe",
1360 "img",
1361 "input",
1362 "label",
1363 "mark",
1364 "math",
1365 "meter",
1366 "noscript",
1367 "object",
1368 "output",
1369 "picture",
1370 "progress",
1371 "q",
1372 "ruby",
1373 "samp",
1374 "script",
1375 "select",
1376 "small",
1377 "span",
1378 "string",
1379 "sub",
1380 "sup",
1381 "svg",
1382 "textarea",
1383 "time",
1384 "u",
1385 "var",
1386 "video",
1387 "wbr",
1388 "a",
1390 "del",
1391 "ins",
1392 "map",
1393 "custom_comment",
1398 "custom_cdata",
1399 ];
1400
1401 if allowed_tags_p.contains(&tag.as_str()) {
1402 permitted_in_p = true;
1403 }
1404 }
1405
1406 if forbidden_tag {
1408 }
1410
1411 if partial_line {
1413 if permitted_in_p {
1414 return if let Some((node, offset)) = self.block_process_paragraph(lines, row, &[]) {
1418 Some((node, offset, 0))
1419 } else {
1420 None
1422 };
1423 } else {
1424 let tmp_line = &line[..index];
1426 block_lines.push(tmp_line.to_string());
1427
1428 let remaining = &line[index..];
1434
1435 if let LineType::Html(_) = block_line_type(remaining, &[]) {
1436 html_end_col = index;
1437 }
1438 }
1439 } else {
1440 block_lines.push(line.to_string());
1442 }
1443
1444 let node = NodeData {
1447 tag: "html".to_string(),
1448 text: Some(block_lines.join("\n")),
1450 misc: None,
1451 contents: vec![],
1452 };
1453
1454 Some((node, row + 1, html_end_col))
1456 } else {
1457 None
1458 }
1459 }
1460
1461 fn block_process_html_tags(&self, line: &str, html_type: HtmlType) -> Option<(String, Vec<char>, Vec<char>)> {
1462 let mut opener;
1463 let mut closer;
1464 let tag;
1465
1466 match html_type {
1467 HtmlType::Normal => {
1468 let mut tag_vec: Vec<char> = vec![];
1472
1473 let mut index = 1;
1475 let line_chars: Vec<char> = line.trim_start().chars().collect();
1476 loop {
1477 if index >= line_chars.len() {
1478 break;
1479 }
1480
1481 let character = line_chars[index];
1482
1483 index += 1;
1485 match character {
1486 'a'..='z' | 'A'..='Z' | '0'..='9' => {
1487 tag_vec.push(character);
1488 }
1489 _ => {
1490 break;
1492 }
1493 }
1494 }
1495
1496 if tag_vec.is_empty() {
1498 return None;
1500 }
1501
1502 tag = tag_vec.iter().collect();
1507
1508 opener = vec!['<'];
1513 opener.extend(&tag_vec);
1514
1515 if self.html_void.contains(&tag) {
1517 closer = vec!['/', '>'];
1518 } else {
1519 closer = vec!['<', '/'];
1520 closer.extend(&tag_vec);
1521 closer.push('>');
1522 }
1523 }
1524 HtmlType::Comment => {
1525 opener = vec!['<', '!', '-', '-'];
1527 closer = vec!['-', '-', '>'];
1528 tag = "custom_comment".to_string();
1529 }
1530 HtmlType::CData => {
1531 opener = vec!['<', '!', '[', 'C', 'D', 'A', 'T', 'A', '['];
1533 closer = vec![']', ']', '>'];
1534 tag = "custom_cdata".to_string();
1535 }
1536 }
1537
1538 Some((tag, opener, closer))
1539 }
1540
1541 fn block_process_table_alignment(&self, line: &str) -> HashMap<i32, String> {
1542 let mut alignment: HashMap<i32, String> = HashMap::new();
1543
1544 let mut last_character: Option<char> = None;
1545 let mut start = false;
1546 let mut end = false;
1547 let mut counter = 0;
1548 for character in line.trim_end_matches('|').chars() {
1549 match character {
1550 '|' => {
1551 if last_character.is_none() {
1553 last_character = Some(character);
1555 continue;
1556 }
1557
1558 counter += 1;
1559
1560 if start && end {
1561 alignment.insert(counter, "center".to_string());
1562 } else if start {
1563 alignment.insert(counter, "left".to_string());
1564 } else if end {
1565 alignment.insert(counter, "right".to_string());
1566 } else {
1567 }
1569
1570 start = false;
1572 end = false;
1573 }
1574 ':' => {
1575 if let Some(last) = last_character {
1580 if last == ' ' || last == '|' {
1581 start = true;
1582 }
1583 if last == '-' {
1584 end = true;
1585 }
1586 }
1587 }
1588 _ => {
1589 }
1591 }
1592
1593 last_character = Some(character);
1594 }
1595
1596 counter += 1;
1597 if start && end {
1599 alignment.insert(counter, "center".to_string());
1600 } else if start {
1601 alignment.insert(counter, "left".to_string());
1602 } else if end {
1603 alignment.insert(counter, "right".to_string());
1604 } else {
1605 }
1607
1608 alignment
1609 }
1610
1611 fn block_merge(&self, nodes: Vec<NodeData>, mut depth: usize, slides: bool) -> String {
1612 let mut result: Vec<String> = vec![];
1613
1614 let mut slide_number = 0;
1615
1616 if slides {
1617 result.push(format!("{:indent$}<section class='slide' id='{}' style='display:none'>", "", slide_number, indent = (depth * self.indentation)));
1619
1620 depth += 1;
1621 }
1622
1623 for node in nodes {
1624 let tag = node.tag.as_str();
1625 let mut slide = false;
1626 let (opening, content, closing) = match tag {
1627 "html" => {
1629 if let Some(text) = node.text {
1630 result.push(text);
1631 }
1632 (None, None, None)
1633 }
1634 "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => {
1637 if let Some(text) = node.text {
1638 let processed = self.inline_process(text, 0);
1639
1640 let formatted = format!("<{}>{}</{}>", tag, processed, tag);
1642
1643 (Some(formatted), None, None)
1644 } else {
1645 (None, None, None)
1646 }
1647 }
1648
1649 "p" => {
1650 if let Some(text) = node.text {
1651 let processed = self.inline_process(text, depth + 1);
1652
1653 let open = format!("<{}>", tag);
1655 let close = format!("</{}>", tag);
1656
1657 (Some(open), Some(processed), Some(close))
1658 } else {
1659 (None, None, None)
1660 }
1661 }
1662
1663 "hr" => {
1664 if !slides {
1665 (Some("<hr />".to_string()), None, None)
1667 } else {
1668 slide_number += 1;
1670 slide = true;
1671 (Some("</section>".to_string()), None, Some(format!("<section class='slide' id='{}' style='display:none'>", slide_number)))
1672 }
1673 }
1674
1675 "pre" => {
1676 if let Some(contents) = node.text {
1677
1678 let formatted = if let Some(language) = node.misc {
1680 format!("<pre><code class=\"language-{}\">\n{}\n</code></pre>", language, contents)
1682 } else {
1683 format!("<pre><code>\n{}\n</code></pre>", contents)
1684 };
1685
1686 result.push(formatted);
1687 }
1688 (None, None, None)
1689 }
1690
1691 "table" | "thead" | "tbody" | "tr" |
1697 "blockquote" |
1699 "ul" | "ol"
1701 => {
1702 let processed = self.block_merge(node.contents, depth + 1, false);
1703 let open = format!("<{}>", tag);
1704 let close = format!("</{}>", tag);
1705 (Some(open), Some(processed), Some(close))
1706 }
1707
1708 "th" | "td" => {
1709 let processed = if let Some(text) = node.text {
1710 self.inline_process(text, depth + 1)
1711 } else {
1712 "".to_string()
1713 };
1714
1715 let open = if let Some(alignment) = node.misc {
1716 format!("<{} style=\"text-align: {};\">", tag, alignment)
1719 } else {
1720 format!("<{}>", tag)
1721 };
1722
1723 let close = format!("</{}>", tag);
1724
1725 (Some(open), Some(processed), Some(close))
1726 }
1727
1728 "li" => {
1729 let processed = if !node.contents.is_empty() {
1732 self.block_merge(node.contents, depth + 1, false)
1733 } else if let Some(text) = node.text {
1734 self.inline_process(text, depth + 1)
1735 } else {
1736 "".to_string()
1737 };
1738
1739
1740 let open = if let Some(value) = node.misc {
1741 format!("<{} value=\"{}\">", tag, value)
1742 } else {
1743 format!("<{}>", tag)
1744 };
1745
1746 let close = format!("</{}>", tag);
1747
1748 (Some(open), Some(processed), Some(close))
1749 }
1750
1751 _ => {
1752 (None, None, None)
1753 }
1754 };
1755
1756 if slide {
1757 depth -= 1;
1758 }
1759 if let Some(x_opening) = opening {
1760 let indented = format!("{:indent$}{}", "", x_opening, indent = (depth * self.indentation));
1761 result.push(indented)
1762 }
1763 if let Some(x_content) = content {
1764 let indented = format!("{:indent$}{}", "", x_content, indent = 0);
1765 result.push(indented)
1767 }
1768 if let Some(x_closing) = closing {
1769 let indented = format!("{:indent$}{}", "", x_closing, indent = (depth * self.indentation));
1770 result.push(indented)
1771 }
1772 if slide {
1773 depth += 1;
1774 }
1775 }
1776
1777 if slides {
1778 result.push(format!("{:indent$}</section>", "", indent = ((depth - 1) * self.indentation)));
1780 }
1781
1782 result.join("\n")
1783 }
1784
1785 fn inline_process(&self, input: String, depth: usize) -> String {
1786 let nodes = self.inline_spans(input);
1788
1789 let merged = Converter::inline_merge(nodes);
1791
1792 let line_breaks_added = merged.replace(" \n", "\n<br />\n");
1795
1796 self.inline_indention(line_breaks_added, depth)
1798 }
1799
1800 fn inline_spans(&self, input: String) -> Vec<NodeData> {
1802 let mut node_data: Vec<NodeData> = vec![];
1846
1847 let mut characters: Vec<char> = input.chars().collect();
1848
1849 let mut index = 0;
1850
1851 let mut span_closer: Vec<char> = vec![];
1852 let mut span_start = 0;
1853
1854 let mut span_active = false;
1863 let mut span_finished = false;
1864 let mut span_reset = false;
1865
1866 let mut span_text = true; let mut span_normal = false;
1869 let mut span_html = false;
1870
1871 let mut span_normal_type: Option<String> = None;
1872
1873 let mut html_tag_vec = vec![];
1874 let mut html_tag_complete = false;
1875
1876 loop {
1877 if index >= characters.len() {
1878 break;
1879 }
1880
1881 let character = characters[index];
1882 let character_next = if (index + 1) < characters.len() { Some(characters[index + 1]) } else { None };
1883 let character_last_escape = if (index as isize - 1) > 0 { characters[index - 1] == '\\' } else { false };
1884
1885 if !span_active {
1887 match character {
1888 '/' => {
1890 if let Some(next) = character_next {
1892 if next == '/' {
1893 if character_last_escape {
1894 characters.remove(index - 1);
1896
1897 index -= 1;
1900 } else {
1901 span_normal_type = Some("em".to_string());
1902 span_closer = vec!['/', '/'];
1903 span_active = true;
1904 span_normal = true;
1905 span_text = false;
1906 }
1907 }
1908 }
1909 }
1910 '*' => {
1912 if let Some(next) = character_next {
1914 if next == '*' {
1915 if character_last_escape {
1916 characters.remove(index - 1);
1918
1919 index -= 1;
1922 } else {
1923 span_normal_type = Some("strong".to_string());
1924 span_closer = vec!['*', '*'];
1925 span_active = true;
1926 span_normal = true;
1927 span_text = false;
1928 }
1929 }
1930 }
1931 }
1932 '_' => {
1934 if let Some(next) = character_next {
1936 if next == '_' {
1937 if character_last_escape {
1938 characters.remove(index - 1);
1940
1941 index -= 1;
1944 } else {
1945 span_normal_type = Some("u".to_string());
1946 span_closer = vec!['_', '_'];
1947 span_active = true;
1948 span_normal = true;
1949 span_text = false;
1950 }
1951 }
1952 }
1953 }
1954 '~' => {
1956 if let Some(next) = character_next {
1958 if next == '~' {
1959 if character_last_escape {
1960 characters.remove(index - 1);
1962
1963 index -= 1;
1966 } else {
1967 span_normal_type = Some("s".to_string());
1968 span_closer = vec!['~', '~'];
1969 span_active = true;
1970 span_normal = true;
1971 span_text = false;
1972 }
1973 }
1974 }
1975 }
1976 '`' => {
1978 if let Some(next) = character_next {
1980 if next == '`' {
1981 if character_last_escape {
1982 characters.remove(index - 1);
1984
1985 index -= 1;
1988 } else {
1989 span_normal_type = Some("code".to_string());
1990 span_closer = vec!['`', '`'];
1991 span_active = true;
1992 span_normal = true;
1993 span_text = false;
1994 }
1995 }
1996 }
1997 }
1998 '>' => {
1999 if let Some(next) = character_next {
2002 if next == '!' {
2003 if character_last_escape {
2004 characters.remove(index - 1);
2006
2007 index -= 1;
2010 } else {
2011 span_normal_type = Some("spoiler".to_string());
2012 span_closer = vec!['!', '<'];
2013 span_active = true;
2014 span_normal = true;
2015 span_text = false;
2016 }
2017 }
2018 }
2019 }
2020 '<' => {
2021 span_active = true;
2023 span_html = true;
2024 span_text = false;
2025
2026 if let Some(next) = character_next {
2027 match next {
2029 '<' => {
2030 span_html = false;
2032
2033 span_normal_type = Some("autolink".to_string());
2035 span_closer = vec!['>', '>'];
2036 span_normal = true;
2037 }
2038 ' ' | '\t' | '/' | '>' => {
2039 span_active = false;
2041 span_html = false;
2042 span_text = true;
2043 }
2044 _ => {}
2045 }
2046 }
2047
2048 if span_active && character_last_escape {
2049 span_active = false;
2051 span_html = false;
2052 span_text = true;
2053 span_normal_type = None;
2054 span_closer = vec![];
2055 span_normal = false;
2056
2057 characters.remove(index - 1);
2058
2059 index -= 1;
2062 }
2063 }
2064 '[' => {
2065 if character_last_escape {
2066 characters.remove(index - 1);
2068
2069 index -= 1;
2072 } else if let Some((node, offset)) = self.inline_spans_links(&characters[index..], "a".to_string()) {
2073 let text: String = characters[span_start..index].iter().collect();
2075 node_data.push(NodeData {
2076 tag: "text".to_string(),
2077 misc: None,
2078 text: Some(text),
2079 contents: vec![],
2080 });
2081
2082 node_data.push(node);
2084 index += offset;
2085
2086 span_start = index + 1;
2088 }
2089 }
2090 '!' => {
2091 if let Some(next) = character_next {
2092 if next == '[' {
2093 if character_last_escape {
2094 characters.remove(index - 1);
2096
2097 index -= 1;
2100 } else {
2101 if let Some((node, offset)) = self.inline_spans_links(&characters[(index + 1)..], "img".to_string()) {
2103 let text: String = characters[span_start..index].iter().collect();
2105 node_data.push(NodeData {
2106 tag: "text".to_string(),
2107 misc: None,
2108 text: Some(text),
2109 contents: vec![],
2110 });
2111
2112 node_data.push(node);
2114 index += offset + 1;
2115
2116 span_start = index + 2;
2118 }
2119 }
2120 }
2121 }
2122 }
2123
2124 _ => {
2125 }
2127 }
2128
2129 if span_active {
2130 let text: String = characters[span_start..index].iter().collect();
2133 node_data.push(NodeData {
2134 tag: "text".to_string(),
2135 misc: None,
2136 text: Some(text),
2137 contents: vec![],
2138 });
2139
2140 span_start = index;
2141
2142 if span_normal {
2144 index += 2;
2145 } else {
2146 index += 1;
2147 }
2148
2149 continue;
2150 }
2151 }
2152
2153 if span_active {
2155 if span_normal {
2156 if span_closer[0] == character {
2159 if character_last_escape {
2160 characters.remove(index - 1);
2161
2162 index -= 1;
2165 } else if let Some(next) = character_next {
2166 if span_closer[1] == next {
2167 span_finished = true;
2169 }
2170 }
2171 }
2172 }
2173
2174 if span_html {
2175 if !html_tag_complete {
2177 match character {
2178 ' ' | '\t' => {
2179 html_tag_complete = true;
2180 }
2181 '/' | '>' => {
2182 if characters[index - 1] == '\\' {
2184 html_tag_vec.pop();
2187 html_tag_vec.push(character);
2188 } else {
2189 html_tag_complete = true;
2190 }
2191 }
2192 _ => {
2193 html_tag_vec.push(character);
2195 }
2196 }
2197 }
2198 if html_tag_complete {
2199 if span_closer.is_empty() {
2200 let tag: String = html_tag_vec.iter().collect();
2202
2203 if self.html_void.contains(&tag) {
2204 span_closer = vec!['/', '>'];
2205 } else {
2206 span_closer = vec!['<', '/'];
2207 span_closer.extend(&html_tag_vec);
2208 span_closer.push('>');
2209 }
2210 span_closer.reverse();
2212 }
2213 let mut matches = true;
2218 for (position, closing_char) in span_closer.iter().enumerate() {
2219 let index_new: isize = (index as isize) - (position as isize);
2220 if index_new < 0 {
2221 span_reset = true;
2222 matches = false;
2223 break;
2224 }
2225
2226 if &characters[index_new as usize] != closing_char {
2227 matches = false;
2228 break;
2229 }
2230 }
2231 if matches {
2232 span_finished = true;
2233 }
2234 }
2235 }
2236 }
2237
2238 if character_next.is_none() && !span_finished {
2239 if span_text {
2240 span_finished = true;
2241 } else {
2242 span_reset = true;
2243 }
2244 }
2245
2246 if span_reset {
2247 if characters[span_start] == '<' {
2249 let replacement_char = ['&', 'l', 't', ';'];
2250 characters.splice(span_start..=span_start, replacement_char.iter().cloned());
2251 }
2252
2253 span_closer = vec![];
2254
2255 span_active = false;
2256 span_finished = false;
2257 span_reset = false; span_normal = false;
2260 span_html = false;
2261 span_text = true;
2262
2263 span_normal_type = None;
2264
2265 html_tag_vec = vec![];
2266 html_tag_complete = false;
2267
2268 index = span_start;
2270 index += 1;
2272
2273 continue;
2274 }
2275
2276 if span_finished {
2277 if span_text {
2278 let text: String = characters[span_start..=index].iter().collect();
2279 node_data.push(NodeData {
2280 tag: "text".to_string(),
2281 misc: None,
2282 text: Some(text),
2283 contents: vec![],
2284 });
2285
2286 span_start = index;
2287
2288 if span_normal {
2290 index += 1;
2291 } else {
2292 index += 0;
2293 }
2294 }
2295
2296 if span_normal {
2297 let text_raw: String = characters[(span_start + 2)..=(index - 1)].iter().collect();
2299
2300 let span_type = if let Some(span) = span_normal_type { span } else { "text".to_string() };
2301
2302 let (text, contents) = match span_type.as_str() {
2303 "text" => (Some(text_raw), vec![]),
2304 "code" => (Some(text_raw), vec![]),
2305 "autolink" => (Some(text_raw), vec![]),
2306 _ => (None, self.inline_spans(text_raw)),
2307 };
2308
2309 node_data.push(NodeData {
2310 tag: span_type,
2311 misc: None,
2312 text,
2313 contents,
2314 });
2315
2316 span_start = index + 2;
2318
2319 index += 1;
2322 }
2323
2324 if span_html {
2325 let text: String = characters[span_start..=index].iter().collect();
2326
2327 node_data.push(NodeData {
2328 tag: "html".to_string(),
2329 misc: None,
2330 text: Some(text),
2331 contents: vec![],
2332 });
2333
2334 span_start = index + 1;
2335 }
2336
2337 span_closer = vec![];
2338
2339 span_active = false;
2340 span_finished = false;
2341
2342 span_normal = false;
2343 span_html = false;
2344 span_text = true;
2345
2346 span_normal_type = None;
2347
2348 html_tag_vec = vec![];
2349 html_tag_complete = false;
2350 }
2351
2352 index += 1;
2353 }
2354
2355 if characters.is_empty() || span_start < (characters.len() - 1) {
2357 let text: String = characters[span_start..].iter().collect();
2358 node_data.push(NodeData {
2359 tag: "text".to_string(),
2360 misc: None,
2361 text: Some(text),
2362 contents: vec![],
2363 });
2364 }
2365
2366 node_data
2367 }
2368
2369 fn inline_spans_links(&self, characters: &[char], tag: String) -> Option<(NodeData, usize)> {
2370 let references = self.references.clone();
2371
2372 let mut index = 1;
2374
2375 let mut block_first = vec![];
2376
2377 let mut link_type = None;
2378 loop {
2379 if index >= characters.len() {
2380 break;
2381 }
2382
2383 let character = characters[index];
2384 let character_next = if (index + 1) < characters.len() { Some(characters[index + 1]) } else { None };
2385 let character_last_escape = if (index as isize - 1) > 0 { characters[index - 1] == '\\' } else { false };
2386
2387 match character {
2388 ']' => {
2389 if character_last_escape {
2390 block_first.pop();
2391 block_first.push(character)
2392 } else {
2393 if let Some(next) = character_next {
2395 match next {
2396 '(' => {
2397 link_type = Some("classic".to_string());
2398 index += 2;
2400 }
2401 '[' => {
2402 link_type = Some("reference_named".to_string());
2403 index += 2;
2404 }
2405 _ => {
2406 link_type = Some("reference_anon".to_string());
2408 }
2409 }
2410 } else {
2411 link_type = Some("reference_anon".to_string());
2413 }
2414
2415 break;
2417 }
2418 }
2419
2420 _ => block_first.push(character),
2421 }
2422
2423 index += 1;
2424 }
2425
2426 if let Some(type_) = &link_type {
2428 if let "reference_anon" = type_.as_str() {
2430 let reference: String = block_first.iter().collect();
2431
2432 return match references.get(&reference) {
2433 Some(link_data) => {
2434 let url = link_data.url.clone();
2435
2436 let contents = if let Some(title) = &link_data.title {
2437 self.inline_spans(title.clone())
2439 } else {
2440 self.inline_spans(reference)
2442 };
2443
2444 let node = NodeData {
2445 tag,
2446 misc: link_data.title.clone(),
2447 text: Some(url),
2449 contents,
2450 };
2451
2452 Some((node, index))
2453 }
2454 None => {
2455 None
2457 }
2458 };
2459 }
2460 }
2461
2462 let mut block_second = vec![];
2463 let mut angle_brackets = false;
2464 loop {
2465 if index >= characters.len() {
2466 break;
2467 }
2468
2469 let character = characters[index];
2470 let character_last_escape = if (index as isize - 1) > 0 { characters[index - 1] == '\\' } else { false };
2471
2472 if let Some(type_) = &link_type {
2475 match type_.as_str() {
2476 "classic" => {
2477 match character {
2478 '<' => {
2479 if block_second.is_empty() {
2480 angle_brackets = true;
2481 }
2482
2483 block_second.push(character)
2485 }
2486 ' ' | '\t' => {
2487 if angle_brackets {
2488 block_second.push(character)
2489 } else if block_second.is_empty() {
2490 } else {
2492 index += 1;
2494 break;
2496 }
2497 }
2498 ')' => {
2499 if angle_brackets {
2500 block_second.push(character)
2501 } else if character_last_escape {
2502 block_second.pop();
2503 block_second.push(character);
2504 } else {
2505 let text_raw: String = block_first.iter().collect();
2509 let contents = self.inline_spans(text_raw);
2510 let url: String = block_second.iter().collect();
2511 let node = NodeData {
2512 tag,
2513 misc: None,
2514 text: Some(url),
2515 contents,
2516 };
2517
2518 return Some((node, index));
2519 }
2520 }
2521 '\n' => {
2522 return None;
2525 }
2526 '>' => {
2527 if angle_brackets {
2528 if character_last_escape {
2531 block_second.pop();
2532 block_second.push(character);
2533 } else {
2534 block_second.remove(0);
2536 break;
2537 }
2538 } else {
2539 block_second.push(character)
2540 }
2541 }
2542 _ => block_second.push(character),
2543 }
2544 }
2545 "reference_named" => match character {
2546 ']' => {
2547 if character_last_escape {
2548 block_second.pop();
2549 block_second.push(character);
2550 } else {
2551 break;
2552 }
2553 }
2554 _ => block_second.push(character),
2555 },
2556 _ => {
2557 }
2559 }
2560 }
2561
2562 index += 1;
2563 }
2564
2565 if let Some(type_) = &link_type {
2567 if let "reference_named" = type_.as_str() {
2569 let reference: String = block_second.iter().collect();
2570
2571 return match references.get(&reference) {
2572 Some(link_data) => {
2573 let text_raw: String = block_first.iter().collect();
2574 let contents = self.inline_spans(text_raw);
2575
2576 let node = NodeData {
2577 tag,
2578 misc: link_data.title.clone(),
2579 text: Some(link_data.url.clone()),
2581 contents,
2582 };
2583
2584 Some((node, index))
2585 }
2586 None => {
2587 None
2589 }
2590 };
2591 }
2592 }
2593
2594 let mut block_third = vec![];
2606 let mut delimiter = (' ', false, false);
2607 loop {
2608 if index >= characters.len() {
2609 break;
2610 }
2611
2612 let character = characters[index];
2613 let character_last_escape = if (index as isize - 1) > 0 { characters[index - 1] == '\\' } else { false };
2614
2615 match character {
2616 '\'' | '"' => {
2618 if !delimiter.1 {
2619 delimiter = (character, true, false);
2621 } else if delimiter.0 == character {
2622 if character_last_escape {
2624 block_third.pop();
2625 block_third.push(character);
2626 } else {
2627 delimiter = (character, true, true);
2629 }
2630 } else {
2631 block_third.push(character);
2632 }
2633 }
2634
2635 ')' => {
2637 if delimiter.1 && !delimiter.2 {
2638 block_third.push(character);
2640 } else {
2641 if character_last_escape {
2643 block_third.pop();
2644 block_third.push(character);
2645 } else {
2646 let text_raw: String = block_first.iter().collect();
2650 let contents = self.inline_spans(text_raw);
2651
2652 let url: String = block_second.iter().collect();
2653
2654 let title = if block_third.is_empty() {
2655 None
2656 } else {
2657 let title_tmp: String = block_third.iter().collect();
2658 Some(title_tmp)
2659 };
2660
2661 let node = NodeData {
2662 tag,
2663 misc: title,
2664 text: Some(url),
2665 contents,
2666 };
2667
2668 return Some((node, index));
2669 }
2670 }
2671 }
2672
2673 ' ' | '\t' => {
2674 if delimiter.1 && !delimiter.2 {
2675 block_third.push(character);
2677 } else {
2678 }
2680 }
2681 _ => {
2682 block_third.push(character);
2683 }
2684 }
2685
2686 index += 1;
2687 }
2688
2689 None
2692 }
2693
2694 fn inline_merge(nodes: Vec<NodeData>) -> String {
2695 let mut result: Vec<String> = vec![];
2696
2697 for node in nodes {
2698 let tag = node.tag.as_str();
2699 let (opening, content, closing) = match tag {
2700 "html" | "text" => {
2702 if let Some(text) = node.text {
2703 result.push(text);
2704 }
2705 (None, None, None)
2706 }
2707 "autolink" => {
2709 if let Some(text) = node.text {
2710 let (cleaned, mail, tel) = if text.starts_with("mailto:") {
2711 (text.replacen("mailto:", "", 1), true, false)
2712 } else if text.starts_with("MAILTO:") {
2713 (text.replacen("MAILTO:", "", 1), true, false)
2714 } else if text.starts_with("tel:") {
2715 (text.replacen("tel:", "", 1), false, true)
2716 } else if text.starts_with("TEL:") {
2717 (text.replacen("TEL:", "", 1), false, true)
2718 } else {
2719 (text, false, false)
2720 };
2721
2722 let formatted = if cleaned.contains('@') || mail {
2724 format!("<a target='_blank' rel='noopener noreferrer' href='mailto:{}'>{}</a>", &cleaned, &cleaned)
2725 } else if tel {
2726 format!("<a target='_blank' rel='noopener noreferrer' href='tel:{}'>{}</a>", &cleaned, &cleaned)
2727 } else {
2728 format!("<a target='_blank' rel='noopener noreferrer' href='{}'>{}</a>", &cleaned, &cleaned)
2729 };
2730
2731 result.push(formatted);
2732 }
2733 (None, None, None)
2734 }
2735
2736 "code" => {
2738 if let Some(text) = node.text {
2739 let open = format!("<{}>", tag);
2741 let close = format!("</{}>", tag);
2742
2743 let cleaned = text.replace('<', "<").replace('>', ">");
2744
2745 (Some(open), Some(cleaned), Some(close))
2746 } else {
2747 (None, None, None)
2748 }
2749 }
2750
2751 "em" | "strong" | "u" | "s" => {
2753 let processed = Converter::inline_merge(node.contents);
2754 let open = format!("<{}>", tag);
2755 let close = format!("</{}>", tag);
2756 (Some(open), Some(processed), Some(close))
2757 }
2758
2759 "spoiler" => {
2761 let processed = Converter::inline_merge(node.contents);
2762 let open = "<span class='md-spoiler'>".to_string();
2763 let close = "</span>".to_string();
2764 (Some(open), Some(processed), Some(close))
2765 }
2766
2767 "a" => {
2768 let url = node.text.unwrap_or_default();
2769 let open = if let Some(title) = node.misc {
2770 format!("<a target='_blank' rel='noopener noreferrer' href='{}' title='{}'>", url, title)
2771 } else {
2772 format!("<a target='_blank' rel='noopener noreferrer' href='{}'>", url)
2773 };
2774
2775 let processed = Converter::inline_merge(node.contents);
2776 let close = "</a>".to_string();
2777
2778 (Some(open), Some(processed), Some(close))
2779 }
2780
2781 "img" => {
2782 let url = node.text.unwrap_or_default();
2783 let alt = Converter::inline_merge(node.contents);
2784 let open = if let Some(title) = node.misc {
2785 format!("<img src='{}' alt='{}' title='{}' />", url, alt, title)
2786 } else {
2787 format!("<img src='{}' alt='{}' />", url, alt)
2788 };
2789
2790 (Some(open), None, None)
2791 }
2792
2793 _ => (None, None, None),
2794 };
2795
2796 if let Some(data) = opening {
2797 result.push(data)
2798 }
2799 if let Some(data) = content {
2800 result.push(data)
2801 }
2802 if let Some(data) = closing {
2803 result.push(data)
2804 }
2805 }
2806
2807 result.join("")
2808 }
2809
2810 fn inline_indention(&self, input: String, depth: usize) -> String {
2811 let mut result: Vec<String> = vec![];
2812
2813 let lines = input.split('\n').collect::<Vec<_>>();
2814 for line in lines {
2815 let indented = format!("{:indent$}{}", "", line, indent = (depth * self.indentation));
2816 result.push(indented);
2817 }
2818
2819 result.join("\n")
2820 }
2821}
2822fn compare_lists(list_type: LineType, li_type: LineType) -> bool {
2825 if let LineType::OL(_, _) = list_type {
2826 if let LineType::OL(_, _) = li_type {
2827 return true;
2828 }
2829 }
2830
2831 if let LineType::UL(_, _) = list_type {
2832 if let LineType::UL(_, _) = li_type {
2833 return true;
2834 }
2835 }
2836
2837 false
2838}