GwR catalog updates wip

This commit is contained in:
GRiker 2010-12-25 11:16:05 -07:00
parent 033f55522d
commit ac3c3ec086
7 changed files with 411 additions and 305 deletions

View File

@ -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;
}

View File

@ -0,0 +1,41 @@
<html xmlns="{xmlns}">
<head>
<title>{title_str}</title>
<meta name="catalog description header" http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="stylesheet.css" media="screen" />
</head>
<body>
<p class="title">{title}</p>
<p class="series_id"><a class="series_id">{series} [{series_index}]</a></p>
<hr class="header_divider" />
<p class="author">{author_prefix}<a class="author">{author}</a></p>
<p class="genres">{genres}</p>
<p class="formats">{formats}</p>
<table width="100%" border="0">
<tr>
<td class="thumbnail" rowspan="7"></td>
<td>&#160;</td>
</tr>
<tr>
<td>&#160;</td>
</tr>
<tr>
<td class="publisher">{publisher}</td>
</tr>
<tr>
<td class="date">{pubyear}</td>
</tr>
<tr>
<td class="rating">{rating}</td>
</tr>
<tr>
<td class="notes">{note_source}: {note_content}</td>
</tr>
<tr>
<td>&#160;</td>
</tr>
</table>
<hr class="description_divider" />
<div class="description"></div>
</body>
</html>

View File

@ -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

View File

@ -6,9 +6,11 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__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)

View File

@ -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):
'''

View File

@ -35,10 +35,10 @@
</size>
</property>
<property name="toolTip">
<string>Sections to include in generated catalog. A minimal catalog includes 'Books by Author'.</string>
<string>Sections to include in catalog. All catalogs include 'Books by Author'.</string>
</property>
<property name="title">
<string>Included sections (Books by Author included by default)</string>
<string>Included sections</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
@ -100,7 +100,8 @@ p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Default pattern &lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Courier New,courier';&quot;&gt;\[.+\]&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;excludes tags of the form [&lt;span style=&quot; font-style:italic;&quot;&gt;tag&lt;/span&gt;]&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;excludes tags of the form [&lt;span style=&quot; font-family:'Courier New,courier';&quot;&gt;tag&lt;/span&gt;], &lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;e.g., [Project Gutenberg]&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="title">
<string>Excluded genres</string>
@ -184,7 +185,7 @@ p, li { white-space: pre-wrap; }
</size>
</property>
<property name="toolTip">
<string>Exclude matching books from generated catalog</string>
<string>Books matching either pattern will not be included in generated catalog. </string>
</property>
<property name="title">
<string>Excluded books</string>
@ -279,7 +280,7 @@ p, li { white-space: pre-wrap; }
</size>
</property>
<property name="toolTip">
<string>Column containing exclusion criteria</string>
<string>Column containing additional exclusion criteria</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
@ -455,7 +456,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QLineEdit" name="wishlist_tag">
<property name="toolTip">
<string>Wishlist items will be displayed with ✕</string>
<string>Books tagged as Wishlist items will be displayed with ✕</string>
</property>
</widget>
</item>
@ -497,7 +498,7 @@ p, li { white-space: pre-wrap; }
</sizepolicy>
</property>
<property name="toolTip">
<string>Size hint for cover thumbnails included in Descriptions</string>
<string>Size hint for Description cover thumbnails</string>
</property>
<property name="suffix">
<string>&quot;</string>
@ -540,8 +541,11 @@ p, li { white-space: pre-wrap; }
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Header note</string>
<string>Description note</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
@ -563,7 +567,7 @@ p, li { white-space: pre-wrap; }
</size>
</property>
<property name="toolTip">
<string>Column containing header note</string>
<string>Custom column source for note to include in Description header area</string>
</property>
</widget>
</item>
@ -602,7 +606,7 @@ p, li { white-space: pre-wrap; }
</size>
</property>
<property name="toolTip">
<string>Column containing additional content to merge</string>
<string>Additional content merged with Comments during catalog generation</string>
</property>
</widget>
</item>
@ -616,7 +620,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QRadioButton" name="merge_before">
<property name="toolTip">
<string>Merge before Comments</string>
<string>Merge additional content before Comments</string>
</property>
<property name="text">
<string>Before</string>
@ -626,7 +630,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QRadioButton" name="merge_after">
<property name="toolTip">
<string>Merge after Comments</string>
<string>Merge additional content after Comments</string>
</property>
<property name="text">
<string>After</string>
@ -643,7 +647,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QCheckBox" name="include_hr">
<property name="toolTip">
<string>Separate with horizontal rule</string>
<string>Separate Comments and additional content with horizontal rule</string>
</property>
<property name="text">
<string>&lt;hr /&gt;</string>

View File

@ -1,22 +1,24 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__copyright__ = '2010, Greg Riker <griker at hotmail.com>'
__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,6 +119,9 @@ class CSV_XML(CatalogPlugin):
for entry in data:
outstr = []
for field in fields:
if field.startswith('#'):
item = db.get_field(entry['id'],field,index_is_id=True)
else:
item = entry[field]
if item is None:
outstr.append('""')
@ -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,6 +962,7 @@ class EPUB_MOBI(CatalogPlugin):
break
# Confirm/create 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)
@ -965,7 +981,7 @@ class EPUB_MOBI(CatalogPlugin):
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(" 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)
@ -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)
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
#<p class="title"><a name="<database_id>"></a><em>Book Title</em></p>
emTag = Tag(soup, "em")
if title['series']:
# title<br />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(' &middot; '))
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(' &middot; '.join(formats)))
# Insert the cover <img> 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'] + '<br/>' ))
else:
publisherTag.insert(0,NavigableString('<br/>'))
# Insert the publication date
pubdateTag = body.find(attrs={'class':'date'})
if title['date'] is not None:
pubdateTag.insert(0,NavigableString(title['date'] + '<br/>'))
else:
pubdateTag.insert(0,NavigableString('<br/>'))
# 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 <br/>' % (star_string,empty_stars)))
else:
#ratingLabel = body.find('td',text="Rating").replaceWith("Unrated")
ratingTag.insert(0,NavigableString('<br/>'))
# 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'] + '<br/>'))
else:
pass
# notes_labelTag = body.find(attrs={'class':'notes_label'})
# empty_labelTag = Tag(soup, "td")
# empty_labelTag.insert(0,NavigableString('<br/>'))
# 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 = '&#160;'
MIDDOT = '&#183;'
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 \
'<hr class="description_divider"/>'
header = '''
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="stylesheet.css" media="screen" />
<title></title>
</head>
<body>
<p class="title"></p>
{0}
<p class="author"></p>
<!--p class="series"></p-->
<p class="tags">&nbsp;</p>
<p class="formats">&nbsp;</p>
<table width="100%" border="0">
<tr>
<td class="thumbnail" rowspan="7" width="40%"></td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td class="publisher"></td>
</tr>
<tr>
<td class="date"></td>
</tr>
<tr>
<td class="rating"></td>
</tr>
<tr>
<td class="notes"></td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
</table>
<hr class="description_divider" />
<div class="description"></div>
</body>
</html>
'''.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 <br/>' % (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 <a class="series">
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):