← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~mjthompson/openlp/opensong_import into lp:openlp

 

Martin Thompson has proposed merging lp:~mjthompson/openlp/opensong_import into lp:openlp.

Requested reviews:
  Jonathan Corwin (j-corwin)


Makes use of existing SOF import classes to import Opensong format files.  Successfully imports all the Songs Of Fellowship files and all of the "default" set of opensong files.

Fixed a bunch of bugs spotted in the last attempt.  CCLI number is now imported.  Themes are imported as Topics.

Temporary GUI option for import created, as with SOF import.

Still todo
 - make it work on files with non-ASCII chars in.



-- 
https://code.launchpad.net/~mjthompson/openlp/opensong_import/+merge/30047
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-07-04 16:40:53 +0000
+++ openlp/plugins/songs/lib/__init__.py	2010-07-15 20:35:49 +0000
@@ -137,10 +137,11 @@
             unicode(VerseType.to_string(VerseType.Other)).lower():
             return VerseType.Other
 
-from xml import LyricsXML, SongXMLBuilder, SongXMLParser
+from lyrics_xml import LyricsXML, SongXMLBuilder, SongXMLParser
 from songstab import SongsTab
 from mediaitem import SongMediaItem
 from songimport import SongImport
+from opensongimport import OpenSongImport
 try:
     from sofimport import SofImport
     from oooimport import OooImport

=== added file 'openlp/plugins/songs/lib/opensongimport.py'
--- openlp/plugins/songs/lib/opensongimport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/opensongimport.py	2010-07-15 20:35:49 +0000
@@ -0,0 +1,240 @@
+# -*- 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 os
+import re
+
+from songimport import SongImport
+from lxml.etree import Element
+from lxml import objectify
+
+from zipfile import ZipFile
+
+import logging
+log = logging.getLogger(__name__)
+
+class OpenSongImportError(Exception):
+    pass
+
+class OpenSongImport(object):
+    """
+    Import songs exported from OpenSong - the format is described loosly here:
+    http://www.opensong.org/d/manual/song_file_format_specification
+
+    However, it doesn't describe the <lyrics> section, so here's an attempt:
+
+    Verses can be expressed in one of 2 ways:
+    <lyrics>
+    [v1]List of words
+    Another Line
+
+    [v2]Some words for the 2nd verse
+    etc...
+    </lyrics>
+
+    The 'v' can be left out - it is implied
+    or:
+    <lyrics>
+    [V]
+    1List of words
+    2Some words for the 2nd Verse
+
+    1Another Line
+    2etc...
+    </lyrics>
+
+    Either or both forms can be used in one song.  The Number does not
+    necessarily appear at the start of the line
+
+    The [v1] labels can have either upper or lower case Vs
+    Other labels can be used also:
+      C - Chorus
+      B - Bridge
+
+    Guitar chords can be provided 'above' the lyrics (the line is
+    preceeded by a'.') and _s can be used to signify long-drawn-out
+    words:
+
+    . A7        Bm
+    1 Some____ Words
+
+    Chords and _s are removed by this importer.
+
+    The verses etc. are imported and tagged appropriately.
+
+    The <presentation> tag is used to populate the OpenLP verse
+    display order field.  The Author and Copyright tags are also
+    imported to the appropriate places.
+
+    """
+    def __init__(self, songmanager):
+        """
+        Initialise the class. Requires a songmanager class which 
+        is passed to SongImport for writing song to disk
+        """
+        self.songmanager = songmanager
+        self.song = None
+
+    def do_import(self, filename, commit=True):
+        """
+        Import either a single opensong file, or a zipfile
+        containing multiple opensong files If the commit parameter is
+        set False, the import will not be committed to the database
+        (useful for test scripts)
+        """
+        ext=os.path.splitext(filename)[1]
+        if ext.lower() == ".zip":
+            log.info('Zipfile found %s', filename)
+            z=ZipFile(filename, u'r')
+            for song in z.infolist():
+                parts=os.path.split(song.filename)
+                if parts[-1] == u'':
+                    #No final part => directory
+                    continue
+                songfile = z.open(song)
+                self.do_import_file(songfile)
+                if commit:
+                    self.finish()
+        else:
+            log.info('Direct import %s', filename)
+            file = open(filename)
+            self.do_import_file(file)
+            if commit:
+                self.finish()
+
+   
+    def do_import_file(self, file):
+        """
+        Process the OpenSong file - pass in a file-like object,
+        not a filename
+        """            
+        self.song_import = SongImport(self.songmanager)
+        tree = objectify.parse(file)
+        root = tree.getroot()
+        fields = dir(root)
+        decode = {u'copyright':self.song_import.add_copyright,
+                u'ccli':self.song_import.set_ccli_number,
+                u'author':self.song_import.parse_author,
+                u'title':self.song_import.set_title,
+                u'aka':self.song_import.set_alternate_title,
+                u'hymn_number':self.song_import.set_song_number}
+        for (attr, fn) in decode.items():
+            if attr in fields:
+                fn(unicode(root.__getattr__(attr)))
+
+        res = []
+        if u'theme' in fields:
+            self.song_import.topics.append(unicode(root.theme))
+        if u'alttheme' in fields:
+            self.song_import.topics.append(unicode(root.alttheme))
+        # data storage while importing
+        verses = {}
+        lyrics = unicode(root.lyrics)
+        # keep track of a "default" verse order, in case none is specified
+        our_verse_order = []
+        verses_seen = {}
+        # in the absence of any other indication, verses are the default,
+        # erm, versetype!
+        versetype = u'V'
+        for l in lyrics.split(u'\n'):
+            # remove comments
+            semicolon = l.find(u';')
+            if semicolon >= 0:
+                l = l[:semicolon]
+            l = l.strip()
+            if len(l) == 0:
+                continue
+            # skip inline guitar chords and page and column breaks
+            if l[0] == u'.' or l.startswith(u'---') or l.startswith(u'-!!'):
+                continue
+            
+            # verse/chorus/etc. marker
+            if l[0] == u'[':
+                versetype = l[1].upper()
+                if versetype.isdigit():
+                    versenum = versetype
+                    versetype = u'V'
+                elif l[2] != u']':
+                    # there's a number to go with it - extract that as well
+                    right_bracket = l.find(u']')
+                    versenum = l[2:right_bracket]
+                else:
+                    # if there's no number, assume it's no.1
+                    versenum = u'1'
+                continue
+            words = None
+
+            # number at start of line.. it's verse number
+            if l[0].isdigit():
+                versenum = l[0]
+                words = l[1:].strip()
+            if words is None and \
+                   versenum is not None and \
+                   versetype is not None:
+                words = l
+            if versenum is not None:
+                versetag = u'%s%s'%(versetype,versenum)
+                if not verses.has_key(versetype):
+                    verses[versetype] = {}
+                if not verses[versetype].has_key(versenum):
+                    verses[versetype][versenum] = [] # storage for lines in this verse
+                if not verses_seen.has_key(versetag):
+                    verses_seen[versetag] = 1
+                    our_verse_order.append(versetag)
+            if words:
+                # Tidy text and remove the ____s from extended words
+                words=self.song_import.tidy_text(words)
+                words=words.replace('_', '')
+                verses[versetype][versenum].append(words)
+        # done parsing
+        versetypes = verses.keys()
+        versetypes.sort()
+        versetags = {}
+        verse_renames = {}
+        for v in versetypes:
+            versenums = verses[v].keys()
+            versenums.sort()
+            for n in versenums:
+                versetag = u'%s%s' %(v,n)
+                lines = u'\n'.join(verses[v][n])
+                self.song_import.verses.append([versetag, lines])
+                versetags[versetag] = 1 # keep track of what we have for error checking later
+        # now figure out the presentation order
+        if u'presentation' in fields and root.presentation != u'':
+            order = unicode(root.presentation)
+            order = order.split()
+        else:
+            assert len(our_verse_order)>0
+            order = our_verse_order
+        for tag in order:
+            if len(tag) == 1:
+                tag = tag + u'1' # Assume it's no.1 if it's not there
+            if not versetags.has_key(tag):
+                log.warn(u'Got order %s but not in versetags, skipping', tag)
+            else:
+                self.song_import.verse_order_list.append(tag)
+    def finish(self):
+        """ Separate function, allows test suite to not pollute database"""
+        self.song_import.finish()

=== modified file 'openlp/plugins/songs/lib/songimport.py'
--- openlp/plugins/songs/lib/songimport.py	2010-07-03 17:46:00 +0000
+++ openlp/plugins/songs/lib/songimport.py	2010-07-15 20:35:49 +0000
@@ -163,6 +163,12 @@
         """
         self.song_number = song_number
 
+    def set_ccli_number(self, cclino):
+        """
+        Set the ccli number
+        """
+        self.ccli_number = cclino
+
     def set_song_book(self, song_book, publisher):
         """
         Set the song book name and publisher
@@ -184,8 +190,8 @@
         """
         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".
+        However need to check for 'Mr and Mrs Smith' and turn it to
+        'Mr Smith' and 'Mrs Smith'.
         """
         for author in text.split(u','):
             authors = author.split(u'&')
@@ -267,7 +273,7 @@
 
     def commit_song(self):
         """
-        Write the song and it's fields to disk
+        Write the song and its fields to disk
         """
         song = Song()
         song.title = self.title
@@ -319,13 +325,14 @@
                 self.manager.save_object(song_book)
             song.song_book_id = song_book.id
         for topictext in self.topics:
-            topic = self.manager.get_object_filtered(Topic,
-                Topic.name == topictext)
+            if len(topictext) == 0:
+                continue
+            topic = self.manager.get_object_filtered(Topic, Topic.name == topictext)
             if topic is None:
                 topic = Topic()
                 topic.name = topictext
                 self.manager.save_object(topic)
-            song.topics.append(topictext)
+            song.topics.append(topic)
         self.manager.save_object(song)
 
     def print_song(self):

=== modified file 'openlp/plugins/songs/lib/songxml.py'
--- openlp/plugins/songs/lib/songxml.py	2010-07-03 13:26:29 +0000
+++ openlp/plugins/songs/lib/songxml.py	2010-07-15 20:35:49 +0000
@@ -60,175 +60,6 @@
 # TODO: Song: Import ChangingSong
 # TODO: Song: Export ChangingSong
 
-_BLANK_OPENSONG_XML = \
-'''<?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(object):
-    """
-    Class for import of OpenSong
-    """
-    def __init__(self, xmlContent = None):
-        """
-        Initialize from given xml content
-        """
-        self._set_from_xml(_BLANK_OPENSONG_XML, 'song')
-        if xmlContent:
-            self._set_from_xml(xmlContent, 'song')
-
-    def _set_from_xml(self, xml, root_tag):
-        """
-        Set song properties from given xml content.
-
-        ``xml``
-            Formatted xml tags and values.
-        ``root_tag``
-            The root tag of the xml.
-        """
-        root = ElementTree(element=XML(xml))
-        xml_iter = root.getiterator()
-        for element in xml_iter:
-            if element.tag != root_tag:
-                text = element.text
-                if text is None:
-                    val = text
-                elif isinstance(text, basestring):
-                    # Strings need special handling to sort the colours out
-                    if text[0] == u'$':
-                        # This might be a hex number, let's try to convert it.
-                        try:
-                            val = int(text[1:], 16)
-                        except ValueError:
-                            pass
-                    else:
-                        # Let's just see if it's a integer.
-                        try:
-                            val = int(text)
-                        except ValueError:
-                            # Ok, it seems to be a string.
-                            val = text
-                    if hasattr(self, u'post_tag_hook'):
-                        (element.tag, val) = \
-                            self.post_tag_hook(element.tag, val)
-                setattr(self, element.tag, val)
-
-    def __str__(self):
-        """
-        Return string with all public attributes
-
-        The string is formatted with one attribute per line
-        If the string is split on newline then the length of the
-        list is equal to the number of attributes
-        """
-        attributes = []
-        for attrib in dir(self):
-            if not attrib.startswith(u'_'):
-                attributes.append(
-                    u'%30s : %s' % (attrib, getattr(self, attrib)))
-        return u'\n'.join(attributes)
-
-    def _get_as_string(self):
-        """
-        Return one string with all public attributes
-        """
-        result = u''
-        for attrib in dir(self):
-            if not attrib.startswith(u'_'):
-                result += u'_%s_' % getattr(self, attrib)
-        return result
-
-    def get_author_list(self):
-        """Convert author field to an authorlist
-
-        in OpenSong an author list may be separated by '/'
-        return as a string
-        """
-        if self.author:
-            list = self.author.split(u' and ')
-            res = [item.strip() for item in list]
-            return u', '.join(res)
-
-    def get_category_array(self):
-        """Convert theme and alttheme into category_array
-
-        return as a string
-        """
-        res = []
-        if self.theme:
-            res.append(self.theme)
-        if self.alttheme:
-            res.append(self.alttheme)
-        return u', u'.join(res)
-
-    def _reorder_verse(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 digit in '1234567890 ':
-            tagPending = True
-            for line in tmpVerse:
-                if line.startswith(digit):
-                    if tagPending:
-                        tagPending = False
-                        tagChar = tag.strip(u'[]').lower()
-                        if 'v' == tagChar:
-                            newtag = "Verse"
-                        elif 'c' == tagChar:
-                            newtag = "Chorus"
-                        elif 'b' == tagChar:
-                            newtag = "Bridge"
-                        elif 'p' == tagChar:
-                            newtag = "Pre-chorus"
-                        else:
-                            newtag = tagChar
-                        tagString = (u'# %s %s' % (newtag, digit)).rstrip()
-                        res.append(tagString)
-                    res.append(line[1:])
-                if (len(line) == 0) and (not tagPending):
-                    res.append(line)
-        return res
-
-    def get_lyrics(self):
-        """
-        Convert the lyrics to openlp lyrics format
-        return as list of strings
-        """
-        lyrics = self.lyrics.split(u'\n')
-        tmpVerse = []
-        finalLyrics = []
-        tag = ""
-        for lyric in lyrics:
-            line = lyric.rstrip()
-            if not line.startswith(u'.'):
-                # drop all chords
-                tmpVerse.append(line)
-                if line:
-                    if line.startswith(u'['):
-                        tag = line
-                else:
-                    reorderedVerse = self._reorder_verse(tag, tmpVerse)
-                    finalLyrics.extend(reorderedVerse)
-                    tag = ""
-                    tmpVerse = []
-        # catch up final verse
-        reorderedVerse = self._reorder_verse(tag, tmpVerse)
-        finalLyrics.extend(reorderedVerse)
-        return finalLyrics
-
-
 class Song(object):
     """Handling song properties and methods
 
@@ -275,7 +106,7 @@
         show_author_list -- 0: no show, 1: show
         show_copyright -- 0: no show, 1: show
         show_song_cclino -- 0: no show, 1: show
-        theme -- name of theme or blank
+        theme_name -- name of theme or blank
         category_array -- list of user defined properties (hymn, gospel)
         song_book -- name of originating book
         song_number -- number of the song, related to a songbook
@@ -298,7 +129,7 @@
         self.show_copyright = 1
         self.show_song_cclino = 1
         self.show_title = 1
-        self.theme = ""
+        self.theme_name = ""
         self.category_array = None
         self.song_book = ""
         self.song_number = ""
@@ -307,40 +138,6 @@
         self.set_lyrics(u'')
         return
 
-    def from_opensong_buffer(self, xmlcontent):
-        """Initialize from buffer(string) of xml lines in opensong format"""
-        self._reset()
-        opensong = _OpenSong(xmlcontent)
-        if opensong.title:
-            self.set_title(opensong.title)
-        if opensong.copyright:
-            self.set_copyright(opensong.copyright)
-        if opensong.presentation:
-            self.set_verse_order(opensong.presentation)
-        if opensong.ccli:
-            self.set_song_cclino(opensong.ccli)
-        self.set_author_list(opensong.get_author_list())
-        self.set_category_array(opensong.get_category_array())
-        self.set_lyrics(opensong.get_lyrics())
-
-    def from_opensong_file(self, xmlfilename):
-        """
-        Initialize from file containing xml
-        xmlfilename -- path to xml file
-        """
-        osfile = None
-        try:
-            osfile = open(xmlfilename, 'r')
-            list = [line for line in osfile]
-            osfile.close()
-            xml = "".join(list)
-            self.from_opensong_buffer(xml)
-        except IOError:
-            log.exception(u'Failed to load opensong xml file')
-        finally:
-            if osfile:
-                osfile.close()
-
     def _remove_punctuation(self, title):
         """Remove the puntuation chars from title
 
@@ -424,7 +221,7 @@
         self.set_title(sName)
         self.set_author_list(author_list)
         self.set_copyright(sCopyright)
-        self.set_song_cclino(sCcli)
+        self.set_ccli_number(sCcli)
         self.set_lyrics(lyrics)
 
     def from_ccli_text_file(self, textFileName):
@@ -479,21 +276,21 @@
         """Set the copyright string"""
         self.copyright = copyright
 
-    def get_song_cclino(self):
+    def get_ccli_number(self):
         """Return the songCclino"""
-        return self._assure_string(self.song_cclino)
-
-    def set_song_cclino(self, song_cclino):
-        """Set the song_cclino"""
-        self.song_cclino = song_cclino
-
-    def get_theme(self):
+        return self._assure_string(self.ccli_number)
+
+    def set_ccli_number(self, ccli_number):
+        """Set the ccli_number"""
+        self.ccli_number = ccli_number
+
+    def get_theme_name(self):
         """Return the theme name for the song"""
-        return self._assure_string(self.theme)
+        return self._assure_string(self.theme_name)
 
-    def set_theme(self, theme):
+    def set_theme_name(self, theme_name):
         """Set the theme name (string)"""
-        self.theme = theme
+        self.theme_name = theme_name
 
     def get_song_book(self):
         """Return the song_book (string)"""
@@ -532,9 +329,9 @@
 
         asOneString
         True -- string:
-          "John Newton, A Parker"
+          'John Newton, A Parker'
         False -- list of strings
-          ["John Newton", u'A Parker"]
+          ['John Newton', u'A Parker']
         """
         if asOneString:
             res = self._assure_string(self.author_list)
@@ -557,9 +354,9 @@
 
         asOneString
         True -- string:
-          "Hymn, Gospel"
+          'Hymn, Gospel'
         False -- list of strings
-          ["Hymn", u'Gospel"]
+          ['Hymn', u'Gospel']
         """
         if asOneString:
             res = self._assure_string(self.category_array)
@@ -601,13 +398,13 @@
         """Set the show_copyright flag (bool)"""
         self.show_copyright = show_copyright
 
-    def get_show_song_cclino(self):
+    def get_show_ccli_number(self):
         """Return the showSongCclino (string)"""
-        return self.show_song_cclino
+        return self.show_ccli_number
 
-    def set_show_song_cclino(self, show_song_cclino):
-        """Set the show_song_cclino flag (bool)"""
-        self.show_song_cclino = show_song_cclino
+    def set_show_ccli_number(self, show_ccli_number):
+        """Set the show_ccli_number flag (bool)"""
+        self.show_ccli_number = show_ccli_number
 
     def get_lyrics(self):
         """Return the lyrics as a list of strings
@@ -674,7 +471,7 @@
 
         slideNumber -- 1 .. numberOfSlides
         Returns a list as:
-        [theme (string),
+        [theme_name (string),
          title (string),
          authorlist (string),
          copyright (string),
@@ -699,13 +496,13 @@
             cpright = self.get_copyright()
         else:
             cpright = ""
-        if self.show_song_cclino:
-            ccli = self.get_song_cclino()
+        if self.show_ccli_number:
+            ccli = self.get_ccli_number()
         else:
             ccli = ""
-        theme = self.get_theme()
+        theme_name = self.get_theme_name()
         # examine the slide for a theme
-        res.append(theme)
+        res.append(theme_name)
         res.append(title)
         res.append(author)
         res.append(cpright)

=== added directory 'openlp/plugins/songs/lib/test'
=== added file 'openlp/plugins/songs/lib/test/test.opensong'
--- openlp/plugins/songs/lib/test/test.opensong	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/test/test.opensong	2010-07-15 20:35:49 +0000
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<song>
+  <title>Martins Test</title>
+  <author>MartiÑ Thómpson</author>
+  <copyright>2010 Martin Thompson</copyright>
+  <hymn_number>1</hymn_number>
+  <presentation>V1 C V2 C2 V3 B1 V1</presentation>
+  <ccli>Blah</ccli>
+  <capo print="false"></capo>
+  <key></key>
+  <aka></aka>
+  <key_line></key_line>
+  <user1></user1>
+  <user2></user2>
+  <user3></user3>
+  <theme>TestTheme</theme>
+  <alttheme>TestAltTheme</alttheme>
+  <tempo></tempo>
+  <time_sig></time_sig>
+  <lyrics>;Comment
+. A   B C
+1 v1 Line 1___
+2 v2 Line 1___
+. A B C7
+1 V1 Line 2
+2 V2 Line 2
+ 
+[3]
+ V3 Line 1
+ V3 Line 2
+
+[b1]
+ Bridge 1
+---
+-!!
+ Bridge 1 line 2
+
+[C]
+. A     B
+ Chorus 1
+ 
+[C2]
+.  A    B
+ Chorus 2
+ </lyrics>
+  <style index="default_style">
+  <title enabled="true" valign="bottom" align="center" include_verse="false" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="26" bold="true" italic="true" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#000000"/>
+  <subtitle enabled="true" valign="bottom" align="center" descriptive="false" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="18" bold="true" italic="true" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#000000"/>
+  <song_subtitle>author</song_subtitle>
+  <body enabled="true" auto_scale="false" valign="middle" align="center" highlight_chorus="true" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="34" bold="true" italic="false" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#FF0000">
+  <tabs/>
+</body>
+  <background strip_footer="0" color="#408080" position="1"/>
+</style></song>

=== added file 'openlp/plugins/songs/lib/test/test.opensong.zip'
Binary files openlp/plugins/songs/lib/test/test.opensong.zip	1970-01-01 00:00:00 +0000 and openlp/plugins/songs/lib/test/test.opensong.zip	2010-07-15 20:35:49 +0000 differ
=== added file 'openlp/plugins/songs/lib/test/test2.opensong'
--- openlp/plugins/songs/lib/test/test2.opensong	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/test/test2.opensong	2010-07-15 20:35:49 +0000
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<song>
+  <title>Martins 2nd Test</title>
+  <author>Martin Thompson</author>
+  <copyright>2010 Martin Thompson</copyright>
+  <hymn_number>2</hymn_number>
+  <presentation></presentation>
+  <ccli>Blah</ccli>
+  <capo print="false"></capo>
+  <key></key>
+  <aka></aka>
+  <key_line></key_line>
+  <user1></user1>
+  <user2></user2>
+  <user3></user3>
+  <theme></theme>
+  <tempo></tempo>
+  <time_sig></time_sig>
+  <lyrics>;Comment
+[V]
+. A   B C
+1 v1 Line 1___
+2 v2 Line 1___
+. A B C7
+1 V1 Line 2
+2 V2 Line 2
+ 
+[b1]
+ Bridge 1
+ Bridge 1 line 2
+[C1]
+ Chorus 1
+ 
+[C2]
+ Chorus 2
+ </lyrics>
+  <style index="default_style">
+  <title enabled="true" valign="bottom" align="center" include_verse="false" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="26" bold="true" italic="true" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#000000"/>
+  <subtitle enabled="true" valign="bottom" align="center" descriptive="false" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="18" bold="true" italic="true" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#000000"/>
+  <song_subtitle>author</song_subtitle>
+  <body enabled="true" auto_scale="false" valign="middle" align="center" highlight_chorus="true" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="34" bold="true" italic="false" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#FF0000">
+  <tabs/>
+</body>
+  <background strip_footer="0" color="#408080" position="1"/>
+</style></song>

=== added file 'openlp/plugins/songs/lib/test/test_importing_lots.py'
--- openlp/plugins/songs/lib/test/test_importing_lots.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/test/test_importing_lots.py	2010-07-15 20:35:49 +0000
@@ -0,0 +1,57 @@
+from openlp.plugins.songs.lib.opensongimport import OpenSongImport
+from openlp.plugins.songs.lib.db import init_schema
+from openlp.core.lib.db import Manager
+from glob import glob
+from zipfile import ZipFile
+import os
+from traceback import print_exc
+import sys
+import codecs
+
+def opensong_import_lots():
+    ziploc = u'/home/mjt/openlp/OpenSong_Data/'
+    files = []
+    #files = [u'test.opensong.zip', ziploc+u'ADond.zip']
+    files.extend(glob(ziploc+u'Songs.zip'))
+    #files.extend(glob(ziploc+u'SOF.zip'))
+    #files.extend(glob(ziploc+u'spanish_songs_for_opensong.zip'))
+#    files.extend(glob(ziploc+u'opensong_*.zip'))
+    errfile = codecs.open(u'import_lots_errors.txt', u'w', u'utf8')
+    manager = Manager(u'songs', init_schema)
+    for file in files:
+        print u'Importing', file
+        z = ZipFile(file, u'r')
+        for song in z.infolist():
+            # need to handle unicode filenames (CP437 -  Winzip does this)
+            filename = song.filename#.decode('cp852')
+            parts = os.path.split(filename)
+            if parts[-1] == u'':
+                #No final part => directory
+                continue
+            print "  ", file, ":",filename,
+            songfile = z.open(song)
+            #z.extract(song)
+            #songfile=open(filename, u'r')
+            o = OpenSongImport(manager)
+            try:
+                o.do_import_file(songfile)
+                # o.song_import.print_song()
+            except:
+                print "Failure",
+                
+                errfile.write(u'Failure: %s:%s\n' %(file, filename.decode('cp437')))
+                songfile = z.open(song)
+                for l in songfile.readlines():
+                    l = l.decode('utf8')
+                    print(u'  |%s\n' % l.strip())
+                    errfile.write(u'  |%s\n'%l.strip())   
+                print_exc(3, file = errfile)
+                print_exc(3)
+                sys.exit(1)
+                # continue
+            #o.finish()
+            print "OK"
+            #os.unlink(filename)
+            # o.song_import.print_song()
+if __name__ == "__main__":
+    opensong_import_lots()

=== added file 'openlp/plugins/songs/lib/test/test_opensongimport.py'
--- openlp/plugins/songs/lib/test/test_opensongimport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/test/test_opensongimport.py	2010-07-15 20:35:49 +0000
@@ -0,0 +1,90 @@
+# -*- 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                          #
+###############################################################################
+from openlp.plugins.songs.lib.opensongimport import OpenSongImport
+from openlp.core.lib.db import Manager
+from openlp.plugins.songs.lib.db import init_schema
+from openlp.plugins.songs.songsplugin import SongsPlugin
+import sys
+
+def test():
+    manager = Manager(u'songs', init_schema)
+    o = OpenSongImport(manager)
+    o.do_import(u'test.opensong', commit=False)
+    o.finish()
+    o.song_import.print_song()
+    assert o.song_import.copyright == u'2010 Martin Thompson'
+    assert o.song_import.authors == [u'MartiÑ Thómpson']
+    assert o.song_import.title == u'Martins Test'
+    assert o.song_import.alternate_title == u''
+    assert o.song_import.song_number == u'1'
+    assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.song_import.verses 
+    assert [u'C1', u'Chorus 1'] in o.song_import.verses 
+    assert [u'C2', u'Chorus 2'] in o.song_import.verses 
+    assert not [u'C3', u'Chorus 3'] in o.song_import.verses 
+    assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.song_import.verses 
+    assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.song_import.verses
+    assert o.song_import.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3', u'B1', u'V1']
+    assert o.song_import.ccli_number == u'Blah'
+    assert o.song_import.topics == [u'TestTheme', u'TestAltTheme']
+    o.do_import(u'test.opensong.zip', commit=False)
+    o.song_import.print_song()
+    o.finish()
+    assert o.song_import.copyright == u'2010 Martin Thompson'
+    assert o.song_import.authors == [u'MartiÑ Thómpson']
+    assert o.song_import.title == u'Martins Test'
+    assert o.song_import.alternate_title == u''
+    assert o.song_import.song_number == u'1'
+    assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.song_import.verses 
+    assert [u'C1', u'Chorus 1'] in o.song_import.verses 
+    assert [u'C2', u'Chorus 2'] in o.song_import.verses 
+    assert not [u'C3', u'Chorus 3'] in o.song_import.verses 
+    assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.song_import.verses 
+    assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.song_import.verses
+    assert o.song_import.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3', u'B1', u'V1']
+
+    o = OpenSongImport(manager)
+    o.do_import(u'test2.opensong', commit=False)
+    # o.finish()
+    o.song_import.print_song()
+    assert o.song_import.copyright == u'2010 Martin Thompson'
+    assert o.song_import.authors == [u'Martin Thompson']
+    assert o.song_import.title == u'Martins 2nd Test'
+    assert o.song_import.alternate_title == u''
+    assert o.song_import.song_number == u'2'
+    print o.song_import.verses
+    assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.song_import.verses 
+    assert [u'C1', u'Chorus 1'] in o.song_import.verses 
+    assert [u'C2', u'Chorus 2'] in o.song_import.verses 
+    assert not [u'C3', u'Chorus 3'] in o.song_import.verses 
+    assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.song_import.verses 
+    assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.song_import.verses
+    print o.song_import.verse_order_list
+    assert o.song_import.verse_order_list == [u'V1', u'V2', u'B1', u'C1', u'C2']
+
+    print "Tests passed"
+    pass
+
+if __name__ == "__main__":
+    test()

=== modified file 'openlp/plugins/songs/lib/xml.py'
--- openlp/plugins/songs/lib/xml.py	2010-07-05 21:23:39 +0000
+++ openlp/plugins/songs/lib/xml.py	2010-07-15 20:35:49 +0000
@@ -76,8 +76,8 @@
         ``content``
             The actual text of the verse to be stored.
         """
-        #log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content))
-        verse = etree.Element(u'verse', type=type, label=number)
+        # log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content))
+        verse = etree.Element(u'verse', type = unicode(type), label = unicode(number))
         verse.text = etree.CDATA(content)
         self.lyrics.append(verse)
 

=== modified file 'openlp/plugins/songs/songsplugin.py'
--- openlp/plugins/songs/songsplugin.py	2010-07-12 21:11:00 +0000
+++ openlp/plugins/songs/songsplugin.py	2010-07-15 20:35:49 +0000
@@ -39,6 +39,8 @@
 except ImportError:
     OOo_available = False
 
+from openlp.plugins.songs.lib import OpenSongImport
+
 log = logging.getLogger(__name__)
 
 class SongsPlugin(Plugin):
@@ -137,6 +139,25 @@
                 QtCore.SIGNAL(u'triggered()'), self.onImportSofItemClick)
             QtCore.QObject.connect(self.ImportOooItem,
                 QtCore.SIGNAL(u'triggered()'), self.onImportOooItemClick)
+        # OpenSong import menu item - will be removed and the
+        # functionality will be contained within the import wizard
+        self.ImportOpenSongItem = QtGui.QAction(import_menu)
+        self.ImportOpenSongItem.setObjectName(u'ImportOpenSongItem')
+        self.ImportOpenSongItem.setText(
+            translate('SongsPlugin',
+                'OpenSong (temp menu item)'))
+        self.ImportOpenSongItem.setToolTip(
+            translate('SongsPlugin',
+                'Import songs from OpenSong files' +
+                '(either raw text or ZIPfiles)'))
+        self.ImportOpenSongItem.setStatusTip(
+            translate('SongsPlugin',
+                'Import songs from OpenSong files' +
+                '(either raw text or ZIPfiles)'))
+        import_menu.addAction(self.ImportOpenSongItem)
+        QtCore.QObject.connect(self.ImportOpenSongItem,
+                QtCore.SIGNAL(u'triggered()'), self.onImportOpenSongItemClick)
+
 
     def addExportMenuItem(self, export_menu):
         """
@@ -177,6 +198,26 @@
                 QtGui.QMessageBox.Ok)
         Receiver.send_message(u'songs_load_list')
 
+    def onImportOpenSongItemClick(self):
+        filenames = QtGui.QFileDialog.getOpenFileNames(
+            None, translate('SongsPlugin',
+                'Open OpenSong file'),
+            u'', u'All files (*.*)')
+        try:
+            for filename in filenames:
+                importer = OpenSongImport(self.manager)
+                importer.do_import(unicode(filename))
+        except:
+            log.exception('Could not import OpenSong file')
+            QtGui.QMessageBox.critical(None,
+                translate('SongsPlugin',
+                    'Import Error'),
+                translate('SongsPlugin',
+                    'Error importing OpenSong file'),
+                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok),
+                QtGui.QMessageBox.Ok)
+        Receiver.send_message(u'songs_load_list')
+
     def onImportOooItemClick(self):
         filenames = QtGui.QFileDialog.getOpenFileNames(
             None, translate('SongsPlugin',


Follow ups