fa5fd055a3a4f4206cab9051e0dc0140dc36371d
[debian/jabref.git] / src / java / net / sf / jabref / AuthorList.java
1 package net.sf.jabref;
2
3 import java.util.Vector;
4 import java.util.WeakHashMap;
5
6 /**
7  * This is an immutable class representing information of either <CODE>author</CODE>
8  * or <CODE>editor</CODE> field in bibtex record.
9  * <p>
10  * Constructor performs parsing of raw field text and stores preformatted data.
11  * Various accessor methods return author/editor field in different formats.
12  * <p>
13  * Parsing algorithm is designed to satisfy two requirements: (a) when author's
14  * name is typed correctly, the result should coincide with the one of BiBTeX;
15  * (b) for erroneous names, output should be reasonable (but may differ from
16  * BiBTeX output). The following rules are used:
17  * <ol>
18  * <li> 'author field' is a sequence of tokens;
19  * <ul>
20  * <li> tokens are separated by sequences of whitespaces (<CODE>Character.isWhitespace(c)==true</CODE>),
21  * commas (,), dashes (-), and tildas (~);
22  * <li> every comma separates tokens, while sequences of other separators are
23  * equivalent to a single separator; for example: "a - b" consists of 2 tokens
24  * ("a" and "b"), while "a,-,b" consists of 3 tokens ("a", "", and "b")
25  * <li> anything enclosed in braces belonges to a single token; for example:
26  * "abc x{a,b,-~ c}x" consists of 2 tokens, while "abc xa,b,-~ cx" consists of 4
27  * tokens ("abc", "xa","b", and "cx");
28  * <li> a token followed immediately by a dash is "dash-terminated" token, and
29  * all other tokens are "space-terminated" tokens; for example: in "a-b- c - d"
30  * tokens "a" and "b" are dash-terminated and "c" and "d" are space-terminated;
31  * <li> for the purposes of splitting of 'author name' into parts and
32  * construction of abbreviation of first name, one needs definitions of first
33  * latter of a token, case of a token, and abbreviation of a token:
34  * <ul>
35  * <li> 'first letter' of a token is the first letter character (<CODE>Character.isLetter(c)==true</CODE>)
36  * that does not belong to a sequence of letters that immediately follows "\"
37  * character, with one exception: if "\" is followed by "aa", "AA", "ae", "AE",
38  * "l", "L", "o", "O", "oe", "OE", "i", or "j" followed by non-letter, the
39  * 'first letter' of a token is a letter that follows "\"; for example: in
40  * "a{x}b" 'first letter' is "a", in "{\"{U}}bel" 'first letter' is "U", in
41  * "{\noopsort{\"o}}xyz" 'first letter' is "o", in "{\AE}x" 'first letter' is
42  * "A", in "\aex\ijk\Oe\j" 'first letter' is "j"; if there is no letter
43  * satisfying the above rule, 'first letter' is undefined;
44  * <li> token is "lower-case" token, if its first letter id defined and is
45  * lower-case (<CODE>Character.isLowerCase(c)==true</CODE>), and token is
46  * "upper-case" token otherwise;
47  * <li> 'abbreviation' of a token is the shortest prefix of the token that (a)
48  * contains 'first letter' and (b) is braces-balanced; if 'first letter' is
49  * undefined, 'abbreviation' is the token itself; in the above examples,
50  * 'abbreviation's are "a", "{\"{U}}", "{\noopsort{\"o}}", "{\AE}",
51  * "\aex\ijk\Oe\j";
52  * </ul>
53  * <li> the behavior based on the above definitions will be erroneous only in
54  * one case: if the first-name-token is "{\noopsort{A}}john", we abbreviate it
55  * as "{\noopsort{A}}.", while BiBTeX produces "j."; fixing this problem,
56  * however, requires processing of the preabmle;
57  * </ul>
58  * <li> 'author name's in 'author field' are subsequences of tokens separated by
59  * token "and" ("and" is case-insensitive); if 'author name' is an empty
60  * sequence of tokens, it is ignored; for examle, both "John Smith and Peter
61  * Black" and "and and John Smith and and Peter Black" consists of 2 'author
62  * name's "Johm Smith" and "Peter Black" (in erroneous situations, this is a bit
63  * different from BiBTeX behavior);
64  * <li> 'author name' consists of 'first-part', 'von-part', 'last-part', and
65  * 'junior-part', each of which is a sequence of tokens; how a sequence of
66  * tokens has to be splitted into these parts, depends the number of commas:
67  * <ul>
68  * <li> no commas, all tokens are upper-case: 'junior-part' and 'von-part' are
69  * empty, 'last-part' consist of the last token, 'first-part' consists of all
70  * other tokens ('first-part' is empty, if 'author name' consists of a single
71  * token); for example, in "John James Smith", 'last-part'="Smith" and
72  * 'first-part'="John James";
73  * <li> no commas, there exists lower-case token: 'junior-part' is empty,
74  * 'first-part' consists of all upper-case tokens before the first lower-case
75  * token, 'von-part' consists of lower-case tokens starting the first lower-case
76  * token and ending the lower-case token that is followed by upper-case token,
77  * 'last-part' consists of the rest of tokens; note that both 'first-part' and
78  * 'latst-part' may be empty and 'last-part' may contain lower-case tokens; for
79  * example: in "von der", 'first-part'='last-part'="", 'von-part'="von der"; in
80  * "Charles Louis Xavier Joseph de la Vall{\'e}e la Poussin",
81  * 'first-part'="Charles Louis Xavier Joseph", 'von-part'="de la",
82  * 'last-part'="Vall{\'e}e la Poussin";
83  * <li> one comma: 'junior-part' is empty, 'first-part' consists of all tokens
84  * after comma, 'von-part' consists of the longest sequence of lower-case tokens
85  * in the very beginning, 'last-part' consists of all tokens after 'von-part'
86  * and before comma; note that any part can be empty; for example: in "de la
87  * Vall{\'e}e la Poussin, Charles Louis Xavier Joseph", 'first-part'="Charles
88  * Louis Xavier Joseph", 'von-part'="de la", 'last-part'="Vall{\'e}e la
89  * Poussin"; in "Joseph de la Vall{\'e}e la Poussin, Charles Louis Xavier",
90  * 'first-part'="Charles Louis Xavier", 'von-part'="", 'last-part'="Joseph de la
91  * Vall{\'e}e la Poussin";
92  * <li> two or more commas (any comma after the second one is ignored; it merely
93  * separates tokens): 'junior-part' consists of all tokens between first and
94  * second commas, 'first-part' consists of all tokens after the second comma,
95  * tokens before the first comma are splitted into 'von-part' and 'last-part'
96  * similarly to the case of one comma; for example: in "de la Vall{\'e}e
97  * Poussin, Jr., Charles Louis Xavier Joseph", 'first-part'="Charles Louis
98  * Xavier Joseph", 'von-part'="de la", 'last-part'="Vall{\'e}e la Poussin", and
99  * 'junior-part'="Jr.";
100  * </ul>
101  * <li> when 'first-part', 'last-part', 'von-part', or 'junior-part' is
102  * reconstructed from tokens, tokens in a part are separated either by space or
103  * by dash, depending on whether the token before the separator was
104  * space-terminated or dash-terminated; for the last token in a part it does not
105  * matter whether it was dash- or space-terminated;
106  * <li> when 'first-part' is abbreviated, each token is replaced by its
107  * abbreviation followed by a period; separators are the same as in the case of
108  * non-abbreviated name; for example: in "Heinrich-{\"{U}}bel Kurt von Minich",
109  * 'first-part'="Heinrich-{\"{U}}bel Kurt", and its abbreviation is "H.-{\"{U}}.
110  * K."
111  * </ol>
112  * 
113  * @see tests.net.sf.jabref.AuthorListTest Testcases for this class.
114  */
115 public class AuthorList {
116
117         private Vector authors; // of Author
118
119         // Variables for storing computed strings, so they only need be created
120         // once:
121         private String authorsNatbib = null, authorsFirstFirstAnds = null,
122                 authorsAlph = null;
123
124         private String[] authorsFirstFirst = new String[4], authorsLastOnly = new String[2],
125         authorLastFirstAnds = new String[2], 
126         authorsLastFirst = new String[4];
127
128         // The following variables are used only during parsing
129
130         private String orig; // the raw bibtex author/editor field
131
132         // the following variables are updated by getToken procedure
133         private int token_start; // index in orig
134
135         private int token_end; // to point 'abc' in ' abc xyz', start=2 and end=5
136
137         // the following variables are valid only if getToken returns TOKEN_WORD
138         private int token_abbr; // end of token abbreviation (always: token_start <
139
140         // token_abbr <= token_end)
141
142         private char token_term; // either space or dash
143
144         private boolean token_case; // true if upper-case token, false if lower-case
145
146         // token
147
148         // Tokens of one author name.
149         // Each token occupies TGL consecutive entries in this vector (as described
150         // below)
151         private Vector tokens;
152
153         private static final int TOKEN_GROUP_LENGTH = 4; // number of entries for
154
155         // a token
156
157         // the following are offsets of an entry in a group of entries for one token
158         private static final int OFFSET_TOKEN = 0; // String -- token itself;
159
160         private static final int OFFSET_TOKEN_ABBR = 1; // String -- token
161
162         // abbreviation;
163
164         private static final int OFFSET_TOKEN_TERM = 2; // Character -- token
165
166         // terminator (either " " or
167         // "-")
168
169         // private static final int OFFSET_TOKEN_CASE = 3; // Boolean --
170         // true=uppercase, false=lowercase
171         // the following are indices in 'tokens' vector created during parsing of
172         // author name
173         // and later used to properly split author name into parts
174         int von_start, // first lower-case token (-1 if all tokens upper-case)
175                 last_start, // first upper-case token after first lower-case token (-1
176                 // if does not exist)
177                 comma_first, // token after first comma (-1 if no commas)
178                 comma_second; // token after second comma (-1 if no commas or only one
179
180         // comma)
181
182         // Token types (returned by getToken procedure)
183         private static final int TOKEN_EOF = 0;
184
185         private static final int TOKEN_AND = 1;
186
187         private static final int TOKEN_COMMA = 2;
188
189         private static final int TOKEN_WORD = 3;
190
191         // Constant Hashtable containing names of TeX special characters
192         private static final java.util.Hashtable tex_names = new java.util.Hashtable();
193         // and static constructor to initialize it
194         static {
195                 tex_names.put("aa", "aa"); // only keys are important in this table
196                 tex_names.put("ae", "ae");
197                 tex_names.put("l", "l");
198                 tex_names.put("o", "o");
199                 tex_names.put("oe", "oe");
200                 tex_names.put("i", "i");
201                 tex_names.put("AA", "AA");
202                 tex_names.put("AE", "AE");
203                 tex_names.put("L", "L");
204                 tex_names.put("O", "O");
205                 tex_names.put("OE", "OE");
206                 tex_names.put("j", "j");
207         }
208
209         static WeakHashMap authorCache = new WeakHashMap();
210
211         /**
212          * Parses the parameter strings and stores preformatted author information.
213          * 
214          * Don't call this constructor directly but rather use the getAuthorList()
215          * method which caches its results.
216          * 
217          * @param bibtex_authors
218          *            contents of either <CODE>author</CODE> or <CODE>editor</CODE>
219          *            bibtex field.
220          */
221         protected AuthorList(String bibtex_authors) {
222                 authors = new Vector(5); // 5 seems to be reasonable initial size
223                 orig = bibtex_authors; // initialization
224                 token_start = 0;
225                 token_end = 0; // of parser
226                 while (token_start < orig.length()) {
227                         Author author = getAuthor();
228                         if (author != null)
229                                 authors.add(author);
230                 }
231                 // clean-up
232                 orig = null;
233                 tokens = null;
234         }
235
236         /**
237          * Retrieve an AuthorList for the given string of authors or editors.
238          * 
239          * This function tries to cache AuthorLists by string passed in.
240          * 
241          * @param authors
242          *            The string of authors or editors in bibtex format to parse.
243          * @return An AuthorList object representing the given authors.
244          */
245         public static AuthorList getAuthorList(String authors) {
246                 AuthorList authorList = (AuthorList) authorCache.get(authors);
247                 if (authorList == null) {
248                         authorList = new AuthorList(authors);
249                         authorCache.put(authors, authorList);
250                 }
251                 return authorList;
252         }
253
254         /**
255          * This is a convenience method for getAuthorsFirstFirst()
256          * 
257          * @see net.sf.jabref.AuthorList#getAuthorsFirstFirst
258          */
259         public static String fixAuthor_firstNameFirstCommas(String authors, boolean abbr,
260                 boolean oxfordComma) {
261                 return getAuthorList(authors).getAuthorsFirstFirst(abbr, oxfordComma);
262         }
263
264         /**
265          * This is a convenience method for getAuthorsFirstFirstAnds()
266          * 
267          * @see net.sf.jabref.AuthorList#getAuthorsFirstFirstAnds
268          */
269         public static String fixAuthor_firstNameFirst(String authors) {
270                 return getAuthorList(authors).getAuthorsFirstFirstAnds();
271         }
272
273         /**
274          * This is a convenience method for getAuthorsLastFirst()
275          * 
276          * @see net.sf.jabref.AuthorList#getAuthorsLastFirst
277          */
278         public static String fixAuthor_lastNameFirstCommas(String authors, boolean abbr,
279                 boolean oxfordComma) {
280                 return getAuthorList(authors).getAuthorsLastFirst(abbr, oxfordComma);
281         }
282
283         /**
284          * This is a convenience method for getAuthorsLastFirstAnds(true)
285          * 
286          * @see net.sf.jabref.AuthorList#getAuthorsLastFirstAnds
287          */
288         public static String fixAuthor_lastNameFirst(String authors) {
289                 return getAuthorList(authors).getAuthorsLastFirstAnds(false);
290         }
291         
292         /**
293          * This is a convenience method for getAuthorsLastFirstAnds()
294          * 
295          * @see net.sf.jabref.AuthorList#getAuthorsLastFirstAnds
296          */
297         public static String fixAuthor_lastNameFirst(String authors, boolean abbreviate) {
298                 return getAuthorList(authors).getAuthorsLastFirstAnds(abbreviate);
299         }
300
301         /**
302          * This is a convenience method for getAuthorsLastOnly()
303          * 
304          * @see net.sf.jabref.AuthorList#getAuthorsLastOnly
305          */
306         public static String fixAuthor_lastNameOnlyCommas(String authors, boolean oxfordComma) {
307                 return getAuthorList(authors).getAuthorsLastOnly(oxfordComma);
308         }
309
310         /**
311          * This is a convenience method for getAuthorsForAlphabetization()
312          * 
313          * @see net.sf.jabref.AuthorList#getAuthorsForAlphabetization
314          */
315         public static String fixAuthorForAlphabetization(String authors) {
316                 return getAuthorList(authors).getAuthorsForAlphabetization();
317         }
318
319         /**
320          * This is a convenience method for getAuthorsNatbib()
321          * 
322          * @see net.sf.jabref.AuthorList#getAuthorsNatbib
323          */
324         public static String fixAuthor_Natbib(String authors) {
325                 return AuthorList.getAuthorList(authors).getAuthorsNatbib();
326         }
327
328         /**
329          * Parses one author name and returns preformatted information.
330          * 
331          * @return Preformatted author name; <CODE>null</CODE> if author name is
332          *         empty.
333          */
334         private Author getAuthor() {
335
336                 tokens = new Vector(); // initialization
337                 von_start = -1;
338                 last_start = -1;
339                 comma_first = -1;
340                 comma_second = -1;
341
342                 // First step: collect tokens in 'tokens' Vector and calculate indices
343                 token_loop: while (true) {
344                         int token = getToken();
345                         cases: switch (token) {
346                         case TOKEN_EOF:
347                         case TOKEN_AND:
348                                 break token_loop;
349                         case TOKEN_COMMA:
350                                 if (comma_first < 0)
351                                         comma_first = tokens.size();
352                                 else if (comma_second < 0)
353                                         comma_second = tokens.size();
354                                 break cases;
355                         case TOKEN_WORD:
356                                 tokens.add(orig.substring(token_start, token_end));
357                                 tokens.add(orig.substring(token_start, token_abbr));
358                                 tokens.add(new Character(token_term));
359                                 tokens.add(Boolean.valueOf(token_case));
360                                 if (comma_first >= 0)
361                                         break cases;
362                                 if (last_start >= 0)
363                                         break cases;
364                                 if (von_start < 0) {
365                                         if (!token_case) {
366                                                 von_start = tokens.size() - TOKEN_GROUP_LENGTH;
367                                                 break cases;
368                                         }
369                                 } else if (last_start < 0 && token_case) {
370                                         last_start = tokens.size() - TOKEN_GROUP_LENGTH;
371                                         break cases;
372                                 }
373                         }
374                 }// end token_loop
375
376                 // Second step: split name into parts (here: calculate indices
377                 // of parts in 'tokens' Vector)
378                 if (tokens.size() == 0)
379                         return null; // no author information
380
381                 // the following negatives indicate absence of the corresponding part
382                 int first_part_start = -1, von_part_start = -1, last_part_start = -1, jr_part_start = -1;
383                 int first_part_end = 0, von_part_end = 0, last_part_end = 0, jr_part_end = 0;
384                 if (comma_first < 0) { // no commas
385                         if (von_start < 0) { // no 'von part'
386                                 last_part_end = tokens.size();
387                                 last_part_start = tokens.size() - TOKEN_GROUP_LENGTH;
388                                 first_part_end = last_part_start;
389                                 if (first_part_end > 0)
390                                         first_part_start = 0;
391                         } else { // 'von part' is present
392                                 if (last_start >= 0) {
393                                         last_part_end = tokens.size();
394                                         last_part_start = last_start;
395                                         von_part_end = last_part_start;
396                                 } else {
397                                         von_part_end = tokens.size();
398                                 }
399                                 von_part_start = von_start;
400                                 first_part_end = von_part_start;
401                                 if (first_part_end > 0)
402                                         first_part_start = 0;
403                         }
404                 } else { // commas are present: it affects only 'first part' and
405                         // 'junior part'
406                         first_part_end = tokens.size();
407                         if (comma_second < 0) { // one comma
408                                 if (comma_first < first_part_end)
409                                         first_part_start = comma_first;
410                         } else { // two or more commas
411                                 if (comma_second < first_part_end)
412                                         first_part_start = comma_second;
413                                 jr_part_end = comma_second;
414                                 if (comma_first < jr_part_end)
415                                         jr_part_start = comma_first;
416                         }
417                         if (von_start != 0) { // no 'von part'
418                                 last_part_end = comma_first;
419                                 if (last_part_end > 0)
420                                         last_part_start = 0;
421                         } else { // 'von part' is present
422                                 if (last_start < 0) {
423                                         von_part_end = comma_first;
424                                 } else {
425                                         last_part_end = comma_first;
426                                         last_part_start = last_start;
427                                         von_part_end = last_part_start;
428                                 }
429                                 von_part_start = 0;
430                         }
431                 }
432
433                 // Third step: do actual splitting, construct Author object
434                 return new Author((first_part_start < 0 ? null : concatTokens(first_part_start,
435                         first_part_end, OFFSET_TOKEN, false)), (first_part_start < 0 ? null : concatTokens(
436                         first_part_start, first_part_end, OFFSET_TOKEN_ABBR, true)), (von_part_start < 0 ? null
437                         : concatTokens(von_part_start, von_part_end, OFFSET_TOKEN, false)),
438                         (last_part_start < 0 ? null : concatTokens(last_part_start, last_part_end,
439                                 OFFSET_TOKEN, false)), (jr_part_start < 0 ? null : concatTokens(jr_part_start,
440                                 jr_part_end, OFFSET_TOKEN, false)));
441         }
442
443         /**
444          * Concatenates list of tokens from 'tokens' Vector. Tokens are separated by
445          * spaces or dashes, dependeing on stored in 'tokens'. Callers always ensure
446          * that start < end; thus, there exists at least one token to be
447          * concatenated.
448          * 
449          * @param start
450          *            index of the first token to be concatenated in 'tokens' Vector
451          *            (always divisible by TOKEN_GROUP_LENGTH).
452          * @param end
453          *            index of the first token not to be concatenated in 'tokens'
454          *            Vector (always divisible by TOKEN_GROUP_LENGTH).
455          * @param offset
456          *            offset within token group (used to request concatenation of
457          *            either full tokens or abbreviation).
458          * @param dot_after
459          *            <CODE>true</CODE> -- add period after each token, <CODE>false</CODE> --
460          *            do not add.
461          * @return the result of concatenation.
462          */
463         private String concatTokens(int start, int end, int offset, boolean dot_after) {
464                 StringBuffer res = new StringBuffer();
465                 // Here we always have start < end
466                 res.append((String) tokens.get(start + offset));
467                 if (dot_after)
468                         res.append('.');
469                 start += TOKEN_GROUP_LENGTH;
470                 while (start < end) {
471                         res.append(tokens.get(start - TOKEN_GROUP_LENGTH + OFFSET_TOKEN_TERM));
472                         res.append((String) tokens.get(start + offset));
473                         if (dot_after)
474                                 res.append('.');
475                         start += TOKEN_GROUP_LENGTH;
476                 }
477                 return res.toString();
478         }
479
480         /**
481          * Parses the next token.
482          * <p>
483          * The string being parsed is stored in global variable <CODE>orig</CODE>,
484          * and position which parsing has to start from is stored in global variable
485          * <CODE>token_end</CODE>; thus, <CODE>token_end</CODE> has to be set
486          * to 0 before the first invocation. Procedure updates <CODE>token_end</CODE>;
487          * thus, subsequent invocations do not require any additional variable
488          * settings.
489          * <p>
490          * The type of the token is returned; if it is <CODE>TOKEN_WORD</CODE>,
491          * additional information is given in global variables <CODE>token_start</CODE>,
492          * <CODE>token_end</CODE>, <CODE>token_abbr</CODE>, <CODE>token_term</CODE>,
493          * and <CODE>token_case</CODE>; namely: <CODE>orig.substring(token_start,token_end)</CODE>
494          * is the thext of the token, <CODE>orig.substring(token_start,token_abbr)</CODE>
495          * is the token abbreviation, <CODE>token_term</CODE> contains token
496          * terminator (space or dash), and <CODE>token_case</CODE> is <CODE>true</CODE>,
497          * if token is upper-case and <CODE>false</CODE> if token is lower-case.
498          * 
499          * @return <CODE>TOKEN_EOF</CODE> -- no more tokens, <CODE>TOKEN_COMMA</CODE> --
500          *         token is comma, <CODE>TOKEN_AND</CODE> -- token is the word
501          *         "and" (or "And", or "aND", etc.), <CODE>TOKEN_WORD</CODE> --
502          *         token is a word; additional information is given in global
503          *         variables <CODE>token_start</CODE>, <CODE>token_end</CODE>,
504          *         <CODE>token_abbr</CODE>, <CODE>token_term</CODE>, and
505          *         <CODE>token_case</CODE>.
506          */
507         private int getToken() {
508                 token_start = token_end;
509                 while (token_start < orig.length()) {
510                         char c = orig.charAt(token_start);
511                         if (!(c == '~' || c == '-' || Character.isWhitespace(c)))
512                                 break;
513                         token_start++;
514                 }
515                 token_end = token_start;
516                 if (token_start >= orig.length())
517                         return TOKEN_EOF;
518                 if (orig.charAt(token_start) == ',') {
519                         token_end++;
520                         return TOKEN_COMMA;
521                 }
522                 token_abbr = -1;
523                 token_term = ' ';
524                 token_case = true;
525                 int braces_level = 0;
526                 int current_backslash = -1;
527                 boolean first_letter_is_found = false;
528                 while (token_end < orig.length()) {
529                         char c = orig.charAt(token_end);
530                         if (c == '{') {
531                                 braces_level++;
532                         }
533                         if (braces_level > 0)
534                                 if (c == '}')
535                                         braces_level--;
536                         if (first_letter_is_found && token_abbr < 0 && braces_level == 0)
537                                 token_abbr = token_end;
538                         if (!first_letter_is_found && current_backslash < 0 && Character.isLetter(c)) {
539                                 token_case = Character.isUpperCase(c);
540                                 first_letter_is_found = true;
541                         }
542                         if (current_backslash >= 0 && !Character.isLetter(c)) {
543                                 if (!first_letter_is_found) {
544                                         String tex_cmd_name = orig.substring(current_backslash + 1, token_end);
545                                         if (tex_names.get(tex_cmd_name) != null) {
546                                                 token_case = Character.isUpperCase(tex_cmd_name.charAt(0));
547                                                 first_letter_is_found = true;
548                                         }
549                                 }
550                                 current_backslash = -1;
551                         }
552                         if (c == '\\')
553                                 current_backslash = token_end;
554                         if (braces_level == 0)
555                                 if (c == ',' || c == '~' || /* c=='-' || */Character.isWhitespace(c))
556                                         break;
557                         // Morten Alver 18 Apr 2006: Removed check for hyphen '-' above to
558                         // prevent
559                         // problems with names like Bailey-Jones getting broken up and
560                         // sorted wrong.
561                         token_end++;
562                 }
563                 if (token_abbr < 0)
564                         token_abbr = token_end;
565                 if (token_end < orig.length() && orig.charAt(token_end) == '-')
566                         token_term = '-';
567                 if (orig.substring(token_start, token_end).equalsIgnoreCase("and"))
568                         return TOKEN_AND;
569                 else
570                         return TOKEN_WORD;
571         }
572
573         /**
574          * Returns the number of author names in this object.
575          * 
576          * @return the number of author names in this object.
577          */
578         public int size() {
579                 return authors.size();
580         }
581
582         /**
583          * Returns the <CODE>Author</CODE> object for the i-th author.
584          * 
585          * @param i
586          *            Index of the author (from 0 to <CODE>size()-1</CODE>).
587          * @return the <CODE>Author</CODE> object.
588          */
589         public Author getAuthor(int i) {
590                 return (Author) authors.get(i);
591         }
592
593         /**
594          * Returns the list of authors in "natbib" format.
595          * <p>
596          * <ul>
597          * <li>"John Smith" -> "Smith"</li>
598          * <li>"John Smith and Black Brown, Peter" ==> "Smith and Black Brown"</li>
599          * <li>"John von Neumann and John Smith and Black Brown, Peter" ==> "von
600          * Neumann et al." </li>
601          * </ul>
602          * 
603          * @return formatted list of authors.
604          */
605         public String getAuthorsNatbib() {
606                 // Check if we've computed this before:
607                 if (authorsNatbib != null)
608                         return authorsNatbib;
609
610                 StringBuffer res = new StringBuffer();
611                 if (size() > 0) {
612                         res.append(getAuthor(0).getLastOnly());
613                         if (size() == 2) {
614                                 res.append(" and ");
615                                 res.append(getAuthor(1).getLastOnly());
616                         } else if (size() > 2) {
617                                 res.append(" et al.");
618                         }
619                 }
620                 authorsNatbib = res.toString();
621                 return authorsNatbib;
622         }
623
624         /**
625          * Returns the list of authors separated by commas with last name only; If
626          * the list consists of three or more authors, "and" is inserted before the
627          * last author's name.
628          * <p>
629          * 
630          * <ul>
631          * <li> "John Smith" ==> "Smith"</li>
632          * <li> "John Smith and Black Brown, Peter" ==> "Smith and Black Brown"</li>
633          * <li> "John von Neumann and John Smith and Black Brown, Peter" ==> "von
634          * Neumann, Smith and Black Brown".</li>
635          * </ul>
636          * 
637          * @param oxfordComma
638          *            Whether to put a comma before the and at the end.
639          * 
640          * @see http://en.wikipedia.org/wiki/Serial_comma For an detailed
641          *      explaination about the Oxford comma.
642          * 
643          * @return formatted list of authors.
644          */
645         public String getAuthorsLastOnly(boolean oxfordComma) {
646
647                 int abbrInt = (oxfordComma ? 0 : 1);
648
649                 // Check if we've computed this before:
650                 if (authorsLastOnly[abbrInt] != null)
651                         return authorsLastOnly[abbrInt];
652
653                 StringBuffer res = new StringBuffer();
654                 if (size() > 0) {
655                         res.append(getAuthor(0).getLastOnly());
656                         int i = 1;
657                         while (i < size() - 1) {
658                                 res.append(", ");
659                                 res.append(getAuthor(i).getLastOnly());
660                                 i++;
661                         }
662                         if (size() > 2 && oxfordComma)
663                                 res.append(",");
664                         if (size() > 1) {
665                                 res.append(" and ");
666                                 res.append(getAuthor(i).getLastOnly());
667                         }
668                 }
669                 authorsLastOnly[abbrInt] = res.toString();
670                 return authorsLastOnly[abbrInt];
671         }
672
673         /**
674          * Returns the list of authors separated by commas with first names after
675          * last name; first names are abbreviated or not depending on parameter. If
676          * the list consists of three or more authors, "and" is inserted before the
677          * last author's name.
678          * <p>
679          * 
680          * <ul>
681          * <li> "John Smith" ==> "Smith, John" or "Smith, J."</li>
682          * <li> "John Smith and Black Brown, Peter" ==> "Smith, John and Black
683          * Brown, Peter" or "Smith, J. and Black Brown, P."</li>
684          * <li> "John von Neumann and John Smith and Black Brown, Peter" ==> "von
685          * Neumann, John, Smith, John and Black Brown, Peter" or "von Neumann, J.,
686          * Smith, J. and Black Brown, P.".</li>
687          * </ul>
688          * 
689          * @param abbreviate
690          *            whether to abbreivate first names.
691          * 
692          * @param oxfordComma
693          *            Whether to put a comma before the and at the end.
694          * 
695          * @see http://en.wikipedia.org/wiki/Serial_comma For an detailed
696          *      explaination about the Oxford comma.
697          * 
698          * @return formatted list of authors.
699          */
700         public String getAuthorsLastFirst(boolean abbreviate, boolean oxfordComma) {
701                 int abbrInt = (abbreviate ? 0 : 1);
702                 abbrInt += (oxfordComma ? 0 : 2);
703
704                 // Check if we've computed this before:
705                 if (authorsLastFirst[abbrInt] != null)
706                         return authorsLastFirst[abbrInt];
707
708                 StringBuffer res = new StringBuffer();
709                 if (size() > 0) {
710                         res.append(getAuthor(0).getLastFirst(abbreviate));
711                         int i = 1;
712                         while (i < size() - 1) {
713                                 res.append(", ");
714                                 res.append(getAuthor(i).getLastFirst(abbreviate));
715                                 i++;
716                         }
717                         if (size() > 2 && oxfordComma)
718                                 res.append(",");
719                         if (size() > 1) {
720                                 res.append(" and ");
721                                 res.append(getAuthor(i).getLastFirst(abbreviate));
722                         }
723                 }
724                 authorsLastFirst[abbrInt] = res.toString();
725                 return authorsLastFirst[abbrInt];
726         }
727
728         /**
729          * Returns the list of authors separated by "and"s with first names after
730          * last name; first names are not abbreviated.
731          * <p>
732          * <ul>
733          * <li>"John Smith" ==> "Smith, John"</li>
734          * <li>"John Smith and Black Brown, Peter" ==> "Smith, John and Black
735          * Brown, Peter"</li>
736          * <li>"John von Neumann and John Smith and Black Brown, Peter" ==> "von
737          * Neumann, John and Smith, John and Black Brown, Peter".</li>
738          * </ul>
739          * 
740          * @return formatted list of authors.
741          */
742         public String getAuthorsLastFirstAnds(boolean abbreviate) {
743
744                 int abbrInt = (abbreviate ? 0 : 1);
745                 // Check if we've computed this before:
746                 if (authorLastFirstAnds[abbrInt] != null)
747                         return authorLastFirstAnds[abbrInt];
748
749                 StringBuffer res = new StringBuffer();
750                 if (size() > 0) {
751                         res.append(getAuthor(0).getLastFirst(abbreviate));
752                         for (int i = 1; i < size(); i++) {
753                                 res.append(" and ");
754                                 res.append(getAuthor(i).getLastFirst(abbreviate));
755                         }
756                 }
757
758                 authorLastFirstAnds[abbrInt] = res.toString();
759                 return authorLastFirstAnds[abbrInt];
760         }
761
762         /**
763          * Returns the list of authors separated by commas with first names before
764          * last name; first names are abbreviated or not depending on parameter. If
765          * the list consists of three or more authors, "and" is inserted before the
766          * last author's name.
767          * <p>
768          * <ul>
769          * <li>"John Smith" ==> "John Smith" or "J. Smith"</li>
770          * <li>"John Smith and Black Brown, Peter" ==> "John Smith and Peter Black
771          * Brown" or "J. Smith and P. Black Brown"</li>
772          * <li> "John von Neumann and John Smith and Black Brown, Peter" ==> "John
773          * von Neumann, John Smith and Peter Black Brown" or "J. von Neumann, J.
774          * Smith and P. Black Brown" </li>
775          * </ul>
776          * 
777          * @param abbr
778          *            whether to abbreivate first names.
779          * 
780          * @param oxfordComma
781          *            Whether to put a comma before the and at the end.
782          * 
783          * @see http://en.wikipedia.org/wiki/Serial_comma For an detailed
784          *      explaination about the Oxford comma.
785          * 
786          * @return formatted list of authors.
787          */
788         public String getAuthorsFirstFirst(boolean abbr, boolean oxfordComma) {
789
790                 int abbrInt = (abbr ? 0 : 1);
791                 abbrInt += (oxfordComma ? 0 : 2);
792
793                 // Check if we've computed this before:
794                 if (authorsFirstFirst[abbrInt] != null)
795                         return authorsFirstFirst[abbrInt];
796
797                 StringBuffer res = new StringBuffer();
798                 if (size() > 0) {
799                         res.append(getAuthor(0).getFirstLast(abbr));
800                         int i = 1;
801                         while (i < size() - 1) {
802                                 res.append(", ");
803                                 res.append(getAuthor(i).getFirstLast(abbr));
804                                 i++;
805                         }
806                         if (size() > 2 && oxfordComma)
807                                 res.append(",");
808                         if (size() > 1) {
809                                 res.append(" and ");
810                                 res.append(getAuthor(i).getFirstLast(abbr));
811                         }
812                 }
813                 authorsFirstFirst[abbrInt] = res.toString();
814                 return authorsFirstFirst[abbrInt];
815         }
816
817         /**
818          * Returns the list of authors separated by "and"s with first names before
819          * last name; first names are not abbreviated.
820          * <p>
821          * <ul>
822          * <li>"John Smith" ==> "John Smith"</li>
823          * <li>"John Smith and Black Brown, Peter" ==> "John Smith and Peter Black
824          * Brown"</li>
825          * <li>"John von Neumann and John Smith and Black Brown, Peter" ==> "John
826          * von Neumann and John Smith and Peter Black Brown" </li>
827          * </li>
828          * 
829          * @return formatted list of authors.
830          */
831         public String getAuthorsFirstFirstAnds() {
832                 // Check if we've computed this before:
833                 if (authorsFirstFirstAnds != null)
834                         return authorsFirstFirstAnds;
835
836                 StringBuffer res = new StringBuffer();
837                 if (size() > 0) {
838                         res.append(getAuthor(0).getFirstLast(false));
839                         for (int i = 1; i < size(); i++) {
840                                 res.append(" and ");
841                                 res.append(getAuthor(i).getFirstLast(false));
842                         }
843                 }
844                 authorsFirstFirstAnds = res.toString();
845                 return authorsFirstFirstAnds;
846         }
847
848         /**
849          * Returns the list of authors in a form suitable for alphabetization. This
850          * means that last names come first, never preceded by "von" particles, and
851          * that any braces are removed. First names are abbreviated so the same name
852          * is treated similarly if abbreviated in one case and not in another. This
853          * form is not intended to be suitable for presentation, only for sorting.
854          * 
855          * <p>
856          * <ul>
857          * <li>"John Smith" ==> "Smith, J.";</li>
858          * 
859          * 
860          * @return formatted list of authors
861          */
862         public String getAuthorsForAlphabetization() {
863                 if (authorsAlph != null)
864                         return authorsAlph;
865
866                 StringBuffer res = new StringBuffer();
867                 if (size() > 0) {
868                         res.append(getAuthor(0).getNameForAlphabetization());
869                         for (int i = 1; i < size(); i++) {
870                                 res.append(" and ");
871                                 res.append(getAuthor(i).getNameForAlphabetization());
872                         }
873                 }
874                 authorsAlph = res.toString();
875                 return authorsAlph;
876         }
877
878         /**
879          * This is an immutable class that keeps information regarding single
880          * author. It is just a container for the information, with very simple
881          * methods to access it.
882          * <p>
883          * Current usage: only methods <code>getLastOnly</code>,
884          * <code>getFirstLast</code>, and <code>getLastFirst</code> are used;
885          * all other methods are provided for completeness.
886          */
887         public static class Author {
888                 private final String first_part;
889
890                 private final String first_abbr;
891
892                 private final String von_part;
893
894                 private final String last_part;
895
896                 private final String jr_part;
897
898                 /**
899                  * Creates the Author object. If any part of the name is absent, <CODE>null</CODE>
900                  * must be passes; otherwise other methods may return erroneous results.
901                  * 
902                  * @param first
903                  *            the first name of the author (may consist of several
904                  *            tokens, like "Charles Louis Xavier Joseph" in "Charles
905                  *            Louis Xavier Joseph de la Vall{\'e}e Poussin")
906                  * @param firstabbr
907                  *            the abbreviated first name of the author (may consist of
908                  *            several tokens, like "C. L. X. J." in "Charles Louis
909                  *            Xavier Joseph de la Vall{\'e}e Poussin"). It is a
910                  *            responsibility of the caller to create a reasonable
911                  *            abbreviation of the first name.
912                  * @param von
913                  *            the von part of the author's name (may consist of several
914                  *            tokens, like "de la" in "Charles Louis Xavier Joseph de la
915                  *            Vall{\'e}e Poussin")
916                  * @param last
917                  *            the lats name of the author (may consist of several
918                  *            tokens, like "Vall{\'e}e Poussin" in "Charles Louis Xavier
919                  *            Joseph de la Vall{\'e}e Poussin")
920                  * @param jr
921                  *            the junior part of the author's name (may consist of
922                  *            several tokens, like "Jr. III" in "Smith, Jr. III, John")
923                  */
924                 public Author(String first, String firstabbr, String von, String last, String jr) {
925                         first_part = first;
926                         first_abbr = firstabbr;
927                         von_part = von;
928                         last_part = last;
929                         jr_part = jr;
930                 }
931
932                 /**
933                  * Returns the first name of the author stored in this object ("First").
934                  * 
935                  * @return first name of the author (may consist of several tokens)
936                  */
937                 public String getFirst() {
938                         return first_part;
939                 }
940
941                 /**
942                  * Returns the abbreviated first name of the author stored in this
943                  * object ("F.").
944                  * 
945                  * @return abbreviated first name of the author (may consist of several
946                  *         tokens)
947                  */
948                 public String getFirstAbbr() {
949                         return first_abbr;
950                 }
951
952                 /**
953                  * Returns the von part of the author's name stored in this object
954                  * ("von").
955                  * 
956                  * @return von part of the author's name (may consist of several tokens)
957                  */
958                 public String getVon() {
959                         return von_part;
960                 }
961
962                 /**
963                  * Returns the last name of the author stored in this object ("Last").
964                  * 
965                  * @return last name of the author (may consist of several tokens)
966                  */
967                 public String getLast() {
968                         return last_part;
969                 }
970
971                 /**
972                  * Returns the junior part of the author's name stored in this object
973                  * ("Jr").
974                  * 
975                  * @return junior part of the author's name (may consist of several
976                  *         tokens) or null if the author does not have a Jr. Part
977                  */
978                 public String getJr() {
979                         return jr_part;
980                 }
981
982                 /**
983                  * Returns von-part followed by last name ("von Last"). If both fields
984                  * were specified as <CODE>null</CODE>, the empty string <CODE>""</CODE>
985                  * is returned.
986                  * 
987                  * @return 'von Last'
988                  */
989                 public String getLastOnly() {
990                         if (von_part == null) {
991                                 return (last_part == null ? "" : last_part);
992                         } else {
993                                 return (last_part == null ? von_part : von_part + " " + last_part);
994                         }
995                 }
996
997                 /**
998                  * Returns the author's name in form 'von Last, Jr., First' with the
999                  * first name full or abbreviated depending on parameter.
1000                  * 
1001                  * @param abbr
1002                  *            <CODE>true</CODE> - abbreviate first name, <CODE>false</CODE> -
1003                  *            do not abbreviate
1004                  * @return 'von Last, Jr., First' (if <CODE>abbr==false</CODE>) or
1005                  *         'von Last, Jr., F.' (if <CODE>abbr==true</CODE>)
1006                  */
1007                 public String getLastFirst(boolean abbr) {
1008                         String res = getLastOnly();
1009                         if (jr_part != null)
1010                                 res += ", " + jr_part;
1011                         if (abbr) {
1012                                 if (first_abbr != null)
1013                                         res += ", " + first_abbr;
1014                         } else {
1015                                 if (first_part != null)
1016                                         res += ", " + first_part;
1017                         }
1018                         return res;
1019                 }
1020
1021                 /**
1022                  * Returns the author's name in form 'First von Last, Jr.' with the
1023                  * first name full or abbreviated depending on parameter.
1024                  * 
1025                  * @param abbr
1026                  *            <CODE>true</CODE> - abbreviate first name, <CODE>false</CODE> -
1027                  *            do not abbreviate
1028                  * @return 'First von Last, Jr.' (if <CODE>abbr==false</CODE>) or 'F.
1029                  *         von Last, Jr.' (if <CODE>abbr==true</CODE>)
1030                  */
1031                 public String getFirstLast(boolean abbr) {
1032                         String res = getLastOnly();
1033                         if (abbr) {
1034                                 res = (first_abbr == null ? "" : first_abbr + " ") + res;
1035                         } else {
1036                                 res = (first_part == null ? "" : first_part + " ") + res;
1037                         }
1038                         if (jr_part != null)
1039                                 res += ", " + jr_part;
1040                         return res;
1041                 }
1042
1043                 /**
1044                  * Returns the name as "Last, Jr, F." omitting the von-part and removing
1045                  * starting braces.
1046                  * 
1047                  * @return "Last, Jr, F." as described above or "" if all these parts
1048                  *         are empty.
1049                  */
1050                 public String getNameForAlphabetization() {
1051                         StringBuffer res = new StringBuffer();
1052                         if (last_part != null)
1053                                 res.append(last_part);
1054                         if (jr_part != null) {
1055                                 res.append(", ");
1056                                 res.append(jr_part);
1057                         }
1058                         if (first_abbr != null) {
1059                                 res.append(", ");
1060                                 res.append(first_abbr);
1061                         }
1062                         while ((res.length() > 0) && (res.charAt(0) == '{'))
1063                                 res.deleteCharAt(0);
1064                         return res.toString();
1065                 }
1066         }// end Author
1067 }// end AuthorList