4 /* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2003
5 Free Software Foundation, Inc.
6 Written by James Clark (jjc@jclark.com)
8 This file is part of groff.
10 groff is free software; you can redistribute it and/or modify it under
11 the terms of the GNU General Public License as published by the Free
12 Software Foundation; either version 2, or (at your option) any later
15 groff is distributed in the hope that it will be useful, but WITHOUT ANY
16 WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 You should have received a copy of the GNU General Public License along
21 with groff; see the file COPYING. If not, write to the Free Software
22 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
29 static const char *find_day(const char *, const char *, const char **);
30 static int find_month(const char *start
, const char *end
);
31 static void abbreviate_names(string
&);
33 #define DEFAULT_ARTICLES "the\000a\000an"
35 string
articles(DEFAULT_ARTICLES
, sizeof(DEFAULT_ARTICLES
));
37 // Multiple occurrences of fields are separated by FIELD_SEPARATOR.
38 const char FIELD_SEPARATOR
= '\0';
40 const char MULTI_FIELD_NAMES
[] = "AE";
41 const char *AUTHOR_FIELDS
= "AQ";
43 enum { OTHER
, JOURNAL_ARTICLE
, BOOK
, ARTICLE_IN_BOOK
, TECH_REPORT
, BELL_TM
};
45 const char *reference_types
[] = {
54 static string temp_fields
[256];
56 reference::reference(const char *start
, int len
, reference_id
*ridp
)
57 : h(0), merged(0), no(-1), field(0), nfields(0), label_ptr(0),
58 computed_authors(0), last_needed_author(-1), nauthors(-1)
61 for (i
= 0; i
< 256; i
++)
62 field_index
[i
] = NULL_FIELD_INDEX
;
69 const char *end
= start
+ len
;
70 const char *ptr
= start
;
73 if (ptr
+ 1 < end
&& ptr
[1] != '\0'
74 && ((ptr
[1] != '%' && ptr
[1] == annotation_field
)
75 || (ptr
+ 2 < end
&& ptr
[1] == '%' && ptr
[2] != '\0'
76 && discard_fields
.search(ptr
[2]) < 0))) {
79 string
&f
= temp_fields
[(unsigned char)ptr
[1]];
81 while (ptr
< end
&& csspace(*ptr
))
93 if (ptr
>= end
|| *ptr
== '%')
97 else if (ptr
+ 1 < end
&& ptr
[1] != '\0' && ptr
[1] != '%'
98 && discard_fields
.search(ptr
[1]) < 0) {
99 string
&f
= temp_fields
[(unsigned char)ptr
[1]];
100 if (f
.length() > 0) {
101 if (strchr(MULTI_FIELD_NAMES
, ptr
[1]) != 0)
102 f
+= FIELD_SEPARATOR
;
112 while (ptr
< end
&& *ptr
!= '\n')
114 // strip trailing white space
116 while (q
> p
&& q
[-1] != '\n' && csspace(q
[-1]))
134 while (ptr
< end
&& *ptr
++ != '\n')
136 if (ptr
>= end
|| *ptr
== '%')
141 for (i
= 0; i
< 256; i
++)
142 if (temp_fields
[i
].length() > 0)
144 field
= new string
[nfields
];
146 for (i
= 0; i
< 256; i
++)
147 if (temp_fields
[i
].length() > 0) {
148 field
[j
].move(temp_fields
[i
]);
149 if (abbreviate_fields
.search(i
) >= 0)
150 abbreviate_names(field
[j
]);
156 reference::~reference()
159 ad_delete(nfields
) field
;
162 // ref is the inline, this is the database ref
164 void reference::merge(reference
&ref
)
167 for (i
= 0; i
< 256; i
++)
168 if (field_index
[i
] != NULL_FIELD_INDEX
)
169 temp_fields
[i
].move(field
[field_index
[i
]]);
170 for (i
= 0; i
< 256; i
++)
171 if (ref
.field_index
[i
] != NULL_FIELD_INDEX
)
172 temp_fields
[i
].move(ref
.field
[ref
.field_index
[i
]]);
173 for (i
= 0; i
< 256; i
++)
174 field_index
[i
] = NULL_FIELD_INDEX
;
175 int old_nfields
= nfields
;
177 for (i
= 0; i
< 256; i
++)
178 if (temp_fields
[i
].length() > 0)
180 if (nfields
!= old_nfields
) {
182 ad_delete(old_nfields
) field
;
183 field
= new string
[nfields
];
186 for (i
= 0; i
< 256; i
++)
187 if (temp_fields
[i
].length() > 0) {
188 field
[j
].move(temp_fields
[i
]);
195 void reference::insert_field(unsigned char c
, string
&s
)
197 assert(s
.length() > 0);
198 if (field_index
[c
] != NULL_FIELD_INDEX
) {
199 field
[field_index
[c
]].move(s
);
202 assert(field_index
[c
] == NULL_FIELD_INDEX
);
203 string
*old_field
= field
;
204 field
= new string
[nfields
+ 1];
207 for (i
= 0; i
< int(c
); i
++)
208 if (field_index
[i
] != NULL_FIELD_INDEX
)
210 for (i
= 0; i
< pos
; i
++)
211 field
[i
].move(old_field
[i
]);
213 for (i
= pos
; i
< nfields
; i
++)
214 field
[i
+ 1].move(old_field
[i
]);
216 ad_delete(nfields
) old_field
;
218 field_index
[c
] = pos
;
219 for (i
= c
+ 1; i
< 256; i
++)
220 if (field_index
[i
] != NULL_FIELD_INDEX
)
224 void reference::delete_field(unsigned char c
)
226 if (field_index
[c
] == NULL_FIELD_INDEX
)
228 string
*old_field
= field
;
229 field
= new string
[nfields
- 1];
231 for (i
= 0; i
< int(field_index
[c
]); i
++)
232 field
[i
].move(old_field
[i
]);
233 for (i
= field_index
[c
]; i
< nfields
- 1; i
++)
234 field
[i
].move(old_field
[i
+ 1]);
236 ad_delete(nfields
) old_field
;
238 field_index
[c
] = NULL_FIELD_INDEX
;
239 for (i
= c
+ 1; i
< 256; i
++)
240 if (field_index
[i
] != NULL_FIELD_INDEX
)
244 void reference::compute_hash_code()
250 for (int i
= 0; i
< nfields
; i
++)
251 if (field
[i
].length() > 0) {
253 h
^= hash_string(field
[i
].contents(), field
[i
].length());
258 void reference::set_number(int n
)
263 const char SORT_SEP
= '\001';
264 const char SORT_SUB_SEP
= '\002';
265 const char SORT_SUB_SUB_SEP
= '\003';
267 // sep specifies additional word separators
269 void sortify_words(const char *s
, const char *end
, const char *sep
,
273 int need_separator
= 0;
275 const char *token_start
= s
;
276 if (!get_token(&s
, end
))
278 if ((s
- token_start
== 1
279 && (*token_start
== ' '
280 || *token_start
== '\n'
281 || (sep
&& *token_start
!= '\0'
282 && strchr(sep
, *token_start
) != 0)))
283 || (s
- token_start
== 2
284 && token_start
[0] == '\\' && token_start
[1] == ' ')) {
289 const token_info
*ti
= lookup_token(token_start
, s
);
290 if (ti
->sortify_non_empty(token_start
, s
)) {
291 if (need_separator
) {
295 ti
->sortify(token_start
, s
, result
);
302 void sortify_word(const char *s
, const char *end
, string
&result
)
305 const char *token_start
= s
;
306 if (!get_token(&s
, end
))
308 const token_info
*ti
= lookup_token(token_start
, s
);
309 ti
->sortify(token_start
, s
, result
);
313 void sortify_other(const char *s
, int len
, string
&key
)
315 sortify_words(s
, s
+ len
, 0, key
);
318 void sortify_title(const char *s
, int len
, string
&key
)
320 const char *end
= s
+ len
;
321 for (; s
< end
&& (*s
== ' ' || *s
== '\n'); s
++)
325 const char *token_start
= ptr
;
326 if (!get_token(&ptr
, end
))
328 if (ptr
- token_start
== 1
329 && (*token_start
== ' ' || *token_start
== '\n'))
333 unsigned int first_word_len
= ptr
- s
- 1;
334 const char *ae
= articles
.contents() + articles
.length();
335 for (const char *a
= articles
.contents();
337 a
= strchr(a
, '\0') + 1)
338 if (first_word_len
== strlen(a
)) {
340 for (j
= 0; j
< first_word_len
; j
++)
341 if (a
[j
] != cmlower(s
[j
]))
343 if (j
>= first_word_len
) {
345 for (; s
< end
&& (*s
== ' ' || *s
== '\n'); s
++)
351 sortify_words(s
, end
, 0, key
);
354 void sortify_name(const char *s
, int len
, string
&key
)
356 const char *last_name_end
;
357 const char *last_name
= find_last_name(s
, s
+ len
, &last_name_end
);
358 sortify_word(last_name
, last_name_end
, key
);
359 key
+= SORT_SUB_SUB_SEP
;
361 sortify_words(s
, last_name
, ".", key
);
362 key
+= SORT_SUB_SUB_SEP
;
363 if (last_name_end
< s
+ len
)
364 sortify_words(last_name_end
, s
+ len
, ".,", key
);
367 void sortify_date(const char *s
, int len
, string
&key
)
369 const char *year_end
;
370 const char *year_start
= find_year(s
, s
+ len
, &year_end
);
372 // Things without years are often `forthcoming', so it makes sense
373 // that they sort after things with explicit years.
375 sortify_words(s
, s
+ len
, 0, key
);
378 int n
= year_end
- year_start
;
383 while (year_start
< year_end
)
384 key
+= *year_start
++;
385 int m
= find_month(s
, s
+ len
);
390 const char *day_start
= find_day(s
, s
+ len
, &day_end
);
393 if (day_end
- day_start
== 1)
395 while (day_start
< day_end
)
399 // SORT_{SUB,SUB_SUB}_SEP can creep in from use of @ in label specification.
401 void sortify_label(const char *s
, int len
, string
&key
)
403 const char *end
= s
+ len
;
407 ptr
< end
&& *ptr
!= SORT_SUB_SEP
&& *ptr
!= SORT_SUB_SUB_SEP
;
411 sortify_words(s
, ptr
, 0, key
);
419 void reference::compute_sort_key()
421 if (sort_fields
.length() == 0)
424 const char *sf
= sort_fields
.contents();
425 while (*sf
!= '\0') {
426 sort_key
+= SORT_SEP
;
433 else if (csdigit(*sf
)) {
435 long l
= strtol(sf
, &ptr
, 10);
436 if (l
== 0 && ptr
== sf
)
449 sortify_label(label
.contents(), label
.length(), sort_key
);
450 else if (f
== AUTHOR_FIELDS
[0])
451 sortify_authors(n
, sort_key
);
453 sortify_field(f
, n
, sort_key
);
455 sort_fields
.set_length(sort_fields
.length() - 1);
458 void reference::sortify_authors(int n
, string
&result
) const
460 for (const char *p
= AUTHOR_FIELDS
; *p
!= '\0'; p
++)
461 if (contains_field(*p
)) {
462 sortify_field(*p
, n
, result
);
465 sortify_field(AUTHOR_FIELDS
[0], n
, result
);
468 void reference::canonicalize_authors(string
&result
) const
470 int len
= result
.length();
471 sortify_authors(INT_MAX
, result
);
472 if (result
.length() > len
)
473 result
+= SORT_SUB_SEP
;
476 void reference::sortify_field(unsigned char f
, int n
, string
&result
) const
478 typedef void (*sortify_t
)(const char *, int, string
&);
479 sortify_t sortifier
= sortify_other
;
483 sortifier
= sortify_name
;
486 sortifier
= sortify_date
;
491 sortifier
= sortify_title
;
494 int fi
= field_index
[(unsigned char)f
];
495 if (fi
!= NULL_FIELD_INDEX
) {
496 string
&str
= field
[fi
];
497 const char *start
= str
.contents();
498 const char *end
= start
+ str
.length();
499 for (int i
= 0; i
< n
&& start
< end
; i
++) {
500 const char *p
= start
;
501 while (start
< end
&& *start
!= FIELD_SEPARATOR
)
504 result
+= SORT_SUB_SEP
;
505 (*sortifier
)(p
, start
- p
, result
);
512 int compare_reference(const reference
&r1
, const reference
&r2
)
516 const char *s1
= r1
.sort_key
.contents();
517 int n1
= r1
.sort_key
.length();
518 const char *s2
= r2
.sort_key
.contents();
519 int n2
= r2
.sort_key
.length();
520 for (; n1
> 0 && n2
> 0; --n1
, --n2
, ++s1
, ++s2
)
522 return (int)(unsigned char)*s1
- (int)(unsigned char)*s2
;
527 return r1
.no
- r2
.no
;
530 int same_reference(const reference
&r1
, const reference
&r2
)
532 if (!r1
.rid
.is_null() && r1
.rid
== r2
.rid
)
536 if (r1
.nfields
!= r2
.nfields
)
539 for (i
= 0; i
< 256; i
++)
540 if (r1
.field_index
!= r2
.field_index
)
542 for (i
= 0; i
< r1
.nfields
; i
++)
543 if (r1
.field
[i
] != r2
.field
[i
])
548 const char *find_last_name(const char *start
, const char *end
,
551 const char *ptr
= start
;
552 const char *last_word
= start
;
554 const char *token_start
= ptr
;
555 if (!get_token(&ptr
, end
))
557 if (ptr
- token_start
== 1) {
558 if (*token_start
== ',') {
562 else if (*token_start
== ' ' || *token_start
== '\n') {
563 if (ptr
< end
&& *ptr
!= ' ' && *ptr
!= '\n')
572 void abbreviate_name(const char *ptr
, const char *end
, string
&result
)
574 const char *last_name_end
;
575 const char *last_name_start
= find_last_name(ptr
, end
, &last_name_end
);
578 const char *token_start
= ptr
;
579 if (!get_token(&ptr
, last_name_start
))
581 const token_info
*ti
= lookup_token(token_start
, ptr
);
583 if ((ptr
- token_start
== 1 && *token_start
== ' ')
584 || (ptr
- token_start
== 2 && token_start
[0] == '\\'
585 && token_start
[1] == ' '))
588 result
+= period_before_initial
;
590 result
+= period_before_other
;
593 result
.append(token_start
, ptr
- token_start
);
594 if (ti
->is_upper()) {
595 const char *lower_ptr
= ptr
;
599 if (!get_token(&ptr
, last_name_start
))
601 if ((ptr
- token_start
== 1 && *token_start
== ' ')
602 || (ptr
- token_start
== 2 && token_start
[0] == '\\'
603 && token_start
[1] == ' '))
605 ti
= lookup_token(token_start
, ptr
);
606 if (ti
->is_hyphen()) {
607 const char *ptr1
= ptr
;
608 if (get_token(&ptr1
, last_name_start
)) {
609 ti
= lookup_token(ptr
, ptr1
);
610 if (ti
->is_upper()) {
611 result
+= period_before_hyphen
;
612 result
.append(token_start
, ptr1
- token_start
);
617 else if (ti
->is_upper()) {
618 // MacDougal -> MacD.
619 result
.append(lower_ptr
, ptr
- lower_ptr
);
623 else if (first_token
&& ti
->is_accent()) {
624 result
.append(token_start
, ptr
- token_start
);
633 result
+= period_before_last_name
;
634 result
.append(last_name_start
, end
- last_name_start
);
637 static void abbreviate_names(string
&result
)
641 const char *ptr
= str
.contents();
642 const char *end
= ptr
+ str
.length();
644 const char *name_end
= (char *)memchr(ptr
, FIELD_SEPARATOR
, end
- ptr
);
647 abbreviate_name(ptr
, name_end
, result
);
651 result
+= FIELD_SEPARATOR
;
655 void reverse_name(const char *ptr
, const char *name_end
, string
&result
)
657 const char *last_name_end
;
658 const char *last_name_start
= find_last_name(ptr
, name_end
, &last_name_end
);
659 result
.append(last_name_start
, last_name_end
- last_name_start
);
660 while (last_name_start
> ptr
661 && (last_name_start
[-1] == ' ' || last_name_start
[-1] == '\n'))
663 if (last_name_start
> ptr
) {
665 result
.append(ptr
, last_name_start
- ptr
);
667 if (last_name_end
< name_end
)
668 result
.append(last_name_end
, name_end
- last_name_end
);
671 void reverse_names(string
&result
, int n
)
677 const char *ptr
= str
.contents();
678 const char *end
= ptr
+ str
.length();
681 result
.append(ptr
, end
- ptr
);
684 const char *name_end
= (char *)memchr(ptr
, FIELD_SEPARATOR
, end
- ptr
);
687 reverse_name(ptr
, name_end
, result
);
691 result
+= FIELD_SEPARATOR
;
695 // Return number of field separators.
697 int join_fields(string
&f
)
699 const char *ptr
= f
.contents();
700 int len
= f
.length();
703 for (j
= 0; j
< len
; j
++)
704 if (ptr
[j
] == FIELD_SEPARATOR
)
706 if (nfield_seps
== 0)
709 int field_seps_left
= nfield_seps
;
710 for (j
= 0; j
< len
; j
++) {
711 if (ptr
[j
] == FIELD_SEPARATOR
) {
712 if (nfield_seps
== 1)
713 temp
+= join_authors_exactly_two
;
714 else if (--field_seps_left
== 0)
715 temp
+= join_authors_last_two
;
717 temp
+= join_authors_default
;
726 void uppercase(const char *start
, const char *end
, string
&result
)
729 const char *token_start
= start
;
730 if (!get_token(&start
, end
))
732 const token_info
*ti
= lookup_token(token_start
, start
);
733 ti
->upper_case(token_start
, start
, result
);
737 void lowercase(const char *start
, const char *end
, string
&result
)
740 const char *token_start
= start
;
741 if (!get_token(&start
, end
))
743 const token_info
*ti
= lookup_token(token_start
, start
);
744 ti
->lower_case(token_start
, start
, result
);
748 void capitalize(const char *ptr
, const char *end
, string
&result
)
750 int in_small_point_size
= 0;
752 const char *start
= ptr
;
753 if (!get_token(&ptr
, end
))
755 const token_info
*ti
= lookup_token(start
, ptr
);
756 const char *char_end
= ptr
;
757 int is_lower
= ti
->is_lower();
758 if ((is_lower
|| ti
->is_upper()) && get_token(&ptr
, end
)) {
759 const token_info
*ti2
= lookup_token(char_end
, ptr
);
760 if (!ti2
->is_accent())
764 if (!in_small_point_size
) {
766 in_small_point_size
= 1;
768 ti
->upper_case(start
, char_end
, result
);
769 result
.append(char_end
, ptr
- char_end
);
772 if (in_small_point_size
) {
774 in_small_point_size
= 0;
776 result
.append(start
, ptr
- start
);
779 if (in_small_point_size
)
783 void capitalize_field(string
&str
)
786 capitalize(str
.contents(), str
.contents() + str
.length(), temp
);
790 int is_terminated(const char *ptr
, const char *end
)
792 const char *last_token
= end
;
795 if (!get_token(&ptr
, end
))
799 return end
- last_token
== 1
800 && (*last_token
== '.' || *last_token
== '!' || *last_token
== '?');
803 void reference::output(FILE *fp
)
806 for (int i
= 0; i
< 256; i
++)
807 if (field_index
[i
] != NULL_FIELD_INDEX
&& i
!= annotation_field
) {
808 string
&f
= field
[field_index
[i
]];
810 int j
= reverse_fields
.search(i
);
813 int len
= reverse_fields
.length();
814 if (++j
< len
&& csdigit(reverse_fields
[j
])) {
815 n
= reverse_fields
[j
] - '0';
816 for (++j
; j
< len
&& csdigit(reverse_fields
[j
]); j
++)
817 // should check for overflow
818 n
= n
*10 + reverse_fields
[j
] - '0';
825 int is_multiple
= join_fields(f
) > 0;
826 if (capitalize_fields
.search(i
) >= 0)
828 if (memchr(f
.contents(), '\n', f
.length()) == 0) {
829 fprintf(fp
, ".ds [%c ", i
);
830 if (f
[0] == ' ' || f
[0] == '\\' || f
[0] == '"')
836 fprintf(fp
, ".de [%c\n", i
);
841 int multiple_pages
= 0;
842 const char *s
= f
.contents();
843 const char *end
= f
.contents() + f
.length();
845 const char *token_start
= s
;
846 if (!get_token(&s
, end
))
848 const token_info
*ti
= lookup_token(token_start
, s
);
849 if (ti
->is_hyphen() || ti
->is_range_sep()) {
854 fprintf(fp
, ".nr [P %d\n", multiple_pages
);
857 fprintf(fp
, ".nr [E %d\n", is_multiple
);
859 for (const char *p
= "TAO"; *p
; p
++) {
860 int fi
= field_index
[(unsigned char)*p
];
861 if (fi
!= NULL_FIELD_INDEX
) {
862 string
&f
= field
[fi
];
863 fprintf(fp
, ".nr [%c %d\n", *p
,
864 is_terminated(f
.contents(), f
.contents() + f
.length()));
868 fprintf(fp
, ".][ %d %s\n", t
, reference_types
[t
]);
869 if (annotation_macro
.length() > 0 && annotation_field
>= 0
870 && field_index
[annotation_field
] != NULL_FIELD_INDEX
) {
872 put_string(annotation_macro
, fp
);
874 put_string(field
[field_index
[annotation_field
]], fp
);
878 void reference::print_sort_key_comment(FILE *fp
)
881 put_string(sort_key
, fp
);
885 const char *find_year(const char *start
, const char *end
, const char **endp
)
888 while (start
< end
&& !csdigit(*start
))
890 const char *ptr
= start
;
893 while (ptr
< end
&& csdigit(*ptr
))
895 if (ptr
- start
== 4 || ptr
- start
== 3
897 && (start
[0] >= '4' || (start
[0] == '3' && start
[1] >= '2')))) {
906 static const char *find_day(const char *start
, const char *end
,
910 while (start
< end
&& !csdigit(*start
))
912 const char *ptr
= start
;
915 while (ptr
< end
&& csdigit(*ptr
))
917 if ((ptr
- start
== 1 && start
[0] != '0')
918 || (ptr
- start
== 2 &&
921 || (start
[0] == '3' && start
[1] <= '1')
922 || (start
[0] == '0' && start
[1] != '0')))) {
931 static int find_month(const char *start
, const char *end
)
933 static const char *months
[] = {
948 while (start
< end
&& !csalpha(*start
))
950 const char *ptr
= start
;
953 while (ptr
< end
&& csalpha(*ptr
))
955 if (ptr
- start
>= 3) {
956 for (unsigned int i
= 0; i
< sizeof(months
)/sizeof(months
[0]); i
++) {
957 const char *q
= months
[i
];
958 const char *p
= start
;
959 for (; p
< ptr
; p
++, q
++)
960 if (cmlower(*p
) != *q
)
971 int reference::contains_field(char c
) const
973 return field_index
[(unsigned char)c
] != NULL_FIELD_INDEX
;
976 int reference::classify()
978 if (contains_field('J'))
979 return JOURNAL_ARTICLE
;
980 if (contains_field('B'))
981 return ARTICLE_IN_BOOK
;
982 if (contains_field('G'))
984 if (contains_field('R'))
986 if (contains_field('I'))
988 if (contains_field('M'))
993 const char *reference::get_year(const char **endp
) const
995 if (field_index
['D'] != NULL_FIELD_INDEX
) {
996 string
&date
= field
[field_index
['D']];
997 const char *start
= date
.contents();
998 const char *end
= start
+ date
.length();
999 return find_year(start
, end
, endp
);
1005 const char *reference::get_field(unsigned char c
, const char **endp
) const
1007 if (field_index
[c
] != NULL_FIELD_INDEX
) {
1008 string
&f
= field
[field_index
[c
]];
1009 const char *start
= f
.contents();
1010 *endp
= start
+ f
.length();
1017 const char *reference::get_date(const char **endp
) const
1019 return get_field('D', endp
);
1022 const char *nth_field(int i
, const char *start
, const char **endp
)
1025 start
= (char *)memchr(start
, FIELD_SEPARATOR
, *endp
- start
);
1030 const char *e
= (char *)memchr(start
, FIELD_SEPARATOR
, *endp
- start
);
1036 const char *reference::get_author(int i
, const char **endp
) const
1038 for (const char *f
= AUTHOR_FIELDS
; *f
!= '\0'; f
++) {
1039 const char *start
= get_field(*f
, endp
);
1041 if (strchr(MULTI_FIELD_NAMES
, *f
) != 0)
1042 return nth_field(i
, start
, endp
);
1052 const char *reference::get_author_last_name(int i
, const char **endp
) const
1054 for (const char *f
= AUTHOR_FIELDS
; *f
!= '\0'; f
++) {
1055 const char *start
= get_field(*f
, endp
);
1057 if (strchr(MULTI_FIELD_NAMES
, *f
) != 0) {
1058 start
= nth_field(i
, start
, endp
);
1063 return find_last_name(start
, *endp
, endp
);
1071 void reference::set_date(string
&d
)
1073 if (d
.length() == 0)
1076 insert_field('D', d
);
1079 int same_year(const reference
&r1
, const reference
&r2
)
1082 const char *ys1
= r1
.get_year(&ye1
);
1084 const char *ys2
= r2
.get_year(&ye2
);
1087 return same_date(r1
, r2
);
1093 else if (ye1
- ys1
!= ye2
- ys2
)
1096 return memcmp(ys1
, ys2
, ye1
- ys1
) == 0;
1099 int same_date(const reference
&r1
, const reference
&r2
)
1102 const char *s1
= r1
.get_date(&e1
);
1104 const char *s2
= r2
.get_date(&e2
);
1109 else if (e1
- s1
!= e2
- s2
)
1112 return memcmp(s1
, s2
, e1
- s1
) == 0;
1115 const char *reference::get_sort_field(int i
, int si
, int ssi
,
1116 const char **endp
) const
1118 const char *start
= sort_key
.contents();
1119 const char *end
= start
+ sort_key
.length();
1125 start
= (char *)memchr(start
, SORT_SEP
, end
- start
);
1130 const char *e
= (char *)memchr(start
, SORT_SEP
, end
- start
);
1138 start
= (char *)memchr(start
, SORT_SUB_SEP
, end
- start
);
1143 e
= (char *)memchr(start
, SORT_SUB_SEP
, end
- start
);
1150 while (--ssi
>= 0) {
1151 start
= (char *)memchr(start
, SORT_SUB_SUB_SEP
, end
- start
);
1156 e
= (char *)memchr(start
, SORT_SUB_SUB_SEP
, end
- start
);