Repository: sciunto-org/python-bibtexparser
Pull request #83 (2015-10-10 14:41:20)

two more features for bibtexwriter
1. align_values checks how wide the longest key is in the whole bib-database and aligns all values according to this maximal width
2. display_order allows to have custom ordering of the fields of each entry instead of just alphabetical

The according unit tests are added as well.


Patch:
diff --git a/bibtexparser/bwriter.py b/bibtexparser/bwriter.py
index 54c7882..65472a4 100644
--- a/bibtexparser/bwriter.py
+++ b/bibtexparser/bwriter.py
@@ -43,15 +43,25 @@ class BibTexWriter(object):
         self.contents = ['comments', 'preambles', 'strings', 'entries']
         #: Character(s) for indenting BibTeX field-value pairs. Default: single space.
         self.indent = ' '
+        #: 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
         #: Characters(s) for separating BibTeX entries. Default: new line.
         self.entry_separator = '\n'
-        #: Tuple of fields for ordering entries. Set to `None` to disable sorting. Default: BibTeX key `('ID', )`.
+        #: 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: '[]'
+        self.display_order = []
         #: BibTeX syntax allows comma first syntax
         #: (common in functional languages), use this to enable
         #: comma first syntax as the bwritter output
         self.comma_first = False
 
+        #: internal variable used if self.align_values = True
+        self._max_field_width = 0
+
+
     def write(self, bib_database):
         """
         Converts a bibliographic database to a BibTeX-formatted string.
@@ -79,6 +89,11 @@ class BibTexWriter(object):
         else:
             entries = bib_database.entries
 
+        if self.align_values:
+            # determine maximum field width to be used
+            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
@@ -88,15 +103,22 @@ class BibTexWriter(object):
         # Write BibTeX key
         bibtex += '@' + entry['ENTRYTYPE'] + '{' + entry['ID']
 
+        # create display_order of fields for this entry
+        # 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
+        more_fields = [i for i in sorted(entry) if i not in self.display_order]
+        display_order += [i for i in sorted(entry) if i not in self.display_order]
+
         # Write field = value lines
-        for field in [i for i in sorted(entry) if i not in ['ENTRYTYPE', 'ID']]:
+        for field in [i for i in display_order if i not in ['ENTRYTYPE', 'ID']]:
             try:
                 if self.comma_first:
-                    bibtex += "\n" + self.indent + ", " + field + " = {" + entry[field] + "}"
+                    bibtex += "\n" + self.indent + ", " + "{0:<{1}}".format(field, self._max_field_width) + " = {" + entry[field] + "}"
                 else:
-                    bibtex += ",\n" + self.indent + field + " = {" + entry[field] + "}"
+                    bibtex += ",\n" + self.indent + "{0:<{1}}".format(field, self._max_field_width) + " = {" + entry[field] + "}"
             except TypeError:
-                raise TypeError("The field %s in entry %s must be a string"
+                raise TypeError(u"The field %s in entry %s must be a string"
                                 % (field, entry['ID']))
         bibtex += "\n}\n" + self.entry_separator
         return bibtex


Test patch:
diff --git a/bibtexparser/tests/test_bibtexwriter.py b/bibtexparser/tests/test_bibtexwriter.py
index 9cba849..25b1270 100644
--- a/bibtexparser/tests/test_bibtexwriter.py
+++ b/bibtexparser/tests/test_bibtexwriter.py
@@ -70,6 +70,60 @@ class TestBibTexWriter(unittest.TestCase):
 """
         self.assertEqual(result, expected)
 
+    def test_align(self):
+        bib_database = BibDatabase()
+        bib_database.entries = [{'ID': 'abc123',
+                                 'ENTRYTYPE': 'book',
+                                 'author': 'test',
+                                 'thisisaverylongkey': 'longvalue'}]
+        writer = BibTexWriter()
+        writer.align_values = True
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@book{abc123,
+ author             = {test},
+ thisisaverylongkey = {longvalue}
+}
+
+"""
+        self.assertEqual(result, expected)
+
+        with open('bibtexparser/tests/data/multiple_entries_and_comments.bib') as bibtex_file:
+            bib_database = bibtexparser.load(bibtex_file)
+        writer = BibTexWriter()
+        writer.contents = ['entries']
+        writer.align_values = True
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@book{Toto3000,
+ author    = {Toto, A and Titi, B},
+ title     = {A title}
+}
+
+@article{Wigner1938,
+ author    = {Wigner, E.},
+ doi       = {10.1039/TF9383400029},
+ issn      = {0014-7672},
+ journal   = {Trans. Faraday Soc.},
+ owner     = {fr},
+ pages     = {29--41},
+ publisher = {The Royal Society of Chemistry},
+ title     = {The transition state method},
+ volume    = {34},
+ year      = {1938}
+}
+
+@book{Yablon2005,
+ author    = {Yablon, A.D.},
+ publisher = {Springer},
+ title     = {Optical fiber fusion slicing},
+ year      = {2005}
+}
+
+"""
+        self.assertEqual(result, expected)
+
+
     def test_entry_separator(self):
         bib_database = BibDatabase()
         bib_database.entries = [{'ID': 'abc123',
@@ -82,6 +136,42 @@ class TestBibTexWriter(unittest.TestCase):
 """@book{abc123,
  author = {test}
 }
+"""
+        self.assertEqual(result, expected)
+
+    def test_display_order(self):
+        with open('bibtexparser/tests/data/multiple_entries_and_comments.bib') as bibtex_file:
+            bib_database = bibtexparser.load(bibtex_file)
+        writer = BibTexWriter()
+        writer.contents = ['entries']
+        writer.display_order = ['year', 'publisher', 'title']
+        result = bibtexparser.dumps(bib_database, writer)
+        expected = \
+"""@book{Toto3000,
+ title = {A title},
+ author = {Toto, A and Titi, B}
+}
+
+@article{Wigner1938,
+ year = {1938},
+ publisher = {The Royal Society of Chemistry},
+ title = {The transition state method},
+ author = {Wigner, E.},
+ doi = {10.1039/TF9383400029},
+ issn = {0014-7672},
+ journal = {Trans. Faraday Soc.},
+ owner = {fr},
+ pages = {29--41},
+ volume = {34}
+}
+
+@book{Yablon2005,
+ year = {2005},
+ publisher = {Springer},
+ title = {Optical fiber fusion slicing},
+ author = {Yablon, A.D.}
+}
+
 """
         self.assertEqual(result, expected)
 
@@ -144,4 +234,3 @@ class TestEntrySorting(unittest.TestCase):
         expected = "@book{a\n}\n\n@article{b,\n year = {2000}\n}\n\n@book{c,\n year = {2010}\n}\n\n"
         self.assertEqual(result, expected)
 
-