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