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 {
|
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.publisher, td.date {
|
td {
|
||||||
font-weight:bold;
|
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
td.rating, td.notes {
|
td.publisher, td.date {
|
||||||
text-align: center;
|
font-weight:bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
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 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
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
'''
|
'''
|
||||||
|
@ -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; }
|
|||||||
</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;">
|
</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;">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;"><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>
|
||||||
<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>"</string>
|
<string>"</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><hr /></string>
|
<string><hr /></string>
|
||||||
|
@ -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(' · '))
|
|
||||||
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
|
# 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 = ' '
|
||||||
|
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 \
|
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"> </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)
|
|
||||||
|
|
||||||
# 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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user