├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── biblatex_check.py ├── input.bib └── tests └── input.bib /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Pezmc 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.com/donate/?hosted_button_id=PG64JUEFDZL74'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | test: 9 | name: Testing 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-python@v4 14 | with: 15 | python-version: '3.10' 16 | - name: Run tests 17 | run: | 18 | CORRECT_N_PROBLEMS=$(grep -oP '\d+(?= errors expected)' tests/input.bib) 19 | N_PROBLEMS=$(python ./biblatex_check.py -N -b tests/input.bib | grep -oP '\d+(?= problems)') 20 | if [[ "$N_PROBLEMS" == "$CORRECT_N_PROBLEMS" ]]; then 21 | echo "Correct number of problems" 22 | else 23 | echo "Incorrect number of problems, $N_PROBLEMS instead of 15" 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | 217 | #generated html files 218 | biblatex_check.html 219 | output.html 220 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## How do I... 4 | 5 | * [Use This Guide](#introduction)? 6 | * Ask or Say Something? 🤔🐛😱 7 | * [Request Support](#request-support) 8 | * [Report an Error or Bug](#report-an-error-or-bug) 9 | * [Request a Feature](#request-a-feature) 10 | * Make Something? 🤓👩🏽‍💻📜🍳 11 | * [Project Setup](#project-setup) 12 | * [Contribute Documentation](#contribute-documentation) 13 | * [Contribute Code](#contribute-code) 14 | 15 | ## Introduction 16 | 17 | Thank you so much for your interest in contributing!. All types of contributions are encouraged and valued. 18 | 19 | ## Request Support 20 | 21 | If you have a question about this project, how to use it, or just need clarification about something: 22 | 23 | * Open an Issue at https://github.com/Pezmc/BibLatex-Check/issues 24 | * Provide as much context as you can about what you're running into. 25 | * Provide project and python version 26 | 27 | ## Report an Error or Bug 28 | 29 | If you run into an error or bug with the project: 30 | 31 | * Open an Issue at https://github.com/Pezmc/BibLatex-Check/issues 32 | * Include *reproduction steps* that someone else can follow to recreate the bug or error on their own. 33 | * Provide project and python version 34 | 35 | ## Request a Feature 36 | 37 | If the project doesn't do something you need or want it to do: 38 | 39 | * Open an Issue at https://github.com/Pezmc/BibLatex-Check/issues 40 | * Provide as much context as you can about what you're running into. 41 | * Please try and be clear about why existing features and alternatives would not work for you. 42 | 43 | Note: The team is unlikely to be able to accept every single feature request that is filed. Please understand if they need to say no. 44 | 45 | ## Project Setup 46 | 47 | So you wanna contribute some code! That's great! This project uses GitHub Pull Requests to manage contributions, so [read up on how to fork a GitHub project and file a PR](https://guides.github.com/activities/forking) if you've never done it before. 48 | 49 | If you want to go the usual route and run the project locally, though: 50 | 51 | * Ensure python 2 or 3 is installed 52 | * [Fork the project](https://guides.github.com/activities/forking/#fork) 53 | 54 | Then in your terminal: 55 | 56 | * `cd path/to/your/clone` 57 | * `./biblatex_check.py -b tests/input.bib` 58 | 59 | And you should be ready to go! 60 | 61 | ## Contribute Documentation 62 | 63 | Documentation is a super important, critical part of this project. 64 | 65 | Documentation contributions of any size are welcome! Feel free to file a PR even if you're just rewording a sentence to be more clear, or fixing a spelling mistake! 66 | 67 | ## Contribute Code 68 | 69 | We like code commits a lot! They're super handy, and they keep the project going and doing the work it needs to do to be useful to others. 70 | 71 | Code contributions of just about any size are acceptable! 72 | 73 | The main difference between code contributions and documentation contributions is that contributing code requires inclusion of relevant tests for the code being added or changed. Contributions without accompanying tests will be held off until a test is added, unless the maintainers consider the specific tests to be either impossible, or way too much of a burden for such a contribution. 74 | 75 | Please be sure to run the test files using both Python 2.7 and Python 3, more details in the readme. 76 | 77 | ## Attribution 78 | 79 | This guide was generated using the WeAllJS `CONTRIBUTING.md` generator. [Make your own](https://npm.im/weallcontribute)! 80 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pez Cuckow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This project no longer under active development 2 | ### It remains fully functional and maintenance is still completed 3 | 4 | Assistance with enhancements, feature requests and bug fixes are all very welcome! 5 | 6 | Any PR's will be reviewed promptly. 7 | 8 | --- 9 | 10 | BibLatex-Check 11 | ============== 12 | 13 | **A web based version of this checker is now available: https://github.com/Pezmc/BibLatex-Linter** 14 | 15 | *A python2/3 script for checking BibLatex .bib files* 16 | 17 | BibTeX Check is a small Python script that goes through a list of references and checks if certain required fields are available, for instance, if each publication is assigned a year or if a journal article has a volume and issue number. 18 | 19 | Additionally, it allows for consistency checks of names of conference proceedings and could easily be extended to other needs. 20 | 21 | The results of the check are printed to an html file, which includes links to Google Scholar, DBLP, etc. for each flawed reference. These links help retrieving missing information and correcting the entries efficiently. 22 | 23 | Please note that it is **not a BibLaTeX validator**. And in the current version, it might not yet be able to parse every valid bib file. The software targets the specific needs of Computer Scientist, but may be applicable in other fields as well. 24 | 25 | For use in automated environments, BibLaTeX-Check returns errors on the console (can be disabled). 26 | Further, it returns an exit code depending on whether problems have been found. 27 | 28 | The html output is tested with Firefox and Chrome, but the current version does not properly work with Internet Explorer. 29 | 30 | ## Getting Started 31 | 32 | Just copy the file into a directory with write permission, then run the script 33 | 34 | ./biblatex_check.py <-b input.bib> [-a input.aux] [-o output.html] 35 | 36 | If you provide the additional aux file (created when compiling a tex document), then the check of the bib file is restricted to only those entries that are really cited in the tex document. 37 | 38 | ## Options 39 | 40 | Specify these when calling the script. 41 | 42 | - -b (--bib=file.bib) Set the input Bib File 43 | - -a (--aux=file.aux) Set the input Aux File 44 | - -o (--output=file.html) Write results to the HTML Output File. 45 | - -v (--view) Open in Browser. Use together with -o. 46 | - -N (--no-console) Do not print problems to console. An exit code is always returned. 47 | 48 | ## Help 49 | 50 | See `./biblatex_check.py -h` for basic help. 51 | 52 | If your getting an environment error, try using `python ./biblatex_check.py` or `python3 ./biblatex_check.py` depending on your OS. 53 | 54 | ## Alternatives 55 | 56 | BibLatex check is adapted from [BibTex Check](https://code.google.com/p/bibtex-check/) by Fabian Beck, which can be used to validate BibTex files. 57 | 58 | See [BibTex vs BibLaTex vs NatBib](http://tex.stackexchange.com/questions/25701/bibtex-vs-biber-and-biblatex-vs-natbib) for a comparison of different referencing packages. 59 | 60 | ## Screenshot 61 | 62 | ![Screenshots of the BibLatex check screen](/../screenshots/screenshots/checkscreen.png?raw=true "BibLatex Check") 63 | 64 | ## Development 65 | 66 | The checker is a single python script that takes .bib files as input and prints to console and/or an html file. 67 | 68 | It maintains compatibility with Python 2, so any changes should be run against both Python 2.7 and 3. 69 | 70 | Any bug fixes should be paired with a new test case in `tests/input.bib` 71 | 72 | ### "Running" the tests 73 | 74 | ```bash 75 | python3 ./biblatex_check.py -b tests/input.bib 76 | python2 ./biblatex_check.py -b tests/input.bib 77 | ``` 78 | 79 | Then _manually_ confirm the number of errors matches the details top of `tests/input.bib` 80 | 81 | 82 | ## License 83 | 84 | MIT license 85 | -------------------------------------------------------------------------------- /biblatex_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | BibLaTeX check on missing fields and consistent name conventions, 5 | especially developed for requirements in Computer Science. 6 | """ 7 | 8 | __author__ = "Pez Cuckow" 9 | __version__ = "1.0.0" 10 | __credits__ = ["Pez Cuckow", "BibTex Check 0.2.0 by Fabian Beck"] 11 | __license__ = "MIT" 12 | __email__ = "emailpezcuckow.com" 13 | 14 | #################################################################### 15 | # Properties (please change according to your needs) 16 | #################################################################### 17 | 18 | # links 19 | citeulikeUsername = "" # if no username is provided, no CiteULike links appear 20 | citeulikeHref = "http://www.citeulike.org/user/" + citeulikeUsername + "/article/" 21 | 22 | libraries = [ 23 | ("Scholar", "http://scholar.google.de/scholar?hl=en&q="), 24 | ("Google", "https://www.google.com/search?q="), 25 | ("DBLP", "http://dblp.org/search/index.php#query="), 26 | ("IEEE", "http://ieeexplore.ieee.org/search/searchresult.jsp?queryText="), 27 | ("ACM", "http://dl.acm.org/results.cfm?query="), 28 | ] 29 | 30 | # fields that are required for a specific type of entry 31 | requiredEntryFields = { 32 | "article": ["author", "title", "journaltitle/journal", "year/date"], 33 | "book": ["author", "title", "year/date"], 34 | "mvbook": "book", 35 | "inbook": ["author", "title", "booktitle", "year/date"], 36 | "bookinbook": "inbook", 37 | "suppbook": "inbook", 38 | "booklet": ["author/editor", "title", "year/date"], 39 | "collection": ["editor", "title", "year/date"], 40 | "mvcollection": "collection", 41 | "incollection": ["author", "title", "booktitle", "year/date"], 42 | "suppcollection": "incollection", 43 | "manual": ["author/editor", "title", "year/date"], 44 | "misc": ["author/editor", "title", "year/date"], 45 | "online": ["author/editor", "title", "year/date", "url"], 46 | "patent": ["author", "title", "number", "year/date"], 47 | "periodical": ["editor", "title", "year/date"], 48 | "suppperiodical": "article", 49 | "proceedings": ["title", "year/date"], 50 | "mvproceedings": "proceedings", 51 | "inproceedings": ["author", "title", "booktitle", "year/date"], 52 | "reference": "collection", 53 | "mvreference": "collection", 54 | "inreference": "incollection", 55 | "report": ["author", "title", "type", "institution", "year/date"], 56 | "thesis": ["author", "title", "type", "institution", "year/date"], 57 | "unpublished": ["author", "title", "year/date"], 58 | # semi aliases (differing fields) 59 | "mastersthesis": ["author", "title", "institution", "year/date"], 60 | "techreport": ["author", "title", "institution", "year/date"], 61 | # other aliases 62 | "conference": "inproceedings", 63 | "electronic": "online", 64 | "phdthesis": "mastersthesis", 65 | "www": "online", 66 | "school": "mastersthesis", 67 | } 68 | 69 | # BibLaTeX has backwards compatibility with BibTeX for these fiends 70 | fieldAliases = {"school": "institution", "address": "location"} 71 | 72 | #################################################################### 73 | 74 | import string 75 | import re 76 | import sys 77 | from optparse import OptionParser 78 | 79 | ### Parse Args ### 80 | 81 | usage = ( 82 | sys.argv[0] 83 | + " [-b|--bib=] [-a|--aux=] [-o|--output=] [-v|--view] [-h|--help]" 84 | ) 85 | 86 | parser = OptionParser(usage) 87 | 88 | parser.add_option( 89 | "-b", 90 | "--bib", 91 | dest="bibFile", 92 | help="Bib File", 93 | metavar="input.bib", 94 | default="input.bib", 95 | ) 96 | 97 | parser.add_option( 98 | "-a", 99 | "--aux", 100 | dest="auxFile", 101 | help="Aux File", 102 | metavar="input.aux", 103 | default="references.aux", 104 | ) 105 | 106 | parser.add_option( 107 | "-o", "--output", dest="htmlOutput", help="HTML Output File", metavar="output.html" 108 | ) 109 | 110 | parser.add_option( 111 | "-v", "--view", dest="view", action="store_true", help="Open in Browser" 112 | ) 113 | 114 | parser.add_option( 115 | "-N", 116 | "--no-console", 117 | dest="no_console", 118 | action="store_true", 119 | help="Do not print problems to console", 120 | ) 121 | 122 | (options, args) = parser.parse_args() 123 | 124 | ### Backport Python 3 open(encoding="utf-8") to Python 2 ### 125 | # based on http://stackoverflow.com/questions/10971033/backporting-python-3-openencoding-utf-8-to-python-2 126 | 127 | if sys.version_info[0] > 2: 128 | # py3k 129 | pass 130 | else: 131 | # py2 132 | import codecs 133 | import warnings 134 | 135 | reload(sys) 136 | sys.setdefaultencoding("utf8") 137 | 138 | def open( 139 | file, 140 | mode="r", 141 | buffering=-1, 142 | encoding=None, 143 | errors=None, 144 | newline=None, 145 | closefd=True, 146 | opener=None, 147 | ): 148 | if newline is not None: 149 | warnings.warn("newline is not supported in py2") 150 | if not closefd: 151 | warnings.warn("closefd is not supported in py2") 152 | if opener is not None: 153 | warnings.warn("opener is not supported in py2") 154 | return codecs.open( 155 | filename=file, 156 | mode=mode, 157 | encoding=encoding, 158 | errors=errors, 159 | buffering=buffering, 160 | ) 161 | 162 | 163 | ### Handle Args ### 164 | 165 | print("INFO: Reading references from '" + options.bibFile + "'") 166 | try: 167 | fIn = open(options.bibFile, "r", encoding="utf8") 168 | except IOError as e: 169 | print( 170 | "ERROR: Input bib file '" 171 | + options.bibFile 172 | + "' doesn't exist or is not readable" 173 | ) 174 | sys.exit(-1) 175 | 176 | if options.no_console: 177 | print("INFO: Will suppress problems on console") 178 | 179 | if options.htmlOutput: 180 | print( 181 | "INFO: Will output HTML to '" 182 | + options.htmlOutput 183 | + "'" 184 | + (" and auto open in the default web browser" if options.view else "") 185 | ) 186 | 187 | # Filter by reference ID's that are used 188 | usedIds = set() 189 | if options.auxFile: 190 | print("INFO: Filtering by references found in '" + options.auxFile + "'") 191 | try: 192 | fInAux = open(options.auxFile, "r", encoding="utf8") 193 | for auxLine in fInAux: 194 | if auxLine.startswith("\\citation"): 195 | entryIds = auxLine.split("{")[1].rstrip("} \n").split(", ") 196 | for entryId in entryIds: 197 | if entryId != "": 198 | usedIds.add(entryId) 199 | fInAux.close() 200 | except IOError as e: 201 | print( 202 | "WARNING: Aux file '" 203 | + options.auxFile 204 | + "' doesn't exist -> not restricting entries" 205 | ) 206 | 207 | ### Methods ### 208 | 209 | removePunctuationMap = dict((ord(char), None) for char in string.punctuation) 210 | 211 | 212 | def resolveAliasedRequiredFields(entryRequiredFields, requiredFieldsDict): 213 | # Aliases use a string to point at another set of fields 214 | while isinstance(entryRequiredFields, str): 215 | entryRequiredFields = requiredFieldsDict.get(entryRequiredFields) 216 | 217 | return entryRequiredFields 218 | 219 | 220 | def generateEntryProblemsHTML( 221 | itemHTML, itemId, type, articleId, title, problems, author, lineNumber 222 | ): 223 | cleanedTitle = title.translate(removePunctuationMap) 224 | html = "
" 225 | html += "

" + itemId + " (" + type + ")

" 226 | html += "" 243 | html += "
" + title + " (" + author + ")" 244 | html += "
" 245 | html += "
    " 246 | 247 | for subproblem in problems: 248 | html += "
  • " + subproblem + "
  • " 249 | if not options.no_console: 250 | errorMessage = "PROBLEM: {}:{} - {} - {}\n".format( 251 | options.bibFile, lineNumber, itemId, subproblem 252 | ) 253 | sys.stderr.write(errorMessage) 254 | 255 | html += "
" 256 | html += "
" 257 | html += "
Current BibLaTex Entry
" 258 | html += "
" + itemHTML + "
" 259 | html += "
" 260 | 261 | return html 262 | 263 | 264 | ### Globals ### 265 | 266 | entriesIds = [] 267 | entriesProblemsHTML = [] 268 | 269 | entryArticleId = "" 270 | entryAuthor = "" 271 | entryFields = [] 272 | entryHTML = "" 273 | entryId = "" 274 | entryProblems = [] 275 | entryTitle = "" 276 | entryType = "" 277 | 278 | counterFlawedNames = 0 279 | counterMissingCommas = 0 280 | counterMissingFields = 0 281 | counterNonUniqueId = 0 282 | counterWrongFieldNames = 0 283 | counterWrongTypes = 0 284 | 285 | lastLine = 0 286 | 287 | ### Global Abusing Handlers ### 288 | 289 | 290 | def handleNewEntryStarting(line): 291 | global entryArticleId, entryAuthor, entryFields, entryHTML, entryId, entryProblems, entryTitle, entryType 292 | global counterMissingCommas, counterNonUniqueId 293 | 294 | entryFields = [] 295 | entryProblems = [] 296 | 297 | entryId = line.split("{")[1].rstrip(",\n") 298 | 299 | if line[-1] != ",": 300 | entryProblems.append("missing comma at '@" + entryId + "' definition") 301 | counterMissingCommas += 1 302 | 303 | if entryId in entriesIds: 304 | entryProblems.append("non-unique id: '" + entryId + "'") 305 | counterNonUniqueId += 1 306 | else: 307 | entriesIds.append(entryId) 308 | 309 | entryType = line.split("{")[0].strip("@ ") 310 | entryHTML = line + "
" 311 | 312 | 313 | def handleEntryEnding(lineNumber, line): 314 | global entryArticleId, entryAuthor, entryFields, entryHTML, entryId, entryProblems, entryTitle, entryType 315 | global counterMissingFields, counterMissingCommas, removePunctuationMap 316 | global entriesProblemsHTML 317 | global lastLine 318 | 319 | # Last line of entry is allowed to have missing comma 320 | if lastLine == lineNumber - 1: 321 | entryProblems = entryProblems[:-1] 322 | counterMissingCommas -= 1 323 | 324 | # Support for type aliases 325 | entryFields = list(map( 326 | lambda typeName: fieldAliases.get(typeName) 327 | if typeName in fieldAliases 328 | else typeName, 329 | entryFields, 330 | )) 331 | 332 | entryHTML += line + "
" 333 | 334 | if entryId in usedIds or not usedIds: 335 | entryRequiredFields = requiredEntryFields.get(entryType.lower()) 336 | entryRequiredFields = resolveAliasedRequiredFields( 337 | entryRequiredFields, requiredEntryFields 338 | ) 339 | 340 | for requiredEntryField in entryRequiredFields: 341 | # support for author/editor syntax 342 | requiredEntryField = requiredEntryField.split("/") 343 | 344 | # at least one the required fields is not found 345 | if set(requiredEntryField).isdisjoint(entryFields): 346 | entryProblems.append( 347 | "missing field '" + "/".join(requiredEntryField) + "'" 348 | ) 349 | counterMissingFields += 1 350 | 351 | else: 352 | entryProblems = [] 353 | 354 | if entryId in usedIds or (entryId and not usedIds): 355 | entryProblemsHTML = generateEntryProblemsHTML( 356 | entryHTML, 357 | entryId, 358 | entryType, 359 | entryArticleId, 360 | entryTitle, 361 | entryProblems, 362 | entryAuthor, 363 | lineNumber, 364 | ) 365 | entriesProblemsHTML.append(entryProblemsHTML) 366 | 367 | 368 | def handleEntryLine(lineNumber, line): 369 | global entryHTML, entryId 370 | global usedIds 371 | 372 | if line != "": 373 | entryHTML += line + "
" 374 | 375 | if entryId in usedIds or not usedIds: 376 | if "=" in line: 377 | handleEntryField(lineNumber, line) 378 | 379 | 380 | def handleEntryField(lineNumber, line): 381 | global entryArticleId, entryAuthor, entryFields, entryHTML, entryId, entryProblems, entryTitle, entryType 382 | global counterFlawedNames, counterWrongTypes, counterWrongFieldNames, counterMissingCommas 383 | global lastLine 384 | 385 | fieldName = line.split("=")[0].strip().lower() # biblatex is not case sensitive 386 | fieldValue = line.split("=")[1].strip(", \n").strip("{} \n") 387 | 388 | entryFields.append(fieldName) 389 | 390 | # Checks per field type 391 | if fieldName == "author": 392 | entryAuthor = "".join(filter(lambda x: not (x in '\\"{}'), fieldValue.split(" and ")[0])) 393 | for author in fieldValue.split(" and "): 394 | comp = author.split(",") 395 | if len(comp) == 0: 396 | entryProblems.append( 397 | "too little name components for an author in field 'author'" 398 | ) 399 | elif len(comp) > 2: 400 | entryProblems.append( 401 | "too many name components for an author in field 'author'" 402 | ) 403 | elif len(comp) == 2: 404 | if comp[0].strip() == "": 405 | entryProblems.append( 406 | "last name of an author in field 'author' empty" 407 | ) 408 | if comp[1].strip() == "": 409 | entryProblems.append( 410 | "first name of an author in field 'author' empty" 411 | ) 412 | 413 | elif fieldName == "citeulike-article-id": 414 | entryArticleId = fieldValue 415 | 416 | elif fieldName == "title": 417 | entryTitle = re.sub(r"\}|\{", r"", fieldValue) 418 | 419 | ############################################################### 420 | # Checks (please (de)activate/extend to your needs) 421 | ############################################################### 422 | 423 | # check if type 'proceedings' might be 'inproceedings' 424 | elif entryType == "proceedings" and fieldName == "pages": 425 | entryProblems.append( 426 | "wrong type: maybe should be 'inproceedings' because entry has page numbers" 427 | ) 428 | counterWrongTypes += 1 429 | 430 | # check if abbreviations are used in journal titles 431 | elif entryType == "article" and fieldName in ("journal", "journaltitle"): 432 | if "." in line: 433 | entryProblems.append( 434 | "flawed name: abbreviated journal title '" + fieldValue + "'" 435 | ) 436 | counterFlawedNames += 1 437 | 438 | # check booktitle format; expected format "ICBAB '13: Proceeding of the 13th International Conference on Bla and Blubb" 439 | # if entryType == "inproceedings" and fieldName == "booktitle": 440 | # 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: 441 | # entryProblems.append("flawed name: inconsistent formatting of booktitle '"+fieldValue+"'") 442 | # counterFlawedNames += 1 443 | 444 | # check if title is capitalized (heuristic) 445 | # if fieldName == "title": 446 | # for word in entryTitle.split(" "): 447 | # word = word.strip(":") 448 | # if len(word) > 7 and word[0].islower() and not "-" in word and not "_" in word and not "[" in word: 449 | # entryProblems.append("flawed name: non-capitalized title '"+entryTitle+"'") 450 | # counterFlawedNames += 1 451 | # break 452 | 453 | # check for commas at end of line 454 | if line[-1] != ",": 455 | entryProblems.append( 456 | "missing comma at end of line, at '" + fieldName + "' field definition." 457 | ) 458 | counterMissingCommas += 1 459 | lastLine = lineNumber 460 | 461 | 462 | ### Parse input file ### 463 | 464 | for (bibLineNumber, bibLine) in enumerate(fIn): 465 | bibLine = bibLine.strip("\n") 466 | 467 | # Staring a new entry 468 | if bibLine.startswith("@"): 469 | handleNewEntryStarting(bibLine) 470 | 471 | # Closing out the current entry 472 | elif bibLine.startswith("}"): 473 | handleEntryEnding(bibLineNumber, bibLine) 474 | 475 | else: 476 | handleEntryLine(bibLineNumber, bibLine) 477 | 478 | fIn.close() 479 | 480 | problemCount = ( 481 | counterMissingFields 482 | + counterFlawedNames 483 | + counterWrongFieldNames 484 | + counterWrongTypes 485 | + counterNonUniqueId 486 | + counterMissingCommas 487 | ) 488 | 489 | # Write out our HTML file 490 | if options.htmlOutput: 491 | html = open(options.htmlOutput, "w", encoding="utf8") 492 | html.write( 493 | """ 494 | 495 | 496 | 497 | BibLatex Check 498 | 663 | 664 | 733 | 734 | 735 |
736 |

BibLaTeX Check

737 |
738 | 739 |
740 | 741 | 746 | 747 | 751 | 752 | 753 |
754 |
755 |
756 |
757 | """ 758 | ) 759 | html.write("

Info

    ") 760 | html.write("
  • bib file: " + options.bibFile + "
  • ") 761 | html.write("
  • aux file: " + options.auxFile + "
  • ") 762 | html.write("
  • # entries with errors: " + str(len(entriesProblemsHTML)) + "
  • ") 763 | html.write("
  • # problems: " + str(problemCount) + "
    • ") 764 | html.write("
    • # missing fields: " + str(counterMissingFields) + "
    • ") 765 | html.write("
    • # flawed names: " + str(counterFlawedNames) + "
    • ") 766 | html.write("
    • # wrong types: " + str(counterWrongTypes) + "
    • ") 767 | html.write("
    • # non-unique id: " + str(counterNonUniqueId) + "
    • ") 768 | html.write("
    • # wrong field: " + str(counterWrongFieldNames) + "
    • ") 769 | html.write("
    • # missing comma: " + str(counterMissingCommas) + "
    • ") 770 | html.write("
") 771 | 772 | entriesProblemsHTML.sort() 773 | for problem in entriesProblemsHTML: 774 | html.write(problem) 775 | html.write("") 776 | html.close() 777 | 778 | if options.view: 779 | import os 780 | import pathlib 781 | import webbrowser 782 | 783 | webbrowser.open(pathlib.Path(os.path.abspath(html.name)).as_uri()) 784 | 785 | print("SUCCESS: Report {} has been generated".format(options.htmlOutput)) 786 | 787 | if problemCount > 0: 788 | print("WARNING: Found {} problems.".format(problemCount)) 789 | sys.exit(-1) 790 | -------------------------------------------------------------------------------- /input.bib: -------------------------------------------------------------------------------- 1 | % Example input/text file for the biblatex checker 2 | % This file should pass with no errors 3 | 4 | @misc{lehman2006biblatex, 5 | title={The biblatex package}, 6 | author={Lehman, Philipp and others}, 7 | year={2006} 8 | } 9 | 10 | @book{lamport1986latex, 11 | author={Lamport, Leslie}, 12 | title={LaTEX: User's Guide \& Reference Manual}, 13 | year={1986}, 14 | publisher={Addison-Wesley} 15 | } 16 | 17 | @book{loeliger2012version, 18 | title={Version Control with Git: Powerful tools and techniques for collaborative software development}, 19 | author={Loeliger, Jon and McCullough, Matthew}, 20 | year={2012}, 21 | publisher={" O'Reilly Media, Inc."} 22 | } 23 | 24 | @online{torvalds2010git, 25 | title={GIT: Fast Version Control System}, 26 | author={Torvalds, Linus and Hamano, Junio}, 27 | url={http://git-scm.com}, 28 | year={2010} 29 | } 30 | 31 | @www{lamport1994latex, 32 | author={Creodocs Limited}, 33 | title={LaTeX Templates: The best source of free quality LaTeX templates}, 34 | url={http://latextemplates.com}, 35 | year={2017} 36 | } -------------------------------------------------------------------------------- /tests/input.bib: -------------------------------------------------------------------------------- 1 | % This file should fail with the commented errors 2 | % 15 errors expected 3 | 4 | % "misc": ["author/editor", "title", "year/date"] 5 | % year/date missing 6 | @misc{lehman2006biblatex, 7 | title={The biblatex package}, 8 | author={Lehman, Philipp and others} 9 | } 10 | 11 | % "book": ["author", "title", "year/date"] 12 | % author missing 13 | @book{lamport1986latex, 14 | title={LaTEX: User's Guide \& Reference Manual}, 15 | year={1986} 16 | } 17 | 18 | % "unpublished": ["author", "title", "year/date"], 19 | % author and year missing 20 | @unpublished{loeliger2012version, 21 | title={Not yet published book} 22 | } 23 | 24 | % "techreport": ["author", "title", "institution", "year/date"] 25 | % institution and title missing 26 | @techreport{torvalds2010git, 27 | author={Torvalds, Linus and Hamano, Junio}, 28 | year={2010} 29 | } 30 | 31 | % "electronic": "online", "online": ["author/editor", "title", "year/date", "url"] 32 | % url missing 33 | @electronic{lamport1994latex, 34 | editor={Creodocs Limited}, 35 | title={LaTeX Templates: The best source of free quality LaTeX templates}, 36 | year={2017} 37 | } 38 | 39 | % "conference": "inproceedings", "inproceedings": ["author", "title", "booktitle", "year/date"], 40 | % book title and year/date missing. 41 | @conference{emberFest2017, 42 | author={Joe Bloggs}, 43 | title={All About Ember} 44 | } 45 | 46 | % "article": ["author", "title", "journal", "year"] 47 | % missing commas on author and journal lines 48 | @book{elephants, 49 | author={Rodney A. Brooks} 50 | title={Elephants Don't Play Chess}, 51 | journal={Robotics and Autonomous Systems} 52 | year={1990} 53 | } 54 | 55 | % "techreport": ["author", "title", "institution", "year/date"] 56 | @techreport{paneMyers 57 | author={John Pane and Brad Myers}, 58 | title={Usability Issues in the Design of Novice Programming Systems} 59 | institution={Carnegie Mellon University} 60 | year={1996}, 61 | } 62 | 63 | % "article": ["author", "title", "journal", "year"] 64 | % wrong author 65 | @techreport{nistFIPS197Advanced2001, 66 | title = {{{FIPS}} 197, {{Advanced Encryption Standard}} ({{AES}})}, 67 | author = {, NIST}, 68 | institution = {National Institute of Standards and Technology}, 69 | date = {2001-11-26}, 70 | pages = {51}, 71 | langid = {english} 72 | } 73 | 74 | % "report": ["author", "title", "type", "institution", "year/date"], 75 | % 3 author components (should be 'lastname, firstname') 76 | @report{petersonObservationsModelingSeismic1993, 77 | title = {Observations and Modeling of Seismic Background Noise}, 78 | author = {Peterson, Jon, R}, 79 | date = {1993}, 80 | institution = {{U.S. Geological Survey}}, 81 | doi = {10.3133/ofr93322}, 82 | url = {http://pubs.er.usgs.gov/publication/ofr93322}, 83 | urldate = {2020-12-30}, 84 | number = {93-322}, 85 | series = {Open-{{File Report}}}, 86 | type = {USGS Numbered Series} 87 | } 88 | 89 | % "report": ["author", "title", "type", "institution", "year/date"], 90 | % no errors, school is an alias for institution 91 | @report{bibLatexChecker, 92 | author={Pez Cuckow}, 93 | title={BibLatex-Check}, 94 | school={Robotics and Autonomous Systems}, 95 | address={Address is an alias fro location}, 96 | type = {Website}, 97 | year = {2014}, 98 | } 99 | 100 | % "book": ["author", "title", "year/date"], 101 | % Capitals handled, and no errors in author field 102 | % year/date field missing 103 | @book {hartshorne1977algebraic, 104 | AUTHOR = {Hartshorne R}, 105 | TITLE = {Algebraic geometry}, 106 | NOTE = {Graduate Texts in Mathematics, No. 52}, 107 | } 108 | --------------------------------------------------------------------------------