← Back to team overview

openlp-core team mailing list archive

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

 

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

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~j-corwin/openlp/bug-1015823/+merge/113303

This adds an import for the SongPro text file export format.

It is based on Martin Barrett's example attached to http://support.openlp.org/issues/569, although somewhat rewritten.

I'm sharing the strip_rtf function from the ewimport, which will clash with mahfiaz's merge and his new and improved strip rtf function, but I got bored waiting! 
-- 
https://code.launchpad.net/~j-corwin/openlp/bug-1015823/+merge/113303
Your team OpenLP Core is requested to review the proposed merge of lp:~j-corwin/openlp/bug-1015823 into lp:openlp.
=== modified file 'openlp/plugins/songs/lib/__init__.py'
--- openlp/plugins/songs/lib/__init__.py	2012-06-22 14:14:53 +0000
+++ openlp/plugins/songs/lib/__init__.py	2012-07-03 21:29:27 +0000
@@ -36,6 +36,7 @@
 
 WHITESPACE = re.compile(r'[\W_]+', re.UNICODE)
 APOSTROPHE = re.compile(u'[\'`’ʻ′]', re.UNICODE)
+RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}')
 
 class VerseType(object):
     """
@@ -366,6 +367,101 @@
     if song.copyright:
         song.copyright = CONTROL_CHARS.sub(u'', song.copyright).strip()
 
+def strip_rtf(blob, encoding):
+    depth = 0
+    control = False
+    clear_text = []
+    control_word = []
+
+    # workaround for \tx bug: remove one pair of curly braces
+    # if \tx is encountered
+    match = RTF_STRIPPING_REGEX.search(blob)
+    if match:
+        # start and end indices of match are curly braces - filter them out
+        blob = ''.join([blob[i] for i in xrange(len(blob))
+                        if i != match.start() and i !=match.end()])
+    for c in blob:
+        if control:
+            # for delimiters, set control to False
+            if c == '{':
+                if control_word:
+                    depth += 1
+                control = False
+            elif c == '}':
+                if control_word:
+                    depth -= 1
+                control = False
+            elif c == '\\':
+                new_control = bool(control_word)
+                control = False
+            elif c.isspace():
+                control = False
+            else:
+                control_word.append(c)
+                if len(control_word) == 3 and control_word[0] == '\'':
+                    control = False
+            if not control:
+                if not control_word:
+                    if c == '{' or c == '}' or c == '\\':
+                        clear_text.append(c)
+                else:
+                    control_str = ''.join(control_word)
+                    if control_str == 'par' or control_str == 'line':
+                        clear_text.append(u'\n')
+                    elif control_str == 'tab':
+                        clear_text.append(u'\t')
+                    # Prefer the encoding specified by the RTF data to that
+                    # specified by the Paradox table header
+                    # West European encoding
+                    elif control_str == 'fcharset0':
+                        encoding = u'cp1252'
+                    # Greek encoding
+                    elif control_str == 'fcharset161':
+                        encoding = u'cp1253'
+                    # Turkish encoding
+                    elif control_str == 'fcharset162':
+                        encoding = u'cp1254'
+                    # Vietnamese encoding
+                    elif control_str == 'fcharset163':
+                        encoding = u'cp1258'
+                    # Hebrew encoding
+                    elif control_str == 'fcharset177':
+                        encoding = u'cp1255'
+                    # Arabic encoding
+                    elif control_str == 'fcharset178':
+                        encoding = u'cp1256'
+                    # Baltic encoding
+                    elif control_str == 'fcharset186':
+                        encoding = u'cp1257'
+                    # Cyrillic encoding
+                    elif control_str == 'fcharset204':
+                        encoding = u'cp1251'
+                    # Thai encoding
+                    elif control_str == 'fcharset222':
+                        encoding = u'cp874'
+                    # Central+East European encoding
+                    elif control_str == 'fcharset238':
+                        encoding = u'cp1250'
+                    elif control_str[0] == '\'':
+                        s = chr(int(control_str[1:3], 16))
+                        clear_text.append(s.decode(encoding))
+                    del control_word[:]
+            if c == '\\' and new_control:
+                control = True
+        elif c == '{':
+            depth += 1
+        elif c == '}':
+            depth -= 1
+        elif depth > 2:
+            continue
+        elif c == '\n' or c == '\r':
+            continue
+        elif c == '\\':
+            control = True
+        else:
+            clear_text.append(c)
+    return u''.join(clear_text)
+
 from xml import OpenLyrics, SongXML
 from songstab import SongsTab
 from mediaitem import SongMediaItem

=== modified file 'openlp/plugins/songs/lib/ewimport.py'
--- openlp/plugins/songs/lib/ewimport.py	2012-06-22 14:14:53 +0000
+++ openlp/plugins/songs/lib/ewimport.py	2012-07-03 21:29:27 +0000
@@ -36,110 +36,14 @@
 
 from openlp.core.lib import translate
 from openlp.plugins.songs.lib import VerseType
-from openlp.plugins.songs.lib import retrieve_windows_encoding
+from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
 from songimport import SongImport
 
-RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}')
 # regex: at least two newlines, can have spaces between them
 SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*')
 NUMBER_REGEX = re.compile(r'[0-9]+')
 NOTE_REGEX = re.compile(r'\(.*?\)')
 
-def strip_rtf(blob, encoding):
-    depth = 0
-    control = False
-    clear_text = []
-    control_word = []
-
-    # workaround for \tx bug: remove one pair of curly braces
-    # if \tx is encountered
-    match = RTF_STRIPPING_REGEX.search(blob)
-    if match:
-        # start and end indices of match are curly braces - filter them out
-        blob = ''.join([blob[i] for i in xrange(len(blob))
-            if i != match.start() and i !=match.end()])
-
-    for c in blob:
-        if control:
-            # for delimiters, set control to False
-            if c == '{':
-                if control_word:
-                    depth += 1
-                control = False
-            elif c == '}':
-                if control_word:
-                    depth -= 1
-                control = False
-            elif c == '\\':
-                new_control = bool(control_word)
-                control = False
-            elif c.isspace():
-                control = False
-            else:
-                control_word.append(c)
-                if len(control_word) == 3 and control_word[0] == '\'':
-                    control = False
-            if not control:
-                if not control_word:
-                    if c == '{' or c == '}' or c == '\\':
-                        clear_text.append(c)
-                else:
-                    control_str = ''.join(control_word)
-                    if control_str == 'par' or control_str == 'line':
-                        clear_text.append(u'\n')
-                    elif control_str == 'tab':
-                        clear_text.append(u'\t')
-                    # Prefer the encoding specified by the RTF data to that
-                    # specified by the Paradox table header
-                    # West European encoding
-                    elif control_str == 'fcharset0':
-                        encoding = u'cp1252'
-                    # Greek encoding
-                    elif control_str == 'fcharset161':
-                        encoding = u'cp1253'
-                    # Turkish encoding
-                    elif control_str == 'fcharset162':
-                        encoding = u'cp1254'
-                    # Vietnamese encoding
-                    elif control_str == 'fcharset163':
-                        encoding = u'cp1258'
-                    # Hebrew encoding
-                    elif control_str == 'fcharset177':
-                        encoding = u'cp1255'
-                    # Arabic encoding
-                    elif control_str == 'fcharset178':
-                        encoding = u'cp1256'
-                    # Baltic encoding
-                    elif control_str == 'fcharset186':
-                        encoding = u'cp1257'
-                    # Cyrillic encoding
-                    elif control_str == 'fcharset204':
-                        encoding = u'cp1251'
-                    # Thai encoding
-                    elif control_str == 'fcharset222':
-                        encoding = u'cp874'
-                    # Central+East European encoding
-                    elif control_str == 'fcharset238':
-                        encoding = u'cp1250'
-                    elif control_str[0] == '\'':
-                        s = chr(int(control_str[1:3], 16))
-                        clear_text.append(s.decode(encoding))
-                    del control_word[:]
-            if c == '\\' and new_control:
-                control = True
-        elif c == '{':
-            depth += 1
-        elif c == '}':
-            depth -= 1
-        elif depth > 2:
-            continue
-        elif c == '\n' or c == '\r':
-            continue
-        elif c == '\\':
-            control = True
-        else:
-            clear_text.append(c)
-    return u''.join(clear_text)
 
 class FieldDescEntry:
     def __init__(self, name, type, size):

=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2012-06-22 14:14:53 +0000
+++ openlp/plugins/songs/lib/importer.py	2012-07-03 21:29:27 +0000
@@ -44,6 +44,7 @@
 from ewimport import EasyWorshipSongImport
 from songbeamerimport import SongBeamerImport
 from songshowplusimport import SongShowPlusImport
+from songproimport import SongProImport
 from foilpresenterimport import FoilPresenterImport
 from zionworximport import ZionWorxImport
 # Imports that might fail
@@ -143,11 +144,11 @@
     OpenSong = 9
     PowerSong = 10
     SongBeamer = 11
-    SongShowPlus = 12
-    SongsOfFellowship = 13
-    WordsOfWorship = 14
-    ZionWorx = 15
-    #CSV = 16
+    SongPro = 12
+    SongShowPlus = 13
+    SongsOfFellowship = 14
+    WordsOfWorship = 15
+    ZionWorx = 16
 
     # Set optional attribute defaults
     __defaults__ = {
@@ -258,6 +259,18 @@
             u'filter': u'%s (*.sng)' % translate('SongsPlugin.ImportWizardForm',
                 'SongBeamer Files')
         },
+        SongPro: {
+            u'class': SongProImport,
+            u'name': u'SongPro',
+            u'prefix': u'songPro',
+            u'selectMode': SongFormatSelect.SingleFile,
+            u'filter': u'%s (*.txt)' % translate('SongsPlugin.ImportWizardForm',
+                'SongPro Text Files'),
+            u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
+                'SongPro (Export File)'),
+            u'descriptionText': translate('SongsPlugin.ImportWizardForm',
+                'In SongPro, export your songs using the File -> Export menu')
+        },
         SongShowPlus: {
             u'class': SongShowPlusImport,
             u'name': u'SongShow Plus',
@@ -293,12 +306,6 @@
                 'First convert your ZionWorx database to a CSV text file, as '
                 'explained in the <a href="http://manual.openlp.org/songs.html'
                 '#importing-from-zionworx">User Manual</a>.')
-#        },
-#        CSV: {
-#            u'class': CSVImport,
-#            u'name': WizardStrings.CSV,
-#            u'prefix': u'csv',
-#            u'selectMode': SongFormatSelect.SingleFile
         }
     }
 
@@ -320,6 +327,7 @@
             SongFormat.OpenSong,
             SongFormat.PowerSong,
             SongFormat.SongBeamer,
+            SongFormat.SongPro,
             SongFormat.SongShowPlus,
             SongFormat.SongsOfFellowship,
             SongFormat.WordsOfWorship,

=== added file 'openlp/plugins/songs/lib/songproimport.py'
--- openlp/plugins/songs/lib/songproimport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/songproimport.py	2012-07-03 21:29:27 +0000
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2012 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+The :mod:`songproimport` module provides the functionality for importing SongPro
+songs into the OpenLP database.
+"""
+import re
+import os
+import logging
+
+from openlp.core.lib import translate
+from openlp.plugins.songs.lib import strip_rtf
+from openlp.plugins.songs.lib.songimport import SongImport
+
+log = logging.getLogger(__name__)
+
+class SongProImport(SongImport):
+    """
+    The :class:`SongProImport` class provides the ability to import song files
+    from SongPro export files.
+
+    **SongPro Song File Format:**
+
+    SongPro has the option to export under its File menu
+    This produces files containing single or multiple songs
+    The file is text with lines tagged with # followed by an identifier.
+    This is documented here: http://creationsoftware.com/ImportIdentifiers.php
+    An example here: http://creationsoftware.com/ExampleImportingManySongs.txt
+
+    #A - next line is the Song Author
+    #B - the lines following until next tagged line are the "Bridge" words
+        (can be in rtf or plain text) which we map as B1
+    #C - the lines following until next tagged line are the chorus words
+        (can be in rtf or plain text)
+        which we map as C1
+    #D - the lines following until next tagged line are the "Ending" words
+        (can be in rtf or plain text) which we map as E1
+    #E - this song ends here, so we process the song -
+        and start again at the next line
+    #G - next line is the Group
+    #M - next line is the Song Number
+    #N - next line are Notes
+    #R - next line is the SongCopyright
+    #O - next line is the Verse Sequence
+    #T - next line is the Song Title
+    #1 - #7 the lines following until next tagged line are the verse x words
+        (can be in rtf or plain text)
+    """
+    def __init__(self, manager, **kwargs):
+        """
+        Initialise the SongPro importer.
+        """
+        SongImport.__init__(self, manager, **kwargs)
+
+    def doImport(self):
+        """
+        Receive a single file or a list of files to import.
+        """
+        with open(self.importSource, 'r') as songs_file:
+            self.importWizard.progressBar.setMaximum(0)
+            tag = u''
+            text = u''
+            for file_line in songs_file:
+                if self.stopImportFlag:
+                    break
+                file_line = unicode(file_line, u'cp1252')
+                file_text = file_line.rstrip()
+                if file_text and file_text[0] == u'#':
+                    self.processSection(tag, text.rstrip())
+                    tag = file_text[1:]
+                    text = u''
+                else:
+                    text += file_line
+
+    def processSection(self, tag, text):
+        """
+        Process a section of the song, i.e. title, verse etc.
+        """
+        if tag == u'T':
+            self.setDefaults()
+            if text:
+                self.title = text
+            self.importWizard.incrementProgressBar(u'Processing song ' + text,
+                0)
+            return
+        elif tag == u'E':
+            self.finish()
+            return
+        if u'rtf1' in text:
+            text = strip_rtf(text, u'cp1252').rstrip()
+        if not text:
+            return
+        if tag == u'A':
+            self.parseAuthor(text)
+        elif tag in [u'B', u'C']:
+            self.addVerse(text, tag)
+        elif tag == u'D':
+            self.addVerse(text, u'E')
+        elif tag == u'G':
+            self.topics.append(text)
+        elif tag == u'M':
+            matches = re.findall(r'\d+', text)
+            if matches:
+                self.songNumber = matches[-1]
+                self.songBookName = text[:text.rfind(self.songNumber)]
+        elif tag == u'N':
+            self.comments = text
+        elif tag == u'O':
+            for char in text:
+                if char == u'C':
+                    self.verseOrderList.append(u'C1')
+                elif char == u'B':
+                    self.verseOrderList.append(u'B1')
+                elif char == u'D':
+                    self.verseOrderList.append(u'E1')
+                elif u'1' <= char <= u'7':
+                    self.verseOrderList.append(u'V' + char)
+        elif tag == u'R':
+            self.addCopyright(text)
+        elif u'1' <= tag <= u'7':
+            self.addVerse(text, u'V' + tag[1:])


Follow ups