Cleanup code style and add bible tests

bzr-revno: 2335
This commit is contained in:
Tim Bentley 2014-03-17 07:10:07 +00:00
commit ff78a99fc1
74 changed files with 2323 additions and 2183 deletions

View File

@ -82,11 +82,8 @@ def upgrade_db(url, upgrade):
"""
Upgrade a database.
``url``
The url of the database to upgrade.
``upgrade``
The python module that contains the upgrade instructions.
:param url: The url of the database to upgrade.
:param upgrade: The python module that contains the upgrade instructions.
"""
session, metadata = init_db(url)
@ -99,7 +96,7 @@ def upgrade_db(url, upgrade):
metadata_table = Table('metadata', metadata,
Column('key', types.Unicode(64), primary_key=True),
Column('value', types.UnicodeText(), default=None)
)
)
metadata_table.create(checkfirst=True)
mapper(Metadata, metadata_table)
version_meta = session.query(Metadata).get('version')
@ -137,11 +134,8 @@ def delete_database(plugin_name, db_file_name=None):
"""
Remove a database file from the system.
``plugin_name``
The name of the plugin to remove the database for
``db_file_name``
The database file name. Defaults to None resulting in the plugin_name being used.
:param plugin_name: The name of the plugin to remove the database for
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
"""
db_file_path = None
if db_file_name:
@ -175,17 +169,11 @@ class Manager(object):
Runs the initialisation process that includes creating the connection to the database and the tables if they do
not exist.
``plugin_name``
The name to setup paths and settings section names
``init_schema``
The init_schema function for this database
``upgrade_schema``
The upgrade_schema function for this database
``db_file_name``
The file name to use for this database. Defaults to None resulting in the plugin_name being used.
:param plugin_name: The name to setup paths and settings section names
:param init_schema: The init_schema function for this database
:param db_file_name: The upgrade_schema function for this database
:param upgrade_mod: The file name to use for this database. Defaults to None resulting in the plugin_name
being used.
"""
settings = Settings()
settings.beginGroup(plugin_name)
@ -208,7 +196,10 @@ class Manager(object):
self.db_url += '?charset=%s' % urlquote(db_encoding)
settings.endGroup()
if upgrade_mod:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
try:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
except (SQLAlchemyError, DBAPIError):
log.exception('Error loading database: %s', self.db_url)
if db_ver > up_ver:
critical_error_message_box(
translate('OpenLP.Manager', 'Database Error'),
@ -229,11 +220,8 @@ class Manager(object):
"""
Save an object to the database
``object_instance``
The object to save
``commit``
Commit the session with this object
:param object_instance: The object to save
:param commit: Commit the session with this object
"""
for try_count in range(3):
try:
@ -262,11 +250,8 @@ class Manager(object):
"""
Save a list of objects to the database
``object_list``
The list of objects to save
``commit``
Commit the session with this object
:param object_list: The list of objects to save
:param commit: Commit the session with this object
"""
for try_count in range(3):
try:
@ -295,11 +280,8 @@ class Manager(object):
"""
Return the details of an object
``object_class``
The type of object to return
``key``
The unique reference or primary key for the instance to return
:param object_class: The type of object to return
:param key: The unique reference or primary key for the instance to return
"""
if not key:
return object_class()
@ -319,11 +301,8 @@ class Manager(object):
"""
Returns an object matching specified criteria
``object_class``
The type of object to return
``filter_clause``
The criteria to select the object by
:param object_class: The type of object to return
:param filter_clause: The criteria to select the object by
"""
for try_count in range(3):
try:
@ -340,14 +319,9 @@ class Manager(object):
"""
Returns all the objects from the database
``object_class``
The type of objects to return
``filter_clause``
The filter governing selection of objects to return. Defaults to None.
``order_by_ref``
Any parameters to order the returned objects by. Defaults to None.
:param object_class: The type of objects to return
:param filter_clause: The filter governing selection of objects to return. Defaults to None.
:param order_by_ref: Any parameters to order the returned objects by. Defaults to None.
"""
query = self.session.query(object_class)
if filter_clause is not None:
@ -371,11 +345,8 @@ class Manager(object):
"""
Returns a count of the number of objects in the database.
``object_class``
The type of objects to return.
``filter_clause``
The filter governing selection of objects to return. Defaults to None.
:param object_class: The type of objects to return.
:param filter_clause: The filter governing selection of objects to return. Defaults to None.
"""
query = self.session.query(object_class)
if filter_clause is not None:
@ -395,11 +366,8 @@ class Manager(object):
"""
Delete an object from the database
``object_class``
The type of object to delete
``key``
The unique reference or primary key for the instance to be deleted
:param object_class: The type of object to delete
:param key: The unique reference or primary key for the instance to be deleted
"""
if key != 0:
object_instance = self.get_object(object_class, key)
@ -432,11 +400,8 @@ class Manager(object):
Delete all object records. This method should only be used for simple tables and **not** ones with
relationships. The relationships are not deleted from the database and this will lead to database corruptions.
``object_class``
The type of object to delete
``filter_clause``
The filter governing selection of objects to return. Defaults to None.
:param object_class: The type of object to delete
:param filter_clause: The filter governing selection of objects to return. Defaults to None.
"""
for try_count in range(3):
try:

View File

@ -116,20 +116,20 @@ class BiblePlugin(Plugin):
if QtGui.QMessageBox.information(self.main_window,
translate('OpenLP', 'Information'),
translate('OpenLP', 'Bible format has changed.\nYou have to upgrade your existing Bibles.\n'
'Should OpenLP upgrade now?'),
'Should OpenLP upgrade now?'),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) == \
QtGui.QMessageBox.Yes:
self.on_tools_upgrade_Item_triggered()
def add_import_menu_item(self, import_menu):
self.import_bible_item = create_action(import_menu, 'importBibleItem',
text=translate('BiblesPlugin', '&Bible'), visible=False,
triggers=self.on_bible_import_click)
text=translate('BiblesPlugin', '&Bible'), visible=False,
triggers=self.on_bible_import_click)
import_menu.addAction(self.import_bible_item)
def add_export_menu_Item(self, export_menu):
self.export_bible_item = create_action(export_menu, 'exportBibleItem',
text=translate('BiblesPlugin', '&Bible'), visible=False)
text=translate('BiblesPlugin', '&Bible'), visible=False)
export_menu.addAction(self.export_bible_item)
def add_tools_menu_item(self, tools_menu):
@ -140,7 +140,8 @@ class BiblePlugin(Plugin):
The actual **Tools** menu item, so that your actions can use it as their parent.
"""
log.debug('add tools menu')
self.tools_upgrade_item = create_action(tools_menu, 'toolsUpgradeItem',
self.tools_upgrade_item = create_action(
tools_menu, 'toolsUpgradeItem',
text=translate('BiblesPlugin', '&Upgrade older Bibles'),
statustip=translate('BiblesPlugin', 'Upgrade the Bible databases to the latest format.'),
visible=False, triggers=self.on_tools_upgrade_Item_triggered)
@ -162,14 +163,16 @@ class BiblePlugin(Plugin):
def about(self):
about_text = translate('BiblesPlugin', '<strong>Bible Plugin</strong>'
'<br />The Bible plugin provides the ability to display Bible '
'verses from different sources during the service.')
'<br />The Bible plugin provides the ability to display Bible '
'verses from different sources during the service.')
return about_text
def uses_theme(self, theme):
"""
Called to find out if the bible plugin is currently using a theme. Returns ``True`` if the theme is being used,
otherwise returns ``False``.
:param theme: The theme
"""
return str(self.settings_tab.bible_theme) == theme
@ -178,11 +181,8 @@ class BiblePlugin(Plugin):
Rename the theme the bible plugin is using making the plugin use the
new name.
``old_theme``
The name of the theme the plugin should stop using. Unused for this particular plugin.
``new_theme``
The new name the plugin should now use.
:param old_theme: The name of the theme the plugin should stop using. Unused for this particular plugin.
:param new_theme: The new name the plugin should now use.
"""
self.settings_tab.bible_theme = new_theme
self.settings_tab.save()
@ -207,8 +207,7 @@ class BiblePlugin(Plugin):
'new': translate('BiblesPlugin', 'Add a new Bible.'),
'edit': translate('BiblesPlugin', 'Edit the selected Bible.'),
'delete': translate('BiblesPlugin', 'Delete the selected Bible.'),
'preview': translate('BiblesPlugin',
'Preview the selected Bible.'),
'preview': translate('BiblesPlugin', 'Preview the selected Bible.'),
'live': translate('BiblesPlugin', 'Send the selected Bible live.'),
'service': translate('BiblesPlugin', 'Add the selected Bible to the service.')
}

View File

@ -182,9 +182,10 @@ def update_reference_separators():
"""
Updates separators and matches for parsing and formating scripture references.
"""
default_separators = translate('BiblesPlugin',
':|v|V|verse|verses;;-|to;;,|and;;end Double-semicolon delimited separators for parsing references. '
'Consult the developers for further information.').split(';;')
default_separators = \
translate('BiblesPlugin',
':|v|V|verse|verses;;-|to;;,|and;;end Double-semicolon delimited separators for parsing references. '
'Consult the developers for further information.').split(';;')
settings = Settings()
settings.beginGroup('bibles')
custom_separators = [
@ -206,8 +207,7 @@ def update_reference_separators():
for character in '\\.^$*+?{}[]()':
source_string = source_string.replace(character, '\\' + character)
# add various unicode alternatives
source_string = source_string.replace('-',
'(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])')
source_string = source_string.replace('-', '(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])')
source_string = source_string.replace(',', '(?:[,\u201A])')
REFERENCE_SEPARATORS['sep_%s' % role] = '\s*(?:%s)\s*' % source_string
REFERENCE_SEPARATORS['sep_%s_default' % role] = default_separators[index]
@ -227,8 +227,7 @@ def get_reference_separator(separator_type):
"""
Provides separators for parsing and formatting scripture references.
``separator_type``
The role and format of the separator.
:param separator_type: The role and format of the separator.
"""
if not REFERENCE_SEPARATORS:
update_reference_separators()
@ -239,8 +238,7 @@ def get_reference_match(match_type):
"""
Provides matches for parsing scripture references strings.
``match_type``
The type of match is ``range_separator``, ``range`` or ``full``.
:param match_type: The type of match is ``range_separator``, ``range`` or ``full``.
"""
if not REFERENCE_MATCHES:
update_reference_separators()
@ -252,19 +250,10 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
This is the next generation über-awesome function that takes a person's typed in string and converts it to a list
of references to be queried from the Bible database files.
``reference``
A string. The Bible reference to parse.
``bible``
A object. The Bible database object.
``language_selection``
An int. The language selection the user has choosen in settings section.
``book_ref_id``
A string. The book reference id.
Returns ``None`` or a reference list.
:param reference: A string. The Bible reference to parse.
:param bible: A object. The Bible database object.
:param language_selection: An int. The language selection the user has chosen in settings section.
:param book_ref_id: A string. The book reference id.
The reference list is a list of tuples, with each tuple structured like this::
@ -410,15 +399,9 @@ class SearchResults(object):
"""
Create the search result object.
``book``
The book of the Bible.
``chapter``
The chapter of the book.
``verse_list``
The list of verses for this reading.
:param book: The book of the Bible.
:param chapter: The chapter of the book.
:param verse_list: The list of verses for this reading.
"""
self.book = book
self.chapter = chapter

View File

@ -120,7 +120,7 @@ class BiblesTab(SettingsTab):
self.end_separator_line_edit = QtGui.QLineEdit(self.scripture_reference_group_box)
self.end_separator_line_edit.setObjectName('end_separator_line_edit')
self.end_separator_line_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(r'[^0-9]*'),
self.end_separator_line_edit))
self.end_separator_line_edit))
self.scripture_reference_layout.addWidget(self.end_separator_line_edit, 3, 1)
self.left_layout.addWidget(self.scripture_reference_group_box)
self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
@ -142,7 +142,7 @@ class BiblesTab(SettingsTab):
self.new_chapters_check_box.stateChanged.connect(self.on_new_chapters_check_box_changed)
self.display_style_combo_box.activated.connect(self.on_display_style_combo_box_changed)
self.bible_theme_combo_box.activated.connect(self.on_bible_theme_combo_box_changed)
self.layout_style_combo_box.activated.connect(self.on_layout_style_combo_boxChanged)
self.layout_style_combo_box.activated.connect(self.on_layout_style_combo_box_changed)
self.bible_second_check_box.stateChanged.connect(self.on_bible_second_check_box)
self.verse_separator_check_box.clicked.connect(self.on_verse_separator_check_box_clicked)
self.verse_separator_line_edit.textEdited.connect(self.on_verse_separator_line_edit_edited)
@ -170,15 +170,15 @@ class BiblesTab(SettingsTab):
self.layout_style_combo_box.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
self.layout_style_combo_box.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
self.display_style_combo_box.setItemText(DisplayStyle.NoBrackets,
translate('BiblesPlugin.BiblesTab', 'No Brackets'))
translate('BiblesPlugin.BiblesTab', 'No Brackets'))
self.display_style_combo_box.setItemText(DisplayStyle.Round,
translate('BiblesPlugin.BiblesTab', '( And )'))
translate('BiblesPlugin.BiblesTab', '( And )'))
self.display_style_combo_box.setItemText(DisplayStyle.Curly,
translate('BiblesPlugin.BiblesTab', '{ And }'))
translate('BiblesPlugin.BiblesTab', '{ And }'))
self.display_style_combo_box.setItemText(DisplayStyle.Square,
translate('BiblesPlugin.BiblesTab', '[ And ]'))
translate('BiblesPlugin.BiblesTab', '[ And ]'))
self.change_note_label.setText(translate('BiblesPlugin.BiblesTab',
'Note:\nChanges do not affect verses already in the service.'))
'Note:\nChanges do not affect verses already in the service.'))
self.bible_second_check_box.setText(translate('BiblesPlugin.BiblesTab', 'Display second Bible verses'))
self.scripture_reference_group_box.setTitle(translate('BiblesPlugin.BiblesTab', 'Custom Scripture References'))
self.verse_separator_check_box.setText(translate('BiblesPlugin.BiblesTab', 'Verse Separator:'))
@ -186,21 +186,21 @@ class BiblesTab(SettingsTab):
self.list_separator_check_box.setText(translate('BiblesPlugin.BiblesTab', 'List Separator:'))
self.end_separator_check_box.setText(translate('BiblesPlugin.BiblesTab', 'End Mark:'))
tip_text = translate('BiblesPlugin.BiblesTab',
'Multiple alternative verse separators may be defined.\nThey have to be separated by a vertical bar "|".'
'\nPlease clear this edit line to use the default value.')
'Multiple alternative verse separators may be defined.\nThey have to be separated by a '
'vertical bar "|".\nPlease clear this edit line to use the default value.')
self.verse_separator_line_edit.setToolTip(tip_text)
self.range_separator_line_edit.setToolTip(tip_text)
self.list_separator_line_edit.setToolTip(tip_text)
self.end_separator_line_edit.setToolTip(tip_text)
self.language_selection_group_box.setTitle(translate('BiblesPlugin.BiblesTab', 'Default Bible Language'))
self.language_selection_label.setText(translate('BiblesPlugin.BiblesTab',
'Book name language in search field,\nsearch results and on display:'))
self.language_selection_combo_box.setItemText(LanguageSelection.Bible,
translate('BiblesPlugin.BiblesTab', 'Bible Language'))
self.language_selection_combo_box.setItemText(LanguageSelection.Application,
translate('BiblesPlugin.BiblesTab', 'Application Language'))
self.language_selection_combo_box.setItemText(LanguageSelection.English,
translate('BiblesPlugin.BiblesTab', 'English'))
self.language_selection_label.setText(
translate('BiblesPlugin.BiblesTab', 'Book name language in search field,\nsearch results and on display:'))
self.language_selection_combo_box.setItemText(
LanguageSelection.Bible, translate('BiblesPlugin.BiblesTab', 'Bible Language'))
self.language_selection_combo_box.setItemText(
LanguageSelection.Application, translate('BiblesPlugin.BiblesTab', 'Application Language'))
self.language_selection_combo_box.setItemText(
LanguageSelection.English, translate('BiblesPlugin.BiblesTab', 'English'))
def on_bible_theme_combo_box_changed(self):
self.bible_theme = self.bible_theme_combo_box.currentText()
@ -208,7 +208,7 @@ class BiblesTab(SettingsTab):
def on_display_style_combo_box_changed(self):
self.display_style = self.display_style_combo_box.currentIndex()
def on_layout_style_combo_boxChanged(self):
def on_layout_style_combo_box_changed(self):
self.layout_style = self.layout_style_combo_box.currentIndex()
def on_language_selection_combo_box_changed(self):
@ -238,11 +238,11 @@ class BiblesTab(SettingsTab):
self.verse_separator_line_edit.setFocus()
else:
self.verse_separator_line_edit.setText(get_reference_separator('sep_v_default'))
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
def on_verse_separator_line_edit_edited(self, text):
self.verse_separator_check_box.setChecked(True)
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(False))
def on_verse_separator_line_edit_finished(self):
if self.verse_separator_line_edit.isModified():
@ -250,18 +250,18 @@ class BiblesTab(SettingsTab):
if text == get_reference_separator('sep_v_default') or not text.replace('|', ''):
self.verse_separator_check_box.setChecked(False)
self.verse_separator_line_edit.setText(get_reference_separator('sep_v_default'))
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(True))
def on_range_separator_check_box_clicked(self, checked):
if checked:
self.range_separator_line_edit.setFocus()
else:
self.range_separator_line_edit.setText(get_reference_separator('sep_r_default'))
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
def on_range_separator_line_edit_edited(self, text):
self.range_separator_check_box.setChecked(True)
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(False))
def on_range_separator_line_edit_finished(self):
if self.range_separator_line_edit.isModified():
@ -269,18 +269,18 @@ class BiblesTab(SettingsTab):
if text == get_reference_separator('sep_r_default') or not text.replace('|', ''):
self.range_separator_check_box.setChecked(False)
self.range_separator_line_edit.setText(get_reference_separator('sep_r_default'))
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(True))
def on_list_separator_check_box_clicked(self, checked):
if checked:
self.list_separator_line_edit.setFocus()
else:
self.list_separator_line_edit.setText(get_reference_separator('sep_l_default'))
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
def on_list_separator_line_edit_edited(self, text):
self.list_separator_check_box.setChecked(True)
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(False))
def on_list_separator_line_edit_finished(self):
if self.list_separator_line_edit.isModified():
@ -288,18 +288,18 @@ class BiblesTab(SettingsTab):
if text == get_reference_separator('sep_l_default') or not text.replace('|', ''):
self.list_separator_check_box.setChecked(False)
self.list_separator_line_edit.setText(get_reference_separator('sep_l_default'))
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(True))
def on_end_separator_check_box_clicked(self, checked):
if checked:
self.end_separator_line_edit.setFocus()
else:
self.end_separator_line_edit.setText(get_reference_separator('sep_e_default'))
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(not checked))
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(not checked))
def on_end_separator_line_edit_edited(self, text):
self.end_separator_check_box.setChecked(True)
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(False))
def on_end_separator_line_edit_finished(self):
if self.end_separator_line_edit.isModified():
@ -307,7 +307,7 @@ class BiblesTab(SettingsTab):
if text == get_reference_separator('sep_e_default') or not text.replace('|', ''):
self.end_separator_check_box.setChecked(False)
self.end_separator_line_edit.setText(get_reference_separator('sep_e_default'))
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(True))
def load(self):
settings = Settings()
@ -327,38 +327,38 @@ class BiblesTab(SettingsTab):
verse_separator = settings.value('verse separator')
if (verse_separator.strip('|') == '') or (verse_separator == get_reference_separator('sep_v_default')):
self.verse_separator_line_edit.setText(get_reference_separator('sep_v_default'))
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(True))
self.verse_separator_check_box.setChecked(False)
else:
self.verse_separator_line_edit.setText(verse_separator)
self.verse_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.verse_separator_line_edit.setPalette(self.get_grey_text_palette(False))
self.verse_separator_check_box.setChecked(True)
range_separator = settings.value('range separator')
if (range_separator.strip('|') == '') or (range_separator == get_reference_separator('sep_r_default')):
self.range_separator_line_edit.setText(get_reference_separator('sep_r_default'))
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(True))
self.range_separator_check_box.setChecked(False)
else:
self.range_separator_line_edit.setText(range_separator)
self.range_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.range_separator_line_edit.setPalette(self.get_grey_text_palette(False))
self.range_separator_check_box.setChecked(True)
list_separator = settings.value('list separator')
if (list_separator.strip('|') == '') or (list_separator == get_reference_separator('sep_l_default')):
self.list_separator_line_edit.setText(get_reference_separator('sep_l_default'))
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(True))
self.list_separator_check_box.setChecked(False)
else:
self.list_separator_line_edit.setText(list_separator)
self.list_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.list_separator_line_edit.setPalette(self.get_grey_text_palette(False))
self.list_separator_check_box.setChecked(True)
end_separator = settings.value('end separator')
if (end_separator.strip('|') == '') or (end_separator == get_reference_separator('sep_e_default')):
self.end_separator_line_edit.setText(get_reference_separator('sep_e_default'))
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(True))
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(True))
self.end_separator_check_box.setChecked(False)
else:
self.end_separator_line_edit.setText(end_separator)
self.end_separator_line_edit.setPalette(self.getGreyTextPalette(False))
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(False))
self.end_separator_check_box.setChecked(True)
self.language_selection = settings.value('book name language')
self.language_selection_combo_box.setCurrentIndex(self.language_selection)
@ -402,7 +402,7 @@ class BiblesTab(SettingsTab):
"""
Called from ThemeManager when the Themes have changed.
``theme_list``
:param theme_list:
The list of available themes::
[u'Bible Theme', u'Song Theme']
@ -412,7 +412,7 @@ class BiblesTab(SettingsTab):
self.bible_theme_combo_box.addItems(theme_list)
find_and_set_in_combo_box(self.bible_theme_combo_box, self.bible_theme)
def getGreyTextPalette(self, greyed):
def get_grey_text_palette(self, greyed):
"""
Returns a QPalette with greyed out text as used for placeholderText.
"""

View File

@ -80,8 +80,8 @@ class CSVBible(BibleDB):
"""
log.info(self.__class__.__name__)
BibleDB.__init__(self, parent, **kwargs)
self.booksfile = kwargs['booksfile']
self.versesfile = kwargs['versefile']
self.books_file = kwargs['books_file']
self.verses_file = kwargs['versefile']
def do_import(self, bible_name=None):
"""
@ -99,8 +99,8 @@ class CSVBible(BibleDB):
book_list = {}
# Populate the Tables
try:
details = get_file_encoding(self.booksfile)
books_file = open(self.booksfile, 'r')
details = get_file_encoding(self.books_file)
books_file = open(self.books_file, 'r')
if not books_file.read(3) == '\xEF\xBB\xBF':
# no BOM was found
books_file.seek(0)
@ -109,10 +109,10 @@ class CSVBible(BibleDB):
if self.stop_import_flag:
break
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing books... %s') %
str(line[2], details['encoding']))
str(line[2], details['encoding']))
book_ref_id = self.get_book_ref_id_by_name(str(line[2], details['encoding']), 67, language_id)
if not book_ref_id:
log.error('Importing books from "%s" failed' % self.booksfile)
log.error('Importing books from "%s" failed' % self.books_file)
return False
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
self.create_book(str(line[2], details['encoding']), book_ref_id, book_details['testament_id'])
@ -131,8 +131,8 @@ class CSVBible(BibleDB):
verse_file = None
try:
book_ptr = None
details = get_file_encoding(self.versesfile)
verse_file = open(self.versesfile, 'rb')
details = get_file_encoding(self.verses_file)
verse_file = open(self.verses_file, 'rb')
if not verse_file.read(3) == '\xEF\xBB\xBF':
# no BOM was found
verse_file.seek(0)
@ -147,8 +147,9 @@ class CSVBible(BibleDB):
if book_ptr != line_book:
book = self.get_book(line_book)
book_ptr = book.name
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible',
'Importing verses from %s... Importing verses from <book name>...') % book.name)
self.wizard.increment_progress_bar(
translate('BiblesPlugin.CSVBible',
'Importing verses from %s... Importing verses from <book name>...') % book.name)
self.session.commit()
try:
verse_text = str(line[3], details['encoding'])
@ -169,6 +170,7 @@ class CSVBible(BibleDB):
else:
return success
def get_file_encoding(filename):
"""
Utility function to get the file encoding.

View File

@ -48,6 +48,7 @@ log = logging.getLogger(__name__)
RESERVED_CHARACTERS = '\\.^$*+?{}[]()'
class BibleMeta(BaseModel):
"""
Bible Meta Data
@ -79,23 +80,23 @@ def init_schema(url):
session, metadata = init_db(url)
meta_table = Table('metadata', metadata,
Column('key', types.Unicode(255), primary_key=True, index=True),
Column('value', types.Unicode(255)),
Column('key', types.Unicode(255), primary_key=True, index=True),
Column('value', types.Unicode(255)),
)
book_table = Table('book', metadata,
Column('id', types.Integer, primary_key=True),
Column('book_reference_id', types.Integer, index=True),
Column('testament_reference_id', types.Integer),
Column('name', types.Unicode(50), index=True),
Column('id', types.Integer, primary_key=True),
Column('book_reference_id', types.Integer, index=True),
Column('testament_reference_id', types.Integer),
Column('name', types.Unicode(50), index=True),
)
verse_table = Table('verse', metadata,
Column('id', types.Integer, primary_key=True, index=True),
Column('book_id', types.Integer, ForeignKey(
'book.id'), index=True),
Column('chapter', types.Integer, index=True),
Column('verse', types.Integer, index=True),
Column('text', types.UnicodeText, index=True),
Column('id', types.Integer, primary_key=True, index=True),
Column('book_id', types.Integer, ForeignKey(
'book.id'), index=True),
Column('chapter', types.Integer, index=True),
Column('verse', types.Integer, index=True),
Column('text', types.UnicodeText, index=True),
)
try:
@ -105,8 +106,7 @@ def init_schema(url):
try:
class_mapper(Book)
except UnmappedClassError:
mapper(Book, book_table,
properties={'verses': relation(Verse, backref='book')})
mapper(Book, book_table, properties={'verses': relation(Verse, backref='book')})
try:
class_mapper(Verse)
except UnmappedClassError:
@ -118,9 +118,8 @@ def init_schema(url):
class BibleDB(QtCore.QObject, Manager):
"""
This class represents a database-bound Bible. It is used as a base class
for all the custom importers, so that the can implement their own import
methods, but benefit from the database methods in here via inheritance,
This class represents a database-bound Bible. It is used as a base class for all the custom importers, so that
the can implement their own import methods, but benefit from the database methods in here via inheritance,
rather than depending on yet another object.
"""
log.info('BibleDB loaded')
@ -130,14 +129,13 @@ class BibleDB(QtCore.QObject, Manager):
The constructor loads up the database and creates and initialises the
tables if the database doesn't exist.
**Required keyword arguments:**
:param parent:
:param kwargs:
``path``
The path to the bible database file.
``path``
The path to the bible database file.
``name``
The name of the database. This is also used as the file name for
SQLite databases.
``name``
The name of the database. This is also used as the file name for SQLite databases.
"""
log.info('BibleDB loaded')
QtCore.QObject.__init__(self)
@ -179,13 +177,11 @@ class BibleDB(QtCore.QObject, Manager):
def register(self, wizard):
"""
This method basically just initialialises the database. It is called
from the Bible Manager when a Bible is imported. Descendant classes
may want to override this method to supply their own custom
This method basically just initialises the database. It is called from the Bible Manager when a Bible is
imported. Descendant classes may want to override this method to supply their own custom
initialisation as well.
``wizard``
The actual Qt wizard form.
:param wizard: The actual Qt wizard form.
"""
self.wizard = wizard
return self.name
@ -194,14 +190,9 @@ class BibleDB(QtCore.QObject, Manager):
"""
Add a book to the database.
``name``
The name of the book.
``bk_ref_id``
The book_reference_id from bibles_resources.sqlite of the book.
``testament``
*Defaults to 1.* The testament_reference_id from
:param name: The name of the book.
:param bk_ref_id: The book_reference_id from bibles_resources.sqlite of the book.
:param testament: *Defaults to 1.* The testament_reference_id from
bibles_resources.sqlite of the testament this book belongs to.
"""
log.debug('BibleDB.create_book("%s", "%s")', name, bk_ref_id)
@ -213,8 +204,7 @@ class BibleDB(QtCore.QObject, Manager):
"""
Update a book in the database.
``book``
The book object
:param book: The book object
"""
log.debug('BibleDB.update_book("%s")', book.name)
return self.save_object(book)
@ -223,31 +213,24 @@ class BibleDB(QtCore.QObject, Manager):
"""
Delete a book from the database.
``db_book``
The book object.
:param db_book: The book object.
"""
log.debug('BibleDB.delete_book("%s")', db_book.name)
if self.delete_object(Book, db_book.id):
return True
return False
def create_chapter(self, book_id, chapter, textlist):
def create_chapter(self, book_id, chapter, text_list):
"""
Add a chapter and its verses to a book.
``book_id``
The id of the book being appended.
``chapter``
The chapter number.
``textlist``
A dict of the verses to be inserted. The key is the verse number,
and the value is the verse text.
:param book_id: The id of the book being appended.
:param chapter: The chapter number.
:param text_list: A dict of the verses to be inserted. The key is the verse number, and the value is the verse text.
"""
log.debug('BibleDBcreate_chapter("%s", "%s")', book_id, chapter)
# Text list has book and chapter as first two elements of the array.
for verse_number, verse_text in textlist.items():
for verse_number, verse_text in text_list.items():
verse = Verse.populate(
book_id=book_id,
chapter=chapter,
@ -261,17 +244,10 @@ class BibleDB(QtCore.QObject, Manager):
"""
Add a single verse to a chapter.
``book_id``
The id of the book being appended.
``chapter``
The chapter number.
``verse``
The verse number.
``text``
The verse text.
:param book_id: The id of the book being appended.
:param chapter: The chapter number.
:param verse: The verse number.
:param text: The verse text.
"""
if not isinstance(text, str):
details = chardet.detect(text)
@ -289,11 +265,8 @@ class BibleDB(QtCore.QObject, Manager):
"""
Utility method to save or update BibleMeta objects in a Bible database.
``key``
The key for this instance.
``value``
The value for this instance.
:param key: The key for this instance.
:param value: The value for this instance.
"""
if not isinstance(value, str):
value = str(value)
@ -309,8 +282,7 @@ class BibleDB(QtCore.QObject, Manager):
"""
Return a book object from the database.
``book``
The name of the book to return.
:param book: The name of the book to return.
"""
log.debug('BibleDB.get_book("%s")', book)
return self.get_object_filtered(Book, Book.name.like(book + '%'))
@ -327,8 +299,7 @@ class BibleDB(QtCore.QObject, Manager):
"""
Return a book object from the database.
``id``
The reference id of the book to return.
:param id: The reference id of the book to return.
"""
log.debug('BibleDB.get_book_by_book_ref_id("%s")', id)
return self.get_object_filtered(Book, Book.book_reference_id.like(id))
@ -357,11 +328,8 @@ class BibleDB(QtCore.QObject, Manager):
"""
Return the id of a named book.
``book``
The name of the book, according to the selected language.
``language_selection``
The language selection the user has chosen in the settings section of the Bible.
:param book: The name of the book, according to the selected language.
:param language_selection: The language selection the user has chosen in the settings section of the Bible.
"""
log.debug('get_book_ref_id_by_localised_name("%s", "%s")', book, language_selection)
from openlp.plugins.bibles.lib import LanguageSelection, BibleStrings
@ -398,8 +366,7 @@ class BibleDB(QtCore.QObject, Manager):
This is probably the most used function. It retrieves the list of
verses based on the user's query.
``reference_list``
This is the list of references the media manager item wants. It is
:param reference_list: This is the list of references the media manager item wants. It is
a list of tuples, with the following format::
(book_reference_id, chapter, start_verse, end_verse)
@ -410,6 +377,7 @@ class BibleDB(QtCore.QObject, Manager):
list of ``Verse`` objects. For example::
[(u'35', 1, 1, 1), (u'35', 2, 2, 3)]
:param show_error:
"""
log.debug('BibleDB.get_verses("%s")', reference_list)
verse_list = []
@ -436,14 +404,14 @@ class BibleDB(QtCore.QObject, Manager):
critical_error_message_box(
translate('BiblesPlugin', 'No Book Found'),
translate('BiblesPlugin', 'No matching book '
'could be found in this Bible. Check that you have spelled the name of the book correctly.'))
'could be found in this Bible. Check that you have spelled the name of the book correctly.'))
return verse_list
def verse_search(self, text):
"""
Search for verses containing text ``text``.
``text``
:param text:
The text to search for. If the text contains commas, it will be
split apart and OR'd on the list of values. If the text just
contains spaces, it will split apart and AND'd on the list of
@ -452,13 +420,11 @@ class BibleDB(QtCore.QObject, Manager):
log.debug('BibleDB.verse_search("%s")', text)
verses = self.session.query(Verse)
if text.find(',') > -1:
keywords = \
['%%%s%%' % keyword.strip() for keyword in text.split(',')]
keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(',')]
or_clause = [Verse.text.like(keyword) for keyword in keywords]
verses = verses.filter(or_(*or_clause))
else:
keywords = \
['%%%s%%' % keyword.strip() for keyword in text.split(' ')]
keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(' ')]
for keyword in keywords:
verses = verses.filter(Verse.text.like(keyword))
verses = verses.all()
@ -468,8 +434,7 @@ class BibleDB(QtCore.QObject, Manager):
"""
Return the number of chapters in a book.
``book``
The book object to get the chapter count for.
:param book: The book object to get the chapter count for.
"""
log.debug('BibleDB.get_chapter_count("%s")', book.name)
count = self.session.query(func.max(Verse.chapter)).join(Book).filter(
@ -482,11 +447,8 @@ class BibleDB(QtCore.QObject, Manager):
"""
Return the number of verses in a chapter.
``book_ref_id``
The book reference id.
``chapter``
The chapter to get the verse count for.
:param book_ref_id: The book reference id.
:param chapter: The chapter to get the verse count for.
"""
log.debug('BibleDB.get_verse_count("%s", "%s")', book_ref_id, chapter)
count = self.session.query(func.max(Verse.verse)).join(Book) \
@ -499,12 +461,10 @@ class BibleDB(QtCore.QObject, Manager):
def get_language(self, bible_name=None):
"""
If no language is given it calls a dialog window where the user could
select the bible language.
If no language is given it calls a dialog window where the user could select the bible language.
Return the language id of a bible.
``book``
The language the bible is.
:param bible_name: The language the bible is.
"""
log.debug('BibleDB.get_language()')
from openlp.plugins.bibles.forms import LanguageForm
@ -521,8 +481,7 @@ class BibleDB(QtCore.QObject, Manager):
def is_old_database(self):
"""
Returns ``True`` if it is a bible database, which has been created
prior to 1.9.6.
Returns ``True`` if it is a bible database, which has been created prior to 1.9.6.
"""
try:
self.session.query(Book).all()
@ -576,9 +535,9 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
Return the cursor object. Instantiate one if it doesn't exist yet.
"""
if BiblesResourcesDB.cursor is None:
filepath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
'bibles', 'resources', 'bibles_resources.sqlite')
conn = sqlite3.connect(filepath)
file_path = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
'bibles', 'resources', 'bibles_resources.sqlite')
conn = sqlite3.connect(file_path)
BiblesResourcesDB.cursor = conn.cursor()
return BiblesResourcesDB.cursor
@ -603,8 +562,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
Return a list of all the books of the Bible.
"""
log.debug('BiblesResourcesDB.get_books()')
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
'abbreviation, chapters FROM book_reference ORDER BY id')
books = BiblesResourcesDB.run_sql(
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference ORDER BY id')
return [{
'id': book[0],
'testament_id': book[1],
@ -618,24 +577,20 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return a book by name or abbreviation.
``name``
The name or abbreviation of the book.
``lower``
True if the comparsion should be only lowercase
:param name: The name or abbreviation of the book.
:param lower: True if the comparsion should be only lowercase
"""
log.debug('BiblesResourcesDB.get_book("%s")', name)
if not isinstance(name, str):
name = str(name)
if lower:
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
'abbreviation, chapters FROM book_reference WHERE '
'LOWER(name) = ? OR LOWER(abbreviation) = ?',
(name.lower(), name.lower()))
books = BiblesResourcesDB.run_sql(
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE '
'LOWER(name) = ? OR LOWER(abbreviation) = ?', (name.lower(), name.lower()))
else:
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
'abbreviation, chapters FROM book_reference WHERE name = ?'
' OR abbreviation = ?', (name, name))
books = BiblesResourcesDB.run_sql(
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE name = ?'
' OR abbreviation = ?', (name, name))
if books:
return {
'id': books[0][0],
@ -652,16 +607,15 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return the books which include string.
``string``
The string to search for in the book names or abbreviations.
:param string: The string to search for in the book names or abbreviations.
"""
log.debug('BiblesResourcesDB.get_book_like("%s")', string)
if not isinstance(string, str):
name = str(string)
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
'abbreviation, chapters FROM book_reference WHERE '
'LOWER(name) LIKE ? OR LOWER(abbreviation) LIKE ?',
('%' + string.lower() + '%', '%' + string.lower() + '%'))
books = BiblesResourcesDB.run_sql(
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE '
'LOWER(name) LIKE ? OR LOWER(abbreviation) LIKE ?',
('%' + string.lower() + '%', '%' + string.lower() + '%'))
if books:
return [{
'id': book[0],
@ -678,14 +632,13 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return a book by id.
``id``
The id of the book.
:param id: The id of the book.
"""
log.debug('BiblesResourcesDB.get_book_by_id("%s")', id)
if not isinstance(id, int):
id = int(id)
books = BiblesResourcesDB.run_sql('SELECT id, testament_id, name, '
'abbreviation, chapters FROM book_reference WHERE id = ?', (id, ))
books = BiblesResourcesDB.run_sql(
'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE id = ?', (id, ))
if books:
return {
'id': books[0][0],
@ -702,16 +655,14 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return the chapter details for a specific chapter of a book.
``book_ref_id``
The id of a book.
``chapter``
The chapter number.
:param book_ref_id: The id of a book.
:param chapter: The chapter number.
"""
log.debug('BiblesResourcesDB.get_chapter("%s", "%s")', book_ref_id, chapter)
if not isinstance(chapter, int):
chapter = int(chapter)
chapters = BiblesResourcesDB.run_sql('SELECT id, book_reference_id, '
chapters = BiblesResourcesDB.run_sql(
'SELECT id, book_reference_id, '
'chapter, verse_count FROM chapters WHERE book_reference_id = ?', (book_ref_id,))
try:
return {
@ -728,8 +679,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return the number of chapters in a book.
``book_ref_id``
The id of the book.
:param book_ref_id: The id of the book.
"""
log.debug('BiblesResourcesDB.get_chapter_count("%s")', book_ref_id)
details = BiblesResourcesDB.get_book_by_id(book_ref_id)
@ -742,11 +692,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return the number of verses in a chapter.
``book``
The id of the book.
``chapter``
The number of the chapter.
:param book_ref_id: The id of the book.
:param chapter: The number of the chapter.
"""
log.debug('BiblesResourcesDB.get_verse_count("%s", "%s")', book_ref_id, chapter)
details = BiblesResourcesDB.get_chapter(book_ref_id, chapter)
@ -759,15 +706,14 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return a download_source_id by source.
``name``
The name or abbreviation of the book.
:param source: The name or abbreviation of the book.
"""
log.debug('BiblesResourcesDB.get_download_source("%s")', source)
if not isinstance(source, str):
source = str(source)
source = source.title()
dl_source = BiblesResourcesDB.run_sql('SELECT id, source FROM '
'download_source WHERE source = ?', (source.lower(),))
dl_source = BiblesResourcesDB.run_sql(
'SELECT id, source FROM download_source WHERE source = ?', (source.lower(),))
if dl_source:
return {
'id': dl_source[0][0],
@ -781,8 +727,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return the bibles a webbible provide for download.
``source``
The source of the webbible.
:param source: The source of the webbible.
"""
log.debug('BiblesResourcesDB.get_webbibles("%s")', source)
if not isinstance(source, str):
@ -806,11 +751,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return the bibles a webbible provide for download.
``abbreviation``
The abbreviation of the webbible.
``source``
The source of the webbible.
:param abbreviation: The abbreviation of the webbible.
:param source: The source of the webbible.
"""
log.debug('BiblesResourcesDB.get_webbibles("%s", "%s")', abbreviation, source)
if not isinstance(abbreviation, str):
@ -818,8 +760,8 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
if not isinstance(source, str):
source = str(source)
source = BiblesResourcesDB.get_download_source(source)
bible = BiblesResourcesDB.run_sql('SELECT id, name, abbreviation, '
'language_id, download_source_id FROM webbibles WHERE '
bible = BiblesResourcesDB.run_sql(
'SELECT id, name, abbreviation, language_id, download_source_id FROM webbibles WHERE '
'download_source_id = ? AND abbreviation = ?', (source['id'], abbreviation))
try:
return {
@ -837,16 +779,14 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
"""
Return a book_reference_id if the name matches.
``name``
The name to search the id.
``language_id``
The language_id for which language should be searched
:param name: The name to search the id.
:param language_id: The language_id for which language should be searched
"""
log.debug('BiblesResourcesDB.get_alternative_book_name("%s", "%s")', name, language_id)
if language_id:
books = BiblesResourcesDB.run_sql('SELECT book_reference_id, name '
'FROM alternative_book_names WHERE language_id = ? ORDER BY id', (language_id, ))
books = BiblesResourcesDB.run_sql(
'SELECT book_reference_id, name FROM alternative_book_names WHERE language_id = ? ORDER BY id',
(language_id, ))
else:
books = BiblesResourcesDB.run_sql('SELECT book_reference_id, name FROM alternative_book_names ORDER BY id')
for book in books:
@ -857,17 +797,15 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
@staticmethod
def get_language(name):
"""
Return a dict containing the language id, name and code by name or
abbreviation.
Return a dict containing the language id, name and code by name or abbreviation.
``name``
The name or abbreviation of the language.
:param name: The name or abbreviation of the language.
"""
log.debug('BiblesResourcesDB.get_language("%s")', name)
if not isinstance(name, str):
name = str(name)
language = BiblesResourcesDB.run_sql('SELECT id, name, code FROM '
'language WHERE name = ? OR code = ?', (name, name.lower()))
language = BiblesResourcesDB.run_sql(
'SELECT id, name, code FROM language WHERE name = ? OR code = ?', (name, name.lower()))
if language:
return {
'id': language[0][0],
@ -920,22 +858,20 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
def get_cursor():
"""
Return the cursor object. Instantiate one if it doesn't exist yet.
If necessary loads up the database and creates the tables if the
database doesn't exist.
If necessary loads up the database and creates the tables if the database doesn't exist.
"""
if AlternativeBookNamesDB.cursor is None:
filepath = os.path.join(
file_path = os.path.join(
AppLocation.get_directory(AppLocation.DataDir), 'bibles', 'alternative_book_names.sqlite')
if not os.path.exists(filepath):
if not os.path.exists(file_path):
#create new DB, create table alternative_book_names
AlternativeBookNamesDB.conn = sqlite3.connect(filepath)
AlternativeBookNamesDB.conn.execute('CREATE TABLE '
'alternative_book_names(id INTEGER NOT NULL, '
'book_reference_id INTEGER, language_id INTEGER, name '
'VARCHAR(50), PRIMARY KEY (id))')
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
AlternativeBookNamesDB.conn.execute(
'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, '
'book_reference_id INTEGER, language_id INTEGER, name VARCHAR(50), PRIMARY KEY (id))')
else:
#use existing DB
AlternativeBookNamesDB.conn = sqlite3.connect(filepath)
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor()
return AlternativeBookNamesDB.cursor
@ -944,14 +880,9 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
"""
Run an SQL query on the database, returning the results.
``query``
The actual SQL query to run.
``parameters``
Any variable parameters to add to the query
``commit``
If a commit statement is necessary this should be True.
:param query: The actual SQL query to run.
:param parameters: Any variable parameters to add to the query
:param commit: If a commit statement is necessary this should be True.
"""
cursor = AlternativeBookNamesDB.get_cursor()
cursor.execute(query, parameters)
@ -964,19 +895,16 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
"""
Return a book_reference_id if the name matches.
``name``
The name to search the id.
``language_id``
The language_id for which language should be searched
:param name: The name to search the id.
:param language_id: The language_id for which language should be searched
"""
log.debug('AlternativeBookNamesDB.get_book_reference_id("%s", "%s")', name, language_id)
if language_id:
books = AlternativeBookNamesDB.run_sql('SELECT book_reference_id, '
'name FROM alternative_book_names WHERE language_id = ?', (language_id, ))
books = AlternativeBookNamesDB.run_sql(
'SELECT book_reference_id, name FROM alternative_book_names WHERE language_id = ?', (language_id, ))
else:
books = AlternativeBookNamesDB.run_sql('SELECT book_reference_id, '
'name FROM alternative_book_names')
books = AlternativeBookNamesDB.run_sql(
'SELECT book_reference_id, name FROM alternative_book_names')
for book in books:
if book[1].lower() == name.lower():
return book[0]
@ -987,19 +915,14 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
"""
Add an alternative book name to the database.
``name``
The name of the alternative book name.
``book_reference_id``
The book_reference_id of the book.
``language_id``
The language to which the alternative book name belong.
:param name: The name of the alternative book name.
:param book_reference_id: The book_reference_id of the book.
:param language_id: The language to which the alternative book name belong.
"""
log.debug('AlternativeBookNamesDB.create_alternative_book_name("%s", '
'"%s", "%s"', name, book_reference_id, language_id)
return AlternativeBookNamesDB.run_sql('INSERT INTO '
'alternative_book_names(book_reference_id, language_id, name) '
log.debug('AlternativeBookNamesDB.create_alternative_book_name("%s", "%s", "%s")',
name, book_reference_id, language_id)
return AlternativeBookNamesDB.run_sql(
'INSERT INTO alternative_book_names(book_reference_id, language_id, name) '
'VALUES (?, ?, ?)', (book_reference_id, language_id, name), True)
@ -1012,8 +935,7 @@ class OldBibleDB(QtCore.QObject, Manager):
def __init__(self, parent, **kwargs):
"""
The constructor loads up the database and creates and initialises the
tables if the database doesn't exist.
The constructor loads up the database and creates and initialises the tables if the database doesn't exist.
**Required keyword arguments:**
@ -1021,8 +943,7 @@ class OldBibleDB(QtCore.QObject, Manager):
The path to the bible database file.
``name``
The name of the database. This is also used as the file name for
SQLite databases.
The name of the database. This is also used as the file name for SQLite databases.
"""
log.info('OldBibleDB loaded')
QtCore.QObject.__init__(self)
@ -1040,8 +961,8 @@ class OldBibleDB(QtCore.QObject, Manager):
Return the cursor object. Instantiate one if it doesn't exist yet.
"""
if self.cursor is None:
filepath = os.path.join(self.path, self.file)
self.connection = sqlite3.connect(filepath)
file_path = os.path.join(self.path, self.file)
self.connection = sqlite3.connect(file_path)
self.cursor = self.connection.cursor()
return self.cursor
@ -1049,11 +970,8 @@ class OldBibleDB(QtCore.QObject, Manager):
"""
Run an SQL query on the database, returning the results.
``query``
The actual SQL query to run.
``parameters``
Any variable parameters to add to the query.
:param query: The actual SQL query to run.
:param parameters: Any variable parameters to add to the query.
"""
cursor = self.get_cursor()
cursor.execute(query, parameters)
@ -1092,9 +1010,9 @@ class OldBibleDB(QtCore.QObject, Manager):
"""
if not isinstance(name, str):
name = str(name)
books = self.run_sql('SELECT id, testament_id, name, '
'abbreviation FROM book WHERE LOWER(name) = ? OR '
'LOWER(abbreviation) = ?', (name.lower(), name.lower()))
books = self.run_sql(
'SELECT id, testament_id, name, abbreviation FROM book WHERE LOWER(name) = ? OR '
'LOWER(abbreviation) = ?', (name.lower(), name.lower()))
if books:
return {
'id': books[0][0],
@ -1122,8 +1040,8 @@ class OldBibleDB(QtCore.QObject, Manager):
"""
Returns the verses of the Bible.
"""
verses = self.run_sql('SELECT book_id, chapter, verse, text FROM '
'verse WHERE book_id = ? ORDER BY id', (book_id, ))
verses = self.run_sql(
'SELECT book_id, chapter, verse, text FROM verse WHERE book_id = ? ORDER BY id', (book_id, ))
if verses:
return [{
'book_id': int(verse[0]),

View File

@ -74,14 +74,9 @@ class BGExtract(object):
"""
Remove a particular element from the BeautifulSoup tree.
``parent``
The element from which items need to be removed.
``tag``
A string of the tab type, e.g. "div"
``class_``
An HTML class attribute for further qualification.
:param parent: The element from which items need to be removed.
:param tag: A string of the tab type, e.g. "div"
:param class_: An HTML class attribute for further qualification.
"""
if class_:
all_tags = parent.find_all(tag, class_)
@ -94,8 +89,7 @@ class BGExtract(object):
"""
Extract a verse (or part of a verse) from a tag.
``tag``
The BeautifulSoup Tag element with the stuff we want.
:param tag: The BeautifulSoup Tag element with the stuff we want.
"""
if isinstance(tag, NavigableString):
return None, str(tag)
@ -122,8 +116,7 @@ class BGExtract(object):
"""
Remove all the rubbish from the HTML page.
``tag``
The base tag within which we want to remove stuff.
:param tag: The base tag within which we want to remove stuff.
"""
self._remove_elements(tag, 'sup', 'crossreference')
self._remove_elements(tag, 'sup', 'footnote')
@ -137,8 +130,7 @@ class BGExtract(object):
"""
Extract all the verses from a pre-prepared list of HTML tags.
``tags``
A list of BeautifulSoup Tag elements.
:param tags: A list of BeautifulSoup Tag elements.
"""
verses = []
tags = tags[::-1]
@ -184,8 +176,7 @@ class BGExtract(object):
Use the old style of parsing for those Bibles on BG who mysteriously have not been migrated to the new (still
broken) HTML.
``div``
The parent div.
:param div: The parent div.
"""
verse_list = {}
# Cater for inconsistent mark up in the first verse of a chapter.
@ -225,14 +216,9 @@ class BGExtract(object):
"""
Access and decode Bibles via the BibleGateway website.
``version``
The version of the Bible like 31 for New International version.
``book_name``
Name of the Book.
``chapter``
Chapter number.
:param version: The version of the Bible like 31 for New International version.
:param book_name: Name of the Book.
:param chapter: Chapter number.
"""
log.debug('BGExtract.get_bible_chapter("%s", "%s", "%s")', version, book_name, chapter)
url_book_name = urllib.parse.quote(book_name.encode("utf-8"))
@ -259,10 +245,9 @@ class BGExtract(object):
def get_books_from_http(self, version):
"""
Load a list of all books a Bible contaions from BibleGateway website.
Load a list of all books a Bible contains from BibleGateway website.
``version``
The version of the Bible like NIV for New International Version
:param version: The version of the Bible like NIV for New International Version
"""
log.debug('BGExtract.get_books_from_http("%s")', version)
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '%s' % version})
@ -328,14 +313,9 @@ class BSExtract(object):
"""
Access and decode bibles via Bibleserver mobile website
``version``
The version of the bible like NIV for New International Version
``book_name``
Text name of bible book e.g. Genesis, 1. John, 1John or Offenbarung
``chapter``
Chapter number
:param version: The version of the bible like NIV for New International Version
:param book_name: Text name of bible book e.g. Genesis, 1. John, 1John or Offenbarung
:param chapter: Chapter number
"""
log.debug('BSExtract.get_bible_chapter("%s", "%s", "%s")', version, book_name, chapter)
url_version = urllib.parse.quote(version.encode("utf-8"))
@ -363,12 +343,11 @@ class BSExtract(object):
"""
Load a list of all books a Bible contains from Bibleserver mobile website.
``version``
The version of the Bible like NIV for New International Version
:param version: The version of the Bible like NIV for New International Version
"""
log.debug('BSExtract.get_books_from_http("%s")', version)
url_version = urllib.parse.quote(version.encode("utf-8"))
chapter_url = 'http://m.bibleserver.com/overlay/selectBook?translation=%s' % (url_version)
chapter_url = 'http://m.bibleserver.com/overlay/selectBook?translation=%s' % url_version
soup = get_soup_for_bible_ref(chapter_url)
if not soup:
return None
@ -408,14 +387,9 @@ class CWExtract(object):
"""
Access and decode bibles via the Crosswalk website
``version``
The version of the Bible like niv for New International Version
``book_name``
Text name of in english e.g. 'gen' for Genesis
``chapter``
Chapter number
:param version: The version of the Bible like niv for New International Version
:param book_name: Text name of in english e.g. 'gen' for Genesis
:param chapter: Chapter number
"""
log.debug('CWExtract.get_bible_chapter("%s", "%s", "%s")', version, book_name, chapter)
url_book_name = book_name.replace(' ', '-')
@ -463,11 +437,10 @@ class CWExtract(object):
"""
Load a list of all books a Bible contain from the Crosswalk website.
``version``
The version of the bible like NIV for New International Version
:param version: The version of the bible like NIV for New International Version
"""
log.debug('CWExtract.get_books_from_http("%s")', version)
chapter_url = 'http://www.biblestudytools.com/%s/' % (version)
chapter_url = 'http://www.biblestudytools.com/%s/' % version
soup = get_soup_for_bible_ref(chapter_url)
if not soup:
return None
@ -533,7 +506,8 @@ class HTTPBible(BibleDB):
failure.
"""
self.wizard.progress_bar.setMaximum(68)
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Bible and loading books...'))
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible',
'Registering Bible and loading books...'))
self.save_meta('download_source', self.download_source)
self.save_meta('download_name', self.download_name)
if self.proxy_server:
@ -552,8 +526,8 @@ class HTTPBible(BibleDB):
handler = BSExtract(self.proxy_server)
books = handler.get_books_from_http(self.download_name)
if not books:
log.error('Importing books from %s - download name: "%s" '\
'failed' % (self.download_source, self.download_name))
log.error('Importing books from %s - download name: "%s" failed' %
(self.download_source, self.download_name))
return False
self.wizard.progress_bar.setMaximum(len(books) + 2)
self.wizard.increment_progress_bar(translate( 'BiblesPlugin.HTTPBible', 'Registering Language...'))
@ -573,12 +547,12 @@ class HTTPBible(BibleDB):
'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing <book name>...') % book)
book_ref_id = self.get_book_ref_id_by_name(book, len(books), language_id)
if not book_ref_id:
log.error('Importing books from %s - download name: "%s" '\
'failed' % (self.download_source, self.download_name))
log.error('Importing books from %s - download name: "%s" failed' %
(self.download_source, self.download_name))
return False
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
log.debug('Book details: Name:%s; id:%s; testament_id:%s',
book, book_ref_id, book_details['testament_id'])
book, book_ref_id, book_details['testament_id'])
self.create_book(book, book_ref_id, book_details['testament_id'])
if self.stop_import_flag:
return False
@ -612,7 +586,7 @@ class HTTPBible(BibleDB):
critical_error_message_box(
translate('BiblesPlugin', 'No Book Found'),
translate('BiblesPlugin', 'No matching book could be found in this Bible. Check that you have '
'spelled the name of the book correctly.'))
'spelled the name of the book correctly.'))
return []
book = db_book.name
if BibleDB.get_verse_count(self, book_id, reference[1]) == 0:
@ -658,8 +632,7 @@ class HTTPBible(BibleDB):
"""
Return the number of chapters in a particular book.
``book``
The book object to get the chapter count for.
:param book: The book object to get the chapter count for.
"""
log.debug('HTTPBible.get_chapter_count("%s")', book.name)
return BiblesResourcesDB.get_chapter_count(book.book_reference_id)
@ -668,11 +641,8 @@ class HTTPBible(BibleDB):
"""
Return the number of verses for the specified chapter and book.
``book``
The name of the book.
``chapter``
The chapter whose verses are being counted.
:param book_id: The name of the book.
:param chapter: The chapter whose verses are being counted.
"""
log.debug('HTTPBible.get_verse_count("%s", %s)', book_id, chapter)
return BiblesResourcesDB.get_verse_count(book_id, chapter)
@ -696,18 +666,11 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
"""
Gets a webpage and returns a parsed and optionally cleaned soup or None.
``reference_url``
The URL to obtain the soup from.
``header``
An optional HTTP header to pass to the bible web server.
``pre_parse_regex``
A regular expression to run on the webpage. Allows manipulation of the webpage before passing to BeautifulSoup
for parsing.
``pre_parse_substitute``
The text to replace any matches to the regular expression with.
:param reference_url: The URL to obtain the soup from.
:param header: An optional HTTP header to pass to the bible web server.
:param pre_parse_regex: A regular expression to run on the webpage. Allows manipulation of the webpage before
passing to BeautifulSoup for parsing.
:param pre_parse_substitute: The text to replace any matches to the regular expression with.
"""
if not reference_url:
return None
@ -742,9 +705,10 @@ def send_error_message(error_type):
critical_error_message_box(
translate('BiblesPlugin.HTTPBible', 'Download Error'),
translate('BiblesPlugin.HTTPBible', 'There was a problem downloading your verse selection. Please check '
'your Internet connection, and if this error continues to occur please consider reporting a bug.'))
'your Internet connection, and if this error continues to occur please consider reporting a bug'
'.'))
elif error_type == 'parse':
critical_error_message_box(
translate('BiblesPlugin.HTTPBible', 'Parse Error'),
translate('BiblesPlugin.HTTPBible', 'There was a problem extracting your verse selection. If this error '
'continues to occur please consider reporting a bug.'))
'continues to occur please consider reporting a bug.'))

View File

@ -40,7 +40,6 @@ from .opensong import OpenSongBible
from .osis import OSISBible
log = logging.getLogger(__name__)
@ -57,10 +56,9 @@ class BibleFormat(object):
@staticmethod
def get_class(format):
"""
Return the appropriate imeplementation class.
Return the appropriate implementation class.
``format``
The Bible format.
:param format: The Bible format.
"""
if format == BibleFormat.OSIS:
return OSISBible
@ -94,9 +92,8 @@ class BibleManager(object):
def __init__(self, parent):
"""
Finds all the bibles defined for the system and creates an interface
object for each bible containing connection information. Throws
Exception if no Bibles are found.
Finds all the bibles defined for the system and creates an interface object for each bible containing
connection information. Throws Exception if no Bibles are found.
Init confirms the bible exists and stores the database path.
"""
@ -114,9 +111,8 @@ class BibleManager(object):
def reload_bibles(self):
"""
Reloads the Bibles from the available Bible databases on disk. If a web
Bible is encountered, an instance of HTTPBible is loaded instead of the
BibleDB class.
Reloads the Bibles from the available Bible databases on disk. If a web Bible is encountered, an instance
of HTTPBible is loaded instead of the BibleDB class.
"""
log.debug('Reload bibles')
files = AppLocation.get_files(self.settings_section, self.suffix)
@ -146,7 +142,7 @@ class BibleManager(object):
download_name = self.db_cache[name].get_object(BibleMeta, 'download_name').value
meta_proxy = self.db_cache[name].get_object(BibleMeta, 'proxy_server')
web_bible = HTTPBible(self.parent, path=self.path, file=filename, download_source=source.value,
download_name=download_name)
download_name=download_name)
if meta_proxy:
web_bible.proxy_server = meta_proxy.value
self.db_cache[name] = web_bible
@ -156,8 +152,7 @@ class BibleManager(object):
"""
Sets the reference to the dialog with the progress bar on it.
``dialog``
The reference to the import wizard.
:param wizard: The reference to the import wizard.
"""
self.import_wizard = wizard
@ -165,11 +160,8 @@ class BibleManager(object):
"""
Register a bible in the bible cache, and then import the verses.
``type``
What type of Bible, one of the ``BibleFormat`` values.
``**kwargs``
Keyword arguments to send to the actual importer class.
:param type: What type of Bible, one of the ``BibleFormat`` values.
:param kwargs: Keyword arguments to send to the actual importer class.
"""
class_ = BibleFormat.get_class(type)
kwargs['path'] = self.path
@ -182,8 +174,7 @@ class BibleManager(object):
"""
Delete a bible completely.
``name``
The name of the bible.
:param name: The name of the bible.
"""
log.debug('BibleManager.delete_bible("%s")', name)
bible = self.db_cache[name]
@ -202,8 +193,7 @@ class BibleManager(object):
"""
Returns a list of Bible books, and the number of chapters in that book.
``bible``
Unicode. The Bible to get the list of books from.
:param bible: Unicode. The Bible to get the list of books from.
"""
log.debug('BibleManager.get_books("%s")', bible)
return [
@ -219,11 +209,8 @@ class BibleManager(object):
"""
Returns a book object by given id.
``bible``
Unicode. The Bible to get the list of books from.
``id``
Unicode. The book_reference_id to get the book for.
:param bible: Unicode. The Bible to get the list of books from.
:param id: Unicode. The book_reference_id to get the book for.
"""
log.debug('BibleManager.get_book_by_id("%s", "%s")', bible, id)
return self.db_cache[bible].get_book_by_book_ref_id(id)
@ -232,22 +219,17 @@ class BibleManager(object):
"""
Returns the number of Chapters for a given book.
``bible``
Unicode. The Bible to get the list of books from.
``book``
The book object to get the chapter count for.
:param bible: Unicode. The Bible to get the list of books from.
:param book: The book object to get the chapter count for.
"""
log.debug('BibleManager.get_book_chapter_count ("%s", "%s")', bible, book.name)
return self.db_cache[bible].get_chapter_count(book)
def get_verse_count(self, bible, book, chapter):
"""
Returns all the number of verses for a given
book and chapterMaxBibleBookVerses.
Returns all the number of verses for a given book and chapterMaxBibleBookVerses.
"""
log.debug('BibleManager.get_verse_count("%s", "%s", %s)',
bible, book, chapter)
log.debug('BibleManager.get_verse_count("%s", "%s", %s)', bible, book, chapter)
language_selection = self.get_language_selection(bible)
book_ref_id = self.db_cache[bible].get_book_ref_id_by_localised_name(book, language_selection)
return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
@ -260,16 +242,14 @@ class BibleManager(object):
log.debug('BibleManager.get_verse_count_by_book_ref_id("%s", "%s", "%s")', bible, book_ref_id, chapter)
return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
def get_verses(self, bible, versetext, book_ref_id=False, show_error=True):
def get_verses(self, bible, verse_text, book_ref_id=False, show_error=True):
"""
Parses a scripture reference, fetches the verses from the Bible
specified, and returns a list of ``Verse`` objects.
``bible``
Unicode. The Bible to use.
``versetext``
Unicode. The scripture reference. Valid scripture references are:
:param bible: Unicode. The Bible to use.
:param verse_text:
Unicode. The scripture reference. Valid scripture references are:
- Genesis 1
- Genesis 1-2
@ -279,55 +259,53 @@ class BibleManager(object):
- Genesis 1:1-2:10
- Genesis 1:1-10,2:1-10
``book_ref_id``
Unicode. The book referece id from the book in versetext.
:param book_ref_id: Unicode. The book reference id from the book in verse_text.
For second bible this is necessary.
:param show_error:
"""
log.debug('BibleManager.get_verses("%s", "%s")', bible, versetext)
log.debug('BibleManager.get_verses("%s", "%s")', bible, verse_text)
if not bible:
if show_error:
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'No Bibles Available'),
translate('BiblesPlugin.BibleManager',
'There are no Bibles currently installed. Please use the '
'Import Wizard to install one or more Bibles.')
)
translate('BiblesPlugin.BibleManager', 'There are no Bibles currently installed. Please use the '
'Import Wizard to install one or more Bibles.')
)
return None
language_selection = self.get_language_selection(bible)
reflist = parse_reference(versetext, self.db_cache[bible],
language_selection, book_ref_id)
if reflist:
return self.db_cache[bible].get_verses(reflist, show_error)
ref_list = parse_reference(verse_text, self.db_cache[bible], language_selection, book_ref_id)
if ref_list:
return self.db_cache[bible].get_verses(ref_list, show_error)
else:
if show_error:
reference_seperators = {
reference_separators = {
'verse': get_reference_separator('sep_v_display'),
'range': get_reference_separator('sep_r_display'),
'list': get_reference_separator('sep_l_display')}
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
translate('BiblesPlugin.BibleManager', 'Your scripture reference is either not supported by '
'OpenLP or is invalid. Please make sure your reference '
'conforms to one of the following patterns or consult the manual:\n\n'
'Book Chapter\n'
'Book Chapter%(range)sChapter\n'
'Book Chapter%(verse)sVerse%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sVerse'
'%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sChapter'
'%(verse)sVerse%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sChapter%(verse)sVerse',
'Please pay attention to the appended "s" of the wildcards '
'and refrain from translating the words inside the names in the brackets.') % reference_seperators
)
'OpenLP or is invalid. Please make sure your reference '
'conforms to one of the following patterns or consult the manual:\n\n'
'Book Chapter\n'
'Book Chapter%(range)sChapter\n'
'Book Chapter%(verse)sVerse%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sVerse'
'%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sChapter'
'%(verse)sVerse%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sChapter%(verse)sVerse',
'Please pay attention to the appended "s" of the wildcards '
'and refrain from translating the words inside the names in the brackets.')
% reference_separators
)
return None
def get_language_selection(self, bible):
"""
Returns the language selection of a bible.
``bible``
Unicode. The Bible to get the language selection from.
:param bible: Unicode. The Bible to get the language selection from.
"""
log.debug('BibleManager.get_language_selection("%s")', bible)
language_selection = self.get_meta_data(bible, 'book_name_language')
@ -347,34 +325,29 @@ class BibleManager(object):
"""
Does a verse search for the given bible and text.
``bible``
The bible to search in (unicode).
``second_bible``
The second bible (unicode). We do not search in this bible.
``text``
The text to search for (unicode).
:param bible: The bible to search in (unicode).
:param second_bible: The second bible (unicode). We do not search in this bible.
:param text: The text to search for (unicode).
"""
log.debug('BibleManager.verse_search("%s", "%s")', bible, text)
if not bible:
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'No Bibles Available'),
translate('BiblesPlugin.BibleManager',
'There are no Bibles currently installed. Please use the Import Wizard to install one or more'
' Bibles.')
)
'There are no Bibles currently installed. Please use the Import Wizard to install one or '
'more Bibles.')
)
return None
# Check if the bible or second_bible is a web bible.
webbible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
second_webbible = ''
web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
second_web_bible = ''
if second_bible:
second_webbible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
if webbible or second_webbible:
second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
if web_bible or second_web_bible:
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used'),
translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.')
)
)
return None
if text:
return self.db_cache[bible].verse_search(text)
@ -382,23 +355,20 @@ class BibleManager(object):
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
translate('BiblesPlugin.BibleManager', 'You did not enter a search keyword.\nYou can separate '
'different keywords by a space to search for all of your keywords and you can separate '
'them by a comma to search for one of them.')
)
'different keywords by a space to search for all of your keywords and you can separate '
'them by a comma to search for one of them.')
)
return None
def save_meta_data(self, bible, version, copyright, permissions,
book_name_language=None):
def save_meta_data(self, bible, version, copyright, permissions, book_name_language=None):
"""
Saves the bibles meta data.
"""
log.debug('save_meta data %s, %s, %s, %s',
bible, version, copyright, permissions)
log.debug('save_meta data %s, %s, %s, %s', bible, version, copyright, permissions)
self.db_cache[bible].save_meta('name', version)
self.db_cache[bible].save_meta('copyright', copyright)
self.db_cache[bible].save_meta('permissions', permissions)
self.db_cache[bible].save_meta('book_name_language',
book_name_language)
self.db_cache[bible].save_meta('book_name_language', book_name_language)
def get_meta_data(self, bible, key):
"""

View File

@ -86,19 +86,19 @@ class BibleMediaItem(MediaManagerItem):
if not bitem.flags() & QtCore.Qt.ItemIsSelectable:
# The item is the "No Search Results" item.
self.list_view.clear()
self.displayResults(bible, second_bible)
self.display_results(bible, second_bible)
return
else:
item_second_bible = self._decode_qt_object(bitem, 'second_bible')
if item_second_bible and second_bible or not item_second_bible and not second_bible:
self.displayResults(bible, second_bible)
self.display_results(bible, second_bible)
elif critical_error_message_box(
message=translate('BiblesPlugin.MediaItem',
'You cannot combine single and dual Bible verse search results. '
'Do you want to delete your search results and start a new search?'),
parent=self, question=True) == QtGui.QMessageBox.Yes:
'You cannot combine single and dual Bible verse search results. '
'Do you want to delete your search results and start a new search?'),
parent=self, question=True) == QtGui.QMessageBox.Yes:
self.list_view.clear()
self.displayResults(bible, second_bible)
self.display_results(bible, second_bible)
def _decode_qt_object(self, bitem, key):
reference = bitem.data(QtCore.Qt.UserRole)
@ -116,8 +116,8 @@ class BibleMediaItem(MediaManagerItem):
self.has_delete_icon = True
self.add_to_service_item = False
def addSearchTab(self, prefix, name):
self.searchTabBar.addTab(name)
def add_search_tab(self, prefix, name):
self.search_tab_bar.addTab(name)
tab = QtGui.QWidget()
tab.setObjectName(prefix + 'Tab')
tab.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
@ -126,15 +126,12 @@ class BibleMediaItem(MediaManagerItem):
setattr(self, prefix + 'Tab', tab)
setattr(self, prefix + 'Layout', layout)
def addSearchFields(self, prefix, name):
def add_search_fields(self, prefix, name):
"""
Creates and adds generic search tab.
``prefix``
The prefix of the tab, this is either ``quick`` or ``advanced``.
``name``
The translated string to display.
:param prefix: The prefix of the tab, this is either ``quick`` or ``advanced``.
:param name: The translated string to display.
"""
if prefix == 'quick':
idx = 2
@ -142,124 +139,126 @@ class BibleMediaItem(MediaManagerItem):
idx = 5
tab = getattr(self, prefix + 'Tab')
layout = getattr(self, prefix + 'Layout')
versionLabel = QtGui.QLabel(tab)
versionLabel.setObjectName(prefix + 'VersionLabel')
layout.addWidget(versionLabel, idx, 0, QtCore.Qt.AlignRight)
versionComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
versionLabel.setBuddy(versionComboBox)
layout.addWidget(versionComboBox, idx, 1, 1, 2)
secondLabel = QtGui.QLabel(tab)
secondLabel.setObjectName(prefix + 'SecondLabel')
layout.addWidget(secondLabel, idx + 1, 0, QtCore.Qt.AlignRight)
secondComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
versionLabel.setBuddy(secondComboBox)
layout.addWidget(secondComboBox, idx + 1, 1, 1, 2)
styleLabel = QtGui.QLabel(tab)
styleLabel.setObjectName(prefix + 'StyleLabel')
layout.addWidget(styleLabel, idx + 2, 0, QtCore.Qt.AlignRight)
styleComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
styleComboBox.addItems(['', '', ''])
layout.addWidget(styleComboBox, idx + 2, 1, 1, 2)
version_label = QtGui.QLabel(tab)
version_label.setObjectName(prefix + 'VersionLabel')
layout.addWidget(version_label, idx, 0, QtCore.Qt.AlignRight)
version_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
version_label.setBuddy(version_combo_box)
layout.addWidget(version_combo_box, idx, 1, 1, 2)
second_label = QtGui.QLabel(tab)
second_label.setObjectName(prefix + 'SecondLabel')
layout.addWidget(second_label, idx + 1, 0, QtCore.Qt.AlignRight)
second_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
version_label.setBuddy(second_combo_box)
layout.addWidget(second_combo_box, idx + 1, 1, 1, 2)
style_label = QtGui.QLabel(tab)
style_label.setObjectName(prefix + 'StyleLabel')
layout.addWidget(style_label, idx + 2, 0, QtCore.Qt.AlignRight)
style_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
style_combo_box.addItems(['', '', ''])
layout.addWidget(style_combo_box, idx + 2, 1, 1, 2)
search_button_layout = QtGui.QHBoxLayout()
search_button_layout.setObjectName(prefix + 'search_button_layout')
search_button_layout.addStretch()
lockButton = QtGui.QToolButton(tab)
lockButton.setIcon(self.unlock_icon)
lockButton.setCheckable(True)
lockButton.setObjectName(prefix + 'LockButton')
search_button_layout.addWidget(lockButton)
searchButton = QtGui.QPushButton(tab)
searchButton.setObjectName(prefix + 'SearchButton')
search_button_layout.addWidget(searchButton)
lock_button = QtGui.QToolButton(tab)
lock_button.setIcon(self.unlock_icon)
lock_button.setCheckable(True)
lock_button.setObjectName(prefix + 'LockButton')
search_button_layout.addWidget(lock_button)
search_button = QtGui.QPushButton(tab)
search_button.setObjectName(prefix + 'SearchButton')
search_button_layout.addWidget(search_button)
layout.addLayout(search_button_layout, idx + 3, 1, 1, 2)
self.page_layout.addWidget(tab)
tab.setVisible(False)
lockButton.toggled.connect(self.onLockButtonToggled)
setattr(self, prefix + 'VersionLabel', versionLabel)
setattr(self, prefix + 'VersionComboBox', versionComboBox)
setattr(self, prefix + 'SecondLabel', secondLabel)
setattr(self, prefix + 'SecondComboBox', secondComboBox)
setattr(self, prefix + 'StyleLabel', styleLabel)
setattr(self, prefix + 'StyleComboBox', styleComboBox)
setattr(self, prefix + 'LockButton', lockButton)
lock_button.toggled.connect(self.on_lock_button_toggled)
setattr(self, prefix + 'VersionLabel', version_label)
setattr(self, prefix + 'VersionComboBox', version_combo_box)
setattr(self, prefix + 'SecondLabel', second_label)
setattr(self, prefix + 'SecondComboBox', second_combo_box)
setattr(self, prefix + 'StyleLabel', style_label)
setattr(self, prefix + 'StyleComboBox', style_combo_box)
setattr(self, prefix + 'LockButton', lock_button)
setattr(self, prefix + 'SearchButtonLayout', search_button_layout)
setattr(self, prefix + 'SearchButton', searchButton)
setattr(self, prefix + 'SearchButton', search_button)
def add_end_header_bar(self):
self.searchTabBar = QtGui.QTabBar(self)
self.searchTabBar.setExpanding(False)
self.searchTabBar.setObjectName('searchTabBar')
self.page_layout.addWidget(self.searchTabBar)
self.search_tab_bar = QtGui.QTabBar(self)
self.search_tab_bar.setExpanding(False)
self.search_tab_bar.setObjectName('search_tab_bar')
self.page_layout.addWidget(self.search_tab_bar)
# Add the Quick Search tab.
self.addSearchTab('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
self.quickSearchLabel = QtGui.QLabel(self.quickTab)
self.quickSearchLabel.setObjectName('quickSearchLabel')
self.quickLayout.addWidget(self.quickSearchLabel, 0, 0, QtCore.Qt.AlignRight)
self.quickSearchEdit = SearchEdit(self.quickTab)
self.quickSearchEdit.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed)
self.quickSearchEdit.setObjectName('quickSearchEdit')
self.quickSearchLabel.setBuddy(self.quickSearchEdit)
self.quickLayout.addWidget(self.quickSearchEdit, 0, 1, 1, 2)
self.addSearchFields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
self.add_search_tab('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
self.quick_search_label = QtGui.QLabel(self.quickTab)
self.quick_search_label.setObjectName('quick_search_label')
self.quickLayout.addWidget(self.quick_search_label, 0, 0, QtCore.Qt.AlignRight)
self.quick_search_edit = SearchEdit(self.quickTab)
self.quick_search_edit.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed)
self.quick_search_edit.setObjectName('quick_search_edit')
self.quick_search_label.setBuddy(self.quick_search_edit)
self.quickLayout.addWidget(self.quick_search_edit, 0, 1, 1, 2)
self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
self.quickTab.setVisible(True)
# Add the Advanced Search tab.
self.addSearchTab('advanced', UiStrings().Advanced)
self.advancedBookLabel = QtGui.QLabel(self.advancedTab)
self.advancedBookLabel.setObjectName('advancedBookLabel')
self.advancedLayout.addWidget(self.advancedBookLabel, 0, 0, QtCore.Qt.AlignRight)
self.advancedBookComboBox = create_horizontal_adjusting_combo_box(self.advancedTab, 'advancedBookComboBox')
self.advancedBookLabel.setBuddy(self.advancedBookComboBox)
self.advancedLayout.addWidget(self.advancedBookComboBox, 0, 1, 1, 2)
self.advancedChapterLabel = QtGui.QLabel(self.advancedTab)
self.advancedChapterLabel.setObjectName('advancedChapterLabel')
self.advancedLayout.addWidget(self.advancedChapterLabel, 1, 1, 1, 2)
self.advancedVerseLabel = QtGui.QLabel(self.advancedTab)
self.advancedVerseLabel.setObjectName('advancedVerseLabel')
self.advancedLayout.addWidget(self.advancedVerseLabel, 1, 2)
self.advancedFromLabel = QtGui.QLabel(self.advancedTab)
self.advancedFromLabel.setObjectName('advancedFromLabel')
self.advancedLayout.addWidget(self.advancedFromLabel, 3, 0, QtCore.Qt.AlignRight)
self.advancedFromChapter = QtGui.QComboBox(self.advancedTab)
self.advancedFromChapter.setObjectName('advancedFromChapter')
self.advancedLayout.addWidget(self.advancedFromChapter, 3, 1)
self.advancedFromVerse = QtGui.QComboBox(self.advancedTab)
self.advancedFromVerse.setObjectName('advancedFromVerse')
self.advancedLayout.addWidget(self.advancedFromVerse, 3, 2)
self.advancedToLabel = QtGui.QLabel(self.advancedTab)
self.advancedToLabel.setObjectName('advancedToLabel')
self.advancedLayout.addWidget(self.advancedToLabel, 4, 0, QtCore.Qt.AlignRight)
self.advancedToChapter = QtGui.QComboBox(self.advancedTab)
self.advancedToChapter.setObjectName('advancedToChapter')
self.advancedLayout.addWidget(self.advancedToChapter, 4, 1)
self.advancedToVerse = QtGui.QComboBox(self.advancedTab)
self.advancedToVerse.setObjectName('advancedToVerse')
self.advancedLayout.addWidget(self.advancedToVerse, 4, 2)
self.addSearchFields('advanced', UiStrings().Advanced)
self.add_search_tab('advanced', UiStrings().Advanced)
self.advanced_book_label = QtGui.QLabel(self.advancedTab)
self.advanced_book_label.setObjectName('advanced_book_label')
self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
self.advanced_book_combo_box = create_horizontal_adjusting_combo_box(self.advancedTab,
'advanced_book_combo_box')
self.advanced_book_label.setBuddy(self.advanced_book_combo_box)
self.advancedLayout.addWidget(self.advanced_book_combo_box, 0, 1, 1, 2)
self.advanced_chapter_label = QtGui.QLabel(self.advancedTab)
self.advanced_chapter_label.setObjectName('advanced_chapter_label')
self.advancedLayout.addWidget(self.advanced_chapter_label, 1, 1, 1, 2)
self.advanced_verse_label = QtGui.QLabel(self.advancedTab)
self.advanced_verse_label.setObjectName('advanced_verse_label')
self.advancedLayout.addWidget(self.advanced_verse_label, 1, 2)
self.advanced_from_label = QtGui.QLabel(self.advancedTab)
self.advanced_from_label.setObjectName('advanced_from_label')
self.advancedLayout.addWidget(self.advanced_from_label, 3, 0, QtCore.Qt.AlignRight)
self.advanced_from_chapter = QtGui.QComboBox(self.advancedTab)
self.advanced_from_chapter.setObjectName('advanced_from_chapter')
self.advancedLayout.addWidget(self.advanced_from_chapter, 3, 1)
self.advanced_from_verse = QtGui.QComboBox(self.advancedTab)
self.advanced_from_verse.setObjectName('advanced_from_verse')
self.advancedLayout.addWidget(self.advanced_from_verse, 3, 2)
self.advanced_to_label = QtGui.QLabel(self.advancedTab)
self.advanced_to_label.setObjectName('advanced_to_label')
self.advancedLayout.addWidget(self.advanced_to_label, 4, 0, QtCore.Qt.AlignRight)
self.advanced_to_chapter = QtGui.QComboBox(self.advancedTab)
self.advanced_to_chapter.setObjectName('advanced_to_chapter')
self.advancedLayout.addWidget(self.advanced_to_chapter, 4, 1)
self.advanced_to_verse = QtGui.QComboBox(self.advancedTab)
self.advanced_to_verse.setObjectName('advanced_to_verse')
self.advancedLayout.addWidget(self.advanced_to_verse, 4, 2)
self.add_search_fields('advanced', UiStrings().Advanced)
# Combo Boxes
self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
self.quickSecondComboBox.activated.connect(self.updateAutoCompleter)
self.advancedVersionComboBox.activated.connect(self.onAdvancedVersionComboBox)
self.advancedSecondComboBox.activated.connect(self.onAdvancedSecondComboBox)
self.advancedBookComboBox.activated.connect(self.onAdvancedBookComboBox)
self.advancedFromChapter.activated.connect(self.onAdvancedFromChapter)
self.advancedFromVerse.activated.connect(self.onAdvancedFromVerse)
self.advancedToChapter.activated.connect(self.onAdvancedToChapter)
QtCore.QObject.connect(self.quickSearchEdit, QtCore.SIGNAL('searchTypeChanged(int)'), self.updateAutoCompleter)
self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
self.quickStyleComboBox.activated.connect(self.onQuickStyleComboBoxChanged)
self.advancedStyleComboBox.activated.connect(self.onAdvancedStyleComboBoxChanged)
self.quickVersionComboBox.activated.connect(self.update_auto_completer)
self.quickSecondComboBox.activated.connect(self.update_auto_completer)
self.advancedVersionComboBox.activated.connect(self.on_advanced_version_combo_box)
self.advancedSecondComboBox.activated.connect(self.on_advanced_second_combo_box)
self.advanced_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
self.advanced_from_chapter.activated.connect(self.on_advanced_from_chapter)
self.advanced_from_verse.activated.connect(self.on_advanced_from_verse)
self.advanced_to_chapter.activated.connect(self.on_advanced_to_chapter)
QtCore.QObject.connect(self.quick_search_edit, QtCore.SIGNAL('searchTypeChanged(int)'),
self.update_auto_completer)
self.quickVersionComboBox.activated.connect(self.update_auto_completer)
self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed)
self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed)
# Buttons
self.advancedSearchButton.clicked.connect(self.onAdvancedSearchButton)
self.quickSearchButton.clicked.connect(self.onQuickSearchButton)
self.advancedSearchButton.clicked.connect(self.on_advanced_search_button)
self.quickSearchButton.clicked.connect(self.on_quick_search_button)
# Other stuff
self.quickSearchEdit.returnPressed.connect(self.onQuickSearchButton)
self.searchTabBar.currentChanged.connect(self.onSearchTabBarCurrentChanged)
self.quick_search_edit.returnPressed.connect(self.on_quick_search_button)
self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
def on_focus(self):
if self.quickTab.isVisible():
self.quickSearchEdit.setFocus()
self.quick_search_edit.setFocus()
else:
self.advancedBookComboBox.setFocus()
self.advanced_book_combo_box.setFocus()
def config_update(self):
log.debug('config_update')
@ -278,7 +277,7 @@ class BibleMediaItem(MediaManagerItem):
def retranslateUi(self):
log.debug('retranslateUi')
self.quickSearchLabel.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
self.quick_search_label.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
self.quickVersionLabel.setText('%s:' % UiStrings().Version)
self.quickSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
self.quickStyleLabel.setText(UiStrings().LayoutStyle)
@ -286,13 +285,13 @@ class BibleMediaItem(MediaManagerItem):
self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
'Toggle to keep or clear the previous results.'))
'Toggle to keep or clear the previous results.'))
self.quickSearchButton.setText(UiStrings().Search)
self.advancedBookLabel.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
self.advancedChapterLabel.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
self.advancedVerseLabel.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
self.advancedFromLabel.setText(translate('BiblesPlugin.MediaItem', 'From:'))
self.advancedToLabel.setText(translate('BiblesPlugin.MediaItem', 'To:'))
self.advanced_book_label.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
self.advanced_chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
self.advanced_verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
self.advanced_from_label.setText(translate('BiblesPlugin.MediaItem', 'From:'))
self.advanced_to_label.setText(translate('BiblesPlugin.MediaItem', 'To:'))
self.advancedVersionLabel.setText('%s:' % UiStrings().Version)
self.advancedSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
self.advancedStyleLabel.setText(UiStrings().LayoutStyle)
@ -300,14 +299,14 @@ class BibleMediaItem(MediaManagerItem):
self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
'Toggle to keep or clear the previous results.'))
'Toggle to keep or clear the previous results.'))
self.advancedSearchButton.setText(UiStrings().Search)
def initialise(self):
log.debug('bible manager initialise')
self.plugin.manager.media = self
self.loadBibles()
self.quickSearchEdit.set_search_types([
self.load_bibles()
self.quick_search_edit.set_search_types([
(BibleSearch.Reference, ':/bibles/bibles_search_reference.png',
translate('BiblesPlugin.MediaItem', 'Scripture Reference'),
translate('BiblesPlugin.MediaItem', 'Search Scripture Reference...')),
@ -315,11 +314,11 @@ class BibleMediaItem(MediaManagerItem):
translate('BiblesPlugin.MediaItem', 'Text Search'),
translate('BiblesPlugin.MediaItem', 'Search Text...'))
])
self.quickSearchEdit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
self.quick_search_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
self.config_update()
log.debug('bible manager initialise complete')
def loadBibles(self):
def load_bibles(self):
log.debug('Loading Bibles')
self.quickVersionComboBox.clear()
self.quickSecondComboBox.clear()
@ -340,80 +339,73 @@ class BibleMediaItem(MediaManagerItem):
bible = Settings().value(self.settings_section + '/advanced bible')
if bible in bibles:
find_and_set_in_combo_box(self.advancedVersionComboBox, bible)
self.initialiseAdvancedBible(str(bible))
self.initialise_advanced_bible(str(bible))
elif bibles:
self.initialiseAdvancedBible(bibles[0])
self.initialise_advanced_bible(bibles[0])
bible = Settings().value(self.settings_section + '/quick bible')
find_and_set_in_combo_box(self.quickVersionComboBox, bible)
def reload_bibles(self, process=False):
log.debug('Reloading Bibles')
self.plugin.manager.reload_bibles()
self.loadBibles()
self.load_bibles()
# If called from first time wizard re-run, process any new bibles.
if process:
self.plugin.app_startup()
self.updateAutoCompleter()
self.update_auto_completer()
def initialiseAdvancedBible(self, bible, last_book_id=None):
def initialise_advanced_bible(self, bible, last_book_id=None):
"""
This initialises the given bible, which means that its book names and
their chapter numbers is added to the combo boxes on the
'Advanced Search' Tab. This is not of any importance of the
'Quick Search' Tab.
This initialises the given bible, which means that its book names and their chapter numbers is added to the
combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab.
``bible``
The bible to initialise (unicode).
``last_book_id``
The "book reference id" of the book which is choosen at the moment.
:param bible: The bible to initialise (unicode).
:param last_book_id: The "book reference id" of the book which is chosen at the moment.
(int)
"""
log.debug('initialiseAdvancedBible %s, %s', bible, last_book_id)
log.debug('initialise_advanced_bible %s, %s', bible, last_book_id)
book_data = self.plugin.manager.get_books(bible)
secondbible = self.advancedSecondComboBox.currentText()
if secondbible != '':
secondbook_data = self.plugin.manager.get_books(secondbible)
second_bible = self.advancedSecondComboBox.currentText()
if second_bible != '':
second_book_data = self.plugin.manager.get_books(second_bible)
book_data_temp = []
for book in book_data:
for secondbook in secondbook_data:
if book['book_reference_id'] == \
secondbook['book_reference_id']:
for second_book in second_book_data:
if book['book_reference_id'] == second_book['book_reference_id']:
book_data_temp.append(book)
book_data = book_data_temp
self.advancedBookComboBox.clear()
self.advanced_book_combo_box.clear()
first = True
initialise_chapter_verse = False
language_selection = self.plugin.manager.get_language_selection(bible)
book_names = BibleStrings().BookNames
for book in book_data:
row = self.advancedBookComboBox.count()
row = self.advanced_book_combo_box.count()
if language_selection == LanguageSelection.Bible:
self.advancedBookComboBox.addItem(book['name'])
self.advanced_book_combo_box.addItem(book['name'])
elif language_selection == LanguageSelection.Application:
data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
self.advancedBookComboBox.addItem(book_names[data['abbreviation']])
self.advanced_book_combo_box.addItem(book_names[data['abbreviation']])
elif language_selection == LanguageSelection.English:
data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
self.advancedBookComboBox.addItem(data['name'])
self.advancedBookComboBox.setItemData(row, book['book_reference_id'])
self.advanced_book_combo_box.addItem(data['name'])
self.advanced_book_combo_box.setItemData(row, book['book_reference_id'])
if first:
first = False
first_book = book
initialise_chapter_verse = True
if last_book_id and last_book_id == int(book['book_reference_id']):
index = self.advancedBookComboBox.findData(book['book_reference_id'])
index = self.advanced_book_combo_box.findData(book['book_reference_id'])
if index == -1:
# Not Found.
index = 0
self.advancedBookComboBox.setCurrentIndex(index)
self.advanced_book_combo_box.setCurrentIndex(index)
initialise_chapter_verse = False
if initialise_chapter_verse:
self.initialiseChapterVerse(bible, first_book['name'],
first_book['book_reference_id'])
self.initialise_chapter_verse(bible, first_book['name'], first_book['book_reference_id'])
def initialiseChapterVerse(self, bible, book, book_ref_id):
log.debug('initialiseChapterVerse %s, %s, %s', bible, book, book_ref_id)
def initialise_chapter_verse(self, bible, book, book_ref_id):
log.debug('initialise_chapter_verse %s, %s, %s', bible, book, book_ref_id)
book = self.plugin.manager.get_book_by_id(bible, book_ref_id)
self.chapter_count = self.plugin.manager.get_chapter_count(bible, book)
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)
@ -422,36 +414,36 @@ class BibleMediaItem(MediaManagerItem):
critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
else:
self.advancedSearchButton.setEnabled(True)
self.adjustComboBox(1, self.chapter_count, self.advancedFromChapter)
self.adjustComboBox(1, self.chapter_count, self.advancedToChapter)
self.adjustComboBox(1, verse_count, self.advancedFromVerse)
self.adjustComboBox(1, verse_count, self.advancedToVerse)
self.adjust_combo_box(1, self.chapter_count, self.advanced_from_chapter)
self.adjust_combo_box(1, self.chapter_count, self.advanced_to_chapter)
self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
def updateAutoCompleter(self):
def update_auto_completer(self):
"""
This updates the bible book completion list for the search field. The
completion depends on the bible. It is only updated when we are doing a
reference search, otherwise the auto completion list is removed.
"""
log.debug('updateAutoCompleter')
log.debug('update_auto_completer')
# Save the current search type to the configuration.
Settings().setValue('%s/last search type' % self.settings_section, self.quickSearchEdit.current_search_type())
Settings().setValue('%s/last search type' % self.settings_section, self.quick_search_edit.current_search_type())
# Save the current bible to the configuration.
Settings().setValue(self.settings_section + '/quick bible', self.quickVersionComboBox.currentText())
books = []
# We have to do a 'Reference Search'.
if self.quickSearchEdit.current_search_type() == BibleSearch.Reference:
if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
bibles = self.plugin.manager.get_bibles()
bible = self.quickVersionComboBox.currentText()
if bible:
book_data = bibles[bible].get_books()
secondbible = self.quickSecondComboBox.currentText()
if secondbible != '':
secondbook_data = bibles[secondbible].get_books()
second_bible = self.quickSecondComboBox.currentText()
if second_bible != '':
second_book_data = bibles[second_bible].get_books()
book_data_temp = []
for book in book_data:
for secondbook in secondbook_data:
if book.book_reference_id == secondbook.book_reference_id:
for second_book in second_book_data:
if book.book_reference_id == second_book.book_reference_id:
book_data_temp.append(book)
book_data = book_data_temp
language_selection = self.plugin.manager.get_language_selection(bible)
@ -467,7 +459,7 @@ class BibleMediaItem(MediaManagerItem):
data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
books.append(data['name'] + ' ')
books.sort(key=get_locale_key)
set_case_insensitive_completer(books, self.quickSearchEdit)
set_case_insensitive_completer(books, self.quick_search_edit)
def on_import_click(self):
if not hasattr(self, 'import_wizard'):
@ -482,9 +474,9 @@ class BibleMediaItem(MediaManagerItem):
elif self.advancedTab.isVisible():
bible = self.advancedVersionComboBox.currentText()
if bible:
self.editBibleForm = EditBibleForm(self, self.main_window, self.plugin.manager)
self.editBibleForm.loadBible(bible)
if self.editBibleForm.exec_():
self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager)
self.edit_bible_form.loadBible(bible)
if self.edit_bible_form.exec_():
self.reload_bibles()
def on_delete_click(self):
@ -493,117 +485,113 @@ class BibleMediaItem(MediaManagerItem):
elif self.advancedTab.isVisible():
bible = self.advancedVersionComboBox.currentText()
if bible:
if QtGui.QMessageBox.question(self, UiStrings().ConfirmDelete,
translate('BiblesPlugin.MediaItem', 'Are you sure you want to completely delete "%s" Bible from '
'OpenLP?\n\nYou will need to re-import this Bible to use it again.') % bible,
if QtGui.QMessageBox.question(
self, UiStrings().ConfirmDelete,
translate('BiblesPlugin.MediaItem', 'Are you sure you want to completely delete "%s" Bible from '
'OpenLP?\n\nYou will need to re-import this Bible to use it again.') % bible,
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
return
self.plugin.manager.delete_bible(bible)
self.reload_bibles()
def onSearchTabBarCurrentChanged(self, index):
def on_search_tab_bar_current_changed(self, index):
if index == 0:
self.advancedTab.setVisible(False)
self.quickTab.setVisible(True)
self.quickSearchEdit.setFocus()
self.quick_search_edit.setFocus()
else:
self.quickTab.setVisible(False)
self.advancedTab.setVisible(True)
self.advancedBookComboBox.setFocus()
self.advanced_book_combo_box.setFocus()
def onLockButtonToggled(self, checked):
def on_lock_button_toggled(self, checked):
if checked:
self.sender().setIcon(self.lock_icon)
else:
self.sender().setIcon(self.unlock_icon)
def onQuickStyleComboBoxChanged(self):
def on_quick_style_combo_box_changed(self):
self.settings.layout_style = self.quickStyleComboBox.currentIndex()
self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
def onAdvancedStyleComboBoxChanged(self):
def on_advanced_style_combo_box_changed(self):
self.settings.layout_style = self.advancedStyleComboBox.currentIndex()
self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
def onAdvancedVersionComboBox(self):
def on_advanced_version_combo_box(self):
Settings().setValue(self.settings_section + '/advanced bible', self.advancedVersionComboBox.currentText())
self.initialiseAdvancedBible(self.advancedVersionComboBox.currentText(),
self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex())))
def onAdvancedSecondComboBox(self):
self.initialiseAdvancedBible(self.advancedVersionComboBox.currentText(),
self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex())))
def onAdvancedBookComboBox(self):
item = int(self.advancedBookComboBox.currentIndex())
self.initialiseChapterVerse(
self.initialise_advanced_bible(
self.advancedVersionComboBox.currentText(),
self.advancedBookComboBox.currentText(),
self.advancedBookComboBox.itemData(item))
self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
def onAdvancedFromVerse(self):
chapter_from = int(self.advancedFromChapter.currentText())
chapter_to = int(self.advancedToChapter.currentText())
def on_advanced_second_combo_box(self):
self.initialise_advanced_bible(
self.advancedVersionComboBox.currentText(),
self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
def on_advanced_book_combo_box(self):
item = int(self.advanced_book_combo_box.currentIndex())
self.initialise_chapter_verse(
self.advancedVersionComboBox.currentText(),
self.advanced_book_combo_box.currentText(),
self.advanced_book_combo_box.itemData(item))
def on_advanced_from_verse(self):
chapter_from = int(self.advanced_from_chapter.currentText())
chapter_to = int(self.advanced_to_chapter.currentText())
if chapter_from == chapter_to:
bible = self.advancedVersionComboBox.currentText()
book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
verse_from = int(self.advancedFromVerse.currentText())
book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
verse_from = int(self.advanced_from_verse.currentText())
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
self.adjustComboBox(verse_from, verse_count, self.advancedToVerse, True)
self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse, True)
def onAdvancedToChapter(self):
def on_advanced_to_chapter(self):
bible = self.advancedVersionComboBox.currentText()
book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
chapter_from = int(self.advancedFromChapter.currentText())
chapter_to = int(self.advancedToChapter.currentText())
verse_from = int(self.advancedFromVerse.currentText())
verse_to = int(self.advancedToVerse.currentText())
book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
chapter_from = int(self.advanced_from_chapter.currentText())
chapter_to = int(self.advanced_to_chapter.currentText())
verse_from = int(self.advanced_from_verse.currentText())
verse_to = int(self.advanced_to_verse.currentText())
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
if chapter_from == chapter_to and verse_from > verse_to:
self.adjustComboBox(verse_from, verse_count, self.advancedToVerse)
self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse)
else:
self.adjustComboBox(1, verse_count, self.advancedToVerse)
self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
def onAdvancedFromChapter(self):
def on_advanced_from_chapter(self):
bible = self.advancedVersionComboBox.currentText()
book_ref_id = self.advancedBookComboBox.itemData(
int(self.advancedBookComboBox.currentIndex()))
chapter_from = int(self.advancedFromChapter.currentText())
chapter_to = int(self.advancedToChapter.currentText())
book_ref_id = self.advanced_book_combo_box.itemData(
int(self.advanced_book_combo_box.currentIndex()))
chapter_from = int(self.advanced_from_chapter.currentText())
chapter_to = int(self.advanced_to_chapter.currentText())
verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_from)
self.adjustComboBox(1, verse_count, self.advancedFromVerse)
self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
if chapter_from > chapter_to:
self.adjustComboBox(1, verse_count, self.advancedToVerse)
self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter)
self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
elif chapter_from == chapter_to:
self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter)
self.adjustComboBox(1, verse_count, self.advancedToVerse, True)
self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
self.adjust_combo_box(1, verse_count, self.advanced_to_verse, True)
else:
self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter, True)
self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter, True)
def adjustComboBox(self, range_from, range_to, combo, restore=False):
def adjust_combo_box(self, range_from, range_to, combo, restore=False):
"""
Adjusts the given como box to the given values.
``range_from``
The first number of the range (int).
``range_to``
The last number of the range (int).
``combo``
The combo box itself (QComboBox).
``restore``
If True, then the combo's currentText will be restored after
:param range_from: The first number of the range (int).
:param range_to: The last number of the range (int).
:param combo: The combo box itself (QComboBox).
:param restore: If True, then the combo's currentText will be restored after
adjusting (if possible).
"""
log.debug('adjustComboBox %s, %s, %s', combo, range_from, range_to)
log.debug('adjust_combo_box %s, %s, %s', combo, range_from, range_to)
if restore:
old_text = combo.currentText()
combo.clear()
@ -611,7 +599,7 @@ class BibleMediaItem(MediaManagerItem):
if restore and combo.findText(old_text) != -1:
combo.setCurrentIndex(combo.findText(old_text))
def onAdvancedSearchButton(self):
def on_advanced_search_button(self):
"""
Does an advanced search and saves the search results.
"""
@ -620,32 +608,32 @@ class BibleMediaItem(MediaManagerItem):
self.application.process_events()
bible = self.advancedVersionComboBox.currentText()
second_bible = self.advancedSecondComboBox.currentText()
book = self.advancedBookComboBox.currentText()
book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
chapter_from = self.advancedFromChapter.currentText()
chapter_to = self.advancedToChapter.currentText()
verse_from = self.advancedFromVerse.currentText()
verse_to = self.advancedToVerse.currentText()
book = self.advanced_book_combo_box.currentText()
book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
chapter_from = self.advanced_from_chapter.currentText()
chapter_to = self.advanced_to_chapter.currentText()
verse_from = self.advanced_from_verse.currentText()
verse_to = self.advanced_to_verse.currentText()
verse_separator = get_reference_separator('sep_v_display')
range_separator = get_reference_separator('sep_r_display')
verse_range = chapter_from + verse_separator + verse_from + range_separator + chapter_to + \
verse_separator + verse_to
versetext = '%s %s' % (book, verse_range)
verse_text = '%s %s' % (book, verse_range)
self.application.set_busy_cursor()
self.search_results = self.plugin.manager.get_verses(bible, versetext, book_ref_id)
self.search_results = self.plugin.manager.get_verses(bible, verse_text, book_ref_id)
if second_bible:
self.second_search_results = self.plugin.manager.get_verses(second_bible, versetext, book_ref_id)
self.second_search_results = self.plugin.manager.get_verses(second_bible, verse_text, book_ref_id)
if not self.advancedLockButton.isChecked():
self.list_view.clear()
if self.list_view.count() != 0:
self.__check_second_bible(bible, second_bible)
elif self.search_results:
self.displayResults(bible, second_bible)
self.display_results(bible, second_bible)
self.advancedSearchButton.setEnabled(True)
self.check_search_result()
self.application.set_normal_cursor()
def onQuickSearchButton(self):
def on_quick_search_button(self):
"""
Does a quick search and saves the search results. Quick search can
either be "Reference Search" or "Text Search".
@ -655,13 +643,13 @@ class BibleMediaItem(MediaManagerItem):
self.application.process_events()
bible = self.quickVersionComboBox.currentText()
second_bible = self.quickSecondComboBox.currentText()
text = self.quickSearchEdit.text()
if self.quickSearchEdit.current_search_type() == BibleSearch.Reference:
text = self.quick_search_edit.text()
if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
# We are doing a 'Reference Search'.
self.search_results = self.plugin.manager.get_verses(bible, text)
if second_bible and self.search_results:
self.second_search_results = self.plugin.manager.get_verses(second_bible, text,
self.search_results[0].book.book_reference_id)
self.second_search_results = \
self.plugin.manager.get_verses(second_bible, text, self.search_results[0].book.book_reference_id)
else:
# We are doing a 'Text Search'.
self.application.set_busy_cursor()
@ -681,13 +669,13 @@ class BibleMediaItem(MediaManagerItem):
count += 1
continue
new_search_results.append(verse)
text.append((verse.book.book_reference_id, verse.chapter,
verse.verse, verse.verse))
text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
if passage_not_found:
QtGui.QMessageBox.information(self, translate('BiblesPlugin.MediaItem', 'Information'),
QtGui.QMessageBox.information(
self, translate('BiblesPlugin.MediaItem', 'Information'),
translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses '
'that are in the main Bible. Only verses found in both Bibles will be shown. %d verses '
'have not been included in the results.') % count,
'that are in the main Bible. Only verses found in both Bibles will be shown. %d '
'verses have not been included in the results.') % count,
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
self.search_results = new_search_results
self.second_search_results = bibles[second_bible].get_verses(text)
@ -696,24 +684,24 @@ class BibleMediaItem(MediaManagerItem):
if self.list_view.count() != 0 and self.search_results:
self.__check_second_bible(bible, second_bible)
elif self.search_results:
self.displayResults(bible, second_bible)
self.display_results(bible, second_bible)
self.quickSearchButton.setEnabled(True)
self.check_search_result()
self.application.set_normal_cursor()
def displayResults(self, bible, second_bible=''):
def display_results(self, bible, second_bible=''):
"""
Displays the search results in the media manager. All data needed for
further action is saved for/in each row.
"""
items = self.buildDisplayResults(bible, second_bible, self.search_results)
items = self.build_display_results(bible, second_bible, self.search_results)
for bible_verse in items:
self.list_view.addItem(bible_verse)
self.list_view.selectAll()
self.search_results = {}
self.second_search_results = {}
def buildDisplayResults(self, bible, second_bible, search_results):
def build_display_results(self, bible, second_bible, search_results):
"""
Displays the search results in the media manager. All data needed for
further action is saved for/in each row.
@ -764,7 +752,7 @@ class BibleMediaItem(MediaManagerItem):
log.exception('The second_search_results does not have as many verses as the search_results.')
break
bible_text = '%s %d%s%d (%s, %s)' % (book, verse.chapter, verse_separator, verse.verse, version,
second_version)
second_version)
else:
bible_text = '%s %d%s%d (%s)' % (book, verse.chapter, verse_separator, verse.verse, version)
bible_verse = QtGui.QListWidgetItem(bible_text)
@ -772,11 +760,16 @@ class BibleMediaItem(MediaManagerItem):
items.append(bible_verse)
return items
def generate_slide_data(self, service_item, item=None, xmlVersion=False,
remote=False, context=ServiceItemContext.Service):
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Service):
"""
Generates and formats the slides for the service item as well as the
service item's title.
Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on
:param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated
"""
log.debug('generating slide data')
if item:
@ -806,7 +799,7 @@ class BibleMediaItem(MediaManagerItem):
second_permissions = self._decode_qt_object(bitem, 'second_permissions')
second_text = self._decode_qt_object(bitem, 'second_text')
verses.add(book, chapter, verse, version, copyright, permissions)
verse_text = self.formatVerse(old_chapter, chapter, verse)
verse_text = self.format_verse(old_chapter, chapter, verse)
if second_bible:
bible_text = '%s%s\n\n%s&nbsp;%s' % (verse_text, text, verse_text, second_text)
raw_slides.append(bible_text.rstrip())
@ -825,8 +818,8 @@ class BibleMediaItem(MediaManagerItem):
bible_text = bible_text.strip(' ')
if not old_item:
start_item = bitem
elif self.checkTitle(bitem, old_item):
raw_title.append(self.formatTitle(start_item, old_item))
elif self.check_title(bitem, old_item):
raw_title.append(self.format_title(start_item, old_item))
start_item = bitem
old_item = bitem
old_chapter = chapter
@ -835,7 +828,7 @@ class BibleMediaItem(MediaManagerItem):
if second_bible:
verses.add_version(second_version, second_copyright, second_permissions)
service_item.raw_footer.append(verses.format_versions())
raw_title.append(self.formatTitle(start_item, bitem))
raw_title.append(self.format_title(start_item, bitem))
# If there are no more items we check whether we have to add bible_text.
if bible_text:
raw_slides.append(bible_text.lstrip())
@ -858,17 +851,14 @@ class BibleMediaItem(MediaManagerItem):
service_item.add_from_text(slide)
return True
def formatTitle(self, start_bitem, old_bitem):
def format_title(self, start_bitem, old_bitem):
"""
This method is called, when we have to change the title, because
we are at the end of a verse range. E. g. if we want to add
Genesis 1:1-6 as well as Daniel 2:14.
``start_item``
The first item of a range.
``old_item``
The last item of a range.
:param start_bitem: The first item of a range.
:param old_bitem: The last item of a range.
"""
verse_separator = get_reference_separator('sep_v_display')
range_separator = get_reference_separator('sep_r_display')
@ -893,17 +883,15 @@ class BibleMediaItem(MediaManagerItem):
range_separator + old_chapter + verse_separator + old_verse
return '%s %s (%s)' % (start_book, verse_range, bibles)
def checkTitle(self, bitem, old_bitem):
def check_title(self, bitem, old_bitem):
"""
This method checks if we are at the end of an verse range. If that is
the case, we return True, otherwise False. E. g. if we added
Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
``item``
The item we are dealing with at the moment.
``old_item``
The item we were previously dealing with.
:param bitem: The item we are dealing with at the moment.
:param old_bitem: The item we were previously dealing with.
"""
# Get all the necessary meta data.
book = self._decode_qt_object(bitem, 'book')
@ -930,22 +918,18 @@ class BibleMediaItem(MediaManagerItem):
return True
return False
def formatVerse(self, old_chapter, chapter, verse):
def format_verse(self, old_chapter, chapter, verse):
"""
Formats and returns the text, each verse starts with, for the given
chapter and verse. The text is either surrounded by round, square,
curly brackets or no brackets at all. For example::
u'{su}1:1{/su}'
``old_chapter``
The previous verse's chapter number (int).
``chapter``
The chapter number (int).
``verse``
The verse number (int).
:param old_chapter: The previous verse's chapter number (int).
:param chapter: The chapter number (int).
:param verse: The verse number (int).
"""
verse_separator = get_reference_separator('sep_v_display')
if not self.settings.is_verse_number_visible:
@ -969,16 +953,15 @@ class BibleMediaItem(MediaManagerItem):
bible = self.quickVersionComboBox.currentText()
search_results = self.plugin.manager.get_verses(bible, string, False, showError)
if search_results:
versetext = ' '.join([verse.text for verse in search_results])
return [[string, versetext]]
verse_text = ' '.join([verse.text for verse in search_results])
return [[string, verse_text]]
return []
def create_item_from_id(self, item_id):
"""
Create a media item from an item id.
"""
item = QtGui.QListWidgetItem()
bible = self.quickVersionComboBox.currentText()
search_results = self.plugin.manager.get_verses(bible, item_id, False)
items = self.buildDisplayResults(bible, '', search_results)
items = self.build_display_results(bible, '', search_results)
return items

View File

@ -55,8 +55,7 @@ class OpenSongBible(BibleDB):
"""
Recursively get all text in an objectify element and its child elements.
``element``
An objectify element to get the text from
:param element: An objectify element to get the text from
"""
verse_text = ''
if element.text:
@ -122,14 +121,16 @@ class OpenSongBible(BibleDB):
else:
verse_number += 1
self.create_verse(db_book.id, chapter_number, verse_number, self.get_text(verse))
self.wizard.increment_progress_bar(translate('BiblesPlugin.Opensong', 'Importing %s %s...',
'Importing <book name> <chapter>...')) % (db_book.name, chapter_number)
self.wizard.increment_progress_bar(
translate('BiblesPlugin.Opensong', 'Importing %s %s...',
'Importing <book name> <chapter>...')) % (db_book.name, chapter_number)
self.session.commit()
self.application.process_events()
except etree.XMLSyntaxError as inst:
critical_error_message_box(message=translate('BiblesPlugin.OpenSongImport',
'Incorrect Bible file type supplied. OpenSong Bibles may be '
'compressed. You must decompress them before import.'))
critical_error_message_box(
message=translate('BiblesPlugin.OpenSongImport',
'Incorrect Bible file type supplied. OpenSong Bibles may be '
'compressed. You must decompress them before import.'))
log.exception(inst)
success = False
except (IOError, AttributeError):

View File

@ -84,8 +84,8 @@ class OSISBible(BibleDB):
success = True
last_chapter = 0
match_count = 0
self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport',
'Detecting encoding (this may take a few minutes)...'))
self.wizard.increment_progress_bar(
translate('BiblesPlugin.OsisImport', 'Detecting encoding (this may take a few minutes)...'))
try:
detect_file = open(self.filename, 'r')
details = chardet.detect(detect_file.read(1048576))
@ -101,7 +101,7 @@ class OSISBible(BibleDB):
osis = codecs.open(self.filename, 'r', details['encoding'])
repl = replacement
language_id = False
# Decide if the bible propably contains only NT or AT and NT or
# Decide if the bible probably contains only NT or AT and NT or
# AT, NT and Apocrypha
if lines_in_file < 11500:
book_count = 27
@ -154,10 +154,11 @@ class OSISBible(BibleDB):
if last_chapter != chapter:
if last_chapter != 0:
self.session.commit()
self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport', 'Importing %s %s...',
'Importing <book name> <chapter>...') % (book_details['name'], chapter))
self.wizard.increment_progress_bar(
translate('BiblesPlugin.OsisImport', 'Importing %s %s...',
'Importing <book name> <chapter>...') % (book_details['name'], chapter))
last_chapter = chapter
# All of this rigmarol below is because the mod2osis tool from the Sword library embeds XML in the
# All of this rigmarole below is because the mod2osis tool from the Sword library embeds XML in the
# OSIS but neglects to enclose the verse text (with XML) in <[CDATA[ ]]> tags.
verse_text = self.note_regex.sub('', verse_text)
verse_text = self.title_regex.sub('', verse_text)

View File

@ -42,18 +42,15 @@ class VerseReferenceList(object):
def add(self, book, chapter, verse, version, copyright, permission):
self.add_version(version, copyright, permission)
if not self.verse_list or self.verse_list[self.current_index]['book'] != book:
self.verse_list.append({'version': version, 'book': book,
'chapter': chapter, 'start': verse, 'end': verse})
self.verse_list.append({'version': version, 'book': book, 'chapter': chapter, 'start': verse, 'end': verse})
self.current_index += 1
elif self.verse_list[self.current_index]['chapter'] != chapter:
self.verse_list.append({'version': version, 'book': book,
'chapter': chapter, 'start': verse, 'end': verse})
self.verse_list.append({'version': version, 'book': book, 'chapter': chapter, 'start': verse, 'end': verse})
self.current_index += 1
elif (self.verse_list[self.current_index]['end'] + 1) == verse:
self.verse_list[self.current_index]['end'] = verse
else:
self.verse_list.append({'version': version, 'book': book,
'chapter': chapter, 'start': verse, 'end': verse})
self.verse_list.append({'version': version, 'book': book, 'chapter': chapter, 'start': verse, 'end': verse})
self.current_index += 1
def add_version(self, version, copyright, permission):

View File

@ -49,14 +49,9 @@ class AddGroupForm(QtGui.QDialog, Ui_AddGroupDialog):
"""
Show the form.
``clear``
Set to False if the text input box should not be cleared when showing the dialog (default: True).
``show_top_level_group``
Set to True when "-- Top level group --" should be showed as first item (default: False).
``selected_group``
The ID of the group that should be selected by default when showing the dialog.
:param clear: Set to False if the text input box should not be cleared when showing the dialog (default: True).
:param show_top_level_group: Set to True when "-- Top level group --" should be showed as first item (default: False).
:param selected_group: The ID of the group that should be selected by default when showing the dialog.
"""
if clear:
self.name_edit.clear()
@ -76,7 +71,7 @@ class AddGroupForm(QtGui.QDialog, Ui_AddGroupDialog):
"""
if not self.name_edit.text():
critical_error_message_box(message=translate('ImagePlugin.AddGroupForm',
'You need to type in a group name.'))
'You need to type in a group name.'))
self.name_edit.setFocus()
return False
else:

View File

@ -57,16 +57,16 @@ class ImagePlugin(Plugin):
def about(self):
about_text = translate('ImagePlugin', '<strong>Image Plugin</strong>'
'<br />The image plugin provides displaying of images.<br />One '
'of the distinguishing features of this plugin is the ability to '
'group a number of images together in the service manager, making '
'the displaying of multiple images easier. This plugin can also '
'make use of OpenLP\'s "timed looping" feature to create a slide '
'show that runs automatically. In addition to this, images from '
'the plugin can be used to override the current theme\'s '
'background, which renders text-based items like songs with the '
'selected image as a background instead of the background '
'provided by the theme.')
'<br />The image plugin provides displaying of images.<br />One '
'of the distinguishing features of this plugin is the ability to '
'group a number of images together in the service manager, making '
'the displaying of multiple images easier. This plugin can also '
'make use of OpenLP\'s "timed looping" feature to create a slide '
'show that runs automatically. In addition to this, images from '
'the plugin can be used to override the current theme\'s '
'background, which renders text-based items like songs with the '
'selected image as a background instead of the background '
'provided by the theme.')
return about_text
def app_startup(self):

View File

@ -80,17 +80,17 @@ def init_schema(url):
# Definition of the "image_groups" table
image_groups_table = Table('image_groups', metadata,
Column('id', types.Integer(), primary_key=True),
Column('parent_id', types.Integer()),
Column('group_name', types.Unicode(128))
)
Column('id', types.Integer(), primary_key=True),
Column('parent_id', types.Integer()),
Column('group_name', types.Unicode(128))
)
# Definition of the "image_filenames" table
image_filenames_table = Table('image_filenames', metadata,
Column('id', types.Integer(), primary_key=True),
Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
Column('filename', types.Unicode(255), nullable=False)
)
Column('id', types.Integer(), primary_key=True),
Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
Column('filename', types.Unicode(255), nullable=False)
)
mapper(ImageGroups, image_groups_table)
mapper(ImageFilenames, image_filenames_table)

View File

@ -76,8 +76,8 @@ class ImageMediaItem(MediaManagerItem):
self.on_new_prompt = translate('ImagePlugin.MediaItem', 'Select Image(s)')
file_formats = get_images_filter()
self.on_new_file_masks = '%s;;%s (*.*) (*)' % (file_formats, UiStrings().AllFiles)
self.addGroupAction.setText(UiStrings().AddGroup)
self.addGroupAction.setToolTip(UiStrings().AddGroup)
self.add_group_action.setText(UiStrings().AddGroup)
self.add_group_action.setToolTip(UiStrings().AddGroup)
self.replace_action.setText(UiStrings().ReplaceBG)
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
self.reset_action.setText(UiStrings().ResetBG)
@ -120,31 +120,36 @@ class ImageMediaItem(MediaManagerItem):
# define and add the context menu
self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
if self.has_edit_icon:
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
text=self.plugin.get_string(StringContent.Edit)['title'],
icon=':/general/general_edit.png',
triggers=self.on_edit_click)
create_widget_action(self.list_view, separator=True)
if self.has_delete_icon:
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
text=self.plugin.get_string(StringContent.Delete)['title'],
icon=':/general/general_delete.png',
can_shortcuts=True, triggers=self.on_delete_click)
create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),
text=self.plugin.get_string(StringContent.Preview)['title'],
icon=':/general/general_preview.png',
can_shortcuts=True,
triggers=self.on_preview_click)
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Live.title()),
text=self.plugin.get_string(StringContent.Live)['title'],
icon=':/general/general_live.png',
can_shortcuts=True,
triggers=self.on_live_click)
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Service.title()),
can_shortcuts=True,
text=self.plugin.get_string(StringContent.Service)['title'],
@ -152,7 +157,8 @@ class ImageMediaItem(MediaManagerItem):
triggers=self.on_add_click)
if self.add_to_service_item:
create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
icon=':/general/general_add.png',
triggers=self.on_add_edit_click)
@ -170,9 +176,11 @@ class ImageMediaItem(MediaManagerItem):
Add custom actions to the context menu.
"""
create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
text=UiStrings().AddGroup, icon=':/images/image_new_group.png', triggers=self.on_add_group_click)
create_widget_action(self.list_view,
create_widget_action(
self.list_view,
text=self.plugin.get_string(StringContent.Load)['tooltip'],
icon=':/general/general_open.png', triggers=self.on_file_click)
@ -180,24 +188,26 @@ class ImageMediaItem(MediaManagerItem):
"""
Add custom buttons to the start of the toolbar.
"""
self.addGroupAction = self.toolbar.add_toolbar_action('addGroupAction',
icon=':/images/image_new_group.png', triggers=self.on_add_group_click)
self.add_group_action = self.toolbar.add_toolbar_action('add_group_action',
icon=':/images/image_new_group.png',
triggers=self.on_add_group_click)
def add_end_header_bar(self):
"""
Add custom buttons to the end of the toolbar
"""
self.replace_action = self.toolbar.add_toolbar_action('replace_action',
icon=':/slides/slide_blank.png', triggers=self.on_replace_click)
icon=':/slides/slide_blank.png',
triggers=self.on_replace_click)
self.reset_action = self.toolbar.add_toolbar_action('reset_action',
icon=':/system/system_close.png', visible=False, triggers=self.on_reset_click)
icon=':/system/system_close.png',
visible=False, triggers=self.on_reset_click)
def recursively_delete_group(self, image_group):
"""
Recursively deletes a group and all groups and images in it.
``image_group``
The ImageGroups instance of the group that will be deleted.
:param image_group: The ImageGroups instance of the group that will be deleted.
"""
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
for image in images:
@ -215,7 +225,7 @@ class ImageMediaItem(MediaManagerItem):
# Turn off auto preview triggers.
self.list_view.blockSignals(True)
if check_item_selected(self.list_view, translate('ImagePlugin.MediaItem',
'You must select an image or group to delete.')):
'You must select an image or group to delete.')):
item_list = self.list_view.selectedItems()
self.application.set_busy_cursor()
self.main_window.display_progress_bar(len(item_list))
@ -230,12 +240,14 @@ class ImageMediaItem(MediaManagerItem):
row_item.parent().removeChild(row_item)
self.manager.delete_object(ImageFilenames, row_item.data(0, QtCore.Qt.UserRole).id)
elif isinstance(item_data, ImageGroups):
if QtGui.QMessageBox.question(self.list_view.parent(),
if QtGui.QMessageBox.question(
self.list_view.parent(),
translate('ImagePlugin.MediaItem', 'Remove group'),
translate('ImagePlugin.MediaItem',
'Are you sure you want to remove "%s" and everything in it?') % item_data.group_name,
'Are you sure you want to remove "%s" and everything in it?') %
item_data.group_name,
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes:
QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes:
self.recursively_delete_group(item_data)
self.manager.delete_object(ImageGroups, row_item.data(0, QtCore.Qt.UserRole).id)
if item_data.parent_id == 0:
@ -253,11 +265,8 @@ class ImageMediaItem(MediaManagerItem):
"""
Recursively add subgroups to the given parent group in a QTreeWidget.
``group_list``
The List object that contains all QTreeWidgetItems.
``parent_group_id``
The ID of the group that will be added recursively.
:param group_list: The List object that contains all QTreeWidgetItems.
:param parent_group_id: The ID of the group that will be added recursively.
"""
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
@ -278,14 +287,10 @@ class ImageMediaItem(MediaManagerItem):
"""
Recursively add groups to the combobox in the 'Add group' dialog.
``combobox``
The QComboBox to add the options to.
``parent_group_id``
The ID of the group that will be added.
``prefix``
A string containing the prefix that will be added in front of the groupname for each level of the tree.
:param combobox: The QComboBox to add the options to.
:param parent_group_id: The ID of the group that will be added.
:param prefix: A string containing the prefix that will be added in front of the groupname for each level of
the tree.
"""
if parent_group_id == 0:
combobox.clear()
@ -300,11 +305,8 @@ class ImageMediaItem(MediaManagerItem):
"""
Expand groups in the widget recursively.
``group_id``
The ID of the group that will be expanded.
``root_item``
This option is only used for recursion purposes.
:param group_id: The ID of the group that will be expanded.
:param root_item: This option is only used for recursion purposes.
"""
return_value = False
if root_item is None:
@ -323,14 +325,9 @@ class ImageMediaItem(MediaManagerItem):
"""
Replace the list of images and groups in the interface.
``images``
A List of ImageFilenames objects that will be used to reload the mediamanager list.
``initial_load``
When set to False, the busy cursor and progressbar will be shown while loading images.
``open_group``
ImageGroups object of the group that must be expanded after reloading the list in the interface.
:param images: A List of Image Filenames objects that will be used to reload the mediamanager list.
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
:param open_group: ImageGroups object of the group that must be expanded after reloading the list in the interface.
"""
if not initial_load:
self.application.set_busy_cursor()
@ -375,11 +372,8 @@ class ImageMediaItem(MediaManagerItem):
Process a list for files either from the File Dialog or from Drag and Drop.
This method is overloaded from MediaManagerItem.
``files``
A List of strings containing the filenames of the files to be loaded
``target_group``
The QTreeWidgetItem of the group that will be the parent of the added files
:param files: A List of strings containing the filenames of the files to be loaded
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
"""
self.application.set_normal_cursor()
self.load_list(files, target_group)
@ -390,14 +384,9 @@ class ImageMediaItem(MediaManagerItem):
"""
Add new images to the database. This method is called when adding images using the Add button or DnD.
``images``
A List of strings containing the filenames of the files to be loaded
``target_group``
The QTreeWidgetItem of the group that will be the parent of the added files
``initial_load``
When set to False, the busy cursor and progressbar will be shown while loading images
:param images: A List of strings containing the filenames of the files to be loaded
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
"""
parent_group = None
if target_group is None:
@ -464,30 +453,25 @@ class ImageMediaItem(MediaManagerItem):
# Save the new images in the database
self.save_new_images_list(images, group_id=parent_group.id, reload_list=False)
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename),
initial_load=initial_load, open_group=parent_group)
initial_load=initial_load, open_group=parent_group)
self.application.set_normal_cursor()
def save_new_images_list(self, images_list, group_id=0, reload_list=True):
"""
Convert a list of image filenames to ImageFilenames objects and save them in the database.
``images_list``
A List of strings containing image filenames
``group_id``
The ID of the group to save the images in
``reload_list``
This boolean is set to True when the list in the interface should be reloaded after saving the new images
:param images_list: A List of strings containing image filenames
:param group_id: The ID of the group to save the images in
:param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving the new images
"""
for filename in images_list:
if not isinstance(filename, str):
continue
log.debug('Adding new image: %s', filename)
imageFile = ImageFilenames()
imageFile.group_id = group_id
imageFile.filename = str(filename)
self.manager.save_object(imageFile)
image_file = ImageFilenames()
image_file.group_id = group_id
image_file.filename = str(filename)
self.manager.save_object(image_file)
self.main_window.increment_progress_bar()
if reload_list and images_list:
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename))
@ -496,8 +480,7 @@ class ImageMediaItem(MediaManagerItem):
"""
Handle drag-and-drop moving of images within the media manager
``target``
This contains the QTreeWidget that is the target of the DnD action
:param target: This contains the QTreeWidget that is the target of the DnD action
"""
items_to_move = self.list_view.selectedItems()
# Determine group to move images to
@ -538,10 +521,16 @@ class ImageMediaItem(MediaManagerItem):
image_items.sort(key=lambda item: get_locale_key(item.text(0)))
target_group.addChildren(image_items)
def generate_slide_data(self, service_item, item=None, xml_version=False,
remote=False, context=ServiceItemContext.Service):
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Service):
"""
Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on
:param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated
"""
background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
if item:
@ -561,40 +550,41 @@ class ImageMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.CanAppend)
# force a nonexistent theme
service_item.theme = -1
missing_items_filenames = []
images_filenames = []
missing_items_file_names = []
images_file_names = []
# Expand groups to images
for bitem in items:
if isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageGroups) or bitem.data(0, QtCore.Qt.UserRole) is None:
for index in range(0, bitem.childCount()):
if isinstance(bitem.child(index).data(0, QtCore.Qt.UserRole), ImageFilenames):
images_filenames.append(bitem.child(index).data(0, QtCore.Qt.UserRole).filename)
images_file_names.append(bitem.child(index).data(0, QtCore.Qt.UserRole).filename)
elif isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
images_filenames.append(bitem.data(0, QtCore.Qt.UserRole).filename)
images_file_names.append(bitem.data(0, QtCore.Qt.UserRole).filename)
# Don't try to display empty groups
if not images_filenames:
if not images_file_names:
return False
# Find missing files
for filename in images_filenames:
for filename in images_file_names:
if not os.path.exists(filename):
missing_items_filenames.append(filename)
missing_items_file_names.append(filename)
# We cannot continue, as all images do not exist.
if not images_filenames:
if not images_file_names:
if not remote:
critical_error_message_box(
translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s') %
'\n'.join(missing_items_filenames))
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s')
% '\n'.join(missing_items_file_names))
return False
# We have missing as well as existing images. We ask what to do.
elif missing_items_filenames and QtGui.QMessageBox.question(self,
translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s\n'
'Do you want to add the other images anyway?') % '\n'.join(missing_items_filenames),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No:
elif missing_items_file_names and QtGui.QMessageBox.question(
self, translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: %s\n'
'Do you want to add the other images anyway?') % '\n'.join(missing_items_file_names),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == \
QtGui.QMessageBox.No:
return False
# Continue with the existing images.
for filename in images_filenames:
for filename in images_file_names:
name = os.path.split(filename)[1]
service_item.add_from_image(filename, name, background)
return True
@ -603,8 +593,7 @@ class ImageMediaItem(MediaManagerItem):
"""
Returns *True* if the given Group already exists in the database, otherwise *False*.
``new_group``
The ImageGroups object that contains the name of the group that will be checked
:param new_group: The ImageGroups object that contains the name of the group that will be checked
"""
groups = self.manager.get_all_objects(ImageGroups, ImageGroups.group_name == new_group.group_name)
if groups:
@ -633,8 +622,8 @@ class ImageMediaItem(MediaManagerItem):
group_name=self.add_group_form.name_edit.text())
if not self.check_group_exists(new_group):
if self.manager.save_object(new_group):
self.load_full_list(self.manager.get_all_objects(ImageFilenames,
order_by_ref=ImageFilenames.filename))
self.load_full_list(self.manager.get_all_objects(
ImageFilenames, order_by_ref=ImageFilenames.filename))
self.expand_group(new_group.id)
self.fill_groups_combobox(self.choose_group_form.group_combobox)
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
@ -659,9 +648,10 @@ class ImageMediaItem(MediaManagerItem):
def on_replace_click(self):
"""
Called to replace Live backgound with the image selected.
Called to replace Live background with the image selected.
"""
if check_item_selected(self.list_view,
if check_item_selected(
self.list_view,
translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')):
background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
bitem = self.list_view.selectedItems()[0]
@ -673,24 +663,24 @@ class ImageMediaItem(MediaManagerItem):
if self.live_controller.display.direct_image(filename, background):
self.reset_action.setVisible(True)
else:
critical_error_message_box(UiStrings().LiveBGError,
critical_error_message_box(
UiStrings().LiveBGError,
translate('ImagePlugin.MediaItem', 'There was no display item to amend.'))
else:
critical_error_message_box(UiStrings().LiveBGError,
critical_error_message_box(
UiStrings().LiveBGError,
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
'the image file "%s" no longer exists.') % filename)
'the image file "%s" no longer exists.') % filename)
def search(self, string, show_error=True):
"""
Perform a search on the image file names.
``string``
The glob to search for
``show_error``
Unused.
:param string: The glob to search for
:param show_error: Unused.
"""
files = self.manager.get_all_objects(ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
files = self.manager.get_all_objects(
ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
order_by_ref=ImageFilenames.filename)
results = []
for file_object in files:

View File

@ -131,6 +131,11 @@ class MediaMediaItem(MediaManagerItem):
self.display_type_combo_box.currentIndexChanged.connect(self.override_player_changed)
def override_player_changed(self, index):
"""
The Player has been overridden
:param index: Index
"""
player = get_media_players()[0]
if index == 0:
set_media_players(player)
@ -178,9 +183,15 @@ class MediaMediaItem(MediaManagerItem):
'the media file "%s" no longer exists.') % filename)
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Live):
context=ServiceItemContext.Service):
"""
Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on
:param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated
"""
if item is None:
item = self.list_view.currentItem()
@ -269,6 +280,12 @@ class MediaMediaItem(MediaManagerItem):
def load_list(self, media, target_group=None):
# Sort the media by its filename considering language specific characters.
"""
Load the media list
:param media: The media
:param target_group:
"""
media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1]))
for track in media:
track_info = QtCore.QFileInfo(track)

View File

@ -33,14 +33,6 @@ from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import SettingsTab
class MediaQ_check_box(QtGui.QCheckBox):
"""
MediaQ_check_box adds an extra property, playerName to the Q_check_box class.
"""
def setPlayerName(self, name):
self.playerName = name
class MediaTab(SettingsTab):
"""
MediaTab is the Media settings tab in the settings dialog.

View File

@ -66,7 +66,7 @@ class MediaPlugin(Plugin):
def about(self):
about_text = translate('MediaPlugin', '<strong>Media Plugin</strong>'
'<br />The media plugin provides playback of audio and video.')
'<br />The media plugin provides playback of audio and video.')
return about_text
def set_plugin_text_strings(self):

View File

@ -52,6 +52,7 @@ else:
import uno
from com.sun.star.beans import PropertyValue
from com.sun.star.task import ErrorCodeIOException
uno_available = True
except ImportError:
uno_available = False
@ -183,9 +184,9 @@ class ImpressController(PresentationController):
docs = desktop.getComponents()
cnt = 0
if docs.hasElements():
list = docs.createEnumeration()
while list.hasMoreElements():
doc = list.nextElement()
list_elements = docs.createEnumeration()
while list_elements.hasMoreElements():
doc = list_elements.nextElement()
if doc.getImplementationName() != 'com.sun.star.comp.framework.BackingComp':
cnt += 1
if cnt > 0:
@ -203,7 +204,7 @@ class ImpressDocument(PresentationDocument):
Class which holds information and controls a single presentation.
"""
def __init__(self, controller, presentation):
def __init__ (self, controller, presentation):
"""
Constructor, store information about the file and initialise.
"""
@ -225,10 +226,10 @@ class ImpressDocument(PresentationDocument):
if desktop is None:
self.controller.start_process()
desktop = self.controller.get_com_desktop()
url = 'file:///' + self.filepath.replace('\\', '/').replace(':', '|').replace(' ', '%20')
url = 'file:///' + self.file_path.replace('\\', '/').replace(':', '|').replace(' ', '%20')
else:
desktop = self.controller.get_uno_desktop()
url = uno.systemPathToFileUrl(self.filepath)
url = uno.systemPathToFileUrl(self.file_path)
if desktop is None:
return False
self.desktop = desktop
@ -352,7 +353,7 @@ class ImpressDocument(PresentationDocument):
log.debug('unblank screen OpenOffice')
return self.control.resume()
def blank_screen(self):
def blank_screen (self):
"""
Blanks the screen.
"""
@ -409,11 +410,13 @@ class ImpressDocument(PresentationDocument):
"""
return self.document.getDrawPages().getCount()
def goto_slide(self, slideno):
def goto_slide(self, slide_no):
"""
Go to a specific slide (from 1).
:param slide_no: The slide the text is required for, starting at 1
"""
self.control.gotoSlideIndex(slideno-1)
self.control.gotoSlideIndex(slide_no - 1)
def next_step(self):
"""
@ -435,8 +438,7 @@ class ImpressDocument(PresentationDocument):
"""
Returns the text on the slide.
``slide_no``
The slide the text is required for, starting at 1
:param slide_no: The slide the text is required for, starting at 1
"""
return self.__get_text_from_page(slide_no)
@ -444,8 +446,7 @@ class ImpressDocument(PresentationDocument):
"""
Returns the text in the slide notes.
``slide_no``
The slide the notes are required for, starting at 1
:param slide_no: The slide the notes are required for, starting at 1
"""
return self.__get_text_from_page(slide_no, True)
@ -453,8 +454,8 @@ class ImpressDocument(PresentationDocument):
"""
Return any text extracted from the presentation page.
``notes``
A boolean. If set the method searches the notes of the slide.
:param slide_no: The slide the notes are required for, starting at 1
:param notes: A boolean. If set the method searches the notes of the slide.
"""
text = ''
pages = self.document.getDrawPages()

View File

@ -183,7 +183,7 @@ class PresentationMediaItem(MediaManagerItem):
translate('PresentationPlugin.MediaItem',
'A presentation with that filename already exists.'))
continue
controller_name = self.findControllerByType(filename)
controller_name = self.find_controller_by_type(filename)
if controller_name:
controller = self.controllers[controller_name]
doc = controller.add_document(file)
@ -240,11 +240,16 @@ class PresentationMediaItem(MediaManagerItem):
self.list_view.takeItem(row)
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
def generate_slide_data(self, service_item, item=None, xml_version=False,
remote=False, context=ServiceItemContext.Service, presentation_file=None):
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Service, presentation_file=None):
"""
Load the relevant information for displaying the presentation in the slidecontroller. In the case of
powerpoints, an image for each slide.
Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on
:param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated
"""
if item:
items = [item]
@ -272,7 +277,7 @@ class PresentationMediaItem(MediaManagerItem):
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
processor = self.findControllerByType(filename)
processor = self.find_controller_by_type(filename)
if not processor:
return False
controller = self.controllers[processor]
@ -282,13 +287,13 @@ class PresentationMediaItem(MediaManagerItem):
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
doc.load_presentation()
i = 1
imagefile = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), imagefile)
image_file = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), image_file)
while os.path.isfile(image):
service_item.add_from_image(image, name)
i += 1
imagefile = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), imagefile)
image_file = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), image_file)
doc.close_presentation()
return True
else:
@ -307,7 +312,7 @@ class PresentationMediaItem(MediaManagerItem):
service_item.title = name
if os.path.exists(filename):
if service_item.processor == self.automatic:
service_item.processor = self.findControllerByType(filename)
service_item.processor = self.find_controller_by_type(filename)
if not service_item.processor:
return False
controller = self.controllers[service_item.processor]
@ -340,11 +345,13 @@ class PresentationMediaItem(MediaManagerItem):
'The presentation %s no longer exists.') % filename)
return False
def findControllerByType(self, filename):
def find_controller_by_type(self, filename):
"""
Determine the default application controller to use for the selected file type. This is used if "Automatic" is
set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension.
If none found, then look for a controller which "also supports" it instead.
:param filename: The file name
"""
file_type = os.path.splitext(filename)[1][1:]
if not file_type:
@ -360,6 +367,13 @@ class PresentationMediaItem(MediaManagerItem):
return None
def search(self, string, show_error):
"""
Search in files
:param string: name to be found
:param show_error: not used
:return:
"""
files = Settings().value(self.settings_section + '/presentations files')
results = []
string = string.lower()

View File

@ -71,7 +71,7 @@ class Controller(object):
return
self.doc.slidenumber = slide_no
self.hide_mode = hide_mode
log.debug('add_handler, slidenumber: %d' % slide_no)
log.debug('add_handler, slide_number: %d' % slide_no)
if self.is_live:
if hide_mode == HideMode.Screen:
Registry().execute('live_display_hide', HideMode.Screen)
@ -342,7 +342,7 @@ class MessageListener(object):
# so handler & processor is set to None, and we skip adding the handler.
self.handler = None
if self.handler == self.media_item.automatic:
self.handler = self.media_item.findControllerByType(file)
self.handler = self.media_item.find_controller_by_type(file)
if not self.handler:
return
if is_live:
@ -359,6 +359,8 @@ class MessageListener(object):
def slide(self, message):
"""
React to the message to move to a specific slide.
:param message: The message {1} is_live {2} slide
"""
is_live = message[1]
slide = message[2]
@ -370,6 +372,8 @@ class MessageListener(object):
def first(self, message):
"""
React to the message to move to the first slide.
:param message: The message {1} is_live
"""
is_live = message[1]
if is_live:
@ -380,6 +384,8 @@ class MessageListener(object):
def last(self, message):
"""
React to the message to move to the last slide.
:param message: The message {1} is_live
"""
is_live = message[1]
if is_live:
@ -390,6 +396,8 @@ class MessageListener(object):
def next(self, message):
"""
React to the message to move to the next animation/slide.
:param message: The message {1} is_live
"""
is_live = message[1]
if is_live:
@ -400,6 +408,8 @@ class MessageListener(object):
def previous(self, message):
"""
React to the message to move to the previous animation/slide.
:param message: The message {1} is_live
"""
is_live = message[1]
if is_live:
@ -410,6 +420,8 @@ class MessageListener(object):
def shutdown(self, message):
"""
React to message to shutdown the presentation. I.e. end the show and close the file.
:param message: The message {1} is_live
"""
is_live = message[1]
if is_live:
@ -420,6 +432,8 @@ class MessageListener(object):
def hide(self, message):
"""
React to the message to show the desktop.
:param message: The message {1} is_live
"""
is_live = message[1]
if is_live:
@ -428,6 +442,8 @@ class MessageListener(object):
def blank(self, message):
"""
React to the message to blank the display.
:param message: The message {1} is_live {2} slide
"""
is_live = message[1]
hide_mode = message[2]
@ -437,6 +453,8 @@ class MessageListener(object):
def unblank(self, message):
"""
React to the message to unblank the display.
:param message: The message {1} is_live
"""
is_live = message[1]
if is_live:

View File

@ -113,7 +113,7 @@ class PdfController(PresentationController):
self.gsbin = ''
self.also_supports = []
# Use the user defined program if given
if (Settings().value('presentations/enable_pdf_program')):
if Settings().value('presentations/enable_pdf_program'):
pdf_program = Settings().value('presentations/pdf_program')
program_type = self.check_binary(pdf_program)
if program_type == 'gs':
@ -197,7 +197,7 @@ class PdfDocument(PresentationDocument):
runlog = []
try:
runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
'-sFile=' + self.filepath, gs_resolution_script])
'-sFile=' + self.file_path, gs_resolution_script])
except CalledProcessError as e:
log.debug(' '.join(e.cmd))
log.debug(e.output)
@ -246,13 +246,13 @@ class PdfDocument(PresentationDocument):
os.makedirs(self.get_temp_folder())
if self.controller.mudrawbin:
runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()),
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath])
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path])
elif self.controller.gsbin:
resolution = self.gs_get_resolution(size)
runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
'-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
'-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'),
self.filepath])
self.file_path])
created_files = sorted(os.listdir(self.get_temp_folder()))
for fn in created_files:
if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):

View File

@ -112,6 +112,9 @@ class PowerpointDocument(PresentationDocument):
def __init__(self, controller, presentation):
"""
Constructor, store information about the file and initialise.
:param controller:
:param presentation:
"""
log.debug('Init Presentation Powerpoint')
super(PowerpointDocument, self).__init__(controller, presentation)
@ -126,7 +129,7 @@ class PowerpointDocument(PresentationDocument):
if not self.controller.process or not self.controller.process.Visible:
self.controller.start_process()
try:
self.controller.process.Presentations.Open(self.filepath, False, False, True)
self.controller.process.Presentations.Open(self.file_path, False, False, True)
except pywintypes.com_error:
log.debug('PPT open failed')
return False
@ -275,12 +278,14 @@ class PowerpointDocument(PresentationDocument):
log.debug('get_slide_count')
return self.presentation.Slides.Count
def goto_slide(self, slideno):
def goto_slide(self, slide_no):
"""
Moves to a specific slide in the presentation.
:param slide_no: The slide the text is required for, starting at 1
"""
log.debug('goto_slide')
self.presentation.SlideShowWindow.View.GotoSlide(slideno)
self.presentation.SlideShowWindow.View.GotoSlide(slide_no)
def next_step(self):
"""
@ -302,8 +307,7 @@ class PowerpointDocument(PresentationDocument):
"""
Returns the text on the slide.
``slide_no``
The slide the text is required for, starting at 1.
:param slide_no: The slide the text is required for, starting at 1
"""
return _get_text_from_shapes(self.presentation.Slides(slide_no).Shapes)
@ -311,8 +315,7 @@ class PowerpointDocument(PresentationDocument):
"""
Returns the text on the slide.
``slide_no``
The slide the notes are required for, starting at 1.
:param slide_no: The slide the text is required for, starting at 1
"""
return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
@ -321,8 +324,7 @@ def _get_text_from_shapes(shapes):
"""
Returns any text extracted from the shapes on a presentation slide.
``shapes``
A set of shapes to search for text.
:param shapes: A set of shapes to search for text.
"""
text = ''
for index in range(shapes.Count):

View File

@ -124,7 +124,7 @@ class PptviewDocument(PresentationDocument):
temp_folder = self.get_temp_folder()
size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
file_path = os.path.normpath(self.filepath)
file_path = os.path.normpath(self.file_path)
preview_path = os.path.join(temp_folder, 'slide')
# Ensure that the paths are null terminated
file_path = file_path.encode('utf-16-le') + b'\0'
@ -228,11 +228,13 @@ class PptviewDocument(PresentationDocument):
"""
return self.controller.process.GetSlideCount(self.ppt_id)
def goto_slide(self, slideno):
def goto_slide(self, slide_no):
"""
Moves to a specific slide in the presentation.
:param slide_no: The slide the text is required for, starting at 1
"""
self.controller.process.GotoSlide(self.ppt_id, slideno)
self.controller.process.GotoSlide(self.ppt_id, slide_no)
def next_step(self):
"""

View File

@ -103,8 +103,8 @@ class PresentationDocument(object):
"""
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
"""
self.slidenumber = 0
self.filepath = name
self.slide_number = 0
self.file_path = name
check_directory_exists(self.get_thumbnail_folder())
def load_presentation(self):
@ -131,7 +131,7 @@ class PresentationDocument(object):
"""
Return just the filename of the presentation, without the directory
"""
return os.path.split(self.filepath)[1]
return os.path.split(self.file_path)[1]
def get_thumbnail_folder(self):
"""
@ -149,10 +149,10 @@ class PresentationDocument(object):
"""
Returns ``True`` if the thumbnail images exist and are more recent than the powerpoint file.
"""
lastimage = self.get_thumbnail_path(self.get_slide_count(), True)
if not (lastimage and os.path.isfile(lastimage)):
last_image = self.get_thumbnail_path(self.get_slide_count(), True)
if not (last_image and os.path.isfile(last_image)):
return False
return validate_thumb(self.filepath, lastimage)
return validate_thumb(self.file_path, last_image)
def close_presentation(self):
"""
@ -253,8 +253,7 @@ class PresentationDocument(object):
``slide_no``
The slide an image is required for, starting at 1
"""
path = os.path.join(self.get_thumbnail_folder(),
self.controller.thumbnail_prefix + str(slide_no) + '.png')
path = os.path.join(self.get_thumbnail_folder(), self.controller.thumbnail_prefix + str(slide_no) + '.png')
if os.path.isfile(path) or not check_exists:
return path
else:
@ -268,21 +267,20 @@ class PresentationDocument(object):
return
if not hide_mode:
current = self.get_slide_number()
if current == self.slidenumber:
if current == self.slide_number:
return
self.slidenumber = current
self.slide_number = current
if is_live:
prefix = 'live'
else:
prefix = 'preview'
Registry().execute('slidecontroller_%s_change' % prefix, self.slidenumber - 1)
Registry().execute('slidecontroller_%s_change' % prefix, self.slide_number - 1)
def get_slide_text(self, slide_no):
"""
Returns the text on the slide
``slide_no``
The slide the text is required for, starting at 1
:param slide_no: The slide the text is required for, starting at 1
"""
return ''
@ -290,8 +288,7 @@ class PresentationDocument(object):
"""
Returns the text on the slide
``slide_no``
The slide the notes are required for, starting at 1
:param slide_no: The slide the text is required for, starting at 1
"""
return ''
@ -350,6 +347,7 @@ class PresentationController(object):
def __init__(self, plugin=None, name='PresentationController', document_class=PresentationDocument):
"""
This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins
to populate common data. This method *must* be overridden, like so::
class MyPresentationController(PresentationController):
@ -357,11 +355,9 @@ class PresentationController(object):
PresentationController.__init(
self, plugin, u'My Presenter App')
``plugin``
Defaults to *None*. The presentationplugin object
``name``
Name of the application, to appear in the application
:param plugin: Defaults to *None*. The presentationplugin object
:param name: Name of the application, to appear in the application
:param document_class:
"""
self.supports = []
self.also_supports = []

View File

@ -2485,7 +2485,7 @@ $.mobile.transitionFallbacks = {};
return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
},
//return the substring of a filepath before the sub-page key, for making a server request
//return the substring of a file_path before the sub-page key, for making a server request
getFilePath: function( path ) {
var splitkey = '&' + $.mobile.subPageUrlKey;
return path && path.split( splitkey )[0].split( dialogHashKey )[0];

View File

@ -201,7 +201,7 @@ class SongImportForm(OpenLPWizard):
def validateCurrentPage(self):
"""
Re-implement the validateCurrentPage() method. Validate the current page before moving on to the next page.
Provide each song format class with a chance to validate its input by overriding isValidSource().
Provide each song format class with a chance to validate its input by overriding is_valid_source().
"""
if self.currentPage() == self.welcome_page:
return True
@ -217,7 +217,7 @@ class SongImportForm(OpenLPWizard):
import_source = self.format_widgets[this_format]['file_path_edit'].text()
error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs)
focus_button = self.format_widgets[this_format]['browseButton']
if not class_.isValidSource(import_source):
if not class_.is_valid_source(import_source):
critical_error_message_box(error_title, error_msg)
focus_button.setFocus()
return False
@ -334,7 +334,7 @@ class SongImportForm(OpenLPWizard):
def perform_wizard(self):
"""
Perform the actual import. This method pulls in the correct importer class, and then runs the ``doImport``
Perform the actual import. This method pulls in the correct importer class, and then runs the ``do_import``
method of the importer to do the actual importing.
"""
source_format = self.current_format
@ -349,7 +349,7 @@ class SongImportForm(OpenLPWizard):
importer = self.plugin.import_songs(
source_format,
filenames=self.get_list_of_files(self.format_widgets[source_format]['file_list_widget']))
importer.doImport()
importer.do_import()
self.progress_label.setText(WizardStrings.FinishedImport)
def on_error_copy_to_button_clicked(self):

View File

@ -389,7 +389,6 @@ def clean_song(manager, song):
song.lyrics = str(song.lyrics, encoding='utf8')
verses = SongXML().get_verses(song.lyrics)
song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses])
# The song does not have any author, add one.
if not song.authors:
name = SongStrings.AuthorUnknown

View File

@ -38,26 +38,23 @@ from .songimport import SongImport
log = logging.getLogger(__name__)
class CCLIFileImport(SongImport):
"""
The :class:`CCLIFileImport` class provides OpenLP with the ability to import
CCLI SongSelect song files in both .txt and .usr formats. See
`<http://www.ccli.com/>`_ for more details.
The :class:`CCLIFileImport` class provides OpenLP with the ability to import CCLI SongSelect song files in
both .txt and .usr formats. See `<http://www.ccli.com/>`_ for more details.
"""
def __init__(self, manager, **kwargs):
"""
Initialise the import.
``manager``
The song manager for the running OpenLP installation.
``filenames``
The files to be imported.
:param manager: The song manager for the running OpenLP installation.
:param kwargs: The files to be imported.
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Import either a ``.usr`` or a ``.txt`` SongSelect file.
"""
@ -85,27 +82,24 @@ class CCLIFileImport(SongImport):
ext = os.path.splitext(filename)[1]
if ext.lower() == '.usr':
log.info('SongSelect .usr format file found: %s', filename)
if not self.doImportUsrFile(lines):
self.logError(filename)
if not self.do_import_usr_file(lines):
self.log_error(filename)
elif ext.lower() == '.txt':
log.info('SongSelect .txt format file found: %s', filename)
if not self.doImportTxtFile(lines):
self.logError(filename)
if not self.do_import_txt_file(lines):
self.log_error(filename)
else:
self.logError(filename,
self.log_error(filename,
translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid extension.'))
log.info('Extension %s is not valid', filename)
if self.stop_import_flag:
return
def doImportUsrFile(self, textList):
def do_import_usr_file(self, text_list):
"""
The :func:`doImport_usr_file` method provides OpenLP with the ability
to import CCLI SongSelect songs in *USR* file format.
``textList``
An array of strings containing the usr file content.
**SongSelect .usr file format**
``[File]``
@ -155,17 +149,18 @@ class CCLIFileImport(SongImport):
*Fields* description
e.g. *Words=Above all powers....* [/n = CR, /n/t = CRLF]
:param text_list: An array of strings containing the usr file content.
"""
log.debug('USR file text: %s', textList)
log.debug('USR file text: %s', text_list)
song_author = ''
song_topics = ''
for line in textList:
for line in text_list:
if line.startswith('[S '):
ccli, line = line.split(']', 1)
if ccli.startswith('[S A'):
self.ccliNumber = ccli[4:].strip()
self.ccli_number = ccli[4:].strip()
else:
self.ccliNumber = ccli[3:].strip()
self.ccli_number = ccli[3:].strip()
if line.startswith('Title='):
self.title = line[6:].strip()
elif line.startswith('Author='):
@ -176,7 +171,7 @@ class CCLIFileImport(SongImport):
song_topics = line[7:].strip().replace(' | ', '/t')
elif line.startswith('Fields='):
# Fields contain single line indicating verse, chorus, etc,
# /t delimited, same as with words field. store seperately
# /t delimited, same as with words field. store separately
# and process at end.
song_fields = line[7:].strip()
elif line.startswith('Words='):
@ -213,7 +208,7 @@ class CCLIFileImport(SongImport):
verse_type = VerseType.tags[VerseType.Other]
verse_text = verse_lines[1]
if verse_text:
self.addVerse(verse_text, verse_type)
self.add_verse(verse_text, verse_type)
check_first_verse_line = False
# Handle multiple authors
author_list = song_author.split('/')
@ -223,17 +218,16 @@ class CCLIFileImport(SongImport):
separated = author.split(',')
if len(separated) > 1:
author = ' '.join(map(str.strip, reversed(separated)))
self.addAuthor(author.strip())
self.add_author(author.strip())
self.topics = [topic.strip() for topic in song_topics.split('/t')]
return self.finish()
def doImportTxtFile(self, textList):
def do_import_txt_file(self, text_list):
"""
The :func:`doImport_txt_file` method provides OpenLP with the ability
to import CCLI SongSelect songs in *TXT* file format.
``textList``
An array of strings containing the txt file content.
:param text_list: An array of strings containing the txt file content.
SongSelect .txt file format::
@ -259,20 +253,20 @@ class CCLIFileImport(SongImport):
# e.g. CCL-Liedlizenznummer: 14 / CCLI License No. 14
"""
log.debug('TXT file text: %s', textList)
log.debug('TXT file text: %s', text_list)
line_number = 0
check_first_verse_line = False
verse_text = ''
song_author = ''
verse_start = False
for line in textList:
for line in text_list:
clean_line = line.strip()
if not clean_line:
if line_number == 0:
continue
elif verse_start:
if verse_text:
self.addVerse(verse_text, verse_type)
self.add_verse(verse_text, verse_type)
verse_text = ''
verse_start = False
else:
@ -286,7 +280,7 @@ class CCLIFileImport(SongImport):
if clean_line.startswith('CCLI'):
line_number += 1
ccli_parts = clean_line.split(' ')
self.ccliNumber = ccli_parts[len(ccli_parts) - 1]
self.ccli_number = ccli_parts[len(ccli_parts) - 1]
elif not verse_start:
# We have the verse descriptor
verse_desc_parts = clean_line.split(' ')
@ -348,5 +342,5 @@ class CCLIFileImport(SongImport):
author_list = song_author.split('|')
# Clean spaces before and after author names.
for author_name in author_list:
self.addAuthor(author_name.strip())
self.add_author(author_name.strip())
return self.finish()

View File

@ -87,6 +87,7 @@ class Topic(BaseModel):
"""
pass
def init_schema(url):
"""
Setup the songs database connection and initialise the database schema.
@ -183,8 +184,7 @@ def init_schema(url):
# Definition of the "media_files" table
media_files_table = Table('media_files', metadata,
Column('id', types.Integer(), primary_key=True),
Column('song_id', types.Integer(), ForeignKey('songs.id'),
default=None),
Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None),
Column('file_name', types.Unicode(255), nullable=False),
Column('type', types.Unicode(64), nullable=False, default='audio'),
Column('weight', types.Integer(), default=0)
@ -200,8 +200,7 @@ def init_schema(url):
# Definition of the "songs" table
songs_table = Table('songs', metadata,
Column('id', types.Integer(), primary_key=True),
Column('song_book_id', types.Integer(),
ForeignKey('song_books.id'), default=None),
Column('song_book_id', types.Integer(), ForeignKey('song_books.id'), default=None),
Column('title', types.Unicode(255), nullable=False),
Column('alternate_title', types.Unicode(255)),
Column('lyrics', types.UnicodeText, nullable=False),
@ -214,8 +213,7 @@ def init_schema(url):
Column('search_title', types.Unicode(255), index=True, nullable=False),
Column('search_lyrics', types.UnicodeText, nullable=False),
Column('create_date', types.DateTime(), default=func.now()),
Column('last_modified', types.DateTime(), default=func.now(),
onupdate=func.now()),
Column('last_modified', types.DateTime(), default=func.now(), onupdate=func.now()),
Column('temporary', types.Boolean(), default=False)
)
@ -227,18 +225,14 @@ def init_schema(url):
# Definition of the "authors_songs" table
authors_songs_table = Table('authors_songs', metadata,
Column('author_id', types.Integer(),
ForeignKey('authors.id'), primary_key=True),
Column('song_id', types.Integer(),
ForeignKey('songs.id'), primary_key=True)
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True)
)
# Definition of the "songs_topics" table
songs_topics_table = Table('songs_topics', metadata,
Column('song_id', types.Integer(),
ForeignKey('songs.id'), primary_key=True),
Column('topic_id', types.Integer(),
ForeignKey('topics.id'), primary_key=True)
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
Column('topic_id', types.Integer(), ForeignKey('topics.id'), primary_key=True)
)
mapper(Author, authors_table)
@ -246,13 +240,10 @@ def init_schema(url):
mapper(MediaFile, media_files_table)
mapper(Song, songs_table,
properties={
'authors': relation(Author, backref='songs',
secondary=authors_songs_table, lazy=False),
'authors': relation(Author, backref='songs', secondary=authors_songs_table, lazy=False),
'book': relation(Book, backref='songs'),
'media_files': relation(MediaFile, backref='songs',
order_by=media_files_table.c.weight),
'topics': relation(Topic, backref='songs',
secondary=songs_topics_table)
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
})
mapper(Topic, topics_table)

View File

@ -40,6 +40,7 @@ from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__)
class DreamBeamImport(SongImport):
"""
The :class:`DreamBeamImport` class provides the ability to import song files from
@ -83,7 +84,7 @@ class DreamBeamImport(SongImport):
* \*.xml
"""
def doImport(self):
def do_import(self):
"""
Receive a single file or a list of files to import.
"""
@ -92,18 +93,19 @@ class DreamBeamImport(SongImport):
for file in self.import_source:
if self.stop_import_flag:
return
self.setDefaults()
self.set_defaults()
parser = etree.XMLParser(remove_blank_text=True)
try:
parsed_file = etree.parse(open(file, 'r'), parser)
except etree.XMLSyntaxError:
log.exception('XML syntax error in file %s' % file)
self.logError(file, SongStrings.XMLSyntaxError)
self.log_error(file, SongStrings.XMLSyntaxError)
continue
xml = etree.tostring(parsed_file).decode()
song_xml = objectify.fromstring(xml)
if song_xml.tag != 'DreamSong':
self.logError(file,
self.log_error(
file,
translate('SongsPlugin.DreamBeamImport', 'Invalid DreamBeam song file. Missing DreamSong tag.'))
continue
if hasattr(song_xml, 'Version'):
@ -121,15 +123,15 @@ class DreamBeamImport(SongImport):
verse_type = lyrics_item.get('Type')
verse_number = lyrics_item.get('Number')
verse_text = str(lyrics_item.text)
self.addVerse(verse_text, ('%s%s' % (verse_type[:1], verse_number)))
self.add_verse(verse_text, ('%s%s' % (verse_type[:1], verse_number)))
if hasattr(song_xml, 'Collection'):
self.songBookName = str(song_xml.Collection.text)
self.song_book_name = str(song_xml.Collection.text)
if hasattr(song_xml, 'Number'):
self.songNumber = str(song_xml.Number.text)
self.song_number = str(song_xml.Number.text)
if hasattr(song_xml, 'Sequence'):
for LyricsSequenceItem in (song_xml.Sequence.iterchildren()):
self.verseOrderList.append("%s%s" % (LyricsSequenceItem.get('Type')[:1],
LyricsSequenceItem.get('Number')))
for lyrics_sequence_item in (song_xml.Sequence.iterchildren()):
self.verse_order_list.append("%s%s" % (lyrics_sequence_item.get('Type')[:1],
lyrics_sequence_item.get('Number')))
if hasattr(song_xml, 'Notes'):
self.comments = str(song_xml.Notes.text)
else:
@ -138,15 +140,15 @@ class DreamBeamImport(SongImport):
if hasattr(song_xml.Text1, 'Text'):
self.lyrics = str(song_xml.Text1.Text.text)
for verse in self.lyrics.split('\n\n\n'):
self.addVerse(verse)
self.add_verse(verse)
if hasattr(song_xml.Text2, 'Text'):
author_copyright = song_xml.Text2.Text.text
if author_copyright:
author_copyright = str(author_copyright)
if author_copyright.find(
str(SongStrings.CopyrightSymbol)) >= 0:
self.addCopyright(author_copyright)
self.add_copyright(author_copyright)
else:
self.parse_author(author_copyright)
if not self.finish():
self.logError(file)
self.log_error(file)

View File

@ -37,6 +37,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
class EasySlidesImport(SongImport):
"""
Import songs exported from EasySlides
@ -50,7 +51,7 @@ class EasySlidesImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
log.info('Importing EasySlides XML file %s', self.import_source)
parser = etree.XMLParser(remove_blank_text=True)
parsed_file = etree.parse(self.import_source, parser)
@ -60,18 +61,18 @@ class EasySlidesImport(SongImport):
for song in song_xml.Item:
if self.stop_import_flag:
return
self._parseSong(song)
self._parse_song(song)
def _parseSong(self, song):
def _parse_song(self, song):
self._success = True
self._add_unicode_attribute('title', song.Title1, True)
if hasattr(song, 'Title2'):
self._add_unicode_attribute('alternateTitle', song.Title2)
self._add_unicode_attribute('alternate_title', song.Title2)
if hasattr(song, 'SongNumber'):
self._add_unicode_attribute('songNumber', song.SongNumber)
if self.songNumber == '0':
self.songNumber = ''
self._addAuthors(song)
self._add_unicode_attribute('song_number', song.SongNumber)
if self.song_number == '0':
self.song_number = ''
self._add_authors(song)
if hasattr(song, 'Copyright'):
self._add_copyright(song.Copyright)
if hasattr(song, 'LicenceAdmin1'):
@ -79,13 +80,13 @@ class EasySlidesImport(SongImport):
if hasattr(song, 'LicenceAdmin2'):
self._add_copyright(song.LicenceAdmin2)
if hasattr(song, 'BookReference'):
self._add_unicode_attribute('songBookName', song.BookReference)
self._parseAndAddLyrics(song)
self._add_unicode_attribute('song_book_name', song.BookReference)
self._parse_and_add_lyrics(song)
if self._success:
if not self.finish():
self.logError(song.Title1 if song.Title1 else '')
self.log_error(song.Title1 if song.Title1 else '')
else:
self.setDefaults()
self.set_defaults()
def _add_unicode_attribute(self, self_attribute, import_attribute, mandatory=False):
"""
@ -94,14 +95,9 @@ class EasySlidesImport(SongImport):
present _success is set to False so the importer can react
appropriately.
``self_attribute``
The attribute in the song model to populate.
``import_attribute``
The imported value to convert to unicode and save to the song.
``mandatory``
Signals that this attribute must exist in a valid song.
:param self_attribute: The attribute in the song model to populate.
:param import_attribute: The imported value to convert to unicode and save to the song.
:param mandatory: Signals that this attribute must exist in a valid song.
"""
try:
setattr(self, self_attribute, str(import_attribute).strip())
@ -113,7 +109,7 @@ class EasySlidesImport(SongImport):
if mandatory:
self._success = False
def _addAuthors(self, song):
def _add_authors(self, song):
try:
authors = str(song.Writer).split(',')
self.authors = [author.strip() for author in authors if author.strip()]
@ -125,21 +121,24 @@ class EasySlidesImport(SongImport):
def _add_copyright(self, element):
"""
Add a piece of copyright to the total copyright information for the
song.
Add a piece of copyright to the total copyright information for the song.
``element``
The imported variable to get the data from.
:param element: The imported variable to get the data from.
"""
try:
self.addCopyright(str(element).strip())
self.add_copyright(str(element).strip())
except UnicodeDecodeError:
log.exception('Unicode error on decoding copyright: %s' % element)
self._success = False
except AttributeError:
pass
def _parseAndAddLyrics(self, song):
def _parse_and_add_lyrics(self, song):
"""
Process the song lyrics
:param song: The song details
"""
try:
lyrics = str(song.Contents).strip()
except UnicodeDecodeError:
@ -151,29 +150,29 @@ class EasySlidesImport(SongImport):
lines = lyrics.split('\n')
# we go over all lines first, to determine information,
# which tells us how to parse verses later
regionlines = {}
separatorlines = 0
region_lines = {}
separator_lines = 0
for line in lines:
line = line.strip()
if not line:
continue
elif line[1:7] == 'region':
# this is region separator, probably [region 2]
region = self._extractRegion(line)
regionlines[region] = 1 + regionlines.get(region, 0)
region = self._extract_region(line)
region_lines[region] = 1 + region_lines.get(region, 0)
elif line[0] == '[':
separatorlines += 1
separator_lines += 1
# if the song has separators
separators = (separatorlines > 0)
separators = (separator_lines > 0)
# the number of different regions in song - 1
if len(regionlines) > 1:
if len(region_lines) > 1:
log.info('EasySlidesImport: the file contained a song named "%s"'
'with more than two regions, but only two regions are tested, encountered regions were: %s',
self.title, ','.join(list(regionlines.keys())))
'with more than two regions, but only two regions are tested, encountered regions were: %s',
self.title, ','.join(list(region_lines.keys())))
# if the song has regions
regions = (len(regionlines) > 0)
regions = (len(region_lines) > 0)
# if the regions are inside verses
regionsInVerses = (regions and regionlines[list(regionlines.keys())[0]] > 1)
regions_in_verses = (regions and region_lines[list(region_lines.keys())[0]] > 1)
MarkTypes = {
'CHORUS': VerseType.tags[VerseType.Chorus],
'VERSE': VerseType.tags[VerseType.Verse],
@ -185,21 +184,20 @@ class EasySlidesImport(SongImport):
verses = {}
# list as [region, versetype, versenum, instance]
our_verse_order = []
defaultregion = '1'
reg = defaultregion
default_region = '1'
reg = default_region
verses[reg] = {}
# instance differentiates occurrences of same verse tag
vt = 'V'
vn = '1'
inst = 1
for line in lines:
line = line.strip()
if not line:
if separators:
# separators are used, so empty line means slide break
# inside verse
if self._listHas(verses, [reg, vt, vn, inst]):
if self._list_has(verses, [reg, vt, vn, inst]):
inst += 1
else:
# separators are not used, so empty line starts a new verse
@ -207,9 +205,9 @@ class EasySlidesImport(SongImport):
vn = len(verses[reg].get(vt, {})) + 1
inst = 1
elif line[0:7] == '[region':
reg = self._extractRegion(line)
reg = self._extract_region(line)
verses.setdefault(reg, {})
if not regionsInVerses:
if not regions_in_verses:
vt = 'V'
vn = '1'
inst = 1
@ -224,10 +222,10 @@ class EasySlidesImport(SongImport):
marker = match.group(1).strip()
vn = match.group(2)
vt = MarkTypes.get(marker, 'O') if marker else 'V'
if regionsInVerses:
region = defaultregion
if regions_in_verses:
region = default_region
inst = 1
if self._listHas(verses, [reg, vt, vn, inst]):
if self._list_has(verses, [reg, vt, vn, inst]):
inst = len(verses[reg][vt][vn]) + 1
else:
if not [reg, vt, vn, inst] in our_verse_order:
@ -235,21 +233,19 @@ class EasySlidesImport(SongImport):
verses[reg].setdefault(vt, {})
verses[reg][vt].setdefault(vn, {})
verses[reg][vt][vn].setdefault(inst, [])
verses[reg][vt][vn][inst].append(self.tidyText(line))
verses[reg][vt][vn][inst].append(self.tidy_text(line))
# done parsing
versetags = []
# we use our_verse_order to ensure, we insert lyrics in the same order
# as these appeared originally in the file
for [reg, vt, vn, inst] in our_verse_order:
if self._listHas(verses, [reg, vt, vn, inst]):
if self._list_has(verses, [reg, vt, vn, inst]):
# this is false, but needs user input
lang = None
versetag = '%s%s' % (vt, vn)
versetags.append(versetag)
lines = '\n'.join(verses[reg][vt][vn][inst])
self.verses.append([versetag, lines, lang])
SeqTypes = {
'p': 'P1',
'q': 'P2',
@ -271,25 +267,37 @@ class EasySlidesImport(SongImport):
else:
continue
if tag in versetags:
self.verseOrderList.append(tag)
self.verse_order_list.append(tag)
else:
log.info('Got order item %s, which is not in versetags, dropping item from presentation order',
tag)
log.info('Got order item %s, which is not in versetags, dropping item from presentation order', tag)
except UnicodeDecodeError:
log.exception('Unicode decode error while decoding Sequence')
self._success = False
except AttributeError:
pass
def _listHas(self, lst, subitems):
for subitem in subitems:
if subitem in lst:
lst = lst[subitem]
def _list_has(self, lst, sub_items):
"""
See if the list has sub items
:param lst: The list to check
:param sub_items: sub item list
:return:
"""
for sub_item in sub_items:
if sub_item in lst:
lst = lst[sub_item]
else:
return False
return True
def _extractRegion(self, line):
def _extract_region(self, line):
# this was true already: line[0:7] == u'[region':
"""
Extract the region from text
:param line: The line of text
:return:
"""
right_bracket = line.find(']')
return line[7:right_bracket].strip()

View File

@ -75,7 +75,12 @@ class EasyWorshipSongImport(SongImport):
def __init__(self, manager, **kwargs):
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Import the songs
:return:
"""
# Open the DB and MB files if they exist
import_source_mb = self.import_source.replace('.DB', '.MB')
if not os.path.isfile(self.import_source) or not os.path.isfile(import_source_mb):
@ -84,12 +89,12 @@ class EasyWorshipSongImport(SongImport):
if db_size < 0x800:
return
db_file = open(self.import_source, 'rb')
self.memoFile = open(import_source_mb, 'rb')
self.memo_file = open(import_source_mb, 'rb')
# Don't accept files that are clearly not paradox files
record_size, header_size, block_size, first_block, num_fields = struct.unpack('<hhxb8xh17xh', db_file.read(35))
if header_size != 0x800 or block_size < 1 or block_size > 4:
db_file.close()
self.memoFile.close()
self.memo_file.close()
return
# Take a stab at how text is encoded
self.encoding = 'cp1252'
@ -124,20 +129,20 @@ class EasyWorshipSongImport(SongImport):
db_file.seek(4 + (num_fields * 4) + 261, os.SEEK_CUR)
field_names = db_file.read(header_size - db_file.tell()).split(b'\0', num_fields)
field_names.pop()
field_descs = []
field_descriptions = []
for i, field_name in enumerate(field_names):
field_type, field_size = struct.unpack_from('BB', field_info, i * 2)
field_descs.append(FieldDescEntry(field_name, field_type, field_size))
self.setRecordStruct(field_descs)
field_descriptions.append(FieldDescEntry(field_name, field_type, field_size))
self.set_record_struct(field_descriptions)
# Pick out the field description indexes we will need
try:
success = True
fi_title = self.findField(b'Title')
fi_author = self.findField(b'Author')
fi_copy = self.findField(b'Copyright')
fi_admin = self.findField(b'Administrator')
fi_words = self.findField(b'Words')
fi_ccli = self.findField(b'Song Number')
fi_title = self.find_field(b'Title')
fi_author = self.find_field(b'Author')
fi_copy = self.find_field(b'Copyright')
fi_admin = self.find_field(b'Administrator')
fi_words = self.find_field(b'Words')
fi_ccli = self.find_field(b'Song Number')
except IndexError:
# This is the wrong table
success = False
@ -162,15 +167,15 @@ class EasyWorshipSongImport(SongImport):
if self.stop_import_flag:
break
raw_record = db_file.read(record_size)
self.fields = self.recordStruct.unpack(raw_record)
self.setDefaults()
self.title = self.getField(fi_title).decode()
self.fields = self.record_structure.unpack(raw_record)
self.set_defaults()
self.title = self.get_field(fi_title).decode()
# Get remaining fields.
copy = self.getField(fi_copy)
admin = self.getField(fi_admin)
ccli = self.getField(fi_ccli)
authors = self.getField(fi_author)
words = self.getField(fi_words)
copy = self.get_field(fi_copy)
admin = self.get_field(fi_admin)
ccli = self.get_field(fi_ccli)
authors = self.get_field(fi_author)
words = self.get_field(fi_words)
# Set the SongImport object members.
if copy:
self.copyright = copy.decode()
@ -180,7 +185,7 @@ class EasyWorshipSongImport(SongImport):
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
'Administered by %s') % admin.decode()
if ccli:
self.ccliNumber = ccli.decode()
self.ccli_number = ccli.decode()
if authors:
# Split up the authors
author_list = authors.split(b'/')
@ -189,7 +194,7 @@ class EasyWorshipSongImport(SongImport):
if len(author_list) < 2:
author_list = authors.split(b',')
for author_name in author_list:
self.addAuthor(author_name.decode().strip())
self.add_author(author_name.decode().strip())
if words:
# Format the lyrics
result = strip_rtf(words.decode(), self.encoding)
@ -227,24 +232,35 @@ class EasyWorshipSongImport(SongImport):
if not number_found:
verse_type += '1'
break
self.addVerse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
if len(self.comments) > 5:
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
'\n[above are Song Tags with notes imported from EasyWorship]'))
'\n[above are Song Tags with notes imported from EasyWorship]'))
if self.stop_import_flag:
break
if not self.finish():
self.logError(self.import_source)
self.log_error(self.import_source)
db_file.close()
self.memoFile.close()
self.memo_file.close()
def findField(self, field_name):
return [i for i, x in enumerate(self.fieldDescs) if x.name == field_name][0]
def find_field(self, field_name):
"""
Find a field in the descriptions
def setRecordStruct(self, field_descs):
:param field_name: field to find
:return:
"""
return [i for i, x in enumerate(self.field_descriptions) if x.name == field_name][0]
def set_record_struct(self, field_descriptions):
"""
Save the record structure
:param field_descriptions: An array of field descriptions
"""
# Begin with empty field struct list
fsl = ['>']
for field_desc in field_descs:
for field_desc in field_descriptions:
if field_desc.field_type == FieldType.String:
fsl.append('%ds' % field_desc.size)
elif field_desc.field_type == FieldType.Int16:
@ -261,12 +277,18 @@ class EasyWorshipSongImport(SongImport):
fsl.append('Q')
else:
fsl.append('%ds' % field_desc.size)
self.recordStruct = struct.Struct(''.join(fsl))
self.fieldDescs = field_descs
self.record_structure = struct.Struct(''.join(fsl))
self.field_descriptions = field_descriptions
def getField(self, field_desc_index):
def get_field(self, field_desc_index):
"""
Extract the field
:param field_desc_index: Field index value
:return:
"""
field = self.fields[field_desc_index]
field_desc = self.fieldDescs[field_desc_index]
field_desc = self.field_descriptions[field_desc_index]
# Return None in case of 'blank' entries
if isinstance(field, bytes):
if not field.rstrip(b'\0'):
@ -281,23 +303,23 @@ class EasyWorshipSongImport(SongImport):
elif field_desc.field_type == FieldType.Int32:
return field ^ 0x80000000
elif field_desc.field_type == FieldType.Logical:
return (field ^ 0x80 == 1)
return field ^ 0x80 == 1
elif field_desc.field_type == FieldType.Memo or field_desc.field_type == FieldType.Blob:
block_start, blob_size = struct.unpack_from('<II', field, len(field)-10)
sub_block = block_start & 0xff
block_start &= ~0xff
self.memoFile.seek(block_start)
memo_block_type, = struct.unpack('b', self.memoFile.read(1))
self.memo_file.seek(block_start)
memo_block_type, = struct.unpack('b', self.memo_file.read(1))
if memo_block_type == 2:
self.memoFile.seek(8, os.SEEK_CUR)
self.memo_file.seek(8, os.SEEK_CUR)
elif memo_block_type == 3:
if sub_block > 63:
return b''
self.memoFile.seek(11 + (5 * sub_block), os.SEEK_CUR)
sub_block_start, = struct.unpack('B', self.memoFile.read(1))
self.memoFile.seek(block_start + (sub_block_start * 16))
self.memo_file.seek(11 + (5 * sub_block), os.SEEK_CUR)
sub_block_start, = struct.unpack('B', self.memo_file.read(1))
self.memo_file.seek(block_start + (sub_block_start * 16))
else:
return b''
return self.memoFile.read(blob_size)
return self.memo_file.read(blob_size)
else:
return 0

View File

@ -106,6 +106,7 @@ from openlp.plugins.songs.lib.xml import SongXML
log = logging.getLogger(__name__)
class FoilPresenterImport(SongImport):
"""
This provides the Foilpresenter import.
@ -116,9 +117,9 @@ class FoilPresenterImport(SongImport):
"""
log.debug('initialise FoilPresenterImport')
SongImport.__init__(self, manager, **kwargs)
self.FoilPresenter = FoilPresenter(self.manager, self)
self.foil_presenter = FoilPresenter(self.manager, self)
def doImport(self):
def do_import(self):
"""
Imports the songs.
"""
@ -131,9 +132,9 @@ class FoilPresenterImport(SongImport):
try:
parsed_file = etree.parse(file_path, parser)
xml = etree.tostring(parsed_file).decode()
self.FoilPresenter.xml_to_song(xml)
self.foil_presenter.xml_to_song(xml)
except etree.XMLSyntaxError:
self.logError(file_path, SongStrings.XMLSyntaxError)
self.log_error(file_path, SongStrings.XMLSyntaxError)
log.exception('XML syntax error in file %s' % file_path)
@ -245,8 +246,7 @@ class FoilPresenter(object):
"""
This returns the text of an element as unicode string.
``element``
The element.
:param element: The element
"""
if element is not None:
return str(element)
@ -256,11 +256,8 @@ class FoilPresenter(object):
"""
Adds the authors specified in the XML to the song.
``foilpresenterfolie``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
authors = []
try:
@ -324,8 +321,7 @@ class FoilPresenter(object):
break
author_temp = []
for author in strings:
temp = re.split(',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);',
author)
temp = re.split(',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author)
for tempx in temp:
author_temp.append(tempx)
for author in author_temp:
@ -349,7 +345,7 @@ class FoilPresenter(object):
if author is None:
# We need to create a new author, as the author does not exist.
author = Author.populate(display_name=display_name, last_name=display_name.split(' ')[-1],
first_name=' '.join(display_name.split(' ')[:-1]))
first_name=' '.join(display_name.split(' ')[:-1]))
self.manager.save_object(author)
song.authors.append(author)
@ -357,11 +353,8 @@ class FoilPresenter(object):
"""
Adds the CCLI number to the song.
``foilpresenterfolie``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
try:
song.ccli_number = self._child(foilpresenterfolie.ccliid)
@ -372,11 +365,8 @@ class FoilPresenter(object):
"""
Joins the comments specified in the XML and add it to the song.
``foilpresenterfolie``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
try:
song.comments = self._child(foilpresenterfolie.notiz)
@ -387,11 +377,8 @@ class FoilPresenter(object):
"""
Adds the copyright to the song.
``foilpresenterfolie``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
try:
song.copyright = self._child(foilpresenterfolie.copyright.text_)
@ -402,11 +389,8 @@ class FoilPresenter(object):
"""
Processes the verses and search_lyrics for the song.
``foilpresenterfolie``
The foilpresenterfolie object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The foilpresenterfolie object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
sxml = SongXML()
temp_verse_order = {}
@ -423,9 +407,9 @@ class FoilPresenter(object):
VerseType.tags[VerseType.PreChorus]: 1
}
if not hasattr(foilpresenterfolie.strophen, 'strophe'):
self.importer.logError(self._child(foilpresenterfolie.titel),
str(translate('SongsPlugin.FoilPresenterSongImport',
'Invalid Foilpresenter song file. No verses found.')))
self.importer.log_error(self._child(foilpresenterfolie.titel),
str(translate('SongsPlugin.FoilPresenterSongImport',
'Invalid Foilpresenter song file. No verses found.')))
self.save_song = False
return
for strophe in foilpresenterfolie.strophen.strophe:
@ -478,10 +462,8 @@ class FoilPresenter(object):
verse_number = str(int(verse_number) + 1)
verse_type_index = VerseType.from_tag(verse_type[0])
verse_type = VerseType.tags[verse_type_index]
temp_verse_order[verse_sortnr] = ''.join((verse_type[0],
verse_number))
temp_verse_order_backup.append(''.join((verse_type[0],
verse_number)))
temp_verse_order[verse_sortnr] = ''.join((verse_type[0], verse_number))
temp_verse_order_backup.append(''.join((verse_type[0], verse_number)))
sxml.add_verse_to_lyrics(verse_type, verse_number, text)
song.lyrics = str(sxml.extract_xml(), 'utf-8')
# Process verse order
@ -506,11 +488,8 @@ class FoilPresenter(object):
"""
Adds the song book and song number specified in the XML to the song.
``foilpresenterfolie``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
song.song_book_id = 0
song.song_number = ''
@ -518,8 +497,7 @@ class FoilPresenter(object):
for bucheintrag in foilpresenterfolie.buch.bucheintrag:
book_name = self._child(bucheintrag.name)
if book_name:
book = self.manager.get_object_filtered(Book,
Book.name == book_name)
book = self.manager.get_object_filtered(Book, Book.name == book_name)
if book is None:
# We need to create a book, because it does not exist.
book = Book.populate(name=book_name, publisher='')
@ -539,11 +517,8 @@ class FoilPresenter(object):
"""
Processes the titles specified in the song's XML.
``foilpresenterfolie``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
try:
for title_string in foilpresenterfolie.titel.titelstring:
@ -561,18 +536,14 @@ class FoilPresenter(object):
"""
Adds the topics to the song.
``foilpresenterfolie``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param foilpresenterfolie: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
try:
for name in foilpresenterfolie.kategorien.name:
topic_text = self._child(name)
if topic_text:
topic = self.manager.get_object_filtered(Topic,
Topic.name == topic_text)
topic = self.manager.get_object_filtered(Topic, Topic.name == topic_text)
if topic is None:
# We need to create a topic, because it does not exist.
topic = Topic.populate(name=topic_text)

View File

@ -82,6 +82,7 @@ if os.name == 'nt':
except ImportError:
log.exception('Error importing %s', 'WorshipCenterProImport')
class SongFormatSelect(object):
"""
This is a special enumeration class listing available file selection modes.
@ -99,47 +100,47 @@ class SongFormat(object):
Required attributes for each song format:
``u'class'``
``'class'``
Import class, e.g. ``OpenLyricsImport``
``u'name'``
Name of the format, e.g. ``u'OpenLyrics'``
``'name'``
Name of the format, e.g. ``'OpenLyrics'``
``u'prefix'``
Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'``
``'prefix'``
Prefix for Qt objects. Use mixedCase, e.g. ``'open_lyrics'``
See ``SongImportForm.add_file_select_item()``
Optional attributes for each song format:
``u'canDisable'``
``'canDisable'``
Whether song format importer is disablable.
If ``True``, then ``u'disabledLabelText'`` must also be defined.
If ``True``, then ``'disabledLabelText'`` must also be defined.
``u'availability'``
``'availability'``
Whether song format importer is available.
``u'selectMode'``
``'selectMode'``
Whether format accepts single file, multiple files, or single folder
(as per ``SongFormatSelect`` options).
``u'filter'``
``'filter'``
File extension filter for ``QFileDialog``.
Optional/custom text Strings for ``SongImportForm`` widgets:
``u'comboBoxText'``
Combo box selector (default value is the format's ``u'name'``).
``'comboBoxText'``
Combo box selector (default value is the format's ``'name'``).
``u'disabledLabelText'``
``'disabledLabelText'``
Required for disablable song formats.
``u'getFilesTitle'``
Title for ``QFileDialog`` (default includes the format's ``u'name'``).
``'getFilesTitle'``
Title for ``QFileDialog`` (default includes the format's ``'name'``).
``u'invalidSourceMsg'``
Message displayed if ``isValidSource()`` returns ``False``.
``'invalidSourceMsg'``
Message displayed if ``is_valid_source()`` returns ``False``.
``u'descriptionText'``
``'descriptionText'``
Short description (1-2 lines) about the song format.
"""
# Song formats (ordered alphabetically after Generic)
@ -184,7 +185,7 @@ class SongFormat(object):
OpenLyrics: {
'class': OpenLyricsImport,
'name': 'OpenLyrics',
'prefix': 'openLyrics',
'prefix': 'open_lyrics',
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files'),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'OpenLyrics or OpenLP 2.0 Exported Song')
},
@ -200,8 +201,8 @@ class SongFormat(object):
'prefix': 'generic',
'canDisable': True,
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The generic document/presentation importer has been disabled '
'because OpenLP cannot access OpenOffice or LibreOffice.'),
'The generic document/presentation importer has been disabled '
'because OpenLP cannot access OpenOffice or LibreOffice.'),
'getFilesTitle': translate('SongsPlugin.ImportWizardForm', 'Select Document/Presentation Files')
},
CCLI: {
@ -241,13 +242,12 @@ class SongFormat(object):
'prefix': 'mediaShout',
'canDisable': True,
'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm',
'MediaShout Database'),
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'MediaShout Database'),
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The MediaShout importer is only supported on Windows. It has '
'been disabled due to a missing Python module. If you want to '
'use this importer, you will need to install the "pyodbc" '
'module.')
'The MediaShout importer is only supported on Windows. It has '
'been disabled due to a missing Python module. If you want to '
'use this importer, you will need to install the "pyodbc" '
'module.')
},
OpenSong: {
'class': OpenSongImport,
@ -259,15 +259,14 @@ class SongFormat(object):
'name': 'PowerSong 1.0',
'prefix': 'powerSong',
'selectMode': SongFormatSelect.SingleFolder,
'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm',
'You need to specify a valid PowerSong 1.0 database folder.')
'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm', 'You need to specify a valid PowerSong 1.0 '
'database folder.')
},
SongBeamer: {
'class': SongBeamerImport,
'name': 'SongBeamer',
'prefix': 'songBeamer',
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm',
'SongBeamer Files')
'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm', 'SongBeamer Files')
},
SongPro: {
'class': SongProImport,
@ -277,7 +276,7 @@ class SongFormat(object):
'filter': '%s (*.txt)' % translate('SongsPlugin.ImportWizardForm', 'SongPro Text Files'),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'SongPro (Export File)'),
'descriptionText': translate('SongsPlugin.ImportWizardForm',
'In SongPro, export your songs using the File -> Export menu')
'In SongPro, export your songs using the File -> Export menu')
},
SongShowPlus: {
'class': SongShowPlusImport,
@ -291,8 +290,8 @@ class SongFormat(object):
'canDisable': True,
'filter': '%s (*.rtf)' % translate('SongsPlugin.ImportWizardForm', 'Songs Of Fellowship Song Files'),
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The Songs of Fellowship importer has been disabled because '
'OpenLP cannot access OpenOffice or LibreOffice.')
'The Songs of Fellowship importer has been disabled because '
'OpenLP cannot access OpenOffice or LibreOffice.')
},
SundayPlus: {
'class': SundayPlusImport,
@ -304,8 +303,7 @@ class SongFormat(object):
'class': WowImport,
'name': 'Words of Worship',
'prefix': 'wordsOfWorship',
'filter': '%s (*.wsg *.wow-song)' %
translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
'filter': '%s (*.wsg *.wow-song)' % translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
},
WorshipCenterPro: {
'name': 'WorshipCenter Pro',
@ -314,8 +312,9 @@ class SongFormat(object):
'selectMode': SongFormatSelect.SingleFile,
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'WorshipCenter Pro Song Files'),
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The WorshipCenter Pro importer is only supported on Windows. It has been disabled due to a missing '
'Python module. If you want to use this importer, you will need to install the "pyodbc" module.')
'The WorshipCenter Pro importer is only supported on Windows. It has been '
'disabled due to a missing Python module. If you want to use this '
'importer, you will need to install the "pyodbc" module.')
},
ZionWorx: {
'class': ZionWorxImport,
@ -324,9 +323,9 @@ class SongFormat(object):
'selectMode': SongFormatSelect.SingleFile,
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'ZionWorx (CSV)'),
'descriptionText': translate('SongsPlugin.ImportWizardForm',
'First convert your ZionWorx database to a CSV text file, as '
'explained in the <a href="http://manual.openlp.org/songs.html'
'#importing-from-zionworx">User Manual</a>.')
'First convert your ZionWorx database to a CSV text file, as '
'explained in the <a href="http://manual.openlp.org/songs.html'
'#importing-from-zionworx">User Manual</a>.')
}
}

View File

@ -36,8 +36,8 @@ from PyQt4 import QtCore, QtGui
from sqlalchemy.sql import or_
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
check_item_selected, create_separated_list
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, check_item_selected, \
create_separated_list
from openlp.core.lib.ui import create_widget_action
from openlp.plugins.songs.forms.editsongform import EditSongForm
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
@ -97,21 +97,21 @@ class SongMediaItem(MediaManagerItem):
def add_end_header_bar(self):
self.toolbar.addSeparator()
## Song Maintenance Button ##
self.maintenanceAction = self.toolbar.add_toolbar_action('maintenanceAction',
icon=':/songs/song_maintenance.png',
triggers=self.on_song_maintenance_click)
self.maintenance_action = self.toolbar.add_toolbar_action('maintenance_action',
icon=':/songs/song_maintenance.png',
triggers=self.on_song_maintenance_click)
self.add_search_to_toolbar()
# Signals and slots
Registry().register_function('songs_load_list', self.on_song_list_load)
Registry().register_function('songs_preview', self.on_preview_click)
QtCore.QObject.connect(self.search_text_edit, QtCore.SIGNAL('cleared()'), self.on_clear_text_button_click)
QtCore.QObject.connect(self.search_text_edit, QtCore.SIGNAL('searchTypeChanged(int)'),
self.on_search_text_button_clicked)
QtCore.QObject.connect(
self.search_text_edit, QtCore.SIGNAL('searchTypeChanged(int)'), self.on_search_text_button_clicked)
def add_custom_context_actions(self):
create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view,
text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png',
create_widget_action(
self.list_view, text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png',
triggers=self.on_clone_click)
def on_focus(self):
@ -123,15 +123,15 @@ class SongMediaItem(MediaManagerItem):
"""
log.debug('config_updated')
self.search_as_you_type = Settings().value(self.settings_section + '/search as type')
self.updateServiceOnEdit = Settings().value(self.settings_section + '/update service on edit')
self.addSongFromService = Settings().value(self.settings_section + '/add song from service',)
self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit')
self.add_song_from_service = Settings().value(self.settings_section + '/add song from service',)
def retranslateUi(self):
self.search_text_label.setText('%s:' % UiStrings().Search)
self.search_text_button.setText(UiStrings().Search)
self.maintenanceAction.setText(SongStrings.SongMaintenance)
self.maintenanceAction.setToolTip(translate('SongsPlugin.MediaItem',
'Maintain the lists of authors, topics and books.'))
self.maintenance_action.setText(SongStrings.SongMaintenance)
self.maintenance_action.setToolTip(translate('SongsPlugin.MediaItem',
'Maintain the lists of authors, topics and books.'))
def initialise(self):
"""
@ -139,7 +139,7 @@ class SongMediaItem(MediaManagerItem):
"""
self.song_maintenance_form = SongMaintenanceForm(self.plugin.manager, self)
self.edit_song_form = EditSongForm(self, self.main_window, self.plugin.manager)
self.openLyrics = OpenLyrics(self.plugin.manager)
self.open_lyrics = OpenLyrics(self.plugin.manager)
self.search_text_edit.set_search_types([
(SongSearch.Entire, ':/songs/song_search_all.png',
translate('SongsPlugin.MediaItem', 'Entire Song'),
@ -154,8 +154,7 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem', 'Search Authors...')),
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
translate('SongsPlugin.MediaItem', 'Search Song Books...')),
(SongSearch.Themes, ':/slides/slide_theme.png',
UiStrings().Themes, UiStrings().SearchThemes)
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)
])
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
self.config_update()
@ -172,64 +171,65 @@ class SongMediaItem(MediaManagerItem):
self.display_results_song(search_results)
elif search_type == SongSearch.Titles:
log.debug('Titles Search')
search_results = self.plugin.manager.get_all_objects(Song,
Song.search_title.like('%' + clean_string(search_keywords) + '%'))
search_string = '%' + clean_string(search_keywords) + '%'
search_results = self.plugin.manager.get_all_objects(Song, Song.search_title.like(search_string))
self.display_results_song(search_results)
elif search_type == SongSearch.Lyrics:
log.debug('Lyrics Search')
search_results = self.plugin.manager.get_all_objects(Song,
Song.search_lyrics.like('%' + clean_string(search_keywords) + '%'))
search_string = '%' + clean_string(search_keywords) + '%'
search_results = self.plugin.manager.get_all_objects(Song, Song.search_lyrics.like(search_string))
self.display_results_song(search_results)
elif search_type == SongSearch.Authors:
log.debug('Authors Search')
search_results = self.plugin.manager.get_all_objects(Author,
Author.display_name.like('%' + search_keywords + '%'), Author.display_name.asc())
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(
Author, Author.display_name.like(search_string), Author.display_name.asc())
self.display_results_author(search_results)
elif search_type == SongSearch.Books:
log.debug('Books Search')
search_results = self.plugin.manager.get_all_objects(Book,
Book.name.like('%' + search_keywords + '%'), Book.name.asc())
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(Book, Book.name.like(search_string), Book.name.asc())
song_number = False
if not search_results:
search_keywords = search_keywords.rpartition(' ')
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(Book,
Book.name.like('%' + search_keywords[0] + '%'), Book.name.asc())
Book.name.like(search_string), Book.name.asc())
song_number = re.sub(r'[^0-9]', '', search_keywords[2])
self.display_results_book(search_results, song_number)
elif search_type == SongSearch.Themes:
log.debug('Theme Search')
search_results = self.plugin.manager.get_all_objects(Song,
Song.theme_name.like('%' + search_keywords + '%'))
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(Song, Song.theme_name.like(search_string))
self.display_results_song(search_results)
self.check_search_result()
def search_entire(self, search_keywords):
return self.plugin.manager.get_all_objects(Song,
or_(Song.search_title.like('%' + clean_string(search_keywords) + '%'),
Song.search_lyrics.like('%' + clean_string(search_keywords) + '%'),
Song.comments.like('%' + search_keywords.lower() + '%')))
search_string = '%' + clean_string(search_keywords) + '%'
return self.plugin.manager.get_all_objects(
Song, or_(Song.search_title.like(search_string), Song.search_lyrics.like(search_string),
Song.comments.like(search_string)))
def on_song_list_load(self):
"""
Handle the exit from the edit dialog and trigger remote updates
of songs
Handle the exit from the edit dialog and trigger remote updates of songs
"""
log.debug('on_song_list_load - start')
# Called to redisplay the song list screen edit from a search or from the exit of the Song edit dialog. If
# remote editing is active Trigger it and clean up so it will not update again. Push edits to the service
# manager to update items
if self.edit_item and self.updateServiceOnEdit and not self.remote_triggered:
if self.edit_item and self.update_service_on_edit and not self.remote_triggered:
item = self.build_service_item(self.edit_item)
self.service_manager.replace_service_item(item)
self.on_search_text_button_clicked()
log.debug('on_song_list_load - finished')
def display_results_song(self, searchresults):
def display_results_song(self, search_results):
log.debug('display results Song')
self.save_auto_select_id()
self.list_view.clear()
searchresults.sort(key=lambda song: song.sort_key)
for song in searchresults:
search_results.sort(key=lambda song: song.sort_key)
for song in search_results:
# Do not display temporary songs
if song.temporary:
continue
@ -244,10 +244,10 @@ class SongMediaItem(MediaManagerItem):
self.list_view.setCurrentItem(song_name)
self.auto_select_id = -1
def display_results_author(self, searchresults):
def display_results_author(self, search_results):
log.debug('display results Author')
self.list_view.clear()
for author in searchresults:
for author in search_results:
for song in author.songs:
# Do not display temporary songs
if song.temporary:
@ -257,12 +257,11 @@ class SongMediaItem(MediaManagerItem):
song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name)
def display_results_book(self, searchresults, song_number=False):
def display_results_book(self, search_results, song_number=False):
log.debug('display results Book')
self.list_view.clear()
for book in searchresults:
songs = sorted(book.songs, key=lambda song:
int(re.match(r'[0-9]+', '0' + song.song_number).group()))
for book in search_results:
songs = sorted(book.songs, key=lambda song: int(re.match(r'[0-9]+', '0' + song.song_number).group()))
for song in songs:
# Do not display temporary songs
if song.temporary:
@ -305,9 +304,9 @@ class SongMediaItem(MediaManagerItem):
Registry().execute('songs_load_list')
def on_export_click(self):
if not hasattr(self, 'exportWizard'):
self.exportWizard = SongExportForm(self, self.plugin)
self.exportWizard.exec_()
if not hasattr(self, 'export_wizard'):
self.export_wizard = SongExportForm(self, self.plugin)
self.export_wizard.exec_()
def on_new_click(self):
log.debug('on_new_click')
@ -362,10 +361,10 @@ class SongMediaItem(MediaManagerItem):
"""
if check_item_selected(self.list_view, UiStrings().SelectDelete):
items = self.list_view.selectedIndexes()
if QtGui.QMessageBox.question(self,
UiStrings().ConfirmDelete,
if QtGui.QMessageBox.question(
self, UiStrings().ConfirmDelete,
translate('SongsPlugin.MediaItem', 'Are you sure you want to delete the %n selected song(s)?', '',
QtCore.QCoreApplication.CodecForTr, len(items)),
QtCore.QCoreApplication.CodecForTr, len(items)),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
return
@ -388,17 +387,23 @@ class SongMediaItem(MediaManagerItem):
self.edit_item = self.list_view.currentItem()
item_id = self.edit_item.data(QtCore.Qt.UserRole)
old_song = self.plugin.manager.get_object(Song, item_id)
song_xml = self.openLyrics.song_to_xml(old_song)
new_song = self.openLyrics.xml_to_song(song_xml)
new_song.title = '%s <%s>' % (new_song.title,
translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
song_xml = self.open_lyrics.song_to_xml(old_song)
new_song = self.open_lyrics.xml_to_song(song_xml)
new_song.title = '%s <%s>' % \
(new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
self.plugin.manager.save_object(new_song)
self.on_song_list_load()
def generate_slide_data(self, service_item, item=None, xmlVersion=False,
remote=False, context=ServiceItemContext.Service):
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Service):
"""
Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on
:param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated
"""
log.debug('generate_slide_data: %s, %s, %s' % (service_item, item, self.remote_song))
item_id = self._get_id_of_item_to_generate(item, self.remote_song)
@ -411,50 +416,45 @@ class SongMediaItem(MediaManagerItem):
song = self.plugin.manager.get_object(Song, item_id)
service_item.theme = song.theme_name
service_item.edit_id = item_id
if song.lyrics.startswith('<?xml version='):
verse_list = SongXML().get_verses(song.lyrics)
# no verse list or only 1 space (in error)
verse_tags_translated = False
if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None:
verse_tags_translated = True
if not song.verse_order.strip():
for verse in verse_list:
# We cannot use from_loose_input() here, because database is supposed to contain English lowercase
# singlechar tags.
verse_tag = verse[0]['type']
verse_index = None
if len(verse_tag) > 1:
verse_index = VerseType.from_translated_string(verse_tag)
if verse_index is None:
verse_index = VerseType.from_string(verse_tag, None)
verse_list = SongXML().get_verses(song.lyrics)
# no verse list or only 1 space (in error)
verse_tags_translated = False
if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None:
verse_tags_translated = True
if not song.verse_order.strip():
for verse in verse_list:
# We cannot use from_loose_input() here, because database is supposed to contain English lowercase
# singlechar tags.
verse_tag = verse[0]['type']
verse_index = None
if len(verse_tag) > 1:
verse_index = VerseType.from_translated_string(verse_tag)
if verse_index is None:
verse_index = VerseType.from_tag(verse_tag)
verse_tag = VerseType.translated_tags[verse_index].upper()
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
service_item.add_from_text(str(verse[1]), verse_def)
else:
# Loop through the verse list and expand the song accordingly.
for order in song.verse_order.lower().split():
if not order:
break
for verse in verse_list:
if verse[0]['type'][0].lower() == order[0] and (verse[0]['label'].lower() == order[1:] or \
not order[1:]):
if verse_tags_translated:
verse_index = VerseType.from_translated_tag(verse[0]['type'])
else:
verse_index = VerseType.from_tag(verse[0]['type'])
verse_tag = VerseType.translated_tags[verse_index]
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
service_item.add_from_text(verse[1], verse_def)
verse_index = VerseType.from_string(verse_tag, None)
if verse_index is None:
verse_index = VerseType.from_tag(verse_tag)
verse_tag = VerseType.translated_tags[verse_index].upper()
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
service_item.add_from_text(str(verse[1]), verse_def)
else:
verses = song.lyrics.split('\n\n')
for slide in verses:
service_item.add_from_text(str(slide))
# Loop through the verse list and expand the song accordingly.
for order in song.verse_order.lower().split():
if not order:
break
for verse in verse_list:
if verse[0]['type'][0].lower() == \
order[0] and (verse[0]['label'].lower() == order[1:] or not order[1:]):
if verse_tags_translated:
verse_index = VerseType.from_translated_tag(verse[0]['type'])
else:
verse_index = VerseType.from_tag(verse[0]['type'])
verse_tag = VerseType.translated_tags[verse_index]
verse_def = '%s%s' % (verse_tag, verse[0]['label'])
service_item.add_from_text(verse[1], verse_def)
service_item.title = song.title
author_list = self.generate_footer(service_item, song)
service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)}
service_item.xml_version = self.openLyrics.song_to_xml(song)
service_item.xml_version = self.open_lyrics.song_to_xml(song)
# Add the audio file to the service item.
if song.media_files:
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
@ -466,11 +466,8 @@ class SongMediaItem(MediaManagerItem):
Generates the song footer based on a song and adds details to a service item.
author_list is only required for initial song generation.
``item``
The service item to be amended
``song``
The song to be used to generate the footer
:param item: The service item to be amended
:param song: The song to be used to generate the footer
"""
author_list = [str(author.display_name) for author in song.authors]
item.audit = [
@ -481,8 +478,8 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer.append(create_separated_list(author_list))
item.raw_footer.append(song.copyright)
if Settings().value('core/ccli number'):
item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') +
Settings().value('core/ccli number'))
item.raw_footer.append(translate('SongsPlugin.MediaItem',
'CCLI License: ') + Settings().value('core/ccli number'))
return author_list
def service_load(self, item):
@ -500,8 +497,8 @@ class SongMediaItem(MediaManagerItem):
Song.search_title == (re.compile(r'\W+', re.UNICODE).sub(' ',
item.data_string['title'].strip()) + '@').strip().lower(), Song.search_title.asc())
else:
search_results = self.plugin.manager.get_all_objects(Song,
Song.search_title == item.data_string['title'], Song.search_title.asc())
search_results = self.plugin.manager.get_all_objects(
Song, Song.search_title == item.data_string['title'], Song.search_title.asc())
edit_id = 0
add_song = True
if search_results:
@ -521,16 +518,16 @@ class SongMediaItem(MediaManagerItem):
# If there's any backing tracks, copy them over.
if item.background_audio:
self._update_background_audio(song, item)
if add_song and self.addSongFromService:
song = self.openLyrics.xml_to_song(item.xml_version)
if add_song and self.add_song_from_service:
song = self.open_lyrics.xml_to_song(item.xml_version)
# If there's any backing tracks, copy them over.
if item.background_audio:
self._update_background_audio(song, item)
editId = song.id
edit_id = song.id
self.on_search_text_button_clicked()
elif add_song and not self.addSongFromService:
elif add_song and not self.add_song_from_service:
# Make sure we temporary import formatting tags.
song = self.openLyrics.xml_to_song(item.xml_version, True)
song = self.open_lyrics.xml_to_song(item.xml_version, True)
# If there's any backing tracks, copy them over.
if item.background_audio:
self._update_background_audio(song, item)
@ -540,9 +537,11 @@ class SongMediaItem(MediaManagerItem):
self.generate_footer(item, song)
return item
def search(self, string, showError):
def search(self, string, show_error):
"""
Search for some songs
:param string: The string to show
:param show_error: Is this an error?
"""
search_results = self.search_entire(string)
return [[song.id, song.title] for song in search_results]

View File

@ -37,6 +37,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E']
class MediaShoutImport(SongImport):
"""
The :class:`MediaShoutImport` class provides the ability to import the
@ -48,61 +49,57 @@ class MediaShoutImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Receive a single file to import.
"""
try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};'
'DBQ=%s;PWD=6NOZ4eHK7k' % self.import_source)
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=6NOZ4eHK7k' %
self.import_source)
except:
# Unfortunately no specific exception type
self.logError(self.import_source,
translate('SongsPlugin.MediaShoutImport', 'Unable to open the MediaShout database.'))
self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
'Unable to open the MediaShout database.'))
return
cursor = conn.cursor()
cursor.execute('SELECT Record, Title, Author, Copyright, '
'SongID, CCLI, Notes FROM Songs ORDER BY Title')
cursor.execute('SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title')
songs = cursor.fetchall()
self.import_wizard.progress_bar.setMaximum(len(songs))
for song in songs:
if self.stop_import_flag:
break
cursor.execute('SELECT Type, Number, Text FROM Verses '
'WHERE Record = %s ORDER BY Type, Number' % song.Record)
cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = %s ORDER BY Type, Number'
% song.Record)
verses = cursor.fetchall()
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder '
'WHERE Record = %s ORDER BY POrder' % song.Record)
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = %s ORDER BY POrder' % song.Record)
verse_order = cursor.fetchall()
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes '
'ON SongThemes.ThemeId = Themes.ThemeId '
'WHERE SongThemes.Record = %s' % song.Record)
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
'WHERE SongThemes.Record = %s' % song.Record)
topics = cursor.fetchall()
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups '
'ON SongGroups.GroupId = Groups.GroupId '
'WHERE SongGroups.Record = %s' % song.Record)
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId '
'WHERE SongGroups.Record = %s' % song.Record)
topics += cursor.fetchall()
self.processSong(song, verses, verse_order, topics)
self.process_song(song, verses, verse_order, topics)
def processSong(self, song, verses, verse_order, topics):
def process_song(self, song, verses, verse_order, topics):
"""
Create the song, i.e. title, verse etc.
"""
self.setDefaults()
self.set_defaults()
self.title = song.Title
self.parse_author(song.Author)
self.addCopyright(song.Copyright)
self.add_copyright(song.Copyright)
self.comments = song.Notes
for topic in topics:
self.topics.append(topic.Name)
if '-' in song.SongID:
self.songBookName, self.songNumber = song.SongID.split('-', 1)
self.song_book_name, self.song_number = song.SongID.split('-', 1)
else:
self.songBookName = song.SongID
self.song_book_name = song.SongID
for verse in verses:
tag = VERSE_TAGS[verse.Type] + str(verse.Number) if verse.Type < len(VERSE_TAGS) else 'O'
self.addVerse(verse.Text, tag)
self.add_verse(verse.Text, tag)
for order in verse_order:
if order.Type < len(VERSE_TAGS):
self.verseOrderList.append(VERSE_TAGS[order.Type] + str(order.Number))
self.verse_order_list.append(VERSE_TAGS[order.Type] + str(order.Number))
self.finish()

View File

@ -45,6 +45,7 @@ from .songimport import SongImport
log = logging.getLogger(__name__)
class OpenLPSongImport(SongImport):
"""
The :class:`OpenLPSongImport` class provides OpenLP with the ability to
@ -54,20 +55,17 @@ class OpenLPSongImport(SongImport):
"""
Initialise the import.
``manager``
The song manager for the running OpenLP installation.
``source_db``
The database providing the data to import.
:param manager: The song manager for the running OpenLP installation.
:param kwargs: The database providing the data to import.
"""
SongImport.__init__(self, manager, **kwargs)
self.sourceSession = None
self.source_session = None
def doImport(self, progressDialog=None):
def do_import(self, progress_dialog=None):
"""
Run the import for an OpenLP version 2 song database.
``progressDialog``
``progress_dialog``
The QProgressDialog used when importing songs from the FRW.
"""
@ -77,28 +75,24 @@ class OpenLPSongImport(SongImport):
"""
pass
class OldBook(BaseModel):
"""
Book model
"""
pass
class OldMediaFile(BaseModel):
"""
MediaFile model
"""
pass
class OldSong(BaseModel):
"""
Song model
"""
pass
class OldTopic(BaseModel):
"""
Topic model
@ -107,15 +101,15 @@ class OpenLPSongImport(SongImport):
# Check the file type
if not self.import_source.endswith('.sqlite'):
self.logError(self.import_source,
translate('SongsPlugin.OpenLPSongImport', 'Not a valid OpenLP 2.0 song database.'))
self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport',
'Not a valid OpenLP 2.0 song database.'))
return
self.import_source = 'sqlite:///%s' % self.import_source
# Load the db file
engine = create_engine(self.import_source)
source_meta = MetaData()
source_meta.reflect(engine)
self.sourceSession = scoped_session(sessionmaker(bind=engine))
self.source_session = scoped_session(sessionmaker(bind=engine))
if 'media_files' in list(source_meta.tables.keys()):
has_media_files = True
else:
@ -143,14 +137,13 @@ class OpenLPSongImport(SongImport):
}
if has_media_files:
if isinstance(source_media_files_songs_table, Table):
song_props['media_files'] = relation(OldMediaFile,
backref='songs',
secondary=source_media_files_songs_table)
song_props['media_files'] = relation(OldMediaFile, backref='songs',
secondary=source_media_files_songs_table)
else:
song_props['media_files'] = relation(OldMediaFile,
backref='songs',
foreign_keys=[source_media_files_table.c.song_id],
primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id)
song_props['media_files'] = \
relation(OldMediaFile, backref='songs',
foreign_keys=[source_media_files_table.c.song_id],
primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id)
try:
class_mapper(OldAuthor)
except UnmappedClassError:
@ -168,7 +161,7 @@ class OpenLPSongImport(SongImport):
except UnmappedClassError:
mapper(OldTopic, source_topics_table)
source_songs = self.sourceSession.query(OldSong).all()
source_songs = self.source_session.query(OldSong).all()
if self.import_wizard:
self.import_wizard.progress_bar.setMaximum(len(source_songs))
for song in source_songs:
@ -212,17 +205,17 @@ class OpenLPSongImport(SongImport):
if has_media_files:
if song.media_files:
for media_file in song.media_files:
existing_media_file = self.manager.get_object_filtered(MediaFile,
MediaFile.file_name == media_file.file_name)
existing_media_file = self.manager.get_object_filtered(
MediaFile, MediaFile.file_name == media_file.file_name)
if existing_media_file:
new_song.media_files.append(existing_media_file)
else:
new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name))
clean_song(self.manager, new_song)
self.manager.save_object(new_song)
if progressDialog:
progressDialog.setValue(progressDialog.value() + 1)
progressDialog.setLabelText(WizardStrings.ImportingType % new_song.title)
if progress_dialog:
progress_dialog.setValue(progress_dialog.value() + 1)
progress_dialog.setLabelText(WizardStrings.ImportingType % new_song.title)
else:
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
if self.stop_import_flag:

View File

@ -58,20 +58,19 @@ class OooImport(SongImport):
"""
def __init__(self, manager, **kwargs):
"""
Initialise the class. Requires a songmanager class which is passed
to SongImport for writing song to disk
Initialise the class. Requires a songmanager class which is passed to SongImport for writing song to disk
"""
SongImport.__init__(self, manager, **kwargs)
self.document = None
self.processStarted = False
self.process_started = False
def doImport(self):
def do_import(self):
if not isinstance(self.import_source, list):
return
try:
self.startOoo()
self.start_ooo()
except NoConnectException as exc:
self.logError(
self.log_error(
self.import_source[0],
translate('SongsPlugin.SongImport', 'Cannot access OpenOffice or LibreOffice'))
log.error(exc)
@ -82,34 +81,34 @@ class OooImport(SongImport):
break
filename = str(filename)
if os.path.isfile(filename):
self.openOooFile(filename)
self.open_ooo_file(filename)
if self.document:
self.processOooDocument()
self.closeOooFile()
self.process_ooo_document()
self.close_ooo_file()
else:
self.logError(self.filepath, translate('SongsPlugin.SongImport', 'Unable to open file'))
self.log_error(self.file_path, translate('SongsPlugin.SongImport', 'Unable to open file'))
else:
self.logError(self.filepath, translate('SongsPlugin.SongImport', 'File not found'))
self.closeOoo()
self.log_error(self.file_path, translate('SongsPlugin.SongImport', 'File not found'))
self.close_ooo()
def processOooDocument(self):
def process_ooo_document(self):
"""
Handle the import process for OpenOffice files. This method facilitates
allowing subclasses to handle specific types of OpenOffice files.
"""
if self.document.supportsService("com.sun.star.presentation.PresentationDocument"):
self.processPres()
self.process_presentation()
if self.document.supportsService("com.sun.star.text.TextDocument"):
self.processDoc()
self.process_doc()
def startOoo(self):
def start_ooo(self):
"""
Start OpenOffice.org process
TODO: The presentation/Impress plugin may already have it running
"""
if os.name == 'nt':
self.startOooProcess()
self.desktop = self.oooManager.createInstance('com.sun.star.frame.Desktop')
self.start_ooo_process()
self.desktop = self.ooo_manager.createInstance('com.sun.star.frame.Desktop')
else:
context = uno.getComponentContext()
resolver = context.ServiceManager.createInstanceWithContext('com.sun.star.bridge.UnoUrlResolver', context)
@ -121,7 +120,7 @@ class OooImport(SongImport):
except NoConnectException:
time.sleep(0.1)
log.exception("Failed to resolve uno connection")
self.startOooProcess()
self.start_ooo_process()
loop += 1
else:
manager = uno_instance.ServiceManager
@ -129,60 +128,62 @@ class OooImport(SongImport):
return
raise Exception('Unable to start LibreOffice')
def startOooProcess(self):
def start_ooo_process(self):
"""
Start the OO Process
"""
try:
if os.name == 'nt':
self.oooManager = Dispatch('com.sun.star.ServiceManager')
self.oooManager._FlagAsMethod('Bridge_GetStruct')
self.oooManager._FlagAsMethod('Bridge_GetValueObject')
self.ooo_manager = Dispatch('com.sun.star.ServiceManager')
self.ooo_manager._FlagAsMethod('Bridge_GetStruct')
self.ooo_manager._FlagAsMethod('Bridge_GetValueObject')
else:
cmd = get_uno_command()
process = QtCore.QProcess()
process.startDetached(cmd)
self.processStarted = True
self.process_started = True
except:
log.exception("startOooProcess failed")
log.exception("start_ooo_process failed")
def openOooFile(self, filepath):
def open_ooo_file(self, file_path):
"""
Open the passed file in OpenOffice.org Impress
"""
self.filepath = filepath
self.file_path = file_path
if os.name == 'nt':
url = filepath.replace('\\', '/')
url = file_path.replace('\\', '/')
url = url.replace(':', '|').replace(' ', '%20')
url = 'file:///' + url
else:
url = uno.systemPathToFileUrl(filepath)
url = uno.systemPathToFileUrl(file_path)
properties = []
properties = tuple(properties)
try:
self.document = self.desktop.loadComponentFromURL(url, '_blank',
0, properties)
self.document = self.desktop.loadComponentFromURL(url, '_blank', 0, properties)
if not self.document.supportsService("com.sun.star.presentation.PresentationDocument") and not \
self.document.supportsService("com.sun.star.text.TextDocument"):
self.closeOooFile()
self.close_ooo_file()
else:
self.import_wizard.increment_progress_bar('Processing file ' + filepath, 0)
self.import_wizard.increment_progress_bar('Processing file ' + file_path, 0)
except AttributeError:
log.exception("openOooFile failed: %s", url)
log.exception("open_ooo_file failed: %s", url)
return
def closeOooFile(self):
def close_ooo_file(self):
"""
Close file.
"""
self.document.close(True)
self.document = None
def closeOoo(self):
def close_ooo(self):
"""
Close OOo. But only if we started it and not on windows
"""
if self.processStarted:
if self.process_started:
self.desktop.terminate()
def processPres(self):
def process_presentation(self):
"""
Process the file
"""
@ -194,47 +195,52 @@ class OooImport(SongImport):
self.import_wizard.increment_progress_bar('Import cancelled', 0)
return
slide = slides.getByIndex(slide_no)
slidetext = ''
slide_text = ''
for idx in range(slide.getCount()):
shape = slide.getByIndex(idx)
if shape.supportsService("com.sun.star.drawing.Text"):
if shape.getString().strip() != '':
slidetext += shape.getString().strip() + '\n\n'
if slidetext.strip() == '':
slidetext = '\f'
text += slidetext
self.processSongsText(text)
slide_text += shape.getString().strip() + '\n\n'
if slide_text.strip() == '':
slide_text = '\f'
text += slide_text
self.process_songs_text(text)
return
def processDoc(self):
def process_doc(self):
"""
Process the doc file, a paragraph at a time
"""
text = ''
paragraphs = self.document.getText().createEnumeration()
while paragraphs.hasMoreElements():
paratext = ''
para_text = ''
paragraph = paragraphs.nextElement()
if paragraph.supportsService("com.sun.star.text.Paragraph"):
textportions = paragraph.createEnumeration()
while textportions.hasMoreElements():
textportion = textportions.nextElement()
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
paratext += '\f'
paratext += textportion.getString()
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
paratext += '\f'
text += paratext + '\n'
self.processSongsText(text)
text_portions = paragraph.createEnumeration()
while text_portions.hasMoreElements():
text_portion = text_portions.nextElement()
if text_portion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
para_text += '\f'
para_text += text_portion.getString()
if text_portion.BreakType in (PAGE_AFTER, PAGE_BOTH):
para_text += '\f'
text += para_text + '\n'
self.process_songs_text(text)
def processSongsText(self, text):
songtexts = self.tidyText(text).split('\f')
self.setDefaults()
for songtext in songtexts:
if songtext.strip():
self.processSongText(songtext.strip())
if self.checkComplete():
def process_songs_text(self, text):
"""
Process the songs text
:param text: The text.
"""
song_texts = self.tidy_text(text).split('\f')
self.set_defaults()
for song_text in song_texts:
if song_text.strip():
self.process_song_text(song_text.strip())
if self.check_complete():
self.finish()
self.setDefaults()
if self.checkComplete():
self.set_defaults()
if self.check_complete():
self.finish()

View File

@ -27,8 +27,8 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`openlyricsexport` module provides the functionality for exporting
songs from the database to the OpenLyrics format.
The :mod:`openlyricsexport` module provides the functionality for exporting songs from the database to the OpenLyrics
format.
"""
import logging
import os
@ -62,15 +62,15 @@ class OpenLyricsExport(object):
Export the songs.
"""
log.debug('started OpenLyricsExport')
openLyrics = OpenLyrics(self.manager)
open_lyrics = OpenLyrics(self.manager)
self.parent.progress_bar.setMaximum(len(self.songs))
for song in self.songs:
self.application.process_events()
if self.parent.stop_export_flag:
return False
self.parent.increment_progress_bar(translate('SongsPlugin.OpenLyricsExport', 'Exporting "%s"...') %
song.title)
xml = openLyrics.song_to_xml(song)
song.title)
xml = open_lyrics.song_to_xml(song)
tree = etree.ElementTree(etree.fromstring(xml.encode()))
filename = '%s (%s)' % (song.title, ', '.join([author.display_name for author in song.authors]))
filename = clean_filename(filename)
@ -78,8 +78,8 @@ class OpenLyricsExport(object):
filename = '%s.xml' % filename[0:250 - len(self.save_path)]
# Pass a file object, because lxml does not cope with some special
# characters in the path (see lp:757673 and lp:744337).
tree.write(open(os.path.join(self.save_path, filename), 'wb'),
encoding='utf-8', xml_declaration=True, pretty_print=True)
tree.write(open(os.path.join(self.save_path, filename), 'wb'), encoding='utf-8', xml_declaration=True,
pretty_print=True)
return True
def _get_application(self):

View File

@ -54,9 +54,9 @@ class OpenLyricsImport(SongImport):
"""
log.debug('initialise OpenLyricsImport')
SongImport.__init__(self, manager, **kwargs)
self.openLyrics = OpenLyrics(self.manager)
self.open_lyrics = OpenLyrics(self.manager)
def doImport(self):
def do_import(self):
"""
Imports the songs.
"""
@ -71,11 +71,11 @@ class OpenLyricsImport(SongImport):
# special characters in the path (see lp:757673 and lp:744337).
parsed_file = etree.parse(open(file_path, 'r'), parser)
xml = etree.tostring(parsed_file).decode()
self.openLyrics.xml_to_song(xml)
self.open_lyrics.xml_to_song(xml)
except etree.XMLSyntaxError:
log.exception('XML syntax error in file %s' % file_path)
self.logError(file_path, SongStrings.XMLSyntaxError)
self.log_error(file_path, SongStrings.XMLSyntaxError)
except OpenLyricsError as exception:
log.exception('OpenLyricsException %d in file %s: %s'
% (exception.type, file_path, exception.log_message))
self.logError(file_path, exception.display_message)
log.exception('OpenLyricsException %d in file %s: %s' %
(exception.type, file_path, exception.log_message))
self.log_error(file_path, exception.display_message)

View File

@ -109,34 +109,34 @@ class OpenSongImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source:
if self.stop_import_flag:
return
song_file = open(filename)
self.doImportFile(song_file)
self.do_import_file(song_file)
song_file.close()
def doImportFile(self, file):
def do_import_file(self, file):
"""
Process the OpenSong file - pass in a file-like object, not a file path.
"""
self.setDefaults()
self.set_defaults()
try:
tree = objectify.parse(file)
except (Error, LxmlError):
self.logError(file.name, SongStrings.XMLSyntaxError)
self.log_error(file.name, SongStrings.XMLSyntaxError)
log.exception('Error parsing XML')
return
root = tree.getroot()
if root.tag != 'song':
self.logError(file.name, str(
translate('SongsPlugin.OpenSongImport', ('Invalid OpenSong song file. Missing song tag.'))))
self.log_error(file.name, str(
translate('SongsPlugin.OpenSongImport', 'Invalid OpenSong song file. Missing song tag.')))
return
fields = dir(root)
decode = {
'copyright': self.addCopyright,
'copyright': self.add_copyright,
'ccli': 'ccli_number',
'author': self.parse_author,
'title': 'title',
@ -207,7 +207,7 @@ class OpenSongImport(SongImport):
verses[verse_tag][verse_num][inst] = []
our_verse_order.append([verse_tag, verse_num, inst])
# Tidy text and remove the ____s from extended words
this_line = self.tidyText(this_line)
this_line = self.tidy_text(this_line)
this_line = this_line.replace('_', '')
this_line = this_line.replace('|', '\n')
this_line = this_line.strip()
@ -218,15 +218,15 @@ class OpenSongImport(SongImport):
for (verse_tag, verse_num, inst) in our_verse_order:
lines = '\n'.join(verses[verse_tag][verse_num][inst])
length = 0
while(length < len(verse_num) and verse_num[length].isnumeric()):
while length < len(verse_num) and verse_num[length].isnumeric():
length += 1
verse_def = '%s%s' % (verse_tag, verse_num[:length])
verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \
if verse_def in verse_joints else lines
for verse_def, lines in verse_joints.items():
self.addVerse(lines, verse_def)
self.add_verse(lines, verse_def)
if not self.verses:
self.addVerse('')
self.add_verse('')
# figure out the presentation order, if present
if 'presentation' in fields and root.presentation:
order = str(root.presentation)
@ -246,9 +246,9 @@ class OpenSongImport(SongImport):
verse_num = '1'
verse_def = '%s%s' % (verse_tag, verse_num)
if verse_num in verses.get(verse_tag, {}):
self.verseOrderList.append(verse_def)
self.verse_order_list.append(verse_def)
else:
log.info('Got order %s but not in verse tags, dropping'
'this item from presentation order', verse_def)
log.info('Got order %s but not in verse tags, dropping this item from presentation order',
verse_def)
if not self.finish():
self.logError(file.name)
self.log_error(file.name)

View File

@ -39,6 +39,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
class PowerSongImport(SongImport):
"""
The :class:`PowerSongImport` class provides the ability to import song files
@ -73,7 +74,7 @@ class PowerSongImport(SongImport):
* .song
"""
@staticmethod
def isValidSource(import_source):
def is_valid_source(import_source):
"""
Checks if source is a PowerSong 1.0 folder:
* is a directory
@ -85,12 +86,12 @@ class PowerSongImport(SongImport):
return True
return False
def doImport(self):
def do_import(self):
"""
Receive either a list of files or a folder (unicode) to import.
"""
from .importer import SongFormat
PS_string = SongFormat.get(SongFormat.PowerSong, 'name')
ps_string = SongFormat.get(SongFormat.PowerSong, 'name')
if isinstance(self.import_source, str):
if os.path.isdir(self.import_source):
dir = self.import_source
@ -101,27 +102,27 @@ class PowerSongImport(SongImport):
else:
self.import_source = ''
if not self.import_source or not isinstance(self.import_source, list):
self.logError(translate('SongsPlugin.PowerSongImport', 'No songs to import.'),
translate('SongsPlugin.PowerSongImport', 'No %s files found.') % PS_string)
self.log_error(translate('SongsPlugin.PowerSongImport', 'No songs to import.'),
translate('SongsPlugin.PowerSongImport', 'No %s files found.') % ps_string)
return
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for file in self.import_source:
if self.stop_import_flag:
return
self.setDefaults()
self.set_defaults()
parse_error = False
with open(file, 'rb') as song_data:
while True:
try:
label = self._readString(song_data)
label = self._read_string(song_data)
if not label:
break
field = self._readString(song_data)
field = self._read_string(song_data)
except ValueError:
parse_error = True
self.logError(os.path.basename(file), str(
self.log_error(os.path.basename(file), str(
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Unexpected byte value.')) %
PS_string)
ps_string)
break
else:
if label == 'TITLE':
@ -130,38 +131,38 @@ class PowerSongImport(SongImport):
self.parse_author(field)
elif label == 'COPYRIGHTLINE':
found_copyright = True
self._parseCopyrightCCLI(field)
self._parse_copyright_cCCLI(field)
elif label == 'PART':
self.addVerse(field)
self.add_verse(field)
if parse_error:
continue
# Check that file had TITLE field
if not self.title:
self.logError(os.path.basename(file), str(
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "TITLE" header.')) % PS_string)
self.log_error(os.path.basename(file), str(
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "TITLE" header.')) % ps_string)
continue
# Check that file had COPYRIGHTLINE label
if not found_copyright:
self.logError(self.title, str(
self.log_error(self.title, str(
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "COPYRIGHTLINE" header.')) %
PS_string)
ps_string)
continue
# Check that file had at least one verse
if not self.verses:
self.logError(self.title, str(
self.log_error(self.title, str(
translate('SongsPlugin.PowerSongImport', 'Verses not found. Missing "PART" header.')))
continue
if not self.finish():
self.logError(self.title)
self.log_error(self.title)
def _readString(self, file_object):
def _read_string(self, file_object):
"""
Reads in next variable-length string.
"""
string_len = self._read7BitEncodedInteger(file_object)
string_len = self._read_7_bit_encoded_integer(file_object)
return str(file_object.read(string_len), 'utf-8', 'ignore')
def _read7BitEncodedInteger(self, file_object):
def _read_7_bit_encoded_integer(self, file_object):
"""
Reads in a 32-bit integer in compressed 7-bit format.
@ -179,7 +180,7 @@ class PowerSongImport(SongImport):
# Check for corrupted stream (since max 5 bytes per 32-bit integer)
if i == 5:
raise ValueError
byte = self._readByte(file_object)
byte = self._read_byte(file_object)
# Strip high bit and shift left
val += (byte & 0x7f) << shift
shift += 7
@ -189,7 +190,7 @@ class PowerSongImport(SongImport):
i += 1
return val
def _readByte(self, file_object):
def _read_byte(self, file_object):
"""
Reads in next byte as an unsigned integer
@ -202,7 +203,7 @@ class PowerSongImport(SongImport):
else:
return ord(byte_str)
def _parseCopyrightCCLI(self, field):
def _parse_copyright_cCCLI(self, field):
"""
Look for CCLI song number, and get copyright
"""
@ -211,8 +212,8 @@ class PowerSongImport(SongImport):
copyright = ccli_no
ccli_no = ''
if copyright:
self.addCopyright(copyright.rstrip('\n').replace('\n', ' '))
self.add_copyright(copyright.rstrip('\n').replace('\n', ' '))
if ccli_no:
ccli_no = ccli_no.strip(' :')
if ccli_no.isdigit():
self.ccliNumber = ccli_no
self.ccli_number = ccli_no

View File

@ -61,6 +61,7 @@ try:
except ImportError:
ITALIC = 2
class SofImport(OooImport):
"""
Import songs provided on disks with the Songs of Fellowship music books
@ -68,8 +69,8 @@ class SofImport(OooImport):
Use OpenOffice.org Writer for processing the rtf file
The three books are not only inconsistant with each other, they are
inconsistant in themselves too with their formatting. Not only this, but
The three books are not only inconsistent with each other, they are
inconsistent in themselves too with their formatting. Not only this, but
the 1+2 book does not space out verses correctly. This script attempts
to sort it out, but doesn't get it 100% right. But better than having to
type them all out!
@ -85,18 +86,18 @@ class SofImport(OooImport):
OooImport.__init__(self, manager, **kwargs)
self.song = False
def processOooDocument(self):
def process_ooo_document(self):
"""
Handle the import process for SoF files.
"""
self.processSofFile()
self.process_sof_file()
def processSofFile(self):
def process_sof_file(self):
"""
Process the RTF file, a paragraph at a time
"""
self.blankLines = 0
self.newSong()
self.blank_lines = 0
self.new_song()
try:
paragraphs = self.document.getText().createEnumeration()
while paragraphs.hasMoreElements():
@ -104,65 +105,68 @@ class SofImport(OooImport):
return
paragraph = paragraphs.nextElement()
if paragraph.supportsService("com.sun.star.text.Paragraph"):
self.processParagraph(paragraph)
self.process_paragraph(paragraph)
except RuntimeException as exc:
log.exception('Error processing file: %s', exc)
if not self.finish():
self.logError(self.filepath)
self.log_error(self.file_path)
def processParagraph(self, paragraph):
def process_paragraph(self, paragraph):
"""
Process a paragraph.
In the first book, a paragraph is a single line. In the latter ones
they may contain multiple lines.
Each paragraph contains textportions. Each textportion has it's own
styling, e.g. italics, bold etc.
In the first book, a paragraph is a single line. In the latter ones they may contain multiple lines.
Each paragraph contains textportions. Each textportion has it's own styling, e.g. italics, bold etc.
Also check for page breaks, which indicates a new song in books 1+2.
In later books, there may not be line breaks, so check for 3 or more
newlines
In later books, there may not be line breaks, so check for 3 or more newlines
:param paragraph: The paragraph text
"""
text = ''
textportions = paragraph.createEnumeration()
while textportions.hasMoreElements():
textportion = textportions.nextElement()
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
self.processParagraphText(text)
self.newSong()
text_portions = paragraph.createEnumeration()
while text_portions.hasMoreElements():
text_portion = text_portions.nextElement()
if text_portion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
self.process_paragraph_text(text)
self.new_song()
text = ''
text += self.processTextPortion(textportion)
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
self.processParagraphText(text)
self.newSong()
text += self.process_text_portion(text_portion)
if text_portion.BreakType in (PAGE_AFTER, PAGE_BOTH):
self.process_paragraph_text(text)
self.new_song()
text = ''
self.processParagraphText(text)
self.process_paragraph_text(text)
def processParagraphText(self, text):
def process_paragraph_text(self, text):
"""
Split the paragraph text into multiple lines and process
:param text: The text
"""
for line in text.split('\n'):
self.processParagraphLine(line)
if self.blankLines > 2:
self.newSong()
self.process_paragraph_line(line)
if self.blank_lines > 2:
self.new_song()
def processParagraphLine(self, text):
def process_paragraph_line(self, text):
"""
Process a single line. Throw away that text which isn't relevant, i.e.
stuff that appears at the end of the song.
Anything that is OK, append to the current verse
:param text: The text
"""
text = text.strip()
if text == '':
self.blankLines += 1
if self.blankLines > 1:
self.blank_lines += 1
if self.blank_lines > 1:
return
if self.title != '':
self.finishVerse()
self.finish_verse()
return
self.blankLines = 0
if self.skipToCloseBracket:
self.blank_lines = 0
if self.skip_to_close_bracket:
if text.endswith(')'):
self.skipToCloseBracket = False
self.skip_to_close_bracket = False
return
if text.startswith('CCL Licence'):
self.italics = False
@ -170,90 +174,93 @@ class SofImport(OooImport):
if text == 'A Songs of Fellowship Worship Resource':
return
if text.startswith('(NB.') or text.startswith('(Regrettably') or text.startswith('(From'):
self.skipToCloseBracket = True
self.skip_to_close_bracket = True
return
if text.startswith('Copyright'):
self.addCopyright(text)
self.add_copyright(text)
return
if text == '(Repeat)':
self.finishVerse()
self.repeatVerse()
self.finish_verse()
self.repeat_verse()
return
if self.title == '':
if self.copyright == '':
self.addSofAuthor(text)
self.add_sof_author(text)
else:
self.addCopyright(text)
self.add_copyright(text)
return
self.addVerseLine(text)
self.add_verse_line(text)
def processTextPortion(self, textportion):
def process_text_portion(self, text_portion):
"""
Process a text portion. Here we just get the text and detect if
it's bold or italics. If it's bold then its a song number or song title.
Song titles are in all capitals, so we must bring the capitalization
into line
Process a text portion. Here we just get the text and detect if it's bold or italics. If it's bold then its a
song number or song title.
Song titles are in all capitals, so we must bring the capitalization into line
:param text_portion: A Piece of text
"""
text = textportion.getString()
text = self.tidyText(text)
text = text_portion.getString()
text = self.tidy_text(text)
if text.strip() == '':
return text
if textportion.CharWeight == BOLD:
boldtext = text.strip()
if boldtext.isdigit() and self.songNumber == '':
self.addSongNumber(boldtext)
if text_portion.CharWeight == BOLD:
bold_text = text.strip()
if bold_text.isdigit() and self.song_number == '':
self.add_song_number(bold_text)
return ''
text = self.uncapText(text)
text = self.uncap_text(text)
if self.title == '':
self.addTitle(text)
self.add_title(text)
return text
if text.strip().startswith('('):
return text
self.italics = (textportion.CharPosture == ITALIC)
self.italics = (text_portion.CharPosture == ITALIC)
return text
def newSong(self):
def new_song(self):
"""
A change of song. Store the old, create a new
... but only if the last song was complete. If not, stick with it
"""
if self.song:
self.finishVerse()
if not self.checkComplete():
self.finish_verse()
if not self.check_complete():
return
self.finish()
self.song = True
self.setDefaults()
self.skipToCloseBracket = False
self.isChorus = False
self.set_defaults()
self.skip_to_close_bracket = False
self.is_chorus = False
self.italics = False
self.currentVerse = ''
self.current__verse = ''
def addSongNumber(self, song_no):
def add_song_number(self, song_no):
"""
Add a song number, store as alternate title. Also use the song
number to work out which songbook we're in
Add a song number, store as alternate title. Also use the song number to work out which songbook we're in
:param song_no: The Song number
"""
self.songNumber = song_no
self.alternateTitle = song_no + '.'
self.songBook_pub = 'Kingsway Publications'
self.song_number = song_no
self.alternate_title = song_no + '.'
self.song_book_pub = 'Kingsway Publications'
if int(song_no) <= 640:
self.songBook = 'Songs of Fellowship 1'
self.song_book = 'Songs of Fellowship 1'
elif int(song_no) <= 1150:
self.songBook = 'Songs of Fellowship 2'
self.song_book = 'Songs of Fellowship 2'
elif int(song_no) <= 1690:
self.songBook = 'Songs of Fellowship 3'
self.song_book = 'Songs of Fellowship 3'
elif int(song_no) <= 2200:
self.songBook = 'Songs of Fellowship 4'
self.song_book = 'Songs of Fellowship 4'
elif int(song_no) <= 2710:
self.songBook = 'Songs of Fellowship 5'
self.song_book = 'Songs of Fellowship 5'
else:
self.songBook = 'Songs of Fellowship Other'
self.song_book = 'Songs of Fellowship Other'
def addTitle(self, text):
def add_title(self, text):
"""
Add the title to the song. Strip some leading/trailing punctuation that
we don't want in a title
Add the title to the song. Strip some leading/trailing punctuation that we don't want in a title
:param text: Title text
"""
title = text.strip()
if title.startswith('\''):
@ -263,50 +270,50 @@ class SofImport(OooImport):
self.title = title
self.import_wizard.increment_progress_bar('Processing song ' + title, 0)
def addSofAuthor(self, text):
def add_sof_author(self, text):
"""
Add the author. OpenLP stores them individually so split by 'and', '&'
and comma.
However need to check for "Mr and Mrs Smith" and turn it to
"Mr Smith" and "Mrs Smith".
Add the author. OpenLP stores them individually so split by 'and', '&' and comma.
However need to check for "Mr and Mrs Smith" and turn it to "Mr Smith" and "Mrs Smith".
:param text: Author text
"""
text = text.replace(' and ', ' & ')
self.parse_author(text)
def addVerseLine(self, text):
def add_verse_line(self, text):
"""
Add a line to the current verse. If the formatting has changed and
we're beyond the second line of first verse, then this indicates
a change of verse. Italics are a chorus
"""
if self.italics != self.isChorus and ((len(self.verses) > 0) or
(self.currentVerse.count('\n') > 1)):
self.finishVerse()
if self.italics:
self.isChorus = True
self.currentVerse += text + '\n'
Add a line to the current verse. If the formatting has changed and we're beyond the second line of first verse,
then this indicates a change of verse. Italics are a chorus
def finishVerse(self):
:param text: The verse text
"""
Verse is finished, store it. Note in book 1+2, some songs are formatted
incorrectly. Here we try and split songs with missing line breaks into
the correct number of verses.
if self.italics != self.is_chorus and ((len(self.verses) > 0) or
(self.current__verse.count('\n') > 1)):
self.finish_verse()
if self.italics:
self.is_chorus = True
self.current__verse += text + '\n'
def finish_verse(self):
"""
if self.currentVerse.strip() == '':
Verse is finished, store it. Note in book 1+2, some songs are formatted incorrectly. Here we try and split
songs with missing line breaks into the correct number of verses.
"""
if self.current__verse.strip() == '':
return
if self.isChorus:
if self.is_chorus:
versetag = 'C'
splitat = None
else:
versetag = 'V'
splitat = self.verseSplits(self.songNumber)
splitat = self.verse_splits(self.song_number)
if splitat:
ln = 0
verse = ''
for line in self.currentVerse.split('\n'):
for line in self.current__verse.split('\n'):
ln += 1
if line == '' or ln > splitat:
self.addSofVerse(verse, versetag)
self.add_sof_verse(verse, versetag)
ln = 0
if line:
verse = line + '\n'
@ -315,19 +322,19 @@ class SofImport(OooImport):
else:
verse += line + '\n'
if verse:
self.addSofVerse(verse, versetag)
self.add_sof_verse(verse, versetag)
else:
self.addSofVerse(self.currentVerse, versetag)
self.currentVerse = ''
self.isChorus = False
self.add_sof_verse(self.current__verse, versetag)
self.current__verse = ''
self.is_chorus = False
def addSofVerse(self, lyrics, tag):
self.addVerse(lyrics, tag)
if not self.isChorus and 'C1' in self.verseOrderListGenerated:
self.verseOrderListGenerated.append('C1')
self.verseOrderListGenerated_useful = True
def add_sof_verse(self, lyrics, tag):
self.add_verse(lyrics, tag)
if not self.is_chorus and 'C1' in self.verse_order_list_generated:
self.verse_order_list_generated.append('C1')
self.verse_order_list_generated_useful = True
def uncapText(self, text):
def uncap_text(self, text):
"""
Words in the title are in all capitals, so we lowercase them.
However some of these words, e.g. referring to God need a leading
@ -336,28 +343,26 @@ class SofImport(OooImport):
There is a complicated word "One", which is sometimes lower and
sometimes upper depending on context. Never mind, keep it lower.
"""
textarr = re.split('(\W+)', text)
textarr[0] = textarr[0].capitalize()
for i in range(1, len(textarr)):
text_arr = re.split('(\W+)', text)
text_arr[0] = text_arr[0].capitalize()
for i in range(1, len(text_arr)):
# Do not translate these. Fixed strings in SOF song file
if textarr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY',
'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD', 'FATHER',
'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE',
'I\'M', 'I\'LL', 'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS',
'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'', 'JIREH',
'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S',
'FATHER\'S', 'ELIJAH' 'MARTHA', 'CHRISTMAS', 'ALPHA',
'OMEGA'):
textarr[i] = textarr[i].capitalize()
if text_arr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY', 'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD',
'FATHER', 'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE', 'I\'M', 'I\'LL',
'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS', 'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'',
'JIREH', 'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S', 'FATHER\'S', 'ELIJAH' 'MARTHA',
'CHRISTMAS', 'ALPHA', 'OMEGA'):
text_arr[i] = text_arr[i].capitalize()
else:
textarr[i] = textarr[i].lower()
text = ''.join(textarr)
text_arr[i] = text_arr[i].lower()
text = ''.join(text_arr)
return text
def verseSplits(self, song_number):
def verse_splits(self, song_number):
"""
Because someone at Kingsway forgot to check the 1+2 RTF file,
some verses were not formatted correctly.
Because someone at Kingsway forgot to check the 1+2 RTF file, some verses were not formatted correctly.
:param song_number: The Song number
"""
if song_number == 11:
return 8

View File

@ -40,6 +40,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
class SongBeamerTypes(object):
MarkTypes = {
'Refrain': VerseType.tags[VerseType.Chorus],
@ -98,7 +99,7 @@ class SongBeamerImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Receive a single file or a list of files to import.
"""
@ -109,9 +110,9 @@ class SongBeamerImport(SongImport):
# TODO: check that it is a valid SongBeamer file
if self.stop_import_flag:
return
self.setDefaults()
self.currentVerse = ''
self.currentVerseType = VerseType.tags[VerseType.Verse]
self.set_defaults()
self.current_verse = ''
self.current_verse_type = VerseType.tags[VerseType.Verse]
read_verses = False
file_name = os.path.split(import_file)[1]
if os.path.isfile(import_file):
@ -132,39 +133,38 @@ class SongBeamerImport(SongImport):
if line.startswith('#') and not read_verses:
self.parseTags(line)
elif line.startswith('---'):
if self.currentVerse:
self.replaceHtmlTags()
self.addVerse(self.currentVerse, self.currentVerseType)
self.currentVerse = ''
self.currentVerseType = VerseType.tags[VerseType.Verse]
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
self.current_verse = ''
self.current_verse_type = VerseType.tags[VerseType.Verse]
read_verses = True
verse_start = True
elif read_verses:
if verse_start:
verse_start = False
if not self.checkVerseMarks(line):
self.currentVerse = line + '\n'
if not self.check_verse_marks(line):
self.current_verse = line + '\n'
else:
self.currentVerse += line + '\n'
if self.currentVerse:
self.replaceHtmlTags()
self.addVerse(self.currentVerse, self.currentVerseType)
self.current_verse += line + '\n'
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
if not self.finish():
self.logError(import_file)
self.log_error(import_file)
def replaceHtmlTags(self):
def replace_html_tags(self):
"""
This can be called to replace SongBeamer's specific (html) tags with OpenLP's specific (html) tags.
"""
for pair in SongBeamerImport.HTML_TAG_PAIRS:
self.currentVerse = pair[0].sub(pair[1], self.currentVerse)
self.current_verse = pair[0].sub(pair[1], self.current_verse)
def parseTags(self, line):
"""
Parses a meta data line.
``line``
The line in the file. It should consist of a tag and a value for this tag (unicode)::
:param line: The line in the file. It should consist of a tag and a value for this tag (unicode)::
u'#Title=Nearer my God to Thee'
"""
@ -174,7 +174,7 @@ class SongBeamerImport(SongImport):
if not tag_val[0] or not tag_val[1]:
return
if tag_val[0] == '#(c)':
self.addCopyright(tag_val[1])
self.add_copyright(tag_val[1])
elif tag_val[0] == '#AddCopyrightInfo':
pass
elif tag_val[0] == '#Author':
@ -186,7 +186,7 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == '#Categories':
self.topics = tag_val[1].split(',')
elif tag_val[0] == '#CCLI':
self.ccliNumber = tag_val[1]
self.ccli_number = tag_val[1]
elif tag_val[0] == '#Chords':
pass
elif tag_val[0] == '#ChurchSongID':
@ -233,10 +233,10 @@ class SongBeamerImport(SongImport):
song_book_pub = tag_val[1]
elif tag_val[0] == '#Songbook' or tag_val[0] == '#SongBook':
book_data = tag_val[1].split('/')
self.songBookName = book_data[0].strip()
self.song_book_name = book_data[0].strip()
if len(book_data) == 2:
number = book_data[1].strip()
self.songNumber = number if number.isdigit() else ''
self.song_number = number if number.isdigit() else ''
elif tag_val[0] == '#Speed':
pass
elif tag_val[0] == 'Tempo':
@ -267,20 +267,19 @@ class SongBeamerImport(SongImport):
# TODO: add the verse order.
pass
def checkVerseMarks(self, line):
def check_verse_marks(self, line):
"""
Check and add the verse's MarkType. Returns ``True`` if the given linE contains a correct verse mark otherwise
``False``.
``line``
The line to check for marks (unicode).
:param line: The line to check for marks (unicode).
"""
marks = line.split(' ')
if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes:
self.currentVerseType = SongBeamerTypes.MarkTypes[marks[0]]
self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0]]
if len(marks) == 2:
# If we have a digit, we append it to current_verse_type.
if marks[1].isdigit():
self.currentVerseType += marks[1]
self.current_verse_type += marks[1]
return True
return False

View File

@ -56,11 +56,8 @@ def songs_probably_equal(song1, song2):
"""
Calculate and return whether two songs are probably equal.
``song1``
The first song to compare.
``song2``
The second song to compare.
:param song1: The first song to compare.
:param song2: The second song to compare.
"""
if len(song1.search_lyrics) < len(song2.search_lyrics):
small = song1.search_lyrics
@ -96,8 +93,7 @@ def _op_length(opcode):
"""
Return the length of a given difference.
``opcode``
The difference.
:param opcode: The difference.
"""
return max(opcode[2] - opcode[1], opcode[4] - opcode[3])
@ -107,33 +103,28 @@ def _remove_typos(diff):
Remove typos from a diff set. A typo is a small difference (<max_typo_size)
surrounded by larger equal passages (>min_fragment_size).
``diff``
The diff set to remove the typos from.
:param diff: The diff set to remove the typos from.
"""
# Remove typo at beginning of the string.
if len(diff) >= 2:
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and \
_op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
del diff[0]
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and _op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
del diff[0]
# Remove typos in the middle of the string.
if len(diff) >= 3:
for index in range(len(diff) - 3, -1, -1):
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
diff[index + 1][0] != "equal" and _op_length(diff[index + 1]) <= MAX_TYPO_SIZE and \
_op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
del diff[index + 1]
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and diff[index + 1][0] != "equal" and \
_op_length(diff[index + 1]) <= MAX_TYPO_SIZE and _op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
del diff[index + 1]
# Remove typo at the end of the string.
if len(diff) >= 2:
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and \
diff[-1][0] != "equal" and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
del diff[-1]
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and diff[-1][0] != "equal" \
and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
del diff[-1]
# Merge the bordering equal passages that occured by removing differences.
for index in range(len(diff) - 2, -1, -1):
if diff[index][0] == "equal" and _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
diff[index + 1][0] == "equal" and _op_length(diff[index + 1]) >= MIN_FRAGMENT_SIZE:
diff[index] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3],
diff[index + 1][4])
del diff[index + 1]
diff[index + 1][0] == "equal" and _op_length(diff[index + 1]) >= MIN_FRAGMENT_SIZE:
diff[index] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3], diff[index + 1][4])
del diff[index + 1]
return diff

View File

@ -53,7 +53,7 @@ class SongImport(QtCore.QObject):
as necessary
"""
@staticmethod
def isValidSource(import_source):
def is_valid_source(import_source):
"""
Override this method to validate the source prior to import.
"""
@ -63,10 +63,8 @@ class SongImport(QtCore.QObject):
"""
Initialise and create defaults for properties
``manager``
An instance of a SongManager, through which all database access is
performed.
:param manager: An instance of a SongManager, through which all database access is performed.
:param kwargs:
"""
self.manager = manager
QtCore.QObject.__init__(self)
@ -82,56 +80,51 @@ class SongImport(QtCore.QObject):
self.import_wizard = None
self.song = None
self.stop_import_flag = False
self.setDefaults()
self.set_defaults()
Registry().register_function('openlp_stop_wizard', self.stop_import)
def setDefaults(self):
def set_defaults(self):
"""
Create defaults for properties - call this before each song
if importing many songs at once to ensure a clean beginning
"""
self.title = ''
self.songNumber = ''
self.song_number = ''
self.alternate_title = ''
self.copyright = ''
self.comments = ''
self.themeName = ''
self.ccliNumber = ''
self.theme_name = ''
self.ccli_number = ''
self.authors = []
self.topics = []
self.mediaFiles = []
self.songBookName = ''
self.songBookPub = ''
self.verseOrderListGeneratedUseful = False
self.verseOrderListGenerated = []
self.verseOrderList = []
self.media_files = []
self.song_book_name = ''
self.song_book_pub = ''
self.verse_order_list_generated_useful = False
self.verse_order_list_generated = []
self.verse_order_list = []
self.verses = []
self.verseCounts = {}
self.copyrightString = translate('SongsPlugin.SongImport', 'copyright')
self.verse_counts = {}
self.copyright_string = translate('SongsPlugin.SongImport', 'copyright')
def logError(self, filepath, reason=SongStrings.SongIncomplete):
def log_error(self, file_path, reason=SongStrings.SongIncomplete):
"""
This should be called, when a song could not be imported.
``filepath``
This should be the file path if ``self.import_source`` is a list
with different files. If it is not a list, but a single file (for
instance a database), then this should be the song's title.
``reason``
The reason why the import failed. The string should be as
informative as possible.
:param file_path: This should be the file path if ``self.import_source`` is a list with different files. If it
is not a list, but a single file (for instance a database), then this should be the song's title.
:param reason: The reason why the import failed. The string should be as informative as possible.
"""
self.setDefaults()
self.set_defaults()
if self.import_wizard is None:
return
if self.import_wizard.error_report_text_edit.isHidden():
self.import_wizard.error_report_text_edit.setText(translate('SongsPlugin.SongImport',
'The following songs could not be imported:'))
self.import_wizard.error_report_text_edit.setText(
translate('SongsPlugin.SongImport', 'The following songs could not be imported:'))
self.import_wizard.error_report_text_edit.setVisible(True)
self.import_wizard.error_copy_to_button.setVisible(True)
self.import_wizard.error_save_to_button.setVisible(True)
self.import_wizard.error_report_text_edit.append('- %s (%s)' % (filepath, reason))
self.import_wizard.error_report_text_edit.append('- %s (%s)' % (file_path, reason))
def stop_import(self):
"""
@ -143,10 +136,9 @@ class SongImport(QtCore.QObject):
def register(self, import_wizard):
self.import_wizard = import_wizard
def tidyText(self, text):
def tidy_text(self, text):
"""
Get rid of some dodgy unicode and formatting characters we're not
interested in. Some can be converted to ascii.
Get rid of some dodgy unicode and formatting characters we're not interested in. Some can be converted to ascii.
"""
text = text.replace('\u2018', '\'')
text = text.replace('\u2019', '\'')
@ -161,21 +153,31 @@ class SongImport(QtCore.QObject):
text = re.sub(r' ?(\n{5}|\f)+ ?', '\f', text)
return text
def processSongText(self, text):
def process_song_text(self, text):
"""
Process the song text from import
:param text: Some text
"""
verse_texts = text.split('\n\n')
for verse_text in verse_texts:
if verse_text.strip() != '':
self.processVerseText(verse_text.strip())
self.process_verse_text(verse_text.strip())
def processVerseText(self, text):
def process_verse_text(self, text):
"""
Process the song verse text from import
:param text: Some text
"""
lines = text.split('\n')
if text.lower().find(self.copyrightString) >= 0 or text.find(str(SongStrings.CopyrightSymbol)) >= 0:
if text.lower().find(self.copyright_string) >= 0 or text.find(str(SongStrings.CopyrightSymbol)) >= 0:
copyright_found = False
for line in lines:
if (copyright_found or line.lower().find(self.copyrightString) >= 0 or
if (copyright_found or line.lower().find(self.copyright_string) >= 0 or
line.find(str(SongStrings.CopyrightSymbol)) >= 0):
copyright_found = True
self.addCopyright(line)
self.add_copyright(line)
else:
self.parse_author(line)
return
@ -184,9 +186,9 @@ class SongImport(QtCore.QObject):
return
if not self.title:
self.title = lines[0]
self.addVerse(text)
self.add_verse(text)
def addCopyright(self, copyright):
def add_copyright(self, copyright):
"""
Build the copyright field
"""
@ -198,9 +200,8 @@ class SongImport(QtCore.QObject):
def parse_author(self, text):
"""
Add the author. OpenLP stores them individually so split by 'and', '&'
and comma. However need to check for 'Mr and Mrs Smith' and turn it to
'Mr Smith' and 'Mrs Smith'.
Add the author. OpenLP stores them individually so split by 'and', '&' and comma. However need to check
for 'Mr and Mrs Smith' and turn it to 'Mr Smith' and 'Mrs Smith'.
"""
for author in text.split(','):
authors = author.split('&')
@ -211,9 +212,9 @@ class SongImport(QtCore.QObject):
if author2.endswith('.'):
author2 = author2[:-1]
if author2:
self.addAuthor(author2)
self.add_author(author2)
def addAuthor(self, author):
def add_author(self, author):
"""
Add an author to the list
"""
@ -221,63 +222,56 @@ class SongImport(QtCore.QObject):
return
self.authors.append(author)
def addMediaFile(self, filename, weight=0):
def add_media_file(self, filename, weight=0):
"""
Add a media file to the list
"""
if filename in [x[0] for x in self.mediaFiles]:
if filename in [x[0] for x in self.media_files]:
return
self.mediaFiles.append((filename, weight))
self.media_files.append((filename, weight))
def addVerse(self, verse_text, verse_def='v', lang=None):
def add_verse(self, verse_text, verse_def='v', lang=None):
"""
Add a verse. This is the whole verse, lines split by \\n. It will also
attempt to detect duplicates. In this case it will just add to the verse
order.
``verse_text``
The text of the verse.
``verse_def``
The verse tag can be v1/c1/b etc, or 'v' and 'c' (will count the
:param verse_text: The text of the verse.
:param verse_def: The verse tag can be v1/c1/b etc, or 'v' and 'c' (will count the
verses/choruses itself) or None, where it will assume verse.
``lang``
The language code (ISO-639) of the verse, for example *en* or *de*.
:param lang: The language code (ISO-639) of the verse, for example *en* or *de*.
"""
for (old_verse_def, old_verse, old_lang) in self.verses:
if old_verse.strip() == verse_text.strip():
self.verseOrderListGenerated.append(old_verse_def)
self.verseOrderListGeneratedUseful = True
self.verse_order_list_generated.append(old_verse_def)
self.verse_order_list_generated_useful = True
return
if verse_def[0] in self.verseCounts:
self.verseCounts[verse_def[0]] += 1
if verse_def[0] in self.verse_counts:
self.verse_counts[verse_def[0]] += 1
else:
self.verseCounts[verse_def[0]] = 1
self.verse_counts[verse_def[0]] = 1
if len(verse_def) == 1:
verse_def += str(self.verseCounts[verse_def[0]])
elif int(verse_def[1:]) > self.verseCounts[verse_def[0]]:
self.verseCounts[verse_def[0]] = int(verse_def[1:])
verse_def += str(self.verse_counts[verse_def[0]])
elif int(verse_def[1:]) > self.verse_counts[verse_def[0]]:
self.verse_counts[verse_def[0]] = int(verse_def[1:])
self.verses.append([verse_def, verse_text.rstrip(), lang])
# A verse_def refers to all verses with that name, adding it once adds every instance, so do not add if already
# used.
if verse_def not in self.verseOrderListGenerated:
self.verseOrderListGenerated.append(verse_def)
if verse_def not in self.verse_order_list_generated:
self.verse_order_list_generated.append(verse_def)
def repeatVerse(self):
def repeat_verse(self):
"""
Repeat the previous verse in the verse order
"""
if self.verseOrderListGenerated:
self.verseOrderListGenerated.append(self.verseOrderListGenerated[-1])
self.verseOrderListGeneratedUseful = True
if self.verse_order_list_generated:
self.verse_order_list_generated.append(self.verse_order_list_generated[-1])
self.verse_order_list_generated_useful = True
def checkComplete(self):
def check_complete(self):
"""
Check the mandatory fields are entered (i.e. title and a verse)
Author not checked here, if no author then "Author unknown" is
automatically added
Author not checked here, if no author then "Author unknown" is automatically added
"""
if not self.title or not self.verses:
return False
@ -288,8 +282,8 @@ class SongImport(QtCore.QObject):
"""
All fields have been set to this song. Write the song to disk.
"""
if not self.checkComplete():
self.setDefaults()
if not self.check_complete():
self.set_defaults()
return False
log.info('committing song %s to database', self.title)
song = Song()
@ -301,7 +295,7 @@ class SongImport(QtCore.QObject):
song.search_title = ''
song.search_lyrics = ''
song.verse_order = ''
song.song_number = self.songNumber
song.song_number = self.song_number
verses_changed_to_other = {}
sxml = SongXML()
other_count = 1
@ -317,32 +311,32 @@ class SongImport(QtCore.QObject):
verse_def = new_verse_def
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
song.lyrics = str(sxml.extract_xml(), 'utf-8')
if not self.verseOrderList and self.verseOrderListGeneratedUseful:
self.verseOrderList = self.verseOrderListGenerated
self.verseOrderList = [verses_changed_to_other.get(v, v) for v in self.verseOrderList]
song.verse_order = ' '.join(self.verseOrderList)
if not self.verse_order_list and self.verse_order_list_generated_useful:
self.verse_order_list = self.verse_order_list_generated
self.verse_order_list = [verses_changed_to_other.get(v, v) for v in self.verse_order_list]
song.verse_order = ' '.join(self.verse_order_list)
song.copyright = self.copyright
song.comments = self.comments
song.theme_name = self.themeName
song.ccli_number = self.ccliNumber
for authortext in self.authors:
author = self.manager.get_object_filtered(Author, Author.display_name == authortext)
song.theme_name = self.theme_name
song.ccli_number = self.ccli_number
for author_text in self.authors:
author = self.manager.get_object_filtered(Author, Author.display_name == author_text)
if not author:
author = Author.populate(display_name=authortext,
last_name=authortext.split(' ')[-1],
first_name=' '.join(authortext.split(' ')[:-1]))
author = Author.populate(display_name=author_text,
last_name=author_text.split(' ')[-1],
first_name=' '.join(author_text.split(' ')[:-1]))
song.authors.append(author)
if self.songBookName:
song_book = self.manager.get_object_filtered(Book, Book.name == self.songBookName)
if self.song_book_name:
song_book = self.manager.get_object_filtered(Book, Book.name == self.song_book_name)
if song_book is None:
song_book = Book.populate(name=self.songBookName, publisher=self.songBookPub)
song_book = Book.populate(name=self.song_book_name, publisher=self.song_book_pub)
song.book = song_book
for topictext in self.topics:
if not topictext:
for topic_text in self.topics:
if not topic_text:
continue
topic = self.manager.get_object_filtered(Topic, Topic.name == topictext)
topic = self.manager.get_object_filtered(Topic, Topic.name == topic_text)
if topic is None:
topic = Topic.populate(name=topictext)
topic = Topic.populate(name=topic_text)
song.topics.append(topic)
# We need to save the song now, before adding the media files, so that
# we know where to save the media files to.
@ -350,29 +344,29 @@ class SongImport(QtCore.QObject):
self.manager.save_object(song)
# Now loop through the media files, copy them to the correct location,
# and save the song again.
for filename, weight in self.mediaFiles:
for filename, weight in self.media_files:
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_name == filename)
if not media_file:
if os.path.dirname(filename):
filename = self.copyMediaFile(song.id, filename)
filename = self.copy_media_file(song.id, filename)
song.media_files.append(MediaFile.populate(file_name=filename, weight=weight))
self.manager.save_object(song)
self.setDefaults()
self.set_defaults()
return True
def copyMediaFile(self, song_id, filename):
def copy_media_file(self, song_id, filename):
"""
This method copies the media file to the correct location and returns
the new file location.
``filename``
The file to copy.
:param song_id:
:param filename: The file to copy.
"""
if not hasattr(self, 'save_path'):
self.save_path = os.path.join(AppLocation.get_section_data_path(self.import_wizard.plugin.name),
'audio', str(song_id))
'audio', str(song_id))
check_directory_exists(self.save_path)
if not filename.startswith(self.save_path):
oldfile, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1])
shutil.copyfile(oldfile, filename)
old_file, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1])
shutil.copyfile(old_file, filename)
return filename

View File

@ -74,7 +74,7 @@ class SongProImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Receive a single file or a list of files to import.
"""
@ -89,18 +89,18 @@ class SongProImport(SongImport):
file_line = str(file_line, 'cp1252')
file_text = file_line.rstrip()
if file_text and file_text[0] == '#':
self.processSection(tag, text.rstrip())
self.process_section(tag, text.rstrip())
tag = file_text[1:]
text = ''
else:
text += file_line
def processSection(self, tag, text):
def process_section(self, tag, text):
"""
Process a section of the song, i.e. title, verse etc.
"""
if tag == 'T':
self.setDefaults()
self.set_defaults()
if text:
self.title = text
return
@ -118,29 +118,29 @@ class SongProImport(SongImport):
if tag == 'A':
self.parse_author(text)
elif tag in ['B', 'C']:
self.addVerse(text, tag)
self.add_verse(text, tag)
elif tag == 'D':
self.addVerse(text, 'E')
self.add_verse(text, 'E')
elif tag == 'G':
self.topics.append(text)
elif tag == 'M':
matches = re.findall(r'\d+', text)
if matches:
self.songNumber = matches[-1]
self.songBookName = text[:text.rfind(self.songNumber)]
self.song_number = matches[-1]
self.song_book_name = text[:text.rfind(self.song_number)]
elif tag == 'N':
self.comments = text
elif tag == 'O':
for char in text:
if char == 'C':
self.verseOrderList.append('C1')
self.verse_order_list.append('C1')
elif char == 'B':
self.verseOrderList.append('B1')
self.verse_order_list.append('B1')
elif char == 'D':
self.verseOrderList.append('E1')
self.verse_order_list.append('E1')
elif '1' <= char <= '7':
self.verseOrderList.append('V' + char)
self.verse_order_list.append('V' + char)
elif tag == 'R':
self.addCopyright(text)
self.add_copyright(text)
elif '1' <= tag <= '7':
self.addVerse(text, 'V' + tag[1:])
self.add_verse(text, 'V' + tag[1:])

View File

@ -94,7 +94,7 @@ class SongShowPlusImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Receive a single file or a list of files to import.
"""
@ -141,19 +141,19 @@ class SongShowPlusImport(SongImport):
authors = self.decode(data).split(" / ")
for author in authors:
if author.find(",") !=-1:
authorParts = author.split(", ")
author = authorParts[1] + " " + authorParts[0]
author_parts = author.split(", ")
author = author_parts[1] + " " + author_parts[0]
self.parse_author(author)
elif block_key == COPYRIGHT:
self.addCopyright(self.decode(data))
self.add_copyright(self.decode(data))
elif block_key == CCLI_NO:
self.ccliNumber = int(data)
self.ccli_number = int(data)
elif block_key == VERSE:
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
elif block_key == CHORUS:
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
elif block_key == BRIDGE:
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
self.add_verse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
elif block_key == TOPIC:
self.topics.append(self.decode(data))
elif block_key == COMMENTS:
@ -165,21 +165,28 @@ class SongShowPlusImport(SongImport):
verse_tag = self.decode(verse_tag)
self.ssp_verse_order_list.append(verse_tag)
elif block_key == SONG_BOOK:
self.songBookName = self.decode(data)
self.song_book_name = self.decode(data)
elif block_key == SONG_NUMBER:
self.songNumber = ord(data)
self.song_number = ord(data)
elif block_key == CUSTOM_VERSE:
verse_tag = self.to_openlp_verse_tag(verse_name)
self.addVerse(self.decode(data), verse_tag)
self.add_verse(self.decode(data), verse_tag)
else:
log.debug("Unrecognised blockKey: %s, data: %s" % (block_key, data))
song_data.seek(next_block_starts)
self.verseOrderList = self.ssp_verse_order_list
self.verse_order_list = self.ssp_verse_order_list
song_data.close()
if not self.finish():
self.logError(file)
self.log_error(file)
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
"""
Handle OpenLP verse tags
:param verse_name: The verse name
:param ignore_unique: Ignore if unique
:return: The verse tags and verse number concatenated
"""
# Have we got any digits? If so, verse number is everything from the digits to the end (OpenLP does not have
# concept of part verses, so just ignore any non integers on the end (including floats))
match = re.match(r'(\D*)(\d+)', verse_name)

View File

@ -71,10 +71,10 @@ class SongsTab(SettingsTab):
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
self.search_as_type_check_box.setText(translate('SongsPlugin.SongsTab', 'Enable search as you type'))
self.tool_bar_active_check_box.setText(translate('SongsPlugin.SongsTab',
'Display verses on live tool bar'))
'Display verses on live tool bar'))
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
'Import missing songs from service files'))
'Import missing songs from service files'))
def on_search_as_type_check_box_changed(self, check_state):
self.song_search = (check_state == QtCore.Qt.Checked)

View File

@ -48,6 +48,7 @@ HOTKEY_TO_VERSE_TYPE = {
'+': 'b',
'Z': 'o'}
class SundayPlusImport(SongImport):
"""
Import Sunday Plus songs
@ -62,31 +63,38 @@ class SundayPlusImport(SongImport):
SongImport.__init__(self, manager, **kwargs)
self.encoding = 'us-ascii'
def doImport(self):
def do_import(self):
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for filename in self.import_source:
if self.stop_import_flag:
return
song_file = open(filename)
self.doImportFile(song_file)
self.do_import_file(song_file)
song_file.close()
def doImportFile(self, file):
def do_import_file(self, file):
"""
Process the Sunday Plus file object.
"""
self.setDefaults()
self.set_defaults()
if not self.parse(file.read()):
self.logError(file.name)
self.log_error(file.name)
return
if not self.title:
self.title = self.titleFromFilename(file.name)
self.title = self.title_from_filename(file.name)
if not self.finish():
self.logError(file.name)
self.log_error(file.name)
def parse(self, data, cell=False):
"""
Process the records
:param data: The data to be processed
:param cell: ?
:return:
"""
if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
self.logError('File is malformed')
self.log_error('File is malformed')
return False
i = 1
verse_type = VerseType.tags[VerseType.Verse]
@ -126,7 +134,7 @@ class SundayPlusImport(SongImport):
elif name == 'Author':
author = self.decode(self.unescape(value))
if len(author):
self.addAuthor(author)
self.add_author(author)
elif name == 'Copyright':
self.copyright = self.decode(self.unescape(value))
elif name[0:4] == 'CELL':
@ -160,20 +168,26 @@ class SundayPlusImport(SongImport):
if line[:3].lower() == 'ccl':
m = re.search(r'[0-9]+', line)
if m:
self.ccliNumber = int(m.group(0))
self.ccli_number = int(m.group(0))
continue
elif line.lower() == 'public domain':
self.copyright = 'Public Domain'
continue
processed_lines.append(line)
self.addVerse('\n'.join(processed_lines).strip(), verse_type)
self.add_verse('\n'.join(processed_lines).strip(), verse_type)
if end == -1:
break
i = end + 1
i += 1
return True
def titleFromFilename(self, filename):
def title_from_filename(self, filename):
"""
Extract the title from the filename
:param filename: File name
:return:
"""
title = os.path.split(filename)[1]
if title.endswith('.ptf'):
title = title[:-4]

View File

@ -51,7 +51,7 @@ class WorshipCenterProImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Receive a single file to import.
"""
@ -60,7 +60,7 @@ class WorshipCenterProImport(SongImport):
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
log.warn('Unable to connect the WorshipCenter Pro database %s. %s', self.import_source, str(e))
# Unfortunately no specific exception type
self.logError(self.import_source,
self.log_error(self.import_source,
translate('SongsPlugin.WorshipCenterProImport', 'Unable to connect the WorshipCenter Pro database.'))
return
cursor = conn.cursor()
@ -76,10 +76,10 @@ class WorshipCenterProImport(SongImport):
for song in songs:
if self.stop_import_flag:
break
self.setDefaults()
self.set_defaults()
self.title = songs[song]['TITLE']
lyrics = songs[song]['LYRICS'].strip('&crlf;&crlf;')
for verse in lyrics.split('&crlf;&crlf;'):
verse = verse.replace('&crlf;', '\n')
self.addVerse(verse)
self.add_verse(verse)
self.finish()

View File

@ -40,6 +40,7 @@ BLOCK_TYPES = ('V', 'C', 'B')
log = logging.getLogger(__name__)
class WowImport(SongImport):
"""
The :class:`WowImport` class provides the ability to import song files from
@ -101,7 +102,7 @@ class WowImport(SongImport):
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
def do_import(self):
"""
Receive a single file or a list of files to import.
"""
@ -110,38 +111,42 @@ class WowImport(SongImport):
for source in self.import_source:
if self.stop_import_flag:
return
self.setDefaults()
self.set_defaults()
song_data = open(source, 'rb')
if song_data.read(19) != 'WoW File\nSong Words':
self.logError(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
('Invalid Words of Worship song file. Missing "Wow File\\nSong Words" header.'))))
self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "Wow File\\nSong '
'Words" header.')))
continue
# Seek to byte which stores number of blocks in the song
song_data.seek(56)
no_of_blocks = ord(song_data.read(1))
song_data.seek(66)
if song_data.read(16) != 'CSongDoc::CBlock':
self.logError(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
('Invalid Words of Worship song file. Missing "CSongDoc::CBlock" string.'))))
self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
'string.')))
continue
# Seek to the beginning of the first block
song_data.seek(82)
for block in range(no_of_blocks):
self.linesToRead = ord(song_data.read(4)[:1])
self.lines_to_read = ord(song_data.read(4)[:1])
block_text = ''
while self.linesToRead:
self.lineText = str(song_data.read(ord(song_data.read(1))), 'cp1252')
while self.lines_to_read:
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
song_data.seek(1, os.SEEK_CUR)
if block_text:
block_text += '\n'
block_text += self.lineText
self.linesToRead -= 1
block_text += self.line_text
self.lines_to_read -= 1
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
# Blocks are separated by 2 bytes, skip them, but not if
# this is the last block!
if block + 1 < no_of_blocks:
song_data.seek(2, os.SEEK_CUR)
self.addVerse(block_text, block_type)
self.add_verse(block_text, block_type)
# Now to extract the author
author_length = ord(song_data.read(1))
if author_length:
@ -149,10 +154,10 @@ class WowImport(SongImport):
# Finally the copyright
copyright_length = ord(song_data.read(1))
if copyright_length:
self.addCopyright(str(song_data.read(copyright_length), 'cp1252'))
self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
file_name = os.path.split(source)[1]
# Get the song title
self.title = file_name.rpartition('.')[0]
song_data.close()
if not self.finish():
self.logError(source)
self.log_error(source)

View File

@ -97,21 +97,13 @@ class SongXML(object):
"""
Add a verse to the ``<lyrics>`` tag.
``type``
A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*. Any other
type is **not** allowed, this also includes translated types.
``number``
An integer denoting the number of the item, for example: verse 1.
``content``
The actual text of the verse to be stored.
``lang``
The verse's language code (ISO-639). This is not required, but should be added if available.
:param type: A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*.
Any other type is **not** allowed, this also includes translated types.
:param number: An integer denoting the number of the item, for example: verse 1.
:param content: The actual text of the verse to be stored.
:param lang: The verse's language code (ISO-639). This is not required, but should be added if available.
"""
verse = etree.Element('verse', type=str(type),
label=str(number))
verse = etree.Element('verse', type=str(type), label=str(number))
if lang:
verse.set('lang', lang)
verse.text = etree.CDATA(content)
@ -121,16 +113,13 @@ class SongXML(object):
"""
Extract our newly created XML song.
"""
return etree.tostring(self.song_xml, encoding='UTF-8',
xml_declaration=True)
return etree.tostring(self.song_xml, encoding='UTF-8', xml_declaration=True)
def get_verses(self, xml):
"""
Iterates through the verses in the XML and returns a list of verses and their attributes.
``xml``
The XML of the song to be parsed.
:param xml: The XML of the song to be parsed.
The returned list has the following format::
[[{'type': 'v', 'label': '1'}, u"optional slide split 1[---]optional slide split 2"],
@ -351,8 +340,7 @@ class OpenLyrics(object):
The first unicode string are the start tags (for the next slide). The second unicode string are the end tags.
``text``
The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
:param text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
{st}{r}Text text text
"""
@ -378,12 +366,9 @@ class OpenLyrics(object):
Create and save a song from OpenLyrics format xml to the database. Since we also export XML from external
sources (e. g. OpenLyrics import), we cannot ensure, that it completely conforms to the OpenLyrics standard.
``xml``
The XML to parse (unicode).
``parse_and_temporary_save``
Switch to skip processing the whole song and storing the songs in the database with a temporary flag.
Defaults to ``False``.
:param xml: The XML to parse (unicode).
:param parse_and_temporary_save: Switch to skip processing the whole song and storing the songs in the database
with a temporary flag. Defaults to ``False``.
"""
# No xml get out of here.
if not xml:
@ -418,6 +403,15 @@ class OpenLyrics(object):
return song
def _add_text_to_element(self, tag, parent, text=None, label=None):
"""
Build an element
:param tag: A Tag
:param parent: Its parent
:param text: Some text to be added
:param label: And a label
:return:
"""
if label:
element = etree.Element(tag, name=str(label))
else:
@ -430,6 +424,9 @@ class OpenLyrics(object):
def _add_tag_to_formatting(self, tag_name, tags_element):
"""
Add new formatting tag to the element ``<format>`` if the tag is not present yet.
:param tag_name: The tag_name
:param tags_element: Some tag elements
"""
available_tags = FormattingTags.get_html_tags()
start_tag = '{%s}' % tag_name
@ -477,16 +474,16 @@ class OpenLyrics(object):
def _extract_xml(self, xml):
"""
Extract our newly created XML song.
:param xml: The XML
"""
return etree.tostring(xml, encoding='UTF-8',
xml_declaration=True)
return etree.tostring(xml, encoding='UTF-8', xml_declaration=True)
def _text(self, element):
"""
This returns the text of an element as unicode string.
``element``
The element.
:param element: The element.
"""
if element.text is not None:
return str(element.text)
@ -496,11 +493,8 @@ class OpenLyrics(object):
"""
Adds the authors specified in the XML to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object
"""
authors = []
if hasattr(properties, 'authors'):
@ -509,24 +503,20 @@ class OpenLyrics(object):
if display_name:
authors.append(display_name)
for display_name in authors:
author = self.manager.get_object_filtered(Author,
Author.display_name == display_name)
author = self.manager.get_object_filtered(Author, Author.display_name == display_name)
if author is None:
# We need to create a new author, as the author does not exist.
author = Author.populate(display_name=display_name,
last_name=display_name.split(' ')[-1],
first_name=' '.join(display_name.split(' ')[:-1]))
last_name=display_name.split(' ')[-1],
first_name=' '.join(display_name.split(' ')[:-1]))
song.authors.append(author)
def _process_cclinumber(self, properties, song):
"""
Adds the CCLI number to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(properties, 'ccliNo'):
song.ccli_number = self._text(properties.ccliNo)
@ -535,11 +525,8 @@ class OpenLyrics(object):
"""
Joins the comments specified in the XML and add it to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(properties, 'comments'):
comments_list = []
@ -553,11 +540,8 @@ class OpenLyrics(object):
"""
Adds the copyright to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(properties, 'copyright'):
song.copyright = self._text(properties.copyright)
@ -566,6 +550,9 @@ class OpenLyrics(object):
"""
Process the formatting tags from the song and either add missing tags temporary or permanently to the
formatting tag list.
:param song_xml: The song XML
:param temporary: Is the song temporary?
"""
if not hasattr(song_xml, 'format'):
return
@ -603,11 +590,9 @@ class OpenLyrics(object):
Converts the xml text with mixed content to OpenLP representation. Chords are skipped and formatting tags are
converted.
``element``
The property object (lxml.etree.Element).
``newlines``
The switch to enable/disable processing of line breaks <br/>. The <br/> is used since OpenLyrics 0.8.
:param element: The property object (lxml.etree.Element).
:param newlines: The switch to enable/disable processing of line breaks <br/>. The <br/> is used since
OpenLyrics 0.8.
"""
text = ''
use_endtag = True
@ -655,8 +640,8 @@ class OpenLyrics(object):
"""
Converts lyrics lines to OpenLP representation.
``lines``
The lines object (lxml.objectify.ObjectifiedElement).
:param lines: The lines object (lxml.objectify.ObjectifiedElement).
:param version:
"""
text = ''
# Convert lxml.objectify to lxml.etree representation.
@ -682,14 +667,9 @@ class OpenLyrics(object):
"""
Processes the verses and search_lyrics for the song.
``properties``
The properties object (lxml.objectify.ObjectifiedElement).
``song_xml``
The objectified song (lxml.objectify.ObjectifiedElement).
``song_obj``
The song object.
:param properties: The properties object (lxml.objectify.ObjectifiedElement).
:param song_xml: The objectified song (lxml.objectify.ObjectifiedElement).
:param song_obj: The song object.
"""
sxml = SongXML()
verses = {}
@ -698,12 +678,12 @@ class OpenLyrics(object):
lyrics = song_xml.lyrics
except AttributeError:
raise OpenLyricsError(OpenLyricsError.LyricsError, '<lyrics> tag is missing.',
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
try:
verse_list = lyrics.verse
except AttributeError:
raise OpenLyricsError(OpenLyricsError.VerseError, '<verse> tag is missing.',
translate('OpenLP.OpenLyricsImportError', '<verse> tag is missing.'))
translate('OpenLP.OpenLyricsImportError', '<verse> tag is missing.'))
# Loop over the "verse" elements.
for verse in verse_list:
text = ''
@ -712,8 +692,7 @@ class OpenLyrics(object):
if text:
text += '\n'
# Append text from "lines" element to verse text.
text += self._process_verse_lines(lines,
version=song_xml.get('version'))
text += self._process_verse_lines(lines, version=song_xml.get('version'))
# Add an optional split to the verse text.
if lines.get('break') is not None:
text += '\n[---]'
@ -749,11 +728,8 @@ class OpenLyrics(object):
"""
Adds the song book and song number specified in the XML to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
song.song_book_id = None
song.song_number = ''
@ -775,11 +751,8 @@ class OpenLyrics(object):
"""
Processes the titles specified in the song's XML.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
for title in properties.titles.title:
if not song.title:
@ -792,11 +765,8 @@ class OpenLyrics(object):
"""
Adds the topics to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(properties, 'themes'):
for topic_text in properties.themes.theme:

View File

@ -41,6 +41,7 @@ log = logging.getLogger(__name__)
# Used to strip control chars (except 10=LF, 13=CR)
CONTROL_CHARS_MAP = dict.fromkeys(list(range(10)) + [11, 12] + list(range(14,32)) + [127])
class ZionWorxImport(SongImport):
"""
The :class:`ZionWorxImport` class provides the ability to import songs
@ -78,19 +79,19 @@ class ZionWorxImport(SongImport):
* Note: This is the default format of the Python ``csv`` module.
"""
def doImport(self):
def do_import(self):
"""
Receive a CSV file (from a ZionWorx database dump) to import.
"""
with open(self.import_source, 'rb') as songs_file:
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
'DefaultStyle']
'DefaultStyle']
songs_reader = csv.DictReader(songs_file, field_names)
try:
records = list(songs_reader)
except csv.Error as e:
self.logError(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'),
translate('SongsPlugin.ZionWorxImport', 'Line %d: %s') % (songs_reader.line_num, e))
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'),
translate('SongsPlugin.ZionWorxImport', 'Line %d: %s') % (songs_reader.line_num, e))
return
num_records = len(records)
log.info('%s records found in CSV file' % num_records)
@ -98,20 +99,20 @@ class ZionWorxImport(SongImport):
for index, record in enumerate(records, 1):
if self.stop_import_flag:
return
self.setDefaults()
self.set_defaults()
try:
self.title = self._decode(record['Title1'])
if record['Title2']:
self.alternate_title = self._decode(record['Title2'])
self.parse_author(self._decode(record['Writer']))
self.addCopyright(self._decode(record['Copyright']))
self.add_copyright(self._decode(record['Copyright']))
lyrics = self._decode(record['Lyrics'])
except UnicodeDecodeError as e:
self.logError(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
continue
except TypeError as e:
self.logError(translate(
self.log_error(translate(
'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx CSV format.'), 'TypeError: %s' % e)
return
verse = ''
@ -119,19 +120,18 @@ class ZionWorxImport(SongImport):
if line and not line.isspace():
verse += line + '\n'
elif verse:
self.addVerse(verse)
self.add_verse(verse)
verse = ''
if verse:
self.addVerse(verse)
self.add_verse(verse)
title = self.title
if not self.finish():
self.logError(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
+ (': "' + title + '"' if title else ''))
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
+ (': "' + title + '"' if title else ''))
def _decode(self, str):
"""
Decodes CSV input to unicode, stripping all control characters (except
new lines).
Decodes CSV input to unicode, stripping all control characters (except new lines).
"""
# This encoding choice seems OK. ZionWorx has no option for setting the
# encoding for its songs, so we assume encoding is always the same.

View File

@ -314,7 +314,7 @@ class SongsPlugin(Plugin):
self.application.process_events()
for db in song_dbs:
importer = OpenLPSongImport(self.manager, filename=db)
importer.doImport(progress)
importer.do_import(progress)
self.application.process_events()
progress.setValue(song_count)
self.media_item.on_search_text_button_clicked()

View File

@ -1,3 +1,28 @@
"""
Tests for the Bibles plugin
"""
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################

View File

@ -85,3 +85,5 @@ class TestLib(TestCase):
# THEN: It should be False
self.assertFalse(has_verse_list, 'The SearchResults object should have a verse list')

View File

@ -83,7 +83,7 @@ class TestVerseReferenceList(TestCase):
# THEN: The current index should be 0 and the end pointer of the entry should be '2'
self.assertEqual(reference_list.current_index, 0, 'The current index should be 0')
self.assertEqual(reference_list.verse_list[0]['end'], next_verse,
'The end in first entry should be %u' % next_verse)
'The end in first entry should be %u' % next_verse)
def add_another_verse_test(self):
"""
@ -124,8 +124,8 @@ class TestVerseReferenceList(TestCase):
# THEN: the data will be appended to the list
self.assertEqual(len(reference_list.version_list), 1, 'The version data should be appended')
self.assertEqual(reference_list.version_list[0],
{'version': version, 'copyright': copyright_, 'permission': permission},
'The version data should be appended')
{'version': version, 'copyright': copyright_, 'permission': permission},
'The version data should be appended')
def add_existing_version_test(self):
"""

View File

@ -118,7 +118,7 @@ class TestPptviewDocument(TestCase):
self.mock_os.path.isdir.return_value = True
self.mock_controller.process.OpenPPT.return_value = 0
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.filepath = 'test\path.ppt'
instance.file_path = 'test\path.ppt'
if os.name == 'nt':
result = instance.load_presentation()
@ -138,7 +138,7 @@ class TestPptviewDocument(TestCase):
self.mock_os.path.isdir.return_value = False
self.mock_controller.process.OpenPPT.return_value = -1
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.filepath = 'test\path.ppt'
instance.file_path = 'test\path.ppt'
if os.name == 'nt':
result = instance.load_presentation()

View File

@ -154,13 +154,13 @@ class TestEasyWorshipSongImport(TestCase):
def find_field_exists_test(self):
"""
Test finding an existing field in a given list using the :mod:`findField`
Test finding an existing field in a given list using the :mod:`find_field`
"""
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions.
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager)
importer.fieldDescs = TEST_FIELD_DESCS
importer.field_descriptions = TEST_FIELD_DESCS
# WHEN: Called with a field name that exists
existing_fields = ['Title', 'Text Percentage Bottom', 'RecID', 'Default Background', 'Words',
@ -168,28 +168,28 @@ class TestEasyWorshipSongImport(TestCase):
for field_name in existing_fields:
# THEN: The item corresponding the index returned should have the same name attribute
self.assertEquals(importer.fieldDescs[importer.findField(field_name)].name, field_name)
self.assertEquals(importer.field_descriptions[importer.find_field(field_name)].name, field_name)
def find_non_existing_field_test(self):
"""
Test finding an non-existing field in a given list using the :mod:`findField`
Test finding an non-existing field in a given list using the :mod:`find_field`
"""
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager)
importer.fieldDescs = TEST_FIELD_DESCS
importer.field_descriptions = TEST_FIELD_DESCS
# WHEN: Called with a field name that does not exist
non_existing_fields = ['BK Gradient Shading', 'BK Gradient Variant', 'Favorite', 'Copyright']
for field_name in non_existing_fields:
# THEN: The importer object should not be None
self.assertRaises(IndexError, importer.findField, field_name)
self.assertRaises(IndexError, importer.find_field, field_name)
def set_record_struct_test(self):
"""
Test the :mod:`setRecordStruct` module
Test the :mod:`set_record_struct` module
"""
# GIVEN: A mocked out SongImport class, a mocked out struct class, and a mocked out "manager" and a list of
# field descriptions
@ -198,17 +198,17 @@ class TestEasyWorshipSongImport(TestCase):
mocked_manager = MagicMock()
importer = EasyWorshipSongImport(mocked_manager)
# WHEN: setRecordStruct is called with a list of field descriptions
return_value = importer.setRecordStruct(TEST_FIELD_DESCS)
# WHEN: set_record_struct is called with a list of field descriptions
return_value = importer.set_record_struct(TEST_FIELD_DESCS)
# THEN: setRecordStruct should return None and Struct should be called with a value representing
# THEN: set_record_struct should return None and Struct should be called with a value representing
# the list of field descriptions
self.assertIsNone(return_value, 'setRecordStruct should return None')
self.assertIsNone(return_value, 'set_record_struct should return None')
mocked_struct.Struct.assert_called_with('>50sHIB250s250s10sQ')
def get_field_test(self):
"""
Test the :mod:`getField` module
Test the :mod:`get_field` module
"""
# GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
@ -216,20 +216,20 @@ class TestEasyWorshipSongImport(TestCase):
importer = EasyWorshipSongImport(mocked_manager)
importer.encoding = TEST_DATA_ENCODING
importer.fields = TEST_FIELDS
importer.fieldDescs = TEST_FIELD_DESCS
importer.field_descriptions = TEST_FIELD_DESCS
field_results = [(0, b'A Heart Like Thine'), (1, 100), (2, 102), (3, True), (6, None), (7, None)]
# WHEN: Called with test data
for field_index, result in field_results:
return_value = importer.getField(field_index)
return_value = importer.get_field(field_index)
# THEN: getField should return the known results
# THEN: get_field should return the known results
self.assertEquals(return_value, result,
'getField should return "%s" when called with "%s"' % (result, TEST_FIELDS[field_index]))
'get_field should return "%s" when called with "%s"' % (result, TEST_FIELDS[field_index]))
def get_memo_field_test(self):
"""
Test the :mod:`getField` module
Test the :mod:`get_field` module
"""
for test_results in GET_MEMO_FIELD_TEST_RESULTS:
# GIVEN: A mocked out SongImport class, a mocked out "manager", a mocked out memo_file and an encoding
@ -237,20 +237,20 @@ class TestEasyWorshipSongImport(TestCase):
mocked_manager = MagicMock()
mocked_memo_file = MagicMock()
importer = EasyWorshipSongImport(mocked_manager)
importer.memoFile = mocked_memo_file
importer.memo_file = mocked_memo_file
importer.encoding = TEST_DATA_ENCODING
# WHEN: Supplied with test fields and test field descriptions
importer.fields = TEST_FIELDS
importer.fieldDescs = TEST_FIELD_DESCS
importer.field_descriptions = TEST_FIELD_DESCS
field_index = test_results[0]
mocked_memo_file.read.return_value = test_results[1]
get_field_result = test_results[2]['return']
get_field_read_calls = test_results[2]['read']
get_field_seek_calls = test_results[2]['seek']
# THEN: getField should return the appropriate value with the appropriate mocked objects being called
self.assertEquals(importer.getField(field_index), get_field_result)
# THEN: get_field should return the appropriate value with the appropriate mocked objects being called
self.assertEquals(importer.get_field(field_index), get_field_result)
for call in get_field_read_calls:
mocked_memo_file.read.assert_any_call(call)
for call in get_field_seek_calls:
@ -261,7 +261,7 @@ class TestEasyWorshipSongImport(TestCase):
def do_import_source_test(self):
"""
Test the :mod:`doImport` module opens the correct files
Test the :mod:`do_import` module opens the correct files
"""
# GIVEN: A mocked out SongImport class, a mocked out "manager"
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
@ -273,14 +273,14 @@ class TestEasyWorshipSongImport(TestCase):
# WHEN: Supplied with an import source
importer.import_source = 'Songs.DB'
# THEN: doImport should return None having called os.path.isfile
self.assertIsNone(importer.doImport(), 'doImport should return None')
# THEN: do_import should return None having called os.path.isfile
self.assertIsNone(importer.do_import(), 'do_import should return None')
mocked_os_path.isfile.assert_any_call('Songs.DB')
mocked_os_path.isfile.assert_any_call('Songs.MB')
def do_import_database_validity_test(self):
"""
Test the :mod:`doImport` module handles invalid database files correctly
Test the :mod:`do_import` module handles invalid database files correctly
"""
# GIVEN: A mocked out SongImport class, os.path and a mocked out "manager"
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
@ -293,13 +293,13 @@ class TestEasyWorshipSongImport(TestCase):
# WHEN: DB file size is less than 0x800
mocked_os_path.getsize.return_value = 0x7FF
# THEN: doImport should return None having called os.path.isfile
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
# THEN: do_import should return None having called os.path.isfile
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
mocked_os_path.getsize.assert_any_call('Songs.DB')
def do_import_memo_validty_test(self):
"""
Test the :mod:`doImport` module handles invalid memo files correctly
Test the :mod:`do_import` module handles invalid memo files correctly
"""
# GIVEN: A mocked out SongImport class, a mocked out "manager"
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
@ -316,9 +316,9 @@ class TestEasyWorshipSongImport(TestCase):
struct_unpack_return_values = [(0, 0x700, 2, 0, 0), (0, 0x800, 0, 0, 0), (0, 0x800, 5, 0, 0)]
mocked_struct.unpack.side_effect = struct_unpack_return_values
# THEN: doImport should return None having called closed the open files db and memo files.
# THEN: do_import should return None having called closed the open files db and memo files.
for effect in struct_unpack_return_values:
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
self.assertEqual(mocked_open().close.call_count, 2,
'The open db and memo files should have been closed')
mocked_open().close.reset_mock()
@ -326,7 +326,7 @@ class TestEasyWorshipSongImport(TestCase):
def code_page_to_encoding_test(self):
"""
Test the :mod:`doImport` converts the code page to the encoding correctly
Test the :mod:`do_import` converts the code page to the encoding correctly
"""
# GIVEN: A mocked out SongImport class, a mocked out "manager"
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
@ -345,8 +345,8 @@ class TestEasyWorshipSongImport(TestCase):
mocked_struct.unpack.side_effect = struct_unpack_return_values
mocked_retrieve_windows_encoding.return_value = False
# THEN: doImport should return None having called retrieve_windows_encoding with the correct encoding.
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
# THEN: do_import should return None having called retrieve_windows_encoding with the correct encoding.
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
mocked_retrieve_windows_encoding.assert_call(encoding)
def file_import_test(self):
@ -369,8 +369,8 @@ class TestEasyWorshipSongImport(TestCase):
importer = EasyWorshipSongImportLogger(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = False
importer.addAuthor = mocked_add_author
importer.addVerse = mocked_add_verse
importer.add_author = mocked_add_author
importer.add_verse = mocked_add_verse
importer.title = mocked_title
importer.finish = mocked_finish
importer.topics = []
@ -378,9 +378,9 @@ class TestEasyWorshipSongImport(TestCase):
# WHEN: Importing each file
importer.import_source = os.path.join(TEST_PATH, 'Songs.DB')
# THEN: doImport should return none, the song data should be as expected, and finish should have been
# THEN: do_import should return none, the song data should be as expected, and finish should have been
# called.
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
for song_data in SONG_TEST_DATA:
title = song_data['title']
author_calls = song_data['authors']
@ -394,11 +394,11 @@ class TestEasyWorshipSongImport(TestCase):
if song_copyright:
self.assertEqual(importer.copyright, song_copyright)
if ccli_number:
self.assertEquals(importer.ccliNumber, ccli_number, 'ccliNumber for %s should be %s'
self.assertEquals(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s'
% (title, ccli_number))
for verse_text, verse_tag in add_verse_calls:
mocked_add_verse.assert_any_call(verse_text, verse_tag)
if verse_order_list:
self.assertEquals(importer.verseOrderList, verse_order_list, 'verseOrderList for %s should be %s'
self.assertEquals(importer.verse_order_list, verse_order_list, 'verse_order_list for %s should be %s'
% (title, verse_order_list))
mocked_finish.assert_called_with()

View File

@ -116,17 +116,17 @@ class TestFoilPresenter(TestCase):
def create_foil_presenter_test(self):
"""
Test creating an instance of the FoilPresenter class
Test creating an instance of the foil_presenter class
"""
# GIVEN: A mocked out "manager" and "SongImport" instance
mocked_manager = MagicMock()
mocked_song_import = MagicMock()
# WHEN: An FoilPresenter instance is created
# WHEN: An foil_presenter instance is created
foil_presenter_instance = FoilPresenter(mocked_manager, mocked_song_import)
# THEN: The instance should not be None
self.assertIsNotNone(foil_presenter_instance, 'FoilPresenter instance should not be none')
self.assertIsNotNone(foil_presenter_instance, 'foil_presenter instance should not be none')
def no_xml_test(self):
"""
@ -169,7 +169,7 @@ class TestFoilPresenter(TestCase):
# WHEN: xml_to_song is called with a string without an xml encoding declaration
foil_presenter_instance.xml_to_song('<foilpresenterfolie>')
# THEN: the string shiuld have been left intact
# THEN: the string should have been left intact
self.mocked_re.compile.sub.called_with('<foilpresenterfolie>')
def process_lyrics_no_verses_test(self):
@ -188,7 +188,7 @@ class TestFoilPresenter(TestCase):
# WHEN: _process_lyrics is called
result = foil_presenter_instance._process_lyrics(mock_foilpresenterfolie, mocked_song)
# THEN: _process_lyrics should return None and the song_import logError method should have been called once
# THEN: _process_lyrics should return None and the song_import log_error method should have been called once
self.assertIsNone(result)
self.mocked_song_import.logError.assert_called_once_with('Element Text', 'Translated String')
self.mocked_song_import.log_error.assert_called_once_with('Element Text', 'Translated String')
self.process_lyrics_patcher.start()

View File

@ -73,7 +73,7 @@ class TestSongBeamerImport(TestCase):
def invalid_import_source_test(self):
"""
Test SongBeamerImport.doImport handles different invalid import_source values
Test SongBeamerImport.do_import handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
@ -87,14 +87,14 @@ class TestSongBeamerImport(TestCase):
for source in ['not a list', 0]:
importer.import_source = source
# THEN: doImport should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.doImport(), 'doImport should return None when import_source is not a list')
# THEN: do_import should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False,
'setMaxium on import_wizard.progress_bar should not have been called')
def valid_import_source_test(self):
"""
Test SongBeamerImport.doImport handles different invalid import_source values
Test SongBeamerImport.do_import handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
@ -107,10 +107,10 @@ class TestSongBeamerImport(TestCase):
# WHEN: Import source is a list
importer.import_source = ['List', 'of', 'files']
# THEN: doImport should return none and the progress bar setMaximum should be called with the length of
# THEN: do_import should return none and the progress bar setMaximum should be called with the length of
# import_source.
self.assertIsNone(importer.doImport(),
'doImport should return None when import_source is a list and stop_import_flag is True')
self.assertIsNone(importer.do_import(),
'do_import should return None when import_source is a list and stop_import_flag is True')
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
def file_import_test(self):
@ -130,7 +130,7 @@ class TestSongBeamerImport(TestCase):
importer = SongBeamerImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = False
importer.addVerse = mocked_add_verse
importer.add_verse = mocked_add_verse
importer.finish = mocked_finish
# WHEN: Importing each file
@ -140,16 +140,16 @@ class TestSongBeamerImport(TestCase):
song_book_name = SONG_TEST_DATA[song_file]['song_book_name']
song_number = SONG_TEST_DATA[song_file]['song_number']
# THEN: doImport should return none, the song data should be as expected, and finish should have been
# THEN: do_import should return none, the song data should be as expected, and finish should have been
# called.
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (song_file, title))
for verse_text, verse_tag in add_verse_calls:
mocked_add_verse.assert_any_call(verse_text, verse_tag)
if song_book_name:
self.assertEquals(importer.songBookName, song_book_name, 'songBookName for %s should be "%s"'
self.assertEquals(importer.song_book_name, song_book_name, 'song_book_name for %s should be "%s"'
% (song_file, song_book_name))
if song_number:
self.assertEquals(importer.songNumber, song_number, 'songNumber for %s should be %s'
self.assertEquals(importer.song_number, song_number, 'song_number for %s should be %s'
% (song_file, song_number))
mocked_finish.assert_called_with()

View File

@ -43,6 +43,7 @@ TEST_PATH = os.path.abspath(
class TestSongShowPlusFileImport(SongImportTestHelper):
def __init__(self, *args, **kwargs):
self.importer_class_name = 'SongShowPlusImport'
self.importer_module_name = 'songshowplusimport'
@ -78,7 +79,7 @@ class TestSongShowPlusImport(TestCase):
def invalid_import_source_test(self):
"""
Test SongShowPlusImport.doImport handles different invalid import_source values
Test SongShowPlusImport.do_import handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.songshowplusimport.SongImport'):
@ -92,14 +93,14 @@ class TestSongShowPlusImport(TestCase):
for source in ['not a list', 0]:
importer.import_source = source
# THEN: doImport should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.doImport(), 'doImport should return None when import_source is not a list')
# THEN: do_import should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False,
'setMaximum on import_wizard.progress_bar should not have been called')
def valid_import_source_test(self):
"""
Test SongShowPlusImport.doImport handles different invalid import_source values
Test SongShowPlusImport.do_import handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.songshowplusimport.SongImport'):
@ -112,10 +113,10 @@ class TestSongShowPlusImport(TestCase):
# WHEN: Import source is a list
importer.import_source = ['List', 'of', 'files']
# THEN: doImport should return none and the progress bar setMaximum should be called with the length of
# THEN: do_import should return none and the progress bar setMaximum should be called with the length of
# import_source.
self.assertIsNone(importer.doImport(),
'doImport should return None when import_source is a list and stop_import_flag is True')
self.assertIsNone(importer.do_import(),
'do_import should return None when import_source is a list and stop_import_flag is True')
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
def to_openlp_verse_tag_test(self):

View File

@ -152,7 +152,7 @@ class TestWorshipCenterProSongImport(TestCase):
Test that exceptions raised by pyodbc are handled
"""
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module, a mocked out translate method,
# a mocked "manager" and a mocked out logError method.
# a mocked "manager" and a mocked out log_error method.
with patch('openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
patch('openlp.plugins.songs.lib.worshipcenterproimport.pyodbc.connect') as mocked_pyodbc_connect, \
patch('openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
@ -160,17 +160,17 @@ class TestWorshipCenterProSongImport(TestCase):
mocked_log_error = MagicMock()
mocked_translate.return_value = 'Translated Text'
importer = WorshipCenterProImport(mocked_manager)
importer.logError = mocked_log_error
importer.log_error = mocked_log_error
importer.import_source = 'import_source'
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]
mocked_pyodbc_connect.side_effect = pyodbc_errors
# WHEN: Calling the doImport method
# WHEN: Calling the do_import method
for effect in pyodbc_errors:
return_value = importer.doImport()
return_value = importer.do_import()
# THEN: doImport should return None, and pyodbc, translate & logError are called with known calls
self.assertIsNone(return_value, 'doImport should return None when pyodbc raises an exception.')
# THEN: do_import should return None, and pyodbc, translate & log_error are called with known calls
self.assertIsNone(return_value, 'do_import should return None when pyodbc raises an exception.')
mocked_pyodbc_connect.assert_called_with( 'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
mocked_translate.assert_called_with('SongsPlugin.WorshipCenterProImport',
'Unable to connect the WorshipCenter Pro database.')
@ -181,7 +181,7 @@ class TestWorshipCenterProSongImport(TestCase):
Test that a simulated WorshipCenter Pro recordset is imported correctly
"""
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module with a simulated recordset, a mocked out
# translate method, a mocked "manager", addVerse method & mocked_finish method.
# translate method, a mocked "manager", add_verse method & mocked_finish method.
with patch('openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
patch('openlp.plugins.songs.lib.worshipcenterproimport.pyodbc') as mocked_pyodbc, \
patch('openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
@ -194,17 +194,17 @@ class TestWorshipCenterProSongImport(TestCase):
importer = WorshipCenterProImportLogger(mocked_manager)
importer.import_source = 'import_source'
importer.import_wizard = mocked_import_wizard
importer.addVerse = mocked_add_verse
importer.add_verse = mocked_add_verse
importer.stop_import_flag = False
importer.finish = mocked_finish
# WHEN: Calling the doImport method
return_value = importer.doImport()
# WHEN: Calling the do_import method
return_value = importer.do_import()
# THEN: doImport should return None, and pyodbc, import_wizard, importer.title and addVerse are called with
# THEN: do_import should return None, and pyodbc, import_wizard, importer.title and add_verse are called with
# known calls
self.assertIsNone(return_value, 'doImport should return None when pyodbc raises an exception.')
self.assertIsNone(return_value, 'do_import should return None when pyodbc raises an exception.')
mocked_pyodbc.connect.assert_called_with('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
mocked_pyodbc.connect().cursor.assert_any_call()
mocked_pyodbc.connect().cursor().execute.assert_called_with('SELECT ID, Field, Value FROM __SONGDATA')
@ -220,4 +220,4 @@ class TestWorshipCenterProSongImport(TestCase):
for call in verse_calls:
mocked_add_verse.assert_any_call(call)
self.assertEqual(mocked_add_verse.call_count, add_verse_call_count,
'Incorrect number of calls made to addVerse')
'Incorrect number of calls made to add_verse')

View File

@ -51,9 +51,9 @@ class SongImportTestHelper(TestCase):
Patch and set up the mocks required.
"""
self.add_copyright_patcher = patch(
'openlp.plugins.songs.lib.%s.%s.addCopyright' % (self.importer_module_name, self.importer_class_name))
'openlp.plugins.songs.lib.%s.%s.add_copyright' % (self.importer_module_name, self.importer_class_name))
self.add_verse_patcher = patch(
'openlp.plugins.songs.lib.%s.%s.addVerse' % (self.importer_module_name, self.importer_class_name))
'openlp.plugins.songs.lib.%s.%s.add_verse' % (self.importer_module_name, self.importer_class_name))
self.finish_patcher = patch(
'openlp.plugins.songs.lib.%s.%s.finish' % (self.importer_module_name, self.importer_class_name))
self.parse_author_patcher = patch(
@ -107,16 +107,16 @@ class SongImportTestHelper(TestCase):
topics = self._get_data(result_data, 'topics')
verse_order_list = self._get_data(result_data, 'verse_order_list')
# THEN: doImport should return none, the song data should be as expected, and finish should have been
# THEN: do_import should return none, the song data should be as expected, and finish should have been
# called.
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title))
for author in author_calls:
self.mocked_parse_author.assert_any_call(author)
if song_copyright:
self.mocked_add_copyright.assert_called_with(song_copyright)
if ccli_number:
self.assertEquals(importer.ccliNumber, ccli_number, 'ccliNumber for %s should be %s'
self.assertEquals(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s'
% (source_file_name, ccli_number))
for verse_text, verse_tag in add_verse_calls:
self.mocked_add_verse.assert_any_call(verse_text, verse_tag)
@ -126,13 +126,13 @@ class SongImportTestHelper(TestCase):
self.assertEquals(importer.comments, comments, 'comments for %s should be "%s"'
% (source_file_name, comments))
if song_book_name:
self.assertEquals(importer.songBookName, song_book_name, 'songBookName for %s should be "%s"'
self.assertEquals(importer.song_book_name, song_book_name, 'song_book_name for %s should be "%s"'
% (source_file_name, song_book_name))
if song_number:
self.assertEquals(importer.songNumber, song_number, 'songNumber for %s should be %s'
self.assertEquals(importer.song_number, song_number, 'song_number for %s should be %s'
% (source_file_name, song_number))
if verse_order_list:
self.assertEquals(importer.verseOrderList, [], 'verseOrderList for %s should be %s'
self.assertEquals(importer.verse_order_list, [], 'verse_order_list for %s should be %s'
% (source_file_name, verse_order_list))
self.mocked_finish.assert_called_with()

View File

@ -1 +1,28 @@
__author__ = 'tim'
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################

View File

@ -1,3 +1,31 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.plugin.bible.lib.https package.
"""

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Functional tests to test the Bible Manager class and related methods.
"""
from unittest import TestCase
from openlp.core.common import Registry, Settings
from openlp.plugins.bibles.lib import BibleManager, LanguageSelection
from tests.interfaces import MagicMock, patch
from tests.utils.constants import TEST_RESOURCES_PATH
from tests.helpers.testmixin import TestMixin
class TestBibleManager(TestCase, TestMixin):
def setUp(self):
"""
Set up the environment for testing bible queries with 1 Timothy 3
"""
self.build_settings()
Registry.create()
Registry().register('service_list', MagicMock())
Registry().register('application', MagicMock())
bible_settings = {
'bibles/proxy name': '',
'bibles/db type': 'sqlite',
'bibles/book name language': LanguageSelection.Bible,
'bibles/verse separator': '',
'bibles/range separator': '',
'bibles/list separator': '',
'bibles/end separator': '',
}
Settings().extend_default_settings(bible_settings)
with patch('openlp.core.common.applocation.Settings') as mocked_class, \
patch('openlp.core.common.AppLocation.get_section_data_path') as mocked_get_section_data_path, \
patch('openlp.core.common.AppLocation.get_files') as mocked_get_files:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_files()
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False
mocked_get_files.return_value = ["tests.sqlite"]
mocked_get_section_data_path.return_value = TEST_RESOURCES_PATH + "/bibles"
self.manager = BibleManager(MagicMock())
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
self.destroy_settings()
def get_books_test(self):
"""
Test the get_books method
"""
# GIVEN given a bible in the bible manager
# WHEN asking for the books of the bible
books = self.manager.get_books('tests')
# THEN a list of books should be returned
self.assertEqual(66, len(books), 'There should be 66 books in the bible')
def get_book_by_id_test(self):
"""
Test the get_book_by_id method
"""
# GIVEN given a bible in the bible manager
# WHEN asking for the book of the bible
book = self.manager.get_book_by_id('tests', 54)
# THEN a book should be returned
self.assertEqual('1 Timothy', book.name, '1 Timothy should have been returned from the bible')
def get_chapter_count_test(self):
"""
Test the get_chapter_count method
"""
# GIVEN given a bible in the bible manager
# WHEN asking for the chapters in a book of the bible
book = self.manager.get_book_by_id('tests', 54)
chapter = self.manager.get_chapter_count('tests', book)
# THEN the chapter count should be returned
self.assertEqual(6, chapter, '1 Timothy should have 6 chapters returned from the bible')
def get_verse_count_by_book_ref_id_test(self):
"""
Test the get_verse_count_by_book_ref_id method
"""
# GIVEN given a bible in the bible manager
# WHEN asking for the number of verses in a book of the bible
verses = self.manager.get_verse_count_by_book_ref_id('tests', 54, 3)
# THEN the chapter count should be returned
self.assertEqual(16, verses, '1 Timothy v3 should have 16 verses returned from the bible')

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the lib submodule of the Bibles plugin.
"""
from unittest import TestCase
from tests.interfaces import MagicMock, patch
from openlp.core.common import Registry, Settings
from openlp.plugins.bibles.lib import BibleManager, parse_reference, LanguageSelection
from tests.utils.constants import TEST_RESOURCES_PATH
from tests.helpers.testmixin import TestMixin
class TestBibleManager(TestCase, TestMixin):
def setUp(self):
"""
Set up the environment for testing bible parse reference
"""
self.build_settings()
Registry.create()
Registry().register('service_list', MagicMock())
Registry().register('application', MagicMock())
bible_settings = {
'bibles/proxy name': '',
'bibles/db type': 'sqlite',
'bibles/book name language': LanguageSelection.Bible,
'bibles/verse separator': '',
'bibles/range separator': '',
'bibles/list separator': '',
'bibles/end separator': '',
}
Settings().extend_default_settings(bible_settings)
with patch('openlp.core.common.applocation.Settings') as mocked_class, \
patch('openlp.core.common.AppLocation.get_section_data_path') as mocked_get_section_data_path, \
patch('openlp.core.common.AppLocation.get_files') as mocked_get_files:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_files()
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False
mocked_get_files.return_value = ["tests.sqlite"]
mocked_get_section_data_path.return_value = TEST_RESOURCES_PATH + "/bibles"
self.manager = BibleManager(MagicMock())
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
self.destroy_settings()
def parse_reference_one_test(self):
"""
Test the parse_reference method with 1 Timothy 1
"""
# GIVEN given a bible in the bible manager
# WHEN asking to parse the bible reference
results = parse_reference('1 Timothy 1', self.manager.db_cache['tests'], MagicMock(), 54)
# THEN a verse array should be returned
self.assertEquals([(54, 1, 1, -1)], results , "The bible verses should matches the expected results")
def parse_reference_two_test(self):
"""
Test the parse_reference method with 1 Timothy 1:1-2
"""
# GIVEN given a bible in the bible manager
# WHEN asking to parse the bible reference
results = parse_reference('1 Timothy 1:1-2', self.manager.db_cache['tests'], MagicMock(), 54)
# THEN a verse array should be returned
self.assertEquals([(54, 1, 1, 2)], results , "The bible verses should matches the expected results")
def parse_reference_three_test(self):
"""
Test the parse_reference method with 1 Timothy 1:1-2
"""
# GIVEN given a bible in the bible manager
# WHEN asking to parse the bible reference
results = parse_reference('1 Timothy 1:1-2:1', self.manager.db_cache['tests'], MagicMock(), 54)
# THEN a verse array should be returned
self.assertEquals([(54,1,1,-1),(54,2,1,1)], results , "The bible verses should matches the expected results")

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################

View File

@ -1,3 +1,31 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Module to test the EditCustomForm.
"""

Binary file not shown.