openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #05468
[Merge] lp:~googol-hush/openlp/OpenLyrics into lp:openlp
Andreas Preikschat has proposed merging lp:~googol-hush/openlp/OpenLyrics into lp:openlp.
Requested reviews:
Tim Bentley (trb143)
For more details, see:
https://code.launchpad.net/~googol-hush/openlp/OpenLyrics/+merge/45646
- added OpenLyrics importer
- continued to implement OpenLyrics features
- split class
- fix wrong use of "theme"
- and other thinks
Importing songs with multiple languages has not been considered (yet). (One song with all languages is imported.)
Cheers
--
https://code.launchpad.net/~googol-hush/openlp/OpenLyrics/+merge/45646
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/ui/__init__.py'
--- openlp/core/ui/__init__.py 2011-01-05 16:57:49 +0000
+++ openlp/core/ui/__init__.py 2011-01-09 19:37:54 +0000
@@ -35,11 +35,11 @@
``Blank``
This mode is used to hide all output, specifically by covering the
display with a black screen.
-
+
``Theme``
This mode is used to hide all output, but covers the display with the
current theme background, as opposed to black.
-
+
``Desktop``
This mode hides all output by minimising the display, leaving the user's
desktop showing.
=== modified file 'openlp/plugins/bibles/forms/bibleimportwizard.py'
--- openlp/plugins/bibles/forms/bibleimportwizard.py 2010-12-27 22:57:35 +0000
+++ openlp/plugins/bibles/forms/bibleimportwizard.py 2011-01-09 19:37:54 +0000
@@ -234,7 +234,7 @@
QtGui.QSizePolicy.Minimum)
self.openlp1Layout.setItem(1, QtGui.QFormLayout.LabelRole,
self.openlp1Spacer)
- self.selectStack.addWidget(self.openlp1Widget)
+ self.selectStack.addWidget(self.openlp1Widget)
self.selectPageLayout.addLayout(self.selectStack)
bibleImportWizard.addPage(self.selectPage)
# License Page
=== modified file 'openlp/plugins/songs/forms/authorsdialog.py'
--- openlp/plugins/songs/forms/authorsdialog.py 2011-01-04 21:06:50 +0000
+++ openlp/plugins/songs/forms/authorsdialog.py 2011-01-09 19:37:54 +0000
@@ -54,7 +54,7 @@
self.displayEdit.setObjectName(u'displayEdit')
self.displayLabel.setBuddy(self.displayEdit)
self.authorLayout.addRow(self.displayLabel, self.displayEdit)
- self.dialogLayout.addLayout(self.authorLayout)
+ self.dialogLayout.addLayout(self.authorLayout)
self.buttonBox = QtGui.QDialogButtonBox(authorsDialog)
self.buttonBox.setStandardButtons(
QtGui.QDialogButtonBox.Save | QtGui.QDialogButtonBox.Cancel)
=== modified file 'openlp/plugins/songs/forms/editsongform.py'
--- openlp/plugins/songs/forms/editsongform.py 2011-01-05 10:48:47 +0000
+++ openlp/plugins/songs/forms/editsongform.py 2011-01-09 19:37:54 +0000
@@ -31,7 +31,7 @@
from openlp.core.lib import Receiver, translate
from openlp.plugins.songs.forms import EditVerseForm
-from openlp.plugins.songs.lib import SongXMLBuilder, SongXMLParser, VerseType
+from openlp.plugins.songs.lib import SongXML, VerseType
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic
from editsongdialog import Ui_EditSongDialog
@@ -263,8 +263,8 @@
if isinstance(self.song.lyrics, buffer):
self.song.lyrics = unicode(self.song.lyrics)
if self.song.lyrics.startswith(u'<?xml version='):
- songXML = SongXMLParser(self.song.lyrics)
- verseList = songXML.get_verses()
+ songXML = SongXML()
+ verseList = songXML.get_verses(self.song.lyrics)
for count, verse in enumerate(verseList):
self.verseListWidget.setRowCount(
self.verseListWidget.rowCount() + 1)
@@ -731,7 +731,7 @@
def processLyrics(self):
log.debug(u'processLyrics')
try:
- sxml = SongXMLBuilder()
+ sxml = SongXML()
text = u''
multiple = []
for i in range(0, self.verseListWidget.rowCount()):
=== modified file 'openlp/plugins/songs/forms/songimportform.py'
--- openlp/plugins/songs/forms/songimportform.py 2010-12-29 16:35:10 +0000
+++ openlp/plugins/songs/forms/songimportform.py 2011-01-09 19:37:54 +0000
@@ -73,12 +73,12 @@
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 @@
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 @@
'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 @@
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()
=== modified file 'openlp/plugins/songs/forms/songimportwizard.py'
--- openlp/plugins/songs/forms/songimportwizard.py 2010-12-27 18:23:46 +0000
+++ openlp/plugins/songs/forms/songimportwizard.py 2011-01-09 19:37:54 +0000
@@ -39,7 +39,7 @@
QtGui.QWizard.IndependentPages |
QtGui.QWizard.NoBackButtonOnStartPage |
QtGui.QWizard.NoBackButtonOnLastPage)
- # Welcome Page
+ # Welcome Page
self.welcomePage = QtGui.QWizardPage()
self.welcomePage.setPixmap(QtGui.QWizard.WatermarkPixmap,
QtGui.QPixmap(u':/wizards/wizard_importsong.bmp'))
@@ -81,9 +81,6 @@
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 @@
'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 '
=== modified file 'openlp/plugins/songs/lib/__init__.py'
--- openlp/plugins/songs/lib/__init__.py 2011-01-04 10:13:41 +0000
+++ openlp/plugins/songs/lib/__init__.py 2011-01-09 19:37:54 +0000
@@ -175,6 +175,6 @@
return None
return filter(lambda item: item[1] == choice[0], encodings)[0][0]
-from xml import LyricsXML, SongXMLBuilder, SongXMLParser, OpenLyricsParser
+from xml import OpenLyrics, SongXML
from songstab import SongsTab
from mediaitem import SongMediaItem
=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py 2010-12-26 11:04:47 +0000
+++ openlp/plugins/songs/lib/importer.py 2011-01-09 19:37:54 +0000
@@ -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 @@
"""
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 @@
return EasyWorshipSongImport
elif format == SongFormat.SongBeamer:
return SongBeamerImport
-# else:
return None
@staticmethod
=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py 2011-01-07 18:58:06 +0000
+++ openlp/plugins/songs/lib/mediaitem.py 2011-01-09 19:37:54 +0000
@@ -35,7 +35,7 @@
ItemCapabilities, translate, check_item_selected
from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
SongImportForm
-from openlp.plugins.songs.lib import SongXMLParser, OpenLyricsParser
+from openlp.plugins.songs.lib import OpenLyrics, SongXML
from openlp.plugins.songs.lib.db import Author, Song
from openlp.core.lib.searchedit import SearchEdit
@@ -58,7 +58,7 @@
self.ListViewWithDnD_class = SongListView
MediaManagerItem.__init__(self, parent, self, icon)
self.edit_song_form = EditSongForm(self, self.parent.manager)
- self.openLyrics = OpenLyricsParser(self.parent.manager)
+ self.openLyrics = OpenLyrics(self.parent.manager)
self.singleServiceItem = False
self.song_maintenance_form = SongMaintenanceForm(
self.parent.manager, self)
@@ -322,15 +322,14 @@
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]
@@ -362,8 +361,7 @@
service_item.theme = song.theme_name
service_item.edit_id = item_id
if song.lyrics.startswith(u'<?xml version='):
- songXML = SongXMLParser(song.lyrics)
- verseList = songXML.get_verses()
+ verseList = SongXML().get_verses(song.lyrics)
# no verse list or only 1 space (in error)
if not song.verse_order or not song.verse_order.strip():
for verse in verseList:
@@ -404,8 +402,8 @@
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
@@ -416,8 +414,8 @@
log.debug(u'serviceLoad')
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() ,
+ 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())
author_list = item.data_string[u'authors'].split(u', ')
# The service item always has an author (at least it has u'' as
@@ -426,7 +424,6 @@
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:
@@ -453,7 +450,7 @@
# 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):
"""
=== added file 'openlp/plugins/songs/lib/openlyricsimport.py'
--- openlp/plugins/songs/lib/openlyricsimport.py 1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/openlyricsimport.py 2011-01-09 19:37:54 +0000
@@ -0,0 +1,77 @@
+# -*- 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 logging
+import os
+
+from lxml import etree
+
+from openlp.core.lib import translate
+from openlp.plugins.songs.lib.songimport import SongImport
+from openlp.plugins.songs.lib import OpenLyrics
+
+log = logging.getLogger(__name__)
+
+class OpenLyricsImport(SongImport):
+ """
+ This provides the Openlyrics import.
+ """
+ def __init__(self, master_manager, **kwargs):
+ """
+ Initialise the import.
+ """
+ log.debug(u'initialise OpenLyricsImport')
+ SongImport.__init__(self, master_manager)
+ self.master_manager = master_manager
+ self.openLyrics = OpenLyrics(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
+ self.import_wizard.incrementProgressBar(unicode(translate(
+ 'SongsPlugin.OpenLyricsImport', 'Importing %s...')) %
+ os.path.basename(file_path))
+ parser = etree.XMLParser(remove_blank_text=True)
+ file = etree.parse(file_path, parser)
+ xml = unicode(etree.tostring(file))
+ if self.openLyrics.xml_to_song(xml) == 0:
+ log.debug(u'File could not be imported: %s' % file_path)
+ # Importing this song failed! For now we stop import.
+ return False
+ return True
=== modified file 'openlp/plugins/songs/lib/songimport.py'
--- openlp/plugins/songs/lib/songimport.py 2010-12-26 11:04:47 +0000
+++ openlp/plugins/songs/lib/songimport.py 2011-01-09 19:37:54 +0000
@@ -31,7 +31,7 @@
from openlp.core.lib import Receiver, translate
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
-from openlp.plugins.songs.lib.xml import SongXMLBuilder
+from openlp.plugins.songs.lib.xml import SongXML
log = logging.getLogger(__name__)
@@ -270,7 +270,7 @@
song.song_number = self.song_number
song.search_lyrics = u''
verses_changed_to_other = {}
- sxml = SongXMLBuilder()
+ sxml = SongXML()
other_count = 1
for (versetag, versetext) in self.verses:
if versetag[0] == u'C':
=== modified file 'openlp/plugins/songs/lib/xml.py'
--- openlp/plugins/songs/lib/xml.py 2010-12-26 11:04:47 +0000
+++ openlp/plugins/songs/lib/xml.py 2011-01-09 19:37:54 +0000
@@ -24,55 +24,73 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-The :mod:`xml` module provides the XML functionality for songs
+The :mod:`xml` module provides the XML functionality.
-The basic XML is of the format::
+The basic XML for storing the lyrics in the song database is of the format::
<?xml version="1.0" encoding="UTF-8"?>
<song version="1.0">
- <lyrics language="en">
+ <lyrics>
<verse type="chorus" label="1">
<![CDATA[ ... ]]>
</verse>
</lyrics>
</song>
+
+
+The XML of `OpenLyrics <http://openlyrics.info/>`_ songs is of the format::
+
+ <song xmlns="http://openlyrics.info/namespace/2009/song"
+ version="0.7"
+ createdIn="OpenLP 1.9.0"
+ modifiedIn="ChangingSong 0.0.1"
+ modifiedDate="2010-01-28T13:15:30+01:00">
+ <properties>
+ <titles>
+ <title>Amazing Grace</title>
+ </titles>
+ </properties>
+ <lyrics>
+ <verse name="v1">
+ <lines>
+ <line>Amazing grace how sweet the sound</line>
+ </lines>
+ </verse>
+ </lyrics>
+ </song>
"""
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__)
-class SongXMLBuilder(object):
- """
- This class builds the XML used to describe songs.
- """
- log.info(u'SongXMLBuilder Loaded')
-
- def __init__(self, song_language=None):
- """
- Set up the song builder.
-
- ``song_language``
- The language used in this song
- """
- lang = u'en'
- if song_language:
- lang = song_language
+class SongXML(object):
+ """
+ This class builds and parses the XML used to describe songs.
+ """
+ log.info(u'SongXML Loaded')
+
+ def __init__(self):
+ """
+ Set up the default variables.
+ """
self.song_xml = objectify.fromstring(u'<song version="1.0" />')
- self.lyrics = etree.SubElement(self.song_xml, u'lyrics', language=lang)
+ self.lyrics = etree.SubElement(self.song_xml, u'lyrics')
def add_verse_to_lyrics(self, type, number, content):
"""
- Add a verse to the ``<lyrics>`` tag.
+ Add a verse to the *<lyrics>* tag.
``type``
- A string denoting the type of verse. Possible values are "Chorus",
- "Verse", "Bridge", and "Custom".
+ A string denoting the type of verse. Possible values are "V",
+ "C", "B", "P", "I", "E" and "O".
``number``
An integer denoting the number of the item, for example: verse 1.
@@ -80,18 +98,11 @@
``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)
- def dump_xml(self):
- """
- Debugging aid to dump XML so that we can see what we have.
- """
- return etree.tostring(self.song_xml, encoding=u'UTF-8',
- xml_declaration=True, pretty_print=True)
-
def extract_xml(self):
"""
Extract our newly created XML song.
@@ -99,16 +110,10 @@
return etree.tostring(self.song_xml, encoding=u'UTF-8',
xml_declaration=True)
-
-class SongXMLParser(object):
- """
- A class to read in and parse a song's XML.
- """
- log.info(u'SongXMLParser Loaded')
-
- def __init__(self, xml):
+ def get_verses(self, xml):
"""
- Set up our song XML parser.
+ Iterates through the verses in the XML and returns a list of verses
+ and their attributes.
``xml``
The XML of the song to be parsed.
@@ -120,12 +125,6 @@
self.song_xml = objectify.fromstring(xml)
except etree.XMLSyntaxError:
log.exception(u'Invalid xml %s', xml)
-
- def get_verses(self):
- """
- Iterates through the verses in the XML and returns a list of verses
- and their attributes.
- """
xml_iter = self.song_xml.getiterator()
verse_list = []
for element in xml_iter:
@@ -142,137 +141,109 @@
return etree.dump(self.song_xml)
-class LyricsXML(object):
- """
- This class represents the XML in the ``lyrics`` field of a song.
- """
- def __init__(self, song=None):
- if song:
- if song.lyrics.startswith(u'<?xml'):
- self.parse(song.lyrics)
- else:
- self.extract(song.lyrics)
- else:
- self.languages = []
-
- def parse(self, xml):
- """
- Parse XML from the ``lyrics`` field in the database, and set the list
- of verses from it.
-
- ``xml``
- The XML to parse.
- """
- try:
- self.languages = []
- song = objectify.fromstring(xml)
- for lyrics in song.lyrics:
- language = {
- u'language': lyrics.attrib[u'language'],
- u'verses': []
- }
- for verse in lyrics.verse:
- language[u'verses'].append({
- u'type': verse.attrib[u'type'],
- u'label': verse.attrib[u'label'],
- u'text': unicode(verse.text)
- })
- self.lyrics.append(language)
- return True
- except etree.XMLSyntaxError:
- return False
-
- def extract(self, text):
- """
- If the ``lyrics`` field in the database is not XML, this method is
- called and used to construct the verse structure similar to the output
- of the ``parse`` function.
-
- ``text``
- The text to pull verses out of.
- """
- 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
- self.languages[0][u'verses'].append({
- u'type': u'verse',
- u'label': unicode(counter),
- u'text': verse
- })
- return True
-
- def add_verse(self, type, label, text):
- """
- Add a verse to the list of verses.
-
- ``type``
- The type of list, one of "verse", "chorus", "bridge", "pre-chorus",
- "intro", "outtro".
-
- ``label``
- The number associated with this verse, like 1 or 2.
-
- ``text``
- The text of the verse.
- """
- self.verses.append({
- u'type': type,
- u'label': label,
- u'text': text
- })
-
- def export(self):
- """
- Build up the XML for the verse structure.
- """
- lyrics_output = u''
- for language in self.languages:
- verse_output = u''
- for verse in language[u'verses']:
- verse_output = verse_output + \
- u'<verse type="%s" label="%s"><![CDATA[%s]]></verse>' % \
- (verse[u'type'], verse[u'label'], verse[u'text'])
- lyrics_output = lyrics_output + \
- u'<lyrics language="%s">%s</lyrics>' % \
- (language[u'language'], verse_output)
- song_output = u'<?xml version="1.0" encoding="UTF-8"?>' + \
- u'<song version="1.0">%s</song>' % lyrics_output
- return song_output
-
-
-class OpenLyricsParser(object):
- """
- This class represents the converter for Song to/from OpenLyrics XML.
+class OpenLyrics(object):
+ """
+ This class represents the converter for OpenLyrics XML to/from a song.
+
+ As OpenLyrics has a rich set of different features, we cannot support them
+ all. The following features are supported by the :class:`OpenLyricsParser`::
+
+ *<authors>*
+ OpenLP does not support the attribute *type* and *lang*.
+
+ *<chord>*
+ This property is not supported.
+
+ *<comments>*
+ The *<comments>* property is fully supported. But comments in lyrics
+ are not supported.
+
+ *<copyright>*
+ This property is fully supported.
+
+ *<customVersion>*
+ This property is not supported.
+
+ *<key>*
+ This property is not supported.
+
+ *<keywords>*
+ This property is not supported.
+
+ *<lines>*
+ The attribute *part* is not supported.
+
+ *<publisher>*
+ This property is not supported.
+
+ *<songbooks>*
+ As OpenLP does only support one songbook, we cannot consider more than
+ one songbook.
+
+ *<tempo>*
+ This property is not supported.
+
+ *<themes>*
+ Topics, as they are called in OpenLP, are fully supported, whereby only
+ the topic text (e. g. Grace) is considered, but neither the *id* nor
+ *lang*.
+
+ *<transposition>*
+ This property is not supported.
+
+ *<variant>*
+ This property is not supported.
+
+ *<verse name="v1a" lang="he" translit="en">*
+ The attribute *translit* and *lang* are not supported.
+
+ *<verseOrder>*
+ OpenLP supports this property.
"""
def __init__(self, manager):
self.manager = manager
- def song_to_xml(self, song):
- """
- Convert the song to OpenLyrics Format
- """
- song_xml_parser = SongXMLParser(song.lyrics)
- verse_list = song_xml_parser.get_verses()
+ def song_to_xml(self, song, pretty_print=False):
+ """
+ Convert the song to OpenLyrics Format.
+ """
+ sxml = SongXML()
+ verse_list = sxml.get_verses(song.lyrics)
song_xml = objectify.fromstring(
u'<song version="0.7" createdIn="OpenLP 2.0"/>')
properties = etree.SubElement(song_xml, u'properties')
titles = etree.SubElement(properties, u'titles')
- self._add_text_to_element(u'title', titles, song.title)
+ self._add_text_to_element(u'title', titles, song.title.strip())
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)
+ self._add_text_to_element(
+ u'title', titles, song.alternate_title.strip())
+ 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' % (
@@ -282,78 +253,36 @@
element = self._add_text_to_element(u'lines', element)
for line in unicode(verse[1]).split(u'\n'):
self._add_text_to_element(u'line', element, line)
- return self._extract_xml(song_xml)
+ return self._extract_xml(song_xml, pretty_print)
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.
+
+ ``xml``
+ The XML to parse (unicode).
"""
- # 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 from xml.
+ 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':
- 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
- try:
- song.ccli_number = unicode(properties.ccliNo.text)
- except:
- 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.search_title = unicode(song.title)
- song.alternate_title = u''
- else:
- song.alternate_title = unicode(title.text)
- 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''
- for lyrics in song_xml.lyrics:
- for verse in song_xml.lyrics.verse:
- text = u''
- for line in verse.lines.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)
- search_text = search_text + text
- song.search_lyrics = search_text.lower()
- song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
- song.comments = u''
- song.song_number = u''
- # Process Authors
- try:
- for author in properties.authors.author:
- self._process_author(author.text, song)
- except:
- # No Author in XML so ignore
- pass
+ self._process_copyright(properties, song)
+ self._process_cclinumber(properties, song)
+ self._process_titles(properties, song)
+ # The verse order is processed with the lyrics!
+ self._process_lyrics(properties, song_xml.lyrics, song)
+ self._process_comments(properties, song)
+ self._process_authors(properties, song)
+ self._process_songbooks(properties, song)
+ self._process_topics(properties, song)
self.manager.save_object(song)
return song.id
@@ -367,33 +296,258 @@
parent.append(element)
return element
+ def _extract_xml(self, xml, pretty_print):
+ """
+ Extract our newly created XML song.
+ """
+ return etree.tostring(xml, encoding=u'UTF-8',
+ xml_declaration=True, pretty_print=pretty_print)
+
+ def _get(self, element, attribute):
+ """
+ This returns the element's attribute as unicode string.
+
+ ``element``
+ The element.
+
+ ``attribute``
+ The element's attribute (unicode).
+ """
+ if element.get(attribute) is not None:
+ return unicode(element.get(attribute))
+ return u''
+
+ def _text(self, element):
+ """
+ This returns the text of an element as unicode string.
+
+ ``element``
+ The element.
+ """
+ if element.text is not None:
+ return unicode(element.text)
+ return u''
+
+ def _process_authors(self, properties, song):
+ """
+ Adds the authors specified in the XML to the song.
+
+ ``properties``
+ The property object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song object.
+ """
+ authors = []
+ try:
+ for author in properties.authors.author:
+ display_name = self._text(author)
+ if display_name:
+ authors.append(display_name)
+ except AttributeError:
+ pass
+ if not authors:
+ # Add "Author unknown" (can be translated).
+ authors.append((unicode(translate('SongsPlugin.XML',
+ 'Author unknown'))))
+ for display_name in authors:
+ author = self.manager.get_object_filtered(Author,
+ Author.display_name == display_name)
+ if author is None:
+ # We need to create a new author, as the author does not exist.
+ author = Author.populate(display_name=display_name,
+ last_name=display_name.split(u' ')[-1],
+ first_name=u' '.join(display_name.split(u' ')[:-1]))
+ self.manager.save_object(author)
+ song.authors.append(author)
+
+ def _process_cclinumber(self, properties, song):
+ """
+ Adds the CCLI number to the song.
+
+ ``properties``
+ The property object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song object.
+ """
+ try:
+ song.ccli_number = self._text(properties.ccliNo)
+ except AttributeError:
+ song.ccli_number = u''
+
+ def _process_comments(self, properties, song):
+ """
+ Joins the comments specified in the XML and add it to the song.
+
+ ``properties``
+ The property object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song object.
+ """
+ try:
+ comments_list = []
+ for comment in properties.comments.comment:
+ commenttext = self._text(comment)
+ if commenttext:
+ comments_list.append(commenttext)
+ song.comments = u'\n'.join(comments_list)
+ except AttributeError:
+ song.comments = u''
+
+ def _process_copyright(self, properties, song):
+ """
+ Adds the copyright to the song.
+
+ ``properties``
+ The property object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song object.
+ """
+ try:
+ song.copyright = self._text(properties.copyright)
+ except AttributeError:
+ song.copyright = u''
+
+ def _process_lyrics(self, properties, lyrics, song):
+ """
+ Processes the verses and search_lyrics for the song.
+
+ ``properties``
+ The properties object (lxml.objectify.ObjectifiedElement).
+
+ ``lyrics``
+ The lyrics object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song object.
+ """
+ sxml = SongXML()
+ search_text = u''
+ temp_verse_order = []
+ for verse in lyrics.verse:
+ text = u''
+ for lines in verse.lines:
+ if text:
+ text += u'\n'
+ text += u'\n'.join([unicode(line) for line in lines.line])
+ verse_name = self._get(verse, u'name')
+ verse_type = unicode(VerseType.expand_string(verse_name[0]))[0]
+ verse_number = re.compile(u'[a-zA-Z]*').sub(u'', verse_name)
+ verse_part = re.compile(u'[0-9]*').sub(u'', verse_name[1:])
+ # OpenLyrics allows e. g. "c", but we need "c1".
+ if not verse_number:
+ verse_number = u'1'
+ temp_verse_order.append((verse_type, verse_number, verse_part))
+ 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
+ try:
+ song.verse_order = self._text(properties.verseOrder)
+ except AttributeError:
+ # We have to process the temp_verse_order, as the verseOrder
+ # property is not present.
+ previous_type = u''
+ previous_number = u''
+ previous_part = u''
+ verse_order = []
+ # Currently we do not support different "parts"!
+ for name in temp_verse_order:
+ if name[0] == previous_type:
+ if name[1] != previous_number:
+ verse_order.append(u''.join((name[0], name[1])))
+ else:
+ verse_order.append(u''.join((name[0], name[1])))
+ previous_type = name[0]
+ previous_number = name[1]
+ previous_part = name[2]
+ song.verse_order = u' '.join(verse_order)
+
+ def _process_songbooks(self, properties, song):
+ """
+ Adds the song book and song number specified in the XML to the song.
+
+ ``properties``
+ The property object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song object.
+ """
+ song.song_book_id = 0
+ song.song_number = u''
+ try:
+ for songbook in properties.songbooks.songbook:
+ bookname = self._get(songbook, u'name')
+ if bookname:
+ book = self.manager.get_object_filtered(Book,
+ Book.name == bookname)
+ if book is None:
+ # We need to create a book, because it does not exist.
+ book = Book.populate(name=bookname, publisher=u'')
+ self.manager.save_object(book)
+ song.song_book_id = book.id
+ try:
+ if self._get(songbook, u'entry'):
+ song.song_number = self._get(songbook, u'entry')
+ except AttributeError:
+ pass
+ # We does only support one song book, so take the first one.
+ break
+ except AttributeError:
+ pass
+
+ def _process_titles(self, properties, song):
+ """
+ Processes the titles specified in the song's XML.
+
+ ``properties``
+ The property object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song 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()
+
+ def _process_topics(self, properties, song):
+ """
+ Adds the topics to the song.
+
+ ``properties``
+ The property object (lxml.objectify.ObjectifiedElement).
+
+ ``song``
+ The song object.
+ """
+ try:
+ for topictext in properties.themes.theme:
+ topictext = self._text(topictext)
+ if topictext:
+ topic = self.manager.get_object_filtered(Topic,
+ Topic.name == topictext)
+ if topic is None:
+ # We need to create a topic, because it does not exist.
+ topic = Topic.populate(name=topictext)
+ self.manager.save_object(topic)
+ 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)
-
- def _extract_xml(self, xml):
- """
- Extract our newly created XML song.
- """
- return etree.tostring(xml, encoding=u'UTF-8',
- xml_declaration=True)
-
- def _process_author(self, name, song):
- """
- Find or create an Author from display_name.
- """
- name = unicode(name)
- 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)
\ No newline at end of file
=== modified file 'openlp/plugins/songs/songsplugin.py'
--- openlp/plugins/songs/songsplugin.py 2010-12-31 02:17:41 +0000
+++ openlp/plugins/songs/songsplugin.py 2011-01-09 19:37:54 +0000
@@ -31,7 +31,7 @@
from openlp.core.lib import Plugin, StringContent, build_icon, translate
from openlp.core.lib.db import Manager
-from openlp.plugins.songs.lib import SongMediaItem, SongsTab, SongXMLParser
+from openlp.plugins.songs.lib import SongMediaItem, SongsTab, SongXML
from openlp.plugins.songs.lib.db import init_schema, Song
from openlp.plugins.songs.lib.importer import SongFormat
@@ -153,7 +153,7 @@
song.search_title = self.whitespace.sub(u' ', song.title.lower() + \
u' ' + song.alternate_title.lower())
lyrics = u''
- verses = SongXMLParser(song.lyrics).get_verses()
+ verses = SongXML().get_verses(song.lyrics)
for verse in verses:
lyrics = lyrics + self.whitespace.sub(u' ', verse[1]) + u' '
song.search_lyrics = lyrics.lower()
Follow ups