obsidian_cli_inspector/parser/
wikilink.rs1use super::{normalize_note_identifier, Link, LinkType};
2
3pub fn extract_wikilinks(content: &str) -> Vec<Link> {
4 let mut links = Vec::new();
5 let content_chars: Vec<char> = content.chars().collect();
6 let mut pos = 0;
7
8 while pos < content_chars.len() {
9 if pos + 1 < content_chars.len() {
11 let is_embed = content_chars[pos] == '!' && content_chars[pos + 1] == '[';
12 let is_wikilink = content_chars[pos] == '[' && content_chars[pos + 1] == '[';
13
14 if is_embed {
15 if let Some(link) = parse_wikilink(&content_chars, pos + 1, true) {
17 links.push(link);
18 }
19 pos += 1;
20 } else if is_wikilink {
21 if let Some(link) = parse_wikilink(&content_chars, pos, false) {
23 links.push(link);
24 }
25 pos += 1;
26 }
27 }
28 pos += 1;
29 }
30
31 links
32}
33
34pub fn parse_wikilink(chars: &[char], start: usize, is_embed: bool) -> Option<Link> {
35 if start + 3 >= chars.len() || chars[start] != '[' || chars[start + 1] != '[' {
36 return None;
37 }
38
39 let mut end = start + 2;
40 while end + 1 < chars.len() && !(chars[end] == ']' && chars[end + 1] == ']') {
41 end += 1;
42 }
43
44 if end + 1 >= chars.len() {
45 return None;
46 }
47
48 let content: String = chars[start + 2..end].iter().collect();
49 let (text, alias, heading_ref, block_ref) = parse_link_content(&content);
50 let text = normalize_note_identifier(&text);
51
52 if text.is_empty() {
53 return None;
54 }
55
56 Some(Link {
57 text,
58 alias,
59 heading_ref,
60 block_ref,
61 is_embed,
62 link_type: LinkType::Wiki,
63 })
64}
65
66fn parse_link_content(content: &str) -> (String, Option<String>, Option<String>, Option<String>) {
67 let mut alias = None;
68 let mut heading_ref = None;
69 let mut block_ref = None;
70
71 let text = if let Some(pipe_pos) = content.find('|') {
73 let t = content[..pipe_pos].trim().to_string();
74 alias = Some(content[pipe_pos + 1..].trim().to_string());
75 t
76 } else {
77 content.to_string()
78 };
79
80 let text = if let Some(hash_pos) = text.find('#') {
82 let heading = text[hash_pos + 1..].trim().to_string();
83 let t = text[..hash_pos].trim().to_string();
84
85 if let Some(stripped) = heading.strip_prefix('^') {
86 block_ref = Some(stripped.to_string());
87 } else {
88 heading_ref = Some(heading);
89 }
90 t
91 } else {
92 text
93 };
94
95 (text, alias, heading_ref, block_ref)
96}