Implemented song creation from OpenSong xml format incl sample files and tests

bzr-revno: 78
This commit is contained in:
Carsten Tinggaard 2008-11-04 21:24:26 +00:00
parent 893cba185c
commit 2278ba9a6e
7 changed files with 476 additions and 38 deletions

View File

@ -56,6 +56,122 @@ _blankSongXml = \
</Song>
'''
_blankOpenSongXml = \
'''<?xml version="1.0" encoding="UTF-8"?>
<song>
<title></title>
<author></author>
<copyright></copyright>
<presentation></presentation>
<ccli></ccli>
<lyrics></lyrics>
<theme></theme>
<alttheme></alttheme>
</song>
'''
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

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<song>
<title>Amazing Grace</title>
<author>John Newton</author>
<copyright>1982 Jubilate Hymns Limited</copyright>
<presentation></presentation>
<capo print=""></capo>
<tempo></tempo>
<timesig></timesig>
<ccli>1037882</ccli>
<theme>God: Attributes</theme>
<alttheme></alttheme>
<user1></user1>
<user2></user2>
<user3></user3>
<lyrics>[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!</lyrics></song>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?><song><title>På en fjern ensom høj</title><author></author><copyright></copyright><ccli></ccli><lyrics>[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.
</lyrics><presentation>V1 C1 V2 C2 V3 C3 V4 C4</presentation></song>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<song>
<title>The Solid Rock</title>
<author>Edward Mote and John B. Dykes</author>
<copyright>Public Domain</copyright>
<presentation>V1 C V2 C V3 C V4 C</presentation>
<ccli>101740</ccli>
<theme>Christ: Victory</theme>
<alttheme>Fruit: Peace/Comfort</alttheme>
<lyrics>[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.</lyrics></song>

View File

@ -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) == [""])
if '__main__' == __name__:
r = Test_Basic()
r.test_asString()

View File

@ -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 = \
'''<?xml version="1.0" encoding="UTF-8"?>
<song>
<title></title>
<author></author>
<copyright></copyright>
<presentation></presentation>
<ccli></ccli>
<theme></theme>
<lyrics>[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</lyrics>
</song>
'''
_sample2 = \
'''<?xml version="1.0" encoding="UTF-8"?>
<song>
<title></title>
<author></author>
<copyright></copyright>
<presentation></presentation>
<ccli></ccli>
<theme></theme>
<lyrics>[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</lyrics>
</song>
'''
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()

View File

@ -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)