diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 7dcb98de5..d03bdefd6 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -278,7 +278,7 @@ class VerseType(object): 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: diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index bee0e0f0d..d57c8fbcc 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -527,15 +527,7 @@ class SongMediaItem(MediaManagerItem): add_song = True if search_results: for song in search_results: - author_list = item.data_string['authors'] - same_authors = True - for author in song.authors: - if author.display_name in author_list: - author_list = author_list.replace(author.display_name, '', 1) - else: - same_authors = False - break - if same_authors and author_list.strip(', ') == '': + if self._authors_match(song, item.data_string['authors']): add_song = False edit_id = song.id break @@ -561,6 +553,23 @@ class SongMediaItem(MediaManagerItem): self.generate_footer(item, song) return item + def _authors_match(self, song, authors): + """ + Checks whether authors from a song in the database match the authors of the song to be imported. + + :param song: A list of authors from the song in the database + :param authors: A string with authors from the song to be imported + :return: True when Authors do match, else False. + """ + author_list = authors.split(', ') + for author in song.authors: + if author.display_name in author_list: + author_list.remove(author.display_name) + else: + return False + # List must be empty at the end + return not author_list + def search(self, string, show_error): """ Search for some songs diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index bee0989a0..3d9733dd8 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -45,11 +45,11 @@ class OpenSongImport(SongImport): """ 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 `_ page on the OpenSong web site. However, it doesn't describe the 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 @@ class OpenSongImport(SongImport): 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 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 @@ class OpenSongImport(SongImport): """ 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 @@ class OpenSongImport(SongImport): '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 @@ class OpenSongImport(SongImport): 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 @@ class OpenSongImport(SongImport): # 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 @@ class OpenSongImport(SongImport): 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 @@ class OpenSongImport(SongImport): # 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) diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 754288546..b8fcc604b 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -188,13 +188,61 @@ class SongImport(QtCore.QObject): 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 diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index 22291c6a6..bc22a4577 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -153,3 +153,52 @@ class TestMediaItem(TestCase, TestMixin): # THEN: The songbook should be in the footer self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12']) + + def authors_match_test(self): + """ + Test the author matching when importing a song from a service + """ + # GIVEN: A song and a string with authors + song = MagicMock() + song.authors = [] + author = MagicMock() + author.display_name = "Hans Wurst" + song.authors.append(author) + author2 = MagicMock() + author2.display_name = "Max Mustermann" + song.authors.append(author2) + # There are occasions where an author appears twice in a song (with different types). + # We need to make sure that this case works (lp#1313538) + author3 = MagicMock() + author3.display_name = "Max Mustermann" + song.authors.append(author3) + authors_str = "Hans Wurst, Max Mustermann, Max Mustermann" + + # WHEN: Checking for matching + result = self.media_item._authors_match(song, authors_str) + + # THEN: They should match + self.assertTrue(result, "Authors should match") + + def authors_dont_match_test(self): + # GIVEN: A song and a string with authors + song = MagicMock() + song.authors = [] + author = MagicMock() + author.display_name = "Hans Wurst" + song.authors.append(author) + author2 = MagicMock() + author2.display_name = "Max Mustermann" + song.authors.append(author2) + # There are occasions where an author appears twice in a song (with different types). + # We need to make sure that this case works (lp#1313538) + author3 = MagicMock() + author3.display_name = "Max Mustermann" + song.authors.append(author3) + + # WHEN: An author is missing in the string + authors_str = "Hans Wurst, Max Mustermann" + result = self.media_item._authors_match(song, authors_str) + + # THEN: They should not match + self.assertFalse(result, "Authors should not match") diff --git a/tests/functional/openlp_plugins/songs/test_opensongimport.py b/tests/functional/openlp_plugins/songs/test_opensongimport.py new file mode 100644 index 000000000..70d3b342a --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_opensongimport.py @@ -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)) diff --git a/tests/helpers/songfileimport.py b/tests/helpers/songfileimport.py index 5364c2c3b..36beef6e5 100644 --- a/tests/helpers/songfileimport.py +++ b/tests/helpers/songfileimport.py @@ -33,7 +33,7 @@ song files from third party applications. 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 @@ class SongImportTestHelper(TestCase): '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 @@ class SongImportTestHelper(TestCase): 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 @@ class SongImportTestHelper(TestCase): 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 @@ class SongImportTestHelper(TestCase): 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() diff --git a/tests/resources/opensongsongs/Amazing Grace b/tests/resources/opensongsongs/Amazing Grace new file mode 100644 index 000000000..97062dc21 --- /dev/null +++ b/tests/resources/opensongsongs/Amazing Grace @@ -0,0 +1,56 @@ + + + Amazing Grace (Demonstration) + John Newton, Edwin Excell & John P. Rees + Public Domain + V1 V2 V3 V4 V5 + + + 22025 + God: Assurance/Grace/Salvation + Worship: Praise + + + + [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. + + + Demonstration Songs 0 + + + + + + \ No newline at end of file diff --git a/tests/resources/opensongsongs/Amazing Grace.json b/tests/resources/opensongsongs/Amazing Grace.json new file mode 100644 index 000000000..97b8c77b7 --- /dev/null +++ b/tests/resources/opensongsongs/Amazing Grace.json @@ -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 diff --git a/tests/resources/opensongsongs/Beautiful Garden Of Prayer b/tests/resources/opensongsongs/Beautiful Garden Of Prayer new file mode 100644 index 000000000..29cd26cc8 --- /dev/null +++ b/tests/resources/opensongsongs/Beautiful Garden Of Prayer @@ -0,0 +1,56 @@ + + + Beautiful Garden Of Prayer (Demonstration) + Eleanor Allen Schroll & James H. Fillmore + Public Domain + V1 C V2 C V3 C + + + 60252 + God: Prayer/Devotion + Prayer: Prayer/Devotion + + + + +;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. + + DS0 + + + + + + \ No newline at end of file diff --git a/tests/resources/opensongsongs/Beautiful Garden Of Prayer.json b/tests/resources/opensongsongs/Beautiful Garden Of Prayer.json new file mode 100644 index 000000000..392bbaa18 --- /dev/null +++ b/tests/resources/opensongsongs/Beautiful Garden Of Prayer.json @@ -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 diff --git a/tests/resources/opensongsongs/One, Two, Three, Four, Five b/tests/resources/opensongsongs/One, Two, Three, Four, Five new file mode 100644 index 000000000..cc6bc107f --- /dev/null +++ b/tests/resources/opensongsongs/One, Two, Three, Four, Five @@ -0,0 +1,38 @@ + + + 12345 + Traditional + Public Domain + T + + + + + + + + + +;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. + + + + + + + + \ No newline at end of file diff --git a/tests/resources/opensongsongs/One, Two, Three, Four, Five.json b/tests/resources/opensongsongs/One, Two, Three, Four, Five.json new file mode 100644 index 000000000..30fe71c64 --- /dev/null +++ b/tests/resources/opensongsongs/One, Two, Three, Four, Five.json @@ -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