← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~tomasgroth/openlp/importer-fixes into lp:openlp

 

Tomas Groth has proposed merging lp:~tomasgroth/openlp/importer-fixes into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)
Related bugs:
  Bug #1531319 in OpenLP: "The presentation display isn't updated on windows"
  https://bugs.launchpad.net/openlp/+bug/1531319

For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/importer-fixes/+merge/282093

Disable OpenGL in the maindisplay on windows for now, fixes bug.
Fix for easyslide importer and added test.
Made song-importer-list sorted, again.
Small cleanup of videopsalm
Made sundayplus importer work with python3. Added tests.
Fix some tests on windows.
Pep8 fixes

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~tomasgroth/openlp/importer-fixes into lp:openlp.
=== modified file 'openlp/core/common/settings.py'
--- openlp/core/common/settings.py	2015-12-31 22:46:06 +0000
+++ openlp/core/common/settings.py	2016-01-09 13:41:57 +0000
@@ -252,7 +252,8 @@
             '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/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
+                                 QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
             'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],
             'shortcuts/editSong': [],
             'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
@@ -329,7 +330,8 @@
             'shortcuts/moveBottom': [QtGui.QKeySequence(QtCore.Qt.Key_End)],
             'shortcuts/moveDown': [QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
             'shortcuts/nextTrackItem': [],
-            'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down), QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
+            'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
+                                        QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
             'shortcuts/nextItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
                                            QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
             'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
@@ -339,7 +341,8 @@
                                          QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F1)],
             'shortcuts/openService': [],
             'shortcuts/saveService': [],
-            'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up), QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
+            'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
+                                            QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
             'shortcuts/playbackPause': [],
             'shortcuts/playbackPlay': [],
             'shortcuts/playbackStop': [],

=== modified file 'openlp/core/ui/maindisplay.py'
--- openlp/core/ui/maindisplay.py	2015-12-31 22:46:06 +0000
+++ openlp/core/ui/maindisplay.py	2016-01-09 13:41:57 +0000
@@ -34,7 +34,7 @@
 
 from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtOpenGL, QtGui, QtMultimedia
 
-from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, Settings, translate, is_macosx
+from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, Settings, translate, is_macosx, is_win
 from openlp.core.lib import ServiceItem, ImageSource, ScreenList, build_html, expand_tags, image_to_byte
 from openlp.core.lib.theme import BackgroundType
 from openlp.core.ui import HideMode, AlertLocation
@@ -90,7 +90,7 @@
         # OpenGL. Only white blank screen is shown on the 2nd monitor all the
         # time. We need to investigate more how to use OpenGL properly on Mac OS
         # X.
-        if not is_macosx():
+        if not is_macosx() and not is_win():
             self.setViewport(QtOpenGL.QGLWidget())
 
     def setup(self):
@@ -121,7 +121,8 @@
 
         :param event: The event to be handled
         """
-        self.web_view.setGeometry(0, 0, self.width(), self.height())
+        if hasattr(self, 'web_view'):
+            self.web_view.setGeometry(0, 0, self.width(), self.height())
 
     def is_web_loaded(self, field=None):
         """

=== modified file 'openlp/core/ui/themeform.py'
--- openlp/core/ui/themeform.py	2015-12-31 22:46:06 +0000
+++ openlp/core/ui/themeform.py	2016-01-09 13:41:57 +0000
@@ -159,7 +159,7 @@
         if not event:
             event = QtGui.QResizeEvent(self.size(), self.size())
         QtWidgets.QWizard.resizeEvent(self, event)
-        if self.currentPage() == self.preview_page:
+        if hasattr(self, 'preview_page') and self.currentPage() == self.preview_page:
             frame_width = self.preview_box_label.lineWidth()
             pixmap_width = self.preview_area.width() - 2 * frame_width
             pixmap_height = self.preview_area.height() - 2 * frame_width

=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
--- openlp/plugins/custom/lib/mediaitem.py	2016-01-08 17:28:47 +0000
+++ openlp/plugins/custom/lib/mediaitem.py	2016-01-09 13:41:57 +0000
@@ -190,7 +190,7 @@
             if QtWidgets.QMessageBox.question(
                     self, UiStrings().ConfirmDelete,
                     translate('CustomPlugin.MediaItem',
-                              'Are you sure you want to delete the "%d" selected custom slide(s)?') %len(items),
+                              'Are you sure you want to delete the "%d" selected custom slide(s)?') % len(items),
                     QtWidgets.QMessageBox.StandardButtons(
                         QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
                     QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:

=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importer.py	2016-01-09 13:41:57 +0000
@@ -153,23 +153,23 @@
     EasyWorshipDB = 6
     EasyWorshipService = 7
     FoilPresenter = 8
-    MediaShout = 9
-    OpenSong = 10
-    PowerPraise = 11
-    PowerSong = 12
-    PresentationManager = 13
-    ProPresenter = 14
-    SongBeamer = 15
-    SongPro = 16
-    SongShowPlus = 17
-    SongsOfFellowship = 18
-    SundayPlus = 19
-    WordsOfWorship = 20
-    WorshipAssistant = 21
-    WorshipCenterPro = 22
-    ZionWorx = 23
-    Lyrix = 24
-    VideoPsalm = 25
+    Lyrix = 9
+    MediaShout = 10
+    OpenSong = 11
+    PowerPraise = 12
+    PowerSong = 13
+    PresentationManager = 14
+    ProPresenter = 15
+    SongBeamer = 16
+    SongPro = 17
+    SongShowPlus = 18
+    SongsOfFellowship = 19
+    SundayPlus = 20
+    VideoPsalm = 21
+    WordsOfWorship = 22
+    WorshipAssistant = 23
+    WorshipCenterPro = 24
+    ZionWorx = 25
 
     # Set optional attribute defaults
     __defaults__ = {

=== modified file 'openlp/plugins/songs/lib/importers/easyslides.py'
--- openlp/plugins/songs/lib/importers/easyslides.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/easyslides.py	2016-01-09 13:41:57 +0000
@@ -65,7 +65,8 @@
             self._add_unicode_attribute('song_number', song.SongNumber)
         if self.song_number == '0':
             self.song_number = ''
-        self._add_authors(song)
+        if hasattr(song, 'Writer'):
+            self._add_authors(song.Writer)
         if hasattr(song, 'Copyright'):
             self._add_copyright(song.Copyright)
         if hasattr(song, 'LicenceAdmin1'):
@@ -102,15 +103,13 @@
             if mandatory:
                 self._success = False
 
-    def _add_authors(self, song):
+    def _add_authors(self, writer):
         try:
-            authors = str(song.Writer).split(',')
-            self.authors = [author.strip() for author in authors if author.strip()]
-        except UnicodeDecodeError:
+            self.parse_author(str(writer))
+        except UnicodeDecodeError as e:
+            print(e)
             log.exception('Unicode decode error while decoding Writer')
             self._success = False
-        except AttributeError:
-            pass
 
     def _add_copyright(self, element):
         """
@@ -234,11 +233,10 @@
         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
-                lang = None
                 versetag = '%s%s' % (vt, vn)
                 versetags.append(versetag)
                 lines = '\n'.join(verses[reg][vt][vn][inst])
-                self.verses.append([versetag, lines, lang])
+                self.add_verse(lines, versetag)
         SeqTypes = {
             'p': 'P1',
             'q': 'P2',

=== modified file 'openlp/plugins/songs/lib/importers/sundayplus.py'
--- openlp/plugins/songs/lib/importers/sundayplus.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/sundayplus.py	2016-01-09 13:41:57 +0000
@@ -22,6 +22,8 @@
 
 import os
 import re
+import logging
+
 
 from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
 from openlp.plugins.songs.lib import strip_rtf
@@ -53,8 +55,8 @@
         """
         Initialise the class.
         """
-        SongImport.__init__(self, manager, **kwargs)
-        self.encoding = 'us-ascii'
+        super(SundayPlusImport, self).__init__(manager, **kwargs)
+        self.encoding = 'cp1252'
 
     def do_import(self):
         self.import_wizard.progress_bar.setMaximum(len(self.import_source))
@@ -73,7 +75,7 @@
         if not self.parse(file.read()):
             self.log_error(file.name)
             return
-        if not self.title:
+        if self.title == '':
             self.title = self.title_from_filename(file.name)
         if not self.finish():
             self.log_error(file.name)
@@ -86,7 +88,7 @@
         :param cell: ?
         :return:
         """
-        if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
+        if not cell and (len(data) == 0 or data[0:1] != b'[' or data.strip()[-1:] != b']'):
             self.log_error('File is malformed')
             return False
         i = 1
@@ -94,31 +96,31 @@
         while i < len(data):
             # Data is held as #name: value pairs inside groups marked as [].
             # Now we are looking for the name.
-            if data[i:i + 1] == '#':
-                name_end = data.find(':', i + 1)
-                name = data[i + 1:name_end].upper()
+            if data[i:i + 1] == b'#':
+                name_end = data.find(b':', i + 1)
+                name = data[i + 1:name_end].decode(self.encoding).upper()
                 i = name_end + 1
-                while data[i:i + 1] == ' ':
+                while data[i:i + 1] == b' ':
                     i += 1
-                if data[i:i + 1] == '"':
-                    end = data.find('"', i + 1)
+                if data[i:i + 1] == b'"':
+                    end = data.find(b'"', i + 1)
                     value = data[i + 1:end]
-                elif data[i:i + 1] == '[':
+                elif data[i:i + 1] == b'[':
                     j = i
                     inside_quotes = False
                     while j < len(data):
                         char = data[j:j + 1]
-                        if char == '"':
+                        if char == b'"':
                             inside_quotes = not inside_quotes
-                        elif not inside_quotes and char == ']':
+                        elif not inside_quotes and char == b']':
                             end = j + 1
                             break
                         j += 1
                     value = data[i:end]
                 else:
-                    end = data.find(',', i + 1)
-                    if data.find('(', i, end) != -1:
-                        end = data.find(')', i) + 1
+                    end = data.find(b',', i + 1)
+                    if data.find(b'(', i, end) != -1:
+                        end = data.find(b')', i) + 1
                     value = data[i:end]
                 # If we are in the main group.
                 if not cell:
@@ -129,27 +131,29 @@
                         if len(author):
                             self.add_author(author)
                     elif name == 'COPYRIGHT':
-                        self.copyright = self.decode(self.unescape(value))
+                        self.add_copyright(self.decode(self.unescape(value)))
                     elif name[0:4] == 'CELL':
                         self.parse(value, cell=name[4:])
                 # We are in a verse group.
                 else:
                     if name == 'MARKER_NAME':
-                        value = value.strip()
+                        value = self.decode(value).strip()
                         if len(value):
                             verse_type = VerseType.tags[VerseType.from_loose_input(value[0])]
                             if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
                                 verse_type = "%s%s" % (verse_type, value[-1])
                     elif name == 'HOTKEY':
+                        value = self.decode(value).strip()
                         # HOTKEY always appears after MARKER_NAME, so it
                         # effectively overrides MARKER_NAME, if present.
                         if len(value) and value in list(HOTKEY_TO_VERSE_TYPE.keys()):
                             verse_type = HOTKEY_TO_VERSE_TYPE[value]
                     if name == 'RTF':
                         value = self.unescape(value)
+                        value = self.decode(value)
                         result = strip_rtf(value, self.encoding)
                         if result is None:
-                            return
+                            return False
                         verse, self.encoding = result
                         lines = verse.strip().split('\n')
                         # If any line inside any verse contains CCLI or
@@ -164,7 +168,7 @@
                                     self.ccli_number = int(m.group(0))
                                     continue
                             elif line.lower() == 'public domain':
-                                self.copyright = 'Public Domain'
+                                self.add_copyright('Public Domain')
                                 continue
                             processed_lines.append(line)
                         self.add_verse('\n'.join(processed_lines).strip(), verse_type)
@@ -192,11 +196,11 @@
     def decode(self, blob):
         while True:
             try:
-                return str(blob, self.encoding)
-            except:
+                return blob.decode(self.encoding)
+            except Exception as e:
                 self.encoding = retrieve_windows_encoding()
 
     def unescape(self, text):
-        text = text.replace('^^', '"')
-        text = text.replace('^', '\'')
+        text = text.replace(b'^^', b'"')
+        text = text.replace(b'^', b'\'')
         return text.strip()

=== modified file 'openlp/plugins/songs/lib/importers/videopsalm.py'
--- openlp/plugins/songs/lib/importers/videopsalm.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/videopsalm.py	2016-01-09 13:41:57 +0000
@@ -28,7 +28,6 @@
 import os
 
 from openlp.core.common import translate
-from openlp.plugins.songs.lib import VerseType
 from openlp.plugins.songs.lib.importers.songimport import SongImport
 from openlp.plugins.songs.lib.db import AuthorType
 

=== modified file 'tests/functional/openlp_core_lib/test_mediamanageritem.py'
--- tests/functional/openlp_core_lib/test_mediamanageritem.py	2016-01-07 12:36:11 +0000
+++ tests/functional/openlp_core_lib/test_mediamanageritem.py	2016-01-09 13:41:57 +0000
@@ -75,7 +75,6 @@
         self.assertTrue(mmi.has_delete_icon, 'By default a delete icon should be present')
         self.assertFalse(mmi.add_to_service_item, 'There should be no add_to_service icon by default')
 
-
     @patch(u'openlp.core.lib.mediamanageritem.Settings')
     @patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click')
     def on_double_clicked_go_live_test(self, mocked_on_live_click, MockedSettings):

=== modified file 'tests/functional/openlp_core_lib/test_projectordb.py'
--- tests/functional/openlp_core_lib/test_projectordb.py	2016-01-07 21:57:01 +0000
+++ tests/functional/openlp_core_lib/test_projectordb.py	2016-01-09 13:41:57 +0000
@@ -185,4 +185,3 @@
         # THEN: Projector should have the same source entry
         item = self.projector.get_projector_by_id(item_id)
         self.assertTrue(compare_source(item.source_list[0], source))
-

=== added file 'tests/functional/openlp_plugins/songs/test_easyslidesimport.py'
--- tests/functional/openlp_plugins/songs/test_easyslidesimport.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_easyslidesimport.py	2016-01-09 13:41:57 +0000
@@ -0,0 +1,45 @@
+# 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 EasySlides song importer.
+"""
+
+import os
+
+from tests.helpers.songfileimport import SongImportTestHelper
+
+TEST_PATH = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'easyslidessongs'))
+
+
+class TestEasySlidesFileImport(SongImportTestHelper):
+
+    def __init__(self, *args, **kwargs):
+        self.importer_class_name = 'EasySlidesImport'
+        self.importer_module_name = 'easyslides'
+        super(TestEasySlidesFileImport, self).__init__(*args, **kwargs)
+
+    def test_song_import(self):
+        """
+        Test that loading an EasySlides file works correctly on various files
+        """
+        self.file_import(os.path.join(TEST_PATH, 'amazing-grace.xml'),
+                         self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

=== modified file 'tests/functional/openlp_plugins/songs/test_songformat.py'
--- tests/functional/openlp_plugins/songs/test_songformat.py	2016-01-05 21:35:35 +0000
+++ tests/functional/openlp_plugins/songs/test_songformat.py	2016-01-09 13:41:57 +0000
@@ -81,4 +81,3 @@
             # THEN: Return all attributes that were specified
             self.assertEquals(len(SongFormat.get(song_format, 'canDisable', 'availability')), 2,
                     "Did not return the correct number of attributes when retrieving multiple attributes at once")
-

=== added file 'tests/functional/openlp_plugins/songs/test_sundayplusimport.py'
--- tests/functional/openlp_plugins/songs/test_sundayplusimport.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_sundayplusimport.py	2016-01-09 13:41:57 +0000
@@ -0,0 +1,51 @@
+# 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 VideoPsalm song importer.
+"""
+
+import os
+
+from tests.helpers.songfileimport import SongImportTestHelper
+from tests.functional import patch
+
+TEST_PATH = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'sundayplussongs'))
+
+
+class TestSundayPlusFileImport(SongImportTestHelper):
+
+    def __init__(self, *args, **kwargs):
+        self.importer_class_name = 'SundayPlusImport'
+        self.importer_module_name = 'sundayplus'
+        super(TestSundayPlusFileImport, self).__init__(*args, **kwargs)
+
+    def test_song_import(self):
+        """
+        Test that loading an SundayPlus file works correctly on various files
+        """
+        with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \
+                mocked_retrieve_windows_encoding:
+            mocked_retrieve_windows_encoding.return_value = 'cp1252'
+            self.file_import([os.path.join(TEST_PATH, 'Abba Fader.ptf')],
+                             self.load_external_result_data(os.path.join(TEST_PATH, 'abba-fader.json')))
+            self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.ptf')],
+                             self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

=== modified file 'tests/functional/openlp_plugins/songs/test_videopsalm.py'
--- tests/functional/openlp_plugins/songs/test_videopsalm.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_plugins/songs/test_videopsalm.py	2016-01-09 13:41:57 +0000
@@ -26,7 +26,6 @@
 from unittest import TestCase
 
 from tests.helpers.songfileimport import SongImportTestHelper
-from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
 from openlp.core.common import Registry
 from tests.functional import patch, MagicMock
 

=== modified file 'tests/helpers/testmixin.py'
--- tests/helpers/testmixin.py	2015-12-31 22:46:06 +0000
+++ tests/helpers/testmixin.py	2016-01-09 13:41:57 +0000
@@ -48,13 +48,17 @@
         """
         Build the settings Object and initialise it
         """
-        Settings.setDefaultFormat(Settings.IniFormat)
         self.fd, self.ini_file = mkstemp('.ini')
-        Settings().set_filename(self.ini_file)
+        Settings.set_filename(self.ini_file)
+        Settings().setDefaultFormat(Settings.IniFormat)
+        # Needed on windows to make sure a Settings object is available during the tests
+        self.setting = Settings()
+        Settings().setValue('themes/global theme', 'my_theme')
 
     def destroy_settings(self):
         """
         Destroy the Settings Object
         """
+        del self.setting
         os.close(self.fd)
         os.unlink(Settings().fileName())

=== modified file 'tests/interfaces/openlp_core_ui/test_projectoreditform.py'
--- tests/interfaces/openlp_core_ui/test_projectoreditform.py	2016-01-07 21:36:43 +0000
+++ tests/interfaces/openlp_core_ui/test_projectoreditform.py	2016-01-09 13:41:57 +0000
@@ -103,5 +103,3 @@
                              'Projector edit form should be marked as existing entry')
             self.assertTrue((item.ip is TEST1_DATA['ip'] and item.name is TEST1_DATA['name']),
                             'Projector edit form should have TEST1_DATA() instance to edit')
-
-

=== added directory 'tests/resources/easyslidessongs'
=== added file 'tests/resources/easyslidessongs/Amazing Grace.json'
--- tests/resources/easyslidessongs/Amazing Grace.json	1970-01-01 00:00:00 +0000
+++ tests/resources/easyslidessongs/Amazing Grace.json	2016-01-09 13:41:57 +0000
@@ -0,0 +1,32 @@
+{
+    "title": "Amazing Grace",
+    "authors": [
+        "John Newton (1725-1807)"
+    ],
+    "verses": [
+        [
+            "Amazing grace! How sweet the sound\nThat saved a wretch like me;\nI once was lost, but now am found,\nWas blind, but now I see.",
+            "V1"
+        ],
+        [
+            "'Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!",
+            "V2"
+        ],
+        [
+            "Through many dangers, toils and snares\nI have already come;\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
+            "V3"
+        ],
+        [
+            "The Lord has promised good to me,\nHis word my hope secures;\nHe will my shield and portion be\nAs long as life endures.",
+            "V4"
+        ],
+        [
+            "Yes, when this heart and flesh shall fail,\nAnd mortal life shall cease,\nI shall possess within the veil\nA life of joy and peace.",
+            "V5"
+        ],
+        [
+            "When we've been there a thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise\nThan when we first begun.",
+            "V6"
+        ]
+    ]
+}

=== added file 'tests/resources/easyslidessongs/amazing-grace.xml'
--- tests/resources/easyslidessongs/amazing-grace.xml	1970-01-01 00:00:00 +0000
+++ tests/resources/easyslidessongs/amazing-grace.xml	2016-01-09 13:41:57 +0000
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<EasiSlides>
+  <Item>
+    <Title1>Amazing Grace</Title1>
+    <Title2 />
+    <Folder>English</Folder>
+    <SongNumber>0</SongNumber>
+    <Contents>[1]
+Amazing grace! How sweet the sound
+That saved a wretch like me;
+I once was lost, but now am found,
+Was blind, but now I see.
+[2]
+'Twas grace that taught my heart to fear,
+And grace my fears relieved;
+How precious did that grace appear,
+The hour I first believed!
+[3]
+Through many dangers, toils and snares
+I have already come;
+'Tis grace that brought me safe thus far,
+And grace will lead me home.
+[4]
+The Lord has promised good to me,
+His word my hope secures;
+He will my shield and portion be
+As long as life endures.
+[5]
+Yes, when this heart and flesh shall fail,
+And mortal life shall cease,
+I shall possess within the veil
+A life of joy and peace.
+[6]
+When we've been there a thousand years,
+Bright shining as the sun,
+We've no less days to sing God's praise
+Than when we first begun.</Contents>
+    <Notations />
+    <Sequence />
+    <Writer>John Newton (1725-1807)</Writer>
+    <Copyright />
+    <Category />
+    <Timing />
+    <MusicKey />
+    <Capo>-1</Capo>
+    <LicenceAdmin1>Public Domain</LicenceAdmin1>
+    <LicenceAdmin2 />
+    <BookReference>SF19, MP31, TS18</BookReference>
+    <UserReference />
+    <FormatData />
+    <Settings />
+  </Item>
+</EasiSlides>

=== added directory 'tests/resources/sundayplussongs'
=== added file 'tests/resources/sundayplussongs/Abba Fader.ptf'
--- tests/resources/sundayplussongs/Abba Fader.ptf	1970-01-01 00:00:00 +0000
+++ tests/resources/sundayplussongs/Abba Fader.ptf	2016-01-09 13:41:57 +0000
@@ -0,0 +1,8 @@
+[#PTFVersion: 2, #GLOBAL_RECT: rect(47,2,1026,770), #opacity: 100, #SHADOW_ON: 0, #SHADOW_COLOR: rgb( 0, 0, 0), #SHADOW_OPACITY: 100, #SHADOW_POSITION: "RB", #SHADOW_OFFSET: [0, 0], #FILE_TYPE: "Song", #title: "Abba Fader", #Author: "Ok�", #Copyright: "ccc", #CELL1: [#MARKER_NAME: "Abba Fader", #Hotkey: "1", #rtf: "{\rtf1\ansi\ansicpg1252\deff0\deflang1053{\fonttbl{\f0\froman\fprq2\fcharset0 Verdana;}{\f1\froman\fcharset0 Verdana;}}
+{\colortbl ;\red255\green255\blue0;\red224\green223\blue227;}
+\viewkind4\uc1\pard\cf1\b\f0\fs86 Abba Fader\par
+\par
+Vi \^e4r h\^e4r f\^f6r att prisa Dig\line Vi \^e4r h\^e4r med f\^f6rv\^e4ntan\line Vi \^e4r h\^e4r som ett enat folk\line Vi kommer fram till Dig\line Med v\^e5r lovs\^e5ng\line\fs59\line\fs86 Vi ropar Abba Fader\line Du som har all makt\line Vi ropar Abba Fader\line Till Dig st\^e5r allt v\^e5rt hopp\line Vi ropar Abba Fader\line V\^e5r fr\^e4lsare, befriare \^e4r Du\b0\line\pard\tx720\f1\par
+\cf2\par
+}
+", #Align: #Left]]
\ No newline at end of file

=== added file 'tests/resources/sundayplussongs/Amazing Grace.json'
--- tests/resources/sundayplussongs/Amazing Grace.json	1970-01-01 00:00:00 +0000
+++ tests/resources/sundayplussongs/Amazing Grace.json	2016-01-09 13:41:57 +0000
@@ -0,0 +1,26 @@
+{
+    "title": "Amazing Grace",
+    "authors": [
+        "John Newton"
+    ],
+    "copyright": "Public Domain",
+    "cclinumber": "1234",
+    "verses": [
+        [
+            "Amazing grace how sweet the sound\nThat saved a wretch like me;\nI once was lost, but now am found,\nWas blind, but now I see.",
+            "v"
+        ],
+        [
+            "’Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!",
+            "v"
+        ],
+        [
+            "Through many dangers, toils and snares\nI have already come;\n’Tis grace hath brought me safe thus far,\nAnd grace will lead me home.",
+            "v"
+        ],
+        [
+            "When we’ve been there ten thousand years,\nBright shining as the sun,\nWe’ve no less days to sing God’s praise\nThan when we first begun.",
+            "v"
+        ]
+    ]
+}

=== added file 'tests/resources/sundayplussongs/Amazing Grace.ptf'
--- tests/resources/sundayplussongs/Amazing Grace.ptf	1970-01-01 00:00:00 +0000
+++ tests/resources/sundayplussongs/Amazing Grace.ptf	2016-01-09 13:41:57 +0000
@@ -0,0 +1,43 @@
+[#GLOBAL_RECT: rect(100, 150, 900, 1024), #Opacity: 100, #SHADOW_ON: 1, #SHADOW_COLOR: rgb( 0, 0, 0 ), #SHADOW_OPACITY: 100, #SHADOW_POSITION: "RB", #SHADOW_OFFSET: [4, 4], #FILE_TYPE: "Song", #title: "", #Author: "John Newton", #Copyright: "Public Domain", #CCLI: "1234", #Cell1: [#rtf: "{\rtf1\ansi\deff0 {\fonttbl{\f0\fswiss Arial;}{\f1\fmodern Monotype Corsiva;}{\f2\fswiss MS Serif;}{\f3\fnil Verdana;}}{\colortbl
+\red0\green0\blue0;\red0\green0\blue224;\red224\green0\blue0;\red224\green0\blue224;\red102\green102\blue153;\red51\green153\blue102;
+\red0\green255\blue0;\red255\green255\blue0;\red248\green248\blue248;}{\stylesheet{\s0\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Normal;}{\s2
+\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Normal Text;}{\s3\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Plain Text;}{\s4\fs130\cf4\ql\li0\ri0\fi0\sb0\sa0
+\sl0 heading 1;}{\s5\fs192\cf5\ql\li0\ri0\fi0\sb0\sa0\sl0 heading 2;}{\s6\fs96\cf4\ql\li0\ri0\fi0\sb0\sa0\sl0 Body Text;}{\s7\b\fs96
+\cf6\ql\li0\ri0\fi0\sb0\sa0\sl0 Author;}{\s8\b\fs40\ql\li0\ri0\fi0\sb0\sa0\sl0 CCLI;}{\s9\b\fs40\ql\li0\ri0\fi0\sb0\sa0\sl0 Copyright;}
+{\s10\fs120\cf7\ql\li0\ri0\fi0\sb0\sa0\sl0 Lyrics;}{\s11\b\f1\fs144\ql\li0\ri0\fi0\sb0\sa0\sl0 Title;}{\s12\f2\fs16 02 VERSES;}{\s13
+\f2\fs16 03 CHORUS (itals);}{\s14\f2\fs12 01a ATTRIBUTION (1 fig);}{\s15\f2\fs12 01b ATTRIBUTION (2 fig);}{\s16\f2\fs12 01c ATTRIBUTION (3 fig);}
+{\s17\f2\fs12 01d ATTRIBUTION (4 fig);}}\margl1800 \margr1800 \margt1440 \margb1440 \pard \f0\fs24{\pard \s12\b\f3\fs96\cf8\ql\li0
+\ri0\fi0\sb0\sa0\sl0 Amazing grace how sweet the sound\par
+That saved a wretch like me;\par
+I once was lost, but now am found,\par
+Was blind, but now I see.\par
+\par
+}}", #Align: #left, #MARKER_NAME: "AMAZING GRACE", #Hotkey: "1"], #Cell2: [#rtf: "{\rtf1\ansi\deff0 {\fonttbl{\f0\fswiss Arial;}{\f1\fmodern Monotype Corsiva;}{\f2\fswiss MS Serif;}{\f3\fnil Verdana;}}{\colortbl
+\red0\green0\blue0;\red0\green0\blue224;\red224\green0\blue0;\red224\green0\blue224;\red102\green102\blue153;\red51\green153\blue102;
+\red0\green255\blue0;\red255\green255\blue0;\red248\green248\blue248;}{\stylesheet{\s0\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Normal Text;}
+{\s2\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Normal;}{\s3\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Plain Text;}{\s4\fs130\cf4\ql\li0\ri0\fi0\sb0\sa0
+\sl0 heading 1;}{\s5\fs192\cf5\ql\li0\ri0\fi0\sb0\sa0\sl0 heading 2;}{\s6\fs96\cf4\ql\li0\ri0\fi0\sb0\sa0\sl0 Body Text;}{\s7\b\fs96
+\cf6 Author;}{\s8\b\fs40 CCLI;}{\s9\b\fs40 Copyright;}{\s10\fs120\cf7 Lyrics;}{\s11\b\f1\fs144 Title;}{\s12\f2\fs16 02 VERSES;}}\margl1800 
+\margr1800 \margt1440 \margb1440 \pard \f0\fs24{\b\f3\fs96\cf8 \^92Twas grace that taught my heart to fear,\par
+And grace my fears relieved;\par
+How precious did that grace appear,\par
+The hour I first believed!\par
+\par
+}}", #Align: #left, #MARKER_NAME: "AMAZING GRACE", #Hotkey: "2"], #Cell3: [#rtf: "{\rtf1\ansi\deff0 {\fonttbl{\f0\fswiss Arial;}{\f1\fmodern Monotype Corsiva;}{\f2\fswiss MS Serif;}{\f3\fnil Verdana;}}{\colortbl
+\red0\green0\blue0;\red0\green0\blue224;\red224\green0\blue0;\red224\green0\blue224;\red102\green102\blue153;\red51\green153\blue102;
+\red0\green255\blue0;\red255\green255\blue0;\red248\green248\blue248;}{\stylesheet{\s0\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Normal Text;}
+{\s2\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Normal;}{\s3\fs24\ql\li0\ri0\fi0\sb0\sa0\sl0 Plain Text;}{\s4\fs130\cf4\ql\li0\ri0\fi0\sb0\sa0
+\sl0 heading 1;}{\s5\fs192\cf5\ql\li0\ri0\fi0\sb0\sa0\sl0 heading 2;}{\s6\fs96\cf4\ql\li0\ri0\fi0\sb0\sa0\sl0 Body Text;}{\s7\b\fs96
+\cf6 Author;}{\s8\b\fs40 CCLI;}{\s9\b\fs40 Copyright;}{\s10\fs120\cf7 Lyrics;}{\s11\b\f1\fs144 Title;}{\s12\f2\fs16 02 VERSES;}}\margl1800 
+\margr1800 \margt1440 \margb1440 \pard \f0\fs24{\b\f3\fs96\cf8 Through many dangers, toils and snares\par
+I have already come;\par
+\^92Tis grace hath brought me safe thus far,\par
+And grace will lead me home.\par
+\par
+}}", #Align: #left, #MARKER_NAME: "AMAZING GRACE", #Hotkey: "3"], #CELL4: [#rtf: "{\rtf1\ansi\deff0 {\fonttbl{\f0\fswiss Arial;}{\f1\fnil Verdana;}}{\colortbl\red0\green0\blue0;\red0\green0\blue224;\red224\green0
+\blue0;\red224\green0\blue224;\red248\green248\blue248;}{\stylesheet{\s0\fs24 Normal Text;}}\margl1800 \margr1800 \margt1440 \margb1440 
+\pard \f0\fs24{\b\f1\fs96\cf4 When we\^92ve been there ten thousand years,\par
+Bright shining as the sun,\par
+We\^92ve no less days to sing God\^92s praise\par
+Than when we first begun.\par
+}}", #Align: #left, #MARKER_NAME: "AMAZING GRACE", #Hotkey: "4"], #CELL5: [:]]

=== added file 'tests/resources/sundayplussongs/abba-fader.json'
--- tests/resources/sundayplussongs/abba-fader.json	1970-01-01 00:00:00 +0000
+++ tests/resources/sundayplussongs/abba-fader.json	2016-01-09 13:41:57 +0000
@@ -0,0 +1,13 @@
+{
+    "authors": [
+        ["Okänd"]
+    ],
+    "title": "Abba Fader",
+    "verse_order_list": [],
+    "verses": [
+        [
+            "Abba Fader\n\nVi är här för att prisa Dig\nVi är här med förväntan\nVi är här som ett enat folk\nVi kommer fram till Dig\nMed vår lovsång\n\nVi ropar Abba Fader\nDu som har all makt\nVi ropar Abba Fader\nTill Dig står allt vårt hopp\nVi ropar Abba Fader\nVår frälsare, befriare är Du",
+            "v1"
+        ]
+    ]
+}


Follow ups