mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
GwR catalog updates wip
This commit is contained in:
parent
033f55522d
commit
ac3c3ec086
@ -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;
|
||||
}
|
41
resources/catalog/template.xhtml
Normal file
41
resources/catalog/template.xhtml
Normal 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> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </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> </td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr class="description_divider" />
|
||||
<div class="description"></div>
|
||||
</body>
|
||||
</html>
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
'''
|
||||
|
@ -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; }
|
||||
</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></string>
|
||||
<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></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>"</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><hr /></string>
|
||||
|
@ -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(' · '))
|
||||
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 <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 = ' '
|
||||
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 \
|
||||
'<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"> </p>
|
||||
<p class="formats"> </p>
|
||||
<table width="100%" border="0">
|
||||
<tr>
|
||||
<td class="thumbnail" rowspan="7" width="40%"></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </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> </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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user