openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #05436
[Merge] lp:~googol-hush/openlp/OpenLyrics into lp:openlp
Andreas Preikschat has proposed merging lp:~googol-hush/openlp/OpenLyrics into lp:openlp.
Requested reviews:
OpenLP Core (openlp-core)
For more details, see:
https://code.launchpad.net/~googol-hush/openlp/OpenLyrics/+merge/45619
- 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/45619
Your team OpenLP Core is requested to review the proposed merge of lp:~googol-hush/openlp/OpenLyrics into lp:openlp.
=== 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-08 20:54:57 +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-08 20:54:57 +0000
@@ -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-08 20:54:57 +0000
@@ -175,6 +175,7 @@
return None
return filter(lambda item: item[1] == choice[0], encodings)[0][0]
-from xml import LyricsXML, SongXMLBuilder, SongXMLParser, OpenLyricsParser
+from xml import OpenLyricsBuilder, OpenLyricsParser, SongXMLBuilder, \
+ SongXMLParser
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-08 20:54:57 +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-05 16:50:28 +0000
+++ openlp/plugins/songs/lib/mediaitem.py 2011-01-08 20:54:57 +0000
@@ -35,7 +35,8 @@
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 OpenLyricsBuilder, OpenLyricsParser, \
+ SongXMLParser
from openlp.plugins.songs.lib.db import Author, Song
from openlp.core.lib.searchedit import SearchEdit
@@ -58,7 +59,8 @@
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.openLyricsParser = OpenLyricsParser(self.parent.manager)
+ self.openLyricsBuilder = OpenLyricsBuilder(self.parent.manager)
self.singleServiceItem = False
self.song_maintenance_form = SongMaintenanceForm(
self.parent.manager, self)
@@ -312,15 +314,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]
@@ -394,9 +395,9 @@
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.xml_version = self.openLyrics.song_to_xml(song)
+ service_item.data_string = {u'title': song.search_title,
+ u'authors': author_list}
+ service_item.xml_version = self.openLyricsBuilder.song_to_xml(song)
return True
def serviceLoad(self, item):
@@ -407,7 +408,7 @@
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
@@ -416,7 +417,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:
@@ -439,11 +439,11 @@
break
if add_song:
if self.addSongFromService:
- editId = self.openLyrics.xml_to_song(item.xml_version)
+ editId = self.openLyricsParser.xml_to_song(item.xml_version)
# 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-08 20:54:57 +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 OpenLyricsParser
+
+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.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
+ 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.openLyricsParser.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/xml.py'
--- openlp/plugins/songs/lib/xml.py 2010-12-26 11:04:47 +0000
+++ openlp/plugins/songs/lib/xml.py 2011-01-08 20:54:57 +0000
@@ -24,9 +24,9 @@
# 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">
@@ -36,14 +36,38 @@
</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__)
@@ -80,8 +104,8 @@
``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)
@@ -142,117 +166,115 @@
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 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': []}]
+# for counter, verse in enumerate(verses):
+# 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 OpenLyricsBuilder(object):
+ """
+ This class represents the converter for song to OpenLyrics XML.
"""
def __init__(self, manager):
self.manager = manager
- def song_to_xml(self, song):
+ def song_to_xml(self, song, pretty_print=False):
"""
- 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 +285,33 @@
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' % (
@@ -282,118 +321,364 @@
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 _add_text_to_element(self, tag, parent, text=None, label=None):
+ if label:
+ element = etree.Element(tag, name=unicode(label))
+ else:
+ element = etree.Element(tag)
+ if text:
+ element.text = unicode(text)
+ 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 _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)
+
+
+class OpenLyricsParser(object):
+ """
+ This class represents the converter for OpenLyrics XML to 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 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
+ 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':
+ 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
+
+ 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''
- 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:
+
+ 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 = SongXMLBuilder()
+ 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
- if theme_name:
- song.theme_name = theme_name
- else:
- song.theme_name = u''
- # Process Titles
+
+ 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 = 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''
- 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
+
+ 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 author in properties.authors.author:
- self._process_author(author.text, song)
- except:
- # No Author in XML so ignore
+ 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
- self.manager.save_object(song)
- return song.id
-
- def _add_text_to_element(self, tag, parent, text=None, label=None):
- if label:
- element = etree.Element(tag, name=unicode(label))
- else:
- element = etree.Element(tag)
- if text:
- element.text = unicode(text)
- parent.append(element)
- return element
-
- 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