3ed173df17bb424ef2a088de1dfca6cdbfbfa7a3
[platform/upstream/groff.git] / src / preproc / refer / ref.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989-2014  Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
4
5 This file is part of groff.
6
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19      
20 #include "refer.h"
21 #include "refid.h"
22 #include "ref.h"
23 #include "token.h"
24
25 static const char *find_day(const char *, const char *, const char **);
26 static int find_month(const char *start, const char *end);
27 static void abbreviate_names(string &);
28
29 #define DEFAULT_ARTICLES "the\000a\000an"
30      
31 string articles(DEFAULT_ARTICLES, sizeof(DEFAULT_ARTICLES));
32
33 // Multiple occurrences of fields are separated by FIELD_SEPARATOR.
34 const char FIELD_SEPARATOR = '\0';
35
36 const char MULTI_FIELD_NAMES[] = "AE";
37 const char *AUTHOR_FIELDS = "AQ";
38
39 enum { OTHER, JOURNAL_ARTICLE, BOOK, ARTICLE_IN_BOOK, TECH_REPORT, BELL_TM };
40
41 const char *reference_types[] = {
42   "other",
43   "journal-article",
44   "book",
45   "article-in-book",
46   "tech-report",
47   "bell-tm",
48 };
49
50 static string temp_fields[256];
51
52 reference::reference(const char *start, int len, reference_id *ridp)
53 : h(0), merged(0), no(-1), field(0), nfields(0), label_ptr(0),
54   computed_authors(0), last_needed_author(-1), nauthors(-1)
55 {
56   int i;
57   for (i = 0; i < 256; i++)
58     field_index[i] = NULL_FIELD_INDEX;
59   if (ridp)
60     rid = *ridp;
61   if (start == 0)
62     return;
63   if (len <= 0)
64     return;
65   const char *end = start + len;
66   const char *ptr = start;
67   assert(*ptr == '%');
68   while (ptr < end) {
69     if (ptr + 1 < end && ptr[1] != '\0'
70         && ((ptr[1] != '%' && ptr[1] == annotation_field)
71             || (ptr + 2 < end && ptr[1] == '%' && ptr[2] != '\0'
72                 && discard_fields.search(ptr[2]) < 0))) {
73       if (ptr[1] == '%')
74         ptr++;
75       string &f = temp_fields[(unsigned char)ptr[1]];
76       ptr += 2;
77       while (ptr < end && csspace(*ptr))
78         ptr++;
79       for (;;) {
80         for (;;) {
81           if (ptr >= end) {
82             f += '\n';
83             break;
84           }
85           f += *ptr;
86           if (*ptr++ == '\n')
87             break;
88         }
89         if (ptr >= end || *ptr == '%')
90           break;
91       }
92     }
93     else if (ptr + 1 < end && ptr[1] != '\0' && ptr[1] != '%'
94              && discard_fields.search(ptr[1]) < 0) {
95       string &f = temp_fields[(unsigned char)ptr[1]];
96       if (f.length() > 0) {
97         if (strchr(MULTI_FIELD_NAMES, ptr[1]) != 0)
98           f += FIELD_SEPARATOR;
99         else
100           f.clear();
101       }
102       ptr += 2;
103       if (ptr < end) {
104         if (*ptr == ' ')
105           ptr++;
106         for (;;) {
107           const char *p = ptr;
108           while (ptr < end && *ptr != '\n')
109             ptr++;
110           // strip trailing white space
111           const char *q = ptr;
112           while (q > p && q[-1] != '\n' && csspace(q[-1]))
113             q--;
114           while (p < q)
115             f += *p++;
116           if (ptr >= end)
117             break;
118           ptr++;
119           if (ptr >= end)
120             break;
121           if (*ptr == '%')
122             break;
123           f += ' ';
124         }
125       }
126     }
127     else {
128       // skip this field
129       for (;;) {
130         while (ptr < end && *ptr++ != '\n')
131           ;
132         if (ptr >= end || *ptr == '%')
133           break;
134       }
135     }
136   }
137   for (i = 0; i < 256; i++)
138     if (temp_fields[i].length() > 0)
139       nfields++;
140   field = new string[nfields];
141   int j = 0;
142   for (i = 0; i < 256; i++)
143     if (temp_fields[i].length() > 0) {
144       field[j].move(temp_fields[i]);
145       if (abbreviate_fields.search(i) >= 0)
146         abbreviate_names(field[j]);
147       field_index[i] = j;
148       j++;
149     }
150 }
151
152 reference::~reference()
153 {
154   if (nfields > 0)
155     ad_delete(nfields) field;
156 }
157
158 // ref is the inline, this is the database ref
159
160 void reference::merge(reference &ref)
161 {
162   int i;
163   for (i = 0; i < 256; i++)
164     if (field_index[i] != NULL_FIELD_INDEX)
165       temp_fields[i].move(field[field_index[i]]);
166   for (i = 0; i < 256; i++)
167     if (ref.field_index[i] != NULL_FIELD_INDEX)
168       temp_fields[i].move(ref.field[ref.field_index[i]]);
169   for (i = 0; i < 256; i++)
170     field_index[i] = NULL_FIELD_INDEX;
171   int old_nfields = nfields;
172   nfields = 0;
173   for (i = 0; i < 256; i++)
174     if (temp_fields[i].length() > 0)
175       nfields++;
176   if (nfields != old_nfields) {
177     if (old_nfields > 0)
178       ad_delete(old_nfields) field;
179     field = new string[nfields];
180   }
181   int j = 0;
182   for (i = 0; i < 256; i++)
183     if (temp_fields[i].length() > 0) {
184       field[j].move(temp_fields[i]);
185       field_index[i] = j;
186       j++;
187     }
188   merged = 1;
189 }
190
191 void reference::insert_field(unsigned char c, string &s)
192 {
193   assert(s.length() > 0);
194   if (field_index[c] != NULL_FIELD_INDEX) {
195     field[field_index[c]].move(s);
196     return;
197   }
198   assert(field_index[c] == NULL_FIELD_INDEX);
199   string *old_field = field;
200   field = new string[nfields + 1];
201   int pos = 0;
202   int i;
203   for (i = 0; i < int(c); i++)
204     if (field_index[i] != NULL_FIELD_INDEX)
205       pos++;
206   for (i = 0; i < pos; i++)
207     field[i].move(old_field[i]);
208   field[pos].move(s);
209   for (i = pos; i < nfields; i++)
210     field[i + 1].move(old_field[i]);
211   if (nfields > 0)
212     ad_delete(nfields) old_field;
213   nfields++;
214   field_index[c] = pos;
215   for (i = c + 1; i < 256; i++)
216     if (field_index[i] != NULL_FIELD_INDEX)
217       field_index[i] += 1;
218 }
219
220 void reference::delete_field(unsigned char c)
221 {
222   if (field_index[c] == NULL_FIELD_INDEX)
223     return;
224   string *old_field = field;
225   field = new string[nfields - 1];
226   int i;
227   for (i = 0; i < int(field_index[c]); i++)
228     field[i].move(old_field[i]);
229   for (i = field_index[c]; i < nfields - 1; i++)
230     field[i].move(old_field[i + 1]);
231   if (nfields > 0)
232     ad_delete(nfields) old_field;
233   nfields--;
234   field_index[c] = NULL_FIELD_INDEX;
235   for (i = c + 1; i < 256; i++)
236     if (field_index[i] != NULL_FIELD_INDEX)
237       field_index[i] -= 1;
238 }
239     
240 void reference::compute_hash_code()
241 {
242   if (!rid.is_null())
243     h = rid.hash();
244   else {
245     h = 0;
246     for (int i = 0; i < nfields; i++)
247       if (field[i].length() > 0) {
248         h <<= 4;
249         h ^= hash_string(field[i].contents(), field[i].length());
250       }
251   }
252 }
253
254 void reference::set_number(int n)
255 {
256   no = n;
257 }
258
259 const char SORT_SEP = '\001';
260 const char SORT_SUB_SEP = '\002';
261 const char SORT_SUB_SUB_SEP = '\003';
262
263 // sep specifies additional word separators
264
265 void sortify_words(const char *s, const char *end, const char *sep,
266                    string &result)
267 {
268   int non_empty = 0;
269   int need_separator = 0;
270   for (;;) {
271     const char *token_start = s;
272     if (!get_token(&s, end))
273       break;
274     if ((s - token_start == 1
275          && (*token_start == ' '
276              || *token_start == '\n'
277              || (sep && *token_start != '\0'
278                  && strchr(sep, *token_start) != 0)))
279         || (s - token_start == 2
280             && token_start[0] == '\\' && token_start[1] == ' ')) {
281       if (non_empty)
282         need_separator = 1;
283     }
284     else {
285       const token_info *ti = lookup_token(token_start, s);
286       if (ti->sortify_non_empty(token_start, s)) {
287         if (need_separator) {
288           result += ' ';
289           need_separator = 0;
290         }
291         ti->sortify(token_start, s, result);
292         non_empty = 1;
293       }
294     }
295   }
296 }
297
298 void sortify_word(const char *s, const char *end, string &result)
299 {
300   for (;;) {
301     const char *token_start = s;
302     if (!get_token(&s, end))
303       break;
304     const token_info *ti = lookup_token(token_start, s);
305     ti->sortify(token_start, s, result);
306   }
307 }
308
309 void sortify_other(const char *s, int len, string &key)
310 {
311   sortify_words(s, s + len, 0, key);
312 }
313
314 void sortify_title(const char *s, int len, string &key)
315 {
316   const char *end = s + len;
317   for (; s < end && (*s == ' ' || *s == '\n'); s++) 
318     ;
319   const char *ptr = s;
320   for (;;) {
321     const char *token_start = ptr;
322     if (!get_token(&ptr, end))
323       break;
324     if (ptr - token_start == 1
325         && (*token_start == ' ' || *token_start == '\n'))
326       break;
327   }
328   if (ptr < end) {
329     unsigned int first_word_len = ptr - s - 1;
330     const char *ae = articles.contents() + articles.length();
331     for (const char *a = articles.contents();
332          a < ae;
333          a = strchr(a, '\0') + 1)
334       if (first_word_len == strlen(a)) {
335         unsigned int j;
336         for (j = 0; j < first_word_len; j++)
337           if (a[j] != cmlower(s[j]))
338             break;
339         if (j >= first_word_len) {
340           s = ptr;
341           for (; s < end && (*s == ' ' || *s == '\n'); s++)
342             ;
343           break;
344         }
345       }
346   }
347   sortify_words(s, end, 0, key);
348 }
349
350 void sortify_name(const char *s, int len, string &key)
351 {
352   const char *last_name_end;
353   const char *last_name = find_last_name(s, s + len, &last_name_end);
354   sortify_word(last_name, last_name_end, key);
355   key += SORT_SUB_SUB_SEP;
356   if (last_name > s)
357     sortify_words(s, last_name, ".", key);
358   key += SORT_SUB_SUB_SEP;
359   if (last_name_end < s + len)
360     sortify_words(last_name_end, s + len, ".,", key);
361 }
362
363 void sortify_date(const char *s, int len, string &key)
364 {
365   const char *year_end;
366   const char *year_start = find_year(s, s + len, &year_end);
367   if (!year_start) {
368     // Things without years are often `forthcoming', so it makes sense
369     // that they sort after things with explicit years.
370     key += 'A';
371     sortify_words(s, s + len, 0, key);
372     return;
373   }
374   int n = year_end - year_start;
375   while (n < 4) {
376     key += '0';
377     n++;
378   }
379   while (year_start < year_end)
380     key += *year_start++;
381   int m = find_month(s, s + len);
382   if (m < 0)
383     return;
384   key += 'A' + m;
385   const char *day_end;
386   const char *day_start = find_day(s, s + len, &day_end);
387   if (!day_start)
388     return;
389   if (day_end - day_start == 1)
390     key += '0';
391   while (day_start < day_end)
392     key += *day_start++;
393 }
394
395 // SORT_{SUB,SUB_SUB}_SEP can creep in from use of @ in label specification.
396
397 void sortify_label(const char *s, int len, string &key)
398 {
399   const char *end = s + len;
400   for (;;) {
401     const char *ptr;
402     for (ptr = s;
403          ptr < end && *ptr != SORT_SUB_SEP && *ptr != SORT_SUB_SUB_SEP;
404          ptr++)
405       ;
406     if (ptr > s)
407       sortify_words(s, ptr, 0, key);
408     s = ptr;
409     if (s >= end)
410       break;
411     key += *s++;
412   }
413 }
414
415 void reference::compute_sort_key()
416 {
417   if (sort_fields.length() == 0)
418     return;
419   sort_fields += '\0';
420   const char *sf = sort_fields.contents();
421   int first_time = 1;
422   while (*sf != '\0') {
423     if (!first_time)
424       sort_key += SORT_SEP;
425     first_time = 0;
426     char f = *sf++;
427     int n = 1;
428     if (*sf == '+') {
429       n = INT_MAX;
430       sf++;
431     }
432     else if (csdigit(*sf)) {
433       char *ptr;
434       long l = strtol(sf, &ptr, 10);
435       if (l == 0 && ptr == sf)
436         ;
437       else {
438         sf = ptr;
439         if (l < 0) {
440           n = 1;
441         }
442         else {
443           n = int(l);
444         }
445       }
446     }
447     if (f == '.')
448       sortify_label(label.contents(), label.length(), sort_key);
449     else if (f == AUTHOR_FIELDS[0])
450       sortify_authors(n, sort_key);
451     else
452       sortify_field(f, n, sort_key);
453   }
454   sort_fields.set_length(sort_fields.length() - 1);
455 }
456
457 void reference::sortify_authors(int n, string &result) const
458 {
459   for (const char *p = AUTHOR_FIELDS; *p != '\0'; p++)
460     if (contains_field(*p)) {
461       sortify_field(*p, n, result);
462       return;
463     }
464   sortify_field(AUTHOR_FIELDS[0], n, result);
465 }
466
467 void reference::canonicalize_authors(string &result) const
468 {
469   int len = result.length();
470   sortify_authors(INT_MAX, result);
471   if (result.length() > len)
472     result += SORT_SUB_SEP;
473 }
474
475 void reference::sortify_field(unsigned char f, int n, string &result) const
476 {
477   typedef void (*sortify_t)(const char *, int, string &);
478   sortify_t sortifier = sortify_other;
479   switch (f) {
480   case 'A':
481   case 'E':
482     sortifier = sortify_name;
483     break;
484   case 'D':
485     sortifier = sortify_date;
486     break;
487   case 'B':
488   case 'J':
489   case 'T':
490     sortifier = sortify_title;
491     break;
492   }
493   int fi = field_index[(unsigned char)f];
494   if (fi != NULL_FIELD_INDEX) {
495     string &str = field[fi];
496     const char *start = str.contents();
497     const char *end = start + str.length();
498     for (int i = 0; i < n && start < end; i++) {
499       const char *p = start;
500       while (start < end && *start != FIELD_SEPARATOR)
501         start++;
502       if (i > 0)
503         result += SORT_SUB_SEP;
504       (*sortifier)(p, start - p, result);
505       if (start < end)
506         start++;
507     }
508   }
509 }
510
511 int compare_reference(const reference &r1, const reference &r2)
512 {
513   assert(r1.no >= 0);
514   assert(r2.no >= 0);
515   const char *s1 = r1.sort_key.contents();
516   int n1 = r1.sort_key.length();
517   const char *s2 = r2.sort_key.contents();
518   int n2 = r2.sort_key.length();
519   for (; n1 > 0 && n2 > 0; --n1, --n2, ++s1, ++s2)
520     if (*s1 != *s2)
521       return (int)(unsigned char)*s1 - (int)(unsigned char)*s2;
522   if (n2 > 0)
523     return -1;
524   if (n1 > 0)
525     return 1;
526   return r1.no - r2.no;
527 }
528
529 int same_reference(const reference &r1, const reference &r2)
530 {
531   if (!r1.rid.is_null() && r1.rid == r2.rid)
532     return 1;
533   if (r1.h != r2.h)
534     return 0;
535   if (r1.nfields != r2.nfields)
536     return 0;
537   int i = 0; 
538   for (i = 0; i < 256; i++)
539     if (r1.field_index != r2.field_index)
540       return 0;
541   for (i = 0; i < r1.nfields; i++)
542     if (r1.field[i] != r2.field[i])
543       return 0;
544   return 1;
545 }
546
547 const char *find_last_name(const char *start, const char *end,
548                            const char **endp)
549 {
550   const char *ptr = start;
551   const char *last_word = start;
552   for (;;) {
553     const char *token_start = ptr;
554     if (!get_token(&ptr, end))
555       break;
556     if (ptr - token_start == 1) {
557       if (*token_start == ',') {
558         *endp = token_start;
559         return last_word;
560       }
561       else if (*token_start == ' ' || *token_start == '\n') {
562         if (ptr < end && *ptr != ' ' && *ptr != '\n')
563           last_word = ptr;
564       }
565     }
566   }
567   *endp = end;
568   return last_word;
569 }
570
571 void abbreviate_name(const char *ptr, const char *end, string &result)
572 {
573   const char *last_name_end;
574   const char *last_name_start = find_last_name(ptr, end, &last_name_end);
575   int need_period = 0;
576   for (;;) {
577     const char *token_start = ptr;
578     if (!get_token(&ptr, last_name_start))
579       break;
580     const token_info *ti = lookup_token(token_start, ptr);
581     if (need_period) {
582       if ((ptr - token_start == 1 && *token_start == ' ')
583           || (ptr - token_start == 2 && token_start[0] == '\\'
584               && token_start[1] == ' '))
585         continue;
586       if (ti->is_upper())
587         result += period_before_initial;
588       else
589         result += period_before_other;
590       need_period = 0;
591     }
592     result.append(token_start, ptr - token_start);
593     if (ti->is_upper()) {
594       const char *lower_ptr = ptr;
595       int first_token = 1;
596       for (;;) {
597         token_start = ptr;
598         if (!get_token(&ptr, last_name_start))
599           break;
600         if ((ptr - token_start == 1 && *token_start == ' ')
601             || (ptr - token_start == 2 && token_start[0] == '\\'
602                 && token_start[1] == ' '))
603           break;
604         ti = lookup_token(token_start, ptr);
605         if (ti->is_hyphen()) {
606           const char *ptr1 = ptr;
607           if (get_token(&ptr1, last_name_start)) {
608             ti = lookup_token(ptr, ptr1);
609             if (ti->is_upper()) {
610               result += period_before_hyphen;
611               result.append(token_start, ptr1 - token_start);
612               ptr = ptr1;
613             }
614           }
615         }
616         else if (ti->is_upper()) {
617           // MacDougal -> MacD.
618           result.append(lower_ptr, ptr - lower_ptr);
619           lower_ptr = ptr;
620           first_token = 1;
621         }
622         else if (first_token && ti->is_accent()) {
623           result.append(token_start, ptr - token_start);
624           lower_ptr = ptr;
625         }
626         first_token = 0;
627       }
628       need_period = 1;
629     }
630   }
631   if (need_period)
632     result += period_before_last_name;
633   result.append(last_name_start, end - last_name_start);
634 }
635
636 static void abbreviate_names(string &result)
637 {
638   string str;
639   str.move(result);
640   const char *ptr = str.contents();
641   const char *end = ptr + str.length();
642   while (ptr < end) {
643     const char *name_end = (char *)memchr(ptr, FIELD_SEPARATOR, end - ptr);
644     if (name_end == 0)
645       name_end = end;
646     abbreviate_name(ptr, name_end, result);
647     if (name_end >= end)
648       break;
649     ptr = name_end + 1;
650     result += FIELD_SEPARATOR;
651   }
652 }
653
654 void reverse_name(const char *ptr, const char *name_end, string &result)
655 {
656   const char *last_name_end;
657   const char *last_name_start = find_last_name(ptr, name_end, &last_name_end);
658   result.append(last_name_start, last_name_end - last_name_start);
659   while (last_name_start > ptr
660          && (last_name_start[-1] == ' ' || last_name_start[-1] == '\n'))
661     last_name_start--;
662   if (last_name_start > ptr) {
663     result += ", ";
664     result.append(ptr, last_name_start - ptr);
665   }
666   if (last_name_end < name_end)
667     result.append(last_name_end, name_end - last_name_end);
668 }
669
670 void reverse_names(string &result, int n)
671 {
672   if (n <= 0)
673     return;
674   string str;
675   str.move(result);
676   const char *ptr = str.contents();
677   const char *end = ptr + str.length();
678   while (ptr < end) {
679     if (--n < 0) {
680       result.append(ptr, end - ptr);
681       break;
682     }
683     const char *name_end = (char *)memchr(ptr, FIELD_SEPARATOR, end - ptr);
684     if (name_end == 0)
685       name_end = end;
686     reverse_name(ptr, name_end, result);
687     if (name_end >= end)
688       break;
689     ptr = name_end + 1;
690     result += FIELD_SEPARATOR;
691   }
692 }
693
694 // Return number of field separators.
695
696 int join_fields(string &f)
697 {
698   const char *ptr = f.contents();
699   int len = f.length();
700   int nfield_seps = 0;
701   int j;
702   for (j = 0; j < len; j++)
703     if (ptr[j] == FIELD_SEPARATOR)
704       nfield_seps++;
705   if (nfield_seps == 0)
706     return 0;
707   string temp;
708   int field_seps_left = nfield_seps;
709   for (j = 0; j < len; j++) {
710     if (ptr[j] == FIELD_SEPARATOR) {
711       if (nfield_seps == 1)
712         temp += join_authors_exactly_two;
713       else if (--field_seps_left == 0)
714         temp += join_authors_last_two;
715       else
716         temp += join_authors_default;
717     }
718     else
719       temp += ptr[j];
720   }
721   f = temp;
722   return nfield_seps;
723 }
724
725 void uppercase(const char *start, const char *end, string &result)
726 {
727   for (;;) {
728     const char *token_start = start;
729     if (!get_token(&start, end))
730       break;
731     const token_info *ti = lookup_token(token_start, start);
732     ti->upper_case(token_start, start, result);
733   }
734 }
735
736 void lowercase(const char *start, const char *end, string &result)
737 {
738   for (;;) {
739     const char *token_start = start;
740     if (!get_token(&start, end))
741       break;
742     const token_info *ti = lookup_token(token_start, start);
743     ti->lower_case(token_start, start, result);
744   }
745 }
746
747 void capitalize(const char *ptr, const char *end, string &result)
748 {
749   int in_small_point_size = 0;
750   for (;;) {
751     const char *start = ptr;
752     if (!get_token(&ptr, end))
753       break;
754     const token_info *ti = lookup_token(start, ptr);
755     const char *char_end = ptr;
756     int is_lower = ti->is_lower();
757     if ((is_lower || ti->is_upper()) && get_token(&ptr, end)) {
758       const token_info *ti2 = lookup_token(char_end, ptr);
759       if (!ti2->is_accent())
760         ptr = char_end;
761     }
762     if (is_lower) {
763       if (!in_small_point_size) {
764         result += "\\s-2";
765         in_small_point_size = 1;
766       }
767       ti->upper_case(start, char_end, result);
768       result.append(char_end, ptr - char_end);
769     }
770     else {
771       if (in_small_point_size) {
772         result += "\\s+2";
773         in_small_point_size = 0;
774       }
775       result.append(start, ptr - start);
776     }
777   }
778   if (in_small_point_size)
779     result += "\\s+2";
780 }
781
782 void capitalize_field(string &str)
783 {
784   string temp;
785   capitalize(str.contents(), str.contents() + str.length(), temp);
786   str.move(temp);
787 }
788
789 int is_terminated(const char *ptr, const char *end)
790 {
791   const char *last_token = end;
792   for (;;) {
793     const char *p = ptr;
794     if (!get_token(&ptr, end))
795       break;
796     last_token = p;
797   }
798   return end - last_token == 1
799     && (*last_token == '.' || *last_token == '!' || *last_token == '?');
800 }
801
802 void reference::output(FILE *fp)
803 {
804   fputs(".]-\n", fp);
805   for (int i = 0; i < 256; i++)
806     if (field_index[i] != NULL_FIELD_INDEX && i != annotation_field) {
807       string &f = field[field_index[i]];
808       if (!csdigit(i)) {
809         int j = reverse_fields.search(i);
810         if (j >= 0) {
811           int n;
812           int len = reverse_fields.length();
813           if (++j < len && csdigit(reverse_fields[j])) {
814             n = reverse_fields[j] - '0';
815             for (++j; j < len && csdigit(reverse_fields[j]); j++)
816               // should check for overflow
817               n = n*10 + reverse_fields[j] - '0';
818           }
819           else 
820             n = INT_MAX;
821           reverse_names(f, n);
822         }
823       }
824       int is_multiple = join_fields(f) > 0;
825       if (capitalize_fields.search(i) >= 0)
826         capitalize_field(f);
827       if (memchr(f.contents(), '\n', f.length()) == 0) {
828         fprintf(fp, ".ds [%c ", i);
829         if (f[0] == ' ' || f[0] == '\\' || f[0] == '"')
830           putc('"', fp);
831         put_string(f, fp);
832         putc('\n', fp);
833       }
834       else {
835         fprintf(fp, ".de [%c\n", i);
836         put_string(f, fp);
837         fputs("..\n", fp);
838       }
839       if (i == 'P') {
840         int multiple_pages = 0;
841         const char *s = f.contents();
842         const char *end = f.contents() + f.length();
843         for (;;) {
844           const char *token_start = s;
845           if (!get_token(&s, end))
846             break;
847           const token_info *ti = lookup_token(token_start, s);
848           if (ti->is_hyphen() || ti->is_range_sep()) {
849             multiple_pages = 1;
850             break;
851           }
852         }
853         fprintf(fp, ".nr [P %d\n", multiple_pages);
854       }
855       else if (i == 'E')
856         fprintf(fp, ".nr [E %d\n", is_multiple);
857     }
858   for (const char *p = "TAO"; *p; p++) {
859     int fi = field_index[(unsigned char)*p];
860     if (fi != NULL_FIELD_INDEX) {
861       string &f = field[fi];
862       fprintf(fp, ".nr [%c %d\n", *p,
863               is_terminated(f.contents(), f.contents() + f.length()));
864     }
865   }
866   int t = classify();
867   fprintf(fp, ".][ %d %s\n", t, reference_types[t]);
868   if (annotation_macro.length() > 0 && annotation_field >= 0
869       && field_index[annotation_field] != NULL_FIELD_INDEX) {
870     putc('.', fp);
871     put_string(annotation_macro, fp);
872     putc('\n', fp);
873     put_string(field[field_index[annotation_field]], fp);
874   }
875 }
876
877 void reference::print_sort_key_comment(FILE *fp)
878 {
879   fputs(".\\\"", fp);
880   put_string(sort_key, fp);
881   putc('\n', fp);
882 }
883
884 const char *find_year(const char *start, const char *end, const char **endp)
885 {
886   for (;;) {
887     while (start < end && !csdigit(*start))
888       start++;
889     const char *ptr = start;
890     if (start == end)
891       break;
892     while (ptr < end && csdigit(*ptr))
893       ptr++;
894     if (ptr - start == 4 || ptr - start == 3
895         || (ptr - start == 2
896             && (start[0] >= '4' || (start[0] == '3' && start[1] >= '2')))) {
897       *endp = ptr;
898       return start;
899     }
900     start = ptr;
901   }
902   return 0;
903 }
904
905 static const char *find_day(const char *start, const char *end,
906                             const char **endp)
907 {
908   for (;;) {
909     while (start < end && !csdigit(*start))
910       start++;
911     const char *ptr = start;
912     if (start == end)
913       break;
914     while (ptr < end && csdigit(*ptr))
915       ptr++;
916     if ((ptr - start == 1 && start[0] != '0')
917         || (ptr - start == 2 &&
918             (start[0] == '1'
919              || start[0] == '2'
920              || (start[0] == '3' && start[1] <= '1')
921              || (start[0] == '0' && start[1] != '0')))) {
922       *endp = ptr;
923       return start;
924     }
925     start = ptr;
926   }
927   return 0;
928 }
929
930 static int find_month(const char *start, const char *end)
931 {
932   static const char *months[] = {
933     "january",
934     "february",
935     "march",
936     "april",
937     "may",
938     "june",
939     "july",
940     "august",
941     "september",
942     "october",
943     "november",
944     "december",
945   };
946   for (;;) {
947     while (start < end && !csalpha(*start))
948       start++;
949     const char *ptr = start;
950     if (start == end)
951       break;
952     while (ptr < end && csalpha(*ptr))
953       ptr++;
954     if (ptr - start >= 3) {
955       for (unsigned int i = 0; i < sizeof(months)/sizeof(months[0]); i++) {
956         const char *q = months[i];
957         const char *p = start;
958         for (; p < ptr; p++, q++)
959           if (cmlower(*p) != *q)
960             break;
961         if (p >= ptr)
962           return i;
963       }
964     }
965     start = ptr;
966   }
967   return -1;
968 }
969
970 int reference::contains_field(char c) const
971 {
972   return field_index[(unsigned char)c] != NULL_FIELD_INDEX;
973 }
974
975 int reference::classify()
976 {
977   if (contains_field('J'))
978     return JOURNAL_ARTICLE;
979   if (contains_field('B'))
980     return ARTICLE_IN_BOOK;
981   if (contains_field('G'))
982     return TECH_REPORT;
983   if (contains_field('R'))
984     return TECH_REPORT;
985   if (contains_field('I'))
986     return BOOK;
987   if (contains_field('M'))
988     return BELL_TM;
989   return OTHER;
990 }
991
992 const char *reference::get_year(const char **endp) const
993 {
994   if (field_index['D'] != NULL_FIELD_INDEX) {
995     string &date = field[field_index['D']];
996     const char *start = date.contents();
997     const char *end = start + date.length();
998     return find_year(start, end, endp);
999   }
1000   else
1001     return 0;
1002 }
1003
1004 const char *reference::get_field(unsigned char c, const char **endp) const
1005 {
1006   if (field_index[c] != NULL_FIELD_INDEX) {
1007     string &f = field[field_index[c]];
1008     const char *start = f.contents();
1009     *endp = start + f.length();
1010     return start;
1011   }
1012   else
1013     return 0;
1014 }
1015
1016 const char *reference::get_date(const char **endp) const
1017 {
1018   return get_field('D', endp);
1019 }
1020
1021 const char *nth_field(int i, const char *start, const char **endp)
1022 {
1023   while (--i >= 0) {
1024     start = (char *)memchr(start, FIELD_SEPARATOR, *endp - start);
1025     if (!start)
1026       return 0;
1027     start++;
1028   }
1029   const char *e = (char *)memchr(start, FIELD_SEPARATOR, *endp - start);
1030   if (e)
1031     *endp = e;
1032   return start;
1033 }
1034
1035 const char *reference::get_author(int i, const char **endp) const
1036 {
1037   for (const char *f = AUTHOR_FIELDS; *f != '\0'; f++) {
1038     const char *start = get_field(*f, endp);
1039     if (start) {
1040       if (strchr(MULTI_FIELD_NAMES, *f) != 0)
1041         return nth_field(i, start, endp);
1042       else if (i == 0)
1043         return start;
1044       else
1045         return 0;
1046     }
1047   }
1048   return 0;
1049 }
1050
1051 const char *reference::get_author_last_name(int i, const char **endp) const
1052 {
1053   for (const char *f = AUTHOR_FIELDS; *f != '\0'; f++) {
1054     const char *start = get_field(*f, endp);
1055     if (start) {
1056       if (strchr(MULTI_FIELD_NAMES, *f) != 0) {
1057         start = nth_field(i, start, endp);
1058         if (!start)
1059           return 0;
1060       }
1061       if (*f == 'A')
1062         return find_last_name(start, *endp, endp);
1063       else
1064         return start;
1065     }
1066   }
1067   return 0;
1068 }
1069
1070 void reference::set_date(string &d)
1071 {
1072   if (d.length() == 0)
1073     delete_field('D');
1074   else
1075     insert_field('D', d);
1076 }
1077
1078 int same_year(const reference &r1, const reference &r2)
1079 {
1080   const char *ye1;
1081   const char *ys1 = r1.get_year(&ye1);
1082   const char *ye2;
1083   const char *ys2 = r2.get_year(&ye2);
1084   if (ys1 == 0) {
1085     if (ys2 == 0)
1086       return same_date(r1, r2);
1087     else
1088       return 0;
1089   }
1090   else if (ys2 == 0)
1091     return 0;
1092   else if (ye1 - ys1 != ye2 - ys2)
1093     return 0;
1094   else
1095     return memcmp(ys1, ys2, ye1 - ys1) == 0;
1096 }
1097
1098 int same_date(const reference &r1, const reference &r2)
1099 {
1100   const char *e1;
1101   const char *s1 = r1.get_date(&e1);
1102   const char *e2;
1103   const char *s2 = r2.get_date(&e2);
1104   if (s1 == 0)
1105     return s2 == 0;
1106   else if (s2 == 0)
1107     return 0;
1108   else if (e1 - s1 != e2 - s2)
1109     return 0;
1110   else
1111     return memcmp(s1, s2, e1 - s1) == 0;
1112 }
1113
1114 const char *reference::get_sort_field(int i, int si, int ssi,
1115                                       const char **endp) const
1116 {
1117   const char *start = sort_key.contents();
1118   const char *end = start + sort_key.length();
1119   if (i < 0) {
1120     *endp = end;
1121     return start;
1122   }
1123   while (--i >= 0) {
1124     start = (char *)memchr(start, SORT_SEP, end - start);
1125     if (!start)
1126       return 0;
1127     start++;
1128   }
1129   const char *e = (char *)memchr(start, SORT_SEP, end - start);
1130   if (e)
1131     end = e;
1132   if (si < 0) {
1133     *endp = end;
1134     return start;
1135   }
1136   while (--si >= 0) {
1137     start = (char *)memchr(start, SORT_SUB_SEP, end - start);
1138     if (!start)
1139       return 0;
1140     start++;
1141   }
1142   e = (char *)memchr(start, SORT_SUB_SEP, end - start);
1143   if (e)
1144     end = e;
1145   if (ssi < 0) {
1146     *endp = end;
1147     return start;
1148   }
1149   while (--ssi >= 0) {
1150     start = (char *)memchr(start, SORT_SUB_SUB_SEP, end - start);
1151     if (!start)
1152       return 0;
1153     start++;
1154   }
1155   e = (char *)memchr(start, SORT_SUB_SUB_SEP, end - start);
1156   if (e)
1157     end = e;
1158   *endp = end;
1159   return start;
1160 }
1161