Merge lp:~raoul-snyman/openlp/wordproject-importer into lp:openlp

Proposed by Raoul Snyman
Status: Merged
Merged at revision: 2708
Proposed branch: lp:~raoul-snyman/openlp/wordproject-importer
Merge into: lp:openlp
Diff against target: 1184 lines (+933/-15)
9 files modified
openlp/core/ui/lib/wizard.py (+2/-0)
openlp/plugins/bibles/forms/bibleimportform.py (+63/-13)
openlp/plugins/bibles/lib/importers/wordproject.py (+169/-0)
openlp/plugins/bibles/lib/manager.py (+6/-1)
openlp/plugins/bibles/lib/mediaitem.py (+1/-0)
openlp/plugins/media/lib/mediaitem.py (+2/-1)
tests/functional/openlp_plugins/bibles/test_wordprojectimport.py (+220/-0)
tests/resources/bibles/wordproject_chapter.htm (+248/-0)
tests/resources/bibles/wordproject_index.htm (+222/-0)
To merge this branch: bzr merge lp:~raoul-snyman/openlp/wordproject-importer
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Review via email: mp+311826@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/ui/lib/wizard.py'
2--- openlp/core/ui/lib/wizard.py 2016-04-26 19:04:22 +0000
3+++ openlp/core/ui/lib/wizard.py 2016-11-25 14:28:50 +0000
4@@ -46,6 +46,7 @@
5 OSIS = 'OSIS'
6 ZEF = 'Zefania'
7 SWORD = 'Sword'
8+ WordProject = 'WordProject'
9 # These strings should need a good reason to be retranslated elsewhere.
10 FinishedImport = translate('OpenLP.Ui', 'Finished import.')
11 FormatLabel = translate('OpenLP.Ui', 'Format:')
12@@ -95,6 +96,7 @@
13 super(OpenLPWizard, self).__init__(parent)
14 self.plugin = plugin
15 self.with_progress_page = add_progress_page
16+ self.setFixedWidth(640)
17 self.setObjectName(name)
18 self.open_icon = build_icon(':/general/general_open.png')
19 self.delete_icon = build_icon(':/general/general_delete.png')
20
21=== modified file 'openlp/plugins/bibles/forms/bibleimportform.py'
22--- openlp/plugins/bibles/forms/bibleimportform.py 2016-08-29 16:11:09 +0000
23+++ openlp/plugins/bibles/forms/bibleimportform.py 2016-11-25 14:28:50 +0000
24@@ -125,6 +125,7 @@
25 self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked)
26 self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked)
27 self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked)
28+ self.wordproject_browse_button.clicked.connect(self.on_wordproject_browse_button_clicked)
29 self.web_update_button.clicked.connect(self.on_web_update_button_clicked)
30 self.sword_browse_button.clicked.connect(self.on_sword_browse_button_clicked)
31 self.sword_zipbrowse_button.clicked.connect(self.on_sword_zipbrowse_button_clicked)
32@@ -143,7 +144,7 @@
33 self.format_label = QtWidgets.QLabel(self.select_page)
34 self.format_label.setObjectName('FormatLabel')
35 self.format_combo_box = QtWidgets.QComboBox(self.select_page)
36- self.format_combo_box.addItems(['', '', '', '', '', ''])
37+ self.format_combo_box.addItems(['', '', '', '', '', '', ''])
38 self.format_combo_box.setObjectName('FormatComboBox')
39 self.format_layout.addRow(self.format_label, self.format_combo_box)
40 self.spacer = QtWidgets.QSpacerItem(10, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
41@@ -161,6 +162,7 @@
42 self.osis_file_layout = QtWidgets.QHBoxLayout()
43 self.osis_file_layout.setObjectName('OsisFileLayout')
44 self.osis_file_edit = QtWidgets.QLineEdit(self.osis_widget)
45+ self.osis_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
46 self.osis_file_edit.setObjectName('OsisFileEdit')
47 self.osis_file_layout.addWidget(self.osis_file_edit)
48 self.osis_browse_button = QtWidgets.QToolButton(self.osis_widget)
49@@ -180,6 +182,7 @@
50 self.csv_books_layout = QtWidgets.QHBoxLayout()
51 self.csv_books_layout.setObjectName('CsvBooksLayout')
52 self.csv_books_edit = QtWidgets.QLineEdit(self.csv_widget)
53+ self.csv_books_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
54 self.csv_books_edit.setObjectName('CsvBooksEdit')
55 self.csv_books_layout.addWidget(self.csv_books_edit)
56 self.csv_books_button = QtWidgets.QToolButton(self.csv_widget)
57@@ -192,6 +195,7 @@
58 self.csv_verses_layout = QtWidgets.QHBoxLayout()
59 self.csv_verses_layout.setObjectName('CsvVersesLayout')
60 self.csv_verses_edit = QtWidgets.QLineEdit(self.csv_widget)
61+ self.csv_verses_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
62 self.csv_verses_edit.setObjectName('CsvVersesEdit')
63 self.csv_verses_layout.addWidget(self.csv_verses_edit)
64 self.csv_verses_button = QtWidgets.QToolButton(self.csv_widget)
65@@ -211,6 +215,7 @@
66 self.open_song_file_layout = QtWidgets.QHBoxLayout()
67 self.open_song_file_layout.setObjectName('OpenSongFileLayout')
68 self.open_song_file_edit = QtWidgets.QLineEdit(self.open_song_widget)
69+ self.open_song_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
70 self.open_song_file_edit.setObjectName('OpenSongFileEdit')
71 self.open_song_file_layout.addWidget(self.open_song_file_edit)
72 self.open_song_browse_button = QtWidgets.QToolButton(self.open_song_widget)
73@@ -300,61 +305,84 @@
74 self.sword_widget = QtWidgets.QWidget(self.select_page)
75 self.sword_widget.setObjectName('SwordWidget')
76 self.sword_layout = QtWidgets.QVBoxLayout(self.sword_widget)
77+ self.sword_layout.setContentsMargins(0, 0, 0, 0)
78 self.sword_layout.setObjectName('SwordLayout')
79 self.sword_tab_widget = QtWidgets.QTabWidget(self.sword_widget)
80 self.sword_tab_widget.setObjectName('SwordTabWidget')
81 self.sword_folder_tab = QtWidgets.QWidget(self.sword_tab_widget)
82 self.sword_folder_tab.setObjectName('SwordFolderTab')
83- self.sword_folder_tab_layout = QtWidgets.QGridLayout(self.sword_folder_tab)
84+ self.sword_folder_tab_layout = QtWidgets.QFormLayout(self.sword_folder_tab)
85 self.sword_folder_tab_layout.setObjectName('SwordTabFolderLayout')
86 self.sword_folder_label = QtWidgets.QLabel(self.sword_folder_tab)
87 self.sword_folder_label.setObjectName('SwordSourceLabel')
88- self.sword_folder_tab_layout.addWidget(self.sword_folder_label, 0, 0)
89 self.sword_folder_label.setObjectName('SwordFolderLabel')
90 self.sword_folder_edit = QtWidgets.QLineEdit(self.sword_folder_tab)
91+ self.sword_folder_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
92 self.sword_folder_edit.setObjectName('SwordFolderEdit')
93 self.sword_browse_button = QtWidgets.QToolButton(self.sword_folder_tab)
94 self.sword_browse_button.setIcon(self.open_icon)
95 self.sword_browse_button.setObjectName('SwordBrowseButton')
96- self.sword_folder_tab_layout.addWidget(self.sword_folder_edit, 0, 1)
97- self.sword_folder_tab_layout.addWidget(self.sword_browse_button, 0, 2)
98+ self.sword_folder_layout = QtWidgets.QHBoxLayout()
99+ self.sword_folder_layout.addWidget(self.sword_folder_edit)
100+ self.sword_folder_layout.addWidget(self.sword_browse_button)
101+ self.sword_folder_tab_layout.addRow(self.sword_folder_label, self.sword_folder_layout)
102 self.sword_bible_label = QtWidgets.QLabel(self.sword_folder_tab)
103 self.sword_bible_label.setObjectName('SwordBibleLabel')
104- self.sword_folder_tab_layout.addWidget(self.sword_bible_label, 1, 0)
105 self.sword_bible_combo_box = QtWidgets.QComboBox(self.sword_folder_tab)
106 self.sword_bible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
107 self.sword_bible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
108 self.sword_bible_combo_box.setObjectName('SwordBibleComboBox')
109- self.sword_folder_tab_layout.addWidget(self.sword_bible_combo_box, 1, 1)
110+ self.sword_folder_tab_layout.addRow(self.sword_bible_label, self.sword_bible_combo_box)
111 self.sword_tab_widget.addTab(self.sword_folder_tab, '')
112 self.sword_zip_tab = QtWidgets.QWidget(self.sword_tab_widget)
113 self.sword_zip_tab.setObjectName('SwordZipTab')
114- self.sword_zip_layout = QtWidgets.QGridLayout(self.sword_zip_tab)
115+ self.sword_zip_layout = QtWidgets.QFormLayout(self.sword_zip_tab)
116 self.sword_zip_layout.setObjectName('SwordZipLayout')
117 self.sword_zipfile_label = QtWidgets.QLabel(self.sword_zip_tab)
118 self.sword_zipfile_label.setObjectName('SwordZipFileLabel')
119 self.sword_zipfile_edit = QtWidgets.QLineEdit(self.sword_zip_tab)
120+ self.sword_zipfile_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
121 self.sword_zipfile_edit.setObjectName('SwordZipFileEdit')
122 self.sword_zipbrowse_button = QtWidgets.QToolButton(self.sword_zip_tab)
123 self.sword_zipbrowse_button.setIcon(self.open_icon)
124 self.sword_zipbrowse_button.setObjectName('SwordZipBrowseButton')
125+ self.sword_zipfile_layout = QtWidgets.QHBoxLayout()
126+ self.sword_zipfile_layout.addWidget(self.sword_zipfile_edit)
127+ self.sword_zipfile_layout.addWidget(self.sword_zipbrowse_button)
128+ self.sword_zip_layout.addRow(self.sword_zipfile_label, self.sword_zipfile_layout)
129 self.sword_zipbible_label = QtWidgets.QLabel(self.sword_folder_tab)
130 self.sword_zipbible_label.setObjectName('SwordZipBibleLabel')
131 self.sword_zipbible_combo_box = QtWidgets.QComboBox(self.sword_zip_tab)
132 self.sword_zipbible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
133 self.sword_zipbible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
134 self.sword_zipbible_combo_box.setObjectName('SwordZipBibleComboBox')
135- self.sword_zip_layout.addWidget(self.sword_zipfile_label, 0, 0)
136- self.sword_zip_layout.addWidget(self.sword_zipfile_edit, 0, 1)
137- self.sword_zip_layout.addWidget(self.sword_zipbrowse_button, 0, 2)
138- self.sword_zip_layout.addWidget(self.sword_zipbible_label, 1, 0)
139- self.sword_zip_layout.addWidget(self.sword_zipbible_combo_box, 1, 1)
140+ self.sword_zip_layout.addRow(self.sword_zipbible_label, self.sword_zipbible_combo_box)
141 self.sword_tab_widget.addTab(self.sword_zip_tab, '')
142 self.sword_layout.addWidget(self.sword_tab_widget)
143 self.sword_disabled_label = QtWidgets.QLabel(self.sword_widget)
144 self.sword_disabled_label.setObjectName('SwordDisabledLabel')
145 self.sword_layout.addWidget(self.sword_disabled_label)
146 self.select_stack.addWidget(self.sword_widget)
147+ self.wordproject_widget = QtWidgets.QWidget(self.select_page)
148+ self.wordproject_widget.setObjectName('WordProjectWidget')
149+ self.wordproject_layout = QtWidgets.QFormLayout(self.wordproject_widget)
150+ self.wordproject_layout.setContentsMargins(0, 0, 0, 0)
151+ self.wordproject_layout.setObjectName('WordProjectLayout')
152+ self.wordproject_file_label = QtWidgets.QLabel(self.wordproject_widget)
153+ self.wordproject_file_label.setObjectName('WordProjectFileLabel')
154+ self.wordproject_file_layout = QtWidgets.QHBoxLayout()
155+ self.wordproject_file_layout.setObjectName('WordProjectFileLayout')
156+ self.wordproject_file_edit = QtWidgets.QLineEdit(self.wordproject_widget)
157+ self.wordproject_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
158+ self.wordproject_file_edit.setObjectName('WordProjectFileEdit')
159+ self.wordproject_file_layout.addWidget(self.wordproject_file_edit)
160+ self.wordproject_browse_button = QtWidgets.QToolButton(self.wordproject_widget)
161+ self.wordproject_browse_button.setIcon(self.open_icon)
162+ self.wordproject_browse_button.setObjectName('WordProjectBrowseButton')
163+ self.wordproject_file_layout.addWidget(self.wordproject_browse_button)
164+ self.wordproject_layout.addRow(self.wordproject_file_label, self.wordproject_file_layout)
165+ self.wordproject_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer)
166+ self.select_stack.addWidget(self.wordproject_widget)
167 self.select_page_layout.addLayout(self.select_stack)
168 self.addPage(self.select_page)
169 # License Page
170@@ -400,6 +428,7 @@
171 self.format_combo_box.setItemText(BibleFormat.OSIS, WizardStrings.OSIS)
172 self.format_combo_box.setItemText(BibleFormat.CSV, WizardStrings.CSV)
173 self.format_combo_box.setItemText(BibleFormat.OpenSong, WizardStrings.OS)
174+ self.format_combo_box.setItemText(BibleFormat.WordProject, WizardStrings.WordProject)
175 self.format_combo_box.setItemText(BibleFormat.WebDownload, translate('BiblesPlugin.ImportWizardForm',
176 'Web Download'))
177 self.format_combo_box.setItemText(BibleFormat.Zefania, WizardStrings.ZEF)
178@@ -410,6 +439,7 @@
179 self.open_song_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
180 self.web_source_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Location:'))
181 self.zefania_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
182+ self.wordproject_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
183 self.web_update_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Click to download bible list'))
184 self.web_update_button.setText(translate('BiblesPlugin.ImportWizardForm', 'Download bible list'))
185 self.web_source_combo_box.setItemText(WebDownload.Crosswalk, translate('BiblesPlugin.ImportWizardForm',
186@@ -468,6 +498,7 @@
187 """
188 Validate the current page before moving on to the next page.
189 """
190+ log.debug(self.size())
191 if self.currentPage() == self.welcome_page:
192 return True
193 elif self.currentPage() == self.select_page:
194@@ -504,6 +535,12 @@
195 critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.ZEF)
196 self.zefania_file_edit.setFocus()
197 return False
198+ elif self.field('source_format') == BibleFormat.WordProject:
199+ if not self.field('wordproject_file'):
200+ critical_error_message_box(UiStrings().NFSs,
201+ WizardStrings.YouSpecifyFile % WizardStrings.WordProject)
202+ self.wordproject_file_edit.setFocus()
203+ return False
204 elif self.field('source_format') == BibleFormat.WebDownload:
205 # If count is 0 the bible list has not yet been downloaded
206 if self.web_translation_combo_box.count() == 0:
207@@ -627,6 +664,14 @@
208 self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit,
209 'last directory import')
210
211+ def on_wordproject_browse_button_clicked(self):
212+ """
213+ Show the file open dialog for the WordProject file.
214+ """
215+ # TODO: Verify format() with variable template
216+ self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.WordProject, self.wordproject_file_edit,
217+ 'last directory import')
218+
219 def on_web_update_button_clicked(self):
220 """
221 Download list of bibles from Crosswalk, BibleServer and BibleGateway.
222@@ -707,6 +752,7 @@
223 self.select_page.registerField('csv_versefile', self.csv_verses_edit)
224 self.select_page.registerField('opensong_file', self.open_song_file_edit)
225 self.select_page.registerField('zefania_file', self.zefania_file_edit)
226+ self.select_page.registerField('wordproject_file', self.wordproject_file_edit)
227 self.select_page.registerField('web_location', self.web_source_combo_box)
228 self.select_page.registerField('web_biblename', self.web_translation_combo_box)
229 self.select_page.registerField('sword_folder_path', self.sword_folder_edit)
230@@ -799,6 +845,10 @@
231 # Import a Zefania bible.
232 importer = self.manager.import_bible(BibleFormat.Zefania, name=license_version,
233 filename=self.field('zefania_file'))
234+ elif bible_type == BibleFormat.WordProject:
235+ # Import a WordProject bible.
236+ importer = self.manager.import_bible(BibleFormat.WordProject, name=license_version,
237+ filename=self.field('wordproject_file'))
238 elif bible_type == BibleFormat.SWORD:
239 # Import a SWORD bible.
240 if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab):
241
242=== added file 'openlp/plugins/bibles/lib/importers/wordproject.py'
243--- openlp/plugins/bibles/lib/importers/wordproject.py 1970-01-01 00:00:00 +0000
244+++ openlp/plugins/bibles/lib/importers/wordproject.py 2016-11-25 14:28:50 +0000
245@@ -0,0 +1,169 @@
246+# -*- coding: utf-8 -*-
247+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
248+
249+###############################################################################
250+# OpenLP - Open Source Lyrics Projection #
251+# --------------------------------------------------------------------------- #
252+# Copyright (c) 2008-2016 OpenLP Developers #
253+# --------------------------------------------------------------------------- #
254+# This program is free software; you can redistribute it and/or modify it #
255+# under the terms of the GNU General Public License as published by the Free #
256+# Software Foundation; version 2 of the License. #
257+# #
258+# This program is distributed in the hope that it will be useful, but WITHOUT #
259+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
260+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
261+# more details. #
262+# #
263+# You should have received a copy of the GNU General Public License along #
264+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
265+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
266+###############################################################################
267+import os
268+import re
269+import logging
270+from codecs import open as copen
271+from tempfile import TemporaryDirectory
272+from zipfile import ZipFile
273+
274+from bs4 import BeautifulSoup, Tag, NavigableString
275+
276+from openlp.plugins.bibles.lib.bibleimport import BibleImport
277+
278+BOOK_NUMBER_PATTERN = re.compile(r'\[(\d+)\]')
279+REPLACE_SPACES = re.compile(r'\s{2,}')
280+
281+log = logging.getLogger(__name__)
282+
283+
284+class WordProjectBible(BibleImport):
285+ """
286+ `WordProject <http://www.wordproaudio.com/>`_ Bible format importer class.
287+ """
288+ def _cleanup(self):
289+ """
290+ Clean up after ourselves
291+ """
292+ self.tmp.cleanup()
293+
294+ def _unzip_file(self):
295+ """
296+ Unzip the file to a temporary directory
297+ """
298+ self.tmp = TemporaryDirectory()
299+ zip_file = ZipFile(os.path.abspath(self.filename))
300+ zip_file.extractall(self.tmp.name)
301+ self.base_dir = os.path.join(self.tmp.name, os.path.splitext(os.path.basename(self.filename))[0])
302+
303+ def process_books(self):
304+ """
305+ Extract and create the bible books from the parsed html
306+
307+ :param bible_data: parsed xml
308+ :return: None
309+ """
310+ with copen(os.path.join(self.base_dir, 'index.htm'), encoding='utf-8', errors='ignore') as index_file:
311+ page = index_file.read()
312+ soup = BeautifulSoup(page, 'lxml')
313+ bible_books = soup.find('div', 'textOptions').find_all('li')
314+ book_count = len(bible_books)
315+ for li_book in bible_books:
316+ log.debug(li_book)
317+ if self.stop_import_flag:
318+ break
319+ # Sometimes the structure is "[1] <a>Genesis</a>", and sometimes it's "<a>[1] Genesis</a>"
320+ if isinstance(li_book.contents[0], NavigableString) and str(li_book.contents[0]).strip():
321+ book_string = str(li_book.contents[0])
322+ book_name = str(li_book.a.contents[0])
323+ elif li_book.a:
324+ book_string, book_name = str(li_book.a.contents[0]).split(' ', 1)
325+ book_link = li_book.a['href']
326+ book_id = int(BOOK_NUMBER_PATTERN.search(book_string).group(1))
327+ book_name = book_name.strip()
328+ db_book = self.find_and_create_book(book_name, book_count, self.language_id, book_id)
329+ self.process_chapters(db_book, book_id, book_link)
330+ self.session.commit()
331+
332+ def process_chapters(self, db_book, book_id, book_link):
333+ """
334+ Extract the chapters, and do some initial processing of the verses
335+
336+ :param book: An OpenLP bible database book object
337+ :param chapters: parsed chapters
338+ :return: None
339+ """
340+ log.debug(book_link)
341+ book_file = os.path.join(self.base_dir, os.path.normpath(book_link))
342+ with copen(book_file, encoding='utf-8', errors='ignore') as f:
343+ page = f.read()
344+ soup = BeautifulSoup(page, 'lxml')
345+ header_div = soup.find('div', 'textHeader')
346+ chapters_p = header_div.find('p')
347+ if not chapters_p:
348+ chapters_p = soup.p
349+ log.debug(chapters_p)
350+ for item in chapters_p.contents:
351+ if self.stop_import_flag:
352+ break
353+ if isinstance(item, Tag) and item.name in ['a', 'span']:
354+ chapter_number = int(item.string.strip())
355+ self.set_current_chapter(db_book.name, chapter_number)
356+ self.process_verses(db_book, book_id, chapter_number)
357+
358+ def process_verses(self, db_book, book_number, chapter_number):
359+ """
360+ Get the verses for a particular book
361+ """
362+ chapter_file_name = os.path.join(self.base_dir, '{:02d}'.format(book_number), '{}.htm'.format(chapter_number))
363+ with copen(chapter_file_name, encoding='utf-8', errors='ignore') as chapter_file:
364+ page = chapter_file.read()
365+ soup = BeautifulSoup(page, 'lxml')
366+ text_body = soup.find('div', 'textBody')
367+ if text_body:
368+ verses_p = text_body.find('p')
369+ else:
370+ verses_p = soup.find_all('p')[2]
371+ verse_number = 0
372+ verse_text = ''
373+ for item in verses_p.contents:
374+ if self.stop_import_flag:
375+ break
376+ if isinstance(item, Tag) and 'verse' in item.get('class', []):
377+ if verse_number > 0:
378+ self.process_verse(db_book, chapter_number, verse_number, verse_text.strip())
379+ verse_number = int(item.string.strip())
380+ verse_text = ''
381+ elif isinstance(item, NavigableString):
382+ verse_text += str(item)
383+ elif isinstance(item, Tag) and item.name in ['span', 'a']:
384+ verse_text += str(item.string)
385+ else:
386+ log.warning('Can\'t store %s', item)
387+ self.process_verse(db_book, chapter_number, verse_number, verse_text.strip())
388+
389+ def process_verse(self, db_book, chapter_number, verse_number, verse_text):
390+ """
391+ Process a verse element
392+ :param book: A database Book object
393+ :param chapter_number: The chapter number to add the verses to (int)
394+ :param element: The verse element to process. (etree element type)
395+ :param use_milestones: set to True to process a 'milestone' verse. Defaults to False
396+ :return: None
397+ """
398+ if verse_text:
399+ log.debug('%s %s:%s %s', db_book.name, chapter_number, verse_number, verse_text.strip())
400+ self.create_verse(db_book.id, chapter_number, verse_number, verse_text.strip())
401+
402+ def do_import(self, bible_name=None):
403+ """
404+ Loads a Bible from file.
405+ """
406+ self.log_debug('Starting WordProject import from "{name}"'.format(name=self.filename))
407+ self._unzip_file()
408+ self.language_id = self.get_language_id(None, bible_name=self.filename)
409+ result = False
410+ if self.language_id:
411+ self.process_books()
412+ result = True
413+ self._cleanup()
414+ return result
415
416=== modified file 'openlp/plugins/bibles/lib/manager.py'
417--- openlp/plugins/bibles/lib/manager.py 2016-11-01 19:17:57 +0000
418+++ openlp/plugins/bibles/lib/manager.py 2016-11-25 14:28:50 +0000
419@@ -31,6 +31,7 @@
420 from .importers.opensong import OpenSongBible
421 from .importers.osis import OSISBible
422 from .importers.zefania import ZefaniaBible
423+from .importers.wordproject import WordProjectBible
424 try:
425 from .importers.sword import SwordBible
426 except:
427@@ -50,6 +51,7 @@
428 WebDownload = 3
429 Zefania = 4
430 SWORD = 5
431+ WordProject = 6
432
433 @staticmethod
434 def get_class(bible_format):
435@@ -70,6 +72,8 @@
436 return ZefaniaBible
437 elif bible_format == BibleFormat.SWORD:
438 return SwordBible
439+ elif bible_format == BibleFormat.WordProject:
440+ return WordProjectBible
441 else:
442 return None
443
444@@ -84,7 +88,8 @@
445 BibleFormat.OpenSong,
446 BibleFormat.WebDownload,
447 BibleFormat.Zefania,
448- BibleFormat.SWORD
449+ BibleFormat.SWORD,
450+ BibleFormat.WordProject
451 ]
452
453
454
455=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
456--- openlp/plugins/bibles/lib/mediaitem.py 2016-11-15 22:44:11 +0000
457+++ openlp/plugins/bibles/lib/mediaitem.py 2016-11-25 14:28:50 +0000
458@@ -425,6 +425,7 @@
459 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)
460 if verse_count == 0:
461 self.advancedSearchButton.setEnabled(False)
462+ log.warning('Not enough chapters in %s', book_ref_id)
463 critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
464 else:
465 self.advancedSearchButton.setEnabled(True)
466
467=== modified file 'openlp/plugins/media/lib/mediaitem.py'
468--- openlp/plugins/media/lib/mediaitem.py 2016-08-03 21:19:14 +0000
469+++ openlp/plugins/media/lib/mediaitem.py 2016-11-25 14:28:50 +0000
470@@ -150,7 +150,8 @@
471 triggers=self.on_replace_click)
472 if 'webkit' not in get_media_players()[0]:
473 self.replace_action.setDisabled(True)
474- self.replace_action_context.setDisabled(True)
475+ if hasattr(self, 'replace_action_context'):
476+ self.replace_action_context.setDisabled(True)
477 self.reset_action = self.toolbar.add_toolbar_action('reset_action', icon=':/system/system_close.png',
478 visible=False, triggers=self.on_reset_click)
479 self.media_widget = QtWidgets.QWidget(self)
480
481=== added file 'tests/functional/openlp_plugins/bibles/test_wordprojectimport.py'
482--- tests/functional/openlp_plugins/bibles/test_wordprojectimport.py 1970-01-01 00:00:00 +0000
483+++ tests/functional/openlp_plugins/bibles/test_wordprojectimport.py 2016-11-25 14:28:50 +0000
484@@ -0,0 +1,220 @@
485+# -*- coding: utf-8 -*-
486+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
487+
488+###############################################################################
489+# OpenLP - Open Source Lyrics Projection #
490+# --------------------------------------------------------------------------- #
491+# Copyright (c) 2008-2016 OpenLP Developers #
492+# --------------------------------------------------------------------------- #
493+# This program is free software; you can redistribute it and/or modify it #
494+# under the terms of the GNU General Public License as published by the Free #
495+# Software Foundation; version 2 of the License. #
496+# #
497+# This program is distributed in the hope that it will be useful, but WITHOUT #
498+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
499+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
500+# more details. #
501+# #
502+# You should have received a copy of the GNU General Public License along #
503+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
504+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
505+###############################################################################
506+"""
507+This module contains tests for the WordProject Bible importer.
508+"""
509+
510+import os
511+import json
512+from unittest import TestCase
513+
514+from openlp.plugins.bibles.lib.importers.wordproject import WordProjectBible
515+from openlp.plugins.bibles.lib.db import BibleDB
516+
517+from tests.functional import MagicMock, patch, call
518+
519+TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
520+ '..', '..', '..', 'resources', 'bibles'))
521+INDEX_PAGE = open(os.path.join(TEST_PATH, 'wordproject_index.htm')).read()
522+CHAPTER_PAGE = open(os.path.join(TEST_PATH, 'wordproject_chapter.htm')).read()
523+
524+
525+class TestWordProjectImport(TestCase):
526+ """
527+ Test the functions in the :mod:`wordprojectimport` module.
528+ """
529+
530+ def setUp(self):
531+ self.registry_patcher = patch('openlp.plugins.bibles.lib.bibleimport.Registry')
532+ self.addCleanup(self.registry_patcher.stop)
533+ self.registry_patcher.start()
534+ self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
535+ self.addCleanup(self.manager_patcher.stop)
536+ self.manager_patcher.start()
537+
538+ @patch('openlp.plugins.bibles.lib.importers.wordproject.os')
539+ @patch('openlp.plugins.bibles.lib.importers.wordproject.copen')
540+ def test_process_books(self, mocked_open, mocked_os):
541+ """
542+ Test the process_books() method
543+ """
544+ # GIVEN: A WordProject importer and a bunch of mocked things
545+ importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
546+ importer.base_dir = ''
547+ importer.stop_import_flag = False
548+ importer.language_id = 'en'
549+ mocked_open.return_value.__enter__.return_value.read.return_value = INDEX_PAGE
550+ mocked_os.path.join.side_effect = lambda *x: ''.join(x)
551+
552+ # WHEN: process_books() is called
553+ with patch.object(importer, 'find_and_create_book') as mocked_find_and_create_book, \
554+ patch.object(importer, 'process_chapters') as mocked_process_chapters, \
555+ patch.object(importer, 'session') as mocked_session:
556+ importer.process_books()
557+
558+ # THEN: The right methods should have been called
559+ mocked_os.path.join.assert_called_once_with('', 'index.htm')
560+ mocked_open.assert_called_once_with('index.htm', encoding='utf-8', errors='ignore')
561+ assert mocked_find_and_create_book.call_count == 66, 'There should be 66 books'
562+ assert mocked_process_chapters.call_count == 66, 'There should be 66 books'
563+ assert mocked_session.commit.call_count == 66, 'There should be 66 books'
564+
565+ @patch('openlp.plugins.bibles.lib.importers.wordproject.os')
566+ @patch('openlp.plugins.bibles.lib.importers.wordproject.copen')
567+ def test_process_chapters(self, mocked_open, mocked_os):
568+ """
569+ Test the process_chapters() method
570+ """
571+ # GIVEN: A WordProject importer and a bunch of mocked things
572+ importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
573+ importer.base_dir = ''
574+ importer.stop_import_flag = False
575+ importer.language_id = 'en'
576+ mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE
577+ mocked_os.path.join.side_effect = lambda *x: ''.join(x)
578+ mocked_os.path.normpath.side_effect = lambda x: x
579+ mocked_db_book = MagicMock()
580+ mocked_db_book.name = 'Genesis'
581+ book_id = 1
582+ book_link = '01/1.htm'
583+
584+ # WHEN: process_chapters() is called
585+ with patch.object(importer, 'set_current_chapter') as mocked_set_current_chapter, \
586+ patch.object(importer, 'process_verses') as mocked_process_verses:
587+ importer.process_chapters(mocked_db_book, book_id, book_link)
588+
589+ # THEN: The right methods should have been called
590+ expected_set_current_chapter_calls = [call('Genesis', ch) for ch in range(1, 51)]
591+ expected_process_verses_calls = [call(mocked_db_book, 1, ch) for ch in range(1, 51)]
592+ mocked_os.path.join.assert_called_once_with('', '01/1.htm')
593+ mocked_open.assert_called_once_with('01/1.htm', encoding='utf-8', errors='ignore')
594+ assert mocked_set_current_chapter.call_args_list == expected_set_current_chapter_calls
595+ assert mocked_process_verses.call_args_list == expected_process_verses_calls
596+
597+ @patch('openlp.plugins.bibles.lib.importers.wordproject.os')
598+ @patch('openlp.plugins.bibles.lib.importers.wordproject.copen')
599+ def test_process_verses(self, mocked_open, mocked_os):
600+ """
601+ Test the process_verses() method
602+ """
603+ # GIVEN: A WordProject importer and a bunch of mocked things
604+ importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
605+ importer.base_dir = ''
606+ importer.stop_import_flag = False
607+ importer.language_id = 'en'
608+ mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE
609+ mocked_os.path.join.side_effect = lambda *x: '/'.join(x)
610+ mocked_db_book = MagicMock()
611+ mocked_db_book.name = 'Genesis'
612+ book_number = 1
613+ chapter_number = 1
614+
615+ # WHEN: process_verses() is called
616+ with patch.object(importer, 'process_verse') as mocked_process_verse:
617+ importer.process_verses(mocked_db_book, book_number, chapter_number)
618+
619+ # THEN: All the right methods should have been called
620+ mocked_os.path.join.assert_called_once_with('', '01', '1.htm')
621+ mocked_open.assert_called_once_with('/01/1.htm', encoding='utf-8', errors='ignore')
622+ assert mocked_process_verse.call_count == 31
623+
624+ def test_process_verse(self):
625+ """
626+ Test the process_verse() method
627+ """
628+ # GIVEN: An importer and a mocked method
629+ importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
630+ mocked_db_book = MagicMock()
631+ mocked_db_book.id = 1
632+ chapter_number = 1
633+ verse_number = 1
634+ verse_text = ' In the beginning, God created the heavens and the earth '
635+
636+ # WHEN: process_verse() is called
637+ with patch.object(importer, 'create_verse') as mocked_create_verse:
638+ importer.process_verse(mocked_db_book, chapter_number, verse_number, verse_text)
639+
640+ # THEN: The create_verse() method should have been called
641+ mocked_create_verse.assert_called_once_with(1, 1, 1, 'In the beginning, God created the heavens and the earth')
642+
643+ def test_process_verse_no_text(self):
644+ """
645+ Test the process_verse() method when there's no text
646+ """
647+ # GIVEN: An importer and a mocked method
648+ importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
649+ mocked_db_book = MagicMock()
650+ mocked_db_book.id = 1
651+ chapter_number = 1
652+ verse_number = 1
653+ verse_text = ''
654+
655+ # WHEN: process_verse() is called
656+ with patch.object(importer, 'create_verse') as mocked_create_verse:
657+ importer.process_verse(mocked_db_book, chapter_number, verse_number, verse_text)
658+
659+ # THEN: The create_verse() method should NOT have been called
660+ assert mocked_create_verse.call_count == 0
661+
662+ def test_do_import(self):
663+ """
664+ Test the do_import() method
665+ """
666+ # GIVEN: An importer and mocked methods
667+ importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
668+
669+ # WHEN: do_import() is called
670+ with patch.object(importer, '_unzip_file') as mocked_unzip_file, \
671+ patch.object(importer, 'get_language_id') as mocked_get_language_id, \
672+ patch.object(importer, 'process_books') as mocked_process_books, \
673+ patch.object(importer, '_cleanup') as mocked_cleanup:
674+ mocked_get_language_id.return_value = 1
675+ result = importer.do_import()
676+
677+ # THEN: The correct methods should have been called
678+ mocked_unzip_file.assert_called_once_with()
679+ mocked_get_language_id.assert_called_once_with(None, bible_name='kj.zip')
680+ mocked_process_books.assert_called_once_with()
681+ mocked_cleanup.assert_called_once_with()
682+ assert result is True
683+
684+ def test_do_import_no_language(self):
685+ """
686+ Test the do_import() method when the language is not available
687+ """
688+ # GIVEN: An importer and mocked methods
689+ importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
690+
691+ # WHEN: do_import() is called
692+ with patch.object(importer, '_unzip_file') as mocked_unzip_file, \
693+ patch.object(importer, 'get_language_id') as mocked_get_language_id, \
694+ patch.object(importer, 'process_books') as mocked_process_books, \
695+ patch.object(importer, '_cleanup') as mocked_cleanup:
696+ mocked_get_language_id.return_value = None
697+ result = importer.do_import()
698+
699+ # THEN: The correct methods should have been called
700+ mocked_unzip_file.assert_called_once_with()
701+ mocked_get_language_id.assert_called_once_with(None, bible_name='kj.zip')
702+ assert mocked_process_books.call_count == 0
703+ mocked_cleanup.assert_called_once_with()
704+ assert result is False
705
706=== added file 'tests/resources/bibles/wordproject_chapter.htm'
707--- tests/resources/bibles/wordproject_chapter.htm 1970-01-01 00:00:00 +0000
708+++ tests/resources/bibles/wordproject_chapter.htm 2016-11-25 14:28:50 +0000
709@@ -0,0 +1,248 @@
710+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
711+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
712+<head>
713+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
714+ <title>Creation of the world, Genesis Chapter 1</title>
715+ <meta name="description" content="Creation of the world, Genesis Chapter 1" />
716+ <meta name="keywords" content="Holy Bible, Old Testament, scriptures, Creation, faith, heaven, hell, God, Jesus" />
717+ <!-- Mobile viewport optimisation -->
718+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
719+
720+<link rel="stylesheet" type="text/css" href="../_assets/css/css.css" />
721+<link rel="stylesheet" type="text/css" href="../_assets/css/style.css" />
722+<link rel="stylesheet" type="text/css" href="../_assets/css/page-player.css" />
723+
724+ <!--[if lte IE 7]>
725+ <link href="../_assets/css/iehacks.css" rel="stylesheet" type="text/css" />
726+ <![endif]-->
727+ <!-- google analytics -->
728+ <script type="text/javascript">
729+
730+ var _gaq = _gaq || [];
731+ _gaq.push(['_setAccount', 'UA-39700598-1']);
732+ _gaq.push(['_trackPageview']);
733+
734+ (function() {
735+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
736+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
737+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
738+ })();
739+
740+</script>
741+
742+ <!--[if lt IE 9]>
743+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
744+ <![endif]-->
745+ <!-- google analytics -->
746+ <script type="text/javascript">
747+
748+ var _gaq = _gaq || [];
749+ _gaq.push(['_setAccount', 'UA-39700598-1']);
750+ _gaq.push(['_trackPageview']);
751+
752+ (function() {
753+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
754+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
755+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
756+ })();
757+
758+</script>
759+
760+
761+</head>
762+<a name="mytop"></a>
763+<body>
764+<header class="ym-noprint">
765+ <div class="ym-wrapper">
766+ <div class="ym-wbox">
767+ <h1><strong>Word</strong><em>Project</em></h1>
768+ </div>
769+ </div>
770+</header>
771+<!--lang nav-->
772+<!--nav id="nav">
773+ <div class="ym-wrapper">
774+ <div class="ym-hlist">
775+ <ul>
776+ <li><a title="Home" href="../../../index.htm" target="_top">Home</a></li>
777+ <li class="active"><a title="Bibles" href="../../../bibles/index.htm" target="_self">Bibles</a></li>
778+ <li><a title="Audio Bible" href="../../../bibles/audio/01_english/b01.htm" target="_top">Audio</a></li>
779+ <li><a title="Selected Bible Verses" href="../../../bibles/verses/english/index.htm" target="_top">Verses</a></li>
780+ <li><a title="Parallel Bibles" href="../../../bibles/parallel/index.htm" target="_top">Multi</a></li>
781+ <li><a title="Resourcces" href="../../../bibles/resources/index.htm" target="_top">Resources</a></li>
782+ <li><a title="Search" href="../../../bibles/search/index.htm" target="_top">Search</a></li>
783+ <li><a title="Download this Bible [language]" href="../../../download/bibles/index.htm" target="_top">Download</a></li>
784+ </ul>
785+ </div>
786+ </div>
787+</nav-->
788+<div class="ym-wrapper ym-noprint">
789+ <div class="ym-wbox">
790+<!--share buttons-->
791+<div style="margin: 10px 1px 5px 20px;" align="right">
792+<!-- Facebook -->
793+<a title="Click to share on Facebook" href="http://www.facebook.com/sharer.php?u=http://wordproject.org/bibles/kj/01/1.htm" target="_blank"><img src="../_assets/img/facebook_2.png" alt="facebook" /></a>
794+<!-- Twitter -->
795+<a title="Click to share on Twitter" href="http://twitter.com/share?url=http://wordproject.org/bibles/kj/01/1.htm&text=Read this page &hashtags=wordproject" target="_blank"><img src="../_assets/img/twitter_2.png" alt="twitter" /></a>
796+<!-- Google+ -->
797+<a title="Click to share on Google plus" href="https://plus.google.com/share?url=http://wordproject.org/bibles/kj/01/1.htm" target="_blank"><img src="../_assets/img/google+_2.png" alt="google" /></a>
798+<!-- LinkedIn -->
799+<a title="Click to share on Linkedin" href="http://www.linkedin.com/shareArticle?mini=true&url=http://www.wordproject.org" target="_blank"><img src="../_assets/img/linkin_2.png" alt="linkin" /></a></p>
800+</div>
801+<!--/share buttons-->
802+ <div class=" ym-grid">
803+ <div class="ym-g62 ym-gl breadCrumbs"> <!--a title="Home" href="http://www.wordproject.org/index.htm" target="_top">Home</a> / <a title="Bibles" href="../../index.htm" target="_self">Bibles</a--> / <a href="../index.htm" target="_self">KJV</a></div>
804+<div class="ym-g38 ym-gr alignRight ym-noprint"><a class="decreaseFont ym-button">-</a><a class="resetFont ym-button">Reset</a><a class="increaseFont ym-button">+</a>
805+ </div>
806+</div>
807+</div>
808+</div>
809+<div id="main" class="ym-clearfix" role="main">
810+ <div class="ym-wrapper">
811+ <div class="ym-wbox">
812+ <div class="textOptions">
813+<div class="textHeader">
814+ <h2>Genesis</h2>
815+<a name="0"></a>
816+<p class="ym-noprint"> Chapter:
817+
818+<span class="c1">1</span>
819+<a href="2.htm#0">2</a>
820+<a href="3.htm#0">3</a>
821+<a href="4.htm#0">4</a>
822+<a href="5.htm#0">5</a>
823+<a href="6.htm#0">6</a>
824+<a href="7.htm#0">7</a>
825+<a href="8.htm#0">8</a>
826+<a href="9.htm#0">9</a>
827+<a href="10.htm#0">10</a>
828+<a href="11.htm#0">11</a>
829+<a href="12.htm#0">12</a>
830+<a href="13.htm#0">13</a>
831+<a href="14.htm#0">14</a>
832+<a href="15.htm#0">15</a>
833+<a href="16.htm#0">16</a>
834+<a href="17.htm#0">17</a>
835+<a href="18.htm#0">18</a>
836+<a href="19.htm#0">19</a>
837+<a href="20.htm#0">20</a>
838+<a href="21.htm#0">21</a>
839+<a href="22.htm#0">22</a>
840+<a href="23.htm#0">23</a>
841+<a href="24.htm#0">24</a>
842+<a href="25.htm#0">25</a>
843+<a href="26.htm#0">26</a>
844+<a href="27.htm#0">27</a>
845+<a href="28.htm#0">28</a>
846+<a href="29.htm#0">29</a>
847+<a href="30.htm#0">30</a>
848+<a href="31.htm#0">31</a>
849+<a href="32.htm#0">32</a>
850+<a href="33.htm#0">33</a>
851+<a href="34.htm#0">34</a>
852+<a href="35.htm#0">35</a>
853+<a href="36.htm#0">36</a>
854+<a href="37.htm#0">37</a>
855+<a href="38.htm#0">38</a>
856+<a href="39.htm#0">39</a>
857+<a href="40.htm#0">40</a>
858+<a href="41.htm#0">41</a>
859+<a href="42.htm#0">42</a>
860+<a href="43.htm#0">43</a>
861+<a href="44.htm#0">44</a>
862+<a href="45.htm#0">45</a>
863+<a href="46.htm#0">46</a>
864+<a href="47.htm#0">47</a>
865+<a href="48.htm#0">48</a>
866+<a href="49.htm#0">49</a>
867+<a href="50.htm#0">50</a>
868+<!--end of chapters-->
869+</p>
870+</div>
871+<div class="textAudio ym-noprint"><ul class="playlist">
872+ <li class="noMargin">
873+<!--start audio link--><a href="http://audio2.wordproject.com/bibles/app/audio/1/1/1.mp3">Genesis - Chapter 1 </a></li><!--/audioRef-->
874+ </ul>
875+ </div>
876+
877+ <!--end audio-->
878+ <hr />
879+<div class="textBody" id="textBody">
880+ <h3>Chapter 1</h3>
881+
882+<!--... the Word of God:--></a>
883+ <p><span class="verse" id="1">1</span> In the beginning God created the heaven and the earth.
884+<br /><span class="verse" id="2">2</span> And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.
885+<br /><span class="verse" id="3">3</span> And God said, Let there be light: and there was light.
886+<br /><span class="verse" id="4">4</span> And God saw the light, that it was good: and God divided the light from the darkness.
887+<br /><span class="verse" id="5">5</span> And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.
888+<br /><span class="verse" id="6">6</span> And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.
889+<br /><span class="verse" id="7">7</span> And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.
890+<br /><span class="verse" id="8">8</span> And God called the firmament Heaven. And the evening and the morning were the second day.
891+<br /><span class="verse" id="9">9</span> And God said, Let the waters under the heaven be gathered together unto one place, and let the dry land appear: and it was so.
892+<br /><span class="verse" id="10">10</span> And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good.
893+<br /><span class="verse" id="11">11</span> And God said, Let the earth bring forth grass, the herb yielding seed, and the fruit tree yielding fruit after his kind, whose seed is in itself, upon the earth: and it was so.
894+<br /><span class="verse" id="12">12</span> And the earth brought forth grass, and herb yielding seed after his kind, and the tree yielding fruit, whose seed was in itself, after his kind: and God saw that it was good.
895+<br /><span class="verse" id="13">13</span> And the evening and the morning were the third day.
896+<br /><span class="verse" id="14">14</span> And God said, Let there be lights in the firmament of the heaven to divide the day from the night; and let them be for signs, and for seasons, and for days, and years:
897+<br /><span class="verse" id="15">15</span> And let them be for lights in the firmament of the heaven to give light upon the earth: and it was so.
898+<br /><span class="verse" id="16">16</span> And God made two great lights; the greater light to rule the day, and the lesser light to rule the night: he made the stars also.
899+<br /><span class="verse" id="17">17</span> And God set them in the firmament of the heaven to give light upon the earth,
900+<br /><span class="verse" id="18">18</span> And to rule over the day and over the night, and to divide the light from the darkness: and God saw that it was good.
901+<br /><span class="verse" id="19">19</span> And the evening and the morning were the fourth day.
902+<br /><span class="verse" id="20">20</span> And God said, Let the waters bring forth abundantly the moving creature that hath life, and fowl that may fly above the earth in the open firmament of heaven.
903+<br /><span class="verse" id="21">21</span> And God created great whales, and every living creature that moveth, which the waters brought forth abundantly, after their kind, and every winged fowl after his kind: and God saw that it was good.
904+<br /><span class="verse" id="22">22</span> And God blessed them, saying, Be fruitful, and multiply, and fill the waters in the seas, and let fowl multiply in the earth.
905+<br /><span class="verse" id="23">23</span> And the evening and the morning were the fifth day.
906+<br /><span class="verse" id="24">24</span> And God said, Let the earth bring forth the living creature after his kind, cattle, and creeping thing, and beast of the earth after his kind: and it was so.
907+<br /><span class="verse" id="25">25</span> And God made the beast of the earth after his kind, and cattle after their kind, and every thing that creepeth upon the earth after his kind: and God saw that it was good.
908+<br /><span class="verse" id="26">26</span> And God said, Let us make man in our image, after our likeness: and let them have dominion over the fish of the sea, and over the fowl of the air, and over the cattle, and over all the earth, and over every creeping thing that creepeth upon the earth.
909+<br /><span class="verse" id="27">27</span> So God created man in his own image, in the image of God created he him; male and female created he them.
910+<br /><span class="verse" id="28">28</span> And God blessed them, and God said unto them, Be fruitful, and multiply, and replenish the earth, and subdue it: and have dominion over the fish of the sea, and over the fowl of the air, and over every living thing that moveth upon the earth.
911+<br /><span class="verse" id="29">29</span> And God said, Behold, I have given you every herb bearing seed, which is upon the face of all the earth, and every tree, in the which is the fruit of a tree yielding seed; to you it shall be for meat.
912+<br /><span class="verse" id="30">30</span> And to every beast of the earth, and to every fowl of the air, and to every thing that creepeth upon the earth, wherein there is life, I have given every green herb for meat: and it was so.
913+<br /><span class="verse" id="31">31</span> And God saw every thing that he had made, and, behold, it was very good. And the evening and the morning were the sixth day.
914+</p>
915+<!--... sharper than any twoedged sword... -->
916+</div>
917+</div><!-- ym-wbox end -->
918+</div><!-- ym-wrapper end -->
919+</div><!-- ym-wrapper end -->
920+</div><!-- ym-wrapper end -->
921+<!--..sharper than any twoedged sword...-->
922+<div class="ym-wrapper">
923+ <div class="ym-wbox">
924+<div class="alignRight ym-noprint">
925+ <p><a title="Print this page" href="javascript:window.print()" class="ym-button">&nbsp;<img src="../_assets/img/printer.gif" alt="printer" width="25" height="25" align="absbottom" />&nbsp;</a>
926+ <a class="ym-button" title="Page TOP" href="#mytop">&nbsp;<img src="../_assets/img/arrow_up.png" alt="arrowup" width="25" height="25" align="absbottom" />&nbsp;</a>
927+ <!--next chapter start-->
928+ <a class="ym-button" title="Next chapter" href="2.htm#0">&nbsp;<img src="../_assets/img/arrow_right.png" alt="arrowright" align="absbottom" />&nbsp;</a></p>
929+ <!--next chapter end-->
930+ </div>
931+</div>
932+</div>
933+<footer>
934+ <div class="ym-wrapper">
935+ <div class="ym-wbox">
936+ <p class="alignCenter">Wordproject® is a registered name of the <a href="http://www.wordproject.org">International Biblical Association</a>, a non-profit organization registered in Macau, China. </p>
937+ <p class="alignCenter"><a href="http://www.wordproject.org/contact/new/index.htm" target="_top">Contact</a> | <a href="http://www.wordproject.org/contact/new/disclaim.htm" target="_top"> Disclaimer</a> |
938+ <a href="http://www.wordproject.org/contact/new/state.htm" target="_top">Statement of Faith</a> |
939+ <a href="http://www.wordproject.org/contact/new/mstate.htm" target="_top">Mission</a> |
940+ <a href="http://www.wordproject.org/contact/new/copyrights.htm" target="_top">Copyrights</a></p>
941+ </div>
942+ </div>
943+</footer>
944+</body>
945+</script><script type="text/javascript" src="../_assets/js/jquery-1.8.0.min.js"></script>
946+<script type="text/javascript" src="../_assets/js/soundmanager2.js"></script>
947+<script type="text/javascript" src="../_assets/js/page-player.js"></script>
948+<script type="text/javascript" src="../_assets/js/script.js"></script>
949+<script type="text/javascript">
950+
951+soundManager.setup({
952+ url: '../_assets/swf/'
953+});
954+
955+</script>
956+</html>
957+
958
959=== added file 'tests/resources/bibles/wordproject_index.htm'
960--- tests/resources/bibles/wordproject_index.htm 1970-01-01 00:00:00 +0000
961+++ tests/resources/bibles/wordproject_index.htm 2016-11-25 14:28:50 +0000
962@@ -0,0 +1,222 @@
963+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
964+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
965+<head>
966+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
967+ <title>The Holy Bible in the English language with audio narration - KJV</title>
968+ <meta name="title" content="The Holy Bible in the English language with audio narration - KJV" />
969+<meta name="description" content="Bible books: choose the book you wish to read or listen to" />
970+<meta name="keywords" content="Bible, Holy, New testament, Old testament, Scriptures, God, Jesus" />
971+<link rel="shortcut icon" href="http://www.wordproject.org/favicon.ico" />
972+<meta name="robots" content="index, follow" />
973+ <!-- Mobile viewport optimisation -->
974+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
975+
976+<link rel="stylesheet" type="text/css" href="_assets/css/css.css" />
977+<link rel="stylesheet" type="text/css" href="_assets/css/style.css" />
978+<link rel="stylesheet" type="text/css" href="_assets/css/page-player.css" />
979+<link rel="stylesheet" type="text/css" href="_assets/css/tables_bibles.css" />
980+
981+ <!--[if lte IE 7]>
982+ <link href="_assets/css/iehacks.css" rel="stylesheet" type="text/css" />
983+ <![endif]-->
984+
985+ <!--[if lt IE 9]>
986+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
987+ <![endif]-->
988+ <!-- google analytics -->
989+ <script type="text/javascript">
990+
991+ var _gaq = _gaq || [];
992+ _gaq.push(['_setAccount', 'UA-39700598-1']);
993+ _gaq.push(['_trackPageview']);
994+
995+ (function() {
996+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
997+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
998+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
999+ })();
1000+
1001+</script>
1002+
1003+
1004+ <style type="text/css">
1005+<!--
1006+.style1 {font-size: medium}
1007+-->
1008+ </style>
1009+</head>
1010+<a name="mytop"></a>
1011+<body>
1012+<header class="ym-noprint">
1013+ <div class="ym-wrapper">
1014+ <div class="ym-wbox">
1015+ <h1><strong>Word</strong><em>Project</em></h1>
1016+ </div>
1017+ </div>
1018+</header>
1019+<!--index nav-->
1020+<!--nav id="nav">
1021+ <div class="ym-wrapper">
1022+ <div class="ym-hlist">
1023+ <ul>
1024+ <li><a title="Home" href="../../index.htm" target="_top">Home</a></li>
1025+ <li class="active"><a title="Bibles" href="../../bibles/index.htm" target="_self">Bibles</a></li>
1026+ <li><a title="Audio Bible" href="../../bibles/audio/index.htm" target="_top">Audio</a></li>
1027+ <li><a title="Selected Bible Verses" href="../../bibles/verses/index.htm" target="_top">Verses</a></li>
1028+ <li><a title="Parallel Bibles" href="../../bibles/parallel/index.htm" target="_top">Multi</a></li>
1029+ <li><a title="Resourcces" href="../../bibles/resources/index.htm" target="_top">Resources</a></li>
1030+ <li><a title="Search" href="../../bibles/search/index.htm" target="_top">Search</a></li>
1031+ <li><a title="Download this Bible [language]" href="../../download/bibles/index.htm" target="_top">Download</a></li>
1032+ </ul>
1033+ </div>
1034+ </div>
1035+</nav-->
1036+<div class="ym-wrapper ym-noprint">
1037+ <div class="ym-wbox">
1038+<!--share buttons-->
1039+<div style="margin: 10px 1px 5px 20px;" align="right">
1040+<!-- Facebook -->
1041+<a title="Click to share on Facebook" href="http://www.facebook.com/sharer.php?u=http://wordproject.org/bibles/kj/index.htm" target="_blank"><img src="_assets/img/facebook_2.png" alt="facebook" /></a>
1042+<!-- Twitter -->
1043+<a title="Click to share on Twitter" href="http://twitter.com/share?url=http://wordproject.org/bibles/kj/index.htm&text=Read this page &hashtags=wordproject" target="_blank"><img src="_assets/img/twitter_2.png" alt="twitter" /></a>
1044+<!-- Google+ -->
1045+<a title="Click to share on Google plus" href="https://plus.google.com/share?url=http://wordproject.org/bibles/kj/index.htm" target="_blank"><img src="_assets/img/google+_2.png" alt="google" /></a>
1046+<!-- LinkedIn -->
1047+<a title="Click to share on Linkedin" href="http://www.linkedin.com/shareArticle?mini=true&url=http://www.wordproject.org" target="_blank"><img src="_assets/img/linkin_2.png" alt="linkin" /></a></p>
1048+</div>
1049+<!--/share buttons-->
1050+ <div class=" ym-grid">
1051+ <div class="ym-g62 ym-gl breadCrumbs"> <a title="Home" href="http://www.wordproject.org/index.htm" target="_top">Home</a> / <!--a title="Bibles" href="../index.htm" target="_self">Bibles</a--> / </div>
1052+ <div class="ym-g38 ym-gr alignRight ym-noprint"><a class="decreaseFont ym-button">-</a><a class="resetFont ym-button">Reset</a><a class="increaseFont ym-button">+</a>
1053+ </div>
1054+</div>
1055+</div>
1056+</div>
1057+<div id="main" class="ym-clearfix" role="main">
1058+ <div class="ym-wrapper">
1059+ <div class="ym-wbox">
1060+ <div class="textOptions">
1061+<div class="textHeader">
1062+ <h2>English Bible </h2><span class="faded">King James Version</span>
1063+<a name="0"></a>
1064+ <p class="faded">Please, choose a book of the Holy Bible in the English language:</p>
1065+<hr />
1066+</div>
1067+<div class="ym-grid linearize-level-2">
1068+ <!-- chapter list -->
1069+ <div class="ym-g50 ym-gl">
1070+ <h3>Old Testament</h3>
1071+ <ul id="maintab" class="shadetabs">
1072+ <li><a href="01/1.htm">[1] Genesis <i class="icon-headphones pull-right"></i></a></li>
1073+ <li><a href="02/1.htm">[2] Exodus <i class="icon-headphones pull-right"></i></a></li>
1074+ <li><a href="03/1.htm">[3] Leviticus <i class="icon-headphones pull-right"></i></a></li>
1075+ <li><a href="04/1.htm">[4] Numbers <i class="icon-headphones pull-right"></i></a></li>
1076+ <li><a href="05/1.htm">[5] Deuteronomy <i class="icon-headphones pull-right"></i></a></li>
1077+ <li><a href="06/1.htm">[6] Joshua <i class="icon-headphones pull-right"></i></a></li>
1078+ <li><a href="07/1.htm">[7] Judges <i class="icon-headphones pull-right"></i></a></li>
1079+ <li><a href="08/1.htm">[8] Ruth <i class="icon-headphones pull-right"></i></a></li>
1080+ <li><a href="09/1.htm">[9] 1 Samuel <i class="icon-headphones pull-right"></i></a></li>
1081+ <li><a href="10/1.htm">[10] 2 Samuel <i class="icon-headphones pull-right"></i></a></li>
1082+ <li><a href="11/1.htm">[11] 1 Kings <i class="icon-headphones pull-right"></i></a></li>
1083+ <li><a href="12/1.htm">[12] 2 Kings <i class="icon-headphones pull-right"></i></a></li>
1084+ <li><a href="13/1.htm">[13] 1 Chronicles <i class="icon-headphones pull-right"></i> </a></li>
1085+ <li><a href="14/1.htm">[14] 2 Chronicles <i class="icon-headphones pull-right"></i></a></li>
1086+ <li><a href="15/1.htm">[15] Ezra <i class="icon-headphones pull-right"></i></a></li>
1087+ <li><a href="16/1.htm">[16] Nehemiah <i class="icon-headphones pull-right"></i></a></li>
1088+ <li><a href="17/1.htm">[17] Esther <i class="icon-headphones pull-right"></i></a></li>
1089+ <li><a href="18/1.htm">[18] Job <i class="icon-headphones pull-right"></i></a></li>
1090+ <li><a href="19/1.htm">[19] Psalms <i class="icon-headphones pull-right"></i></a></li>
1091+ <li><a href="20/1.htm">[20] Proverbs <i class="icon-headphones pull-right"></i></a></li>
1092+ <li><a href="21/1.htm">[21] Ecclesiastes <i class="icon-headphones pull-right"></i></a></li>
1093+ <li><a href="22/1.htm">[22] Song of Songs <i class="icon-headphones pull-right"></i></a></li>
1094+ <li><a href="23/1.htm">[23] Isaiah <i class="icon-headphones pull-right"></i></a></li>
1095+ <li><a href="24/1.htm">[24] Jeremiah <i class="icon-headphones pull-right"></i></a></li>
1096+ <li><a href="25/1.htm">[25] Lamentations <i class="icon-headphones pull-right"></i></a></li>
1097+ <li><a href="26/1.htm">[26] Ezekiel <i class="icon-headphones pull-right"></i></a></li>
1098+ <li><a href="27/1.htm">[27] Daniel <i class="icon-headphones pull-right"></i></a></li>
1099+ <li><a href="28/1.htm">[28] Hosea <i class="icon-headphones pull-right"></i></a></li>
1100+ <li><a href="29/1.htm">[29] Joel <i class="icon-headphones pull-right"></i></a></li>
1101+ <li><a href="30/1.htm">[30] Amos <i class="icon-headphones pull-right"></i></a></li>
1102+ <li><a href="31/1.htm">[31] Obadiah <i class="icon-headphones pull-right"></i></a></li>
1103+ <li><a href="32/1.htm">[32] Jonah <i class="icon-headphones pull-right"></i></a></li>
1104+ <li><a href="33/1.htm">[33] Micah <i class="icon-headphones pull-right"></i></a></li>
1105+ <li><a href="34/1.htm">[34] Nahum <i class="icon-headphones pull-right"></i></a></li>
1106+ <li><a href="35/1.htm">[35] Habakkuk <i class="icon-headphones pull-right"></i></a></li>
1107+ <li><a href="36/1.htm">[36] Zephaniah <i class="icon-headphones pull-right"></i></a></li>
1108+ <li><a href="37/1.htm">[37] Haggai <i class="icon-headphones pull-right"></i></a></li>
1109+ <li><a href="38/1.htm">[38] Zechariah <i class="icon-headphones pull-right"></i></a></li>
1110+ <li><a href="39/1.htm">[39] Malachi <i class="icon-headphones pull-right"></i></a></li>
1111+ </ul>
1112+ </div> <div class="ym-g50 ym-gr">
1113+ <h3>New Testament</h3>
1114+ <ul id="maintab" class="shadetabs">
1115+ <li><a href="40/1.htm">[40] Matthew <i class="icon-headphones pull-right"></i></a></li>
1116+ <li><a href="41/1.htm">[41] Mark <i class="icon-headphones pull-right"></i></a></li>
1117+ <li><a href="42/1.htm">[42] Luke <i class="icon-headphones pull-right"></i></a></li>
1118+ <li><a href="43/1.htm">[43] John <i class="icon-headphones pull-right"></i></a></li>
1119+ <li><a href="44/1.htm">[44] Acts <i class="icon-headphones pull-right"></i></a></li>
1120+ <li><a href="45/1.htm">[45] Romans <i class="icon-headphones pull-right"></i></a></li>
1121+ <li><a href="46/1.htm">[46] 1 Corinthians <i class="icon-headphones pull-right"></i></a></li>
1122+ <li><a href="47/1.htm">[47] 2 Corinthians <i class="icon-headphones pull-right"></i></a></li>
1123+ <li><a href="48/1.htm">[48] Galatians <i class="icon-headphones pull-right"></i></a></li>
1124+ <li><a href="49/1.htm">[49] Ephesians <i class="icon-headphones pull-right"></i></a></li>
1125+ <li><a href="50/1.htm">[50] Philippians <i class="icon-headphones pull-right"></i></a></li>
1126+ <li><a href="51/1.htm">[51] Colossians <i class="icon-headphones pull-right"></i></a></li>
1127+ <li><a href="52/1.htm">[52] 1 Thessalonians <i class="icon-headphones pull-right"></i></a></li>
1128+ <li><a href="53/1.htm">[53] 2 Thessalonians <i class="icon-headphones pull-right"></i></a></li>
1129+ <li><a href="54/1.htm">[54] 1 Timothy <i class="icon-headphones pull-right"></i></a></li>
1130+ <li> <a href="55/1.htm">[55] 2 Timothy <i class="icon-headphones pull-right"></i></a></li>
1131+ <li><a href="56/1.htm">[56] Titus <i class="icon-headphones pull-right"></i></a></li>
1132+ <li><a href="57/1.htm">[57] Philemon <i class="icon-headphones pull-right"></i></a></li>
1133+ <li><a href="58/1.htm">[58] Hebrews <i class="icon-headphones pull-right"></i></a></li>
1134+ <li><a href="59/1.htm">[59] James <i class="icon-headphones pull-right"></i></a></li>
1135+ <li> <a href="60/1.htm">[60] 1 Peter <i class="icon-headphones pull-right"></i></a></li>
1136+ <li><a href="61/1.htm">[61] 2 Peter <i class="icon-headphones pull-right"></i></a></li>
1137+ <li> <a href="62/1.htm">[62] 1 John <i class="icon-headphones pull-right"></i></a></li>
1138+ <li><a href="63/1.htm">[63] 2 John <i class="icon-headphones pull-right"></i></a></li>
1139+ <li><a href="64/1.htm">[64] 3 John <i class="icon-headphones pull-right"></i></a></li>
1140+ <li><a href="65/1.htm">[65] Jude <i class="icon-headphones pull-right"></i></a></li>
1141+ <li><a href="66/1.htm">[66] Revelation <i class="icon-headphones pull-right"></i></a></li>
1142+ </ul>
1143+ </div>
1144+ <!-- end chapter list -->
1145+</div>
1146+
1147+</div>
1148+</div><!-- ym-wbox end -->
1149+</div><!-- ym-wrapper end -->
1150+</div><!-- ym-wrapper end -->
1151+<div class="ym-wrapper">
1152+ <div class="ym-wbox">
1153+<div class="alignRight ym-noprint">
1154+ <p><a class="ym-button" href="#mytop"><i class="icon-circle-arrow-up icon-white"></i> Top</a>
1155+
1156+</p>
1157+
1158+</div>
1159+</div>
1160+</div>
1161+<footer>
1162+ <div class="ym-wrapper">
1163+ <div class="ym-wbox">
1164+ <p class="alignCenter">Wordproject® is a registered name of the <a href="http://www.wordproject.org">International Biblical Association</a>, a non-profit organization registered in Macau, China. </p>
1165+ <p class="alignCenter"><a href="http://www.wordproject.org/contact/new/index.htm" target="_top">Contact</a> | <a href="http://www.wordproject.org/contact/new/disclaim.htm" target="_top"> Disclaimer</a> |
1166+ <a href="http://www.wordproject.org/contact/new/state.htm" target="_top">Statement of Faith</a> |
1167+ <a href="http://www.wordproject.org/contact/new/mstate.htm" target="_top">Mission</a> |
1168+ <a href="http://www.wordproject.org/contact/new/copyrights.htm" target="_top">Copyrights</a></p>
1169+ </div>
1170+ </div>
1171+</footer>
1172+</body>
1173+</script><script type="text/javascript" src="_assets/js/jquery-1.8.0.min.js"></script>
1174+<script type="text/javascript" src="_assets/js/soundmanager2.js"></script>
1175+<script type="text/javascript" src="_assets/js/page-player.js"></script>
1176+<script type="text/javascript" src="_assets/js/script.js"></script>
1177+<script type="text/javascript">
1178+
1179+soundManager.setup({
1180+ url: '_assets/swf/'
1181+});
1182+
1183+</script>
1184+</html>