├── Makefile ├── README └── refer.c /Makefile: -------------------------------------------------------------------------------- 1 | CC = cc 2 | CFLAGS = -Wall -O2 3 | LDFLAGS = 4 | 5 | OBJS = refer.o 6 | 7 | all: refer 8 | .c.o: 9 | $(CC) -c $(CFLAGS) $< 10 | refer: $(OBJS) 11 | $(CC) -o $@ $(OBJS) $(LDFLAGS) 12 | clean: 13 | rm -f *.o refer 14 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NEATREFER 2 | ========= 3 | 4 | Neatrefer troff preprocessor is a small refer clone for managing 5 | bibliographic references. It assumes that each record in the database 6 | has an 'L' key, which specifies the label of the record. When 7 | referencing, only this key is looked up among the records in the 8 | database. Therefore, Neatrefer is not compatible with the original 9 | refer. 10 | 11 | Citing in Neatrefer can be done by including the label of the record 12 | between .[ and .] request lines. In addition, other citation macros 13 | can be defined using -o option. For example with -ocite option, 14 | the record with label utp87 can be cited as follows: 15 | 16 | .cite utp87 17 | \*[cite utp87] 18 | 19 | When using numbered citation style Neatrefer replaces them with the 20 | following lines: 21 | 22 | .cite 1 23 | \*[cite 1] 24 | 25 | And when using author-year citation style (-a option), Neatrefer 26 | replaces them with with the following lines (after the citation macro, 27 | publication date, the number of authors, and last names of the authors 28 | appear): 29 | 30 | .cite 1987 2 Dougherty O'Reilly 31 | \*[cite 1987 2 Dougherty O'Reilly] 32 | 33 | With -m option, Neatrefer allows multiple references in a single 34 | citation, as a comma separated list of labels; refer tries to use 35 | reference intervals for them if possible (-a and -m cannot be used 36 | together). 37 | -------------------------------------------------------------------------------- /refer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * NEATREFER - A REFER CLONE FOR NEATROFF 3 | * 4 | * Copyright (C) 2011-2017 Ali Gholami Rudi 5 | * 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. 9 | * 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. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define NREFS (1 << 14) 26 | #define LEN(a) (sizeof(a) / sizeof((a)[0])) 27 | 28 | struct ref { 29 | char *keys[128]; /* reference keys */ 30 | char *auth[128]; /* authors */ 31 | int id; /* allocated reference id */ 32 | int nauth; 33 | }; 34 | 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; 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 int sortall; /* sort references */ 45 | static char *refmac; /* citation macro name */ 46 | static char *refmac_auth; /* author-year citation macro name */ 47 | static FILE *refdb; /* the database file */ 48 | 49 | #define ref_label(ref) ((ref)->keys['L']) 50 | 51 | /* the next input line */ 52 | static char *lnget(void) 53 | { 54 | static char buf[1024]; 55 | return fgets(buf, sizeof(buf), stdin); 56 | } 57 | 58 | /* write an output line */ 59 | static void lnput(char *s, int n) 60 | { 61 | write(1, s, n >= 0 ? n : strlen(s)); 62 | } 63 | 64 | /* the next refer database input line */ 65 | static char *dbget(void) 66 | { 67 | static char buf[1024]; 68 | return refdb ? fgets(buf, sizeof(buf), refdb) : NULL; 69 | } 70 | 71 | static char *sdup(char *s) 72 | { 73 | char *e = strchr(s, '\n') ? strchr(s, '\n') : strchr(s, '\0'); 74 | char *r; 75 | int n = e - s; 76 | r = malloc(n + 1); 77 | memcpy(r, s, n); 78 | r[n] = '\0'; 79 | return r; 80 | } 81 | 82 | /* format author names as J. Smith */ 83 | static char *ref_author(char *ref) 84 | { 85 | char *res; 86 | char *out; 87 | char *beg; 88 | if (!initials) 89 | return sdup(ref); 90 | res = malloc(strlen(ref) + 32); 91 | out = res; 92 | while (1) { 93 | while (*ref == ' ' || *ref == '.') 94 | ref++; 95 | if (*ref == '\0') 96 | break; 97 | beg = ref; 98 | while (*ref && *ref != ' ' && *ref != '.') 99 | ref++; 100 | if (out != res) 101 | *out++ = ' '; 102 | if (islower((unsigned char) *beg) || *ref == '\0') { 103 | while (beg < ref) 104 | *out++ = *beg++; 105 | } else { /* initials */ 106 | do { 107 | *out++ = *beg++; 108 | *out++ = '.'; 109 | while (beg < ref && *beg != '-') 110 | beg++; 111 | if (*beg == '-') /* handling J.-K. Smith */ 112 | *out++ = *beg++; 113 | } while (beg < ref); 114 | } 115 | } 116 | *out = '\0'; 117 | return res; 118 | } 119 | 120 | /* strip excess whitespace */ 121 | static void rstrip(char *s) 122 | { 123 | int i; 124 | int last = -1; 125 | for (i = 0; s[i]; i++) 126 | if (s[i] != ' ' && s[i] != '\n') 127 | last = i; 128 | s[last + 1] = '\0'; 129 | } 130 | 131 | /* read a single refer record */ 132 | static void db_ref(struct ref *ref, char *ln) 133 | { 134 | do { 135 | if (ln[0] == '%' && ln[1] >= 'A' && ln[1] <= 'Z') { 136 | char *r = ln + 2; 137 | while (isspace((unsigned char) *r)) 138 | r++; 139 | rstrip(r); 140 | if (ln[1] == 'A') 141 | ref->auth[ref->nauth++] = ref_author(r); 142 | else 143 | ref->keys[(unsigned char) ln[1]] = sdup(r); 144 | ref->id = -1; 145 | } 146 | } while ((ln = dbget()) && ln[0] != '\n'); 147 | } 148 | 149 | /* parse a refer-style bib file and fill refs[] */ 150 | static int db_parse(void) 151 | { 152 | char *ln; 153 | while ((ln = dbget())) 154 | if (ln[0] != '\n') 155 | db_ref(&refs[refs_n++], ln); 156 | return 0; 157 | } 158 | 159 | static char fields[] = "LTABERJDVNPITOH"; 160 | static char fields_flag[] = "OP"; 161 | static char *kinds[] = {"Other", "Article", "Book", "In book", "Report"}; 162 | 163 | static int ref_kind(struct ref *r) 164 | { 165 | if (r->keys['J']) 166 | return 1; 167 | if (r->keys['B']) 168 | return 3; 169 | if (r->keys['R']) 170 | return 4; 171 | if (r->keys['I']) 172 | return 2; 173 | return 0; 174 | } 175 | 176 | /* print the given reference */ 177 | static void ref_ins(struct ref *ref, int id) 178 | { 179 | char buf[1 << 12]; 180 | char *s = buf; 181 | int kind = ref_kind(ref); 182 | int j; 183 | s += sprintf(s, ".ds [F %d\n", id); 184 | s += sprintf(s, ".]-\n"); 185 | if (ref->nauth) { 186 | s += sprintf(s, ".ds [A "); 187 | for (j = 0; j < ref->nauth; j++) 188 | s += sprintf(s, "%s%s", j ? ", " : "", ref->auth[j]); 189 | s += sprintf(s, "\n"); 190 | } 191 | for (j = 'B'; j <= 'Z'; j++) { 192 | char *val = ref->keys[j]; 193 | if (!val || !strchr(fields, j)) 194 | continue; 195 | s += sprintf(s, ".ds [%c %s\n", j, val ? val : ""); 196 | if (strchr(fields_flag, j)) 197 | s += sprintf(s, ".nr [%c 1\n", j); 198 | } 199 | s += sprintf(s, ".][ %d %s\n", kind, kinds[kind]); 200 | lnput(buf, s - buf); 201 | } 202 | 203 | static char *lastname(char *name) 204 | { 205 | char *last = name; 206 | while (*name) { 207 | if (!islower((unsigned char) last[0])) 208 | last = name; 209 | while (*name && *name != ' ') 210 | if (*name++ == '\\') 211 | name++; 212 | while (*name == ' ') 213 | name++; 214 | } 215 | return last; 216 | } 217 | 218 | static int refcmp(struct ref *r1, struct ref *r2) 219 | { 220 | if (!r2->nauth || (r1->keys['H'] && !r2->keys['H'])) 221 | return -1; 222 | if (!r1->nauth || (!r1->keys['H'] && r2->keys['H'])) 223 | return 1; 224 | return strcmp(lastname(r1->auth[0]), lastname(r2->auth[0])); 225 | } 226 | 227 | /* print all references */ 228 | static void ref_all(void) 229 | { 230 | int i, j; 231 | struct ref **sorted; 232 | sorted = malloc(cites_n * sizeof(sorted[0])); 233 | memcpy(sorted, cites, cites_n * sizeof(sorted[0])); 234 | if (sortall == 'a') { 235 | for (i = 1; i < cites_n; i++) { 236 | for (j = i - 1; j >= 0 && refcmp(cites[i], sorted[j]) < 0; j--) 237 | sorted[j + 1] = sorted[j]; 238 | sorted[j + 1] = cites[i]; 239 | } 240 | } 241 | lnput(".]<\n", -1); 242 | for (i = 0; i < cites_n; i++) 243 | ref_ins(sorted[i], sorted[i]->id + 1); 244 | lnput(".]>", -1); 245 | free(sorted); 246 | } 247 | 248 | static int intcmp(void *v1, void *v2) 249 | { 250 | return *(int *) v1 - *(int *) v2; 251 | } 252 | 253 | /* the given label was referenced; add it to cites[] */ 254 | static int refer_seen(char *label) 255 | { 256 | int i; 257 | for (i = 0; i < refs_n; i++) 258 | if (ref_label(&refs[i]) && !strcmp(label, ref_label(&refs[i]))) 259 | break; 260 | if (i == refs_n) 261 | return -1; 262 | if (refs[i].id < 0) { 263 | refs[i].id = cites_n++; 264 | cites[refs[i].id] = &refs[i]; 265 | } 266 | return refs[i].id; 267 | } 268 | 269 | static void refer_quote(char *d, char *s) 270 | { 271 | if (!strchr(s, ' ') && s[0] != '"') { 272 | strcpy(d, s); 273 | } else { 274 | *d++ = '"'; 275 | while (*s) { 276 | if (*s == '"') 277 | *d++ = '"'; 278 | *d++ = *s++; 279 | } 280 | *d++ = '"'; 281 | *d = '\0'; 282 | } 283 | } 284 | 285 | /* replace .[ .] macros with reference numbers or author-year */ 286 | static int refer_cite(int *id, char *s, int auth) 287 | { 288 | char msg[256]; 289 | char label[256]; 290 | int nid = 0; 291 | int i = 0; 292 | msg[0] = '\0'; 293 | while (!nid || multiref) { 294 | char *r = label; 295 | while (*s && strchr(" \t\n,", (unsigned char) *s)) 296 | s++; 297 | while (*s && !strchr(" \t\n,]", (unsigned char) *s)) 298 | *r++ = *s++; 299 | *r = '\0'; 300 | if (!strcmp("$LIST$", label)) { 301 | ref_all(); 302 | break; 303 | } 304 | id[nid] = refer_seen(label); 305 | if (id[nid] < 0) 306 | fprintf(stderr, "refer: <%s> not found\n", label); 307 | else 308 | nid++; 309 | if (!*s || *s == '\n' || *s == ']') 310 | break; 311 | } 312 | if (!auth) { /* numbered citations */ 313 | /* sort references for cleaner reference intervals */ 314 | qsort(id, nid, sizeof(id[0]), (void *) intcmp); 315 | while (i < nid) { 316 | int beg = i++; 317 | /* reading reference intervals */ 318 | while (i < nid && id[i] == id[i - 1] + 1) 319 | i++; 320 | if (beg) 321 | sprintf(msg + strlen(msg), ","); 322 | if (beg == i - 1) 323 | sprintf(msg + strlen(msg), "%d", id[beg] + 1); 324 | else 325 | sprintf(msg + strlen(msg), "%d%s%d", 326 | id[beg] + 1, beg < i - 2 ? "\\-" : ",", id[i - 1] + 1); 327 | } 328 | } else if (nid) { /* year + authors citations */ 329 | struct ref *ref = cites[id[0]]; 330 | sprintf(msg, "%s %d", ref->keys['D'] ? ref->keys['D'] : "-", ref->nauth); 331 | for (i = 0; i < ref->nauth; i++) { 332 | sprintf(msg + strlen(msg), " "); 333 | refer_quote(msg + strlen(msg), lastname(ref->auth[i])); 334 | } 335 | } 336 | lnput(msg, -1); 337 | return nid; 338 | } 339 | 340 | static int slen(char *s, int delim) 341 | { 342 | char *r = strchr(s, delim); 343 | return r ? r - s : strchr(s, '\0') - s; 344 | } 345 | 346 | static int refer_reqname(char *mac, int maclen, char *s) 347 | { 348 | int i = 0; 349 | if (*s++ != '.') 350 | return 1; 351 | for (i = 0; i < maclen && *s && *s != ' '; i++) 352 | mac[i] = *s++; 353 | mac[i] = '\0'; 354 | return *s != ' '; 355 | } 356 | 357 | static int refer_macname(char *mac, int maclen, char *s) 358 | { 359 | int i = 0; 360 | if (*s++ != '\\') 361 | return 1; 362 | if (*s++ != '*') 363 | return 1; 364 | if (*s++ != '[') 365 | return 1; 366 | for (i = 0; i < maclen && *s && *s != ' '; i++) 367 | mac[i] = *s++; 368 | mac[i] = '\0'; 369 | return *s != ' '; 370 | } 371 | 372 | /* return 1 if mac is a citation macro */ 373 | static int refer_refmac(char *pat, char *mac) 374 | { 375 | char *s = pat ? strstr(pat, mac) : NULL; 376 | if (!mac[0] || !s) 377 | return 0; 378 | return (s == pat || s[-1] == ',') && 379 | (!s[strlen(mac)] || s[strlen(mac)] == ','); 380 | } 381 | 382 | static void refer_insert(int *id, int id_n) 383 | { 384 | int i; 385 | for (i = 0; i < id_n; i++) 386 | ref_ins(cites[id[i]], ++inserted); 387 | } 388 | 389 | static void refer(void) 390 | { 391 | char mac[256]; 392 | int id[256]; 393 | char *s, *r, *ln; 394 | while ((ln = lnget())) { 395 | int id_n = 0; 396 | /* multi-line citations: .[ rudi17 .] */ 397 | if (ln[0] == '.' && ln[1] == '[') { 398 | lnput(ln + 2, slen(ln + 2, '\n')); 399 | if ((ln = lnget())) { 400 | id_n = refer_cite(id, ln, 0); 401 | while (ln && (ln[0] != '.' || ln[1] != ']')) 402 | ln = lnget(); 403 | if (ln) 404 | lnput(ln + 2, -1); 405 | } 406 | if (!accumulate) 407 | refer_insert(id, id_n); 408 | continue; 409 | } 410 | /* single line citation .cite rudi17 */ 411 | if (ln[0] == '.' && !refer_reqname(mac, sizeof(mac), ln) && 412 | (refer_refmac(refmac, mac) || refer_refmac(refmac_auth, mac))) { 413 | int i = 1; 414 | while (ln[i] && ln[i] != ' ') 415 | i++; 416 | while (ln[i] && ln[i] == ' ') 417 | i++; 418 | lnput(ln, i); 419 | id_n = refer_cite(id, ln + i, refer_refmac(refmac_auth, mac)); 420 | while (ln[i] && ln[i] != ' ' && ln[i] != '\n') 421 | i++; 422 | lnput(ln + i, -1); 423 | if (!accumulate) 424 | refer_insert(id, id_n); 425 | continue; 426 | } 427 | s = ln; 428 | r = s; 429 | /* inline citations \*[cite rudi17] */ 430 | while ((r = strchr(r, '\\'))) { 431 | r++; 432 | if (refer_macname(mac, sizeof(mac), r - 1)) 433 | continue; 434 | if (!refer_refmac(refmac, mac) && !refer_refmac(refmac_auth, mac)) 435 | continue; 436 | if (!strchr(r, ']')) 437 | continue; 438 | r = strchr(r, ' ') + 1; 439 | lnput(s, r - s); 440 | id_n = refer_cite(id, r, refer_refmac(refmac_auth, mac)); 441 | while (*r && *r != ' ' && *r != ']') 442 | r++; 443 | s = r; 444 | } 445 | lnput(s, -1); 446 | if (!accumulate) 447 | refer_insert(id, id_n); 448 | } 449 | } 450 | 451 | static char *usage = 452 | "Usage neatrefer [options] output\n" 453 | "Options:\n" 454 | "\t-p bib \tspecify the database file\n" 455 | "\t-e \taccumulate references\n" 456 | "\t-m \tmerge multiple references in a single .[/.] block\n" 457 | "\t-i \tinitials for authors' first and middle names\n" 458 | "\t-o xy \tcitation macro (\\*[xy label])\n" 459 | "\t-a xy \tauthor-year citation macro (\\*[xy label])\n" 460 | "\t-sa \tsort by author last names\n"; 461 | 462 | int main(int argc, char *argv[]) 463 | { 464 | int i, j; 465 | for (i = 1; i < argc; i++) { 466 | switch (argv[i][0] == '-' ? argv[i][1] : 'h') { 467 | case 'm': 468 | multiref = 1; 469 | break; 470 | case 'e': 471 | accumulate = 1; 472 | break; 473 | case 'p': 474 | refdb = fopen(argv[i][2] ? argv[i] + 2 : argv[++i], "r"); 475 | if (refdb) { 476 | db_parse(); 477 | fclose(refdb); 478 | } 479 | refdb = NULL; 480 | break; 481 | case 'o': 482 | refmac = argv[i][2] ? argv[i] + 2 : argv[++i]; 483 | break; 484 | case 'i': 485 | initials = 1; 486 | break; 487 | case 'a': 488 | refmac_auth = argv[i][2] ? argv[i] + 2 : argv[++i]; 489 | break; 490 | case 's': 491 | sortall = (unsigned char) (argv[i][2] ? argv[i][2] : argv[++i][0]); 492 | break; 493 | default: 494 | fprintf(stderr, "%s", usage); 495 | return 1; 496 | } 497 | } 498 | if (refauth && multiref) { 499 | fprintf(stderr, "refer: cannot use -m with -a\n"); 500 | return 1; 501 | } 502 | refer(); 503 | for (i = 0; i < refs_n; i++) 504 | for (j = 0; j < LEN(refs[i].keys); j++) 505 | if (refs[i].keys[j]) 506 | free(refs[i].keys[j]); 507 | for (i = 0; i < refs_n; i++) 508 | for (j = 0; j < LEN(refs[i].auth); j++) 509 | if (refs[i].auth[j]) 510 | free(refs[i].auth[j]); 511 | return 0; 512 | } 513 | --------------------------------------------------------------------------------