diff --git a/openlp/core/common/openlpmixin.py b/openlp/core/common/openlpmixin.py
index 94505b86b..2f1d5bbba 100644
--- a/openlp/core/common/openlpmixin.py
+++ b/openlp/core/common/openlpmixin.py
@@ -71,6 +71,12 @@ class OpenLPMixin(object):
"""
self.logger.info(message)
+ def log_warning(self, message):
+ """
+ Common log warning handler
+ """
+ self.logger.warning(message)
+
def log_error(self, message):
"""
Common log error handler which prints the calling path
diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py
index 1708b14bd..8f70fafff 100644
--- a/openlp/core/common/settings.py
+++ b/openlp/core/common/settings.py
@@ -214,7 +214,10 @@ class Settings(QtCore.QSettings):
('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
- ('advanced/default image', '/core/logo file', []) # Default image renamed + moved to general after 2.4.
+ ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
+ ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
+ ('shortcuts/offlineHelpItem', 'shortcuts/HelpItem', []), # Online and Offline help were combined in 2.6.
+ ('shortcuts/onlineHelpItem', 'shortcuts/HelpItem', []) # Online and Offline help were combined in 2.6.
]
@staticmethod
@@ -261,11 +264,10 @@ class Settings(QtCore.QSettings):
'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)],
'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)],
'shortcuts/desktopScreen': [QtGui.QKeySequence(QtCore.Qt.Key_D)],
- 'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
- QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
+ 'shortcuts/desktopScreenEnable': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
+ 'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],
'shortcuts/editSong': [],
- 'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
'shortcuts/expand': [QtGui.QKeySequence(QtCore.Qt.Key_Plus)],
'shortcuts/exportThemeItem': [],
'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New)],
@@ -274,6 +276,7 @@ class Settings(QtCore.QSettings):
'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save)],
'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open)],
'shortcuts/goLive': [],
+ 'shortcuts/HelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
'shortcuts/importThemeItem': [],
'shortcuts/importBibleItem': [],
'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
@@ -334,8 +337,6 @@ class Settings(QtCore.QSettings):
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
'shortcuts/newService': [],
- 'shortcuts/offlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
- 'shortcuts/onlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
'shortcuts/openService': [],
'shortcuts/saveService': [],
'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
@@ -380,6 +381,7 @@ class Settings(QtCore.QSettings):
'shortcuts/themeScreen': [QtGui.QKeySequence(QtCore.Qt.Key_T)],
'shortcuts/toolsReindexItem': [],
'shortcuts/toolsFindDuplicates': [],
+ 'shortcuts/toolsSongListReport': [],
'shortcuts/toolsAlertItem': [QtGui.QKeySequence(QtCore.Qt.Key_F7)],
'shortcuts/toolsFirstTimeWizard': [],
'shortcuts/toolsOpenDataFolder': [],
diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py
index 41aa4c4e7..05b968506 100644
--- a/openlp/core/lib/__init__.py
+++ b/openlp/core/lib/__init__.py
@@ -131,16 +131,16 @@ def build_icon(icon):
location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string.
:return: The build icon.
"""
- button_icon = QtGui.QIcon()
if isinstance(icon, QtGui.QIcon):
- button_icon = icon
- elif isinstance(icon, str):
- if icon.startswith(':/'):
- button_icon.addPixmap(QtGui.QPixmap(icon), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- else:
- button_icon.addPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(icon)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ return icon
+ pix_map = None
+ button_icon = QtGui.QIcon()
+ if isinstance(icon, str):
+ pix_map = QtGui.QPixmap(icon)
elif isinstance(icon, QtGui.QImage):
- button_icon.addPixmap(QtGui.QPixmap.fromImage(icon), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ pix_map = QtGui.QPixmap.fromImage(icon)
+ if pix_map:
+ button_icon.addPixmap(pix_map, QtGui.QIcon.Normal, QtGui.QIcon.Off)
return button_icon
diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py
index 2edea93cf..0eaeff476 100644
--- a/openlp/core/lib/mediamanageritem.py
+++ b/openlp/core/lib/mediamanageritem.py
@@ -266,7 +266,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
self.search_text_layout.setObjectName('search_text_layout')
self.search_text_label = QtWidgets.QLabel(self.search_widget)
self.search_text_label.setObjectName('search_text_label')
- self.search_text_edit = SearchEdit(self.search_widget)
+ self.search_text_edit = SearchEdit(self.search_widget, self.settings_section)
self.search_text_edit.setObjectName('search_text_edit')
self.search_text_label.setBuddy(self.search_text_edit)
self.search_text_layout.addRow(self.search_text_label, self.search_text_edit)
diff --git a/openlp/core/lib/searchedit.py b/openlp/core/lib/searchedit.py
index 41e54957d..1fcc0e618 100644
--- a/openlp/core/lib/searchedit.py
+++ b/openlp/core/lib/searchedit.py
@@ -26,6 +26,7 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.lib import build_icon
from openlp.core.lib.ui import create_widget_action
+from openlp.core.common import Settings
log = logging.getLogger(__name__)
@@ -37,11 +38,12 @@ class SearchEdit(QtWidgets.QLineEdit):
searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant)
cleared = QtCore.pyqtSignal()
- def __init__(self, parent):
+ def __init__(self, parent, settings_section):
"""
Constructor.
"""
- super(SearchEdit, self).__init__(parent)
+ super().__init__(parent)
+ self.settings_section = settings_section
self._current_search_type = -1
self.clear_button = QtWidgets.QToolButton(self)
self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png'))
@@ -100,14 +102,10 @@ class SearchEdit(QtWidgets.QLineEdit):
menu = self.menu_button.menu()
for action in menu.actions():
if identifier == action.data():
- # setPlaceholderText has been implemented in Qt 4.7 and in at least PyQt 4.9 (I am not sure, if it was
- # implemented in PyQt 4.8).
- try:
- self.setPlaceholderText(action.placeholder_text)
- except AttributeError:
- pass
+ self.setPlaceholderText(action.placeholder_text)
self.menu_button.setDefaultAction(action)
self._current_search_type = identifier
+ Settings().setValue('{section}/last search type'.format(section=self.settings_section), identifier)
self.searchTypeChanged.emit(identifier)
return True
@@ -130,14 +128,10 @@ class SearchEdit(QtWidgets.QLineEdit):
(2, ":/songs/authors.png", "Authors", "Search Authors...")
"""
menu = QtWidgets.QMenu(self)
- first = None
for identifier, icon, title, placeholder in items:
action = create_widget_action(
menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered)
action.placeholder_text = placeholder
- if first is None:
- first = action
- self._current_search_type = identifier
if not hasattr(self, 'menu_button'):
self.menu_button = QtWidgets.QToolButton(self)
self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png'))
@@ -146,7 +140,8 @@ class SearchEdit(QtWidgets.QLineEdit):
self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }')
self.menu_button.resize(QtCore.QSize(28, 18))
self.menu_button.setMenu(menu)
- self.menu_button.setDefaultAction(first)
+ self.set_current_search_type(
+ Settings().value('{section}/last search type'.format(section=self.settings_section)))
self.menu_button.show()
self._update_style_sheet()
diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py
index 7341287c7..36e039cee 100644
--- a/openlp/core/ui/exceptiondialog.py
+++ b/openlp/core/ui/exceptiondialog.py
@@ -38,7 +38,7 @@ class Ui_ExceptionDialog(object):
Set up the UI.
"""
exception_dialog.setObjectName('exception_dialog')
- exception_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ exception_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
self.exception_layout = QtWidgets.QVBoxLayout(exception_dialog)
self.exception_layout.setObjectName('exception_layout')
self.message_layout = QtWidgets.QHBoxLayout()
@@ -97,9 +97,9 @@ class Ui_ExceptionDialog(object):
translate('OpenLP.ExceptionDialog', 'Please describe what you were trying to do. '
' If possible, write in English.'))
exception_part1 = (translate('OpenLP.ExceptionDialog',
- 'Oops, OpenLP hit a problem and couldn\'t recover!
'
- 'You can help the OpenLP developers to fix this'
- ' by sending them a bug report to {email}{newlines}'
+ 'Oops, OpenLP hit a problem and couldn\'t recover!
'
+ 'You can help the OpenLP developers to fix this'
+ ' by sending them a bug report to {email}{newlines}'
).format(email=' bugs@openlp.org',
newlines='
'))
self.message_label.setText(
@@ -107,7 +107,7 @@ class Ui_ExceptionDialog(object):
'No email app? You can save this '
'information to a file and '
'send it from your mail on browser via an attachment.
'
- 'Thank you for being part of making OpenLP better! '
+ 'Thank you for being part of making OpenLP better! '
).format(first_part=exception_part1))
self.send_report_button.setText(translate('OpenLP.ExceptionDialog', 'Send E-Mail'))
self.save_report_button.setText(translate('OpenLP.ExceptionDialog', 'Save to File'))
diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py
index 2122acfaa..9e58ac8b2 100644
--- a/openlp/core/ui/exceptionform.py
+++ b/openlp/core/ui/exceptionform.py
@@ -208,7 +208,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
self.__button_state(False)
self.description_word_count.setText(
translate('OpenLP.ExceptionDialog', 'Please enter a more detailed description of the situation'
- ))
+ ''))
def on_attach_file_button_clicked(self):
"""
diff --git a/openlp/core/ui/filerenamedialog.py b/openlp/core/ui/filerenamedialog.py
index ede60bcfb..cc4574279 100644
--- a/openlp/core/ui/filerenamedialog.py
+++ b/openlp/core/ui/filerenamedialog.py
@@ -37,7 +37,7 @@ class Ui_FileRenameDialog(object):
Set up the UI
"""
file_rename_dialog.setObjectName('file_rename_dialog')
- file_rename_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ file_rename_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
file_rename_dialog.resize(300, 10)
self.dialog_layout = QtWidgets.QGridLayout(file_rename_dialog)
self.dialog_layout.setObjectName('dialog_layout')
diff --git a/openlp/core/ui/firsttimelanguagedialog.py b/openlp/core/ui/firsttimelanguagedialog.py
index dd1389ba2..f32ec9076 100644
--- a/openlp/core/ui/firsttimelanguagedialog.py
+++ b/openlp/core/ui/firsttimelanguagedialog.py
@@ -38,7 +38,7 @@ class Ui_FirstTimeLanguageDialog(object):
Set up the UI.
"""
language_dialog.setObjectName('language_dialog')
- language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ language_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
language_dialog.resize(300, 50)
self.dialog_layout = QtWidgets.QVBoxLayout(language_dialog)
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py
index 7b93c1680..63937ebb2 100644
--- a/openlp/core/ui/firsttimewizard.py
+++ b/openlp/core/ui/firsttimewizard.py
@@ -55,7 +55,7 @@ class UiFirstTimeWizard(object):
:param first_time_wizard: The wizard form
"""
first_time_wizard.setObjectName('first_time_wizard')
- first_time_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ first_time_wizard.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
first_time_wizard.resize(550, 386)
first_time_wizard.setModal(True)
first_time_wizard.setOptions(QtWidgets.QWizard.IndependentPages | QtWidgets.QWizard.NoBackButtonOnStartPage |
diff --git a/openlp/core/ui/formattingtagdialog.py b/openlp/core/ui/formattingtagdialog.py
index 19412921d..83ec18115 100644
--- a/openlp/core/ui/formattingtagdialog.py
+++ b/openlp/core/ui/formattingtagdialog.py
@@ -38,7 +38,7 @@ class Ui_FormattingTagDialog(object):
Set up the UI
"""
formatting_tag_dialog.setObjectName('formatting_tag_dialog')
- formatting_tag_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ formatting_tag_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
formatting_tag_dialog.resize(725, 548)
self.list_data_grid_layout = QtWidgets.QVBoxLayout(formatting_tag_dialog)
self.list_data_grid_layout.setContentsMargins(8, 8, 8, 8)
diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py
index 463aad73f..629e55e0f 100644
--- a/openlp/core/ui/generaltab.py
+++ b/openlp/core/ui/generaltab.py
@@ -44,7 +44,7 @@ class GeneralTab(SettingsTab):
self.logo_file = ':/graphics/openlp-splash-screen.png'
self.logo_background_color = '#ffffff'
self.screens = ScreenList()
- self.icon_path = ':/icon/openlp-logo-16x16.png'
+ self.icon_path = ':/icon/openlp-logo.svg'
general_translated = translate('OpenLP.GeneralTab', 'General')
super(GeneralTab, self).__init__(parent, 'Core', general_translated)
diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py
index 92b29d16f..b8bd126dd 100644
--- a/openlp/core/ui/mainwindow.py
+++ b/openlp/core/ui/mainwindow.py
@@ -309,21 +309,13 @@ class Ui_MainWindow(object):
self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
if is_win():
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm')
- self.offline_help_item = create_action(main_window, 'offlineHelpItem',
- icon=':/system/system_help_contents.png',
- can_shortcuts=True,
- category=UiStrings().Help, triggers=self.on_offline_help_clicked)
elif is_macosx():
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
'..', 'Resources', 'OpenLP.help')
- self.offline_help_item = create_action(main_window, 'offlineHelpItem',
- icon=':/system/system_help_contents.png',
- can_shortcuts=True,
- category=UiStrings().Help, triggers=self.on_offline_help_clicked)
- self.on_line_help_item = create_action(main_window, 'onlineHelpItem',
- icon=':/system/system_online_help.png',
- can_shortcuts=True,
- category=UiStrings().Help, triggers=self.on_online_help_clicked)
+ self.on_help_item = create_action(main_window, 'HelpItem',
+ icon=':/system/system_help_contents.png',
+ can_shortcuts=True,
+ category=UiStrings().Help, triggers=self.on_help_clicked)
self.web_site_item = create_action(main_window, 'webSiteItem', can_shortcuts=True, category=UiStrings().Help)
# Shortcuts not connected to buttons or menu entries.
self.search_shortcut_action = create_action(main_window,
@@ -362,11 +354,7 @@ class Ui_MainWindow(object):
add_actions(self.tools_menu, (self.tools_open_data_folder, None))
add_actions(self.tools_menu, (self.tools_first_time_wizard, None))
add_actions(self.tools_menu, [self.update_theme_images])
- if (is_win() or is_macosx()) and (hasattr(sys, 'frozen') and sys.frozen == 1):
- add_actions(self.help_menu, (self.offline_help_item, self.on_line_help_item, None, self.web_site_item,
- self.about_item))
- else:
- add_actions(self.help_menu, (self.on_line_help_item, None, self.web_site_item, self.about_item))
+ add_actions(self.help_menu, (self.on_help_item, None, self.web_site_item, self.about_item))
add_actions(self.menu_bar, (self.file_menu.menuAction(), self.view_menu.menuAction(),
self.tools_menu.menuAction(), self.settings_menu.menuAction(), self.help_menu.menuAction()))
add_actions(self, [self.search_shortcut_action])
@@ -462,9 +450,7 @@ class Ui_MainWindow(object):
'from here.'))
self.about_item.setText(translate('OpenLP.MainWindow', '&About'))
self.about_item.setStatusTip(translate('OpenLP.MainWindow', 'More information about OpenLP.'))
- if is_win() or is_macosx():
- self.offline_help_item.setText(translate('OpenLP.MainWindow', '&User Guide'))
- self.on_line_help_item.setText(translate('OpenLP.MainWindow', '&Online Help'))
+ self.on_help_item.setText(translate('OpenLP.MainWindow', '&User Manual'))
self.search_shortcut_action.setText(UiStrings().Search)
self.search_shortcut_action.setToolTip(
translate('OpenLP.MainWindow', 'Jump to the search box of the current active plugin.'))
@@ -778,18 +764,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
import webbrowser
webbrowser.open_new('http://openlp.org/')
- def on_offline_help_clicked(self):
+ def on_help_clicked(self):
"""
- Load the local OpenLP help file
+ If is_macosx or is_win, open the local OpenLP help file.
+ Use the Online manual in other cases. (Linux)
"""
- QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + self.local_help_file))
-
- def on_online_help_clicked(self):
- """
- Load the online OpenLP manual
- """
- import webbrowser
- webbrowser.open_new('http://manual.openlp.org/')
+ if is_macosx() or is_win():
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + self.local_help_file))
+ else:
+ import webbrowser
+ webbrowser.open_new('http://manual.openlp.org/')
def on_about_item_clicked(self):
"""
diff --git a/openlp/core/ui/media/systemplayer.py b/openlp/core/ui/media/systemplayer.py
index a36ce445b..e632f9b7b 100644
--- a/openlp/core/ui/media/systemplayer.py
+++ b/openlp/core/ui/media/systemplayer.py
@@ -95,6 +95,16 @@ class SystemPlayer(MediaPlayer):
mime_type_list.append(ext)
log.info('MediaPlugin: %s extensions: %s', mime_type, ' '.join(extensions))
+ def disconnect_slots(self, signal):
+ """
+ Safely disconnect the slots from `signal`
+ """
+ try:
+ signal.disconnect()
+ except TypeError:
+ # If disconnect() is called on a signal without slots, it throws a TypeError
+ pass
+
def setup(self, display):
"""
Set up the player widgets
@@ -160,6 +170,7 @@ class SystemPlayer(MediaPlayer):
if start_time > 0:
self.seek(display, controller.media_info.start_time * 1000)
self.volume(display, controller.media_info.volume)
+ self.disconnect_slots(display.media_player.durationChanged)
display.media_player.durationChanged.connect(functools.partial(self.set_duration, controller))
self.set_state(MediaState.Playing, display)
display.video_widget.raise_()
diff --git a/openlp/core/ui/plugindialog.py b/openlp/core/ui/plugindialog.py
index ad461cd44..cd41d77a8 100644
--- a/openlp/core/ui/plugindialog.py
+++ b/openlp/core/ui/plugindialog.py
@@ -38,7 +38,7 @@ class Ui_PluginViewDialog(object):
Set up the UI
"""
plugin_view_dialog.setObjectName('plugin_view_dialog')
- plugin_view_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ plugin_view_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
plugin_view_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
self.plugin_layout = QtWidgets.QVBoxLayout(plugin_view_dialog)
self.plugin_layout.setObjectName('plugin_layout')
diff --git a/openlp/core/ui/printservicedialog.py b/openlp/core/ui/printservicedialog.py
index c658d1496..f82bfecd4 100644
--- a/openlp/core/ui/printservicedialog.py
+++ b/openlp/core/ui/printservicedialog.py
@@ -50,7 +50,7 @@ class Ui_PrintServiceDialog(object):
Set up the UI
"""
print_service_dialog.setObjectName('print_service_dialog')
- print_service_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ print_service_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
print_service_dialog.resize(664, 594)
self.main_layout = QtWidgets.QVBoxLayout(print_service_dialog)
self.main_layout.setSpacing(0)
diff --git a/openlp/core/ui/projector/editform.py b/openlp/core/ui/projector/editform.py
index f4cf8a774..f15d5b550 100644
--- a/openlp/core/ui/projector/editform.py
+++ b/openlp/core/ui/projector/editform.py
@@ -47,7 +47,7 @@ class Ui_ProjectorEditForm(object):
Create the interface layout.
"""
edit_projector_dialog.setObjectName('edit_projector_dialog')
- edit_projector_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo-32x32.png'))
+ edit_projector_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
edit_projector_dialog.setMinimumWidth(400)
edit_projector_dialog.setModal(True)
# Define the basic layout
diff --git a/openlp/core/ui/serviceitemeditdialog.py b/openlp/core/ui/serviceitemeditdialog.py
index bbff73f3d..6dc45a847 100644
--- a/openlp/core/ui/serviceitemeditdialog.py
+++ b/openlp/core/ui/serviceitemeditdialog.py
@@ -38,7 +38,7 @@ class Ui_ServiceItemEditDialog(object):
Set up the UI
"""
serviceItemEditDialog.setObjectName('serviceItemEditDialog')
- serviceItemEditDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ serviceItemEditDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
self.dialog_layout = QtWidgets.QGridLayout(serviceItemEditDialog)
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
self.dialog_layout.setSpacing(8)
diff --git a/openlp/core/ui/settingsdialog.py b/openlp/core/ui/settingsdialog.py
index 3b6c4bc00..97c4c7233 100644
--- a/openlp/core/ui/settingsdialog.py
+++ b/openlp/core/ui/settingsdialog.py
@@ -38,7 +38,7 @@ class Ui_SettingsDialog(object):
Set up the UI
"""
settings_dialog.setObjectName('settings_dialog')
- settings_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ settings_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
settings_dialog.resize(800, 700)
self.dialog_layout = QtWidgets.QGridLayout(settings_dialog)
self.dialog_layout.setObjectName('dialog_layout')
diff --git a/openlp/core/ui/shortcutlistdialog.py b/openlp/core/ui/shortcutlistdialog.py
index ed1156025..4de2d4861 100644
--- a/openlp/core/ui/shortcutlistdialog.py
+++ b/openlp/core/ui/shortcutlistdialog.py
@@ -72,7 +72,7 @@ class Ui_ShortcutListDialog(object):
Set up the UI
"""
shortcutListDialog.setObjectName('shortcutListDialog')
- shortcutListDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ shortcutListDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
shortcutListDialog.resize(500, 438)
self.shortcut_list_layout = QtWidgets.QVBoxLayout(shortcutListDialog)
self.shortcut_list_layout.setObjectName('shortcut_list_layout')
diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py
index 1266d1cc4..eb91313c9 100644
--- a/openlp/core/ui/shortcutlistform.py
+++ b/openlp/core/ui/shortcutlistform.py
@@ -426,11 +426,11 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
is_valid = False
if not is_valid:
text = translate('OpenLP.ShortcutListDialog',
- 'The shortcut "{key}" is already assigned to another action, please'
- ' use a different shortcut.'
+ 'The shortcut "{key}" is already assigned to another action,\n'
+ 'please use a different shortcut.'
).format(key=self.get_shortcut_string(key_sequence))
self.main_window.warning_message(translate('OpenLP.ShortcutListDialog', 'Duplicate Shortcut'),
- text, for_display=True)
+ text)
self.dialog_was_shown = True
return is_valid
diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py
index 7121e5227..13a09d5df 100644
--- a/openlp/core/ui/slidecontroller.py
+++ b/openlp/core/ui/slidecontroller.py
@@ -234,25 +234,32 @@ class SlideController(DisplayController, RegistryProperties):
self.hide_menu.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
self.hide_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Hide'), self.toolbar))
self.toolbar.add_toolbar_widget(self.hide_menu)
- self.blank_screen = create_action(self, 'blankScreen',
- text=translate('OpenLP.SlideController', 'Blank Screen'),
- icon=':/slides/slide_blank.png',
- checked=False, can_shortcuts=True, category=self.category,
- triggers=self.on_blank_display)
- self.theme_screen = create_action(self, 'themeScreen',
- text=translate('OpenLP.SlideController', 'Blank to Theme'),
- icon=':/slides/slide_theme.png',
- checked=False, can_shortcuts=True, category=self.category,
- triggers=self.on_theme_display)
+ # The order of the blank to modes in Shortcuts list comes from here.
+ self.desktop_screen_enable = create_action(self, 'desktopScreenEnable',
+ text=translate('OpenLP.SlideController', 'Show Desktop'),
+ icon=':/slides/slide_desktop.png', can_shortcuts=True,
+ context=QtCore.Qt.WidgetWithChildrenShortcut,
+ category=self.category, triggers=self.on_hide_display_enable)
self.desktop_screen = create_action(self, 'desktopScreen',
- text=translate('OpenLP.SlideController', 'Show Desktop'),
+ text=translate('OpenLP.SlideController', 'Toggle Desktop'),
icon=':/slides/slide_desktop.png',
checked=False, can_shortcuts=True, category=self.category,
triggers=self.on_hide_display)
+ self.theme_screen = create_action(self, 'themeScreen',
+ text=translate('OpenLP.SlideController', 'Toggle Blank to Theme'),
+ icon=':/slides/slide_theme.png',
+ checked=False, can_shortcuts=True, category=self.category,
+ triggers=self.on_theme_display)
+ self.blank_screen = create_action(self, 'blankScreen',
+ text=translate('OpenLP.SlideController', 'Toggle Blank Screen'),
+ icon=':/slides/slide_blank.png',
+ checked=False, can_shortcuts=True, category=self.category,
+ triggers=self.on_blank_display)
self.hide_menu.setDefaultAction(self.blank_screen)
self.hide_menu.menu().addAction(self.blank_screen)
self.hide_menu.menu().addAction(self.theme_screen)
self.hide_menu.menu().addAction(self.desktop_screen)
+ self.hide_menu.menu().addAction(self.desktop_screen_enable)
# Wide menu of display control buttons.
self.blank_screen_button = QtWidgets.QToolButton(self.toolbar)
self.blank_screen_button.setObjectName('blank_screen_button')
@@ -512,23 +519,6 @@ class SlideController(DisplayController, RegistryProperties):
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category,
triggers=self.service_next)
- self.escape_item = create_action(parent, 'escapeItem',
- text=translate('OpenLP.SlideController', 'Escape Item'),
- can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
- category=self.category,
- triggers=self.live_escape)
-
- def live_escape(self, field=None):
- """
- If you press ESC on the live screen it should close the display temporarily.
- """
- self.display.setVisible(False)
- self.media_controller.media_stop(self)
- # Stop looping if active
- if self.play_slides_loop.isChecked():
- self.on_play_slides_loop(False)
- elif self.play_slides_once.isChecked():
- self.on_play_slides_once(False)
def toggle_display(self, action):
"""
@@ -622,7 +612,7 @@ class SlideController(DisplayController, RegistryProperties):
widget.addActions([
self.previous_item, self.next_item,
self.previous_service, self.next_service,
- self.escape_item,
+ self.desktop_screen_enable,
self.desktop_screen,
self.theme_screen,
self.blank_screen])
@@ -965,7 +955,7 @@ class SlideController(DisplayController, RegistryProperties):
else:
Registry().execute('live_display_show')
else:
- self.live_escape()
+ self.on_hide_display_enable()
def on_slide_blank(self):
"""
@@ -1025,6 +1015,7 @@ class SlideController(DisplayController, RegistryProperties):
def on_hide_display(self, checked=None):
"""
Handle the Hide screen button
+ This toggles the desktop screen.
:param checked: the new state of the of the widget
"""
@@ -1043,6 +1034,20 @@ class SlideController(DisplayController, RegistryProperties):
self.update_preview()
self.on_toggle_loop()
+ def on_hide_display_enable(self, checked=None):
+ """
+ Handle the on_hide_display_enable
+ This only enables the desktop screen.
+
+ :param checked: the new state of the of the widget
+ """
+ self.blank_screen.setChecked(False)
+ self.theme_screen.setChecked(False)
+ Registry().execute('live_display_hide', HideMode.Screen)
+ self.desktop_screen.setChecked(True)
+ self.update_preview()
+ self.on_toggle_loop()
+
def blank_plugin(self):
"""
Blank/Hide the display screen within a plugin if required.
diff --git a/openlp/core/ui/starttimedialog.py b/openlp/core/ui/starttimedialog.py
index 42287596c..e20d625d0 100644
--- a/openlp/core/ui/starttimedialog.py
+++ b/openlp/core/ui/starttimedialog.py
@@ -38,7 +38,7 @@ class Ui_StartTimeDialog(object):
Set up the UI
"""
StartTimeDialog.setObjectName('StartTimeDialog')
- StartTimeDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ StartTimeDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
StartTimeDialog.resize(350, 10)
self.dialog_layout = QtWidgets.QGridLayout(StartTimeDialog)
self.dialog_layout.setObjectName('dialog_layout')
diff --git a/openlp/core/ui/themelayoutdialog.py b/openlp/core/ui/themelayoutdialog.py
index 5289e1cc2..119611f33 100644
--- a/openlp/core/ui/themelayoutdialog.py
+++ b/openlp/core/ui/themelayoutdialog.py
@@ -38,7 +38,7 @@ class Ui_ThemeLayoutDialog(object):
Set up the UI
"""
themeLayoutDialog.setObjectName('themeLayoutDialogDialog')
- themeLayoutDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ themeLayoutDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
self.preview_layout = QtWidgets.QVBoxLayout(themeLayoutDialog)
self.preview_layout.setObjectName('preview_layout')
self.preview_area = QtWidgets.QWidget(themeLayoutDialog)
diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py
index 427e9836b..95262cf8f 100644
--- a/openlp/core/ui/themewizard.py
+++ b/openlp/core/ui/themewizard.py
@@ -40,7 +40,7 @@ class Ui_ThemeWizard(object):
Set up the UI
"""
theme_wizard.setObjectName('OpenLP.ThemeWizard')
- theme_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ theme_wizard.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
theme_wizard.setModal(True)
theme_wizard.setOptions(QtWidgets.QWizard.IndependentPages |
QtWidgets.QWizard.NoBackButtonOnStartPage | QtWidgets.QWizard.HaveCustomButton1)
diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py
index 81af23ae1..83df7a09c 100644
--- a/openlp/plugins/alerts/forms/alertdialog.py
+++ b/openlp/plugins/alerts/forms/alertdialog.py
@@ -39,7 +39,7 @@ class Ui_AlertDialog(object):
"""
alert_dialog.setObjectName('alert_dialog')
alert_dialog.resize(400, 300)
- alert_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ alert_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
self.alert_dialog_layout = QtWidgets.QGridLayout(alert_dialog)
self.alert_dialog_layout.setObjectName('alert_dialog_layout')
self.alert_text_layout = QtWidgets.QFormLayout()
diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py
index f63b85a92..e9168d695 100644
--- a/openlp/plugins/bibles/bibleplugin.py
+++ b/openlp/plugins/bibles/bibleplugin.py
@@ -140,10 +140,10 @@ class BiblePlugin(Plugin):
def uses_theme(self, theme):
"""
- Called to find out if the bible plugin is currently using a theme. Returns ``1`` if the theme is being used,
- otherwise returns ``0``.
+ Called to find out if the bible plugin is currently using a theme.
:param theme: The theme
+ :return: 1 if the theme is being used, otherwise returns 0
"""
if str(self.settings_tab.bible_theme) == theme:
return 1
@@ -151,11 +151,11 @@ class BiblePlugin(Plugin):
def rename_theme(self, old_theme, new_theme):
"""
- Rename the theme the bible plugin is using making the plugin use the
- new name.
+ Rename the theme the bible plugin is using, making the plugin use the new name.
: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.
+ :return: None
"""
self.settings_tab.bible_theme = new_theme
self.settings_tab.save()
diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py
index 3d02228ca..e1e062155 100644
--- a/openlp/plugins/bibles/forms/bibleimportform.py
+++ b/openlp/plugins/bibles/forms/bibleimportform.py
@@ -25,6 +25,7 @@ The bible import functions for OpenLP
import logging
import os
import urllib.error
+from lxml import etree
from PyQt5 import QtWidgets
try:
@@ -33,14 +34,15 @@ try:
except:
PYSWORD_AVAILABLE = False
-from openlp.core.common import AppLocation, Settings, UiStrings, translate, clean_filename
+from openlp.core.common import AppLocation, Settings, UiStrings, trace_error_handler, translate
+from openlp.core.common.languagemanager import get_locale_key
from openlp.core.lib.db import delete_database
+from openlp.core.lib.exceptions import ValidationError
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
-from openlp.core.common.languagemanager import get_locale_key
-from openlp.plugins.bibles.lib.manager import BibleFormat
from openlp.plugins.bibles.lib.db import clean_filename
from openlp.plugins.bibles.lib.importers.http import CWExtract, BGExtract, BSExtract
+from openlp.plugins.bibles.lib.manager import BibleFormat
log = logging.getLogger(__name__)
@@ -809,16 +811,22 @@ class BibleImportForm(OpenLPWizard):
sword_path=self.field('sword_zip_path'),
sword_key=self.sword_zipbible_combo_box.itemData(
self.sword_zipbible_combo_box.currentIndex()))
- if importer.do_import(license_version):
- self.manager.save_meta_data(license_version, license_version, license_copyright, license_permissions)
- self.manager.reload_bibles()
- if bible_type == BibleFormat.WebDownload:
- self.progress_label.setText(
- translate('BiblesPlugin.ImportWizardForm', 'Registered Bible. Please note, that verses will be '
- 'downloaded on demand and thus an internet connection is required.'))
- else:
- self.progress_label.setText(WizardStrings.FinishedImport)
- else:
- self.progress_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Your Bible import failed.'))
- del self.manager.db_cache[importer.name]
- delete_database(self.plugin.settings_section, importer.file)
+
+ try:
+ if importer.do_import(license_version) and not importer.stop_import_flag:
+ self.manager.save_meta_data(license_version, license_version, license_copyright, license_permissions)
+ self.manager.reload_bibles()
+ if bible_type == BibleFormat.WebDownload:
+ self.progress_label.setText(
+ translate('BiblesPlugin.ImportWizardForm', 'Registered Bible. Please note, that verses will be '
+ 'downloaded on demand and thus an internet connection is required.'))
+ else:
+ self.progress_label.setText(WizardStrings.FinishedImport)
+ return
+ except (AttributeError, ValidationError, etree.XMLSyntaxError):
+ log.exception('Importing bible failed')
+ trace_error_handler(log)
+
+ self.progress_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Your Bible import failed.'))
+ del self.manager.db_cache[importer.name]
+ delete_database(self.plugin.settings_section, importer.file)
diff --git a/openlp/plugins/bibles/forms/booknamedialog.py b/openlp/plugins/bibles/forms/booknamedialog.py
index 6679dc1f3..18adde74b 100644
--- a/openlp/plugins/bibles/forms/booknamedialog.py
+++ b/openlp/plugins/bibles/forms/booknamedialog.py
@@ -30,7 +30,7 @@ from openlp.core.lib.ui import create_button_box
class Ui_BookNameDialog(object):
def setupUi(self, book_name_dialog):
book_name_dialog.setObjectName('book_name_dialog')
- book_name_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ book_name_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
book_name_dialog.resize(400, 271)
self.book_name_layout = QtWidgets.QVBoxLayout(book_name_dialog)
self.book_name_layout.setSpacing(8)
diff --git a/openlp/plugins/bibles/forms/editbibledialog.py b/openlp/plugins/bibles/forms/editbibledialog.py
index f1e833637..73ff81239 100644
--- a/openlp/plugins/bibles/forms/editbibledialog.py
+++ b/openlp/plugins/bibles/forms/editbibledialog.py
@@ -32,7 +32,7 @@ from openlp.plugins.bibles.lib.db import BiblesResourcesDB
class Ui_EditBibleDialog(object):
def setupUi(self, edit_bible_dialog):
edit_bible_dialog.setObjectName('edit_bible_dialog')
- edit_bible_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ edit_bible_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
edit_bible_dialog.resize(520, 400)
edit_bible_dialog.setModal(True)
self.dialog_layout = QtWidgets.QVBoxLayout(edit_bible_dialog)
diff --git a/openlp/plugins/bibles/forms/languagedialog.py b/openlp/plugins/bibles/forms/languagedialog.py
index ea5c291a0..554c7e35d 100644
--- a/openlp/plugins/bibles/forms/languagedialog.py
+++ b/openlp/plugins/bibles/forms/languagedialog.py
@@ -30,7 +30,7 @@ from openlp.core.lib.ui import create_button_box
class Ui_LanguageDialog(object):
def setupUi(self, language_dialog):
language_dialog.setObjectName('language_dialog')
- language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ language_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
language_dialog.resize(400, 165)
self.language_layout = QtWidgets.QVBoxLayout(language_dialog)
self.language_layout.setSpacing(8)
diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py
index 804755d18..e730009e7 100644
--- a/openlp/plugins/bibles/lib/__init__.py
+++ b/openlp/plugins/bibles/lib/__init__.py
@@ -173,7 +173,7 @@ class BibleStrings(object):
def update_reference_separators():
"""
- Updates separators and matches for parsing and formating scripture references.
+ Updates separators and matches for parsing and formatting scripture references.
"""
default_separators = [
'|'.join([
@@ -215,7 +215,7 @@ def update_reference_separators():
# escape reserved characters
for character in '\\.^$*+?{}[]()':
source_string = source_string.replace(character, '\\' + character)
- # add various unicode alternatives
+ # add various Unicode alternatives
source_string = source_string.replace('-', '(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])')
source_string = source_string.replace(',', '(?:[,\u201A])')
REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = '\s*(?:{source})\s*'.format(source=source_string)
diff --git a/openlp/plugins/bibles/lib/bibleimport.py b/openlp/plugins/bibles/lib/bibleimport.py
index 4d015223b..5eb22e47d 100644
--- a/openlp/plugins/bibles/lib/bibleimport.py
+++ b/openlp/plugins/bibles/lib/bibleimport.py
@@ -20,25 +20,84 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-import logging
-
from lxml import etree, objectify
+from zipfile import is_zipfile
-from openlp.core.common import OpenLPMixin, languages
+from openlp.core.common import OpenLPMixin, Registry, RegistryProperties, languages, translate
from openlp.core.lib import ValidationError
-from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
-
-log = logging.getLogger(__name__)
+from openlp.core.lib.ui import critical_error_message_box
+from openlp.plugins.bibles.lib.db import AlternativeBookNamesDB, BibleDB, BiblesResourcesDB
-class BibleImport(OpenLPMixin, BibleDB):
+class BibleImport(OpenLPMixin, RegistryProperties, BibleDB):
"""
Helper class to import bibles from a third party source into OpenLP
"""
- # TODO: Test
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filename = kwargs['filename'] if 'filename' in kwargs else None
+ self.wizard = None
+ self.stop_import_flag = False
+ Registry().register_function('openlp_stop_wizard', self.stop_import)
+
+ @staticmethod
+ def is_compressed(file):
+ """
+ Check if the supplied file is compressed
+
+ :param file: A path to the file to check
+ """
+ if is_zipfile(file):
+ critical_error_message_box(
+ message=translate('BiblesPlugin.BibleImport',
+ 'The file "{file}" you supplied is compressed. You must decompress it before import.'
+ ).format(file=file))
+ return True
+ return False
+
+ def get_book_ref_id_by_name(self, book, maxbooks=66, language_id=None):
+ """
+ Find the book id from the name or abbreviation of the book. If it doesn't currently exist, ask the user.
+
+ :param book: The name or abbreviation of the book
+ :param maxbooks: The number of books in the bible
+ :param language_id: The language_id of the bible
+ :return: The id of the bible, or None
+ """
+ self.log_debug('BibleDB.get_book_ref_id_by_name:("{book}", "{lang}")'.format(book=book, lang=language_id))
+ book_temp = BiblesResourcesDB.get_book(book, True)
+ if book_temp:
+ return book_temp['id']
+ book_id = BiblesResourcesDB.get_alternative_book_name(book)
+ if book_id:
+ return book_id
+ book_id = AlternativeBookNamesDB.get_book_reference_id(book)
+ if book_id:
+ return book_id
+ from openlp.plugins.bibles.forms import BookNameForm
+ book_name = BookNameForm(self.wizard)
+ if book_name.exec(book, self.get_books(), maxbooks) and book_name.book_id:
+ AlternativeBookNamesDB.create_alternative_book_name(book, book_name.book_id, language_id)
+ return book_name.book_id
+
+ 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.
+ Return the language id of a bible.
+
+ :param bible_name: The language the bible is.
+ """
+ self.log_debug('BibleImpoer.get_language()')
+ from openlp.plugins.bibles.forms import LanguageForm
+ language_id = None
+ language_form = LanguageForm(self.wizard)
+ if language_form.exec(bible_name):
+ combo_box = language_form.language_combo_box
+ language_id = combo_box.itemData(combo_box.currentIndex())
+ if not language_id:
+ return None
+ self.save_meta('language_id', language_id)
+ return language_id
def get_language_id(self, file_language=None, bible_name=None):
"""
@@ -58,8 +117,8 @@ class BibleImport(OpenLPMixin, BibleDB):
language_id = self.get_language(bible_name)
if not language_id:
# User cancelled get_language dialog
- log.error('Language detection failed when importing from "{name}". User aborted language selection.'
- .format(name=bible_name))
+ self.log_error('Language detection failed when importing from "{name}". User aborted language selection.'
+ .format(name=bible_name))
return None
self.save_meta('language_id', language_id)
return language_id
@@ -77,7 +136,7 @@ class BibleImport(OpenLPMixin, BibleDB):
if name:
book_ref_id = self.get_book_ref_id_by_name(name, no_of_books, language_id)
else:
- log.debug('No book name supplied. Falling back to guess_id')
+ self.log_debug('No book name supplied. Falling back to guess_id')
book_ref_id = guess_id
if not book_ref_id:
raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.filename))
@@ -87,8 +146,7 @@ class BibleImport(OpenLPMixin, BibleDB):
'importing {file}'.format(book_ref=book_ref_id, file=self.filename))
return self.create_book(name, book_ref_id, book_details['testament_id'])
- @staticmethod
- def parse_xml(filename, use_objectify=False, elements=None, tags=None):
+ def parse_xml(self, filename, use_objectify=False, elements=None, tags=None):
"""
Parse and clean the supplied file by removing any elements or tags we don't use.
:param filename: The filename of the xml file to parse. Str
@@ -97,17 +155,80 @@ class BibleImport(OpenLPMixin, BibleDB):
:param tags: A tuple of element names (Str) to remove, preserving their content.
:return: The root element of the xml document
"""
- with open(filename, 'rb') as import_file:
- # NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
- # detection, and the two mechanisms together interfere with each other.
- if not use_objectify:
- tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
- else:
- tree = objectify.parse(import_file, parser=objectify.makeparser(recover=True))
- if elements:
- # Strip tags we don't use - remove content
- etree.strip_elements(tree, elements, with_tail=False)
- if tags:
- # Strip tags we don't use - keep content
- etree.strip_tags(tree, tags)
- return tree.getroot()
+ try:
+ with open(filename, 'rb') as import_file:
+ # NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own
+ # encoding detection, and the two mechanisms together interfere with each other.
+ if not use_objectify:
+ tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
+ else:
+ tree = objectify.parse(import_file, parser=objectify.makeparser(recover=True))
+ if elements or tags:
+ self.wizard.increment_progress_bar(
+ translate('BiblesPlugin.OsisImport', 'Removing unused tags (this may take a few minutes)...'))
+ if elements:
+ # Strip tags we don't use - remove content
+ etree.strip_elements(tree, elements, with_tail=False)
+ if tags:
+ # Strip tags we don't use - keep content
+ etree.strip_tags(tree, tags)
+ return tree.getroot()
+ except OSError as e:
+ self.log_exception('Opening {file_name} failed.'.format(file_name=e.filename))
+ critical_error_message_box(
+ title='An Error Occured When Opening A File',
+ message='The following error occurred when trying to open\n{file_name}:\n\n{error}'
+ .format(file_name=e.filename, error=e.strerror))
+ return None
+
+ def register(self, wizard):
+ """
+ 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.
+
+ :param wizard: The actual Qt wizard form.
+ """
+ self.wizard = wizard
+ return self.name
+
+ def set_current_chapter(self, book_name, chapter_name):
+ self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport', 'Importing {book} {chapter}...')
+ .format(book=book_name, chapter=chapter_name))
+
+ def stop_import(self):
+ """
+ Stops the import of the Bible.
+ """
+ self.log_debug('Stopping import')
+ self.stop_import_flag = True
+
+ def validate_xml_file(self, filename, tag):
+ """
+ Validate the supplied file
+
+ :param filename: The supplied file
+ :param tag: The expected root tag type
+ :return: True if valid. ValidationError is raised otherwise.
+ """
+ if BibleImport.is_compressed(filename):
+ raise ValidationError(msg='Compressed file')
+ bible = self.parse_xml(filename, use_objectify=True)
+ if bible is None:
+ raise ValidationError(msg='Error when opening file')
+ root_tag = bible.tag.lower()
+ bible_type = translate('BiblesPlugin.BibleImport', 'unknown type of',
+ 'This looks like an unknown type of XML bible.')
+ if root_tag == tag:
+ return True
+ elif root_tag == 'bible':
+ bible_type = "OpenSong"
+ elif root_tag == '{http://www.bibletechnologies.net/2003/osis/namespace}osis':
+ bible_type = 'OSIS'
+ elif root_tag == 'xmlbible':
+ bible_type = 'Zefania'
+ critical_error_message_box(
+ message=translate('BiblesPlugin.BibleImport',
+ 'Incorrect Bible file type supplied. This looks like an {bible_type} XML bible.'
+ .format(bible_type=bible_type)))
+ raise ValidationError(msg='Invalid xml.')
diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py
index e5142ae98..3a0a89757 100644
--- a/openlp/plugins/bibles/lib/db.py
+++ b/openlp/plugins/bibles/lib/db.py
@@ -33,7 +33,7 @@ from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import class_mapper, mapper, relation
from sqlalchemy.orm.exc import UnmappedClassError
-from openlp.core.common import Registry, RegistryProperties, AppLocation, translate, clean_filename
+from openlp.core.common import AppLocation, translate, clean_filename
from openlp.core.lib.db import BaseModel, init_db, Manager
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib import upgrade
@@ -106,7 +106,7 @@ def init_schema(url):
return session
-class BibleDB(Manager, RegistryProperties):
+class BibleDB(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,
@@ -140,7 +140,6 @@ class BibleDB(Manager, RegistryProperties):
raise KeyError('Missing keyword argument "path".')
if 'name' not in kwargs and 'file' not in kwargs:
raise KeyError('Missing keyword argument "name" or "file".')
- self.stop_import_flag = False
if 'name' in kwargs:
self.name = kwargs['name']
if not isinstance(self.name, str):
@@ -153,15 +152,6 @@ class BibleDB(Manager, RegistryProperties):
self.get_name()
if 'path' in kwargs:
self.path = kwargs['path']
- self.wizard = None
- Registry().register_function('openlp_stop_wizard', self.stop_import)
-
- def stop_import(self):
- """
- Stops the import of the Bible.
- """
- log.debug('Stopping import')
- self.stop_import_flag = True
def get_name(self):
"""
@@ -171,17 +161,6 @@ class BibleDB(Manager, RegistryProperties):
self.name = version_name.value if version_name else None
return self.name
- def register(self, wizard):
- """
- 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.
-
- :param wizard: The actual Qt wizard form.
- """
- self.wizard = wizard
- return self.name
-
def create_book(self, name, bk_ref_id, testament=1):
"""
Add a book to the database.
@@ -306,26 +285,6 @@ class BibleDB(Manager, RegistryProperties):
log.debug('BibleDB.get_book_by_book_ref_id("{ref}")'.format(ref=ref_id))
return self.get_object_filtered(Book, Book.book_reference_id.like(ref_id))
- def get_book_ref_id_by_name(self, book, maxbooks, language_id=None):
- log.debug('BibleDB.get_book_ref_id_by_name:("{book}", "{lang}")'.format(book=book, lang=language_id))
- book_id = None
- if BiblesResourcesDB.get_book(book, True):
- book_temp = BiblesResourcesDB.get_book(book, True)
- book_id = book_temp['id']
- elif BiblesResourcesDB.get_alternative_book_name(book):
- book_id = BiblesResourcesDB.get_alternative_book_name(book)
- elif AlternativeBookNamesDB.get_book_reference_id(book):
- book_id = AlternativeBookNamesDB.get_book_reference_id(book)
- else:
- from openlp.plugins.bibles.forms import BookNameForm
- book_name = BookNameForm(self.wizard)
- if book_name.exec(book, self.get_books(), maxbooks):
- book_id = book_name.book_id
- if book_id:
- AlternativeBookNamesDB.create_alternative_book_name(
- book, book_id, language_id)
- return book_id
-
def get_book_ref_id_by_localised_name(self, book, language_selection):
"""
Return the id of a named book.
@@ -462,25 +421,6 @@ class BibleDB(Manager, RegistryProperties):
return 0
return count
- 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.
- Return the language id of a bible.
-
- :param bible_name: The language the bible is.
- """
- log.debug('BibleDB.get_language()')
- from openlp.plugins.bibles.forms import LanguageForm
- language_id = None
- language_form = LanguageForm(self.wizard)
- if language_form.exec(bible_name):
- combo_box = language_form.language_combo_box
- language_id = combo_box.itemData(combo_box.currentIndex())
- if not language_id:
- return None
- self.save_meta('language_id', language_id)
- return language_id
-
def dump_bible(self):
"""
Utility debugging method to dump the contents of a bible.
diff --git a/openlp/plugins/bibles/lib/importers/csvbible.py b/openlp/plugins/bibles/lib/importers/csvbible.py
index 549cec581..d9edd6cc7 100644
--- a/openlp/plugins/bibles/lib/importers/csvbible.py
+++ b/openlp/plugins/bibles/lib/importers/csvbible.py
@@ -50,7 +50,6 @@ There are two acceptable formats of the verses file. They are:
All CSV files are expected to use a comma (',') as the delimiter and double quotes ('"') as the quote symbol.
"""
import csv
-import logging
from collections import namedtuple
from openlp.core.common import get_file_encoding, translate
@@ -58,8 +57,6 @@ from openlp.core.lib.exceptions import ValidationError
from openlp.plugins.bibles.lib.bibleimport import BibleImport
-log = logging.getLogger(__name__)
-
Book = namedtuple('Book', 'id, testament_id, name, abbreviation')
Verse = namedtuple('Verse', 'book_id_name, chapter_number, number, text')
@@ -68,15 +65,13 @@ class CSVBible(BibleImport):
"""
This class provides a specialisation for importing of CSV Bibles.
"""
- log.info('CSVBible loaded')
-
def __init__(self, *args, **kwargs):
"""
Loads a Bible from a set of CSV files. This class assumes the files contain all the information and a clean
bible is being loaded.
"""
- log.info(self.__class__.__name__)
super().__init__(*args, **kwargs)
+ self.log_info(self.__class__.__name__)
self.books_file = kwargs['booksfile']
self.verses_file = kwargs['versefile']
@@ -123,12 +118,11 @@ class CSVBible(BibleImport):
number_of_books = len(books)
for book in books:
if self.stop_import_flag:
- return None
+ break
self.wizard.increment_progress_bar(
translate('BiblesPlugin.CSVBible', 'Importing books... {book}').format(book=book.name))
self.find_and_create_book(book.name, number_of_books, self.language_id)
book_list.update({int(book.id): book.name})
- self.application.process_events()
return book_list
def process_verses(self, verses, books):
@@ -142,7 +136,7 @@ class CSVBible(BibleImport):
book_ptr = None
for verse in verses:
if self.stop_import_flag:
- return None
+ break
verse_book = self.get_book_name(verse.book_id_name, books)
if book_ptr != verse_book:
book = self.get_book(verse_book)
@@ -151,9 +145,7 @@ class CSVBible(BibleImport):
translate('BiblesPlugin.CSVBible', 'Importing verses from {book}...',
'Importing verses from ...').format(book=book.name))
self.session.commit()
- self.create_verse(book.id, verse.chapter_number, verse.number, verse.text)
- self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.'))
- self.application.process_events()
+ self.create_verse(book.id, int(verse.chapter_number), int(verse.number), verse.text)
self.session.commit()
def do_import(self, bible_name=None):
@@ -163,24 +155,16 @@ class CSVBible(BibleImport):
:param bible_name: Optional name of the bible being imported. Str or None
:return: True if the import was successful, False if it failed or was cancelled
"""
- try:
- self.language_id = self.get_language(bible_name)
- if not self.language_id:
- raise ValidationError(msg='Invalid language selected')
- books = self.parse_csv_file(self.books_file, Book)
- self.wizard.progress_bar.setValue(0)
- self.wizard.progress_bar.setMinimum(0)
- self.wizard.progress_bar.setMaximum(len(books))
- book_list = self.process_books(books)
- if self.stop_import_flag:
- return False
- verses = self.parse_csv_file(self.verses_file, Verse)
- self.wizard.progress_bar.setValue(0)
- self.wizard.progress_bar.setMaximum(len(books) + 1)
- self.process_verses(verses, book_list)
- if self.stop_import_flag:
- return False
- except ValidationError:
- log.exception('Could not import CSV bible')
+ self.language_id = self.get_language(bible_name)
+ if not self.language_id:
return False
+ books = self.parse_csv_file(self.books_file, Book)
+ self.wizard.progress_bar.setValue(0)
+ self.wizard.progress_bar.setMinimum(0)
+ self.wizard.progress_bar.setMaximum(len(books))
+ book_list = self.process_books(books)
+ verses = self.parse_csv_file(self.verses_file, Verse)
+ self.wizard.progress_bar.setValue(0)
+ self.wizard.progress_bar.setMaximum(len(books) + 1)
+ self.process_verses(verses, book_list)
return True
diff --git a/openlp/plugins/bibles/lib/importers/http.py b/openlp/plugins/bibles/lib/importers/http.py
index 6921c9005..d41187d93 100644
--- a/openlp/plugins/bibles/lib/importers/http.py
+++ b/openlp/plugins/bibles/lib/importers/http.py
@@ -493,7 +493,7 @@ class CWExtract(RegistryProperties):
for verse in verses_div:
self.application.process_events()
verse_number = int(verse.find('strong').contents[0])
- verse_span = verse.find('span')
+ verse_span = verse.find('span', class_='verse-%d' % verse_number)
tags_to_remove = verse_span.find_all(['a', 'sup'])
for tag in tags_to_remove:
tag.decompose()
diff --git a/openlp/plugins/bibles/lib/importers/opensong.py b/openlp/plugins/bibles/lib/importers/opensong.py
index 43c1cf8ca..d75f37e03 100644
--- a/openlp/plugins/bibles/lib/importers/opensong.py
+++ b/openlp/plugins/bibles/lib/importers/opensong.py
@@ -20,109 +20,126 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-import logging
-from lxml import etree, objectify
-
-from openlp.core.common import translate, trace_error_handler
-from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.bibleimport import BibleImport
-from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
-log = logging.getLogger(__name__)
+def get_text(element):
+ """
+ Recursively get all text in an objectify element and its child elements.
+
+ :param element: An objectify element to get the text from
+ :return: The text content of the element (str)
+ """
+ verse_text = ''
+ if element.text:
+ verse_text = element.text
+ for sub_element in element.iterchildren():
+ verse_text += get_text(sub_element)
+ if element.tail:
+ verse_text += element.tail
+ return verse_text
+
+
+def parse_chapter_number(number, previous_number):
+ """
+ Parse the chapter number
+
+ :param number: The raw data from the xml
+ :param previous_number: The previous chapter number
+ :return: Number of current chapter. (Int)
+ """
+ if number:
+ return int(number.split()[-1])
+ return previous_number + 1
class OpenSongBible(BibleImport):
"""
OpenSong Bible format importer class. This class is used to import Bibles from OpenSong's XML format.
"""
- def get_text(self, element):
- """
- Recursively get all text in an objectify element and its child elements.
- :param element: An objectify element to get the text from
+ def parse_verse_number(self, number, previous_number):
"""
- verse_text = ''
- if element.text:
- verse_text = element.text
- for sub_element in element.iterchildren():
- verse_text += self.get_text(sub_element)
- if element.tail:
- verse_text += element.tail
- return verse_text
+ Parse the verse number retrieved from the xml
+
+ :param number: The raw data from the xml
+ :param previous_number: The previous verse number
+ :return: Number of current verse. (Int)
+ """
+ if not number:
+ return previous_number + 1
+ try:
+ return int(number)
+ except ValueError:
+ verse_parts = number.split('-')
+ if len(verse_parts) > 1:
+ number = int(verse_parts[0])
+ return number
+ except TypeError:
+ self.log_warning('Illegal verse number: {verse_no}'.format(verse_no=str(number)))
+ return previous_number + 1
+
+ def process_books(self, books):
+ """
+ Extract and create the books from the objectified xml
+
+ :param books: Objectified xml
+ :return: None
+ """
+ for book in books:
+ if self.stop_import_flag:
+ break
+ db_book = self.find_and_create_book(str(book.attrib['n']), len(books), self.language_id)
+ self.process_chapters(db_book, book.c)
+ self.session.commit()
+
+ def process_chapters(self, book, chapters):
+ """
+ Extract and create the chapters from the objectified xml for the book `book`
+
+ :param book: A database Book object to add the chapters to
+ :param chapters: Objectified xml containing chapters
+ :return: None
+ """
+ chapter_number = 0
+ for chapter in chapters:
+ if self.stop_import_flag:
+ break
+ chapter_number = parse_chapter_number(chapter.attrib['n'], chapter_number)
+ self.set_current_chapter(book.name, chapter_number)
+ self.process_verses(book, chapter_number, chapter.v)
+
+ def process_verses(self, book, chapter_number, verses):
+ """
+ Extract and create the verses from the objectified xml
+
+ :param book: A database Book object
+ :param chapter_number: The chapter number to add the verses to (int)
+ :param verses: Objectified xml containing verses
+ :return: None
+ """
+ verse_number = 0
+ for verse in verses:
+ if self.stop_import_flag:
+ break
+ verse_number = self.parse_verse_number(verse.attrib['n'], verse_number)
+ self.create_verse(book.id, chapter_number, verse_number, get_text(verse))
def do_import(self, bible_name=None):
"""
- Loads a Bible from file.
+ Loads an Open Song Bible from a file.
+
+ :param bible_name: The name of the bible being imported
+ :return: True if import completed, False if import was unsuccessful
"""
- log.debug('Starting OpenSong import from "{name}"'.format(name=self.filename))
- success = True
- try:
- bible = self.parse_xml(self.filename, use_objectify=True)
- # Check that we're not trying to import a Zefania XML bible, it is sometimes refered to as 'OpenSong'
- if bible.tag.upper() == 'XMLBIBLE':
- critical_error_message_box(
- message=translate('BiblesPlugin.OpenSongImport',
- 'Incorrect Bible file type supplied. This looks like a Zefania XML bible, '
- 'please use the Zefania import option.'))
- return False
- # No language info in the opensong format, so ask the user
- language_id = self.get_language_id(bible_name=self.filename)
- if not language_id:
- return False
- for book in bible.b:
- if self.stop_import_flag:
- break
- book_ref_id = self.get_book_ref_id_by_name(str(book.attrib['n']), len(bible.b), language_id)
- if not book_ref_id:
- log.error('Importing books from "{name}" failed'.format(name=self.filename))
- return False
- book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
- db_book = self.create_book(book.attrib['n'], book_ref_id, book_details['testament_id'])
- chapter_number = 0
- for chapter in book.c:
- if self.stop_import_flag:
- break
- number = chapter.attrib['n']
- if number:
- chapter_number = int(number.split()[-1])
- else:
- chapter_number += 1
- verse_number = 0
- for verse in chapter.v:
- if self.stop_import_flag:
- break
- number = verse.attrib['n']
- if number:
- try:
- number = int(number)
- except ValueError:
- verse_parts = number.split('-')
- if len(verse_parts) > 1:
- number = int(verse_parts[0])
- except TypeError:
- log.warning('Illegal verse number: {verse:d}'.format(verse=verse.attrib['n']))
- verse_number = number
- 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 {name} {chapter}...'
- ).format(name=db_book.name, chapter=chapter_number))
- self.session.commit()
- self.application.process_events()
- except etree.XMLSyntaxError as inst:
- trace_error_handler(log)
- 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):
- log.exception('Loading Bible from OpenSong file failed')
- success = False
- if self.stop_import_flag:
+ self.log_debug('Starting OpenSong import from "{name}"'.format(name=self.filename))
+ self.validate_xml_file(self.filename, 'bible')
+ bible = self.parse_xml(self.filename, use_objectify=True)
+ if bible is None:
return False
- else:
- return success
+ # No language info in the opensong format, so ask the user
+ self.language_id = self.get_language_id(bible_name=self.filename)
+ if not self.language_id:
+ return False
+ self.process_books(bible.b)
+ return True
diff --git a/openlp/plugins/bibles/lib/importers/osis.py b/openlp/plugins/bibles/lib/importers/osis.py
index 99a138acd..3c799daa3 100644
--- a/openlp/plugins/bibles/lib/importers/osis.py
+++ b/openlp/plugins/bibles/lib/importers/osis.py
@@ -20,15 +20,9 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-import logging
from lxml import etree
-from openlp.core.common import translate, trace_error_handler
-from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.bibleimport import BibleImport
-from openlp.plugins.bibles.lib.db import BiblesResourcesDB
-
-log = logging.getLogger(__name__)
NS = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
# Tags we don't use and can remove the content
@@ -74,104 +68,106 @@ REMOVABLE_TAGS = (
'{http://www.bibletechnologies.net/2003/OSIS/namespace}caption'
)
-
-def replacement(match):
- return match.group(2).upper()
+# Precompile a few xpath-querys
+verse_in_chapter = etree.XPath('//ns:chapter[1]/ns:verse', namespaces=NS)
+text_in_verse = etree.XPath('//ns:verse[1]/text()', namespaces=NS)
class OSISBible(BibleImport):
"""
`OSIS `_ Bible format importer class.
"""
+ def process_books(self, bible_data):
+ """
+ Extract and create the bible books from the parsed xml
+
+ :param bible_data: parsed xml
+ :return: None
+ """
+ # Find books in the bible
+ bible_books = bible_data.xpath("//ns:div[@type='book']", namespaces=NS)
+ no_of_books = len(bible_books)
+ for book in bible_books:
+ if self.stop_import_flag:
+ break
+ # Remove div-tags in the book
+ etree.strip_tags(book, '{http://www.bibletechnologies.net/2003/OSIS/namespace}div')
+ db_book = self.find_and_create_book(book.get('osisID'), no_of_books, self.language_id)
+ self.process_chapters(db_book, book)
+ self.session.commit()
+
+ def process_chapters(self, book, chapters):
+ """
+ Extract the chapters, and do some initial processing of the verses
+
+ :param book: An OpenLP bible database book object
+ :param chapters: parsed chapters
+ :return: None
+ """
+ # Find out if chapter-tags contains the verses, or if it is used as milestone/anchor
+ if verse_in_chapter(chapters):
+ # The chapter tags contains the verses
+ for chapter in chapters:
+ chapter_number = int(chapter.get("osisID").split('.')[1])
+ self.set_current_chapter(book.name, chapter_number)
+ # Find out if verse-tags contains the text, or if it is used as milestone/anchor
+ if not text_in_verse(chapter):
+ # verse-tags are used as milestone
+ for verse in chapter:
+ # If this tag marks the start of a verse, the verse text is between this tag and
+ # the next tag, which the "tail" attribute gives us.
+ self.process_verse(book, chapter_number, verse, use_milestones=True)
+ else:
+ # Verse-tags contains the text
+ for verse in chapter:
+ self.process_verse(book, chapter_number, verse)
+ else:
+ # The chapter tags is used as milestones. For now we assume verses is also milestones
+ chapter_number = 0
+ for element in chapters:
+ if element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter' \
+ and element.get('sID'):
+ chapter_number = int(element.get("osisID").split('.')[1])
+ self.set_current_chapter(book.name, chapter_number)
+ elif element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}verse':
+ # If this tag marks the start of a verse, the verse text is between this tag and
+ # the next tag, which the "tail" attribute gives us.
+ self.process_verse(book, chapter_number, element, use_milestones=True)
+
+ def process_verse(self, book, chapter_number, element, use_milestones=False):
+ """
+ Process a verse element
+ :param book: A database Book object
+ :param chapter_number: The chapter number to add the verses to (int)
+ :param element: The verse element to process. (etree element type)
+ :param use_milestones: set to True to process a 'milestone' verse. Defaults to False
+ :return: None
+ """
+ osis_id = element.get("osisID")
+ if not osis_id:
+ return None
+ verse_number = int(osis_id.split('.')[2])
+ verse_text = ''
+ if use_milestones and element.get('sID'):
+ verse_text = element.tail
+ elif not use_milestones:
+ verse_text = element.text
+ if verse_text:
+ self.create_verse(book.id, chapter_number, verse_number, verse_text.strip())
+
def do_import(self, bible_name=None):
"""
Loads a Bible from file.
"""
- log.debug('Starting OSIS import from "{name}"'.format(name=self.filename))
- success = True
- try:
- self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport',
- 'Removing unused tags (this may take a few minutes)...'))
- osis_bible_tree = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
- # Find bible language]
- language = osis_bible_tree.xpath("//ns:osisText/@xml:lang", namespaces=NS)
- language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename)
- if not language_id:
- return False
- num_books = int(osis_bible_tree.xpath("count(//ns:div[@type='book'])", namespaces=NS))
- # Precompile a few xpath-querys
- verse_in_chapter = etree.XPath('count(//ns:chapter[1]/ns:verse)', namespaces=NS)
- text_in_verse = etree.XPath('count(//ns:verse[1]/text())', namespaces=NS)
- # Find books in the bible
- bible_books = osis_bible_tree.xpath("//ns:div[@type='book']", namespaces=NS)
- for book in bible_books:
- if self.stop_import_flag:
- break
- # Remove div-tags in the book
- etree.strip_tags(book, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}div'))
- book_ref_id = self.get_book_ref_id_by_name(book.get('osisID'), num_books, language_id)
- if not book_ref_id:
- log.error('Importing books from "{name}" failed'.format(name=self.filename))
- return False
- book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
- db_book = self.create_book(book_details['name'], book_ref_id, book_details['testament_id'])
- # Find out if chapter-tags contains the verses, or if it is used as milestone/anchor
- if int(verse_in_chapter(book)) > 0:
- # The chapter tags contains the verses
- for chapter in book:
- chapter_number = chapter.get("osisID").split('.')[1]
- # Find out if verse-tags contains the text, or if it is used as milestone/anchor
- if int(text_in_verse(chapter)) == 0:
- # verse-tags are used as milestone
- for verse in chapter:
- # If this tag marks the start of a verse, the verse text is between this tag and
- # the next tag, which the "tail" attribute gives us.
- if verse.get('sID'):
- verse_number = verse.get("osisID").split('.')[2]
- verse_text = verse.tail
- if verse_text:
- self.create_verse(db_book.id, chapter_number, verse_number, verse_text.strip())
- else:
- # Verse-tags contains the text
- for verse in chapter:
- verse_number = verse.get("osisID").split('.')[2]
- if verse.text:
- self.create_verse(db_book.id, chapter_number, verse_number, verse.text.strip())
- self.wizard.increment_progress_bar(
- translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...') %
- {'bookname': db_book.name, 'chapter': chapter_number})
- else:
- # The chapter tags is used as milestones. For now we assume verses is also milestones
- chapter_number = 0
- for element in book:
- if element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter' \
- and element.get('sID'):
- chapter_number = element.get("osisID").split('.')[1]
- self.wizard.increment_progress_bar(
- translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...') %
- {'bookname': db_book.name, 'chapter': chapter_number})
- elif element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}verse' \
- and element.get('sID'):
- # If this tag marks the start of a verse, the verse text is between this tag and
- # the next tag, which the "tail" attribute gives us.
- verse_number = element.get("osisID").split('.')[2]
- verse_text = element.tail
- if verse_text:
- self.create_verse(db_book.id, chapter_number, verse_number, verse_text.strip())
- self.session.commit()
- self.application.process_events()
- except (ValueError, IOError):
- log.exception('Loading bible from OSIS file failed')
- trace_error_handler(log)
- success = False
- except etree.XMLSyntaxError as e:
- log.exception('Loading bible from OSIS file failed')
- trace_error_handler(log)
- success = False
- critical_error_message_box(message=translate('BiblesPlugin.OsisImport',
- 'The file is not a valid OSIS-XML file:'
- '\n{text}').format(text=e.msg))
- if self.stop_import_flag:
+ self.log_debug('Starting OSIS import from "{name}"'.format(name=self.filename))
+ self.validate_xml_file(self.filename, '{http://www.bibletechnologies.net/2003/osis/namespace}osis')
+ bible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
+ if bible is None:
return False
- else:
- return success
+ # Find bible language
+ language = bible.xpath("//ns:osisText/@xml:lang", namespaces=NS)
+ self.language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename)
+ if not self.language_id:
+ return False
+ self.process_books(bible)
+ return True
diff --git a/openlp/plugins/bibles/lib/importers/zefania.py b/openlp/plugins/bibles/lib/importers/zefania.py
index 61ee41166..5d8bddf4e 100644
--- a/openlp/plugins/bibles/lib/importers/zefania.py
+++ b/openlp/plugins/bibles/lib/importers/zefania.py
@@ -54,7 +54,7 @@ class ZefaniaBible(BibleImport):
language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename)
if not language_id:
return False
- num_books = int(xmlbible.xpath('count(//BIBLEBOOK)'))
+ no_of_books = int(xmlbible.xpath('count(//BIBLEBOOK)'))
self.wizard.progress_bar.setMaximum(int(xmlbible.xpath('count(//CHAPTER)')))
for BIBLEBOOK in xmlbible:
if self.stop_import_flag:
@@ -64,7 +64,7 @@ class ZefaniaBible(BibleImport):
if not bname and not bnumber:
continue
if bname:
- book_ref_id = self.get_book_ref_id_by_name(bname, num_books, language_id)
+ book_ref_id = self.get_book_ref_id_by_name(bname, no_of_books, language_id)
else:
log.debug('Could not find a name, will use number, basically a guess.')
book_ref_id = int(bnumber)
@@ -79,7 +79,8 @@ class ZefaniaBible(BibleImport):
chapter_number = CHAPTER.get("cnumber")
for VERS in CHAPTER:
verse_number = VERS.get("vnumber")
- self.create_verse(db_book.id, chapter_number, verse_number, VERS.text.replace(' ', '\n'))
+ self.create_verse(
+ db_book.id, int(chapter_number), int(verse_number), VERS.text.replace(' ', '\n'))
self.wizard.increment_progress_bar(
translate('BiblesPlugin.Zefnia',
'Importing {book} {chapter}...').format(book=db_book.name,
diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py
index d2286bed2..fbb6fb6e7 100644
--- a/openlp/plugins/bibles/lib/manager.py
+++ b/openlp/plugins/bibles/lib/manager.py
@@ -23,8 +23,8 @@
import logging
import os
-from openlp.core.common import RegistryProperties, AppLocation, Settings, translate, delete_file, UiStrings
-from openlp.plugins.bibles.lib import parse_reference, LanguageSelection
+from openlp.core.common import AppLocation, OpenLPMixin, RegistryProperties, Settings, translate, delete_file, UiStrings
+from openlp.plugins.bibles.lib import LanguageSelection, parse_reference
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta
from .importers.csvbible import CSVBible
from .importers.http import HTTPBible
@@ -88,7 +88,7 @@ class BibleFormat(object):
]
-class BibleManager(RegistryProperties):
+class BibleManager(OpenLPMixin, RegistryProperties):
"""
The Bible manager which holds and manages all the Bibles.
"""
@@ -131,7 +131,7 @@ class BibleManager(RegistryProperties):
name = bible.get_name()
# Remove corrupted files.
if name is None:
- bible.session.close()
+ bible.session.close_all()
delete_file(os.path.join(self.path, filename))
continue
log.debug('Bible Name: "{name}"'.format(name=name))
@@ -178,7 +178,7 @@ class BibleManager(RegistryProperties):
"""
log.debug('BibleManager.delete_bible("{name}")'.format(name=name))
bible = self.db_cache[name]
- bible.session.close()
+ bible.session.close_all()
bible.session = None
return delete_file(os.path.join(bible.path, bible.file))
@@ -367,7 +367,6 @@ class BibleManager(RegistryProperties):
second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
if web_bible or second_web_bible:
# If either Bible is Web, cursor is reset to normal and search ends w/o any message.
- self.check_search_result()
self.application.set_normal_cursor()
return None
# Fetch the results from db. If no results are found, return None, no message is given for this.
diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py
index b8ff3a752..9e65ec78b 100644
--- a/openlp/plugins/bibles/lib/mediaitem.py
+++ b/openlp/plugins/bibles/lib/mediaitem.py
@@ -199,7 +199,7 @@ class BibleMediaItem(MediaManagerItem):
self.quick_search_label = QtWidgets.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 = SearchEdit(self.quickTab, self.settings_section)
self.quick_search_edit.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed)
self.quick_search_edit.setObjectName('quick_search_edit')
self.quick_search_label.setBuddy(self.quick_search_edit)
@@ -254,8 +254,8 @@ class BibleMediaItem(MediaManagerItem):
self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed)
self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed)
# Buttons
- self.advancedClearButton.clicked.connect(self.on_clear_button)
- self.quickClearButton.clicked.connect(self.on_clear_button)
+ self.advancedClearButton.clicked.connect(self.on_advanced_clear_button_clicked)
+ self.quickClearButton.clicked.connect(self.on_clear_button_clicked)
self.advancedSearchButton.clicked.connect(self.on_advanced_search_button)
self.quickSearchButton.clicked.connect(self.on_quick_search_button)
# Other stuff
@@ -333,8 +333,8 @@ class BibleMediaItem(MediaManagerItem):
translate('BiblesPlugin.MediaItem', 'Text Search'),
translate('BiblesPlugin.MediaItem', 'Search Text...'))
])
- text = self.settings_section
- self.quick_search_edit.set_current_search_type(Settings().value('{text}/last search type'.format(text=text)))
+ if Settings().value(self.settings_section + '/reset to combined quick search'):
+ self.quick_search_edit.set_current_search_type(BibleSearch.Combined)
self.config_update()
log.debug('bible manager initialise complete')
@@ -444,15 +444,6 @@ class BibleMediaItem(MediaManagerItem):
only updated when we are doing reference or combined search, in text search the completion list is removed.
"""
log.debug('update_auto_completer')
- # Save the current search type to the configuration. If setting for automatically resetting the search type to
- # Combined is enabled, use that otherwise use the currently selected search type.
- # Note: This setting requires a restart to take effect.
- if Settings().value(self.settings_section + '/reset to combined quick search'):
- Settings().setValue('{section}/last search type'.format(section=self.settings_section),
- BibleSearch.Combined)
- else:
- Settings().setValue('{section}/last search type'.format(section=self.settings_section),
- self.quick_search_edit.current_search_type())
# Save the current bible to the configuration.
Settings().setValue('{section}/quick bible'.format(section=self.settings_section),
self.quickVersionComboBox.currentText())
@@ -548,19 +539,31 @@ class BibleMediaItem(MediaManagerItem):
self.advancedTab.setVisible(True)
self.advanced_book_combo_box.setFocus()
- def on_clear_button(self):
+ def on_clear_button_clicked(self):
# Clear the list, then set the "No search Results" message, then clear the text field and give it focus.
self.list_view.clear()
self.check_search_result()
self.quick_search_edit.clear()
self.quick_search_edit.setFocus()
+ def on_advanced_clear_button_clicked(self):
+ # The same as the on_clear_button_clicked, but gives focus to Book name field in "Select" (advanced).
+ self.list_view.clear()
+ self.check_search_result()
+ self.advanced_book_combo_box.setFocus()
+
def on_lock_button_toggled(self, checked):
- self.quick_search_edit.setFocus()
+ """
+ Toggle the lock button, if Search tab is used, set focus to search field.
+ :param checked: The state of the toggle button. bool
+ :return: None
+ """
if checked:
self.sender().setIcon(self.lock_icon)
else:
self.sender().setIcon(self.unlock_icon)
+ if self.quickTab.isVisible():
+ self.quick_search_edit.setFocus()
def on_quick_style_combo_box_changed(self):
self.settings.layout_style = self.quickStyleComboBox.currentIndex()
diff --git a/openlp/plugins/bibles/lib/upgrade.py b/openlp/plugins/bibles/lib/upgrade.py
index aebd088e8..9907c71cc 100644
--- a/openlp/plugins/bibles/lib/upgrade.py
+++ b/openlp/plugins/bibles/lib/upgrade.py
@@ -24,8 +24,6 @@ The :mod:`upgrade` module provides a way for the database and schema that is the
"""
import logging
-from sqlalchemy import delete, func, insert, select
-
log = logging.getLogger(__name__)
__version__ = 1
@@ -35,166 +33,6 @@ def upgrade_1(session, metadata):
"""
Version 1 upgrade.
- This upgrade renames a number of keys to a single naming convention.
+ This upgrade renamed a number of keys to a single naming convention.
"""
- metadata_table = metadata.tables['metadata']
- # Copy "Version" to "name" ("version" used by upgrade system)
- try:
- session.execute(insert(metadata_table).values(
- key='name',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'Version'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'Version'))
- except:
- log.exception('Exception when upgrading Version')
- # Copy "Copyright" to "copyright"
- try:
- session.execute(insert(metadata_table).values(
- key='copyright',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'Copyright'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'Copyright'))
- except:
- log.exception('Exception when upgrading Copyright')
- # Copy "Permissions" to "permissions"
- try:
- session.execute(insert(metadata_table).values(
- key='permissions',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'Permissions'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'Permissions'))
- except:
- log.exception('Exception when upgrading Permissions')
- # Copy "Bookname language" to "book_name_language"
- try:
- value_count = session.execute(
- select(
- [func.count(metadata_table.c.value)],
- metadata_table.c.key == 'Bookname language'
- )
- ).scalar()
- if value_count > 0:
- session.execute(insert(metadata_table).values(
- key='book_name_language',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'Bookname language'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'Bookname language'))
- except:
- log.exception('Exception when upgrading Bookname language')
- # Copy "download source" to "download_source"
- try:
- value_count = session.execute(
- select(
- [func.count(metadata_table.c.value)],
- metadata_table.c.key == 'download source'
- )
- ).scalar()
- log.debug('download source: {count}'.format(count=value_count))
- if value_count > 0:
- session.execute(insert(metadata_table).values(
- key='download_source',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'download source'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'download source'))
- except:
- log.exception('Exception when upgrading download source')
- # Copy "download name" to "download_name"
- try:
- value_count = session.execute(
- select(
- [func.count(metadata_table.c.value)],
- metadata_table.c.key == 'download name'
- )
- ).scalar()
- log.debug('download name: {count}'.format(count=value_count))
- if value_count > 0:
- session.execute(insert(metadata_table).values(
- key='download_name',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'download name'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'download name'))
- except:
- log.exception('Exception when upgrading download name')
- # Copy "proxy server" to "proxy_server"
- try:
- value_count = session.execute(
- select(
- [func.count(metadata_table.c.value)],
- metadata_table.c.key == 'proxy server'
- )
- ).scalar()
- log.debug('proxy server: {count}'.format(count=value_count))
- if value_count > 0:
- session.execute(insert(metadata_table).values(
- key='proxy_server',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'proxy server'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'proxy server'))
- except:
- log.exception('Exception when upgrading proxy server')
- # Copy "proxy username" to "proxy_username"
- try:
- value_count = session.execute(
- select(
- [func.count(metadata_table.c.value)],
- metadata_table.c.key == 'proxy username'
- )
- ).scalar()
- log.debug('proxy username: {count}'.format(count=value_count))
- if value_count > 0:
- session.execute(insert(metadata_table).values(
- key='proxy_username',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'proxy username'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'proxy username'))
- except:
- log.exception('Exception when upgrading proxy username')
- # Copy "proxy password" to "proxy_password"
- try:
- value_count = session.execute(
- select(
- [func.count(metadata_table.c.value)],
- metadata_table.c.key == 'proxy password'
- )
- ).scalar()
- log.debug('proxy password: {count}'.format(count=value_count))
- if value_count > 0:
- session.execute(insert(metadata_table).values(
- key='proxy_password',
- value=select(
- [metadata_table.c.value],
- metadata_table.c.key == 'proxy password'
- ).as_scalar()
- ))
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'proxy password'))
- except:
- log.exception('Exception when upgrading proxy password')
- try:
- session.execute(delete(metadata_table).where(metadata_table.c.key == 'dbversion'))
- except:
- log.exception('Exception when deleting dbversion')
- session.commit()
+ log.info('No upgrades to perform')
diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py
index b686e5522..d25ff337b 100644
--- a/openlp/plugins/custom/forms/editcustomdialog.py
+++ b/openlp/plugins/custom/forms/editcustomdialog.py
@@ -34,7 +34,7 @@ class Ui_CustomEditDialog(object):
:param custom_edit_dialog: The Dialog
"""
custom_edit_dialog.setObjectName('custom_edit_dialog')
- custom_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ custom_edit_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
custom_edit_dialog.resize(450, 350)
self.dialog_layout = QtWidgets.QVBoxLayout(custom_edit_dialog)
self.dialog_layout.setObjectName('dialog_layout')
diff --git a/openlp/plugins/custom/forms/editcustomslidedialog.py b/openlp/plugins/custom/forms/editcustomslidedialog.py
index 5f46c6f28..d08679eee 100644
--- a/openlp/plugins/custom/forms/editcustomslidedialog.py
+++ b/openlp/plugins/custom/forms/editcustomslidedialog.py
@@ -31,7 +31,7 @@ from openlp.core.ui.lib import SpellTextEdit
class Ui_CustomSlideEditDialog(object):
def setupUi(self, custom_slide_edit_dialog):
custom_slide_edit_dialog.setObjectName('custom_slide_edit_dialog')
- custom_slide_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ custom_slide_edit_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
custom_slide_edit_dialog.resize(350, 300)
self.dialog_layout = QtWidgets.QVBoxLayout(custom_slide_edit_dialog)
self.slide_text_edit = SpellTextEdit(self)
diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py
index e9e26111f..e5d58f76e 100644
--- a/openlp/plugins/custom/lib/mediaitem.py
+++ b/openlp/plugins/custom/lib/mediaitem.py
@@ -105,8 +105,6 @@ class CustomMediaItem(MediaManagerItem):
[(CustomSearch.Titles, ':/songs/song_search_title.png', translate('SongsPlugin.MediaItem', 'Titles'),
translate('SongsPlugin.MediaItem', 'Search Titles...')),
(CustomSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)])
- text = '{section}/last search type'.format(section=self.settings_section)
- self.search_text_edit.set_current_search_type(Settings().value(text))
self.load_list(self.plugin.db_manager.get_all_objects(CustomSlide, order_by_ref=CustomSlide.title))
self.config_update()
@@ -250,9 +248,6 @@ class CustomMediaItem(MediaManagerItem):
"""
Search the plugin database
"""
- # Save the current search type to the configuration.
- Settings().setValue('{section}/last search type'.format(section=self.settings_section),
- self.search_text_edit.current_search_type())
# Reload the list considering the new search type.
search_type = self.search_text_edit.current_search_type()
search_keywords = '%{search}%'.format(search=self.whitespace.sub(' ', self.search_text_edit.displayText()))
@@ -350,7 +345,7 @@ class CustomMediaItem(MediaManagerItem):
:param string: The search string
:param show_error: The error string to be show.
"""
- search = '%{search}%'.forma(search=string.lower())
+ search = '%{search}%'.format(search=string.lower())
search_results = self.plugin.db_manager.get_all_objects(CustomSlide,
or_(func.lower(CustomSlide.title).like(search),
func.lower(CustomSlide.text).like(search)),
diff --git a/openlp/plugins/songs/forms/authorsdialog.py b/openlp/plugins/songs/forms/authorsdialog.py
index 666baffec..7b2995480 100644
--- a/openlp/plugins/songs/forms/authorsdialog.py
+++ b/openlp/plugins/songs/forms/authorsdialog.py
@@ -36,7 +36,7 @@ class Ui_AuthorsDialog(object):
Set up the UI for the dialog.
"""
authors_dialog.setObjectName('authors_dialog')
- authors_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ authors_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
authors_dialog.resize(300, 10)
authors_dialog.setModal(True)
self.dialog_layout = QtWidgets.QVBoxLayout(authors_dialog)
diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py
index 46e37c1cd..b23fe1189 100644
--- a/openlp/plugins/songs/forms/editsongdialog.py
+++ b/openlp/plugins/songs/forms/editsongdialog.py
@@ -36,7 +36,7 @@ class Ui_EditSongDialog(object):
"""
def setupUi(self, edit_song_dialog):
edit_song_dialog.setObjectName('edit_song_dialog')
- edit_song_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ edit_song_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
edit_song_dialog.resize(900, 600)
edit_song_dialog.setModal(True)
self.dialog_layout = QtWidgets.QVBoxLayout(edit_song_dialog)
diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py
index 200e1436f..a17c9fb5f 100644
--- a/openlp/plugins/songs/forms/editsongform.py
+++ b/openlp/plugins/songs/forms/editsongform.py
@@ -317,7 +317,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.song.verse_order = re.sub('([' + verse.upper() + verse.lower() + '])(\W|$)',
r'\g<1>1\2', self.song.verse_order)
except:
- log.exception('Problem processing song Lyrics \n{xml}'.forma(xml=sxml.dump_xml()))
+ log.exception('Problem processing song Lyrics \n{xml}'.format(xml=sxml.dump_xml()))
raise
def keyPressEvent(self, event):
diff --git a/openlp/plugins/songs/forms/mediafilesdialog.py b/openlp/plugins/songs/forms/mediafilesdialog.py
index 4309eb91f..2b9bcceab 100644
--- a/openlp/plugins/songs/forms/mediafilesdialog.py
+++ b/openlp/plugins/songs/forms/mediafilesdialog.py
@@ -35,7 +35,7 @@ class Ui_MediaFilesDialog(object):
Set up the user interface.
"""
media_files_dialog.setObjectName('media_files_dialog')
- media_files_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ media_files_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
media_files_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
media_files_dialog.resize(400, 300)
media_files_dialog.setModal(True)
diff --git a/openlp/plugins/songs/forms/songbookdialog.py b/openlp/plugins/songs/forms/songbookdialog.py
index 7e1c04d20..10ed154b4 100644
--- a/openlp/plugins/songs/forms/songbookdialog.py
+++ b/openlp/plugins/songs/forms/songbookdialog.py
@@ -35,7 +35,7 @@ class Ui_SongBookDialog(object):
Set up the user interface.
"""
song_book_dialog.setObjectName('song_book_dialog')
- song_book_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ song_book_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
song_book_dialog.resize(300, 10)
self.dialog_layout = QtWidgets.QVBoxLayout(song_book_dialog)
self.dialog_layout.setObjectName('dialog_layout')
diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py
index 27f317963..b0d044b2d 100644
--- a/openlp/plugins/songs/forms/songexportform.py
+++ b/openlp/plugins/songs/forms/songexportform.py
@@ -51,7 +51,7 @@ class SongExportForm(OpenLPWizard):
:param parent: The QWidget-derived parent of the wizard.
:param plugin: The songs plugin.
"""
- super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_exportsong.bmp')
+ super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_song.bmp')
self.stop_export_flag = False
Registry().register_function('openlp_stop_wizard', self.stop_export)
diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py
index 2539973da..b25021ca9 100644
--- a/openlp/plugins/songs/forms/songimportform.py
+++ b/openlp/plugins/songs/forms/songimportform.py
@@ -52,7 +52,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
:param parent: The QWidget-derived parent of the wizard.
:param plugin: The songs plugin.
"""
- super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_importsong.bmp')
+ super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_song.bmp')
self.clipboard = self.main_window.clipboard
def setupUi(self, image):
diff --git a/openlp/plugins/songs/forms/songmaintenancedialog.py b/openlp/plugins/songs/forms/songmaintenancedialog.py
index cd224267b..c0c532688 100644
--- a/openlp/plugins/songs/forms/songmaintenancedialog.py
+++ b/openlp/plugins/songs/forms/songmaintenancedialog.py
@@ -37,7 +37,7 @@ class Ui_SongMaintenanceDialog(object):
Set up the user interface for the song maintenance dialog
"""
song_maintenance_dialog.setObjectName('song_maintenance_dialog')
- song_maintenance_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ song_maintenance_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
song_maintenance_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
song_maintenance_dialog.resize(10, 350)
self.dialog_layout = QtWidgets.QGridLayout(song_maintenance_dialog)
diff --git a/openlp/plugins/songs/forms/songreviewwidget.py b/openlp/plugins/songs/forms/songreviewwidget.py
index c8873f028..2e5814626 100644
--- a/openlp/plugins/songs/forms/songreviewwidget.py
+++ b/openlp/plugins/songs/forms/songreviewwidget.py
@@ -182,7 +182,7 @@ class SongReviewWidget(QtWidgets.QWidget):
self.song_vertical_layout.addWidget(self.song_group_box)
self.song_remove_button = QtWidgets.QPushButton(self)
self.song_remove_button.setObjectName('song_remove_button')
- self.song_remove_button.setIcon(build_icon(':/songs/song_delete.png'))
+ self.song_remove_button.setIcon(build_icon(':/general/general_delete.png'))
self.song_remove_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.song_vertical_layout.addWidget(self.song_remove_button, alignment=QtCore.Qt.AlignHCenter)
diff --git a/openlp/plugins/songs/forms/topicsdialog.py b/openlp/plugins/songs/forms/topicsdialog.py
index 1ed3ac61b..90c19238d 100644
--- a/openlp/plugins/songs/forms/topicsdialog.py
+++ b/openlp/plugins/songs/forms/topicsdialog.py
@@ -35,7 +35,7 @@ class Ui_TopicsDialog(object):
Set up the user interface for the topics dialog.
"""
topics_dialog.setObjectName('topics_dialog')
- topics_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ topics_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
topics_dialog.resize(300, 10)
self.dialog_layout = QtWidgets.QVBoxLayout(topics_dialog)
self.dialog_layout.setObjectName('dialog_layout')
diff --git a/openlp/plugins/songs/lib/importers/mediashout.py b/openlp/plugins/songs/lib/importers/mediashout.py
index a3bd7bbbc..9b916cd43 100644
--- a/openlp/plugins/songs/lib/importers/mediashout.py
+++ b/openlp/plugins/songs/lib/importers/mediashout.py
@@ -101,7 +101,7 @@ class MediaShoutImport(SongImport):
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.add_verse(verse.Text, tag)
+ self.add_verse(self.tidy_text(verse.Text), tag)
for order in verse_order:
if order.Type < len(VERSE_TAGS):
self.verse_order_list.append(VERSE_TAGS[order.Type] + str(order.Number))
diff --git a/openlp/plugins/songs/lib/importers/songimport.py b/openlp/plugins/songs/lib/importers/songimport.py
index 6482b7232..3d8f60c85 100644
--- a/openlp/plugins/songs/lib/importers/songimport.py
+++ b/openlp/plugins/songs/lib/importers/songimport.py
@@ -140,10 +140,13 @@ class SongImport(QtCore.QObject):
text = text.replace('\u2026', '...')
text = text.replace('\u2013', '-')
text = text.replace('\u2014', '-')
+ # Replace vertical tab with 2 linebreaks
+ text = text.replace('\v', '\n\n')
+ # Replace form feed (page break) with 2 linebreaks
+ text = text.replace('\f', '\n\n')
# Remove surplus blank lines, spaces, trailing/leading spaces
- text = re.sub(r'[ \t\v]+', ' ', text)
+ text = re.sub(r'[ \t]+', ' ', text)
text = re.sub(r' ?(\r\n?|\n) ?', '\n', text)
- text = re.sub(r' ?(\n{5}|\f)+ ?', '\f', text)
return text
def process_song_text(self, text):
diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py
index b89858019..02c5d526b 100644
--- a/openlp/plugins/songs/lib/mediaitem.py
+++ b/openlp/plugins/songs/lib/mediaitem.py
@@ -166,14 +166,9 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem', 'CCLI number'),
translate('SongsPlugin.MediaItem', 'Search CCLI number...'))
])
- self.search_text_edit.set_current_search_type(
- Settings().value('{section}/last search type'.format(section=self.settings_section)))
self.config_update()
def on_search_text_button_clicked(self):
- # Save the current search type to the configuration.
- Settings().setValue('{section}/last search type'.format(section=self.settings_section),
- self.search_text_edit.current_search_type())
# Reload the list considering the new search type.
search_keywords = str(self.search_text_edit.displayText())
search_type = self.search_text_edit.current_search_type()
diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py
index b45a2fc31..0018be826 100644
--- a/openlp/plugins/songs/lib/openlyricsxml.py
+++ b/openlp/plugins/songs/lib/openlyricsxml.py
@@ -470,7 +470,7 @@ class OpenLyrics(object):
self._add_tag_to_formatting(tag, tags_element)
# Replace end tags.
for tag in end_tags:
- text = text.replace('{{{tag}}}'.format(tag=tag), '')
+ text = text.replace('{{/{tag}}}'.format(tag=tag), '')
# Replace \n with .
text = text.replace('\n', ' ')
element = etree.XML('{text}'.format(text=text))
diff --git a/openlp/plugins/songs/lib/songcompare.py b/openlp/plugins/songs/lib/songcompare.py
index 44e17a3ac..1d19deaaa 100644
--- a/openlp/plugins/songs/lib/songcompare.py
+++ b/openlp/plugins/songs/lib/songcompare.py
@@ -46,13 +46,13 @@ MIN_BLOCK_SIZE = 70
MAX_TYPO_SIZE = 3
-def songs_probably_equal(song_tupel):
+def songs_probably_equal(song_tuple):
"""
Calculate and return whether two songs are probably equal.
- :param song_tupel: A tuple of two songs to compare.
+ :param song_tuple: A tuple of two songs to compare.
"""
- song1, song2 = song_tupel
+ song1, song2 = song_tuple
pos1, lyrics1 = song1
pos2, lyrics2 = song2
if len(lyrics1) < len(lyrics2):
diff --git a/openlp/plugins/songs/reporting.py b/openlp/plugins/songs/reporting.py
new file mode 100644
index 000000000..98b6713fe
--- /dev/null
+++ b/openlp/plugins/songs/reporting.py
@@ -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-2016 OpenLP Developers #
+# --------------------------------------------------------------------------- #
+# 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 #
+###############################################################################
+"""
+The :mod:`db` module provides the ability to provide a csv file of all songs
+"""
+import csv
+import logging
+
+from PyQt5 import QtWidgets
+
+from openlp.core.common import Registry, translate
+from openlp.core.lib.ui import critical_error_message_box
+from openlp.plugins.songs.lib.db import Song
+
+
+log = logging.getLogger(__name__)
+
+
+def report_song_list():
+ """
+ Export the song list as a CSV file.
+ :return: Nothing
+ """
+ main_window = Registry().get('main_window')
+ plugin = Registry().get('songs').plugin
+ report_file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
+ main_window,
+ translate('SongPlugin.ReportSongList', 'Save File'),
+ translate('SongPlugin.ReportSongList', 'song_extract.csv'),
+ translate('SongPlugin.ReportSongList', 'CSV format (*.csv)'))
+
+ if not report_file_name:
+ main_window.error_message(
+ translate('SongPlugin.ReportSongList', 'Output Path Not Selected'),
+ translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your '
+ 'report. \nPlease select an existing path '
+ 'on your computer.')
+ )
+ return
+ if not report_file_name.endswith('csv'):
+ report_file_name += '.csv'
+ file_handle = None
+ Registry().get('application').set_busy_cursor()
+ try:
+ file_handle = open(report_file_name, 'wt')
+ fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
+ writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
+ headers = dict((n, n) for n in fieldnames)
+ writer.writerow(headers)
+ song_list = plugin.manager.get_all_objects(Song)
+ for song in song_list:
+ author_list = []
+ for author_song in song.authors_songs:
+ author_list.append(author_song.author.display_name)
+ author_string = ' | '.join(author_list)
+ book_list = []
+ for book_song in song.songbook_entries:
+ if hasattr(book_song, 'entry') and book_song.entry:
+ book_list.append('{name} #{entry}'.format(name=book_song.songbook.name, entry=book_song.entry))
+ book_string = ' | '.join(book_list)
+ topic_list = []
+ for topic_song in song.topics:
+ if hasattr(topic_song, 'name'):
+ topic_list.append(topic_song.name)
+ topic_string = ' | '.join(topic_list)
+ writer.writerow({'Title': song.title,
+ 'Alternative Title': song.alternate_title,
+ 'Copyright': song.copyright,
+ 'Author(s)': author_string,
+ 'Song Book': book_string,
+ 'Topic': topic_string})
+ Registry().get('application').set_normal_cursor()
+ main_window.information_message(
+ translate('SongPlugin.ReportSongList', 'Report Creation'),
+ translate('SongPlugin.ReportSongList',
+ 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
+ )
+ except OSError as ose:
+ Registry().get('application').set_normal_cursor()
+ log.exception('Failed to write out song usage records')
+ critical_error_message_box(translate('SongPlugin.ReportSongList', 'Song Extraction Failed'),
+ translate('SongPlugin.ReportSongList',
+ 'An error occurred while extracting: {error}'
+ ).format(error=ose.strerror))
+ finally:
+ if file_handle:
+ file_handle.close()
diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py
index 298960c7c..ead15bcac 100644
--- a/openlp/plugins/songs/songsplugin.py
+++ b/openlp/plugins/songs/songsplugin.py
@@ -36,6 +36,7 @@ from openlp.core.common.actions import ActionList
from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.core.lib.db import Manager
from openlp.core.lib.ui import create_action
+from openlp.plugins.songs import reporting
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
from openlp.plugins.songs.forms.songselectform import SongSelectForm
from openlp.plugins.songs.lib import clean_song, upgrade
@@ -105,13 +106,13 @@ class SongsPlugin(Plugin):
self.songselect_form.initialise()
self.song_import_item.setVisible(True)
self.song_export_item.setVisible(True)
- self.tools_reindex_item.setVisible(True)
- self.tools_find_duplicates.setVisible(True)
+ self.song_tools_menu.menuAction().setVisible(True)
action_list = ActionList.get_instance()
action_list.add_action(self.song_import_item, UiStrings().Import)
action_list.add_action(self.song_export_item, UiStrings().Export)
action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
+ action_list.add_action(self.tools_report_song_list, UiStrings().Tools)
def add_import_menu_item(self, import_menu):
"""
@@ -154,19 +155,37 @@ class SongsPlugin(Plugin):
:param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent.
"""
log.info('add tools menu')
+ self.tools_menu = tools_menu
+ self.song_tools_menu = QtWidgets.QMenu(tools_menu)
+ self.song_tools_menu.setObjectName('song_tools_menu')
+ self.song_tools_menu.setTitle(translate('SongsPlugin', 'Songs'))
self.tools_reindex_item = create_action(
tools_menu, 'toolsReindexItem',
text=translate('SongsPlugin', '&Re-index Songs'),
icon=':/plugins/plugin_songs.png',
statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'),
- visible=False, triggers=self.on_tools_reindex_item_triggered)
- tools_menu.addAction(self.tools_reindex_item)
+ triggers=self.on_tools_reindex_item_triggered)
self.tools_find_duplicates = create_action(
tools_menu, 'toolsFindDuplicates',
text=translate('SongsPlugin', 'Find &Duplicate Songs'),
statustip=translate('SongsPlugin', 'Find and remove duplicate songs in the song database.'),
- visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True)
- tools_menu.addAction(self.tools_find_duplicates)
+ triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True)
+ self.tools_report_song_list = create_action(
+ tools_menu, 'toolsSongListReport',
+ text=translate('SongsPlugin', 'Song List Report'),
+ statustip=translate('SongsPlugin', 'Produce a CSV file of all the songs in the database.'),
+ triggers=self.on_tools_report_song_list_triggered)
+
+ self.tools_menu.addAction(self.song_tools_menu.menuAction())
+ self.song_tools_menu.addAction(self.tools_reindex_item)
+ self.song_tools_menu.addAction(self.tools_find_duplicates)
+ self.song_tools_menu.addAction(self.tools_report_song_list)
+
+ self.song_tools_menu.menuAction().setVisible(False)
+
+ @staticmethod
+ def on_tools_report_song_list_triggered():
+ reporting.report_song_list()
def on_tools_reindex_item_triggered(self):
"""
@@ -329,13 +348,13 @@ class SongsPlugin(Plugin):
self.manager.finalise()
self.song_import_item.setVisible(False)
self.song_export_item.setVisible(False)
- self.tools_reindex_item.setVisible(False)
- self.tools_find_duplicates.setVisible(False)
action_list = ActionList.get_instance()
action_list.remove_action(self.song_import_item, UiStrings().Import)
action_list.remove_action(self.song_export_item, UiStrings().Export)
action_list.remove_action(self.tools_reindex_item, UiStrings().Tools)
action_list.remove_action(self.tools_find_duplicates, UiStrings().Tools)
+ action_list.add_action(self.tools_report_song_list, UiStrings().Tools)
+ self.song_tools_menu.menuAction().setVisible(False)
super(SongsPlugin, self).finalise()
def new_service_created(self):
diff --git a/openlp/plugins/songusage/forms/songusagedeletedialog.py b/openlp/plugins/songusage/forms/songusagedeletedialog.py
index 40fabe3f7..6f3f72651 100644
--- a/openlp/plugins/songusage/forms/songusagedeletedialog.py
+++ b/openlp/plugins/songusage/forms/songusagedeletedialog.py
@@ -38,7 +38,7 @@ class Ui_SongUsageDeleteDialog(object):
:param song_usage_delete_dialog:
"""
song_usage_delete_dialog.setObjectName('song_usage_delete_dialog')
- song_usage_delete_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ song_usage_delete_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
song_usage_delete_dialog.resize(291, 243)
self.vertical_layout = QtWidgets.QVBoxLayout(song_usage_delete_dialog)
self.vertical_layout.setSpacing(8)
diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py
index b665e74c2..e9e4a6bbe 100644
--- a/openlp/plugins/songusage/forms/songusagedetaildialog.py
+++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py
@@ -38,7 +38,7 @@ class Ui_SongUsageDetailDialog(object):
:param song_usage_detail_dialog:
"""
song_usage_detail_dialog.setObjectName('song_usage_detail_dialog')
- song_usage_detail_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ song_usage_detail_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
song_usage_detail_dialog.resize(609, 413)
self.vertical_layout = QtWidgets.QVBoxLayout(song_usage_detail_dialog)
self.vertical_layout.setSpacing(8)
diff --git a/resources/images/about-new.bmp b/resources/images/about-new.bmp
deleted file mode 100644
index b523f6b60..000000000
Binary files a/resources/images/about-new.bmp and /dev/null differ
diff --git a/resources/images/bibles_search_clear.png b/resources/images/bibles_search_clear.png
index 551ce7c96..19a1665ba 100644
Binary files a/resources/images/bibles_search_clear.png and b/resources/images/bibles_search_clear.png differ
diff --git a/resources/images/bibles_search_combined.png b/resources/images/bibles_search_combined.png
index 161b3fcd6..8fc16c886 100644
Binary files a/resources/images/bibles_search_combined.png and b/resources/images/bibles_search_combined.png differ
diff --git a/resources/images/bibles_upgrade_alert.png b/resources/images/bibles_upgrade_alert.png
deleted file mode 100644
index 331aa2687..000000000
Binary files a/resources/images/bibles_upgrade_alert.png and /dev/null differ
diff --git a/resources/images/custom_delete.png b/resources/images/custom_delete.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/custom_delete.png and /dev/null differ
diff --git a/resources/images/custom_edit.png b/resources/images/custom_edit.png
deleted file mode 100644
index d79aa5151..000000000
Binary files a/resources/images/custom_edit.png and /dev/null differ
diff --git a/resources/images/custom_new.png b/resources/images/custom_new.png
deleted file mode 100644
index 8431237bd..000000000
Binary files a/resources/images/custom_new.png and /dev/null differ
diff --git a/resources/images/general_search_clear.png b/resources/images/general_search_clear.png
deleted file mode 100644
index 6c4b83b7a..000000000
Binary files a/resources/images/general_search_clear.png and /dev/null differ
diff --git a/resources/images/image_clapperboard.png b/resources/images/image_clapperboard.png
deleted file mode 100644
index e46056914..000000000
Binary files a/resources/images/image_clapperboard.png and /dev/null differ
diff --git a/resources/images/image_delete.png b/resources/images/image_delete.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/image_delete.png and /dev/null differ
diff --git a/resources/images/image_load.png b/resources/images/image_load.png
deleted file mode 100644
index eb211e233..000000000
Binary files a/resources/images/image_load.png and /dev/null differ
diff --git a/resources/images/import_load.png b/resources/images/import_load.png
deleted file mode 100644
index eb211e233..000000000
Binary files a/resources/images/import_load.png and /dev/null differ
diff --git a/resources/images/import_move_to_list.png b/resources/images/import_move_to_list.png
deleted file mode 100644
index 5c0005856..000000000
Binary files a/resources/images/import_move_to_list.png and /dev/null differ
diff --git a/resources/images/import_remove.png b/resources/images/import_remove.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/import_remove.png and /dev/null differ
diff --git a/resources/images/import_selectall.png b/resources/images/import_selectall.png
deleted file mode 100644
index 0f0d9f152..000000000
Binary files a/resources/images/import_selectall.png and /dev/null differ
diff --git a/resources/images/messagebox_critical.png b/resources/images/messagebox_critical.png
deleted file mode 100644
index 3fb497a19..000000000
Binary files a/resources/images/messagebox_critical.png and /dev/null differ
diff --git a/resources/images/messagebox_info.png b/resources/images/messagebox_info.png
deleted file mode 100644
index 4e3a257f3..000000000
Binary files a/resources/images/messagebox_info.png and /dev/null differ
diff --git a/resources/images/messagebox_warning.png b/resources/images/messagebox_warning.png
deleted file mode 100644
index 331aa2687..000000000
Binary files a/resources/images/messagebox_warning.png and /dev/null differ
diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc
index b45cc745d..ebff58a0a 100644
--- a/resources/images/openlp-2.qrc
+++ b/resources/images/openlp-2.qrc
@@ -23,8 +23,7 @@
topic_maintenance.pngsong_author_edit.pngsong_book_edit.png
- song_delete.png
-
+
image_group.pngimage_new_group.png
@@ -33,7 +32,6 @@
bibles_search_combined.pngbibles_search_text.pngbibles_search_reference.png
- bibles_upgrade_alert.pngbibles_search_clear.pngbibles_search_unlock.pngbibles_search_lock.png
@@ -71,7 +69,6 @@
general_back.png
- slide_close.pngslide_next.pngslide_blank.pngslide_desktop.png
@@ -83,12 +80,6 @@
media_playback_next.png
- openlp-logo-16x16.png
- openlp-logo-32x32.png
- openlp-logo-48x48.png
- openlp-logo-64x64.png
- openlp-logo-128x128.png
- openlp-logo-256x256.pngopenlp-logo.svg
@@ -97,24 +88,18 @@
openlp-about-logo.png
- import_selectall.png
- import_move_to_list.png
- import_remove.png
- import_load.png
-
+
export_load.pngopenlp-osx-wizard.png
- wizard_exportsong.bmp
- wizard_importsong.bmp
+ wizard_song.bmpwizard_importbible.bmpwizard_firsttime.bmpwizard_createtheme.bmpwizard_duplicateremoval.bmp
- wizard_createprojector.png
-
+
service_collapse_all.pngservice_expand_all.png
@@ -130,7 +115,6 @@
clear_shortcut.pngsystem_about.pngsystem_help_contents.png
- system_online_help.pngsystem_mediamanager.pngsystem_volunteer.pngsystem_servicemanager.png
@@ -156,10 +140,7 @@
multimedia-player.png
- messagebox_critical.png
- messagebox_info.png
- messagebox_warning.png
-
+
network_server.pngnetwork_ssl.png
@@ -171,10 +152,8 @@
tools_add.png
- tools_alert.png
-
+
- theme_delete.pngtheme_new.pngtheme_edit.png
@@ -202,12 +181,10 @@
projector_power_on_tiled.pngprojector_show.pngprojector_show_tiled.png
- projector_spacer.pngprojector_warmup.png
- projector_view.png
-
+
android_app_qr.pngios_app_qr.png
-
+
\ No newline at end of file
diff --git a/resources/images/openlp-about-logo.svg b/resources/images/openlp-about-logo.svg
deleted file mode 100644
index d4f9c2dbe..000000000
--- a/resources/images/openlp-about-logo.svg
+++ /dev/null
@@ -1,323 +0,0 @@
-
-
-
-
diff --git a/resources/images/openlp-default-dualdisplay.svg b/resources/images/openlp-default-dualdisplay.svg
deleted file mode 100644
index 40ebedeac..000000000
--- a/resources/images/openlp-default-dualdisplay.svg
+++ /dev/null
@@ -1,489 +0,0 @@
-
-
-
diff --git a/resources/images/openlp-logo-256x256.png b/resources/images/openlp-logo-256x256.png
deleted file mode 100644
index 0fcf37a7c..000000000
Binary files a/resources/images/openlp-logo-256x256.png and /dev/null differ
diff --git a/resources/images/openlp-logo-420x420.png b/resources/images/openlp-logo-420x420.png
deleted file mode 100644
index 81b893b9a..000000000
Binary files a/resources/images/openlp-logo-420x420.png and /dev/null differ
diff --git a/resources/images/openlp-logo.svg b/resources/images/openlp-logo.svg
index c5e7985e1..d1ff61bec 100644
--- a/resources/images/openlp-logo.svg
+++ b/resources/images/openlp-logo.svg
@@ -1,6 +1,4 @@
-
-
diff --git a/resources/images/openlp-splash-screen.svg b/resources/images/openlp-splash-screen.svg
deleted file mode 100644
index b45f2015c..000000000
--- a/resources/images/openlp-splash-screen.svg
+++ /dev/null
@@ -1,296 +0,0 @@
-
-
-
-
diff --git a/resources/images/openlp.org.ico b/resources/images/openlp.org.ico
deleted file mode 100644
index f11e76809..000000000
Binary files a/resources/images/openlp.org.ico and /dev/null differ
diff --git a/resources/images/presentation_delete.png b/resources/images/presentation_delete.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/presentation_delete.png and /dev/null differ
diff --git a/resources/images/presentation_load.png b/resources/images/presentation_load.png
deleted file mode 100644
index eb211e233..000000000
Binary files a/resources/images/presentation_load.png and /dev/null differ
diff --git a/resources/images/projector_connectors.png b/resources/images/projector_connectors.png
deleted file mode 100644
index 77a6a4dd7..000000000
Binary files a/resources/images/projector_connectors.png and /dev/null differ
diff --git a/resources/images/projector_spacer.png b/resources/images/projector_spacer.png
deleted file mode 100644
index ca5a54a04..000000000
Binary files a/resources/images/projector_spacer.png and /dev/null differ
diff --git a/resources/images/projector_view.png b/resources/images/projector_view.png
deleted file mode 100644
index e4d7e971b..000000000
Binary files a/resources/images/projector_view.png and /dev/null differ
diff --git a/resources/images/service_delete.png b/resources/images/service_delete.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/service_delete.png and /dev/null differ
diff --git a/resources/images/service_edit.png b/resources/images/service_edit.png
deleted file mode 100644
index 84e345d22..000000000
Binary files a/resources/images/service_edit.png and /dev/null differ
diff --git a/resources/images/service_new.png b/resources/images/service_new.png
deleted file mode 100644
index 8431237bd..000000000
Binary files a/resources/images/service_new.png and /dev/null differ
diff --git a/resources/images/service_open.png b/resources/images/service_open.png
deleted file mode 100644
index eb211e233..000000000
Binary files a/resources/images/service_open.png and /dev/null differ
diff --git a/resources/images/service_save.png b/resources/images/service_save.png
deleted file mode 100644
index 8072aea32..000000000
Binary files a/resources/images/service_save.png and /dev/null differ
diff --git a/resources/images/slide_close.png b/resources/images/slide_close.png
deleted file mode 100644
index 2c2f99eb4..000000000
Binary files a/resources/images/slide_close.png and /dev/null differ
diff --git a/resources/images/song_delete.png b/resources/images/song_delete.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/song_delete.png and /dev/null differ
diff --git a/resources/images/song_edit.png b/resources/images/song_edit.png
deleted file mode 100644
index 84e345d22..000000000
Binary files a/resources/images/song_edit.png and /dev/null differ
diff --git a/resources/images/song_export.png b/resources/images/song_export.png
deleted file mode 100644
index d26519b7f..000000000
Binary files a/resources/images/song_export.png and /dev/null differ
diff --git a/resources/images/song_new.png b/resources/images/song_new.png
deleted file mode 100644
index 8431237bd..000000000
Binary files a/resources/images/song_new.png and /dev/null differ
diff --git a/resources/images/system_add.png b/resources/images/system_add.png
deleted file mode 100644
index 7bb9b1be8..000000000
Binary files a/resources/images/system_add.png and /dev/null differ
diff --git a/resources/images/system_live.png b/resources/images/system_live.png
deleted file mode 100644
index cb2be2258..000000000
Binary files a/resources/images/system_live.png and /dev/null differ
diff --git a/resources/images/system_online_help.png b/resources/images/system_online_help.png
deleted file mode 100644
index 670c0716f..000000000
Binary files a/resources/images/system_online_help.png and /dev/null differ
diff --git a/resources/images/system_preview.png b/resources/images/system_preview.png
deleted file mode 100644
index d196792bb..000000000
Binary files a/resources/images/system_preview.png and /dev/null differ
diff --git a/resources/images/theme_delete.png b/resources/images/theme_delete.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/theme_delete.png and /dev/null differ
diff --git a/resources/images/theme_export.png b/resources/images/theme_export.png
deleted file mode 100644
index d26519b7f..000000000
Binary files a/resources/images/theme_export.png and /dev/null differ
diff --git a/resources/images/theme_import.png b/resources/images/theme_import.png
deleted file mode 100644
index 0ee789269..000000000
Binary files a/resources/images/theme_import.png and /dev/null differ
diff --git a/resources/images/tools_alert.png b/resources/images/tools_alert.png
deleted file mode 100644
index 331aa2687..000000000
Binary files a/resources/images/tools_alert.png and /dev/null differ
diff --git a/resources/images/video_delete.png b/resources/images/video_delete.png
deleted file mode 100644
index ef8e685e2..000000000
Binary files a/resources/images/video_delete.png and /dev/null differ
diff --git a/resources/images/video_load.png b/resources/images/video_load.png
deleted file mode 100644
index eb211e233..000000000
Binary files a/resources/images/video_load.png and /dev/null differ
diff --git a/resources/images/wizard_createprojector.png b/resources/images/wizard_createprojector.png
deleted file mode 100644
index d17a01e41..000000000
Binary files a/resources/images/wizard_createprojector.png and /dev/null differ
diff --git a/resources/images/wizard_importsong.bmp b/resources/images/wizard_importsong.bmp
deleted file mode 100644
index 948422dcc..000000000
Binary files a/resources/images/wizard_importsong.bmp and /dev/null differ
diff --git a/resources/images/wizard_exportsong.bmp b/resources/images/wizard_song.bmp
similarity index 100%
rename from resources/images/wizard_exportsong.bmp
rename to resources/images/wizard_song.bmp
diff --git a/scripts/clean_up_resources.py b/scripts/clean_up_resources.py
new file mode 100755
index 000000000..ce4cc8f6d
--- /dev/null
+++ b/scripts/clean_up_resources.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers #
+# --------------------------------------------------------------------------- #
+# 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 #
+###############################################################################
+
+import fnmatch
+import os
+
+from lxml import etree
+
+resource_path = os.path.join('..', 'resources', 'images')
+resource_file_path = os.path.join(resource_path, 'openlp-2.qrc')
+src_directory = os.path.join('..', 'openlp')
+
+RESOURCES_TO_IGNORE = [
+ 'openlp.svg',
+ 'OpenLP.ico',
+ 'openlp-2.qrc',
+ 'openlp-logo-16x16.png',
+ 'openlp-logo-32x32.png',
+ 'openlp-logo-48x48.png',
+ 'openlp-logo-64x64.png',
+ 'openlp-logo-128x128.png',
+ 'README.txt'
+]
+
+tree = etree.parse(resource_file_path)
+root = tree.getroot()
+
+print('Looking for unused resources listed in openlp-2.qrc')
+print('----------------------------------------------------------------')
+resources = root.findall('.//file')
+for current_dir, dirs, files in os.walk(src_directory):
+ for file_name in files:
+ if not fnmatch.fnmatch(file_name, '*.py'):
+ continue
+ file_path = os.path.join(current_dir, file_name)
+ with open(file_path) as source_file:
+ file_contents = source_file.read()
+ # Create a copy of the resources list so that we don't change the list while we're iterating through it!
+ for resource in list(resources):
+ if resource.text in file_contents:
+ resources.remove(resource)
+if resources:
+ print('Unused resources listed in openlp-2.qrc:')
+ print(*(x.text for x in resources), sep='\n')
+ print('----------------------------------------------------------------')
+ remove = None
+ while remove != 'yes' and remove != 'no':
+ remove = input('Would you like to remove these files from openlp-2.qrc? (yes/no)')
+ if remove == 'yes':
+ for resource in resources:
+ parent = resource.find('..')
+ parent.remove(resource)
+ tree.write(resource_file_path, encoding='utf8')
+else:
+ print('No unused resources listed in openlp-2.qrc')
+print('----------------------------------------------------------------')
+
+print('\nLooking for resource files which are not lited in openlp-2.qrc')
+print('----------------------------------------------------------------')
+resources = [x.text for x in root.findall('.//file')]
+removable_resources = []
+for resource_name in os.listdir(resource_path):
+ if resource_name not in RESOURCES_TO_IGNORE and resource_name not in resources:
+ removable_resources.append(resource_name)
+if removable_resources:
+ print('Resource files not listed in openlp-2.qrc:')
+ print(*removable_resources, sep='\n')
+ print('----------------------------------------------------------------')
+ remove = None
+ while remove != 'yes' and remove != 'no':
+ remove = input('Would you like to delete these files from the resource folder? (yes/no)')
+ print('----------------------------------------------------------------')
+ if remove == 'yes':
+ for resource in removable_resources:
+ resource_file = os.path.join(resource_path, resource)
+ print('Removing {file}'.format(file=resource_file))
+ os.remove(resource_file)
+else:
+ print('All resource files are listed!')
diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py
index 145be21f4..db95044fd 100644
--- a/tests/functional/openlp_core_lib/test_lib.py
+++ b/tests/functional/openlp_core_lib/test_lib.py
@@ -189,16 +189,14 @@ class TestLib(TestCase):
"""
Test the build_icon() function with a QIcon instance
"""
- with patch('openlp.core.lib.QtGui') as MockedQtGui:
- # GIVEN: A mocked QIcon
- MockedQtGui.QIcon = MagicMock
- mocked_icon = MockedQtGui.QIcon()
+ # GIVEN: An icon QIcon
+ icon = QtGui.QIcon()
- # WHEN: We pass a QIcon instance in
- result = build_icon(mocked_icon)
+ # WHEN: We pass a QIcon instance in
+ result = build_icon(icon)
- # THEN: The result should be our mocked QIcon
- self.assertIs(mocked_icon, result, 'The result should be the mocked QIcon')
+ # THEN: The result should be the same icon as we passed in
+ self.assertIs(icon, result, 'The result should be the same icon as we passed in')
def test_build_icon_with_resource(self):
"""
@@ -223,6 +221,30 @@ class TestLib(TestCase):
self.assertIsInstance(result, MagicMock, 'The result should be a MagicMock, because we mocked it out')
def test_image_to_byte(self):
+ """
+ Test the image_to_byte() function
+ """
+ with patch('openlp.core.lib.QtCore') as MockedQtCore:
+ # GIVEN: A set of mocked-out Qt classes
+ mocked_byte_array = MagicMock()
+ MockedQtCore.QByteArray.return_value = mocked_byte_array
+ mocked_buffer = MagicMock()
+ MockedQtCore.QBuffer.return_value = mocked_buffer
+ MockedQtCore.QIODevice.WriteOnly = 'writeonly'
+ mocked_image = MagicMock()
+
+ # WHEN: We convert an image to a byte array
+ result = image_to_byte(mocked_image, base_64=False)
+
+ # THEN: We should receive the mocked_buffer
+ MockedQtCore.QByteArray.assert_called_with()
+ MockedQtCore.QBuffer.assert_called_with(mocked_byte_array)
+ mocked_buffer.open.assert_called_with('writeonly')
+ mocked_image.save.assert_called_with(mocked_buffer, "PNG")
+ self.assertFalse(mocked_byte_array.toBase64.called)
+ self.assertEqual(mocked_byte_array, result, 'The mocked out byte array should be returned')
+
+ def test_image_to_byte_base_64(self):
"""
Test the image_to_byte() function
"""
diff --git a/tests/functional/openlp_core_ui/test_exceptionform.py b/tests/functional/openlp_core_ui/test_exceptionform.py
index 452a8dee9..493b2baeb 100644
--- a/tests/functional/openlp_core_ui/test_exceptionform.py
+++ b/tests/functional/openlp_core_ui/test_exceptionform.py
@@ -24,18 +24,13 @@ Package to test the openlp.core.ui.exeptionform package.
"""
import os
-import socket
import tempfile
-import urllib
from unittest import TestCase
from unittest.mock import mock_open
-from PyQt5.QtCore import QUrlQuery
-
from openlp.core.common import Registry
-from openlp.core.ui.firsttimeform import FirstTimeForm
-from tests.functional import MagicMock, patch
+from tests.functional import patch
from tests.helpers.testmixin import TestMixin
from openlp.core.ui import exceptionform
diff --git a/tests/functional/openlp_core_ui/test_servicemanager.py b/tests/functional/openlp_core_ui/test_servicemanager.py
index 3e4af8c97..e0366c7d2 100644
--- a/tests/functional/openlp_core_ui/test_servicemanager.py
+++ b/tests/functional/openlp_core_ui/test_servicemanager.py
@@ -28,6 +28,7 @@ from unittest import TestCase
import PyQt5
from openlp.core.common import Registry, ThemeLevel
+from openlp.core.ui.lib.toolbar import OpenLPToolbar
from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities
from openlp.core.ui import ServiceManager
@@ -544,8 +545,8 @@ class TestServiceManager(TestCase):
self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1,
'Should have be called once')
- @patch(u'openlp.core.ui.servicemanager.Settings')
- @patch(u'PyQt5.QtCore.QTimer.singleShot')
+ @patch('openlp.core.ui.servicemanager.Settings')
+ @patch('PyQt5.QtCore.QTimer.singleShot')
def test_single_click_preview_true(self, mocked_singleShot, MockedSettings):
"""
Test that when "Preview items when clicked in Service Manager" enabled the preview timer starts
@@ -561,8 +562,8 @@ class TestServiceManager(TestCase):
mocked_singleShot.assert_called_with(PyQt5.QtWidgets.QApplication.instance().doubleClickInterval(),
service_manager.on_single_click_preview_timeout)
- @patch(u'openlp.core.ui.servicemanager.Settings')
- @patch(u'PyQt5.QtCore.QTimer.singleShot')
+ @patch('openlp.core.ui.servicemanager.Settings')
+ @patch('PyQt5.QtCore.QTimer.singleShot')
def test_single_click_preview_false(self, mocked_singleShot, MockedSettings):
"""
Test that when "Preview items when clicked in Service Manager" disabled the preview timer doesn't start
@@ -577,9 +578,9 @@ class TestServiceManager(TestCase):
# THEN: timer should not be started
self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called')
- @patch(u'openlp.core.ui.servicemanager.Settings')
- @patch(u'PyQt5.QtCore.QTimer.singleShot')
- @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live')
+ @patch('openlp.core.ui.servicemanager.Settings')
+ @patch('PyQt5.QtCore.QTimer.singleShot')
+ @patch('openlp.core.ui.servicemanager.ServiceManager.make_live')
def test_single_click_preview_double(self, mocked_make_live, mocked_singleShot, MockedSettings):
"""
Test that when a double click has registered the preview timer doesn't start
@@ -596,7 +597,7 @@ class TestServiceManager(TestCase):
mocked_make_live.assert_called_with()
self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called')
- @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview')
+ @patch('openlp.core.ui.servicemanager.ServiceManager.make_preview')
def test_single_click_timeout_single(self, mocked_make_preview):
"""
Test that when a single click has been registered, the item is sent to preview
@@ -609,8 +610,8 @@ class TestServiceManager(TestCase):
self.assertEqual(mocked_make_preview.call_count, 1,
'ServiceManager.make_preview() should have been called once')
- @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview')
- @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live')
+ @patch('openlp.core.ui.servicemanager.ServiceManager.make_preview')
+ @patch('openlp.core.ui.servicemanager.ServiceManager.make_live')
def test_single_click_timeout_double(self, mocked_make_live, mocked_make_preview):
"""
Test that when a double click has been registered, the item does not goes to preview
@@ -623,9 +624,9 @@ class TestServiceManager(TestCase):
# THEN: make_preview() should not have been called
self.assertEqual(mocked_make_preview.call_count, 0, 'ServiceManager.make_preview() should not be called')
- @patch(u'openlp.core.ui.servicemanager.shutil.copy')
- @patch(u'openlp.core.ui.servicemanager.zipfile')
- @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as')
+ @patch('openlp.core.ui.servicemanager.shutil.copy')
+ @patch('openlp.core.ui.servicemanager.zipfile')
+ @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as')
def test_save_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy):
"""
Test that when a PermissionError is raised when trying to save a file, it is handled correctly
@@ -652,9 +653,9 @@ class TestServiceManager(TestCase):
self.assertTrue(result)
mocked_save_file_as.assert_called_with()
- @patch(u'openlp.core.ui.servicemanager.shutil.copy')
- @patch(u'openlp.core.ui.servicemanager.zipfile')
- @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as')
+ @patch('openlp.core.ui.servicemanager.shutil.copy')
+ @patch('openlp.core.ui.servicemanager.zipfile')
+ @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as')
def test_save_local_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy):
"""
Test that when a PermissionError is raised when trying to save a local file, it is handled correctly
@@ -679,3 +680,66 @@ class TestServiceManager(TestCase):
# THEN: The "save_as" method is called to save the service
self.assertTrue(result)
mocked_save_file_as.assert_called_with()
+
+ @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items')
+ def test_theme_change_global(self, mocked_regenerate_service_items):
+ """
+ Test that when a Toolbar theme combobox displays correctly when the theme is set to Global
+ """
+ # GIVEN: A service manager, a service to display with a theme level in the renderer
+ mocked_renderer = MagicMock()
+ service_manager = ServiceManager(None)
+ Registry().register('renderer', mocked_renderer)
+ service_manager.toolbar = OpenLPToolbar(None)
+ service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock())
+ service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock())
+
+ # WHEN: The service manager has a Global theme
+ mocked_renderer.theme_level = ThemeLevel.Global
+ result = service_manager.theme_change()
+
+ # THEN: The the theme toolbar should not be visible
+ self.assertFalse(service_manager.toolbar.actions['theme_combo_box'].isVisible(),
+ 'The visibility should be False')
+
+ @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items')
+ def test_theme_change_service(self, mocked_regenerate_service_items):
+ """
+ Test that when a Toolbar theme combobox displays correctly when the theme is set to Theme
+ """
+ # GIVEN: A service manager, a service to display with a theme level in the renderer
+ mocked_renderer = MagicMock()
+ service_manager = ServiceManager(None)
+ Registry().register('renderer', mocked_renderer)
+ service_manager.toolbar = OpenLPToolbar(None)
+ service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock())
+ service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock())
+
+ # WHEN: The service manager has a Service theme
+ mocked_renderer.theme_level = ThemeLevel.Service
+ result = service_manager.theme_change()
+
+ # THEN: The the theme toolbar should be visible
+ self.assertTrue(service_manager.toolbar.actions['theme_combo_box'].isVisible(),
+ 'The visibility should be True')
+
+ @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items')
+ def test_theme_change_song(self, mocked_regenerate_service_items):
+ """
+ Test that when a Toolbar theme combobox displays correctly when the theme is set to Song
+ """
+ # GIVEN: A service manager, a service to display with a theme level in the renderer
+ mocked_renderer = MagicMock()
+ service_manager = ServiceManager(None)
+ Registry().register('renderer', mocked_renderer)
+ service_manager.toolbar = OpenLPToolbar(None)
+ service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock())
+ service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock())
+
+ # WHEN: The service manager has a Song theme
+ mocked_renderer.theme_level = ThemeLevel.Song
+ result = service_manager.theme_change()
+
+ # THEN: The the theme toolbar should be visible
+ self.assertTrue(service_manager.toolbar.actions['theme_combo_box'].isVisible(),
+ 'The visibility should be True')
diff --git a/tests/functional/openlp_core_ui/test_settingsform.py b/tests/functional/openlp_core_ui/test_settingsform.py
index 051bc7af1..3f6f0aaad 100644
--- a/tests/functional/openlp_core_ui/test_settingsform.py
+++ b/tests/functional/openlp_core_ui/test_settingsform.py
@@ -48,7 +48,7 @@ class TestSettingsForm(TestCase):
general_tab = MagicMock()
general_tab.tab_title = 'mock'
general_tab.tab_title_visible = 'Mock'
- general_tab.icon_path = ':/icon/openlp-logo-16x16.png'
+ general_tab.icon_path = ':/icon/openlp-logo.svg'
# WHEN: We insert the general tab
with patch.object(settings_form.stacked_layout, 'addWidget') as mocked_add_widget, \
@@ -86,14 +86,14 @@ class TestSettingsForm(TestCase):
general_tab = QtWidgets.QWidget(None)
general_tab.tab_title = 'mock-general'
general_tab.tab_title_visible = 'Mock General'
- general_tab.icon_path = ':/icon/openlp-logo-16x16.png'
+ general_tab.icon_path = ':/icon/openlp-logo.svg'
mocked_general_save = MagicMock()
general_tab.save = mocked_general_save
settings_form.insert_tab(general_tab, is_visible=True)
themes_tab = QtWidgets.QWidget(None)
themes_tab.tab_title = 'mock-themes'
themes_tab.tab_title_visible = 'Mock Themes'
- themes_tab.icon_path = ':/icon/openlp-logo-16x16.png'
+ themes_tab.icon_path = ':/icon/openlp-logo.svg'
mocked_theme_save = MagicMock()
themes_tab.save = mocked_theme_save
settings_form.insert_tab(themes_tab, is_visible=False)
@@ -114,7 +114,7 @@ class TestSettingsForm(TestCase):
general_tab = QtWidgets.QWidget(None)
general_tab.tab_title = 'mock'
general_tab.tab_title_visible = 'Mock'
- general_tab.icon_path = ':/icon/openlp-logo-16x16.png'
+ general_tab.icon_path = ':/icon/openlp-logo.svg'
settings_form.insert_tab(general_tab, is_visible=True)
with patch.object(settings_form.stacked_layout, 'count') as mocked_count:
@@ -133,14 +133,14 @@ class TestSettingsForm(TestCase):
general_tab = QtWidgets.QWidget(None)
general_tab.tab_title = 'mock-general'
general_tab.tab_title_visible = 'Mock General'
- general_tab.icon_path = ':/icon/openlp-logo-16x16.png'
+ general_tab.icon_path = ':/icon/openlp-logo.svg'
mocked_general_cancel = MagicMock()
general_tab.cancel = mocked_general_cancel
settings_form.insert_tab(general_tab, is_visible=True)
themes_tab = QtWidgets.QWidget(None)
themes_tab.tab_title = 'mock-themes'
themes_tab.tab_title_visible = 'Mock Themes'
- themes_tab.icon_path = ':/icon/openlp-logo-16x16.png'
+ themes_tab.icon_path = ':/icon/openlp-logo.svg'
mocked_theme_cancel = MagicMock()
themes_tab.cancel = mocked_theme_cancel
settings_form.insert_tab(themes_tab, is_visible=False)
diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py
index ef1ce5793..b93952097 100644
--- a/tests/functional/openlp_core_ui/test_slidecontroller.py
+++ b/tests/functional/openlp_core_ui/test_slidecontroller.py
@@ -208,29 +208,6 @@ class TestSlideController(TestCase):
mocked_on_theme_display.assert_called_once_with(False)
mocked_on_hide_display.assert_called_once_with(False)
- def test_live_escape(self):
- """
- Test that when the live_escape() method is called, the display is set to invisible and any media is stopped
- """
- # GIVEN: A new SlideController instance and mocked out display and media_controller
- mocked_display = MagicMock()
- mocked_media_controller = MagicMock()
- Registry.create()
- Registry().register('media_controller', mocked_media_controller)
- slide_controller = SlideController(None)
- slide_controller.display = mocked_display
- play_slides = MagicMock()
- play_slides.isChecked.return_value = False
- slide_controller.play_slides_loop = play_slides
- slide_controller.play_slides_once = play_slides
-
- # WHEN: live_escape() is called
- slide_controller.live_escape()
-
- # THEN: the display should be set to invisible and the media controller stopped
- mocked_display.setVisible.assert_called_once_with(False)
- mocked_media_controller.media_stop.assert_called_once_with(slide_controller)
-
def test_on_go_live_live_controller(self):
"""
Test that when the on_go_live() method is called the message is sent to the live controller and focus is
@@ -697,7 +674,7 @@ class TestSlideController(TestCase):
slide_controller.next_item = MagicMock()
slide_controller.previous_service = MagicMock()
slide_controller.next_service = MagicMock()
- slide_controller.escape_item = MagicMock()
+ slide_controller.desktop_screen_enable = MagicMock()
slide_controller.desktop_screen = MagicMock()
slide_controller.blank_screen = MagicMock()
slide_controller.theme_screen = MagicMock()
@@ -709,7 +686,7 @@ class TestSlideController(TestCase):
mocked_widget.addActions.assert_called_with([
slide_controller.previous_item, slide_controller.next_item,
slide_controller.previous_service, slide_controller.next_service,
- slide_controller.escape_item, slide_controller.desktop_screen,
+ slide_controller.desktop_screen_enable, slide_controller.desktop_screen,
slide_controller.theme_screen, slide_controller.blank_screen
])
diff --git a/tests/functional/openlp_core_ui_media/test_systemplayer.py b/tests/functional/openlp_core_ui_media/test_systemplayer.py
index d1873d2df..2bac0089b 100644
--- a/tests/functional/openlp_core_ui_media/test_systemplayer.py
+++ b/tests/functional/openlp_core_ui_media/test_systemplayer.py
@@ -107,6 +107,22 @@ class TestSystemPlayer(TestCase):
mocked_video_widget.hide.assert_called_once_with()
self.assertTrue(player.has_own_widget)
+ def test_disconnect_slots(self):
+ """
+ Test that we the disconnect slots method catches the TypeError
+ """
+ # GIVEN: A SystemPlayer class and a signal that throws a TypeError
+ player = SystemPlayer(self)
+ mocked_signal = MagicMock()
+ mocked_signal.disconnect.side_effect = \
+ TypeError('disconnect() failed between \'durationChanged\' and all its connections')
+
+ # WHEN: disconnect_slots() is called
+ player.disconnect_slots(mocked_signal)
+
+ # THEN: disconnect should have been called and the exception should have been ignored
+ mocked_signal.disconnect.assert_called_once_with()
+
def test_check_available(self):
"""
Test the check_available() method on SystemPlayer
@@ -198,7 +214,8 @@ class TestSystemPlayer(TestCase):
with patch.object(player, 'get_live_state') as mocked_get_live_state, \
patch.object(player, 'seek') as mocked_seek, \
patch.object(player, 'volume') as mocked_volume, \
- patch.object(player, 'set_state') as mocked_set_state:
+ patch.object(player, 'set_state') as mocked_set_state, \
+ patch.object(player, 'disconnect_slots') as mocked_disconnect_slots:
mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PlayingState
result = player.play(mocked_display)
@@ -207,6 +224,7 @@ class TestSystemPlayer(TestCase):
mocked_display.media_player.play.assert_called_once_with()
mocked_seek.assert_called_once_with(mocked_display, 1000)
mocked_volume.assert_called_once_with(mocked_display, 1)
+ mocked_disconnect_slots.assert_called_once_with(mocked_display.media_player.durationChanged)
mocked_display.media_player.durationChanged.connect.assert_called_once_with('function')
mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display)
mocked_display.video_widget.raise_.assert_called_once_with()
diff --git a/tests/functional/openlp_plugins/bibles/test_bibleimport.py b/tests/functional/openlp_plugins/bibles/test_bibleimport.py
index e2076df55..a2244dbf7 100644
--- a/tests/functional/openlp_plugins/bibles/test_bibleimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_bibleimport.py
@@ -27,9 +27,12 @@ from io import BytesIO
from lxml import etree, objectify
from unittest import TestCase
+from PyQt5.QtWidgets import QDialog
from openlp.core.common.languages import Language
+from openlp.core.lib.exceptions import ValidationError
from openlp.plugins.bibles.lib.bibleimport import BibleImport
+from openlp.plugins.bibles.lib.db import BibleDB
from tests.functional import MagicMock, patch
@@ -39,22 +42,103 @@ class TestBibleImport(TestCase):
"""
def setUp(self):
- test_file = BytesIO(b'\n'
- b'\n'
- b'
\n'
+ b' Testdatatodiscard\n'
+ b''
+ )
+ self.open_patcher = patch('builtins.open')
+ self.addCleanup(self.open_patcher.stop)
+ self.mocked_open = self.open_patcher.start()
+ self.critical_error_message_box_patcher = \
+ patch('openlp.plugins.bibles.lib.bibleimport.critical_error_message_box')
+ self.addCleanup(self.critical_error_message_box_patcher.stop)
+ self.mocked_critical_error_message_box = self.critical_error_message_box_patcher.start()
self.setup_patcher = patch('openlp.plugins.bibles.lib.db.BibleDB._setup')
-
- self.addCleanup(self.file_patcher.stop)
- self.addCleanup(self.log_patcher.stop)
self.addCleanup(self.setup_patcher.stop)
-
- self.file_patcher.start()
- self.mock_log = self.log_patcher.start()
self.setup_patcher.start()
+ self.translate_patcher = patch('openlp.plugins.bibles.lib.bibleimport.translate',
+ side_effect=lambda module, string_to_translate, *args: string_to_translate)
+ self.addCleanup(self.translate_patcher.stop)
+ self.mocked_translate = self.translate_patcher.start()
+ self.registry_patcher = patch('openlp.plugins.bibles.lib.bibleimport.Registry')
+ self.addCleanup(self.registry_patcher.stop)
+ self.registry_patcher.start()
+
+ def init_kwargs_none_test(self):
+ """
+ Test the initialisation of the BibleImport Class when no key word arguments are supplied
+ """
+ # GIVEN: A patched BibleDB._setup, BibleImport class and mocked parent
+ # WHEN: Creating an instance of BibleImport with no key word arguments
+ instance = BibleImport(MagicMock())
+
+ # THEN: The filename attribute should be None
+ self.assertIsNone(instance.filename)
+ self.assertIsInstance(instance, BibleDB)
+
+ def init_kwargs_set_test(self):
+ """
+ Test the initialisation of the BibleImport Class when supplied with select keyword arguments
+ """
+ # GIVEN: A patched BibleDB._setup, BibleImport class and mocked parent
+ # WHEN: Creating an instance of BibleImport with selected key word arguments
+ kwargs = {'filename': 'bible.xml'}
+ instance = BibleImport(MagicMock(), **kwargs)
+
+ # THEN: The filename keyword should be set to bible.xml
+ self.assertEqual(instance.filename, 'bible.xml')
+ self.assertIsInstance(instance, BibleDB)
+
+ def get_language_canceled_test(self):
+ """
+ Test the BibleImport.get_language method when the user rejects the dialog box
+ """
+ # GIVEN: A mocked LanguageForm with an exec method which returns QtDialog.Rejected and an instance of BibleDB
+ with patch.object(BibleDB, '_setup'), patch('openlp.plugins.bibles.forms.LanguageForm') as mocked_language_form:
+
+ # The integer value of QtDialog.Rejected is 0. Using the enumeration causes a seg fault for some reason
+ mocked_language_form_instance = MagicMock(**{'exec.return_value': 0})
+ mocked_language_form.return_value = mocked_language_form_instance
+ instance = BibleImport(MagicMock())
+ mocked_wizard = MagicMock()
+ instance.wizard = mocked_wizard
+
+ # WHEN: Calling get_language()
+ result = instance.get_language()
+
+ # THEN: get_language() should return False
+ mocked_language_form.assert_called_once_with(mocked_wizard)
+ mocked_language_form_instance.exec.assert_called_once_with(None)
+ self.assertFalse(result, 'get_language() should return False if the user rejects the dialog box')
+
+ def get_language_accepted_test(self):
+ """
+ Test the BibleImport.get_language method when the user accepts the dialog box
+ """
+ # GIVEN: A mocked LanguageForm with an exec method which returns QtDialog.Accepted an instance of BibleDB and
+ # a combobox with the selected item data as 10
+ with patch.object(BibleDB, 'save_meta'), patch.object(BibleDB, '_setup'), \
+ patch('openlp.plugins.bibles.forms.LanguageForm') as mocked_language_form:
+
+ # The integer value of QtDialog.Accepted is 1. Using the enumeration causes a seg fault for some reason
+ mocked_language_form_instance = MagicMock(**{'exec.return_value': 1,
+ 'language_combo_box.itemData.return_value': 10})
+ mocked_language_form.return_value = mocked_language_form_instance
+ instance = BibleImport(MagicMock())
+ mocked_wizard = MagicMock()
+ instance.wizard = mocked_wizard
+
+ # WHEN: Calling get_language()
+ result = instance.get_language('Bible Name')
+
+ # THEN: get_language() should return the id of the selected language in the combo box
+ mocked_language_form.assert_called_once_with(mocked_wizard)
+ mocked_language_form_instance.exec.assert_called_once_with('Bible Name')
+ self.assertEqual(result, 10, 'get_language() should return the id of the language the user has chosen when '
+ 'they accept the dialog box')
def get_language_id_language_found_test(self):
"""
@@ -63,7 +147,7 @@ class TestBibleImport(TestCase):
# GIVEN: A mocked languages.get_language which returns language and an instance of BibleImport
with patch('openlp.core.common.languages.get_language', return_value=Language(30, 'English', 'en')) \
as mocked_languages_get_language, \
- patch('openlp.plugins.bibles.lib.db.BibleDB.get_language') as mocked_db_get_language:
+ patch.object(BibleImport, 'get_language') as mocked_db_get_language:
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
@@ -81,9 +165,8 @@ class TestBibleImport(TestCase):
Test get_language_id() when called with a name not found in the languages list
"""
# GIVEN: A mocked languages.get_language which returns language and an instance of BibleImport
- with patch('openlp.core.common.languages.get_language', return_value=None) \
- as mocked_languages_get_language, \
- patch('openlp.plugins.bibles.lib.db.BibleDB.get_language', return_value=20) as mocked_db_get_language:
+ with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \
+ patch.object(BibleImport, 'get_language', return_value=20) as mocked_db_get_language:
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
@@ -103,8 +186,8 @@ class TestBibleImport(TestCase):
# GIVEN: A mocked languages.get_language which returns None a mocked BibleDB.get_language which returns a
# language id.
with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \
- patch('openlp.plugins.bibles.lib.db.BibleDB.get_language', return_value=40) as mocked_db_get_language:
- self.mock_log.error.reset_mock()
+ patch.object(BibleImport, 'get_language', return_value=40) as mocked_db_get_language, \
+ patch.object(BibleImport, 'log_error') as mocked_log_error:
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
@@ -114,7 +197,7 @@ class TestBibleImport(TestCase):
# THEN: The id of the language returned from BibleDB.get_language should be returned
mocked_languages_get_language.assert_called_once_with('English')
mocked_db_get_language.assert_called_once_with('KJV')
- self.assertFalse(self.mock_log.error.called)
+ self.assertFalse(mocked_log_error.error.called)
instance.save_meta.assert_called_once_with('language_id', 40)
self.assertEqual(result, 40)
@@ -125,8 +208,8 @@ class TestBibleImport(TestCase):
# GIVEN: A mocked languages.get_language which returns None a mocked BibleDB.get_language which returns a
# language id.
with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \
- patch('openlp.plugins.bibles.lib.db.BibleDB.get_language', return_value=None) as mocked_db_get_language:
- self.mock_log.error.reset_mock()
+ patch.object(BibleImport, 'get_language', return_value=None) as mocked_db_get_language, \
+ patch.object(BibleImport, 'log_error') as mocked_log_error:
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
@@ -136,18 +219,148 @@ class TestBibleImport(TestCase):
# THEN: None should be returned and an error should be logged
mocked_languages_get_language.assert_called_once_with('Qwerty')
mocked_db_get_language.assert_called_once_with('KJV')
- self.mock_log.error.assert_called_once_with('Language detection failed when importing from "KJV". '
- 'User aborted language selection.')
+ mocked_log_error.assert_called_once_with(
+ 'Language detection failed when importing from "KJV". User aborted language selection.')
self.assertFalse(instance.save_meta.called)
self.assertIsNone(result)
+ def get_book_ref_id_by_name_get_book_test(self):
+ """
+ Test get_book_ref_id_by_name when the book is found as a book in BiblesResourcesDB
+ """
+ # GIVEN: An instance of BibleImport and a mocked BiblesResourcesDB which returns a book id when get_book is
+ # called
+ with patch.object(BibleImport, 'log_debug'), \
+ patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB',
+ **{'get_book.return_value': {'id': 20}}):
+ instance = BibleImport(MagicMock())
+
+ # WHEN: Calling get_book_ref_id_by_name
+ result = instance.get_book_ref_id_by_name('Gen', 66, 4)
+
+ # THEN: The bible id should be returned
+ self.assertEqual(result, 20)
+
+ def get_book_ref_id_by_name_get_alternative_book_name_test(self):
+ """
+ Test get_book_ref_id_by_name when the book is found as an alternative book in BiblesResourcesDB
+ """
+ # GIVEN: An instance of BibleImport and a mocked BiblesResourcesDB which returns a book id when
+ # get_alternative_book_name is called
+ with patch.object(BibleImport, 'log_debug'), \
+ patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB',
+ **{'get_book.return_value': None, 'get_alternative_book_name.return_value': 30}):
+ instance = BibleImport(MagicMock())
+
+ # WHEN: Calling get_book_ref_id_by_name
+ result = instance.get_book_ref_id_by_name('Gen', 66, 4)
+
+ # THEN: The bible id should be returned
+ self.assertEqual(result, 30)
+
+ def get_book_ref_id_by_name_get_book_reference_id_test(self):
+ """
+ Test get_book_ref_id_by_name when the book is found as a book in AlternativeBookNamesDB
+ """
+ # GIVEN: An instance of BibleImport and a mocked AlternativeBookNamesDB which returns a book id when
+ # get_book_reference_id is called
+ with patch.object(BibleImport, 'log_debug'), \
+ patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB',
+ **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}), \
+ patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB',
+ **{'get_book_reference_id.return_value': 40}):
+ instance = BibleImport(MagicMock())
+
+ # WHEN: Calling get_book_ref_id_by_name
+ result = instance.get_book_ref_id_by_name('Gen', 66, 4)
+
+ # THEN: The bible id should be returned
+ self.assertEqual(result, 40)
+
+ def get_book_ref_id_by_name_book_name_form_rejected_test(self):
+ """
+ Test get_book_ref_id_by_name when the user rejects the BookNameForm
+ """
+ # GIVEN: An instance of BibleImport and a mocked BookNameForm which simulates a user rejecting the dialog
+ with patch.object(BibleImport, 'log_debug'), patch.object(BibleImport, 'get_books'), \
+ patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB',
+ **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}), \
+ patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB',
+ **{'get_book_reference_id.return_value': None}), \
+ patch('openlp.plugins.bibles.forms.BookNameForm',
+ return_value=MagicMock(**{'exec.return_value': QDialog.Rejected})):
+ instance = BibleImport(MagicMock())
+
+ # WHEN: Calling get_book_ref_id_by_name
+ result = instance.get_book_ref_id_by_name('Gen', 66, 4)
+
+ # THEN: None should be returned
+ self.assertIsNone(result)
+
+ def get_book_ref_id_by_name_book_name_form_accepted_test(self):
+ """
+ Test get_book_ref_id_by_name when the user accepts the BookNameForm
+ """
+ # GIVEN: An instance of BibleImport and a mocked BookNameForm which simulates a user accepting the dialog
+ with patch.object(BibleImport, 'log_debug'), patch.object(BibleImport, 'get_books'), \
+ patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB',
+ **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}), \
+ patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB',
+ **{'get_book_reference_id.return_value': None}) as mocked_alternative_book_names_db, \
+ patch('openlp.plugins.bibles.forms.BookNameForm',
+ return_value=MagicMock(**{'exec.return_value': QDialog.Accepted, 'book_id': 50})):
+ instance = BibleImport(MagicMock())
+
+ # WHEN: Calling get_book_ref_id_by_name
+ result = instance.get_book_ref_id_by_name('Gen', 66, 4)
+
+ # THEN: An alternative book name should be created and a bible id should be returned
+ mocked_alternative_book_names_db.create_alternative_book_name.assert_called_once_with('Gen', 50, 4)
+ self.assertEqual(result, 50)
+
+ def is_compressed_compressed_test(self):
+ """
+ Test is_compressed when the 'file' being tested is compressed
+ """
+ # GIVEN: An instance of BibleImport and a mocked is_zipfile which returns True
+ with patch('openlp.plugins.bibles.lib.bibleimport.is_zipfile', return_value=True):
+ instance = BibleImport(MagicMock())
+
+ # WHEN: Calling is_compressed
+ result = instance.is_compressed('file.ext')
+
+ # THEN: Then critical_error_message_box should be called informing the user that the file is compressed and
+ # True should be returned
+ self.mocked_critical_error_message_box.assert_called_once_with(
+ message='The file "file.ext" you supplied is compressed. You must decompress it before import.')
+ self.assertTrue(result)
+
+ def is_compressed_not_compressed_test(self):
+ """
+ Test is_compressed when the 'file' being tested is not compressed
+ """
+ # GIVEN: An instance of BibleImport and a mocked is_zipfile which returns False
+ with patch('openlp.plugins.bibles.lib.bibleimport.is_zipfile', return_value=False):
+ instance = BibleImport(MagicMock())
+
+ # WHEN: Calling is_compressed
+ result = instance.is_compressed('file.ext')
+
+ # THEN: False should be returned and critical_error_message_box should not have been called
+ self.assertFalse(result)
+ self.assertFalse(self.mocked_critical_error_message_box.called)
+
def parse_xml_etree_test(self):
"""
Test BibleImport.parse_xml() when called with the use_objectify default value
"""
- # GIVEN: A sample "file" to parse
+ # GIVEN: A sample "file" to parse and an instance of BibleImport
+ self.mocked_open.return_value = self.test_file
+ instance = BibleImport(MagicMock())
+ instance.wizard = MagicMock()
+
# WHEN: Calling parse_xml
- result = BibleImport.parse_xml('file.tst')
+ result = instance.parse_xml('file.tst')
# THEN: The result returned should contain the correct data, and should be an instance of eetree_Element
self.assertEqual(etree.tostring(result),
@@ -159,9 +372,13 @@ class TestBibleImport(TestCase):
"""
Test BibleImport.parse_xml() when called with use_objectify set to True
"""
- # GIVEN: A sample "file" to parse
+ # GIVEN: A sample "file" to parse and an instance of BibleImport
+ self.mocked_open.return_value = self.test_file
+ instance = BibleImport(MagicMock())
+ instance.wizard = MagicMock()
+
# WHEN: Calling parse_xml
- result = BibleImport.parse_xml('file.tst', use_objectify=True)
+ result = instance.parse_xml('file.tst', use_objectify=True)
# THEN: The result returned should contain the correct data, and should be an instance of ObjectifiedElement
self.assertEqual(etree.tostring(result),
@@ -173,11 +390,14 @@ class TestBibleImport(TestCase):
"""
Test BibleImport.parse_xml() when given a tuple of elements to remove
"""
- # GIVEN: A tuple of elements to remove
+ # GIVEN: A tuple of elements to remove and an instance of BibleImport
+ self.mocked_open.return_value = self.test_file
elements = ('unsupported', 'x', 'y')
+ instance = BibleImport(MagicMock())
+ instance.wizard = MagicMock()
# WHEN: Calling parse_xml, with a test file
- result = BibleImport.parse_xml('file.tst', elements=elements)
+ result = instance.parse_xml('file.tst', elements=elements)
# THEN: The result returned should contain the correct data
self.assertEqual(etree.tostring(result),
@@ -187,11 +407,14 @@ class TestBibleImport(TestCase):
"""
Test BibleImport.parse_xml() when given a tuple of tags to remove
"""
- # GIVEN: A tuple of tags to remove
+ # GIVEN: A tuple of tags to remove and an instance of BibleImport
+ self.mocked_open.return_value = self.test_file
tags = ('div', 'p', 'a')
+ instance = BibleImport(MagicMock())
+ instance.wizard = MagicMock()
# WHEN: Calling parse_xml, with a test file
- result = BibleImport.parse_xml('file.tst', tags=tags)
+ result = instance.parse_xml('file.tst', tags=tags)
# THEN: The result returned should contain the correct data
self.assertEqual(etree.tostring(result), b'\n Testdatatokeep\n Test'
@@ -201,12 +424,192 @@ class TestBibleImport(TestCase):
"""
Test BibleImport.parse_xml() when given a tuple of elements and of tags to remove
"""
- # GIVEN: A tuple of elements and of tags to remove
+ # GIVEN: A tuple of elements and of tags to remove and an instacne of BibleImport
+ self.mocked_open.return_value = self.test_file
elements = ('unsupported', 'x', 'y')
tags = ('div', 'p', 'a')
+ instance = BibleImport(MagicMock())
+ instance.wizard = MagicMock()
# WHEN: Calling parse_xml, with a test file
- result = BibleImport.parse_xml('file.tst', elements=elements, tags=tags)
+ result = instance.parse_xml('file.tst', elements=elements, tags=tags)
# THEN: The result returned should contain the correct data
self.assertEqual(etree.tostring(result), b'\n Testdatatokeep\n \n')
+
+ def parse_xml_file_file_not_found_exception_test(self):
+ """
+ Test that parse_xml handles a FileNotFoundError exception correctly
+ """
+ with patch.object(BibleImport, 'log_exception') as mocked_log_exception:
+ # GIVEN: A mocked open which raises a FileNotFoundError and an instance of BibleImporter
+ exception = FileNotFoundError()
+ exception.filename = 'file.tst'
+ exception.strerror = 'No such file or directory'
+ self.mocked_open.side_effect = exception
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling parse_xml
+ result = importer.parse_xml('file.tst')
+
+ # THEN: parse_xml should have caught the error, informed the user and returned None
+ mocked_log_exception.assert_called_once_with('Opening file.tst failed.')
+ self.mocked_critical_error_message_box.assert_called_once_with(
+ title='An Error Occured When Opening A File',
+ message='The following error occurred when trying to open\nfile.tst:\n\nNo such file or directory')
+ self.assertIsNone(result)
+
+ def parse_xml_file_permission_error_exception_test(self):
+ """
+ Test that parse_xml handles a PermissionError exception correctly
+ """
+ with patch.object(BibleImport, 'log_exception') as mocked_log_exception:
+ # GIVEN: A mocked open which raises a PermissionError and an instance of BibleImporter
+ exception = PermissionError()
+ exception.filename = 'file.tst'
+ exception.strerror = 'Permission denied'
+ self.mocked_open.side_effect = exception
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling parse_xml
+ result = importer.parse_xml('file.tst')
+
+ # THEN: parse_xml should have caught the error, informed the user and returned None
+ mocked_log_exception.assert_called_once_with('Opening file.tst failed.')
+ self.mocked_critical_error_message_box.assert_called_once_with(
+ title='An Error Occured When Opening A File',
+ message='The following error occurred when trying to open\nfile.tst:\n\nPermission denied')
+ self.assertIsNone(result)
+
+ def set_current_chapter_test(self):
+ """
+ Test set_current_chapter
+ """
+ # GIVEN: An instance of BibleImport and a mocked wizard
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer.wizard = MagicMock()
+
+ # WHEN: Calling set_current_chapter
+ importer.set_current_chapter('Book_Name', 'Chapter')
+
+ # THEN: Increment_progress_bar should have been called with a text string
+ importer.wizard.increment_progress_bar.assert_called_once_with('Importing Book_Name Chapter...')
+
+ def validate_xml_file_compressed_file_test(self):
+ """
+ Test that validate_xml_file raises a ValidationError when is_compressed returns True
+ """
+ # GIVEN: A mocked parse_xml which returns None
+ with patch.object(BibleImport, 'is_compressed', return_value=True):
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling is_compressed
+ # THEN: ValidationError should be raised, with the message 'Compressed file'
+ with self.assertRaises(ValidationError) as context:
+ importer.validate_xml_file('file.name', 'xbible')
+ self.assertEqual(context.exception.msg, 'Compressed file')
+
+ def validate_xml_file_parse_xml_fails_test(self):
+ """
+ Test that validate_xml_file raises a ValidationError when parse_xml returns None
+ """
+ # GIVEN: A mocked parse_xml which returns None
+ with patch.object(BibleImport, 'parse_xml', return_value=None), \
+ patch.object(BibleImport, 'is_compressed', return_value=False):
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling validate_xml_file
+ # THEN: ValidationError should be raised, with the message 'Error when opening file'
+ # the user that an OpenSong bible was found
+ with self.assertRaises(ValidationError) as context:
+ importer.validate_xml_file('file.name', 'xbible')
+ self.assertEqual(context.exception.msg, 'Error when opening file')
+
+ def validate_xml_file_success_test(self):
+ """
+ Test that validate_xml_file returns True with valid XML
+ """
+ # GIVEN: Some test data with an OpenSong Bible "bible" root tag
+ with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \
+ patch.object(BibleImport, 'is_compressed', return_value=False):
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling validate_xml_file
+ result = importer.validate_xml_file('file.name', 'bible')
+
+ # THEN: True should be returned
+ self.assertTrue(result)
+
+ def validate_xml_file_opensong_root_test(self):
+ """
+ Test that validate_xml_file raises a ValidationError with an OpenSong root tag
+ """
+ # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport
+ with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \
+ patch.object(BibleImport, 'is_compressed', return_value=False):
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling validate_xml_file
+ # THEN: ValidationError should be raised, and the critical error message box should was called informing
+ # the user that an OpenSong bible was found
+ with self.assertRaises(ValidationError) as context:
+ importer.validate_xml_file('file.name', 'xbible')
+ self.assertEqual(context.exception.msg, 'Invalid xml.')
+ self.mocked_critical_error_message_box.assert_called_once_with(
+ message='Incorrect Bible file type supplied. This looks like an OpenSong XML bible.')
+
+ def validate_xml_file_osis_root_test(self):
+ """
+ Test that validate_xml_file raises a ValidationError with an OSIS root tag
+ """
+ # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport
+ with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring(
+ '')), \
+ patch.object(BibleImport, 'is_compressed', return_value=False):
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling validate_xml_file
+ # THEN: ValidationError should be raised, and the critical error message box should was called informing
+ # the user that an OSIS bible was found
+ with self.assertRaises(ValidationError) as context:
+ importer.validate_xml_file('file.name', 'xbible')
+ self.assertEqual(context.exception.msg, 'Invalid xml.')
+ self.mocked_critical_error_message_box.assert_called_once_with(
+ message='Incorrect Bible file type supplied. This looks like an OSIS XML bible.')
+
+ def validate_xml_file_zefania_root_test(self):
+ """
+ Test that validate_xml_file raises a ValidationError with an Zefania root tag
+ """
+ # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport
+ with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \
+ patch.object(BibleImport, 'is_compressed', return_value=False):
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling validate_xml_file
+ # THEN: ValidationError should be raised, and the critical error message box should was called informing
+ # the user that an Zefania bible was found
+ with self.assertRaises(ValidationError) as context:
+ importer.validate_xml_file('file.name', 'xbible')
+ self.assertEqual(context.exception.msg, 'Invalid xml.')
+ self.mocked_critical_error_message_box.assert_called_once_with(
+ message='Incorrect Bible file type supplied. This looks like an Zefania XML bible.')
+
+ def validate_xml_file_unknown_root_test(self):
+ """
+ Test that validate_xml_file raises a ValidationError with an unknown root tag
+ """
+ # GIVEN: Some test data with an unknown root tag and an instance of BibleImport
+ with patch.object(
+ BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \
+ patch.object(BibleImport, 'is_compressed', return_value=False):
+ importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling validate_xml_file
+ # THEN: ValidationError should be raised, and the critical error message box should was called informing
+ # the user that a unknown xml bible was found
+ with self.assertRaises(ValidationError) as context:
+ importer.validate_xml_file('file.name', 'xbible')
+ self.assertEqual(context.exception.msg, 'Invalid xml.')
+ self.mocked_critical_error_message_box.assert_called_once_with(
+ message='Incorrect Bible file type supplied. This looks like an unknown type of XML bible.')
diff --git a/tests/functional/openlp_plugins/bibles/test_http.py b/tests/functional/openlp_plugins/bibles/test_bibleserver.py
similarity index 100%
rename from tests/functional/openlp_plugins/bibles/test_http.py
rename to tests/functional/openlp_plugins/bibles/test_bibleserver.py
diff --git a/tests/functional/openlp_plugins/bibles/test_csvimport.py b/tests/functional/openlp_plugins/bibles/test_csvimport.py
index ada03a07d..8eff7274e 100644
--- a/tests/functional/openlp_plugins/bibles/test_csvimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_csvimport.py
@@ -46,10 +46,10 @@ class TestCSVImport(TestCase):
def setUp(self):
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
- self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
self.addCleanup(self.manager_patcher.stop)
- self.addCleanup(self.registry_patcher.stop)
self.manager_patcher.start()
+ self.registry_patcher = patch('openlp.plugins.bibles.lib.bibleimport.Registry')
+ self.addCleanup(self.registry_patcher.stop)
self.registry_patcher.start()
def test_create_importer(self):
@@ -194,9 +194,9 @@ class TestCSVImport(TestCase):
# WHEN: Calling process_books
result = importer.process_books(['Book 1'])
- # THEN: increment_progress_bar should not be called and the return value should be None
+ # THEN: increment_progress_bar should not be called and the return value should be an empty dictionary
self.assertFalse(importer.wizard.increment_progress_bar.called)
- self.assertIsNone(result)
+ self.assertEqual(result, {})
def process_books_test(self):
"""
@@ -207,7 +207,6 @@ class TestCSVImport(TestCase):
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.translate'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
- type(importer).application = PropertyMock()
importer.find_and_create_book = MagicMock()
importer.language_id = 10
importer.stop_import_flag = False
@@ -222,7 +221,6 @@ class TestCSVImport(TestCase):
# The returned data should be a dictionary with both song's id and names.
self.assertEqual(importer.find_and_create_book.mock_calls,
[call('1. Mosebog', 2, 10), call('2. Mosebog', 2, 10)])
- importer.application.process_events.assert_called_once_with()
self.assertDictEqual(result, {1: '1. Mosebog', 2: '2. Mosebog'})
def process_verses_stopped_import_test(self):
@@ -233,19 +231,16 @@ class TestCSVImport(TestCase):
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
- type(importer).application = PropertyMock()
importer.get_book_name = MagicMock()
importer.session = MagicMock()
importer.stop_import_flag = True
importer.wizard = MagicMock()
# WHEN: Calling process_verses
- result = importer.process_verses([], [])
+ result = importer.process_verses(['Dummy Verse'], [])
# THEN: get_book_name should not be called and the return value should be None
self.assertFalse(importer.get_book_name.called)
- importer.wizard.increment_progress_bar.assert_called_once_with('Importing verses... done.')
- importer.application.process_events.assert_called_once_with()
self.assertIsNone(result)
def process_verses_successful_test(self):
@@ -257,7 +252,6 @@ class TestCSVImport(TestCase):
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.translate'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
- type(importer).application = PropertyMock()
importer.create_verse = MagicMock()
importer.get_book = MagicMock(return_value=Book('1', '1', '1. Mosebog', '1Mos'))
importer.get_book_name = MagicMock(return_value='1. Mosebog')
@@ -280,7 +274,6 @@ class TestCSVImport(TestCase):
[call('1', 1, 1, 'I Begyndelsen skabte Gud Himmelen og Jorden.'),
call('1', 1, 2, 'Og Jorden var øde og tom, og der var Mørke over Verdensdybet. '
'Men Guds Ånd svævede over Vandene.')])
- importer.application.process_events.assert_called_once_with()
def do_import_invalid_language_id_test(self):
"""
@@ -288,82 +281,24 @@ class TestCSVImport(TestCase):
"""
# GIVEN: An instance of CSVBible and a mocked get_language which simulates the user cancelling the language box
mocked_manager = MagicMock()
- with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
- patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
+ with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
importer.get_language = MagicMock(return_value=None)
# WHEN: Calling do_import
result = importer.do_import('Bible Name')
- # THEN: The log.exception method should have been called to show that it reached the except clause.
- # False should be returned.
+ # THEN: The False should be returned.
importer.get_language.assert_called_once_with('Bible Name')
- mocked_log.exception.assert_called_once_with('Could not import CSV bible')
self.assertFalse(result)
- def do_import_stop_import_test(self):
- """
- Test do_import when the import is stopped
- """
- # GIVEN: An instance of CSVBible with stop_import set to True
- mocked_manager = MagicMock()
- with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
- patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
- importer.get_language = MagicMock(return_value=10)
- importer.parse_csv_file = MagicMock(return_value=['Book 1', 'Book 2', 'Book 3'])
- importer.process_books = MagicMock()
- importer.stop_import_flag = True
- importer.wizard = MagicMock()
-
- # WHEN: Calling do_import
- result = importer.do_import('Bible Name')
-
- # THEN: log.exception should not be called, parse_csv_file should only be called once,
- # and False should be returned.
- self.assertFalse(mocked_log.exception.called)
- importer.parse_csv_file.assert_called_once_with('books.csv', Book)
- importer.process_books.assert_called_once_with(['Book 1', 'Book 2', 'Book 3'])
- self.assertFalse(result)
-
- def do_import_stop_import_2_test(self):
- """
- Test do_import when the import is stopped
- """
- # GIVEN: An instance of CSVBible with stop_import which is True the second time of calling
- mocked_manager = MagicMock()
- with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
- patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
- CSVBible.stop_import_flag = PropertyMock(side_effect=[False, True])
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verses.csv')
- importer.get_language = MagicMock(return_value=10)
- importer.parse_csv_file = MagicMock(side_effect=[['Book 1'], ['Verse 1']])
- importer.process_books = MagicMock(return_value=['Book 1'])
- importer.process_verses = MagicMock(return_value=['Verse 1'])
- importer.wizard = MagicMock()
-
- # WHEN: Calling do_import
- result = importer.do_import('Bible Name')
-
- # THEN: log.exception should not be called, parse_csv_file should be called twice,
- # and False should be returned.
- self.assertFalse(mocked_log.exception.called)
- self.assertEqual(importer.parse_csv_file.mock_calls, [call('books.csv', Book), call('verses.csv', Verse)])
- importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1'])
- self.assertFalse(result)
-
- # Cleanup
- del CSVBible.stop_import_flag
-
def do_import_success_test(self):
"""
Test do_import when the import succeeds
"""
# GIVEN: An instance of CSVBible
mocked_manager = MagicMock()
- with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
- patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
+ with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verses.csv')
importer.get_language = MagicMock(return_value=10)
importer.parse_csv_file = MagicMock(side_effect=[['Book 1'], ['Verse 1']])
@@ -376,9 +311,8 @@ class TestCSVImport(TestCase):
# WHEN: Calling do_import
result = importer.do_import('Bible Name')
- # THEN: log.exception should not be called, parse_csv_file should be called twice,
+ # THEN: parse_csv_file should be called twice,
# and True should be returned.
- self.assertFalse(mocked_log.exception.called)
self.assertEqual(importer.parse_csv_file.mock_calls, [call('books.csv', Book), call('verses.csv', Verse)])
importer.process_books.assert_called_once_with(['Book 1'])
importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1'])
@@ -413,6 +347,6 @@ class TestCSVImport(TestCase):
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
- importer.create_verse.assert_any_call(importer.get_book().id, '1', verse_tag, verse_text)
+ importer.create_verse.assert_any_call(importer.get_book().id, 1, verse_tag, verse_text)
importer.create_book.assert_any_call('1. Mosebog', importer.get_book_ref_id_by_name(), 1)
importer.create_book.assert_any_call('1. Krønikebog', importer.get_book_ref_id_by_name(), 1)
diff --git a/tests/functional/openlp_plugins/bibles/test_db.py b/tests/functional/openlp_plugins/bibles/test_db.py
index 2807a8a3e..75e008953 100644
--- a/tests/functional/openlp_plugins/bibles/test_db.py
+++ b/tests/functional/openlp_plugins/bibles/test_db.py
@@ -25,63 +25,9 @@ This module contains tests for the db submodule of the Bibles plugin.
from unittest import TestCase
-from openlp.plugins.bibles.lib.db import BibleDB
-from tests.functional import MagicMock, patch
-
class TestBibleDB(TestCase):
"""
Test the functions in the BibleDB class.
"""
-
- def test_get_language_canceled(self):
- """
- Test the BibleDB.get_language method when the user rejects the dialog box
- """
- # GIVEN: A mocked LanguageForm with an exec method which returns QtDialog.Rejected and an instance of BibleDB
- with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
- patch('openlp.plugins.bibles.forms.LanguageForm') as mocked_language_form:
-
- # The integer value of QtDialog.Rejected is 0. Using the enumeration causes a seg fault for some reason
- mocked_language_form_instance = MagicMock(**{'exec.return_value': 0})
- mocked_language_form.return_value = mocked_language_form_instance
- mocked_parent = MagicMock()
- instance = BibleDB(mocked_parent)
- mocked_wizard = MagicMock()
- instance.wizard = mocked_wizard
-
- # WHEN: Calling get_language()
- result = instance.get_language()
-
- # THEN: get_language() should return False
- mocked_language_form.assert_called_once_with(mocked_wizard)
- mocked_language_form_instance.exec.assert_called_once_with(None)
- self.assertFalse(result, 'get_language() should return False if the user rejects the dialog box')
-
- def test_get_language_accepted(self):
- """
- Test the BibleDB.get_language method when the user accepts the dialog box
- """
- # GIVEN: A mocked LanguageForm with an exec method which returns QtDialog.Accepted an instance of BibleDB and
- # a combobox with the selected item data as 10
- with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'), \
- patch('openlp.plugins.bibles.lib.db.BibleDB.save_meta'), \
- patch('openlp.plugins.bibles.forms.LanguageForm') as mocked_language_form:
-
- # The integer value of QtDialog.Accepted is 1. Using the enumeration causes a seg fault for some reason
- mocked_language_form_instance = MagicMock(**{'exec.return_value': 1,
- 'language_combo_box.itemData.return_value': 10})
- mocked_language_form.return_value = mocked_language_form_instance
- mocked_parent = MagicMock()
- instance = BibleDB(mocked_parent)
- mocked_wizard = MagicMock()
- instance.wizard = mocked_wizard
-
- # WHEN: Calling get_language()
- result = instance.get_language('Bible Name')
-
- # THEN: get_language() should return the id of the selected language in the combo box
- mocked_language_form.assert_called_once_with(mocked_wizard)
- mocked_language_form_instance.exec.assert_called_once_with('Bible Name')
- self.assertEqual(result, 10, 'get_language() should return the id of the language the user has chosen when '
- 'they accept the dialog box')
+ pass
diff --git a/tests/functional/openlp_plugins/bibles/test_manager.py b/tests/functional/openlp_plugins/bibles/test_manager.py
new file mode 100644
index 000000000..d18d3cfc6
--- /dev/null
+++ b/tests/functional/openlp_plugins/bibles/test_manager.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers #
+# --------------------------------------------------------------------------- #
+# 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 manager submodule of the Bibles plugin.
+"""
+from unittest import TestCase
+from unittest.mock import MagicMock, patch
+
+from openlp.plugins.bibles.lib.manager import BibleManager
+
+
+class TestManager(TestCase):
+ """
+ Test the functions in the :mod:`manager` module.
+ """
+
+ def setUp(self):
+ app_location_patcher = patch('openlp.plugins.bibles.lib.manager.AppLocation')
+ self.addCleanup(app_location_patcher.stop)
+ app_location_patcher.start()
+ log_patcher = patch('openlp.plugins.bibles.lib.manager.log')
+ self.addCleanup(log_patcher.stop)
+ self.mocked_log = log_patcher.start()
+ settings_patcher = patch('openlp.plugins.bibles.lib.manager.Settings')
+ self.addCleanup(settings_patcher.stop)
+ settings_patcher.start()
+
+ def test_delete_bible(self):
+ """
+ Test the BibleManager delete_bible method
+ """
+ # GIVEN: An instance of BibleManager and a mocked bible
+ with patch.object(BibleManager, 'reload_bibles'), \
+ patch('openlp.plugins.bibles.lib.manager.os.path.join', side_effect=lambda x, y: '{}/{}'.format(x, y)),\
+ patch('openlp.plugins.bibles.lib.manager.delete_file', return_value=True) as mocked_delete_file:
+ instance = BibleManager(MagicMock())
+ # We need to keep a reference to the mock for close_all as it gets set to None later on!
+ mocked_close_all = MagicMock()
+ mocked_bible = MagicMock(file='KJV.sqlite', path='bibles', **{'session.close_all': mocked_close_all})
+ instance.db_cache = {'KJV': mocked_bible}
+
+ # WHEN: Calling delete_bible with 'KJV'
+ result = instance.delete_bible('KJV')
+
+ # THEN: The session should have been closed and set to None, the bible should be deleted, and the result of
+ # the deletion returned.
+ self.assertTrue(result)
+ mocked_close_all.assert_called_once_with()
+ self.assertIsNone(mocked_bible.session)
+ mocked_delete_file.assert_called_once_with('bibles/KJV.sqlite')
diff --git a/tests/functional/openlp_plugins/bibles/test_mediaitem.py b/tests/functional/openlp_plugins/bibles/test_mediaitem.py
index 05418f177..28a755fd6 100644
--- a/tests/functional/openlp_plugins/bibles/test_mediaitem.py
+++ b/tests/functional/openlp_plugins/bibles/test_mediaitem.py
@@ -114,7 +114,28 @@ class TestMediaItem(TestCase, TestMixin):
self.assertEqual(self.media_item.search_results, {})
self.assertEqual(self.media_item.second_search_results, {})
- def on_quick_search_button_general_test(self):
+ def test_required_icons(self):
+ """
+ Test that all the required icons are set properly.
+ """
+ # GIVEN: Mocked icons that need to be called.
+ self.media_item.has_import_icon = MagicMock()
+ self.media_item.has_new_icon = MagicMock()
+ self.media_item.has_edit_icon = MagicMock()
+ self.media_item.has_delete_icon = MagicMock()
+ self.media_item.add_to_service_item = MagicMock()
+
+ # WHEN: self.media_item.required_icons is called
+ self.media_item.required_icons()
+
+ # THEN: On windows it should return True, on other platforms False
+ self.assertTrue(self.media_item.has_import_icon, 'Check that the icon is as True.')
+ self.assertFalse(self.media_item.has_new_icon, 'Check that the icon is called as False.')
+ self.assertTrue(self.media_item.has_edit_icon, 'Check that the icon is called as True.')
+ self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.')
+ self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False')
+
+ def test_on_quick_search_button_general(self):
"""
Test that general things, which should be called on all Quick searches are called.
"""
@@ -150,3 +171,60 @@ class TestMediaItem(TestCase, TestMixin):
self.assertEqual(2, self.media_item.quickSearchButton.setEnabled.call_count, 'Disable and Enable the button')
self.assertEqual(1, self.media_item.check_search_result.call_count, 'Check results Should had been called once')
self.assertEqual(1, self.app.set_normal_cursor.call_count, 'Normal cursor should had been called once')
+
+ def test_on_clear_button_clicked(self):
+ """
+ Test that the on_clear_button_clicked works properly. (Used by Bible search tab)
+ """
+ # GIVEN: Mocked list_view, check_search_results & quick_search_edit.
+ self.media_item.list_view = MagicMock()
+ self.media_item.check_search_result = MagicMock()
+ self.media_item.quick_search_edit = MagicMock()
+
+ # WHEN: on_clear_button_clicked is called
+ self.media_item.on_clear_button_clicked()
+
+ # THEN: Search result should be reset and search field should receive focus.
+ self.media_item.list_view.clear.assert_called_once_with(),
+ self.media_item.check_search_result.assert_called_once_with(),
+ self.media_item.quick_search_edit.clear.assert_called_once_with(),
+ self.media_item.quick_search_edit.setFocus.assert_called_once_with()
+
+ def test_on_lock_button_toggled_search_tab_lock_icon(self):
+ """
+ Test that "on_lock_button_toggled" gives focus to the right field and toggles the lock properly.
+ """
+ # GIVEN: Mocked sender & Search edit, quickTab returning value = True on isVisible.
+ self.media_item.sender = MagicMock()
+ self.media_item.quick_search_edit = MagicMock()
+ self.media_item.quickTab = MagicMock(**{'isVisible.return_value': True})
+
+ self.media_item.lock_icon = 'lock icon'
+ sender_instance_mock = MagicMock()
+ self.media_item.sender = MagicMock(return_value=sender_instance_mock)
+
+ # WHEN: on_lock_button_toggled is called and checked returns = True.
+ self.media_item.on_lock_button_toggled(True)
+
+ # THEN: on_quick_search_edit should receive focus and Lock icon should be set.
+ self.media_item.quick_search_edit.setFocus.assert_called_once_with()
+ sender_instance_mock.setIcon.assert_called_once_with('lock icon')
+
+ def test_on_lock_button_toggled_unlock_icon(self):
+ """
+ Test that lock button unlocks properly and lock toggles properly.
+ """
+ # GIVEN: Mocked sender & Search edit, quickTab returning value = False on isVisible.
+ self.media_item.sender = MagicMock()
+ self.media_item.quick_search_edit = MagicMock()
+ self.media_item.quickTab = MagicMock()
+ self.media_item.quickTab.isVisible = MagicMock()
+ self.media_item.unlock_icon = 'unlock icon'
+ sender_instance_mock = MagicMock()
+ self.media_item.sender = MagicMock(return_value=sender_instance_mock)
+
+ # WHEN: on_lock_button_toggled is called and checked returns = False.
+ self.media_item.on_lock_button_toggled(False)
+
+ # THEN: Unlock icon should be set.
+ sender_instance_mock.setIcon.assert_called_once_with('unlock icon')
diff --git a/tests/functional/openlp_plugins/bibles/test_opensongimport.py b/tests/functional/openlp_plugins/bibles/test_opensongimport.py
index d6997135b..68ebdd37c 100644
--- a/tests/functional/openlp_plugins/bibles/test_opensongimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_opensongimport.py
@@ -23,32 +23,38 @@
This module contains tests for the OpenSong Bible importer.
"""
-import os
import json
+import os
from unittest import TestCase
-from tests.functional import MagicMock, patch
-from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible
-from openlp.plugins.bibles.lib.db import BibleDB
+from lxml import objectify
+
+from tests.functional import MagicMock, patch, call
+from tests.helpers.testmixin import TestMixin
+from openlp.core.common import Registry
+from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible, get_text, parse_chapter_number
+from openlp.plugins.bibles.lib.bibleimport import BibleImport
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..', 'resources', 'bibles'))
-class TestOpenSongImport(TestCase):
+class TestOpenSongImport(TestCase, TestMixin):
"""
Test the functions in the :mod:`opensongimport` module.
"""
def setUp(self):
- self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
- self.registry_patcher.start()
+ self.find_and_create_book_patch = patch.object(BibleImport, 'find_and_create_book')
+ self.addCleanup(self.find_and_create_book_patch.stop)
+ self.mocked_find_and_create_book = self.find_and_create_book_patch.start()
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
+ self.addCleanup(self.manager_patcher.stop)
self.manager_patcher.start()
-
- def tearDown(self):
- self.registry_patcher.stop()
- self.manager_patcher.stop()
+ self.setup_application()
+ self.app.process_events = MagicMock()
+ Registry.create()
+ Registry().register('application', self.app)
def test_create_importer(self):
"""
@@ -61,7 +67,332 @@ class TestOpenSongImport(TestCase):
importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
# THEN: The importer should be an instance of BibleDB
- self.assertIsInstance(importer, BibleDB)
+ self.assertIsInstance(importer, BibleImport)
+
+ def get_text_no_text_test(self):
+ """
+ Test that get_text handles elements containing text in a combination of text and tail attributes
+ """
+ # GIVEN: Some test data which contains an empty element and an instance of OpenSongBible
+ test_data = objectify.fromstring('')
+
+ # WHEN: Calling get_text
+ result = get_text(test_data)
+
+ # THEN: A blank string should be returned
+ self.assertEqual(result, '')
+
+ def get_text_text_test(self):
+ """
+ Test that get_text handles elements containing text in a combination of text and tail attributes
+ """
+ # GIVEN: Some test data which contains all possible permutation of text and tail text possible and an instance
+ # of OpenSongBible
+ test_data = objectify.fromstring('Element text '
+ 'sub_text_tail text sub_text_tail tail '
+ 'sub_text text '
+ 'sub_tail tail')
+
+ # WHEN: Calling get_text
+ result = get_text(test_data)
+
+ # THEN: The text returned should be as expected
+ self.assertEqual(result, 'Element text sub_text_tail text sub_text_tail tail sub_text text sub_tail tail')
+
+ def parse_chapter_number_test(self):
+ """
+ Test parse_chapter_number when supplied with chapter number and an instance of OpenSongBible
+ """
+ # GIVEN: The number 10 represented as a string
+ # WHEN: Calling parse_chapter_nnumber
+ result = parse_chapter_number('10', 0)
+
+ # THEN: The 10 should be returned as an Int
+ self.assertEqual(result, 10)
+
+ def parse_chapter_number_empty_attribute_test(self):
+ """
+ Testparse_chapter_number when the chapter number is an empty string. (Bug #1074727)
+ """
+ # GIVEN: An empty string, and the previous chapter number set as 12 and an instance of OpenSongBible
+ # WHEN: Calling parse_chapter_number
+ result = parse_chapter_number('', 12)
+
+ # THEN: parse_chapter_number should increment the previous verse number
+ self.assertEqual(result, 13)
+
+ def parse_verse_number_valid_verse_no_test(self):
+ """
+ Test parse_verse_number when supplied with a valid verse number
+ """
+ # GIVEN: An instance of OpenSongBible, the number 15 represented as a string and an instance of OpenSongBible
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling parse_verse_number
+ result = importer.parse_verse_number('15', 0)
+
+ # THEN: parse_verse_number should return the verse number
+ self.assertEqual(result, 15)
+
+ def parse_verse_number_verse_range_test(self):
+ """
+ Test parse_verse_number when supplied with a verse range
+ """
+ # GIVEN: An instance of OpenSongBible, and the range 24-26 represented as a string
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling parse_verse_number
+ result = importer.parse_verse_number('24-26', 0)
+
+ # THEN: parse_verse_number should return the first verse number in the range
+ self.assertEqual(result, 24)
+
+ def parse_verse_number_invalid_verse_no_test(self):
+ """
+ Test parse_verse_number when supplied with a invalid verse number
+ """
+ # GIVEN: An instance of OpenSongBible, a non numeric string represented as a string
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling parse_verse_number
+ result = importer.parse_verse_number('invalid', 41)
+
+ # THEN: parse_verse_number should increment the previous verse number
+ self.assertEqual(result, 42)
+
+ def parse_verse_number_empty_attribute_test(self):
+ """
+ Test parse_verse_number when the verse number is an empty string. (Bug #1074727)
+ """
+ # GIVEN: An instance of OpenSongBible, an empty string, and the previous verse number set as 14
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ # WHEN: Calling parse_verse_number
+ result = importer.parse_verse_number('', 14)
+
+ # THEN: parse_verse_number should increment the previous verse number
+ self.assertEqual(result, 15)
+
+ def parse_verse_number_invalid_type_test(self):
+ """
+ Test parse_verse_number when the verse number is an invalid type)
+ """
+ with patch.object(OpenSongBible, 'log_warning')as mocked_log_warning:
+ # GIVEN: An instanceofOpenSongBible, a Tuple, and the previous verse number set as 12
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling parse_verse_number
+ result = importer.parse_verse_number((1, 2, 3), 12)
+
+ # THEN: parse_verse_number should log the verse number it was called with increment the previous verse
+ # number
+ mocked_log_warning.assert_called_once_with('Illegal verse number: (1, 2, 3)')
+ self.assertEqual(result, 13)
+
+ def process_books_stop_import_test(self):
+ """
+ Test process_books when stop_import is set to True
+ """
+ # GIVEN: An instance of OpenSongBible
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: stop_import_flag is set to True
+ importer.stop_import_flag = True
+ importer.process_books(['Book'])
+
+ # THEN: find_and_create_book should not have been called
+ self.assertFalse(self.mocked_find_and_create_book.called)
+
+ def process_books_completes_test(self):
+ """
+ Test process_books when it processes all books
+ """
+ # GIVEN: An instance of OpenSongBible Importer and two mocked books
+ self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2']
+ with patch.object(OpenSongBible, 'process_chapters') as mocked_process_chapters:
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ book1 = MagicMock()
+ book1.attrib = {'n': 'Name1'}
+ book1.c = 'Chapter1'
+ book2 = MagicMock()
+ book2.attrib = {'n': 'Name2'}
+ book2.c = 'Chapter2'
+ importer.language_id = 10
+ importer.session = MagicMock()
+ importer.stop_import_flag = False
+
+ # WHEN: Calling process_books with the two books
+ importer.process_books([book1, book2])
+
+ # THEN: find_and_create_book and process_books should be called with the details from the mocked books
+ self.assertEqual(self.mocked_find_and_create_book.call_args_list,
+ [call('Name1', 2, 10), call('Name2', 2, 10)])
+ self.assertEqual(mocked_process_chapters.call_args_list,
+ [call('db_book1', 'Chapter1'), call('db_book2', 'Chapter2')])
+ self.assertEqual(importer.session.commit.call_count, 2)
+
+ def process_chapters_stop_import_test(self):
+ """
+ Test process_chapters when stop_import is set to True
+ """
+ # GIVEN: An isntance of OpenSongBible
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer.parse_chapter_number = MagicMock()
+
+ # WHEN: stop_import_flag is set to True
+ importer.stop_import_flag = True
+ importer.process_chapters('Book', ['Chapter1'])
+
+ # THEN: importer.parse_chapter_number not have been called
+ self.assertFalse(importer.parse_chapter_number.called)
+
+ @patch('openlp.plugins.bibles.lib.importers.opensong.parse_chapter_number', **{'side_effect': [1, 2]})
+ def process_chapters_completes_test(self, mocked_parse_chapter_number):
+ """
+ Test process_chapters when it completes
+ """
+ # GIVEN: An instance of OpenSongBible
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer.wizard = MagicMock()
+
+ # WHEN: called with some valid data
+ book = MagicMock()
+ book.name = "Book"
+ chapter1 = MagicMock()
+ chapter1.attrib = {'n': '1'}
+ chapter1.c = 'Chapter1'
+ chapter1.v = ['Chapter1 Verses']
+ chapter2 = MagicMock()
+ chapter2.attrib = {'n': '2'}
+ chapter2.c = 'Chapter2'
+ chapter2.v = ['Chapter2 Verses']
+
+ importer.process_verses = MagicMock()
+ importer.stop_import_flag = False
+ importer.process_chapters(book, [chapter1, chapter2])
+
+ # THEN: parse_chapter_number, process_verses and increment_process_bar should have been called
+ self.assertEqual(mocked_parse_chapter_number.call_args_list, [call('1', 0), call('2', 1)])
+ self.assertEqual(
+ importer.process_verses.call_args_list,
+ [call(book, 1, ['Chapter1 Verses']), call(book, 2, ['Chapter2 Verses'])])
+ self.assertEqual(importer.wizard.increment_progress_bar.call_args_list,
+ [call('Importing Book 1...'), call('Importing Book 2...')])
+
+ def process_verses_stop_import_test(self):
+ """
+ Test process_verses when stop_import is set to True
+ """
+ # GIVEN: An isntance of OpenSongBible
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer.parse_verse_number = MagicMock()
+
+ # WHEN: stop_import_flag is set to True
+ importer.stop_import_flag = True
+ importer.process_verses('Book', 1, 'Verses')
+
+ # THEN: importer.parse_verse_number not have been called
+ self.assertFalse(importer.parse_verse_number.called)
+
+ def process_verses_completes_test(self):
+ """
+ Test process_verses when it completes
+ """
+ with patch('openlp.plugins.bibles.lib.importers.opensong.get_text',
+ **{'side_effect': ['Verse1 Text', 'Verse2 Text']}) as mocked_get_text, \
+ patch.object(OpenSongBible, 'parse_verse_number',
+ **{'side_effect': [1, 2]}) as mocked_parse_verse_number:
+ # GIVEN: An instance of OpenSongBible
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer.wizard = MagicMock()
+
+ # WHEN: called with some valid data
+ book = MagicMock()
+ book.id = 1
+ verse1 = MagicMock()
+ verse1.attrib = {'n': '1'}
+ verse1.c = 'Chapter1'
+ verse1.v = ['Chapter1 Verses']
+ verse2 = MagicMock()
+ verse2.attrib = {'n': '2'}
+ verse2.c = 'Chapter2'
+ verse2.v = ['Chapter2 Verses']
+
+ importer.create_verse = MagicMock()
+ importer.stop_import_flag = False
+ importer.process_verses(book, 1, [verse1, verse2])
+
+ # THEN: parse_chapter_number, process_verses and increment_process_bar should have been called
+ self.assertEqual(mocked_parse_verse_number.call_args_list, [call('1', 0), call('2', 1)])
+ self.assertEqual(mocked_get_text.call_args_list, [call(verse1), call(verse2)])
+ self.assertEqual(
+ importer.create_verse.call_args_list,
+ [call(1, 1, 1, 'Verse1 Text'), call(1, 1, 2, 'Verse2 Text')])
+
+ def do_import_parse_xml_fails_test(self):
+ """
+ Test do_import when parse_xml fails (returns None)
+ """
+ # GIVEN: An instance of OpenSongBible and a mocked parse_xml which returns False
+ with patch.object(OpenSongBible, 'log_debug'), \
+ patch.object(OpenSongBible, 'validate_xml_file'), \
+ patch.object(OpenSongBible, 'parse_xml', return_value=None), \
+ patch.object(OpenSongBible, 'get_language_id') as mocked_language_id:
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling do_import
+ result = importer.do_import()
+
+ # THEN: do_import should return False and get_language_id should have not been called
+ self.assertFalse(result)
+ self.assertFalse(mocked_language_id.called)
+
+ def do_import_no_language_test(self):
+ """
+ Test do_import when the user cancels the language selection dialog
+ """
+ # GIVEN: An instance of OpenSongBible and a mocked get_language which returns False
+ with patch.object(OpenSongBible, 'log_debug'), \
+ patch.object(OpenSongBible, 'validate_xml_file'), \
+ patch.object(OpenSongBible, 'parse_xml'), \
+ patch.object(OpenSongBible, 'get_language_id', return_value=False), \
+ patch.object(OpenSongBible, 'process_books') as mocked_process_books:
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling do_import
+ result = importer.do_import()
+
+ # THEN: do_import should return False and process_books should have not been called
+ self.assertFalse(result)
+ self.assertFalse(mocked_process_books.called)
+
+ def do_import_completes_test(self):
+ """
+ Test do_import when it completes successfully
+ """
+ # GIVEN: An instance of OpenSongBible
+ with patch.object(OpenSongBible, 'log_debug'), \
+ patch.object(OpenSongBible, 'validate_xml_file'), \
+ patch.object(OpenSongBible, 'parse_xml'), \
+ patch.object(OpenSongBible, 'get_language_id', return_value=10), \
+ patch.object(OpenSongBible, 'process_books'):
+ importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling do_import
+ result = importer.do_import()
+
+ # THEN: do_import should return True
+ self.assertTrue(result)
+
+
+class TestOpenSongImportFileImports(TestCase, TestMixin):
+ """
+ Test the functions in the :mod:`opensongimport` module.
+ """
+ def setUp(self):
+ self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
+ self.addCleanup(self.manager_patcher.stop)
+ self.manager_patcher.start()
def test_file_import(self):
"""
@@ -92,22 +423,3 @@ class TestOpenSongImport(TestCase):
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
importer.create_verse.assert_any_call(importer.create_book().id, 1, int(verse_tag), verse_text)
-
- def test_zefania_import_error(self):
- """
- Test that we give an error message if trying to import a zefania bible
- """
- # GIVEN: A mocked out "manager" and mocked out critical_error_message_box and an import
- with patch('openlp.plugins.bibles.lib.importers.opensong.critical_error_message_box') as \
- mocked_critical_error_message_box:
- mocked_manager = MagicMock()
- importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
-
- # WHEN: An trying to import a zefania bible
- importer.filename = os.path.join(TEST_PATH, 'zefania-dk1933.xml')
- importer.do_import()
-
- # THEN: The importer should have "shown" an error message
- mocked_critical_error_message_box.assert_called_with(message='Incorrect Bible file type supplied. '
- 'This looks like a Zefania XML bible, '
- 'please use the Zefania import option.')
diff --git a/tests/functional/openlp_plugins/bibles/test_osisimport.py b/tests/functional/openlp_plugins/bibles/test_osisimport.py
index e1700fbb7..18f591faf 100644
--- a/tests/functional/openlp_plugins/bibles/test_osisimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_osisimport.py
@@ -27,29 +27,35 @@ import os
import json
from unittest import TestCase
-from tests.functional import MagicMock, patch
-from openlp.plugins.bibles.lib.importers.osis import OSISBible
+from tests.functional import MagicMock, call, patch
+from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BibleDB
+from openlp.plugins.bibles.lib.importers.osis import OSISBible
-TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..', 'resources', 'bibles'))
+TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'bibles'))
class TestOsisImport(TestCase):
"""
Test the functions in the :mod:`osisimport` module.
"""
-
def setUp(self):
- self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
+ self.etree_patcher = patch('openlp.plugins.bibles.lib.importers.osis.etree')
+ self.addCleanup(self.etree_patcher.stop)
+ self.mocked_etree = self.etree_patcher.start()
+ self.create_verse_patcher = patch('openlp.plugins.bibles.lib.db.BibleDB.create_verse')
+ self.addCleanup(self.create_verse_patcher.stop)
+ self.mocked_create_verse = self.create_verse_patcher.start()
+ self.find_and_create_book_patch = patch.object(BibleImport, 'find_and_create_book')
+ self.addCleanup(self.find_and_create_book_patch.stop)
+ self.mocked_find_and_create_book = self.find_and_create_book_patch.start()
+ self.registry_patcher = patch('openlp.plugins.bibles.lib.bibleimport.Registry')
+ self.addCleanup(self.registry_patcher.stop)
self.registry_patcher.start()
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
+ self.addCleanup(self.manager_patcher.stop)
self.manager_patcher.start()
- def tearDown(self):
- self.registry_patcher.stop()
- self.manager_patcher.stop()
-
def test_create_importer(self):
"""
Test creating an instance of the OSIS file importer
@@ -63,6 +69,353 @@ class TestOsisImport(TestCase):
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleDB)
+ def process_books_stop_import_test(self):
+ """
+ Test process_books when stop_import is set to True
+ """
+ # GIVEN: An instance of OSISBible adn some mocked data
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ mocked_data = MagicMock(**{'xpath.return_value': ['Book']})
+
+ # WHEN: stop_import_flag is set to True and process_books is called
+ importer.stop_import_flag = True
+ importer.process_books(mocked_data)
+
+ # THEN: find_and_create_book should not have been called
+ self.assertFalse(self.mocked_find_and_create_book.called)
+
+ def process_books_completes_test(self):
+ """
+ Test process_books when it processes all books
+ """
+ # GIVEN: An instance of OSISBible Importer and two mocked books
+ self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2']
+ with patch.object(OSISBible, 'process_chapters') as mocked_process_chapters:
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ book1 = MagicMock()
+ book1.get.return_value = 'Name1'
+ book2 = MagicMock()
+ book2.get.return_value = 'Name2'
+ mocked_data = MagicMock(**{'xpath.return_value': [book1, book2]})
+ importer.language_id = 10
+ importer.session = MagicMock()
+ importer.stop_import_flag = False
+
+ # WHEN: Calling process_books with the two books
+ importer.process_books(mocked_data)
+
+ # THEN: find_and_create_book and process_books should be called with the details from the mocked books
+ self.assertEqual(self.mocked_find_and_create_book.call_args_list,
+ [call('Name1', 2, 10), call('Name2', 2, 10)])
+ self.assertEqual(mocked_process_chapters.call_args_list,
+ [call('db_book1', book1), call('db_book2', book2)])
+ self.assertEqual(importer.session.commit.call_count, 2)
+
+ def process_chapters_verse_in_chapter_verse_text_test(self):
+ """
+ Test process_chapters when supplied with an etree element with a verse element nested in it
+ """
+ with patch('openlp.plugins.bibles.lib.importers.osis.verse_in_chapter', return_value=True), \
+ patch('openlp.plugins.bibles.lib.importers.osis.text_in_verse', return_value=True), \
+ patch.object(OSISBible, 'set_current_chapter') as mocked_set_current_chapter, \
+ patch.object(OSISBible, 'process_verse') as mocked_process_verse:
+
+ # GIVEN: Some test data and an instance of OSISBible
+ test_book = MagicMock()
+ test_verse = MagicMock()
+ test_verse.tail = '\n ' # Whitespace
+ test_verse.text = 'Verse Text'
+ test_chapter = MagicMock()
+ test_chapter.__iter__.return_value = [test_verse]
+ test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_chapters
+ importer.process_chapters(test_book, [test_chapter])
+
+ # THEN: set_current_chapter and process_verse should have been called with the test data
+ mocked_set_current_chapter.assert_called_once_with(test_book.name, 2)
+ mocked_process_verse.assert_called_once_with(test_book, 2, test_verse)
+
+ def process_chapters_verse_in_chapter_verse_milestone_test(self):
+ """
+ Test process_chapters when supplied with an etree element with a verse element nested, when the verse system is
+ based on milestones
+ """
+ with patch('openlp.plugins.bibles.lib.importers.osis.verse_in_chapter', return_value=True), \
+ patch('openlp.plugins.bibles.lib.importers.osis.text_in_verse', return_value=False), \
+ patch.object(OSISBible, 'set_current_chapter') as mocked_set_current_chapter, \
+ patch.object(OSISBible, 'process_verse') as mocked_process_verse:
+
+ # GIVEN: Some test data and an instance of OSISBible
+ test_book = MagicMock()
+ test_verse = MagicMock()
+ test_verse.tail = '\n ' # Whitespace
+ test_verse.text = 'Verse Text'
+ test_chapter = MagicMock()
+ test_chapter.__iter__.return_value = [test_verse]
+ test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_chapters
+ importer.process_chapters(test_book, [test_chapter])
+
+ # THEN: set_current_chapter and process_verse should have been called with the test data
+ mocked_set_current_chapter.assert_called_once_with(test_book.name, 2)
+ mocked_process_verse.assert_called_once_with(test_book, 2, test_verse, use_milestones=True)
+
+ def process_chapters_milestones_chapter_no_sid_test(self):
+ """
+ Test process_chapters when supplied with an etree element with a chapter and verse element in the milestone
+ configuration, where the chapter is the "closing" milestone. (Missing the sID attribute)
+ """
+ with patch('openlp.plugins.bibles.lib.importers.osis.verse_in_chapter', return_value=False), \
+ patch.object(OSISBible, 'set_current_chapter') as mocked_set_current_chapter, \
+ patch.object(OSISBible, 'process_verse') as mocked_process_verse:
+
+ # GIVEN: Some test data and an instance of OSISBible
+ test_book = MagicMock()
+ test_chapter = MagicMock()
+ test_chapter.tag = '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter'
+ test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4'}.get(x)
+
+ # WHEN: Calling process_chapters
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer.process_chapters(test_book, [test_chapter])
+
+ # THEN: neither set_current_chapter or process_verse should have been called
+ self.assertFalse(mocked_set_current_chapter.called)
+ self.assertFalse(mocked_process_verse.called)
+
+ def process_chapters_milestones_chapter_sid_test(self):
+ """
+ Test process_chapters when supplied with an etree element with a chapter and verse element in the milestone
+ configuration, where the chapter is the "opening" milestone. (Has the sID attribute)
+ """
+ with patch('openlp.plugins.bibles.lib.importers.osis.verse_in_chapter', return_value=False), \
+ patch.object(OSISBible, 'set_current_chapter') as mocked_set_current_chapter, \
+ patch.object(OSISBible, 'process_verse') as mocked_process_verse:
+
+ # GIVEN: Some test data and an instance of OSISBible
+ test_book = MagicMock()
+ test_chapter = MagicMock()
+ test_chapter.tag = '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter'
+ test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_chapters
+ importer.process_chapters(test_book, [test_chapter])
+
+ # THEN: set_current_chapter should have been called with the test data
+ mocked_set_current_chapter.assert_called_once_with(test_book.name, 2)
+ self.assertFalse(mocked_process_verse.called)
+
+ def process_chapters_milestones_verse_tag_test(self):
+ """
+ Test process_chapters when supplied with an etree element with a chapter and verse element in the milestone
+ configuration, where the verse is the "opening" milestone. (Has the sID attribute)
+ """
+ with patch('openlp.plugins.bibles.lib.importers.osis.verse_in_chapter', return_value=False), \
+ patch.object(OSISBible, 'set_current_chapter') as mocked_set_current_chapter, \
+ patch.object(OSISBible, 'process_verse') as mocked_process_verse:
+
+ # GIVEN: Some test data and an instance of OSISBible
+ test_book = MagicMock()
+ test_verse = MagicMock()
+ test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ test_verse.tag = '{http://www.bibletechnologies.net/2003/OSIS/namespace}verse'
+ test_verse.tail = '\n ' # Whitespace
+ test_verse.text = 'Verse Text'
+
+ # WHEN: Calling process_chapters
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer.process_chapters(test_book, [test_verse])
+
+ # THEN: process_verse should have been called with the test data
+ self.assertFalse(mocked_set_current_chapter.called)
+ mocked_process_verse.assert_called_once_with(test_book, 0, test_verse, use_milestones=True)
+
+ def process_verse_no_osis_id_test(self):
+ """
+ Test process_verse when the element supplied does not have and osisID attribute
+ """
+ # GIVEN: An instance of OSISBible, and some mocked test data
+ test_book = MagicMock()
+ test_verse = MagicMock()
+ test_verse.get.side_effect = lambda x: {}.get(x)
+ test_verse.tail = 'Verse Text'
+ test_verse.text = None
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_verse with the test data
+ importer.process_verse(test_book, 2, test_verse)
+
+ # THEN: create_verse should not have been called
+ self.assertFalse(self.mocked_create_verse.called)
+
+ def process_verse_use_milestones_no_s_id_test(self):
+ """
+ Test process_verse when called with use_milestones set to True, but the element supplied does not have and sID
+ attribute
+ """
+ # GIVEN: An instance of OSISBible, and some mocked test data
+ test_book = MagicMock()
+ test_verse = MagicMock()
+ test_verse.get.side_effect = lambda x: {}.get(x)
+ test_verse.tail = 'Verse Text'
+ test_verse.text = None
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_verse with the test data
+ importer.process_verse(test_book, 2, test_verse)
+
+ # THEN: create_verse should not have been called
+ self.assertFalse(self.mocked_create_verse.called)
+
+ def process_verse_use_milestones_no_tail_test(self):
+ """
+ Test process_verse when called with use_milestones set to True, but the element supplied does not have a 'tail'
+ """
+ # GIVEN: An instance of OSISBible, and some mocked test data
+ test_book = MagicMock()
+ test_verse = MagicMock()
+ test_verse.tail = None
+ test_verse.text = None
+ test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_verse with the test data
+ importer.process_verse(test_book, 2, test_verse, use_milestones=True)
+
+ # THEN: create_verse should not have been called
+ self.assertFalse(self.mocked_create_verse.called)
+
+ def process_verse_use_milestones_success_test(self):
+ """
+ Test process_verse when called with use_milestones set to True, and the verse element successfully imports
+ """
+ # GIVEN: An instance of OSISBible, and some mocked test data
+ test_book = MagicMock()
+ test_book.id = 1
+ test_verse = MagicMock()
+ test_verse.tail = 'Verse Text'
+ test_verse.text = None
+ test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_verse with the test data
+ importer.process_verse(test_book, 2, test_verse, use_milestones=True)
+
+ # THEN: create_verse should have been called with the test data
+ self.mocked_create_verse.assert_called_once_with(1, 2, 4, 'Verse Text')
+
+ def process_verse_no_text_test(self):
+ """
+ Test process_verse when called with an empty verse element
+ """
+ # GIVEN: An instance of OSISBible, and some mocked test data
+ test_book = MagicMock()
+ test_book.id = 1
+ test_verse = MagicMock()
+ test_verse.tail = '\n ' # Whitespace
+ test_verse.text = None
+ test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_verse with the test data
+ importer.process_verse(test_book, 2, test_verse)
+
+ # THEN: create_verse should not have been called
+ self.assertFalse(self.mocked_create_verse.called)
+
+ def process_verse_success_test(self):
+ """
+ Test process_verse when called with an element with text set
+ """
+ # GIVEN: An instance of OSISBible, and some mocked test data
+ test_book = MagicMock()
+ test_book.id = 1
+ test_verse = MagicMock()
+ test_verse.tail = '\n ' # Whitespace
+ test_verse.text = 'Verse Text'
+ test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling process_verse with the test data
+ importer.process_verse(test_book, 2, test_verse)
+
+ # THEN: create_verse should have been called with the test data
+ self.mocked_create_verse.assert_called_once_with(1, 2, 4, 'Verse Text')
+
+ def do_import_parse_xml_fails_test(self):
+ """
+ Test do_import when parse_xml fails (returns None)
+ """
+ # GIVEN: An instance of OpenSongBible and a mocked parse_xml which returns False
+ with patch.object(OSISBible, 'log_debug'), \
+ patch.object(OSISBible, 'validate_xml_file'), \
+ patch.object(OSISBible, 'parse_xml', return_value=None), \
+ patch.object(OSISBible, 'get_language_id') as mocked_language_id:
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling do_import
+ result = importer.do_import()
+
+ # THEN: do_import should return False and get_language_id should have not been called
+ self.assertFalse(result)
+ self.assertFalse(mocked_language_id.called)
+
+ def do_import_no_language_test(self):
+ """
+ Test do_import when the user cancels the language selection dialog
+ """
+ # GIVEN: An instance of OpenSongBible and a mocked get_language which returns False
+ with patch.object(OSISBible, 'log_debug'), \
+ patch.object(OSISBible, 'validate_xml_file'), \
+ patch.object(OSISBible, 'parse_xml'), \
+ patch.object(OSISBible, 'get_language_id', **{'return_value': False}), \
+ patch.object(OSISBible, 'process_books') as mocked_process_books:
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling do_import
+ result = importer.do_import()
+
+ # THEN: do_import should return False and process_books should have not been called
+ self.assertFalse(result)
+ self.assertFalse(mocked_process_books.called)
+
+ def do_import_completes_test(self):
+ """
+ Test do_import when it completes successfully
+ """
+ # GIVEN: An instance of OpenSongBible
+ with patch.object(OSISBible, 'log_debug'), \
+ patch.object(OSISBible, 'validate_xml_file'), \
+ patch.object(OSISBible, 'parse_xml'), \
+ patch.object(OSISBible, 'get_language_id', **{'return_value': 10}), \
+ patch.object(OSISBible, 'process_books'):
+ importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+
+ # WHEN: Calling do_import
+ result = importer.do_import()
+
+ # THEN: do_import should return True
+ self.assertTrue(result)
+
+
+class TestOsisImportFileImports(TestCase):
+ """
+ Test the functions in the :mod:`osisimport` module.
+ """
+ def setUp(self):
+ self.registry_patcher = patch('openlp.plugins.bibles.lib.bibleimport.Registry')
+ self.addCleanup(self.registry_patcher.stop)
+ self.registry_patcher.start()
+ self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
+ self.addCleanup(self.manager_patcher.stop)
+ self.manager_patcher.start()
+
def test_file_import_nested_tags(self):
"""
Test the actual import of OSIS Bible file, with nested chapter and verse tags
@@ -91,7 +444,7 @@ class TestOsisImport(TestCase):
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
- importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
+ importer.create_verse.assert_any_call(importer.create_book().id, 1, verse_tag, verse_text)
def test_file_import_mixed_tags(self):
"""
@@ -121,7 +474,7 @@ class TestOsisImport(TestCase):
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
- importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
+ importer.create_verse.assert_any_call(importer.create_book().id, 1, verse_tag, verse_text)
def test_file_import_milestone_tags(self):
"""
@@ -151,7 +504,7 @@ class TestOsisImport(TestCase):
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
- importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
+ importer.create_verse.assert_any_call(importer.create_book().id, 1, verse_tag, verse_text)
def test_file_import_empty_verse_tags(self):
"""
@@ -181,4 +534,4 @@ class TestOsisImport(TestCase):
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
- importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
+ importer.create_verse.assert_any_call(importer.create_book().id, 1, verse_tag, verse_text)
diff --git a/tests/functional/openlp_plugins/bibles/test_swordimport.py b/tests/functional/openlp_plugins/bibles/test_swordimport.py
index 51e629f53..14480bdd1 100644
--- a/tests/functional/openlp_plugins/bibles/test_swordimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_swordimport.py
@@ -46,7 +46,7 @@ class TestSwordImport(TestCase):
"""
def setUp(self):
- self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
+ self.registry_patcher = patch('openlp.plugins.bibles.lib.bibleimport.Registry')
self.registry_patcher.start()
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
self.manager_patcher.start()
@@ -70,8 +70,7 @@ class TestSwordImport(TestCase):
@patch('openlp.plugins.bibles.lib.importers.sword.SwordBible.application')
@patch('openlp.plugins.bibles.lib.importers.sword.modules')
- @patch('openlp.core.common.languages')
- def test_simple_import(self, mocked_languages, mocked_pysword_modules, mocked_application):
+ def test_simple_import(self, mocked_pysword_modules, mocked_application):
"""
Test that a simple SWORD import works
"""
@@ -88,7 +87,7 @@ class TestSwordImport(TestCase):
importer.create_verse = MagicMock()
importer.create_book = MagicMock()
importer.session = MagicMock()
- mocked_languages.get_language.return_value = 'Danish'
+ importer.get_language = MagicMock(return_value='Danish')
mocked_bible = MagicMock()
mocked_genesis = MagicMock()
mocked_genesis.name = 'Genesis'
diff --git a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py
index 200a36f45..510961a65 100644
--- a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py
@@ -41,15 +41,13 @@ class TestZefaniaImport(TestCase):
"""
def setUp(self):
- self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
+ self.registry_patcher = patch('openlp.plugins.bibles.lib.bibleimport.Registry')
+ self.addCleanup(self.registry_patcher.stop)
self.registry_patcher.start()
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
+ self.addCleanup(self.manager_patcher.stop)
self.manager_patcher.start()
- def tearDown(self):
- self.registry_patcher.stop()
- self.manager_patcher.stop()
-
def test_create_importer(self):
"""
Test creating an instance of the Zefania file importer
@@ -90,7 +88,7 @@ class TestZefaniaImport(TestCase):
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
- importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
+ importer.create_verse.assert_any_call(importer.create_book().id, 1, verse_tag, verse_text)
importer.create_book.assert_any_call('Genesis', 1, 1)
def test_file_import_no_book_name(self):
@@ -120,5 +118,5 @@ class TestZefaniaImport(TestCase):
# THEN: The create_verse() method should have been called with each verse in the file.
self.assertTrue(importer.create_verse.called)
for verse_tag, verse_text in test_data['verses']:
- importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
+ importer.create_verse.assert_any_call(importer.create_book().id, 1, verse_tag, verse_text)
importer.create_book.assert_any_call('Exodus', 2, 1)
diff --git a/tests/functional/openlp_plugins/songs/test_mediashout.py b/tests/functional/openlp_plugins/songs/test_mediashout.py
new file mode 100644
index 000000000..c08b04df7
--- /dev/null
+++ b/tests/functional/openlp_plugins/songs/test_mediashout.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers #
+# --------------------------------------------------------------------------- #
+# 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 #
+###############################################################################
+"""
+Test the MediaShout importer
+"""
+from unittest import TestCase, skipUnless
+from collections import namedtuple
+
+from openlp.core.common import Registry
+try:
+ from openlp.plugins.songs.lib.importers.mediashout import MediaShoutImport
+ CAN_RUN_TESTS = True
+except ImportError:
+ CAN_RUN_TESTS = False
+
+from tests.functional import MagicMock, patch, call
+
+
+@skipUnless(CAN_RUN_TESTS, 'Not Windows, skipping test')
+class TestMediaShoutImport(TestCase):
+ """
+ Test the MediaShout importer
+ """
+ def setUp(self):
+ """
+ Set the tests up
+ """
+ Registry().create()
+
+ def test_constructor(self):
+ """
+ Test the MediaShoutImport constructor
+ """
+ # GIVEN: A MediaShoutImport class
+ # WHEN: It is created
+ importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
+
+ # THEN: It should not be None
+ self.assertIsNotNone(importer)
+
+ @patch('openlp.plugins.songs.lib.importers.mediashout.pyodbc')
+ def test_do_import_fails_to_connect(self, mocked_pyodbc):
+ """
+ Test that do_import exits early when unable to connect to the database
+ """
+ # GIVEN: A MediaShoutImport instance
+ importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
+ mocked_pyodbc.connect.side_effect = Exception('Unable to connect')
+
+ # WHEN: do_import is called
+ with patch.object(importer, 'log_error') as mocked_log_error:
+ importer.do_import()
+
+ # THEN: The songs should have been imported
+ mocked_log_error.assert_called_once_with('mediashout.db', 'Unable to open the MediaShout database.')
+
+ @patch('openlp.plugins.songs.lib.importers.mediashout.pyodbc')
+ def test_do_import(self, mocked_pyodbc):
+ """
+ Test the MediaShoutImport do_import method
+ """
+ SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes')
+ VerseRecord = namedtuple('VerseRecord', 'Type, Number, Text')
+ PlayOrderRecord = namedtuple('PlayOrderRecord', 'Type, Number, POrder')
+ ThemeRecord = namedtuple('ThemeRecord', 'Name')
+ GroupRecord = namedtuple('GroupRecord', 'Name')
+ song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '')
+ verse = VerseRecord('Verse', 1, 'Amazing grace, how sweet the sound\nThat saved a wretch like me')
+ play_order = PlayOrderRecord('Verse', 1, 1)
+ theme = ThemeRecord('Grace')
+ group = GroupRecord('Hymns')
+
+ # GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
+ importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
+ mocked_cursor = MagicMock()
+ mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]]
+ mocked_cursor.tables.fetchone.return_value = True
+ mocked_connection = MagicMock()
+ mocked_connection.cursor.return_value = mocked_cursor
+ mocked_pyodbc.connect.return_value = mocked_connection
+
+ # WHEN: do_import is called
+ with patch.object(importer, 'import_wizard') as mocked_import_wizard, \
+ patch.object(importer, 'process_song') as mocked_process_song:
+ importer.do_import()
+
+ # THEN: The songs should have been imported
+ expected_execute_calls = [
+ call('SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title'),
+ call('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', 1.0),
+ call('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder', 1.0),
+ call('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
+ 'WHERE SongThemes.Record = ?', 1.0),
+ call('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId '
+ 'WHERE SongGroups.Record = ?', 1.0)
+ ]
+ self.assertEqual(expected_execute_calls, mocked_cursor.execute.call_args_list)
+ mocked_process_song.assert_called_once_with(song, [verse], [play_order], [theme, group])
+
+ @patch('openlp.plugins.songs.lib.importers.mediashout.pyodbc')
+ def test_do_import_breaks_on_stop(self, mocked_pyodbc):
+ """
+ Test the MediaShoutImport do_import stops when the user presses the cancel button
+ """
+ SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes')
+ song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '')
+
+ # GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
+ importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
+ mocked_cursor = MagicMock()
+ mocked_cursor.fetchall.return_value = [song]
+ mocked_connection = MagicMock()
+ mocked_connection.cursor.return_value = mocked_cursor
+ mocked_pyodbc.connect.return_value = mocked_connection
+
+ # WHEN: do_import is called, but cancelled
+ with patch.object(importer, 'import_wizard') as mocked_import_wizard, \
+ patch.object(importer, 'process_song') as mocked_process_song:
+ importer.stop_import_flag = True
+ importer.do_import()
+
+ # THEN: The songs should have been imported
+ mocked_cursor.execute.assert_called_once_with(
+ 'SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title')
+ mocked_process_song.assert_not_called()
+
+ def test_process_song(self):
+ """
+ Test the process_song method of the MediaShoutImport
+ """
+ # GIVEN: An importer and a song
+ SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes')
+ VerseRecord = namedtuple('VerseRecord', 'Type, Number, Text')
+ PlayOrderRecord = namedtuple('PlayOrderRecord', 'Type, Number, POrder')
+ ThemeRecord = namedtuple('ThemeRecord', 'Name')
+ GroupRecord = namedtuple('GroupRecord', 'Name')
+ song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 'Hymns', '654321',
+ 'Great old hymn')
+ verse = VerseRecord(0, 1, 'Amazing grace, how sweet the sound\nThat saved a wretch like me')
+ play_order = PlayOrderRecord(0, 1, 1)
+ theme = ThemeRecord('Grace')
+ group = GroupRecord('Hymns')
+ importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
+
+ # WHEN: A song is processed
+ with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
+ patch.object(importer, 'parse_author') as mocked_parse_author, \
+ patch.object(importer, 'add_copyright') as mocked_add_copyright, \
+ patch.object(importer, 'add_verse') as mocked_add_verse, \
+ patch.object(importer, 'finish') as mocked_finish:
+ importer.topics = []
+ importer.verse_order_list = []
+ importer.process_song(song, [verse], [play_order], [theme, group])
+
+ # THEN: It should be added to the database
+ mocked_set_defaults.assert_called_once_with()
+ self.assertEqual('Amazing Grace', importer.title)
+ mocked_parse_author.assert_called_once_with('William Wilberforce')
+ mocked_add_copyright.assert_called_once_with('Public Domain')
+ self.assertEqual('Great old hymn', importer.comments)
+ self.assertEqual(['Grace', 'Hymns'], importer.topics)
+ self.assertEqual('Hymns', importer.song_book_name)
+ self.assertEqual('', importer.song_number)
+ mocked_add_verse.assert_called_once_with(
+ 'Amazing grace, how sweet the sound\nThat saved a wretch like me', 'V1')
+ self.assertEqual(['V1'], importer.verse_order_list)
+ mocked_finish.assert_called_once_with()
+
+ def test_process_song_with_song_number(self):
+ """
+ Test the process_song method with a song that has a song number
+ """
+ # GIVEN: An importer and a song
+ SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes')
+ VerseRecord = namedtuple('VerseRecord', 'Type, Number, Text')
+ PlayOrderRecord = namedtuple('PlayOrderRecord', 'Type, Number, POrder')
+ ThemeRecord = namedtuple('ThemeRecord', 'Name')
+ GroupRecord = namedtuple('GroupRecord', 'Name')
+ song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 'Hymns-2', '654321',
+ 'Great old hymn')
+ verse = VerseRecord(0, 1, 'Amazing grace, how sweet the sound\nThat saved a wretch like me')
+ play_order = PlayOrderRecord(0, 1, 1)
+ theme = ThemeRecord('Grace')
+ group = GroupRecord('Hymns')
+ importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
+
+ # WHEN: A song is processed
+ with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
+ patch.object(importer, 'parse_author') as mocked_parse_author, \
+ patch.object(importer, 'add_copyright') as mocked_add_copyright, \
+ patch.object(importer, 'add_verse') as mocked_add_verse, \
+ patch.object(importer, 'finish') as mocked_finish:
+ importer.topics = []
+ importer.verse_order_list = []
+ importer.process_song(song, [verse], [play_order], [theme, group])
+
+ # THEN: It should be added to the database
+ mocked_set_defaults.assert_called_once_with()
+ self.assertEqual('Amazing Grace', importer.title)
+ mocked_parse_author.assert_called_once_with('William Wilberforce')
+ mocked_add_copyright.assert_called_once_with('Public Domain')
+ self.assertEqual('Great old hymn', importer.comments)
+ self.assertEqual(['Grace', 'Hymns'], importer.topics)
+ self.assertEqual('Hymns', importer.song_book_name)
+ self.assertEqual('2', importer.song_number)
+ mocked_add_verse.assert_called_once_with(
+ 'Amazing grace, how sweet the sound\nThat saved a wretch like me', 'V1')
+ self.assertEqual(['V1'], importer.verse_order_list)
+ mocked_finish.assert_called_once_with()
diff --git a/tests/interfaces/openlp_core_lib/test_searchedit.py b/tests/interfaces/openlp_core_lib/test_searchedit.py
index a4c4472f5..464c6217d 100644
--- a/tests/interfaces/openlp_core_lib/test_searchedit.py
+++ b/tests/interfaces/openlp_core_lib/test_searchedit.py
@@ -23,6 +23,7 @@
Module to test the EditCustomForm.
"""
from unittest import TestCase
+from unittest.mock import MagicMock, call, patch
from PyQt5 import QtCore, QtGui, QtTest, QtWidgets
@@ -58,7 +59,12 @@ class TestSearchEdit(TestCase, TestMixin):
self.main_window = QtWidgets.QMainWindow()
Registry().register('main_window', self.main_window)
- self.search_edit = SearchEdit(self.main_window)
+ settings_patcher = patch(
+ 'openlp.core.lib.searchedit.Settings', return_value=MagicMock(**{'value.return_value': SearchTypes.First}))
+ self.addCleanup(settings_patcher.stop)
+ self.mocked_settings = settings_patcher.start()
+
+ self.search_edit = SearchEdit(self.main_window, 'settings_section')
# To complete set up we have to set the search types.
self.search_edit.set_search_types(SEARCH_TYPES)
@@ -78,8 +84,11 @@ class TestSearchEdit(TestCase, TestMixin):
# WHEN:
- # THEN: The first search type should be the first one in the list.
- assert self.search_edit.current_search_type() == SearchTypes.First, "The first search type should be selected."
+ # THEN: The first search type should be the first one in the list. The selected type should be saved in the
+ # settings
+ self.assertEqual(self.search_edit.current_search_type(), SearchTypes.First,
+ "The first search type should be selected.")
+ self.mocked_settings().setValue.assert_called_once_with('settings_section/last search type', 0)
def test_set_current_search_type(self):
"""
@@ -90,11 +99,13 @@ class TestSearchEdit(TestCase, TestMixin):
result = self.search_edit.set_current_search_type(SearchTypes.Second)
# THEN:
- assert result, "The call should return success (True)."
- assert self.search_edit.current_search_type() == SearchTypes.Second,\
- "The search type should be SearchTypes.Second"
- assert self.search_edit.placeholderText() == SECOND_PLACEHOLDER_TEXT,\
- "The correct placeholder text should be 'Second Placeholder Text'."
+ self.assertTrue(result, "The call should return success (True).")
+ self.assertEqual(self.search_edit.current_search_type(), SearchTypes.Second,
+ "The search type should be SearchTypes.Second")
+ self.assertEqual(self.search_edit.placeholderText(), SECOND_PLACEHOLDER_TEXT,
+ "The correct placeholder text should be 'Second Placeholder Text'.")
+ self.mocked_settings().setValue.assert_has_calls(
+ [call('settings_section/last search type', 0), call('settings_section/last search type', 1)])
def test_clear_button_visibility(self):
"""
diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py
index 084bfa476..7068898fe 100644
--- a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py
+++ b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py
@@ -163,3 +163,19 @@ class TestBibleHTTP(TestCase):
# THEN: The list should not be None, and some known bibles should be there
self.assertIsNotNone(bibles)
self.assertIn(('Giovanni Diodati 1649 (Italian)', 'gdb', 'it'), bibles)
+
+ def test_crosswalk_get_verse_text(self):
+ """
+ Test verse text from Crosswalk.com
+ """
+ # GIVEN: A new Crosswalk extraction class
+ handler = CWExtract()
+
+ # WHEN: downloading NIV Genesis from Crosswalk
+ niv_genesis_chapter_one = handler.get_bible_chapter('niv', 'Genesis', 1)
+
+ # THEN: The verse list should contain the verses
+ self.assertTrue(niv_genesis_chapter_one.has_verse_list())
+ self.assertEquals('In the beginning God created the heavens and the earth.',
+ niv_genesis_chapter_one.verse_list[1],
+ 'The first chapter of genesis should have been fetched.')
diff --git a/tests/resources/bibles/dk1933.json b/tests/resources/bibles/dk1933.json
index f364cb47e..2b792eeb3 100644
--- a/tests/resources/bibles/dk1933.json
+++ b/tests/resources/bibles/dk1933.json
@@ -2,15 +2,15 @@
"book": "Genesis",
"chapter": 1,
"verses": [
- [ "1", "I Begyndelsen skabte Gud Himmelen og Jorden."],
- [ "2", "Og Jorden var øde og tom, og der var Mørke over Verdensdybet. Men Guds Ånd svævede over Vandene." ],
- [ "3", "Og Gud sagde: \"Der blive Lys!\" Og der blev Lys." ],
- [ "4", "Og Gud så, at Lyset var godt, og Gud satte Skel mellem Lyset og Mørket," ],
- [ "5", "og Gud kaldte Lyset Dag, og Mørket kaldte han Nat. Og det blev Aften, og det blev Morgen, første Dag." ],
- [ "6", "Derpå sagde Gud: \"Der blive en Hvælving midt i Vandene til at skille Vandene ad!\"" ],
- [ "7", "Og således skete det: Gud gjorde Hvælvingen og skilte Vandet under Hvælvingen fra Vandet over Hvælvingen;" ],
- [ "8", "og Gud kaldte Hvælvingen Himmel. Og det blev Aften, og det blev Morgen, anden Dag." ],
- [ "9", "Derpå sagde Gud: \"Vandet under Himmelen samle sig på eet Sted, så det faste Land kommer til Syne!\" Og således skete det;" ],
- [ "10", "og Gud kaldte det faste Land Jord, og Stedet, hvor Vandet samlede sig, kaldte han Hav. Og Gud så, at det var godt." ]
+ [ 1, "I Begyndelsen skabte Gud Himmelen og Jorden."],
+ [ 2, "Og Jorden var øde og tom, og der var Mørke over Verdensdybet. Men Guds Ånd svævede over Vandene." ],
+ [ 3, "Og Gud sagde: \"Der blive Lys!\" Og der blev Lys." ],
+ [ 4, "Og Gud så, at Lyset var godt, og Gud satte Skel mellem Lyset og Mørket," ],
+ [ 5, "og Gud kaldte Lyset Dag, og Mørket kaldte han Nat. Og det blev Aften, og det blev Morgen, første Dag." ],
+ [ 6, "Derpå sagde Gud: \"Der blive en Hvælving midt i Vandene til at skille Vandene ad!\"" ],
+ [ 7, "Og således skete det: Gud gjorde Hvælvingen og skilte Vandet under Hvælvingen fra Vandet over Hvælvingen;" ],
+ [ 8, "og Gud kaldte Hvælvingen Himmel. Og det blev Aften, og det blev Morgen, anden Dag." ],
+ [ 9, "Derpå sagde Gud: \"Vandet under Himmelen samle sig på eet Sted, så det faste Land kommer til Syne!\" Og således skete det;" ],
+ [ 10, "og Gud kaldte det faste Land Jord, og Stedet, hvor Vandet samlede sig, kaldte han Hav. Og Gud så, at det var godt." ]
]
}
\ No newline at end of file
diff --git a/tests/resources/bibles/kjv.json b/tests/resources/bibles/kjv.json
index a375a1b40..dacabab44 100644
--- a/tests/resources/bibles/kjv.json
+++ b/tests/resources/bibles/kjv.json
@@ -2,15 +2,15 @@
"book": "Genesis",
"chapter": 1,
"verses": [
- [ "1", "In the beginning God created the heaven and the earth."],
- [ "2", "And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters." ],
- [ "3", "And God said, Let there be light: and there was light." ],
- [ "4", "And God saw the light, that it was good: and God divided the light from the darkness." ],
- [ "5", "And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day." ],
- [ "6", "And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters." ],
- [ "7", "And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so." ],
- [ "8", "And God called the firmament Heaven. And the evening and the morning were the second day." ],
- [ "9", "And God said, Let the waters under the heaven be gathered together unto one place, and let the dry land appear: and it was so." ],
- [ "10", "And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good." ]
+ [ 1, "In the beginning God created the heaven and the earth."],
+ [ 2, "And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters." ],
+ [ 3, "And God said, Let there be light: and there was light." ],
+ [ 4, "And God saw the light, that it was good: and God divided the light from the darkness." ],
+ [ 5, "And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day." ],
+ [ 6, "And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters." ],
+ [ 7, "And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so." ],
+ [ 8, "And God called the firmament Heaven. And the evening and the morning were the second day." ],
+ [ 9, "And God said, Let the waters under the heaven be gathered together unto one place, and let the dry land appear: and it was so." ],
+ [ 10, "And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good." ]
]
}
diff --git a/tests/resources/bibles/rst.json b/tests/resources/bibles/rst.json
index d8aca09ac..1fb612b11 100644
--- a/tests/resources/bibles/rst.json
+++ b/tests/resources/bibles/rst.json
@@ -2,15 +2,15 @@
"book": "Exodus",
"chapter": 1,
"verses": [
- [ "1", "Вот имена сынов Израилевых, которые вошли в Египет с Иаковом, вошли каждый с домом своим:" ],
- [ "2", "Рувим, Симеон, Левий и Иуда," ],
- [ "3", "Иссахар, Завулон и Вениамин," ],
- [ "4", "Дан и Неффалим, Гад и Асир." ],
- [ "5", "Всех же душ, происшедших от чресл Иакова, было семьдесят, а Иосиф был [уже] в Египте." ],
- [ "6", "И умер Иосиф и все братья его и весь род их;" ],
- [ "7", "а сыны Израилевы расплодились и размножились, и возросли и усилились чрезвычайно, и наполнилась ими земля та." ],
- [ "8", "И восстал в Египте новый царь, который не знал Иосифа," ],
- [ "9", "и сказал народу своему: вот, народ сынов Израилевых многочислен и сильнее нас;" ],
- [ "10", "перехитрим же его, чтобы он не размножался; иначе, когда случится война, соединится и он с нашими неприятелями, и вооружится против нас, и выйдет из земли [нашей]." ]
+ [ 1, "Вот имена сынов Израилевых, которые вошли в Египет с Иаковом, вошли каждый с домом своим:" ],
+ [ 2, "Рувим, Симеон, Левий и Иуда," ],
+ [ 3, "Иссахар, Завулон и Вениамин," ],
+ [ 4, "Дан и Неффалим, Гад и Асир." ],
+ [ 5, "Всех же душ, происшедших от чресл Иакова, было семьдесят, а Иосиф был [уже] в Египте." ],
+ [ 6, "И умер Иосиф и все братья его и весь род их;" ],
+ [ 7, "а сыны Израилевы расплодились и размножились, и возросли и усилились чрезвычайно, и наполнилась ими земля та." ],
+ [ 8, "И восстал в Египте новый царь, который не знал Иосифа," ],
+ [ 9, "и сказал народу своему: вот, народ сынов Израилевых многочислен и сильнее нас;" ],
+ [ 10, "перехитрим же его, чтобы он не размножался; иначе, когда случится война, соединится и он с нашими неприятелями, и вооружится против нас, и выйдет из земли [нашей]." ]
]
}
diff --git a/tests/resources/bibles/web.json b/tests/resources/bibles/web.json
index 0fbc95669..069d5e276 100644
--- a/tests/resources/bibles/web.json
+++ b/tests/resources/bibles/web.json
@@ -2,15 +2,15 @@
"book": "Genesis",
"chapter": "1",
"verses": [
- [ "1", "In the beginning God created the heavens and the earth."],
- [ "2", "Now the earth was formless and empty. Darkness was on the surface of the deep. God’s Spirit was hovering over the surface of the waters." ],
- [ "3", "God said, “Let there be light,” and there was light." ],
- [ "4", "God saw the light, and saw that it was good. God divided the light from the darkness." ],
- [ "5", "God called the light “day,” and the darkness he called “night.” There was evening and there was morning, one day." ],
- [ "6", "God said, “Let there be an expanse in the middle of the waters, and let it divide the waters from the waters.”" ],
- [ "7", "God made the expanse, and divided the waters which were under the expanse from the waters which were above the expanse; and it was so." ],
- [ "8", "God called the expanse “sky.” There was evening and there was morning, a second day." ],
- [ "9", "God said, “Let the waters under the sky be gathered together to one place, and let the dry land appear;” and it was so." ],
- [ "10", "God called the dry land “earth,” and the gathering together of the waters he called “seas.” God saw that it was good." ]
+ [ 1, "In the beginning God created the heavens and the earth."],
+ [ 2, "Now the earth was formless and empty. Darkness was on the surface of the deep. God’s Spirit was hovering over the surface of the waters." ],
+ [ 3, "God said, “Let there be light,” and there was light." ],
+ [ 4, "God saw the light, and saw that it was good. God divided the light from the darkness." ],
+ [ 5, "God called the light “day,” and the darkness he called “night.” There was evening and there was morning, one day." ],
+ [ 6, "God said, “Let there be an expanse in the middle of the waters, and let it divide the waters from the waters.”" ],
+ [ 7, "God made the expanse, and divided the waters which were under the expanse from the waters which were above the expanse; and it was so." ],
+ [ 8, "God called the expanse “sky.” There was evening and there was morning, a second day." ],
+ [ 9, "God said, “Let the waters under the sky be gathered together to one place, and let the dry land appear;” and it was so." ],
+ [ 10, "God called the dry land “earth,” and the gathering together of the waters he called “seas.” God saw that it was good." ]
]
}