diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css
index 07a95661be..dd25566b22 100644
--- a/resources/catalog/stylesheet.css
+++ b/resources/catalog/stylesheet.css
@@ -2,19 +2,29 @@ body { background-color: white; }
p.title {
margin-top:0em;
- margin-bottom:1em;
+ margin-bottom:0em;
text-align:center;
font-style:italic;
font-size:xx-large;
- border-bottom: solid black 2px;
+ }
+
+p.series_id {
+ margin-top:0em;
+ margin-bottom:0em;
+ text-align:center;
+ }
+
+a.series_id {
+ font-style:normal;
+ font-size:large;
}
p.author {
+ font-size:large;
margin-top:0em;
margin-bottom:0em;
text-align: center;
text-indent: 0em;
- font-size:large;
}
p.author_index {
@@ -26,7 +36,8 @@ p.author_index {
text-indent: 0em;
}
-p.tags {
+p.genres {
+ font-style:normal;
margin-top:0.5em;
margin-bottom:0em;
text-align: left;
@@ -124,22 +135,37 @@ hr.description_divider {
border-left: solid white 0px;
}
+hr.header_divider {
+ width:100%;
+ border-top: solid white 1px;
+ border-right: solid white 0px;
+ border-bottom: solid black 2px;
+ border-left: solid white 0px;
+ }
+
hr.merged_comments_divider {
width:80%;
margin-left:10%;
border-top: solid white 0px;
border-right: solid white 0px;
- border-bottom: dotted grey 2px;
+ border-bottom: dashed gray 2px;
border-left: solid white 0px;
}
-td.publisher, td.date {
- font-weight:bold;
+td {
text-align:center;
}
-td.rating, td.notes {
- text-align: center;
+td.publisher, td.date {
+ font-weight:bold;
}
+
+td.rating{
+ }
+
+td.notes {
+ font-size: 100%;
+ }
+
td.thumbnail img {
-webkit-box-shadow: 4px 4px 12px #999;
}
\ No newline at end of file
diff --git a/resources/catalog/template.xhtml b/resources/catalog/template.xhtml
new file mode 100644
index 0000000000..409086d343
--- /dev/null
+++ b/resources/catalog/template.xhtml
@@ -0,0 +1,41 @@
+
+
+ {title_str}
+
+
+
+
+ {title}
+ {series} [{series_index}]
+
+ {author_prefix}{author}
+ {genres}
+ {formats}
+
+
+ |
+ |
+
+
+ |
+
+
+ {publisher} |
+
+
+ {pubyear} |
+
+
+ {rating} |
+
+
+ {note_source}: {note_content} |
+
+
+ |
+
+
+
+
+
+
diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py
index a76cb71acd..770d405203 100644
--- a/src/calibre/customize/__init__.py
+++ b/src/calibre/customize/__init__.py
@@ -307,6 +307,14 @@ class CatalogPlugin(Plugin): # {{{
#: cli_options parsed in library.cli:catalog_option_parser()
cli_options = []
+ def _field_sorter(self, key):
+ '''
+ Custom fields sort after standard fields
+ '''
+ if key.startswith('#'):
+ return '~%s' % key[1:]
+ else:
+ return key
def search_sort_db(self, db, opts):
@@ -315,18 +323,18 @@ class CatalogPlugin(Plugin): # {{{
if opts.sort_by:
# 2nd arg = ascending
db.sort(opts.sort_by, True)
-
return db.get_data_as_dict(ids=opts.ids)
- def get_output_fields(self, opts):
+ def get_output_fields(self, db, opts):
# Return a list of requested fields, with opts.sort_by first
- all_fields = set(
+ all_std_fields = set(
['author_sort','authors','comments','cover','formats',
'id','isbn','ondevice','pubdate','publisher','rating',
'series_index','series','size','tags','timestamp',
'title','uuid'])
+ all_custom_fields = set(db.custom_field_keys())
+ all_fields = all_std_fields.union(all_custom_fields)
- fields = all_fields
if opts.fields != 'all':
# Make a list from opts.fields
requested_fields = set(opts.fields.split(','))
@@ -337,7 +345,7 @@ class CatalogPlugin(Plugin): # {{{
if not opts.connected_device['is_device_connected'] and 'ondevice' in fields:
fields.pop(int(fields.index('ondevice')))
- fields.sort()
+ fields = sorted(fields, key=self._field_sorter)
if opts.sort_by and opts.sort_by in fields:
fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
return fields
diff --git a/src/calibre/gui2/catalog/catalog_csv_xml.py b/src/calibre/gui2/catalog/catalog_csv_xml.py
index 077d4cbbca..eeca5f7f72 100644
--- a/src/calibre/gui2/catalog/catalog_csv_xml.py
+++ b/src/calibre/gui2/catalog/catalog_csv_xml.py
@@ -6,9 +6,11 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-
+import os
from calibre.gui2 import gprefs
from calibre.gui2.catalog.catalog_csv_xml_ui import Ui_Form
+from calibre.library.database2 import LibraryDatabase2
+from calibre.utils.config import prefs
from PyQt4.Qt import QWidget, QListWidgetItem
class PluginWidget(QWidget, Ui_Form):
@@ -28,6 +30,13 @@ class PluginWidget(QWidget, Ui_Form):
self.all_fields.append(x)
QListWidgetItem(x, self.db_fields)
+ dbpath = os.path.abspath(prefs['library_path'])
+ db = LibraryDatabase2(dbpath)
+ for x in sorted(db.custom_field_keys()):
+ self.all_fields.append(x)
+ QListWidgetItem(x, self.db_fields)
+
+
def initialize(self, name, db):
self.name = name
fields = gprefs.get(name+'_db_fields', self.all_fields)
diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py
index f5fabca80e..e3cb8ab5fe 100644
--- a/src/calibre/gui2/catalog/catalog_epub_mobi.py
+++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py
@@ -237,7 +237,7 @@ class PluginWidget(QWidget,Ui_Form):
custom_fields = {}
for custom_field in all_custom_fields:
field_md = self.db.metadata_for_field(custom_field)
- if field_md['datatype'] in ['composite','datetime','enumeration','text']:
+ if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']:
custom_fields[field_md['name']] = {'field':custom_field,
'datatype':field_md['datatype']}
# Blank field first
@@ -298,6 +298,7 @@ class PluginWidget(QWidget,Ui_Form):
if new_source > '':
exclude_source_spec = self.exclude_source_fields[str(new_source)]
self.exclude_source_field_name = exclude_source_spec['field']
+ self.exclude_pattern.setEnabled(True)
# Change pattern input widget to match the source field datatype
if exclude_source_spec['datatype'] in ['bool','composite','datetime','text']:
@@ -309,7 +310,7 @@ class PluginWidget(QWidget,Ui_Form):
self.exclude_pattern = dw
self.exclude_spec_hl.addWidget(dw)
else:
- self.exclude_pattern.setText('')
+ self.exclude_pattern.setEnabled(False)
def header_note_source_field_changed(self,new_index):
'''
diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui
index 481763c76d..63ccb912c5 100644
--- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui
+++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui
@@ -35,10 +35,10 @@
- Sections to include in generated catalog. A minimal catalog includes 'Books by Author'.
+ Sections to include in catalog. All catalogs include 'Books by Author'.
- Included sections (Books by Author included by default)
+ Included sections
-
@@ -100,7 +100,8 @@ p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Default pattern </p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">\[.+\]</span></p>
-<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">excludes tags of the form [<span style=" font-style:italic;">tag</span>]</p></body></html>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">excludes tags of the form [<span style=" font-family:'Courier New,courier';">tag</span>], </p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">e.g., [Project Gutenberg]</p></body></html>
Excluded genres
@@ -184,7 +185,7 @@ p, li { white-space: pre-wrap; }
- Exclude matching books from generated catalog
+ Books matching either pattern will not be included in generated catalog.
Excluded books
@@ -279,7 +280,7 @@ p, li { white-space: pre-wrap; }
- Column containing exclusion criteria
+ Column containing additional exclusion criteria
QComboBox::AdjustToMinimumContentsLengthWithIcon
@@ -455,7 +456,7 @@ p, li { white-space: pre-wrap; }
-
- Wishlist items will be displayed with ✕
+ Books tagged as Wishlist items will be displayed with ✕
@@ -497,7 +498,7 @@ p, li { white-space: pre-wrap; }
- Size hint for cover thumbnails included in Descriptions
+ Size hint for Description cover thumbnails
"
@@ -540,8 +541,11 @@ p, li { white-space: pre-wrap; }
16777215
+
+
+
- Header note
+ Description note
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -563,7 +567,7 @@ p, li { white-space: pre-wrap; }
- Column containing header note
+ Custom column source for note to include in Description header area
@@ -602,7 +606,7 @@ p, li { white-space: pre-wrap; }
- Column containing additional content to merge
+ Additional content merged with Comments during catalog generation
@@ -616,7 +620,7 @@ p, li { white-space: pre-wrap; }
-
- Merge before Comments
+ Merge additional content before Comments
Before
@@ -626,7 +630,7 @@ p, li { white-space: pre-wrap; }
-
- Merge after Comments
+ Merge additional content after Comments
After
@@ -643,7 +647,7 @@ p, li { white-space: pre-wrap; }
-
- Separate with horizontal rule
+ Separate Comments and additional content with horizontal rule
<hr />
diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py
index eb85ad9e6e..168f4968e2 100644
--- a/src/calibre/library/catalog.py
+++ b/src/calibre/library/catalog.py
@@ -1,22 +1,24 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
-__copyright__ = '2010, Greg Riker '
+__copyright__ = '2010, Greg Riker'
import codecs, datetime, htmlentitydefs, os, re, shutil, time, zlib
from contextlib import closing
from collections import namedtuple
from copy import deepcopy
from xml.sax.saxutils import escape
+from lxml import etree
from calibre import prints, prepare_string_for_xml, strftime
from calibre.constants import preferred_encoding
from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
+from calibre.ebooks.oeb.base import RECOVER_PARSER, XHTML_NS
from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.utils.config import config_dir
-from calibre.utils.date import isoformat, now as nowf
+from calibre.utils.date import format_date, isoformat, now as nowf
from calibre.utils.logging import default_log as log
from calibre.utils.zipfile import ZipFile, ZipInfo
from calibre.utils.magick.draw import thumbnail
@@ -26,6 +28,7 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments',
'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' ]
@@ -96,7 +99,7 @@ class CSV_XML(CatalogPlugin):
#raise SystemExit(1)
# Get the requested output fields as a list
- fields = self.get_output_fields(opts)
+ fields = self.get_output_fields(db, opts)
# If connected device, add 'On Device' values to data
if opts.connected_device['is_device_connected'] and 'ondevice' in fields:
@@ -116,7 +119,10 @@ class CSV_XML(CatalogPlugin):
for entry in data:
outstr = []
for field in fields:
- item = entry[field]
+ if field.startswith('#'):
+ item = db.get_field(entry['id'],field,index_is_id=True)
+ else:
+ item = entry[field]
if item is None:
outstr.append('""')
continue
@@ -141,7 +147,7 @@ class CSV_XML(CatalogPlugin):
outfile.close()
elif self.fmt == 'xml':
- from lxml import etree
+ #from lxml import etree
from lxml.builder import E
root = E.calibredb()
@@ -149,6 +155,14 @@ class CSV_XML(CatalogPlugin):
record = E.record()
root.append(record)
+ for field in fields:
+ if field.startswith('#'):
+ val = db.get_field(r['id'],field,index_is_id=True)
+ if not isinstance(val, (str, unicode)):
+ val = unicode(val)
+ item = getattr(E, field.replace('#','_'))(val)
+ record.append(item)
+
for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size',
'isbn','ondevice'):
if field in fields:
@@ -470,7 +484,7 @@ class BIBTEX(CatalogPlugin):
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
# Get the requested output fields as a list
- fields = self.get_output_fields(opts)
+ fields = self.get_output_fields(db, opts)
if not len(data):
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
@@ -918,7 +932,8 @@ class EPUB_MOBI(CatalogPlugin):
self.__genres = None
self.genres = []
self.__genre_tags_dict = None
- self.__htmlFileList = []
+ self.__htmlFileList_1 = []
+ self.__htmlFileList_2 = []
self.__markerTags = self.getMarkerTags()
self.__ncxSoup = None
self.__output_profile = None
@@ -947,33 +962,34 @@ class EPUB_MOBI(CatalogPlugin):
break
# Confirm/create thumbs archive.
- if not os.path.exists(self.__cache_dir):
- self.opts.log.info(" creating new thumb cache '%s'" % self.__cache_dir)
- os.makedirs(self.__cache_dir)
- if not os.path.exists(self.__archive_path):
- self.opts.log.info(' creating thumbnail archive, thumb_width: %1.2f"' %
- float(self.opts.thumb_width))
- zfw = ZipFile(self.__archive_path, mode='w')
- zfw.writestr("Catalog Thumbs Archive",'')
- #zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width)
- zfw.close()
- else:
- with closing(ZipFile(self.__archive_path, mode='r')) as zfr:
- try:
- cached_thumb_width = zfr.read('thumb_width')
- except:
- cached_thumb_width = "-1"
-
- if float(cached_thumb_width) != float(self.opts.thumb_width):
- self.opts.log.info(" invalidating cache at '%s'" % self.__archive_path)
- self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' %
- (float(cached_thumb_width),float(self.opts.thumb_width)))
- os.remove(self.__archive_path)
- with closing(ZipFile(self.__archive_path, mode='w')) as zfw:
- zfw.writestr("Catalog Thumbs Archive",'')
+ if self.opts.generate_descriptions:
+ if not os.path.exists(self.__cache_dir):
+ self.opts.log.info(" creating new thumb cache '%s'" % self.__cache_dir)
+ os.makedirs(self.__cache_dir)
+ if not os.path.exists(self.__archive_path):
+ self.opts.log.info(' creating thumbnail archive, thumb_width: %1.2f"' %
+ float(self.opts.thumb_width))
+ zfw = ZipFile(self.__archive_path, mode='w')
+ zfw.writestr("Catalog Thumbs Archive",'')
+ #zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width)
+ zfw.close()
else:
- self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' %
- (self.__archive_path, float(cached_thumb_width)))
+ with closing(ZipFile(self.__archive_path, mode='r')) as zfr:
+ try:
+ cached_thumb_width = zfr.read('thumb_width')
+ except:
+ cached_thumb_width = "-1"
+
+ if float(cached_thumb_width) != float(self.opts.thumb_width):
+ self.opts.log.info(" refreshing cache at '%s'" % self.__archive_path)
+ self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' %
+ (float(cached_thumb_width),float(self.opts.thumb_width)))
+ os.remove(self.__archive_path)
+ with closing(ZipFile(self.__archive_path, mode='w')) as zfw:
+ zfw.writestr("Catalog Thumbs Archive",'')
+ else:
+ self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' %
+ (self.__archive_path, float(cached_thumb_width)))
# Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX
if self.opts.generate_titles:
@@ -1129,11 +1145,18 @@ class EPUB_MOBI(CatalogPlugin):
self.__genre_tags_dict = val
return property(fget=fget, fset=fset)
@dynamic_property
- def htmlFileList(self):
+ def htmlFileList_1(self):
def fget(self):
- return self.__htmlFileList
+ return self.__htmlFileList_1
def fset(self, val):
- self.__htmlFileList = val
+ self.__htmlFileList_1 = val
+ return property(fget=fget, fset=fset)
+ @dynamic_property
+ def htmlFileList_2(self):
+ def fget(self):
+ return self.__htmlFileList_2
+ def fset(self, val):
+ self.__htmlFileList_2 = val
return property(fget=fget, fset=fset)
@dynamic_property
def libraryPath(self):
@@ -1303,12 +1326,12 @@ class EPUB_MOBI(CatalogPlugin):
self.generateHTMLByTitle()
if self.opts.generate_series:
self.generateHTMLBySeries()
+ if self.opts.generate_genres:
+ self.generateHTMLByTags()
if self.opts.generate_recently_added:
self.generateHTMLByDateAdded()
if self.generateRecentlyRead:
self.generateHTMLByDateRead()
- if self.opts.generate_genres:
- self.generateHTMLByTags()
if self.opts.generate_descriptions:
self.generateThumbnails()
self.generateOPF()
@@ -1318,12 +1341,12 @@ class EPUB_MOBI(CatalogPlugin):
self.generateNCXByTitle("Titles")
if self.opts.generate_series:
self.generateNCXBySeries("Series")
+ if self.opts.generate_genres:
+ self.generateNCXByGenre("Genres")
if self.opts.generate_recently_added:
self.generateNCXByDateAdded("Recently Added")
if self.generateRecentlyRead:
self.generateNCXByDateRead("Recently Read")
- if self.opts.generate_genres:
- self.generateNCXByGenre("Genres")
if self.opts.generate_descriptions:
self.generateNCXDescriptions("Descriptions")
@@ -1475,12 +1498,19 @@ class EPUB_MOBI(CatalogPlugin):
this_title['formats'] = formats
# Add user notes to be displayed in header
+ # Special case handling for datetime fields
if self.opts.header_note_source_field:
+ field_md = self.__db.metadata_for_field(self.opts.header_note_source_field)
notes = self.__db.get_field(record['id'],
- self.opts.header_note_source_field,
- index_is_id=True)
+ self.opts.header_note_source_field,
+ index_is_id=True)
+ if field_md['datatype'] == 'datetime':
+ # Reformat date fields to match UI presentation: dd MMM YYYY
+ notes = format_date(notes,'dd MMM yyyy')
+
if notes:
- this_title['notes'] = notes
+ this_title['notes'] = {'source':field_md['name'],
+ 'content':notes}
titles.append(this_title)
@@ -1679,183 +1709,10 @@ class EPUB_MOBI(CatalogPlugin):
(title_num, len(self.booksByTitle)),
float(title_num*100/len(self.booksByTitle))/100)
- # Generate the header
- soup = self.generateHTMLDescriptionHeader("%s" % title['title'])
- body = soup.find('body')
+ # Generate the header from user-customizable template
+ soup = self.generateHTMLDescriptionHeader(title)
- btc = 0
- # Insert the anchor
- aTag = Tag(soup, "a")
- aTag['name'] = "book%d" % int(title['id'])
- body.insert(btc, aTag)
- btc += 1
-
- # Insert the book title
- #
Book Title
- emTag = Tag(soup, "em")
- if title['series']:
- # title
series series_index
- if self.opts.generate_series:
- brTag = Tag(soup,'br')
- title_tokens = list(title['title'].partition(':'))
- emTag.insert(0, escape(NavigableString(title_tokens[2].strip())))
- emTag.insert(1, brTag)
- smallTag = Tag(soup,'small')
- aTag = Tag(soup,'a')
- aTag['href'] = "%s.html#%s_series" % ('BySeries',
- re.sub('\W','',title['series']).lower())
- aTag.insert(0, title_tokens[0])
- smallTag.insert(0, aTag)
- emTag.insert(2, smallTag)
- else:
- brTag = Tag(soup,'br')
- title_tokens = list(title['title'].partition(':'))
- emTag.insert(0, escape(NavigableString(title_tokens[2].strip())))
- emTag.insert(1, brTag)
- smallTag = Tag(soup,'small')
- smallTag.insert(0, escape(NavigableString(title_tokens[0])))
- emTag.insert(2, smallTag)
- else:
- emTag.insert(0, NavigableString(escape(title['title'])))
- titleTag = body.find(attrs={'class':'title'})
- titleTag.insert(0,emTag)
-
- # Create the author anchor
- authorTag = body.find(attrs={'class':'author'})
- aTag = Tag(soup, "a")
- aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
- self.generateAuthorAnchor(title['author']))
- aTag.insert(0, title['author'])
-
- # Prefix author with read|reading|none symbol or missing symbol
- if self.opts.wishlist_tag in title.get('tags', []):
- authorTag.insert(0, NavigableString(self.MISSING_SYMBOL + " by "))
- else:
- if title['read']:
- authorTag.insert(0, NavigableString(self.READ_SYMBOL + " by "))
- elif self.opts.connected_kindle and title['id'] in self.bookmarked_books:
- authorTag.insert(0, NavigableString(self.READING_SYMBOL + " by "))
- else:
- #authorTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + " by "))
- authorTag.insert(0, NavigableString("by "))
- authorTag.insert(1, aTag)
-
- '''
- # Insert Series info or remove.
- seriesTag = body.find(attrs={'class':'series'})
- if title['series']:
- # Insert a spacer to match the author indent
- stc = 0
- fontTag = Tag(soup,"font")
- fontTag['style'] = 'color:white;font-size:large'
- if self.opts.fmt == 'epub':
- fontTag['style'] += ';opacity: 0.0'
- fontTag.insert(0, NavigableString("by "))
- seriesTag.insert(stc, fontTag)
- stc += 1
- if float(title['series_index']) - int(title['series_index']):
- series_str = 'Series: %s [%4.2f]' % (title['series'], title['series_index'])
- else:
- series_str = '%s [%d]' % (title['series'], title['series_index'])
- seriesTag.insert(stc,NavigableString(series_str))
- else:
- seriesTag.extract()
- '''
- # Insert linked genres
- if 'tags' in title:
- tagsTag = body.find(attrs={'class':'tags'})
- ttc = 0
-
- '''
- # Insert a spacer to match the author indent
- fontTag = Tag(soup,"font")
- fontTag['style'] = 'color:white;font-size:large'
- if self.opts.fmt == 'epub':
- fontTag['style'] += ';opacity: 0.0'
- fontTag.insert(0, NavigableString(" by "))
- tagsTag.insert(ttc, fontTag)
- ttc += 1
- '''
-
- for tag in title.get('tags', []):
- aTag = Tag(soup,'a')
- #print "aTag: %s" % "Genre_%s.html" % re.sub("\W","",tag.lower())
- if self.opts.generate_genres:
- aTag['href'] = "Genre_%s.html" % re.sub("\W","",tag.lower())
- aTag.insert(0,escape(NavigableString(tag)))
- emTag = Tag(soup, "em")
- emTag.insert(0, aTag)
- if ttc < len(title['tags'])-1:
- emTag.insert(1, NavigableString(' · '))
- tagsTag.insert(ttc, emTag)
- ttc += 1
-
- # Insert formats
- if 'formats' in title:
- formatsTag = body.find(attrs={'class':'formats'})
- formats = []
- for format in sorted(title['formats']):
- formats.append(format.rpartition('.')[2].upper())
- formatsTag.insert(0, NavigableString(' · '.join(formats)))
-
- # Insert the cover
if available
- imgTag = Tag(soup,"img")
- if 'cover' in title:
- imgTag['src'] = "../images/thumbnail_%d.jpg" % int(title['id'])
- else:
- imgTag['src'] = "../images/thumbnail_default.jpg"
- imgTag['alt'] = "cover"
-
- '''
- if self.opts.fmt == 'mobi':
- imgTag['style'] = 'width: %dpx; height:%dpx;' % (self.thumbWidth, self.thumbHeight)
- '''
-
- thumbnailTag = body.find(attrs={'class':'thumbnail'})
- thumbnailTag.insert(0,imgTag)
-
- # Insert the publisher
- publisherTag = body.find(attrs={'class':'publisher'})
- if 'publisher' in title:
- publisherTag.insert(0,NavigableString(title['publisher'] + '
' ))
- else:
- publisherTag.insert(0,NavigableString('
'))
-
- # Insert the publication date
- pubdateTag = body.find(attrs={'class':'date'})
- if title['date'] is not None:
- pubdateTag.insert(0,NavigableString(title['date'] + '
'))
- else:
- pubdateTag.insert(0,NavigableString('
'))
-
- # Insert the rating, remove if unrated
- # Render different ratings chars for epub/mobi
- stars = int(title['rating']) / 2
- ratingTag = body.find(attrs={'class':'rating'})
- if stars:
- star_string = self.FULL_RATING_SYMBOL * stars
- empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars)
- ratingTag.insert(0,NavigableString('%s%s
' % (star_string,empty_stars)))
- else:
- #ratingLabel = body.find('td',text="Rating").replaceWith("Unrated")
- ratingTag.insert(0,NavigableString('
'))
-
- # Insert user notes or remove Notes label. Notes > 1 line will push formatting down
- if 'notes' in title:
- notesTag = body.find(attrs={'class':'notes'})
- notesTag.insert(0,NavigableString(title['notes'] + '
'))
- else:
- pass
-# notes_labelTag = body.find(attrs={'class':'notes_label'})
-# empty_labelTag = Tag(soup, "td")
-# empty_labelTag.insert(0,NavigableString('
'))
-# notes_labelTag.replaceWith(empty_labelTag)
-
- # Insert the blurb
- if 'description' in title and title['description'] > '':
- blurbTag = body.find(attrs={'class':'description'})
- blurbTag.insert(0,NavigableString(title['description']))
# Write the book entry to contentdir
outfile = open("%s/book_%d.html" % (self.contentDir, int(title['id'])), 'w')
@@ -1994,7 +1851,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify())
outfile.close()
- self.htmlFileList.append("content/ByAlphaTitle.html")
+ self.htmlFileList_1.append("content/ByAlphaTitle.html")
def generateHTMLByAuthor(self):
# Write books by author A-Z
@@ -2176,7 +2033,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify())
outfile.close()
- self.htmlFileList.append("content/ByAlphaAuthor.html")
+ self.htmlFileList_1.append("content/ByAlphaAuthor.html")
def generateHTMLByDateAdded(self):
# Write books by reverse chronological order
@@ -2451,7 +2308,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify())
outfile.close()
- self.htmlFileList.append("content/ByDateAdded.html")
+ self.htmlFileList_2.append("content/ByDateAdded.html")
def generateHTMLByDateRead(self):
# Write books by active bookmarks
@@ -2642,7 +2499,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify())
outfile.close()
- self.htmlFileList.append("content/ByDateRead.html")
+ self.htmlFileList_2.append("content/ByDateRead.html")
def generateHTMLBySeries(self):
'''
@@ -2673,7 +2530,9 @@ class EPUB_MOBI(CatalogPlugin):
self.opts.search_text = search_phrase
# Fetch the database as a dictionary
- self.booksBySeries = self.plugin.search_sort_db(self.db, self.opts)
+ data = self.plugin.search_sort_db(self.db, self.opts)
+ self.booksBySeries = self.processExclusions(data)
+
if not self.booksBySeries:
self.opts.generate_series = False
self.opts.log(" no series found in selected books, cancelling series generation")
@@ -2822,7 +2681,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify())
outfile.close()
- self.htmlFileList.append("content/BySeries.html")
+ self.htmlFileList_1.append("content/BySeries.html")
def generateHTMLByTags(self):
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
@@ -3080,7 +2939,8 @@ class EPUB_MOBI(CatalogPlugin):
else self.booksByTitle
# Add html_files to manifest and spine
- for file in self.htmlFileList:
+ for file in self.htmlFileList_1:
+ # By Author, By Title, By Series,
itemTag = Tag(soup, "item")
start = file.find('/') + 1
end = file.find('.')
@@ -3114,6 +2974,23 @@ class EPUB_MOBI(CatalogPlugin):
spine.insert(stc, itemrefTag)
stc += 1
+ for file in self.htmlFileList_2:
+ # By Date Added, By Date Read
+ itemTag = Tag(soup, "item")
+ start = file.find('/') + 1
+ end = file.find('.')
+ itemTag['href'] = file
+ itemTag['id'] = file[start:end].lower()
+ itemTag['media-type'] = "application/xhtml+xml"
+ manifest.insert(mtc, itemTag)
+ mtc += 1
+
+ # spine
+ itemrefTag = Tag(soup, "itemref")
+ itemrefTag['idref'] = file[start:end].lower()
+ spine.insert(stc, itemrefTag)
+ stc += 1
+
for book in sort_descriptions_by:
# manifest
itemTag = Tag(soup, "item")
@@ -4346,59 +4223,199 @@ class EPUB_MOBI(CatalogPlugin):
return titles_spanned
- def generateHTMLDescriptionHeader(self, title):
+ def generateHTMLDescriptionHeader(self, book):
+ '''
+ Generate description header from template
+ '''
+ NBSP = ' '
+ MIDDOT = '·'
+ def generate_html():
+ args = dict(
+ author=author,
+ author_prefix=author_prefix,
+ css=css,
+ formats=formats,
+ genres=genres,
+ note_content=note_content,
+ note_source=note_source,
+ pubdate=pubdate,
+ publisher=publisher,
+ pubmonth=pubmonth,
+ pubyear=pubyear,
+ rating=rating,
+ series=series,
+ series_index=series_index,
+ title=title,
+ title_str=title_str,
+ xmlns=XHTML_NS,
+ )
- title_border = '' if self.opts.fmt == 'epub' else \
- '
'
- header = '''
-
-
-
-
-
-
-
-
-
- {0}
-
-
-
-
-
-
- |
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
-
-
-
-
- '''.format(title_border)
+ generated_html = P('catalog/template.xhtml',
+ data=True).decode('utf-8').format(**args)
- # Insert the supplied title
+ soup = BeautifulSoup(generated_html)
+ return soup.renderContents(None)
+
+ if False:
+ print "title metadata:\n%s" % ', '.join(sorted(book.keys()))
+ for item in sorted(book.keys()):
+ try:
+ print "%s: %s%s" % (item, book[item][:50], '...' if len(book[item])>50 else '')
+ except:
+ print "%s: %s" % (item, book[item])
+
+ # Generate the template arguments
+ css = P('catalog/stylesheet.css', data=True).decode('utf-8')
+ title_str = escape(book['title'])
+
+ # Title/series
+ if book['series']:
+ series_id, _, title = book['title'].partition(':')
+ title = escape(title.strip())
+ series = escape(book['series'])
+ series_index = str(book['series_index'])
+ if series_index.endswith('.0'):
+ series_index = series_index[:-2]
+ else:
+ title = escape(book['title'])
+ series = ''
+ series_index = ''
+
+ # Author, author_prefix (read|reading|none symbol or missing symbol)
+ author = book['author']
+ if self.opts.wishlist_tag in book.get('tags', []):
+ author_prefix = self.MISSING_SYMBOL + " by "
+ else:
+ if book['read']:
+ author_prefix = self.READ_SYMBOL + " by "
+ elif self.opts.connected_kindle and book['id'] in self.bookmarked_books:
+ author_prefix = self.READING_SYMBOL + " by "
+ else:
+ author_prefix = "by "
+
+ # Genres
+ genres = ''
+ if 'tags' in book:
+ _soup = BeautifulSoup('')
+ genresTag = Tag(_soup,'p')
+ gtc = 0
+ for tag in book.get('tags', []):
+ aTag = Tag(_soup,'a')
+ if self.opts.generate_genres:
+ aTag['href'] = "Genre_%s.html" % re.sub("\W","",tag.lower())
+ aTag.insert(0,escape(NavigableString(tag)))
+ genresTag.insert(gtc, aTag)
+ gtc += 1
+ if gtc < len(book['tags']):
+ genresTag.insert(gtc, NavigableString(' %s ' % MIDDOT))
+ gtc += 1
+ genres = genresTag.renderContents()
+
+ # Formats
+ formats = []
+ if 'formats' in book:
+ for format in sorted(book['formats']):
+ formats.append(format.rpartition('.')[2].upper())
+ formats = ' %s ' % MIDDOT.join(formats)
+
+ pubdate = book['date']
+ pubmonth, pubyear = pubdate.split(' ')
+
+ '''
+ # Thumb
+ # This doesn't make it through the etree.fromstring parsing
+ _soup = BeautifulSoup('')
+ imgTag = Tag(_soup,"img")
+ if 'cover' in book:
+ imgTag['src'] = "../images/thumbnail_%d.jpg" % int(book['id'])
+ else:
+ imgTag['src'] = "../images/thumbnail_default.jpg"
+ imgTag['alt'] = "cover thumbnail"
+ thumb = imgTag.renderContents()
+ '''
+
+ # Publisher
+ publisher = NBSP
+ if 'publisher' in book:
+ publisher = book['publisher']
+
+ # Rating
+ stars = int(book['rating']) / 2
+ rating = NBSP
+ if stars:
+ star_string = self.FULL_RATING_SYMBOL * stars
+ empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars)
+ rating = '%s%s
' % (star_string,empty_stars)
+
+ # Notes
+ note_source = NBSP
+ note_content = NBSP
+ if 'notes' in book:
+ note_source = book['notes']['source']
+ note_content = book['notes']['content']
+
+ # >>>> Populate the template <<<<
+ if True:
+ root = etree.fromstring(generate_html(), parser=RECOVER_PARSER)
+ else:
+ root = etree.fromstring(generate_html())
+ header = etree.tostring(root, pretty_print=True, encoding='utf-8')
soup = BeautifulSoup(header, selfClosingTags=['mbp:pagebreak'])
- titleTag = soup.find('title')
- titleTag.insert(0,NavigableString(escape(title)))
+
+
+
+ # >>>> Post-process the template <<<<
+ body = soup.find('body')
+ btc = 0
+ # Insert the title anchor for inbound links
+ aTag = Tag(soup, "a")
+ aTag['name'] = "book%d" % int(book['id'])
+ body.insert(btc, aTag)
+ btc += 1
+
+ # Insert the link to the series or remove
+ aTag = body.find('a', attrs={'class':'series_id'})
+ if book['series']:
+ if self.opts.generate_series:
+ aTag['href'] = "%s.html#%s_series" % ('BySeries',
+ re.sub('\W','',book['series']).lower())
+ else:
+ aTag.extract()
+
+ # Insert the author link (always)
+ aTag = body.find('a', attrs={'class':'author'})
+ aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
+ self.generateAuthorAnchor(book['author']))
+
+ if not genres:
+ genresTag = body.find('p',attrs={'class':'genres'})
+ genresTag.extract()
+
+ if not formats:
+ formatsTag = body.find('p',attrs={'class':'formats'})
+ formatsTag.extract()
+
+ if note_content == NBSP:
+ tdTag = body.find('td', attrs={'class':'notes'})
+ tdTag.contents[0].replaceWith(NBSP)
+
+ # Cover thumb
+ tdTag = body.find('td', attrs={'class':'thumbnail'})
+ imgTag = Tag(soup,"img")
+ if 'cover' in book:
+ imgTag['src'] = "../images/thumbnail_%d.jpg" % int(book['id'])
+ else:
+ imgTag['src'] = "../images/thumbnail_default.jpg"
+ imgTag['alt'] = "cover thumbnail"
+ tdTag.insert(0,imgTag)
+
+ # The Blurb
+ if 'description' in book and book['description'] > '':
+ blurbTag = body.find(attrs={'class':'description'})
+ blurbTag.insert(0,NavigableString(book['description']))
+
+ if False:
+ print soup.prettify()
return soup
def generateHTMLEmptyHeader(self, title):