More work on chords

This commit is contained in:
Tomas Groth 2016-07-25 22:07:07 +02:00
parent 530f119938
commit 611c970eb0
10 changed files with 169 additions and 54 deletions

View File

@ -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 = '<span class="ws">' + whitespaces + '</span>'
return '<span class="chord"><span><strong>' + chord + '</strong></span></span>' + 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 = '<span class="chordline">'
else:
new_line = '<span class="chordline firstchordline">'
chords_on_last_line = True
new_line += re.sub(r'(.*?)\[(.+?)\](.*?)',
r'\1<span class="chord"><span><strong>\2</strong></span></span>\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<span class="chord"><span><strong>\2</strong></span></span>\3', line)
new_line += '</span>'
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)

View File

@ -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;

View File

@ -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=/<span class="chord"><span><strong>([\(\w#b♭\+\*\d/\)-]+)<\/strong><\/span><\/span>([\u0080-\uFFFF,\w]*)([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(<br>)?/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=/<span class="chord"><span><strong>([\(\w#b♭\+\*\d/\)-]+)<\/strong><\/span><\/span>([\u0080-\uFFFF,\w]*)(<span class="ws">.+?<\/span>)?([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(<br>)?/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 += '&nbsp;';}
for (c = 0; c < $chordlen - $taillen + 2; c++) {w += '&nbsp;';}
}
} 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 += '&nbsp;';}
for (c = 0; c < $chordlen - $taillen + 1; c++) {w += '&nbsp;';}
}
};
} else {
if (!$2 && $3.charAt(0) == ' ') {for (c = 0; c < $1len; c++) {w += '&nbsp;';}}
if (!$tail && $remainder.charAt(0) == ' ') {for (c = 0; c < $chordlen; c++) {w += '&nbsp;';}}
}
return $.grep(['<span class="chord"><span><strong>', $1, '</strong></span>', $2, w, $3, '</span>', $4], Boolean).join('');
if (w!='') {
w='<span class="ws">' + w + '</span>';
}
return $.grep(['<span class="chord"><span><strong>', $chord, '</strong></span></span>', $tail, w, $remainder, $end], Boolean).join('');
};
$("#verseorder span").removeClass("currenttag");
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");

View File

@ -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('_', '')

View File

@ -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:

View File

@ -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*.
``<chord>``
This property is not supported.
This property is fully supported.
``<comments>``
The ``<comments>`` 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'<chord name="\1"/>', text)
new_text = re.sub(r'\[(\w.*?)\]', r'<chord name="\1"/>', 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 <br/>. The <br/> 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 <line> 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

View File

@ -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')],

View File

@ -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')))

View File

@ -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"
]
]

View File

@ -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"
]
]