# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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 #
###############################################################################
from lxml import etree, objectify
from zipfile import is_zipfile
from openlp.core.common import OpenLPMixin, Registry, RegistryProperties, languages, translate
from openlp.core.lib import ValidationError
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.db import AlternativeBookNamesDB, BibleDB, BiblesResourcesDB
[docs]class BibleImport(OpenLPMixin, RegistryProperties, BibleDB):
"""
Helper class to import bibles from a third party source into OpenLP
"""
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)
[docs] @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
[docs] 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
[docs] 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
[docs] def get_language_id(self, file_language=None, bible_name=None):
"""
Get the language_id for the language of the bible. Fallback to user input if we cannot do this pragmatically.
:param file_language: Language of the bible. Possibly retrieved from the file being imported. Str
:param bible_name: Name of the bible to display on the get_language dialog. Str
:return: The id of a language Int or None
"""
language_id = None
if file_language:
language = languages.get_language(file_language)
if language and language.id:
language_id = language.id
if not language_id:
# The language couldn't be detected, ask the user
language_id = self.get_language(bible_name)
if not language_id:
# User cancelled get_language dialog
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
[docs] def find_and_create_book(self, name, no_of_books, language_id, guess_id=None):
"""
Find the OpenLP book id and then create the book in this bible db
:param name: Name of the book. If None, then fall back to the guess_id Str
:param no_of_books: The total number of books contained in this bible Int
:param language_id: The OpenLP id of the language of this bible Int
:param guess_id: The guessed id of the book, used if name is None Int
:return:
"""
if name:
book_ref_id = self.get_book_ref_id_by_name(name, no_of_books, language_id)
else:
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))
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
if book_details is None:
raise ValidationError(msg='book_ref_id: {book_ref} Could not be found in the BibleResourcesDB while '
'importing {file}'.format(book_ref=book_ref_id, file=self.filename))
return self.create_book(name, book_ref_id, book_details['testament_id'])
[docs] 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
:param use_objectify: Use the objectify parser rather than the etree parser. (Bool)
:param elements: A tuple of element names (Str) to remove along with their content.
:param tags: A tuple of element names (Str) to remove, preserving their content.
:return: The root element of the xml document
"""
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
[docs] 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
[docs] 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))
[docs] def stop_import(self):
"""
Stops the import of the Bible.
"""
self.log_debug('Stopping import')
self.stop_import_flag = True
[docs] 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.')