Conversion: Add an option under Structure Detection to set the 'Start reading at' metadata with an XPath expression. Fixes #1043233 (Add ability to create start point in ebooks)

This commit is contained in:
Kovid Goyal 2012-08-30 16:55:06 +05:30
parent fb091d7be0
commit 8d7494e5ec
5 changed files with 94 additions and 49 deletions

View File

@ -170,7 +170,7 @@ def add_pipeline_options(parser, plumber):
'chapter', 'chapter_mark',
'prefer_metadata_cover', 'remove_first_image',
'insert_metadata', 'page_breaks_before',
'remove_fake_margins',
'remove_fake_margins', 'start_reading_at',
]
),

View File

@ -304,6 +304,16 @@ OptionRecommendation(name='chapter_mark',
'to mark chapters.')
),
OptionRecommendation(name='start_reading_at',
recommended_value=None, level=OptionRecommendation.LOW,
help=_('An XPath expression to detect the location in the document'
' at which to start reading. Some ebook reading programs'
' (most prominently the Kindle) use this location as the'
' position at which to open the book. See the XPath tutorial'
' in the calibre User Manual for further help using this'
' feature.')
),
OptionRecommendation(name='extra_css',
recommended_value=None, level=OptionRecommendation.LOW,
help=_('Either the path to a CSS stylesheet or raw CSS. '

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import re
import re, uuid
from lxml import etree
from urlparse import urlparse
@ -80,6 +80,35 @@ class DetectStructure(object):
if not node.title or not node.title.strip():
node.title = _('Unnamed')
if self.opts.start_reading_at:
self.detect_start_reading()
def detect_start_reading(self):
expr = self.opts.start_reading_at
try:
expr = XPath(expr)
except:
self.log.warn(
'Invalid start reading at XPath expression, ignoring: %s'%expr)
return
for item in self.oeb.spine:
if not hasattr(item.data, 'xpath'): continue
matches = expr(item.data)
if matches:
elem = matches[0]
eid = elem.get('id', None)
if not eid:
eid = u'start_reading_at_'+unicode(uuid.uuid4()).replace(u'-', u'')
elem.set('id', eid)
if u'text' in self.oeb.guide:
self.oeb.guide.remove(u'text')
self.oeb.guide.add(u'text', u'Start', item.href+u'#'+eid)
self.log('Setting start reading at position to %s in %s'%(
self.opts.start_reading_at, item.href))
return
self.log.warn("Failed to find start reading at position: %s"%
self.opts.start_reading_at)
def detect_chapters(self):
self.detected_chapters = []

View File

@ -20,7 +20,7 @@ class StructureDetectionWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent,
['chapter', 'chapter_mark',
['chapter', 'chapter_mark', 'start_reading_at',
'remove_first_image', 'remove_fake_margins',
'insert_metadata', 'page_breaks_before']
)
@ -31,15 +31,18 @@ class StructureDetectionWidget(Widget, Ui_Form):
self.opt_chapter.set_msg(_('Detect chapters at (XPath expression):'))
self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
'(XPath expression):'))
self.opt_start_reading_at.set_msg(
_('Start reading at (XPath expression):'))
def break_cycles(self):
Widget.break_cycles(self)
def pre_commit_check(self):
for x in ('chapter', 'page_breaks_before'):
for x in ('chapter', 'page_breaks_before', 'start_reading_at'):
x = getattr(self, 'opt_'+x)
if not x.check():
error_dialog(self, _('Invalid XPath'),
_('The XPath expression %s is invalid.')%x.text).exec_()
return False
return True

View File

@ -14,10 +14,40 @@
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3">
<item row="2" column="3">
<widget class="QCheckBox" name="opt_remove_fake_margins">
<property name="text">
<string>Remove &amp;fake margins</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="4">
<widget class="QLabel" name="label_2">
<property name="text">
<string>The header and footer removal options have been replaced by the Search &amp; Replace options. Click the Search &amp; Replace category in the bar to the left to use these options. Leave the replace field blank and enter your header/footer removal regexps into the search field.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0" rowspan="2" colspan="4">
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
</item>
<item row="3" column="0" colspan="4">
<widget class="QCheckBox" name="opt_insert_metadata">
<property name="text">
<string>Insert &amp;metadata as page at start of book</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="4">
<widget class="XPathEdit" name="opt_start_reading_at" native="true"/>
</item>
<item row="0" column="0" colspan="4">
<widget class="XPathEdit" name="opt_chapter" native="true"/>
</item>
<item row="1" column="0">
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Chapter &amp;mark:</string>
@ -27,44 +57,14 @@
</property>
</widget>
</item>
<item row="1" column="1">
<item row="1" column="2">
<widget class="QComboBox" name="opt_chapter_mark">
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remove_first_image">
<property name="text">
<string>Remove first &amp;image</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_insert_metadata">
<property name="text">
<string>Insert &amp;metadata as page at start of book</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
</item>
<item row="8" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<item row="1" column="3">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -77,22 +77,25 @@
</property>
</spacer>
</item>
<item row="5" column="0" colspan="3">
<widget class="QLabel" name="label_2">
<item row="2" column="0" colspan="3">
<widget class="QCheckBox" name="opt_remove_first_image">
<property name="text">
<string>The header and footer removal options have been replaced by the Search &amp; Replace options. Click the Search &amp; Replace category in the bar to the left to use these options. Leave the replace field blank and enter your header/footer removal regexps into the search field.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
<string>Remove first &amp;image</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QCheckBox" name="opt_remove_fake_margins">
<property name="text">
<string>Remove &amp;fake margins</string>
<item row="8" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>