#! /usr/local/bin/perl $Script = 'tex2bib'; $Version=0.97; # 22 Feb 2006 10:50:35 $Author='Michael Friendly (friendly@yorku.ca)'; # Copyright (c) 1997 Michael Friendly # # License: # This is free software subject to copyright, released according to the # BSD style license. It may be freely used, modified and distributed. # See: http://www.ctan.org/tex-archive/help/Catalogue/licenses.bsd.html # for details and further information. # tex2bib # Input a TeX document containing \bibitems, translate these # to BibTeX format # # Usage: # tex2bib [-k][-i infile] [-o outfile] # -k: regenerate keys # if infile not given, reads from stdin # if outfile not given, prints to stdout # The entire tex document is scanned for \bibitems, ending when # the string '\end{thebibliography}' is read. # Assumes that bibitems are formatted as follows: # -- {key}author(s), (date) at the beginning # -- titles of books or names of journals: {\em title} # -- article titles: after date, `` '' quotes optional # -- volume, pages: {\it vol}, nnn-nnn. # -- publisher/address: address:publisher # # All text in the bibitem which cannot be parsed is included # in a note = { } field # # Changes # 0.96 1998/06/22 Allow \bibitem keys to contain [\w:-] # 0.97 2006/02/21 Added BSD license ################################################################# # Examples of a book, article, inproceedings: #\bibitem{Bertin83}Bertin, J. (1983), # {\em Semiology of Graphics} (trans. W. Berg). Madison, WI: # University of Wisconsin Press. # #\bibitem{Bickel75}Bickel, P. J., Hammel, J. W. and O'Connell, J. W. # (1975). # Sex bias in graduate admissions: data from Berkeley. {\em # Science}, {\it 187}, 398-403. # #\bibitem{Farebrother87}Farebrother, R. W. (1987), # ``Mechanical representations of the ${L}_1$ and ${L}_2$ estimation # problems'', In Y. Dodge (ed.) {\em Statistical data analysis # based on the L1 norm and related methods}, Amsterdam: # North-Holland., 455-464. # These are output as: #@Book{ Bertin:83, # author = {J. Bertin}, # year = 1983, # title = {Semiology of Graphics}, # publisher = {University of Wisconsin Press}, # address = {Madison, WI}, # note = {(trans. W. Berg).} #} #@Article{ Bickel:75, # author = {Bickel, P. J. and Hammel, J. W. and O'Connell, J. W.}, # year = 1975, # title = {Sex Bias in Graduate Admissions: Data from Berkeley}, # journal = {Science}, # volume = 187, # pages = {398-403} #} #@InCollection{ Farebrother:87, # author = {R. W. Farebrother}, # year = 1987, # title = {Mechanical Representations of the ${L}_1$ and ${L}_2$ Estimation Problems}, # booktitle = {Statistical Data Analysis Based on the L1 Norm and Related Methods}, # editor = {Y. Dodge}, # publisher = {North-Holland}, # address = {Amsterdam}, # pages = {455-464} # Text in a bibitem is removed from the bibitem as it is assigned to # bibtex fields. Any text remaining is assigned to a note={ } field # at the end. Since the parsing is heuristic, some manual fixup work # can be expected at the end. ################################################################# require 'getopt.pl' ; Getopt("oikdrt:"); # ARGV now contains [inputfile] and # $opt_o, $opt_i might be set. $default_type = $opt_t || 'Article'; open(STDIN, "<$opt_i") or die "-i $opt_i: can't open.\n" if $opt_i ; open(STDOUT, ">$opt_o") or die "-o $opt_o: can't create.\n" if $opt_o ; ####################### Process input files #################### $bibs=0; while (<>) { /^\s*\\end\{thebibliography/ && last; if (/\\bibitem\s*\{([\w\d:-]+)\}/) { $bibs++; $key = $1; # print STDERR "$bibs key: $key\n"; } # skip if we're still reading tex text (no bibitems encountered) next unless $bibs; chomp; s/^\s*/ /; $lines .= $_; } # All lines containing \bibitems have been read into the $lines string. # Now, split into separate items @items = split(/\\bibitem\s*/, $lines); print STDERR "items has ", scalar(@items), " items\n"; @months = qw(january february march april may jun july august september october november december); $month_pat = join('|', @months); foreach (@months) { $month_abbrv{$_} = substr($_, 0,3); } $ordinal = 'first|second|third|fourth|1st\.?|2nd\.?|3rd\.?|4th\.?'; # title words to ignore for casing; foreach ( qw(a about an and by for from in of on the to von with) ) { $ignore_case{$_} = 1; } $outitems =0; %keys_seen = {}; foreach $i (0..$#items) { undef $title; undef $pages; undef $journal; undef $volume; undef $number; undef $booktitle; undef $editor; undef $edition; undef $month; undef $publisher; undef $address; undef $chapter; undef $rest; print "$items[$i]\n" if $opt_d; # assume each entry starts with # {key}Author, F.I. etc (date[a-z]?) # $items[$i] =~ /^\{([^}]+)\}([^\(]*)\((\d{4})[a-z]?\)[,.]?\s*/; # ($key, $authors, $date) = ($1, $2, $3); $items[$i] =~ /^\{([^}]+)\}\s*/; $key = $1; # print "key: $key\n"; $rest = $'; $rest =~ /\((\d{4})[a-z]?\)[,.]?\s*/ && do { $authors = $`; $date = $1; $rest = $'; }; unless (defined($key)) { print STDERR "Skipping: $authors, $date in \n$items[$i]\n"; next; } $rest =~ s/^[.,]?\s+//; $rest =~ s/\s+$//; # print "key: $key\n"; # $key =~ m/(['A-Za-z]+)([^:]*)/ && # do { $key = "$1:$2"; }; # print "key: $key\n"; &parse_authors; $orig_key = $key; $key = &generate_key if $opt_k or $keys_seen{$key}; # if the $key has already been seen, make a new, unique one if ($keys_seen{$key}) { foreach $suffix (('b'..'z')) { $try = $key . $suffix; # print STDERR "Seen $key ... trying $try\n"; unless ($keys_seen{$try}) {$key = $try; last; }; } print STDERR "New unique key generated: $key (was $orig_key)\n" unless $key eq $orig_key; } $keys_seen{$key}++; # assume it's an article at first $type = '@' . $default_type; if ($rest =~ /^\{\\em\s+/) { #tech report or book $type = '@Book'; $rest =~ m/^\{\\em\s+(['\w ,?:]+)\}[., ]*/; $title = $1; $rest = $'; &parse_edition; &parse_publish; $rest =~ m/\sreport(s?)[,]?\s*/i && do { $type ='@TechReport'; $rest = $` . $'; &parse_report_number; }; } else { #does it begin with quoted title? if ($rest =~ /^``/){ $rest =~ m/^``([^`]+)''[., ]*/; $title = $1; $rest = $'; } else { $rest =~ m#^(['\{\}\w\d,\(\)\\ :\?/-]+)[. ]*#; $title = $1; $rest = $'; } $rest =~ s/^[,\s]+//; if ($rest =~ /^\{\\em\s+/) { $rest =~ m/^\{\\em\s+([\w\\& ,'-?]+)\}[., ]*/; $journal = $1; $rest = $'; } # parse {\it volume (number)} &parse_volume; $rest =~ m/,?\s*(\d{1,4}--?\d{1,4})[.,]?\s*/ && do { $pages = $1; $rest = $`. $'; $pages =~ s/(\d)-(\d)/$1--$2/; }; $rest =~ m/\(($month_pat)\)/i && do { ($month = $1) =~ tr/A-Z/a-z/; $month = $month_abbrv{$month}; $rest = $` . $'; }; $rest =~ m/(technical|tech\.)\s+report[,.]?/i && do { $type ='@TechReport'; $rest = $` . $'; &parse_report_number; }; $rest =~ m/^In\s+/i && do { $rest = $'; $type ='@InCollection'; # maybe, proceedings $rest =~ m/\{\\em\s+([\w ,':!?]+)\}[., ]*/ && do { $booktitle = $1; $rest = $` . $'; $type = '@InProceedings' if $booktitle =~ m/proceedings/i; }; $rest =~ m/([\w., ]+)\s+\(eds?\.\)[,.]?\s*/i && do { $editor = $1; $rest = $` . $'; }; $rest =~ m/Chapter\s+(\d+)[.,]?\s*/i && do { $chapter = $1; $rest = $` . $'; }; &parse_edition; &parse_publish; }; $rest =~ m/proceedings/i && do { $type = '@InProceedings'; # print "$items[$i]\n"; }; } $rest =~ s/^\s+//; $rest =~ s/$date([,]?)//; $title = &case_title($title); $booktitle = &case_title($booktitle) if $booktitle; print "$type\{$key"; print ",\n author\t= {$authors}"; print ",\n year\t= $date"; print ",\n title\t= {$title}" if $title; print ",\n booktitle\t= {$booktitle}" if $booktitle; print ",\n edition\t= {$edition}" if $edition; print ",\n editor\t= {$editor}" if $editor; print ",\n publisher\t= {$publisher}" if $publisher; print ",\n address\t= {$address}" if $address; print ",\n journal\t= {$journal}" if $journal; print ",\n volume\t= $volume" if $volume; print ",\n month\t= $month" if $month; print ",\n number\t= $number" if $number; print ",\n pages\t= {$pages}" if $pages; print ",\n chapter\t= {$chapter}" if $chapter; print ",\n note\t= {$rest}" if $rest; print "\n\}\n"; $outitems++; } print STDERR "$outitems items processed\n"; exit; ########################### subroutines ############################# # separate multiple authors with 'and' sub parse_authors { # fix authors field $authors =~ s/\s+$//; $authors =~ s/\\&/and/; $authors =~ s/([A-Z])\.([A-Z])\./$1. $2./g; $authors =~ s/ ([A-Z])\.,\s+(?!Jr.)/ $1. and /g; $authors =~ s/ and and / and /; # $authors =~ s/Friendly, M./Friendly, Michael/; $authors =~ s/M. Friendly/Friendly, M./; local($commas) = ($authors =~ tr/,/,/); if ($opt_r && $commas == 1) { @auth = split(/, /, $authors); $authors = join(' ',reverse(@auth)); } } # extract last names of authors into @auth array sub split_authors { @auth = split(/ and /, $authors); @auth =~ grep(s/, .*$//g,@auth); # strip trailing initials @auth =~ grep(s/^([A-Z]\.\s*)+//g,@auth); # @auth =~ grep(s/^[a-z ]+//,@auth); # strip any name prefixes foreach $i (0..$#auth) { @n = split(/ /, $auth[$i]); $auth[$i] = pop(@n); # strip anything before lastname $auth[$i] =~ s/[^\w]//g; # remove non-word chars } # print STDERR "Authors: $authors --> ", join('|',@auth),"\n" if scalar(@auth) > 1; } sub parse_volume { $rest =~ m/,?\s*\{\\it\s+([A-Za-z \d()]+)\s*\}[.,]?/ && do { $volume = $1; $rest = $`. $'; $volume =~ m/\(([ \w]+)\)/ && do { $volume = $`; $number = $1; }; $volume = "{$volume}" unless $volume =~ m/^\d*$/; $number = "\{$number\}" unless $number =~ m/^\d*$/; }; } # find publisher and address in book/inproceedings items # assume the format is address:publisher sub parse_publish { local($before, $after); $rest =~ m#:\s*([\w-\\/& ]+)[.]?# && do { $publisher = $1; ($before, $after) = ($`, $'); $before =~ m/([\w, ]+)$/ && do { $address = $1; $before = $`; $address =~ s/^\s+//; }; $rest = $before . $after; }; } sub parse_report_number { $rest =~ m/\s*(no\.|number)\s+(\d+)[,.]?\s*/i && do { $number = $2; $rest = $` . $'; } } sub parse_edition { $rest =~ m/\(?($ordinal).*(edition|ed\.)\)?\.?\s*/i && do { $edition = $1; $rest = $` . $'; } } # Initial-caps for all non-ignored words sub case_title { local($t) = @_; local(@words) = split(/\s+/, $t); local($w) =0; local($colon); foreach (@words) { $w++; next if /^[A-Z]/; unless ($colon) {next if $ignore_case{$_}}; s/^([a-z])/\u$1/; $colon = tr/:/:/; } join(' ', @words); } sub generate_key { local($yr, $key); ($yr = $date) =~ s/^1\d//; &split_authors; if (scalar(@auth) < 3) { $key = join('', @auth, ':', $yr); } else { $key = join('', $auth[0], '-etal:', $yr); } # print STDERR "New key: $key (", join('|', @auth), ": $date)\n"; $key; }