Skip to main content

asciidoc_parser/document/
author_line.rs

1use std::slice::Iter;
2
3use crate::{HasSpan, Parser, Span, document::Author};
4
5/// The author line is directly after the document title line in the document
6/// header. When the content on this line is structured correctly, the processor
7/// assigns the content to the built-in author and email attributes.
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct AuthorLine<'src> {
10    authors: Vec<Author>,
11    source: Span<'src>,
12}
13
14impl<'src> AuthorLine<'src> {
15    pub(crate) fn parse(source: Span<'src>, parser: &mut Parser) -> Self {
16        let authors: Vec<Author> = source
17            .data()
18            .split("; ")
19            .filter_map(|raw_author| Author::parse(raw_author, parser))
20            .collect();
21
22        for (index, author) in authors.iter().enumerate() {
23            set_nth_attribute(parser, "author", index, author.name());
24            set_nth_attribute(parser, "authorinitials", index, author.initials());
25            set_nth_attribute(parser, "firstname", index, author.firstname());
26
27            if let Some(middlename) = author.middlename() {
28                set_nth_attribute(parser, "middlename", index, middlename);
29            }
30
31            if let Some(lastname) = author.lastname() {
32                set_nth_attribute(parser, "lastname", index, lastname);
33            }
34
35            if let Some(email) = author.email() {
36                set_nth_attribute(parser, "email", index, email);
37            }
38        }
39
40        Self { authors, source }
41    }
42
43    /// Return an iterator over the authors in this author line.
44    pub fn authors(&'src self) -> Iter<'src, Author> {
45        self.authors.iter()
46    }
47}
48
49fn set_nth_attribute<V: AsRef<str>>(parser: &mut Parser, name: &str, index: usize, value: V) {
50    let name = if index == 0 {
51        name.to_string()
52    } else {
53        format!("{name}_{count}", count = index + 1)
54    };
55
56    parser.set_attribute_by_value_from_header(name, value);
57}
58
59impl<'src> HasSpan<'src> for AuthorLine<'src> {
60    fn span(&self) -> Span<'src> {
61        self.source
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use crate::{parser::ModificationContext, tests::prelude::*};
68
69    #[test]
70    fn empty_line() {
71        let mut parser = Parser::default();
72
73        let al = crate::document::AuthorLine::parse(crate::Span::new(""), &mut parser);
74
75        assert_eq!(
76            &al,
77            AuthorLine {
78                authors: &[],
79                source: Span {
80                    data: "",
81                    line: 1,
82                    col: 1,
83                    offset: 0,
84                },
85            }
86        );
87    }
88
89    #[test]
90    fn attr_sub_with_html_encoding_fallback() {
91        // Test case for code coverage: input contains attributes but after expansion
92        // doesn't match AUTHOR regex and contains angle brackets that need HTML
93        // encoding.
94        let mut parser = Parser::default().with_intrinsic_attribute(
95            "weird-content",
96            "Complex <weird> & stuff",
97            ModificationContext::Anywhere,
98        );
99
100        let al = crate::document::AuthorLine::parse(
101            crate::Span::new("Some {weird-content} pattern"),
102            &mut parser,
103        );
104
105        assert_eq!(
106            al,
107            AuthorLine {
108                authors: &[Author {
109                    name: "Some Complex &lt;weird&gt; &amp; stuff pattern",
110                    firstname: "Some Complex &lt;weird&gt; &amp; stuff pattern",
111                    middlename: None,
112                    lastname: None,
113                    email: None,
114                },],
115                source: Span {
116                    data: "Some {weird-content} pattern",
117                    line: 1,
118                    col: 1,
119                    offset: 0,
120                },
121            }
122        );
123    }
124
125    #[test]
126    fn empty_author() {
127        let mut parser = Parser::default();
128
129        let al = crate::document::AuthorLine::parse(
130            crate::Span::new("Author One; ; Author Three"),
131            &mut parser,
132        );
133
134        assert_eq!(
135            al,
136            AuthorLine {
137                authors: &[
138                    Author {
139                        name: "Author One",
140                        firstname: "Author",
141                        middlename: None,
142                        lastname: Some("One",),
143                        email: None,
144                    },
145                    Author {
146                        name: "Author Three",
147                        firstname: "Author",
148                        middlename: None,
149                        lastname: Some("Three",),
150                        email: None,
151                    },
152                ],
153                source: Span {
154                    data: "Author One; ; Author Three",
155                    line: 1,
156                    col: 1,
157                    offset: 0,
158                },
159            }
160        );
161    }
162
163    #[test]
164    fn one_simple_author() {
165        let mut parser = Parser::default();
166
167        let al = crate::document::AuthorLine::parse(
168            crate::Span::new("Kismet R. Lee <kismet@asciidoctor.org>"),
169            &mut parser,
170        );
171
172        assert_eq!(
173            &al,
174            AuthorLine {
175                authors: &[Author {
176                    name: "Kismet R. Lee",
177                    firstname: "Kismet",
178                    middlename: Some("R.",),
179                    lastname: Some("Lee",),
180                    email: Some("kismet@asciidoctor.org",),
181                },],
182                source: Span {
183                    data: "Kismet R. Lee <kismet@asciidoctor.org>",
184                    line: 1,
185                    col: 1,
186                    offset: 0,
187                },
188            }
189        );
190    }
191
192    #[test]
193    fn author_without_middle_name() {
194        let mut parser = Parser::default();
195
196        let al = crate::document::AuthorLine::parse(
197            crate::Span::new("Doc Writer <doc@example.com>"),
198            &mut parser,
199        );
200
201        assert_eq!(
202            al,
203            AuthorLine {
204                authors: &[Author {
205                    name: "Doc Writer",
206                    firstname: "Doc",
207                    middlename: None,
208                    lastname: Some("Writer",),
209                    email: Some("doc@example.com",),
210                },],
211                source: Span {
212                    data: "Doc Writer <doc@example.com>",
213                    line: 1,
214                    col: 1,
215                    offset: 0,
216                },
217            }
218        );
219    }
220
221    #[test]
222    fn too_many_names() {
223        let mut parser = Parser::default();
224
225        let al = crate::document::AuthorLine::parse(
226            crate::Span::new("Four Names Not Supported <doc@example.com>"),
227            &mut parser,
228        );
229
230        assert_eq!(
231            al,
232            AuthorLine {
233                authors: &[Author {
234                    name: "Four Names Not Supported &lt;doc@example.com&gt;",
235                    firstname: "Four Names Not Supported &lt;doc@example.com&gt;",
236                    middlename: None,
237                    lastname: None,
238                    email: None,
239                },],
240                source: Span {
241                    data: "Four Names Not Supported <doc@example.com>",
242                    line: 1,
243                    col: 1,
244                    offset: 0,
245                },
246            }
247        );
248    }
249
250    #[test]
251    fn one_name() {
252        let mut parser = Parser::default();
253
254        let al = crate::document::AuthorLine::parse(
255            crate::Span::new("John <john@example.com>"),
256            &mut parser,
257        );
258
259        assert_eq!(
260            al,
261            AuthorLine {
262                authors: &[Author {
263                    name: "John",
264                    firstname: "John",
265                    middlename: None,
266                    lastname: None,
267                    email: Some("john@example.com",),
268                },],
269                source: Span {
270                    data: "John <john@example.com>",
271                    line: 1,
272                    col: 1,
273                    offset: 0,
274                },
275            }
276        );
277    }
278
279    #[test]
280    fn underscore_join() {
281        let mut parser = Parser::default();
282
283        let al =
284            crate::document::AuthorLine::parse(crate::Span::new("Mary_Sue Brontë"), &mut parser);
285
286        assert_eq!(
287            al,
288            AuthorLine {
289                authors: &[Author {
290                    name: "Mary Sue Brontë", // Underscore replaced with space
291                    firstname: "Mary Sue",   // Underscore replaced with space
292                    middlename: None,
293                    lastname: Some("Brontë",),
294                    email: None,
295                },],
296                source: Span {
297                    data: "Mary_Sue Brontë",
298                    line: 1,
299                    col: 1,
300                    offset: 0,
301                },
302            }
303        );
304    }
305
306    #[test]
307    fn greek() {
308        let mut parser = Parser::default();
309
310        let al = crate::document::AuthorLine::parse(
311            crate::Span::new("Αλέξανδρος Παπαδόπουλος"),
312            &mut parser,
313        );
314
315        assert_eq!(
316            al,
317            AuthorLine {
318                authors: &[Author {
319                    name: "Αλέξανδρος Παπαδόπουλος",
320                    firstname: "Αλέξανδρος",
321                    middlename: None,
322                    lastname: Some("Παπαδόπουλος",),
323                    email: None,
324                },],
325                source: Span {
326                    data: "Αλέξανδρος Παπαδόπουλος",
327                    line: 1,
328                    col: 1,
329                    offset: 0,
330                },
331            }
332        );
333    }
334
335    #[test]
336    fn japanese() {
337        let mut parser = Parser::default();
338
339        let al = crate::document::AuthorLine::parse(crate::Span::new("山田太郎"), &mut parser);
340
341        assert_eq!(
342            al,
343            AuthorLine {
344                authors: &[Author {
345                    name: "山田太郎",
346                    firstname: "山田太郎",
347                    middlename: None,
348                    lastname: None,
349                    email: None,
350                },],
351                source: Span {
352                    data: "山田太郎",
353                    line: 1,
354                    col: 1,
355                    offset: 0,
356                },
357            }
358        );
359    }
360
361    #[test]
362    fn arabic() {
363        let mut parser = Parser::default();
364
365        let al = crate::document::AuthorLine::parse(crate::Span::new("عبد_الله"), &mut parser);
366
367        assert_eq!(
368            al,
369            AuthorLine {
370                authors: &[Author {
371                    name: "عبد الله",      // Underscore replaced with space
372                    firstname: "عبد الله", // Underscore replaced with space
373                    middlename: None,
374                    lastname: None,
375                    email: None,
376                },],
377                source: Span {
378                    data: "عبد_الله",
379                    line: 1,
380                    col: 1,
381                    offset: 0,
382                },
383            }
384        );
385    }
386
387    #[test]
388    fn underscore_replacement_in_all_name_parts() {
389        let mut parser = Parser::default();
390
391        let al = crate::document::AuthorLine::parse(
392            crate::Span::new("John_Paul Mary_Jane Smith_Jones <email@example.com>"),
393            &mut parser,
394        );
395
396        assert_eq!(
397            al,
398            AuthorLine {
399                authors: &[Author {
400                    name: "John Paul Mary Jane Smith Jones", // Underscore replaced with space
401                    firstname: "John Paul",                  // Underscore replaced with space
402                    middlename: Some("Mary Jane"),           // Underscore replaced with space
403                    lastname: Some("Smith Jones"),           // Underscore replaced with space
404                    email: Some("email@example.com"),
405                },],
406                source: Span {
407                    data: "John_Paul Mary_Jane Smith_Jones <email@example.com>",
408                    line: 1,
409                    col: 1,
410                    offset: 0,
411                },
412            }
413        );
414    }
415
416    #[test]
417    fn multiple_underscores_in_name_parts() {
418        let mut parser = Parser::default();
419
420        let al =
421            crate::document::AuthorLine::parse(crate::Span::new("A_B_C D_E_F G_H_I"), &mut parser);
422
423        assert_eq!(
424            al,
425            AuthorLine {
426                authors: &[Author {
427                    name: "A B C D E F G H I", // Multiple underscores replaced with spaces
428                    firstname: "A B C",        // Multiple underscores replaced with spaces
429                    middlename: Some("D E F"), // Multiple underscores replaced with spaces
430                    lastname: Some("G H I"),   // Multiple underscores replaced with spaces
431                    email: None,
432                },],
433                source: Span {
434                    data: "A_B_C D_E_F G_H_I",
435                    line: 1,
436                    col: 1,
437                    offset: 0,
438                },
439            }
440        );
441    }
442
443    #[test]
444    fn underscore_replacement_with_attribute_substitution() {
445        let mut parser = Parser::default()
446            .with_intrinsic_attribute("first-part", "John_Paul", ModificationContext::Anywhere)
447            .with_intrinsic_attribute("last-part", "Smith_Jones", ModificationContext::Anywhere);
448
449        let al = crate::document::AuthorLine::parse(
450            crate::Span::new("{first-part} {last-part} <email@example.com>"),
451            &mut parser,
452        );
453
454        // Note: This test documents the current behavior where attribute substitution
455        // happens after parsing, which results in HTML encoding of the angle brackets.
456        // The underscore replacement should still work on the attribute-substituted
457        // values.
458        assert_eq!(
459            al,
460            AuthorLine {
461                authors: &[Author {
462                    name: "John Paul Smith Jones &lt;email@example.com&gt;", /* Underscore
463                                                                              * replaced
464                                                                              * with space */
465                    firstname: "John Paul Smith Jones &lt;email@example.com&gt;", /* Underscore
466                                                                                   * replaced with
467                                                                                   * space */
468                    middlename: None,
469                    lastname: None,
470                    email: None,
471                },],
472                source: Span {
473                    data: "{first-part} {last-part} <email@example.com>",
474                    line: 1,
475                    col: 1,
476                    offset: 0,
477                },
478            }
479        );
480    }
481
482    #[test]
483    fn attr_sub_email() {
484        let mut parser = Parser::default()
485            .with_intrinsic_attribute(
486                "jane-email",
487                "jane@example.com",
488                ModificationContext::Anywhere,
489            )
490            .with_intrinsic_attribute(
491                "john-email",
492                "john@example.com",
493                ModificationContext::Anywhere,
494            );
495
496        let al = crate::document::AuthorLine::parse(
497            crate::Span::new("Jane Smith <{jane-email}>; John Doe <{john-email}>"),
498            &mut parser,
499        );
500
501        assert_eq!(
502            al,
503            AuthorLine {
504                authors: &[
505                    Author {
506                        name: "Jane Smith",
507                        firstname: "Jane",
508                        middlename: None,
509                        lastname: Some("Smith",),
510                        email: Some("jane@example.com",),
511                    },
512                    Author {
513                        name: "John Doe",
514                        firstname: "John",
515                        middlename: None,
516                        lastname: Some("Doe",),
517                        email: Some("john@example.com",),
518                    },
519                ],
520                source: Span {
521                    data: "Jane Smith <{jane-email}>; John Doe <{john-email}>",
522                    line: 1,
523                    col: 1,
524                    offset: 0,
525                },
526            }
527        );
528    }
529
530    #[test]
531    fn attr_sub_applied_after_parsing() {
532        // This is to demonstrate compatibility with Ruby asciidoctor behavior. In that
533        // implementation, the attribute substitution is applied *after* parsing for
534        // individual authors, which results in the unexpected treatment that the entire
535        // list is one author with mangled results.
536        let mut parser = Parser::default().with_intrinsic_attribute(
537            "author-list",
538            "Jane Smith <jane@example.com>; John Doe <john@example.com>",
539            ModificationContext::Anywhere,
540        );
541
542        let al = crate::document::AuthorLine::parse(crate::Span::new("{author-list}"), &mut parser);
543
544        assert_eq!(
545            al,
546            AuthorLine {
547                authors: &[Author {
548                    name: "Jane Smith <jane@example.com>; John Doe <john@example.com>",
549                    firstname: "Jane Smith <jane@example.com>; John Doe <john@example.com>",
550                    middlename: None,
551                    lastname: None,
552                    email: None,
553                },],
554                source: Span {
555                    data: "{author-list}",
556                    line: 1,
557                    col: 1,
558                    offset: 0,
559                },
560            }
561        );
562    }
563
564    #[test]
565    fn attr_sub_for_individual_author() {
566        let mut parser = Parser::default().with_intrinsic_attribute(
567            "full-author",
568            "John Doe <john@example.com>",
569            ModificationContext::Anywhere,
570        );
571
572        let al = crate::document::AuthorLine::parse(crate::Span::new("{full-author}"), &mut parser);
573
574        assert_eq!(
575            al,
576            AuthorLine {
577                authors: &[Author {
578                    name: "John Doe <john@example.com>",
579                    firstname: "John Doe <john@example.com>",
580                    middlename: None,
581                    lastname: None,
582                    email: None,
583                },],
584                source: Span {
585                    data: "{full-author}",
586                    line: 1,
587                    col: 1,
588                    offset: 0,
589                },
590            }
591        );
592    }
593
594    #[test]
595    fn err_individual_name_components_as_attributes() {
596        // This approach doesn't work in Ruby AsciiDoctor either.
597        let mut parser = Parser::default()
598            .with_intrinsic_attribute("first-name", "Jane", ModificationContext::Anywhere)
599            .with_intrinsic_attribute("last-name", "Smith", ModificationContext::Anywhere)
600            .with_intrinsic_attribute(
601                "author-email",
602                "jane@example.com",
603                ModificationContext::Anywhere,
604            );
605
606        let al = crate::document::AuthorLine::parse(
607            crate::Span::new("{first-name} {last-name} <{author-email}>"),
608            &mut parser,
609        );
610
611        assert_eq!(
612            al,
613            AuthorLine {
614                authors: &[Author {
615                    name: "Jane Smith &lt;jane@example.com&gt;",
616                    firstname: "Jane Smith &lt;jane@example.com&gt;",
617                    middlename: None,
618                    lastname: None,
619                    email: None,
620                },],
621                source: Span {
622                    data: "{first-name} {last-name} <{author-email}>",
623                    line: 1,
624                    col: 1,
625                    offset: 0,
626                },
627            }
628        );
629    }
630
631    #[test]
632    fn sets_author_attributes_single_author_with_all_parts() {
633        let mut parser = Parser::default();
634        let _doc = parser.parse("= Document Title\nKismet R. Lee <kismet@asciidoctor.org>");
635
636        // Primary author attributes
637        assert_eq!(
638            parser.attribute_value("author"),
639            InterpretedValue::Value("Kismet R. Lee")
640        );
641        assert_eq!(
642            parser.attribute_value("firstname"),
643            InterpretedValue::Value("Kismet")
644        );
645        assert_eq!(
646            parser.attribute_value("middlename"),
647            InterpretedValue::Value("R.")
648        );
649        assert_eq!(
650            parser.attribute_value("lastname"),
651            InterpretedValue::Value("Lee")
652        );
653        assert_eq!(
654            parser.attribute_value("authorinitials"),
655            InterpretedValue::Value("KRL")
656        );
657        assert_eq!(
658            parser.attribute_value("email"),
659            InterpretedValue::Value("kismet@asciidoctor.org")
660        );
661    }
662
663    #[test]
664    fn sets_author_attributes_single_author_without_middle_name() {
665        let mut parser = Parser::default();
666        let _doc = parser.parse("= Document Title\nDoc Writer <doc@example.com>");
667
668        assert_eq!(
669            parser.attribute_value("author"),
670            InterpretedValue::Value("Doc Writer")
671        );
672        assert_eq!(
673            parser.attribute_value("firstname"),
674            InterpretedValue::Value("Doc")
675        );
676        assert_eq!(
677            parser.attribute_value("middlename"),
678            InterpretedValue::Unset
679        );
680        assert_eq!(
681            parser.attribute_value("lastname"),
682            InterpretedValue::Value("Writer")
683        );
684        assert_eq!(
685            parser.attribute_value("authorinitials"),
686            InterpretedValue::Value("DW")
687        );
688        assert_eq!(
689            parser.attribute_value("email"),
690            InterpretedValue::Value("doc@example.com")
691        );
692    }
693
694    #[test]
695    fn sets_author_attributes_single_author_first_name_only() {
696        let mut parser = Parser::default();
697        let _doc = parser.parse("= Document Title\nJohn <john@example.com>");
698
699        assert_eq!(
700            parser.attribute_value("author"),
701            InterpretedValue::Value("John")
702        );
703        assert_eq!(
704            parser.attribute_value("firstname"),
705            InterpretedValue::Value("John")
706        );
707        assert_eq!(
708            parser.attribute_value("middlename"),
709            InterpretedValue::Unset
710        );
711        assert_eq!(parser.attribute_value("lastname"), InterpretedValue::Unset);
712        assert_eq!(
713            parser.attribute_value("authorinitials"),
714            InterpretedValue::Value("J")
715        );
716        assert_eq!(
717            parser.attribute_value("email"),
718            InterpretedValue::Value("john@example.com")
719        );
720    }
721
722    #[test]
723    fn sets_author_attributes_single_author_without_email() {
724        let mut parser = Parser::default();
725        let _doc = parser.parse("= Document Title\nMary Sue Brontë");
726
727        assert_eq!(
728            parser.attribute_value("author"),
729            InterpretedValue::Value("Mary Sue Brontë")
730        );
731        assert_eq!(
732            parser.attribute_value("firstname"),
733            InterpretedValue::Value("Mary")
734        );
735        assert_eq!(
736            parser.attribute_value("middlename"),
737            InterpretedValue::Value("Sue")
738        );
739        assert_eq!(
740            parser.attribute_value("lastname"),
741            InterpretedValue::Value("Brontë")
742        );
743        assert_eq!(
744            parser.attribute_value("authorinitials"),
745            InterpretedValue::Value("MSB")
746        );
747        assert_eq!(parser.attribute_value("email"), InterpretedValue::Unset);
748    }
749
750    #[test]
751    fn sets_author_attributes_multiple_authors() {
752        let mut parser = Parser::default();
753        let _doc = parser
754            .parse("= Document Title\nJane Smith <jane@example.com>; John Doe <john@example.com>");
755
756        // First author (primary)
757        assert_eq!(
758            parser.attribute_value("author"),
759            InterpretedValue::Value("Jane Smith")
760        );
761        assert_eq!(
762            parser.attribute_value("firstname"),
763            InterpretedValue::Value("Jane")
764        );
765        assert_eq!(
766            parser.attribute_value("lastname"),
767            InterpretedValue::Value("Smith")
768        );
769        assert_eq!(
770            parser.attribute_value("authorinitials"),
771            InterpretedValue::Value("JS")
772        );
773        assert_eq!(
774            parser.attribute_value("email"),
775            InterpretedValue::Value("jane@example.com")
776        );
777
778        // Second author
779        assert_eq!(
780            parser.attribute_value("author_2"),
781            InterpretedValue::Value("John Doe")
782        );
783        assert_eq!(
784            parser.attribute_value("firstname_2"),
785            InterpretedValue::Value("John")
786        );
787        assert_eq!(
788            parser.attribute_value("lastname_2"),
789            InterpretedValue::Value("Doe")
790        );
791        assert_eq!(
792            parser.attribute_value("authorinitials_2"),
793            InterpretedValue::Value("JD")
794        );
795        assert_eq!(
796            parser.attribute_value("email_2"),
797            InterpretedValue::Value("john@example.com")
798        );
799
800        // Verify middlename attributes are unset for both authors
801        assert_eq!(
802            parser.attribute_value("middlename"),
803            InterpretedValue::Unset
804        );
805        assert_eq!(
806            parser.attribute_value("middlename_2"),
807            InterpretedValue::Unset
808        );
809    }
810
811    #[test]
812    fn sets_author_attributes_unicode_names() {
813        let mut parser = Parser::default();
814        let _doc = parser.parse("= Document Title\nΑλέξανδρος Μ. Παπαδόπουλος");
815
816        assert_eq!(
817            parser.attribute_value("author"),
818            InterpretedValue::Value("Αλέξανδρος Μ. Παπαδόπουλος")
819        );
820        assert_eq!(
821            parser.attribute_value("firstname"),
822            InterpretedValue::Value("Αλέξανδρος")
823        );
824        assert_eq!(
825            parser.attribute_value("middlename"),
826            InterpretedValue::Value("Μ.")
827        );
828        assert_eq!(
829            parser.attribute_value("lastname"),
830            InterpretedValue::Value("Παπαδόπουλος")
831        );
832        assert_eq!(
833            parser.attribute_value("authorinitials"),
834            InterpretedValue::Value("ΑΜΠ")
835        );
836    }
837
838    #[test]
839    fn semicolon_in_character_reference_not_treated_as_separator() {
840        let mut parser = Parser::default();
841
842        let al = crate::document::AuthorLine::parse(
843            crate::Span::new("AsciiDoc&#174;{empty} WG; Another Author"),
844            &mut parser,
845        );
846
847        assert_eq!(
848            al,
849            AuthorLine {
850                authors: &[
851                    Author {
852                        name: "AsciiDoc&#174; WG",
853                        firstname: "AsciiDoc&#174;",
854                        middlename: None,
855                        lastname: Some("WG"),
856                        email: None,
857                    },
858                    Author {
859                        name: "Another Author",
860                        firstname: "Another",
861                        middlename: None,
862                        lastname: Some("Author"),
863                        email: None,
864                    },
865                ],
866                source: Span {
867                    data: "AsciiDoc&#174;{empty} WG; Another Author",
868                    line: 1,
869                    col: 1,
870                    offset: 0,
871                },
872            }
873        );
874    }
875
876    #[test]
877    fn comprehensive_author_attribute_test() {
878        // This test verifies that all author attribute types work correctly for
879        // multiple authors, including edge cases like missing middle names and emails.
880
881        let mut parser = Parser::default();
882        let doc = parser.parse("= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy");
883
884        assert_eq!(
885            parser.attribute_value("author"),
886            InterpretedValue::Value("First Second Last")
887        );
888
889        assert_eq!(
890            parser.attribute_value("firstname"),
891            InterpretedValue::Value("First")
892        );
893
894        assert_eq!(
895            parser.attribute_value("middlename"),
896            InterpretedValue::Value("Second")
897        );
898
899        assert_eq!(
900            parser.attribute_value("lastname"),
901            InterpretedValue::Value("Last")
902        );
903
904        assert_eq!(
905            parser.attribute_value("authorinitials"),
906            InterpretedValue::Value("FSL")
907        );
908
909        assert_eq!(
910            parser.attribute_value("email"),
911            InterpretedValue::Value("first@example.com")
912        );
913
914        assert_eq!(
915            parser.attribute_value("author_2"),
916            InterpretedValue::Value("Only First")
917        );
918
919        assert_eq!(
920            parser.attribute_value("firstname_2"),
921            InterpretedValue::Value("Only")
922        );
923
924        assert_eq!(
925            parser.attribute_value("middlename_2"),
926            InterpretedValue::Unset
927        );
928
929        assert_eq!(
930            parser.attribute_value("lastname_2"),
931            InterpretedValue::Value("First")
932        );
933
934        assert_eq!(
935            parser.attribute_value("authorinitials_2"),
936            InterpretedValue::Value("OF")
937        );
938
939        assert_eq!(parser.attribute_value("email_2"), InterpretedValue::Unset);
940
941        assert_eq!(
942            parser.attribute_value("author_3"),
943            InterpretedValue::Value("A B C")
944        );
945
946        assert_eq!(
947            parser.attribute_value("firstname_3"),
948            InterpretedValue::Value("A")
949        );
950
951        assert_eq!(
952            parser.attribute_value("middlename_3"),
953            InterpretedValue::Value("B")
954        );
955
956        assert_eq!(
957            parser.attribute_value("lastname_3"),
958            InterpretedValue::Value("C")
959        );
960
961        assert_eq!(
962            parser.attribute_value("authorinitials_3"),
963            InterpretedValue::Value("ABC")
964        );
965
966        assert_eq!(
967            parser.attribute_value("email_3"),
968            InterpretedValue::Value("abc@example.com")
969        );
970
971        assert_eq!(
972            parser.attribute_value("author_4"),
973            InterpretedValue::Value("No Email Guy")
974        );
975
976        assert_eq!(
977            parser.attribute_value("firstname_4"),
978            InterpretedValue::Value("No")
979        );
980
981        assert_eq!(
982            parser.attribute_value("middlename_4"),
983            InterpretedValue::Value("Email")
984        );
985
986        assert_eq!(
987            parser.attribute_value("lastname_4"),
988            InterpretedValue::Value("Guy")
989        );
990
991        assert_eq!(
992            parser.attribute_value("authorinitials_4"),
993            InterpretedValue::Value("NEG")
994        );
995
996        assert_eq!(parser.attribute_value("email_4"), InterpretedValue::Unset);
997
998        assert_eq!(
999            doc,
1000            Document {
1001                header: Header {
1002                    title_source: Some(Span {
1003                        data: "Document Title",
1004                        line: 1,
1005                        col: 3,
1006                        offset: 2,
1007                    },),
1008                    title: Some("Document Title",),
1009                    attributes: &[],
1010                    author_line: Some(AuthorLine {
1011                        authors: &[
1012                            Author {
1013                                name: "First Second Last",
1014                                firstname: "First",
1015                                middlename: Some("Second",),
1016                                lastname: Some("Last",),
1017                                email: Some("first@example.com",),
1018                            },
1019                            Author {
1020                                name: "Only First",
1021                                firstname: "Only",
1022                                middlename: None,
1023                                lastname: Some("First",),
1024                                email: None,
1025                            },
1026                            Author {
1027                                name: "A B C",
1028                                firstname: "A",
1029                                middlename: Some("B",),
1030                                lastname: Some("C",),
1031                                email: Some("abc@example.com",),
1032                            },
1033                            Author {
1034                                name: "No Email Guy",
1035                                firstname: "No",
1036                                middlename: Some("Email",),
1037                                lastname: Some("Guy",),
1038                                email: None,
1039                            },
1040                        ],
1041                        source: Span {
1042                            data: "First Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1043                            line: 2,
1044                            col: 1,
1045                            offset: 17,
1046                        },
1047                    },),
1048                    revision_line: None,
1049                    comments: &[],
1050                    source: Span {
1051                        data: "= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1052                        line: 1,
1053                        col: 1,
1054                        offset: 0,
1055                    },
1056                },
1057                blocks: &[],
1058                source: Span {
1059                    data: "= Document Title\nFirst Second Last <first@example.com>; Only First; A B C <abc@example.com>; No Email Guy",
1060                    line: 1,
1061                    col: 1,
1062                    offset: 0,
1063                },
1064                warnings: &[],
1065                source_map: SourceMap(&[]),
1066                catalog: Catalog::default(),
1067            }
1068        );
1069    }
1070}