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
+}