From e78a0ba0dab521cb83c4ac538f29e4089e618e3b Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 23 Feb 2015 22:35:56 +0000 Subject: [PATCH] Implemented getting languages into combobox. --- .../plugins/bibles/forms/bibleimportform.py | 69 +++++++++++++----- openlp/plugins/bibles/lib/db.py | 5 +- openlp/plugins/bibles/lib/http.py | 37 ++++++++-- .../bibles/resources/bibles_resources.sqlite | Bin 104448 -> 112640 bytes 4 files changed, 85 insertions(+), 26 deletions(-) diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index 019286a68..ee4b01c35 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -34,6 +34,7 @@ from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.core.utils import get_locale_key from openlp.plugins.bibles.lib.manager import BibleFormat from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename +from openlp.plugins.bibles.lib.http import CWExtract, BGExtract, BSExtract log = logging.getLogger(__name__) @@ -98,12 +99,13 @@ class BibleImportForm(OpenLPWizard): """ Set up the signals used in the bible importer. """ - self.web_source_combo_box.currentIndexChanged.connect(self.on_web_source_combo_box_index_changed) + self.web_language_combo_box.currentIndexChanged.connect(self.on_web_language_combo_box_index_changed) self.osis_browse_button.clicked.connect(self.on_osis_browse_button_clicked) self.csv_books_button.clicked.connect(self.on_csv_books_browse_button_clicked) 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.web_update_button.clicked.connect(self.on_web_update_button_clicked) def add_custom_pages(self): """ @@ -202,20 +204,28 @@ class BibleImportForm(OpenLPWizard): self.web_bible_tab.setObjectName('WebBibleTab') self.web_bible_layout = QtGui.QFormLayout(self.web_bible_tab) self.web_bible_layout.setObjectName('WebBibleLayout') + + self.web_update_label = QtGui.QLabel(self.web_bible_tab) + self.web_update_label.setObjectName('WebUpdateLabel') + self.web_bible_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.web_update_label) + self.web_update_button = QtGui.QPushButton(self.web_bible_tab) + self.web_update_button.setObjectName('WebUpdateLabel') + self.web_bible_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.web_update_button) + self.web_source_label = QtGui.QLabel(self.web_bible_tab) self.web_source_label.setObjectName('WebSourceLabel') - self.web_bible_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.web_source_label) - self.web_source_combo_box = QtGui.QComboBox(self.web_bible_tab) - self.web_source_combo_box.setObjectName('WebSourceComboBox') - self.web_source_combo_box.addItems(['', '', '']) - self.web_bible_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.web_source_combo_box) + self.web_bible_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.web_source_label) + self.web_language_combo_box = QtGui.QComboBox(self.web_bible_tab) + self.web_language_combo_box.setObjectName('WebSourceComboBox') + self.web_language_combo_box.addItems(['', '', '']) + self.web_bible_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.web_language_combo_box) self.web_translation_label = QtGui.QLabel(self.web_bible_tab) self.web_translation_label.setObjectName('web_translation_label') - self.web_bible_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.web_translation_label) + self.web_bible_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.web_translation_label) self.web_translation_combo_box = QtGui.QComboBox(self.web_bible_tab) self.web_translation_combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.web_translation_combo_box.setObjectName('WebTranslationComboBox') - self.web_bible_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.web_translation_combo_box) + self.web_bible_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.web_translation_combo_box) self.web_tab_widget.addTab(self.web_bible_tab, '') self.web_proxy_tab = QtGui.QWidget() self.web_proxy_tab.setObjectName('WebProxyTab') @@ -314,12 +324,9 @@ 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.web_source_combo_box.setItemText(WebDownload.Crosswalk, translate('BiblesPlugin.ImportWizardForm', - 'Crosswalk')) - self.web_source_combo_box.setItemText(WebDownload.BibleGateway, translate('BiblesPlugin.ImportWizardForm', - 'BibleGateway')) - self.web_source_combo_box.setItemText(WebDownload.Bibleserver, translate('BiblesPlugin.ImportWizardForm', - 'Bibleserver')) + self.web_update_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Click to fetch bible list')) + self.web_update_button.setText(translate('BiblesPlugin.ImportWizardForm', 'Fetch list')) + #self.web_language_combo_box.setItemText(0, translate('BiblesPlugin.ImportWizardForm', 'Choose a language')) self.web_translation_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible:')) self.web_tab_widget.setTabText(self.web_tab_widget.indexOf(self.web_bible_tab), translate('BiblesPlugin.ImportWizardForm', 'Download Options')) @@ -427,7 +434,7 @@ class BibleImportForm(OpenLPWizard): if self.currentPage() == self.progress_page: return True - def on_web_source_combo_box_index_changed(self, index): + def on_web_language_combo_box_index_changed(self, index): """ Setup the list of Bibles when you select a different source on the web download page. @@ -475,6 +482,32 @@ class BibleImportForm(OpenLPWizard): self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit, 'last directory import') + def on_web_update_button_clicked(self): + """ + Download list of bibles from Crosswalk, BibleServer and BibleGateway, and fill data in comboboxes + """ + # Download from Crosswalk, BiblesGateway, BibleServer + self.web_bible_list = {} + for (source, extractor) in ((WebDownload.Crosswalk, CWExtract()), (WebDownload.BibleGateway, BGExtract()), + (WebDownload.Bibleserver, BSExtract())): + bibles = extractor.get_bibles_from_http() + for (bible_name, bible_key, language_code) in bibles: + if not language_code in self.web_bible_list: + self.web_bible_list[language_code] = {} + self.web_bible_list[language_code]['translations'] = [] + bible_language = BiblesResourcesDB.get_language(language_code) + if bible_language: + self.web_bible_list[language_code]['name'] = bible_language['name'] + self.web_bible_list[language_code]['native_name'] = bible_language['native_name'] + else: + self.web_bible_list[language_code]['name'] = language_code + self.web_bible_list[language_code]['native_name'] = language_code + self.web_bible_list[language_code]['translations'].append((source, bible_name, bible_key)) + # Update combo box + for key in self.web_bible_list.keys(): + self.web_language_combo_box.addItem(self.web_bible_list[key]['native_name'] + ' (' + + self.web_bible_list[key]['name'] + ')', userData=key) + def register_fields(self): """ Register the bible import wizard fields. @@ -485,7 +518,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('web_location', self.web_source_combo_box) + self.select_page.registerField('web_language', self.web_language_combo_box) self.select_page.registerField('web_biblename', self.web_translation_combo_box) self.select_page.registerField('proxy_server', self.web_server_edit) self.select_page.registerField('proxy_username', self.web_user_edit) @@ -509,7 +542,7 @@ class BibleImportForm(OpenLPWizard): self.setField('csv_versefile', '') self.setField('opensong_file', '') self.setField('zefania_file', '') - self.setField('web_location', WebDownload.Crosswalk) + self.setField('web_language', 0) self.setField('web_biblename', self.web_translation_combo_box.currentIndex()) self.setField('proxy_server', settings.value('proxy address')) self.setField('proxy_username', settings.value('proxy username')) @@ -517,7 +550,7 @@ class BibleImportForm(OpenLPWizard): self.setField('license_version', self.version_name_edit.text()) self.setField('license_copyright', self.copyright_edit.text()) self.setField('license_permissions', self.permissions_edit.text()) - self.on_web_source_combo_box_index_changed(WebDownload.Crosswalk) + #self.on_web_language_combo_box_index_changed(WebDownload.Crosswalk) settings.endGroup() def load_Web_Bibles(self): diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 5c8443f23..858afdd63 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -791,12 +791,13 @@ class BiblesResourcesDB(QtCore.QObject, Manager): if not isinstance(name, str): name = str(name) language = BiblesResourcesDB.run_sql( - 'SELECT id, name, code FROM language WHERE name = ? OR code = ?', (name, name.lower())) + 'SELECT id, name, code, native_name FROM language WHERE name = ? OR code = ?', (name, name.lower())) if language: return { 'id': language[0][0], 'name': str(language[0][1]), - 'code': str(language[0][2]) + 'code': str(language[0][2]), + 'native_name': str(language[0][3]) } else: return None diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index d93f645fa..6b1d96b4e 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -74,6 +74,15 @@ BIBLESERVER_LANGUAGE_CODE = { 'fl_21': 'zh' } +CROSSWALK_LANGUAGES = { + 'Portuguese': 'pt', + 'German': 'de', + 'Italian': 'it', + 'EspaƱol': 'es', + 'French' : 'fr', + 'Dutch': 'nl' +} + log = logging.getLogger(__name__) @@ -304,6 +313,8 @@ class BGExtract(RegistryProperties): def get_bibles_from_http(self): """ Load a list of bibles from BibleGateway website. + + returns a list in the form [(biblename, biblekey, language_code)] """ log.debug('BGExtract.get_bibles_from_http') bible_url = 'https://legacy.biblegateway.com/versions/' @@ -313,6 +324,7 @@ class BGExtract(RegistryProperties): bible_select = soup.find('select', {'class': 'translation-dropdown'}) option_tags = bible_select.find_all('option') current_lang = '' + bibles = [] for ot in option_tags: tag_class = '' try: @@ -321,11 +333,12 @@ class BGExtract(RegistryProperties): tag_class = '' tag_text = ot.get_text() if tag_class == 'lang': - current_lang = tag_text[tag_text.find('(') + 1:tag_text.find(')') + 1].lower() + current_lang = tag_text[tag_text.find('(') + 1:tag_text.find(')')].lower() elif tag_class == 'spacer': continue else: - print('biblename: %s, bible_key: %s, class: %s' % (tag_text, ot['value'], current_lang)) + bibles.append((tag_text, ot['value'], current_lang)) + return bibles class BSExtract(RegistryProperties): @@ -390,6 +403,8 @@ class BSExtract(RegistryProperties): def get_bibles_from_http(self): """ Load a list of bibles from Bibleserver website. + + returns a list in the form [(biblename, biblekey, language_code)] """ log.debug('BSExtract.get_bibles_from_http') bible_url = 'http://www.bibleserver.com/index.php?language=2' @@ -397,6 +412,7 @@ class BSExtract(RegistryProperties): if not soup: return None bible_links = soup.find_all('a', {'class': 'trlCell'}) + bibles = [] for link in bible_links: bible_name = link.get_text() # Skip any audio @@ -412,7 +428,8 @@ class BSExtract(RegistryProperties): language_code = BIBLESERVER_LANGUAGE_CODE[css_class] except KeyError: language_code = '' - print('biblename: %s, bible_key: %s, class: %s' % (bible_name, bible_key, language_code)) + bibles.append((bible_name, bible_key, language_code)) + return bibles class CWExtract(RegistryProperties): @@ -487,6 +504,7 @@ class CWExtract(RegistryProperties): def get_bibles_from_http(self): """ Load a list of bibles from Crosswalk website. + returns a list in the form [(biblename, biblekey, language_code)] """ log.debug('CWExtract.get_bibles_from_http') bible_url = 'http://www.biblestudytools.com/search/bible-search.part/' @@ -495,20 +513,27 @@ class CWExtract(RegistryProperties): return None bible_select = soup.find('select') option_tags = bible_select.find_all('option') + bibles = [] for ot in option_tags: - tag_text = ot.get_text() + tag_text = ot.get_text().strip() tag_value = ot['value'] if not tag_value: continue # The names of non-english bibles has their language in parentheses at the end if tag_text.endswith(')'): - language_code = '' + language = tag_text[tag_text.rfind('(') + 1:-1] + if language in CROSSWALK_LANGUAGES: + language_code = CROSSWALK_LANGUAGES[language] + tag_text = tag_text[:tag_text.rfind('(')] + else: + language_code = '' # ... except for the latin vulgate elif 'latin' in tag_text.lower(): language_code = 'la' else: language_code = 'en' - print('biblename: %s, bible_key: %s, class: %s' % (tag_text, tag_value, language_code)) + bibles.append((tag_text, tag_value, language_code)) + return bibles class HTTPBible(BibleDB, RegistryProperties): diff --git a/openlp/plugins/bibles/resources/bibles_resources.sqlite b/openlp/plugins/bibles/resources/bibles_resources.sqlite index 8f1777124032ee11b547fd6e1897f7e9b8b717a9..4b6686055f5de7a9382625806e1231266581ee74 100644 GIT binary patch delta 10724 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