markx/
inline.rs

1#![allow(unused)]
2
3#[derive(Debug, Clone)]
4pub struct Link {
5    link_text: String,
6    href: String,
7    title: String,
8    kind: u8, // 0: normal link; 1: img
9}
10
11#[derive(Debug, Clone)]
12pub enum Span {
13    Code(String),
14    Math(String),
15    Strong(String),
16    Link(Link),
17    Text(String),
18}
19
20impl Span {
21    pub fn tohtml(&self) -> String {
22        match &*self {
23            Self::Code(text) => {
24                format!("<code>{}</code>", text)
25            }
26            Self::Math(text) => {
27                format!("\\({}\\)", text)
28            }
29            Self::Strong(text) => {
30                format!("<strong>{}</strong>", text)
31            }
32            Self::Link(link) => {
33                match link.kind {
34                    1 => {
35                        // image
36                        format!(
37                            "<img src=\"{}\" alt=\"{}\" title=\"{}\">",
38                            link.href, link.link_text, link.title
39                        )
40                    }
41                    _ => {
42                        format!(
43                            "<a href=\"{}\" title=\"{}\">{}</a>",
44                            link.href, link.title, link.link_text,
45                        )
46                    }
47                }
48            }
49            Self::Text(text) => {
50                format!("{}", text)
51            }
52            _ => {
53                format!("")
54            }
55        }
56    }
57}
58
59pub fn parse_inline(input: &String) -> Vec<Span> {
60    let mut spans = Vec::new();
61
62    // Inline spans can not be nested.
63    let mut open_tag = vec![];
64    let mut indexs: Vec<usize> = Vec::new();
65    let mut tags: Vec<char> = Vec::new();
66    for (_index, _tag) in input.char_indices() {
67        match _tag {
68            '`' => match open_tag.last() {
69                Some('`') => {
70                    open_tag.pop();
71                    indexs.push(_index);
72                    tags.push(_tag);
73                }
74                None => {
75                    open_tag.push('`');
76                    indexs.push(_index);
77                    tags.push(_tag);
78                }
79                _ => {}
80            },
81            '$' => match open_tag.last() {
82                Some('$') => {
83                    open_tag.pop();
84                    indexs.push(_index);
85                    tags.push(_tag);
86                }
87                None => {
88                    open_tag.push('$');
89                    indexs.push(_index);
90                    tags.push(_tag);
91                }
92                _ => {}
93            },
94            '!' => match open_tag.last() {
95                None => {
96                    indexs.push(_index);
97                    tags.push(_tag);
98                }
99                _ => {}
100            },
101            '*' => match open_tag.last() {
102                Some('*') => {
103                    open_tag.pop();
104                    indexs.push(_index);
105                    tags.push(_tag);
106                }
107                None => {
108                    open_tag.push('*');
109                    indexs.push(_index);
110                    tags.push(_tag);
111                }
112                _ => {}
113            },
114            '[' => match open_tag.last() {
115                None => {
116                    open_tag.push(_tag);
117                    indexs.push(_index);
118                    tags.push(_tag);
119                }
120                _ => {}
121            },
122            ']' => match open_tag.last() {
123                Some('[') => {
124                    open_tag.pop();
125                    indexs.push(_index);
126                    tags.push(_tag);
127                }
128                _ => {}
129            },
130            '(' => match open_tag.last() {
131                None => {
132                    open_tag.push(_tag);
133                    indexs.push(_index);
134                    tags.push(_tag);
135                }
136                _ => {}
137            },
138            ')' => match open_tag.last() {
139                Some('(') => {
140                    open_tag.pop();
141                    indexs.push(_index);
142                    tags.push(_tag);
143                }
144                _ => {}
145            },
146            '<' => match open_tag.last() {
147                None => {
148                    open_tag.push(_tag);
149                    indexs.push(_index);
150                    tags.push(_tag);
151                }
152                _ => {}
153            },
154            '>' => match open_tag.last() {
155                Some('<') => {
156                    open_tag.pop();
157                    indexs.push(_index);
158                    tags.push(_tag);
159                }
160                _ => {}
161            },
162            '"' => match open_tag.last() {
163                Some('(') => {
164                    open_tag.push(_tag);
165                    indexs.push(_index);
166                    tags.push(_tag);
167                }
168                Some('"') => {
169                    open_tag.pop();
170                    indexs.push(_index);
171                    tags.push(_tag);
172                }
173                _ => {}
174            },
175            _ => {}
176        }
177    }
178
179    let mut idx = 0usize;
180    let mut last = 0usize;
181    let max_len = indexs.len();
182    while idx < max_len {
183        if tags[idx] == '$' && idx + 1 < max_len && tags[idx + 1] == '$' {
184            // store the text
185            if indexs[idx] - last > 0 {
186                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
187            }
188            // store the text
189            spans.push(Span::Math(String::from(
190                &input[indexs[idx] + 1..indexs[idx + 1]],
191            )));
192            // next
193            last = indexs[idx + 1] + 1;
194            idx += 2;
195            continue;
196        } else if tags[idx] == '`' && idx + 1 < max_len && tags[idx + 1] == '`' {
197            // store the text
198            if indexs[idx] - last > 0 {
199                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
200            }
201            // store code
202            spans.push(Span::Code(String::from(
203                &input[indexs[idx] + 1..indexs[idx + 1]],
204            )));
205            // next
206            last = indexs[idx + 1] + 1;
207            idx += 2;
208            continue;
209        } else if tags[idx] == '<' && idx + 1 < max_len && tags[idx + 1] == '>' {
210            // store the text
211            if indexs[idx] - last > 0 {
212                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
213            }
214            // store link
215            let text = String::from(&input[indexs[idx] + 1..indexs[idx + 1]]);
216            spans.push(Span::Link(Link {
217                link_text: text.to_owned(),
218                href: text.to_owned(),
219                title: text.to_owned(),
220                kind: 0,
221            }));
222            // next
223            last = indexs[idx + 1] + 1;
224            idx += 2;
225            continue;
226        } else if tags[idx] == '*'
227            && idx + 3 < max_len
228            && tags[idx + 1] == '*'
229            && tags[idx + 2] == '*'
230            && tags[idx + 3] == '*'
231            && indexs[idx + 1] == indexs[idx] + 1
232            && indexs[idx + 3] == indexs[idx + 2] + 1
233        {
234            // store the text
235            if indexs[idx] - last > 0 {
236                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
237            }
238            // store Strong
239            spans.push(Span::Strong(String::from(
240                &input[indexs[idx + 1] + 1..indexs[idx + 2]],
241            )));
242            // next
243            last = indexs[idx + 3] + 1;
244            idx += 4;
245            continue;
246        } else if tags[idx] == '['
247            && idx + 3 < max_len
248            && tags[idx + 1] == ']'
249            && tags[idx + 2] == '('
250            && tags[idx + 3] == ')'
251            && indexs[idx + 2] == indexs[idx + 1] + 1
252        {
253            // store the text
254            if indexs[idx] - last > 0 {
255                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
256            }
257            // store link
258            spans.push(Span::Link(Link {
259                link_text: String::from(&input[indexs[idx] + 1..indexs[idx + 1]]),
260                href: String::from(&input[indexs[idx + 2] + 1..indexs[idx + 3]]),
261                title: String::from(""),
262                kind: 0,
263            }));
264            // next
265            last = indexs[idx + 3] + 1;
266            idx += 4;
267            continue;
268        } else if tags[idx] == '['
269            && idx + 3 < max_len
270            && tags[idx + 1] == ']'
271            && tags[idx + 2] == '('
272            && tags[idx + 3] == '\"'
273            && tags[idx + 4] == '\"'
274            && tags[idx + 5] == ')'
275            && indexs[idx + 2] == indexs[idx + 1] + 1
276        {
277            // store the text
278            if indexs[idx] - last > 0 {
279                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
280            }
281            // store link
282            spans.push(Span::Link(Link {
283                link_text: String::from(&input[indexs[idx] + 1..indexs[idx + 1]]),
284                href: String::from(&input[indexs[idx + 2] + 1..indexs[idx + 3]]),
285                title: String::from(&input[indexs[idx + 3] + 1..indexs[idx + 4]]),
286                kind: 0,
287            }));
288            // next
289            last = indexs[idx + 5] + 1;
290            idx += 6;
291            continue;
292        } else if tags[idx] == '!'
293            && idx + 4 < max_len
294            && tags[idx + 1] == '['
295            && tags[idx + 2] == ']'
296            && tags[idx + 3] == '('
297            && tags[idx + 4] == ')'
298            && indexs[idx + 1] == indexs[idx] + 1
299            && indexs[idx + 3] == indexs[idx + 2] + 1
300        {
301            // store the text
302            if indexs[idx] - last > 0 {
303                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
304            }
305            // store img
306            spans.push(Span::Link(Link {
307                link_text: String::from(&input[indexs[idx + 1] + 1..indexs[idx + 2]]),
308                href: String::from(&input[indexs[idx + 3] + 1..indexs[idx + 4]]),
309                title: String::from(""),
310                kind: 1,
311            }));
312            // next
313            last = indexs[idx + 4] + 1;
314            idx += 5;
315            continue;
316        } else if tags[idx] == '!'
317            && idx + 4 < max_len
318            && tags[idx + 1] == '['
319            && tags[idx + 2] == ']'
320            && tags[idx + 3] == '('
321            && tags[idx + 4] == '\"'
322            && tags[idx + 5] == '\"'
323            && tags[idx + 6] == ')'
324            && indexs[idx + 1] == indexs[idx] + 1
325            && indexs[idx + 3] == indexs[idx + 2] + 1
326        {
327            // store the text
328            if indexs[idx] - last > 0 {
329                spans.push(Span::Text(String::from(&input[last..indexs[idx]])));
330            }
331            // store img
332            spans.push(Span::Link(Link {
333                link_text: String::from(&input[indexs[idx + 1] + 1..indexs[idx + 2]]),
334                href: String::from(&input[indexs[idx + 3] + 1..indexs[idx + 4]]),
335                title: String::from(&input[indexs[idx + 4] + 1..indexs[idx + 5]]),
336                kind: 1,
337            }));
338            // next
339            last = indexs[idx + 6] + 1;
340            idx += 7;
341            continue;
342        } else {
343            // skip
344            idx += 1;
345        }
346    }
347
348    let the_rest = String::from(&input[last..]);
349    if the_rest.len() > 0 {
350        spans.push(Span::Text(the_rest));
351    }
352
353    // return result
354    spans
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use std::fs;
361
362    #[test]
363    #[ignore]
364    fn test_inline() {
365        // run with cargo test -- --nocapture
366        let input = String::from("`hello`  :+1: hello **我是粗体**,`markx` is good,哈哈。$\\pi$,[链接](/hello/word \"hello\") ![图片](我是图片链接 \"标题\"),解析我");
367
368        let spans = parse_inline(&input);
369        println!("{:?}", spans);
370    }
371
372    #[test]
373    #[ignore]
374    fn test_inline2() {
375        let input = String::from("`int(input(\"请输入你猜的数字:\"))`:`input()` 表示输出一段提示语句,然后以字符串形式接收你输入的内容,`int()` 表示把输入的内容转化为整数。");
376        let spans = parse_inline(&input);
377        println!("{:?}", spans);
378    }
379    #[test]
380    fn span_tohtml() {
381        let c = Span::Code("fn".to_string());
382        let m = Span::Math("f(x)".to_string());
383        let t = Span::Text("f(x)".to_string());
384        let l = Span::Link(Link {
385            link_text: "More".to_string(),
386            href: "/more".to_string(),
387            title: "More".to_string(),
388            kind: 0,
389        });
390        let i = Span::Link(Link {
391            link_text: "上海鲜花港 - 郁金香".to_string(),
392            href: "https://www.w3school.com.cn/i/eg_tulip.jpg".to_string(),
393            title: "上海鲜花港 - 郁金香".to_string(),
394            kind: 1,
395        });
396        println!("{}", c.tohtml());
397        println!("{}", m.tohtml());
398        println!("{}", t.tohtml());
399        println!("{}", l.tohtml());
400        println!("{}", i.tohtml());
401    }
402}