← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~j-corwin/openlp/sof into lp:openlp

 

Jonathan Corwin has proposed merging lp:~j-corwin/openlp/sof into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)


Songs of Fellowship RTF Import
User interface is basic, due to Raoul's plans to redo the song import screens.
-- 
https://code.launchpad.net/~j-corwin/openlp/sof/+merge/22695
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/plugins/songs/lib/__init__.py'
--- openlp/plugins/songs/lib/__init__.py	2010-03-21 23:58:01 +0000
+++ openlp/plugins/songs/lib/__init__.py	2010-04-02 14:15:26 +0000
@@ -26,3 +26,5 @@
 from manager import SongManager
 from songstab import SongsTab
 from mediaitem import SongMediaItem
+from sofimport import SofImport
+from songimport import SongImport

=== modified file 'openlp/plugins/songs/lib/manager.py'
--- openlp/plugins/songs/lib/manager.py	2010-03-21 23:58:01 +0000
+++ openlp/plugins/songs/lib/manager.py	2010-04-02 14:15:26 +0000
@@ -133,6 +133,12 @@
         """
         return self.session.query(Author).get(id)
 
+    def get_author_by_name(self, name):
+        """
+        Get author by display name
+        """
+        return self.session.query(Author).filter_by(display_name=name).first() 
+
     def save_author(self, author):
         """
         Save the Author and refresh the cache
@@ -172,6 +178,12 @@
         """
         return self.session.query(Topic).get(id)
 
+    def get_topic_by_name(self, name):
+        """
+        Get topic by name
+        """
+        return self.session.query(Topic).filter_by(name=name).first() 
+
     def save_topic(self, topic):
         """
         Save the Topic
@@ -211,6 +223,12 @@
         """
         return self.session.query(Book).get(id)
 
+    def get_book_by_name(self, name):
+        """
+        Get book by name
+        """
+        return self.session.query(Book).filter_by(name=name).first() 
+
     def save_book(self, book):
         """
         Save the Book

=== added file 'openlp/plugins/songs/lib/sofimport.py'
--- openlp/plugins/songs/lib/sofimport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/sofimport.py	2010-04-02 14:15:26 +0000
@@ -0,0 +1,515 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2010 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael      #
+# Gorven, Scott Guerrieri, Christian Richter, Maikel Stuivenberg, Martin      #
+# Thompson, Jon Tibble, Carsten Tinggaard                                     #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+
+# OOo API documentation:
+# http://wiki.services.openoffice.org/wiki/Documentation/BASIC_Guide/Structure_of_Text_Documents
+# http://wiki.services.openoffice.org/wiki/Documentation/DevGuide/Text/Iterating_over_Text
+# http://www.oooforum.org/forum/viewtopic.phtml?t=14409
+# http://wiki.services.openoffice.org/wiki/Python
+
+import re
+import os
+import time
+from PyQt4 import QtCore
+from songimport import SongImport
+
+if os.name == u'nt':
+    from win32com.client import Dispatch
+    BOLD = 150.0
+    ITALIC = 2
+    PAGE_BEFORE = 4
+    PAGE_AFTER = 5
+    PAGE_BOTH = 6
+else:
+    import uno
+    from com.sun.star.beans import PropertyValue
+    from com.sun.star.awt.FontWeight import BOLD
+    from com.sun.star.awt.FontSlant import ITALIC
+    from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER, PAGE_BOTH
+
+class SofImport(object):
+    """
+    Import songs provided on disks with the Songs of Fellowship music books
+    VOLS1_2.RTF, sof3words.rtf and sof4words.rtf
+    
+    Use OpenOffice.org Writer for processing the rtf file
+
+    The three books are not only inconsistant with each other, they are 
+    inconsistant in themselves too with their formatting. Not only this, but
+    the 1+2 book does not space out verses correctly. This script attempts
+    to sort it out, but doesn't get it 100% right. But better than having to 
+    type them all out!
+
+    It attempts to detect italiced verses, and treats these as choruses in
+    the verse ordering. Again not perfect, but a start.
+    """
+    def __init__(self, songmanager):
+        """
+        Initialise the class. Requires a songmanager class which is passed
+        to SongImport for writing song to disk
+        """
+        self.song = None
+        self.manager = songmanager
+
+    def import_sof(self, filename):
+        self.start_ooo()
+        self.open_ooo_file(filename)
+        self.process_doc()
+        self.close_ooo()
+
+    def start_ooo(self):
+        """
+        Start OpenOffice.org process
+        TODO: The presentation/Impress plugin may already have it running
+        """
+        if os.name == u'nt':
+            manager = Dispatch(u'com.sun.star.ServiceManager')
+            manager._FlagAsMethod(u'Bridge_GetStruct')
+            manager._FlagAsMethod(u'Bridge_GetValueObject')
+            self.desktop = manager.createInstance(u'com.sun.star.frame.Desktop')
+        else:
+            cmd = u'openoffice.org -nologo -norestore -minimized -invisible ' \
+                + u'-nofirststartwizard ' \
+                + '-accept="socket,host=localhost,port=2002;urp;"'
+            process = QtCore.QProcess()
+            process.startDetached(cmd)
+            process.waitForStarted()
+            context = uno.getComponentContext()
+            resolver = context.ServiceManager.createInstanceWithContext(
+                u'com.sun.star.bridge.UnoUrlResolver', context)
+            ctx = None
+            loop = 0
+            while ctx is None and loop < 5:
+                try:
+                    ctx = resolver.resolve(u'uno:socket,host=localhost,' \
+                        + 'port=2002;urp;StarOffice.ComponentContext')
+                except:
+                    pass
+                time.sleep(1)
+                loop += 1
+            manager = ctx.ServiceManager
+            self.desktop = manager.createInstanceWithContext(
+                "com.sun.star.frame.Desktop", ctx )
+            
+    def open_ooo_file(self, filepath):
+        """
+        Open the passed file in OpenOffice.org Writer
+        """
+        if os.name == u'nt':
+            url = u'file:///' + filepath.replace(u'\\', u'/')
+            url = url.replace(u':', u'|').replace(u' ', u'%20')
+        else:
+            url = uno.systemPathToFileUrl(filepath)
+        properties = []
+        properties = tuple(properties)
+        self.document = self.desktop.loadComponentFromURL(url, u'_blank',
+            0, properties)
+
+    def close_ooo(self):
+        """
+        Close down OpenOffice.org.
+        TODO: Further checks that it have other docs open, e.g. Impress!
+        """
+        self.desktop.terminate()
+
+    def process_doc(self):
+        """
+        Process the RTF file, a paragraph at a time
+        """            
+        self.blanklines = 0
+        self.new_song()
+        paragraphs = self.document.getText().createEnumeration()
+        while paragraphs.hasMoreElements():
+            paragraph = paragraphs.nextElement()
+            if paragraph.supportsService("com.sun.star.text.Paragraph"):
+                self.process_paragraph(paragraph)
+        if self.song:
+            self.song.finish()
+            self.song = None
+
+    def process_paragraph(self, paragraph):
+        """
+        Process a paragraph. 
+        In the first book, a paragraph is a single line. In the latter ones
+        they may contain multiple lines.
+        Each paragraph contains textportions. Each textportion has it's own
+        styling, e.g. italics, bold etc. 
+        Also check for page breaks, which indicates a new song in books 1+2.
+        In later books, there may not be line breaks, so check for 3 or more
+        newlines
+        """
+        text = u''
+        textportions = paragraph.createEnumeration()
+        while textportions.hasMoreElements():
+            textportion = textportions.nextElement()
+            if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
+                self.process_paragraph_text(text)
+                self.new_song()
+                text = u''
+            text += self.process_textportion(textportion)
+            if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
+                self.process_paragraph_text(text)
+                self.new_song()
+                text = u''
+        self.process_paragraph_text(text)
+        
+    def process_paragraph_text(self, text):
+        """
+        Split the paragraph text into multiple lines and process
+        """
+        for line in text.split(u'\n'):
+            self.process_paragraph_line(line)
+        if self.blanklines > 2:
+            self.new_song()
+
+    def process_paragraph_line(self, text):
+        """ 
+        Process a single line. Throw away that text which isn't relevant, i.e.
+        stuff that appears at the end of the song.
+        Anything that is OK, append to the current verse
+        """
+        text = text.strip()        
+        if text == u'':
+            self.blanklines += 1
+            if self.blanklines > 1:
+                return
+            if self.song.get_title() != u'':
+                self.finish_verse()
+            return
+        self.blanklines = 0
+        if self.skip_to_close_bracket:
+            if text.endswith(u')'):
+                self.skip_to_close_bracket = False
+            return 
+        if text.startswith(u'CCL Licence'):
+            self.italics = False
+            return
+        if text == u'A Songs of Fellowship Worship Resource':
+            return
+        if text.startswith(u'(NB.') or text.startswith(u'(Regrettably') \
+            or text.startswith(u'(From'):
+            self.skip_to_close_bracket = True
+            return
+        if text.startswith(u'Copyright'):
+            self.song.add_copyright(text)
+            return
+        if text == u'(Repeat)':
+            self.finish_verse()
+            self.song.repeat_verse()
+            return
+        if self.song.get_title() == u'':
+            if self.song.get_copyright() == u'':
+                self.add_author(text)
+            else:
+                self.song.add_copyright(text)
+            return
+        self.add_verse_line(text)
+
+    def process_textportion(self, textportion):
+        """
+        Process a text portion. Here we just get the text and detect if
+        it's bold or italics. If it's bold then its a song number or song title.
+        Song titles are in all capitals, so we must bring the capitalization
+        into line
+        """
+        text = textportion.getString()
+        text = self.tidy_text(text)
+        if text.strip() == u'':
+            return text
+        if textportion.CharWeight == BOLD:
+            boldtext = text.strip()
+            if boldtext.isdigit() and self.song.get_song_number() == '':
+                self.add_songnumber(boldtext)
+                return u''
+            if self.song.get_title() == u'':
+                text = self.uncap_text(text)
+                self.add_title(text)
+            return text
+        if text.strip().startswith(u'('):
+            return text
+        self.italics = (textportion.CharPosture == ITALIC)
+        return text
+
+    def new_song(self):
+        """
+        A change of song. Store the old, create a new
+        ... but only if the last song was complete. If not, stick with it
+        """
+        if self.song:
+            self.finish_verse()
+            if not self.song.check_complete():
+                return
+            self.song.finish()
+        self.song = SongImport(self.manager)
+        self.skip_to_close_bracket = False
+        self.is_chorus = False
+        self.italics = False
+        self.currentverse = u''
+
+    def add_songnumber(self, song_no):
+        """
+        Add a song number, store as alternate title. Also use the song
+        number to work out which songbook we're in
+        """
+        self.song.set_song_number(song_no)
+        if int(song_no) <= 640:
+            self.song.set_song_book(u'Songs of Fellowship 1', 
+                u'Kingsway Publications')
+        elif int(song_no) <= 1150:
+            self.song.set_song_book(u'Songs of Fellowship 2', 
+                u'Kingsway Publications')
+        elif int(song_no) <= 1690:
+            self.song.set_song_book(u'Songs of Fellowship 3', 
+                u'Kingsway Publications')
+        else:
+            self.song.set_song_book(u'Songs of Fellowship 4', 
+                u'Kingsway Publications')
+
+    def add_title(self, text):
+        """
+        Add the title to the song. Strip some leading/trailing punctuation that
+        we don't want in a title
+        """
+        title = text.strip()
+        if title.startswith(u'\''):
+            title = title[1:]
+        if title.endswith(u','):
+            title = title[:-1]
+        self.song.set_title(title)
+
+    def add_author(self, text):
+        """
+        Add the author. OpenLP stores them individually so split by 'and', '&'
+        and comma.
+        However need to check for "Mr and Mrs Smith" and turn it to 
+        "Mr Smith" and "Mrs Smith".
+        """
+        text = text.replace(u' and ', u' & ')
+        for author in text.split(u','):
+            authors = author.split(u'&')
+            for i in range(len(authors)):
+                author2 = authors[i].strip()
+                if author2.find(u' ') == -1 and i < len(authors) - 1:
+                    author2 = author2 + u' ' \
+                        + authors[i + 1].strip().split(u' ')[-1]
+                self.song.add_author(author2)
+
+    def add_verse_line(self, text):
+        """
+        Add a line to the current verse. If the formatting has changed and
+        we're beyond the second line of first verse, then this indicates
+        a change of verse. Italics are a chorus
+        """
+        if self.italics != self.is_chorus and ((len(self.song.verses) > 0) or 
+            (self.currentverse.count(u'\n') > 1)):
+            self.finish_verse()
+        if self.italics:
+            self.is_chorus = True
+        self.currentverse += text + u'\n'
+
+    def finish_verse(self):
+        """
+        Verse is finished, store it. Note in book 1+2, some songs are formatted
+        incorrectly. Here we try and split songs with missing line breaks into
+        the correct number of verses.
+        """
+        if self.currentverse.strip() == u'':
+            return
+        if self.is_chorus:
+            versetag = 'C'
+            splitat = None
+        else:
+            versetag = 'V'
+            splitat = self.verse_splits(self.song.get_song_number())
+        if splitat:
+            ln = 0
+            verse = u''
+            for line in self.currentverse.split(u'\n'):
+                ln += 1
+                if line == u'' or ln > splitat:
+                    self.song.add_verse(verse, versetag)
+                    ln = 0
+                    if line:
+                        verse = line + u'\n'
+                    else:   
+                        verse = u''
+                else:
+                    verse += line + u'\n'
+            if verse:
+                self.song.add_verse(verse, versetag)
+        else:
+            self.song.add_verse(self.currentverse, versetag)
+        self.currentverse = u''
+        self.is_chorus = False
+
+    def tidy_text(self, text):
+        """
+        Get rid of some dodgy unicode and formatting characters we're not
+        interested in. Some can be converted to ascii.
+        """
+        text = text.replace(u'\t', u' ')
+        text = text.replace(u'\r', u'\n')
+        text = text.replace(u'\u2018', u'\'')
+        text = text.replace(u'\u2019', u'\'')
+        text = text.replace(u'\u201c', u'"')
+        text = text.replace(u'\u201d', u'"')
+        text = text.replace(u'\u2026', u'...')
+        text = text.replace(u'\u2013', u'-')
+        text = text.replace(u'\u2014', u'-')
+        return text
+
+    def uncap_text(self, text):
+        """ 
+        Words in the title are in all capitals, so we lowercase them.
+        However some of these words, e.g. referring to God need a leading 
+        capital letter.
+        
+        There is a complicated word "One", which is sometimes lower and 
+        sometimes upper depending on context. Never mind, keep it lower.
+        """
+        textarr = re.split(u'(\W+)', text)
+        textarr[0] = textarr[0].capitalize()
+        for i in range(1, len(textarr)):
+            # Do not translate these. Fixed strings in SOF song file
+            if textarr[i] in (u'JESUS', u'CHRIST', u'KING', u'ALMIGHTY', 
+                u'REDEEMER', u'SHEPHERD', u'SON', u'GOD', u'LORD', u'FATHER', 
+                u'HOLY', u'SPIRIT', u'LAMB', u'YOU', u'YOUR', u'I', u'I\'VE', 
+                u'I\'M', u'I\'LL', u'SAVIOUR', u'O', u'YOU\'RE', u'HE', u'HIS', 
+                u'HIM', u'ZION', u'EMMANUEL', u'MAJESTY', u'JESUS\'', u'JIREH', 
+                u'JUDAH', u'LION', u'LORD\'S', u'ABRAHAM', u'GOD\'S', 
+                u'FATHER\'S', u'ELIJAH'):
+                textarr[i] = textarr[i].capitalize()
+            else:
+                textarr[i] = textarr[i].lower()
+        text = u''.join(textarr)
+        return text
+        
+    def verse_splits(self, song_number):
+        """
+        Because someone at Kingsway forgot to check the 1+2 RTF file, 
+        some verses were not formatted correctly.
+        """
+        if song_number == 11: return 8
+        if song_number == 18: return 5
+        if song_number == 21: return 6
+        if song_number == 23: return 4
+        if song_number == 24: return 7
+        if song_number == 27: return 4
+        if song_number == 31: return 6
+        if song_number == 49: return 4
+        if song_number == 50: return 8
+        if song_number == 70: return 4	
+        if song_number == 75: return 8
+        if song_number == 79: return 6
+        if song_number == 97: return 7
+        if song_number == 107: return 4
+        if song_number == 109: return 4
+        if song_number == 133: return 4
+        if song_number == 155: return 10
+        if song_number == 156: return 8
+        if song_number == 171: return 4
+        if song_number == 188: return 7
+        if song_number == 192: return 4
+        if song_number == 208: return 8
+        if song_number == 215: return 8
+        if song_number == 220: return 4
+        if song_number == 247: return 6
+        if song_number == 248: return 6
+        if song_number == 251: return 8
+        if song_number == 295: return 8
+        if song_number == 307: return 5
+        if song_number == 314: return 6
+        if song_number == 325: return 8
+        if song_number == 386: return 6
+        if song_number == 415: return 4
+        if song_number == 426: return 4
+        if song_number == 434: return 5
+        if song_number == 437: return 4
+        if song_number == 438: return 6
+        if song_number == 456: return 8
+        if song_number == 461: return 4
+        if song_number == 469: return 4
+        if song_number == 470: return 5
+        if song_number == 476: return 6
+        if song_number == 477: return 7
+        if song_number == 480: return 8
+        if song_number == 482: return 4
+        if song_number == 512: return 4
+        if song_number == 513: return 8
+        if song_number == 518: return 5
+        if song_number == 520: return 4
+        if song_number == 523: return 6
+        if song_number == 526: return 8
+        if song_number == 527: return 4
+        if song_number == 529: return 4
+        if song_number == 537: return 4
+        if song_number == 555: return 6
+        if song_number == 581: return 4
+        if song_number == 589: return 6
+        if song_number == 590: return 4
+        if song_number == 593: return 8
+        if song_number == 596: return 4
+        if song_number == 610: return 6
+        if song_number == 611: return 6
+        if song_number == 619: return 8
+        if song_number == 645: return 5
+        if song_number == 653: return 6
+        if song_number == 683: return 7
+        if song_number == 686: return 4
+        if song_number == 697: return 8
+        if song_number == 698: return 4
+        if song_number == 704: return 6
+        if song_number == 716: return 4
+        if song_number == 717: return 6
+        if song_number == 730: return 4
+        if song_number == 731: return 8
+        if song_number == 732: return 8
+        if song_number == 738: return 4
+        if song_number == 756: return 9
+        if song_number == 815: return 6
+        if song_number == 830: return 8
+        if song_number == 831: return 4
+        if song_number == 876: return 6
+        if song_number == 877: return 6
+        if song_number == 892: return 4
+        if song_number == 894: return 6
+        if song_number == 902: return 8
+        if song_number == 905: return 8
+        if song_number == 921: return 6
+        if song_number == 940: return 7
+        if song_number == 955: return 9
+        if song_number == 968: return 8		
+        if song_number == 972: return 7
+        if song_number == 974: return 4
+        if song_number == 988: return 6
+        if song_number == 991: return 5
+        if song_number == 1002: return 8
+        if song_number == 1024: return 8
+        if song_number == 1044: return 9
+        if song_number == 1088: return 6
+        if song_number == 1117: return 6
+        if song_number == 1119: return 7
+        return None
+                    

=== added file 'openlp/plugins/songs/lib/songimport.py'
--- openlp/plugins/songs/lib/songimport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/songimport.py	2010-04-02 14:15:26 +0000
@@ -0,0 +1,260 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2010 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael      #
+# Gorven, Scott Guerrieri, Christian Richter, Maikel Stuivenberg, Martin      #
+# Thompson, Jon Tibble, Carsten Tinggaard                                     #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+
+import string
+from openlp.core.lib import SongXMLBuilder
+from openlp.plugins.songs.lib.models import Song, Author, Topic, Book
+        
+class SongImport(object):
+    """
+    Helper class for import a song from a third party source into OpenLP
+
+    This class just takes the raw strings, and will work out for itself
+    whether the authors etc already exist and add them or refer to them 
+    as necessary
+    """
+
+    def __init__(self, song_manager):
+        """
+        Initialise and create defaults for properties
+        
+        song_manager is an instance of a SongManager, through which all
+        database access is performed
+        """
+        self.manager = song_manager
+        self.title = u''
+        self.song_number = u''
+        self.copyright = u''
+        self.comment = u''
+        self.theme_name = u''
+        self.ccli_number = u''    
+        self.authors = []           
+        self.topics = []            
+        self.song_book_name = u''   
+        self.song_book_pub = u''   
+        self.verse_order_list = []  
+        self.verses = []            
+        self.versecount = 0
+        self.choruscount = 0
+       
+    def get_title(self):
+        """
+        Return the title
+        """
+        return self.title
+        
+    def get_copyright(self):
+        """
+        Return the copyright
+        """
+        return self.copyright
+        
+    def get_song_number(self):
+        """ 
+        Return the song number (also known as alternate title)
+        """
+        return self.song_number
+        
+    def set_title(self, title):
+        """
+        Set the title
+        """
+        self.title = title
+ 
+    def set_song_number(self, song_number):
+        """ 
+        Set the song number/alternate title
+        """
+        self.song_number = song_number
+        
+    def set_song_book(self, song_book, publisher):
+        """
+        Set the song book name and publisher
+        """
+        self.song_book_name = song_book
+        self.song_book_pub = publisher
+
+    def add_copyright(self, copyright):
+        """ 
+        Build the copyright field
+        """
+        if self.copyright != u'':
+            self.copyright += ' '
+        self.copyright += copyright
+
+    def add_author(self, text):
+        """ 
+        Add an author to the list
+        """
+        self.authors.append(text)
+        
+    def add_verse(self, verse, versetag):
+        """
+        Add a verse. This is the whole verse, lines split by \n
+        Verse tag can be V1/C1/B1 etc, or 'V' and 'C' (will count the verses/
+        choruses itself) or None, where it will assume verse
+        It will also attempt to detect duplicates. In this case it will just
+        add to the verse order
+        """        
+        for (oldversetag, oldverse) in self.verses:
+            if oldverse.strip() == verse.strip():
+                self.verse_order_list.append(oldversetag)
+                return
+        if versetag == u'C':
+            self.choruscount += 1
+            versetag += unicode(self.choruscount)
+        if versetag == u'V' or not versetag:
+            self.versecount += 1
+            versetag = u'V' + unicode(self.versecount)
+        self.verses.append([versetag, verse])
+        self.verse_order_list.append(versetag)
+        if self.choruscount > 0 and not versetag.startswith(u'C'):
+            self.verse_order_list.append(u'C1')
+
+    def repeat_verse(self):
+        """
+        Repeat the previous verse in the verse order
+        """
+        self.verse_order_list.append(self.verse_order_list[-1])
+    
+    def check_complete(self):
+        """
+        Check the mandatory fields are entered (i.e. title and a verse)
+        Author not checked here, if no author then "Author unknown" is 
+        automatically added
+        """
+        if self.title == u'' or len(self.verses) == 0:
+            return False
+        else:
+            return True
+            
+    def remove_punctuation(self, text):	
+        """
+        Remove punctuation from the string for searchable fields
+        """
+        for c in string.punctuation:
+            text = text.replace(c, u'')
+        return text
+            
+    def finish(self):
+        """
+        All fields have been set to this song. Write it away
+        """
+        if len(self.authors) == 0:
+            self.authors.append(u'Author unknown')
+        self.commit_song()
+        #self.print_song()
+        
+    def commit_song(self):
+        """
+        Write the song and it's fields to disk
+        """
+        song = Song()
+        song.title = self.title
+        song.search_title = self.remove_punctuation(self.title)
+        song.song_number = self.song_number
+        song.search_lyrics = u''
+        sxml = SongXMLBuilder()
+        sxml.new_document()
+        sxml.add_lyrics_to_song()
+        for (versetag, versetext) in self.verses:
+            if versetag[0] == u'C':
+                versetype = u'Chorus'
+            elif versetag[0] == u'V':
+                versetype = u'Verse'
+            elif versetag[0] == u'B':
+                versetype = u'Bridge'
+            elif versetag[0] == u'I':
+                versetype = u'Intro'
+            elif versetag[0] == u'P':
+                versetype = u'Prechorus'
+            elif versetag[0] == u'E':
+                versetype = u'Ending'
+            else:
+                versetype = u'Other'
+            sxml.add_verse_to_lyrics(versetype, versetag[1:], versetext)
+            song.search_lyrics += u' ' + self.remove_punctuation(versetext)
+        song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
+        song.verse_order = u' '.join(self.verse_order_list)
+        song.copyright = self.copyright
+        song.comment = self.comment 
+        song.theme_name = self.theme_name 
+        song.ccli_number = self.ccli_number 
+        for authortext in self.authors:
+            author = self.manager.get_author_by_name(authortext)
+            if author is None:
+                author = Author()
+                author.display_name = authortext
+                author.last_name = authortext.split(u' ')[-1]
+                author.first_name = u' '.join(authortext.split(u' ')[:-1])
+                self.manager.save_author(author)
+            song.authors.append(author)
+        if self.song_book_name:
+            song_book = self.manager.get_book_by_name(self.song_book_name)
+            if song_book is None:
+                song_book = Book()
+                song_book.name = self.song_book_name
+                song_book.publisher = self.song_book_pub
+                self.manager.save_book(song_book)
+            song.song_book_id = song_book.id
+        for topictext in self.topics:
+            topic = self.manager.get_topic_by_name(topictext)
+            if topic is None:
+                topic = Topic()
+                topic.name = topictext
+                self.manager.save_topic(topic)
+            song.topics.append(topictext)
+        self.manager.save_song(song)
+        
+    def print_song(self):
+        """ 
+        For debugging 
+        """
+        print u'========================================'   \
+            + u'========================================'
+        print u'TITLE: ' + self.title 
+        for (versetag, versetext) in self.verses:
+            print u'VERSE ' + versetag + u': ' + versetext
+        print u'ORDER: ' + u' '.join(self.verse_order_list)
+        for author in self.authors:
+            print u'AUTHOR: ' + author
+        if self.copyright:
+            print u'COPYRIGHT: ' + self.copyright
+        if self.song_book_name:
+            print u'BOOK: ' + self.song_book_name
+        if self.song_book_pub:
+            print u'BOOK PUBLISHER: ' + self.song_book_pub
+        if self.song_number:
+            print u'NUMBER: ' + self.song_number
+        for topictext in self.topics:        
+            print u'TOPIC: ' + topictext
+        if self.comment:
+            print u'COMMENT: ' + self.comment
+        if self.theme_name:
+            print u'THEME: ' + self.theme_name
+        if self.ccli_number:
+            print u'CCLI: ' + self.ccli_number
+            
+

=== modified file 'openlp/plugins/songs/songsplugin.py'
--- openlp/plugins/songs/songsplugin.py	2010-03-21 23:58:01 +0000
+++ openlp/plugins/songs/songsplugin.py	2010-04-02 14:15:26 +0000
@@ -28,7 +28,8 @@
 from PyQt4 import QtCore, QtGui
 
 from openlp.core.lib import Plugin, build_icon, PluginStatus
-from openlp.plugins.songs.lib import SongManager, SongMediaItem, SongsTab
+from openlp.plugins.songs.lib import SongManager, SongMediaItem, SongsTab, \
+    SofImport
 from openlp.plugins.songs.forms import OpenLPImportForm, OpenSongExportForm, \
     OpenSongImportForm, OpenLPExportForm
 
@@ -102,24 +103,35 @@
         self.ImportOpenlp1Item.setObjectName(u'ImportOpenlp1Item')
         self.ImportOpenlp2Item = QtGui.QAction(import_menu)
         self.ImportOpenlp2Item.setObjectName(u'ImportOpenlp2Item')
+        self.ImportSofItem = QtGui.QAction(import_menu)
+        self.ImportSofItem.setObjectName(u'ImportSofItem')
         # Add to menus
         self.ImportSongMenu.addAction(self.ImportOpenlp1Item)
         self.ImportSongMenu.addAction(self.ImportOpenlp2Item)
         self.ImportSongMenu.addAction(self.ImportOpenSongItem)
+        self.ImportSongMenu.addAction(self.ImportSofItem)
         import_menu.addAction(self.ImportSongMenu.menuAction())
         # Translations...
         self.ImportSongMenu.setTitle(import_menu.trUtf8('&Song'))
         self.ImportOpenSongItem.setText(import_menu.trUtf8('OpenSong'))
         self.ImportOpenlp1Item.setText(import_menu.trUtf8('openlp.org 1.0'))
         self.ImportOpenlp1Item.setToolTip(
-            import_menu.trUtf8('Export songs in openlp.org 1.0 format'))
+            import_menu.trUtf8('Import songs in openlp.org 1.0 format'))
         self.ImportOpenlp1Item.setStatusTip(
-            import_menu.trUtf8('Export songs in openlp.org 1.0 format'))
+            import_menu.trUtf8('Import songs in openlp.org 1.0 format'))
         self.ImportOpenlp2Item.setText(import_menu.trUtf8('OpenLP 2.0'))
         self.ImportOpenlp2Item.setToolTip(
-            import_menu.trUtf8('Export songs in OpenLP 2.0 format'))
+            import_menu.trUtf8('Import songs in OpenLP 2.0 format'))
         self.ImportOpenlp2Item.setStatusTip(
-            import_menu.trUtf8('Export songs in OpenLP 2.0 format'))
+            import_menu.trUtf8('Import songs in OpenLP 2.0 format'))
+        self.ImportSofItem.setText(
+            import_menu.trUtf8('Songs of Fellowship'))
+        self.ImportSofItem.setToolTip(
+            import_menu.trUtf8('Import songs from the VOLS1_2.RTF, sof3words' \
+                + '.rtf and sof4words.rtf supplied with the music books'))
+        self.ImportSofItem.setStatusTip(
+            import_menu.trUtf8('Import songs from the VOLS1_2.RTF, sof3words' \
+                + '.rtf and sof4words.rtf supplied with the music books'))
         # Signals and slots
         QtCore.QObject.connect(self.ImportOpenlp1Item,
             QtCore.SIGNAL(u'triggered()'), self.onImportOpenlp1ItemClick)
@@ -127,6 +139,8 @@
             QtCore.SIGNAL(u'triggered()'), self.onImportOpenlp1ItemClick)
         QtCore.QObject.connect(self.ImportOpenSongItem,
             QtCore.SIGNAL(u'triggered()'), self.onImportOpenSongItemClick)
+        QtCore.QObject.connect(self.ImportSofItem,
+            QtCore.SIGNAL(u'triggered()'), self.onImportSofItemClick)
         self.ImportSongMenu.menuAction().setVisible(False)
 
     def add_export_menu_item(self, export_menu):
@@ -169,6 +183,13 @@
     def onImportOpenSongItemClick(self):
         self.opensong_import_form.show()
 
+    def onImportSofItemClick(self):
+        filename = QtGui.QFileDialog.getOpenFileName(
+            None, self.trUtf8('Open Songs of Fellowship file'),
+            u'', u'Songs of Fellowship file (*.rtf *.RTF)')
+        sofimport = SofImport(self.songmanager)        
+        sofimport.import_sof(unicode(filename))
+
     def onExportOpenlp1ItemClicked(self):
         self.openlp_export_form.show()
 


Follow ups