Songs cleanned up

This commit is contained in:
Tim Bentley 2014-03-08 19:58:58 +00:00
parent a0a2e5efbc
commit f86c6bc77d
12 changed files with 274 additions and 285 deletions

View File

@ -67,7 +67,7 @@ class EasySlidesImport(SongImport):
self._success = True
self._add_unicode_attribute('title', song.Title1, True)
if hasattr(song, 'Title2'):
self._add_unicode_attribute('alternateTitle', song.Title2)
self._add_unicode_attribute('alternate_title', song.Title2)
if hasattr(song, 'SongNumber'):
self._add_unicode_attribute('song_number', song.SongNumber)
if self.song_number == '0':

View File

@ -69,8 +69,8 @@ class SofImport(OooImport):
Use OpenOffice.org Writer for processing the rtf file
The three books are not only inconsistant with each other, they are
inconsistant in themselves too with their formatting. Not only this, but
The three books are not only inconsistent with each other, they are
inconsistent in themselves too with their formatting. Not only this, but
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
type them all out!
@ -90,14 +90,14 @@ class SofImport(OooImport):
"""
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
"""
self.blankLines = 0
self.newSong()
self.blank_lines = 0
self.new_song()
try:
paragraphs = self.document.getText().createEnumeration()
while paragraphs.hasMoreElements():
@ -105,65 +105,68 @@ class SofImport(OooImport):
return
paragraph = paragraphs.nextElement()
if paragraph.supportsService("com.sun.star.text.Paragraph"):
self.processParagraph(paragraph)
self.process_paragraph(paragraph)
except RuntimeException as exc:
log.exception('Error processing file: %s', exc)
if not self.finish():
self.log_error(self.file_path)
def processParagraph(self, paragraph):
def process_paragraph(self, paragraph):
"""
Process a paragraph.
In the first book, a paragraph is a single line. In the latter ones
they may contain multiple lines.
Each paragraph contains textportions. Each textportion has it's own
styling, e.g. italics, bold etc.
In the first book, a paragraph is a single line. In the latter ones they may contain multiple lines.
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.
In later books, there may not be line breaks, so check for 3 or more
newlines
In later books, there may not be line breaks, so check for 3 or more newlines
:param paragraph: The paragraph text
"""
text = ''
textportions = paragraph.createEnumeration()
while textportions.hasMoreElements():
textportion = textportions.nextElement()
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
self.processParagraphText(text)
self.newSong()
text_portions = paragraph.createEnumeration()
while text_portions.hasMoreElements():
text_portion = text_portions.nextElement()
if text_portion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
self.process_paragraph_text(text)
self.new_song()
text = ''
text += self.processTextPortion(textportion)
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
self.processParagraphText(text)
self.newSong()
text += self.process_text_portion(text_portion)
if text_portion.BreakType in (PAGE_AFTER, PAGE_BOTH):
self.process_paragraph_text(text)
self.new_song()
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
:param text: The text
"""
for line in text.split('\n'):
self.processParagraphLine(line)
if self.blankLines > 2:
self.newSong()
self.process_paragraph_line(line)
if self.blank_lines > 2:
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.
stuff that appears at the end of the song.
Anything that is OK, append to the current verse
:param text: The text
"""
text = text.strip()
if text == '':
self.blankLines += 1
if self.blankLines > 1:
self.blank_lines += 1
if self.blank_lines > 1:
return
if self.title != '':
self.finishVerse()
self.finish_verse()
return
self.blankLines = 0
if self.skipToCloseBracket:
self.blank_lines = 0
if self.skip_to_close_bracket:
if text.endswith(')'):
self.skipToCloseBracket = False
self.skip_to_close_bracket = False
return
if text.startswith('CCL Licence'):
self.italics = False
@ -171,90 +174,93 @@ class SofImport(OooImport):
if text == 'A Songs of Fellowship Worship Resource':
return
if text.startswith('(NB.') or text.startswith('(Regrettably') or text.startswith('(From'):
self.skipToCloseBracket = True
self.skip_to_close_bracket = True
return
if text.startswith('Copyright'):
self.add_copyright(text)
return
if text == '(Repeat)':
self.finishVerse()
self.finish_verse()
self.repeat_verse()
return
if self.title == '':
if self.copyright == '':
self.addSofAuthor(text)
self.add_sof_author(text)
else:
self.add_copyright(text)
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
it's bold or italics. If it's bold then its a song number or song title.
Song titles are in all capitals, so we must bring the capitalization
into line
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
song number or song title.
Song titles are in all capitals, so we must bring the capitalization into line
:param text_portion: A Piece of text
"""
text = textportion.getString()
text = text_portion.getString()
text = self.tidy_text(text)
if text.strip() == '':
return text
if textportion.CharWeight == BOLD:
boldtext = text.strip()
if boldtext.isdigit() and self.song_number == '':
self.addSongNumber(boldtext)
if text_portion.CharWeight == BOLD:
bold_text = text.strip()
if bold_text.isdigit() and self.song_number == '':
self.add_song_number(bold_text)
return ''
text = self.uncapText(text)
text = self.uncap_text(text)
if self.title == '':
self.addTitle(text)
self.add_title(text)
return text
if text.strip().startswith('('):
return text
self.italics = (textportion.CharPosture == ITALIC)
self.italics = (text_portion.CharPosture == ITALIC)
return text
def newSong(self):
def new_song(self):
"""
A change of song. Store the old, create a new
... but only if the last song was complete. If not, stick with it
"""
if self.song:
self.finishVerse()
self.finish_verse()
if not self.check_complete():
return
self.finish()
self.song = True
self.set_defaults()
self.skipToCloseBracket = False
self.isChorus = False
self.skip_to_close_bracket = False
self.is_chorus = 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
number to work out which songbook we're in
Add a song number, store as alternate title. Also use the song number to work out which songbook we're in
:param song_no: The Song number
"""
self.song_number = song_no
self.alternateTitle = song_no + '.'
self.songBook_pub = 'Kingsway Publications'
self.alternate_title = song_no + '.'
self.song_book_pub = 'Kingsway Publications'
if int(song_no) <= 640:
self.songBook = 'Songs of Fellowship 1'
self.song_book = 'Songs of Fellowship 1'
elif int(song_no) <= 1150:
self.songBook = 'Songs of Fellowship 2'
self.song_book = 'Songs of Fellowship 2'
elif int(song_no) <= 1690:
self.songBook = 'Songs of Fellowship 3'
self.song_book = 'Songs of Fellowship 3'
elif int(song_no) <= 2200:
self.songBook = 'Songs of Fellowship 4'
self.song_book = 'Songs of Fellowship 4'
elif int(song_no) <= 2710:
self.songBook = 'Songs of Fellowship 5'
self.song_book = 'Songs of Fellowship 5'
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
we don't want in a title
Add the title to the song. Strip some leading/trailing punctuation that we don't want in a title
:param text: Title text
"""
title = text.strip()
if title.startswith('\''):
@ -264,50 +270,50 @@ class SofImport(OooImport):
self.title = title
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', '&'
and comma.
However need to check for "Mr and Mrs Smith" and turn it to
"Mr Smith" and "Mrs Smith".
Add the author. OpenLP stores them individually so split by 'and', '&' and comma.
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 ', ' & ')
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
we're beyond the second line of first verse, then this indicates
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'
Add a line to the current verse. If the formatting has changed and we're beyond the second line of first verse,
then this indicates a change of verse. Italics are a chorus
def finishVerse(self):
:param text: The verse text
"""
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.italics != self.is_chorus and ((len(self.verses) > 0) or
(self.current__verse.count('\n') > 1)):
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
if self.isChorus:
if self.is_chorus:
versetag = 'C'
splitat = None
else:
versetag = 'V'
splitat = self.verseSplits(self.song_number)
splitat = self.verse_splits(self.song_number)
if splitat:
ln = 0
verse = ''
for line in self.currentVerse.split('\n'):
for line in self.current__verse.split('\n'):
ln += 1
if line == '' or ln > splitat:
self.addSofVerse(verse, versetag)
self.add_sof_verse(verse, versetag)
ln = 0
if line:
verse = line + '\n'
@ -316,19 +322,19 @@ class SofImport(OooImport):
else:
verse += line + '\n'
if verse:
self.addSofVerse(verse, versetag)
self.add_sof_verse(verse, versetag)
else:
self.addSofVerse(self.currentVerse, versetag)
self.currentVerse = ''
self.isChorus = False
self.add_sof_verse(self.current__verse, versetag)
self.current__verse = ''
self.is_chorus = False
def addSofVerse(self, lyrics, tag):
def add_sof_verse(self, 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.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.
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
sometimes upper depending on context. Never mind, keep it lower.
"""
textarr = re.split('(\W+)', text)
textarr[0] = textarr[0].capitalize()
for i in range(1, len(textarr)):
text_arr = re.split('(\W+)', text)
text_arr[0] = text_arr[0].capitalize()
for i in range(1, len(text_arr)):
# Do not translate these. Fixed strings in SOF song file
if textarr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY',
'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD', 'FATHER',
'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE',
'I\'M', 'I\'LL', 'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS',
'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'', 'JIREH',
'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S',
'FATHER\'S', 'ELIJAH' 'MARTHA', 'CHRISTMAS', 'ALPHA',
'OMEGA'):
textarr[i] = textarr[i].capitalize()
if text_arr[i] in ('JESUS', 'CHRIST', 'KING', 'ALMIGHTY', 'REDEEMER', 'SHEPHERD', 'SON', 'GOD', 'LORD',
'FATHER', 'HOLY', 'SPIRIT', 'LAMB', 'YOU', 'YOUR', 'I', 'I\'VE', 'I\'M', 'I\'LL',
'SAVIOUR', 'O', 'YOU\'RE', 'HE', 'HIS', 'HIM', 'ZION', 'EMMANUEL', 'MAJESTY', 'JESUS\'',
'JIREH', 'JUDAH', 'LION', 'LORD\'S', 'ABRAHAM', 'GOD\'S', 'FATHER\'S', 'ELIJAH' 'MARTHA',
'CHRISTMAS', 'ALPHA', 'OMEGA'):
text_arr[i] = text_arr[i].capitalize()
else:
textarr[i] = textarr[i].lower()
text = ''.join(textarr)
text_arr[i] = text_arr[i].lower()
text = ''.join(text_arr)
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,
some verses were not formatted correctly.
Because someone at Kingsway forgot to check the 1+2 RTF file, some verses were not formatted correctly.
:param song_number: The Song number
"""
if song_number == 11:
return 8

View File

@ -40,6 +40,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
class SongBeamerTypes(object):
MarkTypes = {
'Refrain': VerseType.tags[VerseType.Chorus],
@ -110,8 +111,8 @@ class SongBeamerImport(SongImport):
if self.stop_import_flag:
return
self.set_defaults()
self.currentVerse = ''
self.currentVerseType = VerseType.tags[VerseType.Verse]
self.current_verse = ''
self.current_verse_type = VerseType.tags[VerseType.Verse]
read_verses = False
file_name = os.path.split(import_file)[1]
if os.path.isfile(import_file):
@ -132,39 +133,38 @@ class SongBeamerImport(SongImport):
if line.startswith('#') and not read_verses:
self.parseTags(line)
elif line.startswith('---'):
if self.currentVerse:
self.replaceHtmlTags()
self.add_verse(self.currentVerse, self.currentVerseType)
self.currentVerse = ''
self.currentVerseType = VerseType.tags[VerseType.Verse]
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
self.current_verse = ''
self.current_verse_type = VerseType.tags[VerseType.Verse]
read_verses = True
verse_start = True
elif read_verses:
if verse_start:
verse_start = False
if not self.checkVerseMarks(line):
self.currentVerse = line + '\n'
if not self.check_verse_marks(line):
self.current_verse = line + '\n'
else:
self.currentVerse += line + '\n'
if self.currentVerse:
self.replaceHtmlTags()
self.add_verse(self.currentVerse, self.currentVerseType)
self.current_verse += line + '\n'
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
if not self.finish():
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.
"""
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):
"""
Parses a meta data line.
``line``
The line in the file. It should consist of a tag and a value for this tag (unicode)::
:param line: 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'
"""
@ -267,20 +267,19 @@ class SongBeamerImport(SongImport):
# TODO: add the verse order.
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
``False``.
``line``
The line to check for marks (unicode).
:param line: The line to check for marks (unicode).
"""
marks = line.split(' ')
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 we have a digit, we append it to current_verse_type.
if marks[1].isdigit():
self.currentVerseType += marks[1]
self.current_verse_type += marks[1]
return True
return False

View File

@ -56,11 +56,8 @@ def songs_probably_equal(song1, song2):
"""
Calculate and return whether two songs are probably equal.
``song1``
The first song to compare.
``song2``
The second song to compare.
:param song1: The first song to compare.
:param song2: The second song to compare.
"""
if len(song1.search_lyrics) < len(song2.search_lyrics):
small = song1.search_lyrics
@ -96,8 +93,7 @@ def _op_length(opcode):
"""
Return the length of a given difference.
``opcode``
The difference.
:param opcode: The difference.
"""
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)
surrounded by larger equal passages (>min_fragment_size).
``diff``
The diff set to remove the typos from.
:param diff: The diff set to remove the typos from.
"""
# Remove typo at beginning of the string.
if len(diff) >= 2:
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and \
_op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
del diff[0]
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and _op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
del diff[0]
# Remove typos in the middle of the string.
if len(diff) >= 3:
for index in range(len(diff) - 3, -1, -1):
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
diff[index + 1][0] != "equal" and _op_length(diff[index + 1]) <= MAX_TYPO_SIZE and \
_op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
del diff[index + 1]
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and diff[index + 1][0] != "equal" and \
_op_length(diff[index + 1]) <= MAX_TYPO_SIZE and _op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
del diff[index + 1]
# Remove typo at the end of the string.
if len(diff) >= 2:
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and \
diff[-1][0] != "equal" and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
del diff[-1]
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and diff[-1][0] != "equal" \
and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
del diff[-1]
# Merge the bordering equal passages that occured by removing differences.
for index in range(len(diff) - 2, -1, -1):
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] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3],
diff[index + 1][4])
del diff[index + 1]
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 + 1][4])
del diff[index + 1]
return diff

View File

@ -271,8 +271,7 @@ class SongImport(QtCore.QObject):
def check_complete(self):
"""
Check the mandatory fields are entered (i.e. title and a verse)
Author not checked here, if no author then "Author unknown" is
automatically added
Author not checked here, if no author then "Author unknown" is automatically added
"""
if not self.title or not self.verses:
return False

View File

@ -89,13 +89,13 @@ class SongProImport(SongImport):
file_line = str(file_line, 'cp1252')
file_text = file_line.rstrip()
if file_text and file_text[0] == '#':
self.processSection(tag, text.rstrip())
self.process_section(tag, text.rstrip())
tag = file_text[1:]
text = ''
else:
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.
"""

View File

@ -141,8 +141,8 @@ class SongShowPlusImport(SongImport):
authors = self.decode(data).split(" / ")
for author in authors:
if author.find(",") !=-1:
authorParts = author.split(", ")
author = authorParts[1] + " " + authorParts[0]
author_parts = author.split(", ")
author = author_parts[1] + " " + author_parts[0]
self.parse_author(author)
elif block_key == COPYRIGHT:
self.add_copyright(self.decode(data))
@ -180,6 +180,13 @@ class SongShowPlusImport(SongImport):
self.log_error(file)
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
# concept of part verses, so just ignore any non integers on the end (including floats))
match = re.match(r'(\D*)(\d+)', verse_name)

View File

@ -71,10 +71,10 @@ class SongsTab(SettingsTab):
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.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.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):
self.song_search = (check_state == QtCore.Qt.Checked)

View File

@ -48,6 +48,7 @@ HOTKEY_TO_VERSE_TYPE = {
'+': 'b',
'Z': 'o'}
class SundayPlusImport(SongImport):
"""
Import Sunday Plus songs
@ -68,10 +69,10 @@ class SundayPlusImport(SongImport):
if self.stop_import_flag:
return
song_file = open(filename)
self.doImportFile(song_file)
self.do_import_file(song_file)
song_file.close()
def doImportFile(self, file):
def do_import_file(self, file):
"""
Process the Sunday Plus file object.
"""
@ -80,11 +81,18 @@ class SundayPlusImport(SongImport):
self.log_error(file.name)
return
if not self.title:
self.title = self.titleFromFilename(file.name)
self.title = self.title_from_filename(file.name)
if not self.finish():
self.log_error(file.name)
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] != ']':
self.log_error('File is malformed')
return False
@ -173,7 +181,13 @@ class SundayPlusImport(SongImport):
i += 1
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]
if title.endswith('.ptf'):
title = title[:-4]

View File

@ -40,6 +40,7 @@ BLOCK_TYPES = ('V', 'C', 'B')
log = logging.getLogger(__name__)
class WowImport(SongImport):
"""
The :class:`WowImport` class provides the ability to import song files from
@ -113,29 +114,33 @@ class WowImport(SongImport):
self.set_defaults()
song_data = open(source, 'rb')
if song_data.read(19) != 'WoW File\nSong Words':
self.log_error(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
('Invalid Words of Worship song file. Missing "Wow File\\nSong Words" header.'))))
self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "Wow File\\nSong '
'Words" header.')))
continue
# Seek to byte which stores number of blocks in the song
song_data.seek(56)
no_of_blocks = ord(song_data.read(1))
song_data.seek(66)
if song_data.read(16) != 'CSongDoc::CBlock':
self.log_error(source, str(translate('SongsPlugin.WordsofWorshipSongImport',
('Invalid Words of Worship song file. Missing "CSongDoc::CBlock" string.'))))
self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
'string.')))
continue
# Seek to the beginning of the first block
song_data.seek(82)
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 = ''
while self.linesToRead:
self.lineText = str(song_data.read(ord(song_data.read(1))), 'cp1252')
while self.lines_to_read:
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
song_data.seek(1, os.SEEK_CUR)
if block_text:
block_text += '\n'
block_text += self.lineText
self.linesToRead -= 1
block_text += self.line_text
self.lines_to_read -= 1
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
# Blocks are separated by 2 bytes, skip them, but not if
# this is the last block!

View File

@ -97,21 +97,13 @@ class SongXML(object):
"""
Add a verse to the ``<lyrics>`` tag.
``type``
A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*. Any other
type is **not** allowed, this also includes translated types.
``number``
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.
:param type: A string denoting the type of verse. Possible values are *v*, *c*, *b*, *p*, *i*, *e* and *o*.
Any other 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.
:param 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),
label=str(number))
verse = etree.Element('verse', type=str(type), label=str(number))
if lang:
verse.set('lang', lang)
verse.text = etree.CDATA(content)
@ -121,16 +113,13 @@ class SongXML(object):
"""
Extract our newly created XML song.
"""
return etree.tostring(self.song_xml, encoding='UTF-8',
xml_declaration=True)
return etree.tostring(self.song_xml, encoding='UTF-8', xml_declaration=True)
def get_verses(self, xml):
"""
Iterates through the verses in the XML and returns a list of verses and their attributes.
``xml``
The XML of the song to be parsed.
:param xml: The XML of the song to be parsed.
The returned list has the following format::
[[{'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.
``text``
The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
:param text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags are allowed::
{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
sources (e. g. OpenLyrics import), we cannot ensure, that it completely conforms to the OpenLyrics standard.
``xml``
The XML to parse (unicode).
``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``.
:param xml: 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``.
"""
# No xml get out of here.
if not xml:
@ -418,6 +403,15 @@ class OpenLyrics(object):
return song
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:
element = etree.Element(tag, name=str(label))
else:
@ -430,6 +424,9 @@ class OpenLyrics(object):
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.
:param tag_name: The tag_name
:param tags_element: Some tag elements
"""
available_tags = FormattingTags.get_html_tags()
start_tag = '{%s}' % tag_name
@ -477,16 +474,16 @@ class OpenLyrics(object):
def _extract_xml(self, xml):
"""
Extract our newly created XML song.
:param xml: The XML
"""
return etree.tostring(xml, encoding='UTF-8',
xml_declaration=True)
return etree.tostring(xml, encoding='UTF-8', xml_declaration=True)
def _text(self, element):
"""
This returns the text of an element as unicode string.
``element``
The element.
:param element: The element.
"""
if element.text is not None:
return str(element.text)
@ -496,11 +493,8 @@ class OpenLyrics(object):
"""
Adds the authors specified in the XML to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object
"""
authors = []
if hasattr(properties, 'authors'):
@ -509,24 +503,20 @@ class OpenLyrics(object):
if display_name:
authors.append(display_name)
for display_name in authors:
author = self.manager.get_object_filtered(Author,
Author.display_name == display_name)
author = self.manager.get_object_filtered(Author, Author.display_name == display_name)
if author is None:
# We need to create a new author, as the author does not exist.
author = Author.populate(display_name=display_name,
last_name=display_name.split(' ')[-1],
first_name=' '.join(display_name.split(' ')[:-1]))
last_name=display_name.split(' ')[-1],
first_name=' '.join(display_name.split(' ')[:-1]))
song.authors.append(author)
def _process_cclinumber(self, properties, song):
"""
Adds the CCLI number to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(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.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(properties, 'comments'):
comments_list = []
@ -553,11 +540,8 @@ class OpenLyrics(object):
"""
Adds the copyright to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(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
formatting tag list.
:param song_xml: The song XML
:param temporary: Is the song temporary?
"""
if not hasattr(song_xml, 'format'):
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
converted.
``element``
The property object (lxml.etree.Element).
``newlines``
The switch to enable/disable processing of line breaks <br/>. The <br/> is used since OpenLyrics 0.8.
: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
OpenLyrics 0.8.
"""
text = ''
use_endtag = True
@ -655,8 +640,8 @@ class OpenLyrics(object):
"""
Converts lyrics lines to OpenLP representation.
``lines``
The lines object (lxml.objectify.ObjectifiedElement).
:param lines: The lines object (lxml.objectify.ObjectifiedElement).
:param version:
"""
text = ''
# Convert lxml.objectify to lxml.etree representation.
@ -682,14 +667,9 @@ class OpenLyrics(object):
"""
Processes the verses and search_lyrics for the song.
``properties``
The properties object (lxml.objectify.ObjectifiedElement).
``song_xml``
The objectified song (lxml.objectify.ObjectifiedElement).
``song_obj``
The song object.
:param properties: The properties object (lxml.objectify.ObjectifiedElement).
:param song_xml: The objectified song (lxml.objectify.ObjectifiedElement).
:param song_obj: The song object.
"""
sxml = SongXML()
verses = {}
@ -698,12 +678,12 @@ class OpenLyrics(object):
lyrics = song_xml.lyrics
except AttributeError:
raise OpenLyricsError(OpenLyricsError.LyricsError, '<lyrics> tag is missing.',
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
translate('OpenLP.OpenLyricsImportError', '<lyrics> tag is missing.'))
try:
verse_list = lyrics.verse
except AttributeError:
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.
for verse in verse_list:
text = ''
@ -712,8 +692,7 @@ class OpenLyrics(object):
if text:
text += '\n'
# Append text from "lines" element to verse text.
text += self._process_verse_lines(lines,
version=song_xml.get('version'))
text += self._process_verse_lines(lines, version=song_xml.get('version'))
# Add an optional split to the verse text.
if lines.get('break') is not None:
text += '\n[---]'
@ -749,11 +728,8 @@ class OpenLyrics(object):
"""
Adds the song book and song number specified in the XML to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
song.song_book_id = None
song.song_number = ''
@ -775,11 +751,8 @@ class OpenLyrics(object):
"""
Processes the titles specified in the song's XML.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
for title in properties.titles.title:
if not song.title:
@ -792,11 +765,8 @@ class OpenLyrics(object):
"""
Adds the topics to the song.
``properties``
The property object (lxml.objectify.ObjectifiedElement).
``song``
The song object.
:param properties: The property object (lxml.objectify.ObjectifiedElement).
:param song: The song object.
"""
if hasattr(properties, 'themes'):
for topic_text in properties.themes.theme:

View File

@ -41,6 +41,7 @@ log = logging.getLogger(__name__)
# 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])
class ZionWorxImport(SongImport):
"""
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:
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
'DefaultStyle']
'DefaultStyle']
songs_reader = csv.DictReader(songs_file, field_names)
try:
records = list(songs_reader)
except csv.Error as e:
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
num_records = len(records)
log.info('%s records found in CSV file' % num_records)
@ -108,7 +109,7 @@ class ZionWorxImport(SongImport):
lyrics = self._decode(record['Lyrics'])
except UnicodeDecodeError as e:
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d' % index),
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
translate('SongsPlugin.ZionWorxImport', 'Decoding error: %s') % e)
continue
except TypeError as e:
self.log_error(translate(
@ -126,12 +127,11 @@ class ZionWorxImport(SongImport):
title = self.title
if not self.finish():
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index
+ (': "' + title + '"' if title else ''))
+ (': "' + title + '"' if title else ''))
def _decode(self, str):
"""
Decodes CSV input to unicode, stripping all control characters (except
new lines).
Decodes CSV input to unicode, stripping all control characters (except new lines).
"""
# This encoding choice seems OK. ZionWorx has no option for setting the
# encoding for its songs, so we assume encoding is always the same.