parse_hyperlinks/iterator.rs
1//! Module providing an iterator over the hyperlinks found in the input text. Consult the
2//! documentation of `parser::parse::take_link()` to see a list of supported markup languages. The
3//! iterator resolves link references.
4
5use crate::parser::parse::take_link;
6use crate::parser::Link;
7use std::borrow::Cow;
8use std::collections::HashMap;
9use std::mem::swap;
10
11#[derive(Debug, PartialEq)]
12/// A collection of `Link` objects grouped by link type.
13struct MarkupLinkCollection<'a> {
14 /// Vector storing all `Link::Text2Dest`, `Link::Text2Label` and `Link::TextLabel2Dest` links.
15 /// The tuple is defined as follows: `(link_first_byte_offset, link_len, Link)`.
16 text2dest_label: Vec<(usize, usize, Link<'a>)>,
17 /// Vector for `Link::Label2Label` links.
18 label2label: Vec<(Cow<'a, str>, Cow<'a, str>)>,
19 /// Vector for `Link::Label2Dest` and `Link::TextLabel2Dest` links.
20 /// The `HashMap`'s key is the `link_label` of the link, the value its
21 /// `(link_destination, link_title)`.
22 label2dest: HashMap<Cow<'a, str>, (Cow<'a, str>, Cow<'a, str>)>,
23}
24
25impl<'a> MarkupLinkCollection<'a> {
26 fn new() -> Self {
27 Self {
28 text2dest_label: Vec::new(),
29 label2label: Vec::new(),
30 label2dest: HashMap::new(),
31 }
32 }
33
34 /// Reads through the whole `Self::input` and extracts all hyperlinks and
35 /// stores them in `Self::HyperlinkCollection` according to their category.
36 /// One type is treated specially: `Link::TextLabel2Dest` are cloned and one
37 /// copy is stored in `HyperlinkCollection::Text2Dest` and the other copy is
38 /// stored in `HyperlinkCollection::Label2Dest`.
39 #[inline]
40 fn from(input: &'a str, render_label2dest: bool) -> Self {
41 let mut i = input;
42 let mut hc = MarkupLinkCollection::new();
43 let mut anonymous_text2label_counter = 0;
44 let mut anonymous_label2x_counter = 0;
45 // This index refers to `input`.
46 let mut input_idx = 0;
47
48 while let Ok((j, (skipped, res))) = take_link(i) {
49 match res {
50 // `Text2Dest` is stored without modification in `hc.text2dest_label`.
51 l if matches!(l, Link::Text2Dest { .. })
52 || matches!(l, Link::Image2Dest { .. })
53 || matches!(l, Link::Image { .. }) =>
54 {
55 let link_offset = input_idx + skipped.len();
56 let link_len = i.len() - j.len() - skipped.len();
57 hc.text2dest_label.push((link_offset, link_len, l));
58 }
59
60 // `Text2label` is stored without modification in `hc.text2dest_label`.
61 Link::Text2Label(text, mut label) => {
62 if label == "_" {
63 anonymous_text2label_counter += 1;
64 label = Cow::Owned(format!("_{}", anonymous_text2label_counter));
65 }
66 let link_offset = input_idx + skipped.len();
67 let link_len = i.len() - j.len() - skipped.len();
68 hc.text2dest_label
69 .push((link_offset, link_len, Link::Text2Label(text, label)))
70 }
71 //`TextLabel2Dest` are cloned and stored in `hc.text2dest_label` as `Text2Dest`
72 // and in `hc.label2dest` (repacked in a `HashMap`).
73 Link::TextLabel2Dest(tl, d, t) => {
74 let link_offset = input_idx + skipped.len();
75 let link_len = i.len() - j.len() - skipped.len();
76 hc.text2dest_label.push((
77 link_offset,
78 link_len,
79 Link::Text2Dest(tl.clone(), d.clone(), t.clone()),
80 ));
81
82 // Silently ignore when overwriting a key that exists already.
83 hc.label2dest.insert(tl, (d, t));
84 }
85
86 // `Label2Label` are unpacked and stored in `hc.label2label`.
87 Link::Label2Label(mut from, to) => {
88 if from == "_" {
89 anonymous_label2x_counter += 1;
90 from = Cow::Owned(format!("_{}", anonymous_label2x_counter));
91 }
92 hc.label2label.push((from, to));
93 }
94
95 // `Label2Dest` are unpacked and stored as `HashMap` in `hc.label2dest`:
96 Link::Label2Dest(mut l, d, t) => {
97 if l == "_" {
98 anonymous_label2x_counter += 1;
99 l = Cow::Owned(format!("_{}", anonymous_label2x_counter));
100 }
101 // Some want to have link reference definitions clickable
102 // too. Strictly speaking they are not links, this is why
103 // this is optional.
104 if render_label2dest {
105 let link_offset = input_idx + skipped.len();
106 let link_len = i.len() - j.len() - skipped.len();
107 hc.text2dest_label.push((
108 link_offset,
109 link_len,
110 Link::Text2Dest(
111 Cow::from(&input[link_offset..link_offset + link_len]),
112 d.clone(),
113 t.clone(),
114 ),
115 ));
116 };
117
118 // Silently ignore when overwriting a key that exists already.
119 hc.label2dest.insert(l, (d, t));
120 }
121 _ => unreachable!(),
122 };
123
124 // Prepare next iteration.
125 input_idx += i.len() - j.len();
126 i = j;
127 }
128
129 hc
130 }
131
132 /// Takes one by one, one item from `HyperlinkCollection::label2label` and
133 /// searches the corresponding label in `HyperlinkCollection::label2dest`.
134 /// When found, add a new item to `HyperlinkCollection::label2dest`. Continue
135 /// until `HyperlinkCollection::label2label` is empty or no more corresponding
136 /// items can be associated.
137 #[inline]
138 fn resolve_label2label_references(&mut self) {
139 let mut nb_no_match = 0;
140 let mut idx = 0;
141 while !self.label2label.is_empty() && nb_no_match < self.label2label.len() {
142 let (key_alias, key) = &self.label2label[idx];
143 // This makes sure, that we advance in the loop.
144 if let Some(value) = self.label2dest.get(key) {
145 let found_new_key = key_alias.clone();
146 let found_value = value.clone();
147 // We advance in the loop, because we remove the element `idx` points to.
148 self.label2label.remove(idx);
149 self.label2dest.insert(found_new_key, found_value);
150 // We give up only, after a complete round without match.
151 nb_no_match = 0;
152 } else {
153 // We advance in the loop because we increment `idx`.
154 idx += 1;
155 nb_no_match += 1;
156 };
157 // Make sure, that `idx` always points to some valid index.
158 if idx >= self.label2label.len() {
159 idx = 0;
160 }
161 }
162 }
163
164 /// Takes one by one, one item of type `Link::Text2Label` from
165 /// `HyperlinkCollection::text2text_label` and searches the corresponding
166 /// label in `HyperlinkCollection::label2dest`. The associated
167 /// `Link::Text2Label` and `Link::Label2Dest` are resolved into a new
168 /// `Link::Text2Dest` object. Then the item form the fist list is replaced by
169 /// this new object. After this operation the
170 /// `HyperlinkCollection::text2text_label` list contains only
171 /// `Link::Text2Dest` and `Link::Image2Dest` objects (and some unresolvable
172 /// `Link::Text2Label` objects).
173 #[inline]
174 fn resolve_text2label_references(&mut self) {
175 let mut idx = 0;
176 while idx < self.text2dest_label.len() {
177 // If we can not resolve the label, we just skip it.
178 if let (input_offset, len, Link::Text2Label(text, label)) = &self.text2dest_label[idx] {
179 if let Some((dest, title)) = &self.label2dest.get(label) {
180 let new_link = if text.is_empty() {
181 (
182 *input_offset,
183 *len,
184 Link::Text2Dest(dest.clone(), dest.clone(), title.clone()),
185 )
186 } else {
187 (
188 *input_offset,
189 *len,
190 Link::Text2Dest(text.clone(), dest.clone(), title.clone()),
191 )
192 };
193 self.text2dest_label[idx] = new_link;
194 };
195 };
196 // We advance in the loop because we increment `idx`.
197 idx += 1;
198 }
199 }
200}
201
202#[derive(Debug, PartialEq)]
203/// The interator's state.
204enum Status<'a> {
205 /// Initial state. Iterator is not started.
206 Init,
207 /// So far only `Text2Dest` and `Image2Dest` links are coming, no links
208 /// need to be resolved.
209 DirectSearch(&'a str),
210 /// As soon as the first reference appears, the remaining text is read and
211 /// all links are resolved. The tuple describes a resolved link. The first
212 /// integer index points to the first byte of the link in `self.input`, the
213 /// second interger is the lenght of the link in `input` bytes. Then follows
214 /// the `Link`.
215 ResolvedLinks(Vec<(usize, usize, Link<'a>)>),
216 /// All links have been returned. From now on only `None` are returned.
217 End,
218}
219
220#[derive(Debug, PartialEq)]
221/// Iterator over all the hyperlinks in the `input` text.
222/// This struct holds the iterator's state and an advancing pointer into the
223/// `input` text. The iterator's `next()` method returns a tuple with a tuples
224/// inside: `Some(((input_split), Link))`.
225///
226/// Each tuple has the following parts:
227/// * `input_split = (skipped_characters, consumed_characters, remaining_characters)`
228/// * `Link` is `Link::Text2Dest`, `Link::Image2Dest`, or `Link::Image`.
229///
230/// # Input split
231///
232/// ```
233/// use parse_hyperlinks::iterator::MarkupLink;
234/// use std::borrow::Cow;
235///
236/// let i = "abc[text0](dest0)efg[text1](dest1)hij<foo@dest2>klm";
237///
238/// let mut iter = MarkupLink::new(i, false);
239/// assert_eq!(iter.next().unwrap().0, ("abc",
240/// "[text0](dest0)",
241/// "efg[text1](dest1)hij<foo@dest2>klm"));
242/// assert_eq!(iter.next().unwrap().0, ("efg",
243/// "[text1](dest1)",
244/// "hij<foo@dest2>klm"));
245/// assert_eq!(iter.next().unwrap().0, ("hij", "<foo@dest2>", "klm"));
246/// assert_eq!(iter.next(), None);
247/// ```
248/// # Link content
249/// ## Markdown
250/// ```
251/// use parse_hyperlinks::parser::Link;
252/// use parse_hyperlinks::iterator::MarkupLink;
253/// use std::borrow::Cow;
254///
255/// let i = r#"abc[text0](dest0 "title0")abc
256/// abc[text1][label1]abc
257/// abc[text2](dest2 "title2")
258/// [text3]: dest3 "title3"
259/// [label1]: dest1 "title1"
260/// abc[text3]abc
261/// "#;
262///
263/// let mut iter = MarkupLink::new(i, false);
264/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text0"), Cow::from("dest0"), Cow::from("title0")));
265/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text1"), Cow::from("dest1"), Cow::from("title1")));
266/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text2"), Cow::from("dest2"), Cow::from("title2")));
267/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text3"), Cow::from("dest3"), Cow::from("title3")));
268/// assert_eq!(iter.next(), None);
269/// ```
270///
271/// ## reStructuredText
272///
273/// ```
274/// use parse_hyperlinks::parser::Link;
275/// use parse_hyperlinks::iterator::MarkupLink;
276/// use std::borrow::Cow;
277///
278/// let i = r#"
279/// abc `text1 <label1_>`_abc
280/// abc text2_ abc
281/// abc text3__ abc
282/// abc text_label4_ abc
283/// abc text5__ abc
284/// .. _label1: dest1
285/// .. _text2: dest2
286/// .. __: dest3
287/// __ dest5
288/// "#;
289///
290/// let mut iter = MarkupLink::new(i, false);
291/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text1"), Cow::from("dest1"), Cow::from("")));
292/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text2"), Cow::from("dest2"), Cow::from("")));
293/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text3"), Cow::from("dest3"), Cow::from("")));
294/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text5"), Cow::from("dest5"), Cow::from("")));
295/// assert_eq!(iter.next(), None);
296///
297/// ```
298/// ## Asciidoc
299///
300/// ```
301/// use parse_hyperlinks::parser::Link;
302/// use parse_hyperlinks::iterator::MarkupLink;
303/// use std::borrow::Cow;
304///
305/// let i = r#"abc
306/// abc https://dest0[text0]abc
307/// abc link:https://dest1[text1]abc
308/// abc {label2}[text2]abc
309/// abc {label3}abc
310/// :label2: https://dest2
311/// :label3: https://dest3
312/// "#;
313///
314/// let mut iter = MarkupLink::new(i, false);
315/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text0"), Cow::from("https://dest0"), Cow::from("")));
316/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text1"), Cow::from("https://dest1"), Cow::from("")));
317/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text2"), Cow::from("https://dest2"), Cow::from("")));
318/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("https://dest3"), Cow::from("https://dest3"), Cow::from("")));
319/// assert_eq!(iter.next(), None);
320/// ```
321///
322/// # HTML
323///
324/// ```
325/// use parse_hyperlinks::parser::Link;
326/// use parse_hyperlinks::iterator::MarkupLink;
327/// use std::borrow::Cow;
328///
329/// let i = r#"abc<a href="dest1" title="title1">text1</a>abc
330/// abc<a href="dest2" title="title2">text2</a>abc
331/// abc<a href="dest3" title="title3">cde<img alt="alt3" src="src3"/>fgh</a>abc
332/// "#;
333///
334/// let mut iter = MarkupLink::new(i, false);
335/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text1"), Cow::from("dest1"), Cow::from("title1")));
336/// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text2"), Cow::from("dest2"), Cow::from("title2")));
337/// assert_eq!(iter.next().unwrap().1, Link::Image2Dest(Cow::from("cde"), Cow::from("alt3"), Cow::from("src3"), Cow::from("fgh"), Cow::from("dest3"), Cow::from("title3")));
338/// assert_eq!(iter.next(), None);
339/// ```
340pub struct MarkupLink<'a> {
341 /// The remaining text input.
342 input: &'a str,
343 /// Status of the `MarkupLink` state machine.
344 status: Status<'a>,
345 /// Index where the last output started.
346 last_output_offset: usize,
347 /// Length of the last output.
348 last_output_len: usize,
349 /// By default, `Label2Dest` link reference definitions are not rendered. If
350 /// `render_label` is true, then `Label2Dest` is rendered like an inline
351 /// link: with the full link reference definition's source as _link text_ and
352 /// the definition's destination as _link destination_.
353 render_label: bool,
354}
355
356/// Constructor for the `MarkupLink` struct.
357impl<'a> MarkupLink<'a> {
358 /// Constructor for the iterator. `input` is the text with hyperlinks to be
359 /// extracted.
360 ///
361 /// # Optional rendering of Label2Dest link reference definitions
362 ///
363 /// By default `Label2Dest` link reference definitions are not rendered:
364 ///
365 /// ```
366 /// use parse_hyperlinks::parser::Link;
367 /// use parse_hyperlinks::iterator::MarkupLink;
368 /// use std::borrow::Cow;
369 ///
370 /// let i = r#"abc[text1][label1]abc
371 /// [label1]: dest1 "title1"
372 /// "#;
373 ///
374 /// let mut iter = MarkupLink::new(i, false);
375 /// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text1"), Cow::from("dest1"), Cow::from("title1")));
376 /// assert_eq!(iter.next(), None);
377 /// ```
378 ///
379 /// If the internal variable `render_label` is true, then `Label2Dest` link
380 /// reference definitions are rendered like inline links: the full
381 /// `Label2Dest` link reference definition's source will appear as _link
382 /// text_ and its destination as _link destination_. To enable this feature,
383 /// construct `MarkupLink` with the second positional parameter set to `true`,
384 /// e.g. `MarkupLink::new(i, true)`.
385 ///
386 /// ```
387 /// use parse_hyperlinks::parser::Link;
388 /// use parse_hyperlinks::iterator::MarkupLink;
389 /// use std::borrow::Cow;
390 ///
391 /// let i = r#"abc[text1][label1]abc
392 /// [label1]: dest1 "title1"
393 /// "#;
394 ///
395 /// let mut iter = MarkupLink::new(i, true);
396 /// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("text1"), Cow::from("dest1"), Cow::from("title1")));
397 /// assert_eq!(iter.next().unwrap().1, Link::Text2Dest(Cow::from("[label1]: dest1 \"title1\""), Cow::from("dest1"), Cow::from("title1")));
398 /// assert_eq!(iter.next(), None);
399 /// ```
400 ///
401 #[inline]
402 pub fn new(input: &'a str, render_label: bool) -> Self {
403 Self {
404 input,
405 status: Status::Init,
406 last_output_offset: 0,
407 last_output_len: 0,
408 render_label,
409 }
410 }
411}
412
413/// Iterator over the hyperlinks (with markup) in the `input`-text.
414/// The iterator's `next()` method returns a tuple with 2 tuples inside:
415/// * `Some(((input_split)(link_content)))`
416///
417/// Each tuple has the following parts:
418/// * `input_split = (skipped_characters, consumed_characters, remaining_characters)`
419/// * `link_content = (link_text, link_destination, link_title)`
420///
421impl<'a> Iterator for MarkupLink<'a> {
422 #[allow(clippy::type_complexity)]
423 type Item = ((&'a str, &'a str, &'a str), Link<'a>);
424 /// The iterator operates in 2 modes:
425 /// 1. `Status::DirectSearch`: This is the starting state. So far
426 /// the iterator has only encountered inline links so far.
427 /// Nothing needs to be resolved and the next method can
428 /// output the link immediately.
429 /// The `next()` method outputs directly the result from the parser
430 /// `parser::take_link()`.
431 /// 2. `Status::ResolvedLinks`: as soon as the iterator encounters
432 /// some reference link, e.g. `Text2label`, `Label2Dest` or
433 /// `Label2Label` link, it switches into `Status::ResolvedLinks` mode.
434 /// The transition happens as follows:
435 /// 1. The `next()` method consumes all the remaining `input` and
436 /// calls the `populate_collection()`,
437 /// `resolve_label2label_references()` and
438 /// `resolve_text2label_references()` methods.
439 /// From now on,
440 /// 2. the `next()` method outputs and deletes
441 /// `HyperlinkCollection::Dest2Text_label[0]`.
442 /// Not resolved `Text2Label` are ignored.
443 fn next(&mut self) -> Option<Self::Item> {
444 let mut output = None;
445 let mut status = Status::Init;
446 swap(&mut status, &mut self.status);
447
448 // Advance state machine.
449 let mut again = true;
450 while again {
451 status = match status {
452 // Advance state machine and match one more time.
453 Status::Init => Status::DirectSearch(self.input),
454
455 Status::DirectSearch(input) => {
456 // We stay in direct mode.
457 match take_link(input) {
458 Ok((remaining_input, (skipped, link)))
459 if matches!(
460 link,
461 Link::Text2Dest(_, _, _)
462 | Link::Image2Dest(_, _, _, _, _, _)
463 | Link::Image(_, _)
464 ) =>
465 {
466 let consumed =
467 &input[skipped.len()..input.len() - remaining_input.len()];
468 // Assinig output.
469 output = Some(((skipped, consumed, remaining_input), link));
470 debug_assert_eq!(input, {
471 let mut s = "".to_string();
472 s.push_str(skipped);
473 s.push_str(consumed);
474 s.push_str(remaining_input);
475 s
476 });
477 // Same state, we leave the loop.
478 again = false;
479 Status::DirectSearch(remaining_input)
480 }
481 _ => {
482 // We switch to resolving mode.
483 self.input = input;
484 let mut hc = MarkupLinkCollection::from(input, self.render_label);
485 hc.resolve_label2label_references();
486 hc.resolve_text2label_references();
487 let mut resolved_links = Vec::new();
488 swap(&mut hc.text2dest_label, &mut resolved_links);
489
490 // Advance state machine and match one more time.
491 Status::ResolvedLinks(resolved_links)
492 }
493 }
494 }
495
496 Status::ResolvedLinks(mut resolved_links) => {
497 while !resolved_links.is_empty() {
498 // if let (input_offset, len, Link::Text2Dest(te, de, ti)) =
499 // resolved_links.remove(0)
500 // Ok((remaining_input, (skipped, link)))
501 match resolved_links.remove(0) {
502 (input_offset, len, link)
503 if matches!(
504 link,
505 Link::Text2Dest(_, _, _)
506 | Link::Image2Dest(_, _, _, _, _, _)
507 | Link::Image(_, _)
508 ) =>
509 {
510 let skipped = &self.input[(self.last_output_offset
511 + self.last_output_len)
512 ..input_offset];
513 let consumed = &self.input[input_offset..input_offset + len];
514 let remaining_input = &self.input[input_offset + len..];
515 // Assign output.
516 output = Some(((skipped, consumed, remaining_input), link));
517 debug_assert_eq!(self.input, {
518 let mut s = (self.input
519 [..self.last_output_offset + self.last_output_len])
520 .to_string();
521 s.push_str(skipped);
522 s.push_str(consumed);
523 s.push_str(remaining_input);
524 s
525 });
526 self.last_output_offset = input_offset;
527 self.last_output_len = len;
528 break;
529 }
530 _ => {}
531 };
532 }
533 again = false;
534 if output.is_some() {
535 Status::ResolvedLinks(resolved_links)
536 } else {
537 Status::End
538 }
539 }
540
541 Status::End => {
542 again = false;
543 output = None;
544 Status::End
545 }
546 }
547 }
548 swap(&mut status, &mut self.status);
549 output
550 }
551}
552
553/// Recognizes hyperlinks in all supported markup languages
554/// and returns the first hyperlink found as
555/// `Some(Link::Text2Dest` or `Some(Link::Image2Dest)`.
556/// Returns `None` if no hyperlink is found.
557///
558/// This function resolves _link references_.
559/// ```
560/// use parse_hyperlinks::parser::Link;
561/// use parse_hyperlinks::iterator::find_first;
562/// use std::borrow::Cow;
563///
564/// let i = r#"abc[t][u]abc
565/// [u]: v "w"
566/// abc"#;
567///
568/// let r = find_first(i);
569/// assert_eq!(r, Some(Link::Text2Dest(Cow::from("t"), Cow::from("v"), Cow::from("w"))));
570/// ```
571pub fn find_first(i: &str) -> Option<Link> {
572 MarkupLink::new(i, false).next().map(|(_, l)| l)
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578
579 #[test]
580 fn test_populate_collection() {
581 let i = r#"[md label1]: md_destination1 "md title1"
582abc [md text2](md_destination2 "md title2")[md text3]: abc[md text4]: abc
583 [md label5]: md_destination5 "md title5"
584abc `rst text1 <rst_destination1>`__abc
585abc `rst text2 <rst_label2_>`_ .. _norst: no .. _norst: no
586.. _rst label3: rst_destination3
587 .. _rst label4: rst_d
588 estination4
589__ rst_label5_
590__ rst_label6_
591abc `rst text5`__abc
592abc `rst text6`__abc
593abc `rst text_label7 <rst_destination7>`_abc
594abc<scheme:md_dest8>abc
595abc<local@md_email8>abc
596abc[http://text9](<http://destination9> "title9")
597abc[defghi](doc10.md "title10")jkl
598"#;
599
600 let hc = MarkupLinkCollection::from(i, false);
601
602 let expected = r#"[
603 (
604 45,
605 39,
606 Text2Dest(
607 "md text2",
608 "md_destination2",
609 "md title2",
610 ),
611 ),
612 (
613 84,
614 10,
615 Text2Label(
616 "md text3",
617 "md text3",
618 ),
619 ),
620 (
621 99,
622 10,
623 Text2Label(
624 "md text4",
625 "md text4",
626 ),
627 ),
628 (
629 163,
630 32,
631 Text2Dest(
632 "rst text1",
633 "rst_destination1",
634 "",
635 ),
636 ),
637 (
638 203,
639 54,
640 Text2Label(
641 "rst text2",
642 "rst_label2",
643 ),
644 ),
645 (
646 366,
647 13,
648 Text2Label(
649 "rst text5",
650 "_1",
651 ),
652 ),
653 (
654 387,
655 13,
656 Text2Label(
657 "rst text6",
658 "_2",
659 ),
660 ),
661 (
662 408,
663 37,
664 Text2Dest(
665 "rst text_label7",
666 "rst_destination7",
667 "",
668 ),
669 ),
670 (
671 452,
672 17,
673 Text2Dest(
674 "scheme:md_dest8",
675 "scheme:md_dest8",
676 "",
677 ),
678 ),
679 (
680 500,
681 46,
682 Text2Dest(
683 "http://text9",
684 "http://destination9",
685 "title9",
686 ),
687 ),
688 (
689 550,
690 47,
691 Image2Dest(
692 "def",
693 "alt10",
694 "img10.png",
695 "ghi",
696 "doc10.md",
697 "title10",
698 ),
699 ),
700]"#;
701 let res = format!("{:#?}", hc.text2dest_label);
702 eprintln!("{}", res);
703 assert_eq!(res, expected);
704 assert_eq!(hc.text2dest_label.len(), 11);
705
706 let expected = r#"[
707 (
708 "_1",
709 "rst_label5",
710 ),
711 (
712 "_2",
713 "rst_label6",
714 ),
715]"#;
716
717 let res = format!("{:#?}", hc.label2label);
718 assert_eq!(hc.label2label.len(), 2);
719 assert_eq!(res, expected);
720
721 //eprintln!("{:#?}", c.label2dest);
722 assert_eq!(hc.label2dest.len(), 5);
723 assert_eq!(
724 *hc.label2dest.get("md label1").unwrap(),
725 (Cow::from("md_destination1"), Cow::from("md title1"))
726 );
727 assert_eq!(
728 *hc.label2dest.get("md label5").unwrap(),
729 (Cow::from("md_destination5"), Cow::from("md title5"))
730 );
731 assert_eq!(
732 *hc.label2dest.get("rst label3").unwrap(),
733 (Cow::from("rst_destination3"), Cow::from(""))
734 );
735 assert_eq!(
736 *hc.label2dest.get("rst label4").unwrap(),
737 (Cow::from("rst_destination4"), Cow::from(""))
738 );
739 assert_eq!(
740 *hc.label2dest.get("rst text_label7").unwrap(),
741 (Cow::from("rst_destination7"), Cow::from(""))
742 );
743 }
744
745 #[test]
746 fn test_resolve_label2label_references() {
747 let i = r#"label2_
748.. _label2: rst_destination2
749 .. _label5: label4_
750 .. _label1: nolabel_
751 .. _label4: label3_
752 .. _label3: label2_
753"#;
754
755 let mut hc = MarkupLinkCollection::from(i, false);
756 hc.resolve_label2label_references();
757 //eprintln!("{:#?}", hc);
758 assert_eq!(hc.label2label.len(), 1);
759 assert_eq!(
760 hc.label2label[0],
761 (Cow::from("label1"), Cow::from("nolabel"))
762 );
763
764 assert_eq!(hc.label2dest.len(), 4);
765 assert_eq!(
766 *hc.label2dest.get("label2").unwrap(),
767 (Cow::from("rst_destination2"), Cow::from(""))
768 );
769 assert_eq!(
770 *hc.label2dest.get("label3").unwrap(),
771 (Cow::from("rst_destination2"), Cow::from(""))
772 );
773 assert_eq!(
774 *hc.label2dest.get("label4").unwrap(),
775 (Cow::from("rst_destination2"), Cow::from(""))
776 );
777 assert_eq!(
778 *hc.label2dest.get("label5").unwrap(),
779 (Cow::from("rst_destination2"), Cow::from(""))
780 );
781 }
782
783 #[test]
784 fn test_resolve_text2label_references() {
785 let i = r#"abc[text1][label1]abc
786 abc [text2](destination2 "title2")
787 [label3]: destination3 "title3"
788 [label1]: destination1 "title1"
789 .. _label4: label3_
790 abc[label3]abc[label5]abc
791 label4_
792 "#;
793
794 let mut hc = MarkupLinkCollection::from(i, false);
795 //eprintln!("{:#?}", hc);
796 hc.resolve_label2label_references();
797 //eprintln!("{:#?}", hc);
798 hc.resolve_text2label_references();
799 //eprintln!("{:#?}", hc);
800
801 let expected = vec![
802 (
803 3,
804 15,
805 Link::Text2Dest(
806 Cow::from("text1"),
807 Cow::from("destination1"),
808 Cow::from("title1"),
809 ),
810 ),
811 (
812 34,
813 30,
814 Link::Text2Dest(
815 Cow::from("text2"),
816 Cow::from("destination2"),
817 Cow::from("title2"),
818 ),
819 ),
820 (
821 191,
822 8,
823 Link::Text2Dest(
824 Cow::from("label3"),
825 Cow::from("destination3"),
826 Cow::from("title3"),
827 ),
828 ),
829 (
830 202,
831 8,
832 Link::Text2Label(Cow::from("label5"), Cow::from("label5")),
833 ),
834 (
835 222,
836 7,
837 Link::Text2Dest(
838 Cow::from("label4"),
839 Cow::from("destination3"),
840 Cow::from("title3"),
841 ),
842 ),
843 ];
844 assert_eq!(hc.text2dest_label, expected);
845 }
846
847 #[test]
848 fn test_resolve_text2label_references2() {
849 let i = r#"
850abc `text1 <label1_>`_abc
851abc text_label2_ abc
852abc text3__ abc
853abc text_label4_ abc
854abc text5__ abc
855 .. _label1: destination1
856 .. _text_label2: destination2
857 .. __: destination3
858 __ destination5
859 "#;
860
861 let mut hc = MarkupLinkCollection::from(i, false);
862 //eprintln!("{:#?}", hc);
863 hc.resolve_label2label_references();
864 //eprintln!("{:#?}", hc);
865 hc.resolve_text2label_references();
866 //eprintln!("{:#?}", hc);
867
868 let expected = vec![
869 (
870 5,
871 18,
872 Link::Text2Dest(Cow::from("text1"), Cow::from("destination1"), Cow::from("")),
873 ),
874 (
875 31,
876 12,
877 Link::Text2Dest(
878 Cow::from("text_label2"),
879 Cow::from("destination2"),
880 Cow::from(""),
881 ),
882 ),
883 (
884 52,
885 7,
886 Link::Text2Dest(Cow::from("text3"), Cow::from("destination3"), Cow::from("")),
887 ),
888 (
889 68,
890 12,
891 Link::Text2Label(Cow::from("text_label4"), Cow::from("text_label4")),
892 ),
893 (
894 89,
895 7,
896 Link::Text2Dest(Cow::from("text5"), Cow::from("destination5"), Cow::from("")),
897 ),
898 ];
899 assert_eq!(hc.text2dest_label, expected);
900 }
901
902 #[test]
903 fn test_resolve_text2label_references3() {
904 let i = r#"
905abc[my homepage]abc
906abc
907
908[my homepage]: https://getreu.net
909abc"#;
910
911 let mut hc = MarkupLinkCollection::from(i, false);
912 eprintln!("{:#?}", hc);
913 hc.resolve_label2label_references();
914 //eprintln!("{:#?}", hc);
915 hc.resolve_text2label_references();
916 //eprintln!("{:#?}", hc);
917
918 let expected = vec![(
919 4,
920 13,
921 Link::Text2Dest(
922 Cow::from("my homepage"),
923 Cow::from("https://getreu.net"),
924 Cow::from(""),
925 ),
926 )];
927 assert_eq!(hc.text2dest_label, expected);
928 }
929
930 #[test]
931 fn test_next() {
932 let i = r#"abc[text0](destination0)abc
933abc[text1][label1]abc
934abc [text2](destination2 "title2")
935 [label3]: destination3 "title3"
936 [label1]: destination1 "title1"
937 .. _label4: label3_
938abc[label3]abc[label5]abc
939label4_
940abc[text5-1text5-2](dest5 "title5")abc
941 "#;
942
943 let mut iter = MarkupLink::new(i, false);
944
945 let expected =
946 Link::Text2Dest(Cow::from("text0"), Cow::from("destination0"), Cow::from(""));
947 let item = iter.next().unwrap();
948 //eprintln!("item: {:#?}", item);
949 assert_eq!(item.1, expected);
950
951 let expected = Link::Text2Dest(
952 Cow::from("text1"),
953 Cow::from("destination1"),
954 Cow::from("title1"),
955 );
956 let item = iter.next().unwrap();
957 //eprintln!("item: {:#?}", item);
958 assert_eq!(item.1, expected);
959
960 let expected = Link::Text2Dest(
961 Cow::from("text2"),
962 Cow::from("destination2"),
963 Cow::from("title2"),
964 );
965 let item = iter.next().unwrap();
966 //eprintln!("item: {:#?}", item);
967 assert_eq!(item.1, expected);
968
969 let expected = Link::Text2Dest(
970 Cow::from("label3"),
971 Cow::from("destination3"),
972 Cow::from("title3"),
973 );
974 let item = iter.next().unwrap();
975 //eprintln!("item: {:#?}", item);
976 assert_eq!(item.1, expected);
977
978 let expected = Link::Text2Dest(
979 Cow::from("label4"),
980 Cow::from("destination3"),
981 Cow::from("title3"),
982 );
983 let item = iter.next().unwrap();
984 //eprintln!("item: {:#?}", item);
985 assert_eq!(item.1, expected);
986
987 let expected = Link::Image2Dest(
988 Cow::from("text5-1"),
989 Cow::from("alt5"),
990 Cow::from("src5"),
991 Cow::from("text5-2"),
992 Cow::from("dest5"),
993 Cow::from("title5"),
994 );
995 let item = iter.next().unwrap();
996 //eprintln!("item: {:#?}", item);
997 assert_eq!(item.1, expected);
998
999 let expected = None;
1000 let item = iter.next();
1001 //eprintln!("item: {:#?}", item);
1002 assert_eq!(item, expected);
1003
1004 let expected = None;
1005 let item = iter.next();
1006 //eprintln!("item: {:#?}", item);
1007 assert_eq!(item, expected);
1008 }
1009
1010 #[test]
1011 fn test_next1() {
1012 let i = r#"Some autolink: <tpnote:locallink.md>,
1013more autolinks: <tpnote:20>, <getreu@web.de>,
1014boring link text: [http://domain.com](http://getreu.net)
1015[Jens Getreu's blog](https://blog.getreu.net "My blog")
1016Some more text."#;
1017 let mut iter = MarkupLink::new(i, false);
1018
1019 let expected = Link::Text2Dest(
1020 Cow::from("tpnote:locallink.md"),
1021 Cow::from("tpnote:locallink.md"),
1022 Cow::from(""),
1023 );
1024 let item = iter.next().unwrap();
1025 //eprintln!("item: {:#?}", item);
1026 assert_eq!(item.1, expected);
1027
1028 let expected = Link::Text2Dest(
1029 Cow::from("tpnote:20"),
1030 Cow::from("tpnote:20"),
1031 Cow::from(""),
1032 );
1033 let item = iter.next().unwrap();
1034 //eprintln!("item: {:#?}", item);
1035 assert_eq!(item.1, expected);
1036
1037 let expected = Link::Text2Dest(
1038 Cow::from("getreu@web.de"),
1039 Cow::from("mailto:getreu@web.de"),
1040 Cow::from(""),
1041 );
1042 let item = iter.next().unwrap();
1043 //eprintln!("item: {:#?}", item);
1044 assert_eq!(item.1, expected);
1045
1046 let expected = Link::Text2Dest(
1047 Cow::from("http://domain.com"),
1048 Cow::from("http://getreu.net"),
1049 Cow::from(""),
1050 );
1051 let item = iter.next().unwrap();
1052 //eprintln!("item: {:#?}", item);
1053 assert_eq!(item.1, expected);
1054 }
1055
1056 #[test]
1057 fn test_next2() {
1058 let i = r#"[te\_xt](ur\_l)[te_xt](ur_l)"#;
1059 let mut iter = MarkupLink::new(i, false);
1060
1061 let expected = Link::Text2Dest(Cow::from("te_xt"), Cow::from("ur_l"), Cow::from(""));
1062 let item = iter.next().unwrap();
1063 //eprintln!("item: {:#?}", item);
1064 assert_eq!(item.1, expected);
1065
1066 let item = iter.next().unwrap();
1067 //eprintln!("item: {:#?}", item);
1068 assert_eq!(item.1, expected);
1069 }
1070}