- add standard verseOrder, if xml does not have any verseOrder property

- added '_get' and '_text' methods which do the same as 'get' and 'text', but make sure that they never return 'None' (but u'' instead)
- fixed wrong use of 'theme'
- implemented songbooks, songnumber and comment
- clean ups
This commit is contained in:
Andreas Preikschat 2011-01-04 10:46:24 +01:00
commit cc229d05af
6 changed files with 304 additions and 104 deletions

View File

@ -73,12 +73,12 @@ class SongImportForm(QtGui.QWizard, Ui_SongImportWizard):
QtCore.QObject.connect(self.openLP1BrowseButton,
QtCore.SIGNAL(u'clicked()'),
self.onOpenLP1BrowseButtonClicked)
#QtCore.QObject.connect(self.openLyricsAddButton,
# QtCore.SIGNAL(u'clicked()'),
# self.onOpenLyricsAddButtonClicked)
#QtCore.QObject.connect(self.openLyricsRemoveButton,
# QtCore.SIGNAL(u'clicked()'),
# self.onOpenLyricsRemoveButtonClicked)
QtCore.QObject.connect(self.openLyricsAddButton,
QtCore.SIGNAL(u'clicked()'),
self.onOpenLyricsAddButtonClicked)
QtCore.QObject.connect(self.openLyricsRemoveButton,
QtCore.SIGNAL(u'clicked()'),
self.onOpenLyricsRemoveButtonClicked)
QtCore.QObject.connect(self.openSongAddButton,
QtCore.SIGNAL(u'clicked()'),
self.onOpenSongAddButtonClicked)
@ -167,16 +167,15 @@ class SongImportForm(QtGui.QWizard, Ui_SongImportWizard):
self.openLP1BrowseButton.setFocus()
return False
elif source_format == SongFormat.OpenLyrics:
# if self.openLyricsFileListWidget.count() == 0:
# QtGui.QMessageBox.critical(self,
# translate('SongsPlugin.ImportWizardForm',
# 'No OpenLyrics Files Selected'),
# translate('SongsPlugin.ImportWizardForm',
# 'You need to add at least one OpenLyrics '
# 'song file to import from.'))
# self.openLyricsAddButton.setFocus()
# return False
return False
if self.openLyricsFileListWidget.count() == 0:
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.ImportWizardForm',
'No OpenLyrics Files Selected'),
translate('SongsPlugin.ImportWizardForm',
'You need to add at least one OpenLyrics '
'song file to import from.'))
self.openLyricsAddButton.setFocus()
return False
elif source_format == SongFormat.OpenSong:
if self.openSongFileListWidget.count() == 0:
QtGui.QMessageBox.critical(self,
@ -337,15 +336,15 @@ class SongImportForm(QtGui.QWizard, Ui_SongImportWizard):
'openlp.org v1.x Databases')
)
#def onOpenLyricsAddButtonClicked(self):
# self.getFiles(
# translate('SongsPlugin.ImportWizardForm',
# 'Select OpenLyrics Files'),
# self.openLyricsFileListWidget
# )
def onOpenLyricsAddButtonClicked(self):
self.getFiles(
translate('SongsPlugin.ImportWizardForm',
'Select OpenLyrics Files'),
self.openLyricsFileListWidget
)
#def onOpenLyricsRemoveButtonClicked(self):
# self.removeSelectedItems(self.openLyricsFileListWidget)
def onOpenLyricsRemoveButtonClicked(self):
self.removeSelectedItems(self.openLyricsFileListWidget)
def onOpenSongAddButtonClicked(self):
self.getFiles(
@ -435,7 +434,7 @@ class SongImportForm(QtGui.QWizard, Ui_SongImportWizard):
self.formatComboBox.setCurrentIndex(0)
self.openLP2FilenameEdit.setText(u'')
self.openLP1FilenameEdit.setText(u'')
#self.openLyricsFileListWidget.clear()
self.openLyricsFileListWidget.clear()
self.openSongFileListWidget.clear()
self.wordsOfWorshipFileListWidget.clear()
self.ccliFileListWidget.clear()

View File

@ -81,9 +81,6 @@ class Ui_SongImportWizard(object):
self.addSingleFileSelectItem(u'openLP1', None, True)
# OpenLyrics
self.addMultiFileSelectItem(u'openLyrics', u'OpenLyrics', True)
# set OpenLyrics to disabled by default
self.openLyricsDisabledWidget.setVisible(True)
self.openLyricsImportWidget.setVisible(False)
# Open Song
self.addMultiFileSelectItem(u'openSong', u'OpenSong')
# Words of Worship
@ -177,10 +174,10 @@ class Ui_SongImportWizard(object):
'importer has been disabled due to a missing Python module. If '
'you want to use this importer, you will need to install the '
'"python-sqlite" module.'))
#self.openLyricsAddButton.setText(
# translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
#self.openLyricsRemoveButton.setText(
# translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
self.openLyricsAddButton.setText(
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
self.openLyricsRemoveButton.setText(
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
self.openLyricsDisabledLabel.setText(
translate('SongsPlugin.ImportWizardForm', 'The OpenLyrics '
'importer has not yet been developed, but as you can see, we are '

View File

@ -26,6 +26,7 @@
from opensongimport import OpenSongImport
from olpimport import OpenLPSongImport
from openlyricsimport import OpenLyricsImport
from wowimport import WowImport
from cclifileimport import CCLIFileImport
from ewimport import EasyWorshipSongImport
@ -77,8 +78,10 @@ class SongFormat(object):
"""
if format == SongFormat.OpenLP2:
return OpenLPSongImport
if format == SongFormat.OpenLP1:
elif format == SongFormat.OpenLP1:
return OpenLP1SongImport
elif format == SongFormat.OpenLyrics:
return OpenLyricsImport
elif format == SongFormat.OpenSong:
return OpenSongImport
elif format == SongFormat.SongsOfFellowship:
@ -93,7 +96,6 @@ class SongFormat(object):
return EasyWorshipSongImport
elif format == SongFormat.SongBeamer:
return SongBeamerImport
# else:
return None
@staticmethod

View File

@ -314,15 +314,14 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem',
'You must select an item to delete.')):
items = self.listView.selectedIndexes()
ans = QtGui.QMessageBox.question(self,
if QtGui.QMessageBox.question(self,
translate('SongsPlugin.MediaItem', 'Delete Song(s)?'),
translate('SongsPlugin.MediaItem',
'Are you sure you want to delete the %n selected song(s)?', '',
QtCore.QCoreApplication.CodecForTr, len(items)),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok|
QtGui.QMessageBox.Cancel),
QtGui.QMessageBox.Ok)
if ans == QtGui.QMessageBox.Cancel:
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok |
QtGui.QMessageBox.Cancel),
QtGui.QMessageBox.Ok) == QtGui.QMessageBox.Cancel:
return
for item in items:
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
@ -396,8 +395,8 @@ class SongMediaItem(MediaManagerItem):
service_item.audit = [
song.title, author_audit, song.copyright, unicode(song.ccli_number)
]
service_item.data_string = {u'title':song.search_title,
u'authors':author_list}
service_item.data_string = {u'title': song.search_title,
u'authors': author_list}
service_item.xml_version = self.openLyrics.song_to_xml(song)
return True
@ -409,7 +408,7 @@ class SongMediaItem(MediaManagerItem):
if item.data_string:
search_results = self.parent.manager.get_all_objects(Song,
Song.search_title ==
item.data_string[u'title'].split(u'@')[0].lower() ,
item.data_string[u'title'].split(u'@')[0].lower(),
Song.search_title.asc())
author_list = item.data_string[u'authors'].split(u', ')
# The service item always has an author (at least it has u'' as
@ -418,7 +417,6 @@ class SongMediaItem(MediaManagerItem):
if u'' in author_list:
author_list.remove(u'')
editId = 0
uuid = item._uuid
add_song = True
if search_results:
for song in search_results:
@ -445,7 +443,7 @@ class SongMediaItem(MediaManagerItem):
# Update service with correct song id.
if editId != 0:
Receiver.send_message(u'service_item_update',
u'%s:%s' %(editId, uuid))
u'%s:%s' % (editId, item._uuid))
def collateSongTitles(self, song_1, song_2):
"""

View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian #
# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Carsten Tinggaard, 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:`openlyricsimport` module provides the functionality for importing
songs which are saved as OpenLyrics files.
"""
import os
from openlp.core.lib import translate
from openlp.plugins.songs.lib.songimport import SongImport
from openlp.plugins.songs.lib import OpenLyricsParser
class OpenLyricsImport(SongImport):
"""
This provides the Openlyrics import.
"""
def __init__(self, master_manager, **kwargs):
"""
Initialise the import.
"""
SongImport.__init__(self, master_manager)
self.master_manager = master_manager
self.openLyricsParser = OpenLyricsParser(master_manager)
if kwargs.has_key(u'filename'):
self.import_source = kwargs[u'filename']
if kwargs.has_key(u'filenames'):
self.import_source = kwargs[u'filenames']
def do_import(self):
"""
Imports the songs.
"""
self.import_wizard.importProgressBar.setMaximum(len(self.import_source))
for file_path in self.import_source:
if self.stop_import_flag:
return False
file = open(file_path)
lines = file.readlines()
file.close()
lines = [line.strip() for line in lines]
xml = u''.join(lines)
self.import_wizard.incrementProgressBar(unicode(translate(
'SongsPlugin.OpenLyricsImport', 'Importing %s...')) %
os.path.basename(file_path))
if self.openLyricsParser.xml_to_song(xml) == 0:
# Importing this song failed! For now we stop import.
return False
return True

View File

@ -42,8 +42,10 @@ import logging
import re
from lxml import etree, objectify
from openlp.core.lib import translate
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.db import Author, Song
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
log = logging.getLogger(__name__)
@ -80,8 +82,8 @@ class SongXMLBuilder(object):
``content``
The actual text of the verse to be stored.
"""
verse = etree.Element(u'verse', type = unicode(type),
label = unicode(number))
verse = etree.Element(u'verse', type=unicode(type),
label=unicode(number))
verse.text = etree.CDATA(content)
self.lyrics.append(verse)
@ -194,9 +196,7 @@ class LyricsXML(object):
text = text.replace('\r\n', '\n')
verses = text.split('\n\n')
self.languages = [{u'language': u'en', u'verses': []}]
counter = 0
for verse in verses:
counter = counter + 1
for counter, verse in enumerate(verses):
self.languages[0][u'verses'].append({
u'type': u'verse',
u'label': unicode(counter),
@ -245,14 +245,16 @@ class LyricsXML(object):
class OpenLyricsParser(object):
"""
This class represents the converter for Song to/from OpenLyrics XML.
This class represents the converter for Song to/from
`OpenLyrics <http://openlyrics.info/>`_ XML.
"""
# TODO: complete OpenLyrics standard implementation as fare as possible!
def __init__(self, manager):
self.manager = manager
def song_to_xml(self, song):
"""
Convert the song to OpenLyrics Format
Convert the song to OpenLyrics Format.
"""
song_xml_parser = SongXMLParser(song.lyrics)
verse_list = song_xml_parser.get_verses()
@ -263,16 +265,33 @@ class OpenLyricsParser(object):
self._add_text_to_element(u'title', titles, song.title)
if song.alternate_title:
self._add_text_to_element(u'title', titles, song.alternate_title)
if song.theme_name:
themes = etree.SubElement(properties, u'themes')
self._add_text_to_element(u'theme', themes, song.theme_name)
self._add_text_to_element(u'copyright', properties, song.copyright)
self._add_text_to_element(u'verseOrder', properties, song.verse_order)
if song.comments:
comments = etree.SubElement(properties, u'comments')
self._add_text_to_element(u'comment', comments, song.comments)
if song.copyright:
self._add_text_to_element(u'copyright', properties, song.copyright)
if song.verse_order:
self._add_text_to_element(
u'verseOrder', properties, song.verse_order)
if song.ccli_number:
self._add_text_to_element(u'ccliNo', properties, song.ccli_number)
authors = etree.SubElement(properties, u'authors')
for author in song.authors:
self._add_text_to_element(u'author', authors, author.display_name)
if song.authors:
authors = etree.SubElement(properties, u'authors')
for author in song.authors:
self._add_text_to_element(
u'author', authors, author.display_name)
book = self.manager.get_object_filtered(
Book, Book.id == song.song_book_id)
if book is not None:
book = book.name
songbooks = etree.SubElement(properties, u'songbooks')
element = self._add_text_to_element(
u'songbook', songbooks, None, book)
element.set(u'entry', song.song_number)
if song.topics:
themes = etree.SubElement(properties, u'themes')
for topic in song.topics:
self._add_text_to_element(u'theme', themes, topic.name)
lyrics = etree.SubElement(song_xml, u'lyrics')
for verse in verse_list:
verse_tag = u'%s%s' % (
@ -286,77 +305,142 @@ class OpenLyricsParser(object):
def xml_to_song(self, xml):
"""
Create a Song from OpenLyrics format xml
Create and save a song from OpenLyrics format xml to the database. Since
we also export XML from external sources (e. g. OpenLyrics import), we
cannot ensure, that it completely conforms to the OpenLyrics standard.
That means, that we for example have to remove chords.
"""
# No xml get out of here
# No xml get out of here.
if not xml:
return 0
song = Song()
if xml[:5] == u'<?xml':
xml = xml[38:]
# Remove chords
xml = re.compile(u'<chord name=".*?"/>').sub(u'', xml)
song_xml = objectify.fromstring(xml)
properties = song_xml.properties
song.copyright = unicode(properties.copyright.text)
if song.copyright == u'None':
# Process Copyright
try:
song.copyright = self._text(properties.copyright)
except AttributeError:
song.copyright = u''
song.verse_order = unicode(properties.verseOrder.text)
if song.verse_order == u'None':
song.verse_order = u''
song.topics = []
song.book = None
theme_name = None
# Process CCLI number
try:
song.ccli_number = unicode(properties.ccliNo.text)
except:
song.ccli_number = self._text(properties.ccliNo)
except AttributeError:
song.ccli_number = u''
try:
theme_name = unicode(properties.themes.theme)
except:
pass
if theme_name:
song.theme_name = theme_name
else:
song.theme_name = u''
# Process Titles
for title in properties.titles.title:
if not song.title:
song.title = unicode(title.text)
song.title = self._text(title)
song.search_title = unicode(song.title)
song.alternate_title = u''
else:
song.alternate_title = unicode(title.text)
song.alternate_title = self._text(title)
song.search_title += u'@' + song.alternate_title
song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
unicode(song.search_title)).lower()
# Process Lyrics
sxml = SongXMLBuilder()
search_text = u''
song.verse_order = u''
for lyrics in song_xml.lyrics:
for verse in song_xml.lyrics.verse:
for verse in lyrics.verse:
text = u''
for line in verse.lines.line:
line = unicode(line)
if not text:
text = line
else:
text += u'\n' + line
for line in verse.lines:
for line in line.line:
line = unicode(line)
if not text:
text = line
else:
text += u'\n' + line
type = VerseType.expand_string(verse.attrib[u'name'][0])
sxml.add_verse_to_lyrics(type, verse.attrib[u'name'][1], text)
# TODO: test this verse_order thing!
song.verse_order += u'%s%s ' % (type[0],
verse.attrib[u'name'][1])
search_text = search_text + text
song.search_lyrics = search_text.lower()
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
song.verse_order = song.verse_order.strip()
# Process verse order
try:
song.verse_order = self._text(properties.verseOrder)
except AttributeError:
# Do not worry, as the verse order has cautionary already been
# saved while creating the verses.
pass
if song.verse_order == u'None':
song.verse_order = u''
# Process Comments
song.comments = u''
song.song_number = u''
try:
for comment in properties.comments.comment:
if not song.comments:
song.comments = self._text(comment)
else:
song.comments += u'\n' + self._text(comment)
except AttributeError:
pass
# Process Authors
try:
for author in properties.authors.author:
self._process_author(author.text, song)
except:
# No Author in XML so ignore
self._process_author(self._text(author), song)
except AttributeError:
pass
if not song.authors:
# Add "Author unknown" (can be translated)
self._process_author(unicode(translate('SongsPlugin.XML',
'Author unknown')), song)
# Process Song Book and Song Number
song.song_book_id = 0
song.song_number = u''
try:
for songbook in properties.songbooks.songbook:
self._process_songbook(self._get(songbook, u'name'), song)
if songbook.get(u'entry'):
song.song_number = self._get(songbook, u'entry')
# OpenLp does only support one song book, so take the first one.
break
except AttributeError:
pass
# Process Topcis
try:
for topic in properties.themes.theme:
self._process_topic(self._text(topic), song)
except AttributeError:
pass
# Properties not yet supported.
song.theme_name = u''
self.manager.save_object(song)
return song.id
def _get(self, element, attribute):
"""
This takes care of empty attributes. It returns the element's attribute.
``element``
The element.
``attribute``
The element's attribute (unicode).
"""
if element.get(attribute) is not None:
return element.get(attribute)
return u''
def _text(self, element):
"""
This takes care of empty texts. It returns the element's text.
``element``
The element.
"""
if element.text is not None:
return unicode(element.text)
return u''
def _add_text_to_element(self, tag, parent, text=None, label=None):
if label:
element = etree.Element(tag, name=unicode(label))
@ -383,17 +467,65 @@ class OpenLyricsParser(object):
def _process_author(self, name, song):
"""
Find or create an Author from display_name.
Finds an existing Author or creates a new Author and adds it to the song
object.
``name``
The display_name of the song (unicode).
``song``
The song the object.
"""
name = unicode(name)
if not name:
# Wrong use of XML here, as no text has been supplied.
return
author = self.manager.get_object_filtered(Author,
Author.display_name == name)
if author:
# should only be one! so take the first
song.authors.append(author)
else:
# Need a new author
new_author = Author.populate(first_name=name.rsplit(u' ', 1)[0],
last_name=name.rsplit(u' ', 1)[1], display_name=name)
self.manager.save_object(new_author)
song.authors.append(new_author)
if author is None:
# We need to create a new author, as the author does not exist.
author = Author.populate(first_name=name.rsplit(u' ', 1)[0],
last_name=name.rsplit(u' ', 1)[1], display_name=name)
self.manager.save_object(author)
song.authors.append(author)
def _process_topic(self, topictext, song):
"""
Finds an existing topic or creates a new topic and adds it to the song
object.
``topictext``
The topictext of the topic (unicode).
``song``
The song object.
"""
if not topictext:
# Wrong use of XML here, as no text has been supplied.
return
topic = self.manager.get_object_filtered(Topic, Topic.name == topictext)
if topic is None:
# We need to create a new topic, as the topic does not exist.
topic = Topic.populate(name=topictext)
self.manager.save_object(topic)
song.topics.append(topic)
def _process_songbook(self, bookname, song):
"""
Finds an existing book or creates a new book and adds it to the song
object.
``bookname``
The name of the book (unicode).
``song``
The song object.
"""
if not bookname:
# Wrong use of XML here, as no text has been supplied.
return
book = self.manager.get_object_filtered(Book, Book.name == bookname)
if book is None:
# We need to create a new book, as the book does not exist.
book = Book.populate(name=bookname, publisher=u'')
self.manager.save_object(book)
song.song_book_id = book.id