- 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.)

bzr-revno: 1203
This commit is contained in:
Andreas Preikschat 2011-01-10 12:21:39 +02:00 committed by Raoul Snyman
commit 98f0b30014
13 changed files with 523 additions and 297 deletions

View File

@ -35,11 +35,11 @@ class HideMode(object):
``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.

View File

@ -234,7 +234,7 @@ class Ui_BibleImportWizard(object):
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

View File

@ -54,7 +54,7 @@ class Ui_AuthorsDialog(object):
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)

View File

@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
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 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
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 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def processLyrics(self):
log.debug(u'processLyrics')
try:
sxml = SongXMLBuilder()
sxml = SongXML()
text = u''
multiple = []
for i in range(0, self.verseListWidget.rowCount()):

View File

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

View File

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

View File

@ -175,6 +175,6 @@ def retrieve_windows_encoding(recommendation=None):
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

View File

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

View File

@ -35,7 +35,7 @@ from openlp.core.lib import MediaManagerItem, BaseListWithDnD, Receiver, \
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 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem',
'You must select an item to delete.')):
items = self.listView.selectedIndexes()
ans = QtGui.QMessageBox.question(self,
if QtGui.QMessageBox.question(self,
translate('SongsPlugin.MediaItem', 'Delete Song(s)?'),
translate('SongsPlugin.MediaItem',
'Are you sure you want to delete the %n selected song(s)?', '',
QtCore.QCoreApplication.CodecForTr, len(items)),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok|
QtGui.QMessageBox.Cancel),
QtGui.QMessageBox.Ok)
if ans == QtGui.QMessageBox.Cancel:
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok |
QtGui.QMessageBox.Cancel),
QtGui.QMessageBox.Ok) == QtGui.QMessageBox.Cancel:
return
for item in items:
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
@ -362,8 +361,7 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
service_item.audit = [
song.title, author_audit, song.copyright, unicode(song.ccli_number)
]
service_item.data_string = {u'title':song.search_title,
u'authors':author_list}
service_item.data_string = {u'title': song.search_title,
u'authors': author_list}
service_item.xml_version = self.openLyrics.song_to_xml(song)
return True
@ -416,8 +414,8 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
if u'' in author_list:
author_list.remove(u'')
editId = 0
uuid = item._uuid
add_song = True
if search_results:
for song in search_results:
@ -453,7 +450,7 @@ class SongMediaItem(MediaManagerItem):
# Update service with correct song id.
if editId != 0:
Receiver.send_message(u'service_item_update',
u'%s:%s' %(editId, uuid))
u'%s:%s' % (editId, item._uuid))
def collateSongTitles(self, song_1, song_2):
"""

View File

@ -0,0 +1,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

View File

@ -31,7 +31,7 @@ from PyQt4 import QtCore
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 @@ class SongImport(QtCore.QObject):
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':

View File

@ -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):
class SongXML(object):
"""
This class builds the XML used to describe songs.
This class builds and parses the XML used to describe songs.
"""
log.info(u'SongXMLBuilder Loaded')
log.info(u'SongXML Loaded')
def __init__(self, song_language=None):
def __init__(self):
"""
Set up the song builder.
``song_language``
The language used in this song
Set up the default variables.
"""
lang = u'en'
if song_language:
lang = song_language
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 @@ class SongXMLBuilder(object):
``content``
The actual text of the verse to be stored.
"""
verse = etree.Element(u'verse', type = unicode(type),
label = unicode(number))
verse = etree.Element(u'verse', type=unicode(type),
label=unicode(number))
verse.text = etree.CDATA(content)
self.lyrics.append(verse)
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 @@ class SongXMLBuilder(object):
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 @@ class SongXMLParser(object):
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 @@ class SongXMLParser(object):
return etree.dump(self.song_xml)
class LyricsXML(object):
class OpenLyrics(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 = []
This class represents the converter for OpenLyrics XML to/from a song.
def parse(self, xml):
"""
Parse XML from the ``lyrics`` field in the database, and set the list
of verses from it.
As OpenLyrics has a rich set of different features, we cannot support them
all. The following features are supported by the :class:`OpenLyricsParser`::
``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
*<authors>*
OpenLP does not support the attribute *type* and *lang*.
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.
*<chord>*
This property is not supported.
``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
*<comments>*
The *<comments>* property is fully supported. But comments in lyrics
are not supported.
def add_verse(self, type, label, text):
"""
Add a verse to the list of verses.
*<copyright>*
This property is fully supported.
``type``
The type of list, one of "verse", "chorus", "bridge", "pre-chorus",
"intro", "outtro".
*<customVersion>*
This property is not supported.
``label``
The number associated with this verse, like 1 or 2.
*<key>*
This property is not supported.
``text``
The text of the verse.
"""
self.verses.append({
u'type': type,
u'label': label,
u'text': text
})
*<keywords>*
This property is not supported.
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
*<lines>*
The attribute *part* is not supported.
*<publisher>*
This property is not supported.
class OpenLyricsParser(object):
"""
This class represents the converter for Song to/from OpenLyrics XML.
*<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):
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()
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 @@ class OpenLyricsParser(object):
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 @@ class OpenLyricsParser(object):
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)

View File

@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
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 @@ class SongsPlugin(Plugin):
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()