| VersificationToKJVMapper.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 or later
5 * as published by the Free Software Foundation. This program is distributed
6 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * © CrossWire Bible Society, 2013 - 2016
18 *
19 */
20 package org.crosswire.jsword.versification;
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.PrintStream;
24 import java.io.UnsupportedEncodingException;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.crosswire.common.util.IOUtil;
32 import org.crosswire.common.util.KeyValuePair;
33 import org.crosswire.common.util.LucidRuntimeException;
34 import org.crosswire.jsword.JSMsg;
35 import org.crosswire.jsword.passage.Key;
36 import org.crosswire.jsword.passage.KeyUtil;
37 import org.crosswire.jsword.passage.NoSuchKeyException;
38 import org.crosswire.jsword.passage.NoSuchVerseException;
39 import org.crosswire.jsword.passage.OsisParser;
40 import org.crosswire.jsword.passage.Passage;
41 import org.crosswire.jsword.passage.RangedPassage;
42 import org.crosswire.jsword.passage.RestrictionType;
43 import org.crosswire.jsword.passage.Verse;
44 import org.crosswire.jsword.passage.VerseKey;
45 import org.crosswire.jsword.passage.VerseRange;
46 import org.crosswire.jsword.versification.system.Versifications;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51 * A Versification mapper allows you to a map a given verse to the KJV versification,
52 * or unmap it from the KJV versification into your own versification.
53 * <p>
54 * A properties-like file will contain the non-KJV versification as they key, and the KJV versification value
55 * as the target value... Duplicate keys are allowed.
56 * </p>
57 * <p>
58 * i.e. Gen.1.1=Gen.1.2 means Gen.1.1 in X versification is Gen.1.2 in the KJV versification
59 * </p>
60 * <p>
61 * You can specify a range on either side. If a range is present on both sides, they have to have the same number of
62 * verses, i.e. verses are mapped verse by verse to each other<br>
63 * Gen.1.1-Gen.1.2=Gen.1.2-Gen.1.3 means Gen.1.1=Gen.1.2 and Gen.1.2=Gen.1.3<br>
64 *<br>
65 * Note: if the cardinality of the left & KJV sides are different by only one, the algorithm makes the
66 * assumption that verse 0 should be disregarded in both ranges.
67 * </p>
68 * <p>
69 * Mappings can be specified by offset. In this case, be aware this maps verse 0 as well. So for example:
70 * </p>
71 * <p>
72 * Ps.19-Ps.20=-1 means Ps.19.0=Ps.18.50, Ps.19.1=Ps.19.0, Ps.19.2=Ps.19.1, etc.<br>
73 * It does not make much sense to have an offset for a single verse, so this is not supported.
74 * Offsetting for multiple ranges however does, and operates range by range, i.e. each range is calculated separately.
75 * Offsetting is somewhat equivalent to specifying ranges, and as a result, the verse 0 behaviour is identical.
76 * </p>
77 * <p>
78 * You can use part-mappings. This is important if you want to preserve transformations from one side to another without
79 * losing resolution of the verse.
80 * </p>
81 * <p>
82 * For example,<br>
83 * if V1 defines Gen.1.1=Gen.1.1, Gen.1.2=Gen1.1<br>
84 * if V2 defines Gen.1.1=Gen.1.1, Gen.1.2=Gen.1.1<br>
85 * then, mapping from V1=>KJV and KJV=>V2 gives you Gen.1.1=>Gen.1.1=>Gen.1.1-Gen.1.2 which is inaccurate if in fact
86 * V1(Gen.1.1) actually equals V2(Gen.1.1). So instead, we use a split on the right hand-side:
87 * </p>
88 * <p>
89 * For example,<br>
90 * V1 defines Gen.1.1=Gen1.1!a, Gen.1.2=Gen.1.1!b<br>
91 * V2 defines Gen.1.1=Gen1.1!a, Gen.1.2=Gen.1.1!b<br>
92 * then, mapping from V1=>KJV and KJV=>V2 gives you Gen.1.1=>Gen.1.1!a=>Gen.1.1, which is now accurate.
93 * A part is a string fragment placed after the end of a key reference. We cannot use # because that is commonly known
94 * as a comment in real properties-file. Using a marker, means we can have meaningful part names if we so choose.
95 * Parts of ranges are not supported.
96 * </p>
97 * <p>
98 * Note: splits should never be seen by a user. The mapping from one versification to another is abstracted
99 * such that the user can simply request the mapping between 2 verse (ranges).
100 * </p>
101 * <p>
102 * Unmapped verses can be specified by inventing ids, either for whole sections, or verse by verse (this would
103 * come in handy if two versifications have the same content, but the KJV doesn't). A section must be preceded
104 * with a '?' character indicating that there will be no need to try and look up a reference.
105 * Gen.1.1=?NewPassage
106 * </p>
107 * <p>
108 * Since not specifying a verse mappings means there is a 1-2-1 unchanged mapping, we need a way of specifying
109 * absent verses altogether:<br>
110 * ?=Gen.1.1<br>
111 * ?=Gen.1.5<br>
112 * means that the non-KJV book simply does not contain verses Gen.1.1 and Gen.1.5 and therefore can't
113 * be mapped.
114 * </p>
115 * <p>
116 * We allow some global flags (one at present):<br>
117 * !zerosUnmapped : means that any mapping to or from a zero verse
118 * </p>
119 * <p>
120 * TODO(CJB): think about whether when returning, we should clone, or make things immutable.
121 * </p>
122 *
123 * @see gnu.lgpl.License The GNU Lesser General Public License for details.
124 * @author Chris Burrell
125 */
126 public class VersificationToKJVMapper {
127
128 /**
129 * @param nonKjv a versification that is not the KJV
130 * @param mapping the mappings from one versification to another
131 */
132 public VersificationToKJVMapper(Versification nonKjv, final FileVersificationMapping mapping) {
133 absentVerses = createEmptyPassage(KJV);
134 toKJVMappings = new HashMap<VerseKey, List<QualifiedKey>>();
135 fromKJVMappings = new HashMap<QualifiedKey, Passage>();
136 this.nonKjv = nonKjv;
137 processMappings(mapping);
138 trace();
139 }
140
141 /**
142 * This is the crux of the decoding facility. The properties are expanded.
143 *
144 * @param mappings the input mappings, in a contracted, short-hand form
145 */
146 private void processMappings(FileVersificationMapping mappings) {
147 final List<KeyValuePair> entries = mappings.getMappings();
148 for (KeyValuePair entry : entries) {
149 try {
150 processEntry(entry);
151 } catch (NoSuchKeyException ex) {
152 // TODO(CJB): should we throw a config exception?
153 LOGGER.error("Unable to process entry [{}] with value [{}]", entry.getKey(), entry.getValue(), ex);
154 hasErrors = true;
155 }
156 }
157 }
158
159 private void processEntry(final KeyValuePair entry) throws NoSuchKeyException {
160 String leftHand = entry.getKey();
161 String kjvHand = entry.getValue();
162
163 if (leftHand == null || leftHand.length() == 0) {
164 LOGGER.error("Left hand must have content");
165 return;
166 }
167
168 // we allow some global flags properties - for a want of a better syntax!
169 // TODO(DMS): remove this from the mapping files
170 if ("!zerosUnmapped".equals(leftHand)) {
171 return;
172 }
173
174 // At this point, the left hand side must be a Verse or a VerseRange
175 // It cannot be prefixed by +, ?, or !.
176 QualifiedKey left = getRange(this.nonKjv, leftHand, null);
177
178 // The right hand side can start with ? which means that the left maps to nothing in the KJV.
179 // The ? leads a section name
180 QualifiedKey kjv = getRange(KJV, kjvHand, left.getKey());
181 addMappings(left, kjv);
182 }
183
184 /**
185 * Adds a 1-Many mapping, by simply listing out all the properties. There is probably
186 * a better way for storing this, perhaps in a tree - but for simplicity, we're going to list them out.
187 *
188 * @param leftHand the left hand side operand
189 * @param kjvVerses the verses that are mapped by the left hand side
190 */
191 private void addMappings(final QualifiedKey leftHand, final QualifiedKey kjvVerses) throws NoSuchVerseException {
192 if (leftHand.getAbsentType() == QualifiedKey.Qualifier.ABSENT_IN_LEFT) {
193 this.absentVerses.addAll(kjvVerses.getKey());
194 } else if (leftHand.getKey().getCardinality() == 1) {
195 add1ToManyMappings(leftHand.getVerse(), kjvVerses);
196 } else {
197 addManyToMany(leftHand, kjvVerses);
198 }
199 }
200
201 /**
202 * Adds a many to many mapping, mappings all the verses on the left hand side to all the verses on the right hand side.
203 * We support 2 types: Many-to-1 and Many-to-Many.
204 *
205 * @param leftHand is assumed to be many
206 * @param kjvVerses could be 1 or many
207 */
208 private void addManyToMany(final QualifiedKey leftHand, final QualifiedKey kjvVerses) {
209 VerseKey leftKeys = leftHand.getKey();
210 VerseKey kjvKeys = kjvVerses.getKey();
211 Iterator<Key> leftIter = leftKeys.iterator();
212
213 if (kjvKeys != null && kjvKeys.getCardinality() != 1) {
214 // We detect if the keys are 1-apart from each other. If so, then we skip verse 0 on both sides.
215 int diff = Math.abs(leftKeys.getCardinality() - kjvKeys.getCardinality());
216
217 if (diff > 1) {
218 reportCardinalityError(leftKeys, kjvKeys);
219 }
220 boolean skipVerse0 = diff == 1;
221
222 Iterator<Key> kjvIter = kjvKeys.iterator();
223 while (leftIter.hasNext()) {
224 Verse leftVerse = (Verse) leftIter.next();
225
226 // hasNext() and next() have to be paired
227 if (!kjvIter.hasNext()) {
228 reportCardinalityError(leftKeys, kjvKeys);
229 }
230
231 Verse rightVerse = (Verse) kjvIter.next();
232 QualifiedKey kjvKey = new QualifiedKey(rightVerse);
233
234 // When the lists are of lengths differing by one
235 // it is because 0 is extra.
236 // When this is encountered on the left side,
237 // we map it and the next verse to the current
238 // right verse.
239 // In this block we'll handle the case of mapping 0
240 // and at the end of the loop we'll handle the next verse.
241 if (skipVerse0 && leftVerse.getVerse() == 0) {
242
243 addForwardMappingFromSingleKeyToRange(leftVerse, kjvKey);
244 addKJVToMapping(kjvKey, leftVerse);
245
246 if (!leftIter.hasNext()) {
247 reportCardinalityError(leftKeys, kjvKeys);
248 }
249
250 leftVerse = (Verse) leftIter.next();
251 }
252
253 // Likewise for the right side,
254 // we map it and the next verse to the current
255 // left verse.
256 // Likewise for this block, it only handles mapping 0
257 // Code at the end will map the following verse
258 if (skipVerse0 && rightVerse.getVerse() == 0) {
259
260 addForwardMappingFromSingleKeyToRange(leftVerse, kjvKey);
261 addKJVToMapping(kjvKey, leftVerse);
262
263 if (!kjvIter.hasNext()) {
264 reportCardinalityError(leftKeys, kjvKeys);
265 }
266
267 rightVerse = (Verse) kjvIter.next();
268 kjvKey = new QualifiedKey(rightVerse);
269 }
270
271 // Now do the normal case mapping
272 addForwardMappingFromSingleKeyToRange(leftVerse, kjvKey);
273 addKJVToMapping(kjvKey, leftVerse);
274 }
275
276 // Check to see if, having exhausted left that there is more
277 // on the right
278 if (kjvIter.hasNext()) {
279 reportCardinalityError(leftKeys, kjvKeys);
280 }
281 } else {
282 while (leftIter.hasNext()) {
283 final Verse leftKey = (Verse) leftIter.next();
284 addForwardMappingFromSingleKeyToRange(leftKey, kjvVerses);
285 addKJVToMapping(kjvVerses, leftKey);
286 }
287 }
288
289 }
290
291 /**
292 * If for some reason cardinalities of keys are different, we report it.
293 *
294 * @param leftKeys the left hand key
295 * @param kjvKeys the kjv qualified key
296 */
297 private void reportCardinalityError(final VerseKey leftKeys, final VerseKey kjvKeys) {
298 // TODO (CJB): change this to a neater exception
299 // then something went wrong, as we have remaining verses
300 throw new LucidRuntimeException(String.format("%s has a cardinality of %s whilst %s has a cardinality of %s.",
301 leftKeys, Integer.toString(leftKeys.getCardinality()),
302 kjvKeys, Integer.toString(kjvKeys.getCardinality())));
303 }
304
305 /**
306 * If leftKey is non-null (i.e. not attached to a simple specifier, then adds to the kjvTo mappings
307 *
308 * @param kjvVerses the kjv verses
309 * @param leftKey the left-hand key, possibly null.
310 */
311 private void addKJVToMapping(final QualifiedKey kjvVerses, final Verse leftKey) {
312 // NOTE(DMS): Both kjvVerses and left key are each a single verse
313 if (leftKey != null) {
314 getNonEmptyKey(this.fromKJVMappings, kjvVerses).addAll(leftKey);
315
316 // If we have a part, then we need to add the whole verse as well...
317 if (!kjvVerses.isWhole()) {
318 getNonEmptyKey(this.fromKJVMappings, QualifiedKey.create(kjvVerses.getKey().getWhole())).addAll(leftKey);
319 }
320 }
321 }
322
323 /**
324 * A simple two way entry between 2 1-1 entries.
325 *
326 * @param leftHand the verse on the left side, left is assumed to be 1 verse only
327 * @param kjvHand the KJV reference
328 * @throws NoSuchVerseException
329 */
330 private void add1ToManyMappings(final Verse leftHand, final QualifiedKey kjvHand) throws NoSuchVerseException {
331 addForwardMappingFromSingleKeyToRange(leftHand, kjvHand);
332 addReverse1ToManyMappings(leftHand, kjvHand);
333 }
334
335 /**
336 * Adds the data into the reverse mappings. Caters for 1-2-1 and 1-2-Many mappings
337 *
338 * @param leftHand the reference of the left hand reference
339 * @param kjvHand the kjv reference/key, qualified with the part
340 */
341 private void addReverse1ToManyMappings(final Verse leftHand, final QualifiedKey kjvHand) {
342 //add the reverse mapping, for 1-1 mappings
343 if (kjvHand.getAbsentType() == QualifiedKey.Qualifier.ABSENT_IN_KJV || kjvHand.getKey().getCardinality() == 1) {
344 // TODO(CJB): deal with parts
345 addKJVToMapping(kjvHand, leftHand);
346 } else {
347 //add the 1-many mappings
348 //expand the key and add them all
349 //Parts are not supported on ranges...
350 Iterator<Key> kjvKeys = kjvHand.getKey().iterator();
351 while (kjvKeys.hasNext()) {
352 addKJVToMapping(new QualifiedKey(KeyUtil.getVerse(kjvKeys.next())), leftHand);
353 }
354 }
355 }
356
357 /**
358 * Adds a forward mappings from left to KJV.
359 *
360 * @param leftHand the left hand reference (corresponding to a non-kjv versification)
361 * @param kjvHand the kjv reference (with part if applicable).
362 */
363 private void addForwardMappingFromSingleKeyToRange(final Verse leftHand, final QualifiedKey kjvHand) {
364 if (leftHand == null) {
365 return;
366 }
367
368 getNonEmptyMappings(this.toKJVMappings, leftHand).add(kjvHand);
369 }
370
371 /**
372 * Gets a non-empty key list, either new or the one existing in the map already.
373 *
374 * @param mappings the map from key to list of values
375 * @param key the key
376 * @return the non-empty mappings list
377 */
378 private VerseKey getNonEmptyKey(final Map<QualifiedKey, Passage> mappings, final QualifiedKey key) {
379 Passage matchingVerses = mappings.get(key);
380 if (matchingVerses == null) {
381 matchingVerses = createEmptyPassage(this.nonKjv);
382 mappings.put(key, matchingVerses);
383 }
384 return matchingVerses;
385 }
386
387 /**
388 * Gets a non-empty list, either new or the one existing in the map already
389 *
390 * @param mappings the map from key to list of values
391 * @param key the key
392 * @param <T> the type of the key
393 * @param <S> the type of the value
394 * @return the separate list of verses
395 */
396 private <T, S> List<S> getNonEmptyMappings(final Map<T, List<S>> mappings, final T key) {
397 List<S> matchingVerses = mappings.get(key);
398 if (matchingVerses == null) {
399 matchingVerses = new ArrayList<S>();
400 mappings.put(key, matchingVerses);
401 }
402 return matchingVerses;
403 }
404
405 /**
406 * Expands a reference to all its verses
407 *
408 * @param versesKey the verses
409 * @return the separate list of verses
410 */
411 private QualifiedKey getRange(final Versification versification, String versesKey, VerseKey offsetBasis) throws NoSuchKeyException {
412 //deal with absent keys in left & absent keys in right, which are simply marked by a '?'
413 if (versesKey == null || versesKey.length() == 0) {
414 throw new NoSuchKeyException(JSMsg.gettext("Cannot understand [{0}] as a chapter or verse.", versesKey));
415 }
416
417 char firstChar = versesKey.charAt(0);
418 switch (firstChar) {
419 case '?':
420 // TODO(CJB): The class JavaDoc has ? on the left side
421 // Where is that in any of the mapping code, other than in tests?
422 // NOTE(DMS): Does not return a range of any kind.
423 return getAbsentQualifiedKey(versification, versesKey);
424 case '+':
425 case '-':
426 // TODO(CJB): Is + or - allowed only on the right hand side
427 // NOTE(DMS): Always returns a QualifiedKey containing a Passage having a VerseRange of 1 or more verses
428 return getOffsetQualifiedKey(versification, versesKey, offsetBasis);
429 default:
430 return getExistingQualifiedKey(versification, versesKey);
431 }
432 }
433
434 /**
435 * Deals with offset markers, indicating a passage is +x or -x verses from this one.
436 *
437 * @param versification the versification of the passed in key
438 * @param versesKey the text of the reference we are trying to parse
439 * @return the qualified key representing this
440 */
441 private QualifiedKey getOffsetQualifiedKey(final Versification versification, final String versesKey, VerseKey offsetBasis) throws NoSuchKeyException {
442 if (offsetBasis == null || offsetBasis.getCardinality() == 0) {
443 // TODO(CJB): internationalize
444 throw new NoSuchKeyException(JSMsg.gettext("Unable to offset the given key [{0}]", offsetBasis));
445 }
446 int offset = Integer.parseInt(versesKey.substring(1));
447
448 // Convert key immediately to the target versification system, namely the KJV, since it is the only
449 // one supported. Convert by ref - since the whole purpose of this is to define equivalents.
450
451 VerseRange vr = null;
452 if (offsetBasis instanceof VerseRange) {
453 vr = (VerseRange) offsetBasis;
454 } else if (offsetBasis instanceof Passage) {
455 Iterator iter = ((Passage) offsetBasis).rangeIterator(RestrictionType.NONE);
456 if (iter.hasNext()) {
457 vr = (VerseRange) iter.next();
458 }
459 }
460 if (vr == null) {
461 // TODO(CJB): internationalize
462 throw new NoSuchKeyException(JSMsg.gettext("Unable to offset the given key [{0}]", offsetBasis));
463 }
464
465 Verse vrStart = vr.getStart();
466 Verse start = vrStart.reversify(versification);
467 // While you can add a negative number, these are optimized for their operation
468 if (offset < 0) {
469 start = versification.subtract(start, -offset);
470 } else if (offset > 0) {
471 start = versification.add(start, offset);
472 }
473 Verse end = start;
474 if (vr.getCardinality() > 1) {
475 end = versification.add(start, vr.getCardinality() - 1);
476 }
477
478 if (start == null || end == null) {
479 hasErrors = true;
480 LOGGER.error("Verse range with offset did not map to correct range in target versification. This mapping will be set to an empty unmapped key.");
481 }
482
483 return start != null && end != null ? new QualifiedKey(new VerseRange(versification, start, end)) : new QualifiedKey(versesKey);
484 }
485
486 /**
487 * Deals with real keys found in the versification.
488 *
489 * @param versification the versification of the passed in key
490 * @param versesKey the text of the reference we are trying to parse
491 * @return the qualified key representing this
492 */
493 private QualifiedKey getExistingQualifiedKey(final Versification versification, final String versesKey) {
494 return new QualifiedKey(osisParser.parseOsisRef(versification, versesKey));
495 }
496
497 /**
498 * Deals with absent markers, whether absent in the KJV or absent in the current versification.
499 *
500 * @param versification the versification of the passed in key
501 * @param versesKey the text of the reference we are trying to parse
502 * @return the qualified key representing this
503 */
504 private QualifiedKey getAbsentQualifiedKey(final Versification versification, final String versesKey) {
505 if (versification.equals(this.nonKjv)) {
506 // we're dealing with a '?', and therefore an ABSENT_IN_LEFT scenarios.
507 // we do not support any other ? markers on the left
508 return new QualifiedKey();
509 }
510 // we're dealing with a ? on the KJV side, therefore we must be looking at
511 // a section name
512 return new QualifiedKey(versesKey);
513 }
514
515 /**
516 * @return the qualified keys associated with the input key.
517 */
518 private List<QualifiedKey> getQualifiedKeys(final Key leftKey) {
519 return this.toKJVMappings.get(leftKey);
520 }
521
522 /**
523 * Maps the full qualified key to its proper equivalent in the KJV.
524 *
525 * @param qualifiedKey the qualified key
526 * @return the list of matching qualified keys in the KJV versification.
527 */
528 public List<QualifiedKey> map(QualifiedKey qualifiedKey) {
529 VerseKey key = qualifiedKey.getKey();
530 if (key instanceof Verse) {
531 List<QualifiedKey> kjvKeys = this.getQualifiedKeys(key);
532 if (kjvKeys == null || kjvKeys.size() == 0) {
533 //then we found no mapping, so we're essentially going to return the same key back...
534 //unless it's a verse 0 and then we'll check the global flag.
535 kjvKeys = new ArrayList<QualifiedKey>();
536 kjvKeys.add(qualifiedKey.reversify(KJV));
537 return kjvKeys;
538 }
539 return kjvKeys;
540 }
541
542 return new ArrayList<QualifiedKey>();
543 }
544
545 /**
546 * Converts a KJV verse to the target versification, from a qualified key, rather than a real key
547 *
548 * @param kjvVerse the verse from the KJV
549 * @return the key in the left-hand versification
550 */
551 public VerseKey unmap(final QualifiedKey kjvVerse) {
552 // TODO(CJB): cope for parts?
553 Passage left = this.fromKJVMappings.get(kjvVerse);
554
555 if (left == null && !kjvVerse.isWhole()) {
556 // Try again, but without the part this time
557 left = this.fromKJVMappings.get(new QualifiedKey(kjvVerse.getVerse().getWhole()));
558 }
559
560 //if we have no mapping, then we are in 1 of two scenarios
561 //the verse is either totally absent, or the verse is not part of the mappings, meaning it is a straight map
562 if (left == null) {
563 VerseKey vk = kjvVerse.getKey();
564 if (vk != null && this.absentVerses.contains(vk)) {
565 return createEmptyPassage(KJV);
566 }
567 return kjvVerse.reversify(this.nonKjv).getKey();
568 }
569 return left;
570 }
571
572 /**
573 * Outputs the mappings for debug purposes to the log file.
574 */
575 private void trace() {
576 if (LOGGER.isTraceEnabled()) {
577 PrintStream ps = null;
578 try {
579 ByteArrayOutputStream os = new ByteArrayOutputStream();
580 ps = new PrintStream(os);
581 dump(ps);
582 String output = os.toString("UTF8");
583 LOGGER.trace(output);
584 } catch (UnsupportedEncodingException e) {
585 // It is impossible!
586 LOGGER.error("Encoding UTF8 not supported.", e);
587 } finally {
588 IOUtil.close(ps);
589 }
590 }
591 }
592
593 /**
594 * Dump a debug representation of this map to the output stream.
595 *
596 * @param out
597 */
598 public void dump(PrintStream out) {
599 String nonKjvName = this.nonKjv.getName();
600 out.println("##############################");
601 out.print(String.format("Mapping between KJV and %s", nonKjvName));
602 out.println("##############################");
603 out.println(" ******************************");
604 out.println(" Forward mappings towards KJV");
605 out.println(" ******************************");
606 for (Map.Entry<VerseKey, List<QualifiedKey>> entry : this.toKJVMappings.entrySet()) {
607 List<QualifiedKey> kjvVerses = entry.getValue();
608 String osisRef = entry.getKey().getOsisRef();
609 for (QualifiedKey q : kjvVerses) {
610 out.println(String.format("\t(%s) %s => (KJV) %s",
611 nonKjvName,
612 osisRef,
613 q.toString()));
614 }
615 }
616
617 out.println(" ******************************");
618 out.println(" Absent verses in left versification:");
619 out.println(String.format("\t[%s]", this.absentVerses.getOsisRef()));
620 out.println(" ******************************");
621 out.println(" Backwards mappings from KJV");
622 out.println(" ******************************");
623 for (Map.Entry<QualifiedKey, Passage> entry : this.fromKJVMappings.entrySet()) {
624 out.println(String.format("\t(KJV): %s => (%s) %s",
625 entry.getKey().toString(),
626 nonKjvName,
627 entry.getValue().getOsisRef()));
628 }
629 }
630
631 /**
632 * Returns whether we initialised with errors
633 */
634 boolean hasErrors() {
635 return hasErrors;
636 }
637
638 /** Simplify creation of an empty passage object of the default type, with the required v11n.
639 *
640 * @param versification required v11n for new Passage
641 * @return empty Passage
642 */
643 private Passage createEmptyPassage(Versification versification) {
644 return new RangedPassage(versification);
645 }
646
647 /* the 'from' or 'left' versification */
648 private Versification nonKjv;
649
650 /* the absent verses, i.e. those present in the KJV, but not in the left versification */
651 private Passage absentVerses;
652 private Map<VerseKey, List<QualifiedKey>> toKJVMappings;
653 private Map<QualifiedKey, Passage> fromKJVMappings;
654 private boolean hasErrors;
655
656 private OsisParser osisParser = new OsisParser();
657
658 private static final Versification KJV = Versifications.instance().getVersification(Versifications.DEFAULT_V11N);
659 private static final Logger LOGGER = LoggerFactory.getLogger(VersificationToKJVMapper.class);
660 }
661