- Added a function to clean songs before saving

- fixed Bug #690774

bzr-revno: 1395
Fixes: https://launchpad.net/bugs/690774
This commit is contained in:
Andreas Preikschat 2011-03-17 06:19:54 +01:00
commit e199927b97
8 changed files with 100 additions and 151 deletions

View File

@ -33,7 +33,7 @@ from openlp.core.lib import Receiver, translate
from openlp.core.lib.ui import UiStrings, add_widget_completer, \
critical_error_message_box
from openlp.plugins.songs.forms import EditVerseForm
from openlp.plugins.songs.lib import SongXML, VerseType
from openlp.plugins.songs.lib import SongXML, VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic
from openlp.plugins.songs.lib.ui import SongStrings
from editsongdialog import Ui_EditSongDialog
@ -728,17 +728,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.title = unicode(self.titleEdit.text())
self.song.alternate_title = unicode(self.alternativeEdit.text())
self.song.copyright = unicode(self.copyrightEdit.text())
if self.song.alternate_title:
self.song.search_title = self.song.title + u'@' + \
self.song.alternate_title
else:
self.song.search_title = self.song.title
# Values will be set when cleaning the song.
self.song.search_title = u''
self.song.search_lyrics = u''
self.song.verse_order = u''
self.song.comments = unicode(self.commentsEdit.toPlainText())
ordertext = unicode(self.verseOrderEdit.text())
order = []
for item in ordertext.split():
verse_tag = VerseType.Tags[
VerseType.from_translated_tag(item[0])]
verse_tag = VerseType.Tags[VerseType.from_translated_tag(item[0])]
verse_num = item[1:].lower()
order.append(u'%s%s' % (verse_tag, verse_num))
self.song.verse_order = u' '.join(order)
@ -756,7 +754,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
else:
self.song.theme_name = None
self.processLyrics()
self.processTitle()
self.song.authors = []
for row in range(self.authorsListView.count()):
item = self.authorsListView.item(row)
@ -767,6 +764,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
item = self.topicsListView.item(row)
topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.song.topics.append(self.manager.get_object(Topic, topicId))
clean_song(self.manager, self.song)
self.manager.save_object(self.song)
if not preview:
self.song = None
@ -779,7 +777,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
log.debug(u'processLyrics')
try:
sxml = SongXML()
text = u''
multiple = []
for i in range(0, self.verseListWidget.rowCount()):
item = self.verseListWidget.item(i, 0)
@ -788,11 +785,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_num = verseId[1:]
sxml.add_verse_to_lyrics(verse_tag, verse_num,
unicode(item.text()))
text = text + self.whitespace.sub(u' ',
unicode(self.verseListWidget.item(i, 0).text())) + u' '
if (verse_num > u'1') and (verse_tag not in multiple):
if verse_num > u'1' and verse_tag not in multiple:
multiple.append(verse_tag)
self.song.search_lyrics = text.lower()
self.song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
for verse in multiple:
self.song.verse_order = re.sub(u'([' + verse.upper() +
@ -801,13 +795,3 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
except:
log.exception(u'Problem processing song Lyrics \n%s',
sxml.dump_xml())
def processTitle(self):
"""
Process the song title entered by the user to remove stray punctuation
characters.
"""
# This method must only be run after the self.song = Song() assignment.
log.debug(u'processTitle')
self.song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
unicode(self.song.search_title)).lower().strip()

View File

@ -23,6 +23,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import re
from PyQt4 import QtGui
@ -244,9 +245,11 @@ def retrieve_windows_encoding(recommendation=None):
return None
return filter(lambda item: item[1] == choice[0], encodings)[0][0]
def add_author_unknown(manager, song):
def clean_song(manager, song):
"""
Add the default author *Author Unknown* to the song.
Cleans the search title, rebuilds the search lyrics, adds a default author
if the song does not have one and other clean ups. This should always
called when a new song is added or changed.
``manager``
The song's manager.
@ -254,12 +257,29 @@ def add_author_unknown(manager, song):
``song``
The song object.
"""
name = SongStrings.AuthorUnknown
author = manager.get_object_filtered(Author, Author.display_name == name)
if author is None:
author = Author.populate(
display_name=name, last_name=u'', first_name=u'')
song.authors.append(author)
song.title = song.title.strip() if song.title else u''
if song.alternate_title is None:
song.alternate_title = u''
song.alternate_title = song.alternate_title.strip()
whitespace = re.compile(r'\W+', re.UNICODE)
song.search_title = (whitespace.sub(u' ', song.title).strip() + u'@' +
whitespace.sub(u' ', song.alternate_title).strip()).strip().lower()
# Remove the old "language" attribute from lyrics tag (prior to 1.9.5). This
# is not very important, but this keeps the database clean. This can be
# removed when everybody has cleaned his songs.
song.lyrics = song.lyrics.replace(u'<lyrics language="en">', u'<lyrics>')
verses = SongXML().get_verses(song.lyrics)
lyrics = u' '.join([whitespace.sub(u' ', verse[1]) for verse in verses])
song.search_lyrics = lyrics.lower()
# The song does not have any author, add one.
if not song.authors:
name = SongStrings.AuthorUnknown
author = manager.get_object_filtered(
Author, Author.display_name == name)
if author is None:
author = Author.populate(
display_name=name, last_name=u'', first_name=u'')
song.authors.append(author)
from xml import OpenLyrics, SongXML
from songstab import SongsTab

View File

@ -94,7 +94,7 @@ import os
from lxml import etree, objectify
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import add_author_unknown, VerseType
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.songimport import SongImport
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
from openlp.plugins.songs.lib.xml import SongXML
@ -212,9 +212,13 @@ class FoilPresenter(object):
# No xml get out of here.
if not xml:
return None
song = Song()
if xml[:5] == u'<?xml':
xml = xml[38:]
song = Song()
# Values will be set when cleaning the song.
song.search_lyrics = u''
song.verse_order = u''
song.search_title = u''
# Because "text" seems to be an reserverd word, we have to recompile it.
xml = re.compile(u'<text>').sub(u'<text_>', xml)
xml = re.compile(u'</text>').sub(u'</text_>', xml)
@ -229,6 +233,7 @@ class FoilPresenter(object):
self._process_authors(foilpresenterfolie, song)
self._process_songbooks(foilpresenterfolie, song)
self._process_topics(foilpresenterfolie, song)
clean_song(self.manager, song)
self.manager.save_object(song)
return song.id
@ -348,8 +353,6 @@ class FoilPresenter(object):
first_name = u' '.join(display_name.split(u' ')[:-1]))
self.manager.save_object(author)
song.authors.append(author)
if not song.authors:
add_author_unknown(self.manager, song)
def _process_cclinumber(self, foilpresenterfolie, song):
"""
@ -407,7 +410,6 @@ class FoilPresenter(object):
The song object.
"""
sxml = SongXML()
search_text = u''
temp_verse_order = {}
temp_verse_order_backup = []
temp_sortnr_backup = 1
@ -452,7 +454,6 @@ class FoilPresenter(object):
else:
verse_type = u'O'
verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name)
#verse_part = re.compile(u'[0-9]*').sub(u'', verse_name[1:])
# Foilpresenter allows e. g. "C", but we need "C1".
if not verse_number:
verse_number = unicode(versenumber[verse_type])
@ -470,8 +471,6 @@ class FoilPresenter(object):
temp_verse_order_backup.append(u''.join((verse_type[0],
verse_number)))
sxml.add_verse_to_lyrics(verse_type, verse_number, text)
search_text = search_text + text
song.search_lyrics = search_text.lower()
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Process verse order
verse_order = []
@ -534,13 +533,9 @@ class FoilPresenter(object):
for titelstring in foilpresenterfolie.titel.titelstring:
if not song.title:
song.title = self._child(titelstring)
song.search_title = unicode(song.title)
song.alternate_title = u''
else:
song.alternate_title = self._child(titelstring)
song.search_title += u'@' + song.alternate_title
song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
unicode(song.search_title)).lower().strip()
def _process_topics(self, foilpresenterfolie, song):
"""
@ -565,10 +560,3 @@ class FoilPresenter(object):
song.topics.append(topic)
except AttributeError:
pass
def _dump_xml(self, xml):
"""
Debugging aid to dump XML so that we can see what we have.
"""
return etree.tostring(xml, encoding=u'UTF-8',
xml_declaration=True, pretty_print=True)

View File

@ -266,9 +266,8 @@ class SongMediaItem(MediaManagerItem):
Receiver.send_message(u'songs_load_list')
def onExportClick(self):
if not hasattr(self, u'export_wizard'):
self.export_wizard = SongExportForm(self, self.parent)
self.export_wizard.exec_()
export_wizard = SongExportForm(self, self.parent)
export_wizard.exec_()
def onNewClick(self):
log.debug(u'onNewClick')
@ -412,29 +411,32 @@ class SongMediaItem(MediaManagerItem):
def serviceLoad(self, item):
"""
Triggered by a song being loaded by the service item
Triggered by a song being loaded by the service manager.
"""
log.debug(u'serviceLoad')
if self.plugin.status != PluginStatus.Active or not item.data_string:
return
search_results = self.parent.manager.get_all_objects(Song,
Song.search_title == re.compile(r'\W+', re.UNICODE).sub(u' ',
item.data_string[u'title'].split(u'@')[0].lower()).strip(),
Song.search_title.asc())
if item.data_string[u'title'].find(u'@') == -1:
# This file seems to be an old one (prior to 1.9.5), which means,
# that the search title (data_string[u'title']) is probably wrong.
# We add "@" to search title and hope that we do not add any
# duplicate. This should work for songs without alternate title.
search_results = self.parent.manager.get_all_objects(Song,
Song.search_title == (re.compile(r'\W+', re.UNICODE).sub(u' ',
item.data_string[u'title'].strip()) + u'@').strip().lower(),
Song.search_title.asc())
else:
search_results = self.parent.manager.get_all_objects(Song,
Song.search_title == item.data_string[u'title'],
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
# author). However, songs saved in the database do not have to have
# an author.
if u'' in author_list:
author_list.remove(u'')
editId = 0
add_song = True
if search_results:
for song in search_results:
same_authors = True
# If the author counts are different, we do not have to do any
# further checking. This is also important when a song does not
# have any author (because we can not loop over an empty list).
# further checking.
if len(song.authors) == len(author_list):
for author in song.authors:
if author.display_name not in author_list:

View File

@ -36,7 +36,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
from openlp.core.lib import translate
from openlp.core.lib.db import BaseModel
from openlp.plugins.songs.lib import add_author_unknown
from openlp.plugins.songs.lib import clean_song
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic #, MediaFile
from songimport import SongImport
@ -167,12 +167,11 @@ class OpenLPSongImport(SongImport):
old_titles = song.search_title.split(u'@')
if len(old_titles) > 1:
new_song.alternate_title = old_titles[1]
else:
new_song.alternate_title = u''
new_song.search_title = song.search_title.strip()
# Values will be set when cleaning the song.
new_song.search_title = u''
new_song.search_lyrics = u''
new_song.song_number = song.song_number
new_song.lyrics = song.lyrics
new_song.search_lyrics = song.search_lyrics
new_song.verse_order = song.verse_order
new_song.copyright = song.copyright
new_song.comments = song.comments
@ -181,31 +180,26 @@ class OpenLPSongImport(SongImport):
for author in song.authors:
existing_author = self.manager.get_object_filtered(
Author, Author.display_name == author.display_name)
if existing_author:
new_song.authors.append(existing_author)
else:
new_song.authors.append(Author.populate(
if existing_author is None:
existing_author = Author.populate(
first_name=author.first_name,
last_name=author.last_name,
display_name=author.display_name))
if not new_song.authors:
add_author_unknown(self.manager, new_song)
display_name=author.display_name)
new_song.authors.append(existing_author)
if song.book:
existing_song_book = self.manager.get_object_filtered(
Book, Book.name == song.book.name)
if existing_song_book:
new_song.book = existing_song_book
else:
new_song.book = Book.populate(name=song.book.name,
if existing_song_book is None:
existing_song_book = Book.populate(name=song.book.name,
publisher=song.book.publisher)
new_song.book = existing_song_book
if song.topics:
for topic in song.topics:
existing_topic = self.manager.get_object_filtered(
Topic, Topic.name == topic.name)
if existing_topic:
new_song.topics.append(existing_topic)
else:
new_song.topics.append(Topic.populate(name=topic.name))
if existing_topic is None:
existing_topic = Topic.populate(name=topic.name)
new_song.topics.append(existing_topic)
# if has_media_files:
# if song.media_files:
# for media_file in song.media_files:
@ -217,6 +211,7 @@ class OpenLPSongImport(SongImport):
# else:
# new_song.media_files.append(MediaFile.populate(
# file_name=media_file.file_name))
clean_song(self.manager, new_song)
self.manager.save_object(new_song)
song_count += 1
if self.stop_import_flag:

View File

@ -29,7 +29,7 @@ import re
from PyQt4 import QtCore
from openlp.core.lib import Receiver, translate
from openlp.plugins.songs.lib import add_author_unknown, VerseType
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.xml import SongXML
@ -245,12 +245,6 @@ class SongImport(QtCore.QObject):
else:
return True
def remove_punctuation(self, text):
"""
Extracts alphanumeric words for searchable fields
"""
return re.sub(r'\W+', u' ', text, re.UNICODE)
def finish(self):
"""
All fields have been set to this song. Write the song to disk.
@ -259,11 +253,11 @@ class SongImport(QtCore.QObject):
song = Song()
song.title = self.title
song.alternate_title = self.alternate_title
song.search_title = self.remove_punctuation(self.title).lower() \
+ '@' + self.remove_punctuation(self.alternate_title).lower()
song.search_title = song.search_title.strip()
song.song_number = self.song_number
# Values will be set when cleaning the song.
song.search_title = u''
song.search_lyrics = u''
song.verse_order = u''
song.song_number = self.song_number
verses_changed_to_other = {}
sxml = SongXML()
other_count = 1
@ -280,8 +274,6 @@ class SongImport(QtCore.QObject):
new_verse_def)
verse_def = new_verse_def
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
song.search_lyrics += u' ' + self.remove_punctuation(verse_text)
song.search_lyrics = song.search_lyrics.lower()
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
if not len(self.verse_order_list) and \
self.verse_order_list_generated_useful:
@ -303,9 +295,6 @@ class SongImport(QtCore.QObject):
last_name=authortext.split(u' ')[-1],
first_name=u' '.join(authortext.split(u' ')[:-1]))
song.authors.append(author)
# No author, add the default author.
if not song.authors:
add_author_unknown(self.manager, song)
for filename in self.media_files:
media_file = self.manager.get_object_filtered(MediaFile,
MediaFile.file_name == filename)
@ -326,6 +315,7 @@ class SongImport(QtCore.QObject):
if topic is None:
topic = Topic.populate(name=topictext)
song.topics.append(topic)
clean_song(self.manager, song)
self.manager.save_object(song)
self.set_defaults()

View File

@ -66,7 +66,7 @@ import re
from lxml import etree, objectify
from openlp.plugins.songs.lib import add_author_unknown, VerseType
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
log = logging.getLogger(__name__)
@ -236,10 +236,9 @@ class OpenLyrics(object):
datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S'))
properties = etree.SubElement(song_xml, u'properties')
titles = etree.SubElement(properties, u'titles')
self._add_text_to_element(u'title', titles, song.title.strip())
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.strip())
self._add_text_to_element(u'title', titles, song.alternate_title)
if song.comments:
comments = etree.SubElement(properties, u'comments')
self._add_text_to_element(u'comment', comments, song.comments)
@ -303,6 +302,10 @@ class OpenLyrics(object):
else:
return None
song = Song()
# Values will be set when cleaning the song.
song.search_lyrics = u''
song.verse_order = u''
song.search_title = u''
self._process_copyright(properties, song)
self._process_cclinumber(properties, song)
self._process_titles(properties, song)
@ -312,6 +315,7 @@ class OpenLyrics(object):
self._process_authors(properties, song)
self._process_songbooks(properties, song)
self._process_topics(properties, song)
clean_song(self.manager, song)
self.manager.save_object(song)
return song.id
@ -382,8 +386,6 @@ class OpenLyrics(object):
last_name=display_name.split(u' ')[-1],
first_name=u' '.join(display_name.split(u' ')[:-1]))
song.authors.append(author)
if not song.authors:
add_author_unknown(self.manager, song)
def _process_cclinumber(self, properties, song):
"""
@ -443,7 +445,6 @@ class OpenLyrics(object):
The song object.
"""
sxml = SongXML()
search_text = u''
for verse in lyrics.verse:
text = u''
for lines in verse.lines:
@ -462,8 +463,6 @@ class OpenLyrics(object):
if self._get(verse, u'lang'):
lang = self._get(verse, u'lang')
sxml.add_verse_to_lyrics(verse_type, verse_number, text, lang)
search_text = search_text + text
song.search_lyrics = search_text.lower()
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Process verse order
if hasattr(properties, u'verseOrder'):
@ -510,13 +509,9 @@ class OpenLyrics(object):
for title in properties.titles.title:
if not song.title:
song.title = self._text(title)
song.search_title = unicode(song.title)
song.alternate_title = u''
else:
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().strip()
def _process_topics(self, properties, song):
"""

View File

@ -25,7 +25,6 @@
###############################################################################
import logging
import re
import os
from tempfile import gettempdir
@ -35,8 +34,7 @@ from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver
from openlp.core.lib.db import Manager
from openlp.core.lib.ui import UiStrings
from openlp.plugins.songs.lib import add_author_unknown, SongMediaItem, \
SongsTab, SongXML
from openlp.plugins.songs.lib import clean_song, SongMediaItem, SongsTab
from openlp.plugins.songs.lib.db import init_schema, Song
from openlp.plugins.songs.lib.importer import SongFormat
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
@ -62,7 +60,6 @@ class SongsPlugin(Plugin):
self.manager = Manager(u'songs', init_schema)
self.icon_path = u':/plugins/plugin_songs.png'
self.icon = build_icon(self.icon_path)
self.whitespace = re.compile(r'\W+', re.UNICODE)
def initialise(self):
log.info(u'Songs Initialising')
@ -141,38 +138,18 @@ class SongsPlugin(Plugin):
Rebuild each song.
"""
maxSongs = self.manager.get_object_count(Song)
if maxSongs == 0:
return
progressDialog = QtGui.QProgressDialog(
translate('SongsPlugin', 'Reindexing songs...'), UiStrings.Cancel,
0, maxSongs + 1, self.formparent)
0, maxSongs, self.formparent)
progressDialog.setWindowModality(QtCore.Qt.WindowModal)
songs = self.manager.get_all_objects(Song)
counter = 0
for song in songs:
counter += 1
# The song does not have any author, add one.
if not song.authors:
add_author_unknown(self.manager, song)
if song.title is None:
song.title = u''
if song.alternate_title is None:
song.alternate_title = u''
song.search_title = self.whitespace.sub(u' ', song.title.lower() +
u' ' + song.alternate_title.lower()).strip()
# Remove the "language" attribute from lyrics tag. This is not very
# important, but this keeps the database clean. This can be removed
# when everybody has run the reindex tool once.
song.lyrics = song.lyrics.replace(
u'<lyrics language="en">', u'<lyrics>')
lyrics = u''
verses = SongXML().get_verses(song.lyrics)
for verse in verses:
lyrics = lyrics + self.whitespace.sub(u' ', verse[1]) + u' '
song.search_lyrics = lyrics.lower()
progressDialog.setValue(counter)
for number, song in enumerate(songs):
clean_song(self.manager, song)
progressDialog.setValue(number + 1)
self.manager.save_objects(songs)
progressDialog.setValue(counter + 1)
self.mediaItem.displayResultsSong(
self.manager.get_all_objects(Song, order_by_ref=Song.search_title))
self.mediaItem.onSearchTextButtonClick()
def onSongImportItemClicked(self):
if self.mediaItem:
@ -183,10 +160,9 @@ class SongsPlugin(Plugin):
self.mediaItem.onExportClick()
def about(self):
about_text = translate('SongsPlugin', '<strong>Songs Plugin</strong>'
return translate('SongsPlugin', '<strong>Songs Plugin</strong>'
'<br />The songs plugin provides the ability to display and '
'manage songs.')
return about_text
def usesTheme(self, theme):
"""
@ -258,6 +234,7 @@ class SongsPlugin(Plugin):
for sfile in os.listdir(db_dir):
if sfile.startswith(u'songs_') and sfile.endswith(u'.sqlite'):
song_dbs.append(os.path.join(db_dir, sfile))
self.onToolsReindexItemTriggered()
if len(song_dbs) == 0:
return
progress = QtGui.QProgressDialog(self.formparent)
@ -273,9 +250,7 @@ class SongsPlugin(Plugin):
importer = OpenLPSongImport(self.manager, filename=db)
importer.do_import()
progress.setValue(len(song_dbs))
self.mediaItem.displayResultsSong(
self.manager.get_all_objects(Song, order_by_ref=Song.search_title))
self.onToolsReindexItemTriggered()
self.mediaItem.onSearchTextButtonClick()
def finalise(self):
"""