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

Proposed by Samuel Findlay
Status: Merged
Merged at revision: 1986
Proposed branch: lp:~sfindlay/openlp/zionworx-import
Merge into: lp:openlp
Prerequisite: lp:~sfindlay/openlp/refactor-song-import
Diff against target: 342 lines (+202/-14)
5 files modified
openlp/core/ui/wizard.py (+1/-1)
openlp/plugins/bibles/lib/csvbible.py (+1/-1)
openlp/plugins/songs/forms/songimportform.py (+37/-9)
openlp/plugins/songs/lib/importer.py (+21/-3)
openlp/plugins/songs/lib/zionworximport.py (+142/-0)
To merge this branch: bzr merge lp:~sfindlay/openlp/zionworx-import
Reviewer Review Type Date Requested Status
Jonathan Corwin (community) Approve
Raoul Snyman Approve
Phill Pending
Review via email: mp+109433@code.launchpad.net

This proposal supersedes a proposal from 2012-06-07.

Commit message

Added ZionWorx importer. Added descriptionLabel to song import wizard.

Description of the change

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

To post a comment you must log in.
Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

189-191: Would it be possible to translate the paragraph into English? :) Remember the users may not be technical so lots of "dump", "CSV" type phrases might scare folk off.

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve
Revision history for this message
Jonathan Corwin (j-corwin) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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 @@
9999
100 def setupUi(self, image):100 def setupUi(self, image):
101 """101 """
102 Set up the wizard UI102 Set up the wizard UI.
103 """103 """
104 self.setModal(True)104 self.setModal(True)
105 self.setWizardStyle(QtGui.QWizard.ModernStyle)105 self.setWizardStyle(QtGui.QWizard.ModernStyle)
106106
=== 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 @@
7373
74 def __init__(self, parent, **kwargs):74 def __init__(self, parent, **kwargs):
75 """75 """
76 Loads a Bible from a set of CVS files.76 Loads a Bible from a set of CSV files.
77 This class assumes the files contain all the information and77 This class assumes the files contain all the information and
78 a clean bible is being loaded.78 a clean bible is being loaded.
79 """79 """
8080
=== 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 @@
133 self.formatLayout.setItem(1, QtGui.QFormLayout.LabelRole,133 self.formatLayout.setItem(1, QtGui.QFormLayout.LabelRole,
134 self.formatSpacer)134 self.formatSpacer)
135 self.sourceLayout.addLayout(self.formatLayout)135 self.sourceLayout.addLayout(self.formatLayout)
136 self.formatHSpacing = self.formatLayout.horizontalSpacing()
137 self.formatVSpacing = self.formatLayout.verticalSpacing()
138 self.formatLayout.setVerticalSpacing(0)
136 self.stackSpacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed,139 self.stackSpacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed,
137 QtGui.QSizePolicy.Expanding)140 QtGui.QSizePolicy.Expanding)
138 self.formatStack = QtGui.QStackedLayout()141 self.formatStack = QtGui.QStackedLayout()
@@ -160,11 +163,15 @@
160 self.sourcePage.setSubTitle(WizardStrings.ImportSelectLong)163 self.sourcePage.setSubTitle(WizardStrings.ImportSelectLong)
161 self.formatLabel.setText(WizardStrings.FormatLabel)164 self.formatLabel.setText(WizardStrings.FormatLabel)
162 for format in SongFormat.get_format_list():165 for format in SongFormat.get_format_list():
163 format_name, custom_combo_text, select_mode = SongFormat.get(166 format_name, custom_combo_text, description_text, select_mode = \
164 format, u'name', u'comboBoxText', u'selectMode')167 SongFormat.get(format, u'name', u'comboBoxText',
165 combo_box_text = custom_combo_text if custom_combo_text \168 u'descriptionText', u'selectMode')
166 else format_name169 combo_box_text = (custom_combo_text if custom_combo_text else
170 format_name)
167 self.formatComboBox.setItemText(format, combo_box_text)171 self.formatComboBox.setItemText(format, combo_box_text)
172 if description_text is not None:
173 self.formatWidgets[format][u'descriptionLabel'].setText(
174 description_text)
168 if select_mode == SongFormatSelect.MultipleFiles:175 if select_mode == SongFormatSelect.MultipleFiles:
169 self.formatWidgets[format][u'addButton'].setText(176 self.formatWidgets[format][u'addButton'].setText(
170 translate('SongsPlugin.ImportWizardForm', 'Add Files...'))177 translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
@@ -205,10 +212,16 @@
205 spacer.changeSize(212 spacer.changeSize(
206 max_label_width - labels[index].minimumSizeHint().width(), 0,213 max_label_width - labels[index].minimumSizeHint().width(), 0,
207 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)214 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
215 # Align descriptionLabels with rest of layout
216 for format in SongFormat.get_format_list():
217 if SongFormat.get(format, u'descriptionText') is not None:
218 self.formatWidgets[format][u'descriptionSpacer'].changeSize(
219 max_label_width + self.formatHSpacing, 0,
220 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
208221
209 def customPageChanged(self, pageId):222 def customPageChanged(self, pageId):
210 """223 """
211 Called when changing to a page other than the progress page224 Called when changing to a page other than the progress page.
212 """225 """
213 if self.page(pageId) == self.sourcePage:226 if self.page(pageId) == self.sourcePage:
214 self.onCurrentIndexChanged(self.formatStack.currentIndex())227 self.onCurrentIndexChanged(self.formatStack.currentIndex())
@@ -235,8 +248,8 @@
235 else:248 else:
236 import_source = \249 import_source = \
237 self.formatWidgets[format][u'filepathEdit'].text()250 self.formatWidgets[format][u'filepathEdit'].text()
238 error_title = UiStrings().IFSs if select_mode == \251 error_title = (UiStrings().IFSs if select_mode ==
239 SongFormatSelect.SingleFile else UiStrings().IFdSs252 SongFormatSelect.SingleFile else UiStrings().IFdSs)
240 focus_button = self.formatWidgets[format][u'browseButton']253 focus_button = self.formatWidgets[format][u'browseButton']
241 if not class_.isValidSource(import_source):254 if not class_.isValidSource(import_source):
242 critical_error_message_box(error_title, error_msg)255 critical_error_message_box(error_title, error_msg)
@@ -395,8 +408,8 @@
395408
396 def addFileSelectItem(self):409 def addFileSelectItem(self):
397 format = self.currentFormat410 format = self.currentFormat
398 prefix, can_disable, select_mode = SongFormat.get(format, u'prefix',411 prefix, can_disable, description_text, select_mode = SongFormat.get(
399 u'canDisable', u'selectMode')412 format, u'prefix', u'canDisable', u'descriptionText', u'selectMode')
400 page = QtGui.QWidget()413 page = QtGui.QWidget()
401 page.setObjectName(prefix + u'Page')414 page.setObjectName(prefix + u'Page')
402 if can_disable:415 if can_disable:
@@ -406,10 +419,25 @@
406 importLayout = QtGui.QVBoxLayout(importWidget)419 importLayout = QtGui.QVBoxLayout(importWidget)
407 importLayout.setMargin(0)420 importLayout.setMargin(0)
408 importLayout.setObjectName(prefix + u'ImportLayout')421 importLayout.setObjectName(prefix + u'ImportLayout')
422 if description_text is not None:
423 descriptionLayout = QtGui.QHBoxLayout()
424 descriptionLayout.setObjectName(prefix + u'DescriptionLayout')
425 descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed,
426 QtGui.QSizePolicy.Fixed)
427 descriptionLayout.addSpacerItem(descriptionSpacer)
428 descriptionLabel = QtGui.QLabel(importWidget)
429 descriptionLabel.setWordWrap(True)
430 descriptionLabel.setOpenExternalLinks(True)
431 descriptionLabel.setObjectName(prefix + u'DescriptionLabel')
432 descriptionLayout.addWidget(descriptionLabel)
433 importLayout.addLayout(descriptionLayout)
434 self.formatWidgets[format][u'descriptionLabel'] = descriptionLabel
435 self.formatWidgets[format][u'descriptionSpacer'] = descriptionSpacer
409 if select_mode == SongFormatSelect.SingleFile or \436 if select_mode == SongFormatSelect.SingleFile or \
410 select_mode == SongFormatSelect.SingleFolder:437 select_mode == SongFormatSelect.SingleFolder:
411 filepathLayout = QtGui.QHBoxLayout()438 filepathLayout = QtGui.QHBoxLayout()
412 filepathLayout.setObjectName(prefix + u'FilepathLayout')439 filepathLayout.setObjectName(prefix + u'FilepathLayout')
440 filepathLayout.setContentsMargins(0, self.formatVSpacing, 0, 0)
413 filepathLabel = QtGui.QLabel(importWidget)441 filepathLabel = QtGui.QLabel(importWidget)
414 filepathLabel.setObjectName(prefix + u'FilepathLabel')442 filepathLabel.setObjectName(prefix + u'FilepathLabel')
415 filepathLayout.addWidget(filepathLabel)443 filepathLayout.addWidget(filepathLabel)
416444
=== 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 @@
44from songbeamerimport import SongBeamerImport44from songbeamerimport import SongBeamerImport
45from songshowplusimport import SongShowPlusImport45from songshowplusimport import SongShowPlusImport
46from foilpresenterimport import FoilPresenterImport46from foilpresenterimport import FoilPresenterImport
47from zionworximport import ZionWorxImport
47# Imports that might fail48# Imports that might fail
48log = logging.getLogger(__name__)49log = logging.getLogger(__name__)
49try:50try:
@@ -111,6 +112,8 @@
111 Title for ``QFileDialog`` (default includes the format's ``u'name'``).112 Title for ``QFileDialog`` (default includes the format's ``u'name'``).
112 ``u'invalidSourceMsg'``113 ``u'invalidSourceMsg'``
113 Message displayed if ``isValidSource()`` returns ``False``.114 Message displayed if ``isValidSource()`` returns ``False``.
115 ``u'descriptionText'``
116 Short description (1-2 lines) about the song format.
114 """117 """
115 # Song formats (ordered alphabetically after Generic)118 # Song formats (ordered alphabetically after Generic)
116 # * Numerical order of song formats is significant as it determines the119 # * Numerical order of song formats is significant as it determines the
@@ -131,7 +134,8 @@
131 SongShowPlus = 12134 SongShowPlus = 12
132 SongsOfFellowship = 13135 SongsOfFellowship = 13
133 WordsOfWorship = 14136 WordsOfWorship = 14
134 #CSV = 15137 ZionWorx = 15
138 #CSV = 16
135139
136 # Set optional attribute defaults140 # Set optional attribute defaults
137 __defaults__ = {141 __defaults__ = {
@@ -142,7 +146,8 @@
142 u'comboBoxText': None,146 u'comboBoxText': None,
143 u'disabledLabelText': u'',147 u'disabledLabelText': u'',
144 u'getFilesTitle': None,148 u'getFilesTitle': None,
145 u'invalidSourceMsg': None149 u'invalidSourceMsg': None,
150 u'descriptionText': None
146 }151 }
147152
148 # Set attribute values for each Song Format153 # Set attribute values for each Song Format
@@ -264,6 +269,18 @@
264 u'prefix': u'wordsOfWorship',269 u'prefix': u'wordsOfWorship',
265 u'filter': u'%s (*.wsg *.wow-song)' % translate(270 u'filter': u'%s (*.wsg *.wow-song)' % translate(
266 'SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')271 'SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
272 },
273 ZionWorx: {
274 u'class': ZionWorxImport,
275 u'name': u'ZionWorx',
276 u'prefix': u'zionWorx',
277 u'selectMode': SongFormatSelect.SingleFile,
278 u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
279 'ZionWorx (CSV)'),
280 u'descriptionText': translate('SongsPlugin.ImportWizardForm',
281 'First convert your ZionWorx database to a CSV text file, as '
282 'explained in the <a href="http://manual.openlp.org/songs.html'
283 '#importing-from-zionworx">User Manual</a>.')
267# },284# },
268# CSV: {285# CSV: {
269# u'class': CSVImport,286# u'class': CSVImport,
@@ -293,7 +310,8 @@
293 SongFormat.SongBeamer,310 SongFormat.SongBeamer,
294 SongFormat.SongShowPlus,311 SongFormat.SongShowPlus,
295 SongFormat.SongsOfFellowship,312 SongFormat.SongsOfFellowship,
296 SongFormat.WordsOfWorship313 SongFormat.WordsOfWorship,
314 SongFormat.ZionWorx
297 ]315 ]
298 316
299 @staticmethod317 @staticmethod
300318
=== 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 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2012 Raoul Snyman #
8# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan #
9# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
10# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
11# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
12# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
13# --------------------------------------------------------------------------- #
14# This program is free software; you can redistribute it and/or modify it #
15# under the terms of the GNU General Public License as published by the Free #
16# Software Foundation; version 2 of the License. #
17# #
18# This program is distributed in the hope that it will be useful, but WITHOUT #
19# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
20# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
21# more details. #
22# #
23# You should have received a copy of the GNU General Public License along #
24# with this program; if not, write to the Free Software Foundation, Inc., 59 #
25# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
26###############################################################################
27"""
28The :mod:`zionworximport` module provides the functionality for importing
29ZionWorx songs into the OpenLP database.
30"""
31import csv
32import logging
33
34from openlp.core.lib import translate
35from openlp.plugins.songs.lib.songimport import SongImport
36
37log = logging.getLogger(__name__)
38
39class ZionWorxImport(SongImport):
40 """
41 The :class:`ZionWorxImport` class provides the ability to import songs
42 from ZionWorx, via a dump of the ZionWorx database to a CSV file.
43
44 ZionWorx song database fields:
45
46 * ``SongNum`` Song ID. (Discarded by importer)
47 * ``Title1`` Main Title.
48 * ``Title2`` Alternate Title.
49 * ``Lyrics`` Song verses, separated by blank lines.
50 * ``Writer`` Song author(s).
51 * ``Copyright`` Copyright information
52 * ``Keywords`` (Discarded by importer)
53 * ``DefaultStyle`` (Discarded by importer)
54
55 ZionWorx has no native export function; it uses the proprietary TurboDB
56 database engine. The TurboDB vendor, dataWeb, provides tools which can
57 export TurboDB tables to other formats, such as freeware console tool
58 TurboDB Data Exchange which is available for Windows and Linux. This command
59 exports the ZionWorx songs table to a CSV file:
60
61 ``tdbdatax MainTable.dat songstable.csv -fsdf -s, -qd``
62
63 * -f Table format: ``sdf`` denotes text file.
64 * -s Separator character between fields.
65 * -q Quote character surrounding fields. ``d`` denotes double-quote.
66
67 CSV format expected by importer:
68
69 * Field separator character is comma ``,``
70 * Fields surrounded by double-quotes ``"``. This enables fields (such as
71 Lyrics) to include new-lines and commas. Double-quotes within a field
72 are denoted by two double-quotes ``""``
73 * Note: This is the default format of the Python ``csv`` module.
74
75 """
76 def doImport(self):
77 """
78 Receive a CSV file (from a ZionWorx database dump) to import.
79 """
80 # Used to strip control chars (10=LF, 13=CR, 127=DEL)
81 self.control_chars_map = dict.fromkeys(
82 range(10) + [11, 12] + range(14,32) + [127])
83 with open(self.importSource, 'rb') as songs_file:
84 fieldnames = [u'SongNum', u'Title1', u'Title2', u'Lyrics',
85 u'Writer', u'Copyright', u'Keywords', u'DefaultStyle']
86 songs_reader = csv.DictReader(songs_file, fieldnames)
87 try:
88 records = list(songs_reader)
89 except csv.Error, e:
90 self.logError(unicode(translate('SongsPlugin.ZionWorxImport',
91 'Error reading CSV file.')),
92 unicode(translate('SongsPlugin.ZionWorxImport',
93 'Line %d: %s' % (songs_reader.line_num, e))))
94 return
95 num_records = len(records)
96 log.info(u'%s records found in CSV file' % num_records)
97 self.importWizard.progressBar.setMaximum(num_records)
98 for index, record in enumerate(records, 1):
99 if self.stopImportFlag:
100 return
101 self.setDefaults()
102 try:
103 self.title = self._decode(record[u'Title1'])
104 if record[u'Title2']:
105 self.alternateTitle = self._decode(record[u'Title2'])
106 self.parseAuthor(self._decode(record[u'Writer']))
107 self.addCopyright(self._decode(record[u'Copyright']))
108 lyrics = self._decode(record[u'Lyrics'])
109 except UnicodeDecodeError, e:
110 self.logError(unicode(translate(
111 'SongsPlugin.ZionWorxImport', 'Record %d' % index)),
112 unicode(translate('SongsPlugin.ZionWorxImport',
113 'Decoding error: %s' % e)))
114 continue
115 except TypeError, e:
116 self.logError(unicode(translate(
117 'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx '
118 'CSV format.')), u'TypeError: %s' % e)
119 return
120 verse = u''
121 for line in lyrics.splitlines():
122 if line and not line.isspace():
123 verse += line + u'\n'
124 elif verse:
125 self.addVerse(verse)
126 verse = u''
127 if verse:
128 self.addVerse(verse)
129 title = self.title
130 if not self.finish():
131 self.logError(unicode(translate(
132 'SongsPlugin.ZionWorxImport', 'Record %d' % index))
133 + (u': "' + title + u'"' if title else u''))
134
135 def _decode(self, str):
136 """
137 Decodes CSV input to unicode, stripping all control characters (except
138 new lines).
139 """
140 # This encoding choice seems OK. ZionWorx has no option for setting the
141 # encoding for its songs, so we assume encoding is always the same.
142 return unicode(str, u'cp1252').translate(self.control_chars_map)