mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
fb091d7be0
commit
8d7494e5ec
@ -170,7 +170,7 @@ def add_pipeline_options(parser, plumber):
|
|||||||
'chapter', 'chapter_mark',
|
'chapter', 'chapter_mark',
|
||||||
'prefer_metadata_cover', 'remove_first_image',
|
'prefer_metadata_cover', 'remove_first_image',
|
||||||
'insert_metadata', 'page_breaks_before',
|
'insert_metadata', 'page_breaks_before',
|
||||||
'remove_fake_margins',
|
'remove_fake_margins', 'start_reading_at',
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -304,6 +304,16 @@ OptionRecommendation(name='chapter_mark',
|
|||||||
'to mark chapters.')
|
'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',
|
OptionRecommendation(name='extra_css',
|
||||||
recommended_value=None, level=OptionRecommendation.LOW,
|
recommended_value=None, level=OptionRecommendation.LOW,
|
||||||
help=_('Either the path to a CSS stylesheet or raw CSS. '
|
help=_('Either the path to a CSS stylesheet or raw CSS. '
|
||||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import re
|
import re, uuid
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
@ -80,6 +80,35 @@ class DetectStructure(object):
|
|||||||
if not node.title or not node.title.strip():
|
if not node.title or not node.title.strip():
|
||||||
node.title = _('Unnamed')
|
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):
|
def detect_chapters(self):
|
||||||
self.detected_chapters = []
|
self.detected_chapters = []
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class StructureDetectionWidget(Widget, Ui_Form):
|
|||||||
|
|
||||||
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
||||||
Widget.__init__(self, parent,
|
Widget.__init__(self, parent,
|
||||||
['chapter', 'chapter_mark',
|
['chapter', 'chapter_mark', 'start_reading_at',
|
||||||
'remove_first_image', 'remove_fake_margins',
|
'remove_first_image', 'remove_fake_margins',
|
||||||
'insert_metadata', 'page_breaks_before']
|
'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_chapter.set_msg(_('Detect chapters at (XPath expression):'))
|
||||||
self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
|
self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
|
||||||
'(XPath expression):'))
|
'(XPath expression):'))
|
||||||
|
self.opt_start_reading_at.set_msg(
|
||||||
|
_('Start reading at (XPath expression):'))
|
||||||
|
|
||||||
def break_cycles(self):
|
def break_cycles(self):
|
||||||
Widget.break_cycles(self)
|
Widget.break_cycles(self)
|
||||||
|
|
||||||
def pre_commit_check(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)
|
x = getattr(self, 'opt_'+x)
|
||||||
if not x.check():
|
if not x.check():
|
||||||
error_dialog(self, _('Invalid XPath'),
|
error_dialog(self, _('Invalid XPath'),
|
||||||
_('The XPath expression %s is invalid.')%x.text).exec_()
|
_('The XPath expression %s is invalid.')%x.text).exec_()
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -14,10 +14,40 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<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 &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 & Replace options. Click the Search & 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 &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"/>
|
<widget class="XPathEdit" name="opt_chapter" native="true"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0" colspan="2">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Chapter &mark:</string>
|
<string>Chapter &mark:</string>
|
||||||
@ -27,44 +57,14 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="2">
|
||||||
<widget class="QComboBox" name="opt_chapter_mark">
|
<widget class="QComboBox" name="opt_chapter_mark">
|
||||||
<property name="minimumContentsLength">
|
<property name="minimumContentsLength">
|
||||||
<number>20</number>
|
<number>20</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" colspan="2">
|
<item row="1" column="3">
|
||||||
<widget class="QCheckBox" name="opt_remove_first_image">
|
|
||||||
<property name="text">
|
|
||||||
<string>Remove first &image</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0" colspan="2">
|
|
||||||
<widget class="QCheckBox" name="opt_insert_metadata">
|
|
||||||
<property name="text">
|
|
||||||
<string>Insert &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">
|
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -77,22 +77,25 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="3">
|
<item row="2" column="0" colspan="3">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QCheckBox" name="opt_remove_first_image">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>The header and footer removal options have been replaced by the Search & Replace options. Click the Search & 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>
|
<string>Remove first &image</string>
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="2">
|
<item row="8" column="0">
|
||||||
<widget class="QCheckBox" name="opt_remove_fake_margins">
|
<spacer name="verticalSpacer">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<string>Remove &fake margins</string>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user