# -*- 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 #
###############################################################################
import logging
import re
from lxml import etree, objectify
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.importers.songimport import SongImport
log = logging.getLogger(__name__)
[docs]class EasySlidesImport(SongImport):
"""
Import songs exported from EasySlides
The format example is here:
http://wiki.openlp.org/Development:EasySlides\_-_Song_Data_Format
"""
def __init__(self, manager, **kwargs):
"""
Initialise the class.
"""
super(EasySlidesImport, self).__init__(manager, **kwargs)
[docs] def do_import(self):
log.info('Importing EasySlides XML file {source}'.format(source=self.import_source))
parser = etree.XMLParser(remove_blank_text=True, recover=True)
parsed_file = etree.parse(self.import_source, parser)
xml = etree.tostring(parsed_file).decode()
song_xml = objectify.fromstring(xml)
self.import_wizard.progress_bar.setMaximum(len(song_xml.Item))
for song in song_xml.Item:
if self.stop_import_flag:
return
self._parse_song(song)
def _parse_song(self, song):
self._success = True
self._add_unicode_attribute('title', song.Title1, True)
if hasattr(song, 'Title2'):
self._add_unicode_attribute('alternate_title', song.Title2)
if hasattr(song, 'SongNumber'):
self._add_unicode_attribute('song_number', song.SongNumber)
if self.song_number == '0':
self.song_number = ''
if hasattr(song, 'Writer'):
self._add_authors(song.Writer)
if hasattr(song, 'Copyright'):
self._add_copyright(song.Copyright)
if hasattr(song, 'LicenceAdmin1'):
self._add_copyright(song.LicenceAdmin1)
if hasattr(song, 'LicenceAdmin2'):
self._add_copyright(song.LicenceAdmin2)
if hasattr(song, 'BookReference'):
self._add_unicode_attribute('song_book_name', song.BookReference)
self._parse_and_add_lyrics(song)
if self._success:
if not self.finish():
self.log_error(song.Title1 if song.Title1 else '')
else:
self.set_defaults()
def _add_unicode_attribute(self, self_attribute, import_attribute, mandatory=False):
"""
Add imported values to the song model converting them to unicode at the
same time. If the unicode decode fails or a mandatory attribute is not
present _success is set to False so the importer can react
appropriately.
:param self_attribute: The attribute in the song model to populate.
:param import_attribute: The imported value to convert to unicode and save to the song.
:param mandatory: Signals that this attribute must exist in a valid song.
"""
try:
setattr(self, self_attribute, str(import_attribute).strip())
except UnicodeDecodeError:
log.exception('UnicodeDecodeError decoding {attribute}'.format(attribute=import_attribute))
self._success = False
except AttributeError:
log.exception('No attribute {attribute}'.format(attribute=import_attribute))
if mandatory:
self._success = False
def _add_authors(self, writer):
try:
self.parse_author(str(writer))
except UnicodeDecodeError as e:
log.exception('Unicode decode error while decoding Writer')
self._success = False
def _add_copyright(self, element):
"""
Add a piece of copyright to the total copyright information for the song.
:param element: The imported variable to get the data from.
"""
try:
self.add_copyright(str(element).strip())
except UnicodeDecodeError:
log.exception('Unicode error on decoding copyright: {element}'.format(element=element))
self._success = False
except AttributeError:
pass
def _parse_and_add_lyrics(self, song):
"""
Process the song lyrics
:param song: The song details
"""
try:
lyrics = str(song.Contents).strip()
except UnicodeDecodeError:
log.exception('Unicode decode error while decoding Contents')
self._success = False
except AttributeError:
log.exception('no Contents')
self._success = False
lines = lyrics.split('\n')
# we go over all lines first, to determine information,
# which tells us how to parse verses later
region_lines = {}
separator_lines = 0
for line in lines:
line = line.strip()
if not line:
continue
elif line[1:7] == 'region':
# this is region separator, probably [region 2]
region = self._extract_region(line)
region_lines[region] = 1 + region_lines.get(region, 0)
elif line[0] == '[':
separator_lines += 1
# if the song has separators
separators = (separator_lines > 0)
# the number of different regions in song - 1
if len(region_lines) > 1:
log.info('EasySlidesImport: the file contained a song named "{title}"'
'with more than two regions, but only two regions are tested, '
'encountered regions were: {keys}'.format(title=self.title,
keys=','.join(list(region_lines.keys()))))
# if the song has regions
regions = (len(region_lines) > 0)
# if the regions are inside verses
regions_in_verses = (regions and region_lines[list(region_lines.keys())[0]] > 1)
MarkTypes = {
'CHORUS': VerseType.tags[VerseType.Chorus],
'VERSE': VerseType.tags[VerseType.Verse],
'INTRO': VerseType.tags[VerseType.Intro],
'ENDING': VerseType.tags[VerseType.Ending],
'BRIDGE': VerseType.tags[VerseType.Bridge],
'PRECHORUS': VerseType.tags[VerseType.PreChorus]
}
verses = {}
# list as [region, versetype, versenum, instance]
our_verse_order = []
default_region = '1'
reg = default_region
verses[reg] = {}
# instance differentiates occurrences of same verse tag
vt = 'v'
vn = '1'
inst = 1
for line in lines:
line = line.strip()
if not line:
if separators:
# separators are used, so empty line means slide break
# inside verse
if self._list_has(verses, [reg, vt, vn, inst]):
inst += 1
else:
# separators are not used, so empty line starts a new verse
vt = 'v'
vn = len(verses[reg].get(vt, {})) + 1
inst = 1
elif line[0:7] == '[region':
reg = self._extract_region(line)
verses.setdefault(reg, {})
if not regions_in_verses:
vt = 'v'
vn = '1'
inst = 1
elif line[0] == '[':
# this is a normal section marker
marker = line[1:line.find(']')].upper()
vn = '1'
# have we got any digits?
# If so, versenumber is everything from the digits to the end
match = re.match('(.*)(\d+.*)', marker)
if match:
marker = match.group(1).strip()
vn = match.group(2)
vt = MarkTypes.get(marker, 'o') if marker else 'v'
if regions_in_verses:
region = default_region
inst = 1
if self._list_has(verses, [reg, vt, vn, inst]):
inst = len(verses[reg][vt][vn]) + 1
else:
if not [reg, vt, vn, inst] in our_verse_order:
our_verse_order.append([reg, vt, vn, inst])
verses[reg].setdefault(vt, {})
verses[reg][vt].setdefault(vn, {})
verses[reg][vt][vn].setdefault(inst, [])
verses[reg][vt][vn][inst].append(self.tidy_text(line))
# done parsing
versetags = []
# we use our_verse_order to ensure, we insert lyrics in the same order
# as these appeared originally in the file
for [reg, vt, vn, inst] in our_verse_order:
if self._list_has(verses, [reg, vt, vn, inst]):
# this is false, but needs user input
versetag = '{tag}{number}'.format(tag=vt, number=vn)
versetags.append(versetag)
lines = '\n'.join(verses[reg][vt][vn][inst])
self.add_verse(lines, versetag)
SeqTypes = {
'p': 'p1',
'q': 'p2',
'c': 'c1',
't': 'c2',
'b': 'b1',
'w': 'b2',
'e': 'e1'}
# Make use of Sequence data, determining the order of verses
try:
order = str(song.Sequence).strip().split(',')
for tag in order:
if not tag:
continue
elif tag[0].isdigit():
tag = 'v' + tag
elif tag.lower() in SeqTypes:
tag = SeqTypes[tag.lower()]
else:
continue
if tag in versetags:
self.verse_order_list.append(tag)
else:
log.info('Got order item {tag}, which is not in versetags, dropping item from presentation '
'order'.format(tag=tag))
except UnicodeDecodeError:
log.exception('Unicode decode error while decoding Sequence')
self._success = False
except AttributeError:
pass
def _list_has(self, lst, sub_items):
"""
See if the list has sub items
:param lst: The list to check
:param sub_items: sub item list
:return:
"""
for sub_item in sub_items:
if sub_item in lst:
lst = lst[sub_item]
else:
return False
return True
def _extract_region(self, line):
# this was true already: line[0:7] == '[region':
"""
Extract the region from text
:param line: The line of text
:return:
"""
right_bracket = line.find(']')
return line[7:right_bracket].strip()