43d457a7cec84276f74894e3e9008f5820359d15
[debian/jabref.git] / src / java / net / sf / jabref / labelPattern / LabelPatternUtil.java
1 /*
2  * Created on 13-Dec-2003
3  */
4 package net.sf.jabref.labelPattern;
5
6 import java.util.ArrayList;
7 import java.util.StringTokenizer;
8
9 import net.sf.jabref.*;
10 import net.sf.jabref.export.layout.format.RemoveLatexCommands;
11
12 /**
13  *
14  * @author Ulrik Stervbo (ulriks AT ruc.dk)
15  */
16 /**
17  * This is the utility class of the LabelPattern package.
18  * @author Ulrik Stervbo (ulriks AT ruc.dk)
19  */
20 public class LabelPatternUtil {
21
22   // All single characters that we can use for extending a key to make it unique:
23   private static String CHARS = "abcdefghijklmnopqrstuvwxyz";
24
25   public static ArrayList DEFAULT_LABELPATTERN;
26   static {
27       updateDefaultPattern();
28   }
29   //"[auth][year]");
30
31   private static BibtexDatabase _db;
32
33   public static void updateDefaultPattern() {
34       DEFAULT_LABELPATTERN = split(Globals.prefs.get("defaultLabelPattern"));
35   }
36
37   /**
38    * This method takes a string of the form [field1]spacer[field2]spacer[field3]...,
39    * where the fields are the (required) fields of a BibTex entry. The string is split
40    * into firlds and spacers by recognizing the [ and ].
41    *
42    * @param labelPattern a <code>String</code>
43    * @return an <code>ArrayList</code> The first item of the list
44    * is a string representation of the key pattern (the parameter),
45    * the second item is the spacer character (a <code>String</code>).
46    */
47   public static ArrayList split(String labelPattern) {
48     // A holder for fields of the entry to be used for the key
49     ArrayList _alist = new ArrayList();
50
51     // Before we do anything, we add the parameter to the ArrayLIst
52     _alist.add(labelPattern);
53
54     //String[] ss = labelPattern.split("\\[|\\]");
55     StringTokenizer tok = new StringTokenizer(labelPattern, "[]", true);
56     while (tok.hasMoreTokens()) {
57       _alist.add(tok.nextToken());
58
59     }
60     return _alist;
61
62     /*
63        // Regular expresion for identifying the fields
64        Pattern pi = Pattern.compile("\\[\\w*\\]");
65        // Regular expresion for identifying the spacer
66        Pattern ps = Pattern.compile("\\].()*\\[");
67
68        // The matcher for the field
69        Matcher mi = pi.matcher(labelPattern);
70        // The matcher for the spacer char
71        Matcher ms = ps.matcher(labelPattern);
72
73        // Before we do anything, we add the parameter to the ArrayLIst
74        _alist.add(labelPattern);
75
76        // If we can find the spacer character
77        if(ms.find()){
78      String t_spacer = ms.group();
79       // Remove the `]' and `[' at the ends
80       // We cant imagine a spacer of omre than one character.
81       t_spacer = t_spacer.substring(1,2);
82       _alist.add(t_spacer);
83        }
84
85        while(mi.find()){
86      // Get the matched string
87      String t_str = mi.group();
88       int _sindex = 1;
89       int _eindex = t_str.length() -1;
90       // Remove the `[' and `]' at the ends
91       t_str = t_str.substring(_sindex, _eindex);
92      _alist.add(t_str);
93        }
94
95        return _alist;*/
96   }
97
98   /**
99    * Generates a BibTeX label according to the pattern for a given entry type, and
100    * returns the <code>Bibtexentry</code> with the unique label.
101    * @param table a <code>LabelPattern</code>
102    * @param database a <code>BibtexDatabase</code>
103    * @param _entry a <code>BibtexEntry</code>
104    * @return modified Bibtexentry
105    */
106   public static BibtexEntry makeLabel(LabelPattern table,
107                                       BibtexDatabase database,
108                                       BibtexEntry _entry) {
109     _db = database;
110     ArrayList _al;
111     String _spacer, _label;
112     StringBuffer _sb = new StringBuffer();
113     boolean forceUpper = false, forceLower = false;
114
115     try {
116       // get the type of entry
117       String _type = _entry.getType().getName().toLowerCase();
118       // Get the arrayList corrosponding to the type
119       _al = table.getValue(_type);
120       int _alSize = _al.size();
121       boolean field = false;
122       for (int i = 1; i < _alSize; i++) {
123         String val = _al.get(i).toString();
124         if (val.equals("[")) {
125           field = true;
126         }
127         else if (val.equals("]")) {
128           field = false;
129         }
130         else if (field) {
131             /* Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 13-Apr-2004
132                Added new pseudo-fields "shortyear" and "veryshorttitle", and
133                and ":lower" modifier for all fields (in a way easily extended to other modifiers).
134                Helpfile help/LabelPatterns.html updated accordingly.
135             */
136             // check whether there is a modifier on the end such as ":lower"
137             //String modifier = null;
138         String[] parts = val.split(":");
139         val = parts[0];
140         //int _mi = val.indexOf(":");
141             //if(_mi != -1 && _mi != val.length()-1 && _mi != 0) { // ":" is in val and isn't first or last character
142                 //modifier=val.substring(_mi+1);
143                 //val=val.substring(0,_mi);
144             //}
145             StringBuffer _sbvalue = new StringBuffer();
146
147             try {
148
149                if (val.startsWith("auth") || val.startsWith("pureauth")) {
150
151                   // For label code "auth...": if there is no author, but there are editor(s)
152                   // (e.g. for an Edited Book), use the editor(s) instead. (saw27@mrao.cam.ac.uk).
153                   // This is what most people want, but in case somebody really needs a field which
154                   // expands to nothing if there is no author (e.g. someone who uses both "auth"
155                   // and "ed" in the same label), we provide an alternative form "pureauth..." which
156                   // does not do this fallback substitution of editor.
157
158                   String authString;
159                   if(val.startsWith("pure")) {
160                     // remove the "pure" prefix so the remaining code in this section functions correctly
161                     val = val.substring(4);
162                     System.out.println("val is now "+val);
163                     authString = _entry.getField("author").toString(); // use even if empty
164                     System.out.println("Got authString " + authString);
165                   } else {
166                     if (_entry.getField("author") == null || _entry.getField("author").toString().equals("")) {
167                       authString = _entry.getField("editor").toString();
168                     } else {
169                       authString = _entry.getField("author").toString();
170                     }
171                   }
172
173                   // Gather all author-related checks, so we don't have to check all all the time.
174                   if (val.equals("auth")) {
175                     _sbvalue.append(firstAuthor(authString));
176                   }
177                   else if (val.equals("authors")) {
178                     _sbvalue.append(allAuthors(authString));
179                   }
180                   else if (val.equals("authorIni")) {
181                     _sbvalue.append(oneAuthorPlusIni(authString));
182                   }
183                   else if (val.matches("authIni[\\d]+")) {
184                     int num = Integer.parseInt(val.substring(7));
185                                         _sbvalue.append(authIniN(authString,num));
186                   }
187                   else if (val.equals("auth.auth.ea")) {
188                     _sbvalue.append(authAuthEa(authString));
189                   }
190                   else if (val.equals("auth.etal")) {
191                     _sbvalue.append(authEtal(authString));
192                   }
193
194                   else if (val.equals("authshort")) {
195                     _sbvalue.append(authshort(authString));
196                   }
197                   else if (val.matches("auth[\\d]+_[\\d]+")) {
198                     String[] nums = val.substring(4).split("_");
199                     _sbvalue.append(authN_M(authString,
200                                             Integer.parseInt(nums[0]),
201                                             Integer.parseInt(nums[1]) - 1));
202                   }
203                   // authN.  First N chars of the first author's last name.
204                   else if (val.matches("auth\\d+")) {
205                     int num = Integer.parseInt(val.substring(4));
206                     String fa = firstAuthor(authString);
207                     if ( num > fa.length() )
208                       num = fa.length();
209                     _sbvalue.append(fa.substring(0,num));
210                   }
211                   else if (val.matches("authors\\d+")) {
212                     _sbvalue.append(NAuthors(authString,Integer.parseInt(val.substring(7))));
213                   }
214
215                   else {
216                     // This "auth" business was a dead end, so just use it literally:
217                     _sbvalue.append(_entry.getField(val).toString());
218                   }
219                 }
220                 else if (val.startsWith("ed")) {
221                   // Gather all markers starting with "ed" here, so we don't have to check all all the time.
222                   if (val.equals("edtr")) {
223                     _sbvalue.append(firstAuthor(_entry.getField("editor").toString()));
224                   }
225                   else if (val.equals("editors")) {
226                     _sbvalue.append(allAuthors(_entry.getField("editor").toString()));
227                   }
228                   else if (val.equals("editorIni")) {
229                       _sbvalue.append(oneAuthorPlusIni(_entry.getField("editor").toString()));
230                   }
231                   else if (val.matches("edtrIni[\\d]+")) {
232                     int num = Integer.parseInt(val.substring(7));
233                                         _sbvalue.append(authIniN(_entry.getField("editor").toString(),num));
234                   }
235                   else if (val.matches("edtr[\\d]+_[\\d]+")) {
236                     String[] nums = val.substring(4).split("_");
237                     _sbvalue.append(authN_M(_entry.getField("editor").toString(), Integer.parseInt(nums[0]),
238                                             Integer.parseInt(nums[1])-1));
239                   }
240                   else if (val.equals("edtr.edtr.ea")) {
241                     _sbvalue.append(authAuthEa(_entry.getField("editor").toString()));
242                   }
243                   else if (val.equals("edtrshort")) {
244                     _sbvalue.append(authshort(_entry.getField("editor").toString()));
245                   }
246                   // authN.  First N chars of the first author's last name.
247                   else if (val.matches("edtr\\d+")) {
248                     int num = Integer.parseInt(val.substring(4));
249                     String fa = firstAuthor(_entry.getField("editor").toString());
250                     if ( num > fa.length() )
251                       num = fa.length();
252                     _sbvalue.append(fa.substring(0,num));
253                   }
254                   else {
255                     // This "ed" business was a dead end, so just use it literally:
256                     _sbvalue.append(_entry.getField(val).toString());
257                   }
258                 }
259                 else if (val.equals("firstpage")) {
260                   _sbvalue.append(firstPage(_entry.getField("pages").toString()));
261                 }
262                 else if (val.equals("lastpage")) {
263                   _sbvalue.append(lastPage(_entry.getField("pages").toString()));
264                 }
265                 else if (val.equals("shorttitle")) {
266                   _sbvalue.append(getTitleWords(3, _entry));
267                 }
268                 else if (val.equals("shortyear")) {
269                   String ss = _entry.getField("year").toString();
270                   if (ss.startsWith("in") || ss.startsWith("sub")) {
271                     _sbvalue.append("IP");
272                   }
273                   else if (ss.length() > 2) {
274                     _sbvalue.append(ss.substring(ss.length() - 2));
275                   }
276                   else {
277                     _sbvalue.append(ss);
278                   }
279                 }
280
281                 else if(val.equals("veryshorttitle")) {
282                   _sbvalue.append(getTitleWords(1, _entry));
283                 }
284
285                else if (val.matches("keyword\\d+")) {
286                     int num = Integer.parseInt(val.substring(7));
287                     String kw = _entry.getField("keywords").toString();
288                     if (kw != null) {
289                         String[] keywords = kw.split("[,;]\\s*");
290                         if ((num > 0) && (num < keywords.length))
291                             _sbvalue.append(keywords[num-1].trim());
292                     }
293                }
294
295                 // we havent seen any special demands
296                 else {
297                   _sbvalue.append(_entry.getField(val).toString());
298                 }
299             }
300             catch (NullPointerException ex) {
301                     //Globals.logger("Key generator warning: field '" + val + "' empty.");
302             }
303             // apply modifier if present
304         if (parts.length > 1) for (int j=1; j<parts.length; j++) {
305             String modifier = parts[j];
306
307             if(modifier.equals("lower")) {
308                 String tmp = _sbvalue.toString().toLowerCase();
309                 _sbvalue = new StringBuffer(tmp);
310                     }
311             else if (modifier.equals("abbr")) {
312                 // Abbreviate - that is,
313                 //System.out.println(_sbvalue.toString());
314                 StringBuffer abbr = new StringBuffer();
315                 String[] words = _sbvalue.toString().replaceAll("[\\{\\}]","")
316                         .split("[ \r\n]");//split("\\b");
317                 for (int word=0; word<words.length; word++)
318                     if (words[word].length() > 0)
319                         abbr.append(words[word].charAt(0));
320                 _sbvalue = abbr;
321             }
322             else {
323                         Globals.logger("Key generator warning: unknown modifier '"+modifier+"'.");
324                     }
325             }
326
327         _sb.append(_sbvalue);
328
329
330         }
331         else {
332           _sb.append(val);
333         }
334       }
335     }
336
337     catch (Exception e) {
338       System.err.println(e);
339     }
340
341     /**
342      * Edited by Morten Alver 2004.02.04.
343      *
344      * We now have a system for easing key duplicate prevention, so
345      * I am changing this method to conform to it.
346      *
347
348         // here we make sure the key is unique
349        _label = makeLabelUnique(_sb.toString());
350        _entry.setField(Globals.KEY_FIELD, _label);
351        return _entry;
352      */
353
354     // Remove all illegal characters from the key.
355     _label = Util.checkLegalKey(_sb.toString());
356
357     // Patch by Toralf Senger:
358     // Remove Regular Expressions while generating Keys
359     String regex = Globals.prefs.get("KeyPatternRegex");
360     if ((regex != null) && (regex.trim().length() > 0)) {
361         String replacement = Globals.prefs.get("KeyPatternReplacement");
362         _label = _label.replaceAll(regex, replacement);
363     }
364
365     if (forceUpper) {
366       _label = _label.toUpperCase();
367     }
368     if (forceLower) {
369       _label = _label.toLowerCase();
370     }
371
372
373     String oldKey = _entry.getCiteKey();
374     int occurences = _db.getNumberOfKeyOccurences(_label);
375     if ((oldKey != null) && oldKey.equals(_label))
376         occurences--; // No change, so we can accept one dupe.
377
378     // Try new keys until we get a unique one:
379     //if (_db.setCiteKeyForEntry(_entry.getId(), _label)) {
380
381     if (occurences == 0) {
382         // No dupes found, so we can just go ahead.
383         if (!_label.equals(oldKey))
384             _db.setCiteKeyForEntry(_entry.getId(), _label);
385         
386     }
387     else {
388
389         // The key is already in use, so we must modify it.
390         int number = 0;
391
392         String moddedKey = _label+getAddition(number);
393         occurences = _db.getNumberOfKeyOccurences(moddedKey);
394         if ((oldKey != null) && oldKey.equals(moddedKey))
395             occurences--;
396         while (occurences > 0) {
397             number++;
398             moddedKey = _label+getAddition(number);
399
400             occurences = _db.getNumberOfKeyOccurences(moddedKey);
401             if ((oldKey != null) && oldKey.equals(moddedKey))
402                 occurences--;
403         }
404
405         /*
406         char c = 'b';
407         String modKey = _label + "a";
408         occurences = _db.getNumberOfKeyOccurences(modKey);
409         if ((oldKey != null) && oldKey.equals(modKey))
410             occurences--;
411         //while (_db.setCiteKeyForEntry(_entry.getId(), modKey)) {
412         while (occurences > 0) {
413             modKey = _label + ( (char) (c++));
414
415             occurences = _db.getNumberOfKeyOccurences(modKey);
416             if ((oldKey != null) && oldKey.equals(modKey))
417                 occurences--;
418         }
419         */
420
421         if (!moddedKey.equals(oldKey))  {
422             _db.setCiteKeyForEntry(_entry.getId(), moddedKey);
423         }
424     }
425     
426     return _entry;
427     /** End of edit, Morten Alver 2004.02.04.  */
428
429   }
430
431     /**
432      * Computes an appendix to a BibTeX key that could make it unique. We use a-z for numbers
433      * 0-25, and then aa-az, ba-bz, etc.
434      * @param number The appendix number.
435      * @return The String to append.
436      */
437     private static String getAddition(int number) {
438         String s = "";
439         if (number >= CHARS.length()) {
440             int lastChar = number % CHARS.length();
441             return getAddition(number/CHARS.length()-1) + CHARS.substring(lastChar, lastChar+1);
442         } else
443             return CHARS.substring(number, number+1);
444     }
445
446
447     static String getTitleWords(int number, BibtexEntry _entry) {
448     String ss = (new RemoveLatexCommands()).format(_entry.getField("title").toString());
449     StringBuffer _sbvalue = new StringBuffer(),
450         current;
451     int piv=0, words = 0;
452
453     // sorry for being English-centric. I guess these
454     // words should really be an editable preference.
455     mainl: while ((piv < ss.length()) && (words < number)) {
456       current = new StringBuffer();
457       // Get the next word:
458       while ((piv<ss.length()) && !Character.isWhitespace(ss.charAt(piv))) {
459         current.append(ss.charAt(piv));
460         piv++;
461         //System.out.println(".. "+piv+" '"+current.toString()+"'");
462       }
463       piv++;
464       // Check if it is ok:
465       String word = current.toString().trim();
466       if (word.length() == 0)
467         continue mainl;
468       for(int _i=0; _i< Globals.SKIP_WORDS.length; _i++) {
469         if (word.equalsIgnoreCase(Globals.SKIP_WORDS[_i])) {
470           continue mainl;
471         }
472       }
473
474       // If we get here, the word was accepted.
475       if (_sbvalue.length() > 0)
476         _sbvalue.append(" ");
477       _sbvalue.append(word);
478       words++;
479     }
480
481     return _sbvalue.toString();
482   }
483
484
485   /**
486    * Tests whether a given label is unique.
487    * @param label a <code>String</code>
488    * @return <code>true</code> if and only if the <code>label</code> is unique
489    */
490   public static boolean isLabelUnique(String label) {
491     boolean _isUnique = true;
492     BibtexEntry _entry;
493     int _dbSize = _db.getEntryCount();
494     // run through the whole DB and check the key field
495     // if this could be made recursive I would be very happy
496     // it kinda sux that we have to run through the whole db.
497     // The idea here is that if we meet NO match, the _duplicate
498     // field will be true
499
500     for (int i = 0; i < _dbSize; i++) {
501       _entry = _db.getEntryById(String.valueOf(i));
502
503       // oh my! there is a match! we better set the uniqueness to false
504       // and leave this for-loop all together
505       if (_entry.getField(BibtexFields.KEY_FIELD).equals(label)) {
506         _isUnique = false;
507         break;
508       }
509     }
510
511     return _isUnique;
512
513   }
514
515   /**
516    * Gets the last name of the first author/editor
517    * @param authorField a <code>String</code>
518    * @return the sur name of an author/editor
519    */
520   private static String firstAuthor(String authorField) {
521     String author = "";
522     // This code was part of 'ApplyRule' in 'ArticleLabelRule'
523     //String[] tokens = ImportFormatReader.fixAuthor_lastNameFirst(authorField).split("\\band\\b");
524       String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\band\\b");
525     if (tokens.length > 0) { // if author is empty
526       String[] firstAuthor = tokens[0].replaceAll("\\s+", " ").split(" ");
527       author += firstAuthor[0];
528
529     }
530     return author;
531   }
532
533   /**
534    * Gets the last name of all authors/editors
535    * @param authorField a <code>String</code>
536    * @return the sur name of all authors/editors
537    */
538   private static String allAuthors(String authorField) {
539     String author = "";
540     // This code was part of 'ApplyRule' in 'ArticleLabelRule'
541     String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\band\\b");
542     int i = 0;
543     while (tokens.length > i) {
544       // convert lastname, firstname to firstname lastname
545       String[] firstAuthor = tokens[i].replaceAll("\\s+", " ").trim().split(" ");
546       // lastname, firstname
547       author += firstAuthor[0];
548       i++;
549     }
550     return author;
551   }
552
553   /**
554    * Gets the surnames of the first N authors and appends EtAl if there are more than N authors
555    * @param authorField a <code>String</code>
556    * @param n the number of desired authors
557    * @return Gets the surnames of the first N authors and appends EtAl if there are more than N authors
558    */
559   private static String NAuthors(String authorField, int n) {
560             String author = "";
561             // This code was part of 'ApplyRule' in 'ArticleLabelRule'
562             String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\band\\b");
563             int i = 0;
564             while (tokens.length > i && i < n) {
565               // convert lastname, firstname to firstname lastname
566               String[] firstAuthor = tokens[i].replaceAll("\\s+", " ").trim().split(" ");
567               // lastname, firstname
568               author += firstAuthor[0];
569               i++;
570             }
571             if (tokens.length <= n) return author;
572             return author += "EtAl";
573   }
574
575   /**
576    * Gets the first part of the last name of the first
577    * author/editor, and appends the last name initial of the
578    * remaining authors/editors.
579    * @param authorField a <code>String</code>
580    * @return the sur name of all authors/editors
581    */
582   private static String oneAuthorPlusIni(String authorField) {
583     final int CHARS_OF_FIRST = 5;
584     authorField = AuthorList.fixAuthorForAlphabetization(authorField);
585     String author = "";
586     // This code was part of 'ApplyRule' in 'ArticleLabelRule'
587     String[] tokens = authorField.split("\\band\\b");
588     int i = 1;
589     if (tokens.length == 0) {
590       return author;
591     }
592     String[] firstAuthor = tokens[0].replaceAll("\\s+", " ").split(" ");
593     author = firstAuthor[0].substring(0,
594                                       (int) Math.min(CHARS_OF_FIRST,
595         firstAuthor[0].length()));
596     while (tokens.length > i) {
597       // convert lastname, firstname to firstname lastname
598       author += tokens[i].trim().charAt(0);
599       i++;
600     }
601     return author;
602
603   }
604
605   /**
606    * auth.auth.ea format:
607    * Isaac Newton and James Maxwell and Albert Einstein (1960)
608    * Isaac Newton and James Maxwell (1960)
609    *  give:
610    * Newton.Maxwell.ea
611    * Newton.Maxwell
612    */
613   private static String authAuthEa(String authorField) {
614     authorField = AuthorList.fixAuthorForAlphabetization(authorField);
615     StringBuffer author = new StringBuffer();
616
617     String[] tokens = authorField.split("\\band\\b");
618     if (tokens.length == 0) {
619       return "";
620     }
621     author.append((tokens[0].split(","))[0]);
622     if (tokens.length >= 2)
623         author.append(".").append((tokens[1].split(","))[0]);
624     if (tokens.length > 2)
625       author.append(".ea");
626
627     return author.toString();
628   }
629
630   /**
631    * auth.etal format:
632    * Isaac Newton and James Maxwell and Albert Einstein (1960)
633    * Isaac Newton and James Maxwell (1960)
634    *  give:
635    * Newton.etal
636    * Newton.Maxwell
637    */
638   private static String authEtal(String authorField) {
639     authorField = AuthorList.fixAuthorForAlphabetization(authorField);
640     StringBuffer author = new StringBuffer();
641
642     String[] tokens = authorField.split("\\band\\b");
643     if (tokens.length == 0) {
644       return "";
645     }
646     author.append((tokens[0].split(","))[0]);
647     if (tokens.length == 2)
648         author.append(".").append((tokens[1].split(","))[0]);
649     else if (tokens.length > 2)
650       author.append(".etal");
651
652     return author.toString();
653   }
654
655   /**
656    * The first N characters of the Mth author/editor.
657    */
658   private static String authN_M(String authorField, int n, int m) {
659     authorField = AuthorList.fixAuthorForAlphabetization(authorField);
660     StringBuffer author = new StringBuffer();
661
662     String[] tokens = authorField.split("\\band\\b");
663     if ((tokens.length <= m) || (n<0) || (m<0)) {
664       return "";
665     }
666     String lastName = (tokens[m].split(","))[0].trim();
667     //System.out.println(lastName);
668     if (lastName.length() <= n)
669       return lastName;
670     else
671       return lastName.substring(0, n);
672   }
673
674   /**
675    * authshort format:
676    * added by Kolja Brix, kbx@users.sourceforge.net
677    *
678    * given author names
679    *   Isaac Newton and James Maxwell and Albert Einstein and N. Bohr
680    *   Isaac Newton and James Maxwell and Albert Einstein
681    *   Isaac Newton and James Maxwell
682    *   Isaac Newton
683    * yield
684    *   NME+
685    *   NME
686    *   NM
687    *   Newton
688    */
689   private static String authshort(String authorField) {
690     authorField = AuthorList.fixAuthorForAlphabetization(authorField);
691     StringBuffer author = new StringBuffer();
692     String[] tokens = authorField.split("\\band\\b");
693     int i = 0;
694
695     if (tokens.length == 1) {
696
697       author.append(authN_M(authorField,authorField.length(),0));
698
699     } else if (tokens.length >= 2) {
700
701       while (tokens.length > i && i<3) {
702         author.append(authN_M(authorField,1,i));
703         i++;
704       }
705
706       if (tokens.length > 3)
707         author.append("+");
708
709     }
710
711     return author.toString();
712   }
713
714   /**
715    * authIniN format:
716    * Each author gets (N div #authors) chars, the remaining
717    * (N mod #authors) chars are equally distributed to the
718    * authors first in the row.
719    * If (N < #authors), only the fist N authors get mentioned.
720    * a) I. Newton and J. Maxwell and A. Einstein and N. Bohr (..)
721    * b) I. Newton and J. Maxwell and A. Einstein
722    * c) I. Newton and J. Maxwell
723    * d) I. Newton
724    * E.g. authIni4 gives: a) NMEB, b) NeME, c) NeMa, d) Newt
725    */
726   private static String authIniN(String authorField, int n) {
727     authorField = AuthorList.fixAuthorForAlphabetization(authorField);
728     StringBuffer author = new StringBuffer();
729     String[] tokens = authorField.split("\\band\\b");
730     int i = 0;
731     int charsAll = n / tokens.length;
732
733     if (tokens.length == 0) {
734       return author.toString();
735     }
736
737     while (tokens.length > i) {
738       if ( i < (n % tokens.length) ) {
739         author.append(authN_M(authorField,charsAll+1,i));
740       } else {
741         author.append(authN_M(authorField,charsAll,i));
742       }
743       i++;
744     }
745
746     if (author.length() <= n)
747       return author.toString();
748     else
749       return author.toString().substring(0, n);
750   }
751
752
753   /**
754    * Split the pages field into two and return the first one
755    * @param pages a <code>String</code>
756    * @return the first page number
757    */
758   private static String firstPage(String pages) {
759     String[] _pages = pages.split("-");
760     return _pages[0];
761   }
762
763   /**
764    * Split the pages field into two and return the last one
765    * @param pages a <code>String</code>
766    * @return the last page number
767    */
768   private static String lastPage(String pages) {
769     String[] _pages = pages.split("-");
770     return _pages[1];
771   }
772
773 }