Added ZionWorx importer. Added descriptionLabel to song import wizard.

bzr-revno: 1986
This commit is contained in:
Samuel Findlay 2012-06-09 19:58:56 +01:00 committed by Tim Bentley
commit f1afae1813
9 changed files with 674 additions and 761 deletions

View File

@ -80,6 +80,10 @@ class UiStrings(object):
self.Help = translate('OpenLP.Ui', 'Help')
self.Hours = translate('OpenLP.Ui', 'h',
'The abbreviated unit for hours')
self.IFdSs = translate('OpenLP.Ui', 'Invalid Folder Selected',
'Singular')
self.IFSs = translate('OpenLP.Ui', 'Invalid File Selected', 'Singular')
self.IFSp = translate('OpenLP.Ui', 'Invalid Files Selected', 'Plural')
self.Image = translate('OpenLP.Ui', 'Image')
self.Import = translate('OpenLP.Ui', 'Import')
self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:')

View File

@ -44,20 +44,9 @@ class WizardStrings(object):
# Applications/Formats we import from or export to. These get used in
# multiple places but do not need translating unless you find evidence of
# the writers translating their own product name.
CCLI = u'CCLI/SongSelect'
CSV = u'CSV'
DB = u'DreamBeam'
EW = u'EasyWorship'
ES = u'EasySlides'
FP = u'Foilpresenter'
OL = u'OpenLyrics'
OS = u'OpenSong'
OSIS = u'OSIS'
PS = u'PowerSong 1.0'
SB = u'SongBeamer'
SoF = u'Songs of Fellowship'
SSP = u'SongShow Plus'
WoW = u'Words of Worship'
# These strings should need a good reason to be retranslated elsewhere.
FinishedImport = translate('OpenLP.Ui', 'Finished import.')
FormatLabel = translate('OpenLP.Ui', 'Format:')
@ -76,10 +65,12 @@ class WizardStrings(object):
PercentSymbolFormat = unicode(translate('OpenLP.Ui', '%p%'))
Ready = translate('OpenLP.Ui', 'Ready.')
StartingImport = translate('OpenLP.Ui', 'Starting import...')
YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify at '
YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify one '
'%s file to import from.', 'A file type e.g. OpenSong'))
YouSpecifyFiles = unicode(translate('OpenLP.Ui', 'You need to specify at '
'least one %s file to import from.', 'A file type e.g. OpenSong'))
YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify a '
'%s folder to import from.', 'A file type e.g. OpenSong'))
YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify one '
'%s folder to import from.', 'A song format e.g. PowerSong'))
class OpenLPWizard(QtGui.QWizard):
@ -108,7 +99,7 @@ class OpenLPWizard(QtGui.QWizard):
def setupUi(self, image):
"""
Set up the wizard UI
Set up the wizard UI.
"""
self.setModal(True)
self.setWizardStyle(QtGui.QWizard.ModernStyle)

View File

@ -73,7 +73,7 @@ class CSVBible(BibleDB):
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.
"""

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,9 @@ The :mod:`importer` modules provides the general song import functionality.
"""
import logging
from openlp.core.lib import translate
from openlp.core.lib.ui import UiStrings
from openlp.core.ui.wizard import WizardStrings
from opensongimport import OpenSongImport
from easyslidesimport import EasySlidesImport
from olpimport import OpenLPSongImport
@ -41,6 +44,7 @@ from ewimport import EasyWorshipSongImport
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:
@ -62,13 +66,58 @@ except ImportError:
log.exception('Error importing %s', 'OooImport')
HAS_OOO = False
class SongFormatSelect(object):
"""
This is a special enumeration class listing available file selection modes.
"""
SingleFile = 0
MultipleFiles = 1
SingleFolder = 2
class SongFormat(object):
"""
This is a special enumeration class that holds the various types of songs,
plus a few helper functions to facilitate generic handling of song types
for importing.
This is a special static class that holds an enumeration of the various
song formats handled by the importer, the attributes of each song format,
and a few helper functions.
Required attributes for each song format:
``u'class'``
Import class, e.g. ``OpenLyricsImport``
``u'name'``
Name of the format, e.g. ``u'OpenLyrics'``
``u'prefix'``
Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'``
See ``SongImportForm.addFileSelectItem()``
Optional attributes for each song format:
``u'canDisable'``
Whether song format importer is disablable.
``u'availability'``
Whether song format importer is available.
``u'selectMode'``
Whether format accepts single file, multiple files, or single folder
(as per ``SongFormatSelect`` options).
``u'filter'``
File extension filter for ``QFileDialog``.
Optional/custom text Strings for ``SongImportForm`` widgets:
``u'comboBoxText'``
Combo box selector (default value is the format's ``u'name'``).
``u'disabledLabelText'``
Required for disablable song formats.
``u'getFilesTitle'``
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.
"""
_format_availability = {}
# Song formats (ordered alphabetically after Generic)
# * Numerical order of song formats is significant as it determines the
# order used by formatComboBox.
Unknown = -1
OpenLyrics = 0
OpenLP2 = 1
@ -85,50 +134,164 @@ class SongFormat(object):
SongShowPlus = 12
SongsOfFellowship = 13
WordsOfWorship = 14
#CSV = 15
ZionWorx = 15
#CSV = 16
# Set optional attribute defaults
__defaults__ = {
u'canDisable': False,
u'availability': True,
u'selectMode': SongFormatSelect.MultipleFiles,
u'filter': u'',
u'comboBoxText': None,
u'disabledLabelText': u'',
u'getFilesTitle': None,
u'invalidSourceMsg': None,
u'descriptionText': None
}
# Set attribute values for each Song Format
__attributes__ = {
OpenLyrics: {
u'class': OpenLyricsImport,
u'name': u'OpenLyrics',
u'prefix': u'openLyrics',
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
'OpenLyrics Files'),
u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
'OpenLyrics or OpenLP 2.0 Exported Song')
},
OpenLP2: {
u'class': OpenLPSongImport,
u'name': UiStrings().OLPV2,
u'prefix': u'openLP2',
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.sqlite)' % (translate(
'SongsPlugin.ImportWizardForm', 'OpenLP 2.0 Databases'))
},
OpenLP1: {
u'name': UiStrings().OLPV1,
u'prefix': u'openLP1',
u'canDisable': True,
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.olp)' % translate('SongsPlugin.ImportWizardForm',
'openlp.org v1.x Databases'),
u'disabledLabelText': WizardStrings.NoSqlite
},
Generic: {
u'name': translate('SongsPlugin.ImportWizardForm',
'Generic Document/Presentation'),
u'prefix': u'generic',
u'canDisable': True,
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The generic document/presentation importer has been disabled '
'because OpenLP cannot access OpenOffice or LibreOffice.'),
u'getFilesTitle': translate('SongsPlugin.ImportWizardForm',
'Select Document/Presentation Files')
},
CCLI: {
u'class': CCLIFileImport,
u'name': u'CCLI/SongSelect',
u'prefix': u'ccli',
u'filter': u'%s (*.usr *.txt)' % translate(
'SongsPlugin.ImportWizardForm', 'CCLI SongSelect Files')
},
DreamBeam: {
u'class': DreamBeamImport,
u'name': u'DreamBeam',
u'prefix': u'dreamBeam',
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
'DreamBeam Song Files')
},
EasySlides: {
u'class': EasySlidesImport,
u'name': u'EasySlides',
u'prefix': u'easySlides',
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.xml)' % translate('SongsPlugin.ImportWizardForm',
'EasySlides XML File')
},
EasyWorship: {
u'class': EasyWorshipSongImport,
u'name': u'EasyWorship',
u'prefix': u'ew',
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.db)' % translate('SongsPlugin.ImportWizardForm',
'EasyWorship Song Database')
},
FoilPresenter: {
u'class': FoilPresenterImport,
u'name': u'Foilpresenter',
u'prefix': u'foilPresenter',
u'filter': u'%s (*.foil)' % translate(
'SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files')
},
OpenSong: {
u'class': OpenSongImport,
u'name': WizardStrings.OS,
u'prefix': u'openSong'
},
PowerSong: {
u'class': PowerSongImport,
u'name': u'PowerSong 1.0',
u'prefix': u'powerSong',
u'selectMode': SongFormatSelect.SingleFolder,
u'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm',
'You need to specify a valid PowerSong 1.0 database folder.')
},
SongBeamer: {
u'class': SongBeamerImport,
u'name': u'SongBeamer',
u'prefix': u'songBeamer',
u'filter': u'%s (*.sng)' % translate('SongsPlugin.ImportWizardForm',
'SongBeamer Files')
},
SongShowPlus: {
u'class': SongShowPlusImport,
u'name': u'SongShow Plus',
u'prefix': u'songShowPlus',
u'filter': u'%s (*.sbsong)' % translate(
'SongsPlugin.ImportWizardForm', 'SongShow Plus Song Files')
},
SongsOfFellowship: {
u'name': u'Songs of Fellowship',
u'prefix': u'songsOfFellowship',
u'canDisable': True,
u'filter': u'%s (*.rtf)' % translate('SongsPlugin.ImportWizardForm',
'Songs Of Fellowship Song Files'),
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The Songs of Fellowship importer has been disabled because '
'OpenLP cannot access OpenOffice or LibreOffice.')
},
WordsOfWorship: {
u'class': WowImport,
u'name': u'Words of Worship',
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,
# u'name': WizardStrings.CSV,
# u'prefix': u'csv',
# u'selectMode': SongFormatSelect.SingleFile
}
}
@staticmethod
def get_class(format):
"""
Return the appropriate implementation class.
``format``
The song format.
"""
if format == SongFormat.OpenLP2:
return OpenLPSongImport
elif format == SongFormat.OpenLP1:
return OpenLP1SongImport
elif format == SongFormat.OpenLyrics:
return OpenLyricsImport
elif format == SongFormat.OpenSong:
return OpenSongImport
elif format == SongFormat.SongsOfFellowship:
return SofImport
elif format == SongFormat.WordsOfWorship:
return WowImport
elif format == SongFormat.Generic:
return OooImport
elif format == SongFormat.CCLI:
return CCLIFileImport
elif format == SongFormat.DreamBeam:
return DreamBeamImport
elif format == SongFormat.PowerSong:
return PowerSongImport
elif format == SongFormat.EasySlides:
return EasySlidesImport
elif format == SongFormat.EasyWorship:
return EasyWorshipSongImport
elif format == SongFormat.SongBeamer:
return SongBeamerImport
elif format == SongFormat.SongShowPlus:
return SongShowPlusImport
elif format == SongFormat.FoilPresenter:
return FoilPresenterImport
return None
@staticmethod
def get_formats_list():
def get_format_list():
"""
Return a list of the supported song formats.
"""
@ -147,26 +310,55 @@ class SongFormat(object):
SongFormat.SongBeamer,
SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship,
SongFormat.WordsOfWorship
SongFormat.WordsOfWorship,
SongFormat.ZionWorx
]
@staticmethod
def set_availability(format, available):
def get(format, *attributes):
"""
Set the availability for a given song format.
Return requested song format attribute(s).
``format``
A song format from SongFormat.
``*attributes``
Zero or more song format attributes from SongFormat.
Return type depends on number of supplied attributes:
:0: Return dict containing all defined attributes for the format.
:1: Return the attribute value.
:>1: Return tuple of requested attribute values.
"""
SongFormat._format_availability[format] = available
if not attributes:
return SongFormat.__attributes__.get(format)
elif len(attributes) == 1:
default = SongFormat.__defaults__.get(attributes[0])
return SongFormat.__attributes__[format].get(attributes[0],
default)
else:
values = []
for attr in attributes:
default = SongFormat.__defaults__.get(attr)
values.append(SongFormat.__attributes__[format].get(attr,
default))
return tuple(values)
@staticmethod
def get_availability(format):
def set(format, attribute, value):
"""
Return the availability of a given song format.
Set specified song format attribute to the supplied value.
"""
return SongFormat._format_availability.get(format, True)
SongFormat.__attributes__[format][attribute] = value
SongFormat.set_availability(SongFormat.OpenLP1, HAS_OPENLP1)
SongFormat.set_availability(SongFormat.SongsOfFellowship, HAS_SOF)
SongFormat.set_availability(SongFormat.Generic, HAS_OOO)
__all__ = [u'SongFormat']
SongFormat.set(SongFormat.OpenLP1, u'availability', HAS_OPENLP1)
if HAS_OPENLP1:
SongFormat.set(SongFormat.OpenLP1, u'class', OpenLP1SongImport)
SongFormat.set(SongFormat.SongsOfFellowship, u'availability', HAS_SOF)
if HAS_SOF:
SongFormat.set(SongFormat.SongsOfFellowship, u'class', SofImport)
SongFormat.set(SongFormat.Generic, u'availability', HAS_OOO)
if HAS_OOO:
SongFormat.set(SongFormat.Generic, u'class', OooImport)
__all__ = [u'SongFormat', u'SongFormatSelect']

View File

@ -33,7 +33,6 @@ import fnmatch
import os
from openlp.core.lib import translate
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
@ -71,18 +70,15 @@ class PowerSongImport(SongImport):
* .song
"""
@staticmethod
def isValidSource(**kwargs):
def isValidSource(import_source):
"""
Checks if source is a PowerSong 1.0 folder:
* is a directory
* contains at least one *.song file
"""
if u'folder' in kwargs:
dir = kwargs[u'folder']
if os.path.isdir(dir):
for file in os.listdir(dir):
if os.path.isdir(import_source):
for file in os.listdir(import_source):
if fnmatch.fnmatch(file, u'*.song'):
return True
return False
@ -91,6 +87,8 @@ class PowerSongImport(SongImport):
"""
Receive either a list of files or a folder (unicode) to import.
"""
from importer import SongFormat
PS_string = SongFormat.get(SongFormat.PowerSong, u'name')
if isinstance(self.importSource, unicode):
if os.path.isdir(self.importSource):
dir = self.importSource
@ -104,7 +102,7 @@ class PowerSongImport(SongImport):
self.logError(unicode(translate('SongsPlugin.PowerSongImport',
'No songs to import.')),
unicode(translate('SongsPlugin.PowerSongImport',
'No %s files found.' % WizardStrings.PS)))
'No %s files found.' % PS_string)))
return
self.importWizard.progressBar.setMaximum(len(self.importSource))
for file in self.importSource:
@ -124,7 +122,7 @@ class PowerSongImport(SongImport):
self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Unexpected byte value.'
% WizardStrings.PS)))
% PS_string)))
break
else:
if label == u'TITLE':
@ -142,15 +140,14 @@ class PowerSongImport(SongImport):
if not self.title:
self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "TITLE" header.'
% WizardStrings.PS)))
'Invalid %s file. Missing "TITLE" header.' % PS_string)))
continue
# Check that file had COPYRIGHTLINE label
if not found_copyright:
self.logError(self.title, unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "COPYRIGHTLINE" '
'header.' % WizardStrings.PS)))
'header.' % PS_string)))
continue
# Check that file had at least one verse
if not self.verses:

View File

@ -51,11 +51,11 @@ class SongImport(QtCore.QObject):
as necessary
"""
@staticmethod
def isValidSource(**kwargs):
def isValidSource(import_source):
"""
Override this method to validate the source prior to import.
"""
pass
return True
def __init__(self, manager, **kwargs):
"""

View File

@ -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)

View File

@ -194,7 +194,7 @@ class SongsPlugin(Plugin):
self.manager.save_object(song)
def importSongs(self, format, **kwargs):
class_ = SongFormat.get_class(format)
class_ = SongFormat.get(format, u'class')
importer = class_(self.manager, **kwargs)
importer.register(self.mediaItem.importWizard)
return importer