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