#!/usr/bin/env python """ BibLaTeX check on missing fields and consistent name conventions, especially developed for requirements in Computer Science. """ __author__ = "Pez Cuckow" __version__ = "0.1.4" __credits__ = ["Pez Cuckow", "BibTex Check 0.2.0 by Fabian Beck"] __license__ = "MIT" __email__ = "emailpezcuckow.com" #################################################################### # Properties (please change according to your needs) #################################################################### # links citeulikeUsername = "" # if no username is profided, no CiteULike links appear citeulikeHref = "http://www.citeulike.org/user/" + \ citeulikeUsername + "/article/" libraries = [("Scholar", "http://scholar.google.de/scholar?hl=en&q="), ("Google", "https://www.google.com/search?q="), ("DBLP", "http://dblp.org/search/index.php#query="), ("IEEE", "http://ieeexplore.ieee.org/search/searchresult.jsp?queryText="), ("ACM", "http://dl.acm.org/results.cfm?query="), ] # fields that are required for a specific type of entry requiredFields = {"article": ["author", "title", "journaltitle/journal", "year/date"], "book": ["author", "title", "year/date"], "mvbook": "book", "inbook": ["author", "title", "booktitle", "year/date"], "bookinbook": "inbook", "suppbook": "inbook", "booklet": ["author/editor", "title", "year/date"], "collection": ["editor", "title", "year/date"], "mvcollection": "collection", "incollection": ["author", "title", "booktitle", "year/date"], "suppcollection": "incollection", "manual": ["author/editor", "title", "year/date"], "misc": ["author/editor", "title", "year/date"], "online": ["author/editor", "title", "year/date", "url"], "patent": ["author", "title", "number", "year/date"], "periodical": ["editor", "title", "year/date"], "suppperiodical": "article", "proceedings": ["title", "year/date"], "mvproceedings": "proceedings", "inproceedings": ["author", "title", "booktitle", "year/date"], "reference": "collection", "mvreference": "collection", "inreference": "incollection", "report": ["author", "title", "type", "institution", "year/date"], "thesis": ["author", "title", "type", "institution", "year/date"], "unpublished": ["author", "title", "year/date"], # semi aliases (differing fields) "mastersthesis": ["author", "title", "institution", "year/date"], "techreport": ["author", "title", "institution", "year/date"], # other aliases "conference": "inproceedings", "electronic": "online", "phdthesis": "mastersthesis", "www": "online", "school": "mastersthesis" } #################################################################### #import os import string import re import sys from optparse import OptionParser # Parse options usage = sys.argv[ 0] + " [-b|--bib=] [-a|--aux=] [-o|--output=] [-v|--view] [-h|--help]" parser = OptionParser(usage) parser.add_option("-b", "--bib", dest="bibFile", help="Bib File", metavar="input.bib", default="input.bib") parser.add_option("-a", "--aux", dest="auxFile", help="Aux File", metavar="input.aux", default="references.aux") parser.add_option("-o", "--output", dest="htmlOutput", help="HTML Output File", metavar="output.html") parser.add_option("-v", "--view", dest="view", action="store_true", help="Open in Browser") parser.add_option("-N", "--no-console", dest="no_console", action="store_true", help="Do not print problems to console") (options, args) = parser.parse_args() # Backporting Python 3 open(encoding="utf-8") to Python 2 # based on http://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2 if sys.version_info[0] > 2: # py3k pass else: # py2 import codecs import warnings reload(sys) sys.setdefaultencoding('utf8') def open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None): if newline is not None: warnings.warn('newline is not supported in py2') if not closefd: warnings.warn('closefd is not supported in py2') if opener is not None: warnings.warn('opener is not supported in py2') return codecs.open(filename=file, mode=mode, encoding=encoding, errors=errors, buffering=buffering) ### End Backport ### print("INFO: Reading references from '" + options.bibFile + "'") try: fIn = open(options.bibFile, 'r', encoding="utf8") except IOError as e: print("ERROR: Input bib file '" + options.bibFile + "' doesn't exist or is not readable") sys.exit(-1) if options.no_console: print("INFO: Will surpress problems on console") if options.htmlOutput: print("INFO: Will output HTML to '" + options.htmlOutput + "'" + (" and auto open in the default web browser" if options.view else "")) else:#output a default file if -o option is not provided options.htmlOutput=options.bibFile.repalce('.bib','.html') # Filter by reference ID's that are used usedIds = set() if options.auxFile: print("INFO: Filtering by references found in '" + options.auxFile + "'") try: fInAux = open(options.auxFile, 'r', encoding="utf8") for line in fInAux: if line.startswith("\\citation"): ids = line.split("{")[1].rstrip("} \n").split(", ") for id in ids: if (id != ""): usedIds.add(id) fInAux.close() except IOError as e: print ("WARNING: Aux file '" + options.auxFile + "' doesn't exist -> not restricting entries") # Go through and check all references completeEntry = "" currentId = "" ids = [] currentType = "" currentArticleId = "" currentTitle = "" fields = [] problems = [] subproblems = [] counterMissingFields = 0 counterFlawedNames = 0 counterWrongTypes = 0 counterNonUniqueId = 0 counterWrongFieldNames = 0 removePunctuationMap = dict((ord(char), None) for char in string.punctuation) for line in fIn: line = line.strip("\n") if line.startswith("@"): if currentId in usedIds or not usedIds: for fieldName, requiredFieldsType in requiredFields.items(): if fieldName == currentType.lower(): # alises use a string to point at another set of fields currentRequiredFields = requiredFieldsType while isinstance(currentRequiredFields, str): currentRequiredFields = requiredFields[currentRequiredFields] # resolve alias for requiredFieldsString in currentRequiredFields: # support for author/editor syntax typeFields = requiredFieldsString.split('/') # at least one the required fields is not found if set(typeFields).isdisjoint(fields): subproblems.append( "missing field '" + requiredFieldsString + "'") counterMissingFields += 1 else: subproblems = [] if currentId in usedIds or (currentId and not usedIds): cleanedTitle = currentTitle.translate(removePunctuationMap) problem = "
" problem += "

" + currentId + " (" + currentType + ")

" problem += "" problem += "
" + currentTitle problem += "
" problem += "
    " for subproblem in subproblems: problem += "
  • " + subproblem + "
  • " if not options.no_console: print("PROBLEM: " + currentId + " - " + subproblem) problem += "
" problem += "
" problem += "
Current BibLaTex Entry
" problem += "
" + completeEntry + "
" problem += "
" problems.append(problem) fields = [] subproblems = [] currentId = line.split("{")[1].rstrip(",\n") if currentId in ids: subproblems.append("non-unique id: '" + currentId + "'") counterNonUniqueId += 1 else: ids.append(currentId) currentType = line.split("{")[0].strip("@ ") completeEntry = line + "
" else: if line != "": completeEntry += line + "
" if currentId in usedIds or not usedIds: if "=" in line: # biblatex is not case sensitive field = line.split("=")[0].strip().lower() fields.append(field) value = line.split("=")[1].strip("{} ,\n") if field == "author": currentAuthor = filter( lambda x: not (x in "\\\"{}"), value.split(" and ")[0]) if field == "citeulike-article-id": currentArticleId = value if field == "title": currentTitle = re.sub(r'\}|\{', r'', value) ############################################################### # Checks (please (de)activate/extend to your needs) ############################################################### # check if type 'proceedings' might be 'inproceedings' if currentType == "proceedings" and field == "pages": subproblems.append( "wrong type: maybe should be 'inproceedings' because entry has page numbers") counterWrongTypes += 1 # check if abbreviations are used in journal titles if currentType == "article" and (field == "journal" or field == "journaltitle"): if "." in line: subproblems.append( "flawed name: abbreviated journal title '" + value + "'") counterFlawedNames += 1 # check booktitle format; expected format "ICBAB '13: Proceeding of the 13th International Conference on Bla and Blubb" # if currentType == "inproceedings" and field == "booktitle": # if ":" not in line or ("Proceedings" not in line and "Companion" not in line) or "." in line or " '" not in line or "workshop" in line or "conference" in line or "symposium" in line: #subproblems.append("flawed name: inconsistent formatting of booktitle '"+value+"'") #counterFlawedNames += 1 # check if title is capitalized (heuristic) # if field == "title": # for word in currentTitle.split(" "): #word = word.strip(":") # if len(word) > 7 and word[0].islower() and not "-" in word and not "_" in word and not "[" in word: #subproblems.append("flawed name: non-capitalized title '"+currentTitle+"'") #counterFlawedNames += 1 # break ############################################################### fIn.close() problemCount = counterMissingFields + counterFlawedNames + counterWrongFieldNames + counterWrongTypes + counterNonUniqueId # Write out our HTML file if options.htmlOutput: html = open(options.htmlOutput, 'w', encoding="utf8") html.write(""" BibLatex Check

BibLaTeX Check


""") html.write("

Info

    ") html.write("
  • bib file: " + options.bibFile + "
  • ") html.write("
  • aux file: " + options.auxFile + "
  • ") html.write("
  • # entries: " + str(len(problems)) + "
  • ") html.write("
  • # problems: " + str(problemCount) + "
    • ") html.write("
    • # missing fields: " + str(counterMissingFields) + "
    • ") html.write("
    • # flawed names: " + str(counterFlawedNames) + "
    • ") html.write("
    • # wrong types: " + str(counterWrongTypes) + "
    • ") html.write("
    • # non-unique id: " + str(counterNonUniqueId) + "
    • ") html.write("
    • # wrong field: " + str(counterWrongFieldNames) + "
    • ") html.write("
") problems.sort() for problem in problems: html.write(problem) html.write("") html.close() if options.view: import webbrowser webbrowser.open(html.name) print("SUCCESS: Report {} has been generated".format(options.htmlOutput)) if problemCount > 0: print("WARNING: Found {} problems.".format(problemCount)) sys.exit(-1)