From 64d0494ff16321c79d4840162b2aac1a9054b668 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Sat, 23 Jun 2012 23:47:26 +0100 Subject: [PATCH 01/32] SongPro import --- openlp/plugins/songs/lib/importer.py | 30 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 6f54114b2..04ac6df56 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -44,6 +44,7 @@ from powersongimport import PowerSongImport 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 @@ class SongFormat(object): 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 @@ class SongFormat(object): 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 @@ class SongFormat(object): 'First convert your ZionWorx database to a CSV text file, as ' 'explained in the User Manual.') -# }, -# CSV: { -# u'class': CSVImport, -# u'name': WizardStrings.CSV, -# u'prefix': u'csv', -# u'selectMode': SongFormatSelect.SingleFile } } @@ -320,6 +327,7 @@ class SongFormat(object): SongFormat.OpenSong, SongFormat.PowerSong, SongFormat.SongBeamer, + SongFormat.SongPro, SongFormat.SongShowPlus, SongFormat.SongsOfFellowship, SongFormat.WordsOfWorship, From f70b9d35474b49322e7e28b90c690848665e6478 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Mon, 25 Jun 2012 23:26:46 +0100 Subject: [PATCH 02/32] Include the import class too perhaps --- openlp/plugins/songs/lib/songproimport.py | 263 ++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 openlp/plugins/songs/lib/songproimport.py diff --git a/openlp/plugins/songs/lib/songproimport.py b/openlp/plugins/songs/lib/songproimport.py new file mode 100644 index 000000000..a22f18d14 --- /dev/null +++ b/openlp/plugins/songs/lib/songproimport.py @@ -0,0 +1,263 @@ +# -*- 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.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 = striprtf(text).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 <= '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:]) + +# replace with mahfiaz's shared one when his import is merged +def striprtf(text): + pattern = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I) + # control words which specify a "destionation". + destinations = frozenset(( + 'aftncn','aftnsep','aftnsepc','annotation','atnauthor','atndate','atnicn','atnid', + 'atnparent','atnref','atntime','atrfend','atrfstart','author','background', + 'bkmkend','bkmkstart','blipuid','buptim','category','colorschememapping', + 'colortbl','comment','company','creatim','datafield','datastore','defchp','defpap', + 'do','doccomm','docvar','dptxbxtext','ebcend','ebcstart','factoidname','falt', + 'fchars','ffdeftext','ffentrymcr','ffexitmcr','ffformat','ffhelptext','ffl', + 'ffname','ffstattext','field','file','filetbl','fldinst','fldrslt','fldtype', + 'fname','fontemb','fontfile','fonttbl','footer','footerf','footerl','footerr', + 'footnote','formfield','ftncn','ftnsep','ftnsepc','g','generator','gridtbl', + 'header','headerf','headerl','headerr','hl','hlfr','hlinkbase','hlloc','hlsrc', + 'hsv','htmltag','info','keycode','keywords','latentstyles','lchars','levelnumbers', + 'leveltext','lfolevel','linkval','list','listlevel','listname','listoverride', + 'listoverridetable','listpicture','liststylename','listtable','listtext', + 'lsdlockedexcept','macc','maccPr','mailmerge','maln','malnScr','manager','margPr', + 'mbar','mbarPr','mbaseJc','mbegChr','mborderBox','mborderBoxPr','mbox','mboxPr', + 'mchr','mcount','mctrlPr','md','mdeg','mdegHide','mden','mdiff','mdPr','me', + 'mendChr','meqArr','meqArrPr','mf','mfName','mfPr','mfunc','mfuncPr','mgroupChr', + 'mgroupChrPr','mgrow','mhideBot','mhideLeft','mhideRight','mhideTop','mhtmltag', + 'mlim','mlimloc','mlimlow','mlimlowPr','mlimupp','mlimuppPr','mm','mmaddfieldname', + 'mmath','mmathPict','mmathPr','mmaxdist','mmc','mmcJc','mmconnectstr', + 'mmconnectstrdata','mmcPr','mmcs','mmdatasource','mmheadersource','mmmailsubject', + 'mmodso','mmodsofilter','mmodsofldmpdata','mmodsomappedname','mmodsoname', + 'mmodsorecipdata','mmodsosort','mmodsosrc','mmodsotable','mmodsoudl', + 'mmodsoudldata','mmodsouniquetag','mmPr','mmquery','mmr','mnary','mnaryPr', + 'mnoBreak','mnum','mobjDist','moMath','moMathPara','moMathParaPr','mopEmu', + 'mphant','mphantPr','mplcHide','mpos','mr','mrad','mradPr','mrPr','msepChr', + 'mshow','mshp','msPre','msPrePr','msSub','msSubPr','msSubSup','msSubSupPr','msSup', + 'msSupPr','mstrikeBLTR','mstrikeH','mstrikeTLBR','mstrikeV','msub','msubHide', + 'msup','msupHide','mtransp','mtype','mvertJc','mvfmf','mvfml','mvtof','mvtol', + 'mzeroAsc','mzeroDesc','mzeroWid','nesttableprops','nextfile','nonesttables', + 'objalias','objclass','objdata','object','objname','objsect','objtime','oldcprops', + 'oldpprops','oldsprops','oldtprops','oleclsid','operator','panose','password', + 'passwordhash','pgp','pgptbl','picprop','pict','pn','pnseclvl','pntext','pntxta', + 'pntxtb','printim','private','propname','protend','protstart','protusertbl','pxe', + 'result','revtbl','revtim','rsidtbl','rxe','shp','shpgrp','shpinst', + 'shppict','shprslt','shptxt','sn','sp','staticval','stylesheet','subject','sv', + 'svb','tc','template','themedata','title','txe','ud','upr','userprops', + 'wgrffmtfilter','windowcaption','writereservation','writereservhash','xe','xform', + 'xmlattrname','xmlattrvalue','xmlclose','xmlname','xmlnstbl', + 'xmlopen', + )) + # Translation of some special characters. + specialchars = { + 'par': '\n', + 'sect': '\n\n', + 'page': '\n\n', + 'line': '\n', + 'tab': '\t', + 'emdash': u'\u2014', + 'endash': u'\u2013', + 'emspace': u'\u2003', + 'enspace': u'\u2002', + 'qmspace': u'\u2005', + 'bullet': u'\u2022', + 'lquote': u'\u2018', + 'rquote': u'\u2019', + 'ldblquote': u'\201C', + 'rdblquote': u'\u201D', + } + stack = [] + ignorable = False # Whether this group (and all inside it) are "ignorable". + ucskip = 1 # Number of ASCII characters to skip after a unicode character. + curskip = 0 # Number of ASCII characters left to skip + out = [] # Output buffer. + for match in pattern.finditer(text): + word,arg,hex,char,brace,tchar = match.groups() + if brace: + curskip = 0 + if brace == '{': + # Push state + stack.append((ucskip,ignorable)) + elif brace == '}': + # Pop state + ucskip,ignorable = stack.pop() + elif char: # \x (not a letter) + curskip = 0 + if char == '~': + if not ignorable: + out.append(u'\xA0') + elif char in '{}\\': + if not ignorable: + out.append(char) + elif char == '*': + ignorable = True + elif word: # \foo + curskip = 0 + if word in destinations: + ignorable = True + elif ignorable: + pass + elif word in specialchars: + out.append(specialchars[word]) + elif word == 'uc': + ucskip = int(arg) + elif word == 'u': + c = int(arg) + if c < 0: c += 0x10000 + if c > 127: out.append(unichr(c)) + else: out.append(chr(c)) + curskip = ucskip + elif hex: # \'xx + if curskip > 0: + curskip -= 1 + elif not ignorable: + c = int(hex,16) + if c > 127: out.append(unichr(c)) + else: out.append(chr(c)) + elif tchar: + if curskip > 0: + curskip -= 1 + elif not ignorable: + out.append(tchar) + return ''.join(out) From c9a7f56cbb4c1f506f22054e8ac2cea553c1f063 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Wed, 27 Jun 2012 00:58:08 +1000 Subject: [PATCH 03/32] ZionWorxImport tidy --- openlp/plugins/songs/lib/zionworximport.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openlp/plugins/songs/lib/zionworximport.py b/openlp/plugins/songs/lib/zionworximport.py index 0e7a1b425..1a00b3a7c 100644 --- a/openlp/plugins/songs/lib/zionworximport.py +++ b/openlp/plugins/songs/lib/zionworximport.py @@ -37,6 +37,9 @@ from openlp.plugins.songs.lib.songimport import SongImport log = logging.getLogger(__name__) +# Used to strip control chars (except 10=LF, 13=CR) +CONTROL_CHARS_MAP = dict.fromkeys(range(10) + [11, 12] + range(14,32) + [127]) + class ZionWorxImport(SongImport): """ The :class:`ZionWorxImport` class provides the ability to import songs @@ -78,13 +81,10 @@ class ZionWorxImport(SongImport): """ 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', + field_names = [u'SongNum', u'Title1', u'Title2', u'Lyrics', u'Writer', u'Copyright', u'Keywords', u'DefaultStyle'] - songs_reader = csv.DictReader(songs_file, fieldnames) + songs_reader = csv.DictReader(songs_file, field_names) try: records = list(songs_reader) except csv.Error, e: @@ -140,4 +140,4 @@ class ZionWorxImport(SongImport): """ # 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) + return unicode(str, u'cp1252').translate(CONTROL_CHARS_MAP) From 30943752334e76fcf3b901ca27ed31978b41b147 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Tue, 3 Jul 2012 22:14:12 +0100 Subject: [PATCH 04/32] Share ew strip_rtf routine --- openlp/plugins/songs/lib/__init__.py | 96 +++++++++++++++++ openlp/plugins/songs/lib/ewimport.py | 98 +---------------- openlp/plugins/songs/lib/songproimport.py | 124 +--------------------- 3 files changed, 100 insertions(+), 218 deletions(-) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index ce41b6faa..7f0bfd2fa 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -36,6 +36,7 @@ from ui import SongStrings 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 @@ def clean_song(manager, song): 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 diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py index 227b8e4b6..020c489bb 100644 --- a/openlp/plugins/songs/lib/ewimport.py +++ b/openlp/plugins/songs/lib/ewimport.py @@ -36,110 +36,14 @@ import re 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): diff --git a/openlp/plugins/songs/lib/songproimport.py b/openlp/plugins/songs/lib/songproimport.py index a22f18d14..ddc5a79b5 100644 --- a/openlp/plugins/songs/lib/songproimport.py +++ b/openlp/plugins/songs/lib/songproimport.py @@ -33,6 +33,7 @@ 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__) @@ -110,7 +111,7 @@ class SongProImport(SongImport): self.finish() return if u'rtf1' in text: - text = striprtf(text).rstrip() + text = strip_rtf(text, u'cp1252').rstrip() if not text: return if tag == u'A': @@ -136,128 +137,9 @@ class SongProImport(SongImport): self.verseOrderList.append(u'B1') elif char == u'D': self.verseOrderList.append(u'E1') - elif u'1' <= char <= '7': + 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:]) - -# replace with mahfiaz's shared one when his import is merged -def striprtf(text): - pattern = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I) - # control words which specify a "destionation". - destinations = frozenset(( - 'aftncn','aftnsep','aftnsepc','annotation','atnauthor','atndate','atnicn','atnid', - 'atnparent','atnref','atntime','atrfend','atrfstart','author','background', - 'bkmkend','bkmkstart','blipuid','buptim','category','colorschememapping', - 'colortbl','comment','company','creatim','datafield','datastore','defchp','defpap', - 'do','doccomm','docvar','dptxbxtext','ebcend','ebcstart','factoidname','falt', - 'fchars','ffdeftext','ffentrymcr','ffexitmcr','ffformat','ffhelptext','ffl', - 'ffname','ffstattext','field','file','filetbl','fldinst','fldrslt','fldtype', - 'fname','fontemb','fontfile','fonttbl','footer','footerf','footerl','footerr', - 'footnote','formfield','ftncn','ftnsep','ftnsepc','g','generator','gridtbl', - 'header','headerf','headerl','headerr','hl','hlfr','hlinkbase','hlloc','hlsrc', - 'hsv','htmltag','info','keycode','keywords','latentstyles','lchars','levelnumbers', - 'leveltext','lfolevel','linkval','list','listlevel','listname','listoverride', - 'listoverridetable','listpicture','liststylename','listtable','listtext', - 'lsdlockedexcept','macc','maccPr','mailmerge','maln','malnScr','manager','margPr', - 'mbar','mbarPr','mbaseJc','mbegChr','mborderBox','mborderBoxPr','mbox','mboxPr', - 'mchr','mcount','mctrlPr','md','mdeg','mdegHide','mden','mdiff','mdPr','me', - 'mendChr','meqArr','meqArrPr','mf','mfName','mfPr','mfunc','mfuncPr','mgroupChr', - 'mgroupChrPr','mgrow','mhideBot','mhideLeft','mhideRight','mhideTop','mhtmltag', - 'mlim','mlimloc','mlimlow','mlimlowPr','mlimupp','mlimuppPr','mm','mmaddfieldname', - 'mmath','mmathPict','mmathPr','mmaxdist','mmc','mmcJc','mmconnectstr', - 'mmconnectstrdata','mmcPr','mmcs','mmdatasource','mmheadersource','mmmailsubject', - 'mmodso','mmodsofilter','mmodsofldmpdata','mmodsomappedname','mmodsoname', - 'mmodsorecipdata','mmodsosort','mmodsosrc','mmodsotable','mmodsoudl', - 'mmodsoudldata','mmodsouniquetag','mmPr','mmquery','mmr','mnary','mnaryPr', - 'mnoBreak','mnum','mobjDist','moMath','moMathPara','moMathParaPr','mopEmu', - 'mphant','mphantPr','mplcHide','mpos','mr','mrad','mradPr','mrPr','msepChr', - 'mshow','mshp','msPre','msPrePr','msSub','msSubPr','msSubSup','msSubSupPr','msSup', - 'msSupPr','mstrikeBLTR','mstrikeH','mstrikeTLBR','mstrikeV','msub','msubHide', - 'msup','msupHide','mtransp','mtype','mvertJc','mvfmf','mvfml','mvtof','mvtol', - 'mzeroAsc','mzeroDesc','mzeroWid','nesttableprops','nextfile','nonesttables', - 'objalias','objclass','objdata','object','objname','objsect','objtime','oldcprops', - 'oldpprops','oldsprops','oldtprops','oleclsid','operator','panose','password', - 'passwordhash','pgp','pgptbl','picprop','pict','pn','pnseclvl','pntext','pntxta', - 'pntxtb','printim','private','propname','protend','protstart','protusertbl','pxe', - 'result','revtbl','revtim','rsidtbl','rxe','shp','shpgrp','shpinst', - 'shppict','shprslt','shptxt','sn','sp','staticval','stylesheet','subject','sv', - 'svb','tc','template','themedata','title','txe','ud','upr','userprops', - 'wgrffmtfilter','windowcaption','writereservation','writereservhash','xe','xform', - 'xmlattrname','xmlattrvalue','xmlclose','xmlname','xmlnstbl', - 'xmlopen', - )) - # Translation of some special characters. - specialchars = { - 'par': '\n', - 'sect': '\n\n', - 'page': '\n\n', - 'line': '\n', - 'tab': '\t', - 'emdash': u'\u2014', - 'endash': u'\u2013', - 'emspace': u'\u2003', - 'enspace': u'\u2002', - 'qmspace': u'\u2005', - 'bullet': u'\u2022', - 'lquote': u'\u2018', - 'rquote': u'\u2019', - 'ldblquote': u'\201C', - 'rdblquote': u'\u201D', - } - stack = [] - ignorable = False # Whether this group (and all inside it) are "ignorable". - ucskip = 1 # Number of ASCII characters to skip after a unicode character. - curskip = 0 # Number of ASCII characters left to skip - out = [] # Output buffer. - for match in pattern.finditer(text): - word,arg,hex,char,brace,tchar = match.groups() - if brace: - curskip = 0 - if brace == '{': - # Push state - stack.append((ucskip,ignorable)) - elif brace == '}': - # Pop state - ucskip,ignorable = stack.pop() - elif char: # \x (not a letter) - curskip = 0 - if char == '~': - if not ignorable: - out.append(u'\xA0') - elif char in '{}\\': - if not ignorable: - out.append(char) - elif char == '*': - ignorable = True - elif word: # \foo - curskip = 0 - if word in destinations: - ignorable = True - elif ignorable: - pass - elif word in specialchars: - out.append(specialchars[word]) - elif word == 'uc': - ucskip = int(arg) - elif word == 'u': - c = int(arg) - if c < 0: c += 0x10000 - if c > 127: out.append(unichr(c)) - else: out.append(chr(c)) - curskip = ucskip - elif hex: # \'xx - if curskip > 0: - curskip -= 1 - elif not ignorable: - c = int(hex,16) - if c > 127: out.append(unichr(c)) - else: out.append(chr(c)) - elif tchar: - if curskip > 0: - curskip -= 1 - elif not ignorable: - out.append(tchar) - return ''.join(out) From 9c1005ac3cbf4ffacef292d9f55a1d8dc76adfd0 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Wed, 4 Jul 2012 19:53:03 +0100 Subject: [PATCH 05/32] Fix copyrights --- openlp/plugins/songs/lib/songproimport.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/songs/lib/songproimport.py b/openlp/plugins/songs/lib/songproimport.py index ddc5a79b5..9ca30ca95 100644 --- a/openlp/plugins/songs/lib/songproimport.py +++ b/openlp/plugins/songs/lib/songproimport.py @@ -6,10 +6,11 @@ # --------------------------------------------------------------------------- # # 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 # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Edwin Lunando, Joshua Miller, Stevan Pettit, # +# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, # +# Simon Scudder, Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon # +# Tibble, Dave Warnock, 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 # From 1edfac99e63662df26d9fd454284a1db658ac572 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Thu, 5 Jul 2012 00:04:11 +0100 Subject: [PATCH 06/32] Add mediashout import --- openlp/plugins/songs/lib/importer.py | 27 +++-- openlp/plugins/songs/lib/mediashoutimport.py | 108 +++++++++++++++++++ 2 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 openlp/plugins/songs/lib/mediashoutimport.py diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 04ac6df56..e5f9344e8 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -47,6 +47,7 @@ from songshowplusimport import SongShowPlusImport from songproimport import SongProImport from foilpresenterimport import FoilPresenterImport from zionworximport import ZionWorxImport +from mediashoutimport import MediaShoutImport # Imports that might fail log = logging.getLogger(__name__) try: @@ -141,14 +142,15 @@ class SongFormat(object): EasySlides = 6 EasyWorship = 7 FoilPresenter = 8 - OpenSong = 9 - PowerSong = 10 - SongBeamer = 11 - SongPro = 12 - SongShowPlus = 13 - SongsOfFellowship = 14 - WordsOfWorship = 15 - ZionWorx = 16 + MediaShout = 9 + OpenSong = 10 + PowerSong = 11 + SongBeamer = 12 + SongPro = 13 + SongShowPlus = 14 + SongsOfFellowship = 15 + WordsOfWorship = 16 + ZionWorx = 17 # Set optional attribute defaults __defaults__ = { @@ -239,6 +241,14 @@ class SongFormat(object): u'filter': u'%s (*.foil)' % translate( 'SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files') }, + MediaShout: { + u'class': MediaShoutImport, + u'name': u'MediaShout', + u'prefix': u'mediaShout', + u'selectMode': SongFormatSelect.SingleFile, + u'filter': u'%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', + 'MediaShout Database') + }, OpenSong: { u'class': OpenSongImport, u'name': WizardStrings.OS, @@ -324,6 +334,7 @@ class SongFormat(object): SongFormat.EasySlides, SongFormat.EasyWorship, SongFormat.FoilPresenter, + SongFormat.MediaShout, SongFormat.OpenSong, SongFormat.PowerSong, SongFormat.SongBeamer, diff --git a/openlp/plugins/songs/lib/mediashoutimport.py b/openlp/plugins/songs/lib/mediashoutimport.py new file mode 100644 index 000000000..c8e21644b --- /dev/null +++ b/openlp/plugins/songs/lib/mediashoutimport.py @@ -0,0 +1,108 @@ +# -*- 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, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Edwin Lunando, Joshua Miller, Stevan Pettit, # +# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, # +# Simon Scudder, Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon # +# Tibble, Dave Warnock, 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:`mediashoutimport` module provides the functionality for importing +a MediaShout database into the OpenLP database. +""" +import re +import os +import logging +import pyodbc + +from openlp.core.lib import translate +from openlp.plugins.songs.lib.songimport import SongImport + +log = logging.getLogger(__name__) + +VERSE_TAGS = [u'V', u'C', u'B', u'O', u'P', u'I', u'E'] + +class MediaShoutImport(SongImport): + """ + The :class:`MediaShoutImport` class provides the ability to import the + MediaShout Access Database + """ + def __init__(self, manager, **kwargs): + """ + Initialise the MediaShout importer. + """ + SongImport.__init__(self, manager, **kwargs) + + def doImport(self): + """ + Receive a single file to import. + """ + conn = pyodbc.connect(u'DRIVER={Microsoft Access Driver (*.mdb)};' + u'DBQ=%s;PWD=6NOZ4eHK7k' % self.importSource) + cursor = conn.cursor() + cursor.execute(u'SELECT Record, Title, Author, Copyright, ' + u'SongID, CCLI, Notes FROM Songs ORDER BY Title') + songs = cursor.fetchall() + self.importWizard.progressBar.setMaximum(len(songs)) + for song in songs: + if self.stopImportFlag: + break + cursor.execute(u'SELECT Type, Number, Text FROM Verses ' + u'WHERE Record = %s ORDER BY Type, Number' % song.Record) + verses = cursor.fetchall() + cursor.execute(u'SELECT Type, Number, POrder FROM PlayOrder ' + u'WHERE Record = %s ORDER BY POrder' % song.Record) + verse_order = cursor.fetchall() + cursor.execute(u'SELECT Name FROM Themes INNER JOIN SongThemes ' + u'ON SongThemes.ThemeId = Themes.ThemeId ' + u'WHERE SongThemes.Record = %s' % song.Record) + topics = cursor.fetchall() + cursor.execute(u'SELECT Name FROM Groups INNER JOIN SongGroups ' + u'ON SongGroups.GroupId = Groups.GroupId ' + u'WHERE SongGroups.Record = %s' % song.Record) + topics += cursor.fetchall() + self.processSong(song, verses, verse_order, topics) + + def processSong(self, song, verses, verse_order, topics): + """ + Create the song, i.e. title, verse etc. + """ + self.setDefaults() + self.title = song.Title + self.parseAuthor(song.Author) + self.addCopyright(song.Copyright) + self.comments = song.Notes + for topic in topics: + self.topics.append(topic.Name) + if u'-' in song.SongID: + self.songBookName, self.songNumber = song.SongID.split(u'-', 1) + else: + self.songBookName = song.SongID + for verse in verses: + tag = VERSE_TAGS[verse.Type] + unicode(verse.Number) \ + if verse.Type < len(VERSE_TAGS) else u'O' + self.addVerse(verse.Text, tag) + for order in verse_order: + if order.Type < len(VERSE_TAGS): + self.verseOrderList.append(VERSE_TAGS[order.Type] + + unicode(order.Number)) + self.finish() From ef29c0e97e6fddf3d4e2e95ddd244dee45268c4c Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Thu, 5 Jul 2012 00:13:19 +0100 Subject: [PATCH 07/32] Available tests --- openlp/plugins/songs/lib/importer.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index e5f9344e8..bfc186fb3 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -47,7 +47,6 @@ from songshowplusimport import SongShowPlusImport from songproimport import SongProImport from foilpresenterimport import FoilPresenterImport from zionworximport import ZionWorxImport -from mediashoutimport import MediaShoutImport # Imports that might fail log = logging.getLogger(__name__) try: @@ -68,7 +67,13 @@ try: except ImportError: log.exception('Error importing %s', 'OooImport') HAS_OOO = False - +HAS_MEDIASHOUT = False +if os.name == u'nt': + try: + from mediashoutimport import MediaShoutImport + HAS_MEDIASHOUT = True + except ImportError: + log.exception('Error importing %s', 'MediaShoutImport') class SongFormatSelect(object): """ @@ -242,9 +247,9 @@ class SongFormat(object): 'SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files') }, MediaShout: { - u'class': MediaShoutImport, u'name': u'MediaShout', u'prefix': u'mediaShout', + u'canDisable': True, u'selectMode': SongFormatSelect.SingleFile, u'filter': u'%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'MediaShout Database') @@ -392,5 +397,8 @@ if HAS_SOF: SongFormat.set(SongFormat.Generic, u'availability', HAS_OOO) if HAS_OOO: SongFormat.set(SongFormat.Generic, u'class', OooImport) +SongFormat.set(SongFormat.MediaShout, u'availability', HAS_MEDIASHOUT) +if HAS_MEDIASHOUT: + SongFormat.set(SongFormat.MediaShout, u'class', MediaShoutImport) __all__ = [u'SongFormat', u'SongFormatSelect'] From 3668adc6ffcd24b3b85a467f016ec73b54bbb49c Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Thu, 5 Jul 2012 19:39:47 +0100 Subject: [PATCH 08/32] ewimport and __init__ back now mahfiaz has merged his --- openlp/plugins/songs/lib/__init__.py | 96 --------------------------- openlp/plugins/songs/lib/ewimport.py | 98 +++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 7f0bfd2fa..ce41b6faa 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -36,7 +36,6 @@ from ui import SongStrings WHITESPACE = re.compile(r'[\W_]+', re.UNICODE) APOSTROPHE = re.compile(u'[\'`’ʻ′]', re.UNICODE) -RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}') class VerseType(object): """ @@ -367,101 +366,6 @@ def clean_song(manager, song): 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 diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py index 020c489bb..227b8e4b6 100644 --- a/openlp/plugins/songs/lib/ewimport.py +++ b/openlp/plugins/songs/lib/ewimport.py @@ -36,14 +36,110 @@ import re from openlp.core.lib import translate from openlp.plugins.songs.lib import VerseType -from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf +from openlp.plugins.songs.lib import retrieve_windows_encoding 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): From 614dc2648bc3945760f6e22c94df52c37fe9af15 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Thu, 5 Jul 2012 20:24:56 +0100 Subject: [PATCH 09/32] Use new strip_rtf routine and error trap --- openlp/plugins/songs/lib/__init__.py | 2 +- openlp/plugins/songs/lib/importer.py | 1 + openlp/plugins/songs/lib/mediashoutimport.py | 8 +++++++- openlp/plugins/songs/lib/songproimport.py | 4 +++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index a51f3f2fc..d984d5a8d 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -476,7 +476,7 @@ def get_encoding(font, font_table, default_encoding, failed=False): Dictionary of fonts and respective encodings. ``default_encoding`` - The defaul encoding to use when font_table is empty or no font is used. + The default encoding to use when font_table is empty or no font is used. ``failed`` A boolean indicating whether the previous encoding didn't work. diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 665377c65..209539475 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -28,6 +28,7 @@ """ The :mod:`importer` modules provides the general song import functionality. """ +import os import logging from openlp.core.lib import translate diff --git a/openlp/plugins/songs/lib/mediashoutimport.py b/openlp/plugins/songs/lib/mediashoutimport.py index c8e21644b..2af9e8cfd 100644 --- a/openlp/plugins/songs/lib/mediashoutimport.py +++ b/openlp/plugins/songs/lib/mediashoutimport.py @@ -56,8 +56,14 @@ class MediaShoutImport(SongImport): """ Receive a single file to import. """ - conn = pyodbc.connect(u'DRIVER={Microsoft Access Driver (*.mdb)};' + try: + conn = pyodbc.connect(u'DRIVER={Microsoft Access Driver (*.mdb)};' u'DBQ=%s;PWD=6NOZ4eHK7k' % self.importSource) + except: # Unfortunately no specific exception type + self.logError(self.importSource, + translate('SongsPlugin.MediaShoutImport', + 'Unable to open the MediaShout database.')) + return cursor = conn.cursor() cursor.execute(u'SELECT Record, Title, Author, Copyright, ' u'SongID, CCLI, Notes FROM Songs ORDER BY Title') diff --git a/openlp/plugins/songs/lib/songproimport.py b/openlp/plugins/songs/lib/songproimport.py index 9ca30ca95..9f4148308 100644 --- a/openlp/plugins/songs/lib/songproimport.py +++ b/openlp/plugins/songs/lib/songproimport.py @@ -81,6 +81,7 @@ class SongProImport(SongImport): """ Receive a single file or a list of files to import. """ + self.encoding = None with open(self.importSource, 'r') as songs_file: self.importWizard.progressBar.setMaximum(0) tag = u'' @@ -112,7 +113,8 @@ class SongProImport(SongImport): self.finish() return if u'rtf1' in text: - text = strip_rtf(text, u'cp1252').rstrip() + text, self.encoding = strip_rtf(text, self.encoding) + text = text.rstrip() if not text: return if tag == u'A': From f9eab33dd9d05d36b8aea791bb906c35bbb02c96 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Fri, 6 Jul 2012 16:25:21 +1000 Subject: [PATCH 10/32] Comment typo --- openlp/core/ui/themeform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 35421c453..d5eed9ff1 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -226,7 +226,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): def onCurrentIdChanged(self, pageId): """ - Detects Page changes and updates as approprate. + Detects Page changes and updates as appropriate. """ if self.page(pageId) == self.areaPositionPage: self.setOption(QtGui.QWizard.HaveCustomButton1, True) From 1717e4fa4ca8259074978b5bec5cb0b39cfcb815 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Fri, 6 Jul 2012 07:31:00 +0100 Subject: [PATCH 11/32] Fix comment location --- openlp/plugins/songs/lib/mediashoutimport.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/songs/lib/mediashoutimport.py b/openlp/plugins/songs/lib/mediashoutimport.py index 2af9e8cfd..5194b4b7b 100644 --- a/openlp/plugins/songs/lib/mediashoutimport.py +++ b/openlp/plugins/songs/lib/mediashoutimport.py @@ -59,7 +59,8 @@ class MediaShoutImport(SongImport): try: conn = pyodbc.connect(u'DRIVER={Microsoft Access Driver (*.mdb)};' u'DBQ=%s;PWD=6NOZ4eHK7k' % self.importSource) - except: # Unfortunately no specific exception type + except: + # Unfortunately no specific exception type self.logError(self.importSource, translate('SongsPlugin.MediaShoutImport', 'Unable to open the MediaShout database.')) From 3c69631b592067e20bed2ef383e300cc0563383f Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 7 Jul 2012 16:54:14 +0200 Subject: [PATCH 12/32] clean ups --- openlp/core/ui/servicemanager.py | 6 +++--- openlp/plugins/presentations/lib/impresscontroller.py | 3 --- openlp/plugins/presentations/lib/powerpointcontroller.py | 5 +---- openlp/plugins/presentations/lib/pptviewcontroller.py | 3 --- .../plugins/presentations/lib/presentationcontroller.py | 9 +++------ openlp/plugins/songs/forms/editsongform.py | 5 ++--- openlp/plugins/songs/lib/mediaitem.py | 6 +++--- openlp/plugins/songs/lib/songimport.py | 5 ++--- 8 files changed, 14 insertions(+), 28 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 0ffb1fff7..d7f745fca 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -39,7 +39,8 @@ log = logging.getLogger(__name__) from PyQt4 import QtCore, QtGui from openlp.core.lib import OpenLPToolbar, ServiceItem, Receiver, build_icon, \ - ItemCapabilities, SettingsManager, translate, str_to_bool + ItemCapabilities, SettingsManager, translate, str_to_bool, \ + check_directory_exists from openlp.core.lib.theme import ThemeLevel from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ @@ -556,8 +557,7 @@ class ServiceManager(QtGui.QWidget): audio_from = os.path.join(self.servicePath, audio_from) save_file = os.path.join(self.servicePath, audio_to) save_path = os.path.split(save_file)[0] - if not os.path.exists(save_path): - os.makedirs(save_path) + check_directory_exists(save_path) if not os.path.exists(save_file): shutil.copy(audio_from, save_file) zip.write(audio_from, audio_to.encode(u'utf-8')) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 1a8754f80..a85f43592 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -227,9 +227,6 @@ class ImpressDocument(PresentationDocument): OpenOffice task started earlier. If OpenOffice is not present is is started. Once the environment is available the presentation is loaded and started. - - ``presentation`` - The file name of the presentatios to the run. """ log.debug(u'Load Presentation OpenOffice') if os.name == u'nt': diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index fcdb5b105..54b9c2144 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -119,10 +119,7 @@ class PowerpointDocument(PresentationDocument): def load_presentation(self): """ Called when a presentation is added to the SlideController. - Opens the PowerPoint file using the process created earlier - - ``presentation`` - The file name of the presentations to run. + Opens the PowerPoint file using the process created earlier. """ log.debug(u'load_presentation') if not self.controller.process or not self.controller.process.Visible: diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 8f068fa05..2e3b28f6e 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -118,9 +118,6 @@ class PptviewDocument(PresentationDocument): Called when a presentation is added to the SlideController. It builds the environment, starts communcations with the background PptView task started earlier. - - ``presentation`` - The file name of the presentations to run. """ log.debug(u'LoadPresentation') renderer = self.controller.plugin.renderer diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index e8e49d2e8..b72e1e9d4 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -47,7 +47,7 @@ class PresentationDocument(object): **Hook Functions** - ``load_presentation(presentation)`` + ``load_presentation()`` Load a presentation file ``close_presentation()`` @@ -104,11 +104,8 @@ class PresentationDocument(object): def load_presentation(self): """ - Called when a presentation is added to the SlideController. - Loads the presentation and starts it - - ``presentation`` - The file name of the presentations to the run. + Called when a presentation is added to the SlideController. Loads the + presentation and starts it. Returns False if the file could not be opened """ diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index e3ed07aba..0b3dddcc9 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -34,7 +34,7 @@ import shutil from PyQt4 import QtCore, QtGui from openlp.core.lib import PluginStatus, Receiver, MediaType, translate, \ - create_separated_list + create_separated_list, check_directory_exists from openlp.core.lib.ui import UiStrings, set_case_insensitive_completer, \ critical_error_message_box, find_and_set_in_combo_box from openlp.core.utils import AppLocation @@ -880,8 +880,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): save_path = os.path.join( AppLocation.get_section_data_path(self.mediaitem.plugin.name), 'audio', str(self.song.id)) - if not os.path.exists(save_path): - os.makedirs(save_path) + check_directory_exists(save_path) self.song.media_files = [] files = [] for row in xrange(self.audioListWidget.count()): diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 47e9ba742..88999dce4 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -36,7 +36,8 @@ from PyQt4 import QtCore, QtGui from sqlalchemy.sql import or_ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ - translate, check_item_selected, PluginStatus, create_separated_list + translate, check_item_selected, PluginStatus, create_separated_list, \ + check_directory_exists from openlp.core.lib.ui import UiStrings, create_widget_action from openlp.core.lib.settings import Settings from openlp.core.utils import AppLocation @@ -89,8 +90,7 @@ class SongMediaItem(MediaManagerItem): dest_file = os.path.join( AppLocation.get_section_data_path(self.plugin.name), u'audio', str(song.id), os.path.split(bga)[1]) - if not os.path.exists(os.path.split(dest_file)[0]): - os.makedirs(os.path.split(dest_file)[0]) + check_directory_exists(os.path.split(dest_file)[0]) shutil.copyfile(os.path.join( AppLocation.get_section_data_path( u'servicemanager'), bga), diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 8dcf4e9f3..134cbdfdc 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -33,7 +33,7 @@ import os from PyQt4 import QtCore -from openlp.core.lib import Receiver, translate +from openlp.core.lib import Receiver, translate, check_directory_exists from openlp.core.ui.wizard import WizardStrings from openlp.core.utils import AppLocation from openlp.plugins.songs.lib import clean_song, VerseType @@ -388,8 +388,7 @@ class SongImport(QtCore.QObject): AppLocation.get_section_data_path( self.importWizard.plugin.name), 'audio', str(song_id)) - if not os.path.exists(self.save_path): - os.makedirs(self.save_path) + check_directory_exists(self.save_path) if not filename.startswith(self.save_path): oldfile, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1]) From e42115e78a36a4173952e6b171cab96694e685f1 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 7 Jul 2012 21:06:42 +0200 Subject: [PATCH 13/32] fixed bug 1022125 (Authors display name is not automatically entered) Fixes: https://launchpad.net/bugs/1022125 --- openlp/plugins/songs/forms/authorsform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/forms/authorsform.py b/openlp/plugins/songs/forms/authorsform.py index 0df330cd1..8dede69a7 100644 --- a/openlp/plugins/songs/forms/authorsform.py +++ b/openlp/plugins/songs/forms/authorsform.py @@ -61,14 +61,14 @@ class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog): def onFirstNameEditTextEdited(self, display_name): if not self._autoDisplayName: return - if not self.lastNameEdit.text(): + if self.lastNameEdit.text(): display_name = display_name + u' ' + self.lastNameEdit.text() self.displayEdit.setText(display_name) def onLastNameEditTextEdited(self, display_name): if not self._autoDisplayName: return - if not self.firstNameEdit.text(): + if self.firstNameEdit.text(): display_name = self.firstNameEdit.text() + u' ' + display_name self.displayEdit.setText(display_name) From ca3e938afe08c6a76f2946d04da83c80fcdda0a1 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Sat, 7 Jul 2012 22:33:17 +0100 Subject: [PATCH 14/32] Remove redundant logging and incrementProgressBar --- openlp/plugins/songs/lib/mediashoutimport.py | 3 --- openlp/plugins/songs/lib/songproimport.py | 7 ++----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/openlp/plugins/songs/lib/mediashoutimport.py b/openlp/plugins/songs/lib/mediashoutimport.py index 5194b4b7b..f81b208ac 100644 --- a/openlp/plugins/songs/lib/mediashoutimport.py +++ b/openlp/plugins/songs/lib/mediashoutimport.py @@ -31,14 +31,11 @@ a MediaShout database into the OpenLP database. """ import re import os -import logging import pyodbc from openlp.core.lib import translate from openlp.plugins.songs.lib.songimport import SongImport -log = logging.getLogger(__name__) - VERSE_TAGS = [u'V', u'C', u'B', u'O', u'P', u'I', u'E'] class MediaShoutImport(SongImport): diff --git a/openlp/plugins/songs/lib/songproimport.py b/openlp/plugins/songs/lib/songproimport.py index 9f4148308..a813f0519 100644 --- a/openlp/plugins/songs/lib/songproimport.py +++ b/openlp/plugins/songs/lib/songproimport.py @@ -31,14 +31,11 @@ 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 @@ -106,8 +103,8 @@ class SongProImport(SongImport): self.setDefaults() if text: self.title = text - self.importWizard.incrementProgressBar(u'Processing song ' + text, - 0) + #self.importWizard.incrementProgressBar(u'Processing song ' + text, + # 0) return elif tag == u'E': self.finish() From 18fc43f739311a27252349e2395a8eef5fb49ceb Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Sun, 8 Jul 2012 09:56:33 +0100 Subject: [PATCH 15/32] Oops. Don't forget to remove the code after temporarily commenting out --- openlp/plugins/songs/lib/songproimport.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openlp/plugins/songs/lib/songproimport.py b/openlp/plugins/songs/lib/songproimport.py index a813f0519..44bf0cb44 100644 --- a/openlp/plugins/songs/lib/songproimport.py +++ b/openlp/plugins/songs/lib/songproimport.py @@ -103,8 +103,6 @@ class SongProImport(SongImport): self.setDefaults() if text: self.title = text - #self.importWizard.incrementProgressBar(u'Processing song ' + text, - # 0) return elif tag == u'E': self.finish() From 8ad5fdad4ae3f7a9efa48ae918d95bc1630c5a3f Mon Sep 17 00:00:00 2001 From: ElderP Date: Sun, 8 Jul 2012 08:53:06 -0400 Subject: [PATCH 16/32] Added code to put logfile into appdir/other if running portable --- openlp/core/__init__.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 17d95547b..4f05d6980 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -207,6 +207,16 @@ class OpenLP(QtGui.QApplication): else: return QtGui.QApplication.event(self, event) +def setupLogging(log_path): + """ + Setup our logging using log_path + """ + check_directory_exists(log_path) + filename = os.path.join(log_path, u'openlp.log') + logfile = logging.FileHandler(filename, u'w') + logfile.setFormatter(logging.Formatter( + u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s')) + log.addHandler(logfile) def main(args=None): """ @@ -231,14 +241,6 @@ def main(args=None): help='Set the Qt4 style (passed directly to Qt4).') parser.add_option('--testing', dest='testing', action='store_true', help='Run by testing framework') - # Set up logging - log_path = AppLocation.get_directory(AppLocation.CacheDir) - check_directory_exists(log_path) - filename = os.path.join(log_path, u'openlp.log') - logfile = logging.FileHandler(filename, u'w') - logfile.setFormatter(logging.Formatter( - u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s')) - log.addHandler(logfile) # Parse command line options and deal with them. # Use args supplied programatically if possible. (options, args) = parser.parse_args(args) if args else parser.parse_args() @@ -261,11 +263,13 @@ def main(args=None): app.setOrganizationName(u'OpenLP') app.setOrganizationDomain(u'openlp.org') if options.portable: - log.info(u'Running portable') app.setApplicationName(u'OpenLPPortable') Settings.setDefaultFormat(Settings.IniFormat) # Get location OpenLPPortable.ini app_path = AppLocation.get_directory(AppLocation.AppDir) + setupLogging(os.path.abspath(os.path.join(app_path, u'..', + u'..', u'Other'))) + log.info(u'Running portable') portable_settings_file = os.path.abspath(os.path.join(app_path, u'..', u'..', u'Data', u'OpenLP.ini')) # Make this our settings file @@ -282,6 +286,8 @@ def main(args=None): portable_settings.sync() else: app.setApplicationName(u'OpenLP') + # Set up logging + setupLogging(AppLocation.get_directory(AppLocation.CacheDir)) app.setApplicationVersion(get_application_version()[u'version']) # Instance check if not options.testing: From 6d236e9671a1824e796f04200fb88073e9fed5d0 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 8 Jul 2012 21:27:43 +0200 Subject: [PATCH 17/32] Fixed two issues in FreeBSD. Firstly, the translation files were never being picked up, and secondly, OpenLP is installed into /usr/local, so we need to make provision for that. --- openlp/core/utils/__init__.py | 5 +++++ resources/__init__.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 resources/__init__.py diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 7f2ef08e2..224ab4344 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -172,6 +172,11 @@ def _get_os_dir_path(dir_type): u'Library', u'Application Support', u'openlp') else: if dir_type == AppLocation.LanguageDir: + prefixes = [u'/usr/local', u'/usr'] + for prefix in prefixes: + directory = os.path.join(prefix, u'share', u'openlp') + if os.path.exists(directory): + return directory return os.path.join(u'/usr', u'share', u'openlp') if XDG_BASE_AVAILABLE: if dir_type == AppLocation.ConfigDir: diff --git a/resources/__init__.py b/resources/__init__.py new file mode 100644 index 000000000..377413191 --- /dev/null +++ b/resources/__init__.py @@ -0,0 +1,34 @@ +# -*- 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, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Edwin Lunando, Joshua Miller, Stevan Pettit, # +# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, # +# Simon Scudder, Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon # +# Tibble, Dave Warnock, 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:`resources` module contains a bunch of resources for OpenLP. + +DO NOT REMOVE THIS FILE, IT IS REQUIRED FOR INCLUDING THE RESOURCES ON SOME +PLATFORMS! +""" + From cc5da478861755581f7449ff82dea280aa670ab6 Mon Sep 17 00:00:00 2001 From: ElderP Date: Sun, 8 Jul 2012 15:32:56 -0400 Subject: [PATCH 18/32] Fixed proc name --- openlp/core/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 4f05d6980..4ca1d30de 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -207,7 +207,7 @@ class OpenLP(QtGui.QApplication): else: return QtGui.QApplication.event(self, event) -def setupLogging(log_path): +def set_up_logging(log_path): """ Setup our logging using log_path """ @@ -267,7 +267,7 @@ def main(args=None): Settings.setDefaultFormat(Settings.IniFormat) # Get location OpenLPPortable.ini app_path = AppLocation.get_directory(AppLocation.AppDir) - setupLogging(os.path.abspath(os.path.join(app_path, u'..', + set_up_logging(os.path.abspath(os.path.join(app_path, u'..', u'..', u'Other'))) log.info(u'Running portable') portable_settings_file = os.path.abspath(os.path.join(app_path, u'..', @@ -287,7 +287,7 @@ def main(args=None): else: app.setApplicationName(u'OpenLP') # Set up logging - setupLogging(AppLocation.get_directory(AppLocation.CacheDir)) + set_up_logging(AppLocation.get_directory(AppLocation.CacheDir)) app.setApplicationVersion(get_application_version()[u'version']) # Instance check if not options.testing: From 14088e71bb002a9d93d27c97a305655f7cb07fdf Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 8 Jul 2012 21:46:40 +0200 Subject: [PATCH 19/32] Implement natural sorting so that our version number is correct. --- setup.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2f8919f4b..51dc2eed9 100755 --- a/setup.py +++ b/setup.py @@ -28,10 +28,53 @@ ############################################################################### from setuptools import setup, find_packages +import re VERSION_FILE = 'openlp/.version' try: + def natural_sort_key(s): + """ + Return a tuple by which s is sorted. + + ``s`` + A string value from the list we want to sort. + """ + def try_int(s): + """ + Convert string s to an integer if possible. Fail silently and return + the string as-is if it isn't an integer. + + ``s`` + The string to try to convert. + """ + try: + return int(s) + except: + return s + return map(try_int, re.findall(r'(\d+|\D+)', s)) + + def natural_compare(a, b): + """ + Compare two strings naturally and return the result. + + ``a`` + A string to compare. + + ``b`` + A string to compare. + """ + return cmp(natural_sort_key(a), natural_sort_key(b)) + + def natural_sort(seq, compare=natural_compare): + """ + Returns a copy of seq, sorted by natural string sort. + """ + import copy + temp = copy.copy(seq) + temp.sort(compare) + return temp + from bzrlib.branch import Branch b = Branch.open_containing('.')[0] b.lock_read() @@ -46,7 +89,8 @@ try: if revision_id in tags: version = u'%s' % tags[revision_id][0] else: - version = '%s-bzr%s' % (sorted(b.tags.get_tag_dict().keys())[-1], revno) + version = '%s-bzr%s' % \ + (natural_sort(b.tags.get_tag_dict().keys())[-1], revno) ver_file = open(VERSION_FILE, u'w') ver_file.write(version) ver_file.close() From 746eb094e787bd3de624fc4fa84e556f206703fe Mon Sep 17 00:00:00 2001 From: ElderP Date: Sun, 8 Jul 2012 18:11:20 -0400 Subject: [PATCH 20/32] Removed comment --- openlp/core/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 4ca1d30de..4ed411ec1 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -286,7 +286,6 @@ def main(args=None): portable_settings.sync() else: app.setApplicationName(u'OpenLP') - # Set up logging set_up_logging(AppLocation.get_directory(AppLocation.CacheDir)) app.setApplicationVersion(get_application_version()[u'version']) # Instance check From 9d73d37b9427d9125090c6e828df9274940de9cc Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 9 Jul 2012 15:49:10 +1000 Subject: [PATCH 21/32] Song Import Wizard: disabledLabelTextfor default and MediaShout --- openlp/plugins/songs/lib/importer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 0c3a0f07f..660cf3f93 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -169,7 +169,8 @@ class SongFormat(object): u'selectMode': SongFormatSelect.MultipleFiles, u'filter': u'', u'comboBoxText': None, - u'disabledLabelText': u'', + u'disabledLabelText': translate('SongsPlugin.ImportWizardForm', + 'This importer has been disabled.'), u'getFilesTitle': None, u'invalidSourceMsg': None, u'descriptionText': None @@ -257,7 +258,11 @@ class SongFormat(object): u'canDisable': True, u'selectMode': SongFormatSelect.SingleFile, u'filter': u'%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', - 'MediaShout Database') + 'MediaShout Database'), + u'disabledLabelText': translate('SongsPlugin.ImportWizardForm', + 'The MediaShout importer has been disabled because the pyodbc ' + 'module is not available. This importer is only available on ' + 'Windows.') }, OpenSong: { u'class': OpenSongImport, From 6089c89f8a1c4cf8818f5b1278e26cc98ccbf9e3 Mon Sep 17 00:00:00 2001 From: Samuel Findlay Date: Mon, 9 Jul 2012 18:12:29 +1000 Subject: [PATCH 22/32] Improved disabledLabelText for MediaShout --- openlp/plugins/songs/lib/importer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 660cf3f93..a8de7172e 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -260,9 +260,10 @@ class SongFormat(object): u'filter': u'%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'MediaShout Database'), u'disabledLabelText': translate('SongsPlugin.ImportWizardForm', - 'The MediaShout importer has been disabled because the pyodbc ' - 'module is not available. This importer is only available on ' - 'Windows.') + 'The MediaShout importer is only supported on Windows. It has ' + 'been disabled due to a missing Python module. If you want to ' + 'use this importer, you will need to install the "pyodbc" ' + 'module.') }, OpenSong: { u'class': OpenSongImport, From 4e03451db73d470e23f556434316edeca99531ef Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 9 Jul 2012 19:56:02 +0200 Subject: [PATCH 23/32] Pulled natural sorting methods out from the try (no real reason for them to be in there) and moved "try_int" to outside the other method (then it is only compiled once). --- setup.py | 87 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/setup.py b/setup.py index 51dc2eed9..b101d2907 100755 --- a/setup.py +++ b/setup.py @@ -31,50 +31,53 @@ from setuptools import setup, find_packages import re VERSION_FILE = 'openlp/.version' +SPLIT_ALPHA_DIGITS = re.compile(r'(\d+|\D+)') + +def try_int(s): + """ + Convert string s to an integer if possible. Fail silently and return + the string as-is if it isn't an integer. + + ``s`` + The string to try to convert. + """ + try: + return int(s) + except Exception: + return s + +def natural_sort_key(s): + """ + Return a tuple by which s is sorted. + + ``s`` + A string value from the list we want to sort. + """ + return map(try_int, SPLIT_ALPHA_DIGITS.findall(s)) + +def natural_compare(a, b): + """ + Compare two strings naturally and return the result. + + ``a`` + A string to compare. + + ``b`` + A string to compare. + """ + return cmp(natural_sort_key(a), natural_sort_key(b)) + +def natural_sort(seq, compare=natural_compare): + """ + Returns a copy of seq, sorted by natural string sort. + """ + import copy + temp = copy.copy(seq) + temp.sort(compare) + return temp try: - def natural_sort_key(s): - """ - Return a tuple by which s is sorted. - - ``s`` - A string value from the list we want to sort. - """ - def try_int(s): - """ - Convert string s to an integer if possible. Fail silently and return - the string as-is if it isn't an integer. - - ``s`` - The string to try to convert. - """ - try: - return int(s) - except: - return s - return map(try_int, re.findall(r'(\d+|\D+)', s)) - - def natural_compare(a, b): - """ - Compare two strings naturally and return the result. - - ``a`` - A string to compare. - - ``b`` - A string to compare. - """ - return cmp(natural_sort_key(a), natural_sort_key(b)) - - def natural_sort(seq, compare=natural_compare): - """ - Returns a copy of seq, sorted by natural string sort. - """ - import copy - temp = copy.copy(seq) - temp.sort(compare) - return temp - + # Try to import Bazaar from bzrlib.branch import Branch b = Branch.open_containing('.')[0] b.lock_read() From ef494325484fce56f27a08dced3690159294bd7f Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 11 Jul 2012 20:06:33 +0200 Subject: [PATCH 24/32] fixed bug 1023522 (OpenLP does not start in debug mode) Fixes: https://launchpad.net/bugs/1023522 --- openlp/core/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 4ed411ec1..13c344367 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -207,6 +207,7 @@ class OpenLP(QtGui.QApplication): else: return QtGui.QApplication.event(self, event) + def set_up_logging(log_path): """ Setup our logging using log_path @@ -217,6 +218,9 @@ def set_up_logging(log_path): logfile.setFormatter(logging.Formatter( u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s')) log.addHandler(logfile) + if log.isEnabledFor(logging.DEBUG): + print 'Logging to:', filename + def main(args=None): """ @@ -247,7 +251,6 @@ def main(args=None): qt_args = [] if options.loglevel.lower() in ['d', 'debug']: log.setLevel(logging.DEBUG) - print 'Logging to:', filename elif options.loglevel.lower() in ['w', 'warning']: log.setLevel(logging.WARNING) else: From a2a19675f90e1f305fa488b691d1ea1d5221e35b Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 11 Jul 2012 20:16:51 +0200 Subject: [PATCH 25/32] changed labels (bug 1022837) Fixes: https://launchpad.net/bugs/1022837 --- openlp/core/ui/formattingtagdialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/core/ui/formattingtagdialog.py b/openlp/core/ui/formattingtagdialog.py index e80ed45d1..1eb03e13c 100644 --- a/openlp/core/ui/formattingtagdialog.py +++ b/openlp/core/ui/formattingtagdialog.py @@ -130,15 +130,15 @@ class Ui_FormattingTagDialog(object): translate('OpenLP.FormattingTagDialog', 'Description')) self.tagLabel.setText(translate('OpenLP.FormattingTagDialog', 'Tag')) self.startTagLabel.setText( - translate('OpenLP.FormattingTagDialog', 'Start tag')) + translate('OpenLP.FormattingTagDialog', 'Start HTML')) self.endTagLabel.setText( - translate('OpenLP.FormattingTagDialog', 'End tag')) + translate('OpenLP.FormattingTagDialog', 'End HTML')) self.deletePushButton.setText(UiStrings().Delete) self.newPushButton.setText(UiStrings().New) self.tagTableWidget.horizontalHeaderItem(0).setText( translate('OpenLP.FormattingTagDialog', 'Description')) self.tagTableWidget.horizontalHeaderItem(1).setText( - translate('OpenLP.FormattingTagDialog', 'Tag Id')) + translate('OpenLP.FormattingTagDialog', 'Tag')) self.tagTableWidget.horizontalHeaderItem(2).setText( translate('OpenLP.FormattingTagDialog', 'Start HTML')) self.tagTableWidget.horizontalHeaderItem(3).setText( From b743abac836a33bd1d536891321e9494be76b506 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 11 Jul 2012 20:28:05 +0200 Subject: [PATCH 26/32] fixed bug 1022831 (Configure Formatting Tags listview keyboard navigation) Fixes: https://launchpad.net/bugs/1022831 --- openlp/core/ui/formattingtagform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py index 9d26e6683..e7b996180 100644 --- a/openlp/core/ui/formattingtagform.py +++ b/openlp/core/ui/formattingtagform.py @@ -49,7 +49,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): QtGui.QDialog.__init__(self, parent) self.setupUi(self) QtCore.QObject.connect(self.tagTableWidget, - QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onRowSelected) + QtCore.SIGNAL(u'itemSelectionChanged()'),self.onRowSelected) QtCore.QObject.connect(self.newPushButton, QtCore.SIGNAL(u'clicked()'), self.onNewClicked) QtCore.QObject.connect(self.savePushButton, From 0a89ddbc862204270907ace5f8ff01c91a3d83a4 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Wed, 11 Jul 2012 23:05:28 +0200 Subject: [PATCH 27/32] fixed traceback when deleting last formatting tag form the list --- openlp/core/ui/formattingtagform.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py index e7b996180..3af107224 100644 --- a/openlp/core/ui/formattingtagform.py +++ b/openlp/core/ui/formattingtagform.py @@ -145,6 +145,9 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): """ if self.selected != -1: FormattingTags.remove_html_tag(self.selected) + # As the first items are protected we should not have to take care + # of negative indexes causing tracebacks. + self.tagTableWidget.selectRow(self.selected - 1) self.selected = -1 FormattingTags.save_html_tags() self._reloadTable() From 22c3c76a5ac581b7e7c71dca3c676f976c4429ed Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sat, 14 Jul 2012 21:35:19 +0100 Subject: [PATCH 28/32] Fixes https://bugs.launchpad.net/openlp/+bug/1015524 Note: If a title is not present in the song, it is still added. I feel this is the right action, as it apears that that is how it was in Foil Presenter --- .../plugins/songs/lib/foilpresenterimport.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openlp/plugins/songs/lib/foilpresenterimport.py b/openlp/plugins/songs/lib/foilpresenterimport.py index 2f17f02bc..f77b2f379 100644 --- a/openlp/plugins/songs/lib/foilpresenterimport.py +++ b/openlp/plugins/songs/lib/foilpresenterimport.py @@ -483,8 +483,9 @@ class FoilPresenter(object): # Process verse order verse_order = [] verse_strophenr = [] - for strophennummer in foilpresenterfolie.reihenfolge.strophennummer: - verse_strophenr.append(strophennummer) + if hasattr(foilpresenterfolie, u'reihenfolge.strophennummer'): + for strophennummer in foilpresenterfolie.reihenfolge.strophennummer: + verse_strophenr.append(strophennummer) # Currently we do not support different "parts"! if u'0' in temp_verse_order: for vers in temp_verse_order_backup: @@ -538,12 +539,13 @@ class FoilPresenter(object): ``song`` The song object. """ - for title_string in foilpresenterfolie.titel.titelstring: - if not song.title: - song.title = self._child(title_string) - song.alternate_title = u'' - else: - song.alternate_title = self._child(title_string) + if hasattr(foilpresenterfolie, u'titel.titelstring'): + for title_string in foilpresenterfolie.titel.titelstring: + if not song.title: + song.title = self._child(title_string) + song.alternate_title = u'' + else: + song.alternate_title = self._child(title_string) def _process_topics(self, foilpresenterfolie, song): """ From b37bf5022c3b42146dc4109f2b5a21ddf2a520ed Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sat, 14 Jul 2012 22:10:20 +0100 Subject: [PATCH 29/32] Now uses the first line of the first verse if no title present --- openlp/plugins/songs/lib/foilpresenterimport.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openlp/plugins/songs/lib/foilpresenterimport.py b/openlp/plugins/songs/lib/foilpresenterimport.py index f77b2f379..744fafcf8 100644 --- a/openlp/plugins/songs/lib/foilpresenterimport.py +++ b/openlp/plugins/songs/lib/foilpresenterimport.py @@ -546,6 +546,11 @@ class FoilPresenter(object): song.alternate_title = u'' else: song.alternate_title = self._child(title_string) + else: + # Use first line of first verse + #if hasattr(foilpresenterfolie, u'strophen.strophe'): + first_line = self._child(foilpresenterfolie.strophen.strophe.text_) + song.title = first_line.split('\n')[0] def _process_topics(self, foilpresenterfolie, song): """ From ae37f749f1e39b56ab45696308b738cfab0a4291 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 15 Jul 2012 09:03:37 +0100 Subject: [PATCH 30/32] A real fix this time! --- openlp/plugins/songs/lib/foilpresenterimport.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/songs/lib/foilpresenterimport.py b/openlp/plugins/songs/lib/foilpresenterimport.py index 744fafcf8..0397d41cb 100644 --- a/openlp/plugins/songs/lib/foilpresenterimport.py +++ b/openlp/plugins/songs/lib/foilpresenterimport.py @@ -483,9 +483,11 @@ class FoilPresenter(object): # Process verse order verse_order = [] verse_strophenr = [] - if hasattr(foilpresenterfolie, u'reihenfolge.strophennummer'): + try: for strophennummer in foilpresenterfolie.reihenfolge.strophennummer: verse_strophenr.append(strophennummer) + except AttributeError: + pass # Currently we do not support different "parts"! if u'0' in temp_verse_order: for vers in temp_verse_order_backup: @@ -539,16 +541,15 @@ class FoilPresenter(object): ``song`` The song object. """ - if hasattr(foilpresenterfolie, u'titel.titelstring'): + try: for title_string in foilpresenterfolie.titel.titelstring: if not song.title: song.title = self._child(title_string) song.alternate_title = u'' else: song.alternate_title = self._child(title_string) - else: + except AttributeError: # Use first line of first verse - #if hasattr(foilpresenterfolie, u'strophen.strophe'): first_line = self._child(foilpresenterfolie.strophen.strophe.text_) song.title = first_line.split('\n')[0] From cf1a0b27ea7b00ed5e5fe2aefc1aa29b8257037b Mon Sep 17 00:00:00 2001 From: ElderP Date: Sun, 15 Jul 2012 07:59:55 -0400 Subject: [PATCH 31/32] Added code to hide data location wizard if running portable --- openlp/core/ui/advancedtab.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index 25c6f7a26..c31728a11 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -545,6 +545,10 @@ class AdvancedTab(SettingsTab): self.currentDataPath)) self.defaultColorButton.setStyleSheet( u'background-color: %s' % self.defaultColor) + # Don't allow data directory move if running portable. + if Settings().value(u'advanced/is portable', + QtCore.QVariant(False)).toBool(): + self.dataDirectoryGroupBox.hide() def save(self): """ From a10c56e97688876c00c65932741a7574f96ee1f7 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Thu, 19 Jul 2012 19:26:04 +0200 Subject: [PATCH 32/32] fixed bug 1023585 (No handlers could be found for logger 'openlp.core.lib') Fixes: https://launchpad.net/bugs/1023585 --- openlp/core/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 13c344367..7ea737709 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -248,6 +248,13 @@ def main(args=None): # Parse command line options and deal with them. # Use args supplied programatically if possible. (options, args) = parser.parse_args(args) if args else parser.parse_args() + if options.portable: + app_path = AppLocation.get_directory(AppLocation.AppDir) + set_up_logging(os.path.abspath(os.path.join(app_path, u'..', + u'..', u'Other'))) + log.info(u'Running portable') + else: + set_up_logging(AppLocation.get_directory(AppLocation.CacheDir)) qt_args = [] if options.loglevel.lower() in ['d', 'debug']: log.setLevel(logging.DEBUG) @@ -269,10 +276,6 @@ def main(args=None): app.setApplicationName(u'OpenLPPortable') Settings.setDefaultFormat(Settings.IniFormat) # Get location OpenLPPortable.ini - app_path = AppLocation.get_directory(AppLocation.AppDir) - set_up_logging(os.path.abspath(os.path.join(app_path, u'..', - u'..', u'Other'))) - log.info(u'Running portable') portable_settings_file = os.path.abspath(os.path.join(app_path, u'..', u'..', u'Data', u'OpenLP.ini')) # Make this our settings file @@ -289,7 +292,6 @@ def main(args=None): portable_settings.sync() else: app.setApplicationName(u'OpenLP') - set_up_logging(AppLocation.get_directory(AppLocation.CacheDir)) app.setApplicationVersion(get_application_version()[u'version']) # Instance check if not options.testing: