1#![allow(dead_code)]
3#![allow(clippy::type_complexity)]
4
5use crate::parser::Link;
6use crate::parser::parse::LABEL_LEN_MAX;
7use crate::parser::percent_decode;
8use nom::Parser;
9use nom::branch::alt;
10use nom::bytes::complete::tag;
11use nom::bytes::complete::tag_no_case;
12use nom::character::complete::char;
13use nom::character::complete::space0;
14use nom::combinator::peek;
15use std::borrow::Cow;
16
17pub fn adoc_text2dest_link(i: &'_ str) -> nom::IResult<&'_ str, Link<'_>> {
20 let (i, (te, de, ti)) = adoc_text2dest(i)?;
21 Ok((i, Link::Text2Dest(te, de, ti)))
22}
23
24pub fn adoc_text2dest(
58 i: &'_ str,
59) -> nom::IResult<&'_ str, (Cow<'_, str>, Cow<'_, str>, Cow<'_, str>)> {
60 let (i, (link_destination, link_text)) = nom::sequence::preceded(
61 space0,
62 nom::sequence::pair(
63 adoc_inline_link_destination,
64 nom::combinator::opt(adoc_link_text),
65 ),
66 )
67 .parse(i)?;
68
69 let link_text = if let Some(lt) = link_text {
70 if lt.is_empty() {
71 link_destination.clone()
72 } else {
73 lt
74 }
75 } else {
76 link_destination.clone()
77 };
78
79 Ok((i, (link_text, link_destination, Cow::Borrowed(""))))
80}
81
82pub fn adoc_label2dest_link(i: &'_ str) -> nom::IResult<&'_ str, Link<'_>> {
85 let (i, (te, de, ti)) = adoc_label2dest(i)?;
86 Ok((i, Link::Label2Dest(te, de, ti)))
87}
88
89pub fn adoc_label2dest(
111 i: &'_ str,
112) -> nom::IResult<&'_ str, (Cow<'_, str>, Cow<'_, str>, Cow<'_, str>)> {
113 let (i, (link_label, link_destination)) = nom::sequence::preceded(
114 space0,
115 nom::sequence::pair(
116 adoc_parse_colon_reference,
117 nom::sequence::delimited(
118 nom::character::complete::space1,
119 adoc_link_reference_definition_destination,
120 nom::character::complete::space0,
121 ),
122 ),
123 )
124 .parse(i)?;
125
126 if !i.is_empty() {
127 let _ = peek::<&str, _>(nom::character::complete::newline).parse(i)?;
128 };
129
130 Ok((
131 i,
132 (
133 Cow::Borrowed(link_label),
134 link_destination,
135 Cow::Borrowed(""),
136 ),
137 ))
138}
139
140pub fn adoc_text2label_link(i: &'_ str) -> nom::IResult<&'_ str, Link<'_>> {
143 let (i, (te, la)) = adoc_text2label(i)?;
144 Ok((i, Link::Text2Label(te, la)))
145}
146
147pub fn adoc_text2label(i: &'_ str) -> nom::IResult<&'_ str, (Cow<'_, str>, Cow<'_, str>)> {
186 let (i, (link_label, link_text)) = alt((
187 nom::sequence::pair(adoc_parse_curly_bracket_reference, adoc_link_text),
188 nom::combinator::map(adoc_parse_curly_bracket_reference, |s| (s, Cow::from(""))),
189 ))
190 .parse(i)?;
191
192 if !i.is_empty() {
194 let _ = nom::character::complete::none_of("[{")(i)?;
195 }
196
197 Ok((i, (link_text, link_label)))
198}
199
200fn adoc_link_text(i: &'_ str) -> nom::IResult<&'_ str, Cow<'_, str>> {
205 nom::sequence::delimited(char('['), remove_newline_take_till(']'), char(']')).parse(i)
206}
207
208fn remove_newline_take_till<'a>(
214 pat: char,
215) -> impl Fn(&'a str) -> nom::IResult<&'a str, Cow<'a, str>> {
216 move |i: &str| {
217 let mut res = Cow::Borrowed("");
218 let mut j = i;
219 while !j.is_empty() {
220 let (k, s1) =
230 nom::bytes::complete::take_till(|c| c == pat || c == '\n' || c == '\\')(j)?;
231
232 res = match res {
234 Cow::Borrowed("") => Cow::Borrowed(s1),
235 Cow::Borrowed(res_str) => {
236 let mut strg = res_str.to_string();
237 strg.push_str(s1);
238 Cow::Owned(strg)
239 }
240 Cow::Owned(mut strg) => {
241 strg.push_str(s1);
242 Cow::Owned(strg)
243 }
244 };
245
246 if let (_, Some(c)) =
249 nom::combinator::opt(nom::combinator::peek(nom::character::complete::anychar))
250 .parse(k)?
251 {
252 let m = match c {
253 c if c == pat => return Ok((k, res)),
256 '\\' => {
258 let (l, _) = char('\\')(k)?;
260 let (l, c) = alt((char(pat), nom::combinator::success('\\'))).parse(l)?;
264
265 let mut strg = res.to_string();
267 strg.push(c);
268 res = Cow::Owned(strg);
270 l
272 }
273 '\n' => {
275 let (l, _) = char('\n')(k)?;
277 let (l, _) = space0(l)?;
278 let _ = nom::combinator::not(char('\n')).parse(l)?;
280
281 let mut strg = res.to_string();
283 strg.push(' ');
284 res = Cow::Owned(strg);
286 l
288 }
289 _ => unreachable!(),
290 };
291 j = m;
292 } else {
293 j = k;
295 }
296 }
297
298 Ok(("", res))
300 }
301}
302
303fn adoc_link_reference_definition_destination(i: &'_ str) -> nom::IResult<&'_ str, Cow<'_, str>> {
309 alt((
310 adoc_parse_http_link_destination,
311 adoc_parse_escaped_link_destination,
312 ))
313 .parse(i)
314}
315
316fn adoc_inline_link_destination(i: &'_ str) -> nom::IResult<&'_ str, Cow<'_, str>> {
321 alt((
322 adoc_parse_http_link_destination,
323 adoc_parse_literal_link_destination,
324 adoc_parse_escaped_link_destination,
325 ))
326 .parse(i)
327}
328
329fn adoc_parse_http_link_destination(i: &'_ str) -> nom::IResult<&'_ str, Cow<'_, str>> {
332 let (j, s) = nom::sequence::preceded(
333 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
334 nom::bytes::complete::take_till1(|c| c == '[' || c == ' ' || c == '\t' || c == '\n'),
335 )
336 .parse(i)?;
337 Ok((j, Cow::Borrowed(s)))
338}
339
340fn adoc_parse_escaped_link_destination(i: &'_ str) -> nom::IResult<&'_ str, Cow<'_, str>> {
344 nom::combinator::map_parser(
345 nom::sequence::preceded(
346 nom::sequence::pair(
347 tag("link:"),
348 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
349 ),
350 nom::bytes::complete::take_till1(|c| {
351 c == '[' || c == ' ' || c == '\t' || c == '\r' || c == '\n'
352 }),
353 ),
354 percent_decode,
355 )
356 .parse(i)
357}
358
359fn adoc_parse_literal_link_destination(i: &'_ str) -> nom::IResult<&'_ str, Cow<'_, str>> {
362 let (j, s) = nom::sequence::preceded(
363 tag("link:"),
364 nom::sequence::delimited(tag("++"), nom::bytes::complete::take_until("++"), tag("++")),
365 )
366 .parse(i)?;
367 Ok((j, Cow::Borrowed(s)))
368}
369
370fn adoc_parse_curly_bracket_reference(i: &'_ str) -> nom::IResult<&'_ str, Cow<'_, str>> {
375 nom::combinator::map(
376 nom::combinator::verify(
377 nom::sequence::delimited(
378 char('{'),
379 nom::bytes::complete::take_till1(|c| {
380 c == '}' || c == ' ' || c == '\t' || c == '\r'
381 }),
382 char('}'),
383 ),
384 |s: &str| s.len() <= LABEL_LEN_MAX,
385 ),
386 Cow::Borrowed,
387 )
388 .parse(i)
389}
390
391fn adoc_parse_colon_reference(i: &str) -> nom::IResult<&str, &str> {
398 nom::combinator::verify(
399 nom::sequence::delimited(
400 char(':'),
401 nom::bytes::complete::take_till1(|c| c == ':' || c == ' ' || c == '\t' || c == '\r'),
402 char(':'),
403 ),
404 |s: &str| s.len() <= LABEL_LEN_MAX,
405 )
406 .parse(i)
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use nom::error::ErrorKind;
413 use std::matches;
414
415 #[test]
416 fn test_adoc_text2dest() {
417 assert_eq!(
418 adoc_text2dest("http://getreu.net[]"),
419 Ok((
420 "",
421 (
422 Cow::from("http://getreu.net"),
423 Cow::from("http://getreu.net"),
424 Cow::from("")
425 )
426 ))
427 );
428
429 assert_eq!(
430 adoc_text2dest("http://getreu.net[]abc"),
431 Ok((
432 "abc",
433 (
434 Cow::from("http://getreu.net"),
435 Cow::from("http://getreu.net"),
436 Cow::from("")
437 )
438 ))
439 );
440
441 assert_eq!(
442 adoc_text2dest(" \t http://getreu.net[My blog]abc"),
443 Ok((
444 "abc",
445 (
446 Cow::from("My blog"),
447 Cow::from("http://getreu.net"),
448 Cow::from("")
449 )
450 ))
451 );
452
453 assert_eq!(
454 adoc_text2dest(r#"http://getreu.net[My blog[1\]]abc"#),
455 Ok((
456 "abc",
457 (
458 Cow::from("My blog[1]"),
459 Cow::from("http://getreu.net"),
460 Cow::from("")
461 )
462 ))
463 );
464
465 assert_eq!(
466 adoc_text2dest("http://getreu.net[My\n blog]abc"),
467 Ok((
468 "abc",
469 (
470 Cow::from("My blog"),
471 Cow::from("http://getreu.net"),
472 Cow::from("")
473 )
474 ))
475 );
476
477 assert_eq!(
478 adoc_text2dest("link:http://getreu.net[My blog]abc"),
479 Ok((
480 "abc",
481 (
482 Cow::from("My blog"),
483 Cow::from("http://getreu.net"),
484 Cow::from("")
485 )
486 ))
487 );
488
489 assert_eq!(
490 adoc_text2dest("link:https://getreu.net/?q=%5Ba%20b%5D[My blog]abc"),
491 Ok((
492 "abc",
493 (
494 Cow::from("My blog"),
495 Cow::from("https://getreu.net/?q=[a b]"),
496 Cow::from("")
497 )
498 ))
499 );
500
501 assert_eq!(
502 adoc_text2dest("link:++https://getreu.net/?q=[a b]++[My blog]abc"),
503 Ok((
504 "abc",
505 (
506 Cow::from("My blog"),
507 Cow::from("https://getreu.net/?q=[a b]"),
508 Cow::from("")
509 )
510 ))
511 );
512 }
513
514 #[test]
515 fn test_adoc_label2dest() {
516 assert_eq!(
517 adoc_label2dest(":label: http://getreu.net\n"),
518 Ok((
519 "\n",
520 (
521 Cow::from("label"),
522 Cow::from("http://getreu.net"),
523 Cow::from("")
524 )
525 ))
526 );
527
528 assert_eq!(
529 adoc_label2dest(" :label: \thttp://getreu.net \t "),
530 Ok((
531 "",
532 (
533 Cow::from("label"),
534 Cow::from("http://getreu.net"),
535 Cow::from("")
536 )
537 ))
538 );
539
540 assert_eq!(
541 adoc_label2dest(" :label: \thttp://getreu.net \t abc").unwrap_err(),
542 nom::Err::Error(nom::error::Error::new("abc", ErrorKind::Char))
543 );
544 }
545
546 #[test]
547 fn test_adoc_link_text() {
548 assert_eq!(adoc_link_text("[text]abc"), Ok(("abc", Cow::from("text"))));
549
550 assert_eq!(
551 adoc_link_text("[te\nxt]abc"),
552 Ok(("abc", Cow::from("te xt")))
553 );
554
555 assert_eq!(
556 adoc_link_text("[te\n\nxt]abc"),
557 Err(nom::Err::Error(nom::error::Error::new(
558 "\nxt]abc",
559 ErrorKind::Not
560 )))
561 );
562
563 assert_eq!(
564 adoc_link_text(r#"[text[i\]]abc"#),
565 Ok(("abc", Cow::from(r#"text[i]"#.to_string())))
566 );
567
568 assert_eq!(
569 adoc_link_text("[textabc"),
570 Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Char)))
571 );
572 }
573
574 #[test]
575 fn test_remove_newline_take_till() {
576 let res = remove_newline_take_till(']')("").unwrap();
577 assert_eq!(res, ("", Cow::from("")));
578 assert!(matches!(res.1, Cow::Borrowed { .. }));
579
580 let res = remove_newline_take_till(']')("text text]abc").unwrap();
581 assert_eq!(res, ("]abc", Cow::from("text text")));
582 assert!(matches!(res.1, Cow::Borrowed { .. }));
583
584 let res = remove_newline_take_till(']')("text text").unwrap();
585 assert_eq!(res, ("", Cow::from("text text")));
586 assert!(matches!(res.1, Cow::Borrowed { .. }));
587
588 let res = remove_newline_take_till(']')(r#"te\]xt]abc"#).unwrap();
589 assert_eq!(res, ("]abc", Cow::from("te]xt")));
590 assert!(matches!(res.1, Cow::Owned { .. }));
591
592 let res = remove_newline_take_till(']')(r#"text\]]abc"#).unwrap();
593 assert_eq!(res, ("]abc", Cow::from("text]")));
594 assert!(matches!(res.1, Cow::Owned { .. }));
595
596 let res = remove_newline_take_till(']')(r#"te\xt]abc"#).unwrap();
597 assert_eq!(res, ("]abc", Cow::from(r#"te\xt"#)));
598 assert!(matches!(res.1, Cow::Owned { .. }));
599
600 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
601 assert_eq!(res, ("]abc", Cow::from("text text")));
602 assert!(matches!(res.1, Cow::Owned { .. }));
603
604 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
605 assert_eq!(res, ("]abc", Cow::from("text text")));
606 assert!(matches!(res.1, Cow::Owned { .. }));
607
608 assert_eq!(
609 remove_newline_take_till(']')("text\n\ntext]abc").unwrap_err(),
610 nom::Err::Error(nom::error::Error::new("\ntext]abc", ErrorKind::Not))
611 );
612
613 assert_eq!(
614 remove_newline_take_till(']')("text\n \n text]abc").unwrap_err(),
615 nom::Err::Error(nom::error::Error::new("\n text]abc", ErrorKind::Not))
616 );
617 }
618
619 #[test]
620 fn test_adoc_parse_http_link_destination() {
621 let res = adoc_parse_http_link_destination("http://destination/").unwrap();
622 assert_eq!(res, ("", Cow::from("http://destination/")));
623 assert!(matches!(res.1, Cow::Borrowed { .. }));
624
625 let res = adoc_parse_http_link_destination("http://destination/\nabc").unwrap();
626 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
627 assert!(matches!(res.1, Cow::Borrowed { .. }));
628
629 let res = adoc_parse_http_link_destination("http://destination/ abc").unwrap();
630 assert_eq!(res, (" abc", Cow::from("http://destination/")));
631 assert!(matches!(res.1, Cow::Borrowed { .. }));
632
633 let res = adoc_parse_http_link_destination("http://destination/[abc").unwrap();
634 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
635 assert!(matches!(res.1, Cow::Borrowed { .. }));
636
637 let res = adoc_parse_http_link_destination("https://destination/[abc").unwrap();
638 assert_eq!(res, ("[abc", Cow::from("https://destination/")));
639 assert!(matches!(res.1, Cow::Borrowed { .. }));
640
641 assert_eq!(
642 adoc_parse_http_link_destination("http:/destination/[abc").unwrap_err(),
643 nom::Err::Error(nom::error::Error::new(
644 "http:/destination/[abc",
645 ErrorKind::Tag
646 ))
647 );
648 }
649
650 #[test]
651 fn test_adoc_parse_escaped_link_destination() {
652 let res = adoc_parse_escaped_link_destination("link:http://destination/").unwrap();
653 assert_eq!(res, ("", Cow::from("http://destination/")));
654 assert!(matches!(res.1, Cow::Borrowed { .. }));
655
656 let res = adoc_parse_escaped_link_destination("link:http://destination/[abc").unwrap();
657 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
658 assert!(matches!(res.1, Cow::Borrowed { .. }));
659
660 let res = adoc_parse_escaped_link_destination("link:http://destination/ abc").unwrap();
661 assert_eq!(res, (" abc", Cow::from("http://destination/")));
662 assert!(matches!(res.1, Cow::Borrowed { .. }));
663
664 let res = adoc_parse_escaped_link_destination("link:http://destination/\nabc").unwrap();
665 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
666 assert!(matches!(res.1, Cow::Borrowed { .. }));
667
668 assert_eq!(
669 adoc_parse_escaped_link_destination("link:httpX:/destination/[abc").unwrap_err(),
670 nom::Err::Error(nom::error::Error::new(
671 "httpX:/destination/[abc",
672 ErrorKind::Tag
673 ))
674 );
675
676 let res = adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%5Ba%20b%5D[abc")
677 .unwrap();
678 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
679 assert!(matches!(res.1, Cow::Owned { .. }));
680
681 assert_eq!(
682 adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%FF%FF[abc")
683 .unwrap_err(),
684 nom::Err::Error(nom::error::Error::new(
685 "https://getreu.net/?q=%FF%FF",
686 ErrorKind::EscapedTransform
687 ))
688 );
689 }
690
691 #[test]
692 fn test_adoc_parse_literal_link_destination() {
693 let res = adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]++[abc")
694 .unwrap();
695 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
696
697 assert_eq!(
698 adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]+[abc")
699 .unwrap_err(),
700 nom::Err::Error(nom::error::Error::new(
701 "https://getreu.net/?q=[a b]+[abc",
702 ErrorKind::TakeUntil
703 ))
704 );
705 }
706
707 #[test]
708 fn test_adoc_text2label() {
709 let res = adoc_text2label("{label}[link text]abc").unwrap();
710 assert_eq!(res, ("abc", (Cow::from("link text"), Cow::from("label"))));
711
712 let res = adoc_text2label("{label}[]abc").unwrap();
713 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
714
715 let res = adoc_text2label("{label}abc").unwrap();
716 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
717
718 let res = adoc_text2label("{label}").unwrap();
719 assert_eq!(res, ("", (Cow::from(""), Cow::from("label"))));
720
721 let res = adoc_text2label("{label} [link text]abc").unwrap();
722 assert_eq!(
723 res,
724 (" [link text]abc", (Cow::from(""), Cow::from("label")))
725 );
726
727 assert_eq!(
728 adoc_text2label("{label}[abc").unwrap_err(),
729 nom::Err::Error(nom::error::Error::new("[abc", ErrorKind::NoneOf))
730 );
731 }
732
733 #[test]
734 fn test_adoc_parse_curly_bracket_reference() {
735 let res = adoc_parse_curly_bracket_reference("{label}").unwrap();
736 assert_eq!(res, ("", Cow::from("label")));
737
738 let res = adoc_parse_curly_bracket_reference("{label}[link text]").unwrap();
739 assert_eq!(res, ("[link text]", Cow::from("label")));
740
741 assert_eq!(
742 adoc_parse_curly_bracket_reference("").unwrap_err(),
743 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
744 );
745
746 assert_eq!(
747 adoc_parse_curly_bracket_reference("{label }").unwrap_err(),
748 nom::Err::Error(nom::error::Error::new(" }", ErrorKind::Char))
749 );
750 assert_eq!(
751 adoc_parse_curly_bracket_reference("").unwrap_err(),
752 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
753 );
754 }
755
756 #[test]
757 fn test_adoc_parse_colon_reference() {
758 let res = adoc_parse_colon_reference(":label:abc").unwrap();
759 assert_eq!(res, ("abc", "label"));
760
761 assert_eq!(
762 adoc_parse_colon_reference(":label abc").unwrap_err(),
763 nom::Err::Error(nom::error::Error::new(" abc", ErrorKind::Char))
764 );
765 }
766}