2010-04-03 00:13:07 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
|
|
# --------------------------------------------------------------------------- #
|
2010-12-26 11:04:47 +00:00
|
|
|
# Copyright (c) 2008-2011 Raoul Snyman #
|
2011-05-26 16:25:54 +00:00
|
|
|
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
|
|
|
|
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
|
2011-05-26 17:11:22 +00:00
|
|
|
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
|
2011-06-12 16:02:52 +00:00
|
|
|
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
2011-06-12 15:41:01 +00:00
|
|
|
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
|
2010-04-03 00:13:07 +00:00
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
# 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 #
|
|
|
|
###############################################################################
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
2011-01-05 16:31:04 +00:00
|
|
|
The :mod:`xml` module provides the XML functionality.
|
2010-04-03 00:13:07 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
The basic XML for storing the lyrics in the song database looks like this::
|
2010-04-03 00:13:07 +00:00
|
|
|
|
2010-07-04 16:40:53 +00:00
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<song version="1.0">
|
2011-01-09 16:52:31 +00:00
|
|
|
<lyrics>
|
2011-03-18 18:43:26 +00:00
|
|
|
<verse type="c" label="1" lang="en">
|
2011-03-30 07:13:04 +00:00
|
|
|
<![CDATA[Chorus virtual slide 1[---]Chorus virtual slide 2]]>
|
2010-07-04 16:40:53 +00:00
|
|
|
</verse>
|
|
|
|
</lyrics>
|
|
|
|
</song>
|
2011-01-05 16:31:04 +00:00
|
|
|
|
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
The XML of an `OpenLyrics <http://openlyrics.info/>`_ song looks like this::
|
2011-01-05 16:31:04 +00:00
|
|
|
|
|
|
|
<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>
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
import logging
|
2010-11-28 13:39:51 +00:00
|
|
|
import re
|
2010-07-04 16:40:53 +00:00
|
|
|
|
|
|
|
from lxml import etree, objectify
|
2011-01-03 07:16:21 +00:00
|
|
|
|
2011-08-27 14:00:24 +00:00
|
|
|
from openlp.core.lib import FormattingTags
|
2011-03-14 18:59:59 +00:00
|
|
|
from openlp.plugins.songs.lib import clean_song, VerseType
|
2011-01-03 13:43:09 +00:00
|
|
|
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
|
2011-03-24 19:12:27 +00:00
|
|
|
from openlp.core.utils import get_application_version
|
2010-07-04 16:40:53 +00:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2011-09-06 13:22:17 +00:00
|
|
|
|
2011-01-09 16:52:31 +00:00
|
|
|
class SongXML(object):
|
2010-04-03 00:13:07 +00:00
|
|
|
"""
|
2011-01-09 16:52:31 +00:00
|
|
|
This class builds and parses the XML used to describe songs.
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
2011-01-09 16:52:31 +00:00
|
|
|
log.info(u'SongXML Loaded')
|
2010-04-03 00:13:07 +00:00
|
|
|
|
2011-01-09 16:52:31 +00:00
|
|
|
def __init__(self):
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
2011-01-09 16:52:31 +00:00
|
|
|
Set up the default variables.
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
|
|
|
self.song_xml = objectify.fromstring(u'<song version="1.0" />')
|
2011-01-09 16:52:31 +00:00
|
|
|
self.lyrics = etree.SubElement(self.song_xml, u'lyrics')
|
2010-07-04 16:40:53 +00:00
|
|
|
|
2011-01-19 19:22:43 +00:00
|
|
|
def add_verse_to_lyrics(self, type, number, content, lang=None):
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
2011-02-10 05:25:08 +00:00
|
|
|
Add a verse to the ``<lyrics>`` tag.
|
2010-07-04 16:40:53 +00:00
|
|
|
|
|
|
|
``type``
|
2011-03-17 20:00:19 +00:00
|
|
|
A string denoting the type of verse. Possible values are *v*,
|
|
|
|
*c*, *b*, *p*, *i*, *e* and *o*.
|
2011-02-09 19:55:53 +00:00
|
|
|
Any other type is **not** allowed, this also includes translated
|
|
|
|
types.
|
2010-07-04 16:40:53 +00:00
|
|
|
|
|
|
|
``number``
|
|
|
|
An integer denoting the number of the item, for example: verse 1.
|
|
|
|
|
|
|
|
``content``
|
|
|
|
The actual text of the verse to be stored.
|
2011-01-19 19:22:43 +00:00
|
|
|
|
|
|
|
``lang``
|
2011-01-19 19:57:08 +00:00
|
|
|
The verse's language code (ISO-639). This is not required, but
|
|
|
|
should be added if available.
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
2011-01-02 16:42:09 +00:00
|
|
|
verse = etree.Element(u'verse', type=unicode(type),
|
|
|
|
label=unicode(number))
|
2011-01-19 19:22:43 +00:00
|
|
|
if lang:
|
|
|
|
verse.set(u'lang', lang)
|
2010-07-04 16:40:53 +00:00
|
|
|
verse.text = etree.CDATA(content)
|
2010-07-05 21:23:39 +00:00
|
|
|
self.lyrics.append(verse)
|
2010-07-04 16:40:53 +00:00
|
|
|
|
|
|
|
def extract_xml(self):
|
|
|
|
"""
|
|
|
|
Extract our newly created XML song.
|
|
|
|
"""
|
2010-07-15 20:26:57 +00:00
|
|
|
return etree.tostring(self.song_xml, encoding=u'UTF-8',
|
2010-07-04 16:40:53 +00:00
|
|
|
xml_declaration=True)
|
|
|
|
|
2011-01-09 16:52:31 +00:00
|
|
|
def get_verses(self, xml):
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
2011-01-09 16:52:31 +00:00
|
|
|
Iterates through the verses in the XML and returns a list of verses
|
|
|
|
and their attributes.
|
2010-07-04 16:40:53 +00:00
|
|
|
|
|
|
|
``xml``
|
|
|
|
The XML of the song to be parsed.
|
2011-01-19 19:22:43 +00:00
|
|
|
|
|
|
|
The returned list has the following format::
|
|
|
|
|
2011-03-30 07:13:04 +00:00
|
|
|
[[{'type': 'v', 'label': '1'},
|
|
|
|
u"virtual slide 1[---]virtual slide 2"],
|
2011-03-17 20:00:19 +00:00
|
|
|
[{'lang': 'en', 'type': 'c', 'label': '1'}, u"English chorus"]]
|
2010-07-04 16:40:53 +00:00
|
|
|
"""
|
|
|
|
self.song_xml = None
|
2011-04-28 23:25:28 +00:00
|
|
|
verse_list = []
|
|
|
|
if not xml.startswith(u'<?xml') and not xml.startswith(u'<song'):
|
|
|
|
# This is an old style song, without XML. Let's handle it correctly
|
|
|
|
# by iterating through the verses, and then recreating the internal
|
|
|
|
# xml object as well.
|
|
|
|
self.song_xml = objectify.fromstring(u'<song version="1.0" />')
|
|
|
|
self.lyrics = etree.SubElement(self.song_xml, u'lyrics')
|
|
|
|
verses = xml.split(u'\n\n')
|
|
|
|
for count, verse in enumerate(verses):
|
|
|
|
verse_list.append([{u'type': u'v', u'label': unicode(count)},
|
|
|
|
unicode(verse)])
|
|
|
|
self.add_verse_to_lyrics(u'v', unicode(count), verse)
|
|
|
|
return verse_list
|
2011-04-29 06:28:09 +00:00
|
|
|
elif xml.startswith(u'<?xml'):
|
2010-07-05 11:39:48 +00:00
|
|
|
xml = xml[38:]
|
2010-07-04 16:40:53 +00:00
|
|
|
try:
|
2010-07-05 11:39:48 +00:00
|
|
|
self.song_xml = objectify.fromstring(xml)
|
2010-07-04 16:40:53 +00:00
|
|
|
except etree.XMLSyntaxError:
|
|
|
|
log.exception(u'Invalid xml %s', xml)
|
|
|
|
xml_iter = self.song_xml.getiterator()
|
|
|
|
for element in xml_iter:
|
|
|
|
if element.tag == u'verse':
|
|
|
|
if element.text is None:
|
|
|
|
element.text = u''
|
2010-07-05 11:39:48 +00:00
|
|
|
verse_list.append([element.attrib, unicode(element.text)])
|
2010-07-04 16:40:53 +00:00
|
|
|
return verse_list
|
|
|
|
|
|
|
|
def dump_xml(self):
|
|
|
|
"""
|
|
|
|
Debugging aid to dump XML so that we can see what we have.
|
|
|
|
"""
|
|
|
|
return etree.dump(self.song_xml)
|
|
|
|
|
|
|
|
|
2011-01-09 16:52:31 +00:00
|
|
|
class OpenLyrics(object):
|
2011-01-05 16:31:04 +00:00
|
|
|
"""
|
2011-01-11 16:49:53 +00:00
|
|
|
This class represents the converter for OpenLyrics XML (version 0.7)
|
|
|
|
to/from a song.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
|
|
|
As OpenLyrics has a rich set of different features, we cannot support them
|
2011-02-10 05:25:08 +00:00
|
|
|
all. The following features are supported by the :class:`OpenLyrics` class:
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<authors>``
|
2011-01-06 09:34:26 +00:00
|
|
|
OpenLP does not support the attribute *type* and *lang*.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<chord>``
|
2011-01-08 20:59:46 +00:00
|
|
|
This property is not supported.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<comments>``
|
|
|
|
The ``<comments>`` property is fully supported. But comments in lyrics
|
2011-01-05 16:31:04 +00:00
|
|
|
are not supported.
|
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<copyright>``
|
2011-01-05 16:31:04 +00:00
|
|
|
This property is fully supported.
|
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<customVersion>``
|
2011-01-05 16:31:04 +00:00
|
|
|
This property is not supported.
|
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<key>``
|
2011-01-08 20:59:46 +00:00
|
|
|
This property is not supported.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<keywords>``
|
2011-01-08 20:59:46 +00:00
|
|
|
This property is not supported.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<lines>``
|
2011-08-22 13:39:02 +00:00
|
|
|
The attribute *part* is not supported. The *break* attribute is
|
2011-08-20 17:06:48 +00:00
|
|
|
supported.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<publisher>``
|
2011-01-08 20:59:46 +00:00
|
|
|
This property is not supported.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<songbooks>``
|
2011-01-05 16:31:04 +00:00
|
|
|
As OpenLP does only support one songbook, we cannot consider more than
|
|
|
|
one songbook.
|
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<tempo>``
|
2011-01-08 20:59:46 +00:00
|
|
|
This property is not supported.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<themes>``
|
2011-01-05 16:31:04 +00:00
|
|
|
Topics, as they are called in OpenLP, are fully supported, whereby only
|
2011-01-06 09:34:26 +00:00
|
|
|
the topic text (e. g. Grace) is considered, but neither the *id* nor
|
|
|
|
*lang*.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<transposition>``
|
2011-01-05 16:31:04 +00:00
|
|
|
This property is not supported.
|
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<variant>``
|
2011-01-08 20:59:46 +00:00
|
|
|
This property is not supported.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<verse name="v1a" lang="he" translit="en">``
|
2011-01-24 19:27:59 +00:00
|
|
|
The attribute *translit* is not supported. Note, the attribute *lang* is
|
2011-08-20 17:06:48 +00:00
|
|
|
considered, but there is not further functionality implemented yet. The
|
|
|
|
following verse "types" are supported by OpenLP:
|
|
|
|
|
|
|
|
* v
|
|
|
|
* c
|
|
|
|
* b
|
|
|
|
* p
|
|
|
|
* i
|
|
|
|
* e
|
|
|
|
* o
|
|
|
|
|
|
|
|
The verse "types" stand for *Verse*, *Chorus*, *Bridge*, *Pre-Chorus*,
|
|
|
|
*Intro*, *Ending* and *Other*. Any numeric value is allowed after the
|
|
|
|
verse type. The complete verse name in OpenLP always consists of the
|
|
|
|
verse type and the verse number. If not number is present *1* is
|
|
|
|
assumed.
|
2011-08-20 17:39:38 +00:00
|
|
|
OpenLP will merge verses which are split up by appending a letter to the
|
|
|
|
verse name, such as *v1a*.
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-02-10 05:25:08 +00:00
|
|
|
``<verseOrder>``
|
2011-01-05 16:31:04 +00:00
|
|
|
OpenLP supports this property.
|
2011-02-10 05:25:08 +00:00
|
|
|
|
2011-01-05 16:31:04 +00:00
|
|
|
"""
|
2011-08-20 17:06:48 +00:00
|
|
|
IMPLEMENTED_VERSION = u'0.8'
|
2011-09-06 13:22:17 +00:00
|
|
|
|
2011-01-05 16:31:04 +00:00
|
|
|
def __init__(self, manager):
|
|
|
|
self.manager = manager
|
2011-09-07 20:54:53 +00:00
|
|
|
self.start_tags_regex = re.compile(r'\{\w+\}') # {abc}
|
|
|
|
self.end_tags_regex = re.compile(r'\{\/\w+\}') # {/abc}
|
2011-01-05 16:31:04 +00:00
|
|
|
|
2011-01-11 16:49:53 +00:00
|
|
|
def song_to_xml(self, song):
|
2011-01-09 16:52:31 +00:00
|
|
|
"""
|
|
|
|
Convert the song to OpenLyrics Format.
|
|
|
|
"""
|
|
|
|
sxml = SongXML()
|
2011-01-24 19:27:59 +00:00
|
|
|
song_xml = objectify.fromstring(u'<song/>')
|
|
|
|
# Append the necessary meta data to the song.
|
|
|
|
song_xml.set(u'xmlns', u'http://openlyrics.info/namespace/2009/song')
|
|
|
|
song_xml.set(u'version', OpenLyrics.IMPLEMENTED_VERSION)
|
2011-08-14 10:56:05 +00:00
|
|
|
application_name = u'OpenLP ' + get_application_version()[u'version']
|
|
|
|
song_xml.set(u'createdIn', application_name)
|
|
|
|
song_xml.set(u'modifiedIn', application_name)
|
2011-08-27 14:00:24 +00:00
|
|
|
# "Convert" 2011-08-27 11:49:15 to 2011-08-27T11:49:15.
|
|
|
|
song_xml.set(u'modifiedDate',
|
|
|
|
unicode(song.last_modified).replace(u' ', u'T'))
|
2011-01-09 16:52:31 +00:00
|
|
|
properties = etree.SubElement(song_xml, u'properties')
|
|
|
|
titles = etree.SubElement(properties, u'titles')
|
2011-03-14 18:59:59 +00:00
|
|
|
self._add_text_to_element(u'title', titles, song.title)
|
2011-01-09 16:52:31 +00:00
|
|
|
if song.alternate_title:
|
2011-03-14 18:59:59 +00:00
|
|
|
self._add_text_to_element(u'title', titles, song.alternate_title)
|
2011-01-09 16:52:31 +00:00
|
|
|
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(
|
2011-01-24 19:27:59 +00:00
|
|
|
u'verseOrder', properties, song.verse_order.lower())
|
2011-01-09 16:52:31 +00:00
|
|
|
if song.ccli_number:
|
|
|
|
self._add_text_to_element(u'ccliNo', properties, song.ccli_number)
|
|
|
|
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)
|
2011-02-10 17:42:13 +00:00
|
|
|
if song.song_number:
|
|
|
|
element.set(u'entry', song.song_number)
|
2011-01-09 16:52:31 +00:00
|
|
|
if song.topics:
|
|
|
|
themes = etree.SubElement(properties, u'themes')
|
|
|
|
for topic in song.topics:
|
|
|
|
self._add_text_to_element(u'theme', themes, topic.name)
|
2011-08-27 14:00:24 +00:00
|
|
|
# Process the formatting tags.
|
2011-09-06 13:14:57 +00:00
|
|
|
# have we any tags in song lyrics?
|
2011-09-07 20:54:53 +00:00
|
|
|
tags_element = None
|
|
|
|
match = re.search(u'\{/?\w+\}', song.lyrics, re.UNICODE)
|
2011-09-06 13:14:57 +00:00
|
|
|
if match:
|
|
|
|
# named 'formatting' - 'format' is built-in fuction in Python
|
|
|
|
formatting = etree.SubElement(song_xml, u'format')
|
2011-09-07 20:54:53 +00:00
|
|
|
tags_element = etree.SubElement(formatting, u'tags')
|
|
|
|
tags_element.set(u'application', u'OpenLP')
|
2011-03-27 13:39:43 +00:00
|
|
|
# Process the song's lyrics.
|
2011-01-09 16:52:31 +00:00
|
|
|
lyrics = etree.SubElement(song_xml, u'lyrics')
|
2011-03-27 13:39:43 +00:00
|
|
|
verse_list = sxml.get_verses(song.lyrics)
|
2011-01-09 16:52:31 +00:00
|
|
|
for verse in verse_list:
|
2011-03-27 13:39:43 +00:00
|
|
|
verse_tag = verse[0][u'type'][0].lower()
|
|
|
|
verse_number = verse[0][u'label']
|
2011-08-20 17:06:48 +00:00
|
|
|
verse_def = verse_tag + verse_number
|
|
|
|
verse_element = \
|
|
|
|
self._add_text_to_element(u'verse', lyrics, None, verse_def)
|
2011-09-06 13:22:17 +00:00
|
|
|
if u'lang' in verse[0]:
|
2011-08-20 17:06:48 +00:00
|
|
|
verse_element.set(u'lang', verse[0][u'lang'])
|
2011-03-29 18:23:01 +00:00
|
|
|
# Create a list with all "virtual" verses.
|
2011-03-30 07:13:04 +00:00
|
|
|
virtual_verses = verse[1].split(u'[---]')
|
2011-03-29 18:23:01 +00:00
|
|
|
for index, virtual_verse in enumerate(virtual_verses):
|
2011-08-20 17:06:48 +00:00
|
|
|
lines_element = \
|
|
|
|
self._add_text_to_element(u'lines', verse_element)
|
|
|
|
# Do not add the break attribute to the last lines element.
|
|
|
|
if index < len(virtual_verses) - 1:
|
|
|
|
lines_element.set(u'break', u'optional')
|
2011-03-29 18:23:01 +00:00
|
|
|
for line in virtual_verse.strip(u'\n').split(u'\n'):
|
2011-09-07 20:54:53 +00:00
|
|
|
# Process only lines containing formatting tags
|
|
|
|
if self.start_tags_regex.search(line):
|
|
|
|
# add formatting tags to text
|
|
|
|
self._add_line_with_tags_to_lines(lines_element, line,
|
|
|
|
tags_element)
|
|
|
|
else:
|
|
|
|
self._add_text_to_element(u'line', lines_element, line)
|
2011-01-11 16:49:53 +00:00
|
|
|
return self._extract_xml(song_xml)
|
2011-01-09 16:52:31 +00:00
|
|
|
|
2011-08-26 15:15:17 +00:00
|
|
|
def xml_to_song(self, xml, only_process_format_tags=False):
|
2010-11-27 15:25:00 +00:00
|
|
|
"""
|
2011-01-03 21:47:49 +00:00
|
|
|
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.
|
2011-01-06 08:13:45 +00:00
|
|
|
|
|
|
|
``xml``
|
|
|
|
The XML to parse (unicode).
|
2011-08-26 15:06:22 +00:00
|
|
|
|
2011-08-26 15:15:17 +00:00
|
|
|
``only_process_format_tags``
|
|
|
|
Switch to skip processing the whole song and to prevent storing the
|
|
|
|
songs to the database. Defaults to ``False``.
|
2010-11-27 15:25:00 +00:00
|
|
|
"""
|
2011-01-03 07:16:21 +00:00
|
|
|
# No xml get out of here.
|
2010-11-29 16:02:41 +00:00
|
|
|
if not xml:
|
2011-01-11 16:49:53 +00:00
|
|
|
return None
|
2010-11-27 20:51:54 +00:00
|
|
|
if xml[:5] == u'<?xml':
|
|
|
|
xml = xml[38:]
|
2010-11-28 13:39:51 +00:00
|
|
|
song_xml = objectify.fromstring(xml)
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(song_xml, u'properties'):
|
2011-01-20 05:42:27 +00:00
|
|
|
properties = song_xml.properties
|
2011-02-20 17:34:57 +00:00
|
|
|
else:
|
2011-01-20 05:42:27 +00:00
|
|
|
return None
|
2011-08-27 14:00:24 +00:00
|
|
|
if float(song_xml.get(u'version')) > 0.6:
|
|
|
|
self._process_formatting_tags(song_xml, only_process_format_tags)
|
|
|
|
if only_process_format_tags:
|
|
|
|
return
|
2011-01-20 05:42:27 +00:00
|
|
|
song = Song()
|
2011-08-27 14:00:24 +00:00
|
|
|
# Values will be set when cleaning the song.
|
|
|
|
song.search_lyrics = u''
|
|
|
|
song.verse_order = u''
|
|
|
|
song.search_title = u''
|
|
|
|
self._process_copyright(properties, song)
|
|
|
|
self._process_cclinumber(properties, song)
|
|
|
|
self._process_titles(properties, song)
|
2011-01-06 08:13:45 +00:00
|
|
|
# The verse order is processed with the lyrics!
|
2011-08-20 17:15:31 +00:00
|
|
|
self._process_lyrics(properties, song_xml, song)
|
2011-08-27 14:00:24 +00:00
|
|
|
self._process_comments(properties, song)
|
|
|
|
self._process_authors(properties, song)
|
|
|
|
self._process_songbooks(properties, song)
|
|
|
|
self._process_topics(properties, song)
|
|
|
|
clean_song(self.manager, song)
|
|
|
|
self.manager.save_object(song)
|
2011-08-28 20:51:44 +00:00
|
|
|
return song
|
2010-11-27 15:25:00 +00:00
|
|
|
|
2011-01-09 16:52:31 +00:00
|
|
|
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
|
|
|
|
|
2011-09-07 20:54:53 +00:00
|
|
|
def _add_line_with_tags_to_lines(self, parent, text, tags_element):
|
|
|
|
start_tags = self.start_tags_regex.findall(text)
|
|
|
|
end_tags = self.end_tags_regex.findall(text)
|
|
|
|
# replace start tags with xml syntax
|
|
|
|
for t in start_tags:
|
|
|
|
text = text.replace(t, u'<tag name="%s">' % t[1:-1])
|
|
|
|
# replace end tags
|
|
|
|
for t in end_tags:
|
|
|
|
text = text.replace(t, u'</tag>')
|
|
|
|
text = u'<line>' + text + u'</line>'
|
|
|
|
element = etree.XML(text)
|
|
|
|
parent.append(element)
|
|
|
|
|
2011-01-11 16:49:53 +00:00
|
|
|
def _extract_xml(self, xml):
|
2011-01-09 16:52:31 +00:00
|
|
|
"""
|
|
|
|
Extract our newly created XML song.
|
|
|
|
"""
|
|
|
|
return etree.tostring(xml, encoding=u'UTF-8',
|
2011-01-11 16:49:53 +00:00
|
|
|
xml_declaration=True)
|
2011-01-09 16:52:31 +00:00
|
|
|
|
2011-01-03 21:47:49 +00:00
|
|
|
def _text(self, element):
|
|
|
|
"""
|
2011-01-06 08:13:45 +00:00
|
|
|
This returns the text of an element as unicode string.
|
2011-01-03 21:47:49 +00:00
|
|
|
|
|
|
|
``element``
|
|
|
|
The element.
|
|
|
|
"""
|
|
|
|
if element.text is not None:
|
|
|
|
return unicode(element.text)
|
|
|
|
return u''
|
|
|
|
|
2011-01-05 16:31:04 +00:00
|
|
|
def _process_authors(self, properties, song):
|
2010-11-27 15:25:00 +00:00
|
|
|
"""
|
2011-01-06 08:13:45 +00:00
|
|
|
Adds the authors specified in the XML to the song.
|
|
|
|
|
|
|
|
``properties``
|
|
|
|
The property object (lxml.objectify.ObjectifiedElement).
|
|
|
|
|
|
|
|
``song``
|
|
|
|
The song object.
|
2010-11-27 15:25:00 +00:00
|
|
|
"""
|
2011-01-05 16:31:04 +00:00
|
|
|
authors = []
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(properties, u'authors'):
|
2011-01-05 16:31:04 +00:00
|
|
|
for author in properties.authors.author:
|
|
|
|
display_name = self._text(author)
|
|
|
|
if display_name:
|
|
|
|
authors.append(display_name)
|
|
|
|
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]))
|
2011-02-25 01:03:25 +00:00
|
|
|
song.authors.append(author)
|
2010-11-27 15:25:00 +00:00
|
|
|
|
2011-01-06 08:13:45 +00:00
|
|
|
def _process_cclinumber(self, properties, song):
|
|
|
|
"""
|
|
|
|
Adds the CCLI number to the song.
|
|
|
|
|
|
|
|
``properties``
|
|
|
|
The property object (lxml.objectify.ObjectifiedElement).
|
|
|
|
|
|
|
|
``song``
|
|
|
|
The song object.
|
|
|
|
"""
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(properties, u'ccliNo'):
|
2011-01-06 08:13:45 +00:00
|
|
|
song.ccli_number = self._text(properties.ccliNo)
|
|
|
|
|
2011-01-05 16:31:04 +00:00
|
|
|
def _process_comments(self, properties, song):
|
2010-11-27 15:25:00 +00:00
|
|
|
"""
|
2011-01-06 08:13:45 +00:00
|
|
|
Joins the comments specified in the XML and add it to the song.
|
|
|
|
|
|
|
|
``properties``
|
|
|
|
The property object (lxml.objectify.ObjectifiedElement).
|
|
|
|
|
|
|
|
``song``
|
|
|
|
The song object.
|
2010-11-27 15:25:00 +00:00
|
|
|
"""
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(properties, u'comments'):
|
2011-02-24 05:47:38 +00:00
|
|
|
comments_list = []
|
2011-01-06 08:13:45 +00:00
|
|
|
for comment in properties.comments.comment:
|
|
|
|
commenttext = self._text(comment)
|
|
|
|
if commenttext:
|
|
|
|
comments_list.append(commenttext)
|
|
|
|
song.comments = u'\n'.join(comments_list)
|
2010-11-28 13:39:51 +00:00
|
|
|
|
2011-01-06 08:13:45 +00:00
|
|
|
def _process_copyright(self, properties, song):
|
2010-11-28 13:39:51 +00:00
|
|
|
"""
|
2011-01-06 08:13:45 +00:00
|
|
|
Adds the copyright to the song.
|
|
|
|
|
|
|
|
``properties``
|
|
|
|
The property object (lxml.objectify.ObjectifiedElement).
|
|
|
|
|
|
|
|
``song``
|
|
|
|
The song object.
|
|
|
|
"""
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(properties, u'copyright'):
|
2011-01-06 08:13:45 +00:00
|
|
|
song.copyright = self._text(properties.copyright)
|
|
|
|
|
2011-08-27 14:00:24 +00:00
|
|
|
def _process_formatting_tags(self, song_xml, temporary):
|
|
|
|
"""
|
|
|
|
Process the formatting tags from the song and either add missing tags
|
|
|
|
temporary or permanently to the formatting tag list.
|
|
|
|
"""
|
|
|
|
if not hasattr(song_xml, u'format'):
|
|
|
|
return
|
|
|
|
found_tags = []
|
|
|
|
for tag in song_xml.format.tags.getchildren():
|
|
|
|
name = tag.get(u'name')
|
|
|
|
if name is None:
|
|
|
|
continue
|
|
|
|
openlp_tag = {
|
|
|
|
u'desc': name,
|
|
|
|
u'start tag': u'{%s}' % name[:5],
|
|
|
|
u'end tag': u'{/%s}' % name[:5],
|
|
|
|
u'start html': tag.open.text,
|
|
|
|
u'end html': tag.close.text,
|
|
|
|
u'protected': False,
|
|
|
|
u'temporary': temporary
|
|
|
|
}
|
|
|
|
found_tags.append(openlp_tag)
|
|
|
|
existing_tag_ids = [tag[u'start tag']
|
|
|
|
for tag in FormattingTags.get_html_tags()]
|
|
|
|
FormattingTags.add_html_tags([tag for tag in found_tags
|
|
|
|
if tag[u'start tag'] not in existing_tag_ids], True)
|
|
|
|
|
2011-08-20 17:15:31 +00:00
|
|
|
def _process_lyrics(self, properties, song_xml, song_obj):
|
2011-01-06 08:13:45 +00:00
|
|
|
"""
|
|
|
|
Processes the verses and search_lyrics for the song.
|
|
|
|
|
|
|
|
``properties``
|
|
|
|
The properties object (lxml.objectify.ObjectifiedElement).
|
|
|
|
|
2011-08-20 17:15:31 +00:00
|
|
|
``song_xml``
|
|
|
|
The objectified song (lxml.objectify.ObjectifiedElement).
|
2011-01-06 08:13:45 +00:00
|
|
|
|
2011-08-20 17:15:31 +00:00
|
|
|
``song_obj``
|
2011-01-06 08:13:45 +00:00
|
|
|
The song object.
|
2011-01-05 16:31:04 +00:00
|
|
|
"""
|
2011-01-09 16:52:31 +00:00
|
|
|
sxml = SongXML()
|
2011-03-27 13:39:43 +00:00
|
|
|
verses = {}
|
2011-03-30 07:13:04 +00:00
|
|
|
verse_def_list = []
|
2011-08-20 17:15:31 +00:00
|
|
|
lyrics = song_xml.lyrics
|
2011-08-22 13:39:02 +00:00
|
|
|
# Loop over the "verse" elements.
|
2011-01-06 08:13:45 +00:00
|
|
|
for verse in lyrics.verse:
|
2011-01-05 16:31:04 +00:00
|
|
|
text = u''
|
2011-08-22 13:39:02 +00:00
|
|
|
# Loop over the "lines" elements.
|
2011-01-05 16:31:04 +00:00
|
|
|
for lines in verse.lines:
|
|
|
|
if text:
|
|
|
|
text += u'\n'
|
2011-08-22 13:39:02 +00:00
|
|
|
# Loop over the "line" elements removing chords.
|
|
|
|
for line in lines.line:
|
|
|
|
if text:
|
|
|
|
text += u'\n'
|
|
|
|
text += u''.join(map(unicode, line.itertext()))
|
|
|
|
# Add a virtual split to the verse text.
|
2011-08-20 17:15:31 +00:00
|
|
|
if lines.get(u'break') is not None:
|
2011-08-20 17:06:48 +00:00
|
|
|
text += u'\n[---]'
|
2011-08-20 17:15:31 +00:00
|
|
|
verse_def = verse.get(u'name', u' ').lower()
|
2011-03-17 20:38:05 +00:00
|
|
|
if verse_def[0] in VerseType.Tags:
|
|
|
|
verse_tag = verse_def[0]
|
2011-03-17 20:23:26 +00:00
|
|
|
else:
|
2011-03-17 20:38:05 +00:00
|
|
|
verse_tag = VerseType.Tags[VerseType.Other]
|
|
|
|
verse_number = re.compile(u'[a-zA-Z]*').sub(u'', verse_def)
|
2011-03-13 14:39:55 +00:00
|
|
|
# OpenLyrics allows e. g. "c", but we need "c1". However, this does
|
|
|
|
# not correct the verse order.
|
|
|
|
if not verse_number:
|
|
|
|
verse_number = u'1'
|
2011-08-20 17:15:31 +00:00
|
|
|
lang = verse.get(u'lang')
|
2011-08-20 17:39:38 +00:00
|
|
|
# In OpenLP 1.9.6 we used v1a, v1b ... to represent visual slide
|
|
|
|
# breaks. In OpenLyrics 0.7 an attribute has been added.
|
|
|
|
if song_xml.get(u'modifiedIn') in (u'1.9.6', u'OpenLP 1.9.6') and \
|
|
|
|
song_xml.get(u'version') == u'0.7' and \
|
2011-09-06 13:22:17 +00:00
|
|
|
(verse_tag, verse_number, lang) in verses:
|
2011-03-30 07:13:04 +00:00
|
|
|
verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text
|
2011-08-20 17:39:38 +00:00
|
|
|
# Merge v1a, v1b, .... to v1.
|
2011-09-06 13:22:17 +00:00
|
|
|
elif (verse_tag, verse_number, lang) in verses:
|
2011-08-20 17:39:38 +00:00
|
|
|
verses[(verse_tag, verse_number, lang)] += u'\n' + text
|
2011-03-27 13:39:43 +00:00
|
|
|
else:
|
|
|
|
verses[(verse_tag, verse_number, lang)] = text
|
2011-03-30 07:13:04 +00:00
|
|
|
verse_def_list.append((verse_tag, verse_number, lang))
|
|
|
|
# We have to use a list to keep the order, as dicts are not sorted.
|
|
|
|
for verse in verse_def_list:
|
2011-03-27 13:39:43 +00:00
|
|
|
sxml.add_verse_to_lyrics(
|
|
|
|
verse[0], verse[1], verses[verse], verse[2])
|
2011-08-20 17:39:38 +00:00
|
|
|
song_obj.lyrics = unicode(sxml.extract_xml(), u'utf-8')
|
2011-01-06 08:13:45 +00:00
|
|
|
# Process verse order
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(properties, u'verseOrder'):
|
2011-08-20 17:39:38 +00:00
|
|
|
song_obj.verse_order = self._text(properties.verseOrder)
|
2011-01-03 07:16:21 +00:00
|
|
|
|
2011-01-05 16:31:04 +00:00
|
|
|
def _process_songbooks(self, properties, song):
|
2011-01-03 07:16:21 +00:00
|
|
|
"""
|
2011-01-06 08:13:45 +00:00
|
|
|
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.
|
2011-01-05 16:31:04 +00:00
|
|
|
"""
|
2011-05-30 11:17:23 +00:00
|
|
|
song.song_book_id = None
|
2011-01-05 16:31:04 +00:00
|
|
|
song.song_number = u''
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(properties, u'songbooks'):
|
2011-01-05 16:31:04 +00:00
|
|
|
for songbook in properties.songbooks.songbook:
|
2011-08-20 17:15:31 +00:00
|
|
|
bookname = songbook.get(u'name', u'')
|
2011-01-05 16:31:04 +00:00
|
|
|
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
|
2011-08-20 17:15:31 +00:00
|
|
|
song.song_number = songbook.get(u'entry', u'')
|
2011-01-11 16:49:53 +00:00
|
|
|
# We only support one song book, so take the first one.
|
2011-01-05 16:31:04 +00:00
|
|
|
break
|
2011-01-03 07:16:21 +00:00
|
|
|
|
2011-01-05 16:31:04 +00:00
|
|
|
def _process_titles(self, properties, song):
|
|
|
|
"""
|
2011-01-06 08:13:45 +00:00
|
|
|
Processes the titles specified in the song's XML.
|
|
|
|
|
|
|
|
``properties``
|
|
|
|
The property object (lxml.objectify.ObjectifiedElement).
|
|
|
|
|
|
|
|
``song``
|
|
|
|
The song object.
|
2011-01-03 07:16:21 +00:00
|
|
|
"""
|
2011-01-05 16:31:04 +00:00
|
|
|
for title in properties.titles.title:
|
|
|
|
if not song.title:
|
|
|
|
song.title = self._text(title)
|
|
|
|
song.alternate_title = u''
|
|
|
|
else:
|
|
|
|
song.alternate_title = self._text(title)
|
2011-01-03 13:43:09 +00:00
|
|
|
|
2011-01-05 16:31:04 +00:00
|
|
|
def _process_topics(self, properties, song):
|
2011-01-03 13:43:09 +00:00
|
|
|
"""
|
2011-01-06 08:13:45 +00:00
|
|
|
Adds the topics to the song.
|
|
|
|
|
|
|
|
``properties``
|
|
|
|
The property object (lxml.objectify.ObjectifiedElement).
|
|
|
|
|
|
|
|
``song``
|
|
|
|
The song object.
|
2011-01-05 16:31:04 +00:00
|
|
|
"""
|
2011-02-20 17:34:57 +00:00
|
|
|
if hasattr(properties, u'themes'):
|
2011-01-05 16:31:04 +00:00
|
|
|
for topictext in properties.themes.theme:
|
|
|
|
topictext = self._text(topictext)
|
2011-01-06 08:13:45 +00:00
|
|
|
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)
|
2011-01-09 16:52:31 +00:00
|
|
|
|
|
|
|
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)
|