forked from openlp/openlp
Songs cleanned up
This commit is contained in:
parent
a0a2e5efbc
commit
f86c6bc77d
@ -67,7 +67,7 @@ class EasySlidesImport(SongImport):
|
|||||||
self._success = True
|
self._success = True
|
||||||
self._add_unicode_attribute('title', song.Title1, True)
|
self._add_unicode_attribute('title', song.Title1, True)
|
||||||
if hasattr(song, 'Title2'):
|
if hasattr(song, 'Title2'):
|
||||||
self._add_unicode_attribute('alternateTitle', song.Title2)
|
self._add_unicode_attribute('alternate_title', song.Title2)
|
||||||
if hasattr(song, 'SongNumber'):
|
if hasattr(song, 'SongNumber'):
|
||||||
self._add_unicode_attribute('song_number', song.SongNumber)
|
self._add_unicode_attribute('song_number', song.SongNumber)
|
||||||
if self.song_number == '0':
|
if self.song_number == '0':
|
||||||
|
@ -69,8 +69,8 @@ class SofImport(OooImport):
|
|||||||
|
|
||||||
Use OpenOffice.org Writer for processing the rtf file
|
Use OpenOffice.org Writer for processing the rtf file
|
||||||
|
|
||||||
The three books are not only inconsistant with each other, they are
|
The three books are not only inconsistent with each other, they are
|
||||||
inconsistant in themselves too with their formatting. Not only this, but
|
inconsistent in themselves too with their formatting. Not only this, but
|
||||||
the 1+2 book does not space out verses correctly. This script attempts
|
the 1+2 book does not space out verses correctly. This script attempts
|
||||||
to sort it out, but doesn't get it 100% right. But better than having to
|
to sort it out, but doesn't get it 100% right. But better than having to
|
||||||
type them all out!
|
type them all out!
|
||||||
@ -90,14 +90,14 @@ class SofImport(OooImport):
|
|||||||
"""
|
"""
|
||||||
Handle the import process for SoF files.
|
Handle the import process for SoF files.
|
||||||
"""
|
"""
|
||||||
self.processSofFile()
|
self.process_sof_file()
|
||||||
|
|
||||||
def processSofFile(self):
|
def process_sof_file(self):
|
||||||
"""
|
"""
|
||||||
Process the RTF file, a paragraph at a time
|
Process the RTF file, a paragraph at a time
|
||||||
"""
|
"""
|
||||||
self.blankLines = 0
|
self.blank_lines = 0
|
||||||
self.newSong()
|
self.new_song()
|
||||||
try:
|
try:
|
||||||
paragraphs = self.document.getText().createEnumeration()
|
paragraphs = self.document.getText().createEnumeration()
|
||||||
while paragraphs.hasMoreElements():
|
while paragraphs.hasMoreElements():
|
||||||
@ -105,65 +105,68 @@ class SofImport(OooImport):
|
|||||||
return
|
return
|
||||||
paragraph = paragraphs.nextElement()
|
paragraph = paragraphs.nextElement()
|
||||||
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
||||||
self.processParagraph(paragraph)
|
self.process_paragraph(paragraph)
|
||||||
except RuntimeException as exc:
|
except RuntimeException as exc:
|
||||||
log.exception('Error processing file: %s', exc)
|
log.exception('Error processing file: %s', exc)
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(self.file_path)
|
self.log_error(self.file_path)
|
||||||
|
|
||||||
def processParagraph(self, paragraph):
|
def process_paragraph(self, paragraph):
|
||||||
"""
|
"""
|
||||||
Process a paragraph.
|
Process a paragraph.
|
||||||
In the first book, a paragraph is a single line. In the latter ones
|
In the first book, a paragraph is a single line. In the latter ones they may contain multiple lines.
|
||||||
they may contain multiple lines.
|
Each paragraph contains textportions. Each textportion has it's own styling, e.g. italics, bold etc.
|
||||||
Each paragraph contains textportions. Each textportion has it's own
|
|
||||||
styling, e.g. italics, bold etc.
|
|
||||||
Also check for page breaks, which indicates a new song in books 1+2.
|
Also check for page breaks, which indicates a new song in books 1+2.
|
||||||
In later books, there may not be line breaks, so check for 3 or more
|
In later books, there may not be line breaks, so check for 3 or more newlines
|
||||||
newlines
|
|
||||||
|
:param paragraph: The paragraph text
|
||||||
"""
|
"""
|
||||||
text = ''
|
text = ''
|
||||||
textportions = paragraph.createEnumeration()
|
text_portions = paragraph.createEnumeration()
|
||||||
while textportions.hasMoreElements():
|
while text_portions.hasMoreElements():
|
||||||
textportion = textportions.nextElement()
|
text_portion = text_portions.nextElement()
|
||||||
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
|
if text_portion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
|
||||||
self.processParagraphText(text)
|
self.process_paragraph_text(text)
|
||||||
self.newSong()
|
self.new_song()
|
||||||
text = ''
|
text = ''
|
||||||
text += self.processTextPortion(textportion)
|
text += self.process_text_portion(text_portion)
|
||||||
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
|
if text_portion.BreakType in (PAGE_AFTER, PAGE_BOTH):
|
||||||
self.processParagraphText(text)
|
self.process_paragraph_text(text)
|
||||||
self.newSong()
|
self.new_song()
|
||||||
text = ''
|
text = ''
|
||||||
self.processParagraphText(text)
|
self.process_paragraph_text(text)
|
||||||
|
|
||||||
def processParagraphText(self, text):
|
def process_paragraph_text(self, text):
|
||||||
"""
|
"""
|
||||||
Split the paragraph text into multiple lines and process
|
Split the paragraph text into multiple lines and process
|
||||||
|
|
||||||
|
:param text: The text
|
||||||
"""
|
"""
|
||||||
for line in text.split('\n'):
|
for line in text.split('\n'):
|
||||||
self.processParagraphLine(line)
|
self.process_paragraph_line(line)
|
||||||
if self.blankLines > 2:
|
if self.blank_lines > 2:
|
||||||
self.newSong()
|
self.new_song()
|
||||||
|
|
||||||
def processParagraphLine(self, text):
|
def process_paragraph_line(self, text):
|
||||||
"""
|
"""
|
||||||
Process a single line. Throw away that text which isn't relevant, i.e.
|
Process a single line. Throw away that text which isn't relevant, i.e.
|
||||||
stuff that appears at the end of the song.
|
stuff that appears at the end of the song.
|
||||||
Anything that is OK, append to the current verse
|
Anything that is OK, append to the current verse
|
||||||
|
|
||||||
|
:param text: The text
|
||||||
"""
|
"""
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
if text == '':
|
if text == '':
|
||||||
self.blankLines += 1
|
self.blank_lines += 1
|
||||||
if self.blankLines > 1:
|
if self.blank_lines > 1:
|
||||||
return
|
return
|
||||||
if self.title != '':
|
if self.title != '':
|
||||||
self.finishVerse()
|
self.finish_verse()
|
||||||
return
|
return
|
||||||
self.blankLines = 0
|
self.blank_lines = 0
|
||||||
if self.skipToCloseBracket:
|
if self.skip_to_close_bracket:
|
||||||
if text.endswith(')'):
|
if text.endswith(')'):
|
||||||
self.skipToCloseBracket = False
|
self.skip_to_close_bracket = False
|
||||||
return
|
return
|
||||||
if text.startswith('CCL Licence'):
|
if text.startswith('CCL Licence'):
|
||||||
self.italics = False
|
self.italics = False
|
||||||
@ -171,90 +174,93 @@ class SofImport(OooImport):
|
|||||||
if text == 'A Songs of Fellowship Worship Resource':
|
if text == 'A Songs of Fellowship Worship Resource':
|
||||||
return
|
return
|
||||||
if text.startswith('(NB.') or text.startswith('(Regrettably') or text.startswith('(From'):
|
if text.startswith('(NB.') or text.startswith('(Regrettably') or text.startswith('(From'):
|
||||||
self.skipToCloseBracket = True
|
self.skip_to_close_bracket = True
|
||||||
return
|
return
|
||||||
if text.startswith('Copyright'):
|
if text.startswith('Copyright'):
|
||||||
self.add_copyright(text)
|
self.add_copyright(text)
|
||||||
return
|
return
|
||||||
if text == '(Repeat)':
|
if text == '(Repeat)':
|
||||||
self.finishVerse()
|
self.finish_verse()
|
||||||
self.repeat_verse()
|
self.repeat_verse()
|
||||||
return
|
return
|
||||||
if self.title == '':
|
if self.title == '':
|
||||||
if self.copyright == '':
|
if self.copyright == '':
|
||||||
self.addSofAuthor(text)
|
self.add_sof_author(text)
|
||||||
else:
|
else:
|
||||||
self.add_copyright(text)
|
self.add_copyright(text)
|
||||||
return
|
return
|
||||||
self.addVerseLine(text)
|
self.add_verse_line(text)
|
||||||
|
|
||||||
def processTextPortion(self, textportion):
|
def process_text_portion(self, text_portion):
|
||||||
"""
|
"""
|
||||||
Process a text portion. Here we just get the text and detect if
|
Process a text portion. Here we just get the text and detect if it's bold or italics. If it's bold then its a
|
||||||
it's bold or italics. If it's bold then its a song number or song title.
|
song number or song title.
|
||||||
Song titles are in all capitals, so we must bring the capitalization
|
Song titles are in all capitals, so we must bring the capitalization into line
|
||||||
into line
|
|
||||||
|
:param text_portion: A Piece of text
|
||||||
"""
|
"""
|
||||||
text = textportion.getString()
|
text = text_portion.getString()
|
||||||
text = self.tidy_text(text)
|
text = self.tidy_text(text)
|
||||||
if text.strip() == '':
|
if text.strip() == '':
|
||||||
return text
|
return text
|
||||||
if textportion.CharWeight == BOLD:
|
if text_portion.CharWeight == BOLD:
|
||||||
boldtext = text.strip()
|
bold_text = text.strip()
|
||||||
if boldtext.isdigit() and self.song_number == '':
|
if bold_text.isdigit() and self.song_number == '':
|
||||||
self.addSongNumber(boldtext)
|
self.add_song_number(bold_text)
|
||||||
return ''
|
return ''
|
||||||
text = self.uncapText(text)
|
text = self.uncap_text(text)
|
||||||
if self.title == '':
|
if self.title == '':
|
||||||
self.addTitle(text)
|
self.add_title(text)
|
||||||
return text
|
return text
|
||||||
if text.strip().startswith('('):
|
if text.strip().startswith('('):
|
||||||
return text
|
return text
|
||||||
self.italics = (textportion.CharPosture == ITALIC)
|
self.italics = (text_portion.CharPosture == ITALIC)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def newSong(self):
|
def new_song(self):
|
||||||
"""
|
"""
|
||||||
A change of song. Store the old, create a new
|
A change of song. Store the old, create a new
|
||||||
... but only if the last song was complete. If not, stick with it
|
... but only if the last song was complete. If not, stick with it
|
||||||
"""
|
"""
|
||||||
if self.song:
|
if self.song:
|
||||||
self.finishVerse()
|
self.finish_verse()
|
||||||
if not self.check_complete():
|
if not self.check_complete():
|
||||||
return
|
return
|
||||||
self.finish()
|
self.finish()
|
||||||
self.song = True
|
self.song = True
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.skipToCloseBracket = False
|
self.skip_to_close_bracket = False
|
||||||
self.isChorus = False
|
self.is_chorus = False
|
||||||
self.italics = False
|
self.italics = False
|
||||||
self.currentVerse = ''
|
self.current__verse = ''
|
||||||
|
|
||||||
def addSongNumber(self, song_no):
|
def add_song_number(self, song_no):
|
||||||
"""
|
"""
|
||||||
Add a song number, store as alternate title. Also use the song
|
Add a song number, store as alternate title. Also use the song number to work out which songbook we're in
|
||||||
number to work out which songbook we're in
|
|
||||||
|
:param song_no: The Song number
|
||||||
"""
|
"""
|
||||||
self.song_number = song_no
|
self.song_number = song_no
|
||||||
self.alternateTitle = song_no + '.'
|
self.alternate_title = song_no + '.'
|
||||||
self.songBook_pub = 'Kingsway Publications'
|
self.song_book_pub = 'Kingsway Publications'
|
||||||
if int(song_no) <= 640:
|
if int(song_no) <= 640:
|
||||||
self.songBook = 'Songs of Fellowship 1'
|
self.song_book = 'Songs of Fellowship 1'
|
||||||
elif int(song_no) <= 1150:
|
elif int(song_no) <= 1150:
|
||||||
self.songBook = 'Songs of Fellowship 2'
|
self.song_book = 'Songs of Fellowship 2'
|
||||||
elif int(song_no) <= 1690:
|
elif int(song_no) <= 1690:
|
||||||
self.songBook = 'Songs of Fellowship 3'
|
self.song_book = 'Songs of Fellowship 3'
|
||||||
elif int(song_no) <= 2200:
|
elif int(song_no) <= 2200:
|
||||||
self.songBook = 'Songs of Fellowship 4'
|
self.song_book = 'Songs of Fellowship 4'
|
||||||
elif int(song_no) <= 2710:
|
elif int(song_no) <= 2710:
|
||||||
self.songBook = 'Songs of Fellowship 5'
|
self.song_book = 'Songs of Fellowship 5'
|
||||||
else:
|
else:
|
||||||
self.songBook = 'Songs of Fellowship Other'
|
self.song_book = 'Songs of Fellowship Other'
|
||||||
|
|
||||||
def addTitle(self, text):
|
def add_title(self, text):
|
||||||
"""
|
"""
|
||||||
Add the title to the song. Strip some leading/trailing punctuation that
|
Add the title to the song. Strip some leading/trailing punctuation that we don't want in a title
|
||||||
we don't want in a title
|
|
||||||
|
:param text: Title text
|
||||||
"""
|
"""
|
||||||
title = text.strip()
|
title = text.strip()
|
||||||
if title.startswith('\''):
|
if title.startswith('\''):
|
||||||
@ -264,50 +270,50 @@ class SofImport(OooImport):
|
|||||||
self.title = title
|
self.title = title
|
||||||
self.import_wizard.increment_progress_bar('Processing song ' + title, 0)
|
self.import_wizard.increment_progress_bar('Processing song ' + title, 0)
|
||||||
|
|
||||||
def addSofAuthor(self, text):
|
def add_sof_author(self, text):
|
||||||
"""
|
"""
|
||||||
Add the author. OpenLP stores them individually so split by 'and', '&'
|
Add the author. OpenLP stores them individually so split by 'and', '&' and comma.
|
||||||
and comma.
|
However need to check for "Mr and Mrs Smith" and turn it to "Mr Smith" and "Mrs Smith".
|
||||||
However need to check for "Mr and Mrs Smith" and turn it to
|
|
||||||
"Mr Smith" and "Mrs Smith".
|
:param text: Author text
|
||||||
"""
|
"""
|
||||||
text = text.replace(' and ', ' & ')
|
text = text.replace(' and ', ' & ')
|
||||||
self.parse_author(text)
|
self.parse_author(text)
|
||||||
|
|
||||||
def addVerseLine(self, text):
|
def add_verse_line(self, text):
|
||||||
"""
|
"""
|
||||||
Add a line to the current verse. If the formatting has changed and
|
Add a line to the current verse. If the formatting has changed and we're beyond the second line of first verse,
|
||||||
we're beyond the second line of first verse, then this indicates
|
then this indicates a change of verse. Italics are a chorus
|
||||||
a change of verse. Italics are a chorus
|
|
||||||
"""
|
|
||||||
if self.italics != self.isChorus and ((len(self.verses) > 0) or
|
|
||||||
(self.currentVerse.count('\n') > 1)):
|
|
||||||
self.finishVerse()
|
|
||||||
if self.italics:
|
|
||||||
self.isChorus = True
|
|
||||||
self.currentVerse += text + '\n'
|
|
||||||
|
|
||||||
def finishVerse(self):
|
:param text: The verse text
|
||||||
"""
|
"""
|
||||||
Verse is finished, store it. Note in book 1+2, some songs are formatted
|
if self.italics != self.is_chorus and ((len(self.verses) > 0) or
|
||||||
incorrectly. Here we try and split songs with missing line breaks into
|
(self.current__verse.count('\n') > 1)):
|
||||||
the correct number of verses.
|
self.finish_verse()
|
||||||
|
if self.italics:
|
||||||
|
self.is_chorus = True
|
||||||
|
self.current__verse += text + '\n'
|
||||||
|
|
||||||
|
def finish_verse(self):
|
||||||
"""
|
"""
|
||||||
if self.currentVerse.strip() == '':
|
Verse is finished, store it. Note in book 1+2, some songs are formatted incorrectly. Here we try and split
|
||||||
|
songs with missing line breaks into the correct number of verses.
|
||||||
|
"""
|
||||||
|
if self.current__verse.strip() == '':
|
||||||
return
|
return
|
||||||
if self.isChorus:
|
if self.is_chorus:
|
||||||
versetag = 'C'
|
versetag = 'C'
|
||||||
splitat = None
|
splitat = None
|
||||||
else:
|
else:
|
||||||
versetag = 'V'
|
versetag = 'V'
|
||||||
splitat = self.verseSplits(self.song_number)
|
splitat = self.verse_splits(self.song_number)
|
||||||
if splitat:
|
if splitat:
|
||||||
ln = 0
|
ln = 0
|
||||||
verse = ''
|
verse = ''
|
||||||
for line in self.currentVerse.split('\n'):
|
for line in self.current__verse.split('\n'):
|
||||||
ln += 1
|
ln += 1
|
||||||
if line == '' or ln > splitat:
|
if line == '' or ln > splitat:
|
||||||
self.addSofVerse(verse, versetag)
|
self.add_sof_verse(verse, versetag)
|
||||||
ln = 0
|
ln = 0
|
||||||
if line:
|
if line:
|
||||||
verse = line + '\n'
|
verse = line + '\n'
|
||||||
@ -316,19 +322,19 @@ class SofImport(OooImport):
|
|||||||
else:
|
else:
|
||||||
verse += line + '\n'
|
verse += line + '\n'
|
||||||
if verse:
|
if verse:
|
||||||
self.addSofVerse(verse, versetag)
|
self.add_sof_verse(verse, versetag)
|
||||||
else:
|
else:
|
||||||
self.addSofVerse(self.currentVerse, versetag)
|
self.add_sof_verse(self.current__verse, versetag)
|
||||||
self.currentVerse = ''
|
self.current__verse = ''
|
||||||
self.isChorus = False
|
self.is_chorus = False
|
||||||
|
|
||||||
def addSofVerse(self, lyrics, tag):
|
def add_sof_verse(self, lyrics, tag):
|
||||||
self.add_verse(lyrics, tag)
|
self.add_verse(lyrics, tag)
|
||||||
if not self.isChorus and 'C1' in self.verse_order_list_generated:
|
if not self.is_chorus and 'C1' in self.verse_order_list_generated:
|
||||||
self.verse_order_list_generated.append('C1')
|
self.verse_order_list_generated.append('C1')
|
||||||
self.verseOrderListGenerated_useful = True
|
self.verse_order_list_generated_useful = True
|
||||||
|
|
||||||
def uncapText(self, text):
|
def uncap_text(self, text):
|
||||||
"""
|
"""
|
||||||
Words in the title are in all capitals, so we lowercase them.
|
Words in the title are in all capitals, so we lowercase them.
|
||||||
However some of these words, e.g. referring to God need a leading
|
However some of these words, e.g. referring to God need a leading
|
||||||
@ -337,28 +343,26 @@ class SofImport(OooImport):
|
|||||||
There is a complicated word "One", which is sometimes lower and
|
There is a complicated word "One", which is sometimes lower and
|
||||||
sometimes upper depending on context. Never mind, keep it lower.
|
sometimes upper depending on context. Never mind, keep it lower.
|
||||||
"""
|
"""
|
||||||
textarr = re.split('(\W+)', text)
|
text_arr = re.split('(\W+)', text)
|
||||||
textarr[0] = textarr[0].capitalize()
|
text_arr[0] = text_arr[0].capitalize()
|
||||||
for i in range(1, len(textarr)):
|
for i in range(1, len(text_arr)):
|
||||||
# Do not translate these. Fixed strings in SOF song file
|
# Do not translate these. Fixed strings in SOF song file
|
||||||
if textarr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY',
|
if text_arr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY', 'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD',
|
||||||
'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD', 'FATHER',
|
'FATHER', 'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE', 'I\'M', 'I\'LL',
|
||||||
'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE',
|
'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS', 'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'',
|
||||||
'I\'M', 'I\'LL', 'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS',
|
'JIREH', 'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S', 'FATHER\'S', 'ELIJAH' 'MARTHA',
|
||||||
'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'', 'JIREH',
|
'CHRISTMAS', 'ALPHA', 'OMEGA'):
|
||||||
'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S',
|
text_arr[i] = text_arr[i].capitalize()
|
||||||
'FATHER\'S', 'ELIJAH' 'MARTHA', 'CHRISTMAS', 'ALPHA',
|
|
||||||
'OMEGA'):
|
|
||||||
textarr[i] = textarr[i].capitalize()
|
|
||||||
else:
|
else:
|
||||||
textarr[i] = textarr[i].lower()
|
text_arr[i] = text_arr[i].lower()
|
||||||
text = ''.join(textarr)
|
text = ''.join(text_arr)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def verseSplits(self, song_number):
|
def verse_splits(self, song_number):
|
||||||
"""
|
"""
|
||||||
Because someone at Kingsway forgot to check the 1+2 RTF file,
|
Because someone at Kingsway forgot to check the 1+2 RTF file, some verses were not formatted correctly.
|
||||||
some verses were not formatted correctly.
|
|
||||||
|
:param song_number: The Song number
|
||||||
"""
|
"""
|
||||||
if song_number == 11:
|
if song_number == 11:
|
||||||
return 8
|
return 8
|
||||||
|
@ -40,6 +40,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SongBeamerTypes(object):
|
class SongBeamerTypes(object):
|
||||||
MarkTypes = {
|
MarkTypes = {
|
||||||
'Refrain': VerseType.tags[VerseType.Chorus],
|
'Refrain': VerseType.tags[VerseType.Chorus],
|
||||||
@ -110,8 +111,8 @@ class SongBeamerImport(SongImport):
|
|||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.currentVerse = ''
|
self.current_verse = ''
|
||||||
self.currentVerseType = VerseType.tags[VerseType.Verse]
|
self.current_verse_type = VerseType.tags[VerseType.Verse]
|
||||||
read_verses = False
|
read_verses = False
|
||||||
file_name = os.path.split(import_file)[1]
|
file_name = os.path.split(import_file)[1]
|
||||||
if os.path.isfile(import_file):
|
if os.path.isfile(import_file):
|
||||||
@ -132,39 +133,38 @@ class SongBeamerImport(SongImport):
|
|||||||
if line.startswith('#') and not read_verses:
|
if line.startswith('#') and not read_verses:
|
||||||
self.parseTags(line)
|
self.parseTags(line)
|
||||||
elif line.startswith('---'):
|
elif line.startswith('---'):
|
||||||
if self.currentVerse:
|
if self.current_verse:
|
||||||
self.replaceHtmlTags()
|
self.replace_html_tags()
|
||||||
self.add_verse(self.currentVerse, self.currentVerseType)
|
self.add_verse(self.current_verse, self.current_verse_type)
|
||||||
self.currentVerse = ''
|
self.current_verse = ''
|
||||||
self.currentVerseType = VerseType.tags[VerseType.Verse]
|
self.current_verse_type = VerseType.tags[VerseType.Verse]
|
||||||
read_verses = True
|
read_verses = True
|
||||||
verse_start = True
|
verse_start = True
|
||||||
elif read_verses:
|
elif read_verses:
|
||||||
if verse_start:
|
if verse_start:
|
||||||
verse_start = False
|
verse_start = False
|
||||||
if not self.checkVerseMarks(line):
|
if not self.check_verse_marks(line):
|
||||||
self.currentVerse = line + '\n'
|
self.current_verse = line + '\n'
|
||||||
else:
|
else:
|
||||||
self.currentVerse += line + '\n'
|
self.current_verse += line + '\n'
|
||||||
if self.currentVerse:
|
if self.current_verse:
|
||||||
self.replaceHtmlTags()
|
self.replace_html_tags()
|
||||||
self.add_verse(self.currentVerse, self.currentVerseType)
|
self.add_verse(self.current_verse, self.current_verse_type)
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(import_file)
|
self.log_error(import_file)
|
||||||
|
|
||||||
def replaceHtmlTags(self):
|
def replace_html_tags(self):
|
||||||
"""
|
"""
|
||||||
This can be called to replace SongBeamer's specific (html) tags with OpenLP's specific (html) tags.
|
This can be called to replace SongBeamer's specific (html) tags with OpenLP's specific (html) tags.
|
||||||
"""
|
"""
|
||||||
for pair in SongBeamerImport.HTML_TAG_PAIRS:
|
for pair in SongBeamerImport.HTML_TAG_PAIRS:
|
||||||
self.currentVerse = pair[0].sub(pair[1], self.currentVerse)
|
self.current_verse = pair[0].sub(pair[1], self.current_verse)
|
||||||
|
|
||||||
def parseTags(self, line):
|
def parseTags(self, line):
|
||||||
"""
|
"""
|
||||||
Parses a meta data line.
|
Parses a meta data line.
|
||||||
|
|
||||||
``line``
|
:param line: The line in the file. It should consist of a tag and a value for this tag (unicode)::
|
||||||
The line in the file. It should consist of a tag and a value for this tag (unicode)::
|
|
||||||
|
|
||||||
u'#Title=Nearer my God to Thee'
|
u'#Title=Nearer my God to Thee'
|
||||||
"""
|
"""
|
||||||
@ -267,20 +267,19 @@ class SongBeamerImport(SongImport):
|
|||||||
# TODO: add the verse order.
|
# TODO: add the verse order.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def checkVerseMarks(self, line):
|
def check_verse_marks(self, line):
|
||||||
"""
|
"""
|
||||||
Check and add the verse's MarkType. Returns ``True`` if the given linE contains a correct verse mark otherwise
|
Check and add the verse's MarkType. Returns ``True`` if the given linE contains a correct verse mark otherwise
|
||||||
``False``.
|
``False``.
|
||||||
|
|
||||||
``line``
|
:param line: The line to check for marks (unicode).
|
||||||
The line to check for marks (unicode).
|
|
||||||
"""
|
"""
|
||||||
marks = line.split(' ')
|
marks = line.split(' ')
|
||||||
if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes:
|
if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes:
|
||||||
self.currentVerseType = SongBeamerTypes.MarkTypes[marks[0]]
|
self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0]]
|
||||||
if len(marks) == 2:
|
if len(marks) == 2:
|
||||||
# If we have a digit, we append it to current_verse_type.
|
# If we have a digit, we append it to current_verse_type.
|
||||||
if marks[1].isdigit():
|
if marks[1].isdigit():
|
||||||
self.currentVerseType += marks[1]
|
self.current_verse_type += marks[1]
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -56,11 +56,8 @@ def songs_probably_equal(song1, song2):
|
|||||||
"""
|
"""
|
||||||
Calculate and return whether two songs are probably equal.
|
Calculate and return whether two songs are probably equal.
|
||||||
|
|
||||||
``song1``
|
:param song1: The first song to compare.
|
||||||
The first song to compare.
|
:param song2: The second song to compare.
|
||||||
|
|
||||||
``song2``
|
|
||||||
The second song to compare.
|
|
||||||
"""
|
"""
|
||||||
if len(song1.search_lyrics) < len(song2.search_lyrics):
|
if len(song1.search_lyrics) < len(song2.search_lyrics):
|
||||||
small = song1.search_lyrics
|
small = song1.search_lyrics
|
||||||
@ -96,8 +93,7 @@ def _op_length(opcode):
|
|||||||
"""
|
"""
|
||||||
Return the length of a given difference.
|
Return the length of a given difference.
|
||||||
|
|
||||||
``opcode``
|
:param opcode: The difference.
|
||||||
The difference.
|
|
||||||
"""
|
"""
|
||||||
return max(opcode[2] - opcode[1], opcode[4] - opcode[3])
|
return max(opcode[2] - opcode[1], opcode[4] - opcode[3])
|
||||||
|
|
||||||
@ -107,33 +103,28 @@ def _remove_typos(diff):
|
|||||||
Remove typos from a diff set. A typo is a small difference (<max_typo_size)
|
Remove typos from a diff set. A typo is a small difference (<max_typo_size)
|
||||||
surrounded by larger equal passages (>min_fragment_size).
|
surrounded by larger equal passages (>min_fragment_size).
|
||||||
|
|
||||||
``diff``
|
:param diff: The diff set to remove the typos from.
|
||||||
The diff set to remove the typos from.
|
|
||||||
"""
|
"""
|
||||||
# Remove typo at beginning of the string.
|
# Remove typo at beginning of the string.
|
||||||
if len(diff) >= 2:
|
if len(diff) >= 2:
|
||||||
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and \
|
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and _op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
|
||||||
_op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
|
del diff[0]
|
||||||
del diff[0]
|
|
||||||
# Remove typos in the middle of the string.
|
# Remove typos in the middle of the string.
|
||||||
if len(diff) >= 3:
|
if len(diff) >= 3:
|
||||||
for index in range(len(diff) - 3, -1, -1):
|
for index in range(len(diff) - 3, -1, -1):
|
||||||
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
|
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and diff[index + 1][0] != "equal" and \
|
||||||
diff[index + 1][0] != "equal" and _op_length(diff[index + 1]) <= MAX_TYPO_SIZE and \
|
_op_length(diff[index + 1]) <= MAX_TYPO_SIZE and _op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
|
||||||
_op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
|
del diff[index + 1]
|
||||||
del diff[index + 1]
|
|
||||||
# Remove typo at the end of the string.
|
# Remove typo at the end of the string.
|
||||||
if len(diff) >= 2:
|
if len(diff) >= 2:
|
||||||
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and \
|
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and diff[-1][0] != "equal" \
|
||||||
diff[-1][0] != "equal" and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
|
and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
|
||||||
del diff[-1]
|
del diff[-1]
|
||||||
|
|
||||||
# Merge the bordering equal passages that occured by removing differences.
|
# Merge the bordering equal passages that occured by removing differences.
|
||||||
for index in range(len(diff) - 2, -1, -1):
|
for index in range(len(diff) - 2, -1, -1):
|
||||||
if diff[index][0] == "equal" and _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
|
if diff[index][0] == "equal" and _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
|
||||||
diff[index + 1][0] == "equal" and _op_length(diff[index + 1]) >= MIN_FRAGMENT_SIZE:
|
diff[index + 1][0] == "equal" and _op_length(diff[index + 1]) >= MIN_FRAGMENT_SIZE:
|
||||||
diff[index] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3],
|
diff[index] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3], diff[index + 1][4])
|
||||||
diff[index + 1][4])
|
del diff[index + 1]
|
||||||
del diff[index + 1]
|
|
||||||
|
|
||||||
return diff
|
return diff
|
||||||
|
@ -271,8 +271,7 @@ class SongImport(QtCore.QObject):
|
|||||||
def check_complete(self):
|
def check_complete(self):
|
||||||
"""
|
"""
|
||||||
Check the mandatory fields are entered (i.e. title and a verse)
|
Check the mandatory fields are entered (i.e. title and a verse)
|
||||||
Author not checked here, if no author then "Author unknown" is
|
Author not checked here, if no author then "Author unknown" is automatically added
|
||||||
automatically added
|
|
||||||
"""
|
"""
|
||||||
if not self.title or not self.verses:
|
if not self.title or not self.verses:
|
||||||
return False
|
return False
|
||||||
|
@ -89,13 +89,13 @@ class SongProImport(SongImport):
|
|||||||
file_line = str(file_line, 'cp1252')
|
file_line = str(file_line, 'cp1252')
|
||||||
file_text = file_line.rstrip()
|
file_text = file_line.rstrip()
|
||||||
if file_text and file_text[0] == '#':
|
if file_text and file_text[0] == '#':
|
||||||
self.processSection(tag, text.rstrip())
|
self.process_section(tag, text.rstrip())
|
||||||
tag = file_text[1:]
|
tag = file_text[1:]
|
||||||
text = ''
|
text = ''
|
||||||
else:
|
else:
|
||||||
text += file_line
|
text += file_line
|
||||||
|
|
||||||
def processSection(self, tag, text):
|
def process_section(self, tag, text):
|
||||||
"""
|
"""
|
||||||
Process a section of the song, i.e. title, verse etc.
|
Process a section of the song, i.e. title, verse etc.
|
||||||
"""
|
"""
|
||||||
|
@ -141,8 +141,8 @@ class SongShowPlusImport(SongImport):
|
|||||||
authors = self.decode(data).split(" / ")
|
authors = self.decode(data).split(" / ")
|
||||||
for author in authors:
|
for author in authors:
|
||||||
if author.find(",") !=-1:
|
if author.find(",") !=-1:
|
||||||
authorParts = author.split(", ")
|
author_parts = author.split(", ")
|
||||||
author = authorParts[1] + " " + authorParts[0]
|
author = author_parts[1] + " " + author_parts[0]
|
||||||
self.parse_author(author)
|
self.parse_author(author)
|
||||||
elif block_key == COPYRIGHT:
|
elif block_key == COPYRIGHT:
|
||||||
self.add_copyright(self.decode(data))
|
self.add_copyright(self.decode(data))
|
||||||
@ -180,6 +180,13 @@ class SongShowPlusImport(SongImport):
|
|||||||
self.log_error(file)
|
self.log_error(file)
|
||||||
|
|
||||||
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
|
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
|
||||||
|
"""
|
||||||
|
Handle OpenLP verse tags
|
||||||
|
|
||||||
|
:param verse_name: The verse name
|
||||||
|
:param ignore_unique: Ignore if unique
|
||||||
|
:return: The verse tags and verse number concatenated
|
||||||
|
"""
|
||||||
# Have we got any digits? If so, verse number is everything from the digits to the end (OpenLP does not have
|
# Have we got any digits? If so, verse number is everything from the digits to the end (OpenLP does not have
|
||||||
# concept of part verses, so just ignore any non integers on the end (including floats))
|
# concept of part verses, so just ignore any non integers on the end (including floats))
|
||||||
match = re.match(r'(\D*)(\d+)', verse_name)
|
match = re.match(r'(\D*)(\d+)', verse_name)
|
||||||
|
@ -71,10 +71,10 @@ class SongsTab(SettingsTab):
|
|||||||
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
|
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
|
||||||
self.search_as_type_check_box.setText(translate('SongsPlugin.SongsTab', 'Enable search as you type'))
|
self.search_as_type_check_box.setText(translate('SongsPlugin.SongsTab', 'Enable search as you type'))
|
||||||
self.tool_bar_active_check_box.setText(translate('SongsPlugin.SongsTab',
|
self.tool_bar_active_check_box.setText(translate('SongsPlugin.SongsTab',
|
||||||
'Display verses on live tool bar'))
|
'Display verses on live tool bar'))
|
||||||
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
|
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
|
||||||
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
|
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
|
||||||
'Import missing songs from service files'))
|
'Import missing songs from service files'))
|
||||||
|
|
||||||
def on_search_as_type_check_box_changed(self, check_state):
|
def on_search_as_type_check_box_changed(self, check_state):
|
||||||
self.song_search = (check_state == QtCore.Qt.Checked)
|
self.song_search = (check_state == QtCore.Qt.Checked)
|
||||||
|
@ -48,6 +48,7 @@ HOTKEY_TO_VERSE_TYPE = {
|
|||||||
'+': 'b',
|
'+': 'b',
|
||||||
'Z': 'o'}
|
'Z': 'o'}
|
||||||
|
|
||||||
|
|
||||||
class SundayPlusImport(SongImport):
|
class SundayPlusImport(SongImport):
|
||||||
"""
|
"""
|
||||||
Import Sunday Plus songs
|
Import Sunday Plus songs
|
||||||
@ -68,10 +69,10 @@ class SundayPlusImport(SongImport):
|
|||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return
|
return
|
||||||
song_file = open(filename)
|
song_file = open(filename)
|
||||||
self.doImportFile(song_file)
|
self.do_import_file(song_file)
|
||||||
song_file.close()
|
song_file.close()
|
||||||
|
|
||||||
def doImportFile(self, file):
|
def do_import_file(self, file):
|
||||||
"""
|
"""
|
||||||
Process the Sunday Plus file object.
|
Process the Sunday Plus file object.
|
||||||
"""
|
"""
|
||||||
@ -80,11 +81,18 @@ class SundayPlusImport(SongImport):
|
|||||||
self.log_error(file.name)
|
self.log_error(file.name)
|
||||||
return
|
return
|
||||||
if not self.title:
|
if not self.title:
|
||||||
self.title = self.titleFromFilename(file.name)
|
self.title = self.title_from_filename(file.name)
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(file.name)
|
self.log_error(file.name)
|
||||||
|
|
||||||
def parse(self, data, cell=False):
|
def parse(self, data, cell=False):
|
||||||
|
"""
|
||||||
|
Process the records
|
||||||
|
|
||||||
|
:param data: The data to be processed
|
||||||
|
:param cell: ?
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
|
if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
|
||||||
self.log_error('File is malformed')
|
self.log_error('File is malformed')
|
||||||
return False
|
return False
|
||||||
@ -173,7 +181,13 @@ class SundayPlusImport(SongImport):
|
|||||||
i += 1
|
i += 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def titleFromFilename(self, filename):
|
def title_from_filename(self, filename):
|
||||||
|
"""
|
||||||
|
Extract the title from the filename
|
||||||
|
|
||||||
|
:param filename: File name
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
title = os.path.split(filename)[1]
|
title = os.path.split(filename)[1]
|
||||||
if title.endswith('.ptf'):
|
if title.endswith('.ptf'):
|
||||||
title = title[:-4]
|
title = title[:-4]
|
||||||
|
@ -40,6 +40,7 @@ BLOCK_TYPES = ('V', 'C', 'B')
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WowImport(SongImport):
|
class WowImport(SongImport):
|
||||||
"""
|
"""
|
||||||
The :class:`WowImport` class provides the ability to import song files from
|
The :class:`WowImport` class provides the ability to import song files from
|
||||||
@ -113,29 +114,33 @@ class WowImport(SongImport):
|
|||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
song_data = open(source, 'rb')
|
song_data = open(source, 'rb')
|
||||||
if song_data.read(19) != 'WoW File\nSong Words':
|
if song_data.read(19) != 'WoW File\nSong Words':
|
||||||
self.log_error(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
|
self.log_error(source,
|
||||||
('Invalid Words of Worship song file. Missing "Wow File\\nSong Words" header.'))))
|
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||||
|
'Invalid Words of Worship song file. Missing "Wow File\\nSong '
|
||||||
|
'Words" header.')))
|
||||||
continue
|
continue
|
||||||
# Seek to byte which stores number of blocks in the song
|
# Seek to byte which stores number of blocks in the song
|
||||||
song_data.seek(56)
|
song_data.seek(56)
|
||||||
no_of_blocks = ord(song_data.read(1))
|
no_of_blocks = ord(song_data.read(1))
|
||||||
song_data.seek(66)
|
song_data.seek(66)
|
||||||
if song_data.read(16) != 'CSongDoc::CBlock':
|
if song_data.read(16) != 'CSongDoc::CBlock':
|
||||||
self.log_error(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
|
self.log_error(source,
|
||||||
('Invalid Words of Worship song file. Missing "CSongDoc::CBlock" string.'))))
|
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||||
|
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
|
||||||
|
'string.')))
|
||||||
continue
|
continue
|
||||||
# Seek to the beginning of the first block
|
# Seek to the beginning of the first block
|
||||||
song_data.seek(82)
|
song_data.seek(82)
|
||||||
for block in range(no_of_blocks):
|
for block in range(no_of_blocks):
|
||||||
self.linesToRead = ord(song_data.read(4)[:1])
|
self.lines_to_read = ord(song_data.read(4)[:1])
|
||||||
block_text = ''
|
block_text = ''
|
||||||
while self.linesToRead:
|
while self.lines_to_read:
|
||||||
self.lineText = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
||||||
song_data.seek(1, os.SEEK_CUR)
|
song_data.seek(1, os.SEEK_CUR)
|
||||||
if block_text:
|
if block_text:
|
||||||
block_text += '\n'
|
block_text += '\n'
|
||||||
block_text += self.lineText
|
block_text += self.line_text
|
||||||
self.linesToRead -= 1
|
self.lines_to_read -= 1
|
||||||
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
|
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
|
||||||
# Blocks are separated by 2 bytes, skip them, but not if
|
# Blocks are separated by 2 bytes, skip them, but not if
|
||||||
# this is the last block!
|
# this is the last block!
|
||||||
|
@ -97,21 +97,13 @@ class SongXML(object):
|
|||||||
"""
|
"""
|
||||||
Add a verse to the ``<lyrics>`` tag.
|
Add a verse to the ``<lyrics>`` tag.
|
||||||
|
|
||||||
``type``
|
:param type: A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*.
|
||||||
A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*. Any other
|
Any other type is **not** allowed, this also includes translated types.
|
||||||
type is **not** allowed, this also includes translated types.
|
:param number: An integer denoting the number of the item, for example: verse 1.
|
||||||
|
:param content: The actual text of the verse to be stored.
|
||||||
``number``
|
:param lang: The verse's language code (ISO-639). This is not required, but should be added if available.
|
||||||
An integer denoting the number of the item, for example: verse 1.
|
|
||||||
|
|
||||||
``content``
|
|
||||||
The actual text of the verse to be stored.
|
|
||||||
|
|
||||||
``lang``
|
|
||||||
The verse's language code (ISO-639). This is not required, but should be added if available.
|
|
||||||
"""
|
"""
|
||||||
verse = etree.Element('verse', type=str(type),
|
verse = etree.Element('verse', type=str(type), label=str(number))
|
||||||
label=str(number))
|
|
||||||
if lang:
|
if lang:
|
||||||
verse.set('lang', lang)
|
verse.set('lang', lang)
|
||||||
verse.text = etree.CDATA(content)
|
verse.text = etree.CDATA(content)
|
||||||
@ -121,16 +113,13 @@ class SongXML(object):
|
|||||||
"""
|
"""
|
||||||
Extract our newly created XML song.
|
Extract our newly created XML song.
|
||||||
"""
|
"""
|
||||||
return etree.tostring(self.song_xml, encoding='UTF-8',
|
return etree.tostring(self.song_xml, encoding='UTF-8', xml_declaration=True)
|
||||||
xml_declaration=True)
|
|
||||||
|
|
||||||
def get_verses(self, xml):
|
def get_verses(self, xml):
|
||||||
"""
|
"""
|
||||||
Iterates through the verses in the XML and returns a list of verses and their attributes.
|
Iterates through the verses in the XML and returns a list of verses and their attributes.
|
||||||
|
|
||||||
``xml``
|
:param xml: The XML of the song to be parsed.
|
||||||
The XML of the song to be parsed.
|
|
||||||
|
|
||||||
The returned list has the following format::
|
The returned list has the following format::
|
||||||
|
|
||||||
[[{'type': 'v', 'label': '1'}, u"optional slide split 1[---]optional slide split 2"],
|
[[{'type': 'v', 'label': '1'}, u"optional slide split 1[---]optional slide split 2"],
|
||||||
@ -351,8 +340,7 @@ class OpenLyrics(object):
|
|||||||
|
|
||||||
The first unicode string are the start tags (for the next slide). The second unicode string are the end tags.
|
The first unicode string are the start tags (for the next slide). The second unicode string are the end tags.
|
||||||
|
|
||||||
``text``
|
:param text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
|
||||||
The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
|
|
||||||
|
|
||||||
{st}{r}Text text text
|
{st}{r}Text text text
|
||||||
"""
|
"""
|
||||||
@ -378,12 +366,9 @@ class OpenLyrics(object):
|
|||||||
Create and save a song from OpenLyrics format xml to the database. Since we also export XML from external
|
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 cannot ensure, that it completely conforms to the OpenLyrics standard.
|
sources (e. g. OpenLyrics import), we cannot ensure, that it completely conforms to the OpenLyrics standard.
|
||||||
|
|
||||||
``xml``
|
:param xml: The XML to parse (unicode).
|
||||||
The XML to parse (unicode).
|
:param parse_and_temporary_save: Switch to skip processing the whole song and storing the songs in the database
|
||||||
|
with a temporary flag. Defaults to ``False``.
|
||||||
``parse_and_temporary_save``
|
|
||||||
Switch to skip processing the whole song and storing the songs in the database with a temporary flag.
|
|
||||||
Defaults to ``False``.
|
|
||||||
"""
|
"""
|
||||||
# No xml get out of here.
|
# No xml get out of here.
|
||||||
if not xml:
|
if not xml:
|
||||||
@ -418,6 +403,15 @@ class OpenLyrics(object):
|
|||||||
return song
|
return song
|
||||||
|
|
||||||
def _add_text_to_element(self, tag, parent, text=None, label=None):
|
def _add_text_to_element(self, tag, parent, text=None, label=None):
|
||||||
|
"""
|
||||||
|
Build an element
|
||||||
|
|
||||||
|
:param tag: A Tag
|
||||||
|
:param parent: Its parent
|
||||||
|
:param text: Some text to be added
|
||||||
|
:param label: And a label
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
if label:
|
if label:
|
||||||
element = etree.Element(tag, name=str(label))
|
element = etree.Element(tag, name=str(label))
|
||||||
else:
|
else:
|
||||||
@ -430,6 +424,9 @@ class OpenLyrics(object):
|
|||||||
def _add_tag_to_formatting(self, tag_name, tags_element):
|
def _add_tag_to_formatting(self, tag_name, tags_element):
|
||||||
"""
|
"""
|
||||||
Add new formatting tag to the element ``<format>`` if the tag is not present yet.
|
Add new formatting tag to the element ``<format>`` if the tag is not present yet.
|
||||||
|
|
||||||
|
:param tag_name: The tag_name
|
||||||
|
:param tags_element: Some tag elements
|
||||||
"""
|
"""
|
||||||
available_tags = FormattingTags.get_html_tags()
|
available_tags = FormattingTags.get_html_tags()
|
||||||
start_tag = '{%s}' % tag_name
|
start_tag = '{%s}' % tag_name
|
||||||
@ -477,16 +474,16 @@ class OpenLyrics(object):
|
|||||||
def _extract_xml(self, xml):
|
def _extract_xml(self, xml):
|
||||||
"""
|
"""
|
||||||
Extract our newly created XML song.
|
Extract our newly created XML song.
|
||||||
|
|
||||||
|
:param xml: The XML
|
||||||
"""
|
"""
|
||||||
return etree.tostring(xml, encoding='UTF-8',
|
return etree.tostring(xml, encoding='UTF-8', xml_declaration=True)
|
||||||
xml_declaration=True)
|
|
||||||
|
|
||||||
def _text(self, element):
|
def _text(self, element):
|
||||||
"""
|
"""
|
||||||
This returns the text of an element as unicode string.
|
This returns the text of an element as unicode string.
|
||||||
|
|
||||||
``element``
|
:param element: The element.
|
||||||
The element.
|
|
||||||
"""
|
"""
|
||||||
if element.text is not None:
|
if element.text is not None:
|
||||||
return str(element.text)
|
return str(element.text)
|
||||||
@ -496,11 +493,8 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Adds the authors specified in the XML to the song.
|
Adds the authors specified in the XML to the song.
|
||||||
|
|
||||||
``properties``
|
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||||
The property object (lxml.objectify.ObjectifiedElement).
|
:param song: The song object
|
||||||
|
|
||||||
``song``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
authors = []
|
authors = []
|
||||||
if hasattr(properties, 'authors'):
|
if hasattr(properties, 'authors'):
|
||||||
@ -509,24 +503,20 @@ class OpenLyrics(object):
|
|||||||
if display_name:
|
if display_name:
|
||||||
authors.append(display_name)
|
authors.append(display_name)
|
||||||
for display_name in authors:
|
for display_name in authors:
|
||||||
author = self.manager.get_object_filtered(Author,
|
author = self.manager.get_object_filtered(Author, Author.display_name == display_name)
|
||||||
Author.display_name == display_name)
|
|
||||||
if author is None:
|
if author is None:
|
||||||
# We need to create a new author, as the author does not exist.
|
# We need to create a new author, as the author does not exist.
|
||||||
author = Author.populate(display_name=display_name,
|
author = Author.populate(display_name=display_name,
|
||||||
last_name=display_name.split(' ')[-1],
|
last_name=display_name.split(' ')[-1],
|
||||||
first_name=' '.join(display_name.split(' ')[:-1]))
|
first_name=' '.join(display_name.split(' ')[:-1]))
|
||||||
song.authors.append(author)
|
song.authors.append(author)
|
||||||
|
|
||||||
def _process_cclinumber(self, properties, song):
|
def _process_cclinumber(self, properties, song):
|
||||||
"""
|
"""
|
||||||
Adds the CCLI number to the song.
|
Adds the CCLI number to the song.
|
||||||
|
|
||||||
``properties``
|
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||||
The property object (lxml.objectify.ObjectifiedElement).
|
:param song: The song object.
|
||||||
|
|
||||||
``song``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
if hasattr(properties, 'ccliNo'):
|
if hasattr(properties, 'ccliNo'):
|
||||||
song.ccli_number = self._text(properties.ccliNo)
|
song.ccli_number = self._text(properties.ccliNo)
|
||||||
@ -535,11 +525,8 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Joins the comments specified in the XML and add it to the song.
|
Joins the comments specified in the XML and add it to the song.
|
||||||
|
|
||||||
``properties``
|
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||||
The property object (lxml.objectify.ObjectifiedElement).
|
:param song: The song object.
|
||||||
|
|
||||||
``song``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
if hasattr(properties, 'comments'):
|
if hasattr(properties, 'comments'):
|
||||||
comments_list = []
|
comments_list = []
|
||||||
@ -553,11 +540,8 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Adds the copyright to the song.
|
Adds the copyright to the song.
|
||||||
|
|
||||||
``properties``
|
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||||
The property object (lxml.objectify.ObjectifiedElement).
|
:param song: The song object.
|
||||||
|
|
||||||
``song``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
if hasattr(properties, 'copyright'):
|
if hasattr(properties, 'copyright'):
|
||||||
song.copyright = self._text(properties.copyright)
|
song.copyright = self._text(properties.copyright)
|
||||||
@ -566,6 +550,9 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Process the formatting tags from the song and either add missing tags temporary or permanently to the
|
Process the formatting tags from the song and either add missing tags temporary or permanently to the
|
||||||
formatting tag list.
|
formatting tag list.
|
||||||
|
|
||||||
|
:param song_xml: The song XML
|
||||||
|
:param temporary: Is the song temporary?
|
||||||
"""
|
"""
|
||||||
if not hasattr(song_xml, 'format'):
|
if not hasattr(song_xml, 'format'):
|
||||||
return
|
return
|
||||||
@ -603,11 +590,9 @@ class OpenLyrics(object):
|
|||||||
Converts the xml text with mixed content to OpenLP representation. Chords are skipped and formatting tags are
|
Converts the xml text with mixed content to OpenLP representation. Chords are skipped and formatting tags are
|
||||||
converted.
|
converted.
|
||||||
|
|
||||||
``element``
|
:param element: The property object (lxml.etree.Element).
|
||||||
The property object (lxml.etree.Element).
|
:param newlines: The switch to enable/disable processing of line breaks <br/>. The <br/> is used since
|
||||||
|
OpenLyrics 0.8.
|
||||||
``newlines``
|
|
||||||
The switch to enable/disable processing of line breaks <br/>. The <br/> is used since OpenLyrics 0.8.
|
|
||||||
"""
|
"""
|
||||||
text = ''
|
text = ''
|
||||||
use_endtag = True
|
use_endtag = True
|
||||||
@ -655,8 +640,8 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Converts lyrics lines to OpenLP representation.
|
Converts lyrics lines to OpenLP representation.
|
||||||
|
|
||||||
``lines``
|
:param lines: The lines object (lxml.objectify.ObjectifiedElement).
|
||||||
The lines object (lxml.objectify.ObjectifiedElement).
|
:param version:
|
||||||
"""
|
"""
|
||||||
text = ''
|
text = ''
|
||||||
# Convert lxml.objectify to lxml.etree representation.
|
# Convert lxml.objectify to lxml.etree representation.
|
||||||
@ -682,14 +667,9 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Processes the verses and search_lyrics for the song.
|
Processes the verses and search_lyrics for the song.
|
||||||
|
|
||||||
``properties``
|
:param properties: The properties object (lxml.objectify.ObjectifiedElement).
|
||||||
The properties object (lxml.objectify.ObjectifiedElement).
|
:param song_xml: The objectified song (lxml.objectify.ObjectifiedElement).
|
||||||
|
:param song_obj: The song object.
|
||||||
``song_xml``
|
|
||||||
The objectified song (lxml.objectify.ObjectifiedElement).
|
|
||||||
|
|
||||||
``song_obj``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
sxml = SongXML()
|
sxml = SongXML()
|
||||||
verses = {}
|
verses = {}
|
||||||
@ -698,12 +678,12 @@ class OpenLyrics(object):
|
|||||||
lyrics = song_xml.lyrics
|
lyrics = song_xml.lyrics
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise OpenLyricsError(OpenLyricsError.LyricsError, '<lyrics> tag is missing.',
|
raise OpenLyricsError(OpenLyricsError.LyricsError, '<lyrics> tag is missing.',
|
||||||
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
|
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
|
||||||
try:
|
try:
|
||||||
verse_list = lyrics.verse
|
verse_list = lyrics.verse
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise OpenLyricsError(OpenLyricsError.VerseError, '<verse> tag is missing.',
|
raise OpenLyricsError(OpenLyricsError.VerseError, '<verse> tag is missing.',
|
||||||
translate('OpenLP.OpenLyricsImportError', '<verse> tag is missing.'))
|
translate('OpenLP.OpenLyricsImportError', '<verse> tag is missing.'))
|
||||||
# Loop over the "verse" elements.
|
# Loop over the "verse" elements.
|
||||||
for verse in verse_list:
|
for verse in verse_list:
|
||||||
text = ''
|
text = ''
|
||||||
@ -712,8 +692,7 @@ class OpenLyrics(object):
|
|||||||
if text:
|
if text:
|
||||||
text += '\n'
|
text += '\n'
|
||||||
# Append text from "lines" element to verse text.
|
# Append text from "lines" element to verse text.
|
||||||
text += self._process_verse_lines(lines,
|
text += self._process_verse_lines(lines, version=song_xml.get('version'))
|
||||||
version=song_xml.get('version'))
|
|
||||||
# Add an optional split to the verse text.
|
# Add an optional split to the verse text.
|
||||||
if lines.get('break') is not None:
|
if lines.get('break') is not None:
|
||||||
text += '\n[---]'
|
text += '\n[---]'
|
||||||
@ -749,11 +728,8 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Adds the song book and song number specified in the XML to the song.
|
Adds the song book and song number specified in the XML to the song.
|
||||||
|
|
||||||
``properties``
|
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||||
The property object (lxml.objectify.ObjectifiedElement).
|
:param song: The song object.
|
||||||
|
|
||||||
``song``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
song.song_book_id = None
|
song.song_book_id = None
|
||||||
song.song_number = ''
|
song.song_number = ''
|
||||||
@ -775,11 +751,8 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Processes the titles specified in the song's XML.
|
Processes the titles specified in the song's XML.
|
||||||
|
|
||||||
``properties``
|
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||||
The property object (lxml.objectify.ObjectifiedElement).
|
:param song: The song object.
|
||||||
|
|
||||||
``song``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
for title in properties.titles.title:
|
for title in properties.titles.title:
|
||||||
if not song.title:
|
if not song.title:
|
||||||
@ -792,11 +765,8 @@ class OpenLyrics(object):
|
|||||||
"""
|
"""
|
||||||
Adds the topics to the song.
|
Adds the topics to the song.
|
||||||
|
|
||||||
``properties``
|
:param properties: The property object (lxml.objectify.ObjectifiedElement).
|
||||||
The property object (lxml.objectify.ObjectifiedElement).
|
:param song: The song object.
|
||||||
|
|
||||||
``song``
|
|
||||||
The song object.
|
|
||||||
"""
|
"""
|
||||||
if hasattr(properties, 'themes'):
|
if hasattr(properties, 'themes'):
|
||||||
for topic_text in properties.themes.theme:
|
for topic_text in properties.themes.theme:
|
||||||
|
@ -41,6 +41,7 @@ log = logging.getLogger(__name__)
|
|||||||
# Used to strip control chars (except 10=LF, 13=CR)
|
# Used to strip control chars (except 10=LF, 13=CR)
|
||||||
CONTROL_CHARS_MAP = dict.fromkeys(list(range(10)) + [11, 12] + list(range(14,32)) + [127])
|
CONTROL_CHARS_MAP = dict.fromkeys(list(range(10)) + [11, 12] + list(range(14,32)) + [127])
|
||||||
|
|
||||||
|
|
||||||
class ZionWorxImport(SongImport):
|
class ZionWorxImport(SongImport):
|
||||||
"""
|
"""
|
||||||
The :class:`ZionWorxImport` class provides the ability to import songs
|
The :class:`ZionWorxImport` class provides the ability to import songs
|
||||||
@ -84,13 +85,13 @@ class ZionWorxImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
with open(self.import_source, 'rb') as songs_file:
|
with open(self.import_source, 'rb') as songs_file:
|
||||||
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
|
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
|
||||||
'DefaultStyle']
|
'DefaultStyle']
|
||||||
songs_reader = csv.DictReader(songs_file, field_names)
|
songs_reader = csv.DictReader(songs_file, field_names)
|
||||||
try:
|
try:
|
||||||
records = list(songs_reader)
|
records = list(songs_reader)
|
||||||
except csv.Error as e:
|
except csv.Error as e:
|
||||||
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'),
|
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Error reading CSV file.'),
|
||||||
translate('SongsPlugin.ZionWorxImport', 'Line %d: %s') % (songs_reader.line_num, e))
|
translate('SongsPlugin.ZionWorxImport', 'Line %d: %s') % (songs_reader.line_num, e))
|
||||||
return
|
return
|
||||||
num_records = len(records)
|
num_records = len(records)
|
||||||
log.info('%s records found in CSV file' % num_records)
|
log.info('%s records found in CSV file' % num_records)
|
||||||
@ -108,7 +109,7 @@ class ZionWorxImport(SongImport):
|
|||||||
lyrics = self._decode(record['Lyrics'])
|
lyrics = self._decode(record['Lyrics'])
|
||||||
except UnicodeDecodeError as e:
|
except UnicodeDecodeError as e:
|
||||||
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
|
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
|
||||||
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
|
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
|
||||||
continue
|
continue
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
self.log_error(translate(
|
self.log_error(translate(
|
||||||
@ -126,12 +127,11 @@ class ZionWorxImport(SongImport):
|
|||||||
title = self.title
|
title = self.title
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
|
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
|
||||||
+ (': "' + title + '"' if title else ''))
|
+ (': "' + title + '"' if title else ''))
|
||||||
|
|
||||||
def _decode(self, str):
|
def _decode(self, str):
|
||||||
"""
|
"""
|
||||||
Decodes CSV input to unicode, stripping all control characters (except
|
Decodes CSV input to unicode, stripping all control characters (except new lines).
|
||||||
new lines).
|
|
||||||
"""
|
"""
|
||||||
# This encoding choice seems OK. ZionWorx has no option for setting the
|
# This encoding choice seems OK. ZionWorx has no option for setting the
|
||||||
# encoding for its songs, so we assume encoding is always the same.
|
# encoding for its songs, so we assume encoding is always the same.
|
||||||
|
Loading…
Reference in New Issue
Block a user