refer: start with an empty message in refer_cite()
[neatrefer.git] / refer.c
blobf193bcc4c36c2b6a14d377997bc44f3ba04c2f07
1 /*
2 * NEATREFER - A REFER CLONE FOR NEATROFF
4 * Copyright (C) 2011-2017 Ali Gholami Rudi <ali at rudi dot ir>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <ctype.h>
19 #include <fcntl.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
25 #define NREFS (1 << 14)
26 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
28 struct ref {
29 char *keys[128]; /* reference keys */
30 char *auth[128]; /* authors */
31 int id; /* allocated reference id */
32 int nauth;
35 static struct ref refs[NREFS]; /* all references in refer database */
36 static int refs_n;
37 static struct ref *cites[NREFS]; /* cited references */
38 static int cites_n = 1;
39 static int inserted; /* number of inserted references */
40 static int multiref; /* allow specifying multiple references */
41 static int accumulate; /* accumulate all references */
42 static int initials; /* initials for authors' first name */
43 static int refauth; /* use author-year citations */
44 static char *refmac; /* citation macro name */
45 static FILE *refdb; /* the database file */
47 #define ref_label(ref) ((ref)->keys['L'])
49 /* the next input line */
50 static char *lnget(void)
52 static char buf[1024];
53 return fgets(buf, sizeof(buf), stdin);
56 /* write an output line */
57 static void lnput(char *s, int n)
59 write(1, s, n >= 0 ? n : strlen(s));
62 /* the next refer database input line */
63 static char *dbget(void)
65 static char buf[1024];
66 return refdb ? fgets(buf, sizeof(buf), refdb) : NULL;
69 static char *sdup(char *s)
71 char *e = strchr(s, '\n') ? strchr(s, '\n') : strchr(s, '\0');
72 char *r;
73 int n = e - s;
74 r = malloc(n + 1);
75 memcpy(r, s, n);
76 r[n] = '\0';
77 return r;
80 /* format author names as J. Smith */
81 static char *ref_author(char *ref)
83 char *res;
84 char *out;
85 char *beg;
86 if (!initials)
87 return sdup(ref);
88 res = malloc(strlen(ref) + 32);
89 out = res;
90 while (1) {
91 while (*ref == ' ' || *ref == '.')
92 ref++;
93 if (*ref == '\0')
94 break;
95 beg = ref;
96 while (*ref && *ref != ' ' && *ref != '.')
97 ref++;
98 if (out != res)
99 *out++ = ' ';
100 if (islower((unsigned char) *beg) || *ref == '\0') {
101 while (beg < ref)
102 *out++ = *beg++;
103 } else { /* initials */
104 do {
105 *out++ = *beg++;
106 *out++ = '.';
107 while (beg < ref && *beg != '-')
108 beg++;
109 if (*beg == '-') /* handling J.-K. Smith */
110 *out++ = *beg++;
111 } while (beg < ref);
114 *out = '\0';
115 return res;
118 /* strip excess whitespace */
119 static void rstrip(char *s)
121 int i;
122 int last = -1;
123 for (i = 0; s[i]; i++)
124 if (s[i] != ' ' && s[i] != '\n')
125 last = i;
126 s[last + 1] = '\0';
129 /* read a single refer record */
130 static void db_ref(struct ref *ref, char *ln)
132 do {
133 if (ln[0] == '%' && ln[1] >= 'A' && ln[1] <= 'Z') {
134 char *r = ln + 2;
135 while (isspace((unsigned char) *r))
136 r++;
137 rstrip(r);
138 if (ln[1] == 'A')
139 ref->auth[ref->nauth++] = ref_author(r);
140 else
141 ref->keys[(unsigned char) ln[1]] = sdup(r);
143 } while ((ln = dbget()) && ln[0] != '\n');
146 /* parse a refer-style bib file and fill refs[] */
147 static int db_parse(void)
149 char *ln;
150 while ((ln = dbget()))
151 if (ln[0] != '\n')
152 db_ref(&refs[refs_n++], ln);
153 return 0;
156 static char fields[] = "LTABRJDVNPITO";
157 static char fields_flag[] = "OP";
158 static char *kinds[] = {"Other", "Article", "Book", "In book", "Report"};
160 static int ref_kind(struct ref *r)
162 if (r->keys['J'])
163 return 1;
164 if (r->keys['B'])
165 return 3;
166 if (r->keys['I'])
167 return 2;
168 if (r->keys['R'])
169 return 4;
170 return 0;
173 /* print the given reference */
174 static void ref_ins(struct ref *ref, int id)
176 char buf[1 << 12];
177 char *s = buf;
178 int kind = ref_kind(ref);
179 int j;
180 s += sprintf(s, ".ds [F %d\n", id);
181 s += sprintf(s, ".]-\n");
182 if (ref->nauth) {
183 s += sprintf(s, ".ds [A ");
184 for (j = 0; j < ref->nauth; j++)
185 s += sprintf(s, "%s%s", j ? ", " : "", ref->auth[j]);
186 s += sprintf(s, "\n");
188 for (j = 'B'; j <= 'Z'; j++) {
189 char *val = ref->keys[j];
190 if (!val || !strchr(fields, j))
191 continue;
192 s += sprintf(s, ".ds [%c %s\n", j, val ? val : "");
193 if (strchr(fields_flag, j))
194 s += sprintf(s, ".nr [%c 1\n", j);
196 s += sprintf(s, ".][ %d %s\n", kind, kinds[kind]);
197 lnput(buf, s - buf);
200 /* print all references */
201 static void ref_all(void)
203 int i;
204 lnput(".]<\n", -1);
205 for (i = 1; i < cites_n; i++)
206 ref_ins(cites[i], i);
207 lnput(".]>", -1);
210 static int intcmp(void *v1, void *v2)
212 return *(int *) v1 - *(int *) v2;
215 /* the given label was referenced; add it to cites[] */
216 static int refer_seen(char *label)
218 int i;
219 for (i = 0; i < refs_n; i++)
220 if (ref_label(&refs[i]) && !strcmp(label, ref_label(&refs[i])))
221 break;
222 if (i == refs_n)
223 return -1;
224 if (!refs[i].id) {
225 refs[i].id = cites_n++;
226 cites[refs[i].id] = &refs[i];
228 return refs[i].id;
231 static char *refer_lastname(char *name)
233 char *last = name;
234 while (*name) {
235 if (!islower((unsigned char) last[0]))
236 last = name;
237 while (*name && *name != ' ')
238 if (*name++ == '\\')
239 name++;
240 while (*name == ' ')
241 name++;
243 return last;
246 static void refer_quote(char *d, char *s)
248 if (!strchr(s, ' ') && s[0] != '"') {
249 strcpy(d, s);
250 } else {
251 *d++ = '"';
252 while (*s) {
253 if (*s == '"')
254 *d++ = '"';
255 *d++ = *s++;
257 *d++ = '"';
258 *d = '\0';
262 /* replace .[ .] macros with reference numbers */
263 static void refer_cite(char *s)
265 char msg[256];
266 char label[256];
267 int id[256];
268 int nid = 0;
269 int i = 0;
270 msg[0] = '\0';
271 while (!nid || multiref) {
272 char *r = label;
273 while (*s && strchr(" \t\n,", (unsigned char) *s))
274 s++;
275 while (*s && !strchr(" \t\n,]", (unsigned char) *s))
276 *r++ = *s++;
277 *r = '\0';
278 if (!strcmp("$LIST$", label)) {
279 ref_all();
280 break;
282 id[nid] = refer_seen(label);
283 if (id[nid] < 0)
284 fprintf(stderr, "refer: <%s> not found\n", label);
285 else
286 nid++;
287 if (!*s || *s == '\n' || *s == ']')
288 break;
290 if (!refauth) { /* numbered citations */
291 /* sort references for cleaner reference intervals */
292 qsort(id, nid, sizeof(id[0]), (void *) intcmp);
293 while (i < nid) {
294 int beg = i++;
295 /* reading reference intervals */
296 while (i < nid && id[i] == id[i - 1] + 1)
297 i++;
298 if (beg)
299 sprintf(msg + strlen(msg), ",");
300 if (beg == i - 1)
301 sprintf(msg + strlen(msg), "%d", id[beg]);
302 else
303 sprintf(msg + strlen(msg), "%d%s%d",
304 id[beg], beg < i - 2 ? "\\-" : ",", id[i - 1]);
306 } else if (nid) { /* year + authors citations */
307 struct ref *ref = cites[id[0]];
308 sprintf(msg, "%s %d", ref->keys['D'] ? ref->keys['D'] : "-", ref->nauth);
309 for (i = 0; i < ref->nauth; i++) {
310 sprintf(msg + strlen(msg), " ");
311 refer_quote(msg + strlen(msg), refer_lastname(ref->auth[i]));
314 lnput(msg, -1);
315 if (!accumulate)
316 for (i = 0; i < nid; i++)
317 ref_ins(cites[id[i]], ++inserted);
320 static int slen(char *s, int delim)
322 char *r = strchr(s, delim);
323 return r ? r - s : strchr(s, '\0') - s;
326 static int refer_reqname(char *mac, int maclen, char *s)
328 int i = 0;
329 if (*s++ != '.')
330 return 1;
331 for (i = 0; i < maclen && *s && *s != ' '; i++)
332 mac[i] = *s++;
333 mac[i] = '\0';
334 return *s != ' ';
337 static int refer_macname(char *mac, int maclen, char *s)
339 int i = 0;
340 if (*s++ != '\\')
341 return 1;
342 if (*s++ != '*')
343 return 1;
344 if (*s++ != '[')
345 return 1;
346 for (i = 0; i < maclen && *s && *s != ' '; i++)
347 mac[i] = *s++;
348 mac[i] = '\0';
349 return *s != ' ';
352 /* return 1 if mac is a citation macro */
353 static int refer_refmac(char *mac)
355 char *s = refmac ? strstr(refmac, mac) : NULL;
356 if (!mac[0] || !s)
357 return 0;
358 return (s == refmac || s[-1] == ',') &&
359 (!s[strlen(mac)] || s[strlen(mac)] == ',');
362 static void refer(void)
364 char mac[256];
365 char *s, *r, *ln;
366 while ((ln = lnget())) {
367 /* multi-line citations: .[ rudi17 .] */
368 if (ln[0] == '.' && ln[1] == '[') {
369 lnput(ln + 2, slen(ln + 2, '\n'));
370 if ((ln = lnget())) {
371 refer_cite(ln);
372 while (ln && (ln[0] != '.' || ln[1] != ']'))
373 ln = lnget();
374 if (ln)
375 lnput(ln + 2, -1);
377 continue;
379 /* single line citation .cite rudi17 */
380 if (ln[0] == '.' && !refer_reqname(mac, sizeof(mac), ln) &&
381 refer_refmac(mac)) {
382 int i = 1;
383 while (ln[i] && ln[i] != ' ')
384 i++;
385 while (ln[i] && ln[i] == ' ')
386 i++;
387 lnput(ln, i);
388 refer_cite(ln + i);
389 while (ln[i] && ln[i] != ' ' && ln[i] != '\n')
390 i++;
391 lnput(ln + i, -1);
392 continue;
394 s = ln;
395 r = s;
396 /* inline citations \*[cite rudi17] */
397 while ((r = strchr(r, '\\'))) {
398 r++;
399 if (refer_macname(mac, sizeof(mac), r - 1))
400 continue;
401 if (!refer_refmac(mac))
402 continue;
403 if (!strchr(r, ']'))
404 continue;
405 r = strchr(r, ' ') + 1;
406 lnput(s, r - s);
407 refer_cite(r);
408 while (*r && *r != ' ' && *r != ']')
409 r++;
410 s = r;
412 lnput(s, -1);
416 static char *usage =
417 "Usage neatrefer [options] <input >output\n"
418 "Options:\n"
419 "\t-p bib \tspecify the database file\n"
420 "\t-e \taccumulate references\n"
421 "\t-m \tmerge multiple references in a single .[/.] block\n"
422 "\t-i \tinitials for authors' first and middle names\n"
423 "\t-o xy \tcitation macro (\\*[xy label])\n"
424 "\t-a \tuse author-year citation style\n";
426 int main(int argc, char *argv[])
428 int i, j;
429 for (i = 1; i < argc; i++) {
430 switch (argv[i][0] == '-' ? argv[i][1] : 'h') {
431 case 'm':
432 multiref = 1;
433 break;
434 case 'e':
435 accumulate = 1;
436 break;
437 case 'p':
438 refdb = fopen(argv[i][2] ? argv[i] + 2 : argv[++i], "r");
439 if (refdb) {
440 db_parse();
441 fclose(refdb);
443 refdb = NULL;
444 break;
445 case 'o':
446 refmac = argv[i][2] ? argv[i] + 2 : argv[++i];
447 break;
448 case 'i':
449 initials = 1;
450 break;
451 case 'a':
452 refauth = 1;
453 break;
454 default:
455 printf("%s", usage);
456 return 1;
459 if (refauth && multiref) {
460 fprintf(stderr, "refer: cannot use -m with -a\n");
461 return 1;
463 refer();
464 for (i = 0; i < refs_n; i++)
465 for (j = 0; j < LEN(refs[i].keys); j++)
466 if (refs[i].keys[j])
467 free(refs[i].keys[j]);
468 for (i = 0; i < refs_n; i++)
469 for (j = 0; j < LEN(refs[i].auth); j++)
470 if (refs[i].auth[j])
471 free(refs[i].auth[j]);
472 return 0;