← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~stewart-e/openlp/bug-1310084 into lp:openlp

 

Stewart Becker has proposed merging lp:~stewart-e/openlp/bug-1310084 into lp:openlp.

Requested reviews:
  Raoul Snyman (raoul-snyman)
  Samuel Mehrbrodt (sam92)
Related bugs:
  Bug #1310084 in OpenLP: "OpenLP crashes if "Automatically open the last service" is set"
  https://bugs.launchpad.net/openlp/+bug/1310084

For more details, see:
https://code.launchpad.net/~stewart-e/openlp/bug-1310084/+merge/217986

Fixed a typo: load_Last_file -> load_last_file
Fixes bug 1310084: OpenLP crashes if "Automatically open the last service" is set

Also added tests for OpenSongImport and fixed some PEP8 issues.

lp:~stewart-e/openlp/bug-1310084 (revision 2378)
[SUCCESS] http://ci.openlp.org/job/Branch-01-Pull/410/
[SUCCESS] http://ci.openlp.org/job/Branch-02-Functional-Tests/366/
[SUCCESS] http://ci.openlp.org/job/Branch-03-Interface-Tests/311/
[SUCCESS] http://ci.openlp.org/job/Branch-04-Windows_Tests/272/
[SUCCESS] http://ci.openlp.org/job/Branch-05a-Code_Analysis/188/
[SUCCESS] http://ci.openlp.org/job/Branch-05b-Test_Coverage/63/
-- 
https://code.launchpad.net/~stewart-e/openlp/bug-1310084/+merge/217986
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2014-04-20 16:02:50 +0000
+++ openlp/core/ui/mainwindow.py	2014-05-01 21:49:02 +0000
@@ -598,7 +598,7 @@
         if self.arguments:
             self.open_cmd_line_files()
         elif Settings().value(self.general_settings_section + '/auto open'):
-            self.service_manager_contents.load_Last_file()
+            self.service_manager_contents.load_last_file()
         self.timer_version_id = self.startTimer(1000)
         view_mode = Settings().value('%s/view mode' % self.general_settings_section)
         if view_mode == 'default':

=== modified file 'openlp/plugins/songs/lib/__init__.py'
--- openlp/plugins/songs/lib/__init__.py	2014-04-02 07:03:53 +0000
+++ openlp/plugins/songs/lib/__init__.py	2014-05-01 21:49:02 +0000
@@ -278,7 +278,7 @@
             if verse_index is None:
                 verse_index = VerseType.from_string(verse_name, default)
         elif len(verse_name) == 1:
-            verse_index = VerseType.from_translated_tag(verse_name, None)
+            verse_index = VerseType.from_translated_tag(verse_name, default)
             if verse_index is None:
                 verse_index = VerseType.from_tag(verse_name, default)
         else:

=== modified file 'openlp/plugins/songs/lib/opensongimport.py'
--- openlp/plugins/songs/lib/opensongimport.py	2014-03-06 22:05:15 +0000
+++ openlp/plugins/songs/lib/opensongimport.py	2014-05-01 21:49:02 +0000
@@ -45,11 +45,11 @@
     """
     Import songs exported from OpenSong
 
-    The format is described loosly on the `OpenSong File Format Specification
+    The format is described loosely on the `OpenSong File Format Specification
     <http://www.opensong.org/d/manual/song_file_format_specification>`_ page on the OpenSong web site. However, it
     doesn't describe the <lyrics> section, so here's an attempt:
 
-    If the first charachter of a line is a space, then the rest of that line is lyrics. If it is not a space the
+    If the first character of a line is a space, then the rest of that line is lyrics. If it is not a space the
     following applies.
 
     Verses can be expressed in one of 2 ways, either in complete verses, or by line grouping, i.e. grouping all line 1's
@@ -93,12 +93,19 @@
 
     All verses are imported and tagged appropriately.
 
-    Guitar chords can be provided "above" the lyrics (the line is preceeded by a period "."), and one or more "_" can
+    Guitar chords can be provided "above" the lyrics (the line is preceded by a period "."), and one or more "_" can
     be used to signify long-drawn-out words. Chords and "_" are removed by this importer. For example::
 
         . A7        Bm
         1 Some____ Words
 
+    Lines that contain only whitespace are ignored.
+    | indicates a blank line, and || a new slide.
+
+        Slide 1 Line 1|Slide 1 Line 2||Slide 2 Line 1|Slide 2 Line 2
+
+    Lines beginning with ; are comments
+
     The <presentation> tag is used to populate the OpenLP verse display order field. The Author and Copyright tags are
     also imported to the appropriate places.
     """
@@ -107,9 +114,14 @@
         """
         Initialise the class.
         """
-        SongImport.__init__(self, manager, **kwargs)
+        super(OpenSongImport, self).__init__(manager, **kwargs)
 
     def do_import(self):
+        """
+        Receive a single file or a list of files to import.
+        """
+        if not isinstance(self.import_source, list):
+            return
         self.import_wizard.progress_bar.setMaximum(len(self.import_source))
         for filename in self.import_source:
             if self.stop_import_flag:
@@ -141,19 +153,41 @@
             'author': self.parse_author,
             'title': 'title',
             'aka': 'alternate_title',
-            'hymn_number': 'song_number'
+            'hymn_number': self.parse_song_book_name_and_number,
+            'user1': self.add_comment,
+            'user2': self.add_comment,
+            'user3': self.add_comment
         }
         for attr, fn_or_string in list(decode.items()):
             if attr in fields:
                 ustring = str(root.__getattr__(attr))
                 if isinstance(fn_or_string, str):
-                    setattr(self, fn_or_string, ustring)
+                    if attr in ['ccli']:
+                        if ustring:
+                            setattr(self, fn_or_string, int(ustring))
+                        else:
+                            setattr(self, fn_or_string, None)
+                    else:
+                        setattr(self, fn_or_string, ustring)
                 else:
                     fn_or_string(ustring)
-        if 'theme' in fields and str(root.theme) not in self.topics:
-            self.topics.append(str(root.theme))
-        if 'alttheme' in fields and str(root.alttheme) not in self.topics:
-            self.topics.append(str(root.alttheme))
+        # Themes look like "God: Awe/Wonder", but we just want
+        # "Awe" and "Wonder".  We use a set to ensure each topic
+        # is only added once, in case it is already there, which
+        # is actually quite likely if the alttheme is set
+        topics = set(self.topics)
+        if 'theme' in fields:
+            theme = str(root.theme)
+            subthemes = theme[theme.find(':')+1:].split('/')
+            for topic in subthemes:
+                topics.add(topic.strip())
+        if 'alttheme' in fields:
+            theme = str(root.alttheme)
+            subthemes = theme[theme.find(':')+1:].split('/')
+            for topic in subthemes:
+                topics.add(topic.strip())
+        self.topics = list(topics)
+        self.topics.sort()
         # data storage while importing
         verses = {}
         # keep track of verses appearance order
@@ -168,7 +202,7 @@
         else:
             lyrics = ''
         for this_line in lyrics.split('\n'):
-            if not this_line:
+            if not this_line.strip():
                 continue
             # skip this line if it is a comment
             if this_line.startswith(';'):
@@ -209,8 +243,14 @@
             # Tidy text and remove the ____s from extended words
             this_line = self.tidy_text(this_line)
             this_line = this_line.replace('_', '')
-            this_line = this_line.replace('|', '\n')
+            this_line = this_line.replace('||', '\n[---]\n')
             this_line = this_line.strip()
+            # If the line consists solely of a '|', then just use the implicit newline
+            # Otherwise, add a newline for each '|'
+            if this_line == '|':
+                this_line = ''
+            else:
+                this_line = this_line.replace('|', '\n')
             verses[verse_tag][verse_num][inst].append(this_line)
         # done parsing
         # add verses in original order
@@ -223,7 +263,14 @@
             verse_def = '%s%s' % (verse_tag, verse_num[:length])
             verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \
                 if verse_def in verse_joints else lines
-        for verse_def, lines in verse_joints.items():
+        # Parsing the dictionary produces the elements in a non-intuitive order.  While it "works", it's not a
+        # natural layout should the user come back to edit the song.  Instead we sort by the verse type, so that we
+        # get all the verses in order (v1, v2, ...), then the chorus(es), bridge(s), pre-chorus(es) etc.  We use a
+        # tuple for the key, since tuples naturally sort in this manner.
+        verse_defs = sorted(verse_joints.keys(),
+                            key=lambda verse_def: (VerseType.from_tag(verse_def[0]), int(verse_def[1:])))
+        for verse_def in verse_defs:
+            lines = verse_joints[verse_def]
             self.add_verse(lines, verse_def)
         if not self.verses:
             self.add_verse('')
@@ -244,6 +291,8 @@
                     # Assume it's no.1 if there are no digits
                     verse_tag = verse_def
                     verse_num = '1'
+                verse_index = VerseType.from_loose_input(verse_tag)
+                verse_tag = VerseType.tags[verse_index]
                 verse_def = '%s%s' % (verse_tag, verse_num)
                 if verse_num in verses.get(verse_tag, {}):
                     self.verse_order_list.append(verse_def)

=== modified file 'openlp/plugins/songs/lib/songimport.py'
--- openlp/plugins/songs/lib/songimport.py	2014-03-21 21:38:08 +0000
+++ openlp/plugins/songs/lib/songimport.py	2014-05-01 21:49:02 +0000
@@ -188,13 +188,61 @@
             self.title = lines[0]
         self.add_verse(text)
 
+    def parse_song_book_name_and_number(self, book_and_number):
+        """
+        Build the book name and song number from a single string
+        """
+        # Turn 'Spring Harvest 1997 No. 34' or
+        # 'Spring Harvest 1997 (34)' or
+        # 'Spring Harvest 1997 34' into
+        # Book name:'Spring Harvest 1997' and
+        # Song number: 34
+        #
+        # Also, turn 'NRH231.' into
+        # Book name:'NRH' and
+        # Song number: 231
+        book_and_number = book_and_number.strip()
+        if not book_and_number:
+            return
+        book_and_number = book_and_number.replace('No.', ' ')
+        if ' ' in book_and_number:
+            parts = book_and_number.split(' ')
+            self.song_book_name = ' '.join(parts[:-1])
+            self.song_number = parts[-1].strip('()')
+        else:
+            # Something like 'ABC123.'
+            match = re.match(r'(.*\D)(\d+)', book_and_number)
+            match_num = re.match(r'(\d+)', book_and_number)
+            if match:
+                # Name and number
+                self.song_book_name = match.group(1)
+                self.song_number = match.group(2)
+            # These last two cases aren't tested yet, but
+            # are here in an attempt to do something vaguely
+            # sensible if we get a string in a different format
+            elif match_num:
+                # Number only
+                self.song_number = match_num.group(1)
+            else:
+                # Name only
+                self.song_book_name = book_and_number
+
+    def add_comment(self, comment):
+        """
+        Build the comments field
+        """
+        if self.comments.find(comment) >= 0:
+            return
+        if comment:
+            self.comments += comment.strip() + '\n'
+
     def add_copyright(self, copyright):
         """
         Build the copyright field
         """
         if self.copyright.find(copyright) >= 0:
             return
-        if self.copyright != '':
+        if self.copyright:
             self.copyright += ' '
         self.copyright += copyright
 

=== modified file 'tests/functional/openlp_core_lib/test_image_manager.py'
--- tests/functional/openlp_core_lib/test_image_manager.py	2014-04-15 20:30:20 +0000
+++ tests/functional/openlp_core_lib/test_image_manager.py	2014-05-01 21:49:02 +0000
@@ -171,4 +171,4 @@
         self.lock.release()
         # The sleep time is adjusted in the test case.
         time.sleep(self.sleep_time)
-        return ''
\ No newline at end of file
+        return ''

=== modified file 'tests/functional/openlp_core_ui/test_media.py'
--- tests/functional/openlp_core_ui/test_media.py	2014-04-20 22:23:26 +0000
+++ tests/functional/openlp_core_ui/test_media.py	2014-05-01 21:49:02 +0000
@@ -125,4 +125,4 @@
 
             # THEN: the used_players should be an empty list, and the overridden player should be an empty string
             self.assertEqual(['vlc', 'webkit', 'phonon'], used_players, 'Used players should be correct')
-            self.assertEqual('vlc,webkit,phonon', overridden_player, 'Overridden player should be a string of players')
\ No newline at end of file
+            self.assertEqual('vlc,webkit,phonon', overridden_player, 'Overridden player should be a string of players')

=== added file 'tests/functional/openlp_plugins/songs/test_opensongimport.py'
--- tests/functional/openlp_plugins/songs/test_opensongimport.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_opensongimport.py	2014-05-01 21:49:02 +0000
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# 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 OpenSong song importer.
+"""
+
+import os
+from unittest import TestCase
+
+from tests.helpers.songfileimport import SongImportTestHelper
+from openlp.plugins.songs.lib.opensongimport import OpenSongImport
+from tests.functional import patch, MagicMock
+
+TEST_PATH = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'opensongsongs'))
+
+
+class TestOpenSongFileImport(SongImportTestHelper):
+
+    def __init__(self, *args, **kwargs):
+        self.importer_class_name = 'OpenSongImport'
+        self.importer_module_name = 'opensongimport'
+        super(TestOpenSongFileImport, self).__init__(*args, **kwargs)
+
+    def test_song_import(self):
+        """
+        Test that loading an OpenSong file works correctly on various files
+        """
+        self.file_import(os.path.join(TEST_PATH, 'Amazing Grace'),
+                         self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
+        self.file_import(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer'),
+                         self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
+        self.file_import(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five'),
+                         self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
+
+
+class TestOpenSongImport(TestCase):
+    """
+    Test the functions in the :mod:`opensongimport` module.
+    """
+    def create_importer_test(self):
+        """
+        Test creating an instance of the OpenSong file importer
+        """
+        # GIVEN: A mocked out SongImport class, and a mocked out "manager"
+        with patch('openlp.plugins.songs.lib.opensongimport.SongImport'):
+            mocked_manager = MagicMock()
+
+            # WHEN: An importer object is created
+            importer = OpenSongImport(mocked_manager, filenames=[])
+
+            # THEN: The importer object should not be None
+            self.assertIsNotNone(importer, 'Import should not be none')
+
+    def invalid_import_source_test(self):
+        """
+        Test OpenSongImport.do_import handles different invalid import_source values
+        """
+        # GIVEN: A mocked out SongImport class, and a mocked out "manager"
+        with patch('openlp.plugins.songs.lib.opensongimport.SongImport'):
+            mocked_manager = MagicMock()
+            mocked_import_wizard = MagicMock()
+            importer = OpenSongImport(mocked_manager, filenames=[])
+            importer.import_wizard = mocked_import_wizard
+            importer.stop_import_flag = True
+
+            # WHEN: Import source is not a list
+            for source in ['not a list', 0]:
+                importer.import_source = source
+
+                # THEN: do_import should return none and the progress bar maximum should not be set.
+                self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
+                self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False,
+                                 'setMaximum on import_wizard.progress_bar should not have been called')
+
+    def valid_import_source_test(self):
+        """
+        Test OpenSongImport.do_import handles different invalid import_source values
+        """
+        # GIVEN: A mocked out SongImport class, and a mocked out "manager"
+        with patch('openlp.plugins.songs.lib.opensongimport.SongImport'):
+            mocked_manager = MagicMock()
+            mocked_import_wizard = MagicMock()
+            importer = OpenSongImport(mocked_manager, filenames=[])
+            importer.import_wizard = mocked_import_wizard
+            importer.stop_import_flag = True
+
+            # WHEN: Import source is a list
+            importer.import_source = ['List', 'of', 'files']
+
+            # THEN: do_import should return none and the progress bar setMaximum should be called with the length of
+            #       import_source.
+            self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is a list '
+                                                    'and stop_import_flag is True')
+            mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))

=== modified file 'tests/helpers/songfileimport.py'
--- tests/helpers/songfileimport.py	2014-04-20 20:19:21 +0000
+++ tests/helpers/songfileimport.py	2014-05-01 21:49:02 +0000
@@ -33,7 +33,7 @@
 import json
 from unittest import TestCase
 
-from tests.functional import patch, MagicMock
+from tests.functional import patch, MagicMock, call
 
 
 class SongImportTestHelper(TestCase):
@@ -56,13 +56,13 @@
             'openlp.plugins.songs.lib.%s.%s.add_verse' % (self.importer_module_name, self.importer_class_name))
         self.finish_patcher = patch(
             'openlp.plugins.songs.lib.%s.%s.finish' % (self.importer_module_name, self.importer_class_name))
-        self.parse_author_patcher = patch(
-            'openlp.plugins.songs.lib.%s.%s.parse_author' % (self.importer_module_name, self.importer_class_name))
+        self.add_author_patcher = patch(
+            'openlp.plugins.songs.lib.%s.%s.add_author' % (self.importer_module_name, self.importer_class_name))
         self.song_import_patcher = patch('openlp.plugins.songs.lib.%s.SongImport' % self.importer_module_name)
         self.mocked_add_copyright = self.add_copyright_patcher.start()
         self.mocked_add_verse = self.add_verse_patcher.start()
         self.mocked_finish = self.finish_patcher.start()
-        self.mocked_parse_author = self.parse_author_patcher.start()
+        self.mocked_add_author = self.add_author_patcher.start()
         self.mocked_song_importer = self.song_import_patcher.start()
         self.mocked_manager = MagicMock()
         self.mocked_import_wizard = MagicMock()
@@ -75,7 +75,7 @@
         self.add_copyright_patcher.stop()
         self.add_verse_patcher.stop()
         self.finish_patcher.stop()
-        self.parse_author_patcher.stop()
+        self.add_author_patcher.stop()
         self.song_import_patcher.stop()
 
     def load_external_result_data(self, file_name):
@@ -112,14 +112,17 @@
         self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
         self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title))
         for author in author_calls:
-            self.mocked_parse_author.assert_any_call(author)
+            self.mocked_add_author.assert_any_call(author)
         if song_copyright:
             self.mocked_add_copyright.assert_called_with(song_copyright)
         if ccli_number:
             self.assertEqual(importer.ccli_number, ccli_number,
                              'ccli_number for %s should be %s' % (source_file_name, ccli_number))
+        expected_calls = []
         for verse_text, verse_tag in add_verse_calls:
             self.mocked_add_verse.assert_any_call(verse_text, verse_tag)
+            expected_calls.append(call(verse_text, verse_tag))
+        self.mocked_add_verse.assert_has_calls(expected_calls, any_order=False)
         if topics:
             self.assertEqual(importer.topics, topics, 'topics for %s should be %s' % (source_file_name, topics))
         if comments:
@@ -132,7 +135,7 @@
             self.assertEqual(importer.song_number, song_number,
                              'song_number for %s should be %s' % (source_file_name, song_number))
         if verse_order_list:
-            self.assertEqual(importer.verse_order_list, [],
+            self.assertEqual(importer.verse_order_list, verse_order_list,
                              'verse_order_list for %s should be %s' % (source_file_name, verse_order_list))
         self.mocked_finish.assert_called_with()
 

=== added directory 'tests/resources/opensongsongs'
=== added file 'tests/resources/opensongsongs/Amazing Grace'
--- tests/resources/opensongsongs/Amazing Grace	1970-01-01 00:00:00 +0000
+++ tests/resources/opensongsongs/Amazing Grace	2014-05-01 21:49:02 +0000
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<song>
+  <title>Amazing Grace (Demonstration)</title>
+  <author>John Newton, Edwin Excell &amp; John P. Rees</author>
+  <copyright>Public Domain </copyright>
+  <presentation>V1 V2 V3 V4 V5</presentation>
+  <capo print="false"></capo>
+  <tempo></tempo>
+  <ccli>22025</ccli>
+  <theme>God: Assurance/Grace/Salvation</theme>
+  <alttheme>Worship: Praise</alttheme>
+  <user1> </user1>
+  <user2> </user2>
+  <user3> </user3>
+  <lyrics>[V]
+;Test the chords format
+;Chords beging with .
+;Verses begin with their verse number
+;Link words with _
+;Comments begin with ;
+.       D              D7           G          D     
+1A______ma________zing grace! How   sweet the  sound!
+2'Twas  grace     that taught my    heart to   fear,
+3The    Lord      has  pro____mised good  to   me,
+4Thro'  ma________ny   dan____gers, toils and  snares
+5When   we've     been there  ten   thou__sand years,
+
+.       Bm         E           A       A7
+1That   saved a    wretch like me!
+2And    grace my   fears  re___lieved.
+3His    Word  my   hope   se___cures.
+4I      have  al___rea____dy   come.
+5Bright shi___ning as     the  sun,
+
+.      D           D7           G           D     
+1I     once  was   lost,   but  now   am    found;
+2How   pre___cious did     that grace ap____pear,
+3He    will  my    shield  and  por___tion  be
+4'Tis  grace that  brought me   safe  thus  far,
+5We've no    less  days    to   sing  God's praise,
+
+.     Bm          A        G      D 
+1Was  blind, but  now   I  see.
+2The  hour   I    first be_lieved.
+3As   long   as   life  en_dures.
+4And  grace  will lead  me home.
+5Than when   we   first be_gun.
+
+</lyrics>
+  <hymn_number>Demonstration Songs 0</hymn_number>
+  <key></key>
+  <aka></aka>
+  <key_line></key_line>
+  <time_sig></time_sig>
+  <style index="default_style"></style>
+</song>
\ No newline at end of file

=== added file 'tests/resources/opensongsongs/Amazing Grace.json'
--- tests/resources/opensongsongs/Amazing Grace.json	1970-01-01 00:00:00 +0000
+++ tests/resources/opensongsongs/Amazing Grace.json	2014-05-01 21:49:02 +0000
@@ -0,0 +1,42 @@
+{
+    "authors": [
+        "John Newton",
+        "Edwin Excell",
+        "John P. Rees"
+    ],
+    "ccli_number": 22025,
+    "comments": "\n\n\n",
+    "copyright": "Public Domain ",
+    "song_book_name": "Demonstration Songs",
+    "song_number": 0,
+    "title": "Amazing Grace (Demonstration)",
+    "topics": [
+        "Assurance",
+        "Grace",
+        "Praise",
+        "Salvation"
+    ],
+    "verse_order_list": [],
+    "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"
+        ],
+        [
+            "The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
+            "v3"
+        ],
+        [
+            "Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
+            "v4"
+        ],
+        [
+            "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.",
+            "v5"
+        ]
+    ]
+}
\ No newline at end of file

=== added file 'tests/resources/opensongsongs/Beautiful Garden Of Prayer'
--- tests/resources/opensongsongs/Beautiful Garden Of Prayer	1970-01-01 00:00:00 +0000
+++ tests/resources/opensongsongs/Beautiful Garden Of Prayer	2014-05-01 21:49:02 +0000
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<song>
+  <title>Beautiful Garden Of Prayer (Demonstration)</title>
+  <author>Eleanor Allen Schroll &amp; James H. Fillmore</author>
+  <copyright>Public Domain </copyright>
+  <presentation>V1 C V2 C V3 C</presentation>
+  <capo print="false"></capo>
+  <tempo></tempo>
+  <ccli>60252</ccli>
+  <theme>God: Prayer/Devotion</theme>
+  <alttheme>Prayer: Prayer/Devotion</alttheme>
+  <user1></user1>
+  <user2></user2>
+  <user3></user3>
+  <lyrics>
+;Test breaks and newlines
+;A single | on the end of a line adds an extra \n
+;Blank lines are ignored, even with a space prefix
+;We also check that the chorus is added after the verses, despite the order in the file
+[V1]
+ There's a garden where Jesus is waiting,
+ 
+ There's a place that is wondrously fair.
+ For it glows with the light of His presence,|
+ 'Tis the beautiful garden of prayer.
+
+;A double || on a line adds a new slide
+[C]
+ O the beautiful garden, the garden of prayer,
+ O the beautiful garden of prayer.
+ There my Savior awaits, and He opens the gates
+ ||
+ To the beautiful garden of prayer.
+
+;A double || on the end of a line adds a new slide
+[V2]
+ There's a garden where Jesus is waiting,
+ And I go with my burden and care.
+ Just to learn from His lips, words of comfort,||
+ In the beautiful garden of prayer.
+
+;A single | on a line adds just one line break
+[V3]
+ There's a garden where Jesus is waiting,
+ And He bids you to come meet Him there,
+ Just to bow and receive a new blessing,
+ |
+ In the beautiful garden of prayer.
+</lyrics>
+  <hymn_number>DS0</hymn_number>
+  <key></key>
+  <aka></aka>
+  <key_line></key_line>
+  <time_sig></time_sig>
+  <style index="default_style"></style>
+</song>
\ No newline at end of file

=== added file 'tests/resources/opensongsongs/Beautiful Garden Of Prayer.json'
--- tests/resources/opensongsongs/Beautiful Garden Of Prayer.json	1970-01-01 00:00:00 +0000
+++ tests/resources/opensongsongs/Beautiful Garden Of Prayer.json	2014-05-01 21:49:02 +0000
@@ -0,0 +1,35 @@
+{
+    "authors": [
+        "Eleanor Allen Schroll",
+        "James H. Fillmore"
+    ],
+    "ccli_number": 60252,
+    "comments": "",
+    "copyright": "Public Domain ",
+    "song_book_name": "DS",
+    "song_number": 0,
+    "title": "Beautiful Garden Of Prayer (Demonstration)",
+    "topics": [
+        "Devotion",
+        "Prayer"
+    ],
+    "verse_order_list": ["v1", "c1", "v2", "c1", "v3", "c1"],
+    "verses": [
+        [
+            "There's a garden where Jesus is waiting,\nThere's a place that is wondrously fair.\nFor it glows with the light of His presence,\n\n'Tis the beautiful garden of prayer.",
+            "v1"
+        ],
+        [
+            "There's a garden where Jesus is waiting,\nAnd I go with my burden and care.\nJust to learn from His lips, words of comfort,\n[---]\nIn the beautiful garden of prayer.",
+            "v2"
+        ],
+        [
+            "There's a garden where Jesus is waiting,\nAnd He bids you to come meet Him there,\nJust to bow and receive a new blessing,\n\nIn the beautiful garden of prayer.",
+            "v3"
+        ],
+        [
+            "O the beautiful garden, the garden of prayer,\nO the beautiful garden of prayer.\nThere my Savior awaits, and He opens the gates\n[---]\nTo the beautiful garden of prayer.",
+            "c1"
+        ]
+    ]
+}
\ No newline at end of file

=== added file 'tests/resources/opensongsongs/One, Two, Three, Four, Five'
--- tests/resources/opensongsongs/One, Two, Three, Four, Five	1970-01-01 00:00:00 +0000
+++ tests/resources/opensongsongs/One, Two, Three, Four, Five	2014-05-01 21:49:02 +0000
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<song>
+  <title>12345</title>
+  <author>Traditional</author>
+  <copyright>Public Domain </copyright>
+  <presentation>T</presentation>
+  <capo print="false"></capo>
+  <ccli></ccli>
+  <tempo></tempo>
+  <theme></theme>
+  <alttheme></alttheme>
+  <user1></user1>
+  <user2></user2>
+  <user3></user3>
+  <lyrics>
+;Test [T]ag element - should be turned into [o]ther
+;And lines beginning with numbers
+;And a title that contains only numeric characters
+;That isdiffernt to the filename
+;And most elements are empty
+[T]
+ 1, 2, 3, 4, 5,
+ Once I caught a fish alive.
+ 6, 7, 8, 9, 10,
+ Then I let it go again.
+ 
+ Why did you let it go?
+ Because it bit my finger so.
+ Which finger did it bite?
+ This little finger on my right.
+</lyrics>
+  <hymn_number></hymn_number>
+  <key></key>
+  <aka></aka>
+  <key_line></key_line>
+  <time_sig></time_sig>
+  <style index="default_style"></style>
+</song>
\ No newline at end of file

=== added file 'tests/resources/opensongsongs/One, Two, Three, Four, Five.json'
--- tests/resources/opensongsongs/One, Two, Three, Four, Five.json	1970-01-01 00:00:00 +0000
+++ tests/resources/opensongsongs/One, Two, Three, Four, Five.json	2014-05-01 21:49:02 +0000
@@ -0,0 +1,17 @@
+{
+    "authors": [
+        "Traditional"
+    ],
+    "comments": "",
+    "copyright": "Public Domain ",
+    "title": "12345",
+    "topics": [
+    ],
+    "verse_order_list": ["o1"],
+    "verses": [
+        [
+            "1, 2, 3, 4, 5,\nOnce I caught a fish alive.\n6, 7, 8, 9, 10,\nThen I let it go again.\nWhy did you let it go?\nBecause it bit my finger so.\nWhich finger did it bite?\nThis little finger on my right.",
+            "o1"
+        ]
+    ]
+}
\ No newline at end of file


Follow ups