diff --git a/src/calibre/gui2/catalog/catalog_bibtex.py b/src/calibre/gui2/catalog/catalog_bibtex.py
index 713aa50319..ea222603b7 100644
--- a/src/calibre/gui2/catalog/catalog_bibtex.py
+++ b/src/calibre/gui2/catalog/catalog_bibtex.py
@@ -15,6 +15,12 @@ class PluginWidget(QWidget, Ui_Form):
TITLE = _('BibTeX Options')
HELP = _('Options specific to')+' BibTeX '+_('output')
+ OPTION_FIELDS = [('bib_cit','{authors}{id}'),
+ ('bib_entry', 0), #mixed
+ ('bibfile_enc', 0), #utf-8
+ ('bibfile_enctag', 0), #strict
+ ('impcit', True) ]
+
sync_enabled = False
formats = set(['bib'])
@@ -23,20 +29,30 @@ class PluginWidget(QWidget, Ui_Form):
self.setupUi(self)
from calibre.library.catalog import FIELDS
self.all_fields = []
- for x in FIELDS:
+ for x in FIELDS :
if x != 'all':
self.all_fields.append(x)
QListWidgetItem(x, self.db_fields)
- def initialize(self, name):
+ def initialize(self, name): #not working properly to update
self.name = name
fields = gprefs.get(name+'_db_fields', self.all_fields)
- # Restore the activated fields from last use
+ # Restore the activated db_fields from last use
for x in xrange(self.db_fields.count()):
item = self.db_fields.item(x)
item.setSelected(unicode(item.text()) in fields)
+ # Update dialog fields from stored options
+ for opt in self.OPTION_FIELDS:
+ opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
+ if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
+ getattr(self, opt[0]).setCurrentIndex(opt_value)
+ elif opt[0] == 'impcit' :
+ getattr(self, opt[0]).setChecked(opt_value)
+ else:
+ getattr(self, opt[0]).setText(opt_value)
def options(self):
+
# Save the currently activated fields
fields = []
for x in xrange(self.db_fields.count()):
@@ -45,8 +61,24 @@ class PluginWidget(QWidget, Ui_Form):
fields.append(unicode(item.text()))
gprefs.set(self.name+'_db_fields', fields)
- # Return a dictionary with current options for this widget
+ # Dictionary currently activated fields
if len(self.db_fields.selectedItems()):
- return {'fields':[unicode(item.text()) for item in self.db_fields.selectedItems()]}
+ opts_dict = {'fields':[unicode(item.text()) for item in self.db_fields.selectedItems()]}
else:
- return {'fields':['all']}
+ opts_dict = {'fields':['all']}
+
+ # Save/return the current options
+ # bib_cit stores as text
+ # 'bibfile_enc','bibfile_enctag' stores as int (Indexes)
+ for opt in self.OPTION_FIELDS:
+ if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
+ opt_value = getattr(self,opt[0]).currentIndex()
+ elif opt[0] == 'impcit' :
+ opt_value = getattr(self, opt[0]).isChecked()
+ else :
+ opt_value = unicode(getattr(self, opt[0]).text())
+ gprefs.set(self.name + '_' + opt[0], opt_value)
+
+ opts_dict[opt[0]] = opt_value
+
+ return opts_dict
diff --git a/src/calibre/gui2/catalog/catalog_bibtex.ui b/src/calibre/gui2/catalog/catalog_bibtex.ui
index 9634f66dcd..7f4920655d 100644
--- a/src/calibre/gui2/catalog/catalog_bibtex.ui
+++ b/src/calibre/gui2/catalog/catalog_bibtex.ui
@@ -1,47 +1,173 @@
-
-
- Form
-
-
-
- 0
- 0
- 579
- 411
-
-
-
- Form
-
-
- -
-
-
- Fields to include in output:
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
-
-
- QAbstractItemView::MultiSelection
-
-
-
-
-
-
-
-
+
+
+ Form
+
+
+
+ 0
+ 0
+ 579
+ 411
+
+
+
+ Form
+
+
+ -
+
+
+ Bib file encoding:
+
+
+
+ -
+
+
+ Fields to include in output:
+
+
+
+ -
+
+
-
+
+ utf-8
+
+
+ -
+
+ cp1252
+
+
+ -
+
+ ascii/LaTeX
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ QAbstractItemView::MultiSelection
+
+
+
+ -
+
+
+ Encoding configuration (change if you have errors) :
+
+
+
+ -
+
+
-
+
+ strict
+
+
+ -
+
+ replace
+
+
+ -
+
+ ignore
+
+
+ -
+
+ backslashreplace
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 60
+
+
+
+
+ -
+
+
+ BibTeX entry type:
+
+
+
+ -
+
+
-
+
+ mixed
+
+
+ -
+
+ misc
+
+
+ -
+
+ book
+
+
+
+
+ -
+
+
+ Create a citation tag?
+
+
+
+ -
+
+
+ Expression to form the BibTeX citation tag:
+
+
+
+ -
+
+
+ -
+
+
+ Some explanation about this template:
+ -The fields availables are 'author_sort', 'authors', 'id',
+ 'isbn', 'pubdate', 'publisher', 'series_index', 'series',
+ 'tags', 'timestamp', 'title', 'uuid'
+ -For list types ie authors and tags, only the first element
+ wil be selected.
+ -For time field, only the date will be used.
+
+
+ false
+
+
+
+
+
+
+
+
diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py
index defffba160..f991df7e23 100644
--- a/src/calibre/library/catalog.py
+++ b/src/calibre/library/catalog.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
__license__ = 'GPL v3'
__copyright__ = '2010, Greg Riker '
@@ -17,14 +19,15 @@ from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.utils.date import isoformat, now as nowf
from calibre.utils.logging import default_log as log
-#Bibtex functions
-from calibre.utils.bibtex import bibtex_author_format, utf8ToBibtex, ValidateCitationKey
-
FIELDS = ['all', 'author_sort', 'authors', 'comments',
'cover', 'formats', 'id', 'isbn', 'pubdate', 'publisher', 'rating',
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
'uuid']
+#Allowed fields for template
+TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
+ 'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
+
class CSV_XML(CatalogPlugin):
'CSV/XML catalog generator'
@@ -115,7 +118,8 @@ class CSV_XML(CatalogPlugin):
item = u'%s' % re.sub(r'[\D]', '', item)
elif field in ['pubdate', 'timestamp']:
item = isoformat(item)
-
+
+ #Format the line
if x < len(fields) - 1:
if item is not None:
outstr += u'"%s",' % unicode(item).replace('"','""')
@@ -207,21 +211,74 @@ class BIBTEX(CatalogPlugin):
'Available fields: %s.\n'
"Default: '%%default'\n"
"Applies to: BIBTEX output format")%', '.join(FIELDS)),
+
+ Option('--sort-by',
+ default = 'id',
+ dest = 'sort_by',
+ action = None,
+ help = _('Output field to sort on.\n'
+ 'Available fields: author_sort, id, rating, size, timestamp, title.\n'
+ "Default: '%default'\n"
+ "Applies to: BIBTEX output format")),
+
+ Option('--create-citation',
+ default = 'True',
+ dest = 'impcit',
+ action = None,
+ help = _('Create a citation for BibTeX entries.\n'
+ 'Boolean value: True, False\n'
+ "Default: '%default'\n"
+ "Applies to: BIBTEX output format")),
+
+ Option('--citation-template',
+ default = '{authors}{id}',
+ dest = 'bib_cit',
+ action = None,
+ help = _('The template for citation creation from database fields.\n'
+ ' Should be a template with {} enclosed fields.\n'
+ 'Available fields: %s.\n'
+ "Default: '%%default'\n"
+ "Applies to: BIBTEX output format")%', '.join(TEMPLATE_ALLOWED_FIELDS)),
+
+ Option('--choose-encoding',
+ default = 'utf8',
+ dest = 'bibfile_enc',
+ action = None,
+ help = _('BibTeX file encoding output.\n'
+ 'Available types: utf8, cp1252, ascii.\n'
+ "Default: '%default'\n"
+ "Applies to: BIBTEX output format")),
+
+ Option('--choose-encoding-configuration',
+ default = 'strict',
+ dest = 'bibfile_enctag',
+ action = None,
+ help = _('BibTeX file encoding flag.\n'
+ 'Available types: strict, replace, ignore, backslashreplace.\n'
+ "Default: '%default'\n"
+ "Applies to: BIBTEX output format")),
Option('--entry-type',
default = 'book',
- dest = 'entry_type',
+ dest = 'bib_entry',
action = None,
- help = _('Entry type for BIBTEX catalog.\n'
+ help = _('Entry type for BibTeX catalog.\n'
'Available types: book, misc, mixed.\n'
"Default: '%default'\n"
"Applies to: BIBTEX output format"))]
-
+
def run(self, path_to_output, opts, db, notification=DummyReporter()):
import codecs
+ from types import StringType, UnicodeType
- def create_bibtex_entry(entry, fields, mode = "mixed"):
+ from calibre.library.save_to_disk import preprocess_template
+ #Bibtex functions
+ from calibre.utils.bibtex import bibtex_author_format, utf8ToBibtex, ValidateCitationKey
+
+ def create_bibtex_entry(entry, fields, mode, template_citation,
+ asccii_bibtex = True, citation_bibtex = True):
+
#Bibtex doesn't like UTF-8 but keep unicode until writing
#Define starting chain or if book valid strict and not book return a Fail string
@@ -234,14 +291,10 @@ class BIBTEX(CatalogPlugin):
#case strict book
return ''
- # Citation tag (not the best should be a user defined thing with regexp)
- if not len(entry["isbn"]) == 0 :
- bibtex_entry.append(u'%s' % utf8ToBibtex(ValidateCitationKey(re.sub(u'[\D]',
- u'', entry["isbn"]))))
- else :
- bibtex_entry.append(u'%s' % utf8ToBibtex(ValidateCitationKey(str(entry["id"]))))
-
- bibtex_entry = [u' '.join(bibtex_entry)]
+ if citation_bibtex :
+ # Citation tag
+ bibtex_entry.append(make_bibtex_citation(entry, template_citation, asccii_bibtex))
+ bibtex_entry = [u' '.join(bibtex_entry)]
for field in fields:
item = entry[field]
@@ -259,7 +312,7 @@ class BIBTEX(CatalogPlugin):
elif field in ['title', 'publisher', 'cover', 'uuid',
'author_sort', 'series'] :
- bibtex_entry.append(u'%s = "%s"' % (field, utf8ToBibtex(item)))
+ bibtex_entry.append(u'%s = "%s"' % (field, utf8ToBibtex(item, asccii_bibtex)))
elif field == 'id' :
bibtex_entry.append(u'calibreid = "%s"' % int(item))
@@ -272,11 +325,13 @@ class BIBTEX(CatalogPlugin):
elif field == 'tags' :
#A list to flatten
- bibtex_entry.append(u'tags = "%s"' % utf8ToBibtex(u', '.join(item)))
+ bibtex_entry.append(u'tags = "%s"' % utf8ToBibtex(u', '.join(item), asccii_bibtex))
elif field == 'comments' :
#\n removal
- bibtex_entry.append(u'note = "%s"' % utf8ToBibtex(item.replace(u'\n',u' ')))
+ item = item.replace(u'\r\n',u' ')
+ item = item.replace(u'\n',u' ')
+ bibtex_entry.append(u'note = "%s"' % utf8ToBibtex(item, asccii_bibtex))
elif field == 'isbn' :
# Could be 9, 10 or 13 digits
@@ -295,8 +350,8 @@ class BIBTEX(CatalogPlugin):
elif field == 'pubdate' :
bibtex_entry.append(u'year = "%s"' % item.year)
#Messing locale in date string formatting
- bibtex_entry.append(u'month = "%s"' % utf8ToBibtex(item.strftime("%b").decode(preferred_encoding)))
- #bibtex_entry.append('month = "%s"' % utf8ToBibtex(item.strftime("%B").decode(getlocale()[1])))
+ bibtex_entry.append(u'month = "%s"' % utf8ToBibtex(item.strftime("%b").decode(preferred_encoding),
+ asccii_bibtex))
bibtex_entry = u',\n '.join(bibtex_entry)
bibtex_entry += u' }\n\n'
@@ -313,8 +368,73 @@ class BIBTEX(CatalogPlugin):
else :
return True
+ def make_bibtex_citation(entry, template_citation, asccii_bibtex):
+
+ #define a function to replace the template entry by its value
+ def tpl_replace(objtplname) :
+
+ tpl_field = re.sub(u'[\{\}]', u'', objtplname.group())
+
+ if tpl_field in TEMPLATE_ALLOWED_FIELDS :
+ if tpl_field in ['pubdate', 'timestamp'] :
+ tpl_field = isoformat(entry[tpl_field]).partition('T')[0]
+ elif tpl_field in ['tags', 'authors'] :
+ tpl_field =entry[tpl_field][0]
+ elif tpl_field in ['id', 'series_index'] :
+ tpl_field = str(entry[tpl_field])
+ else :
+ tpl_field = entry[tpl_field]
+ return tpl_field
+ else:
+ return u''
+
+ if len(template_citation) >0 :
+ tpl_citation = utf8ToBibtex(ValidateCitationKey(re.sub(u'\{[^{}]*\}',
+ tpl_replace, template_citation)), asccii_bibtex)
+
+ if len(tpl_citation) >0 :
+ return tpl_citation
+
+ if len(entry["isbn"]) > 0 :
+ template_citation = u'%s' % re.sub(u'[\D]',u'', entry["isbn"])
+
+ else :
+ template_citation = u'%s' % str(entry["id"])
+
+ if asccii_bibtex :
+ return ValidateCitationKey(template_citation.encode('ascii', 'replace'))
+ else :
+ return ValidateCitationKey(template_citation)
+
self.fmt = path_to_output.rpartition('.')[2]
self.notification = notification
+
+ # Combobox options
+ bibfile_enc = ['utf8', 'cp1252', 'ascii']
+ bibfile_enctag = ['strict', 'replace', 'ignore', 'backslashreplace']
+ bib_entry = ['mixed', 'misc', 'book']
+
+ # Needed beacause CLI return str vs int by widget
+ try:
+ bibfile_enc = bibfile_enc[opts.bibfile_enc]
+ bibfile_enctag = bibfile_enctag[opts.bibfile_enctag]
+ bib_entry = bib_entry[opts.bib_entry]
+ except:
+ if opts.bibfile_enc in bibfile_enc :
+ bibfile_enc = opts.bibfile_enc
+ else :
+ log(" WARNING: incorrect --choose-encoding flag, revert to default")
+ bibfile_enc = bibfile_enc[0]
+ if opts.bibfile_enctag in bibfile_enctag :
+ bibfile_enctag = opts.bibfile_enctag
+ else :
+ log(" WARNING: incorrect --choose-encoding-configuration flag, revert to default")
+ bibfile_enctag = bibfile_enctag[0]
+ if opts.bib_entry in bib_entry :
+ bib_entry = opts.bib_entry
+ else :
+ log(" WARNING: incorrect --entry-type flag, revert to default")
+ bib_entry = bib_entry[0]
if opts.verbose:
opts_dict = vars(opts)
@@ -332,6 +452,10 @@ class BIBTEX(CatalogPlugin):
log(" Fields: %s" % ', '.join(FIELDS[1:]))
else:
log(" Fields: %s" % opts_dict['fields'])
+
+ log(" Output file will be encoded in %s with %s flag" % (bibfile_enc, bibfile_enctag))
+
+ log(" BibTeX entry type is %s with a citation like '%s' flag" % (bib_entry, opts_dict['bib_cit']))
# If a list of ids are provided, don't use search_text
if opts.ids:
@@ -347,20 +471,47 @@ class BIBTEX(CatalogPlugin):
if not len(data):
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
-
+
+ #Entries writing after Bibtex formating (or not)
+ if bibfile_enc != 'ascii' :
+ asccii_bibtex = False
+ else :
+ asccii_bibtex = True
+
+ #Check and go to default in case of bad CLI
+ if isinstance(opts.impcit, (StringType, UnicodeType)) :
+ if opts.impcit == 'False' :
+ citation_bibtex= False
+ elif opts.impcit == 'True' :
+ citation_bibtex= True
+ else :
+ log(" WARNING: incorrect --create-citation, revert to default")
+ citation_bibtex= True
+ else :
+ citation_bibtex= opts.impcit
+
+ template_citation = preprocess_template(opts.bib_cit)
+
#Open output and write entries
- #replace should be an option and not the default to generate errors for improving
- outfile = codecs.open(path_to_output, 'w', 'ascii','replace')
+ outfile = codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)
#File header
- nb_entries = len(opts.ids)
+ nb_entries = len(data)
+
+ #check in book strict if all is ok else throw a warning into log
+ if bib_entry == 'book' :
+ nb_books = len(filter(check_entry_book_valid, data))
+ if nb_books < nb_entries :
+ log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries))
+ nb_entries = nb_books
+
outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n'
% (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding)))
- #Entries wrintng after Bibtex formating
for entry in data:
- outfile.write(create_bibtex_entry(entry, fields))
+ outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
+ asccii_bibtex, citation_bibtex))
outfile.close()
diff --git a/src/calibre/utils/bibtex.py b/src/calibre/utils/bibtex.py
index 1452c7f5e1..f4fc62d9d0 100644
--- a/src/calibre/utils/bibtex.py
+++ b/src/calibre/utils/bibtex.py
@@ -2502,14 +2502,14 @@ def BraceUppercase(text):
return text
def resolveEntities(text):
- for entity in entity_mapping.keys():
- text = text.replace(entity, entity_mapping[entity])
+ for entity, entity_map in entity_mapping.iteritems():
+ text = text.replace(entity, entity_map)
return text
def resolveUnicode(text):
#UTF-8 text as entry
- for unichar in utf8enc2latex_mapping.keys():
- text = text.replace(unichar, utf8enc2latex_mapping[unichar])
+ for unichar, latexenc in utf8enc2latex_mapping.iteritems() :
+ text = text.replace(unichar, latexenc)
return text.replace(u'$}{$', u'')
def escapeSpecialCharacters(text):
@@ -2524,13 +2524,10 @@ def escapeSpecialCharacters(text):
#Calibre functions
#Go from an unicode entry to ASCII Bibtex format without encoding
+#Option to go to official ASCII Bibtex or unofficial UTF-8
def utf8ToBibtex(text, asccii_bibtex = True):
if len(text) == 0:
return ''
- '''try :
- text = text.decode('cp1252')
- except (TypeError, UnicodeDecodeError, ValueError):
- pass '''
text.replace('\\', '\\\\')
text = resolveEntities(text)
if asccii_bibtex :
@@ -2539,6 +2536,4 @@ def utf8ToBibtex(text, asccii_bibtex = True):
def bibtex_author_format(item):
#Format authors for Bibtex compliance (get a list as input)
- item = u' and'.join([author for author in item])
- return utf8ToBibtex(item)
-
+ return utf8ToBibtex(u' and'.join([author for author in item]))