← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~sfindlay/openlp/zionworx-import into lp:openlp

 

Samuel Findlay has proposed merging lp:~sfindlay/openlp/zionworx-import into lp:openlp with lp:~sfindlay/openlp/refactor-song-import as a prerequisite.

Requested reviews:
  phill (phill-ridout)
  Raoul Snyman (raoul-snyman)
  Jonathan Corwin (j-corwin)

For more details, see:
https://code.launchpad.net/~sfindlay/openlp/zionworx-import/+merge/109433

Added ZionWorx song database importer.
* Tested on win7 x64 with databases from 3 users. Total 3171 songs, including non-English characters.
* ZionWorx [1] is freeware and windows-only
* Users can download freeware utility "TurboDB Data Exchange" [2] (Win/Linux) and use this command to dump their ZionWorx database to a CSV file which can then be imported by OpenLP:
>> tdbdatax MainTable.dat songstable.csv -fsdf -s, -qd
* Since the importer is not a direct ZionWorx import, I added a descriptionLabel widget to the import wizard, pointing users to the Manual for further info.
* This descriptionLabel widget is also available to other importers (see bug 832345) [3]

[1] http://www.zionworx.org.uk/
[2] http://www.dataweb.de/en/support/downloads.html
[3] https://bugs.launchpad.net/openlp/+bug/832345
-- 
https://code.launchpad.net/~sfindlay/openlp/zionworx-import/+merge/109433
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/ui/wizard.py'
--- openlp/core/ui/wizard.py	2012-06-08 21:10:26 +0000
+++ openlp/core/ui/wizard.py	2012-06-08 21:10:26 +0000
@@ -99,7 +99,7 @@
 
     def setupUi(self, image):
         """
-        Set up the wizard UI
+        Set up the wizard UI.
         """
         self.setModal(True)
         self.setWizardStyle(QtGui.QWizard.ModernStyle)

=== modified file 'openlp/plugins/bibles/lib/csvbible.py'
--- openlp/plugins/bibles/lib/csvbible.py	2011-12-27 10:33:55 +0000
+++ openlp/plugins/bibles/lib/csvbible.py	2012-06-08 21:10:26 +0000
@@ -73,7 +73,7 @@
 
     def __init__(self, parent, **kwargs):
         """
-        Loads a Bible from a set of CVS files.
+        Loads a Bible from a set of CSV files.
         This class assumes the files contain all the information and
         a clean bible is being loaded.
         """

=== modified file 'openlp/plugins/songs/forms/songimportform.py'
--- openlp/plugins/songs/forms/songimportform.py	2012-06-08 21:10:26 +0000
+++ openlp/plugins/songs/forms/songimportform.py	2012-06-08 21:10:26 +0000
@@ -133,6 +133,9 @@
         self.formatLayout.setItem(1, QtGui.QFormLayout.LabelRole,
             self.formatSpacer)
         self.sourceLayout.addLayout(self.formatLayout)
+        self.formatHSpacing = self.formatLayout.horizontalSpacing()
+        self.formatVSpacing = self.formatLayout.verticalSpacing()
+        self.formatLayout.setVerticalSpacing(0)
         self.stackSpacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed,
             QtGui.QSizePolicy.Expanding)
         self.formatStack = QtGui.QStackedLayout()
@@ -160,11 +163,15 @@
         self.sourcePage.setSubTitle(WizardStrings.ImportSelectLong)
         self.formatLabel.setText(WizardStrings.FormatLabel)
         for format in SongFormat.get_format_list():
-            format_name, custom_combo_text, select_mode = SongFormat.get(
-                format, u'name', u'comboBoxText', u'selectMode')
-            combo_box_text = custom_combo_text if custom_combo_text \
-                else format_name
+            format_name, custom_combo_text, description_text, select_mode = \
+                SongFormat.get(format, u'name', u'comboBoxText',
+                u'descriptionText', u'selectMode')
+            combo_box_text = (custom_combo_text if custom_combo_text else
+                format_name)
             self.formatComboBox.setItemText(format, combo_box_text)
+            if description_text is not None:
+                self.formatWidgets[format][u'descriptionLabel'].setText(
+                    description_text)
             if select_mode == SongFormatSelect.MultipleFiles:
                 self.formatWidgets[format][u'addButton'].setText(
                     translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
@@ -205,10 +212,16 @@
             spacer.changeSize(
                 max_label_width - labels[index].minimumSizeHint().width(), 0,
                 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+        # Align descriptionLabels with rest of layout
+        for format in SongFormat.get_format_list():
+            if SongFormat.get(format, u'descriptionText') is not None:
+                self.formatWidgets[format][u'descriptionSpacer'].changeSize(
+                    max_label_width + self.formatHSpacing, 0,
+                    QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
 
     def customPageChanged(self, pageId):
         """
-        Called when changing to a page other than the progress page
+        Called when changing to a page other than the progress page.
         """
         if self.page(pageId) == self.sourcePage:
             self.onCurrentIndexChanged(self.formatStack.currentIndex())
@@ -235,8 +248,8 @@
             else:
                 import_source = \
                     self.formatWidgets[format][u'filepathEdit'].text()
-                error_title = UiStrings().IFSs if select_mode == \
-                    SongFormatSelect.SingleFile else UiStrings().IFdSs
+                error_title = (UiStrings().IFSs if select_mode ==
+                    SongFormatSelect.SingleFile else UiStrings().IFdSs)
                 focus_button = self.formatWidgets[format][u'browseButton']
             if not class_.isValidSource(import_source):
                 critical_error_message_box(error_title, error_msg)
@@ -395,8 +408,8 @@
 
     def addFileSelectItem(self):
         format = self.currentFormat
-        prefix, can_disable, select_mode = SongFormat.get(format, u'prefix',
-            u'canDisable', u'selectMode')
+        prefix, can_disable, description_text, select_mode = SongFormat.get(
+            format, u'prefix', u'canDisable', u'descriptionText', u'selectMode')
         page = QtGui.QWidget()
         page.setObjectName(prefix + u'Page')
         if can_disable:
@@ -406,10 +419,25 @@
         importLayout = QtGui.QVBoxLayout(importWidget)
         importLayout.setMargin(0)
         importLayout.setObjectName(prefix + u'ImportLayout')
+        if description_text is not None:
+            descriptionLayout = QtGui.QHBoxLayout()
+            descriptionLayout.setObjectName(prefix + u'DescriptionLayout')
+            descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed,
+                QtGui.QSizePolicy.Fixed)
+            descriptionLayout.addSpacerItem(descriptionSpacer)
+            descriptionLabel = QtGui.QLabel(importWidget)
+            descriptionLabel.setWordWrap(True)
+            descriptionLabel.setOpenExternalLinks(True)
+            descriptionLabel.setObjectName(prefix + u'DescriptionLabel')
+            descriptionLayout.addWidget(descriptionLabel)
+            importLayout.addLayout(descriptionLayout)
+            self.formatWidgets[format][u'descriptionLabel'] = descriptionLabel
+            self.formatWidgets[format][u'descriptionSpacer'] = descriptionSpacer
         if select_mode == SongFormatSelect.SingleFile or \
             select_mode == SongFormatSelect.SingleFolder:
             filepathLayout = QtGui.QHBoxLayout()
             filepathLayout.setObjectName(prefix + u'FilepathLayout')
+            filepathLayout.setContentsMargins(0, self.formatVSpacing, 0, 0)
             filepathLabel = QtGui.QLabel(importWidget)
             filepathLabel.setObjectName(prefix + u'FilepathLabel')
             filepathLayout.addWidget(filepathLabel)

=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2012-06-08 21:10:26 +0000
+++ openlp/plugins/songs/lib/importer.py	2012-06-08 21:10:26 +0000
@@ -44,6 +44,7 @@
 from songbeamerimport import SongBeamerImport
 from songshowplusimport import SongShowPlusImport
 from foilpresenterimport import FoilPresenterImport
+from zionworximport import ZionWorxImport
 # Imports that might fail
 log = logging.getLogger(__name__)
 try:
@@ -111,6 +112,8 @@
         Title for ``QFileDialog`` (default includes the format's ``u'name'``).
     ``u'invalidSourceMsg'``
         Message displayed if ``isValidSource()`` returns ``False``.
+    ``u'descriptionText'``
+        Short description (1-2 lines) about the song format.
     """
     # Song formats (ordered alphabetically after Generic)
     # * Numerical order of song formats is significant as it determines the
@@ -131,7 +134,8 @@
     SongShowPlus = 12
     SongsOfFellowship = 13
     WordsOfWorship = 14
-    #CSV = 15
+    ZionWorx = 15
+    #CSV = 16
 
     # Set optional attribute defaults
     __defaults__ = {
@@ -142,7 +146,8 @@
         u'comboBoxText': None,
         u'disabledLabelText': u'',
         u'getFilesTitle': None,
-        u'invalidSourceMsg': None
+        u'invalidSourceMsg': None,
+        u'descriptionText': None
     }
 
     # Set attribute values for each Song Format
@@ -264,6 +269,18 @@
             u'prefix': u'wordsOfWorship',
             u'filter': u'%s (*.wsg *.wow-song)' % translate(
                 'SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
+        },
+        ZionWorx: {
+            u'class': ZionWorxImport,
+            u'name': u'ZionWorx',
+            u'prefix': u'zionWorx',
+            u'selectMode': SongFormatSelect.SingleFile,
+            u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
+                'ZionWorx (CSV)'),
+            u'descriptionText': translate('SongsPlugin.ImportWizardForm',
+                '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,
@@ -293,7 +310,8 @@
             SongFormat.SongBeamer,
             SongFormat.SongShowPlus,
             SongFormat.SongsOfFellowship,
-            SongFormat.WordsOfWorship
+            SongFormat.WordsOfWorship,
+            SongFormat.ZionWorx
         ]
     
     @staticmethod

=== added file 'openlp/plugins/songs/lib/zionworximport.py'
--- openlp/plugins/songs/lib/zionworximport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/zionworximport.py	2012-06-08 21:10:26 +0000
@@ -0,0 +1,142 @@
+# -*- 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:`zionworximport` module provides the functionality for importing
+ZionWorx songs into the OpenLP database.
+"""
+import csv
+import logging
+
+from openlp.core.lib import translate
+from openlp.plugins.songs.lib.songimport import SongImport
+
+log = logging.getLogger(__name__)
+
+class ZionWorxImport(SongImport):
+    """
+    The :class:`ZionWorxImport` class provides the ability to import songs
+    from ZionWorx, via a dump of the ZionWorx database to a CSV file.
+
+    ZionWorx song database fields:
+
+    * ``SongNum`` Song ID. (Discarded by importer)
+    * ``Title1`` Main Title.
+    * ``Title2`` Alternate Title.
+    * ``Lyrics`` Song verses, separated by blank lines.
+    * ``Writer`` Song author(s).
+    * ``Copyright`` Copyright information
+    * ``Keywords`` (Discarded by importer)
+    * ``DefaultStyle`` (Discarded by importer)
+
+    ZionWorx has no native export function; it uses the proprietary TurboDB
+    database engine. The TurboDB vendor, dataWeb, provides tools which can
+    export TurboDB tables to other formats, such as freeware console tool
+    TurboDB Data Exchange which is available for Windows and Linux. This command
+    exports the ZionWorx songs table to a CSV file:
+
+    ``tdbdatax MainTable.dat songstable.csv -fsdf -s, -qd``
+
+    * -f  Table format: ``sdf`` denotes text file.
+    * -s  Separator character between fields.
+    * -q  Quote character surrounding fields. ``d`` denotes double-quote.
+
+    CSV format expected by importer:
+
+    * Field separator character is comma ``,``
+    * Fields surrounded by double-quotes ``"``. This enables fields (such as
+      Lyrics) to include new-lines and commas. Double-quotes within a field
+      are denoted by two double-quotes ``""``
+    * Note: This is the default format of the Python ``csv`` module.
+
+    """
+    def doImport(self):
+        """
+        Receive a CSV file (from a ZionWorx database dump) to import.
+        """
+        # Used to strip control chars (10=LF, 13=CR, 127=DEL)
+        self.control_chars_map = dict.fromkeys(
+            range(10) + [11, 12] + range(14,32) + [127])
+        with open(self.importSource, 'rb') as songs_file:
+            fieldnames = [u'SongNum', u'Title1', u'Title2', u'Lyrics',
+                u'Writer', u'Copyright', u'Keywords', u'DefaultStyle']
+            songs_reader = csv.DictReader(songs_file, fieldnames)
+            try:
+                records = list(songs_reader)
+            except csv.Error, e:
+                self.logError(unicode(translate('SongsPlugin.ZionWorxImport',
+                    'Error reading CSV file.')),
+                    unicode(translate('SongsPlugin.ZionWorxImport',
+                    'Line %d: %s' % (songs_reader.line_num, e))))
+                return
+            num_records = len(records)
+            log.info(u'%s records found in CSV file' % num_records)
+            self.importWizard.progressBar.setMaximum(num_records)
+            for index, record in enumerate(records, 1):
+                if self.stopImportFlag:
+                    return
+                self.setDefaults()
+                try:
+                    self.title = self._decode(record[u'Title1'])
+                    if record[u'Title2']:
+                        self.alternateTitle = self._decode(record[u'Title2'])
+                    self.parseAuthor(self._decode(record[u'Writer']))
+                    self.addCopyright(self._decode(record[u'Copyright']))
+                    lyrics = self._decode(record[u'Lyrics'])
+                except UnicodeDecodeError, e:
+                    self.logError(unicode(translate(
+                        'SongsPlugin.ZionWorxImport', 'Record %d' % index)),
+                        unicode(translate('SongsPlugin.ZionWorxImport',
+                        'Decoding error: %s' % e)))
+                    continue
+                except TypeError, e:
+                    self.logError(unicode(translate(
+                        'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx '
+                        'CSV format.')), u'TypeError: %s' % e)
+                    return
+                verse = u''
+                for line in lyrics.splitlines():
+                    if line and not line.isspace():
+                        verse += line + u'\n'
+                    elif verse:
+                        self.addVerse(verse)
+                        verse = u''
+                if verse:
+                    self.addVerse(verse)
+                title = self.title
+                if not self.finish():
+                    self.logError(unicode(translate(
+                        'SongsPlugin.ZionWorxImport', 'Record %d' % index))
+                        + (u': "' + title + u'"' if title else u''))
+
+    def _decode(self, str):
+        """
+        Decodes CSV input to unicode, stripping all control characters (except
+        new lines).
+        """
+        # This encoding choice seems OK. ZionWorx has no option for setting the
+        # encoding for its songs, so we assume encoding is always the same.
+        return unicode(str, u'cp1252').translate(self.control_chars_map)


Follow ups