New Upstream Release - bibtexparser

Ready changes

Summary

Merged new upstream version: 1.4.0+ds (was: 1.1.0+ds).

Diff

diff --git a/.editorconfig b/.editorconfig
new file mode 100755
index 0000000..40335f7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+charset=utf-8
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+end_of_line = lf
+
+[*.{yaml,yml}]
+indent_size = 2
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..6aaa93d
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,72 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ "master" ]
+  schedule:
+    - cron: '25 22 * * 1'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'python' ]
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v2
+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        
+        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+        # queries: security-extended,security-and-quality
+
+        
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v2
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+    #   If the Autobuild fails above, remove it and uncomment the following three lines. 
+    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+    # - run: |
+    #   echo "Run, Build Application using script"
+    #   ./location_of_script_within_repo/buildscript.sh
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..d2b386e
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,31 @@
+
+name: Tests
+
+on: [push, pull_request]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-node@v2-beta
+        with:
+          node-version: '12'
+          check-latest: true
+
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install dependencies (tf ${{ matrix.tf-version }} )
+        run: |
+          pip install -r requirements.txt
+
+      - name: Run Tests
+        run: |
+          python -m unittest discover -s ./bibtexparser/tests
diff --git a/CHANGELOG b/CHANGELOG
index a1b888e..fd59e95 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,15 +1,53 @@
-v1.xxx
+v1.4.0
 ======
 
+Breaking Changes
+----------------
+* Using common strings in string interpolation is now the default (#311 by @MiWeiss).
+  See the PR for more details, and how to fall back to old behavior.
+
+New Features / Improvements
+---------------------------
+* Add option to adjust alignment of text of multi-line values. (#290 by @michaelfruth)
+* Raise warning if parser is used multiple times (#312 by @mrn97),
+  which leads to a merged library. Set `parser.expect_multiple_parse = True` to disable the warning.
+* Allow preservation of existing order of entry fields in writer (#317 by @michaelfruth)
+
+v1.3.0
+======
+
+Most of these changes come from @MiWeiss personal fork, as one,
+hence have a commit id instead of a PR number in CHANGELOG.
+
+* Support for python 3.10
+* Removing unused dependencies (fb86407 and d4e3f42)
+* No empty extra lines at end of generated files (f84e318)
+* Allow capital AND when splitting author list (a8527ff)
+* Fix problem in `homogenize_latex_encoding` when authors are lists (528714c)
+* Long description in setup.py (#304)
+* Typo fixes (2b2c8ee, 11340f3)
+
+v1.2.0
+======
+
+* Support for python 3 only
+
 v1.1.0
 ======
 
+* Handles declarations on lines starting with spaces after comments (#199)
+* accept extra characters in @string (#148)
+* Add support for BibLaTeX annotations (#208)
+* Strip the latex command str for latex_to_unicode() (#182)
+* Add comma_last to BibTexWriter (#219)
+* Feature: crossref Support (#216)
 * BUGFIX: Fix for pyparsing 2.3.1 (#226)
 * NEW: Add support for BibLaTeX annotations (#208)
 * NEW: Feature: crossref Support (#216)
 * ENH: Handles declarations on lines starting with spaces after comments (#199)
 * ENH: Checks for empty citekeys and whitespaces (#213)
 
+
 v1.0.1
 ======
 
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
deleted file mode 100644
index 93674a8..0000000
--- a/CONTRIBUTORS.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-- François Boulogne
-  Project coordinator
-
-- bibserver's contributors
-  for the parser's core and the permission to release this project under LGPLv3 and BSD
-
-- Shuen-Huei (Drake) Guan
-  Python 2.7 porting
-
-- Sebastien Diemer
-  Bugfix
-
-- Georg C. Brückmann
-  Support for non-standard entry types
-
-- Uwe Schmidt
-  String replacement
-
-- faph
-  coma fixes, optional keys sanitising, refactoring and other improvements
-
-- Steven M. Bellovin
-  Fix braces detection
-
-- Sven Goossens
-  Support for bibtex with leading spaces
-
-- Michal Grochmal
-  Comma first syntax support
-
-- Cschaffner
-  New features in bwriter
-
-- Olivier Mangin
-  Pyparsing implementation of the parser.
-
-- Blair Bonnett
-  customization.splitname() function
diff --git a/MANIFEST.in b/MANIFEST.in
index 3eabe2b..72f1d7e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,5 @@
 include *.txt
 include *.md
+include COPYING
 include docs/Makefile
 include docs/source/*
diff --git a/README.rst b/README.rst
index d7a7996..220597f 100644
--- a/README.rst
+++ b/README.rst
@@ -4,12 +4,10 @@ python-bibtexparser
 Python library to parse `bibtex <https://en.wikipedia.org/wiki/BibTeX>`_ files.
 
 
-IMPORTANT: the library is looking for new maintainers. Please, manifest yourself if you are interested.
-
 .. contents::
 
 
-Bibtexparser relies on `pyparsing <https://pypi.python.org/pypi/pyparsing>`_ and is compatible with Python 2.7 and 3.3 or newer.
+Bibtexparser relies on `pyparsing <https://pypi.python.org/pypi/pyparsing>`_ and is compatible with Python 3.3 or newer.
 
 Documentation
 -------------
@@ -23,7 +21,6 @@ Upgrading
 ---------
 
 Please, read the changelog before upgrading regarding API modifications.
-Prior version 1.0, we do not hesitate to modify the API to get the best API from your feedbacks.
 
 License
 -------
@@ -41,3 +38,7 @@ History and evolutions
 The original source code was part of bibserver from `OKFN <http://github.com/okfn/bibserver>`_. This project is released under the AGPLv3. OKFN and the original authors kindly provided the permission to use a subpart of their project (ie the bibtex parser) under LGPLv3. Many thanks to them!
 
 The parser evolved to a new core based on pyparsing.
+
+News (July 7h, 2022): This library has a new maintainer (`@MiWeiss <https://github.com/MiWeiss>`_).
+Versions 1.x will be mostly bugfixes and maintenance.
+In the meantime, we are working on a new version 2.0.0 which will be a complete rewrite of the library.
diff --git a/RELEASE b/RELEASE
index 8cd85ae..9cb1ab6 100644
--- a/RELEASE
+++ b/RELEASE
@@ -3,9 +3,17 @@ How to release
 
 * Update CHANGELOG
 * Update version in __init__.py
-* git tag -a 'vX'
-* merge in branch latest
-* Send the package on pypi
+* Commit and open PR on Github to see that all tests pass,
+  and then merge the PR.
+* Check out master locally
+* Build wheel
     python setup.py sdist upload
-* tick the doc version on readthedocs
-* Update version in __init__.py
+* Check long description
+    twine check dist/*
+* Upload to pypi-test
+    twine upload --repository testpypi dist/*
+* Upload to pypi
+    twine check dist/*
+* Create release on Github
+* Verify docs are automatically updated
+
diff --git a/bibtexparser/__init__.py b/bibtexparser/__init__.py
index f0ef20f..0518ff0 100644
--- a/bibtexparser/__init__.py
+++ b/bibtexparser/__init__.py
@@ -25,9 +25,7 @@ __all__ = [
     'loads', 'load', 'dumps', 'dump', 'bibdatabase',
     'bparser', 'bwriter', 'bibtexexpression', 'latexenc', 'customization',
 ]
-__version__ = '1.1.0'
-
-import sys
+__version__ = '1.4.0'
 
 from . import bibdatabase, bibtexexpression, bparser, bwriter, latexenc, customization
 
@@ -107,8 +105,4 @@ def dump(bib_database, bibtex_file, writer=None):
     """
     if writer is None:
         writer = bwriter.BibTexWriter()
-    if sys.version_info >= (3, 0):
-        bibtex_file.write(writer.write(bib_database))
-    else:
-        # Encode to UTF-8
-        bibtex_file.write(writer.write(bib_database).encode("utf-8"))
+    bibtex_file.write(writer.write(bib_database))
diff --git a/bibtexparser/bibdatabase.py b/bibtexparser/bibdatabase.py
index 2857d25..75600b7 100644
--- a/bibtexparser/bibdatabase.py
+++ b/bibtexparser/bibdatabase.py
@@ -1,17 +1,10 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 from collections import OrderedDict
-import sys
 import logging
 
 logger = logging.getLogger(__name__)
 
-if sys.version_info >= (3, 0):
-    ustr = str
-else:
-    ustr = unicode
-
 
 STANDARD_TYPES = set([
     'article',
@@ -90,7 +83,7 @@ class BibDatabase(object):
     def entry_sort_key(entry, fields):
         result = []
         for field in fields:
-            result.append(ustr(entry.get(field, '')).lower())  # Sorting always as string
+            result.append(str(entry.get(field, '')).lower())  # Sorting always as string
         return tuple(result)
 
     def _make_entries_dict(self):
@@ -276,4 +269,4 @@ def as_text(text_string_or_expression):
                   (BibDataString, BibDataStringExpression)):
         return text_string_or_expression.get_value()
     else:
-        return ustr(text_string_or_expression)
+        return str(text_string_or_expression)
diff --git a/bibtexparser/bibtexexpression.py b/bibtexparser/bibtexexpression.py
index cf8bdd1..855d2e8 100644
--- a/bibtexparser/bibtexexpression.py
+++ b/bibtexparser/bibtexexpression.py
@@ -99,6 +99,8 @@ class BibtexExpression(object):
     ParseException = pp.ParseException
 
     def __init__(self):
+        # Init parse action functions
+        self.set_string_name_parse_action(lambda s, l, t: None)
 
         # Bibtex keywords
 
@@ -141,7 +143,6 @@ class BibtexExpression(object):
         string_expr = pp.delimitedList(
             (quoted_value | braced_value | string_name), delim='#'
             )('StringExpression')
-        self.set_string_expression_parse_action(lambda s, l, t: None)
         string_expr.addParseAction(self._string_expr_parse_action)
 
         value = (integer | string_expr)('Value')
@@ -270,17 +271,8 @@ class BibtexExpression(object):
     def _string_name_parse_action(self, s, l, t):
         return self._string_name_parse_action_fun(s, l, t)
 
-    def set_string_expression_parse_action(self, fun):
-        """Set the parseAction for string_expression expression.
-
-        .. Note::
-
-            See set_string_name_parse_action.
-        """
-        self._string_expr_parse_action_fun = fun
-
     def _string_expr_parse_action(self, s, l, t):
-        return self._string_expr_parse_action_fun(s, l, t)
+        return BibDataStringExpression.expression_if_needed(t)
 
     def parseFile(self, file_obj):
         return self.main_expression.parseFile(file_obj, parseAll=True)
diff --git a/bibtexparser/bparser.py b/bibtexparser/bparser.py
index c34ba8a..34620a4 100644
--- a/bibtexparser/bparser.py
+++ b/bibtexparser/bparser.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 # Original source: github.com/okfn/bibserver
@@ -7,9 +6,9 @@
 # Etienne Posthumus (epoz)
 # Francois Boulogne <fboulogne at april dot org>
 
-import sys
 import io
 import logging
+import warnings
 
 from bibtexparser.bibdatabase import (BibDatabase, BibDataString, as_text,
                                       BibDataStringExpression, STANDARD_TYPES)
@@ -20,12 +19,6 @@ logger = logging.getLogger(__name__)
 __all__ = ['BibTexParser']
 
 
-if sys.version_info >= (3, 0):
-    ustr = str
-else:
-    ustr = unicode
-
-
 def parse(data, *args, **kwargs):
     parser = BibTexParser(*args, **kwargs)
     return parser.parse(data)
@@ -82,18 +75,24 @@ class BibTexParser(object):
                  ignore_nonstandard_types=True,
                  homogenize_fields=False,
                  interpolate_strings=True,
-                 common_strings=False,
+                 common_strings=True,
                  add_missing_from_crossref=False):
         """
-        Creates a parser for rading BibTeX files
+        Creates a parser for reading BibTeX files
 
         :return: parser
         :rtype: `BibTexParser`
         """
+
+        self._parse_call_count = 0
+        #: Parsers can be called multiple times, merging databases, but raising a warning.
+        #      Set this to `True` to disable the warning. Default: `False`
+        self.expect_multiple_parse = False
+
         self.bib_database = BibDatabase()
 
         #: Load common strings such as months abbreviation
-        #: Default: `False`.
+        #: Default: `True` (new in 1.4.0 - was previously `False`)
         self.common_strings = common_strings
         if self.common_strings:
             self.bib_database.load_common_strings()
@@ -150,6 +149,14 @@ class BibTexParser(object):
         :return: bibliographic database
         :rtype: BibDatabase
         """
+
+        self._parse_call_count += 1
+        if self._parse_call_count == 2 and not self.expect_multiple_parse:
+            warnings.warn("The parser has been called more than once. "
+                          "Subsequent parse calls lead to a combined BibTeX library. \n"
+                          "To avoid the warning, set property `parser.expect_multiple_parse` to `True`.",
+                          category=UserWarning, stacklevel=2)
+
         bibtex_file_obj = self._bibtex_file_obj(bibtex_str)
         try:
             self._expr.parseFile(bibtex_file_obj)
@@ -186,14 +193,6 @@ class BibTexParser(object):
         self._expr.set_string_name_parse_action(
             lambda s, l, t:
                 BibDataString(self.bib_database, t[0]))
-        if self.interpolate_strings:
-            maybe_interpolate = lambda expr: as_text(expr)
-        else:
-            maybe_interpolate = lambda expr: expr
-        self._expr.set_string_expression_parse_action(
-            lambda s, l, t:
-                maybe_interpolate(
-                    BibDataStringExpression.expression_if_needed(t)))
 
         # Add notice to logger
         self._expr.add_log_function(logger.debug)
@@ -220,12 +219,13 @@ class BibTexParser(object):
     def _bibtex_file_obj(self, bibtex_str):
         # Some files have Byte-order marks inserted at the start
         byte = b'\xef\xbb\xbf'
-        if isinstance(bibtex_str, ustr):
-            byte = ustr(byte, self.encoding, 'ignore')
-            if bibtex_str[0] == byte:
+
+        if isinstance(bibtex_str, str):
+            byte = str(byte, self.encoding, 'ignore')
+            if len(bibtex_str) >= 1 and bibtex_str[0] == byte:
                 bibtex_str = bibtex_str[1:]
         else:
-            if bibtex_str[:3] == byte:
+            if len(bibtex_str) >= 3 and bibtex_str[:3] == byte:
                 bibtex_str = bibtex_str[3:]
             bibtex_str = bibtex_str.decode(encoding=self.encoding)
         return io.StringIO(bibtex_str)
@@ -239,7 +239,10 @@ class BibTexParser(object):
         """
         if not val or val == "{}":
             return ''
-        return val
+        elif self.interpolate_strings:
+            return as_text(val)
+        else:
+            return val
 
     def _clean_key(self, key):
         """ Lowercase a key and return as unicode.
@@ -249,8 +252,8 @@ class BibTexParser(object):
         :returns: (unicode) string -- value
         """
         key = key.lower()
-        if not isinstance(key, ustr):
-            return ustr(key, 'utf-8')
+        if not isinstance(key, str):
+            return str(key, 'utf-8')
         else:
             return key
 
@@ -321,7 +324,7 @@ class BibTexParser(object):
         :type string: string
         """
         if string_key in self.bib_database.strings:
-            logger.warning('Overwritting existing string for key: %s.',
+            logger.warning('Overwriting existing string for key: %s.',
                            string_key)
         logger.debug(u'Store string: {} -> {}'.format(string_key, string))
         self.bib_database.strings[string_key] = self._clean_val(string)
diff --git a/bibtexparser/bwriter.py b/bibtexparser/bwriter.py
index 9ccbea2..67190e6 100644
--- a/bibtexparser/bwriter.py
+++ b/bibtexparser/bwriter.py
@@ -1,10 +1,11 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # Author: Francois Boulogne
 # License:
 
 
 import logging
+from enum import Enum, auto
+from typing import Dict, Callable, Iterable
 from bibtexparser.bibdatabase import (BibDatabase, COMMON_STRINGS,
                                       BibDataString,
                                       BibDataStringExpression)
@@ -15,6 +16,38 @@ logger = logging.getLogger(__name__)
 __all__ = ['BibTexWriter']
 
 
+class SortingStrategy(Enum):
+    """
+    Defines different strategies for sorting the entries not defined in :py:attr:`~.BibTexWriter.display_order` and that are added at the end.
+    """
+    ALPHABETICAL_ASC = auto()
+    """
+    Alphabetical sorting in ascending order.
+    """
+    ALPHABETICAL_DESC = auto()
+    """
+    Alphabetical sorting in descending order.
+    """
+    PRESERVE = auto()
+    """
+    Preserves the order of the entries. Entries are not sorted.
+    """
+
+
+def _apply_sorting_strategy(strategy: SortingStrategy, items: Iterable[str]) -> Iterable[str]:
+    """
+    Sorts the items based on the given sorting strategy.
+    """
+    if strategy == SortingStrategy.ALPHABETICAL_ASC:
+        return sorted(items)
+    elif strategy == SortingStrategy.ALPHABETICAL_DESC:
+        return reversed(sorted(items))
+    elif strategy == SortingStrategy.PRESERVE:
+        return items
+    else:
+        raise NotImplementedError(f"The strategy {strategy.name} is not implemented.")
+
+
 def to_bibtex(parsed):
     """
     Convenience function for backwards compatibility.
@@ -59,13 +92,19 @@ class BibTexWriter(object):
         #: Align values. Determines the maximal number of characters used in any fieldname and aligns all values
         #    according to that by filling up with single spaces. Default: False
         self.align_values = False
+        #: Align multi-line values. Formats a multi-line value such that the text is aligned exactly
+        #    on top of each other. Default: False
+        self.align_multiline_values = False
         #: Characters(s) for separating BibTeX entries. Default: new line.
         self.entry_separator = '\n'
         #: Tuple of fields for ordering BibTeX entries. Set to `None` to disable sorting. Default: BibTeX key `('ID', )`.
         self.order_entries_by = ('ID', )
-        #: Tuple of fields for display order in a single BibTeX entry. Fields not listed here will be displayed
-        #: alphabetically at the end. Set to '[]' for alphabetical order. Default: '[]'
+        #: Tuple of fields for display order in a single BibTeX entry. Fields not listed here will be displayed at the
+        #    end in the order defined by display_order_sorting. Default: '[]'
         self.display_order = []
+        # Sorting strategy for entries not contained in display_order. Entries not defined in display_order are added
+        #    at the end in the order defined by this strategy. Default: SortingStrategy.ALPHABETICAL_ASC
+        self.display_order_sorting: SortingStrategy = SortingStrategy.ALPHABETICAL_ASC
         #: BibTeX syntax allows comma first syntax
         #: (common in functional languages), use this to enable
         #: comma first syntax as the bwriter output
@@ -98,7 +137,6 @@ class BibTexWriter(object):
         return bibtex
 
     def _entries_to_bibtex(self, bib_database):
-        bibtex = ''
         if self.order_entries_by:
             # TODO: allow sort field does not exist for entry
             entries = sorted(bib_database.entries, key=lambda x: BibDatabase.entry_sort_key(x, self.order_entries_by))
@@ -110,9 +148,7 @@ class BibTexWriter(object):
             widths = [max(map(len, entry.keys())) for entry in entries]
             self._max_field_width = max(widths)
 
-        for entry in entries:
-            bibtex += self._entry_to_bibtex(entry)
-        return bibtex
+        return self.entry_separator.join(self._entry_to_bibtex(entry) for entry in entries)
 
     def _entry_to_bibtex(self, entry):
         bibtex = ''
@@ -123,7 +159,7 @@ class BibTexWriter(object):
         # first those keys which are both in self.display_order and in entry.keys
         display_order = [i for i in self.display_order if i in entry]
         # then all the other fields sorted alphabetically
-        display_order += [i for i in sorted(entry) if i not in self.display_order]
+        display_order += [i for i in _apply_sorting_strategy(self.display_order_sorting, entry) if i not in self.display_order]
         if self.comma_first:
             field_fmt = u"\n{indent}, {field:<{field_max_w}} = {value}"
         else:
@@ -131,11 +167,31 @@ class BibTexWriter(object):
         # Write field = value lines
         for field in [i for i in display_order if i not in ['ENTRYTYPE', 'ID']]:
             try:
+                value = _str_or_expr_to_bibtex(entry[field])
+
+                if self.align_multiline_values:
+                    # Calculate indent of multi-line values. Text from a multiline string
+                    # should be aligned, i.e., be exactly on top of each other.
+                    # E.g.:       title = {Hello
+                    #                      World}
+                    # Calculate the indent of "World":
+                    # Left of field (whitespaces before e.g. 'title')
+                    value_indent = len(self.indent)
+                    # Field itself (e.g. len('title'))
+                    if self._max_field_width > 0:
+                        value_indent += self._max_field_width
+                    else:
+                        value_indent += len(field)
+                    # Right of field ' = ' (<- 3 chars) + '{' (<- 1 char)
+                    value_indent += 3 + 1
+
+                    value = value.replace('\n', '\n' + value_indent * ' ')
+
                 bibtex += field_fmt.format(
                     indent=self.indent,
                     field=field,
                     field_max_w=self._max_field_width,
-                    value=_str_or_expr_to_bibtex(entry[field]))
+                    value=value)
             except TypeError:
                 raise TypeError(u"The field %s in entry %s must be a string"
                                 % (field, entry['ID']))
@@ -144,7 +200,7 @@ class BibTexWriter(object):
                 bibtex += '\n'+self.indent+','
             else:
                 bibtex += ','
-        bibtex += "\n}\n" + self.entry_separator
+        bibtex += "\n}\n"
         return bibtex
 
     def _comments_to_bibtex(self, bib_database):
diff --git a/bibtexparser/customization.py b/bibtexparser/customization.py
index c8535e3..99c48fb 100644
--- a/bibtexparser/customization.py
+++ b/bibtexparser/customization.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 """
@@ -7,9 +6,9 @@ You can find inspiration from these functions to design yours.
 Each of them takes a record and return the modified record.
 """
 
-import re
 import logging
-
+import re
+import warnings
 from builtins import str
 
 from bibtexparser.latexenc import latex_to_unicode, string_to_latex, protect_uppercase
@@ -73,14 +72,14 @@ def splitname(name, strict_mode=True):
     # We'll iterate over the input once, dividing it into a list of words for
     # each comma-separated section. We'll also calculate the case of each word
     # as we work.
-    sections = [[]]      # Sections of the name.
-    cases = [[]]         # 1 = uppercase, 0 = lowercase, -1 = caseless.
-    word = []            # Current word.
-    case = -1            # Case of the current word.
-    level = 0            # Current brace level.
-    bracestart = False   # Will the next character be the first within a brace?
-    controlseq = True    # Are we currently processing a control sequence?
-    specialchar = None   # Are we currently processing a special character?
+    sections = [[]]  # Sections of the name.
+    cases = [[]]  # 1 = uppercase, 0 = lowercase, -1 = caseless.
+    word = []  # Current word.
+    case = -1  # Case of the current word.
+    level = 0  # Current brace level.
+    bracestart = False  # Will the next character be the first within a brace?
+    controlseq = True  # Are we currently processing a control sequence?
+    specialchar = None  # Are we currently processing a special character?
 
     # Using an iterator allows us to deal with escapes in a simple manner.
     nameiter = iter(name)
@@ -247,12 +246,12 @@ def splitname(name, strict_mode=True):
                 firstl = cases.index(0) - len(cases)
                 lastl = -cases[::-1].index(0) - 1
                 if lastl == -1:
-                    lastl -= 1      # Cannot consume the rest of the string.
+                    lastl -= 1  # Cannot consume the rest of the string.
 
                 # Pull the parts out.
                 parts['first'] = p0[:firstl]
-                parts['von'] = p0[firstl:lastl+1]
-                parts['last'] = p0[lastl+1:]
+                parts['von'] = p0[firstl:lastl + 1]
+                parts['last'] = p0[lastl + 1:]
 
             # No lowercase: last is the last word, first is everything else.
             else:
@@ -288,7 +287,7 @@ def splitname(name, strict_mode=True):
             if 0 in lcases:
                 split = len(lcases) - lcases[::-1].index(0)
                 if split == len(lcases):
-                    split = 0            # Last cannot be empty.
+                    split = 0  # Last cannot be empty.
                 parts['von'] = sections[0][:split]
                 parts['last'] = sections[0][split:]
 
@@ -346,7 +345,8 @@ def author(record):
     """
     if "author" in record:
         if record["author"]:
-            record["author"] = getnames([i.strip() for i in record["author"].replace('\n', ' ').split(" and ")])
+            record["author"] = getnames([i.strip() for i in re.split(r"\ and\ ", record["author"].replace('\n', ' '),
+                                                                     flags=re.IGNORECASE)])
         else:
             del record["author"]
     return record
@@ -366,7 +366,8 @@ def editor(record):
         if record["editor"]:
             record["editor"] = getnames([i.strip() for i in record["editor"].replace('\n', ' ').split(" and ")])
             # convert editor to object
-            record["editor"] = [{"name": i, "ID": i.replace(',', '').replace(' ', '').replace('.', '')} for i in record["editor"]]
+            record["editor"] = [{"name": i, "ID": i.replace(',', '').replace(' ', '').replace('.', '')} for i in
+                                record["editor"]]
         else:
             del record["editor"]
     return record
@@ -418,7 +419,8 @@ def journal(record):
     if "journal" in record:
         # switch journal to object
         if record["journal"]:
-            record["journal"] = {"name": record["journal"], "ID": record["journal"].replace(',', '').replace(' ', '').replace('.', '')}
+            record["journal"] = {"name": record["journal"],
+                                 "ID": record["journal"].replace(',', '').replace(' ', '').replace('.', '')}
 
     return record
 
@@ -482,7 +484,7 @@ def doi(record):
         if nodoi:
             link = record['doi']
             if link.startswith('10'):
-                link = 'http://dx.doi.org/' + link
+                link = 'https://doi.org/' + link
             record['link'].append({"url": link, "anchor": "doi"})
     return record
 
@@ -519,13 +521,21 @@ def homogenize_latex_encoding(record):
     :type record: dict
     :returns: dict -- the modified record.
     """
-    # First, we convert everything to unicode
+    #  First, we convert everything to unicode
     record = convert_to_unicode(record)
     # And then, we fall back
     for val in record:
         if val not in ('ID',):
             logger.debug('Apply string_to_latex to: %s', val)
-            record[val] = string_to_latex(record[val])
+            if isinstance(record[val], list):
+                record[val] = [
+                    string_to_latex(x) for x in record[val]
+                ]
+            elif isinstance(record[val], str):
+                record[val] = string_to_latex(record[val])
+            else:
+                warnings.warn('Unable to homogenize latex encoding for %s: Expected string or list,' % val,
+                              RuntimeWarning)
             if val == 'title':
                 logger.debug('Protect uppercase in title')
                 logger.debug('Before: %s', record[val])
@@ -544,6 +554,7 @@ def add_plaintext_fields(record):
     :type record: dict
     :returns: dict -- the modified record.
     """
+
     def _strip_string(string):
         for stripped in ['{', '}']:
             string = string.replace(stripped, "")
diff --git a/bibtexparser/latexenc.py b/bibtexparser/latexenc.py
index e192fc4..c6a2cfb 100644
--- a/bibtexparser/latexenc.py
+++ b/bibtexparser/latexenc.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 # Original source: github.com/okfn/bibserver
@@ -9,7 +8,6 @@
 
 import itertools
 import re
-import sys
 import unicodedata
 
 __all__ = ['string_to_latex', 'latex_to_unicode', 'protect_uppercase',
@@ -2686,16 +2684,10 @@ def prepare_unicode_to_latex():
         ("\uD7FF", "\\mathtt{9}"),
     )
 
-    if sys.version_info >= (3, 0):
-        unicode_to_latex = to_latex
-        unicode_to_crappy_latex1 = to_crappy1
-        unicode_to_crappy_latex2 = to_crappy2
-        unicode_to_latex_map = dict(unicode_to_latex)
-    else:
-        unicode_to_latex = tuple((k.decode('unicode-escape'), v) for k, v in to_latex)
-        unicode_to_crappy_latex1 = tuple((k.decode('unicode-escape'), v) for k, v in to_crappy1)
-        unicode_to_crappy_latex2 = tuple((k.decode('unicode-escape'), v) for k, v in to_crappy2)
-        unicode_to_latex_map = dict(unicode_to_latex)
+    unicode_to_latex = to_latex
+    unicode_to_crappy_latex1 = to_crappy1
+    unicode_to_crappy_latex2 = to_crappy2
+    unicode_to_latex_map = dict(unicode_to_latex)
 
 
 prepare_unicode_to_latex()
diff --git a/bibtexparser/tests/data/article_comma_first.bib b/bibtexparser/tests/data/article_comma_first.bib
index 83882f4..db86cfc 100644
--- a/bibtexparser/tests/data/article_comma_first.bib
+++ b/bibtexparser/tests/data/article_comma_first.bib
@@ -16,3 +16,4 @@
         , journal = {Nice Journal}
         , comments = {A comment}
         , keyword = {keyword1, keyword2}}
+
diff --git a/bibtexparser/tests/data/article_comma_first_and_trailing_comma_output.bib b/bibtexparser/tests/data/article_comma_first_and_trailing_comma_output.bib
index 6126c7f..7008678 100644
--- a/bibtexparser/tests/data/article_comma_first_and_trailing_comma_output.bib
+++ b/bibtexparser/tests/data/article_comma_first_and_trailing_comma_output.bib
@@ -12,4 +12,3 @@ multilines... and with a french érudit word}
  , year = {2013}
  ,
 }
-
diff --git a/bibtexparser/tests/data/article_comma_normal_multiple.bib b/bibtexparser/tests/data/article_comma_normal_multiple.bib
new file mode 100644
index 0000000..e703785
--- /dev/null
+++ b/bibtexparser/tests/data/article_comma_normal_multiple.bib
@@ -0,0 +1,42 @@
+@article{FuMetalhalideperovskite2019,
+    author = {Yongping Fu and Haiming Zhu and Jie Chen and Matthew P. Hautzinger and X.-Y. Zhu and Song Jin},
+    doi = {10.1038/s41578-019-0080-9},
+    journal = {Nature Reviews Materials},
+    month = {feb},
+    number = {3},
+    pages = {169-188},
+    publisher = {Springer Science and Business Media {LLC}},
+    title = {Metal halide perovskite nanostructures for optoelectronic applications and the study of physical properties},
+    url = {https://www.nature.com/articles/s41578-019-0080-9},
+    volume = {4},
+    year = {2019}
+}
+
+@article{SunEnablingSiliconSolar2014,
+    author = {Ke Sun and Shaohua Shen and Yongqi Liang and Paul E. Burrows and Samuel S. Mao and Deli Wang},
+    doi = {10.1021/cr300459q},
+    journal = {Chemical Reviews},
+    month = {aug},
+    number = {17},
+    pages = {8662-8719},
+    publisher = {American Chemical Society ({ACS})},
+    title = {Enabling Silicon for Solar-Fuel Production},
+    url = {http://pubs.acs.org/doi/10.1021/cr300459q},
+    volume = {114},
+    year = {2014}
+}
+
+@article{LiuPhotocatalytichydrogenproduction2016,
+    author = {Maochang Liu and Yubin Chen and Jinzhan Su and Jinwen Shi and Xixi Wang and Liejin Guo},
+    doi = {10.1038/nenergy.2016.151},
+    impactfactor = {54.000},
+    journal = {Nature Energy},
+    month = {sep},
+    number = {11},
+    pages = {16151},
+    publisher = {Springer Science and Business Media {LLC}},
+    title = {Photocatalytic hydrogen production using twinned nanocrystals and an unanchored {NiSx} co-catalyst},
+    url = {http://www.nature.com/articles/nenergy2016151},
+    volume = {1},
+    year = {2016}
+}
diff --git a/bibtexparser/tests/data/article_comma_normal_single.bib b/bibtexparser/tests/data/article_comma_normal_single.bib
new file mode 100644
index 0000000..24a0937
--- /dev/null
+++ b/bibtexparser/tests/data/article_comma_normal_single.bib
@@ -0,0 +1,13 @@
+@article{FuMetalhalideperovskite2019,
+    author = {Yongping Fu and Haiming Zhu and Jie Chen and Matthew P. Hautzinger and X.-Y. Zhu and Song Jin},
+    doi = {10.1038/s41578-019-0080-9},
+    journal = {Nature Reviews Materials},
+    month = {feb},
+    number = {3},
+    pages = {169-188},
+    publisher = {Springer Science and Business Media {LLC}},
+    title = {Metal halide perovskite nanostructures for optoelectronic applications and the study of physical properties},
+    url = {https://www.nature.com/articles/s41578-019-0080-9},
+    volume = {4},
+    year = {2019}
+}
diff --git a/bibtexparser/tests/data/article_multilines.bib b/bibtexparser/tests/data/article_multilines.bib
new file mode 100644
index 0000000..7a6bbe3
--- /dev/null
+++ b/bibtexparser/tests/data/article_multilines.bib
@@ -0,0 +1,13 @@
+@ARTICLE{Cesar2013,
+  author = {Jean César},
+  title = {A mutline line title is very amazing. It should be
+long enough to test multilines... with two lines or should we
+even test three lines... What an amazing title.},
+  year = {2013},
+  journal = {Nice Journal},
+  abstract = {This is an abstract. This line should be long enough to test
+multilines... and with a french érudit word},
+  comments = {A comment},
+  keyword = {keyword1, keyword2,
+multiline-keyword1, multiline-keyword2},
+}
diff --git a/bibtexparser/tests/data/article_output.bib b/bibtexparser/tests/data/article_output.bib
index 5289e8c..7966875 100644
--- a/bibtexparser/tests/data/article_output.bib
+++ b/bibtexparser/tests/data/article_output.bib
@@ -11,4 +11,3 @@ multilines... and with a french érudit word},
  volume = {12},
  year = {2013}
 }
-
diff --git a/bibtexparser/tests/data/article_trailing_comma_output.bib b/bibtexparser/tests/data/article_trailing_comma_output.bib
index f5cfd6c..de2bacf 100644
--- a/bibtexparser/tests/data/article_trailing_comma_output.bib
+++ b/bibtexparser/tests/data/article_trailing_comma_output.bib
@@ -11,4 +11,3 @@ multilines... and with a french érudit word},
  volume = {12},
  year = {2013},
 }
-
diff --git a/bibtexparser/tests/data/article_with_annotation_output.bib b/bibtexparser/tests/data/article_with_annotation_output.bib
index 7595535..72704f8 100644
--- a/bibtexparser/tests/data/article_with_annotation_output.bib
+++ b/bibtexparser/tests/data/article_with_annotation_output.bib
@@ -12,4 +12,3 @@ multilines... and with a french érudit word},
  volume = {12},
  year = {2013}
 }
-
diff --git a/bibtexparser/tests/data/article_with_strings_output.bib b/bibtexparser/tests/data/article_with_strings_output.bib
index 0c70dbf..1d59b7a 100644
--- a/bibtexparser/tests/data/article_with_strings_output.bib
+++ b/bibtexparser/tests/data/article_with_strings_output.bib
@@ -15,4 +15,3 @@
  volume = {12},
  year = {2013}
 }
-
diff --git a/bibtexparser/tests/data/book_capital_AND.bib b/bibtexparser/tests/data/book_capital_AND.bib
new file mode 100644
index 0000000..afd444d
--- /dev/null
+++ b/bibtexparser/tests/data/book_capital_AND.bib
@@ -0,0 +1,8 @@
+@BOOK{Bird1987,
+  title = {Dynamics of Polymeric Liquid},
+  publisher = {Wiley Edition},
+  year = {1987},
+  author = {Bird, R.B. and Armstrong, R.C. AND Hassager, O.},
+  volume = {1},
+  edition = {2},
+}
diff --git a/bibtexparser/tests/data/book_comma_first.bib b/bibtexparser/tests/data/book_comma_first.bib
index 5691da9..2221cc1 100644
--- a/bibtexparser/tests/data/book_comma_first.bib
+++ b/bibtexparser/tests/data/book_comma_first.bib
@@ -6,4 +6,3 @@
    , volume = {1}
    , year = {1987}
 }
-
diff --git a/bibtexparser/tests/data/book_output.bib b/bibtexparser/tests/data/book_output.bib
index 958f90c..60458e8 100644
--- a/bibtexparser/tests/data/book_output.bib
+++ b/bibtexparser/tests/data/book_output.bib
@@ -6,4 +6,3 @@
  volume = {1},
  year = {1987}
 }
-
diff --git a/bibtexparser/tests/data/empty.bib b/bibtexparser/tests/data/empty.bib
new file mode 100644
index 0000000..e69de29
diff --git a/bibtexparser/tests/data/multiple_entries_and_comments_output.bib b/bibtexparser/tests/data/multiple_entries_and_comments_output.bib
index 969f145..8660a6b 100644
--- a/bibtexparser/tests/data/multiple_entries_and_comments_output.bib
+++ b/bibtexparser/tests/data/multiple_entries_and_comments_output.bib
@@ -26,4 +26,3 @@
  title = {Optical fiber fusion slicing},
  year = {2005}
 }
-
diff --git a/bibtexparser/tests/data/multiple_entries_output.bib b/bibtexparser/tests/data/multiple_entries_output.bib
index a5414b8..718a8ed 100644
--- a/bibtexparser/tests/data/multiple_entries_output.bib
+++ b/bibtexparser/tests/data/multiple_entries_output.bib
@@ -22,4 +22,3 @@
  title = {Optical fiber fusion slicing},
  year = {2005}
 }
-
diff --git a/bibtexparser/tests/test_bibtex_strings.py b/bibtexparser/tests/test_bibtex_strings.py
index 4ca5e3a..5b64c0f 100644
--- a/bibtexparser/tests/test_bibtex_strings.py
+++ b/bibtexparser/tests/test_bibtex_strings.py
@@ -12,24 +12,28 @@ from collections import OrderedDict
 class TestStringParse(unittest.TestCase):
 
     def test_single_string_parse_count(self):
+        parser = BibTexParser(common_strings=False)
         bibtex_str = '@string{name1 = "value1"}\n\n'
-        bib_database = bibtexparser.loads(bibtex_str)
+        bib_database = bibtexparser.loads(bibtex_str, parser)
         self.assertEqual(len(bib_database.strings), 1)
 
     def test_multiple_string_parse_count(self):
+        parser = BibTexParser(common_strings=False)
         bibtex_str = '@string{name1 = "value1"}\n\n@string{name2 = "value2"}\n\n'
-        bib_database = bibtexparser.loads(bibtex_str)
+        bib_database = bibtexparser.loads(bibtex_str, parser)
         self.assertEqual(len(bib_database.strings), 2)
 
     def test_single_string_parse(self):
+        parser = BibTexParser(common_strings=False)
         bibtex_str = '@string{name1 = "value1"}\n\n'
-        bib_database = bibtexparser.loads(bibtex_str)
+        bib_database = bibtexparser.loads(bibtex_str, parser)
         expected = {'name1': 'value1'}
         self.assertEqual(bib_database.strings, expected)
 
     def test_multiple_string_parse(self):
+        parser = BibTexParser(common_strings=False)
         bibtex_str = '@string{name1 = "value1"}\n\n@string{name2 = "value2"}\n\n'
-        bib_database = bibtexparser.loads(bibtex_str)
+        bib_database = bibtexparser.loads(bibtex_str, parser)
         expected = OrderedDict()
         expected['name1'] = 'value1'
         expected['name2'] = 'value2'
@@ -50,8 +54,9 @@ class TestStringParse(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_string_parse_accept_chars(self):
+        parser = BibTexParser(common_strings=False)
         bibtex_str = '@string{pub-ieee-std = {IEEE}}\n\n@string{pub-ieee-std:adr = {New York, NY, USA}}'
-        bib_database = bibtexparser.loads(bibtex_str)
+        bib_database = bibtexparser.loads(bibtex_str, parser)
         self.assertEqual(len(bib_database.strings), 2)
         expected = OrderedDict()
         expected['pub-ieee-std'] = 'IEEE'
diff --git a/bibtexparser/tests/test_bibtexparser.py b/bibtexparser/tests/test_bibtexparser.py
index 041f0e5..462958a 100644
--- a/bibtexparser/tests/test_bibtexparser.py
+++ b/bibtexparser/tests/test_bibtexparser.py
@@ -32,11 +32,20 @@ class TestBibtexParserParserMethods(unittest.TestCase):
 
     def test_parse_bom_str(self):
         parser = BibTexParser()
-        with open(self.input_bom_file_path) as bibtex_file:
+        with open(self.input_bom_file_path, encoding="utf-8") as bibtex_file:
             bibtex_str = bibtex_file.read()
             bibtex_database = parser.parse(bibtex_str)
         self.assertEqual(bibtex_database.entries, self.entries_expected)
 
+    def test_parse_empty(self):
+        parser = BibTexParser()
+        bibtex_database_from_str = parser.parse('')
+        self.assertEqual(bibtex_database_from_str.entries, [])
+
+        with open('bibtexparser/tests/data/empty.bib') as bibtex_file:
+            bibtex_database_from_file = parser.parse_file(bibtex_file)
+        self.assertEqual(bibtex_database_from_file.entries, [])
+
     def test_parse_bom_bytes(self):
         parser = BibTexParser()
         with open(self.input_bom_file_path, 'rb') as bibtex_file:
@@ -73,7 +82,6 @@ class TestBibtexparserWriteMethods(unittest.TestCase):
  volume = {1},
  year = {1987}
 }
-
 """
 
     def test_write_str(self):
diff --git a/bibtexparser/tests/test_bibtexwriter.py b/bibtexparser/tests/test_bibtexwriter.py
index 05dfa43..1c875fe 100644
--- a/bibtexparser/tests/test_bibtexwriter.py
+++ b/bibtexparser/tests/test_bibtexwriter.py
@@ -2,7 +2,7 @@
 import tempfile
 import unittest
 import bibtexparser
-from bibtexparser.bwriter import BibTexWriter
+from bibtexparser.bwriter import BibTexWriter, SortingStrategy
 from bibtexparser.bibdatabase import BibDatabase
 
 
@@ -38,7 +38,6 @@ class TestBibTexWriter(unittest.TestCase):
  title = {Optical fiber fusion slicing},
  year = {2005}
 }
-
 """
         self.assertEqual(result, expected)
 
@@ -68,7 +67,6 @@ class TestBibTexWriter(unittest.TestCase):
 """@book{abc123,
   author = {test}
 }
-
 """
         self.assertEqual(result, expected)
 
@@ -86,7 +84,6 @@ class TestBibTexWriter(unittest.TestCase):
  author             = {test},
  thisisaverylongkey = {longvalue}
 }
-
 """
         self.assertEqual(result, expected)
 
@@ -121,7 +118,6 @@ class TestBibTexWriter(unittest.TestCase):
  title     = {Optical fiber fusion slicing},
  year      = {2005}
 }
-
 """
         self.assertEqual(result, expected)
 
@@ -173,7 +169,180 @@ class TestBibTexWriter(unittest.TestCase):
  title = {Optical fiber fusion slicing},
  author = {Yablon, A.D.}
 }
+"""
+        self.assertEqual(result, expected)
+
+    def test_align_multiline_values(self):
+        with open('bibtexparser/tests/data/article_multilines.bib') as bibtex_file:
+            bib_database = bibtexparser.load(bibtex_file)
+        writer = BibTexWriter()
+        writer.align_multiline_values = True
+        writer.display_order = ["author", "title", "year", "journal", "abstract", "comments", "keyword"]
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@article{Cesar2013,
+ author = {Jean César},
+ title = {A mutline line title is very amazing. It should be
+          long enough to test multilines... with two lines or should we
+          even test three lines... What an amazing title.},
+ year = {2013},
+ journal = {Nice Journal},
+ abstract = {This is an abstract. This line should be long enough to test
+             multilines... and with a french érudit word},
+ comments = {A comment},
+ keyword = {keyword1, keyword2,
+            multiline-keyword1, multiline-keyword2}
+}
+"""
+        self.assertEqual(result, expected)
+
+    def test_align_multiline_values_with_align(self):
+        with open('bibtexparser/tests/data/article_multilines.bib') as bibtex_file:
+            bib_database = bibtexparser.load(bibtex_file)
+        writer = BibTexWriter()
+        writer.align_multiline_values = True
+        writer.align_values = True
+        writer.display_order = ["author", "title", "year", "journal", "abstract", "comments", "keyword"]
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@article{Cesar2013,
+ author    = {Jean César},
+ title     = {A mutline line title is very amazing. It should be
+              long enough to test multilines... with two lines or should we
+              even test three lines... What an amazing title.},
+ year      = {2013},
+ journal   = {Nice Journal},
+ abstract  = {This is an abstract. This line should be long enough to test
+              multilines... and with a french érudit word},
+ comments  = {A comment},
+ keyword   = {keyword1, keyword2,
+              multiline-keyword1, multiline-keyword2}
+}
+"""
+        self.assertEqual(result, expected)
+
+    def test_display_order_sorting(self):
+        bib_database = BibDatabase()
+        bib_database.entries = [{'ID': 'abc123',
+                                 'ENTRYTYPE': 'book',
+                                 'b': 'test2',
+                                 'a': 'test1',
+                                 'd': 'test4',
+                                 'c': 'test3',
+                                 'e': 'test5'}]
+        # Only 'a' is not ordered. As it's only one element, strategy should not matter.
+        for strategy in SortingStrategy:
+            writer = BibTexWriter()
+            writer.display_order = ['b', 'c', 'd', 'e', 'a']
+            writer.display_order_sorting = strategy
+            result = bibtexparser.dumps(bib_database, writer)
+            expected = \
+"""@book{abc123,
+ b = {test2},
+ c = {test3},
+ d = {test4},
+ e = {test5},
+ a = {test1}
+}
+"""
+            self.assertEqual(result, expected)
+
+        # Test ALPHABETICAL_ASC strategy
+        writer = BibTexWriter()
+        writer.display_order = ['c']
+        writer.display_order_sorting = SortingStrategy.ALPHABETICAL_ASC
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@book{abc123,
+ c = {test3},
+ a = {test1},
+ b = {test2},
+ d = {test4},
+ e = {test5}
+}
+"""
+        self.assertEqual(result, expected)
+
+        # Test ALPHABETICAL_DESC strategy
+        writer = BibTexWriter()
+        writer.display_order = ['c']
+        writer.display_order_sorting = SortingStrategy.ALPHABETICAL_DESC
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@book{abc123,
+ c = {test3},
+ e = {test5},
+ d = {test4},
+ b = {test2},
+ a = {test1}
+}
+"""
+        self.assertEqual(result, expected)
+
+        # Test PRESERVE strategy
+        writer = BibTexWriter()
+        writer.display_order = ['c']
+        writer.display_order_sorting = SortingStrategy.PRESERVE
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@book{abc123,
+ c = {test3},
+ b = {test2},
+ a = {test1},
+ d = {test4},
+ e = {test5}
+}
+"""
+        self.assertEqual(result, expected)
 
+    def test_align_multiline_values_with_indent(self):
+        with open('bibtexparser/tests/data/article_multilines.bib') as bibtex_file:
+            bib_database = bibtexparser.load(bibtex_file)
+        writer = BibTexWriter()
+        writer.align_multiline_values = True
+        writer.indent = ' ' * 3
+        writer.display_order = ["author", "title", "year", "journal", "abstract", "comments", "keyword"]
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@article{Cesar2013,
+   author = {Jean César},
+   title = {A mutline line title is very amazing. It should be
+            long enough to test multilines... with two lines or should we
+            even test three lines... What an amazing title.},
+   year = {2013},
+   journal = {Nice Journal},
+   abstract = {This is an abstract. This line should be long enough to test
+               multilines... and with a french érudit word},
+   comments = {A comment},
+   keyword = {keyword1, keyword2,
+              multiline-keyword1, multiline-keyword2}
+}
+"""
+        self.assertEqual(result, expected)
+
+    def test_align_multiline_values_with_align_with_indent(self):
+        with open('bibtexparser/tests/data/article_multilines.bib') as bibtex_file:
+            bib_database = bibtexparser.load(bibtex_file)
+        writer = BibTexWriter()
+        writer.align_multiline_values = True
+        writer.indent = ' ' * 3
+        writer.align_values = True
+        writer.display_order = ["author", "title", "year", "journal", "abstract", "comments", "keyword"]
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@article{Cesar2013,
+   author    = {Jean César},
+   title     = {A mutline line title is very amazing. It should be
+                long enough to test multilines... with two lines or should we
+                even test three lines... What an amazing title.},
+   year      = {2013},
+   journal   = {Nice Journal},
+   abstract  = {This is an abstract. This line should be long enough to test
+                multilines... and with a french érudit word},
+   comments  = {A comment},
+   keyword   = {keyword1, keyword2,
+                multiline-keyword1, multiline-keyword2}
+}
 """
         self.assertEqual(result, expected)
 
@@ -189,35 +358,35 @@ class TestEntrySorting(unittest.TestCase):
 
     def test_sort_default(self):
         result = bibtexparser.dumps(self.bib_database)
-        expected = "@book{a\n}\n\n@article{b\n}\n\n@book{c\n}\n\n"
+        expected = "@book{a\n}\n\n@article{b\n}\n\n@book{c\n}\n"
         self.assertEqual(result, expected)
 
     def test_sort_none(self):
         writer = BibTexWriter()
         writer.order_entries_by = None
         result = bibtexparser.dumps(self.bib_database, writer)
-        expected = "@article{b\n}\n\n@book{c\n}\n\n@book{a\n}\n\n"
+        expected = "@article{b\n}\n\n@book{c\n}\n\n@book{a\n}\n"
         self.assertEqual(result, expected)
 
     def test_sort_id(self):
         writer = BibTexWriter()
         writer.order_entries_by = ('ID', )
         result = bibtexparser.dumps(self.bib_database, writer)
-        expected = "@book{a\n}\n\n@article{b\n}\n\n@book{c\n}\n\n"
+        expected = "@book{a\n}\n\n@article{b\n}\n\n@book{c\n}\n"
         self.assertEqual(result, expected)
 
     def test_sort_type(self):
         writer = BibTexWriter()
         writer.order_entries_by = ('ENTRYTYPE', )
         result = bibtexparser.dumps(self.bib_database, writer)
-        expected = "@article{b\n}\n\n@book{c\n}\n\n@book{a\n}\n\n"
+        expected = "@article{b\n}\n\n@book{c\n}\n\n@book{a\n}\n"
         self.assertEqual(result, expected)
 
     def test_sort_type_id(self):
         writer = BibTexWriter()
         writer.order_entries_by = ('ENTRYTYPE', 'ID')
         result = bibtexparser.dumps(self.bib_database, writer)
-        expected = "@article{b\n}\n\n@book{a\n}\n\n@book{c\n}\n\n"
+        expected = "@article{b\n}\n\n@book{a\n}\n\n@book{c\n}\n"
         self.assertEqual(result, expected)
 
     def test_sort_missing_field(self):
@@ -233,7 +402,7 @@ class TestEntrySorting(unittest.TestCase):
         writer = BibTexWriter()
         writer.order_entries_by = ('year', )
         result = bibtexparser.dumps(bib_database, writer)
-        expected = "@book{a\n}\n\n@article{b,\n year = {2000}\n}\n\n@book{c,\n year = {2010}\n}\n\n"
+        expected = "@book{a\n}\n\n@article{b,\n year = {2000}\n}\n\n@book{c,\n year = {2010}\n}\n"
         self.assertEqual(result, expected)
 
     def test_unicode_problems(self):
@@ -257,7 +426,7 @@ class TestEntrySorting(unittest.TestCase):
         }
         """
         bibdb = bibtexparser.loads(bibtex)
-        with tempfile.TemporaryFile(mode='w+') as bibtex_file:
+        with tempfile.TemporaryFile(mode='w+', encoding="utf-8") as bibtex_file:
             bibtexparser.dump(bibdb, bibtex_file)
             # No exception should be raised
 
diff --git a/bibtexparser/tests/test_bparser.py b/bibtexparser/tests/test_bparser.py
index 516411a..8452f24 100644
--- a/bibtexparser/tests/test_bparser.py
+++ b/bibtexparser/tests/test_bparser.py
@@ -2,9 +2,13 @@
 # -*- coding: utf-8 -*-
 
 from __future__ import unicode_literals
+
+import os
 import unittest
 import codecs
+import warnings
 
+import bibtexparser
 from bibtexparser.bparser import BibTexParser
 from bibtexparser.bibdatabase import (COMMON_STRINGS, BibDataStringExpression)
 from bibtexparser.customization import *
@@ -53,6 +57,13 @@ def customizations_latex(record):
 
 class TestBibtexParserList(unittest.TestCase):
 
+    def test_empty_string(self):
+        bib = BibTexParser("", common_strings=False)
+        self.assertEqual(bib.entries, [])
+        self.assertEqual(bib.comments, [])
+        self.assertEqual(bib.preambles, [])
+        self.assertEqual(bib.strings, {})
+
     ###########
     # ARTICLE
     ###########
@@ -202,7 +213,7 @@ class TestBibtexParserList(unittest.TestCase):
             res = bib.get_entry_list()
         with open('bibtexparser/tests/data/multiple_entries.bib', 'r') as bibfile:
             bib2 = BibTexParser(bibfile.read(), customization=cust2)
-            res2 = bib.get_entry_list()
+            res2 = bib2.get_entry_list()
         self.assertEqual(res, res2)
 
     def test_article_missing_coma(self):
@@ -311,7 +322,8 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_article_no_braces(self):
-        with open('bibtexparser/tests/data/article_no_braces.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/article_no_braces.bib', 'r',
+                  encoding="utf-8") as bibfile:
             bib = BibTexParser(bibfile.read())
             res = bib.get_entry_list()
         expected = [{'ENTRYTYPE': 'article',
@@ -331,7 +343,8 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_article_special_characters(self):
-        with open('bibtexparser/tests/data/article_with_special_characters.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/article_with_special_characters.bib', 'r',
+                  encoding="utf-8") as bibfile:
             bib = BibTexParser(bibfile.read())
             res = bib.get_entry_list()
         expected = [{'ENTRYTYPE': 'article',
@@ -351,7 +364,8 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_article_protection_braces(self):
-        with open('bibtexparser/tests/data/article_with_protection_braces.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/article_with_protection_braces.bib', 'r',
+                  encoding="utf-8") as bibfile:
             bib = BibTexParser(bibfile.read())
             res = bib.get_entry_list()
         expected = [{'ENTRYTYPE': 'article',
@@ -391,7 +405,7 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_book_cust_unicode(self):
-        with open('bibtexparser/tests/data/book.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/book_capital_AND.bib', 'r') as bibfile:
             bib = BibTexParser(bibfile.read(), customization=customizations_unicode)
             res = bib.get_entry_list()
             expected = [{'ENTRYTYPE': 'book',
@@ -523,7 +537,8 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_field_name_with_dash_underscore(self):
-        with open('bibtexparser/tests/data/article_field_name_with_underscore.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/article_field_name_with_underscore.bib', 'r',
+                  encoding="utf-8") as bibfile:
             bib = BibTexParser(bibfile.read())
         res = bib.get_entry_list()
         expected = [{
@@ -543,7 +558,8 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_string_definitions(self):
-        with open('bibtexparser/tests/data/article_with_strings.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/article_with_strings.bib', 'r',
+                  encoding="utf-8") as bibfile:
             bib = BibTexParser(bibfile.read(), common_strings=True)
         res = dict(bib.strings)
         expected = COMMON_STRINGS.copy()
@@ -555,7 +571,8 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_string_is_interpolated(self):
-        with open('bibtexparser/tests/data/article_with_strings.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/article_with_strings.bib', 'r',
+                  encoding="utf-8") as bibfile:
             bib = BibTexParser(bibfile.read(), common_strings=True,
                                interpolate_strings=True)
         res = bib.get_entry_list()
@@ -575,7 +592,8 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res, expected)
 
     def test_string_is_not_interpolated(self):
-        with open('bibtexparser/tests/data/article_with_strings.bib', 'r') as bibfile:
+        with open('bibtexparser/tests/data/article_with_strings.bib', 'r',
+                  encoding="utf-8") as bibfile:
             bib = BibTexParser(bibfile.read(), common_strings=True,
                                interpolate_strings=False)
         res = bib.get_entry_list()[0]
@@ -612,13 +630,82 @@ class TestBibtexParserList(unittest.TestCase):
         self.assertEqual(res_dict, expected_dict)
         self.assertEqual(bib.preambles, ["Blah blah"])
 
+    def test_does_not_fail_on_non_bibtex_with_partial(self):
+        bibraw = '''@misc{this looks,
+          like = a = bibtex file but
+              , is not a real one!
+        '''
+        parser = BibTexParser(common_strings=False)
+        bib = parser.parse(bibraw, partial=False)
+        self.assertEqual(bib.entries, [])
+        self.assertEqual(bib.preambles, [])
+        self.assertEqual(bib.strings, {})
+        self.assertEqual(bib.comments, [
+            '@misc{this looks,\n'
+            '          like = a = bibtex file but\n'
+            '              , is not a real one!'])
+
     def test_no_citekey_parsed_as_comment(self):
-        bib = BibTexParser('@BOOK{, title = "bla"}')
+        bib = BibTexParser('@BOOK{, title = "bla"}', common_strings=False)
         self.assertEqual(bib.entries, [])
         self.assertEqual(bib.preambles, [])
         self.assertEqual(bib.strings, {})
         self.assertEqual(bib.comments, ['@BOOK{, title = "bla"}'])
 
+    def test_parsing_just_once_not_raising_warnings_with_default_settings(self):
+        parser = BibTexParser()
+
+        with open('bibtexparser/tests/data/article_comma_normal_single.bib',
+                  'r', encoding='utf-8') as bibfile:
+            with warnings.catch_warnings(record=True) as warning:
+                warnings.simplefilter("always")
+                bibtexparser.load(bibfile, parser)
+                assert len(warning) == 0
+
+    def test_parsing_twice_raise_warnings_with_default_settings(self):
+        parser = BibTexParser()
+
+        with open('bibtexparser/tests/data/article_comma_normal_single.bib',
+                  'r', encoding='utf-8') as bibfile_first, \
+            open('bibtexparser/tests/data/article_comma_normal_multiple.bib', 'r', encoding='utf-8') \
+                as bibfile_second:
+            with warnings.catch_warnings(record=True) as warning:
+                warnings.simplefilter("always")
+                bibtexparser.load(bibfile_first, parser)
+                bibtexparser.load(bibfile_second, parser)
+                assert len(warning) != 0
+
+    def test_parsing_three_times_raise_warnings_only_once_with_default_settings(self):
+        parser = BibTexParser()
+
+        with open('bibtexparser/tests/data/article_comma_normal_single.bib',
+                  'r', encoding='utf-8') as bibfile_first, \
+            open('bibtexparser/tests/data/article_comma_normal_multiple.bib', 'r', encoding='utf-8') \
+                as bibfile_second, \
+                open('bibtexparser/tests/data/article.bib', 'r', encoding='utf-8') as bibfile_third:
+            with warnings.catch_warnings(record=True) as warning:
+                warnings.simplefilter("always")
+                bibtexparser.load(bibfile_first, parser)
+                bibtexparser.load(bibfile_second, parser)
+                bibtexparser.load(bibfile_third, parser)
+                assert len(warning) == 1
+
+    def test_parsing_three_times_not_raising_a_warning_if_expect_multiple_parse_is_true(self):
+        parser = BibTexParser()
+        parser.expect_multiple_parse = True
+
+        with open('bibtexparser/tests/data/article_comma_normal_single.bib',
+                  'r', encoding='utf-8') as bibfile_first, \
+            open('bibtexparser/tests/data/article_comma_normal_multiple.bib', 'r', encoding='utf-8') \
+                as bibfile_second, \
+                open('bibtexparser/tests/data/article.bib', 'r', encoding='utf-8') as bibfile_third:
+            with warnings.catch_warnings(record=True) as warning:
+                warnings.simplefilter("always")
+                bibtexparser.load(bibfile_first, parser)
+                bibtexparser.load(bibfile_second, parser)
+                bibtexparser.load(bibfile_third, parser)
+                assert len(warning) == 0
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/bibtexparser/tests/test_bwriter.py b/bibtexparser/tests/test_bwriter.py
index 688182f..d34b40b 100644
--- a/bibtexparser/tests/test_bwriter.py
+++ b/bibtexparser/tests/test_bwriter.py
@@ -8,7 +8,6 @@ from __future__ import unicode_literals
 import unittest
 import os
 import io
-import sys
 
 from bibtexparser.bparser import BibTexParser
 from bibtexparser.bwriter import BibTexWriter, to_bibtex
diff --git a/bibtexparser/tests/test_comments.py b/bibtexparser/tests/test_comments.py
index 55e8f1e..679d869 100644
--- a/bibtexparser/tests/test_comments.py
+++ b/bibtexparser/tests/test_comments.py
@@ -131,7 +131,7 @@ Sunt in culpa qui officia deserunt mollit anim id est laborum.
                   "This is a comment\n" \
                   "This is a second comment."
         expected = "This is a comment\nThis is a second comment."
-        bib = BibTexParser(comment)
+        bib = BibTexParser(comment, common_strings=False)
         self.assertEqual(bib.comments, [expected])
         self.assertEqual(bib.strings, {'foo': 'bar'})
 
@@ -141,7 +141,7 @@ Sunt in culpa qui officia deserunt mollit anim id est laborum.
                   "STRING{Baz = \"This should be interpreted as comment.\"}"
         expected = "This is a comment\n" \
                    "STRING{Baz = \"This should be interpreted as comment.\"}"
-        bib = BibTexParser(comment)
+        bib = BibTexParser(comment, common_strings=False)
         self.assertEqual(bib.comments, [expected])
         self.assertEqual(bib.strings, {'foo': 'bar'})
 
diff --git a/bibtexparser/tests/test_crossref_resolving.py b/bibtexparser/tests/test_crossref_resolving.py
index eae99f4..d04dfdf 100644
--- a/bibtexparser/tests/test_crossref_resolving.py
+++ b/bibtexparser/tests/test_crossref_resolving.py
@@ -1,4 +1,4 @@
-import unittest2 as unittest
+import unittest
 from bibtexparser.bibdatabase import BibDatabase
 from bibtexparser.bparser import BibTexParser
 
diff --git a/bibtexparser/tests/test_customization.py b/bibtexparser/tests/test_customization.py
index 2d97538..77450e7 100644
--- a/bibtexparser/tests/test_customization.py
+++ b/bibtexparser/tests/test_customization.py
@@ -42,7 +42,7 @@ class TestBibtexParserMethod(unittest.TestCase):
 
     @unittest.skip('Bug #9')
     def test_getnames_braces(self):
-        names = ['A. {Delgado de Molina}', 'M. Vign{\\\'e}']
+        names = ['A. {Delgado de Molina}', r'M. Vign{\'e}']
         result = getnames(names)
         expected = ['Delgado de Molina, A.', 'Vigné, M.']
         self.assertEqual(result, expected)
@@ -78,40 +78,40 @@ class TestBibtexParserMethod(unittest.TestCase):
     # convert to unicode
     ###########
     def test_convert_to_unicode(self):
-        record = {'toto': '{\`a} \`{a}'}
+        record = {'toto': r'{\`a} \`{a}'}
         result = convert_to_unicode(record)
-        expected = {'toto': 'à à'}
+        expected = {'toto': r'à à'}
         self.assertEqual(result, expected)
-        record = {'toto': '{\\"u} \\"{u}'}
+        record = {'toto': r'{\"u} \"{u}'}
         result = convert_to_unicode(record)
-        expected = {'toto': 'ü ü'}
+        expected = {'toto': r'ü ü'}
         self.assertEqual(result, expected)
         # From issue 121
-        record = {'title': '{Two Gedenk\\"uberlieferung der Angelsachsen}'}
+        record = {'title': r'{Two Gedenk\"uberlieferung der Angelsachsen}'}
         result = convert_to_unicode(record)
-        expected = {'title': 'Two Gedenküberlieferung der Angelsachsen'}
+        expected = {'title': r'Two Gedenküberlieferung der Angelsachsen'}
         self.assertEqual(result, expected)
         # From issue 161
         record = {'title': r"p\^{a}t\'{e}"}
         result = convert_to_unicode(record)
-        expected = {'title': "pâté"}
+        expected = {'title': r"pâté"}
         self.assertEqual(result, expected)
         record = {'title': r"\^{i}le"}
         result = convert_to_unicode(record)
-        expected = {'title': "île"}
+        expected = {'title': r"île"}
         self.assertEqual(result, expected)
         record = {'title': r"\texttimes{}{\texttimes}\texttimes"}
         result = convert_to_unicode(record)
-        expected = {'title': "×××"}
+        expected = {'title': r"×××"}
         self.assertEqual(result, expected)
 
     ###########
     # homogenize
     ###########
     def test_homogenize(self):
-        record = {'toto': 'à {\`a} \`{a}'}
+        record = {'toto': r'à {\`a} \`{a}'}
         result = homogenize_latex_encoding(record)
-        expected = {'toto': '{\`a} {\`a} {\`a}'}
+        expected = {'toto': r'{\`a} {\`a} {\`a}'}
         self.assertEqual(result, expected)
 
     ###########
@@ -119,7 +119,7 @@ class TestBibtexParserMethod(unittest.TestCase):
     ###########
     def test_add_plaintext_fields(self):
         record = {
-            'title': 'On-line {Recognition} of {Handwritten} {Mathematical} {Symbols}',
+            'title': r'On-line {Recognition} of {Handwritten} {Mathematical} {Symbols}',
             'foobar': ['{FFT} {Foobar}', '{foobar}'],
             'foobar2': {'item1': '{FFT} {Foobar}', 'item2': '{foobar}'}
         }
diff --git a/debian/changelog b/debian/changelog
index 0a8d4fc..c2b05ac 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+bibtexparser (1.4.0+ds-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 02 May 2023 03:25:44 -0000
+
 bibtexparser (1.1.0+ds-5) unstable; urgency=medium
 
   * Set upstream metadata fields: Repository-Browse.
diff --git a/docs/source/demo.py b/docs/source/demo.py
new file mode 100644
index 0000000..10c89b6
--- /dev/null
+++ b/docs/source/demo.py
@@ -0,0 +1,53 @@
+import logging
+import logging.config
+import io
+
+logger = logging.getLogger(__name__)
+
+logging.config.dictConfig({
+    'version': 1,
+    'disable_existing_loggers': False,
+    'formatters': {
+        'standard': {
+            'format': '%(asctime)s [%(levelname)s] %(name)s %(funcName)s:%(lineno)d: %(message)s'
+        },
+    },
+    'handlers': {
+        'default': {
+            'level':'DEBUG',
+            'formatter': 'standard',
+            'class':'logging.StreamHandler',
+        },
+    },
+    'loggers': {
+        '': {
+            'handlers': ['default'],
+            'level': 'DEBUG',
+            'formatter': 'standard',
+            'propagate': True
+        }
+    }
+})
+
+
+if __name__ == '__main__':
+    bibtex_source = """@ARTICLE{Cesar2013,
+      author = {Jean César},
+      title = {An amazing title},
+      year = {2013},
+      month = {1},
+      volume = {12},
+      pages = {12--23},
+      journal = {Nice Journal},
+      abstract = {This is an abstract. This line should be long enough to test
+    	 multilines...},
+      comments = {A comment},
+      keywords = {keyword1, keyword2},
+    }
+    """
+
+    from bibtexparser.bparser import BibTexParser
+
+    with io.StringIO(bibtex_source) as f:
+        bp = BibTexParser(f.read())
+        print(bp.get_entry_list())
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 77bd0ab..aa425a6 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -8,16 +8,15 @@ Welcome to BibtexParser's documentation!
 
 
 :Author: François Boulogne, Olivier Mangin, Lucas Verney, and other contributors.
-:Devel: `github.com project <https://github.com/sciunto-org/python-bibtexparser>`_
-:Mirror: `git.sciunto.org <https://git.sciunto.org/mirror/python-bibtexparser>`_
+:Source Code: `github.com project <https://github.com/sciunto-org/python-bibtexparser>`_
 :Bugs: `github.com <https://github.com/sciunto-org/python-bibtexparser/issues>`_
 :Generated: |today|
 :License: LGPL v3 or BSD
 :Version: |release|
 
 BibtexParser is a python library to parse bibtex files. The code relies on `pyparsing <http://pyparsing.wikispaces.com/>`_ and is tested with unittests.
+BibtexParser is used in more than 1300 open-source `repositories <https://github.com/sciunto-org/python-bibtexparser/network/dependents?package_id=UGFja2FnZS01MDI4NzgxNg%3D%3D>`_.
 
-If you use BibtexParser for your project, feel free to send me an email. I would be happy to hear that and to mention your project in the documentation.
 
 Contents:
 
@@ -29,7 +28,6 @@ Contents:
     bibtexparser.rst
     logging.rst
     bibtex_conv.rst
-    who.rst
 
 
 Other projects
diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst
index 8c4a8da..5d1eef3 100644
--- a/docs/source/tutorial.rst
+++ b/docs/source/tutorial.rst
@@ -375,7 +375,7 @@ Using the code would yield the following output.
     bp = BibTexParser(interpolate_strings=False)
     bib_database = bp.parse(bibtex)
     bib_database.entries[0]
-    as_text(bd.entries[0]['author'])
+    as_text(bib_database.entries[0]['author'])
 
 .. code-block:: python
 
diff --git a/docs/source/who.rst b/docs/source/who.rst
deleted file mode 100644
index 8068f1f..0000000
--- a/docs/source/who.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-Who uses BibtexParser?
-======================
-
-If your project uses BibtexParser, you can ask for the addition of a link in this list.
-
-* http://timotheepoisot.fr/2013/11/10/shared-bibtex-file-markdown/
-* https://github.com/Phyks/BMC
-* http://aurelien.naldi.info/research/publications.html
-* http://robot.kut.ac.kr/publications
-* https://git.atelo.org/etlapale/bibgen
-* https://onmenwhostareongraphs.wordpress.com/2015/06/09/graph-display-software-for-author-relationships-with-bibtex-files/
-* https://github.com/vitorfs/parsifal
diff --git a/requirements.txt b/requirements.txt
index 50c1129..e7c21d1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1 @@
-future>=0.16.0
 pyparsing>=2.0.3
-unittest2>=1.1.0
diff --git a/setup.py b/setup.py
index 5e1e1e7..b0b465e 100644
--- a/setup.py
+++ b/setup.py
@@ -12,16 +12,22 @@ with open('bibtexparser/__init__.py') as fh:
             version = line.strip().split()[-1][1:-1]
             break
 
+
+def load_readme():
+    with open("README.rst") as f:
+        return f.read()
+
+
 setup(
-    name         = 'bibtexparser',
-    version      = version,
-    url          = "https://github.com/sciunto-org/python-bibtexparser",
-    author       = "Francois Boulogne and other contributors",
-    license      = "LGPLv3 or BSD",
-    author_email = "devel@sciunto.org",
-    description  = "Bibtex parser for python 2.7 and 3.3 and newer",
-    packages     = ['bibtexparser'],
-    install_requires = ['pyparsing>=2.0.3',
-                        'future>=0.16.0'],
-    extra_requires = {'unittest': 'unittest2>=1.1.0'}
+    name='bibtexparser',
+    version=version,
+    url="https://github.com/sciunto-org/python-bibtexparser",
+    author="Francois Boulogne and other contributors",
+    license="LGPLv3 or BSD",
+    author_email="code@mweiss.ch",
+    description="Bibtex parser for python 3",
+    long_description_content_type="text/x-rst",
+    long_description=load_readme(),
+    packages=['bibtexparser'],
+    install_requires=['pyparsing>=2.0.3'],
 )

More details

Full run details

Historical runs