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 use crate::{parser::ModificationContext, tests::prelude::*};
123
124 #[test]
125 fn empty_line() {
126 let mut parser = Parser::default();
127
128 let al = crate::document::AuthorLine::parse(crate::Span::new(""), &mut parser);
129
130 assert_eq!(
131 &al,
132 AuthorLine {
133 authors: &[],
134 source: Span {
135 data: "",
136 line: 1,
137 col: 1,
138 offset: 0,
139 },
140 }
141 );
142 }
143
144 #[test]
145 fn attr_sub_with_html_encoding_fallback() {
146 let mut parser = Parser::default().with_intrinsic_attribute(
150 "weird-content",
151 "Complex <weird> & stuff",
152 ModificationContext::Anywhere,
153 );
154
155 let al = crate::document::AuthorLine::parse(
156 crate::Span::new("Some {weird-content} pattern"),
157 &mut parser,
158 );
159
160 assert_eq!(
161 al,
162 AuthorLine {
163 authors: &[Author {
164 name: "Some Complex <weird> & stuff pattern",
165 firstname: "Some Complex <weird> & stuff pattern",
166 middlename: None,
167 lastname: None,
168 email: None,
169 },],
170 source: Span {
171 data: "Some {weird-content} pattern",
172 line: 1,
173 col: 1,
174 offset: 0,
175 },
176 }
177 );
178 }
179
180 #[test]
181 fn empty_author() {
182 let mut parser = Parser::default();
183
184 let al = crate::document::AuthorLine::parse(
185 crate::Span::new("Author One; ; Author Three"),
186 &mut parser,
187 );
188
189 assert_eq!(
190 al,
191 AuthorLine {
192 authors: &[
193 Author {
194 name: "Author One",
195 firstname: "Author",
196 middlename: None,
197 lastname: Some("One",),
198 email: None,
199 },
200 Author {
201 name: "Author Three",
202 firstname: "Author",
203 middlename: None,
204 lastname: Some("Three",),
205 email: None,
206 },
207 ],
208 source: Span {
209 data: "Author One; ; Author Three",
210 line: 1,
211 col: 1,
212 offset: 0,
213 },
214 }
215 );
216 }
217
218 #[test]
219 fn one_simple_author() {
220 let mut parser = Parser::default();
221
222 let al = crate::document::AuthorLine::parse(
223 crate::Span::new("Kismet R. Lee <kismet@asciidoctor.org>"),
224 &mut parser,
225 );
226
227 assert_eq!(
228 &al,
229 AuthorLine {
230 authors: &[Author {
231 name: "Kismet R. Lee",
232 firstname: "Kismet",
233 middlename: Some("R.",),
234 lastname: Some("Lee",),
235 email: Some("kismet@asciidoctor.org",),
236 },],
237 source: Span {
238 data: "Kismet R. Lee <kismet@asciidoctor.org>",
239 line: 1,
240 col: 1,
241 offset: 0,
242 },
243 }
244 );
245 }
246
247 #[test]
248 fn author_without_middle_name() {
249 let mut parser = Parser::default();
250
251 let al = crate::document::AuthorLine::parse(
252 crate::Span::new("Doc Writer <doc@example.com>"),
253 &mut parser,
254 );
255
256 assert_eq!(
257 al,
258 AuthorLine {
259 authors: &[Author {
260 name: "Doc Writer",
261 firstname: "Doc",
262 middlename: None,
263 lastname: Some("Writer",),
264 email: Some("doc@example.com",),
265 },],
266 source: Span {
267 data: "Doc Writer <doc@example.com>",
268 line: 1,
269 col: 1,
270 offset: 0,
271 },
272 }
273 );
274 }
275
276 #[test]
277 fn too_many_names() {
278 let mut parser = Parser::default();
279
280 let al = crate::document::AuthorLine::parse(
281 crate::Span::new("Four Names Not Supported <doc@example.com>"),
282 &mut parser,
283 );
284
285 assert_eq!(
286 al,
287 AuthorLine {
288 authors: &[Author {
289 name: "Four Names Not Supported <doc@example.com>",
290 firstname: "Four Names Not Supported <doc@example.com>",
291 middlename: None,
292 lastname: None,
293 email: None,
294 },],
295 source: Span {
296 data: "Four Names Not Supported <doc@example.com>",
297 line: 1,
298 col: 1,
299 offset: 0,
300 },
301 }
302 );
303 }
304
305 #[test]
306 fn fallback_condenses_whitespace_and_keeps_underscores_literal() {
307 let mut parser = Parser::default();
312
313 let al =
314 crate::document::AuthorLine::parse(crate::Span::new("Jane_ Q, Doe"), &mut parser);
315
316 assert_eq!(
317 al,
318 AuthorLine {
319 authors: &[Author {
320 name: "Jane_ Q, Doe",
321 firstname: "Jane_ Q, Doe",
322 middlename: None,
323 lastname: None,
324 email: None,
325 },],
326 source: Span {
327 data: "Jane_ Q, Doe",
328 line: 1,
329 col: 1,
330 offset: 0,
331 },
332 }
333 );
334 }
335
336 #[test]
337 fn one_name() {
338 let mut parser = Parser::default();
339
340 let al = crate::document::AuthorLine::parse(
341 crate::Span::new("John <john@example.com>"),
342 &mut parser,
343 );
344
345 assert_eq!(
346 al,
347 AuthorLine {
348 authors: &[Author {
349 name: "John",
350 firstname: "John",
351 middlename: None,
352 lastname: None,
353 email: Some("john@example.com",),
354 },],
355 source: Span {
356 data: "John <john@example.com>",
357 line: 1,
358 col: 1,
359 offset: 0,
360 },
361 }
362 );
363 }
364
365 #[test]
366 fn underscore_join() {
367 let mut parser = Parser::default();
368
369 let al =
370 crate::document::AuthorLine::parse(crate::Span::new("Mary_Sue Brontë"), &mut parser);
371
372 assert_eq!(
373 al,
374 AuthorLine {
375 authors: &[Author {
376 name: "Mary Sue Brontë", firstname: "Mary Sue", middlename: None,
379 lastname: Some("Brontë",),
380 email: None,
381 },],
382 source: Span {
383 data: "Mary_Sue Brontë",
384 line: 1,
385 col: 1,
386 offset: 0,
387 },
388 }
389 );
390 }
391
392 #[test]
393 fn greek() {
394 let mut parser = Parser::default();
395
396 let al = crate::document::AuthorLine::parse(
397 crate::Span::new("Αλέξανδρος Παπαδόπουλος"),
398 &mut parser,
399 );
400
401 assert_eq!(
402 al,
403 AuthorLine {
404 authors: &[Author {
405 name: "Αλέξανδρος Παπαδόπουλος",
406 firstname: "Αλέξανδρος",
407 middlename: None,
408 lastname: Some("Παπαδόπουλος",),
409 email: None,
410 },],
411 source: Span {
412 data: "Αλέξανδρος Παπαδόπουλος",
413 line: 1,
414 col: 1,
415 offset: 0,
416 },
417 }
418 );
419 }
420
421 #[test]
422 fn japanese() {
423 let mut parser = Parser::default();
424
425 let al = crate::document::AuthorLine::parse(crate::Span::new("山田太郎"), &mut parser);
426
427 assert_eq!(
428 al,
429 AuthorLine {
430 authors: &[Author {
431 name: "山田太郎",
432 firstname: "山田太郎",
433 middlename: None,
434 lastname: None,
435 email: None,
436 },],
437 source: Span {
438 data: "山田太郎",
439 line: 1,
440 col: 1,
441 offset: 0,
442 },
443 }
444 );
445 }
446
447 #[test]
448 fn arabic() {
449 let mut parser = Parser::default();
450
451 let al = crate::document::AuthorLine::parse(crate::Span::new("عبد_الله"), &mut parser);
452
453 assert_eq!(
454 al,
455 AuthorLine {
456 authors: &[Author {
457 name: "عبد الله", firstname: "عبد الله", middlename: None,
460 lastname: None,
461 email: None,
462 },],
463 source: Span {
464 data: "عبد_الله",
465 line: 1,
466 col: 1,
467 offset: 0,
468 },
469 }
470 );
471 }
472
473 #[test]
474 fn underscore_replacement_in_all_name_parts() {
475 let mut parser = Parser::default();
476
477 let al = crate::document::AuthorLine::parse(
478 crate::Span::new("John_Paul Mary_Jane Smith_Jones <email@example.com>"),
479 &mut parser,
480 );
481
482 assert_eq!(
483 al,
484 AuthorLine {
485 authors: &[Author {
486 name: "John Paul Mary Jane Smith Jones", firstname: "John Paul", middlename: Some("Mary Jane"), lastname: Some("Smith Jones"), email: Some("email@example.com"),
491 },],
492 source: Span {
493 data: "John_Paul Mary_Jane Smith_Jones <email@example.com>",
494 line: 1,
495 col: 1,
496 offset: 0,
497 },
498 }
499 );
500 }
501
502 #[test]
503 fn multiple_underscores_in_name_parts() {
504 let mut parser = Parser::default();
505
506 let al =
507 crate::document::AuthorLine::parse(crate::Span::new("A_B_C D_E_F G_H_I"), &mut parser);
508
509 assert_eq!(
510 al,
511 AuthorLine {
512 authors: &[Author {
513 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,
518 },],
519 source: Span {
520 data: "A_B_C D_E_F G_H_I",
521 line: 1,
522 col: 1,
523 offset: 0,
524 },
525 }
526 );
527 }
528
529 #[test]
530 fn underscore_replacement_with_attribute_substitution() {
531 let mut parser = Parser::default()
532 .with_intrinsic_attribute("first-part", "John_Paul", ModificationContext::Anywhere)
533 .with_intrinsic_attribute("last-part", "Smith_Jones", ModificationContext::Anywhere);
534
535 let al = crate::document::AuthorLine::parse(
536 crate::Span::new("{first-part} {last-part} <email@example.com>"),
537 &mut parser,
538 );
539
540 assert_eq!(
545 al,
546 AuthorLine {
547 authors: &[Author {
548 name: "John Paul Smith Jones <email@example.com>", firstname: "John Paul Smith Jones <email@example.com>", middlename: None,
555 lastname: None,
556 email: None,
557 },],
558 source: Span {
559 data: "{first-part} {last-part} <email@example.com>",
560 line: 1,
561 col: 1,
562 offset: 0,
563 },
564 }
565 );
566 }
567
568 #[test]
569 fn attr_sub_email() {
570 let mut parser = Parser::default()
571 .with_intrinsic_attribute(
572 "jane-email",
573 "jane@example.com",
574 ModificationContext::Anywhere,
575 )
576 .with_intrinsic_attribute(
577 "john-email",
578 "john@example.com",
579 ModificationContext::Anywhere,
580 );
581
582 let al = crate::document::AuthorLine::parse(
583 crate::Span::new("Jane Smith <{jane-email}>; John Doe <{john-email}>"),
584 &mut parser,
585 );
586
587 assert_eq!(
588 al,
589 AuthorLine {
590 authors: &[
591 Author {
592 name: "Jane Smith",
593 firstname: "Jane",
594 middlename: None,
595 lastname: Some("Smith",),
596 email: Some("jane@example.com",),
597 },
598 Author {
599 name: "John Doe",
600 firstname: "John",
601 middlename: None,
602 lastname: Some("Doe",),
603 email: Some("john@example.com",),
604 },
605 ],
606 source: Span {
607 data: "Jane Smith <{jane-email}>; John Doe <{john-email}>",
608 line: 1,
609 col: 1,
610 offset: 0,
611 },
612 }
613 );
614 }
615
616 #[test]
617 fn attr_sub_applied_after_parsing() {
618 let mut parser = Parser::default().with_intrinsic_attribute(
623 "author-list",
624 "Jane Smith <jane@example.com>; John Doe <john@example.com>",
625 ModificationContext::Anywhere,
626 );
627
628 let al = crate::document::AuthorLine::parse(crate::Span::new("{author-list}"), &mut parser);
629
630 assert_eq!(
631 al,
632 AuthorLine {
633 authors: &[Author {
634 name: "Jane Smith <jane@example.com>; John Doe <john@example.com>",
635 firstname: "Jane Smith <jane@example.com>; John Doe <john@example.com>",
636 middlename: None,
637 lastname: None,
638 email: None,
639 },],
640 source: Span {
641 data: "{author-list}",
642 line: 1,
643 col: 1,
644 offset: 0,
645 },
646 }
647 );
648 }
649
650 #[test]
651 fn attr_sub_for_individual_author() {
652 let mut parser = Parser::default().with_intrinsic_attribute(
653 "full-author",
654 "John Doe <john@example.com>",
655 ModificationContext::Anywhere,
656 );
657
658 let al = crate::document::AuthorLine::parse(crate::Span::new("{full-author}"), &mut parser);
659
660 assert_eq!(
661 al,
662 AuthorLine {
663 authors: &[Author {
664 name: "John Doe <john@example.com>",
665 firstname: "John Doe <john@example.com>",
666 middlename: None,
667 lastname: None,
668 email: None,
669 },],
670 source: Span {
671 data: "{full-author}",
672 line: 1,
673 col: 1,
674 offset: 0,
675 },
676 }
677 );
678 }
679
680 #[test]
681 fn err_individual_name_components_as_attributes() {
682 let mut parser = Parser::default()
684 .with_intrinsic_attribute("first-name", "Jane", ModificationContext::Anywhere)
685 .with_intrinsic_attribute("last-name", "Smith", ModificationContext::Anywhere)
686 .with_intrinsic_attribute(
687 "author-email",
688 "jane@example.com",
689 ModificationContext::Anywhere,
690 );
691
692 let al = crate::document::AuthorLine::parse(
693 crate::Span::new("{first-name} {last-name} <{author-email}>"),
694 &mut parser,
695 );
696
697 assert_eq!(
698 al,
699 AuthorLine {
700 authors: &[Author {
701 name: "Jane Smith <jane@example.com>",
702 firstname: "Jane Smith <jane@example.com>",
703 middlename: None,
704 lastname: None,
705 email: None,
706 },],
707 source: Span {
708 data: "{first-name} {last-name} <{author-email}>",
709 line: 1,
710 col: 1,
711 offset: 0,
712 },
713 }
714 );
715 }
716
717 #[test]
718 fn sets_author_attributes_single_author_with_all_parts() {
719 let mut parser = Parser::default();
720 let _doc = parser.parse("= Document Title\nKismet R. Lee <kismet@asciidoctor.org>");
721
722 assert_eq!(
724 parser.attribute_value("author"),
725 InterpretedValue::Value("Kismet R. Lee")
726 );
727 assert_eq!(
728 parser.attribute_value("firstname"),
729 InterpretedValue::Value("Kismet")
730 );
731 assert_eq!(
732 parser.attribute_value("middlename"),
733 InterpretedValue::Value("R.")
734 );
735 assert_eq!(
736 parser.attribute_value("lastname"),
737 InterpretedValue::Value("Lee")
738 );
739 assert_eq!(
740 parser.attribute_value("authorinitials"),
741 InterpretedValue::Value("KRL")
742 );
743 assert_eq!(
744 parser.attribute_value("email"),
745 InterpretedValue::Value("kismet@asciidoctor.org")
746 );
747 }
748
749 #[test]
750 fn sets_author_attributes_single_author_without_middle_name() {
751 let mut parser = Parser::default();
752 let _doc = parser.parse("= Document Title\nDoc Writer <doc@example.com>");
753
754 assert_eq!(
755 parser.attribute_value("author"),
756 InterpretedValue::Value("Doc Writer")
757 );
758 assert_eq!(
759 parser.attribute_value("firstname"),
760 InterpretedValue::Value("Doc")
761 );
762 assert_eq!(
763 parser.attribute_value("middlename"),
764 InterpretedValue::Unset
765 );
766 assert_eq!(
767 parser.attribute_value("lastname"),
768 InterpretedValue::Value("Writer")
769 );
770 assert_eq!(
771 parser.attribute_value("authorinitials"),
772 InterpretedValue::Value("DW")
773 );
774 assert_eq!(
775 parser.attribute_value("email"),
776 InterpretedValue::Value("doc@example.com")
777 );
778 }
779
780 #[test]
781 fn sets_author_attributes_single_author_first_name_only() {
782 let mut parser = Parser::default();
783 let _doc = parser.parse("= Document Title\nJohn <john@example.com>");
784
785 assert_eq!(
786 parser.attribute_value("author"),
787 InterpretedValue::Value("John")
788 );
789 assert_eq!(
790 parser.attribute_value("firstname"),
791 InterpretedValue::Value("John")
792 );
793 assert_eq!(
794 parser.attribute_value("middlename"),
795 InterpretedValue::Unset
796 );
797 assert_eq!(parser.attribute_value("lastname"), InterpretedValue::Unset);
798 assert_eq!(
799 parser.attribute_value("authorinitials"),
800 InterpretedValue::Value("J")
801 );
802 assert_eq!(
803 parser.attribute_value("email"),
804 InterpretedValue::Value("john@example.com")
805 );
806 }
807
808 #[test]
809 fn sets_author_attributes_single_author_without_email() {
810 let mut parser = Parser::default();
811 let _doc = parser.parse("= Document Title\nMary Sue Brontë");
812
813 assert_eq!(
814 parser.attribute_value("author"),
815 InterpretedValue::Value("Mary Sue Brontë")
816 );
817 assert_eq!(
818 parser.attribute_value("firstname"),
819 InterpretedValue::Value("Mary")
820 );
821 assert_eq!(
822 parser.attribute_value("middlename"),
823 InterpretedValue::Value("Sue")
824 );
825 assert_eq!(
826 parser.attribute_value("lastname"),
827 InterpretedValue::Value("Brontë")
828 );
829 assert_eq!(
830 parser.attribute_value("authorinitials"),
831 InterpretedValue::Value("MSB")
832 );
833 assert_eq!(parser.attribute_value("email"), InterpretedValue::Unset);
834 }
835
836 #[test]
837 fn sets_author_attributes_multiple_authors() {
838 let mut parser = Parser::default();
839 let _doc = parser
840 .parse("= Document Title\nJane Smith <jane@example.com>; John Doe <john@example.com>");
841
842 assert_eq!(
844 parser.attribute_value("author"),
845 InterpretedValue::Value("Jane Smith")
846 );
847 assert_eq!(
848 parser.attribute_value("firstname"),
849 InterpretedValue::Value("Jane")
850 );
851 assert_eq!(
852 parser.attribute_value("lastname"),
853 InterpretedValue::Value("Smith")
854 );
855 assert_eq!(
856 parser.attribute_value("authorinitials"),
857 InterpretedValue::Value("JS")
858 );
859 assert_eq!(
860 parser.attribute_value("email"),
861 InterpretedValue::Value("jane@example.com")
862 );
863
864 assert_eq!(
867 parser.attribute_value("author_1"),
868 InterpretedValue::Value("Jane Smith")
869 );
870 assert_eq!(
871 parser.attribute_value("firstname_1"),
872 InterpretedValue::Value("Jane")
873 );
874 assert_eq!(
875 parser.attribute_value("lastname_1"),
876 InterpretedValue::Value("Smith")
877 );
878 assert_eq!(
879 parser.attribute_value("authorinitials_1"),
880 InterpretedValue::Value("JS")
881 );
882 assert_eq!(
883 parser.attribute_value("email_1"),
884 InterpretedValue::Value("jane@example.com")
885 );
886
887 assert_eq!(
889 parser.attribute_value("author_2"),
890 InterpretedValue::Value("John Doe")
891 );
892 assert_eq!(
893 parser.attribute_value("firstname_2"),
894 InterpretedValue::Value("John")
895 );
896 assert_eq!(
897 parser.attribute_value("lastname_2"),
898 InterpretedValue::Value("Doe")
899 );
900 assert_eq!(
901 parser.attribute_value("authorinitials_2"),
902 InterpretedValue::Value("JD")
903 );
904 assert_eq!(
905 parser.attribute_value("email_2"),
906 InterpretedValue::Value("john@example.com")
907 );
908
909 assert_eq!(
911 parser.attribute_value("middlename"),
912 InterpretedValue::Unset
913 );
914 assert_eq!(
915 parser.attribute_value("middlename_1"),
916 InterpretedValue::Unset
917 );
918 assert_eq!(
919 parser.attribute_value("middlename_2"),
920 InterpretedValue::Unset
921 );
922
923 assert_eq!(
925 parser.attribute_value("authors"),
926 InterpretedValue::Value("Jane Smith, John Doe")
927 );
928 }
929
930 #[test]
931 fn single_author_sets_combined_authors_but_no_indexed_names() {
932 let mut parser = Parser::default();
933 let _doc = parser.parse("= Document Title\nKismet R. Lee <kismet@asciidoctor.org>");
934
935 assert_eq!(
938 parser.attribute_value("authors"),
939 InterpretedValue::Value("Kismet R. Lee")
940 );
941
942 assert_eq!(parser.attribute_value("author_1"), InterpretedValue::Unset);
945 assert_eq!(
946 parser.attribute_value("firstname_1"),
947 InterpretedValue::Unset
948 );
949 assert_eq!(
950 parser.attribute_value("lastname_1"),
951 InterpretedValue::Unset
952 );
953 }
954
955 #[test]
956 fn sets_author_attributes_unicode_names() {
957 let mut parser = Parser::default();
958 let _doc = parser.parse("= Document Title\nΑλέξανδρος Μ. Παπαδόπουλος");
959
960 assert_eq!(
961 parser.attribute_value("author"),
962 InterpretedValue::Value("Αλέξανδρος Μ. Παπαδόπουλος")
963 );
964 assert_eq!(
965 parser.attribute_value("firstname"),
966 InterpretedValue::Value("Αλέξανδρος")
967 );
968 assert_eq!(
969 parser.attribute_value("middlename"),
970 InterpretedValue::Value("Μ.")
971 );
972 assert_eq!(
973 parser.attribute_value("lastname"),
974 InterpretedValue::Value("Παπαδόπουλος")
975 );
976 assert_eq!(
977 parser.attribute_value("authorinitials"),
978 InterpretedValue::Value("ΑΜΠ")
979 );
980 }
981
982 #[test]
983 fn semicolon_in_character_reference_not_treated_as_separator() {
984 let mut parser = Parser::default();
985
986 let al = crate::document::AuthorLine::parse(
987 crate::Span::new("AsciiDoc®{empty} WG; Another Author"),
988 &mut parser,
989 );
990
991 assert_eq!(
992 al,
993 AuthorLine {
994 authors: &[
995 Author {
996 name: "AsciiDoc® WG",
997 firstname: "AsciiDoc®",
998 middlename: None,
999 lastname: Some("WG"),
1000 email: None,
1001 },
1002 Author {
1003 name: "Another Author",
1004 firstname: "Another",
1005 middlename: None,
1006 lastname: Some("Author"),
1007 email: None,
1008 },
1009 ],
1010 source: Span {
1011 data: "AsciiDoc®{empty} WG; Another Author",
1012 line: 1,
1013 col: 1,
1014 offset: 0,
1015 },
1016 }
1017 );
1018 }
1019
1020 #[test]
1021 fn skips_blank_and_trailing_author_entries() {
1022 let mut parser = Parser::default();
1028
1029 let al = crate::document::AuthorLine::parse(
1030 crate::Span::new("Doc Writer; ; John Smith <john.smith@asciidoc.org>;"),
1031 &mut parser,
1032 );
1033
1034 assert_eq!(
1035 al,
1036 AuthorLine {
1037 authors: &[
1038 Author {
1039 name: "Doc Writer",
1040 firstname: "Doc",
1041 middlename: None,
1042 lastname: Some("Writer"),
1043 email: None,
1044 },
1045 Author {
1046 name: "John Smith",
1047 firstname: "John",
1048 middlename: None,
1049 lastname: Some("Smith"),
1050 email: Some("john.smith@asciidoc.org"),
1051 },
1052 ],
1053 source: Span {
1054 data: "Doc Writer; ; John Smith <john.smith@asciidoc.org>;",
1055 line: 1,
1056 col: 1,
1057 offset: 0,
1058 },
1059 }
1060 );
1061 }
1062
1063 #[test]
1064 fn semicolon_not_followed_by_space_is_single_author() {
1065 let mut parser = Parser::default();
1068
1069 let al = crate::document::AuthorLine::parse(
1070 crate::Span::new("Joe Doe;Smith Johnson"),
1071 &mut parser,
1072 );
1073
1074 assert_eq!(al.authors().len(), 1);
1075 }
1076
1077 #[test]
1078 fn character_reference_followed_by_space_not_treated_as_separator() {
1079 let mut parser = Parser::default();
1082
1083 let al = crate::document::AuthorLine::parse(
1084 crate::Span::new("AsciiDoc® WG; Another Author"),
1085 &mut parser,
1086 );
1087
1088 assert_eq!(
1089 al,
1090 AuthorLine {
1091 authors: &[
1092 Author {
1093 name: "AsciiDoc® WG",
1094 firstname: "AsciiDoc®",
1095 middlename: None,
1096 lastname: Some("WG"),
1097 email: None,
1098 },
1099 Author {
1100 name: "Another Author",
1101 firstname: "Another",
1102 middlename: None,
1103 lastname: Some("Author"),
1104 email: None,
1105 },
1106 ],
1107 source: Span {
1108 data: "AsciiDoc® WG; Another Author",
1109 line: 1,
1110 col: 1,
1111 offset: 0,
1112 },
1113 }
1114 );
1115 }
1116
1117 #[test]
1118 fn invalid_named_entity_does_not_suppress_separator() {
1119 let mut parser = Parser::default();
1123
1124 let al = crate::document::AuthorLine::parse(
1125 crate::Span::new("Alice &Development; Bob"),
1126 &mut parser,
1127 );
1128
1129 assert_eq!(
1130 al,
1131 AuthorLine {
1132 authors: &[
1133 Author {
1134 name: "Alice &Development",
1135 firstname: "Alice",
1136 middlename: None,
1137 lastname: Some("&Development"),
1138 email: None,
1139 },
1140 Author {
1141 name: "Bob",
1142 firstname: "Bob",
1143 middlename: None,
1144 lastname: None,
1145 email: None,
1146 },
1147 ],
1148 source: Span {
1149 data: "Alice &Development; Bob",
1150 line: 1,
1151 col: 1,
1152 offset: 0,
1153 },
1154 }
1155 );
1156 }
1157
1158 #[test]
1159 fn comprehensive_author_attribute_test() {
1160 let mut parser = Parser::default();
1164 let doc = parser.parse("= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy");
1165
1166 assert_eq!(
1167 parser.attribute_value("author"),
1168 InterpretedValue::Value("First Second Last")
1169 );
1170
1171 assert_eq!(
1172 parser.attribute_value("firstname"),
1173 InterpretedValue::Value("First")
1174 );
1175
1176 assert_eq!(
1177 parser.attribute_value("middlename"),
1178 InterpretedValue::Value("Second")
1179 );
1180
1181 assert_eq!(
1182 parser.attribute_value("lastname"),
1183 InterpretedValue::Value("Last")
1184 );
1185
1186 assert_eq!(
1187 parser.attribute_value("authorinitials"),
1188 InterpretedValue::Value("FSL")
1189 );
1190
1191 assert_eq!(
1192 parser.attribute_value("email"),
1193 InterpretedValue::Value("first@example.com")
1194 );
1195
1196 assert_eq!(
1197 parser.attribute_value("author_2"),
1198 InterpretedValue::Value("Only First")
1199 );
1200
1201 assert_eq!(
1202 parser.attribute_value("firstname_2"),
1203 InterpretedValue::Value("Only")
1204 );
1205
1206 assert_eq!(
1207 parser.attribute_value("middlename_2"),
1208 InterpretedValue::Unset
1209 );
1210
1211 assert_eq!(
1212 parser.attribute_value("lastname_2"),
1213 InterpretedValue::Value("First")
1214 );
1215
1216 assert_eq!(
1217 parser.attribute_value("authorinitials_2"),
1218 InterpretedValue::Value("OF")
1219 );
1220
1221 assert_eq!(parser.attribute_value("email_2"), InterpretedValue::Unset);
1222
1223 assert_eq!(
1224 parser.attribute_value("author_3"),
1225 InterpretedValue::Value("A B C")
1226 );
1227
1228 assert_eq!(
1229 parser.attribute_value("firstname_3"),
1230 InterpretedValue::Value("A")
1231 );
1232
1233 assert_eq!(
1234 parser.attribute_value("middlename_3"),
1235 InterpretedValue::Value("B")
1236 );
1237
1238 assert_eq!(
1239 parser.attribute_value("lastname_3"),
1240 InterpretedValue::Value("C")
1241 );
1242
1243 assert_eq!(
1244 parser.attribute_value("authorinitials_3"),
1245 InterpretedValue::Value("ABC")
1246 );
1247
1248 assert_eq!(
1249 parser.attribute_value("email_3"),
1250 InterpretedValue::Value("abc@example.com")
1251 );
1252
1253 assert_eq!(
1254 parser.attribute_value("author_4"),
1255 InterpretedValue::Value("No Email Guy")
1256 );
1257
1258 assert_eq!(
1259 parser.attribute_value("firstname_4"),
1260 InterpretedValue::Value("No")
1261 );
1262
1263 assert_eq!(
1264 parser.attribute_value("middlename_4"),
1265 InterpretedValue::Value("Email")
1266 );
1267
1268 assert_eq!(
1269 parser.attribute_value("lastname_4"),
1270 InterpretedValue::Value("Guy")
1271 );
1272
1273 assert_eq!(
1274 parser.attribute_value("authorinitials_4"),
1275 InterpretedValue::Value("NEG")
1276 );
1277
1278 assert_eq!(parser.attribute_value("email_4"), InterpretedValue::Unset);
1279
1280 assert_eq!(
1281 doc,
1282 Document {
1283 header: Header {
1284 title_source: Some(Span {
1285 data: "Document Title",
1286 line: 1,
1287 col: 3,
1288 offset: 2,
1289 },),
1290 title: Some("Document Title",),
1291 attributes: &[],
1292 author_line: Some(AuthorLine {
1293 authors: &[
1294 Author {
1295 name: "First Second Last",
1296 firstname: "First",
1297 middlename: Some("Second",),
1298 lastname: Some("Last",),
1299 email: Some("first@example.com",),
1300 },
1301 Author {
1302 name: "Only First",
1303 firstname: "Only",
1304 middlename: None,
1305 lastname: Some("First",),
1306 email: None,
1307 },
1308 Author {
1309 name: "A B C",
1310 firstname: "A",
1311 middlename: Some("B",),
1312 lastname: Some("C",),
1313 email: Some("abc@example.com",),
1314 },
1315 Author {
1316 name: "No Email Guy",
1317 firstname: "No",
1318 middlename: Some("Email",),
1319 lastname: Some("Guy",),
1320 email: None,
1321 },
1322 ],
1323 source: Span {
1324 data: "First Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1325 line: 2,
1326 col: 1,
1327 offset: 17,
1328 },
1329 },),
1330 revision_line: None,
1331 comments: &[],
1332 source: Span {
1333 data: "= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1334 line: 1,
1335 col: 1,
1336 offset: 0,
1337 },
1338 },
1339 blocks: &[],
1340 source: Span {
1341 data: "= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1342 line: 1,
1343 col: 1,
1344 offset: 0,
1345 },
1346 warnings: &[],
1347 source_map: SourceMap(&[]),
1348 catalog: Catalog::default(),
1349 }
1350 );
1351 }
1352}