diff --git a/openlp/core/ui/lib/wizard.py b/openlp/core/ui/lib/wizard.py index 5f2321f48..06225a376 100644 --- a/openlp/core/ui/lib/wizard.py +++ b/openlp/core/ui/lib/wizard.py @@ -46,6 +46,7 @@ class WizardStrings(object): OSIS = 'OSIS' ZEF = 'Zefania' SWORD = 'Sword' + WordProject = 'WordProject' # These strings should need a good reason to be retranslated elsewhere. FinishedImport = translate('OpenLP.Ui', 'Finished import.') FormatLabel = translate('OpenLP.Ui', 'Format:') @@ -95,6 +96,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties): super(OpenLPWizard, self).__init__(parent) self.plugin = plugin self.with_progress_page = add_progress_page + self.setFixedWidth(640) self.setObjectName(name) self.open_icon = build_icon(':/general/general_open.png') self.delete_icon = build_icon(':/general/general_delete.png') diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index e1e062155..ae39d821b 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -125,6 +125,7 @@ class BibleImportForm(OpenLPWizard): self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked) self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked) self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked) + self.wordproject_browse_button.clicked.connect(self.on_wordproject_browse_button_clicked) self.web_update_button.clicked.connect(self.on_web_update_button_clicked) self.sword_browse_button.clicked.connect(self.on_sword_browse_button_clicked) self.sword_zipbrowse_button.clicked.connect(self.on_sword_zipbrowse_button_clicked) @@ -143,7 +144,7 @@ class BibleImportForm(OpenLPWizard): self.format_label = QtWidgets.QLabel(self.select_page) self.format_label.setObjectName('FormatLabel') self.format_combo_box = QtWidgets.QComboBox(self.select_page) - self.format_combo_box.addItems(['', '', '', '', '', '']) + self.format_combo_box.addItems(['', '', '', '', '', '', '']) self.format_combo_box.setObjectName('FormatComboBox') self.format_layout.addRow(self.format_label, self.format_combo_box) self.spacer = QtWidgets.QSpacerItem(10, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) @@ -161,6 +162,7 @@ class BibleImportForm(OpenLPWizard): self.osis_file_layout = QtWidgets.QHBoxLayout() self.osis_file_layout.setObjectName('OsisFileLayout') self.osis_file_edit = QtWidgets.QLineEdit(self.osis_widget) + self.osis_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) self.osis_file_edit.setObjectName('OsisFileEdit') self.osis_file_layout.addWidget(self.osis_file_edit) self.osis_browse_button = QtWidgets.QToolButton(self.osis_widget) @@ -180,6 +182,7 @@ class BibleImportForm(OpenLPWizard): self.csv_books_layout = QtWidgets.QHBoxLayout() self.csv_books_layout.setObjectName('CsvBooksLayout') self.csv_books_edit = QtWidgets.QLineEdit(self.csv_widget) + self.csv_books_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) self.csv_books_edit.setObjectName('CsvBooksEdit') self.csv_books_layout.addWidget(self.csv_books_edit) self.csv_books_button = QtWidgets.QToolButton(self.csv_widget) @@ -192,6 +195,7 @@ class BibleImportForm(OpenLPWizard): self.csv_verses_layout = QtWidgets.QHBoxLayout() self.csv_verses_layout.setObjectName('CsvVersesLayout') self.csv_verses_edit = QtWidgets.QLineEdit(self.csv_widget) + self.csv_verses_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) self.csv_verses_edit.setObjectName('CsvVersesEdit') self.csv_verses_layout.addWidget(self.csv_verses_edit) self.csv_verses_button = QtWidgets.QToolButton(self.csv_widget) @@ -211,6 +215,7 @@ class BibleImportForm(OpenLPWizard): self.open_song_file_layout = QtWidgets.QHBoxLayout() self.open_song_file_layout.setObjectName('OpenSongFileLayout') self.open_song_file_edit = QtWidgets.QLineEdit(self.open_song_widget) + self.open_song_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) self.open_song_file_edit.setObjectName('OpenSongFileEdit') self.open_song_file_layout.addWidget(self.open_song_file_edit) self.open_song_browse_button = QtWidgets.QToolButton(self.open_song_widget) @@ -300,61 +305,84 @@ class BibleImportForm(OpenLPWizard): self.sword_widget = QtWidgets.QWidget(self.select_page) self.sword_widget.setObjectName('SwordWidget') self.sword_layout = QtWidgets.QVBoxLayout(self.sword_widget) + self.sword_layout.setContentsMargins(0, 0, 0, 0) self.sword_layout.setObjectName('SwordLayout') self.sword_tab_widget = QtWidgets.QTabWidget(self.sword_widget) self.sword_tab_widget.setObjectName('SwordTabWidget') self.sword_folder_tab = QtWidgets.QWidget(self.sword_tab_widget) self.sword_folder_tab.setObjectName('SwordFolderTab') - self.sword_folder_tab_layout = QtWidgets.QGridLayout(self.sword_folder_tab) + self.sword_folder_tab_layout = QtWidgets.QFormLayout(self.sword_folder_tab) self.sword_folder_tab_layout.setObjectName('SwordTabFolderLayout') self.sword_folder_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_folder_label.setObjectName('SwordSourceLabel') - self.sword_folder_tab_layout.addWidget(self.sword_folder_label, 0, 0) self.sword_folder_label.setObjectName('SwordFolderLabel') self.sword_folder_edit = QtWidgets.QLineEdit(self.sword_folder_tab) + self.sword_folder_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) self.sword_folder_edit.setObjectName('SwordFolderEdit') self.sword_browse_button = QtWidgets.QToolButton(self.sword_folder_tab) self.sword_browse_button.setIcon(self.open_icon) self.sword_browse_button.setObjectName('SwordBrowseButton') - self.sword_folder_tab_layout.addWidget(self.sword_folder_edit, 0, 1) - self.sword_folder_tab_layout.addWidget(self.sword_browse_button, 0, 2) + self.sword_folder_layout = QtWidgets.QHBoxLayout() + self.sword_folder_layout.addWidget(self.sword_folder_edit) + self.sword_folder_layout.addWidget(self.sword_browse_button) + self.sword_folder_tab_layout.addRow(self.sword_folder_label, self.sword_folder_layout) self.sword_bible_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_bible_label.setObjectName('SwordBibleLabel') - self.sword_folder_tab_layout.addWidget(self.sword_bible_label, 1, 0) self.sword_bible_combo_box = QtWidgets.QComboBox(self.sword_folder_tab) self.sword_bible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.sword_bible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically) self.sword_bible_combo_box.setObjectName('SwordBibleComboBox') - self.sword_folder_tab_layout.addWidget(self.sword_bible_combo_box, 1, 1) + self.sword_folder_tab_layout.addRow(self.sword_bible_label, self.sword_bible_combo_box) self.sword_tab_widget.addTab(self.sword_folder_tab, '') self.sword_zip_tab = QtWidgets.QWidget(self.sword_tab_widget) self.sword_zip_tab.setObjectName('SwordZipTab') - self.sword_zip_layout = QtWidgets.QGridLayout(self.sword_zip_tab) + self.sword_zip_layout = QtWidgets.QFormLayout(self.sword_zip_tab) self.sword_zip_layout.setObjectName('SwordZipLayout') self.sword_zipfile_label = QtWidgets.QLabel(self.sword_zip_tab) self.sword_zipfile_label.setObjectName('SwordZipFileLabel') self.sword_zipfile_edit = QtWidgets.QLineEdit(self.sword_zip_tab) + self.sword_zipfile_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) self.sword_zipfile_edit.setObjectName('SwordZipFileEdit') self.sword_zipbrowse_button = QtWidgets.QToolButton(self.sword_zip_tab) self.sword_zipbrowse_button.setIcon(self.open_icon) self.sword_zipbrowse_button.setObjectName('SwordZipBrowseButton') + self.sword_zipfile_layout = QtWidgets.QHBoxLayout() + self.sword_zipfile_layout.addWidget(self.sword_zipfile_edit) + self.sword_zipfile_layout.addWidget(self.sword_zipbrowse_button) + self.sword_zip_layout.addRow(self.sword_zipfile_label, self.sword_zipfile_layout) self.sword_zipbible_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_zipbible_label.setObjectName('SwordZipBibleLabel') self.sword_zipbible_combo_box = QtWidgets.QComboBox(self.sword_zip_tab) self.sword_zipbible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.sword_zipbible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically) self.sword_zipbible_combo_box.setObjectName('SwordZipBibleComboBox') - self.sword_zip_layout.addWidget(self.sword_zipfile_label, 0, 0) - self.sword_zip_layout.addWidget(self.sword_zipfile_edit, 0, 1) - self.sword_zip_layout.addWidget(self.sword_zipbrowse_button, 0, 2) - self.sword_zip_layout.addWidget(self.sword_zipbible_label, 1, 0) - self.sword_zip_layout.addWidget(self.sword_zipbible_combo_box, 1, 1) + self.sword_zip_layout.addRow(self.sword_zipbible_label, self.sword_zipbible_combo_box) self.sword_tab_widget.addTab(self.sword_zip_tab, '') self.sword_layout.addWidget(self.sword_tab_widget) self.sword_disabled_label = QtWidgets.QLabel(self.sword_widget) self.sword_disabled_label.setObjectName('SwordDisabledLabel') self.sword_layout.addWidget(self.sword_disabled_label) self.select_stack.addWidget(self.sword_widget) + self.wordproject_widget = QtWidgets.QWidget(self.select_page) + self.wordproject_widget.setObjectName('WordProjectWidget') + self.wordproject_layout = QtWidgets.QFormLayout(self.wordproject_widget) + self.wordproject_layout.setContentsMargins(0, 0, 0, 0) + self.wordproject_layout.setObjectName('WordProjectLayout') + self.wordproject_file_label = QtWidgets.QLabel(self.wordproject_widget) + self.wordproject_file_label.setObjectName('WordProjectFileLabel') + self.wordproject_file_layout = QtWidgets.QHBoxLayout() + self.wordproject_file_layout.setObjectName('WordProjectFileLayout') + self.wordproject_file_edit = QtWidgets.QLineEdit(self.wordproject_widget) + self.wordproject_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + self.wordproject_file_edit.setObjectName('WordProjectFileEdit') + self.wordproject_file_layout.addWidget(self.wordproject_file_edit) + self.wordproject_browse_button = QtWidgets.QToolButton(self.wordproject_widget) + self.wordproject_browse_button.setIcon(self.open_icon) + self.wordproject_browse_button.setObjectName('WordProjectBrowseButton') + self.wordproject_file_layout.addWidget(self.wordproject_browse_button) + self.wordproject_layout.addRow(self.wordproject_file_label, self.wordproject_file_layout) + self.wordproject_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.select_stack.addWidget(self.wordproject_widget) self.select_page_layout.addLayout(self.select_stack) self.addPage(self.select_page) # License Page @@ -400,6 +428,7 @@ class BibleImportForm(OpenLPWizard): self.format_combo_box.setItemText(BibleFormat.OSIS, WizardStrings.OSIS) self.format_combo_box.setItemText(BibleFormat.CSV, WizardStrings.CSV) self.format_combo_box.setItemText(BibleFormat.OpenSong, WizardStrings.OS) + self.format_combo_box.setItemText(BibleFormat.WordProject, WizardStrings.WordProject) self.format_combo_box.setItemText(BibleFormat.WebDownload, translate('BiblesPlugin.ImportWizardForm', 'Web Download')) self.format_combo_box.setItemText(BibleFormat.Zefania, WizardStrings.ZEF) @@ -410,6 +439,7 @@ class BibleImportForm(OpenLPWizard): self.open_song_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:')) self.web_source_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Location:')) self.zefania_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:')) + self.wordproject_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:')) self.web_update_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Click to download bible list')) self.web_update_button.setText(translate('BiblesPlugin.ImportWizardForm', 'Download bible list')) self.web_source_combo_box.setItemText(WebDownload.Crosswalk, translate('BiblesPlugin.ImportWizardForm', @@ -468,6 +498,7 @@ class BibleImportForm(OpenLPWizard): """ Validate the current page before moving on to the next page. """ + log.debug(self.size()) if self.currentPage() == self.welcome_page: return True elif self.currentPage() == self.select_page: @@ -504,6 +535,12 @@ class BibleImportForm(OpenLPWizard): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.ZEF) self.zefania_file_edit.setFocus() return False + elif self.field('source_format') == BibleFormat.WordProject: + if not self.field('wordproject_file'): + critical_error_message_box(UiStrings().NFSs, + WizardStrings.YouSpecifyFile % WizardStrings.WordProject) + self.wordproject_file_edit.setFocus() + return False elif self.field('source_format') == BibleFormat.WebDownload: # If count is 0 the bible list has not yet been downloaded if self.web_translation_combo_box.count() == 0: @@ -627,6 +664,14 @@ class BibleImportForm(OpenLPWizard): self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit, 'last directory import') + def on_wordproject_browse_button_clicked(self): + """ + Show the file open dialog for the WordProject file. + """ + # TODO: Verify format() with variable template + self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.WordProject, self.wordproject_file_edit, + 'last directory import') + def on_web_update_button_clicked(self): """ Download list of bibles from Crosswalk, BibleServer and BibleGateway. @@ -707,6 +752,7 @@ class BibleImportForm(OpenLPWizard): self.select_page.registerField('csv_versefile', self.csv_verses_edit) self.select_page.registerField('opensong_file', self.open_song_file_edit) self.select_page.registerField('zefania_file', self.zefania_file_edit) + self.select_page.registerField('wordproject_file', self.wordproject_file_edit) self.select_page.registerField('web_location', self.web_source_combo_box) self.select_page.registerField('web_biblename', self.web_translation_combo_box) self.select_page.registerField('sword_folder_path', self.sword_folder_edit) @@ -799,6 +845,10 @@ class BibleImportForm(OpenLPWizard): # Import a Zefania bible. importer = self.manager.import_bible(BibleFormat.Zefania, name=license_version, filename=self.field('zefania_file')) + elif bible_type == BibleFormat.WordProject: + # Import a WordProject bible. + importer = self.manager.import_bible(BibleFormat.WordProject, name=license_version, + filename=self.field('wordproject_file')) elif bible_type == BibleFormat.SWORD: # Import a SWORD bible. if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab): diff --git a/openlp/plugins/bibles/lib/importers/wordproject.py b/openlp/plugins/bibles/lib/importers/wordproject.py new file mode 100644 index 000000000..f48749fc6 --- /dev/null +++ b/openlp/plugins/bibles/lib/importers/wordproject.py @@ -0,0 +1,169 @@ +# -*- 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 os +import re +import logging +from codecs import open as copen +from tempfile import TemporaryDirectory +from zipfile import ZipFile + +from bs4 import BeautifulSoup, Tag, NavigableString + +from openlp.plugins.bibles.lib.bibleimport import BibleImport + +BOOK_NUMBER_PATTERN = re.compile(r'\[(\d+)\]') +REPLACE_SPACES = re.compile(r'\s{2,}') + +log = logging.getLogger(__name__) + + +class WordProjectBible(BibleImport): + """ + `WordProject `_ Bible format importer class. + """ + def _cleanup(self): + """ + Clean up after ourselves + """ + self.tmp.cleanup() + + def _unzip_file(self): + """ + Unzip the file to a temporary directory + """ + self.tmp = TemporaryDirectory() + zip_file = ZipFile(os.path.abspath(self.filename)) + zip_file.extractall(self.tmp.name) + self.base_dir = os.path.join(self.tmp.name, os.path.splitext(os.path.basename(self.filename))[0]) + + def process_books(self): + """ + Extract and create the bible books from the parsed html + + :param bible_data: parsed xml + :return: None + """ + with copen(os.path.join(self.base_dir, 'index.htm'), encoding='utf-8', errors='ignore') as index_file: + page = index_file.read() + soup = BeautifulSoup(page, 'lxml') + bible_books = soup.find('div', 'textOptions').find_all('li') + book_count = len(bible_books) + for li_book in bible_books: + log.debug(li_book) + if self.stop_import_flag: + break + # Sometimes the structure is "[1] Genesis", and sometimes it's "[1] Genesis" + if isinstance(li_book.contents[0], NavigableString) and str(li_book.contents[0]).strip(): + book_string = str(li_book.contents[0]) + book_name = str(li_book.a.contents[0]) + elif li_book.a: + book_string, book_name = str(li_book.a.contents[0]).split(' ', 1) + book_link = li_book.a['href'] + book_id = int(BOOK_NUMBER_PATTERN.search(book_string).group(1)) + book_name = book_name.strip() + db_book = self.find_and_create_book(book_name, book_count, self.language_id, book_id) + self.process_chapters(db_book, book_id, book_link) + self.session.commit() + + def process_chapters(self, db_book, book_id, book_link): + """ + 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 + """ + log.debug(book_link) + book_file = os.path.join(self.base_dir, os.path.normpath(book_link)) + with copen(book_file, encoding='utf-8', errors='ignore') as f: + page = f.read() + soup = BeautifulSoup(page, 'lxml') + header_div = soup.find('div', 'textHeader') + chapters_p = header_div.find('p') + if not chapters_p: + chapters_p = soup.p + log.debug(chapters_p) + for item in chapters_p.contents: + if self.stop_import_flag: + break + if isinstance(item, Tag) and item.name in ['a', 'span']: + chapter_number = int(item.string.strip()) + self.set_current_chapter(db_book.name, chapter_number) + self.process_verses(db_book, book_id, chapter_number) + + def process_verses(self, db_book, book_number, chapter_number): + """ + Get the verses for a particular book + """ + chapter_file_name = os.path.join(self.base_dir, '{:02d}'.format(book_number), '{}.htm'.format(chapter_number)) + with copen(chapter_file_name, encoding='utf-8', errors='ignore') as chapter_file: + page = chapter_file.read() + soup = BeautifulSoup(page, 'lxml') + text_body = soup.find('div', 'textBody') + if text_body: + verses_p = text_body.find('p') + else: + verses_p = soup.find_all('p')[2] + verse_number = 0 + verse_text = '' + for item in verses_p.contents: + if self.stop_import_flag: + break + if isinstance(item, Tag) and 'verse' in item.get('class', []): + if verse_number > 0: + self.process_verse(db_book, chapter_number, verse_number, verse_text.strip()) + verse_number = int(item.string.strip()) + verse_text = '' + elif isinstance(item, NavigableString): + verse_text += str(item) + elif isinstance(item, Tag) and item.name in ['span', 'a']: + verse_text += str(item.string) + else: + log.warning('Can\'t store %s', item) + self.process_verse(db_book, chapter_number, verse_number, verse_text.strip()) + + def process_verse(self, db_book, chapter_number, verse_number, verse_text): + """ + 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 + """ + if verse_text: + log.debug('%s %s:%s %s', db_book.name, chapter_number, verse_number, verse_text.strip()) + self.create_verse(db_book.id, chapter_number, verse_number, verse_text.strip()) + + def do_import(self, bible_name=None): + """ + Loads a Bible from file. + """ + self.log_debug('Starting WordProject import from "{name}"'.format(name=self.filename)) + self._unzip_file() + self.language_id = self.get_language_id(None, bible_name=self.filename) + result = False + if self.language_id: + self.process_books() + result = True + self._cleanup() + return result diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index fbb6fb6e7..084e24270 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -31,6 +31,7 @@ from .importers.http import HTTPBible from .importers.opensong import OpenSongBible from .importers.osis import OSISBible from .importers.zefania import ZefaniaBible +from .importers.wordproject import WordProjectBible try: from .importers.sword import SwordBible except: @@ -50,6 +51,7 @@ class BibleFormat(object): WebDownload = 3 Zefania = 4 SWORD = 5 + WordProject = 6 @staticmethod def get_class(bible_format): @@ -70,6 +72,8 @@ class BibleFormat(object): return ZefaniaBible elif bible_format == BibleFormat.SWORD: return SwordBible + elif bible_format == BibleFormat.WordProject: + return WordProjectBible else: return None @@ -84,7 +88,8 @@ class BibleFormat(object): BibleFormat.OpenSong, BibleFormat.WebDownload, BibleFormat.Zefania, - BibleFormat.SWORD + BibleFormat.SWORD, + BibleFormat.WordProject ] diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 9a04e5360..c9b5c6b1e 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -425,6 +425,7 @@ class BibleMediaItem(MediaManagerItem): verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1) if verse_count == 0: self.advancedSearchButton.setEnabled(False) + log.warning('Not enough chapters in %s', book_ref_id) critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.')) else: self.advancedSearchButton.setEnabled(True) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index dc196fb59..00fccb657 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -150,7 +150,8 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): triggers=self.on_replace_click) if 'webkit' not in get_media_players()[0]: self.replace_action.setDisabled(True) - self.replace_action_context.setDisabled(True) + if hasattr(self, 'replace_action_context'): + self.replace_action_context.setDisabled(True) self.reset_action = self.toolbar.add_toolbar_action('reset_action', icon=':/system/system_close.png', visible=False, triggers=self.on_reset_click) self.media_widget = QtWidgets.QWidget(self) diff --git a/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py new file mode 100644 index 000000000..622f83fa8 --- /dev/null +++ b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py @@ -0,0 +1,220 @@ +# -*- 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 WordProject Bible importer. +""" + +import os +import json +from unittest import TestCase + +from openlp.plugins.bibles.lib.importers.wordproject import WordProjectBible +from openlp.plugins.bibles.lib.db import BibleDB + +from tests.functional import MagicMock, patch, call + +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), + '..', '..', '..', 'resources', 'bibles')) +INDEX_PAGE = open(os.path.join(TEST_PATH, 'wordproject_index.htm')).read() +CHAPTER_PAGE = open(os.path.join(TEST_PATH, 'wordproject_chapter.htm')).read() + + +class TestWordProjectImport(TestCase): + """ + Test the functions in the :mod:`wordprojectimport` 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() + + @patch('openlp.plugins.bibles.lib.importers.wordproject.os') + @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') + def test_process_books(self, mocked_open, mocked_os): + """ + Test the process_books() method + """ + # GIVEN: A WordProject importer and a bunch of mocked things + importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer.base_dir = '' + importer.stop_import_flag = False + importer.language_id = 'en' + mocked_open.return_value.__enter__.return_value.read.return_value = INDEX_PAGE + mocked_os.path.join.side_effect = lambda *x: ''.join(x) + + # WHEN: process_books() is called + with patch.object(importer, 'find_and_create_book') as mocked_find_and_create_book, \ + patch.object(importer, 'process_chapters') as mocked_process_chapters, \ + patch.object(importer, 'session') as mocked_session: + importer.process_books() + + # THEN: The right methods should have been called + mocked_os.path.join.assert_called_once_with('', 'index.htm') + mocked_open.assert_called_once_with('index.htm', encoding='utf-8', errors='ignore') + assert mocked_find_and_create_book.call_count == 66, 'There should be 66 books' + assert mocked_process_chapters.call_count == 66, 'There should be 66 books' + assert mocked_session.commit.call_count == 66, 'There should be 66 books' + + @patch('openlp.plugins.bibles.lib.importers.wordproject.os') + @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') + def test_process_chapters(self, mocked_open, mocked_os): + """ + Test the process_chapters() method + """ + # GIVEN: A WordProject importer and a bunch of mocked things + importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer.base_dir = '' + importer.stop_import_flag = False + importer.language_id = 'en' + mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE + mocked_os.path.join.side_effect = lambda *x: ''.join(x) + mocked_os.path.normpath.side_effect = lambda x: x + mocked_db_book = MagicMock() + mocked_db_book.name = 'Genesis' + book_id = 1 + book_link = '01/1.htm' + + # WHEN: process_chapters() is called + with patch.object(importer, 'set_current_chapter') as mocked_set_current_chapter, \ + patch.object(importer, 'process_verses') as mocked_process_verses: + importer.process_chapters(mocked_db_book, book_id, book_link) + + # THEN: The right methods should have been called + expected_set_current_chapter_calls = [call('Genesis', ch) for ch in range(1, 51)] + expected_process_verses_calls = [call(mocked_db_book, 1, ch) for ch in range(1, 51)] + mocked_os.path.join.assert_called_once_with('', '01/1.htm') + mocked_open.assert_called_once_with('01/1.htm', encoding='utf-8', errors='ignore') + assert mocked_set_current_chapter.call_args_list == expected_set_current_chapter_calls + assert mocked_process_verses.call_args_list == expected_process_verses_calls + + @patch('openlp.plugins.bibles.lib.importers.wordproject.os') + @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') + def test_process_verses(self, mocked_open, mocked_os): + """ + Test the process_verses() method + """ + # GIVEN: A WordProject importer and a bunch of mocked things + importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer.base_dir = '' + importer.stop_import_flag = False + importer.language_id = 'en' + mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE + mocked_os.path.join.side_effect = lambda *x: '/'.join(x) + mocked_db_book = MagicMock() + mocked_db_book.name = 'Genesis' + book_number = 1 + chapter_number = 1 + + # WHEN: process_verses() is called + with patch.object(importer, 'process_verse') as mocked_process_verse: + importer.process_verses(mocked_db_book, book_number, chapter_number) + + # THEN: All the right methods should have been called + mocked_os.path.join.assert_called_once_with('', '01', '1.htm') + mocked_open.assert_called_once_with('/01/1.htm', encoding='utf-8', errors='ignore') + assert mocked_process_verse.call_count == 31 + + def test_process_verse(self): + """ + Test the process_verse() method + """ + # GIVEN: An importer and a mocked method + importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + mocked_db_book = MagicMock() + mocked_db_book.id = 1 + chapter_number = 1 + verse_number = 1 + verse_text = ' In the beginning, God created the heavens and the earth ' + + # WHEN: process_verse() is called + with patch.object(importer, 'create_verse') as mocked_create_verse: + importer.process_verse(mocked_db_book, chapter_number, verse_number, verse_text) + + # THEN: The create_verse() method should have been called + mocked_create_verse.assert_called_once_with(1, 1, 1, 'In the beginning, God created the heavens and the earth') + + def test_process_verse_no_text(self): + """ + Test the process_verse() method when there's no text + """ + # GIVEN: An importer and a mocked method + importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + mocked_db_book = MagicMock() + mocked_db_book.id = 1 + chapter_number = 1 + verse_number = 1 + verse_text = '' + + # WHEN: process_verse() is called + with patch.object(importer, 'create_verse') as mocked_create_verse: + importer.process_verse(mocked_db_book, chapter_number, verse_number, verse_text) + + # THEN: The create_verse() method should NOT have been called + assert mocked_create_verse.call_count == 0 + + def test_do_import(self): + """ + Test the do_import() method + """ + # GIVEN: An importer and mocked methods + importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + + # WHEN: do_import() is called + with patch.object(importer, '_unzip_file') as mocked_unzip_file, \ + patch.object(importer, 'get_language_id') as mocked_get_language_id, \ + patch.object(importer, 'process_books') as mocked_process_books, \ + patch.object(importer, '_cleanup') as mocked_cleanup: + mocked_get_language_id.return_value = 1 + result = importer.do_import() + + # THEN: The correct methods should have been called + mocked_unzip_file.assert_called_once_with() + mocked_get_language_id.assert_called_once_with(None, bible_name='kj.zip') + mocked_process_books.assert_called_once_with() + mocked_cleanup.assert_called_once_with() + assert result is True + + def test_do_import_no_language(self): + """ + Test the do_import() method when the language is not available + """ + # GIVEN: An importer and mocked methods + importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + + # WHEN: do_import() is called + with patch.object(importer, '_unzip_file') as mocked_unzip_file, \ + patch.object(importer, 'get_language_id') as mocked_get_language_id, \ + patch.object(importer, 'process_books') as mocked_process_books, \ + patch.object(importer, '_cleanup') as mocked_cleanup: + mocked_get_language_id.return_value = None + result = importer.do_import() + + # THEN: The correct methods should have been called + mocked_unzip_file.assert_called_once_with() + mocked_get_language_id.assert_called_once_with(None, bible_name='kj.zip') + assert mocked_process_books.call_count == 0 + mocked_cleanup.assert_called_once_with() + assert result is False diff --git a/tests/resources/bibles/wordproject_chapter.htm b/tests/resources/bibles/wordproject_chapter.htm new file mode 100644 index 000000000..fb9b8a272 --- /dev/null +++ b/tests/resources/bibles/wordproject_chapter.htm @@ -0,0 +1,248 @@ + + + + + Creation of the world, Genesis Chapter 1 + + + + + + + + + + + + + + + + + + + + + +
+
+
+

WordProject

+
+
+
+ + +
+
+ +
+ +facebook + +twitter + +google + +linkin

+
+ +
+ + +
+
+
+
+
+
+
+
+

Genesis

+ +

Chapter: + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 + +

+
+ + + +
+
+

Chapter 1

+ + +

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. +
11 And God said, Let the earth bring forth grass, the herb yielding seed, and the fruit tree yielding fruit after his kind, whose seed is in itself, upon the earth: and it was so. +
12 And the earth brought forth grass, and herb yielding seed after his kind, and the tree yielding fruit, whose seed was in itself, after his kind: and God saw that it was good. +
13 And the evening and the morning were the third day. +
14 And God said, Let there be lights in the firmament of the heaven to divide the day from the night; and let them be for signs, and for seasons, and for days, and years: +
15 And let them be for lights in the firmament of the heaven to give light upon the earth: and it was so. +
16 And God made two great lights; the greater light to rule the day, and the lesser light to rule the night: he made the stars also. +
17 And God set them in the firmament of the heaven to give light upon the earth, +
18 And to rule over the day and over the night, and to divide the light from the darkness: and God saw that it was good. +
19 And the evening and the morning were the fourth day. +
20 And God said, Let the waters bring forth abundantly the moving creature that hath life, and fowl that may fly above the earth in the open firmament of heaven. +
21 And God created great whales, and every living creature that moveth, which the waters brought forth abundantly, after their kind, and every winged fowl after his kind: and God saw that it was good. +
22 And God blessed them, saying, Be fruitful, and multiply, and fill the waters in the seas, and let fowl multiply in the earth. +
23 And the evening and the morning were the fifth day. +
24 And God said, Let the earth bring forth the living creature after his kind, cattle, and creeping thing, and beast of the earth after his kind: and it was so. +
25 And God made the beast of the earth after his kind, and cattle after their kind, and every thing that creepeth upon the earth after his kind: and God saw that it was good. +
26 And God said, Let us make man in our image, after our likeness: and let them have dominion over the fish of the sea, and over the fowl of the air, and over the cattle, and over all the earth, and over every creeping thing that creepeth upon the earth. +
27 So God created man in his own image, in the image of God created he him; male and female created he them. +
28 And God blessed them, and God said unto them, Be fruitful, and multiply, and replenish the earth, and subdue it: and have dominion over the fish of the sea, and over the fowl of the air, and over every living thing that moveth upon the earth. +
29 And God said, Behold, I have given you every herb bearing seed, which is upon the face of all the earth, and every tree, in the which is the fruit of a tree yielding seed; to you it shall be for meat. +
30 And to every beast of the earth, and to every fowl of the air, and to every thing that creepeth upon the earth, wherein there is life, I have given every green herb for meat: and it was so. +
31 And God saw every thing that he had made, and, behold, it was very good. And the evening and the morning were the sixth day. +

+ +
+
+
+
+
+ +
+
+
+

 printer  +  arrowup  + +  arrowright 

+ +
+
+
+ + + + + + + + + diff --git a/tests/resources/bibles/wordproject_index.htm b/tests/resources/bibles/wordproject_index.htm new file mode 100644 index 000000000..861ca2dda --- /dev/null +++ b/tests/resources/bibles/wordproject_index.htm @@ -0,0 +1,222 @@ + + + + + The Holy Bible in the English language with audio narration - KJV + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

WordProject

+
+
+
+ + +
+
+ +
+ +facebook + +twitter + +google + +linkin

+
+ +
+ + +
+
+
+
+ +
+
+
+
+

Top + +

+ +
+
+
+ + + + + + + +