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#h&#Cl#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 z&#Hv4(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']))