From 2278ba9a6ec44dc8421e14a56538771fb6f3b563 Mon Sep 17 00:00:00 2001 From: Carsten Tinggaard Date: Tue, 4 Nov 2008 21:24:26 +0000 Subject: [PATCH] Implemented song creation from OpenSong xml format incl sample files and tests bzr-revno: 78 --- openlp/song/song.py | 197 ++++++++++++++++-- openlp/song/test/data_opensong/Amazing Grace | 38 ++++ .../test/data_opensong/PÃ¥ en fjern ensom høj | 56 +++++ openlp/song/test/data_opensong/The Solid Rock | 28 +++ openlp/song/test/test_song_basic.py | 15 +- openlp/song/test/test_song_opensong.py | 120 ++++++++++- openlp/song/test/test_song_verse.py | 60 ++++-- 7 files changed, 476 insertions(+), 38 deletions(-) create mode 100644 openlp/song/test/data_opensong/Amazing Grace create mode 100644 openlp/song/test/data_opensong/PÃ¥ en fjern ensom høj create mode 100644 openlp/song/test/data_opensong/The Solid Rock diff --git a/openlp/song/song.py b/openlp/song/song.py index a66c4b9f5..70e0d9887 100644 --- a/openlp/song/song.py +++ b/openlp/song/song.py @@ -56,6 +56,122 @@ _blankSongXml = \ ''' +_blankOpenSongXml = \ +''' + + + + + + + + + + +''' + +class _OpenSong(XmlRootClass): + """Class for import of OpenSogn""" + + def __init__(self, xmlContent = None): + """Initialize from given xml content""" + super(_OpenSong, self).__init__() + self.FromBuffer(xmlContent) + + def _reset(self): + """Reset all song attributes""" + global _blankOpenSongXml + self._setFromXml(_blankOpenSongXml, "song") + + def FromBuffer(self, xmlContent): + """Initialize from buffer(string) with xml content""" + self._reset() + if xmlContent != None : + self._setFromXml(xmlContent, "song") + + def GetAuthorList(self): + """Convert author field to an authorlist + + in OpenSong an author list may be separated by '/' + return as a string + """ + res = [] + if self.author != None : + lst = self.author.split(' and ') + for l in lst : + res.append(l.strip()) + s = ", ".join(res) + return s + + def GetCategoryArray(self): + """Convert theme and alttheme into categoryArray + + return as a string + """ + res = [] + if self.theme != None : + res.append(self.theme) + if self.alttheme != None : + res.append(self.alttheme) + s = ", ".join(res) + return s + + def _reorderVerse(self, tag, tmpVerse): + """Reorder the verse in case of first char is a number + + tag -- the tag of this verse / verse group + tmpVerse -- list of strings + """ + res = [] + for c in '1234567890 ': + tagPending = True + for l in tmpVerse : + if l.startswith(c) : + if tagPending : + tagPending = False + t = tag.strip("[]").lower() + if 'v' == t : + newtag = "Verse" + elif 'c' == t : + newtag = "Chorus" + else : + #TODO: what is the tags for bridge, pre-chorus? + newtag = t + s = ("# %s %s"%(newtag, c)).rstrip() + res.append(s) + res.append(l[1:]) + if (len(l) == 0) and (not tagPending) : + res.append(l) + return res + + def GetLyrics(self): + """Convert the lyrics to openlp lyrics format + + return as list of strings + """ + lyrics = self.lyrics.split("\n") + tmpVerse = [] + finalLyrics = [] + tag = "" + for l in lyrics: + line = l.rstrip() + if not line.startswith('.') : + # drop all chords + tmpVerse.append(line) + if len(line) > 0 : + if line.startswith('['): + tag = line + else : + r = self._reorderVerse(tag, tmpVerse) + finalLyrics.extend(r) + tag = "" + tmpVerse = [] + # catch up final verse + r = self._reorderVerse(tag, tmpVerse) + finalLyrics.extend(r) + return finalLyrics + + class Song(XmlRootClass) : """Class for handling song properties""" @@ -85,12 +201,43 @@ class Song(XmlRootClass) : self._reset() if xmlContent != None : self._setFromXml(xmlContent, "Song") + self._parseLyrics() def _reset(self): """Reset all song attributes""" global _blankSongXml + self.slideList = [] self._setFromXml(_blankSongXml, "Song") + def FromOpenSongBuffer(self, xmlcontent): + """Initialize from buffer(string) of xml lines in opensong format""" + self._reset() + opensong = _OpenSong(xmlcontent) + if opensong.title != None: + self.SetTitle(opensong.title) + if opensong.copyright != None : + self.SetCopyright(opensong.copyright) + if opensong.presentation != None: + self.SetVerseOrder(opensong.presentation) + if opensong.ccli != None: + self.SetSongCcliNo(opensong.ccli) + self.SetAuthorList(opensong.GetAuthorList()) + self.SetCategoryArray(opensong.GetCategoryArray()) + self.SetLyrics(opensong.GetLyrics()) + + def FromOpenSongFile(self, xmlfilename): + """Initialize from file containing xml + + xmlfilename -- path to xml file + """ + lst = [] + f = open(xmlfilename, 'r') + for line in f : + lst.append(line) + f.close() + xml = "".join(lst) + self.FromOpenSongBuffer(xml) + def _RemovePunctuation(self, title): """Remove the puntuation chars from title @@ -320,25 +467,43 @@ class Song(XmlRootClass) : def SetLyrics(self, lyrics): """Set the lyrics as a list of strings""" - # TODO: check font formatting self.lyrics = lyrics + self._parseLyrics() - def GetNumberOfVerses(self): - """Return the number of verses in the song (int)""" - numOfVerses = 0 + def _parseLyrics(self): + """Parse lyrics into the slidelist""" + # TODO: check font formatting + self.slideList = [] + tmpSlide = [] + metContent = False + for l in self.lyrics : + if len(l) > 0 : + metContent = True + tmpSlide.append(l) + else : + if metContent : + metContent = False + self.slideList.append(tmpSlide) + tmpSlide = [] # - return numOfVerses + if len(tmpSlide) > 0: + self.slideList.append(tmpSlide) + + def GetNumberOfSlides(self): + """Return the number of slides in the song (int)""" + numOfSlides = len(self.slideList) + return numOfSlides - def GetPreviewVerse(self, verseNumber): - """Return the preview text for specified verse number + def GetPreviewSlide(self, slideNumber): + """Return the preview text for specified slide number - verseNumber -- 0: all verses, 1..n : specific verse + slideNumber -- 0: all slides, 1..n : specific slide a list of strings are returned """ return [] - def GetRenderVerse(self, verseNumber): - """Return the verse to be rendered including the additional + def GetRenderSlide(self, slideNumber): + """Return the slide to be rendered including the additional properties Returns a list as: @@ -347,29 +512,31 @@ class Song(XmlRootClass) : authorlist (string), copyright (string), cclino (string), - lyric-verse as a list of strings] + lyric-part as a list of strings] """ res = [] - res.append(self.GetTheme()) if self.showTitle : title = self.GetTitle() else : title = "" - res.append(title) if self.showAuthorList : author = self.GetAuthorList(True) else : author = "" - res.append(author) if self.showCopyright : cpright = self.GetCopyright() else : cpright = "" - res.append(cpright) if self.showSongCcliNo : ccli = self.GetSongCcliNo() else : ccli = "" + # examine the slide for a theme + res.append(self.GetTheme()) + res.append(title) + res.append(author) + res.append(cpright) res.append(ccli) + # append the correct slide return res diff --git a/openlp/song/test/data_opensong/Amazing Grace b/openlp/song/test/data_opensong/Amazing Grace new file mode 100644 index 000000000..06f3edd92 --- /dev/null +++ b/openlp/song/test/data_opensong/Amazing Grace @@ -0,0 +1,38 @@ + + + Amazing Grace + John Newton + 1982 Jubilate Hymns Limited + + + + + 1037882 + God: Attributes + + + + + [V1] +. D D7 G D Bm E A A7 + Amazing grace how sweet the sound that saved a wretch like me; +. D D7 G D Bm A G D + I once was lost but now I'm found, was blind but now I see. + +[V2] +. D D7 G D Bm E A A7 + Twas grace that taught my heart to fear, and grace my fears relieved; +. D D7 G D Bm A G D + How precious did that grace appear the hour I first believed! + +[V3] +. D D7 G D Bm E A A7 + Through many dangers, toils, and snares I have already come; +. D D7 G D Bm A G D + 'Tis grace that brought me safe thus far and grace will lead me home. + +[V4] +. D D7 G D Bm E A A7 + When we've been there ten thousand years bright shining as the sun; +. D D7 G D Bm A G D + We've no less days to sing God's praise than when we'd first begun! \ No newline at end of file diff --git a/openlp/song/test/data_opensong/PÃ¥ en fjern ensom høj b/openlp/song/test/data_opensong/PÃ¥ en fjern ensom høj new file mode 100644 index 000000000..44ffca307 --- /dev/null +++ b/openlp/song/test/data_opensong/PÃ¥ en fjern ensom høj @@ -0,0 +1,56 @@ +PÃ¥ en fjern ensom høj[V1] + PÃ¥ en fjern ensom høj, + Jesu kors dyrest stod, + symbolet pÃ¥ smerte og skam. + O, jeg elsker det kors, + hvor Guds søn gjorde bod, + da forbandelsen blev lagt pÃ¥ ham. + +[C1] + Jeg vil elske det urgamle kors, + i det kraft er der sejer og sang. + Lad mig favne det hellige kors, + det med kronen ombyttes engang. + +[V2] + O, det urgamle kors, + med sin hvile og fred, + tilhyllet i verdens foragt. + Se, det hellige lam, + som pÃ¥ Golgatha stred, + og til jorden Guds nÃ¥de har bragt. + +[C2] + Jeg vil elske det urgamle kors, + i det kraft er der sejer og sang. + Lad mig favne det hellige kors, + det med kronen ombyttes engang. + +[V3] + I det urgamle kors, + i hans blod farvet rødt, + en underfuld skønhed jeg ser. + Ja, det var pÃ¥ det kors, + at han selv blev forstødt, + nu skal aldrig for dommen jeg mer. + +[C3] + Jeg vil elske det urgamle kors, + i det kraft er der sejer og sang. + Lad mig favne det hellige kors, + det med kronen ombyttes engang. + +[V4] + For det urgamle kors, + stÃ¥r mit hjerte i brand, + min plads jeg nu har ved dets fod. + Til han kalder en dag, + mig til himmelens land, + og til hvilen hos Faderen god. + +[C4] + Jeg vil elske det urgamle kors, + i det kraft er der sejer og sang. + Lad mig favne det hellige kors, + det med kronen ombyttes engang. +V1 C1 V2 C2 V3 C3 V4 C4 \ No newline at end of file diff --git a/openlp/song/test/data_opensong/The Solid Rock b/openlp/song/test/data_opensong/The Solid Rock new file mode 100644 index 000000000..bb1eecc97 --- /dev/null +++ b/openlp/song/test/data_opensong/The Solid Rock @@ -0,0 +1,28 @@ + + + The Solid Rock + Edward Mote and John B. Dykes + Public Domain + V1 C V2 C V3 C V4 C + 101740 + Christ: Victory + Fruit: Peace/Comfort + [V] +. E B A B E +1My hope is built on nothing less than Jesus' blood and righteousness; +2When darkness veils His lovely face, I rest on His un___changing grace. +3His oath, His cove_____nant, His blood|support me in the whelming flood; +4When He shall come with trumpet sound, O may I then in Him be found; +. E B A B E +1I dare not trust the sweetest frame, but wholly lean on Jesus' name. +2In every high and stormy gale, my anchor holds within the veil. +3When all around my soul gives way, He then is all my hope and stay. +4Dressed in His righteous___ness alone, fault___less to stand be_fore the throne. + +[C] +. E A + On Christ, the solid rock I stand; +. E B + All other ground is sinking sand, +. A B E + All other ground is sinking sand. \ No newline at end of file diff --git a/openlp/song/test/test_song_basic.py b/openlp/song/test/test_song_basic.py index 70e10a5b9..430668d11 100644 --- a/openlp/song/test/test_song_basic.py +++ b/openlp/song/test/test_song_basic.py @@ -38,13 +38,14 @@ class Test_Basic(object): s = Song() r = s.__str__() l = r.split("\n") - assert(len(l) == 52) + assert(len(l) == 55) def test_asString(self): """Init: Empty asString - initial values""" s = Song() r = s._get_as_string() - flag = r.endswith("__None__None__None__None__None__None__1__1__1__1__None__None__None__None__BlankSong__None_") + #print r + flag = r.endswith("__None__None__None__None__None__None__1__1__1__1__[]__None__None__None__None__BlankSong__None_") assert(flag) def test_Title1(self): @@ -60,7 +61,7 @@ class Test_Basic(object): assert(s.GetTitle() == t) assert(s.GetSearchableTitle() == t) - def test_Title2(self): + def test_Title3(self): """Set a titel with punctuation 1""" s = Song() t1 = "Hey! Come on, ya programmers*" @@ -69,7 +70,7 @@ class Test_Basic(object): assert(s.GetTitle() == t1) assert(s.GetSearchableTitle() == t2) - def test_Title3(self): + def test_Title4(self): """Set a titel with punctuation 2""" s = Song() t1 = "??#Hey! Come on, ya programmers*" @@ -78,7 +79,7 @@ class Test_Basic(object): assert(s.GetTitle() == t1) assert(s.GetSearchableTitle() == t2) - def test_Title4(self): + def test_Title5(self): """Set a title, where searchable title becomes empty - raises an exception""" s = Song() py.test.raises(SongTitleError, s.SetTitle, ",*") @@ -176,4 +177,6 @@ class Test_Basic(object): assert(s.GetCategoryArray(True) == "") assert(s.GetCategoryArray(False) == [""]) - \ No newline at end of file +if '__main__' == __name__: + r = Test_Basic() + r.test_asString() diff --git a/openlp/song/test/test_song_opensong.py b/openlp/song/test/test_song_opensong.py index e0d95166c..10cec81fc 100644 --- a/openlp/song/test/test_song_opensong.py +++ b/openlp/song/test/test_song_opensong.py @@ -1,3 +1,4 @@ +# -*- coding:iso-8859-1 -*- """ OpenLP - Open Source Lyrics Projection Copyright (c) 2008 Raoul Snyman @@ -15,17 +16,126 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ - import os import sys sys.path.append(os.path.abspath("./../../..")) +from openlp.song import * -from openlp.song import Song +__ThisDir__ = os.path.abspath(".") + + +_sample1 = \ +''' + + + + + + + + [V1] +. chord line 1 + verse 1 line 1 +. chord line 2 + verse 1 line 2 + +[V2] + verse 2 line 1 + verse 2 line 2 + +[V3] + verse 3 line 1 + verse 3 line 2 + +[C] +. chorus chord line 1 + chorus line 1 +. chorus chord line 2 + chorus line 2 + +''' + +_sample2 = \ +''' + + + + + + + + [V] +1verse 1 line 1 +2verse 2 line 1 +3verse 3 line 1 +1verse 1 line 2 +2verse 2 line 2 +3verse 3 line 2 + +[C] + chorus line 1 + chorus line 2 + +''' class Test_OpenSong(object): """Test cases for converting from OpenSong xml format to Song""" - def test_Simple(self): - """OpenSong: Simply return True""" - assert(True) + def test_sample1(self): + """OpenSong: handwritten sample1""" + s = Song() + s.FromOpenSongBuffer(_sample1) + l = s.GetLyrics() + assert(len(l) == (4*3+3)) + assert(s.GetNumberOfSlides() == 4) + + def test_sample2(self): + """OpenSong: handwritten sample2""" + s = Song() + s.FromOpenSongBuffer(_sample2) + l = s.GetLyrics() + assert(len(l) == (4*3+3)) + assert(s.GetNumberOfSlides() == 4) + + def test_file1(self): + """OpenSong: parse Amazing Grace""" + global __ThisDir__ + s = Song() + s.FromOpenSongFile("%s/data_opensong/Amazing Grace"%(__ThisDir__)) + assert(s.GetTitle() == "Amazing Grace") + assert(s.GetCopyright() == "1982 Jubilate Hymns Limited") + assert(s.GetSongCcliNo() == "1037882") + assert(s.GetCategoryArray(True) == "God: Attributes") + assert(s.GetAuthorList(True) == "John Newton") + assert(s.GetVerseOrder() == "") + assert(s.GetNumberOfSlides() == 4) + + def test_file2(self): + """OpenSong: parse The Solid Rock""" + s = Song() + s.FromOpenSongFile("%s/data_opensong/The Solid Rock"%(__ThisDir__)) + assert(s.GetTitle() == "The Solid Rock") + assert(s.GetCopyright() == "Public Domain") + assert(s.GetSongCcliNo() == "101740") + assert(s.GetCategoryArray(True) == "Christ: Victory, Fruit: Peace/Comfort") + assert(s.GetAuthorList(True) == "Edward Mote, John B. Dykes") + assert(s.GetVerseOrder() == "V1 C V2 C V3 C V4 C") + assert(s.GetNumberOfSlides() == 5) + + def atest_file3(self): + """OpenSong: parse 'På en fjern ensom høj' (danish)""" + #FIXME: problem with XML convert and danish characters + s = Song() + s.FromOpenSongFile("%s/data_opensong/På en fjern ensom høj"%(__ThisDir__)) + assert(s.GetTitle() == "På en fjern ensom høj") + assert(s.GetCopyright() == "") + assert(s.GetSongCcliNo() == "") + assert(s.GetCategoryArray(True) == "") + assert(s.GetAuthorList(True) == "") + assert(s.GetVerseOrder() == "V1 C1 V2 C2 V3 C3 V4 C4") + assert(s.GetNumberOfSlides() == 8) + +if '__main__' == __name__: + r = Test_OpenSong() + r.atest_file3() diff --git a/openlp/song/test/test_song_verse.py b/openlp/song/test/test_song_verse.py index fcdff3d0d..3dde24bb7 100644 --- a/openlp/song/test/test_song_verse.py +++ b/openlp/song/test/test_song_verse.py @@ -63,48 +63,84 @@ class Test_Verse(object): def test_title_show_noshow(self): """Test the show title flag""" s = self.stdSong() - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) s.SetShowTitle(False) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r, 1) s.SetShowTitle(True) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) def test_author_show_noshow(self): """Test the show author flag""" s = self.stdSong() - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) s.SetShowAuthorList(False) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r, 2) s.SetShowAuthorList(True) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) def test_copyright_show_noshow(self): """Test the show copyright flag""" s = self.stdSong() - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) s.SetShowCopyright(False) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r, 3) s.SetShowCopyright(True) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) def test_ccli_show_noshow(self): """Test the show copyright flag""" s = self.stdSong() - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) s.SetShowSongCcliNo(False) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r, 4) s.SetShowSongCcliNo(True) - r = s.GetRenderVerse(1) + r = s.GetRenderSlide(1) self.check_allfields(r) + + def test_verse1(self): + """Test an empty verse list""" + s = Song() + s.SetLyrics([]) + assert(s.GetNumberOfSlides() == 0) + + def test_verse2(self): + """Test a list with an empty string""" + s = Song() + s.SetLyrics([""]) + assert(s.GetNumberOfSlides() == 0) + + def test_verse3a(self): + """Test a one liner song""" + s = Song() + s.SetLyrics(["Single verse"]) + assert(s.GetNumberOfSlides() == 1) + + def test_verse3b(self): + """Test a one liner song""" + s = Song() + s.SetLyrics(["", "Single verse"]) + assert(s.GetNumberOfSlides() == 1) + + def test_verse3c(self): + """Test a one liner song""" + s = Song() + s.SetLyrics(["", "Single verse", "", ""]) + assert(s.GetNumberOfSlides() == 1) + + def test_verse3d(self): + """Test a one liner song""" + s = Song() + s.SetLyrics(["", "# Verse", "", ""]) + assert(s.GetNumberOfSlides() == 1) \ No newline at end of file