Merge lp:~tomasgroth/openlp/osis-import-fix into lp:openlp

Proposed by Tomas Groth
Status: Merged
Approved by: Tim Bentley
Approved revision: 2383
Merged at revision: 2418
Proposed branch: lp:~tomasgroth/openlp/osis-import-fix
Merge into: lp:openlp
Diff against target: 977 lines (+586/-221)
12 files modified
openlp/plugins/bibles/lib/opensong.py (+3/-1)
openlp/plugins/bibles/lib/osis.py (+135/-129)
openlp/plugins/bibles/lib/zefania.py (+9/-3)
tests/functional/openlp_plugins/bibles/test_opensongimport.py (+24/-44)
tests/functional/openlp_plugins/bibles/test_osisimport.py (+161/-0)
tests/functional/openlp_plugins/bibles/test_zefaniaimport.py (+24/-44)
tests/resources/bibles/dk1933.json (+16/-0)
tests/resources/bibles/kjv.json (+16/-0)
tests/resources/bibles/osis-dk1933.xml (+32/-0)
tests/resources/bibles/osis-kjv.xml (+41/-0)
tests/resources/bibles/osis-web.xml (+109/-0)
tests/resources/bibles/web.json (+16/-0)
To merge this branch: bzr merge lp:~tomasgroth/openlp/osis-import-fix
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Raoul Snyman Approve
Review via email: mp+232404@code.launchpad.net

This proposal supersedes a proposal from 2014-08-24.

Description of the change

Rewrote OSIS import to use XML parsing instead of using regex. Tests included.

To post a comment you must log in.
Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal
Revision history for this message
Tomas Groth (tomasgroth) wrote :
Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Have you tried a few different files from a few different languages?

Revision history for this message
Tomas Groth (tomasgroth) wrote :

> Have you tried a few different files from a few different languages?
So far only those in the tests (2 english and 1 danish). I could probably find more if needed...?

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Please? As much as I know that there have been a number of improvements to the mod2osis tool, I'd prefer to err on the safe side.

Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve
Revision history for this message
Tim Bentley (trb143) :
review: Approve
Revision history for this message
Phill (phill-ridout) wrote :

Did this fix the two related bugs? If so they need setting to fix comited!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/plugins/bibles/lib/opensong.py'
2--- openlp/plugins/bibles/lib/opensong.py 2014-06-30 20:59:22 +0000
3+++ openlp/plugins/bibles/lib/opensong.py 2014-08-27 13:19:28 +0000
4@@ -30,7 +30,7 @@
5 import logging
6 from lxml import etree, objectify
7
8-from openlp.core.common import translate
9+from openlp.core.common import translate, trace_error_handler
10 from openlp.core.lib.ui import critical_error_message_box
11 from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
12
13@@ -88,6 +88,7 @@
14 'Incorrect Bible file type supplied. This looks like a Zefania XML bible, '
15 'please use the Zefania import option.'))
16 return False
17+ # No language info in the opensong format, so ask the user
18 language_id = self.get_language(bible_name)
19 if not language_id:
20 log.error('Importing books from "%s" failed' % self.filename)
21@@ -134,6 +135,7 @@
22 self.session.commit()
23 self.application.process_events()
24 except etree.XMLSyntaxError as inst:
25+ trace_error_handler(log)
26 critical_error_message_box(
27 message=translate('BiblesPlugin.OpenSongImport',
28 'Incorrect Bible file type supplied. OpenSong Bibles may be '
29
30=== modified file 'openlp/plugins/bibles/lib/osis.py'
31--- openlp/plugins/bibles/lib/osis.py 2014-03-09 10:26:28 +0000
32+++ openlp/plugins/bibles/lib/osis.py 2014-08-27 13:19:28 +0000
33@@ -27,14 +27,12 @@
34 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
35 ###############################################################################
36
37-import os
38 import logging
39-import chardet
40-import codecs
41-import re
42+from lxml import etree
43
44-from openlp.core.common import AppLocation, translate
45+from openlp.core.common import translate, trace_error_handler
46 from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
47+from openlp.core.lib.ui import critical_error_message_box
48
49 log = logging.getLogger(__name__)
50
51@@ -53,143 +51,151 @@
52 log.debug(self.__class__.__name__)
53 BibleDB.__init__(self, parent, **kwargs)
54 self.filename = kwargs['filename']
55- self.language_regex = re.compile(r'<language.*>(.*?)</language>')
56- self.verse_regex = re.compile(r'<verse osisID="([a-zA-Z0-9 ]*).([0-9]*).([0-9]*)">(.*?)</verse>')
57- self.note_regex = re.compile(r'<note(.*?)>(.*?)</note>')
58- self.title_regex = re.compile(r'<title(.*?)>(.*?)</title>')
59- self.milestone_regex = re.compile(r'<milestone(.*?)/>')
60- self.fi_regex = re.compile(r'<FI>(.*?)<Fi>')
61- self.rf_regex = re.compile(r'<RF>(.*?)<Rf>')
62- self.lb_regex = re.compile(r'<lb(.*?)>')
63- self.lg_regex = re.compile(r'<lg(.*?)>')
64- self.l_regex = re.compile(r'<l (.*?)>')
65- self.w_regex = re.compile(r'<w (.*?)>')
66- self.q_regex = re.compile(r'<q(.*?)>')
67- self.q1_regex = re.compile(r'<q(.*?)level="1"(.*?)>')
68- self.q2_regex = re.compile(r'<q(.*?)level="2"(.*?)>')
69- self.trans_regex = re.compile(r'<transChange(.*?)>(.*?)</transChange>')
70- self.divine_name_regex = re.compile(r'<divineName(.*?)>(.*?)</divineName>')
71- self.spaces_regex = re.compile(r'([ ]{2,})')
72- filepath = os.path.join(
73- AppLocation.get_directory(AppLocation.PluginsDir), 'bibles', 'resources', 'osisbooks.csv')
74
75 def do_import(self, bible_name=None):
76 """
77 Loads a Bible from file.
78 """
79 log.debug('Starting OSIS import from "%s"' % self.filename)
80- detect_file = None
81- db_book = None
82- osis = None
83+ if not isinstance(self.filename, str):
84+ self.filename = str(self.filename, 'utf8')
85+ import_file = None
86 success = True
87- last_chapter = 0
88- match_count = 0
89- self.wizard.increment_progress_bar(
90- translate('BiblesPlugin.OsisImport', 'Detecting encoding (this may take a few minutes)...'))
91- try:
92- detect_file = open(self.filename, 'r')
93- details = chardet.detect(detect_file.read(1048576))
94- detect_file.seek(0)
95- lines_in_file = int(len(detect_file.readlines()))
96- except IOError:
97- log.exception('Failed to detect OSIS file encoding')
98- return
99- finally:
100- if detect_file:
101- detect_file.close()
102- try:
103- osis = codecs.open(self.filename, 'r', details['encoding'])
104- repl = replacement
105- language_id = False
106- # Decide if the bible probably contains only NT or AT and NT or
107- # AT, NT and Apocrypha
108- if lines_in_file < 11500:
109- book_count = 27
110- chapter_count = 260
111- elif lines_in_file < 34200:
112- book_count = 66
113- chapter_count = 1188
114- else:
115- book_count = 67
116- chapter_count = 1336
117- for file_record in osis:
118+ try:
119+ # NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
120+ # detection, and the two mechanisms together interfere with each other.
121+ import_file = open(self.filename, 'rb')
122+ osis_bible_tree = etree.parse(import_file)
123+ namespace = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
124+ # Find bible language
125+ language_id = None
126+ language = osis_bible_tree.xpath("//ns:osisText/@xml:lang", namespaces=namespace)
127+ if language:
128+ language_id = BiblesResourcesDB.get_language(language[0])
129+ # The language couldn't be detected, ask the user
130+ if not language_id:
131+ language_id = self.get_language(bible_name)
132+ if not language_id:
133+ log.error('Importing books from "%s" failed' % self.filename)
134+ return False
135+ num_books = int(osis_bible_tree.xpath("count(//ns:div[@type='book'])", namespaces=namespace))
136+ self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport',
137+ 'Removing unused tags (this may take a few minutes)...'))
138+ # We strip unused tags from the XML, this should leave us with only chapter, verse and div tags.
139+ # Strip tags we don't use - remove content
140+ etree.strip_elements(osis_bible_tree, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}note',
141+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}milestone',
142+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}title',
143+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}abbr',
144+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}catchWord',
145+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}index',
146+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}rdg',
147+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}rdgGroup',
148+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}figure'),
149+ with_tail=False)
150+ # Strip tags we don't use - keep content
151+ etree.strip_tags(osis_bible_tree, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}p',
152+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}l',
153+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}lg',
154+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}q',
155+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}a',
156+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}w',
157+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}divineName',
158+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}foreign',
159+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}hi',
160+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}inscription',
161+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}mentioned',
162+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}name',
163+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}reference',
164+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}seg',
165+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}transChange',
166+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}salute',
167+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}signed',
168+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}closer',
169+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}speech',
170+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}speaker',
171+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}list',
172+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}item',
173+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}table',
174+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}head',
175+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}row',
176+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}cell',
177+ '{http://www.bibletechnologies.net/2003/OSIS/namespace}caption'))
178+ # Precompile a few xpath-querys
179+ verse_in_chapter = etree.XPath('count(//ns:chapter[1]/ns:verse)', namespaces=namespace)
180+ text_in_verse = etree.XPath('count(//ns:verse[1]/text())', namespaces=namespace)
181+ # Find books in the bible
182+ bible_books = osis_bible_tree.xpath("//ns:div[@type='book']", namespaces=namespace)
183+ for book in bible_books:
184 if self.stop_import_flag:
185 break
186- # Try to find the bible language
187- if not language_id:
188- language_match = self.language_regex.search(file_record)
189- if language_match:
190- language = BiblesResourcesDB.get_language(
191- language_match.group(1))
192- if language:
193- language_id = language['id']
194- self.save_meta('language_id', language_id)
195- continue
196- match = self.verse_regex.search(file_record)
197- if match:
198- # Set meta language_id if not detected till now
199- if not language_id:
200- language_id = self.get_language(bible_name)
201- if not language_id:
202- log.error('Importing books from "%s" failed' % self.filename)
203- return False
204- match_count += 1
205- book = str(match.group(1))
206- chapter = int(match.group(2))
207- verse = int(match.group(3))
208- verse_text = match.group(4)
209- book_ref_id = self.get_book_ref_id_by_name(book, book_count, language_id)
210- if not book_ref_id:
211- log.error('Importing books from "%s" failed' % self.filename)
212- return False
213- book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
214- if not db_book or db_book.name != book_details['name']:
215- log.debug('New book: "%s"' % book_details['name'])
216- db_book = self.create_book(
217- book_details['name'],
218- book_ref_id,
219- book_details['testament_id'])
220- if last_chapter == 0:
221- self.wizard.progress_bar.setMaximum(chapter_count)
222- if last_chapter != chapter:
223- if last_chapter != 0:
224- self.session.commit()
225+ # Remove div-tags in the book
226+ etree.strip_tags(book, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}div'))
227+ book_ref_id = self.get_book_ref_id_by_name(book.get('osisID'), num_books)
228+ if not book_ref_id:
229+ book_ref_id = self.get_book_ref_id_by_localised_name(book.get('osisID'))
230+ if not book_ref_id:
231+ log.error('Importing books from "%s" failed' % self.filename)
232+ return False
233+ book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
234+ db_book = self.create_book(book_details['name'], book_ref_id, book_details['testament_id'])
235+ # Find out if chapter-tags contains the verses, or if it is used as milestone/anchor
236+ if int(verse_in_chapter(book)) > 0:
237+ # The chapter tags contains the verses
238+ for chapter in book:
239+ chapter_number = chapter.get("osisID").split('.')[1]
240+ # Find out if verse-tags contains the text, or if it is used as milestone/anchor
241+ if int(text_in_verse(chapter)) == 0:
242+ # verse-tags are used as milestone
243+ for verse in chapter:
244+ # If this tag marks the start of a verse, the verse text is between this tag and
245+ # the next tag, which the "tail" attribute gives us.
246+ if verse.get('sID'):
247+ verse_number = verse.get("osisID").split('.')[2]
248+ verse_text = verse.tail
249+ if verse_text:
250+ self.create_verse(db_book.id, chapter_number, verse_number, verse_text.strip())
251+ else:
252+ # Verse-tags contains the text
253+ for verse in chapter:
254+ verse_number = verse.get("osisID").split('.')[2]
255+ self.create_verse(db_book.id, chapter_number, verse_number, verse.text.strip())
256 self.wizard.increment_progress_bar(
257- translate('BiblesPlugin.OsisImport', 'Importing %s %s...',
258- 'Importing <book name> <chapter>...') % (book_details['name'], chapter))
259- last_chapter = chapter
260- # All of this rigmarole below is because the mod2osis tool from the Sword library embeds XML in the
261- # OSIS but neglects to enclose the verse text (with XML) in <[CDATA[ ]]> tags.
262- verse_text = self.note_regex.sub('', verse_text)
263- verse_text = self.title_regex.sub('', verse_text)
264- verse_text = self.milestone_regex.sub('', verse_text)
265- verse_text = self.fi_regex.sub('', verse_text)
266- verse_text = self.rf_regex.sub('', verse_text)
267- verse_text = self.lb_regex.sub(' ', verse_text)
268- verse_text = self.lg_regex.sub('', verse_text)
269- verse_text = self.l_regex.sub(' ', verse_text)
270- verse_text = self.w_regex.sub('', verse_text)
271- verse_text = self.q1_regex.sub('"', verse_text)
272- verse_text = self.q2_regex.sub('\'', verse_text)
273- verse_text = self.q_regex.sub('', verse_text)
274- verse_text = self.divine_name_regex.sub(repl, verse_text)
275- verse_text = self.trans_regex.sub('', verse_text)
276- verse_text = verse_text.replace('</lb>', '') \
277- .replace('</l>', '').replace('<lg>', '') \
278- .replace('</lg>', '').replace('</q>', '') \
279- .replace('</div>', '').replace('</w>', '')
280- verse_text = self.spaces_regex.sub(' ', verse_text)
281- self.create_verse(db_book.id, chapter, verse, verse_text)
282- self.application.process_events()
283- self.session.commit()
284- if match_count == 0:
285- success = False
286+ translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...' %
287+ {'bookname': db_book.name, 'chapter': chapter_number}))
288+ else:
289+ # The chapter tags is used as milestones. For now we assume verses is also milestones
290+ chapter_number = 0
291+ for element in book:
292+ if element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter' \
293+ and element.get('sID'):
294+ chapter_number = element.get("osisID").split('.')[1]
295+ self.wizard.increment_progress_bar(
296+ translate('BiblesPlugin.OsisImport', 'Importing %(bookname)s %(chapter)s...' %
297+ {'bookname': db_book.name, 'chapter': chapter_number}))
298+ elif element.tag == '{http://www.bibletechnologies.net/2003/OSIS/namespace}verse' \
299+ and element.get('sID'):
300+ # If this tag marks the start of a verse, the verse text is between this tag and
301+ # the next tag, which the "tail" attribute gives us.
302+ verse_number = element.get("osisID").split('.')[2]
303+ verse_text = element.tail
304+ if verse_text:
305+ self.create_verse(db_book.id, chapter_number, verse_number, verse_text.strip())
306+ self.session.commit()
307+ self.application.process_events()
308 except (ValueError, IOError):
309 log.exception('Loading bible from OSIS file failed')
310- success = False
311+ trace_error_handler(log)
312+ success = False
313+ except etree.XMLSyntaxError as e:
314+ log.exception('Loading bible from OSIS file failed')
315+ trace_error_handler(log)
316+ success = False
317+ critical_error_message_box(message=translate('BiblesPlugin.OsisImport',
318+ 'The file is not a valid OSIS-XML file: \n%s' % e.msg))
319 finally:
320- if osis:
321- osis.close()
322+ if import_file:
323+ import_file.close()
324 if self.stop_import_flag:
325 return False
326 else:
327
328=== modified file 'openlp/plugins/bibles/lib/zefania.py'
329--- openlp/plugins/bibles/lib/zefania.py 2014-05-20 18:41:07 +0000
330+++ openlp/plugins/bibles/lib/zefania.py 2014-08-27 13:19:28 +0000
331@@ -64,11 +64,18 @@
332 # NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
333 # detection, and the two mechanisms together interfere with each other.
334 import_file = open(self.filename, 'rb')
335- language_id = self.get_language(bible_name)
336+ zefania_bible_tree = etree.parse(import_file)
337+ # Find bible language
338+ language_id = None
339+ language = zefania_bible_tree.xpath("/XMLBIBLE/INFORMATION/language/text()")
340+ if language:
341+ language_id = BiblesResourcesDB.get_language(language[0])
342+ # The language couldn't be detected, ask the user
343+ if not language_id:
344+ language_id = self.get_language(bible_name)
345 if not language_id:
346 log.error('Importing books from "%s" failed' % self.filename)
347 return False
348- zefania_bible_tree = etree.parse(import_file)
349 num_books = int(zefania_bible_tree.xpath("count(//BIBLEBOOK)"))
350 # Strip tags we don't use - keep content
351 etree.strip_tags(zefania_bible_tree, ('STYLE', 'GRAM', 'NOTE', 'SUP', 'XREF'))
352@@ -97,7 +104,6 @@
353 self.session.commit()
354 self.application.process_events()
355 except Exception as e:
356- print(str(e))
357 critical_error_message_box(
358 message=translate('BiblesPlugin.ZefaniaImport',
359 'Incorrect Bible file type supplied. Zefania Bibles may be '
360
361=== modified file 'tests/functional/openlp_plugins/bibles/test_opensongimport.py'
362--- tests/functional/openlp_plugins/bibles/test_opensongimport.py 2014-05-21 09:30:36 +0000
363+++ tests/functional/openlp_plugins/bibles/test_opensongimport.py 2014-08-27 13:19:28 +0000
364@@ -31,6 +31,7 @@
365 """
366
367 import os
368+import json
369 from unittest import TestCase
370
371 from tests.functional import MagicMock, patch
372@@ -39,29 +40,6 @@
373
374 TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
375 '..', '..', '..', 'resources', 'bibles'))
376-OPENSONG_TEST_DATA = {
377- 'opensong-dk1933.xml': {
378- 'book': 'Genesis',
379- 'chapter': 1,
380- 'verses': [
381- (1, 'I Begyndelsen skabte Gud Himmelen og Jorden.'),
382- (2, 'Og Jorden var øde og tom, og der var Mørke over Verdensdybet. '
383- 'Men Guds Ånd svævede over Vandene.'),
384- (3, 'Og Gud sagde: "Der blive Lys!" Og der blev Lys.'),
385- (4, 'Og Gud så, at Lyset var godt, og Gud satte Skel mellem Lyset og Mørket,'),
386- (5, 'og Gud kaldte Lyset Dag, og Mørket kaldte han Nat. Og det blev Aften, '
387- 'og det blev Morgen, første Dag.'),
388- (6, 'Derpå sagde Gud: "Der blive en Hvælving midt i Vandene til at skille Vandene ad!"'),
389- (7, 'Og således skete det: Gud gjorde Hvælvingen og skilte Vandet under Hvælvingen '
390- 'fra Vandet over Hvælvingen;'),
391- (8, 'og Gud kaldte Hvælvingen Himmel. Og det blev Aften, og det blev Morgen, anden Dag.'),
392- (9, 'Derpå sagde Gud: "Vandet under Himmelen samle sig på eet Sted, så det faste Land kommer til Syne!" '
393- 'Og således skete det;'),
394- (10, 'og Gud kaldte det faste Land Jord, og Stedet, hvor Vandet samlede sig, kaldte han Hav. Og Gud så, '
395- 'at det var godt.')
396- ]
397- }
398-}
399
400
401 class TestOpenSongImport(TestCase):
402@@ -94,31 +72,33 @@
403
404 def file_import_test(self):
405 """
406- Test the actual import of real song files
407+ Test the actual import of OpenSong Bible file
408 """
409 # GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
410 # get_book_ref_id_by_name, create_verse, create_book, session and get_language.
411+ result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
412+ test_data = json.loads(result_file.read().decode())
413+ bible_file = 'opensong-dk1933.xml'
414 with patch('openlp.plugins.bibles.lib.opensong.OpenSongBible.application'):
415- for bible_file in OPENSONG_TEST_DATA:
416- mocked_manager = MagicMock()
417- mocked_import_wizard = MagicMock()
418- importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
419- importer.wizard = mocked_import_wizard
420- importer.get_book_ref_id_by_name = MagicMock()
421- importer.create_verse = MagicMock()
422- importer.create_book = MagicMock()
423- importer.session = MagicMock()
424- importer.get_language = MagicMock()
425- importer.get_language.return_value = 'Danish'
426-
427- # WHEN: Importing bible file
428- importer.filename = os.path.join(TEST_PATH, bible_file)
429- importer.do_import()
430-
431- # THEN: The create_verse() method should have been called with each verse in the file.
432- self.assertTrue(importer.create_verse.called)
433- for verse_tag, verse_text in OPENSONG_TEST_DATA[bible_file]['verses']:
434- importer.create_verse.assert_any_call(importer.create_book().id, 1, verse_tag, verse_text)
435+ mocked_manager = MagicMock()
436+ mocked_import_wizard = MagicMock()
437+ importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
438+ importer.wizard = mocked_import_wizard
439+ importer.get_book_ref_id_by_name = MagicMock()
440+ importer.create_verse = MagicMock()
441+ importer.create_book = MagicMock()
442+ importer.session = MagicMock()
443+ importer.get_language = MagicMock()
444+ importer.get_language.return_value = 'Danish'
445+
446+ # WHEN: Importing bible file
447+ importer.filename = os.path.join(TEST_PATH, bible_file)
448+ importer.do_import()
449+
450+ # THEN: The create_verse() method should have been called with each verse in the file.
451+ self.assertTrue(importer.create_verse.called)
452+ for verse_tag, verse_text in test_data['verses']:
453+ importer.create_verse.assert_any_call(importer.create_book().id, 1, int(verse_tag), verse_text)
454
455 def zefania_import_error_test(self):
456 """
457
458=== added file 'tests/functional/openlp_plugins/bibles/test_osisimport.py'
459--- tests/functional/openlp_plugins/bibles/test_osisimport.py 1970-01-01 00:00:00 +0000
460+++ tests/functional/openlp_plugins/bibles/test_osisimport.py 2014-08-27 13:19:28 +0000
461@@ -0,0 +1,161 @@
462+# -*- coding: utf-8 -*-
463+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
464+
465+###############################################################################
466+# OpenLP - Open Source Lyrics Projection #
467+# --------------------------------------------------------------------------- #
468+# Copyright (c) 2008-2014 Raoul Snyman #
469+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
470+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
471+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
472+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
473+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
474+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
475+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
476+# --------------------------------------------------------------------------- #
477+# This program is free software; you can redistribute it and/or modify it #
478+# under the terms of the GNU General Public License as published by the Free #
479+# Software Foundation; version 2 of the License. #
480+# #
481+# This program is distributed in the hope that it will be useful, but WITHOUT #
482+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
483+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
484+# more details. #
485+# #
486+# You should have received a copy of the GNU General Public License along #
487+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
488+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
489+###############################################################################
490+"""
491+This module contains tests for the OSIS Bible importer.
492+"""
493+
494+import os
495+import json
496+from unittest import TestCase
497+
498+from tests.functional import MagicMock, patch
499+from openlp.plugins.bibles.lib.osis import OSISBible
500+from openlp.plugins.bibles.lib.db import BibleDB
501+
502+TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
503+ '..', '..', '..', 'resources', 'bibles'))
504+
505+
506+class TestOsisImport(TestCase):
507+ """
508+ Test the functions in the :mod:`osisimport` module.
509+ """
510+
511+ def setUp(self):
512+ self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
513+ self.registry_patcher.start()
514+ self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
515+ self.manager_patcher.start()
516+
517+ def tearDown(self):
518+ self.registry_patcher.stop()
519+ self.manager_patcher.stop()
520+
521+ def create_importer_test(self):
522+ """
523+ Test creating an instance of the OSIS file importer
524+ """
525+ # GIVEN: A mocked out "manager"
526+ mocked_manager = MagicMock()
527+
528+ # WHEN: An importer object is created
529+ importer = OSISBible(mocked_manager, path='.', name='.', filename='')
530+
531+ # THEN: The importer should be an instance of BibleDB
532+ self.assertIsInstance(importer, BibleDB)
533+
534+ def file_import_nested_tags_test(self):
535+ """
536+ Test the actual import of OSIS Bible file, with nested chapter and verse tags
537+ """
538+ # GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
539+ # get_book_ref_id_by_name, create_verse, create_book, session and get_language.
540+ result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
541+ test_data = json.loads(result_file.read().decode())
542+ bible_file = 'osis-dk1933.xml'
543+ with patch('openlp.plugins.bibles.lib.osis.OSISBible.application'):
544+ mocked_manager = MagicMock()
545+ mocked_import_wizard = MagicMock()
546+ importer = OSISBible(mocked_manager, path='.', name='.', filename='')
547+ importer.wizard = mocked_import_wizard
548+ importer.get_book_ref_id_by_name = MagicMock()
549+ importer.create_verse = MagicMock()
550+ importer.create_book = MagicMock()
551+ importer.session = MagicMock()
552+ importer.get_language = MagicMock()
553+ importer.get_language.return_value = 'Danish'
554+
555+ # WHEN: Importing bible file
556+ importer.filename = os.path.join(TEST_PATH, bible_file)
557+ importer.do_import()
558+
559+ # THEN: The create_verse() method should have been called with each verse in the file.
560+ self.assertTrue(importer.create_verse.called)
561+ for verse_tag, verse_text in test_data['verses']:
562+ importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
563+
564+ def file_import_mixed_tags_test(self):
565+ """
566+ Test the actual import of OSIS Bible file, with chapter tags containing milestone verse tags.
567+ """
568+ # GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
569+ # get_book_ref_id_by_name, create_verse, create_book, session and get_language.
570+ result_file = open(os.path.join(TEST_PATH, 'kjv.json'), 'rb')
571+ test_data = json.loads(result_file.read().decode())
572+ bible_file = 'osis-kjv.xml'
573+ with patch('openlp.plugins.bibles.lib.osis.OSISBible.application'):
574+ mocked_manager = MagicMock()
575+ mocked_import_wizard = MagicMock()
576+ importer = OSISBible(mocked_manager, path='.', name='.', filename='')
577+ importer.wizard = mocked_import_wizard
578+ importer.get_book_ref_id_by_name = MagicMock()
579+ importer.create_verse = MagicMock()
580+ importer.create_book = MagicMock()
581+ importer.session = MagicMock()
582+ importer.get_language = MagicMock()
583+ importer.get_language.return_value = 'English'
584+
585+ # WHEN: Importing bible file
586+ importer.filename = os.path.join(TEST_PATH, bible_file)
587+ importer.do_import()
588+
589+ # THEN: The create_verse() method should have been called with each verse in the file.
590+ self.assertTrue(importer.create_verse.called)
591+ for verse_tag, verse_text in test_data['verses']:
592+ importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
593+
594+ def file_import_milestone_tags_test(self):
595+ """
596+ Test the actual import of OSIS Bible file, with milestone chapter and verse tags.
597+ """
598+ # GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
599+ # get_book_ref_id_by_name, create_verse, create_book, session and get_language.
600+ result_file = open(os.path.join(TEST_PATH, 'web.json'), 'rb')
601+ test_data = json.loads(result_file.read().decode())
602+ bible_file = 'osis-web.xml'
603+ with patch('openlp.plugins.bibles.lib.osis.OSISBible.application'):
604+ mocked_manager = MagicMock()
605+ mocked_import_wizard = MagicMock()
606+ importer = OSISBible(mocked_manager, path='.', name='.', filename='')
607+ importer.wizard = mocked_import_wizard
608+ importer.get_book_ref_id_by_name = MagicMock()
609+ importer.create_verse = MagicMock()
610+ importer.create_book = MagicMock()
611+ importer.session = MagicMock()
612+ importer.get_language = MagicMock()
613+ importer.get_language.return_value = 'English'
614+
615+ # WHEN: Importing bible file
616+ importer.filename = os.path.join(TEST_PATH, bible_file)
617+ importer.do_import()
618+
619+ # THEN: The create_verse() method should have been called with each verse in the file.
620+ self.assertTrue(importer.create_verse.called)
621+ for verse_tag, verse_text in test_data['verses']:
622+ importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
623
624=== modified file 'tests/functional/openlp_plugins/bibles/test_zefaniaimport.py'
625--- tests/functional/openlp_plugins/bibles/test_zefaniaimport.py 2014-05-21 09:30:36 +0000
626+++ tests/functional/openlp_plugins/bibles/test_zefaniaimport.py 2014-08-27 13:19:28 +0000
627@@ -31,6 +31,7 @@
628 """
629
630 import os
631+import json
632 from unittest import TestCase
633
634 from tests.functional import MagicMock, patch
635@@ -39,29 +40,6 @@
636
637 TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
638 '..', '..', '..', 'resources', 'bibles'))
639-ZEFANIA_TEST_DATA = {
640- 'zefania-dk1933.xml': {
641- 'book': 'Genesis',
642- 'chapter': 1,
643- 'verses': [
644- ('1', 'I Begyndelsen skabte Gud Himmelen og Jorden.'),
645- ('2', 'Og Jorden var øde og tom, og der var Mørke over Verdensdybet. '
646- 'Men Guds Ånd svævede over Vandene.'),
647- ('3', 'Og Gud sagde: "Der blive Lys!" Og der blev Lys.'),
648- ('4', 'Og Gud så, at Lyset var godt, og Gud satte Skel mellem Lyset og Mørket,'),
649- ('5', 'og Gud kaldte Lyset Dag, og Mørket kaldte han Nat. Og det blev Aften, '
650- 'og det blev Morgen, første Dag.'),
651- ('6', 'Derpå sagde Gud: "Der blive en Hvælving midt i Vandene til at skille Vandene ad!"'),
652- ('7', 'Og således skete det: Gud gjorde Hvælvingen og skilte Vandet under Hvælvingen '
653- 'fra Vandet over Hvælvingen;'),
654- ('8', 'og Gud kaldte Hvælvingen Himmel. Og det blev Aften, og det blev Morgen, anden Dag.'),
655- ('9', 'Derpå sagde Gud: "Vandet under Himmelen samle sig på eet Sted, så det faste Land kommer til Syne!" '
656- 'Og således skete det;'),
657- ('10', 'og Gud kaldte det faste Land Jord, og Stedet, hvor Vandet samlede sig, kaldte han Hav. Og Gud så, '
658- 'at det var godt.')
659- ]
660- }
661-}
662
663
664 class TestZefaniaImport(TestCase):
665@@ -94,28 +72,30 @@
666
667 def file_import_test(self):
668 """
669- Test the actual import of real song files
670+ Test the actual import of Zefania Bible file
671 """
672 # GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
673 # get_book_ref_id_by_name, create_verse, create_book, session and get_language.
674+ result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
675+ test_data = json.loads(result_file.read().decode())
676+ bible_file = 'zefania-dk1933.xml'
677 with patch('openlp.plugins.bibles.lib.zefania.ZefaniaBible.application'):
678- for bible_file in ZEFANIA_TEST_DATA:
679- mocked_manager = MagicMock()
680- mocked_import_wizard = MagicMock()
681- importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
682- importer.wizard = mocked_import_wizard
683- importer.get_book_ref_id_by_name = MagicMock()
684- importer.create_verse = MagicMock()
685- importer.create_book = MagicMock()
686- importer.session = MagicMock()
687- importer.get_language = MagicMock()
688- importer.get_language.return_value = 'Danish'
689-
690- # WHEN: Importing bible file
691- importer.filename = os.path.join(TEST_PATH, bible_file)
692- importer.do_import()
693-
694- # THEN: The create_verse() method should have been called with each verse in the file.
695- self.assertTrue(importer.create_verse.called)
696- for verse_tag, verse_text in ZEFANIA_TEST_DATA[bible_file]['verses']:
697- importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
698+ mocked_manager = MagicMock()
699+ mocked_import_wizard = MagicMock()
700+ importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
701+ importer.wizard = mocked_import_wizard
702+ importer.get_book_ref_id_by_name = MagicMock()
703+ importer.create_verse = MagicMock()
704+ importer.create_book = MagicMock()
705+ importer.session = MagicMock()
706+ importer.get_language = MagicMock()
707+ importer.get_language.return_value = 'Danish'
708+
709+ # WHEN: Importing bible file
710+ importer.filename = os.path.join(TEST_PATH, bible_file)
711+ importer.do_import()
712+
713+ # THEN: The create_verse() method should have been called with each verse in the file.
714+ self.assertTrue(importer.create_verse.called)
715+ for verse_tag, verse_text in test_data['verses']:
716+ importer.create_verse.assert_any_call(importer.create_book().id, '1', verse_tag, verse_text)
717
718=== added file 'tests/resources/bibles/dk1933.json'
719--- tests/resources/bibles/dk1933.json 1970-01-01 00:00:00 +0000
720+++ tests/resources/bibles/dk1933.json 2014-08-27 13:19:28 +0000
721@@ -0,0 +1,16 @@
722+{
723+ "book": "Genesis",
724+ "chapter": 1,
725+ "verses": [
726+ [ "1", "I Begyndelsen skabte Gud Himmelen og Jorden."],
727+ [ "2", "Og Jorden var øde og tom, og der var Mørke over Verdensdybet. Men Guds Ånd svævede over Vandene." ],
728+ [ "3", "Og Gud sagde: \"Der blive Lys!\" Og der blev Lys." ],
729+ [ "4", "Og Gud så, at Lyset var godt, og Gud satte Skel mellem Lyset og Mørket," ],
730+ [ "5", "og Gud kaldte Lyset Dag, og Mørket kaldte han Nat. Og det blev Aften, og det blev Morgen, første Dag." ],
731+ [ "6", "Derpå sagde Gud: \"Der blive en Hvælving midt i Vandene til at skille Vandene ad!\"" ],
732+ [ "7", "Og således skete det: Gud gjorde Hvælvingen og skilte Vandet under Hvælvingen fra Vandet over Hvælvingen;" ],
733+ [ "8", "og Gud kaldte Hvælvingen Himmel. Og det blev Aften, og det blev Morgen, anden Dag." ],
734+ [ "9", "Derpå sagde Gud: \"Vandet under Himmelen samle sig på eet Sted, så det faste Land kommer til Syne!\" Og således skete det;" ],
735+ [ "10", "og Gud kaldte det faste Land Jord, og Stedet, hvor Vandet samlede sig, kaldte han Hav. Og Gud så, at det var godt." ]
736+ ]
737+}
738\ No newline at end of file
739
740=== added file 'tests/resources/bibles/kjv.json'
741--- tests/resources/bibles/kjv.json 1970-01-01 00:00:00 +0000
742+++ tests/resources/bibles/kjv.json 2014-08-27 13:19:28 +0000
743@@ -0,0 +1,16 @@
744+{
745+ "book": "Genesis",
746+ "chapter": 1,
747+ "verses": [
748+ [ "1", "In the beginning God created the heaven and the earth."],
749+ [ "2", "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." ],
750+ [ "3", "And God said, Let there be light: and there was light." ],
751+ [ "4", "And God saw the light, that it was good: and God divided the light from the darkness." ],
752+ [ "5", "And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day." ],
753+ [ "6", "And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters." ],
754+ [ "7", "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." ],
755+ [ "8", "And God called the firmament Heaven. And the evening and the morning were the second day." ],
756+ [ "9", "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." ],
757+ [ "10", "And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good." ]
758+ ]
759+}
760
761=== added file 'tests/resources/bibles/osis-dk1933.xml'
762--- tests/resources/bibles/osis-dk1933.xml 1970-01-01 00:00:00 +0000
763+++ tests/resources/bibles/osis-dk1933.xml 2014-08-27 13:19:28 +0000
764@@ -0,0 +1,32 @@
765+<?xml version="1.0" encoding="UTF-8" ?>
766+
767+<osis xmlns="http://www.bibletechnologies.net/2003/OSIS/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bibletechnologies.net/2003/OSIS/namespace http://www.bibletechnologies.net/osisCore.2.1.1.xsd">
768+ <osisText osisIDWork="DanDetteBiblen" osisRefWork="defaultReferenceScheme" xml:lang="da">
769+ <header>
770+ <work osisWork="DanDetteBiblen">
771+ <title>Dette er Biblen</title>
772+ <identifier type="OSIS">Bible.DanDetteBiblen</identifier>
773+ <refSystem>Bible.KJV</refSystem>
774+ </work>
775+ <work osisWork="defaultReferenceScheme">
776+ <refSystem>Bible.KJV</refSystem>
777+ </work>
778+ </header>
779+ <div type="x-testament">
780+ <div type="book" osisID="Gen">
781+ <chapter osisID="Gen.1">
782+ <verse osisID="Gen.1.1">I Begyndelsen skabte Gud Himmelen og Jorden.</verse>
783+ <verse osisID="Gen.1.2">Og Jorden var øde og tom, og der var Mørke over Verdensdybet. Men Guds Ånd svævede over Vandene.<p /></verse>
784+ <verse osisID="Gen.1.3">Og Gud sagde: "Der blive Lys!" Og der blev Lys.</verse>
785+ <verse osisID="Gen.1.4">Og Gud så, at Lyset var godt, og Gud satte Skel mellem Lyset og Mørket,</verse>
786+ <verse osisID="Gen.1.5">og Gud kaldte Lyset Dag, og Mørket kaldte han Nat. Og det blev Aften, og det blev Morgen, første Dag.<p /></verse>
787+ <verse osisID="Gen.1.6">Derpå sagde Gud: "Der blive en Hvælving midt i Vandene til at skille Vandene ad!"</verse>
788+ <verse osisID="Gen.1.7">Og således skete det: Gud gjorde Hvælvingen og skilte Vandet under Hvælvingen fra Vandet over Hvælvingen;</verse>
789+ <verse osisID="Gen.1.8">og Gud kaldte Hvælvingen Himmel. Og det blev Aften, og det blev Morgen, anden Dag.<p /></verse>
790+ <verse osisID="Gen.1.9">Derpå sagde Gud: "Vandet under Himmelen samle sig på eet Sted, så det faste Land kommer til Syne!" Og således skete det;</verse>
791+ <verse osisID="Gen.1.10">og Gud kaldte det faste Land Jord, og Stedet, hvor Vandet samlede sig, kaldte han Hav. Og Gud så, at det var godt.</verse>
792+ </chapter>
793+ </div>
794+ </div>
795+ </osisText>
796+</osis>
797
798=== added file 'tests/resources/bibles/osis-kjv.xml'
799--- tests/resources/bibles/osis-kjv.xml 1970-01-01 00:00:00 +0000
800+++ tests/resources/bibles/osis-kjv.xml 2014-08-27 13:19:28 +0000
801@@ -0,0 +1,41 @@
802+<?xml version="1.0" encoding="UTF-8"?>
803+<osis xmlns="http://www.bibletechnologies.net/2003/OSIS/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bibletechnologies.net/2003/OSIS/namespace http://www.bibletechnologies.net/osisCore.2.1.1.xsd">
804+<osisText osisIDWork="KJV" osisRefWork="defaultReferenceScheme" xml:lang="en">
805+<header>
806+ <work osisWork="KJV">
807+ <title>King James Version (1769) with Strongs Numbers and Morphology</title>
808+ <identifier type="OSIS">Bible.KJV</identifier>
809+ <scope>Gen-Rev</scope>
810+ <refSystem>Bible.KJV</refSystem>
811+ </work>
812+ <work osisWork="defaultReferenceScheme">
813+ <refSystem>Bible.KJV</refSystem>
814+ </work>
815+ <work osisWork="strong">
816+ <refSystem>Dict.Strongs</refSystem>
817+ </work>
818+ <work osisWork="robinson">
819+ <refSystem>Dict.Robinsons</refSystem>
820+ </work>
821+ <work osisWork="strongMorph">
822+ <refSystem>Dict.strongMorph</refSystem>
823+ </work>
824+</header>
825+<div type="book" osisID="Gen" canonical="true">
826+<title type="main">THE FIRST BOOK OF MOSES CALLED GENESIS</title>
827+<chapter osisID="Gen.1" chapterTitle="CHAPTER 1.">
828+<title type="chapter">CHAPTER 1.</title>
829+<verse osisID="Gen.1.1" sID="Gen.1.1"/><w lemma="strong:H07225">In the beginning</w> <w lemma="strong:H0430">God</w> <w morph="strongMorph:TH8804" lemma="strong:H0853 strong:H01254">created</w> <w lemma="strong:H08064">the heaven</w> <w lemma="strong:H0853">and</w> <w lemma="strong:H0776">the earth</w>.<verse eID="Gen.1.1"/>
830+<verse osisID="Gen.1.2" sID="Gen.1.2"/><w lemma="strong:H0776">And the earth</w> <w morph="strongMorph:TH8804" lemma="strong:H01961">was</w> <w lemma="strong:H08414">without form</w>, <w lemma="strong:H0922">and void</w>; <w lemma="strong:H02822">and darkness</w> <transChange type="added">was</transChange> <w lemma="strong:H06440">upon the face</w> <w lemma="strong:H08415">of the deep</w>. <w lemma="strong:H07307">And the Spirit</w> <w lemma="strong:H0430">of God</w> <w morph="strongMorph:TH8764" lemma="strong:H07363">moved</w> <w lemma="strong:H05921">upon</w> <w lemma="strong:H06440">the face</w> <w lemma="strong:H04325">of the waters</w>.<verse eID="Gen.1.2"/>
831+<verse osisID="Gen.1.3" sID="Gen.1.3"/><milestone type="x-extra-p"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H0559">said</w>, <w morph="strongMorph:TH8799" lemma="strong:H01961">Let there be</w> <w lemma="strong:H0216">light</w>: <w lemma="strong:H0216">and there was light</w>.<verse eID="Gen.1.3"/>
832+<verse osisID="Gen.1.4" sID="Gen.1.4"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H0853 strong:H07200">saw</w> <w lemma="strong:H0216">the light</w>, <w lemma="strong:H03588">that</w> <transChange type="added">it was</transChange> <w lemma="strong:H02896">good</w>: <w lemma="strong:H0430">and God</w> <w morph="strongMorph:TH8686" lemma="strong:H0996 strong:H0914">divided</w> <w lemma="strong:H0216">the light</w> <w lemma="strong:H0996">from</w> <w lemma="strong:H02822">the darkness</w>.<note type="study">the light from…: Heb. between the light and between the darkness</note><verse eID="Gen.1.4"/>
833+<verse osisID="Gen.1.5" sID="Gen.1.5"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H07121">called</w> <w lemma="strong:H0216">the light</w> <w lemma="strong:H03117">Day</w>, <w lemma="strong:H02822">and the darkness</w> <w morph="strongMorph:TH8804" lemma="strong:H07121">he called</w> <w lemma="strong:H03915">Night</w>. <w lemma="strong:H06153">And the evening</w> <w lemma="strong:H01242">and the morning</w> <w lemma="strong:H0259">were the first</w> <w lemma="strong:H03117">day</w>.<note type="study">And the evening…: Heb. And the evening was, and the morning was etc.</note><verse eID="Gen.1.5"/>
834+<verse osisID="Gen.1.6" sID="Gen.1.6"/><milestone type="x-p" marker="¶"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H0559">said</w>, <w lemma="strong:H07549">Let there be a firmament</w> <w lemma="strong:H08432">in the midst</w> <w lemma="strong:H04325">of the waters</w>, <w morph="strongMorph:TH8688" lemma="strong:H0914">and let it divide</w> <w lemma="strong:H04325">the waters</w> <w lemma="strong:H04325">from the waters</w>.<note type="study">firmament: Heb. expansion</note><verse eID="Gen.1.6"/>
835+<verse osisID="Gen.1.7" sID="Gen.1.7"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H06213">made</w> <w lemma="strong:H07549">the firmament</w>, <w morph="strongMorph:TH8686" lemma="strong:H0914">and divided</w> <w lemma="strong:H04325">the waters</w> <w lemma="strong:H0834">which</w> <transChange type="added">were</transChange> <w lemma="strong:H08478">under</w> <w lemma="strong:H07549">the firmament</w> <w lemma="strong:H04325">from the waters</w> <w lemma="strong:H0834">which</w> <transChange type="added">were</transChange> <w lemma="strong:H05921">above</w> <w lemma="strong:H07549">the firmament</w>: <w lemma="strong:H03651">and it was so</w>.<verse eID="Gen.1.7"/>
836+<verse osisID="Gen.1.8" sID="Gen.1.8"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H07121">called</w> <w lemma="strong:H07549">the firmament</w> <w lemma="strong:H08064">Heaven</w>. <w lemma="strong:H06153">And the evening</w> <w lemma="strong:H01242">and the morning</w> <w lemma="strong:H08145">were the second</w> <w lemma="strong:H03117">day</w>.<note type="study">And the evening…: Heb. And the evening was, and the morning was etc.</note><verse eID="Gen.1.8"/>
837+<verse osisID="Gen.1.9" sID="Gen.1.9"/><milestone type="x-p" marker="¶"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H0559">said</w>, <w lemma="strong:H04325">Let the waters</w> <w lemma="strong:H08064">under the heaven</w> <w morph="strongMorph:TH8735" lemma="strong:H06960">be gathered together</w> <w lemma="strong:H0413">unto</w> <w lemma="strong:H0259">one</w> <w lemma="strong:H04725">place</w>, <w lemma="strong:H03004">and let the dry</w> <transChange type="added">land</transChange> <w morph="strongMorph:TH8735" lemma="strong:H07200">appear</w>: and it was so.<verse eID="Gen.1.9"/>
838+<verse osisID="Gen.1.10" sID="Gen.1.10"/><w lemma="strong:H0430">And God</w> <w morph="strongMorph:TH8799" lemma="strong:H07121">called</w> <w lemma="strong:H03004">the dry</w> <transChange type="added">land</transChange> <w lemma="strong:H0776">Earth</w>; <w lemma="strong:H04723">and the gathering together</w> <w lemma="strong:H04325">of the waters</w> <w morph="strongMorph:TH8804" lemma="strong:H07121">called</w> <w lemma="strong:H03220">he Seas</w>: <w lemma="strong:H0430">and God</w> <w morph="strongMorph:TH8799" lemma="strong:H07200">saw</w> that <transChange type="added">it was</transChange> <w lemma="strong:H02896">good</w>.<verse eID="Gen.1.10"/>
839+</chapter>
840+</div>
841+</osisText>
842+</osis>
843
844=== added file 'tests/resources/bibles/osis-web.xml'
845--- tests/resources/bibles/osis-web.xml 1970-01-01 00:00:00 +0000
846+++ tests/resources/bibles/osis-web.xml 2014-08-27 13:19:28 +0000
847@@ -0,0 +1,109 @@
848+<?xml version="1.0" encoding="utf-8"?>
849+<osis xmlns="http://www.bibletechnologies.net/2003/OSIS/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bibletechnologies.net/2003/OSIS/namespace osisCore.2.0.xsd">
850+ <osisText osisIDWork="WEB" osisRefWork="Bible" xml:lang="en">
851+ <header>
852+ <revisionDesc resp="Rainbow Missions, Inc.">
853+ <date>2007-08-26T08.23.41</date>
854+ <p> This draft version of the World English Bible is
855+substantially complete in the New Testament, Genesis, Exodus, Job, Psalms, Proverbs, Ecclesiastes, Song of Solomon, and the “minor” prophets. Editing continues on the other books of the Old Testament. All WEB companion Apocrypha books are still in
856+rough draft form. </p>
857+ <p>Converted web.gbf in GBF to web.osis.xml in
858+an XML format that is mostly compliant with OSIS 2.0 using gbf2osis.exe.
859+(Please see http://ebt.cx/translation/ for links to this software.)</p>
860+ <p>GBF and OSIS metadata fields do not exactly correspond to each other, so
861+the conversion is not perfect in the metadata. However, the Scripture portion
862+should be correct.</p>
863+ <p>No attempt was to convert quotation marks to structural markers using q or
864+speech elements, because this would require language and style-dependent
865+processing, and because the current OSIS specification is deficient in that
866+quotation mark processing is not guaranteed to produce the correct results
867+for all languages and translations. In English texts, the hard part of the
868+conversion to markup is figuring out what ’ means.
869+The other difficulty is that OSIS in no way guarantees that these punctuation
870+marks would be reconstituted properly by software that reads OSIS files
871+for anything other than modern English, and even then, it does not
872+accommodate all styles of punctuation and all cases.
873+We strongly recommend that anyone using OSIS NOT replace quotation mark
874+punctuation in any existing text with q or speech elements. It is better
875+for multiple language processing capabilities to leave the quotation
876+punctuation as part of the text. If you need the q or speech markup, then you
877+may supplement those punctuation marks with those markup elements, but specify
878+the n='' parameter in those elements to indicate that no generation of any
879+punctuation from those markup elements is required or desired. That way you
880+can have BOTH correct punctuation already in the text AND markup so that you
881+can automatically determine when you are in a quotation or not, independent
882+of language. This may be useful for a search by speaker, for example.</p>
883+ <p>The output of gbf2osis marks Jesus' words in a non-standard way using the q
884+element AND quotation marks if they were marked with FR/Fr markers in the GBF
885+file. The OSIS 2.0 specification requires that quotation marks be stripped out,
886+and reinserted by software that reads the OSIS files when q elements are used.
887+This is not acceptable for the reasons given above, and we choose not to do
888+that, but we used the q element with who='Jesus' to indicate Jesus' words.
889+Do not generate any additional punctuation due to these markers. The correct
890+punctuation is already in the text.</p>
891+ <p>OSIS does not currently support footnote start anchors. Therefore, these
892+start anchors have been represented with milestone elements, in case someone
893+might like to use them, for example, to start an href element in a conversion
894+to HTML. (OSIS sort of supports the same idea by allowing a catchword to be
895+defined within a footnote, but I did not implement the processing to convert
896+to this different way of doing things, and it isn't exactly the same, anyway.)</p>
897+ <p>Traditional psalm book titles are rendered as text rather than titles, because
898+the title element does not support containing transChange elements, as would be
899+required to encode the KJV text using OSIS title elements. This may actually be
900+a superior solution, anyway, in that the Masoretic text makes no such distinction
901+(even though many modern typeset Bibles do make a typographic distinction in this
902+case).</p>
903+ <p>The schema location headers were modified to use local copies rather than the
904+standard locations so that these files could be validated and used without an
905+Internet connection active at all times (very important for the developer's
906+remote island location), but you may wish to change them back.</p>
907+ </revisionDesc>
908+ <work osisWork="WEB">
909+ <title>World English Bible</title>
910+ <creator>WEB committee</creator>
911+ <date event="eversion" type="Gregorian">2007-08-26</date>
912+ <publisher>Rainbow Missions, Inc.</publisher>
913+ <type type="OSIS">Bible</type>
914+ <identifier type="OSIS">Bible.en.WEB.draft.2007-08-26</identifier>
915+ <source>http://eBible.org/web/</source>
916+ <language type="SIL">ENG</language>
917+ <coverage>Wherever English is spoken in the world.</coverage>
918+ <rights>The World English Bible is dedicated to the Public Domain by the translators and editors. It is not copyrighted. “World English Bible” and the World English Bible logo are a trademarks of Rainbow
919+Missions, Inc. They may only be used to identify this translation of the Holy Bible as published by Rainbow Missions, Inc., and faithful copies and quotations. “Faithful copies” include copies converted to other formats (i. e. HTML, PDF, etc.) or
920+typeset differently, without altering the text of the Scriptures, except that changing the spellings between preferred American and British usage is allowed. Use of the markings of direct quotes of Jesus Christ for different rendition (i. e. red text)
921+is optional. Comments and typo reports are welcome at http://eBible.org/cgi-bin/comment.cgi. Please see http://eBible.org/web/ for updates, revision status, free downloads, and printed edition purchase information.</rights>
922+ <scope>Gen-Mal</scope>
923+ <scope>Tob-AddEsth</scope>
924+ <scope>Bar-EpJer</scope>
925+ <scope>AddDan</scope>
926+ <scope>Matt-Rev</scope>
927+ <refSystem>Bible.WEB</refSystem>
928+ </work>
929+ </header>
930+ <div type="bookGroup" canonical="true">
931+ <div type="book" osisID="Gen" scope="Gen">
932+ <title type="main" short="Genesis">Genesis </title>
933+ <chapter sID="Gen.1" osisID="Gen.1" />
934+ <p>
935+
936+<verse sID="Gen.1.1" osisID="Gen.1.1" />In the beginning <milestone type="x-noteStartAnchor" />God<note type="translation">The Hebrew word rendered “God” is “Elohim.” After “God,” the Hebrew has the two letters “Aleph Tav” (the first and last letters of the Hebrew alphabet) as a grammatical marker.</note> created the heavens and the earth.
937+<verse eID="Gen.1.1" /><verse sID="Gen.1.2" osisID="Gen.1.2" />Now the earth was formless and empty. Darkness was on the surface of the deep. God’s Spirit was hovering over the surface of the waters.</p>
938+<p>
939+<verse eID="Gen.1.2" />
940+<verse sID="Gen.1.3" osisID="Gen.1.3" />God said, “Let there be light,” and there was light.
941+<verse eID="Gen.1.3" /><verse sID="Gen.1.4" osisID="Gen.1.4" />God saw the light, and saw that it was good. God divided the light from the darkness.
942+<verse eID="Gen.1.4" /><verse sID="Gen.1.5" osisID="Gen.1.5" />God called the light “day,” and the darkness he called “night.” There was evening and there was morning, one day.</p>
943+<p>
944+<verse eID="Gen.1.5" />
945+<verse sID="Gen.1.6" osisID="Gen.1.6" />God said, “Let there be an expanse in the middle of the waters, and let it divide the waters from the waters.”
946+<verse eID="Gen.1.6" /><verse sID="Gen.1.7" osisID="Gen.1.7" />God made the expanse, and divided the waters which were under the expanse from the waters which were above the expanse; and it was so.
947+<verse eID="Gen.1.7" /><verse sID="Gen.1.8" osisID="Gen.1.8" />God called the expanse “sky.” There was evening and there was morning, a second day.</p>
948+<p>
949+<verse eID="Gen.1.8" />
950+<verse sID="Gen.1.9" osisID="Gen.1.9" />God said, “Let the waters under the sky be gathered together to one place, and let the dry land appear;” and it was so.
951+<verse eID="Gen.1.9" /><verse sID="Gen.1.10" osisID="Gen.1.10" />God called the dry land “earth,” and the gathering together of the waters he called “seas.” God saw that it was good.
952+<verse eID="Gen.1.10" /></p>
953+</div>
954+</div>
955+</osisText>
956+</osis>
957
958=== added file 'tests/resources/bibles/web.json'
959--- tests/resources/bibles/web.json 1970-01-01 00:00:00 +0000
960+++ tests/resources/bibles/web.json 2014-08-27 13:19:28 +0000
961@@ -0,0 +1,16 @@
962+{
963+ "book": "Genesis",
964+ "chapter": "1",
965+ "verses": [
966+ [ "1", "In the beginning God created the heavens and the earth."],
967+ [ "2", "Now the earth was formless and empty. Darkness was on the surface of the deep. God’s Spirit was hovering over the surface of the waters." ],
968+ [ "3", "God said, “Let there be light,” and there was light." ],
969+ [ "4", "God saw the light, and saw that it was good. God divided the light from the darkness." ],
970+ [ "5", "God called the light “day,” and the darkness he called “night.” There was evening and there was morning, one day." ],
971+ [ "6", "God said, “Let there be an expanse in the middle of the waters, and let it divide the waters from the waters.”" ],
972+ [ "7", "God made the expanse, and divided the waters which were under the expanse from the waters which were above the expanse; and it was so." ],
973+ [ "8", "God called the expanse “sky.” There was evening and there was morning, a second day." ],
974+ [ "9", "God said, “Let the waters under the sky be gathered together to one place, and let the dry land appear;” and it was so." ],
975+ [ "10", "God called the dry land “earth,” and the gathering together of the waters he called “seas.” God saw that it was good." ]
976+ ]
977+}