diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 90d139673..65c2a972f 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -27,6 +27,7 @@ OpenLP work. import logging import os import re +import math from distutils.version import LooseVersion from PyQt5 import QtCore, QtGui, Qt, QtWidgets @@ -314,6 +315,63 @@ def expand_tags(text): return text +def expand_and_align_chords_in_line(match): + """ + Expand the chords in the line and align them using whitespaces. + NOTE: There is equivalent javascript code in chords.js, in the updateSlide function. Make sure to update both! + + :param match: + :return: The line with expanded html-chords + """ + slimchars = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\' + whitespaces = '' + chordlen = 0 + taillen = 0 + chord = match.group(1) + tail = match.group(2) + remainder = match.group(3) + end = match.group(4) + print('chord: %s, tail: %s, remainder: %s, end: %s' % (chord, tail, remainder, end)) + for chord_char in chord: + if chord_char not in slimchars: + chordlen += 2 + else: + chordlen += 1 + for tail_char in tail: + if tail_char not in slimchars: + taillen += 2 + else: + taillen += 1 + for remainder_char in remainder: + if remainder_char not in slimchars: + taillen += 2 + else: + taillen += 1 + if chordlen >= taillen and end is None: + if tail: + if not remainder: + print() + for c in range(math.ceil((chordlen - taillen) / 2) + 1): + whitespaces += '_' + else: + for c in range(chordlen - taillen + 2): + whitespaces += ' ' + else: + if not remainder: + for c in range(math.floor((chordlen - taillen) / 2)): + whitespaces += '_' + else: + for c in range(chordlen - taillen + 1): + whitespaces += ' ' + else: + if not tail and remainder and remainder[0] == ' ': + for c in range(chordlen): + whitespaces += ' ' + if whitespaces: + whitespaces = '' + whitespaces + '' + return '' + chord + '' + tail + whitespaces + remainder + + def expand_chords(text): """ Expand ChordPro tags @@ -322,21 +380,22 @@ def expand_chords(text): """ text_lines = text.split('{br}') expanded_text_lines = [] - chords_on_last_line = False + chords_on_prev_line = False for line in text_lines: # If a ChordPro is detected in the line, replace it with a html-span tag and wrap the line in a span tag. if '[' in line and ']' in line: - if chords_on_last_line: + if chords_on_prev_line: new_line = '' else: new_line = '' - chords_on_last_line = True - new_line += re.sub(r'(.*?)\[(.+?)\](.*?)', - r'\1\2\3', line) + chords_on_prev_line = True + new_line += re.sub(r'\[(.+?)\]([\u0080-\uFFFF,\w]*)([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?', expand_and_align_chords_in_line, line) + #new_line += re.sub(r'(.*?)\[(.+?)\](.*?)', + # r'\1\2\3', line) new_line += '' expanded_text_lines.append(new_line) else: - chords_on_last_line = False + chords_on_prev_line = False expanded_text_lines.append(line) return '{br}'.join(expanded_text_lines) diff --git a/openlp/plugins/remotes/html/css/chords.css b/openlp/plugins/remotes/html/css/chords.css index 8c15b4637..4ba87e5f8 100644 --- a/openlp/plugins/remotes/html/css/chords.css +++ b/openlp/plugins/remotes/html/css/chords.css @@ -43,8 +43,8 @@ #plus, #minus { display: inline-block; - width: 3vw; - line-height: 3vw; + width: 1.2em; + line-height: 1.2em; vertical-align: middle; color: white; background-color: gray; diff --git a/openlp/plugins/remotes/html/js/chords.js b/openlp/plugins/remotes/html/js/chords.js index ac62fe253..1e673636b 100644 --- a/openlp/plugins/remotes/html/js/chords.js +++ b/openlp/plugins/remotes/html/js/chords.js @@ -178,32 +178,43 @@ window.OpenLP = { var transposeValue = getTransposeValue(OpenLP.currentSlides[0].text.split("\n")[0]); var chordclass=/class="[a-z\s]*chord[a-z\s]*"\s*style="display:\s?none"/g; var chordclassshow='class="chord"'; - var regchord=/([\(\w#b♭\+\*\d/\)-]+)<\/strong><\/span><\/span>([\u0080-\uFFFF,\w]*)([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(
)?/g; - var replaceChords=function(mstr,$1,$2,$3,$4) { - var v='', w=''; - var $1len = 0, $2len = 0, slimchars='fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'; - $1 = transposeChord($1, transposeValue, OpenLP.chordNotation); - for (var i = 0; i < $1.length; i++) if (slimchars.indexOf($1.charAt(i)) === -1) {$1len += 2;} else {$1len += 1;} - for (var i = 0; i < $2.length; i++) if (slimchars.indexOf($2.charAt(i)) === -1) {$2len += 2;} else {$2len += 1;} - for (var i = 0; i < $3.length; i++) if (slimchars.indexOf($2.charAt(i)) === -1) {$2len += 2;} else {$2len += 1;} - if ($1len >= $2len && !$4) { - if ($2.length){ - if (!$3.length) { - for (c = 0; c < Math.ceil(($1len - $2len) / 2) + 1; c++) {w += '_';} + var regchord=/([\(\w#b♭\+\*\d/\)-]+)<\/strong><\/span><\/span>([\u0080-\uFFFF,\w]*)(.+?<\/span>)?([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(
)?/g; + // NOTE: There is equivalent python code in openlp/core/lib/__init__.py, in the expand_and_align_chords_in_line function. Make sure to update both! + var replaceChords=function(mstr,$chord,$tail,$skips,$remainder,$end) { + var v=''; + var w=''; + var $chordlen = 0; + var $taillen = 0; + var slimchars='fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'; + // Transpose chord as dictated by the transpose value in local storage + $chord = transposeChord($chord, transposeValue, OpenLP.chordNotation); + // Replace any padding '_' added to tail + $tail = $tail.replace(/_+$/, '') + console.log('chord: ' +$chord +', tail: ' + $tail + ', remainder: ' + $remainder +', end: ' + $end +', match: ' + mstr) + for (var i = 0; i < $chord.length; i++) if (slimchars.indexOf($chord.charAt(i)) === -1) {$chordlen += 2;} else {$chordlen += 1;} + for (var i = 0; i < $tail.length; i++) if (slimchars.indexOf($tail.charAt(i)) === -1) {$taillen += 2;} else {$taillen += 1;} + for (var i = 0; i < $remainder.length; i++) if (slimchars.indexOf($tail.charAt(i)) === -1) {$taillen += 2;} else {$taillen += 1;} + if ($chordlen >= $taillen && !$end) { + if ($tail.length){ + if (!$remainder.length) { + for (c = 0; c < Math.ceil(($chordlen - $taillen) / 2) + 1; c++) {w += '_';} } else { - for (c = 0; c < $1len - $2len + 2; c++) {w += ' ';} + for (c = 0; c < $chordlen - $taillen + 2; c++) {w += ' ';} } } else { - if (!$3.length) { - for (c = 0; c < Math.floor(($1len - $2len) / 2) + 1; c++) {w += '_';} + if (!$remainder.length) { + for (c = 0; c < Math.floor(($chordlen - $taillen) / 2) + 1; c++) {w += '_';} } else { - for (c = 0; c < $1len - $2len + 1; c++) {w += ' ';} + for (c = 0; c < $chordlen - $taillen + 1; c++) {w += ' ';} } }; } else { - if (!$2 && $3.charAt(0) == ' ') {for (c = 0; c < $1len; c++) {w += ' ';}} + if (!$tail && $remainder.charAt(0) == ' ') {for (c = 0; c < $chordlen; c++) {w += ' ';}} } - return $.grep(['', $1, '', $2, w, $3, '', $4], Boolean).join(''); + if (w!='') { + w='' + w + ''; + } + return $.grep(['', $chord, '', $tail, w, $remainder, $end], Boolean).join(''); }; $("#verseorder span").removeClass("currenttag"); $("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag"); diff --git a/openlp/plugins/songs/lib/importers/opensong.py b/openlp/plugins/songs/lib/importers/opensong.py index 161f8cd68..221c8b772 100644 --- a/openlp/plugins/songs/lib/importers/opensong.py +++ b/openlp/plugins/songs/lib/importers/opensong.py @@ -26,7 +26,7 @@ import re from lxml import objectify from lxml.etree import Error, LxmlError -from openlp.core.common import translate +from openlp.core.common import translate, Settings from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.ui import SongStrings @@ -87,7 +87,7 @@ class OpenSongImport(SongImport): All verses are imported and tagged appropriately. Guitar chords can be provided "above" the lyrics (the line is preceded by a period "."), and one or more "_" can - be used to signify long-drawn-out words. Chords and "_" are removed by this importer. For example:: + be used to signify long-drawn-out words. For example:: . A7 Bm 1 Some____ Words @@ -195,14 +195,34 @@ class OpenSongImport(SongImport): lyrics = str(root.lyrics) else: lyrics = '' + chords = [] for this_line in lyrics.split('\n'): if not this_line.strip(): continue # skip this line if it is a comment if this_line.startswith(';'): continue - # skip guitar chords and page and column breaks - if this_line.startswith('.') or this_line.startswith('---') or this_line.startswith('-!!'): + # skip page and column breaks + if this_line.startswith('---') or this_line.startswith('-!!'): + continue + # guitar chords marker + if this_line.startswith('.'): + # Find the position of the chords so they can be inserted in the lyrics + chords = [] + this_line = this_line[1:] + chord = '' + i = 0 + while i < len(this_line): + if this_line[i] != ' ': + chord_pos = i + chord += this_line[i] + i += 1 + while i < len(this_line) and this_line[i] != ' ': + chord += this_line[i] + i += 1 + chords.append((chord_pos, chord)) + chord = '' + i += 1 continue # verse/chorus/etc. marker if this_line.startswith('['): @@ -228,12 +248,19 @@ class OpenSongImport(SongImport): # number at start of line.. it's verse number if this_line[0].isdigit(): verse_num = this_line[0] - this_line = this_line[1:].strip() + this_line = this_line[1:] verses.setdefault(verse_tag, {}) verses[verse_tag].setdefault(verse_num, {}) if inst not in verses[verse_tag][verse_num]: verses[verse_tag][verse_num][inst] = [] our_verse_order.append([verse_tag, verse_num, inst]) + # If chords exists insert them + if chords and not Settings().value('songs/disable chords import'): + offset = 0 + for (column, chord) in chords: + this_line = '{pre}[{chord}]{post}'.format(pre=this_line[:offset+column], chord=chord, + post=this_line[offset+column:]) + offset += len(chord) + 2 # Tidy text and remove the ____s from extended words this_line = self.tidy_text(this_line) this_line = this_line.replace('_', '') diff --git a/openlp/plugins/songs/lib/importers/videopsalm.py b/openlp/plugins/songs/lib/importers/videopsalm.py index 60c8e27e4..60e76a067 100644 --- a/openlp/plugins/songs/lib/importers/videopsalm.py +++ b/openlp/plugins/songs/lib/importers/videopsalm.py @@ -26,8 +26,9 @@ exproted from Lyrix.""" import logging import json import os +import re -from openlp.core.common import translate +from openlp.core.common import translate, Settings from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.db import AuthorType @@ -113,7 +114,11 @@ class VideoPsalmImport(SongImport): if 'Memo3' in song: self.add_comment(song['Memo3']) for verse in song['Verses']: - self.add_verse(verse['Text'], 'v') + verse_text = verse['Text'] + # Strip out chords if set up to + if not Settings().value('songs/disable chords import'): + verse_text = re.sub(r'\[\w.*?\]', '', verse_text) + self.add_verse(verse_text, 'v') if not self.finish(): self.log_error('Could not import {title}'.format(title=self.title)) except Exception as e: diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py index 91a186329..5236b2fbd 100644 --- a/openlp/plugins/songs/lib/openlyricsxml.py +++ b/openlp/plugins/songs/lib/openlyricsxml.py @@ -61,7 +61,7 @@ import re from lxml import etree, objectify -from openlp.core.common import translate +from openlp.core.common import translate, Settings from openlp.core.common.versionchecker import get_application_version from openlp.core.lib import FormattingTags from openlp.plugins.songs.lib import VerseType, clean_song @@ -154,7 +154,7 @@ class OpenLyrics(object): OpenLP does not support the attribute *lang*. ```` - This property is not supported. + This property is fully supported. ```` The ```` property is fully supported. But comments in lyrics are not supported. @@ -334,7 +334,7 @@ class OpenLyrics(object): :return: the lyrics with the converted chords """ # Process chords. - new_text = re.sub(r'\[(..?.?)\]', r'', text) + new_text = re.sub(r'\[(\w.*?)\]', r'', text) return new_text def _get_missing_tags(self, text): @@ -607,8 +607,7 @@ class OpenLyrics(object): 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. + Converts the xml text with mixed content to OpenLP representation. Chords and formatting tags are converted. :param element: The property object (lxml.etree.Element). :param newlines: The switch to enable/disable processing of line breaks
. The
is used since @@ -620,13 +619,14 @@ class OpenLyrics(object): # TODO: Verify format() with template variables if element.tag == NSMAP % 'comment': if element.tail: - # Append tail text at chord element. + # Append tail text at comment element. text += element.tail return text # Convert chords to ChordPro format which OpenLP uses internally # TODO: Verify format() with template variables elif element.tag == NSMAP % 'chord': - text += '[{chord}]'.format(chord=element.get('name')) + if not Settings().value('songs/disable chords import'): + text += '[{chord}]'.format(chord=element.get('name')) if element.tail: # Append tail text at chord element. text += element.tail @@ -679,7 +679,7 @@ class OpenLyrics(object): text = self._process_lines_mixed_content(element) # OpenLyrics version <= 0.7 contains elements to represent lines. First child element is tested. else: - # Loop over the "line" elements removing comments and chords. + # Loop over the "line" elements removing comments for line in element: # Skip comment lines. # TODO: Verify format() with template variables diff --git a/tests/functional/openlp_plugins/songs/test_opensongimport.py b/tests/functional/openlp_plugins/songs/test_opensongimport.py index d1386005f..c88b10b2b 100644 --- a/tests/functional/openlp_plugins/songs/test_opensongimport.py +++ b/tests/functional/openlp_plugins/songs/test_opensongimport.py @@ -42,10 +42,16 @@ class TestOpenSongFileImport(SongImportTestHelper): self.importer_module_name = 'opensong' super(TestOpenSongFileImport, self).__init__(*args, **kwargs) - def test_song_import(self): + @patch('openlp.plugins.songs.lib.importers.opensong.Settings') + def test_song_import(self, mocked_settings): """ Test that loading an OpenSong file works correctly on various files """ + # Mock out the settings - always return False + mocked_returned_settings = MagicMock() + mocked_returned_settings.value.return_value = False + mocked_settings.return_value = mocked_returned_settings + # Do the test import self.file_import([os.path.join(TEST_PATH, 'Amazing Grace')], self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer')], diff --git a/tests/functional/openlp_plugins/songs/test_videopsalm.py b/tests/functional/openlp_plugins/songs/test_videopsalm.py index 1bf13241d..b4eea453f 100644 --- a/tests/functional/openlp_plugins/songs/test_videopsalm.py +++ b/tests/functional/openlp_plugins/songs/test_videopsalm.py @@ -25,6 +25,7 @@ This module contains tests for the VideoPsalm song importer. import os from tests.helpers.songfileimport import SongImportTestHelper +from tests.functional import patch, MagicMock TEST_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'videopsalmsongs')) @@ -37,9 +38,15 @@ class TestVideoPsalmFileImport(SongImportTestHelper): self.importer_module_name = 'videopsalm' super(TestVideoPsalmFileImport, self).__init__(*args, **kwargs) - def test_song_import(self): + @patch('openlp.plugins.songs.lib.importers.videopsalm.Settings') + def test_song_import(self, mocked_settings): """ Test that loading an VideoPsalm file works correctly on various files """ + # Mock out the settings - always return False + mocked_returned_settings = MagicMock() + mocked_returned_settings.value.return_value = False + mocked_settings.return_value = mocked_returned_settings + # Do the test import self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'), self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold.json'))) diff --git a/tests/resources/opensongsongs/Amazing Grace without CCLI.json b/tests/resources/opensongsongs/Amazing Grace without CCLI.json index 799fd33d9..e830282f0 100644 --- a/tests/resources/opensongsongs/Amazing Grace without CCLI.json +++ b/tests/resources/opensongsongs/Amazing Grace without CCLI.json @@ -19,23 +19,23 @@ "verse_order_list": [], "verses": [ [ - "Amazing grace! How sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.", + "A[D]mazing [D7]grace! How [G]sweet the [D]sound!\nThat [Bm]saved a [E]wretch like [A]me![A7]\nI [D]once was [D7]lost, but [G]now am [D]found;\nWas [Bm]blind, but [A]now I [G]see.[D]", "v1" ], [ - "'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.", + "'Twas [D]grace that [D7]taught my [G]heart to [D]fear,\nAnd [Bm]grace my [E]fears re[A]lieved.[A7]\nHow [D]precious [D7]did that [G]grace ap[D]pear,\nThe [Bm]hour I [A]first be[G]lieved.[D]", "v2" ], [ - "The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.", + "The [D]Lord has [D7]promised [G]good to [D]me,\nHis [Bm]Word my [E]hope se[A]cures.[A7]\nHe [D]will my [D7]shield and [G]portion [D]be\nAs [Bm]long as [A]life en[G]dures.[D]", "v3" ], [ - "Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.", + "Thro' [D]many [D7]dangers, [G]toils and [D]snares\nI [Bm]have al[E]ready [A]come.[A7]\n'Tis [D]grace that [D7]brought me [G]safe thus [D]far,\nAnd [Bm]grace will [A]lead me [G]home.[D]", "v4" ], [ - "When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.", + "When [D]we've been [D7]there ten [G]thousand [D]years,\nBright [Bm]shining [E]as the [A]sun,[A7]\nWe've [D]no less [D7]days to [G]sing God's [D]praise,\nThan [Bm]when we [A]first be[G]gun.[D]", "v5" ] ] diff --git a/tests/resources/opensongsongs/Amazing Grace.json b/tests/resources/opensongsongs/Amazing Grace.json index 97b8c77b7..2fe72b2ba 100644 --- a/tests/resources/opensongsongs/Amazing Grace.json +++ b/tests/resources/opensongsongs/Amazing Grace.json @@ -19,24 +19,24 @@ "verse_order_list": [], "verses": [ [ - "Amazing grace! How sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.", + "A[D]mazing [D7]grace! How [G]sweet the [D]sound!\nThat [Bm]saved a [E]wretch like [A]me![A7]\nI [D]once was [D7]lost, but [G]now am [D]found;\nWas [Bm]blind, but [A]now I [G]see.[D]", "v1" ], [ - "'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.", + "'Twas [D]grace that [D7]taught my [G]heart to [D]fear,\nAnd [Bm]grace my [E]fears re[A]lieved.[A7]\nHow [D]precious [D7]did that [G]grace ap[D]pear,\nThe [Bm]hour I [A]first be[G]lieved.[D]", "v2" ], [ - "The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.", + "The [D]Lord has [D7]promised [G]good to [D]me,\nHis [Bm]Word my [E]hope se[A]cures.[A7]\nHe [D]will my [D7]shield and [G]portion [D]be\nAs [Bm]long as [A]life en[G]dures.[D]", "v3" ], [ - "Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.", + "Thro' [D]many [D7]dangers, [G]toils and [D]snares\nI [Bm]have al[E]ready [A]come.[A7]\n'Tis [D]grace that [D7]brought me [G]safe thus [D]far,\nAnd [Bm]grace will [A]lead me [G]home.[D]", "v4" ], [ - "When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.", + "When [D]we've been [D7]there ten [G]thousand [D]years,\nBright [Bm]shining [E]as the [A]sun,[A7]\nWe've [D]no less [D7]days to [G]sing God's [D]praise,\nThan [Bm]when we [A]first be[G]gun.[D]", "v5" ] ] -} \ No newline at end of file +}