diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py
index ae9d5c1cf..ea5547f27 100644
--- a/openlp/core/lib/formattingtags.py
+++ b/openlp/core/lib/formattingtags.py
@@ -27,9 +27,13 @@
"""
Provide HTML Tag management and Formatting Tag access class
"""
+import cPickle
+
+from PyQt4 import QtCore
from openlp.core.lib import translate
+
class FormattingTags(object):
"""
Static Class to HTML Tags to be access around the code the list is managed
@@ -42,6 +46,8 @@ class FormattingTags(object):
"""
Provide access to the html_expands list.
"""
+ # Load user defined tags otherwise user defined tags are not present.
+ FormattingTags.load_tags()
return FormattingTags.html_expands
@staticmethod
@@ -49,6 +55,8 @@ class FormattingTags(object):
"""
Resets the html_expands list.
"""
+ temporary_tags = [tag for tag in FormattingTags.html_expands
+ if tag.get(u'temporary')]
FormattingTags.html_expands = []
base_tags = []
# Append the base tags.
@@ -56,75 +64,160 @@ class FormattingTags(object):
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Red'),
u'start tag': u'{r}',
u'start html': u'',
- u'end tag': u'{/r}', u'end html': u'', u'protected': True})
- base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Black'),
+ u'end tag': u'{/r}', u'end html': u'', u'protected': True,
+ u'temporary': False})
+ base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Black'),
u'start tag': u'{b}',
u'start html': u'',
- u'end tag': u'{/b}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/b}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Blue'),
u'start tag': u'{bl}',
u'start html': u'',
- u'end tag': u'{/bl}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/bl}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Yellow'),
u'start tag': u'{y}',
u'start html': u'',
- u'end tag': u'{/y}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/y}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Green'),
u'start tag': u'{g}',
u'start html': u'',
- u'end tag': u'{/g}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/g}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Pink'),
u'start tag': u'{pk}',
u'start html': u'',
- u'end tag': u'{/pk}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/pk}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Orange'),
u'start tag': u'{o}',
u'start html': u'',
- u'end tag': u'{/o}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/o}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Purple'),
u'start tag': u'{pp}',
u'start html': u'',
- u'end tag': u'{/pp}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/pp}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'White'),
u'start tag': u'{w}',
u'start html': u'',
- u'end tag': u'{/w}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/w}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({
u'desc': translate('OpenLP.FormattingTags', 'Superscript'),
u'start tag': u'{su}', u'start html': u'',
- u'end tag': u'{/su}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/su}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({
u'desc': translate('OpenLP.FormattingTags', 'Subscript'),
u'start tag': u'{sb}', u'start html': u'',
- u'end tag': u'{/sb}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/sb}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({
u'desc': translate('OpenLP.FormattingTags', 'Paragraph'),
u'start tag': u'{p}', u'start html': u'
', u'end tag': u'{/p}',
- u'end html': u'
', u'protected': True})
+ u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Bold'),
u'start tag': u'{st}', u'start html': u'',
u'end tag': u'{/st}', u'end html': u'',
- u'protected': True})
+ u'protected': True, u'temporary': False})
base_tags.append({
u'desc': translate('OpenLP.FormattingTags', 'Italics'),
u'start tag': u'{it}', u'start html': u'', u'end tag': u'{/it}',
- u'end html': u'', u'protected': True})
+ u'end html': u'', u'protected': True, u'temporary': False})
base_tags.append({
u'desc': translate('OpenLP.FormattingTags', 'Underline'),
u'start tag': u'{u}',
u'start html': u'',
- u'end tag': u'{/u}', u'end html': u'', u'protected': True})
+ u'end tag': u'{/u}', u'end html': u'', u'protected': True,
+ u'temporary': False})
base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Break'),
u'start tag': u'{br}', u'start html': u'
', u'end tag': u'',
- u'end html': u'', u'protected': True})
+ u'end html': u'', u'protected': True, u'temporary': False})
FormattingTags.add_html_tags(base_tags)
+ FormattingTags.add_html_tags(temporary_tags)
@staticmethod
- def add_html_tags(tags):
+ def save_html_tags():
"""
- Add a list of tags to the list
+ Saves all formatting tags except protected ones.
+ """
+ tags = []
+ for tag in FormattingTags.html_expands:
+ if not tag[u'protected'] and not tag.get(u'temporary'):
+ tags.append(tag)
+ # Remove key 'temporary' from tags. It is not needed to be saved.
+ for tag in tags:
+ if u'temporary' in tag:
+ del tag[u'temporary']
+ # Formatting Tags were also known as display tags.
+ QtCore.QSettings().setValue(u'displayTags/html_tags',
+ QtCore.QVariant(cPickle.dumps(tags) if tags else u''))
+
+ @staticmethod
+ def load_tags():
+ """
+ Load the Tags from store so can be used in the system or used to
+ update the display. If Cancel was selected this is needed to reset the
+ dsiplay to the correct version.
+ """
+ # Initial Load of the Tags
+ FormattingTags.reset_html_tags()
+ # Formatting Tags were also known as display tags.
+ user_expands = QtCore.QSettings().value(u'displayTags/html_tags',
+ QtCore.QVariant(u'')).toString()
+ # cPickle only accepts str not unicode strings
+ user_expands_string = str(unicode(user_expands).encode(u'utf8'))
+ if user_expands_string:
+ user_tags = cPickle.loads(user_expands_string)
+ # If we have some user ones added them as well
+ FormattingTags.add_html_tags(user_tags)
+
+ @staticmethod
+ def add_html_tags(tags, save=False):
+ """
+ Add a list of tags to the list.
+
+ ``tags``
+ The list with tags to add.
+
+ ``save``
+ Defaults to ``False``. If set to ``True`` the given ``tags`` are
+ saved to the config.
+
+ Each **tag** has to be a ``dict`` and should have the following keys:
+
+ * desc
+ The formatting tag's description, e. g. **Red**
+
+ * start tag
+ The start tag, e. g. ``{r}``
+
+ * end tag
+ The end tag, e. g. ``{/r}``
+
+ * start html
+ The start html tag. For instance ````
+
+ * end html
+ The end html tag. For example ````
+
+ * protected
+ A boolean stating whether this is a build-in tag or not. Should be
+ ``True`` in most cases.
+
+ * temporary
+ A temporary tag will not be saved, but is also considered when
+ displaying text containing the tag. It has to be a ``boolean``.
"""
FormattingTags.html_expands.extend(tags)
+ if save:
+ FormattingTags.save_html_tags()
@staticmethod
def remove_html_tag(tag_id):
diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py
index 2a8625b1a..e7435e5b7 100644
--- a/openlp/core/ui/formattingtagform.py
+++ b/openlp/core/ui/formattingtagform.py
@@ -30,14 +30,13 @@ protected and included each time loaded. Custom tags can be defined and saved.
The Custom Tag arrays are saved in a pickle so QSettings works on them. Base
Tags cannot be changed.
"""
-import cPickle
-
from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate, FormattingTags
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.formattingtagdialog import Ui_FormattingTagDialog
+
class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
The :class:`FormattingTagForm` manages the settings tab .
@@ -48,7 +47,6 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
- self._loadFormattingTags()
QtCore.QObject.connect(self.tagTableWidget,
QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onRowSelected)
QtCore.QObject.connect(self.newPushButton,
@@ -59,41 +57,24 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
QtCore.SIGNAL(u'pressed()'), self.onDeletePushed)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'rejected()'),
self.close)
+ # Forces reloading of tags from openlp configuration.
+ FormattingTags.load_tags()
def exec_(self):
"""
Load Display and set field state.
"""
# Create initial copy from master
- self._loadFormattingTags()
self._resetTable()
self.selected = -1
return QtGui.QDialog.exec_(self)
- def _loadFormattingTags(self):
- """
- Load the Tags from store so can be used in the system or used to
- update the display. If Cancel was selected this is needed to reset the
- dsiplay to the correct version.
- """
- # Initial Load of the Tags
- FormattingTags.reset_html_tags()
- # Formatting Tags were also known as display tags.
- user_expands = QtCore.QSettings().value(u'displayTags/html_tags',
- QtCore.QVariant(u'')).toString()
- # cPickle only accepts str not unicode strings
- user_expands_string = str(unicode(user_expands).encode(u'utf8'))
- if user_expands_string:
- user_tags = cPickle.loads(user_expands_string)
- # If we have some user ones added them as well
- FormattingTags.add_html_tags(user_tags)
-
def onRowSelected(self):
"""
Table Row selected so display items and set field state.
"""
row = self.tagTableWidget.currentRow()
- html = FormattingTags.get_html_tags()[row]
+ html = FormattingTags.html_expands[row]
self.selected = row
self.descriptionLineEdit.setText(html[u'desc'])
self.tagLineEdit.setText(self._strip(html[u'start tag']))
@@ -118,7 +99,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
Add a new tag to list only if it is not a duplicate.
"""
- for html in FormattingTags.get_html_tags():
+ for html in FormattingTags.html_expands:
if self._strip(html[u'start tag']) == u'n':
critical_error_message_box(
translate('OpenLP.FormattingTagForm', 'Update Error'),
@@ -132,7 +113,8 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
u'start html': translate('OpenLP.FormattingTagForm', ''),
u'end tag': u'{/n}',
u'end html': translate('OpenLP.FormattingTagForm', ''),
- u'protected': False
+ u'protected': False,
+ u'temporary': False
}
FormattingTags.add_html_tags([tag])
self._resetTable()
@@ -149,13 +131,13 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
FormattingTags.remove_html_tag(self.selected)
self.selected = -1
self._resetTable()
- self._saveTable()
+ FormattingTags.save_html_tags()
def onSavedPushed(self):
"""
Update Custom Tag details if not duplicate and save the data.
"""
- html_expands = FormattingTags.get_html_tags()
+ html_expands = FormattingTags.html_expands
if self.selected != -1:
html = html_expands[self.selected]
tag = unicode(self.tagLineEdit.text())
@@ -172,21 +154,11 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
html[u'end html'] = unicode(self.endTagLineEdit.text())
html[u'start tag'] = u'{%s}' % tag
html[u'end tag'] = u'{/%s}' % tag
+ # Keep temporary tags when the user changes one.
+ html[u'temporary'] = False
self.selected = -1
self._resetTable()
- self._saveTable()
-
- def _saveTable(self):
- """
- Saves all formatting tags except protected ones.
- """
- tags = []
- for tag in FormattingTags.get_html_tags():
- if not tag[u'protected']:
- tags.append(tag)
- # Formatting Tags were also known as display tags.
- QtCore.QSettings().setValue(u'displayTags/html_tags',
- QtCore.QVariant(cPickle.dumps(tags) if tags else u''))
+ FormattingTags.save_html_tags()
def _resetTable(self):
"""
@@ -197,9 +169,8 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
self.newPushButton.setEnabled(True)
self.savePushButton.setEnabled(False)
self.deletePushButton.setEnabled(False)
- for linenumber, html in enumerate(FormattingTags.get_html_tags()):
- self.tagTableWidget.setRowCount(
- self.tagTableWidget.rowCount() + 1)
+ for linenumber, html in enumerate(FormattingTags.html_expands):
+ self.tagTableWidget.setRowCount(self.tagTableWidget.rowCount() + 1)
self.tagTableWidget.setItem(linenumber, 0,
QtGui.QTableWidgetItem(html[u'desc']))
self.tagTableWidget.setItem(linenumber, 1,
@@ -208,6 +179,9 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
QtGui.QTableWidgetItem(html[u'start html']))
self.tagTableWidget.setItem(linenumber, 3,
QtGui.QTableWidgetItem(html[u'end html']))
+ # Permanent (persistent) tags do not have this key.
+ if u'temporary' not in html:
+ html[u'temporary'] = False
self.tagTableWidget.resizeRowsToContents()
self.descriptionLineEdit.setText(u'')
self.tagLineEdit.setText(u'')
diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py
index 3612bb002..fbf185474 100644
--- a/openlp/core/utils/__init__.py
+++ b/openlp/core/utils/__init__.py
@@ -127,6 +127,9 @@ class AppLocation(object):
CacheDir = 6
LanguageDir = 7
+ # Base path where data/config/cache dir is located
+ BaseDir = None
+
@staticmethod
def get_directory(dir_type=1):
"""
@@ -152,6 +155,8 @@ class AppLocation(object):
os.path.abspath(os.path.split(sys.argv[0])[0]),
_get_os_dir_path(dir_type))
return os.path.join(app_path, u'i18n')
+ elif dir_type == AppLocation.DataDir and AppLocation.BaseDir:
+ return os.path.join(AppLocation.BaseDir, 'data')
else:
return _get_os_dir_path(dir_type)
diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py
index f20095500..950eff870 100644
--- a/openlp/plugins/songs/lib/mediaitem.py
+++ b/openlp/plugins/songs/lib/mediaitem.py
@@ -562,6 +562,9 @@ class SongMediaItem(MediaManagerItem):
self._updateBackgroundAudio(song, item)
editId = song.id
self.onSearchTextButtonClick()
+ else:
+ # Make sure we temporary import formatting tags.
+ self.openLyrics.xml_to_song(item.xml_version, True)
# Update service with correct song id.
if editId:
Receiver.send_message(u'service_item_update',
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 9da7a0a65..5b36a7cb9 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -61,19 +61,21 @@ The XML of an `OpenLyrics `_ song looks like this::
"""
-import datetime
import logging
import re
from lxml import etree, objectify
+from openlp.core.lib import FormattingTags
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
from openlp.core.utils import get_application_version
log = logging.getLogger(__name__)
-CHORD_REGEX = re.compile(u'')
+NAMESPACE = u'http://openlyrics.info/namespace/2009/song'
+NSMAP = '{' + NAMESPACE + '}' + '%s'
+
class SongXML(object):
"""
@@ -173,7 +175,7 @@ class SongXML(object):
class OpenLyrics(object):
"""
- This class represents the converter for OpenLyrics XML (version 0.7)
+ This class represents the converter for OpenLyrics XML (version 0.8)
to/from a song.
As OpenLyrics has a rich set of different features, we cannot support them
@@ -198,11 +200,15 @@ class OpenLyrics(object):
````
This property is not supported.
+ ````
+ The custom formatting tags are fully supported.
+
````
This property is not supported.
````
- The attribute *part* is not supported.
+ The attribute *part* is not supported. The *break* attribute is
+ supported.
````
This property is not supported.
@@ -227,15 +233,35 @@ class OpenLyrics(object):
````
The attribute *translit* is not supported. Note, the attribute *lang* is
- considered, but there is not further functionality implemented yet.
+ 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.
+ OpenLP will merge verses which are split up by appending a letter to the
+ verse name, such as *v1a*.
````
OpenLP supports this property.
"""
- IMPLEMENTED_VERSION = u'0.7'
+ IMPLEMENTED_VERSION = u'0.8'
+
def __init__(self, manager):
self.manager = manager
+ self.start_tags_regex = re.compile(r'\{(\w+)\}') # {abc} -> abc
+ self.end_tags_regex = re.compile(r'\{\/(\w+)\}') # {/abc} -> abc
def song_to_xml(self, song):
"""
@@ -244,13 +270,14 @@ class OpenLyrics(object):
sxml = SongXML()
song_xml = objectify.fromstring(u'')
# Append the necessary meta data to the song.
- song_xml.set(u'xmlns', u'http://openlyrics.info/namespace/2009/song')
+ song_xml.set(u'xmlns', NAMESPACE)
song_xml.set(u'version', OpenLyrics.IMPLEMENTED_VERSION)
application_name = u'OpenLP ' + get_application_version()[u'version']
song_xml.set(u'createdIn', application_name)
song_xml.set(u'modifiedIn', application_name)
+ # "Convert" 2011-08-27 11:49:15 to 2011-08-27T11:49:15.
song_xml.set(u'modifiedDate',
- datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S'))
+ unicode(song.last_modified).replace(u' ', u'T'))
properties = etree.SubElement(song_xml, u'properties')
titles = etree.SubElement(properties, u'titles')
self._add_text_to_element(u'title', titles, song.title)
@@ -284,29 +311,40 @@ class OpenLyrics(object):
themes = etree.SubElement(properties, u'themes')
for topic in song.topics:
self._add_text_to_element(u'theme', themes, topic.name)
+ # Process the formatting tags.
+ # Have we any tags in song lyrics?
+ tags_element = None
+ match = re.search(u'\{/?\w+\}', song.lyrics, re.UNICODE)
+ if match:
+ # Reset available tags.
+ FormattingTags.reset_html_tags()
+ # Named 'formatting' - 'format' is built-in fuction in Python.
+ format_ = etree.SubElement(song_xml, u'format')
+ tags_element = etree.SubElement(format_, u'tags')
+ tags_element.set(u'application', u'OpenLP')
# Process the song's lyrics.
lyrics = etree.SubElement(song_xml, u'lyrics')
verse_list = sxml.get_verses(song.lyrics)
for verse in verse_list:
verse_tag = verse[0][u'type'][0].lower()
verse_number = verse[0][u'label']
+ verse_def = verse_tag + verse_number
+ verse_element = \
+ self._add_text_to_element(u'verse', lyrics, None, verse_def)
+ if u'lang' in verse[0]:
+ verse_element.set(u'lang', verse[0][u'lang'])
# Create a list with all "virtual" verses.
virtual_verses = verse[1].split(u'[---]')
for index, virtual_verse in enumerate(virtual_verses):
- verse_def = verse_tag + verse_number
- # We need "v1a" because we have more than one virtual verse.
- if len(virtual_verses) > 1:
- verse_def += list(u'abcdefghijklmnopqrstuvwxyz')[index]
- element = \
- self._add_text_to_element(u'verse', lyrics, None, verse_def)
- if verse[0].has_key(u'lang'):
- element.set(u'lang', verse[0][u'lang'])
- element = self._add_text_to_element(u'lines', element)
- for line in virtual_verse.strip(u'\n').split(u'\n'):
- self._add_text_to_element(u'line', element, line)
+ # Add formatting tags to text
+ lines_element = self._add_text_with_tags_to_lines(verse_element,
+ virtual_verse, tags_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')
return self._extract_xml(song_xml)
- def xml_to_song(self, xml):
+ def xml_to_song(self, xml, parse_and_not_save=False):
"""
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
@@ -314,19 +352,26 @@ class OpenLyrics(object):
``xml``
The XML to parse (unicode).
+
+ ``parse_and_not_save``
+ Switch to skip processing the whole song and to prevent storing the
+ songs to the database. Defaults to ``False``.
"""
# No xml get out of here.
if not xml:
return None
if xml[:5] == u' 0.7:
+ self._process_formatting_tags(song_xml, parse_and_not_save)
+ if parse_and_not_save:
+ return
song = Song()
# Values will be set when cleaning the song.
song.search_lyrics = u''
@@ -336,7 +381,7 @@ class OpenLyrics(object):
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_lyrics(properties, song_xml, song)
self._process_comments(properties, song)
self._process_authors(properties, song)
self._process_songbooks(properties, song)
@@ -355,6 +400,57 @@ class OpenLyrics(object):
parent.append(element)
return element
+ def _add_tag_to_formatting(self, tag_name, tags_element):
+ """
+ Add new formatting tag to the element ````
+ if the tag is not present yet.
+ """
+ available_tags = FormattingTags.get_html_tags()
+ start_tag = '{%s}' % tag_name
+ for t in available_tags:
+ if t[u'start tag'] == start_tag:
+ # Create new formatting tag in openlyrics xml.
+ el = self._add_text_to_element(u'tag', tags_element)
+ el.set(u'name', tag_name)
+ el_open = self._add_text_to_element(u'open', el)
+ el_open.text = etree.CDATA(t[u'start html'])
+ # Check if formatting tag contains end tag. Some formatting
+ # tags e.g. {br} has only start tag. If no end tag is present
+ # element has not to be in OpenLyrics xml.
+ if t['end tag']:
+ el_close = self._add_text_to_element(u'close', el)
+ el_close.text = etree.CDATA(t[u'end html'])
+
+ def _add_text_with_tags_to_lines(self, verse_element, text, tags_element):
+ """
+ Convert text with formatting tags from OpenLP format to OpenLyrics
+ format and append it to element ````.
+ """
+ start_tags = self.start_tags_regex.findall(text)
+ end_tags = self.end_tags_regex.findall(text)
+ # Replace start tags with xml syntax.
+ for tag in start_tags:
+ # Tags already converted to xml structure.
+ xml_tags = tags_element.xpath(u'tag/attribute::name')
+ # Some formatting tag has only starting part e.g.
.
+ # Handle this case.
+ if tag in end_tags:
+ text = text.replace(u'{%s}' % tag, u'' % tag)
+ else:
+ text = text.replace(u'{%s}' % tag, u'' % tag)
+ # Add tag to element if tag not present.
+ if tag not in xml_tags:
+ self._add_tag_to_formatting(tag, tags_element)
+ # Replace end tags.
+ for t in end_tags:
+ text = text.replace(u'{/%s}' % t, u'')
+ # Replace \n with
.
+ text = text.replace(u'\n', u'
')
+ text = u'' + text + u''
+ element = etree.XML(text)
+ verse_element.append(element)
+ return element
+
def _extract_xml(self, xml):
"""
Extract our newly created XML song.
@@ -362,20 +458,6 @@ class OpenLyrics(object):
return etree.tostring(xml, encoding=u'UTF-8',
xml_declaration=True)
- 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.
@@ -457,29 +539,155 @@ class OpenLyrics(object):
if hasattr(properties, u'copyright'):
song.copyright = self._text(properties.copyright)
- def _process_lyrics(self, properties, lyrics, song):
+ 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
+ start_tag = u'{%s}' % name[:5]
+ # Some tags have only start tag e.g. {br}
+ end_tag = u'{/' + name[:5] + u'}' if hasattr(tag, 'close') else u''
+ openlp_tag = {
+ u'desc': name,
+ u'start tag': start_tag,
+ u'end tag': end_tag,
+ u'start html': tag.open.text,
+ # Some tags have only start html e.g. {br}
+ u'end html': tag.close.text if hasattr(tag, 'close') else u'',
+ u'protected': False,
+ }
+ # Add 'temporary' key in case the formatting tag should not be
+ # saved otherwise it is supposed that formatting tag is permanent.
+ if temporary:
+ openlp_tag[u'temporary'] = temporary
+ found_tags.append(openlp_tag)
+ existing_tag_ids = [tag[u'start tag']
+ for tag in FormattingTags.get_html_tags()]
+ new_tags = [tag for tag in found_tags
+ if tag[u'start tag'] not in existing_tag_ids]
+ FormattingTags.add_html_tags(new_tags, True)
+
+ def _process_lines_mixed_content(self, element, newlines=True):
+ """
+ Converts the xml text with mixed content to OpenLP representation.
+ Chords are skipped and formatting tags are converted.
+
+ ``element``
+ The property object (lxml.etree.Element).
+
+ ``newlines``
+ The switch to enable/disable processing of line breaks
.
+ The
is used since OpenLyrics 0.8.
+ """
+ text = u''
+ use_endtag = True
+ # Skip elements - not yet supported.
+ if element.tag == NSMAP % u'comment':
+ if element.tail:
+ # Append tail text at chord element.
+ text += element.tail
+ return text
+ # Skip element - not yet supported.
+ elif element.tag == NSMAP % u'chord':
+ if element.tail:
+ # Append tail text at chord element.
+ text += element.tail
+ return text
+ # Convert line breaks
to \n.
+ elif newlines and element.tag == NSMAP % u'br':
+ text += u'\n'
+ if element.tail:
+ text += element.tail
+ return text
+ # Start formatting tag.
+ if element.tag == NSMAP % u'tag':
+ text += u'{%s}' % element.get(u'name')
+ # Some formattings may have only start tag.
+ # Handle this case if element has no children and contains no text.
+ if len(element) == 0 and not element.text:
+ use_endtag = False
+ # Append text from element.
+ if element.text:
+ text += element.text
+ # Process nested formatting tags.
+ for child in element:
+ # Use recursion since nested formatting tags are allowed.
+ text += self._process_lines_mixed_content(child, newlines)
+ # Append text from tail and add formatting end tag.
+ if element.tag == NSMAP % 'tag' and use_endtag:
+ text += u'{/%s}' % element.get(u'name')
+ # Append text from tail.
+ if element.tail:
+ text += element.tail
+ return text
+
+ def _process_verse_lines(self, lines, version):
+ """
+ Converts lyrics lines to OpenLP representation.
+
+ ``lines``
+ The lines object (lxml.objectify.ObjectifiedElement).
+ """
+ text = u''
+ # Convert lxml.objectify to lxml.etree representation.
+ lines = etree.tostring(lines)
+ element = etree.XML(lines)
+
+ # OpenLyrics 0.8 uses
for new lines.
+ # Append text from "lines" element to verse text.
+ if version > '0.7':
+ text = self._process_lines_mixed_content(element)
+ # OpenLyrics version <= 0.7 contais elements to represent lines.
+ # First child element is tested.
+ else:
+ # Loop over the "line" elements removing comments and chords.
+ for line in element:
+ # Skip comment lines.
+ if line.tag == NSMAP % u'comment':
+ continue
+ if text:
+ text += u'\n'
+ text += self._process_lines_mixed_content(line, newlines=False)
+ return text
+
+ def _process_lyrics(self, properties, song_xml, song_obj):
"""
Processes the verses and search_lyrics for the song.
``properties``
The properties object (lxml.objectify.ObjectifiedElement).
- ``lyrics``
- The lyrics object (lxml.objectify.ObjectifiedElement).
+ ``song_xml``
+ The objectified song (lxml.objectify.ObjectifiedElement).
- ``song``
+ ``song_obj``
The song object.
"""
sxml = SongXML()
verses = {}
verse_def_list = []
+ lyrics = song_xml.lyrics
+ # Loop over the "verse" elements.
for verse in lyrics.verse:
text = u''
+ # Loop over the "lines" elements.
for lines in verse.lines:
if text:
text += u'\n'
- text += u'\n'.join([unicode(line) for line in lines.line])
- verse_def = self._get(verse, u'name').lower()
+ # Append text from "lines" element to verse text.
+ text += self._process_verse_lines(lines,
+ version=song_xml.get(u'version'))
+ # Add a virtual split to the verse text.
+ if lines.get(u'break') is not None:
+ text += u'\n[---]'
+ verse_def = verse.get(u'name', u' ').lower()
if verse_def[0] in VerseType.Tags:
verse_tag = verse_def[0]
else:
@@ -489,11 +697,16 @@ class OpenLyrics(object):
# not correct the verse order.
if not verse_number:
verse_number = u'1'
- lang = None
- if self._get(verse, u'lang'):
- lang = self._get(verse, u'lang')
- if verses.has_key((verse_tag, verse_number, lang)):
+ lang = verse.get(u'lang')
+ # 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 \
+ (verse_tag, verse_number, lang) in verses:
verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text
+ # Merge v1a, v1b, .... to v1.
+ elif (verse_tag, verse_number, lang) in verses:
+ verses[(verse_tag, verse_number, lang)] += u'\n' + text
else:
verses[(verse_tag, verse_number, lang)] = text
verse_def_list.append((verse_tag, verse_number, lang))
@@ -501,10 +714,10 @@ class OpenLyrics(object):
for verse in verse_def_list:
sxml.add_verse_to_lyrics(
verse[0], verse[1], verses[verse], verse[2])
- song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
+ song_obj.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Process verse order
if hasattr(properties, u'verseOrder'):
- song.verse_order = self._text(properties.verseOrder)
+ song_obj.verse_order = self._text(properties.verseOrder)
def _process_songbooks(self, properties, song):
"""
@@ -520,7 +733,7 @@ class OpenLyrics(object):
song.song_number = u''
if hasattr(properties, u'songbooks'):
for songbook in properties.songbooks.songbook:
- bookname = self._get(songbook, u'name')
+ bookname = songbook.get(u'name', u'')
if bookname:
book = self.manager.get_object_filtered(Book,
Book.name == bookname)
@@ -529,7 +742,7 @@ class OpenLyrics(object):
book = Book.populate(name=bookname, publisher=u'')
self.manager.save_object(book)
song.song_book_id = book.id
- song.song_number = self._get(songbook, u'entry')
+ song.song_number = songbook.get(u'entry', u'')
# We only support one song book, so take the first one.
break