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