1use std::sync::LazyLock;
2
3use regex::Regex;
4
5use crate::{
6 HasSpan, Parser, Span,
7 document::{Author, set_author_metadata},
8 internal::opaque_iter::opaque_slice_iter,
9};
10
11opaque_slice_iter! {
12 pub struct Authors<'a> yielding Author;
15}
16
17#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct AuthorLine<'src> {
22 authors: Vec<Author>,
23 source: Span<'src>,
24}
25
26impl<'src> AuthorLine<'src> {
27 pub(crate) fn parse(source: Span<'src>, parser: &mut Parser) -> Self {
28 let authors: Vec<Author> = split_authors(source.data())
29 .into_iter()
30 .filter_map(|raw_author| Author::parse(raw_author, parser, false))
31 .collect();
32
33 if !authors.is_empty() {
41 set_author_metadata(parser, &authors);
42 }
43
44 Self { authors, source }
45 }
46
47 pub fn authors(&'src self) -> Authors<'src> {
49 Authors::new(&self.authors)
50 }
51}
52
53static NUMERIC_CHARACTER_REFERENCE: LazyLock<Regex> = LazyLock::new(|| {
64 #[allow(clippy::unwrap_used)]
65 Regex::new(r"&#(?:[0-9]+|[xX][0-9a-fA-F]+);").unwrap()
66});
67
68fn split_authors(data: &str) -> Vec<&str> {
81 let char_ref_terminators: Vec<usize> = NUMERIC_CHARACTER_REFERENCE
84 .find_iter(data)
85 .map(|m| m.end() - 1)
86 .collect();
87
88 let bytes = data.as_bytes();
89 let mut authors: Vec<&str> = Vec::new();
90 let mut start = 0;
91
92 for (index, c) in data.char_indices() {
93 if c != ';' || char_ref_terminators.contains(&index) {
94 continue;
95 }
96
97 let is_separator = match bytes.get(index + 1) {
100 Some(next) => *next == b' ',
101 None => true,
102 };
103
104 if is_separator {
105 authors.push(&data[start..index]);
106 start = index + 1;
107 }
108 }
109
110 authors.push(&data[start..]);
111 authors
112}
113
114impl<'src> HasSpan<'src> for AuthorLine<'src> {
115 fn span(&self) -> Span<'src> {
116 self.source
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 #![allow(clippy::unwrap_used)]
123
124 use crate::{parser::ModificationContext, tests::prelude::*};
125
126 #[test]
127 fn empty_line() {
128 let mut parser = Parser::default();
129
130 let al = crate::document::AuthorLine::parse(crate::Span::new(""), &mut parser);
131
132 assert_eq!(
133 &al,
134 AuthorLine {
135 authors: &[],
136 source: Span {
137 data: "",
138 line: 1,
139 col: 1,
140 offset: 0,
141 },
142 }
143 );
144 }
145
146 #[test]
147 fn implicit_author_name_is_always_header_substituted() {
148 let author_name = |src: &str| {
155 let mut parser = Parser::default();
156 let doc = parser.parse(src);
157
158 doc.header()
159 .author_line()
160 .unwrap()
161 .authors()
162 .next()
163 .unwrap()
164 .name()
165 .to_string()
166 };
167
168 assert_eq!(author_name("= T\n<x> Blogs\n\nbody\n"), "<x> Blogs");
171
172 assert_eq!(
175 author_name(":v: RESOLVED\n= T\n(C) <x> {v} Blogs\n\nbody\n"),
176 "(C) <x> RESOLVED Blogs"
177 );
178
179 assert_eq!(author_name("= T\n(C) Blogs\n\nbody\n"), "(C) Blogs");
181 }
182
183 #[test]
184 fn attr_reference_value_is_inserted_verbatim() {
185 let mut parser = Parser::default().with_intrinsic_attribute(
192 "weird-content",
193 "Complex <weird> & stuff",
194 ModificationContext::Anywhere,
195 );
196
197 let al = crate::document::AuthorLine::parse(
198 crate::Span::new("Some {weird-content} pattern"),
199 &mut parser,
200 );
201
202 assert_eq!(
203 al,
204 AuthorLine {
205 authors: &[Author {
206 name: "Some Complex <weird> & stuff pattern",
207 firstname: "Some Complex <weird> & stuff pattern",
208 middlename: None,
209 lastname: None,
210 email: None,
211 },],
212 source: Span {
213 data: "Some {weird-content} pattern",
214 line: 1,
215 col: 1,
216 offset: 0,
217 },
218 }
219 );
220 }
221
222 #[test]
223 fn empty_author() {
224 let mut parser = Parser::default();
225
226 let al = crate::document::AuthorLine::parse(
227 crate::Span::new("Author One; ; Author Three"),
228 &mut parser,
229 );
230
231 assert_eq!(
232 al,
233 AuthorLine {
234 authors: &[
235 Author {
236 name: "Author One",
237 firstname: "Author",
238 middlename: None,
239 lastname: Some("One",),
240 email: None,
241 },
242 Author {
243 name: "Author Three",
244 firstname: "Author",
245 middlename: None,
246 lastname: Some("Three",),
247 email: None,
248 },
249 ],
250 source: Span {
251 data: "Author One; ; Author Three",
252 line: 1,
253 col: 1,
254 offset: 0,
255 },
256 }
257 );
258 }
259
260 #[test]
261 fn one_simple_author() {
262 let mut parser = Parser::default();
263
264 let al = crate::document::AuthorLine::parse(
265 crate::Span::new("Kismet R. Lee <kismet@asciidoctor.org>"),
266 &mut parser,
267 );
268
269 assert_eq!(
270 &al,
271 AuthorLine {
272 authors: &[Author {
273 name: "Kismet R. Lee",
274 firstname: "Kismet",
275 middlename: Some("R.",),
276 lastname: Some("Lee",),
277 email: Some("kismet@asciidoctor.org",),
278 },],
279 source: Span {
280 data: "Kismet R. Lee <kismet@asciidoctor.org>",
281 line: 1,
282 col: 1,
283 offset: 0,
284 },
285 }
286 );
287 }
288
289 #[test]
290 fn author_without_middle_name() {
291 let mut parser = Parser::default();
292
293 let al = crate::document::AuthorLine::parse(
294 crate::Span::new("Doc Writer <doc@example.com>"),
295 &mut parser,
296 );
297
298 assert_eq!(
299 al,
300 AuthorLine {
301 authors: &[Author {
302 name: "Doc Writer",
303 firstname: "Doc",
304 middlename: None,
305 lastname: Some("Writer",),
306 email: Some("doc@example.com",),
307 },],
308 source: Span {
309 data: "Doc Writer <doc@example.com>",
310 line: 1,
311 col: 1,
312 offset: 0,
313 },
314 }
315 );
316 }
317
318 #[test]
319 fn too_many_names() {
320 let mut parser = Parser::default();
321
322 let al = crate::document::AuthorLine::parse(
323 crate::Span::new("Four Names Not Supported <doc@example.com>"),
324 &mut parser,
325 );
326
327 assert_eq!(
328 al,
329 AuthorLine {
330 authors: &[Author {
331 name: "Four Names Not Supported <doc@example.com>",
336 firstname: "Four Names Not Supported <doc@example.com>",
337 middlename: None,
338 lastname: None,
339 email: None,
340 },],
341 source: Span {
342 data: "Four Names Not Supported <doc@example.com>",
343 line: 1,
344 col: 1,
345 offset: 0,
346 },
347 }
348 );
349 }
350
351 #[test]
352 fn fallback_condenses_whitespace_and_keeps_underscores_literal() {
353 let mut parser = Parser::default();
358
359 let al =
360 crate::document::AuthorLine::parse(crate::Span::new("Jane_ Q, Doe"), &mut parser);
361
362 assert_eq!(
363 al,
364 AuthorLine {
365 authors: &[Author {
366 name: "Jane_ Q, Doe",
367 firstname: "Jane_ Q, Doe",
368 middlename: None,
369 lastname: None,
370 email: None,
371 },],
372 source: Span {
373 data: "Jane_ Q, Doe",
374 line: 1,
375 col: 1,
376 offset: 0,
377 },
378 }
379 );
380 }
381
382 #[test]
383 fn one_name() {
384 let mut parser = Parser::default();
385
386 let al = crate::document::AuthorLine::parse(
387 crate::Span::new("John <john@example.com>"),
388 &mut parser,
389 );
390
391 assert_eq!(
392 al,
393 AuthorLine {
394 authors: &[Author {
395 name: "John",
396 firstname: "John",
397 middlename: None,
398 lastname: None,
399 email: Some("john@example.com",),
400 },],
401 source: Span {
402 data: "John <john@example.com>",
403 line: 1,
404 col: 1,
405 offset: 0,
406 },
407 }
408 );
409 }
410
411 #[test]
412 fn underscore_join() {
413 let mut parser = Parser::default();
414
415 let al =
416 crate::document::AuthorLine::parse(crate::Span::new("Mary_Sue Brontë"), &mut parser);
417
418 assert_eq!(
419 al,
420 AuthorLine {
421 authors: &[Author {
422 name: "Mary Sue Brontë", firstname: "Mary Sue", middlename: None,
425 lastname: Some("Brontë",),
426 email: None,
427 },],
428 source: Span {
429 data: "Mary_Sue Brontë",
430 line: 1,
431 col: 1,
432 offset: 0,
433 },
434 }
435 );
436 }
437
438 #[test]
439 fn greek() {
440 let mut parser = Parser::default();
441
442 let al = crate::document::AuthorLine::parse(
443 crate::Span::new("Αλέξανδρος Παπαδόπουλος"),
444 &mut parser,
445 );
446
447 assert_eq!(
448 al,
449 AuthorLine {
450 authors: &[Author {
451 name: "Αλέξανδρος Παπαδόπουλος",
452 firstname: "Αλέξανδρος",
453 middlename: None,
454 lastname: Some("Παπαδόπουλος",),
455 email: None,
456 },],
457 source: Span {
458 data: "Αλέξανδρος Παπαδόπουλος",
459 line: 1,
460 col: 1,
461 offset: 0,
462 },
463 }
464 );
465 }
466
467 #[test]
468 fn japanese() {
469 let mut parser = Parser::default();
470
471 let al = crate::document::AuthorLine::parse(crate::Span::new("山田太郎"), &mut parser);
472
473 assert_eq!(
474 al,
475 AuthorLine {
476 authors: &[Author {
477 name: "山田太郎",
478 firstname: "山田太郎",
479 middlename: None,
480 lastname: None,
481 email: None,
482 },],
483 source: Span {
484 data: "山田太郎",
485 line: 1,
486 col: 1,
487 offset: 0,
488 },
489 }
490 );
491 }
492
493 #[test]
494 fn arabic() {
495 let mut parser = Parser::default();
496
497 let al = crate::document::AuthorLine::parse(crate::Span::new("عبد_الله"), &mut parser);
498
499 assert_eq!(
500 al,
501 AuthorLine {
502 authors: &[Author {
503 name: "عبد الله", firstname: "عبد الله", middlename: None,
506 lastname: None,
507 email: None,
508 },],
509 source: Span {
510 data: "عبد_الله",
511 line: 1,
512 col: 1,
513 offset: 0,
514 },
515 }
516 );
517 }
518
519 #[test]
520 fn underscore_replacement_in_all_name_parts() {
521 let mut parser = Parser::default();
522
523 let al = crate::document::AuthorLine::parse(
524 crate::Span::new("John_Paul Mary_Jane Smith_Jones <email@example.com>"),
525 &mut parser,
526 );
527
528 assert_eq!(
529 al,
530 AuthorLine {
531 authors: &[Author {
532 name: "John Paul Mary Jane Smith Jones", firstname: "John Paul", middlename: Some("Mary Jane"), lastname: Some("Smith Jones"), email: Some("email@example.com"),
537 },],
538 source: Span {
539 data: "John_Paul Mary_Jane Smith_Jones <email@example.com>",
540 line: 1,
541 col: 1,
542 offset: 0,
543 },
544 }
545 );
546 }
547
548 #[test]
549 fn multiple_underscores_in_name_parts() {
550 let mut parser = Parser::default();
551
552 let al =
553 crate::document::AuthorLine::parse(crate::Span::new("A_B_C D_E_F G_H_I"), &mut parser);
554
555 assert_eq!(
556 al,
557 AuthorLine {
558 authors: &[Author {
559 name: "A B C D E F G H I", firstname: "A B C", middlename: Some("D E F"), lastname: Some("G H I"), email: None,
564 },],
565 source: Span {
566 data: "A_B_C D_E_F G_H_I",
567 line: 1,
568 col: 1,
569 offset: 0,
570 },
571 }
572 );
573 }
574
575 #[test]
576 fn underscore_replacement_with_attribute_substitution() {
577 let mut parser = Parser::default()
578 .with_intrinsic_attribute("first-part", "John_Paul", ModificationContext::Anywhere)
579 .with_intrinsic_attribute("last-part", "Smith_Jones", ModificationContext::Anywhere);
580
581 let al = crate::document::AuthorLine::parse(
582 crate::Span::new("{first-part} {last-part} <email@example.com>"),
583 &mut parser,
584 );
585
586 assert_eq!(
591 al,
592 AuthorLine {
593 authors: &[Author {
594 name: "John Paul Smith Jones <email@example.com>", firstname: "John Paul Smith Jones <email@example.com>", middlename: None,
601 lastname: None,
602 email: None,
603 },],
604 source: Span {
605 data: "{first-part} {last-part} <email@example.com>",
606 line: 1,
607 col: 1,
608 offset: 0,
609 },
610 }
611 );
612 }
613
614 #[test]
615 fn attr_sub_email() {
616 let mut parser = Parser::default()
617 .with_intrinsic_attribute(
618 "jane-email",
619 "jane@example.com",
620 ModificationContext::Anywhere,
621 )
622 .with_intrinsic_attribute(
623 "john-email",
624 "john@example.com",
625 ModificationContext::Anywhere,
626 );
627
628 let al = crate::document::AuthorLine::parse(
629 crate::Span::new("Jane Smith <{jane-email}>; John Doe <{john-email}>"),
630 &mut parser,
631 );
632
633 assert_eq!(
634 al,
635 AuthorLine {
636 authors: &[
637 Author {
638 name: "Jane Smith",
639 firstname: "Jane",
640 middlename: None,
641 lastname: Some("Smith",),
642 email: Some("jane@example.com",),
643 },
644 Author {
645 name: "John Doe",
646 firstname: "John",
647 middlename: None,
648 lastname: Some("Doe",),
649 email: Some("john@example.com",),
650 },
651 ],
652 source: Span {
653 data: "Jane Smith <{jane-email}>; John Doe <{john-email}>",
654 line: 1,
655 col: 1,
656 offset: 0,
657 },
658 }
659 );
660 }
661
662 #[test]
663 fn attr_sub_applied_after_parsing() {
664 let mut parser = Parser::default().with_intrinsic_attribute(
669 "author-list",
670 "Jane Smith <jane@example.com>; John Doe <john@example.com>",
671 ModificationContext::Anywhere,
672 );
673
674 let al = crate::document::AuthorLine::parse(crate::Span::new("{author-list}"), &mut parser);
675
676 assert_eq!(
677 al,
678 AuthorLine {
679 authors: &[Author {
680 name: "Jane Smith <jane@example.com>; John Doe <john@example.com>",
681 firstname: "Jane Smith <jane@example.com>; John Doe <john@example.com>",
682 middlename: None,
683 lastname: None,
684 email: None,
685 },],
686 source: Span {
687 data: "{author-list}",
688 line: 1,
689 col: 1,
690 offset: 0,
691 },
692 }
693 );
694 }
695
696 #[test]
697 fn attr_sub_for_individual_author() {
698 let mut parser = Parser::default().with_intrinsic_attribute(
699 "full-author",
700 "John Doe <john@example.com>",
701 ModificationContext::Anywhere,
702 );
703
704 let al = crate::document::AuthorLine::parse(crate::Span::new("{full-author}"), &mut parser);
705
706 assert_eq!(
707 al,
708 AuthorLine {
709 authors: &[Author {
710 name: "John Doe <john@example.com>",
711 firstname: "John Doe <john@example.com>",
712 middlename: None,
713 lastname: None,
714 email: None,
715 },],
716 source: Span {
717 data: "{full-author}",
718 line: 1,
719 col: 1,
720 offset: 0,
721 },
722 }
723 );
724 }
725
726 #[test]
727 fn err_individual_name_components_as_attributes() {
728 let mut parser = Parser::default()
730 .with_intrinsic_attribute("first-name", "Jane", ModificationContext::Anywhere)
731 .with_intrinsic_attribute("last-name", "Smith", ModificationContext::Anywhere)
732 .with_intrinsic_attribute(
733 "author-email",
734 "jane@example.com",
735 ModificationContext::Anywhere,
736 );
737
738 let al = crate::document::AuthorLine::parse(
739 crate::Span::new("{first-name} {last-name} <{author-email}>"),
740 &mut parser,
741 );
742
743 assert_eq!(
744 al,
745 AuthorLine {
746 authors: &[Author {
747 name: "Jane Smith <jane@example.com>",
748 firstname: "Jane Smith <jane@example.com>",
749 middlename: None,
750 lastname: None,
751 email: None,
752 },],
753 source: Span {
754 data: "{first-name} {last-name} <{author-email}>",
755 line: 1,
756 col: 1,
757 offset: 0,
758 },
759 }
760 );
761 }
762
763 #[test]
764 fn sets_author_attributes_single_author_with_all_parts() {
765 let mut parser = Parser::default();
766 let _doc = parser.parse("= Document Title\nKismet R. Lee <kismet@asciidoctor.org>");
767
768 assert_eq!(
770 parser.attribute_value("author"),
771 InterpretedValue::Value("Kismet R. Lee")
772 );
773 assert_eq!(
774 parser.attribute_value("firstname"),
775 InterpretedValue::Value("Kismet")
776 );
777 assert_eq!(
778 parser.attribute_value("middlename"),
779 InterpretedValue::Value("R.")
780 );
781 assert_eq!(
782 parser.attribute_value("lastname"),
783 InterpretedValue::Value("Lee")
784 );
785 assert_eq!(
786 parser.attribute_value("authorinitials"),
787 InterpretedValue::Value("KRL")
788 );
789 assert_eq!(
790 parser.attribute_value("email"),
791 InterpretedValue::Value("kismet@asciidoctor.org")
792 );
793 }
794
795 #[test]
796 fn sets_author_attributes_single_author_without_middle_name() {
797 let mut parser = Parser::default();
798 let _doc = parser.parse("= Document Title\nDoc Writer <doc@example.com>");
799
800 assert_eq!(
801 parser.attribute_value("author"),
802 InterpretedValue::Value("Doc Writer")
803 );
804 assert_eq!(
805 parser.attribute_value("firstname"),
806 InterpretedValue::Value("Doc")
807 );
808 assert_eq!(
809 parser.attribute_value("middlename"),
810 InterpretedValue::Unset
811 );
812 assert_eq!(
813 parser.attribute_value("lastname"),
814 InterpretedValue::Value("Writer")
815 );
816 assert_eq!(
817 parser.attribute_value("authorinitials"),
818 InterpretedValue::Value("DW")
819 );
820 assert_eq!(
821 parser.attribute_value("email"),
822 InterpretedValue::Value("doc@example.com")
823 );
824 }
825
826 #[test]
827 fn sets_author_attributes_single_author_first_name_only() {
828 let mut parser = Parser::default();
829 let _doc = parser.parse("= Document Title\nJohn <john@example.com>");
830
831 assert_eq!(
832 parser.attribute_value("author"),
833 InterpretedValue::Value("John")
834 );
835 assert_eq!(
836 parser.attribute_value("firstname"),
837 InterpretedValue::Value("John")
838 );
839 assert_eq!(
840 parser.attribute_value("middlename"),
841 InterpretedValue::Unset
842 );
843 assert_eq!(parser.attribute_value("lastname"), InterpretedValue::Unset);
844 assert_eq!(
845 parser.attribute_value("authorinitials"),
846 InterpretedValue::Value("J")
847 );
848 assert_eq!(
849 parser.attribute_value("email"),
850 InterpretedValue::Value("john@example.com")
851 );
852 }
853
854 #[test]
855 fn sets_author_attributes_single_author_without_email() {
856 let mut parser = Parser::default();
857 let _doc = parser.parse("= Document Title\nMary Sue Brontë");
858
859 assert_eq!(
860 parser.attribute_value("author"),
861 InterpretedValue::Value("Mary Sue Brontë")
862 );
863 assert_eq!(
864 parser.attribute_value("firstname"),
865 InterpretedValue::Value("Mary")
866 );
867 assert_eq!(
868 parser.attribute_value("middlename"),
869 InterpretedValue::Value("Sue")
870 );
871 assert_eq!(
872 parser.attribute_value("lastname"),
873 InterpretedValue::Value("Brontë")
874 );
875 assert_eq!(
876 parser.attribute_value("authorinitials"),
877 InterpretedValue::Value("MSB")
878 );
879 assert_eq!(parser.attribute_value("email"), InterpretedValue::Unset);
880 }
881
882 #[test]
883 fn sets_author_attributes_multiple_authors() {
884 let mut parser = Parser::default();
885 let _doc = parser
886 .parse("= Document Title\nJane Smith <jane@example.com>; John Doe <john@example.com>");
887
888 assert_eq!(
890 parser.attribute_value("author"),
891 InterpretedValue::Value("Jane Smith")
892 );
893 assert_eq!(
894 parser.attribute_value("firstname"),
895 InterpretedValue::Value("Jane")
896 );
897 assert_eq!(
898 parser.attribute_value("lastname"),
899 InterpretedValue::Value("Smith")
900 );
901 assert_eq!(
902 parser.attribute_value("authorinitials"),
903 InterpretedValue::Value("JS")
904 );
905 assert_eq!(
906 parser.attribute_value("email"),
907 InterpretedValue::Value("jane@example.com")
908 );
909
910 assert_eq!(
913 parser.attribute_value("author_1"),
914 InterpretedValue::Value("Jane Smith")
915 );
916 assert_eq!(
917 parser.attribute_value("firstname_1"),
918 InterpretedValue::Value("Jane")
919 );
920 assert_eq!(
921 parser.attribute_value("lastname_1"),
922 InterpretedValue::Value("Smith")
923 );
924 assert_eq!(
925 parser.attribute_value("authorinitials_1"),
926 InterpretedValue::Value("JS")
927 );
928 assert_eq!(
929 parser.attribute_value("email_1"),
930 InterpretedValue::Value("jane@example.com")
931 );
932
933 assert_eq!(
935 parser.attribute_value("author_2"),
936 InterpretedValue::Value("John Doe")
937 );
938 assert_eq!(
939 parser.attribute_value("firstname_2"),
940 InterpretedValue::Value("John")
941 );
942 assert_eq!(
943 parser.attribute_value("lastname_2"),
944 InterpretedValue::Value("Doe")
945 );
946 assert_eq!(
947 parser.attribute_value("authorinitials_2"),
948 InterpretedValue::Value("JD")
949 );
950 assert_eq!(
951 parser.attribute_value("email_2"),
952 InterpretedValue::Value("john@example.com")
953 );
954
955 assert_eq!(
957 parser.attribute_value("middlename"),
958 InterpretedValue::Unset
959 );
960 assert_eq!(
961 parser.attribute_value("middlename_1"),
962 InterpretedValue::Unset
963 );
964 assert_eq!(
965 parser.attribute_value("middlename_2"),
966 InterpretedValue::Unset
967 );
968
969 assert_eq!(
971 parser.attribute_value("authors"),
972 InterpretedValue::Value("Jane Smith, John Doe")
973 );
974 }
975
976 #[test]
977 fn single_author_sets_combined_authors_but_no_indexed_names() {
978 let mut parser = Parser::default();
979 let _doc = parser.parse("= Document Title\nKismet R. Lee <kismet@asciidoctor.org>");
980
981 assert_eq!(
984 parser.attribute_value("authors"),
985 InterpretedValue::Value("Kismet R. Lee")
986 );
987
988 assert_eq!(parser.attribute_value("author_1"), InterpretedValue::Unset);
991 assert_eq!(
992 parser.attribute_value("firstname_1"),
993 InterpretedValue::Unset
994 );
995 assert_eq!(
996 parser.attribute_value("lastname_1"),
997 InterpretedValue::Unset
998 );
999 }
1000
1001 #[test]
1002 fn sets_author_attributes_unicode_names() {
1003 let mut parser = Parser::default();
1004 let _doc = parser.parse("= Document Title\nΑλέξανδρος Μ. Παπαδόπουλος");
1005
1006 assert_eq!(
1007 parser.attribute_value("author"),
1008 InterpretedValue::Value("Αλέξανδρος Μ. Παπαδόπουλος")
1009 );
1010 assert_eq!(
1011 parser.attribute_value("firstname"),
1012 InterpretedValue::Value("Αλέξανδρος")
1013 );
1014 assert_eq!(
1015 parser.attribute_value("middlename"),
1016 InterpretedValue::Value("Μ.")
1017 );
1018 assert_eq!(
1019 parser.attribute_value("lastname"),
1020 InterpretedValue::Value("Παπαδόπουλος")
1021 );
1022 assert_eq!(
1023 parser.attribute_value("authorinitials"),
1024 InterpretedValue::Value("ΑΜΠ")
1025 );
1026 }
1027
1028 #[test]
1029 fn semicolon_in_character_reference_not_treated_as_separator() {
1030 let mut parser = Parser::default();
1031
1032 let al = crate::document::AuthorLine::parse(
1033 crate::Span::new("AsciiDoc®{empty} WG; Another Author"),
1034 &mut parser,
1035 );
1036
1037 assert_eq!(
1038 al,
1039 AuthorLine {
1040 authors: &[
1041 Author {
1042 name: "AsciiDoc® WG",
1043 firstname: "AsciiDoc®",
1044 middlename: None,
1045 lastname: Some("WG"),
1046 email: None,
1047 },
1048 Author {
1049 name: "Another Author",
1050 firstname: "Another",
1051 middlename: None,
1052 lastname: Some("Author"),
1053 email: None,
1054 },
1055 ],
1056 source: Span {
1057 data: "AsciiDoc®{empty} WG; Another Author",
1058 line: 1,
1059 col: 1,
1060 offset: 0,
1061 },
1062 }
1063 );
1064 }
1065
1066 #[test]
1067 fn skips_blank_and_trailing_author_entries() {
1068 let mut parser = Parser::default();
1074
1075 let al = crate::document::AuthorLine::parse(
1076 crate::Span::new("Doc Writer; ; John Smith <john.smith@asciidoc.org>;"),
1077 &mut parser,
1078 );
1079
1080 assert_eq!(
1081 al,
1082 AuthorLine {
1083 authors: &[
1084 Author {
1085 name: "Doc Writer",
1086 firstname: "Doc",
1087 middlename: None,
1088 lastname: Some("Writer"),
1089 email: None,
1090 },
1091 Author {
1092 name: "John Smith",
1093 firstname: "John",
1094 middlename: None,
1095 lastname: Some("Smith"),
1096 email: Some("john.smith@asciidoc.org"),
1097 },
1098 ],
1099 source: Span {
1100 data: "Doc Writer; ; John Smith <john.smith@asciidoc.org>;",
1101 line: 1,
1102 col: 1,
1103 offset: 0,
1104 },
1105 }
1106 );
1107 }
1108
1109 #[test]
1110 fn semicolon_not_followed_by_space_is_single_author() {
1111 let mut parser = Parser::default();
1114
1115 let al = crate::document::AuthorLine::parse(
1116 crate::Span::new("Joe Doe;Smith Johnson"),
1117 &mut parser,
1118 );
1119
1120 assert_eq!(al.authors().len(), 1);
1121 }
1122
1123 #[test]
1124 fn character_reference_followed_by_space_not_treated_as_separator() {
1125 let mut parser = Parser::default();
1128
1129 let al = crate::document::AuthorLine::parse(
1130 crate::Span::new("AsciiDoc® WG; Another Author"),
1131 &mut parser,
1132 );
1133
1134 assert_eq!(
1135 al,
1136 AuthorLine {
1137 authors: &[
1138 Author {
1139 name: "AsciiDoc® WG",
1140 firstname: "AsciiDoc®",
1141 middlename: None,
1142 lastname: Some("WG"),
1143 email: None,
1144 },
1145 Author {
1146 name: "Another Author",
1147 firstname: "Another",
1148 middlename: None,
1149 lastname: Some("Author"),
1150 email: None,
1151 },
1152 ],
1153 source: Span {
1154 data: "AsciiDoc® WG; Another Author",
1155 line: 1,
1156 col: 1,
1157 offset: 0,
1158 },
1159 }
1160 );
1161 }
1162
1163 #[test]
1164 fn invalid_named_entity_does_not_suppress_separator() {
1165 let mut parser = Parser::default();
1169
1170 let al = crate::document::AuthorLine::parse(
1171 crate::Span::new("Alice &Development; Bob"),
1172 &mut parser,
1173 );
1174
1175 assert_eq!(
1176 al,
1177 AuthorLine {
1178 authors: &[
1179 Author {
1180 name: "Alice &Development",
1184 firstname: "Alice",
1185 middlename: None,
1186 lastname: Some("&Development"),
1187 email: None,
1188 },
1189 Author {
1190 name: "Bob",
1191 firstname: "Bob",
1192 middlename: None,
1193 lastname: None,
1194 email: None,
1195 },
1196 ],
1197 source: Span {
1198 data: "Alice &Development; Bob",
1199 line: 1,
1200 col: 1,
1201 offset: 0,
1202 },
1203 }
1204 );
1205 }
1206
1207 #[test]
1208 fn comprehensive_author_attribute_test() {
1209 let mut parser = Parser::default();
1213 let doc = parser.parse("= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy");
1214
1215 assert_eq!(
1216 parser.attribute_value("author"),
1217 InterpretedValue::Value("First Second Last")
1218 );
1219
1220 assert_eq!(
1221 parser.attribute_value("firstname"),
1222 InterpretedValue::Value("First")
1223 );
1224
1225 assert_eq!(
1226 parser.attribute_value("middlename"),
1227 InterpretedValue::Value("Second")
1228 );
1229
1230 assert_eq!(
1231 parser.attribute_value("lastname"),
1232 InterpretedValue::Value("Last")
1233 );
1234
1235 assert_eq!(
1236 parser.attribute_value("authorinitials"),
1237 InterpretedValue::Value("FSL")
1238 );
1239
1240 assert_eq!(
1241 parser.attribute_value("email"),
1242 InterpretedValue::Value("first@example.com")
1243 );
1244
1245 assert_eq!(
1246 parser.attribute_value("author_2"),
1247 InterpretedValue::Value("Only First")
1248 );
1249
1250 assert_eq!(
1251 parser.attribute_value("firstname_2"),
1252 InterpretedValue::Value("Only")
1253 );
1254
1255 assert_eq!(
1256 parser.attribute_value("middlename_2"),
1257 InterpretedValue::Unset
1258 );
1259
1260 assert_eq!(
1261 parser.attribute_value("lastname_2"),
1262 InterpretedValue::Value("First")
1263 );
1264
1265 assert_eq!(
1266 parser.attribute_value("authorinitials_2"),
1267 InterpretedValue::Value("OF")
1268 );
1269
1270 assert_eq!(parser.attribute_value("email_2"), InterpretedValue::Unset);
1271
1272 assert_eq!(
1273 parser.attribute_value("author_3"),
1274 InterpretedValue::Value("A B C")
1275 );
1276
1277 assert_eq!(
1278 parser.attribute_value("firstname_3"),
1279 InterpretedValue::Value("A")
1280 );
1281
1282 assert_eq!(
1283 parser.attribute_value("middlename_3"),
1284 InterpretedValue::Value("B")
1285 );
1286
1287 assert_eq!(
1288 parser.attribute_value("lastname_3"),
1289 InterpretedValue::Value("C")
1290 );
1291
1292 assert_eq!(
1293 parser.attribute_value("authorinitials_3"),
1294 InterpretedValue::Value("ABC")
1295 );
1296
1297 assert_eq!(
1298 parser.attribute_value("email_3"),
1299 InterpretedValue::Value("abc@example.com")
1300 );
1301
1302 assert_eq!(
1303 parser.attribute_value("author_4"),
1304 InterpretedValue::Value("No Email Guy")
1305 );
1306
1307 assert_eq!(
1308 parser.attribute_value("firstname_4"),
1309 InterpretedValue::Value("No")
1310 );
1311
1312 assert_eq!(
1313 parser.attribute_value("middlename_4"),
1314 InterpretedValue::Value("Email")
1315 );
1316
1317 assert_eq!(
1318 parser.attribute_value("lastname_4"),
1319 InterpretedValue::Value("Guy")
1320 );
1321
1322 assert_eq!(
1323 parser.attribute_value("authorinitials_4"),
1324 InterpretedValue::Value("NEG")
1325 );
1326
1327 assert_eq!(parser.attribute_value("email_4"), InterpretedValue::Unset);
1328
1329 assert_eq!(
1330 doc,
1331 Document {
1332 header: Header {
1333 title_source: Some(Span {
1334 data: "Document Title",
1335 line: 1,
1336 col: 3,
1337 offset: 2,
1338 },),
1339 title: Some("Document Title",),
1340 attributes: &[],
1341 author_line: Some(AuthorLine {
1342 authors: &[
1343 Author {
1344 name: "First Second Last",
1345 firstname: "First",
1346 middlename: Some("Second",),
1347 lastname: Some("Last",),
1348 email: Some("first@example.com",),
1349 },
1350 Author {
1351 name: "Only First",
1352 firstname: "Only",
1353 middlename: None,
1354 lastname: Some("First",),
1355 email: None,
1356 },
1357 Author {
1358 name: "A B C",
1359 firstname: "A",
1360 middlename: Some("B",),
1361 lastname: Some("C",),
1362 email: Some("abc@example.com",),
1363 },
1364 Author {
1365 name: "No Email Guy",
1366 firstname: "No",
1367 middlename: Some("Email",),
1368 lastname: Some("Guy",),
1369 email: None,
1370 },
1371 ],
1372 source: Span {
1373 data: "First Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1374 line: 2,
1375 col: 1,
1376 offset: 17,
1377 },
1378 },),
1379 revision_line: None,
1380 comments: &[],
1381 source: Span {
1382 data: "= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1383 line: 1,
1384 col: 1,
1385 offset: 0,
1386 },
1387 },
1388 blocks: &[],
1389 source: Span {
1390 data: "= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1391 line: 1,
1392 col: 1,
1393 offset: 0,
1394 },
1395 warnings: &[],
1396 source_map: SourceMap(&[]),
1397 catalog: Catalog::default(),
1398 }
1399 );
1400 }
1401}