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 { p.title {
margin-top:0em; margin-top:0em;
margin-bottom:1em; margin-bottom:0em;
text-align:center; text-align:center;
font-style:italic; font-style:italic;
font-size:xx-large; 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 { p.author {
font-size:large;
margin-top:0em; margin-top:0em;
margin-bottom:0em; margin-bottom:0em;
text-align: center; text-align: center;
text-indent: 0em; text-indent: 0em;
font-size:large;
} }
p.author_index { p.author_index {
@ -26,7 +36,8 @@ p.author_index {
text-indent: 0em; text-indent: 0em;
} }
p.tags { p.genres {
font-style:normal;
margin-top:0.5em; margin-top:0.5em;
margin-bottom:0em; margin-bottom:0em;
text-align: left; text-align: left;
@ -124,22 +135,37 @@ hr.description_divider {
border-left: solid white 0px; 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 { hr.merged_comments_divider {
width:80%; width:80%;
margin-left:10%; margin-left:10%;
border-top: solid white 0px; border-top: solid white 0px;
border-right: solid white 0px; border-right: solid white 0px;
border-bottom: dotted grey 2px; border-bottom: dashed gray 2px;
border-left: solid white 0px; border-left: solid white 0px;
} }
td {
text-align:center;
}
td.publisher, td.date { td.publisher, td.date {
font-weight:bold; font-weight:bold;
text-align:center;
} }
td.rating, td.notes {
text-align: center; td.rating{
} }
td.notes {
font-size: 100%;
}
td.thumbnail img { td.thumbnail img {
-webkit-box-shadow: 4px 4px 12px #999; -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 parsed in library.cli:catalog_option_parser()
cli_options = [] 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): def search_sort_db(self, db, opts):
@ -315,18 +323,18 @@ class CatalogPlugin(Plugin): # {{{
if opts.sort_by: if opts.sort_by:
# 2nd arg = ascending # 2nd arg = ascending
db.sort(opts.sort_by, True) db.sort(opts.sort_by, True)
return db.get_data_as_dict(ids=opts.ids) 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 # Return a list of requested fields, with opts.sort_by first
all_fields = set( all_std_fields = set(
['author_sort','authors','comments','cover','formats', ['author_sort','authors','comments','cover','formats',
'id','isbn','ondevice','pubdate','publisher','rating', 'id','isbn','ondevice','pubdate','publisher','rating',
'series_index','series','size','tags','timestamp', 'series_index','series','size','tags','timestamp',
'title','uuid']) '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': if opts.fields != 'all':
# Make a list from opts.fields # Make a list from opts.fields
requested_fields = set(opts.fields.split(',')) 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: if not opts.connected_device['is_device_connected'] and 'ondevice' in fields:
fields.pop(int(fields.index('ondevice'))) 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: if opts.sort_by and opts.sort_by in fields:
fields.insert(0,fields.pop(int(fields.index(opts.sort_by)))) fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
return fields return fields

View File

@ -6,9 +6,11 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os
from calibre.gui2 import gprefs from calibre.gui2 import gprefs
from calibre.gui2.catalog.catalog_csv_xml_ui import Ui_Form 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 from PyQt4.Qt import QWidget, QListWidgetItem
class PluginWidget(QWidget, Ui_Form): class PluginWidget(QWidget, Ui_Form):
@ -28,6 +30,13 @@ class PluginWidget(QWidget, Ui_Form):
self.all_fields.append(x) self.all_fields.append(x)
QListWidgetItem(x, self.db_fields) 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): def initialize(self, name, db):
self.name = name self.name = name
fields = gprefs.get(name+'_db_fields', self.all_fields) fields = gprefs.get(name+'_db_fields', self.all_fields)

View File

@ -237,7 +237,7 @@ class PluginWidget(QWidget,Ui_Form):
custom_fields = {} custom_fields = {}
for custom_field in all_custom_fields: for custom_field in all_custom_fields:
field_md = self.db.metadata_for_field(custom_field) 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, custom_fields[field_md['name']] = {'field':custom_field,
'datatype':field_md['datatype']} 'datatype':field_md['datatype']}
# Blank field first # Blank field first
@ -298,6 +298,7 @@ class PluginWidget(QWidget,Ui_Form):
if new_source > '': if new_source > '':
exclude_source_spec = self.exclude_source_fields[str(new_source)] exclude_source_spec = self.exclude_source_fields[str(new_source)]
self.exclude_source_field_name = exclude_source_spec['field'] self.exclude_source_field_name = exclude_source_spec['field']
self.exclude_pattern.setEnabled(True)
# Change pattern input widget to match the source field datatype # Change pattern input widget to match the source field datatype
if exclude_source_spec['datatype'] in ['bool','composite','datetime','text']: 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_pattern = dw
self.exclude_spec_hl.addWidget(dw) self.exclude_spec_hl.addWidget(dw)
else: else:
self.exclude_pattern.setText('') self.exclude_pattern.setEnabled(False)
def header_note_source_field_changed(self,new_index): def header_note_source_field_changed(self,new_index):
''' '''

View File

@ -35,10 +35,10 @@
</size> </size>
</property> </property>
<property name="toolTip"> <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>
<property name="title"> <property name="title">
<string>Included sections (Books by Author included by default)</string> <string>Included sections</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0"> <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;/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;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;&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>
<property name="title"> <property name="title">
<string>Excluded genres</string> <string>Excluded genres</string>
@ -184,7 +185,7 @@ p, li { white-space: pre-wrap; }
</size> </size>
</property> </property>
<property name="toolTip"> <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>
<property name="title"> <property name="title">
<string>Excluded books</string> <string>Excluded books</string>
@ -279,7 +280,7 @@ p, li { white-space: pre-wrap; }
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Column containing exclusion criteria</string> <string>Column containing additional exclusion criteria</string>
</property> </property>
<property name="sizeAdjustPolicy"> <property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
@ -455,7 +456,7 @@ p, li { white-space: pre-wrap; }
<item> <item>
<widget class="QLineEdit" name="wishlist_tag"> <widget class="QLineEdit" name="wishlist_tag">
<property name="toolTip"> <property name="toolTip">
<string>Wishlist items will be displayed with ✕</string> <string>Books tagged as Wishlist items will be displayed with ✕</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -497,7 +498,7 @@ p, li { white-space: pre-wrap; }
</sizepolicy> </sizepolicy>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Size hint for cover thumbnails included in Descriptions</string> <string>Size hint for Description cover thumbnails</string>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>&quot;</string> <string>&quot;</string>
@ -540,8 +541,11 @@ p, li { white-space: pre-wrap; }
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="toolTip">
<string/>
</property>
<property name="text"> <property name="text">
<string>Header note</string> <string>Description note</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
@ -563,7 +567,7 @@ p, li { white-space: pre-wrap; }
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Column containing header note</string> <string>Custom column source for note to include in Description header area</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -602,7 +606,7 @@ p, li { white-space: pre-wrap; }
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Column containing additional content to merge</string> <string>Additional content merged with Comments during catalog generation</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -616,7 +620,7 @@ p, li { white-space: pre-wrap; }
<item> <item>
<widget class="QRadioButton" name="merge_before"> <widget class="QRadioButton" name="merge_before">
<property name="toolTip"> <property name="toolTip">
<string>Merge before Comments</string> <string>Merge additional content before Comments</string>
</property> </property>
<property name="text"> <property name="text">
<string>Before</string> <string>Before</string>
@ -626,7 +630,7 @@ p, li { white-space: pre-wrap; }
<item> <item>
<widget class="QRadioButton" name="merge_after"> <widget class="QRadioButton" name="merge_after">
<property name="toolTip"> <property name="toolTip">
<string>Merge after Comments</string> <string>Merge additional content after Comments</string>
</property> </property>
<property name="text"> <property name="text">
<string>After</string> <string>After</string>
@ -643,7 +647,7 @@ p, li { white-space: pre-wrap; }
<item> <item>
<widget class="QCheckBox" name="include_hr"> <widget class="QCheckBox" name="include_hr">
<property name="toolTip"> <property name="toolTip">
<string>Separate with horizontal rule</string> <string>Separate Comments and additional content with horizontal rule</string>
</property> </property>
<property name="text"> <property name="text">
<string>&lt;hr /&gt;</string> <string>&lt;hr /&gt;</string>

View File

@ -1,22 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2010, Greg Riker <griker at hotmail.com>' __copyright__ = '2010, Greg Riker'
import codecs, datetime, htmlentitydefs, os, re, shutil, time, zlib import codecs, datetime, htmlentitydefs, os, re, shutil, time, zlib
from contextlib import closing from contextlib import closing
from collections import namedtuple from collections import namedtuple
from copy import deepcopy from copy import deepcopy
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from lxml import etree
from calibre import prints, prepare_string_for_xml, strftime from calibre import prints, prepare_string_for_xml, strftime
from calibre.constants import preferred_encoding from calibre.constants import preferred_encoding
from calibre.customize import CatalogPlugin from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString 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.ptempfile import PersistentTemporaryDirectory
from calibre.utils.config import config_dir 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.logging import default_log as log
from calibre.utils.zipfile import ZipFile, ZipInfo from calibre.utils.zipfile import ZipFile, ZipInfo
from calibre.utils.magick.draw import thumbnail from calibre.utils.magick.draw import thumbnail
@ -26,6 +28,7 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments',
'series_index', 'series', 'size', 'tags', 'timestamp', 'title', 'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
'uuid'] 'uuid']
#Allowed fields for template #Allowed fields for template
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ] 'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
@ -96,7 +99,7 @@ class CSV_XML(CatalogPlugin):
#raise SystemExit(1) #raise SystemExit(1)
# Get the requested output fields as a list # 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 connected device, add 'On Device' values to data
if opts.connected_device['is_device_connected'] and 'ondevice' in fields: if opts.connected_device['is_device_connected'] and 'ondevice' in fields:
@ -116,6 +119,9 @@ class CSV_XML(CatalogPlugin):
for entry in data: for entry in data:
outstr = [] outstr = []
for field in fields: for field in fields:
if field.startswith('#'):
item = db.get_field(entry['id'],field,index_is_id=True)
else:
item = entry[field] item = entry[field]
if item is None: if item is None:
outstr.append('""') outstr.append('""')
@ -141,7 +147,7 @@ class CSV_XML(CatalogPlugin):
outfile.close() outfile.close()
elif self.fmt == 'xml': elif self.fmt == 'xml':
from lxml import etree #from lxml import etree
from lxml.builder import E from lxml.builder import E
root = E.calibredb() root = E.calibredb()
@ -149,6 +155,14 @@ class CSV_XML(CatalogPlugin):
record = E.record() record = E.record()
root.append(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', for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size',
'isbn','ondevice'): 'isbn','ondevice'):
if field in fields: if field in fields:
@ -470,7 +484,7 @@ class BIBTEX(CatalogPlugin):
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text) log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
# Get the requested output fields as a list # 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): if not len(data):
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text) 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 = None
self.genres = [] self.genres = []
self.__genre_tags_dict = None self.__genre_tags_dict = None
self.__htmlFileList = [] self.__htmlFileList_1 = []
self.__htmlFileList_2 = []
self.__markerTags = self.getMarkerTags() self.__markerTags = self.getMarkerTags()
self.__ncxSoup = None self.__ncxSoup = None
self.__output_profile = None self.__output_profile = None
@ -947,6 +962,7 @@ class EPUB_MOBI(CatalogPlugin):
break break
# Confirm/create thumbs archive. # Confirm/create thumbs archive.
if self.opts.generate_descriptions:
if not os.path.exists(self.__cache_dir): if not os.path.exists(self.__cache_dir):
self.opts.log.info(" creating new thumb cache '%s'" % self.__cache_dir) self.opts.log.info(" creating new thumb cache '%s'" % self.__cache_dir)
os.makedirs(self.__cache_dir) os.makedirs(self.__cache_dir)
@ -965,7 +981,7 @@ class EPUB_MOBI(CatalogPlugin):
cached_thumb_width = "-1" cached_thumb_width = "-1"
if float(cached_thumb_width) != float(self.opts.thumb_width): 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"' % self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' %
(float(cached_thumb_width),float(self.opts.thumb_width))) (float(cached_thumb_width),float(self.opts.thumb_width)))
os.remove(self.__archive_path) os.remove(self.__archive_path)
@ -1129,11 +1145,18 @@ class EPUB_MOBI(CatalogPlugin):
self.__genre_tags_dict = val self.__genre_tags_dict = val
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@dynamic_property @dynamic_property
def htmlFileList(self): def htmlFileList_1(self):
def fget(self): def fget(self):
return self.__htmlFileList return self.__htmlFileList_1
def fset(self, val): 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) return property(fget=fget, fset=fset)
@dynamic_property @dynamic_property
def libraryPath(self): def libraryPath(self):
@ -1303,12 +1326,12 @@ class EPUB_MOBI(CatalogPlugin):
self.generateHTMLByTitle() self.generateHTMLByTitle()
if self.opts.generate_series: if self.opts.generate_series:
self.generateHTMLBySeries() self.generateHTMLBySeries()
if self.opts.generate_genres:
self.generateHTMLByTags()
if self.opts.generate_recently_added: if self.opts.generate_recently_added:
self.generateHTMLByDateAdded() self.generateHTMLByDateAdded()
if self.generateRecentlyRead: if self.generateRecentlyRead:
self.generateHTMLByDateRead() self.generateHTMLByDateRead()
if self.opts.generate_genres:
self.generateHTMLByTags()
if self.opts.generate_descriptions: if self.opts.generate_descriptions:
self.generateThumbnails() self.generateThumbnails()
self.generateOPF() self.generateOPF()
@ -1318,12 +1341,12 @@ class EPUB_MOBI(CatalogPlugin):
self.generateNCXByTitle("Titles") self.generateNCXByTitle("Titles")
if self.opts.generate_series: if self.opts.generate_series:
self.generateNCXBySeries("Series") self.generateNCXBySeries("Series")
if self.opts.generate_genres:
self.generateNCXByGenre("Genres")
if self.opts.generate_recently_added: if self.opts.generate_recently_added:
self.generateNCXByDateAdded("Recently Added") self.generateNCXByDateAdded("Recently Added")
if self.generateRecentlyRead: if self.generateRecentlyRead:
self.generateNCXByDateRead("Recently Read") self.generateNCXByDateRead("Recently Read")
if self.opts.generate_genres:
self.generateNCXByGenre("Genres")
if self.opts.generate_descriptions: if self.opts.generate_descriptions:
self.generateNCXDescriptions("Descriptions") self.generateNCXDescriptions("Descriptions")
@ -1475,12 +1498,19 @@ class EPUB_MOBI(CatalogPlugin):
this_title['formats'] = formats this_title['formats'] = formats
# Add user notes to be displayed in header # Add user notes to be displayed in header
# Special case handling for datetime fields
if self.opts.header_note_source_field: 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'], notes = self.__db.get_field(record['id'],
self.opts.header_note_source_field, self.opts.header_note_source_field,
index_is_id=True) 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: if notes:
this_title['notes'] = notes this_title['notes'] = {'source':field_md['name'],
'content':notes}
titles.append(this_title) titles.append(this_title)
@ -1679,183 +1709,10 @@ class EPUB_MOBI(CatalogPlugin):
(title_num, len(self.booksByTitle)), (title_num, len(self.booksByTitle)),
float(title_num*100/len(self.booksByTitle))/100) float(title_num*100/len(self.booksByTitle))/100)
# Generate the header # Generate the header from user-customizable template
soup = self.generateHTMLDescriptionHeader("%s" % title['title']) soup = self.generateHTMLDescriptionHeader(title)
body = soup.find('body')
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 # Write the book entry to contentdir
outfile = open("%s/book_%d.html" % (self.contentDir, int(title['id'])), 'w') 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 = open(outfile_spec, 'w')
outfile.write(soup.prettify()) outfile.write(soup.prettify())
outfile.close() outfile.close()
self.htmlFileList.append("content/ByAlphaTitle.html") self.htmlFileList_1.append("content/ByAlphaTitle.html")
def generateHTMLByAuthor(self): def generateHTMLByAuthor(self):
# Write books by author A-Z # Write books by author A-Z
@ -2176,7 +2033,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w') outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify()) outfile.write(soup.prettify())
outfile.close() outfile.close()
self.htmlFileList.append("content/ByAlphaAuthor.html") self.htmlFileList_1.append("content/ByAlphaAuthor.html")
def generateHTMLByDateAdded(self): def generateHTMLByDateAdded(self):
# Write books by reverse chronological order # Write books by reverse chronological order
@ -2451,7 +2308,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w') outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify()) outfile.write(soup.prettify())
outfile.close() outfile.close()
self.htmlFileList.append("content/ByDateAdded.html") self.htmlFileList_2.append("content/ByDateAdded.html")
def generateHTMLByDateRead(self): def generateHTMLByDateRead(self):
# Write books by active bookmarks # Write books by active bookmarks
@ -2642,7 +2499,7 @@ class EPUB_MOBI(CatalogPlugin):
outfile = open(outfile_spec, 'w') outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify()) outfile.write(soup.prettify())
outfile.close() outfile.close()
self.htmlFileList.append("content/ByDateRead.html") self.htmlFileList_2.append("content/ByDateRead.html")
def generateHTMLBySeries(self): def generateHTMLBySeries(self):
''' '''
@ -2673,7 +2530,9 @@ class EPUB_MOBI(CatalogPlugin):
self.opts.search_text = search_phrase self.opts.search_text = search_phrase
# Fetch the database as a dictionary # 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: if not self.booksBySeries:
self.opts.generate_series = False self.opts.generate_series = False
self.opts.log(" no series found in selected books, cancelling series generation") 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 = open(outfile_spec, 'w')
outfile.write(soup.prettify()) outfile.write(soup.prettify())
outfile.close() outfile.close()
self.htmlFileList.append("content/BySeries.html") self.htmlFileList_1.append("content/BySeries.html")
def generateHTMLByTags(self): def generateHTMLByTags(self):
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ... # Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
@ -3080,7 +2939,8 @@ class EPUB_MOBI(CatalogPlugin):
else self.booksByTitle else self.booksByTitle
# Add html_files to manifest and spine # 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") itemTag = Tag(soup, "item")
start = file.find('/') + 1 start = file.find('/') + 1
end = file.find('.') end = file.find('.')
@ -3114,6 +2974,23 @@ class EPUB_MOBI(CatalogPlugin):
spine.insert(stc, itemrefTag) spine.insert(stc, itemrefTag)
stc += 1 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: for book in sort_descriptions_by:
# manifest # manifest
itemTag = Tag(soup, "item") itemTag = Tag(soup, "item")
@ -4346,59 +4223,199 @@ class EPUB_MOBI(CatalogPlugin):
return titles_spanned 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 \ generated_html = P('catalog/template.xhtml',
'<hr class="description_divider"/>' data=True).decode('utf-8').format(**args)
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)
# 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']) 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 return soup
def generateHTMLEmptyHeader(self, title): def generateHTMLEmptyHeader(self, title):