From 23733608ef8487107450b3a6af5e91ac7f3f9946 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Sat, 20 Aug 2011 19:06:48 +0200
Subject: [PATCH 01/43] adapted virtual breaks in OpenLyrics implementation
for the upcomming 0.8 release
---
openlp/plugins/songs/lib/xml.py | 48 +++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 14 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 193a823d5..b88c03f6e 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -202,7 +202,8 @@ class OpenLyrics(object):
This property is not supported.
````
- The attribute *part* is not supported.
+ The attribute *part* is not supported. The *break* attribute is fully
+ supported.
````
This property is not supported.
@@ -227,13 +228,28 @@ 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 supports this property.
"""
- IMPLEMENTED_VERSION = u'0.7'
+ IMPLEMENTED_VERSION = u'0.8'
def __init__(self, manager):
self.manager = manager
@@ -290,20 +306,21 @@ class OpenLyrics(object):
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 verse[0].has_key(u'lang'):
+ 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)
+ 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')
for line in virtual_verse.strip(u'\n').split(u'\n'):
- self._add_text_to_element(u'line', element, line)
+ self._add_text_to_element(u'line', lines_element, line)
return self._extract_xml(song_xml)
def xml_to_song(self, xml):
@@ -478,7 +495,10 @@ class OpenLyrics(object):
for lines in verse.lines:
if text:
text += u'\n'
- text += u'\n'.join([unicode(line) for line in lines.line])
+ text += u'\n'.join(map(unicode, lines.line))
+ # Adgetd a virtual split to the verse text.
+ if self._get(lines, u'break'):
+ text += u'\n[---]'
verse_def = self._get(verse, u'name').lower()
if verse_def[0] in VerseType.Tags:
verse_tag = verse_def[0]
From 0ab092e56eef43fec9df31e54b1aa24f822266a2 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Sat, 20 Aug 2011 19:15:31 +0200
Subject: [PATCH 02/43] removed _get method
---
openlp/plugins/songs/lib/xml.py | 37 ++++++++++-----------------------
1 file changed, 11 insertions(+), 26 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index b88c03f6e..b4b9aeffb 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -353,7 +353,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)
@@ -379,20 +379,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.
@@ -474,22 +460,23 @@ class OpenLyrics(object):
if hasattr(properties, u'copyright'):
song.copyright = self._text(properties.copyright)
- def _process_lyrics(self, properties, lyrics, song):
+ 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
for verse in lyrics.verse:
text = u''
for lines in verse.lines:
@@ -497,9 +484,9 @@ class OpenLyrics(object):
text += u'\n'
text += u'\n'.join(map(unicode, lines.line))
# Adgetd a virtual split to the verse text.
- if self._get(lines, u'break'):
+ if lines.get(u'break') is not None:
text += u'\n[---]'
- verse_def = self._get(verse, u'name').lower()
+ verse_def = verse.get(u'name', u' ').lower()
if verse_def[0] in VerseType.Tags:
verse_tag = verse_def[0]
else:
@@ -509,9 +496,7 @@ 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')
+ lang = verse.get(u'lang')
if verses.has_key((verse_tag, verse_number, lang)):
verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text
else:
@@ -540,7 +525,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)
@@ -549,7 +534,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
From c7d875a67f62e82f11060342c9a8d3e8c1e27850 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Sat, 20 Aug 2011 19:39:38 +0200
Subject: [PATCH 03/43] compatibility for OpenLyrics files created with OpenLP
1.9.6 and OpenLyrics 0.7
---
openlp/plugins/songs/lib/xml.py | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index b4b9aeffb..801f61e5e 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -244,6 +244,8 @@ class OpenLyrics(object):
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.
@@ -497,8 +499,15 @@ class OpenLyrics(object):
if not verse_number:
verse_number = u'1'
lang = verse.get(u'lang')
- if verses.has_key((verse_tag, verse_number, 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 \
+ verses.has_key((verse_tag, verse_number, lang)):
verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text
+ # Merge v1a, v1b, .... to v1.
+ elif verses.has_key((verse_tag, verse_number, lang)):
+ 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))
@@ -506,10 +515,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):
"""
From 650dc83aebd4703c76603a5e81cd9e49e9923279 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Mon, 22 Aug 2011 15:39:02 +0200
Subject: [PATCH 04/43] do not use re with xml
---
openlp/plugins/songs/lib/xml.py | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 801f61e5e..3391a1721 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -73,8 +73,6 @@ from openlp.core.utils import get_application_version
log = logging.getLogger(__name__)
-CHORD_REGEX = re.compile(u'')
-
class SongXML(object):
"""
This class builds and parses the XML used to describe songs.
@@ -202,7 +200,7 @@ class OpenLyrics(object):
This property is not supported.
````
- The attribute *part* is not supported. The *break* attribute is fully
+ The attribute *part* is not supported. The *break* attribute is
supported.
````
@@ -339,8 +337,6 @@ class OpenLyrics(object):
return None
if xml[:5] == u'
Date: Mon, 22 Aug 2011 16:11:37 +0200
Subject: [PATCH 05/43] simplification
---
openlp/plugins/songs/lib/xml.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 3391a1721..c6dd90c03 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -265,8 +265,7 @@ class OpenLyrics(object):
application_name = u'OpenLP ' + get_application_version()[u'version']
song_xml.set(u'createdIn', application_name)
song_xml.set(u'modifiedIn', application_name)
- song_xml.set(u'modifiedDate',
- datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S'))
+ song_xml.set(u'modifiedDate', datetime.datetime.now().isoformat())
properties = etree.SubElement(song_xml, u'properties')
titles = etree.SubElement(properties, u'titles')
self._add_text_to_element(u'title', titles, song.title)
From 627b9429aa2bf1fa3fafdf6e02fd1b965ef10862 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Fri, 26 Aug 2011 16:27:53 +0200
Subject: [PATCH 06/43] documented formatting tags; added infrastructure to
allow temporary formatting tags
---
openlp/core/lib/formattingtags.py | 76 ++++++++++++++++++++++-------
openlp/core/ui/formattingtagform.py | 13 +++--
2 files changed, 68 insertions(+), 21 deletions(-)
diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py
index ae9d5c1cf..8230da36a 100644
--- a/openlp/core/lib/formattingtags.py
+++ b/openlp/core/lib/formattingtags.py
@@ -56,73 +56,115 @@ 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})
+ 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)
@staticmethod
def add_html_tags(tags):
"""
- Add a list of tags to the list
+ Add a list of tags to the list.
+
+ ``tags``
+ The list with tags to add.
+
+ 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)
diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py
index 2a8625b1a..4d4739204 100644
--- a/openlp/core/ui/formattingtagform.py
+++ b/openlp/core/ui/formattingtagform.py
@@ -132,7 +132,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()
@@ -172,6 +173,8 @@ 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()
@@ -182,7 +185,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
tags = []
for tag in FormattingTags.get_html_tags():
- if not tag[u'protected']:
+ if not tag[u'protected'] and not tag[u'temporary']:
tags.append(tag)
# Formatting Tags were also known as display tags.
QtCore.QSettings().setValue(u'displayTags/html_tags',
@@ -198,8 +201,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
self.savePushButton.setEnabled(False)
self.deletePushButton.setEnabled(False)
for linenumber, html in enumerate(FormattingTags.get_html_tags()):
- self.tagTableWidget.setRowCount(
- self.tagTableWidget.rowCount() + 1)
+ self.tagTableWidget.setRowCount(self.tagTableWidget.rowCount() + 1)
self.tagTableWidget.setItem(linenumber, 0,
QtGui.QTableWidgetItem(html[u'desc']))
self.tagTableWidget.setItem(linenumber, 1,
@@ -208,6 +210,9 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
QtGui.QTableWidgetItem(html[u'start html']))
self.tagTableWidget.setItem(linenumber, 3,
QtGui.QTableWidgetItem(html[u'end html']))
+ # Tags saved prior to 1.9.7 do not have this key.
+ if not html.has_key(u'temporary'):
+ html[u'temporary'] = False
self.tagTableWidget.resizeRowsToContents()
self.descriptionLineEdit.setText(u'')
self.tagLineEdit.setText(u'')
From 953ff87ff58c01424a13bf0356b259f3daed1552 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Fri, 26 Aug 2011 17:06:22 +0200
Subject: [PATCH 07/43] do not delete temporary tags when resetting the list;
preparations
---
openlp/core/lib/formattingtags.py | 3 +++
openlp/plugins/songs/lib/mediaitem.py | 3 +++
openlp/plugins/songs/lib/xml.py | 11 ++++++++---
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py
index 8230da36a..bb4c0bcc8 100644
--- a/openlp/core/lib/formattingtags.py
+++ b/openlp/core/lib/formattingtags.py
@@ -49,6 +49,8 @@ class FormattingTags(object):
"""
Resets the html_expands list.
"""
+ temporary_tags = [tag for tag in FormattingTags.html_expands
+ if tag[u'temporary']]
FormattingTags.html_expands = []
base_tags = []
# Append the base tags.
@@ -131,6 +133,7 @@ class FormattingTags(object):
u'start tag': u'{br}', u'start html': u'
', u'end tag': u'',
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):
diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py
index a2814a1df..b1a5f3572 100644
--- a/openlp/plugins/songs/lib/mediaitem.py
+++ b/openlp/plugins/songs/lib/mediaitem.py
@@ -513,6 +513,9 @@ class SongMediaItem(MediaManagerItem):
if add_song and self.addSongFromService:
editId = self.openLyrics.xml_to_song(item.xml_version)
self.onSearchTextButtonClick()
+ else:
+ # Make sure we temporary import formatting tags.
+ self.openLyrics.xml_to_song(item.xml_version, False)
# 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 c6dd90c03..79b0bc986 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -322,7 +322,7 @@ class OpenLyrics(object):
self._add_text_to_element(u'line', lines_element, line)
return self._extract_xml(song_xml)
- def xml_to_song(self, xml):
+ def xml_to_song(self, xml, save_to_db=True):
"""
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
@@ -330,6 +330,10 @@ class OpenLyrics(object):
``xml``
The XML to parse (unicode).
+
+ ``save_to_db``
+ Switch to prevent storing songs to the database. Defaults to
+ ``True``.
"""
# No xml get out of here.
if not xml:
@@ -356,8 +360,9 @@ class OpenLyrics(object):
self._process_songbooks(properties, song)
self._process_topics(properties, song)
clean_song(self.manager, song)
- self.manager.save_object(song)
- return song.id
+ if save_to_db:
+ self.manager.save_object(song)
+ return song.id
def _add_text_to_element(self, tag, parent, text=None, label=None):
if label:
From 407b342f45f1d93a23affe54debc8e2229efcbd1 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Fri, 26 Aug 2011 17:15:17 +0200
Subject: [PATCH 08/43] changed 'switch' name
---
openlp/plugins/songs/lib/mediaitem.py | 2 +-
openlp/plugins/songs/lib/xml.py | 35 ++++++++++++++-------------
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py
index b1a5f3572..2f8e37f96 100644
--- a/openlp/plugins/songs/lib/mediaitem.py
+++ b/openlp/plugins/songs/lib/mediaitem.py
@@ -515,7 +515,7 @@ class SongMediaItem(MediaManagerItem):
self.onSearchTextButtonClick()
else:
# Make sure we temporary import formatting tags.
- self.openLyrics.xml_to_song(item.xml_version, False)
+ 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 79b0bc986..4021d3355 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -322,7 +322,7 @@ class OpenLyrics(object):
self._add_text_to_element(u'line', lines_element, line)
return self._extract_xml(song_xml)
- def xml_to_song(self, xml, save_to_db=True):
+ def xml_to_song(self, xml, only_process_format_tags=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
@@ -331,9 +331,9 @@ class OpenLyrics(object):
``xml``
The XML to parse (unicode).
- ``save_to_db``
- Switch to prevent storing songs to the database. Defaults to
- ``True``.
+ ``only_process_format_tags``
+ 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:
@@ -346,21 +346,22 @@ class OpenLyrics(object):
else:
return None
song = Song()
- # 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)
+ if not only_process_format_tags:
+ # 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)
# The verse order is processed with the lyrics!
self._process_lyrics(properties, song_xml, song)
- 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)
- if save_to_db:
+ if not only_process_format_tags:
+ 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)
return song.id
From f3691516fd93c77c057719c5259125a48075f980 Mon Sep 17 00:00:00 2001
From: Andreas Preikschat
Date: Sat, 27 Aug 2011 16:00:24 +0200
Subject: [PATCH 09/43] import tags from xml (part 1)
---
openlp/core/lib/formattingtags.py | 24 +++++++++-
openlp/core/ui/formattingtagform.py | 16 +------
openlp/plugins/songs/lib/xml.py | 70 ++++++++++++++++++++++-------
3 files changed, 78 insertions(+), 32 deletions(-)
diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py
index bb4c0bcc8..529a8c029 100644
--- a/openlp/core/lib/formattingtags.py
+++ b/openlp/core/lib/formattingtags.py
@@ -27,6 +27,9 @@
"""
Provide HTML Tag management and Formatting Tag access class
"""
+import cPickle
+
+from PyQt4 import QtCore
from openlp.core.lib import translate
@@ -136,13 +139,30 @@ class FormattingTags(object):
FormattingTags.add_html_tags(temporary_tags)
@staticmethod
- def add_html_tags(tags):
+ def save_html_tags():
+ """
+ Saves all formatting tags except protected ones.
+ """
+ tags = []
+ for tag in FormattingTags.get_html_tags():
+ if not tag[u'protected'] and not tag[u'temporary']:
+ 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''))
+
+ @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
@@ -170,6 +190,8 @@ class FormattingTags(object):
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 4d4739204..fee27a9c6 100644
--- a/openlp/core/ui/formattingtagform.py
+++ b/openlp/core/ui/formattingtagform.py
@@ -150,7 +150,7 @@ 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):
"""
@@ -177,19 +177,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
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'] and not tag[u'temporary']:
- 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):
"""
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 4021d3355..ac77596de 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -67,6 +67,7 @@ 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
@@ -265,7 +266,9 @@ class OpenLyrics(object):
application_name = u'OpenLP ' + get_application_version()[u'version']
song_xml.set(u'createdIn', application_name)
song_xml.set(u'modifiedIn', application_name)
- song_xml.set(u'modifiedDate', datetime.datetime.now().isoformat())
+ # "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'))
properties = etree.SubElement(song_xml, u'properties')
titles = etree.SubElement(properties, u'titles')
self._add_text_to_element(u'title', titles, song.title)
@@ -299,6 +302,10 @@ 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.
+ format = etree.SubElement(song_xml, u'format')
+ tags = etree.SubElement(format, u'tags')
+ tags.set(u'application', u'OpenLP')
# Process the song's lyrics.
lyrics = etree.SubElement(song_xml, u'lyrics')
verse_list = sxml.get_verses(song.lyrics)
@@ -345,25 +352,27 @@ class OpenLyrics(object):
properties = song_xml.properties
else:
return None
+ 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
song = Song()
- if not only_process_format_tags:
- # 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)
+ # 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)
# The verse order is processed with the lyrics!
self._process_lyrics(properties, song_xml, song)
- if not only_process_format_tags:
- 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)
- return song.id
+ 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)
+ return song.id
def _add_text_to_element(self, tag, parent, text=None, label=None):
if label:
@@ -463,6 +472,33 @@ class OpenLyrics(object):
if hasattr(properties, u'copyright'):
song.copyright = self._text(properties.copyright)
+ 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)
+
def _process_lyrics(self, properties, song_xml, song_obj):
"""
Processes the verses and search_lyrics for the song.
From 4cf750ce9d406aa6759a68133f0333792042d960 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sat, 3 Sep 2011 16:21:36 +0200
Subject: [PATCH 10/43] Wrap db.Manager to a function arg for automated tests
---
openlp/core/__init__.py | 35 ++++++++++++++++--------------
openlp/core/lib/db.py | 12 +++++++++--
testing/conftest.py | 47 ++++++++++++++++++++++++++++++++++++++---
3 files changed, 73 insertions(+), 21 deletions(-)
diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py
index 01d34956e..305398b9a 100644
--- a/openlp/core/__init__.py
+++ b/openlp/core/__init__.py
@@ -228,26 +228,29 @@ def main(args=None):
help='Set the Qt4 style (passed directly to Qt4).')
parser.add_option('--testing', dest='testing',
action='store_true', help='Run by testing framework')
- # Set up logging
- log_path = AppLocation.get_directory(AppLocation.CacheDir)
- check_directory_exists(log_path)
- filename = os.path.join(log_path, u'openlp.log')
- logfile = logging.FileHandler(filename, u'w')
- logfile.setFormatter(logging.Formatter(
- u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
- log.addHandler(logfile)
- logging.addLevelName(15, u'Timer')
# Parse command line options and deal with them.
# Use args supplied programatically if possible.
(options, args) = parser.parse_args(args) if args else parser.parse_args()
+ # Set up logging
+ # In test mode it is skipped
+ if not options.testing:
+ log_path = AppLocation.get_directory(AppLocation.CacheDir)
+ check_directory_exists(log_path)
+ filename = os.path.join(log_path, u'openlp.log')
+ logfile = logging.FileHandler(filename, u'w')
+ logfile.setFormatter(logging.Formatter(
+ u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
+ log.addHandler(logfile)
+ logging.addLevelName(15, u'Timer')
+ if options.loglevel.lower() in ['d', 'debug']:
+ log.setLevel(logging.DEBUG)
+ print 'Logging to:', filename
+ elif options.loglevel.lower() in ['w', 'warning']:
+ log.setLevel(logging.WARNING)
+ else:
+ log.setLevel(logging.INFO)
+ # Deal with other command line options.
qt_args = []
- if options.loglevel.lower() in ['d', 'debug']:
- log.setLevel(logging.DEBUG)
- print 'Logging to:', filename
- elif options.loglevel.lower() in ['w', 'warning']:
- log.setLevel(logging.WARNING)
- else:
- log.setLevel(logging.INFO)
if options.style:
qt_args.extend(['-style', options.style])
# Throw the rest of the arguments at Qt, just in case.
diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py
index 2e5d011cf..7bb42b321 100644
--- a/openlp/core/lib/db.py
+++ b/openlp/core/lib/db.py
@@ -159,7 +159,7 @@ class Manager(object):
Provide generic object persistence management
"""
def __init__(self, plugin_name, init_schema, db_file_name=None,
- upgrade_mod=None):
+ db_file_path=None, upgrade_mod=None):
"""
Runs the initialisation process that includes creating the connection
to the database and the tables if they don't exist.
@@ -176,6 +176,10 @@ class Manager(object):
``db_file_name``
The file name to use for this database. Defaults to None resulting
in the plugin_name being used.
+
+ ``db_file_path``
+ The path to sqlite file to use for this database. This is useful
+ for testing purposes.
"""
settings = QtCore.QSettings()
settings.beginGroup(plugin_name)
@@ -184,7 +188,11 @@ class Manager(object):
db_type = unicode(
settings.value(u'db type', QtCore.QVariant(u'sqlite')).toString())
if db_type == u'sqlite':
- if db_file_name:
+ # For automated tests we need to supply file_path directly
+ if db_file_path:
+ self.db_url = u'sqlite:///%s' % os.path.normpath(
+ os.path.abspath(db_file_path))
+ elif db_file_name:
self.db_url = u'sqlite:///%s/%s' % (
AppLocation.get_section_data_path(plugin_name),
db_file_name)
diff --git a/testing/conftest.py b/testing/conftest.py
index f38018c17..3f3b79bc7 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -30,16 +30,57 @@
Configuration file for pytest framework.
"""
+import logging
+import random
+import string
+
+from PyQt4 import QtCore
+from sqlalchemy.orm import clear_mappers
+
from openlp.core import main as openlp_main
+from openlp.core.lib.db import Manager
+from openlp.plugins.songs.lib.db import init_schema
+
+# set up logging to stderr (console)
+_handler = logging.StreamHandler(stream=None)
+_handler.setFormatter(logging.Formatter(
+ u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
+logging.addLevelName(15, u'Timer')
+log = logging.getLogger()
+log.addHandler(_handler)
+log.setLevel(logging.DEBUG)
# Test function argument to make openlp gui instance persistent for all tests.
-# All test cases have to access the same instance. To allow create multiple
+# Test cases in module have to access the same instance. To allow creating
+# multiple
# instances it would be necessary use diffrent configuraion and data files.
# Created instance will use your OpenLP settings.
def pytest_funcarg__openlpapp(request):
def setup():
return openlp_main(['--testing'])
def teardown(app):
- pass
- return request.cached_setup(setup=setup, teardown=teardown, scope='session')
+ # sqlalchemy allows to map classess to only one database at a time
+ clear_mappers()
+ return request.cached_setup(setup=setup, teardown=teardown, scope='module')
+
+
+# Test function argument to make openlp gui instance persistent for all tests.
+def pytest_funcarg__empty_dbmanager(request):
+ def setup():
+ tmpdir = request.getfuncargvalue('tmpdir')
+ db_file_path = tmpdir.join('songs.sqlite')
+ print db_file_path
+ unique = ''.join(random.choice(string.letters + string.digits)
+ for i in range(8))
+ plugin_name = 'test_songs_%s' % unique
+ settings = QtCore.QSettings()
+ settings.beginGroup(plugin_name)
+ settings.setValue(u'db type', QtCore.QVariant(u'sqlite'))
+ settings.endGroup()
+ manager = Manager(plugin_name, init_schema,
+ db_file_path=db_file_path.strpath)
+ return manager
+ def teardown(manager):
+ clear_mappers()
+ return request.cached_setup(setup=setup, teardown=teardown, scope='function')
From 27897c95fd0fffa855954474b581510257a66d66 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sat, 3 Sep 2011 23:01:04 +0200
Subject: [PATCH 11/43] Add some db.Manager tests
---
testing/conftest.py | 5 ++--
testing/test_app.py | 4 +++
testing/test_songs_db.py | 53 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 60 insertions(+), 2 deletions(-)
create mode 100644 testing/test_songs_db.py
diff --git a/testing/conftest.py b/testing/conftest.py
index 3f3b79bc7..d21ced22d 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -66,11 +66,11 @@ def pytest_funcarg__openlpapp(request):
# Test function argument to make openlp gui instance persistent for all tests.
-def pytest_funcarg__empty_dbmanager(request):
+def pytest_funcarg__empty_songs_db(request):
def setup():
tmpdir = request.getfuncargvalue('tmpdir')
db_file_path = tmpdir.join('songs.sqlite')
- print db_file_path
+ # unique QSettings group
unique = ''.join(random.choice(string.letters + string.digits)
for i in range(8))
plugin_name = 'test_songs_%s' % unique
@@ -82,5 +82,6 @@ def pytest_funcarg__empty_dbmanager(request):
db_file_path=db_file_path.strpath)
return manager
def teardown(manager):
+ # sqlalchemy allows to map classess to only one database at a time
clear_mappers()
return request.cached_setup(setup=setup, teardown=teardown, scope='function')
diff --git a/testing/test_app.py b/testing/test_app.py
index 00cd744ba..2cfda14d0 100644
--- a/testing/test_app.py
+++ b/testing/test_app.py
@@ -26,6 +26,10 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
+"""
+GUI tests
+"""
+
from openlp.core import OpenLP
from openlp.core.ui.mainwindow import MainWindow
diff --git a/testing/test_songs_db.py b/testing/test_songs_db.py
new file mode 100644
index 000000000..4210b3fef
--- /dev/null
+++ b/testing/test_songs_db.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# -*- 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-2011 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
+# Armin Köhler, Joshua Millar, Stevan Pettit, Andreas Preikschat, Mattias #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 #
+###############################################################################
+
+"""
+Songs database tests
+"""
+
+from openlp.plugins.songs.lib.db import Author, Book, MediaFile, Song, Topic
+
+
+def test_empty_songdb(empty_songs_db):
+ g = empty_songs_db.get_all_objects
+ assert g(Author) == []
+ assert g(Book) == []
+ assert g(MediaFile) == []
+ assert g(Song) == []
+ assert g(Topic) == []
+ c = empty_songs_db.get_object_count
+ assert c(Author) == 0
+ assert c(Book) == 0
+ assert c(MediaFile) == 0
+ assert c(Song) == 0
+ assert c(Topic) == 0
+
+
+def test_nonexisting_class(empty_songs_db):
+ # test class not mapped to any sqlalchemy table
+ assert 0
From 445a0ccb498423819552b792316582a3be207b04 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sun, 4 Sep 2011 14:50:25 +0200
Subject: [PATCH 12/43] Add test for empty song database
---
testing/test_songs_db.py | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/testing/test_songs_db.py b/testing/test_songs_db.py
index 4210b3fef..ae8d78251 100644
--- a/testing/test_songs_db.py
+++ b/testing/test_songs_db.py
@@ -30,6 +30,10 @@
Songs database tests
"""
+import pytest
+from sqlalchemy.exc import InvalidRequestError
+from sqlalchemy.orm.exc import UnmappedInstanceError
+
from openlp.plugins.songs.lib.db import Author, Book, MediaFile, Song, Topic
@@ -48,6 +52,24 @@ def test_empty_songdb(empty_songs_db):
assert c(Topic) == 0
-def test_nonexisting_class(empty_songs_db):
+def test_unmapped_class(empty_songs_db):
# test class not mapped to any sqlalchemy table
- assert 0
+ class A(object):
+ pass
+ db = empty_songs_db
+ assert db.save_object(A()) == False
+ assert db.save_objects([A(), A()]) == False
+ # no key - new object instance is created from supplied class
+ assert type(db.get_object(A, key=None)) == A
+
+ with pytest.raises(InvalidRequestError):
+ db.get_object(A, key=1)
+ with pytest.raises(InvalidRequestError):
+ db.get_object_filtered(A, filter_clause=None)
+ with pytest.raises(InvalidRequestError):
+ db.get_all_objects(A)
+ with pytest.raises(InvalidRequestError):
+ db.get_object_count(A)
+
+ assert db.delete_object(A, key=None) == False
+ assert db.delete_all_objects(A) == False
From 57f8f5c54c51ef95dc867deb411dc61cffadcb94 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Mon, 5 Sep 2011 14:55:39 +0200
Subject: [PATCH 13/43] Add test function argument with database containing
some data
---
testing/conftest.py | 48 ++++++++++++++++++++++++++++++++++++---------
1 file changed, 39 insertions(+), 9 deletions(-)
diff --git a/testing/conftest.py b/testing/conftest.py
index d21ced22d..52f7984d8 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -30,10 +30,12 @@
Configuration file for pytest framework.
"""
+import os
import logging
import random
import string
+import py.path
from PyQt4 import QtCore
from sqlalchemy.orm import clear_mappers
@@ -41,6 +43,11 @@ from openlp.core import main as openlp_main
from openlp.core.lib.db import Manager
from openlp.plugins.songs.lib.db import init_schema
+TESTS_PATH = os.path.dirname(os.path.abspath(__file__))
+
+RESOURCES_PATH = os.path.join(TESTS_PATH, 'resources')
+SONGS_PATH = os.path.join(RESOURCES_PATH, 'songs')
+
# set up logging to stderr (console)
_handler = logging.StreamHandler(stream=None)
_handler.setFormatter(logging.Formatter(
@@ -65,19 +72,42 @@ def pytest_funcarg__openlpapp(request):
return request.cached_setup(setup=setup, teardown=teardown, scope='module')
-# Test function argument to make openlp gui instance persistent for all tests.
+def _get_unique_qsettings():
+ # unique QSettings group
+ unique = ''.join(random.choice(string.letters + string.digits)
+ for i in range(8))
+ group_name = 'test_%s' % unique
+ settings = QtCore.QSettings()
+ settings.beginGroup(group_name)
+ settings.setValue(u'db type', QtCore.QVariant(u'sqlite'))
+ settings.endGroup()
+ return group_name
+
+
+# Test function argument giving access to empty song database.
def pytest_funcarg__empty_songs_db(request):
def setup():
tmpdir = request.getfuncargvalue('tmpdir')
db_file_path = tmpdir.join('songs.sqlite')
- # unique QSettings group
- unique = ''.join(random.choice(string.letters + string.digits)
- for i in range(8))
- plugin_name = 'test_songs_%s' % unique
- settings = QtCore.QSettings()
- settings.beginGroup(plugin_name)
- settings.setValue(u'db type', QtCore.QVariant(u'sqlite'))
- settings.endGroup()
+ plugin_name = _get_unique_qsettings()
+ manager = Manager(plugin_name, init_schema,
+ db_file_path=db_file_path.strpath)
+ return manager
+ def teardown(manager):
+ # sqlalchemy allows to map classess to only one database at a time
+ clear_mappers()
+ return request.cached_setup(setup=setup, teardown=teardown, scope='function')
+
+
+# Test function argument giving access to song database.
+def pytest_funcarg__songs_db(request):
+ def setup():
+ tmpdir = request.getfuncargvalue('tmpdir')
+ db_file_path = tmpdir.join('songs.sqlite')
+ # copy test data to tmpdir
+ orig_db = py.path.local(SONGS_PATH).join('songs.sqlite')
+ orig_db.copy(db_file_path)
+ plugin_name = _get_unique_qsettings()
manager = Manager(plugin_name, init_schema,
db_file_path=db_file_path.strpath)
return manager
From d8642636ecc25b658cdea05e1203728a257b2abd Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Mon, 5 Sep 2011 14:57:35 +0200
Subject: [PATCH 14/43] add song example for tests
---
testing/resources/songs/openlyric_test_1.xml | 64 +++++++++++++++++++
testing/resources/songs/songs.sqlite | Bin 0 -> 30720 bytes
2 files changed, 64 insertions(+)
create mode 100644 testing/resources/songs/openlyric_test_1.xml
create mode 100644 testing/resources/songs/songs.sqlite
diff --git a/testing/resources/songs/openlyric_test_1.xml b/testing/resources/songs/openlyric_test_1.xml
new file mode 100644
index 000000000..240736878
--- /dev/null
+++ b/testing/resources/songs/openlyric_test_1.xml
@@ -0,0 +1,64 @@
+
+
+
+
+ Jezu Kriste, štědrý kněže
+
+
+ M. Jan Hus
+
+
+
+
+
+
+
+
+ Jezu Kriste, štědrý kněže,
+ s Otcem, Duchem jeden Bože,
+ štědrost Tvá je naše zboží,
+ z Tvé milosti.
+
+
+
+
+ Ty jsi v světě, bydlil s námi,
+ Tvé tělo trpělo rány
+ za nás za hříšné křesťany,
+ z Tvé milosti.
+
+
+
+
+ Ó, Tvá dobroto důstojná
+ a k nám milosti přehojná!
+ Dáváš nám bohatství mnohá
+ z Tvé milosti.
+
+
+
+
+ Ráčils nás sám zastoupiti,
+ život za nás položiti,
+ tak smrt věčnou zahladiti,
+ z Tvé milosti.
+
+
+
+
+ Ó, křesťané, z bludů vstaňme,
+ dané dobro nám poznejme,
+ k Synu Božímu chvátejme,
+ k té milosti!
+
+
+
+
+ Chvála budiž Bohu Otci,
+ Synu jeho téže moci,
+ Duchu jeho rovné moci,
+ z též milosti!
+
+
+
+
diff --git a/testing/resources/songs/songs.sqlite b/testing/resources/songs/songs.sqlite
new file mode 100644
index 0000000000000000000000000000000000000000..204764f4a95e62f7f6316aa23f3b1aed736e74f1
GIT binary patch
literal 30720
zcmeHQ&2JmW72n}XT8WM#+p@EDRn;RIhD4OML^-nJSeB(obRvJqE={K~jD%Qn*XD}L
zU75R-EuknN$3UHfVHEBqFnZ9ThXgQs=%J_jLh(Q7sX$Ie4nQ674o`G*Vg}mnB6HAJ@#0c4{$V_Umo6vq(zS~hqd43{>QiaQ$)#!pq}($-%fKJcCFVbxON>rT
zPDZ$9n&lYBy4k=0RIuEx7|6GX6!pjvbniNqFK_6kmbz(z)dns@oP>dj7S>Dp;0LtM
zQoXg6+Hix71eBoY1kL`ALCw#fC&bV|7npW^eQ;8`;nXGfl4;mMPT~*<2n5;<0W$xQ
z{1*vc;t&W31a=<+1E@nf-Fx12JtJ+KnOy0QIH%be)3HmN7M=g-V+mg3cw-1WJdE~B
zXtDRvST8xG#V^jDIDyU^>qUH_Kn5a;%bVWT6TLtN!`^yQer8Ocy_>f%`9=XFb7mxV
ze0&5Ob_Slc?OSI?t|iYMdvE0IOn90+=2gjtfoynYnmxkLOw;cpm^>q%8D1TRr}ea9
zof(b+#n6p;c#5C9(OQOR*u|H3AX{<`FS{mQ#qR3X6F?`5(`&kA
zTG+*4s(CXSrkz8LmA^FH@^3YJ?LUz0
zL_o6A<0#WuchUvNbFg0io$EQvz%#7j92KvkG+qI&Sz0#~UMOt>P~}ZVk#@40=X$H9
zr#NprS!n#)c)c5tDpA_pdT3e{9T&R*5$F$M6<16Tz$!m8R~?T)LZMl4EEptO<7qkU
z<_lo%gW~!BjZJ#dkw9R_AwcH8EIp8<2Plc2OZOWn@P>VJ4LCs2j`pc=j<5fJ|CR8y
zHg>2|2U_E*UF&Nl^S@JiBuS4@cSs4%hF-uofy3~R9bm8-Y!$=!-NGJ-yjQ{O1iW}SkYBG9_r%^uD_TB*9zp{R!rp^Zh_
z+H+RG?at1f96c**;Yw}o?QP)G{duRN9zBXaKV9`XyD7$n%I;rxb5aCrh%D5AY982t
zZx&V{h%SLu39gcp$L}SID@~gC(!t5T-HpyX=)}{@x!$WC1?ib`zRP{8t4qCbwAnZZ
z^-RTrcRd?ZilPn-pnKeS`@s_r}-k
zobBAP528+~zklewW`9(4FCE8lf#HhGeF2+KzLwC}=-bd|p+})_nG1Mw%^K`wOEi_#hSIZr{2SR@A{kv@z%J;bM^tl{ki8ZsodCN|t+vbH>_m
zbxR+=11sFKs#!&Y?i2L4Th&5?XhT?ce(jKo{)^b!c{C!h6Q(F9a}%s&6Wk>qc76h=Z=fq+2Z
zWe5z*i>=>BX8rYl@%;ZXC`G+MV0R%z%D^xZ^sZqwT*rV9q5Q4#BT}=g-HB=H&Ud~
zRHzvGgM38(75WbS0t(veD5{D&If6C@XrvVvDM)_r2!6weEBg@_aK%;mm;sz{9F=bfg9a%Wor49|C8yrVd_c{HrArBT+
z=abO*x9PoI>Y$3Q(q3o`njcK(9^iVYR0TrZ;AdxEC!mx@g)#wH1S#deykAj|L6~VD
z6@XQ+*ip)nMBe#fqwjF?Y^SWsRShz&LRN`3jr?yzTJr}K_4Gg`fVN@)!5=vSr2XO_
zaJn_24VVQU4fYeo?TSEU
z|1bIc7xTYetw8AAt_W1-KhfXAkZ)n@q3skQSwPm(bebgKwNn#J22vA{gbCH`3#G53
z5OkQfijXw{uezRAevpT361qlmG|-F+EIq=Cvu+#9q$-E6uGvMJcB1q&U&NU#L^67n
zIyE
zpuFI$5`kGc1W^*QO45g+pcrG7bR1*k%j8VUNY2@owQnZQljh>UkQRhbudyw=28#-9
zkX)mhL84i5jao#hCl#7J6ErEd#g6mVx!w$ujELKsWv$
z0W+?*V%*e-AuEVWY?x(6;cOVH0w#=^b=Qm%vV;^#c(2Ji*lv?F=C|G?iReL##1rGO
w*s<|b$HpgdEcX7yxw2O$@~}g0hwc}=~3FnK3+L}D=`sq>{0~&4Tjmf`2YX_
literal 0
HcmV?d00001
From 15b63b82796d71e2cdd44a2baffa7c70e6ef0b02 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Mon, 5 Sep 2011 14:58:48 +0200
Subject: [PATCH 15/43] add openlyrics validator
---
.../openlyrics/openlyrics_schema.rng | 467 ++++++++++++++++++
testing/resources/openlyrics/validate.py | 26 +
2 files changed, 493 insertions(+)
create mode 100644 testing/resources/openlyrics/openlyrics_schema.rng
create mode 100755 testing/resources/openlyrics/validate.py
diff --git a/testing/resources/openlyrics/openlyrics_schema.rng b/testing/resources/openlyrics/openlyrics_schema.rng
new file mode 100644
index 000000000..9df99ecc5
--- /dev/null
+++ b/testing/resources/openlyrics/openlyrics_schema.rng
@@ -0,0 +1,467 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ words
+ music
+
+
+
+
+
+ translation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -99
+ 99
+
+
+
+
+
+
+
+
+
+
+ 30
+ 250
+
+
+ bpm
+
+
+
+
+
+
+ text
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 999
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ optional
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [0-9]+\.[0-9]+(\.[0-9]+)?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+ (v[1-9]\d?[a-z]?)|([cpb][a-z]?)|([cpbe][1-9]\d?[a-z]?)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
diff --git a/testing/resources/openlyrics/validate.py b/testing/resources/openlyrics/validate.py
new file mode 100755
index 000000000..116f69454
--- /dev/null
+++ b/testing/resources/openlyrics/validate.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+import sys
+
+try:
+ from lxml import etree
+except ImportError:
+ print('Python module "lxml" is required')
+ exit(1)
+
+
+if len(sys.argv) != 3:
+ print('Usage: python %s openlyrics_schema.rng xmlfile.xml' % __file__)
+ exit(1)
+
+
+relaxng_file = sys.argv[1]
+xml_file = sys.argv[2]
+
+relaxng_doc = etree.parse(relaxng_file)
+xml_doc = etree.parse(xml_file)
+
+relaxng = etree.RelaxNG(relaxng_doc)
+
+relaxng.assertValid(xml_doc)
+
From e491e779811161420d4b5abbdcd822bb33ef46a9 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Tue, 6 Sep 2011 00:31:09 +0200
Subject: [PATCH 16/43] add test for openlyrics export
---
testing/conftest.py | 41 +++++++++++++++++++
...lyric_test_1.xml => openlyrics_test_1.xml} | 0
testing/test_app.py | 8 ++--
3 files changed, 45 insertions(+), 4 deletions(-)
rename testing/resources/songs/{openlyric_test_1.xml => openlyrics_test_1.xml} (100%)
diff --git a/testing/conftest.py b/testing/conftest.py
index 52f7984d8..bbcbeac3f 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -31,6 +31,8 @@ Configuration file for pytest framework.
"""
import os
+import sys
+import subprocess
import logging
import random
import string
@@ -58,6 +60,18 @@ log.addHandler(_handler)
log.setLevel(logging.DEBUG)
+# Paths with resources for tests
+def pytest_funcarg__pth(request):
+ def setup():
+ class Pth(object):
+ def __init__(self):
+ self.tests = py.path.local(TESTS_PATH)
+ self.resources = py.path.local(RESOURCES_PATH)
+ self.songs = py.path.local(SONGS_PATH)
+ return Pth()
+ return request.cached_setup(setup=setup, scope='module')
+
+
# Test function argument to make openlp gui instance persistent for all tests.
# Test cases in module have to access the same instance. To allow creating
# multiple
@@ -115,3 +129,30 @@ def pytest_funcarg__songs_db(request):
# sqlalchemy allows to map classess to only one database at a time
clear_mappers()
return request.cached_setup(setup=setup, teardown=teardown, scope='function')
+
+
+class OpenLyricsValidator(object):
+ """Validate xml if it conformns to OpenLyrics xml schema."""
+ def __init__(self, script, schema):
+ self.cmd = [sys.executable, script, schema]
+
+ def validate(self, file_path):
+ self.cmd.append(file_path)
+ print self.cmd
+ retcode = subprocess.call(self.cmd)
+ if retcode == 0:
+ # xml conforms to schema
+ return True
+ else:
+ # xml has invalid syntax
+ return False
+
+
+# Test function argument giving access to song database.
+def pytest_funcarg__openlyrics_validator(request):
+ def setup():
+ script = os.path.join(RESOURCES_PATH, 'openlyrics', 'validate.py')
+ schema = os.path.join(RESOURCES_PATH, 'openlyrics',
+ 'openlyrics_schema.rng')
+ return OpenLyricsValidator(script, schema)
+ return request.cached_setup(setup=setup, scope='session')
diff --git a/testing/resources/songs/openlyric_test_1.xml b/testing/resources/songs/openlyrics_test_1.xml
similarity index 100%
rename from testing/resources/songs/openlyric_test_1.xml
rename to testing/resources/songs/openlyrics_test_1.xml
diff --git a/testing/test_app.py b/testing/test_app.py
index 2cfda14d0..04030d95e 100644
--- a/testing/test_app.py
+++ b/testing/test_app.py
@@ -34,7 +34,7 @@ from openlp.core import OpenLP
from openlp.core.ui.mainwindow import MainWindow
-def test_start_app(openlpapp):
- assert type(openlpapp) == OpenLP
- assert type(openlpapp.mainWindow) == MainWindow
- assert unicode(openlpapp.mainWindow.windowTitle()) == u'OpenLP 2.0'
+#def test_start_app(openlpapp):
+ #assert type(openlpapp) == OpenLP
+ #assert type(openlpapp.mainWindow) == MainWindow
+ #assert unicode(openlpapp.mainWindow.windowTitle()) == u'OpenLP 2.0'
From b15d65feba7442c8b882e90c70508c3eaf363687 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Tue, 6 Sep 2011 12:49:15 +0200
Subject: [PATCH 17/43] add tests for openlyrics
---
testing/test_openlyrics.py | 52 ++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 testing/test_openlyrics.py
diff --git a/testing/test_openlyrics.py b/testing/test_openlyrics.py
new file mode 100644
index 000000000..15ce459e1
--- /dev/null
+++ b/testing/test_openlyrics.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- 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-2011 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
+# Armin Köhler, Joshua Millar, Stevan Pettit, Andreas Preikschat, Mattias #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 #
+###############################################################################
+
+"""
+OpenLyrics import/export tests
+"""
+
+from lxml import etree
+
+from openlp.plugins.songs.lib.db import Song
+from openlp.plugins.songs.lib import OpenLyrics
+
+def test_openlyrics_export(songs_db, openlyrics_validator, pth, tmpdir):
+ # export song to file
+ f = tmpdir.join('out.xml')
+ db = songs_db
+ s = db.get_all_objects(Song)[0]
+ o = OpenLyrics(db)
+ xml = o.song_to_xml(s)
+ tree = etree.ElementTree(etree.fromstring(xml))
+ tree.write(open(f.strpath, u'w'), encoding=u'utf-8', xml_declaration=True,
+ pretty_print=True)
+ # validate file
+ assert openlyrics_validator.validate(f.strpath) == True
+ # string comparison with original file
+ f_orig = pth.songs.join('openlyrics_test_1.xml')
+ assert f.read() == f_orig.read()
From 5d2bde01c26c2d03c0f3ab023210bb8754d66058 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Tue, 6 Sep 2011 15:14:57 +0200
Subject: [PATCH 18/43] For openlyrics export skip adding format tags if not
used.
---
openlp/plugins/songs/lib/xml.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 8f1d87720..671fc9032 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -61,7 +61,6 @@ The XML of an `OpenLyrics `_ song looks like this::
"""
-import datetime
import logging
import re
@@ -303,9 +302,13 @@ class OpenLyrics(object):
for topic in song.topics:
self._add_text_to_element(u'theme', themes, topic.name)
# Process the formatting tags.
- format = etree.SubElement(song_xml, u'format')
- tags = etree.SubElement(format, u'tags')
- tags.set(u'application', u'OpenLP')
+ # have we any tags in song lyrics?
+ match = re.match(u'.*\{/?\w+\}', song.lyrics)
+ if match:
+ # named 'formatting' - 'format' is built-in fuction in Python
+ formatting = etree.SubElement(song_xml, u'format')
+ tags = etree.SubElement(formatting, u'tags')
+ tags.set(u'application', u'OpenLP')
# Process the song's lyrics.
lyrics = etree.SubElement(song_xml, u'lyrics')
verse_list = sxml.get_verses(song.lyrics)
From 6163bc99c31aa08801db01f0e5febaee3b69e144 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Tue, 6 Sep 2011 15:22:17 +0200
Subject: [PATCH 19/43] Do not use depracated dict.has_key() function in
openlyrics code
---
openlp/plugins/songs/lib/xml.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 671fc9032..0dd248fe8 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -73,6 +73,7 @@ from openlp.core.utils import get_application_version
log = logging.getLogger(__name__)
+
class SongXML(object):
"""
This class builds and parses the XML used to describe songs.
@@ -250,6 +251,7 @@ class OpenLyrics(object):
"""
IMPLEMENTED_VERSION = u'0.8'
+
def __init__(self, manager):
self.manager = manager
@@ -318,7 +320,7 @@ class OpenLyrics(object):
verse_def = verse_tag + verse_number
verse_element = \
self._add_text_to_element(u'verse', lyrics, None, verse_def)
- if verse[0].has_key(u'lang'):
+ 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'[---]')
@@ -549,10 +551,10 @@ class OpenLyrics(object):
# 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 \
- verses.has_key((verse_tag, verse_number, lang)):
+ (verse_tag, verse_number, lang) in verses:
verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text
# Merge v1a, v1b, .... to v1.
- elif verses.has_key((verse_tag, verse_number, lang)):
+ 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
From 39946ccbe259aa7779218befd430b94f2bb1a098 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Tue, 6 Sep 2011 23:02:41 +0200
Subject: [PATCH 20/43] Add formatting tags to song example data
---
testing/resources/songs/songs.sqlite | Bin 30720 -> 30720 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/testing/resources/songs/songs.sqlite b/testing/resources/songs/songs.sqlite
index 204764f4a95e62f7f6316aa23f3b1aed736e74f1..f1ae584af22579ee8b55de2c80ce6a96aaae4e43 100644
GIT binary patch
delta 835
zcmd5)&ubGw6yB8;r0r(g3SwJ{&mygm&=vcO7D}xYViXaiC&5G0UE`WfW;-(}Y=(hc
zddZ;_=HQ{H94jcw_9R%4ZTM{2}+du?yc95u|cz0YhNM!B;(J8P=GomU;+Qz~E|DMtF6%)^7!QPt`HcD!O{#WoXi$T1#2KR6%e}C(I(v%^
z1yMnbJHuB+RReaxBNa+G5pGqg1*d#dGogYOXgWA&n9*7??u_MwQ?N9@`tFR`8T$a=
ztO2W2WkvUsQ|Ha%lij@4Sv7&;Qs_u6a|Elu>;MLp5QsJdyvz$-pE(gjMH>OJ`dYe?
zoR+TKN!Ftkv-Iy!Z%-3o9Jt;UPXYnI*jcBbwQ!UXsvq0Wr*)wx@jN#MAWfjEZ+|-p
zRprr#*AXp-*4g~UaEu)46NNlN_l|=d!lAVZjkL=kA|b9C@BnF(pVTr}7U169<+-Kh
U`6c})v6;R@y#G<|X!HH0U!4*?t^fc4
delta 536
zcmZqpz}WDCae}m7qwo0Tz)fiY~eAd3&9P_I0r5CfwpZ@)e-2rzhdPCmxd
zG5HjaoU9I4v4Vd|a%!%Qf=g*~Mry7?R%%LWo`O^U(S50t7x1c0zQV6DIf0*jG9Mog
zQ%L1xRlZob&@Mg!o{+M`3l&oGlZx_7@+aTr^PGH%Ukj+-Nb%^t%(DCvg{nk_yu%BN
z6$>8-|8eKgn7oJI0pz;P+y!QglOKA^5;8>s&6J${;*v}Sg_6q2
zmkXn0p+-Xm4{rw9h45WSkp~~#da#?caZ?3DLn|XwAOb}aEv*YG
LUB@(8fyDs;X{*Nk
From 9fb6936263c2b1d85978c1857ba39ccb6e118ad3 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Wed, 7 Sep 2011 22:54:53 +0200
Subject: [PATCH 21/43] code to add formatting tags to openlyrics lyrics
---
openlp/plugins/songs/lib/xml.py | 30 ++++++++++++++++++++++++++----
testing/test_openlyrics.py | 10 +++++++---
2 files changed, 33 insertions(+), 7 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 0dd248fe8..a20951753 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -254,6 +254,8 @@ class OpenLyrics(object):
def __init__(self, manager):
self.manager = manager
+ self.start_tags_regex = re.compile(r'\{\w+\}') # {abc}
+ self.end_tags_regex = re.compile(r'\{\/\w+\}') # {/abc}
def song_to_xml(self, song):
"""
@@ -305,12 +307,13 @@ class OpenLyrics(object):
self._add_text_to_element(u'theme', themes, topic.name)
# Process the formatting tags.
# have we any tags in song lyrics?
- match = re.match(u'.*\{/?\w+\}', song.lyrics)
+ tags_element = None
+ match = re.search(u'\{/?\w+\}', song.lyrics, re.UNICODE)
if match:
# named 'formatting' - 'format' is built-in fuction in Python
formatting = etree.SubElement(song_xml, u'format')
- tags = etree.SubElement(formatting, u'tags')
- tags.set(u'application', u'OpenLP')
+ tags_element = etree.SubElement(formatting, 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)
@@ -331,7 +334,13 @@ class OpenLyrics(object):
if index < len(virtual_verses) - 1:
lines_element.set(u'break', u'optional')
for line in virtual_verse.strip(u'\n').split(u'\n'):
- self._add_text_to_element(u'line', lines_element, line)
+ # 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)
return self._extract_xml(song_xml)
def xml_to_song(self, xml, only_process_format_tags=False):
@@ -389,6 +398,19 @@ class OpenLyrics(object):
parent.append(element)
return element
+ 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'' % t[1:-1])
+ # replace end tags
+ for t in end_tags:
+ text = text.replace(t, u'')
+ text = u'' + text + u''
+ element = etree.XML(text)
+ parent.append(element)
+
def _extract_xml(self, xml):
"""
Extract our newly created XML song.
diff --git a/testing/test_openlyrics.py b/testing/test_openlyrics.py
index 15ce459e1..2d4ce024e 100644
--- a/testing/test_openlyrics.py
+++ b/testing/test_openlyrics.py
@@ -46,7 +46,11 @@ def test_openlyrics_export(songs_db, openlyrics_validator, pth, tmpdir):
tree.write(open(f.strpath, u'w'), encoding=u'utf-8', xml_declaration=True,
pretty_print=True)
# validate file
- assert openlyrics_validator.validate(f.strpath) == True
- # string comparison with original file
+ #assert openlyrics_validator.validate(f.strpath) == True
+ # string comparison with original file line by line
f_orig = pth.songs.join('openlyrics_test_1.xml')
- assert f.read() == f_orig.read()
+ for l, l_orig in zip(f.readlines(), f_orig.readlines()):
+ # skip line with item modifiedDate - it is unique everytime
+ if l.startswith('' % t[1:-1])
+ for tag in start_tags:
+ name = tag[1:-1]
+ text = text.replace(tag, u'' % name)
+ # add tag to elment if tag not present
+ if name not in xml_tags:
+ self._add_tag_to_formatting(name, tags_element)
# replace end tags
for t in end_tags:
text = text.replace(t, u'')
diff --git a/testing/test_openlyrics.py b/testing/test_openlyrics.py
index 2d4ce024e..32bde3fa0 100644
--- a/testing/test_openlyrics.py
+++ b/testing/test_openlyrics.py
@@ -46,7 +46,7 @@ def test_openlyrics_export(songs_db, openlyrics_validator, pth, tmpdir):
tree.write(open(f.strpath, u'w'), encoding=u'utf-8', xml_declaration=True,
pretty_print=True)
# validate file
- #assert openlyrics_validator.validate(f.strpath) == True
+ assert openlyrics_validator.validate(f.strpath) == True
# string comparison with original file line by line
f_orig = pth.songs.join('openlyrics_test_1.xml')
for l, l_orig in zip(f.readlines(), f_orig.readlines()):
From 11876791a410cf77eff62be41768aa9d4866cc9b Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Thu, 8 Sep 2011 14:11:13 +0200
Subject: [PATCH 23/43] update openlyrics schema to allow nested formatting
tags
---
.../openlyrics/openlyrics_schema.rng | 7 ++-
testing/resources/songs/openlyrics_test_1.xml | 56 ++++++++++++++++---
2 files changed, 53 insertions(+), 10 deletions(-)
diff --git a/testing/resources/openlyrics/openlyrics_schema.rng b/testing/resources/openlyrics/openlyrics_schema.rng
index 9df99ecc5..b4a7813fb 100644
--- a/testing/resources/openlyrics/openlyrics_schema.rng
+++ b/testing/resources/openlyrics/openlyrics_schema.rng
@@ -399,7 +399,12 @@
-
+
+
+
+
+
+
diff --git a/testing/resources/songs/openlyrics_test_1.xml b/testing/resources/songs/openlyrics_test_1.xml
index 240736878..d7a9c12ec 100644
--- a/testing/resources/songs/openlyrics_test_1.xml
+++ b/testing/resources/songs/openlyrics_test_1.xml
@@ -1,5 +1,5 @@
-
+
Jezu Kriste, štědrý kněže
@@ -11,35 +11,73 @@
+
+
+
+ <span style="-webkit-text-fill-color:red">
+ </span>
+
+
+ <span style="-webkit-text-fill-color:blue">
+ </span>
+
+
+ <span style="-webkit-text-fill-color:yellow">
+ </span>
+
+
+ <span style="-webkit-text-fill-color:#FFA500">
+ </span>
+
+
+ <strong>
+ </strong>
+
+
+ <em>
+ </em>
+
+
+ <span style="-webkit-text-fill-color:green">
+ </span>
+
+
+
- Jezu Kriste, štědrý kněže,
- s Otcem, Duchem jeden Bože,
+ Jezu Kriste, štědrý kněže,
+ s Otcem, Duchem jeden Bože,
štědrost Tvá je naše zboží,
- z Tvé milosti.
+ z Tvé milosti.
- Ty jsi v světě, bydlil s námi,
+ Ty jsi v světě, bydlil s námi,
Tvé tělo trpělo rány
za nás za hříšné křesťany,
- z Tvé milosti.
+ z Tvé milosti.
- Ó, Tvá dobroto důstojná
+ Ó, Tvá dobroto důstojná
a k nám milosti přehojná!
Dáváš nám bohatství mnohá
- z Tvé milosti.
+
+
+ z Tvé milosti.
+
+
Ráčils nás sám zastoupiti,
- život za nás položiti,
+
+ život za nás položiti,
+
tak smrt věčnou zahladiti,
z Tvé milosti.
From 5c13123ceff7e3c5ab77c77688d84b02f7a76bab Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sat, 10 Sep 2011 22:15:23 +0200
Subject: [PATCH 24/43] Delete print statement
---
openlp/plugins/songs/lib/xml.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 52f9b5292..573e9b866 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -401,7 +401,6 @@ class OpenLyrics(object):
return element
def _add_tag_to_formatting(self, tag_name, tags_element):
- print '------'
available_tags = FormattingTags.get_html_tags()
start_tag = '{%s}' % tag_name
for t in available_tags:
From 606e19734c0445b3592f30532f006709d982e92a Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sun, 11 Sep 2011 22:29:25 +0200
Subject: [PATCH 25/43] Allow to override data dir for tests
---
openlp/core/utils/__init__.py | 5 +++++
1 file changed, 5 insertions(+)
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)
From 50e1964795a75ae18635181bfad43fe13682cfe8 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sun, 11 Sep 2011 22:39:33 +0200
Subject: [PATCH 26/43] Remove the db_file_path option from db.Manager init
function
---
openlp/core/lib/db.py | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py
index 7635873ad..1b8d086df 100644
--- a/openlp/core/lib/db.py
+++ b/openlp/core/lib/db.py
@@ -159,7 +159,7 @@ class Manager(object):
Provide generic object persistence management
"""
def __init__(self, plugin_name, init_schema, db_file_name=None,
- db_file_path=None, upgrade_mod=None):
+ upgrade_mod=None):
"""
Runs the initialisation process that includes creating the connection
to the database and the tables if they don't exist.
@@ -176,10 +176,6 @@ class Manager(object):
``db_file_name``
The file name to use for this database. Defaults to None resulting
in the plugin_name being used.
-
- ``db_file_path``
- The path to sqlite file to use for this database. This is useful
- for testing purposes.
"""
settings = QtCore.QSettings()
settings.beginGroup(plugin_name)
@@ -188,11 +184,7 @@ class Manager(object):
db_type = unicode(
settings.value(u'db type', QtCore.QVariant(u'sqlite')).toString())
if db_type == u'sqlite':
- # For automated tests we need to supply file_path directly
- if db_file_path:
- self.db_url = u'sqlite:///%s' % os.path.normpath(
- os.path.abspath(db_file_path))
- elif db_file_name:
+ if db_file_name:
self.db_url = u'sqlite:///%s/%s' % (
AppLocation.get_section_data_path(plugin_name),
db_file_name)
From eef786f85c1eedf5194f736ec1ed2fa0ddfbf0b1 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sun, 11 Sep 2011 22:40:30 +0200
Subject: [PATCH 27/43] update tests to work with latest changes
---
testing/conftest.py | 42 +++++++++++++++++++++---------------------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/testing/conftest.py b/testing/conftest.py
index bbcbeac3f..d92702fa1 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -34,14 +34,13 @@ import os
import sys
import subprocess
import logging
-import random
-import string
import py.path
from PyQt4 import QtCore
from sqlalchemy.orm import clear_mappers
from openlp.core import main as openlp_main
+from openlp.core.utils import AppLocation
from openlp.core.lib.db import Manager
from openlp.plugins.songs.lib.db import init_schema
@@ -86,30 +85,29 @@ def pytest_funcarg__openlpapp(request):
return request.cached_setup(setup=setup, teardown=teardown, scope='module')
-def _get_unique_qsettings():
- # unique QSettings group
- unique = ''.join(random.choice(string.letters + string.digits)
- for i in range(8))
- group_name = 'test_%s' % unique
- settings = QtCore.QSettings()
- settings.beginGroup(group_name)
- settings.setValue(u'db type', QtCore.QVariant(u'sqlite'))
- settings.endGroup()
- return group_name
+# Clean up QSettings for all plugins
+def _cleanup_qsettings():
+ s = QtCore.QSettings()
+ keys = s.allKeys()
+ for k in keys:
+ s.setValue(k, QtCore.QVariant(None))
# Test function argument giving access to empty song database.
def pytest_funcarg__empty_songs_db(request):
+ #def get_data_dir(section):
+ # return request.getfuncargvalue('tmpdir').strpath
def setup():
tmpdir = request.getfuncargvalue('tmpdir')
- db_file_path = tmpdir.join('songs.sqlite')
- plugin_name = _get_unique_qsettings()
- manager = Manager(plugin_name, init_schema,
- db_file_path=db_file_path.strpath)
+ # override data dir
+ AppLocation.BaseDir = tmpdir.strpath
+ manager = Manager('songs', init_schema)
return manager
def teardown(manager):
+ _cleanup_qsettings()
# sqlalchemy allows to map classess to only one database at a time
clear_mappers()
+ AppLocation.BaseDir = None
return request.cached_setup(setup=setup, teardown=teardown, scope='function')
@@ -117,17 +115,19 @@ def pytest_funcarg__empty_songs_db(request):
def pytest_funcarg__songs_db(request):
def setup():
tmpdir = request.getfuncargvalue('tmpdir')
- db_file_path = tmpdir.join('songs.sqlite')
+ # override data dir
+ AppLocation.BaseDir = tmpdir.strpath
+ datadir = tmpdir.mkdir(u'data').mkdir( u'songs')
# copy test data to tmpdir
orig_db = py.path.local(SONGS_PATH).join('songs.sqlite')
- orig_db.copy(db_file_path)
- plugin_name = _get_unique_qsettings()
- manager = Manager(plugin_name, init_schema,
- db_file_path=db_file_path.strpath)
+ orig_db.copy(datadir)
+ manager = Manager('songs', init_schema)
return manager
def teardown(manager):
+ _cleanup_qsettings()
# sqlalchemy allows to map classess to only one database at a time
clear_mappers()
+ AppLocation.BaseDir = None
return request.cached_setup(setup=setup, teardown=teardown, scope='function')
From 655245051a0da4f56f195bb7c39db5c2c3d3df48 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sun, 11 Sep 2011 23:53:19 +0200
Subject: [PATCH 28/43] Consolidating and restructuring test code
---
testing/conftest.py | 138 ++++++++++++++++++++-----------------
testing/test_app.py | 11 +--
testing/test_openlyrics.py | 5 +-
testing/test_songs_db.py | 11 +--
4 files changed, 89 insertions(+), 76 deletions(-)
diff --git a/testing/conftest.py b/testing/conftest.py
index d92702fa1..0ba4f34ed 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -49,14 +49,73 @@ TESTS_PATH = os.path.dirname(os.path.abspath(__file__))
RESOURCES_PATH = os.path.join(TESTS_PATH, 'resources')
SONGS_PATH = os.path.join(RESOURCES_PATH, 'songs')
-# set up logging to stderr (console)
-_handler = logging.StreamHandler(stream=None)
-_handler.setFormatter(logging.Formatter(
- u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
-logging.addLevelName(15, u'Timer')
-log = logging.getLogger()
-log.addHandler(_handler)
-log.setLevel(logging.DEBUG)
+
+# class to setup and teardown settings for running openlp tests
+class OpenLPRunner(object):
+ def __init__(self, tmpdir):
+ self.tmpdir = tmpdir
+ self._setup_qapp()
+ self._setup_logging()
+ self._cleanup_qsettings()
+ # override data dir of OpenLP - it points to tmpdir of a test case
+ AppLocation.BaseDir = tmpdir.strpath
+
+ def _setup_qapp(self):
+ QtCore.QCoreApplication.setOrganizationName(u'OpenLP')
+ QtCore.QCoreApplication.setOrganizationDomain(u'openlp.org')
+ QtCore.QCoreApplication.setApplicationName(u'TestOpenLP')
+
+ def _setup_logging(self):
+ # set up logging to stderr/stdout (console)
+ _handler = logging.StreamHandler(stream=None)
+ _handler.setFormatter(logging.Formatter(
+ u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
+ logging.addLevelName(15, u'Timer')
+ log = logging.getLogger()
+ log.addHandler(_handler)
+ log.setLevel(logging.DEBUG)
+
+ def _cleanup_qsettings(self):
+ # Clean up QSettings for all plugins
+ # The issue with QSettings is that is global for a running process
+ # and thus it is necessary to clean it before another test case.
+ # If it would not be cleaned up it could be saved in the system.
+ s = QtCore.QSettings()
+ keys = s.allKeys()
+ for k in keys:
+ s.setValue(k, None)
+
+ ## Public interface
+
+ def get_songs_db(self, empty=False):
+ # return initialized db Manager with empty db or db containing
+ # some example songs
+
+ if not empty:
+ # copy test data to tmpdir
+ datadir = self.tmpdir.mkdir(u'data').mkdir(u'songs')
+ orig_db = py.path.local(SONGS_PATH).join('songs.sqlite')
+ orig_db.copy(datadir)
+
+ manager = Manager('songs', init_schema)
+ return manager
+
+ def get_app(self):
+ # return QGui.QApplication of OpenLP - this object allows
+ # running different gui tests and allows access to gui objects
+ # (e.g MainWindow etc.)
+ # To allow creating multiple instances of OpenLP in one process
+ # it would be necessary use diffrent configuration and data files.
+ # Created instance will use your OpenLP settings.
+ return openlp_main(['--testing'])
+
+ def teardown(self):
+ # clean up code to run after running the test case
+ self._cleanup_qsettings()
+ # sqlalchemy allows to map classess to only one database at a time
+ clear_mappers()
+ # set data dir to original value
+ AppLocation.BaseDir = None
# Paths with resources for tests
@@ -68,66 +127,15 @@ def pytest_funcarg__pth(request):
self.resources = py.path.local(RESOURCES_PATH)
self.songs = py.path.local(SONGS_PATH)
return Pth()
- return request.cached_setup(setup=setup, scope='module')
+ return request.cached_setup(setup=setup, scope='session')
-# Test function argument to make openlp gui instance persistent for all tests.
-# Test cases in module have to access the same instance. To allow creating
-# multiple
-# instances it would be necessary use diffrent configuraion and data files.
-# Created instance will use your OpenLP settings.
-def pytest_funcarg__openlpapp(request):
+# Test function argument giving access to OpenLP runner
+def pytest_funcarg__openlp_runner(request):
def setup():
- return openlp_main(['--testing'])
- def teardown(app):
- # sqlalchemy allows to map classess to only one database at a time
- clear_mappers()
- return request.cached_setup(setup=setup, teardown=teardown, scope='module')
-
-
-# Clean up QSettings for all plugins
-def _cleanup_qsettings():
- s = QtCore.QSettings()
- keys = s.allKeys()
- for k in keys:
- s.setValue(k, QtCore.QVariant(None))
-
-
-# Test function argument giving access to empty song database.
-def pytest_funcarg__empty_songs_db(request):
- #def get_data_dir(section):
- # return request.getfuncargvalue('tmpdir').strpath
- def setup():
- tmpdir = request.getfuncargvalue('tmpdir')
- # override data dir
- AppLocation.BaseDir = tmpdir.strpath
- manager = Manager('songs', init_schema)
- return manager
- def teardown(manager):
- _cleanup_qsettings()
- # sqlalchemy allows to map classess to only one database at a time
- clear_mappers()
- AppLocation.BaseDir = None
- return request.cached_setup(setup=setup, teardown=teardown, scope='function')
-
-
-# Test function argument giving access to song database.
-def pytest_funcarg__songs_db(request):
- def setup():
- tmpdir = request.getfuncargvalue('tmpdir')
- # override data dir
- AppLocation.BaseDir = tmpdir.strpath
- datadir = tmpdir.mkdir(u'data').mkdir( u'songs')
- # copy test data to tmpdir
- orig_db = py.path.local(SONGS_PATH).join('songs.sqlite')
- orig_db.copy(datadir)
- manager = Manager('songs', init_schema)
- return manager
- def teardown(manager):
- _cleanup_qsettings()
- # sqlalchemy allows to map classess to only one database at a time
- clear_mappers()
- AppLocation.BaseDir = None
+ return OpenLPRunner(request.getfuncargvalue('tmpdir'))
+ def teardown(openlp_runner):
+ openlp_runner.teardown()
return request.cached_setup(setup=setup, teardown=teardown, scope='function')
diff --git a/testing/test_app.py b/testing/test_app.py
index 04030d95e..2c7b01d7d 100644
--- a/testing/test_app.py
+++ b/testing/test_app.py
@@ -34,7 +34,10 @@ from openlp.core import OpenLP
from openlp.core.ui.mainwindow import MainWindow
-#def test_start_app(openlpapp):
- #assert type(openlpapp) == OpenLP
- #assert type(openlpapp.mainWindow) == MainWindow
- #assert unicode(openlpapp.mainWindow.windowTitle()) == u'OpenLP 2.0'
+# TODO Uncommend when using custom OpenLP configuration is implemented.
+# Otherwise it would mess up user's OpenLP settings
+#def test_start_app(openlp_runner):
+ #app = openlp_runner.get_app()
+ #assert type(app) == OpenLP
+ #assert type(app.mainWindow) == MainWindow
+ #assert unicode(app.mainWindow.windowTitle()) == u'OpenLP 2.0'
diff --git a/testing/test_openlyrics.py b/testing/test_openlyrics.py
index 32bde3fa0..bf53f053d 100644
--- a/testing/test_openlyrics.py
+++ b/testing/test_openlyrics.py
@@ -35,10 +35,11 @@ from lxml import etree
from openlp.plugins.songs.lib.db import Song
from openlp.plugins.songs.lib import OpenLyrics
-def test_openlyrics_export(songs_db, openlyrics_validator, pth, tmpdir):
+
+def test_openlyrics_export(openlp_runner, openlyrics_validator, pth, tmpdir):
# export song to file
f = tmpdir.join('out.xml')
- db = songs_db
+ db = openlp_runner.get_songs_db()
s = db.get_all_objects(Song)[0]
o = OpenLyrics(db)
xml = o.song_to_xml(s)
diff --git a/testing/test_songs_db.py b/testing/test_songs_db.py
index ae8d78251..ace6929b5 100644
--- a/testing/test_songs_db.py
+++ b/testing/test_songs_db.py
@@ -37,14 +37,15 @@ from sqlalchemy.orm.exc import UnmappedInstanceError
from openlp.plugins.songs.lib.db import Author, Book, MediaFile, Song, Topic
-def test_empty_songdb(empty_songs_db):
- g = empty_songs_db.get_all_objects
+def test_empty_songdb(openlp_runner):
+ db = openlp_runner.get_songs_db(empty=True)
+ g = db.get_all_objects
assert g(Author) == []
assert g(Book) == []
assert g(MediaFile) == []
assert g(Song) == []
assert g(Topic) == []
- c = empty_songs_db.get_object_count
+ c = db.get_object_count
assert c(Author) == 0
assert c(Book) == 0
assert c(MediaFile) == 0
@@ -52,11 +53,11 @@ def test_empty_songdb(empty_songs_db):
assert c(Topic) == 0
-def test_unmapped_class(empty_songs_db):
+def test_unmapped_class(openlp_runner):
# test class not mapped to any sqlalchemy table
class A(object):
pass
- db = empty_songs_db
+ db = openlp_runner.get_songs_db(empty=True)
assert db.save_object(A()) == False
assert db.save_objects([A(), A()]) == False
# no key - new object instance is created from supplied class
From fb664bed0cb835481b435bcc320289538d0f14c7 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Mon, 12 Sep 2011 22:35:39 +0200
Subject: [PATCH 29/43] Remove openlyrics tests for easier merging. They will
be merged later.
---
openlp/core/__init__.py | 35 +-
openlp/core/utils/__init__.py | 5 -
testing/conftest.py | 139 +-----
.../openlyrics/openlyrics_schema.rng | 472 ------------------
testing/resources/openlyrics/validate.py | 26 -
testing/resources/songs/openlyrics_test_1.xml | 102 ----
testing/resources/songs/songs.sqlite | Bin 30720 -> 0 bytes
testing/test_app.py | 15 +-
testing/test_openlyrics.py | 57 ---
testing/test_songs_db.py | 76 ---
10 files changed, 29 insertions(+), 898 deletions(-)
delete mode 100644 testing/resources/openlyrics/openlyrics_schema.rng
delete mode 100755 testing/resources/openlyrics/validate.py
delete mode 100644 testing/resources/songs/openlyrics_test_1.xml
delete mode 100644 testing/resources/songs/songs.sqlite
delete mode 100644 testing/test_openlyrics.py
delete mode 100644 testing/test_songs_db.py
diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py
index 0bec15678..a5347edeb 100644
--- a/openlp/core/__init__.py
+++ b/openlp/core/__init__.py
@@ -228,29 +228,26 @@ def main(args=None):
help='Set the Qt4 style (passed directly to Qt4).')
parser.add_option('--testing', dest='testing',
action='store_true', help='Run by testing framework')
+ # Set up logging
+ log_path = AppLocation.get_directory(AppLocation.CacheDir)
+ check_directory_exists(log_path)
+ filename = os.path.join(log_path, u'openlp.log')
+ logfile = logging.FileHandler(filename, u'w')
+ logfile.setFormatter(logging.Formatter(
+ u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
+ log.addHandler(logfile)
+ logging.addLevelName(15, u'Timer')
# Parse command line options and deal with them.
# Use args supplied programatically if possible.
(options, args) = parser.parse_args(args) if args else parser.parse_args()
- # Set up logging
- # In test mode it is skipped
- if not options.testing:
- log_path = AppLocation.get_directory(AppLocation.CacheDir)
- check_directory_exists(log_path)
- filename = os.path.join(log_path, u'openlp.log')
- logfile = logging.FileHandler(filename, u'w')
- logfile.setFormatter(logging.Formatter(
- u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
- log.addHandler(logfile)
- logging.addLevelName(15, u'Timer')
- if options.loglevel.lower() in ['d', 'debug']:
- log.setLevel(logging.DEBUG)
- print 'Logging to:', filename
- elif options.loglevel.lower() in ['w', 'warning']:
- log.setLevel(logging.WARNING)
- else:
- log.setLevel(logging.INFO)
- # Deal with other command line options.
qt_args = []
+ if options.loglevel.lower() in ['d', 'debug']:
+ log.setLevel(logging.DEBUG)
+ print 'Logging to:', filename
+ elif options.loglevel.lower() in ['w', 'warning']:
+ log.setLevel(logging.WARNING)
+ else:
+ log.setLevel(logging.INFO)
if options.style:
qt_args.extend(['-style', options.style])
# Throw the rest of the arguments at Qt, just in case.
diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py
index fbf185474..3612bb002 100644
--- a/openlp/core/utils/__init__.py
+++ b/openlp/core/utils/__init__.py
@@ -127,9 +127,6 @@ 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):
"""
@@ -155,8 +152,6 @@ 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/testing/conftest.py b/testing/conftest.py
index 0ba4f34ed..f38018c17 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -30,137 +30,16 @@
Configuration file for pytest framework.
"""
-import os
-import sys
-import subprocess
-import logging
-
-import py.path
-from PyQt4 import QtCore
-from sqlalchemy.orm import clear_mappers
-
from openlp.core import main as openlp_main
-from openlp.core.utils import AppLocation
-from openlp.core.lib.db import Manager
-from openlp.plugins.songs.lib.db import init_schema
-
-TESTS_PATH = os.path.dirname(os.path.abspath(__file__))
-
-RESOURCES_PATH = os.path.join(TESTS_PATH, 'resources')
-SONGS_PATH = os.path.join(RESOURCES_PATH, 'songs')
-# class to setup and teardown settings for running openlp tests
-class OpenLPRunner(object):
- def __init__(self, tmpdir):
- self.tmpdir = tmpdir
- self._setup_qapp()
- self._setup_logging()
- self._cleanup_qsettings()
- # override data dir of OpenLP - it points to tmpdir of a test case
- AppLocation.BaseDir = tmpdir.strpath
-
- def _setup_qapp(self):
- QtCore.QCoreApplication.setOrganizationName(u'OpenLP')
- QtCore.QCoreApplication.setOrganizationDomain(u'openlp.org')
- QtCore.QCoreApplication.setApplicationName(u'TestOpenLP')
-
- def _setup_logging(self):
- # set up logging to stderr/stdout (console)
- _handler = logging.StreamHandler(stream=None)
- _handler.setFormatter(logging.Formatter(
- u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
- logging.addLevelName(15, u'Timer')
- log = logging.getLogger()
- log.addHandler(_handler)
- log.setLevel(logging.DEBUG)
-
- def _cleanup_qsettings(self):
- # Clean up QSettings for all plugins
- # The issue with QSettings is that is global for a running process
- # and thus it is necessary to clean it before another test case.
- # If it would not be cleaned up it could be saved in the system.
- s = QtCore.QSettings()
- keys = s.allKeys()
- for k in keys:
- s.setValue(k, None)
-
- ## Public interface
-
- def get_songs_db(self, empty=False):
- # return initialized db Manager with empty db or db containing
- # some example songs
-
- if not empty:
- # copy test data to tmpdir
- datadir = self.tmpdir.mkdir(u'data').mkdir(u'songs')
- orig_db = py.path.local(SONGS_PATH).join('songs.sqlite')
- orig_db.copy(datadir)
-
- manager = Manager('songs', init_schema)
- return manager
-
- def get_app(self):
- # return QGui.QApplication of OpenLP - this object allows
- # running different gui tests and allows access to gui objects
- # (e.g MainWindow etc.)
- # To allow creating multiple instances of OpenLP in one process
- # it would be necessary use diffrent configuration and data files.
- # Created instance will use your OpenLP settings.
+# Test function argument to make openlp gui instance persistent for all tests.
+# All test cases have to access the same instance. To allow create multiple
+# instances it would be necessary use diffrent configuraion and data files.
+# Created instance will use your OpenLP settings.
+def pytest_funcarg__openlpapp(request):
+ def setup():
return openlp_main(['--testing'])
-
- def teardown(self):
- # clean up code to run after running the test case
- self._cleanup_qsettings()
- # sqlalchemy allows to map classess to only one database at a time
- clear_mappers()
- # set data dir to original value
- AppLocation.BaseDir = None
-
-
-# Paths with resources for tests
-def pytest_funcarg__pth(request):
- def setup():
- class Pth(object):
- def __init__(self):
- self.tests = py.path.local(TESTS_PATH)
- self.resources = py.path.local(RESOURCES_PATH)
- self.songs = py.path.local(SONGS_PATH)
- return Pth()
- return request.cached_setup(setup=setup, scope='session')
-
-
-# Test function argument giving access to OpenLP runner
-def pytest_funcarg__openlp_runner(request):
- def setup():
- return OpenLPRunner(request.getfuncargvalue('tmpdir'))
- def teardown(openlp_runner):
- openlp_runner.teardown()
- return request.cached_setup(setup=setup, teardown=teardown, scope='function')
-
-
-class OpenLyricsValidator(object):
- """Validate xml if it conformns to OpenLyrics xml schema."""
- def __init__(self, script, schema):
- self.cmd = [sys.executable, script, schema]
-
- def validate(self, file_path):
- self.cmd.append(file_path)
- print self.cmd
- retcode = subprocess.call(self.cmd)
- if retcode == 0:
- # xml conforms to schema
- return True
- else:
- # xml has invalid syntax
- return False
-
-
-# Test function argument giving access to song database.
-def pytest_funcarg__openlyrics_validator(request):
- def setup():
- script = os.path.join(RESOURCES_PATH, 'openlyrics', 'validate.py')
- schema = os.path.join(RESOURCES_PATH, 'openlyrics',
- 'openlyrics_schema.rng')
- return OpenLyricsValidator(script, schema)
- return request.cached_setup(setup=setup, scope='session')
+ def teardown(app):
+ pass
+ return request.cached_setup(setup=setup, teardown=teardown, scope='session')
diff --git a/testing/resources/openlyrics/openlyrics_schema.rng b/testing/resources/openlyrics/openlyrics_schema.rng
deleted file mode 100644
index b4a7813fb..000000000
--- a/testing/resources/openlyrics/openlyrics_schema.rng
+++ /dev/null
@@ -1,472 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- words
- music
-
-
-
-
-
- translation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -99
- 99
-
-
-
-
-
-
-
-
-
-
- 30
- 250
-
-
- bpm
-
-
-
-
-
-
- text
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- 999
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- optional
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [0-9]+\.[0-9]+(\.[0-9]+)?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
-
- (v[1-9]\d?[a-z]?)|([cpb][a-z]?)|([cpbe][1-9]\d?[a-z]?)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
diff --git a/testing/resources/openlyrics/validate.py b/testing/resources/openlyrics/validate.py
deleted file mode 100755
index 116f69454..000000000
--- a/testing/resources/openlyrics/validate.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-
-try:
- from lxml import etree
-except ImportError:
- print('Python module "lxml" is required')
- exit(1)
-
-
-if len(sys.argv) != 3:
- print('Usage: python %s openlyrics_schema.rng xmlfile.xml' % __file__)
- exit(1)
-
-
-relaxng_file = sys.argv[1]
-xml_file = sys.argv[2]
-
-relaxng_doc = etree.parse(relaxng_file)
-xml_doc = etree.parse(xml_file)
-
-relaxng = etree.RelaxNG(relaxng_doc)
-
-relaxng.assertValid(xml_doc)
-
diff --git a/testing/resources/songs/openlyrics_test_1.xml b/testing/resources/songs/openlyrics_test_1.xml
deleted file mode 100644
index d7a9c12ec..000000000
--- a/testing/resources/songs/openlyrics_test_1.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
- Jezu Kriste, štědrý kněže
-
-
- M. Jan Hus
-
-
-
-
-
-
-
-
- <span style="-webkit-text-fill-color:red">
- </span>
-
-
- <span style="-webkit-text-fill-color:blue">
- </span>
-
-
- <span style="-webkit-text-fill-color:yellow">
- </span>
-
-
- <span style="-webkit-text-fill-color:#FFA500">
- </span>
-
-
- <strong>
- </strong>
-
-
- <em>
- </em>
-
-
- <span style="-webkit-text-fill-color:green">
- </span>
-
-
-
-
-
-
- Jezu Kriste, štědrý kněže,
- s Otcem, Duchem jeden Bože,
- štědrost Tvá je naše zboží,
- z Tvé milosti.
-
-
-
-
- Ty jsi v světě, bydlil s námi,
- Tvé tělo trpělo rány
- za nás za hříšné křesťany,
- z Tvé milosti.
-
-
-
-
- Ó, Tvá dobroto důstojná
- a k nám milosti přehojná!
- Dáváš nám bohatství mnohá
-
-
- z Tvé milosti.
-
-
-
-
-
-
- Ráčils nás sám zastoupiti,
-
- život za nás položiti,
-
- tak smrt věčnou zahladiti,
- z Tvé milosti.
-
-
-
-
- Ó, křesťané, z bludů vstaňme,
- dané dobro nám poznejme,
- k Synu Božímu chvátejme,
- k té milosti!
-
-
-
-
- Chvála budiž Bohu Otci,
- Synu jeho téže moci,
- Duchu jeho rovné moci,
- z též milosti!
-
-
-
-
diff --git a/testing/resources/songs/songs.sqlite b/testing/resources/songs/songs.sqlite
deleted file mode 100644
index f1ae584af22579ee8b55de2c80ce6a96aaae4e43..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 30720
zcmeHQ&2JmW72n}XqD4pXcebvoWGKUs2+*b|TXt;AABs#T@`vowbQ;4*i6yxf*Ie$(
z+@)-aRRK9EY6M8#!o37W4?6VV07efj&{KV(`5*LDAg3aSqNgB6(YlwuH@i#jiXV2+
zAc(|S)63nNH}l^6%^Py{<_+J~A74~0h2AtuMcJZcBFl~;;NT9=DW#a!N|BA2rHHp=*dvOsN_^ReNzRi^;`yg5_&4SSTFYBAJ7&{
zwJM9v;RYM=D8bP2n*AMxnV&OH(9l2^n0DK?!6~=}P6Of)PQyMt@q;745$H7paQ+kV
z?*hE|!4cpHY(4~rNuO|H=)7uLN=8?+h1y?eLDsXXq1V{{yFfw#v_p9)RmLsWxo6v#$TP1_I1r>5D{KE)@nlcP(c@GP8Bw3DN8
zpa|cXg=gfM8`aW^M~rH$w9-bDXvn11jJ9&w$|}VuJzLJggM2jxCG?h(Q*>Ibth{4@
z%+*+BB^q*6F-(i5mTGH2Lv^{nrqJaK5Y?VUL(8;ktW-^F1?nGHV{lQ887s7^S}R3W
zgNAA~2G<8a{P5Itj49Dcmr1WKAW*8}$YrW@iJD8#9s#UTnyKV8Rih>a3o5G75b~u^
zqZ!mHEwcAgZB4I)mSrR{DZJ-$K
z_ApAT3)syX%^8`JVHwrf!V1mR|6p3iEs!B3(*hIM5`QZ7?5Y5>P8tT^O~Gv?biv}MK+l4BmDV)
zqkQUdIXME~J_0!ZMd7|6+$SmWT==*Rhi}<8*M(mXCJYx1JdjW5mY^=+%<1&uvU5#qlwiJ5pv@a4GmX%izMHK+|EODar!Oe&-Ah*ISZm8z1h>0=
z?)TB#WnEmU-MzhSe7ZmD4@@laEd`e9mUt=E7umuDAH4@YbL#%z&C6SdVWORv=Ip
zHv4(kGA8OO(1zI{4DT$-dr=ZhO$t)9Z7+H#+jt+sZjE_oD*?(uMt<#@VZ9I_|w2
zTs;~LO2fnCo^QOJ;SBlEatgZ)+D)|t%L^?-hx)LEhqg62)m+r%ioaQl-`s~VAQy`K
zpO(r{KwWILI*qI|m~9uUd@?n%o!dDsuz`0l)tP;$H>vA8_IaM}Q-+;Stys=of~DA~Wo7Z+cBH=*As=
z59t?n?Fyfl_4mr=rGpeMC|u$EC*pquc=3ZHz!B&b1p0|c;K1j9BK})|7e6=x9DyD|
zAlQeqpAh@||7$_~xSd2fTlwdV-*3IbcjVdBhx
z_Wd6up9|!3@@3$sfd_#v#iQcy#NA?2{H<`sM&!(n0qL#1ZNHp`tZn+2@L-0MN{zV6!67@A3aX6vT&{RZp%LM_@xC@QXM5J~>Tj33htIj@AO(
z(F%7hTUufNWk#b0?jof*wuuz)?JcAgikm|fT5+4<_EFf;=-WODsJv-^|9>HfUu-Ch
zxUd`nj=;+h7!~J;_d}%RBjk*;{?DKPUj`+w=Ll>r1e(wPJpO-kjV#xOBk-ySaR2|S
zBE{)A0-Fy3KL0o0m~xFc019gb@Fx
zz(4@w|F@8!5I7nr2mUPX6MsX#B0qh!zjp+`VdN|OkuTuN
zSLKjnOqZ{mj@!2oPu7fIy!}Q{iUV7z4O@)t^7Sy>g((uZVTr|zVS{(%?4AKBJWOu*
zII1~^(+}J7u%qf65*Gh9y?sC$k;qln3yVQ>g6YiNz8>5vPly}*?94X_C}mNhwg7en
zDdT@@XHYr-VW!)d0Ct5PJIXj>|c8Y)C)2#!o$1Lc$x1T7*UjTx69)RF~@de)j&|4pQT-#nn06qVKK-2%v
z_y6{)C2+la6#;kuFMj{?`QNKn!1eA`1l;+L`a6j%raU-URFG?bGunFC9=VCPL(G?sPc7FHGL)rwxGn8l*@q*$ieJcJ%vZk^T4p%4yr
z$xqgJ%Z{xh|^>!!U!1D4&r=_?*>&ZS9-jp82o8L+$V&9
zdb4$`OV)uwplx$?G?Gnp%GJS4z-|saoID+r*8WHr?8s6l@dn&B({9i%X*X!Dd)5uq
zvI1P0#4f)ImQu(r(m90&qR}aV2G(`Sp0S=AYUh~-3KM*{ZeNX@E?{unzS@3dw%FGzr+-Bg 0.6:
- self._process_formatting_tags(song_xml, only_process_format_tags)
- if only_process_format_tags:
+ 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.
From 82163867fedc2af9dc19d70b87cf4870a29f5270 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Thu, 15 Sep 2011 14:23:40 +0200
Subject: [PATCH 31/43] Add docstrings to some new openlyrics methods.
---
openlp/plugins/songs/lib/xml.py | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index df37b54b6..f49327450 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -172,7 +172,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
@@ -197,6 +197,9 @@ class OpenLyrics(object):
````
This property is not supported.
+ ````
+ The custom formatting tags are fully supported.
+
````
This property is not supported.
@@ -306,13 +309,13 @@ class OpenLyrics(object):
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?
+ # Have we any tags in song lyrics?
tags_element = None
match = re.search(u'\{/?\w+\}', song.lyrics, re.UNICODE)
if match:
- # reset available tags
+ # Reset available tags.
FormattingTags.reset_html_tags()
- # named 'formatting' - 'format' is built-in fuction in Python
+ # Named 'formatting' - 'format' is built-in fuction in Python.
formatting = etree.SubElement(song_xml, u'format')
tags_element = etree.SubElement(formatting, u'tags')
tags_element.set(u'application', u'OpenLP')
@@ -401,11 +404,15 @@ class OpenLyrics(object):
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
+ # Rreate 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)
@@ -414,18 +421,22 @@ class OpenLyrics(object):
el_close.text = etree.CDATA(t[u'end html'])
def _add_line_with_tags_to_lines(self, parent, text, tags_element):
- # tags already converted to xml structure
+ """
+ Convert text with formatting tags from OpenLP format to OpenLyrics
+ format and append it to element ````.
+ """
+ # Tags already converted to xml structure.
xml_tags = tags_element.xpath(u'tag/attribute::name')
start_tags = self.start_tags_regex.findall(text)
end_tags = self.end_tags_regex.findall(text)
- # replace start tags with xml syntax
+ # Replace start tags with xml syntax.
for tag in start_tags:
name = tag[1:-1]
text = text.replace(tag, u'' % name)
- # add tag to elment if tag not present
+ # Add tag to elment if tag not present.
if name not in xml_tags:
self._add_tag_to_formatting(name, tags_element)
- # replace end tags
+ # Replace end tags.
for t in end_tags:
text = text.replace(t, u'')
text = u'' + text + u''
From a7cd6c8a17bf2ca3e4f23937000423ff7d876170 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Thu, 15 Sep 2011 19:44:03 +0200
Subject: [PATCH 32/43] fix typo in comment
---
openlp/plugins/songs/lib/xml.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index f49327450..cab9d9207 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -412,7 +412,7 @@ class OpenLyrics(object):
start_tag = '{%s}' % tag_name
for t in available_tags:
if t[u'start tag'] == start_tag:
- # Rreate new formatting tag in openlyrics xml.
+ # 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)
From 614ad790aeb518e7d3cf3f24d1b4511f14618366 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Fri, 16 Sep 2011 13:31:11 +0200
Subject: [PATCH 33/43] Fix spelling mistake
---
openlp/plugins/songs/lib/xml.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index cab9d9207..96c85e092 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -433,7 +433,7 @@ class OpenLyrics(object):
for tag in start_tags:
name = tag[1:-1]
text = text.replace(tag, u'' % name)
- # Add tag to elment if tag not present.
+ # Add tag to element if tag not present.
if name not in xml_tags:
self._add_tag_to_formatting(name, tags_element)
# Replace end tags.
From 14e94f87580975e810cc94c238f1edf86adcd8fe Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Sun, 18 Sep 2011 01:22:16 +0200
Subject: [PATCH 34/43] RAW code for OpenLyrics import with formatting tags
---
openlp/core/utils/__init__.py | 5 +++
openlp/plugins/songs/lib/xml.py | 60 +++++++++++++++++++++++++++++++--
2 files changed, 62 insertions(+), 3 deletions(-)
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/xml.py b/openlp/plugins/songs/lib/xml.py
index 96c85e092..deac1d4b4 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -73,6 +73,9 @@ from openlp.core.utils import get_application_version
log = logging.getLogger(__name__)
+NAMESPACE = u'http://openlyrics.info/namespace/2009/song'
+NSMAP = '{' + NAMESPACE + '}' + '%s'
+
class SongXML(object):
"""
@@ -267,7 +270,7 @@ 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)
@@ -371,7 +374,8 @@ class OpenLyrics(object):
properties = song_xml.properties
else:
return None
- if float(song_xml.get(u'version')) > 0.6:
+ # Formatting tags are new in OpenLyrics 0.8
+ if float(song_xml.get(u'version')) > 0.7:
self._process_formatting_tags(song_xml, parse_and_not_save)
if parse_and_not_save:
return
@@ -558,6 +562,52 @@ class OpenLyrics(object):
FormattingTags.add_html_tags([tag for tag in found_tags
if tag[u'start tag'] not in existing_tag_ids], True)
+ def _process_lyrics_mixed_content(self, element):
+ text = u''
+
+ #print '1:', repr(text)
+ # Skip element.
+ #if element.tag == u'chord' and element.tail:
+ ## Append tail text at chord element.
+ #text += element.tail
+
+ # Start formatting tag.
+ #print NSMAP % 'tag'
+ #print repr(element.tag)
+ if element.tag == NSMAP % 'tag':
+ text += u'{%s}' % element.get(u'name')
+ print '1:', repr(text)
+
+ # Append text from element
+ if element.text:
+ text += element.text
+ print '2:', repr(text)
+
+ #print '3:', repr(text)
+ # Process nested formatting tags
+ for child in element:
+ # Use recursion since nested formatting tags are allowed.
+ text += self._process_lyrics_mixed_content(child)
+
+ # Append text from tail and add formatting end tag.
+ if element.tag == NSMAP % 'tag':
+ text += u'{/%s}' % element.get(u'name')
+ print '3:', repr(text)
+
+ # Append text from tail
+ if element.tail:
+ text += element.tail
+ print '4:', repr(text)
+
+ return text
+
+ def _process_lyrics_line(self, line):
+ # Convert lxml.objectify to lxml.etree representation
+ line = etree.tostring(line)
+ element = etree.XML(line)
+ print element.nsmap
+ return self._process_lyrics_mixed_content(element)
+
def _process_lyrics(self, properties, song_xml, song_obj):
"""
Processes the verses and search_lyrics for the song.
@@ -586,7 +636,11 @@ class OpenLyrics(object):
for line in lines.line:
if text:
text += u'\n'
- text += u''.join(map(unicode, line.itertext()))
+ text += self._process_lyrics_line(line)
+ #AAA = u''.join(map(unicode, line.itertext()))
+ #print 'AAA:', repr(AAA)
+ #text += AAA
+ #print repr(text)
# Add a virtual split to the verse text.
if lines.get(u'break') is not None:
text += u'\n[---]'
From 2b7f51d4e925616f4542ddc8ff17e88f70790c67 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Mon, 19 Sep 2011 00:43:12 +0200
Subject: [PATCH 35/43] Code cleanup of openlyrics import with formatting tags
---
openlp/plugins/songs/lib/xml.py | 30 +++++++++---------------------
1 file changed, 9 insertions(+), 21 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index deac1d4b4..4db2580fe 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -562,51 +562,43 @@ class OpenLyrics(object):
FormattingTags.add_html_tags([tag for tag in found_tags
if tag[u'start tag'] not in existing_tag_ids], True)
- def _process_lyrics_mixed_content(self, element):
+ def _process_line_mixed_content(self, element):
text = u''
- #print '1:', repr(text)
# Skip element.
- #if element.tag == u'chord' and element.tail:
- ## Append tail text at chord element.
- #text += element.tail
+ if element.tag == u'chord' and element.tail:
+ # Append tail text at chord element.
+ text += element.tail
+ return
# Start formatting tag.
- #print NSMAP % 'tag'
- #print repr(element.tag)
if element.tag == NSMAP % 'tag':
text += u'{%s}' % element.get(u'name')
- print '1:', repr(text)
# Append text from element
if element.text:
text += element.text
- print '2:', repr(text)
- #print '3:', repr(text)
# Process nested formatting tags
for child in element:
# Use recursion since nested formatting tags are allowed.
- text += self._process_lyrics_mixed_content(child)
+ text += self._process_line_mixed_content(child)
# Append text from tail and add formatting end tag.
if element.tag == NSMAP % 'tag':
text += u'{/%s}' % element.get(u'name')
- print '3:', repr(text)
# Append text from tail
if element.tail:
text += element.tail
- print '4:', repr(text)
return text
- def _process_lyrics_line(self, line):
+ def _process_verse_line(self, line):
# Convert lxml.objectify to lxml.etree representation
line = etree.tostring(line)
element = etree.XML(line)
- print element.nsmap
- return self._process_lyrics_mixed_content(element)
+ return self._process_line_mixed_content(element)
def _process_lyrics(self, properties, song_xml, song_obj):
"""
@@ -636,11 +628,7 @@ class OpenLyrics(object):
for line in lines.line:
if text:
text += u'\n'
- text += self._process_lyrics_line(line)
- #AAA = u''.join(map(unicode, line.itertext()))
- #print 'AAA:', repr(AAA)
- #text += AAA
- #print repr(text)
+ text += self._process_verse_line(line)
# Add a virtual split to the verse text.
if lines.get(u'break') is not None:
text += u'\n[---]'
From b27784e8387e98d2d4f4faf46ecaa3bfe6cf1800 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Mon, 19 Sep 2011 01:00:55 +0200
Subject: [PATCH 36/43] Add comments to some new functions
---
openlp/plugins/songs/lib/xml.py | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 4db2580fe..8ce628497 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -563,6 +563,13 @@ class OpenLyrics(object):
if tag[u'start tag'] not in existing_tag_ids], True)
def _process_line_mixed_content(self, element):
+ """
+ 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).
+ """
text = u''
# Skip element.
@@ -575,11 +582,11 @@ class OpenLyrics(object):
if element.tag == NSMAP % 'tag':
text += u'{%s}' % element.get(u'name')
- # Append text from element
+ # Append text from element.
if element.text:
text += element.text
- # Process nested formatting tags
+ # Process nested formatting tags.
for child in element:
# Use recursion since nested formatting tags are allowed.
text += self._process_line_mixed_content(child)
@@ -588,14 +595,20 @@ class OpenLyrics(object):
if element.tag == NSMAP % 'tag':
text += u'{/%s}' % element.get(u'name')
- # Append text from tail
+ # Append text from tail.
if element.tail:
text += element.tail
return text
def _process_verse_line(self, line):
- # Convert lxml.objectify to lxml.etree representation
+ """
+ Converts lyrics line to OpenLP representation.
+
+ ``line``
+ The line object (lxml.objectify.ObjectifiedElement).
+ """
+ # Convert lxml.objectify to lxml.etree representation.
line = etree.tostring(line)
element = etree.XML(line)
return self._process_line_mixed_content(element)
From 039115c7f4dcbcd94b45a8681f74c361cf519bc6 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Mon, 19 Sep 2011 14:59:37 +0200
Subject: [PATCH 37/43] Fix issue with more new lines when having more
element in openlyrics document.
---
openlp/plugins/songs/lib/xml.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 8ce628497..7c268e340 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -638,10 +638,13 @@ class OpenLyrics(object):
if text:
text += u'\n'
# Loop over the "line" elements removing chords.
+ lines_text = u''
for line in lines.line:
- if text:
- text += u'\n'
- text += self._process_verse_line(line)
+ if lines_text:
+ lines_text += u'\n'
+ lines_text += self._process_verse_line(line)
+ # Append text from "lines" element to verse text.
+ text += lines_text
# Add a virtual split to the verse text.
if lines.get(u'break') is not None:
text += u'\n[---]'
From 74c56691947a7cf8b1f95f8de92fd453878cd119 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Tue, 20 Sep 2011 17:52:19 +0200
Subject: [PATCH 38/43] Dropped element for openlyrics parsing and allow
to import formatting tags over multiple lines and tags with only starting tag
---
openlp/plugins/songs/lib/xml.py | 80 +++++++++++++++++++++++----------
1 file changed, 56 insertions(+), 24 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 7c268e340..4484e1700 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -547,12 +547,16 @@ class OpenLyrics(object):
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': u'{%s}' % name[:5],
- u'end tag': u'{/%s}' % name[:5],
+ u'start tag': start_tag,
+ u'end tag': end_tag,
u'start html': tag.open.text,
- u'end html': tag.close.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,
u'temporary': temporary
}
@@ -562,25 +566,45 @@ class OpenLyrics(object):
FormattingTags.add_html_tags([tag for tag in found_tags
if tag[u'start tag'] not in existing_tag_ids], True)
- def _process_line_mixed_content(self, element):
+ 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 element.
- if element.tag == u'chord' and element.tail:
+ # Skip elements - not yet supported.
+ if element.tag == NSMAP % u'comment' and element.tail:
# Append tail text at chord element.
text += element.tail
- return
+ return text
+ # Skip element - not yet supported.
+ elif element.tag == NSMAP % u'chord' and 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 % '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:
@@ -589,10 +613,10 @@ class OpenLyrics(object):
# Process nested formatting tags.
for child in element:
# Use recursion since nested formatting tags are allowed.
- text += self._process_line_mixed_content(child)
+ text += self._process_lines_mixed_content(child, newlines)
# Append text from tail and add formatting end tag.
- if element.tag == NSMAP % 'tag':
+ if element.tag == NSMAP % 'tag' and use_endtag:
text += u'{/%s}' % element.get(u'name')
# Append text from tail.
@@ -601,17 +625,31 @@ class OpenLyrics(object):
return text
- def _process_verse_line(self, line):
+ def _process_verse_lines(self, lines):
"""
- Converts lyrics line to OpenLP representation.
+ Converts lyrics lines to OpenLP representation.
- ``line``
- The line object (lxml.objectify.ObjectifiedElement).
+ ``lines``
+ The lines object (lxml.objectify.ObjectifiedElement).
"""
+ text = u''
# Convert lxml.objectify to lxml.etree representation.
- line = etree.tostring(line)
- element = etree.XML(line)
- return self._process_line_mixed_content(element)
+ lines = etree.tostring(lines)
+ element = etree.XML(lines)
+ # OpenLyrics version <= 0.7 contais elements to represent lines.
+ # First child element is tested.
+ if element[0].tag == NSMAP % 'line':
+ # Loop over the "line" elements removing comments and chords.
+ for line in element:
+ if text:
+ text += u'\n'
+ text += self._process_lines_mixed_content(line, newlines=False)
+ # OpenLyrics 0.8 uses
for new lines.
+ # Append text from "lines" element to verse text.
+ else:
+ text = self._process_lines_mixed_content(element)
+
+ return text
def _process_lyrics(self, properties, song_xml, song_obj):
"""
@@ -637,14 +675,8 @@ class OpenLyrics(object):
for lines in verse.lines:
if text:
text += u'\n'
- # Loop over the "line" elements removing chords.
- lines_text = u''
- for line in lines.line:
- if lines_text:
- lines_text += u'\n'
- lines_text += self._process_verse_line(line)
# Append text from "lines" element to verse text.
- text += lines_text
+ text += self._process_verse_lines(lines)
# Add a virtual split to the verse text.
if lines.get(u'break') is not None:
text += u'\n[---]'
From 6e4c619cbd992ad53cd367b326635b48b4429a27 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Wed, 21 Sep 2011 01:06:43 +0200
Subject: [PATCH 39/43] Add openlyrics export with formatting tags support.
---
openlp/plugins/songs/lib/xml.py | 57 +++++++++++++++++++--------------
1 file changed, 33 insertions(+), 24 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 4484e1700..d99f58396 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -260,8 +260,8 @@ class OpenLyrics(object):
def __init__(self, manager):
self.manager = manager
- self.start_tags_regex = re.compile(r'\{\w+\}') # {abc}
- self.end_tags_regex = re.compile(r'\{\/\w+\}') # {/abc}
+ 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):
"""
@@ -336,19 +336,12 @@ class OpenLyrics(object):
# Create a list with all "virtual" verses.
virtual_verses = verse[1].split(u'[---]')
for index, virtual_verse in enumerate(virtual_verses):
- lines_element = \
- self._add_text_to_element(u'lines', verse_element)
+ # 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')
- for line in virtual_verse.strip(u'\n').split(u'\n'):
- # 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)
return self._extract_xml(song_xml)
def xml_to_song(self, xml, parse_and_not_save=False):
@@ -420,32 +413,48 @@ class OpenLyrics(object):
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_close = self._add_text_to_element(u'close', el)
el_open.text = etree.CDATA(t[u'start html'])
- el_close.text = etree.CDATA(t[u'end 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_line_with_tags_to_lines(self, parent, text, tags_element):
+ 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 ````.
"""
- # Tags already converted to xml structure.
- xml_tags = tags_element.xpath(u'tag/attribute::name')
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:
- name = tag[1:-1]
- text = text.replace(tag, u'' % name)
+ # 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 name not in xml_tags:
- self._add_tag_to_formatting(name, tags_element)
+ 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(t, u'')
- text = u'' + text + u''
+ print repr(t)
+ text = text.replace(u'{/%s}' % t, u'')
+
+ # Replace \n with
.
+ text = text.replace(u'\n', u'
')
+ text = u'' + text + u''
+ print repr(text)
element = etree.XML(text)
- parent.append(element)
+ verse_element.append(element)
+ return element
def _extract_xml(self, xml):
"""
From 8b6887036b05855f28bd7da19ec46ac5325692e5 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Thu, 22 Sep 2011 16:23:51 +0200
Subject: [PATCH 40/43] Fix regressions with users tags in preview and
import/export in openlyrics
---
openlp/core/lib/formattingtags.py | 26 ++++++++++++++++++++++++--
openlp/core/ui/formattingtagform.py | 25 ++-----------------------
openlp/plugins/songs/lib/xml.py | 26 +++++++-------------------
3 files changed, 33 insertions(+), 44 deletions(-)
diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py
index 529a8c029..6a0013635 100644
--- a/openlp/core/lib/formattingtags.py
+++ b/openlp/core/lib/formattingtags.py
@@ -33,6 +33,7 @@ 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
@@ -45,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
@@ -63,7 +66,7 @@ class FormattingTags(object):
u'start html': u'',
u'end tag': u'{/r}', u'end html': u'', u'protected': True,
u'temporary': False})
- base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Black'),
+ 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,
@@ -144,13 +147,32 @@ class FormattingTags(object):
Saves all formatting tags except protected ones.
"""
tags = []
- for tag in FormattingTags.get_html_tags():
+ for tag in FormattingTags.html_expands:
if not tag[u'protected'] and not tag[u'temporary']:
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''))
+ @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):
"""
diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py
index fee27a9c6..df2c3d673 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,
@@ -65,29 +63,10 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
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.
@@ -199,7 +178,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
self.tagTableWidget.setItem(linenumber, 3,
QtGui.QTableWidgetItem(html[u'end html']))
# Tags saved prior to 1.9.7 do not have this key.
- if not html.has_key(u'temporary'):
+ if u'temporary' not in html:
html[u'temporary'] = False
self.tagTableWidget.resizeRowsToContents()
self.descriptionLineEdit.setText(u'')
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index d99f58396..4a0ebc6af 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -319,8 +319,8 @@ class OpenLyrics(object):
# Reset available tags.
FormattingTags.reset_html_tags()
# Named 'formatting' - 'format' is built-in fuction in Python.
- formatting = etree.SubElement(song_xml, u'format')
- tags_element = etree.SubElement(formatting, u'tags')
+ 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')
@@ -337,7 +337,7 @@ class OpenLyrics(object):
virtual_verses = verse[1].split(u'[---]')
for index, virtual_verse in enumerate(virtual_verses):
# Add formatting tags to text
- lines_element =self._add_text_with_tags_to_lines(verse_element,
+ 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:
@@ -428,7 +428,6 @@ class OpenLyrics(object):
"""
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.
@@ -442,16 +441,12 @@ class OpenLyrics(object):
# 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:
- print repr(t)
text = text.replace(u'{/%s}' % t, u'')
-
# Replace \n with
.
text = text.replace(u'\n', u'
')
text = u'' + text + u''
- print repr(text)
element = etree.XML(text)
verse_element.append(element)
return element
@@ -558,7 +553,7 @@ class OpenLyrics(object):
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''
+ end_tag = u'{/' + name[:5] + u'}' if hasattr(tag, 'close') else u''
openlp_tag = {
u'desc': name,
u'start tag': start_tag,
@@ -572,8 +567,9 @@ class OpenLyrics(object):
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)
+ 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):
"""
@@ -589,7 +585,6 @@ class OpenLyrics(object):
"""
text = u''
use_endtag = True
-
# Skip elements - not yet supported.
if element.tag == NSMAP % u'comment' and element.tail:
# Append tail text at chord element.
@@ -606,7 +601,6 @@ class OpenLyrics(object):
if element.tail:
text += element.tail
return text
-
# Start formatting tag.
if element.tag == NSMAP % u'tag':
text += u'{%s}' % element.get(u'name')
@@ -614,24 +608,19 @@ class OpenLyrics(object):
# 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):
@@ -657,7 +646,6 @@ class OpenLyrics(object):
# Append text from "lines" element to verse text.
else:
text = self._process_lines_mixed_content(element)
-
return text
def _process_lyrics(self, properties, song_xml, song_obj):
From 354bec8b33c8c7de68d28a5250cf59acd381a0ee Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Thu, 22 Sep 2011 22:30:15 +0200
Subject: [PATCH 41/43] Fix - do not save temporary key by formatting tags into
openlp configuration
---
openlp/core/lib/formattingtags.py | 8 ++++++--
openlp/core/ui/formattingtagform.py | 2 +-
openlp/plugins/songs/lib/xml.py | 5 ++++-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py
index 6a0013635..ea5547f27 100644
--- a/openlp/core/lib/formattingtags.py
+++ b/openlp/core/lib/formattingtags.py
@@ -56,7 +56,7 @@ class FormattingTags(object):
Resets the html_expands list.
"""
temporary_tags = [tag for tag in FormattingTags.html_expands
- if tag[u'temporary']]
+ if tag.get(u'temporary')]
FormattingTags.html_expands = []
base_tags = []
# Append the base tags.
@@ -148,8 +148,12 @@ class FormattingTags(object):
"""
tags = []
for tag in FormattingTags.html_expands:
- if not tag[u'protected'] and not tag[u'temporary']:
+ 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''))
diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py
index df2c3d673..a08b004ca 100644
--- a/openlp/core/ui/formattingtagform.py
+++ b/openlp/core/ui/formattingtagform.py
@@ -177,7 +177,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
QtGui.QTableWidgetItem(html[u'start html']))
self.tagTableWidget.setItem(linenumber, 3,
QtGui.QTableWidgetItem(html[u'end html']))
- # Tags saved prior to 1.9.7 do not have this key.
+ # Permanent (persistent) tags do not have this key.
if u'temporary' not in html:
html[u'temporary'] = False
self.tagTableWidget.resizeRowsToContents()
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index 4a0ebc6af..d9aafd23f 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -562,8 +562,11 @@ class OpenLyrics(object):
# Some tags have only start html e.g. {br}
u'end html': tag.close.text if hasattr(tag, 'close') else u'',
u'protected': False,
- u'temporary': temporary
}
+ # 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()]
From 1ef223b4a39036a3990172450901cc70f746e9b4 Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Fri, 23 Sep 2011 02:12:55 +0200
Subject: [PATCH 42/43] Fix issues with ignoring comments and chords
---
openlp/plugins/songs/lib/xml.py | 33 ++++++++++++++++++++-------------
1 file changed, 20 insertions(+), 13 deletions(-)
diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py
index d9aafd23f..5b36a7cb9 100644
--- a/openlp/plugins/songs/lib/xml.py
+++ b/openlp/plugins/songs/lib/xml.py
@@ -589,14 +589,16 @@ class OpenLyrics(object):
text = u''
use_endtag = True
# Skip elements - not yet supported.
- if element.tag == NSMAP % u'comment' and element.tail:
- # Append tail text at chord element.
- text += element.tail
+ 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' and element.tail:
- # Append tail text at chord element.
- text += element.tail
+ 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':
@@ -626,7 +628,7 @@ class OpenLyrics(object):
text += element.tail
return text
- def _process_verse_lines(self, lines):
+ def _process_verse_lines(self, lines, version):
"""
Converts lyrics lines to OpenLP representation.
@@ -637,18 +639,22 @@ class OpenLyrics(object):
# 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.
- if element[0].tag == NSMAP % 'line':
+ 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)
- # OpenLyrics 0.8 uses
for new lines.
- # Append text from "lines" element to verse text.
- else:
- text = self._process_lines_mixed_content(element)
return text
def _process_lyrics(self, properties, song_xml, song_obj):
@@ -676,7 +682,8 @@ class OpenLyrics(object):
if text:
text += u'\n'
# Append text from "lines" element to verse text.
- text += self._process_verse_lines(lines)
+ 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[---]'
From 7d016dd6a6f053baa4861d1f250424562e33246c Mon Sep 17 00:00:00 2001
From: Martin Zibricky
Date: Fri, 23 Sep 2011 02:41:01 +0200
Subject: [PATCH 43/43] Fix regressions with formattingtagsform
---
openlp/core/ui/formattingtagform.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py
index a08b004ca..e7435e5b7 100644
--- a/openlp/core/ui/formattingtagform.py
+++ b/openlp/core/ui/formattingtagform.py
@@ -57,6 +57,8 @@ 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):
"""
@@ -72,7 +74,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
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']))
@@ -97,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'),
@@ -135,7 +137,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
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())
@@ -167,7 +169,7 @@ 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()):
+ 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']))