/*******************************************************************************
* Copyright (c) 2012, Directors of the Tyndale STEP Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* Neither the name of the Tyndale House, Cambridge (www.TyndaleHouse.com)
* nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package com.tyndalehouse.step.core.service.jsword.impl;
import com.tyndalehouse.step.core.exceptions.LocalisedException;
import com.tyndalehouse.step.core.exceptions.StepInternalException;
import com.tyndalehouse.step.core.exceptions.TranslatedException;
import com.tyndalehouse.step.core.exceptions.UserExceptionType;
import com.tyndalehouse.step.core.models.InterlinearMode;
import com.tyndalehouse.step.core.models.KeyWrapper;
import com.tyndalehouse.step.core.models.LookupOption;
import com.tyndalehouse.step.core.models.OsisWrapper;
import com.tyndalehouse.step.core.models.StringAndCount;
import com.tyndalehouse.step.core.service.PassageOptionsValidationService;
import com.tyndalehouse.step.core.service.VocabularyService;
import com.tyndalehouse.step.core.service.helpers.VersionResolver;
import com.tyndalehouse.step.core.service.impl.MorphologyServiceImpl;
import com.tyndalehouse.step.core.service.jsword.JSwordPassageService;
import com.tyndalehouse.step.core.service.jsword.JSwordVersificationService;
import com.tyndalehouse.step.core.utils.JSwordUtils;
import com.tyndalehouse.step.core.utils.StringConversionUtils;
import com.tyndalehouse.step.core.utils.StringUtils;
import com.tyndalehouse.step.core.xsl.MultiInterlinearProvider;
import com.tyndalehouse.step.core.xsl.XslConversionType;
import com.tyndalehouse.step.core.xsl.impl.ColorCoderProviderImpl;
import com.tyndalehouse.step.core.xsl.impl.InterleavingProviderImpl;
import com.tyndalehouse.step.core.xsl.impl.MultiInterlinearProviderImpl;
import org.crosswire.common.xml.Converter;
import org.crosswire.common.xml.JDOMSAXEventProvider;
import org.crosswire.common.xml.SAXEventProvider;
import org.crosswire.common.xml.TransformingSAXEventProvider;
import org.crosswire.jsword.book.Book;
import org.crosswire.jsword.book.BookData;
import org.crosswire.jsword.book.BookException;
import org.crosswire.jsword.book.Books;
import org.crosswire.jsword.book.OSISUtil;
import org.crosswire.jsword.book.UnAccenter;
import org.crosswire.jsword.passage.Key;
import org.crosswire.jsword.passage.KeyUtil;
import org.crosswire.jsword.passage.NoSuchKeyException;
import org.crosswire.jsword.passage.Passage;
import org.crosswire.jsword.passage.PassageKeyFactory;
import org.crosswire.jsword.passage.RestrictionType;
import org.crosswire.jsword.passage.RocketPassage;
import org.crosswire.jsword.passage.Verse;
import org.crosswire.jsword.passage.VerseRange;
import org.crosswire.jsword.versification.BibleBook;
import org.crosswire.jsword.versification.Testament;
import org.crosswire.jsword.versification.Versification;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.filter.ElementFilter;
import org.jdom2.filter.Filter;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.xml.transform.TransformerException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.tyndalehouse.step.core.models.InterlinearMode.COLUMN_COMPARE;
import static com.tyndalehouse.step.core.models.InterlinearMode.INTERLEAVED;
import static com.tyndalehouse.step.core.models.InterlinearMode.INTERLEAVED_COMPARE;
import static com.tyndalehouse.step.core.models.InterlinearMode.INTERLINEAR;
import static com.tyndalehouse.step.core.models.InterlinearMode.NONE;
import static com.tyndalehouse.step.core.utils.StringUtils.isBlank;
import static com.tyndalehouse.step.core.utils.StringUtils.isNotBlank;
import static com.tyndalehouse.step.core.utils.ValidateUtils.notNull;
import static java.lang.Integer.parseInt;
import static java.lang.String.format;
import static org.crosswire.common.xml.XMLUtil.writeToString;
import static org.crosswire.jsword.book.OSISUtil.OSIS_ATTR_OSISID;
import static org.crosswire.jsword.book.OSISUtil.OSIS_ELEMENT_VERSE;
/**
* a service providing a wrapper around JSword
*
* @author CJBurrell
*/
@Singleton
public class JSwordPassageServiceImpl implements JSwordPassageService {
private static final int MAX_SMALL_BOOK_CHAPTER_COUNT = 5;
private static final String OSIS_ID_BOOK_CHAPTER = "%s.%s";
private static final String OSIS_CHAPTER_FORMAT = "%s.%d";
private static final String OSIS_CHAPTER_VERSE_FORMAT = "%s.%s.%d";
private static final Logger LOGGER = LoggerFactory.getLogger(JSwordPassageServiceImpl.class);
private final MorphologyServiceImpl morphologyProvider;
private final JSwordVersificationService versificationService;
private final VocabularyService vocabProvider;
private final ColorCoderProviderImpl colorCoder;
private final VersionResolver resolver;
private final PassageOptionsValidationService optionsValidationService;
private final Book kjvaBook;
private final Book esvBook;
/**
* constructs the jsword service.
*
* @param versificationService jsword versification service
* @param morphologyProvider provides morphological information
* @param vocabProvider the service providing lexicon and vocabulary information
* @param colorCoder the service to color code a passage
* @param resolver the resolver
* @param optionsValidationService
*/
@Inject
public JSwordPassageServiceImpl(final JSwordVersificationService versificationService,
final MorphologyServiceImpl morphologyProvider, final VocabularyService vocabProvider,
final ColorCoderProviderImpl colorCoder, final VersionResolver resolver,
final PassageOptionsValidationService optionsValidationService) {
this.versificationService = versificationService;
this.morphologyProvider = morphologyProvider;
this.vocabProvider = vocabProvider;
this.colorCoder = colorCoder;
this.resolver = resolver;
this.optionsValidationService = optionsValidationService;
kjvaBook = Books.installed().getBook("KJVA");
esvBook = Books.installed().getBook(JSwordPassageService.REFERENCE_BOOK);
}
@Override
public KeyWrapper getSiblingChapter(final String reference, final String version,
final boolean previousChapter) {
// getting the next chapter
// FIXME find a way of getting the next chapter from the current key, in the current book, rather than
// relying on versification systems which may contain verses that the Book does not support
final Book currentBook = this.versificationService.getBookFromVersion(version);
final Versification v11n = this.versificationService.getVersificationForVersion(currentBook);
try {
final Key key = currentBook.getKey(reference);
return getSiblingChapter(previousChapter, currentBook, v11n, key);
} catch (final NoSuchKeyException e) {
throw new TranslatedException(e, "invalid_reference_in_book", reference, version);
}
}
private KeyWrapper getSiblingChapter(final boolean previousChapter, final Book currentBook, final Versification v11n, final Key key) throws NoSuchKeyException {
final Verse verse = KeyUtil.getVerse(previousChapter ? key : key.get(key.getCardinality() - 1));
final int chapter = verse.getChapter();
final BibleBook bibleBook = verse.getBook();
Verse targetVerse;
if (previousChapter) {
if (chapter > 1) {
targetVerse = new Verse(v11n, verse.getBook(), chapter - 1, 1);
} else {
// we go down a book
final BibleBook previousBook = getNonIntroPreviousBook(bibleBook, v11n);
if (previousBook == null) {
BibleBook firstBook = getFirstNonIntroBook(v11n);
targetVerse = new Verse(v11n, firstBook, 1, 1);
} else {
targetVerse = new Verse(v11n, previousBook, v11n.getLastChapter(previousBook), 1);
}
}
} else {
final int lastChapterInBook = v11n.getLastChapter(verse.getBook());
if (chapter < lastChapterInBook) {
targetVerse = new Verse(v11n, verse.getBook(), chapter + 1, 1);
} else {
// we go up a book
final BibleBook nextBook = getNonIntroNextBook(bibleBook, v11n);
if (nextBook == null) {
final BibleBook lastBook = v11n.getLastBook();
final int lastChapter = v11n.getLastChapter(lastBook);
final int lastVerse = v11n.getLastVerse(lastBook, lastChapter);
targetVerse = new Verse(v11n, lastBook, lastChapter, 1);
} else {
targetVerse = new Verse(v11n, nextBook, 1, 1);
}
}
}
// now we've got our target verse, use it, trim off the verse number
final Key finalKey = currentBook.getKey(getChapter(targetVerse, v11n));
final KeyWrapper keyWrapper = new KeyWrapper(finalKey);
//check whether the target verse is in the last chapter
if (v11n.getLastChapter(targetVerse.getBook()) == targetVerse.getChapter()) {
keyWrapper.setLastChapter(true);
}
return keyWrapper;
}
/**
* Returns the first non-intro book
*
* @param v11n the alternative versification to be questioned
* @return the first non-intro book.
*/
private BibleBook getFirstNonIntroBook(final Versification v11n) {
BibleBook b = v11n.getFirstBook();
while (BibleBook.INTRO_BIBLE.equals(b) || BibleBook.INTRO_NT.equals(b) || BibleBook.INTRO_OT.equals(b)) {
b = v11n.getNextBook(b);
}
return b;
}
/**
* @param targetVerse the verse for which we want to trim off the verse number
* @param v11n the versification of the book considered, required to deal with 1-chapter books
* @return the reference without the verse number
*/
private String getChapter(final Verse targetVerse, final Versification v11n) {
final String osisID = targetVerse.getOsisID();
final String[] parts = osisID.split("[.]");
if (v11n.getLastChapter(targetVerse.getBook()) == 1) {
// we're dealing with a 1-chapter book, so we only send back the name of the book
return parts[0];
}
// otherwise, we always send back book+chapter
if (parts.length == 3) {
return String.format(OSIS_ID_BOOK_CHAPTER, parts[0], parts[1]);
}
return null;
}
/**
* Gets the non intro next book.
*
* @param bibleBook the current book
* @param v11n the v11n
* @return the next bible book that is not an introduction
*/
private BibleBook getNonIntroNextBook(final BibleBook bibleBook, final Versification v11n) {
BibleBook nextBook = bibleBook;
do {
nextBook = v11n.getNextBook(nextBook);
} while (nextBook != null && isIntro(nextBook));
return nextBook;
}
/**
* Gets the non intro previous book.
*
* @param bibleBook the current book
* @param v11n the v11n
* @return the previous bible book that is not an introduction
*/
private BibleBook getNonIntroPreviousBook(final BibleBook bibleBook, final Versification v11n) {
BibleBook previousBook = bibleBook;
do {
previousBook = v11n.getPreviousBook(previousBook);
} while (previousBook != null && isIntro(previousBook));
return previousBook;
}
/**
* @param book the book to test
* @return true to indicate the book is an introduction to the NT/OT/Bible
*/
private boolean isIntro(final BibleBook book) {
return book.getOSIS().startsWith("Intro");
}
@Override
public KeyWrapper getKeyInfo(final String reference, final String sourceVersion, String version) {
return this.versificationService.convertReference(reference, sourceVersion, version);
}
/**
* Roudns up the reference to the next chapter + 1 (1 if it is the last verse)
*
* @param ref the current reference, split into up-to three parts (book/chapter/verse)
* @param currentKey the current key
* @param currentBook the book containing all valid keys
* @return the next key in the list
*/
Key getNextRef(final String[] ref, final Key currentKey, final Book currentBook) {
switch (ref.length) {
case 3:
return expandToFullChapter(ref[0], ref[1], ref[2], currentBook, currentKey, 1);
case 2:
// if we only have 2 parts, then we take the chapter number +1 and see if that makes sense
return getAdjacentChapter(ref[0], ref[1], currentBook, currentKey, 1);
default:
break;
}
return currentKey;
}
/**
* attempts to resolve to the next previous chapter
*
* @param ref the refParts, each element representing a portion of the OSIS ID
* @param currentKey the key that is currently being examined
* @param currentBook the book that is currently being referenced
* @return the new OSIS ID, whether it exists or not.
*/
Key getPreviousRef(final String[] ref, final Key currentKey, final Book currentBook) {
// are we dealing with something like Book.chapter.verse?
switch (ref.length) {
case 3:
return expandToFullChapter(ref[0], ref[1], ref[2], currentBook, currentKey, -1);
case 2:
return getAdjacentChapter(ref[0], ref[1], currentBook, currentKey, -1);
default:
// we are dealing with a book or something else.
break;
}
return currentKey;
}
/**
* attemps to expand to the next chapter if exists, other returns the same key as currently if no new chapter is
* found
*
* @param bookName the name of book, e.g. Gen
* @param chapterNumber the chapter number
* @param currentBook the book to look for valid keys
* @param currentKey the current position in the book
* @param gap -1 for a previous chapter, +1 for a next chapter
* @return the new key, referring to the next chapter of previous as requested
*/
Key getAdjacentChapter(final String bookName, final String chapterNumber, final Book currentBook,
final Key currentKey, final int gap) {
final int newChapter = parseInt(chapterNumber) + gap;
return getValidOrSameKey(currentBook, currentKey, format(OSIS_CHAPTER_FORMAT, bookName, newChapter));
}
/**
* Expands the key to full chapter, or if it is the last verse in the chapter, then it expands to the next chapter
*
* @param bookName the name of book, e.g. Gen
* @param chapterNumber the chapter number
* @param verseNumber the verse number
* @param currentBook the book to look for valid keys
* @param currentKey the current position in the book
* @param gap the increment to expand to, e.g. 1 to the next chapter, -1 to the previous chapter (value in
* approximate verse numbers)
* @return the new key, whether it refers to this current chapter or the next
*/
Key expandToFullChapter(final String bookName, final String chapterNumber, final String verseNumber,
final Book currentBook, final Key currentKey, final int gap) {
final int nextVerse = parseInt(verseNumber) + gap;
final Key newKey = getValidOrSameKey(currentBook, currentKey,
format(OSIS_CHAPTER_VERSE_FORMAT, bookName, chapterNumber, nextVerse));
// if we're on a beginning of a chapter
if (newKey.getOsisID().endsWith(".0") || newKey.equals(currentKey)) {
return getAdjacentChapter(bookName, chapterNumber, currentBook, currentKey, gap);
}
return currentBook.getValidKey(format(OSIS_ID_BOOK_CHAPTER, bookName, chapterNumber));
}
@Override
public KeyWrapper expandToChapter(final String version, final String reference) {
final Key k = this.versificationService.getBookFromVersion(version).getValidKey(reference);
k.blur(100, RestrictionType.CHAPTER);
return new KeyWrapper(k);
}
/**
* returns a valid key to the book, either the one specified in the newKeyName or the currentKey
*
* @param currentBook the book to look for valid keys
* @param currentKey the current key
* @param newKeyName the new potential key name
* @return the newKey if newKeyName was a good guess, or currentKey if not
*/
private Key getValidOrSameKey(final Book currentBook, final Key currentKey, final String newKeyName) {
final Key validKey = currentBook.getValidKey(newKeyName);
if (validKey.isEmpty()) {
return currentKey;
}
return validKey;
}
@Override
public String getPlainText(final String version, final String reference, final boolean firstVerse) {
final Book book = this.versificationService.getBookFromVersion(version);
try {
Key key = book.getKey(reference);
if (firstVerse) {
key = getFirstVerseExcludingZero(key, book);
}
final BookData data = new BookData(book, key);
return OSISUtil.getCanonicalText(data.getOsisFragment());
} catch (final BookException e) {
throw new LocalisedException(e, e.getMessage());
} catch (final NoSuchKeyException e) {
throw new TranslatedException(e, "invalid_reference_in_book", reference, version);
}
}
/**
* Gets the key for verse 1
*
* @param key the current aggregate key
* @param b the book
* @return the new key representing 1 verse only
*/
@Override
public Key getFirstVerseExcludingZero(final Key key, final Book b) {
if (key.getCardinality() < 1) {
return key;
}
final Key subKey = key.get(0);
if (subKey instanceof Verse) {
final Verse verse = (Verse) subKey;
if (verse.getVerse() == 0) {
// then return verse 1 if available
if (key.getCardinality() > 1) {
return key.get(1);
}
return this.versificationService.getVersificationForVersion(b).add(verse, 1);
}
return verse;
}
return key;
}
@Override
public Key getFirstVersesFromRange(final Key range, final int context) {
if (range instanceof VerseRange) {
final VerseRange verseRange = (VerseRange) range;
final Iterator<Key> iterator = verseRange.iterator();
if (!iterator.hasNext()) {
// empty range
return range;
}
Passage p = KeyUtil.getPassage(range);
final int totalWantedVerses = context + 1;
final int currentBefore = range.getCardinality();
p.trimVerses(totalWantedVerses);
final int currentNow = p.getCardinality();
final int totalAdded = currentNow - currentBefore;
final int leftToCollect = totalWantedVerses - currentNow;
if (leftToCollect > 0) {
p.blur(context, RestrictionType.NONE, true, false);
if (totalAdded < context) {
p.blur(context - totalAdded, RestrictionType.NONE, true, false);
}
}
return p;
}
return range;
}
@Override
public OsisWrapper peakOsisText(final Book bible, final Key key, final List<LookupOption> options) {
options.add(LookupOption.HIDE_XGEN);
final BookData bookData = new BookData(bible, key);
return getTextForBookData(options, null, bookData, NONE);
}
@Override
public OsisWrapper peakOsisText(String[] versions, Key lookupKey, List<LookupOption> options, String interlinearMode) {
// obtain first verse of each reference for display and add "..." on them...
final List<LookupOption> lookupOptions = new ArrayList<LookupOption>(options);
lookupOptions.add(LookupOption.HIDE_XGEN);
return this.getPassageByDisplayMode(Arrays.asList(versions), lookupKey, lookupOptions, interlinearMode);
}
public OsisWrapper getPassageByDisplayMode(List<String> versionsInput, Key reference, List<LookupOption> options, final String interlinearMode) {
if (versionsInput.size() == 0) {
throw new StepInternalException("No versions specified - app error?");
}
final String masterVersion = versionsInput.get(0);
final List<String> extraVersions = this.getExtras(versionsInput);
final InterlinearMode desiredModeOfDisplay = this.optionsValidationService.getDisplayMode(interlinearMode, masterVersion, extraVersions);
final InterlinearMode realModeOfDisplay = this.optionsValidationService.determineDisplayMode(options, desiredModeOfDisplay, true);
if (InterlinearMode.INTERLINEAR.equals(desiredModeOfDisplay) && options.contains(LookupOption.CHAPTER_BOOK_VERSE_NUMBER)) {
//then we're in a search kind of lookup, so add proper verse numbers
options.add(LookupOption.VERSE_NUMBERS);
}
OsisWrapper passageText;
final Set<LookupOption> lookupOptions = this.optionsValidationService.trim(
options, masterVersion, extraVersions,
desiredModeOfDisplay, null);
if (INTERLINEAR != desiredModeOfDisplay && NONE != desiredModeOfDisplay) {
// split the versions
passageText = this.getInterleavedVersions(versionsInput.toArray(new String[versionsInput.size()]), reference, new ArrayList<LookupOption>(lookupOptions),
desiredModeOfDisplay);
} else {
final String extraVersionsAsString = this.getVersionsAsStrings(extraVersions);
passageText = this.getOsisText(masterVersion, reference, new ArrayList<LookupOption>(lookupOptions),
extraVersionsAsString, desiredModeOfDisplay);
}
passageText.setOptions(this.optionsValidationService.optionsToString(
this.optionsValidationService.getAvailableFeaturesForVersion(masterVersion, extraVersions, interlinearMode, realModeOfDisplay).getOptions()));
passageText.setSelectedOptions(this.optionsValidationService.optionsToString(lookupOptions));
return passageText;
}
private String getVersionsAsStrings(final List<String> extraVersions) {
StringBuilder versions = new StringBuilder(9);
for (String s : extraVersions) {
versions.append(s);
if (versions.length() > 0) {
versions.append(',');
}
}
return versions.toString();
}
/**
* Copies all but first into a new array
*
* @param versionsInput the versions input
* @return the list of all versions
*/
private List<String> getExtras(final List<String> versionsInput) {
return versionsInput.subList(1, versionsInput.size());
}
@Override
public OsisWrapper getOsisText(final String version, final String reference) {
return getOsisText(version, reference, new ArrayList<LookupOption>(0), null, NONE);
}
@Override
public OsisWrapper getOsisTextByVerseNumbers(final String version, final String numberedVersion,
final int startVerseId, final int endVerseId, final List<LookupOption> lookupOptions,
final String interlinearVersion, final Boolean roundReference, final boolean ignoreVerse0) {
// coded from numbered version.
final Versification versificationForNumberedVersion = this.versificationService
.getVersificationForVersion(numberedVersion);
final Verse s = versificationForNumberedVersion.decodeOrdinal(startVerseId);
final Verse e = versificationForNumberedVersion.decodeOrdinal(endVerseId);
// convert it over to target versification
final Book lookupVersion = this.versificationService.getBookFromVersion(version);
final VerseRange range = this.versificationService.getVerseRangeForSelectedVerses(version,
numberedVersion, versificationForNumberedVersion, s, e, lookupVersion, roundReference,
ignoreVerse0);
final BookData lookupBookData = new BookData(lookupVersion, range);
return getTextForBookData(lookupOptions, interlinearVersion, lookupBookData, NONE);
}
private OsisWrapper getOsisText(final String version, final Key reference,
final List<LookupOption> options, final String interlinearVersion,
final InterlinearMode displayMode) {
LOGGER.debug("Retrieving text for ({}, {})", version, reference);
final BookData bookData = getBookDataByKey(version, reference);
return getTextForBookData(options, interlinearVersion, bookData, displayMode);
}
@Override
public OsisWrapper getOsisText(final String version, final String reference,
final List<LookupOption> options, final String interlinearVersion,
final InterlinearMode displayMode) {
LOGGER.debug("Retrieving text for ({}, {})", version, reference);
final BookData bookData = getBookData(version, reference);
return getTextForBookData(options, interlinearVersion, bookData, displayMode);
}
/**
* Gets the BookData set up for verse retrieval
*
* @param version the version to be used
* @param reference the reference
* @return the BookData object
*/
BookData getBookData(final String version, final String reference) {
final Book currentBook = this.versificationService.getBookFromVersion(version);
final Versification v11n = this.versificationService.getVersificationForVersion(currentBook);
try {
Key key = currentBook.getKey(reference);
key = normalize(key, v11n);
return new BookData(currentBook, key);
} catch (final NoSuchKeyException e) {
return handlePassageLookupNSKException(reference, currentBook, v11n, e);
}
}
/**
* Gets the BookData set up for verse retrieval
*
* @param version the version to be used
* @param key the reference
* @return the BookData object
*/
BookData getBookDataByKey(final String version, final Key key) {
final Book currentBook = this.versificationService.getBookFromVersion(version);
final Versification v11n = this.versificationService.getVersificationForVersion(currentBook);
try {
Key copyOfKey = normalize(key, v11n);
return new BookData(currentBook, copyOfKey);
} catch (final NoSuchKeyException e) {
return handlePassageLookupNSKException(key.getName(), currentBook, v11n, e);
}
}
/**
* Handles the NoSuchKey Exception when a passage lookup occurs
*
* @param reference the reference that cannot be found
* @param currentBook the current book in question
* @param v11n the associated v11n
* @param e the exception that was the cause
* @return the returned bookdata (of size 0) if we can
*/
private BookData handlePassageLookupNSKException(final String reference, final Book currentBook, final Versification v11n, final NoSuchKeyException e) {
//attempt to resolve the reference in the KJVA and if that isn't present then the ESV
//and if that isn't present, throw the exception anyway.
if (kjvaBook != null) {
try {
//attempt the parse
kjvaBook.getKey(reference);
return new BookData(currentBook, new RocketPassage(v11n));
} catch (NoSuchKeyException ex) {
//swallow this exception, and allow through
}
}
//same thing for esv
if (esvBook != null) {
try {
//attempt the parse
esvBook.getKey(reference);
return new BookData(currentBook, new RocketPassage(v11n));
} catch (NoSuchKeyException ex) {
//swallow this exception, and allow through
}
}
throw new TranslatedException(e, "invalid_reference_in_book", reference, currentBook.getInitials());
}
/**
* @param requestedPassage the key passage object
* @return true if represents a whole book.
*/
private boolean isWholeBook(final Passage requestedPassage) {
final VerseRange rangeAt = requestedPassage.getRangeAt(0, RestrictionType.NONE);
// spanning multiple books?
if (rangeAt.isMultipleBooks()) {
return false;
}
// no range at all?
if (rangeAt.getCardinality() <= 0) {
return false;
}
final Verse firstVerse = rangeAt.getStart();
final Verse endVerse = rangeAt.getEnd();
final Versification versification = firstVerse.getVersification();
return versification.isStartOfBook(firstVerse) && versification.isEndOfBook(endVerse);
}
/**
* Removes verse 0 if present.
*
* @param reference the reference we wish to normalize
* @param v11n the versification that goes with the reference
* @return normalized key, which could be different to the instance passed in
* @throws NoSuchKeyException the exception indicating no key
*/
Key normalize(final Key reference, final Versification v11n) throws NoSuchKeyException {
return reduceKeySize(reference, v11n);
}
/**
* Reduce key size to something acceptable by copyright holders.
*
* @param inputKey the input key
* @param v11n the versification
* @return the key
* @throws NoSuchKeyException the no such key exception
*/
Key reduceKeySize(final Key inputKey, final Versification v11n) throws NoSuchKeyException {
Key key = inputKey;
final int cardinality = key.getCardinality();
// if we're looking at a whole book, then we will deal with it in one way,
final Passage requestedPassage = KeyUtil.getPassage(key);
if (cardinality > MAX_VERSES_RETRIEVED) {
VerseRange firstChapter = requestedPassage.getRangeAt(0, RestrictionType.CHAPTER);
if (firstChapter.getStart().getChapter() == 0) {
key = requestedPassage.getRangeAt(1, RestrictionType.CHAPTER);
} else {
key = firstChapter;
}
} else if (isWholeBook(requestedPassage) && !requestedPassage.getRangeAt(0, RestrictionType.CHAPTER).getStart().getBook().isShortBook()) {
//we only serve whole books if they don't have a single chapter. e.g. Ruth would yield Ruth.1, but Jude would yield Jude
key = requestedPassage.getRangeAt(1, RestrictionType.CHAPTER);
}
return key;
}
/**
* Gets the osis text
*
* @param options the list of lookup options
* @param interlinearVersion the interlinear version if applicable
* @param bookData the bookdata to use to look up the required version/reference combo
* @param displayMode the mode to display the text with
* @return the html text
*/
private OsisWrapper getTextForBookData(final List<LookupOption> options, final String interlinearVersion,
final BookData bookData, final InterlinearMode displayMode) {
// check we have a book in mind and a reference
notNull(bookData, "An internal error occurred", UserExceptionType.SERVICE_VALIDATION_ERROR);
notNull(bookData.getFirstBook(), "An internal error occurred",
UserExceptionType.SERVICE_VALIDATION_ERROR);
Key key = bookData.getKey();
notNull(key, "An internal error occurred", UserExceptionType.SERVICE_VALIDATION_ERROR);
// the original book
final Book book = bookData.getFirstBook();
final Versification versification = this.versificationService.getVersificationForVersion(book);
try {
// first check whether the key is contained in the book
key = normalize(key, versification);
final SAXEventProvider osissep = bookData.getSAXEventProvider();
final TransformingSAXEventProvider htmlsep = executeStyleSheet(versification, options, interlinearVersion,
bookData, osissep, displayMode);
final OsisWrapper osisWrapper = new OsisWrapper(writeToString(htmlsep), key,
getLanguages(book, displayMode, htmlsep, options), versification,
resolver.getShortName(bookData.getFirstBook().getInitials()), displayMode,
interlinearVersion);
if (key instanceof Passage) {
final Passage p = (Passage) key;
final boolean hasMultipleRanges = p.hasRanges(RestrictionType.NONE);
osisWrapper.setMultipleRanges(hasMultipleRanges);
if (hasMultipleRanges) {
// get the first "range" and set up the start and ends
final VerseRange r = p.rangeIterator(RestrictionType.NONE).next();
osisWrapper.setStartRange(versification.getOrdinal(r.getStart()));
osisWrapper.setEndRange(versification.getOrdinal(r.getEnd()));
} else {
Iterator<Key> keys = p.iterator();
Verse start = null;
Verse end = null;
while (keys.hasNext()) {
if (start == null) {
start = (Verse) keys.next();
} else {
end = (Verse) keys.next();
}
}
if (start != null) {
osisWrapper.setStartRange(start.getOrdinal());
}
if (end != null) {
osisWrapper.setEndRange(end.getOrdinal());
} else if (start != null) {
osisWrapper.setEndRange(start.getOrdinal());
}
}
} else if (key instanceof VerseRange) {
final VerseRange vr = (VerseRange) key;
osisWrapper.setStartRange(versification.getOrdinal(vr.getStart()));
osisWrapper.setEndRange(versification.getOrdinal(vr.getEnd()));
osisWrapper.setMultipleRanges(false);
}
return osisWrapper;
} catch (final BookException e) {
throw new LocalisedException(e, e.getMessage());
} catch (final SAXException e) {
throw new StepInternalException(e.getMessage(), e);
} catch (final TransformerException e) {
throw new StepInternalException(e.getMessage(), e);
} catch (final NoSuchKeyException e) {
throw new TranslatedException(e, "invalid_reference_in_book", bookData.getKey().getName(), book.getInitials());
}
}
/**
* Gets languages as set up in the transformer
*
* @param mainBook the main book used for the interlinear/interleaving
* @param mode the mode of interlinear used
* @param htmlsep the transformer
*/
private String[] getLanguages(final Book mainBook, final InterlinearMode mode, final TransformingSAXEventProvider htmlsep, List<LookupOption> options) {
if (mode == InterlinearMode.INTERLINEAR) {
return getLanguagesForInterlinear(mainBook, htmlsep);
} else {
return getLanguagesForInterleaved(mainBook, htmlsep);
}
}
/**
* Used to identify languages from the interleaving modes
*
* @param htmlsep the transformer
* @return the list of language codes
*/
private String[] getLanguagesForInterleaved(final Book mainBook, final TransformingSAXEventProvider htmlsep) {
final InterleavingProviderImpl interleavingProvider = (InterleavingProviderImpl) htmlsep.getParameter("interleavingProvider");
if (interleavingProvider == null) {
return new String[]{mainBook.getLanguage().getCode()};
}
final String[] versions = interleavingProvider.getVersions();
final String[] languages = new String[versions.length];
for (int i = 0; i < versions.length; i++) {
languages[i] = versificationService.getBookFromVersion(versions[i]).getLanguage().getCode();
}
return languages;
}
/**
* Returns the set of languages when set in interlinear mode
*
* @param transformer the transforer
* @return the array of languages
*/
private String[] getLanguagesForInterlinear(final Book mainBook, final TransformingSAXEventProvider transformer) {
final String interlinearVersion = (String) transformer.getParameter("interlinearVersion");
final String[] versions = StringUtils.split(interlinearVersion, ", ?");
final String[] totalVersions = new String[versions.length + 1];
totalVersions[0] = mainBook.getLanguage().getCode();
for (int ii = 0; ii < versions.length; ii++) {
totalVersions[ii + 1] = this.versificationService.getBookFromVersion(versions[ii]).getLanguage().getCode();
}
return totalVersions;
}
@Override
public OsisWrapper getInterleavedVersions(final String[] versions, final String reference,
final List<LookupOption> options, final InterlinearMode displayMode) {
final Book[] books = getValidInterleavedBooks(versions, displayMode);
final Versification v11n = this.versificationService.getVersificationForVersion(books[0]);
try {
Key key = normalize(books[0].getKey(reference), v11n);
return this.getInterleavedVersions(versions, key, options, displayMode);
} catch (NoSuchKeyException nske) {
return doInterleavedVersionsLookup(versions, handlePassageLookupNSKException(reference, books[0], v11n, nske),
v11n, options, displayMode);
}
}
private OsisWrapper getInterleavedVersions(final String[] versions, final Key key,
final List<LookupOption> options, final InterlinearMode displayMode) {
notNull(versions, "No versions were passed in", UserExceptionType.SERVICE_VALIDATION_ERROR);
notNull(key, "No reference was passed in", UserExceptionType.SERVICE_VALIDATION_ERROR);
options.add(LookupOption.VERSE_NEW_LINE);
Book[] books = new Book[versions.length];
for (int i = 0; i < versions.length; i++) {
books[i] = this.versificationService.getBookFromVersion(versions[i]);
}
BookData data = new BookData(books, key, isComparingMode(displayMode));
return doInterleavedVersionsLookup(versions, data, this.versificationService.getVersificationForVersion(books[0]), options, displayMode);
}
private OsisWrapper doInterleavedVersionsLookup(String[] versions, final BookData data,
final Versification v11n,
final List<LookupOption> options,
final InterlinearMode displayMode) {
Book[] books = data.getBooks();
try {
setUnaccenter(data, displayMode);
final TransformingSAXEventProvider transformer = executeStyleSheet(v11n, options, null, data,
data.getSAXEventProvider(), displayMode);
String[] languages = new String[books.length];
for (int ii = 0; ii < books.length; ii++) {
languages[ii] = books[ii].getLanguage().getCode();
}
final Key key = data.getKey();
return new OsisWrapper(writeToString(transformer), key,
languages, v11n, resolver.getShortName(versions[0]), displayMode,
StringUtils.join(versions, 1)
);
} catch (final TransformerException e) {
throw new StepInternalException(e.getMessage(), e);
} catch (final SAXException e) {
throw new StepInternalException(e.getMessage(), e);
} catch (final BookException e) {
throw new LocalisedException(e, e.getMessage());
}
}
/**
* Validates the books given and trims down by removing any following duplicates
*
* @param versions the list of versions we are going to look up
* @param displayMode the display mode
* @return a list of books to use for looking up our data
*/
private Book[] getValidInterleavedBooks(final String[] versions, final InterlinearMode displayMode) {
Book[] books = new Book[versions.length];
for (int ii = 0; ii < versions.length; ii++) {
books[ii] = this.versificationService.getBookFromVersion(versions[ii]);
}
books = removeDifferentLanguageIfCompare(displayMode, books);
books = removeSameBooks(displayMode, books);
return books;
}
/**
* Removes any book which is preceded by itself
*
* @param displayMode the display mode
* @param books the list of books
* @return the new list of books
*/
private Book[] removeSameBooks(final InterlinearMode displayMode, final Book[] books) {
if (isComparingMode(displayMode)) {
final List<Book> trimmedBooks = new ArrayList<Book>(books.length);
trimmedBooks.add(books[0]);
for (int i = 1; i < books.length; i++) {
if (!books[i - 1].getInitials().equals(books[i].getInitials())) {
trimmedBooks.add(books[i]);
}
}
if (trimmedBooks.size() < 2) {
throw new TranslatedException("identical_texts");
}
if (trimmedBooks.size() == books.length) {
return books;
}
final Book[] tBooks = new Book[trimmedBooks.size()];
trimmedBooks.toArray(tBooks);
return tBooks;
}
return books;
}
/**
* Checks that if comparing, we are looking at versions of the same language, or at least two of them
*
* @param displayMode the display mode
* @param books the books that have been found
*/
private Book[] removeDifferentLanguageIfCompare(final InterlinearMode displayMode, final Book[] books) {
if (books.length == 0) {
return books;
}
if (!isComparingMode(displayMode)) {
return books;
}
final String firstLanguage = books[0].getLanguage().getCode();
final List<Book> booksOfSameLanguage = new ArrayList<Book>();
// check that we have at least two books of the same language
for (final Book b : books) {
if (firstLanguage.equals(b.getLanguage().getCode())) {
booksOfSameLanguage.add(b);
}
}
if (booksOfSameLanguage.size() < 2) {
throw new TranslatedException("translations_in_different_languages");
}
return booksOfSameLanguage.toArray(new Book[0]);
}
/**
* @param displayMode the display mode of the passage
* @return true if we are comparing
*/
private boolean isComparingMode(final InterlinearMode displayMode) {
return displayMode == InterlinearMode.COLUMN_COMPARE
|| displayMode == InterlinearMode.INTERLEAVED_COMPARE;
}
/**
* if we're comparing, we want to compare unaccented forms
*
* @param data the data
* @param displayMode the chosen display mode
*/
private void setUnaccenter(final BookData data, final InterlinearMode displayMode) {
if (displayMode == COLUMN_COMPARE || displayMode == INTERLEAVED_COMPARE) {
data.setUnaccenter(new UnAccenter() {
@Override
public String unaccent(final String accentedForm) {
return StringConversionUtils.unAccent(accentedForm).toLowerCase();
}
});
}
}
/**
* Changes the input OSIS document to have extra verses, the ones from the other versions
*
* @param bookDatas the list of all book datas that we will be querying
* @return the provider of events for the stylesheet to execute upon
*/
SAXEventProvider buildInterleavedVersions(final BookData... bookDatas) {
final Map<String, Element> versions = new HashMap<String, Element>();
try {
// obtain OSIS from every version
for (final BookData bookData : bookDatas) {
final Element osis = bookData.getOsis();
versions.put(bookData.getFirstBook().getInitials(), osis);
}
final Filter<Element> verseFilter = new ElementFilter(OSIS_ELEMENT_VERSE);
// select one version and iterate through the others and change the OSIS
boolean firstVersion = true;
final Map<String, Element> versesFromMaster = new HashMap<String, Element>();
// iterate through documents of every version
for (final BookData data : bookDatas) {
final String version = data.getFirstBook().getInitials();
final Element element = versions.get(version);
final Iterator<Element> docIterator = element.getDescendants(verseFilter);
Element previousAppendedElement = null;
// save the first version
while (docIterator.hasNext()) {
final Element e = docIterator.next();
LOGGER.debug("Obtaining verse [{}]", e.getAttributeValue(OSIS_ATTR_OSISID));
final String osisID = e.getAttributeValue(OSIS_ATTR_OSISID).toLowerCase();
if (firstVersion) {
versesFromMaster.put(osisID, e);
} else {
Element childVerse = versesFromMaster.get(osisID);
if (childVerse == null) {
LOGGER.debug("Orphaned row: [{}]", osisID);
childVerse = previousAppendedElement;
}
final Element parentElement = childVerse.getParentElement();
parentElement.addContent(parentElement.indexOf(childVerse), e.clone());
previousAppendedElement = childVerse;
}
}
firstVersion = false;
}
final Element amendedOsis = versions.get(bookDatas[0].getFirstBook().getInitials());
Document doc = amendedOsis.getDocument();
if (doc == null) {
doc = new Document(amendedOsis);
}
if (LOGGER.isDebugEnabled()) {
final XMLOutputter xmlOutputter = new XMLOutputter(Format.getRawFormat());
LOGGER.debug("\n {}", xmlOutputter.outputString(doc));
}
return new JDOMSAXEventProvider(doc);
} catch (final BookException e) {
throw new LocalisedException(e, e.getMessage());
}
}
/**
* Executes the stylesheet
*
* @param masterVersification the versification of the top line
* @param options the list of options to pass in
* @param interlinearVersion the interlinear version(s)
* @param bookData the book data, containing book and reference
* @param osissep the XML SAX provider
* @param displayMode the display mode
* @return a Transforming SAX event provider, from which can be transformed into HTML
* @throws TransformerException an exception in the stylesheet that is being executed
*/
private TransformingSAXEventProvider executeStyleSheet(
final Versification masterVersification,
final List<LookupOption> options,
final String interlinearVersion, final BookData bookData, final SAXEventProvider osissep,
final InterlinearMode displayMode) throws TransformerException {
final XslConversionType requiredTransformation = identifyStyleSheet(options, displayMode);
return (TransformingSAXEventProvider) new Converter() {
@Override
public SAXEventProvider convert(final SAXEventProvider provider) throws TransformerException {
try {
final String file = requiredTransformation.getFile();
final URI resourceURI = getClass().getResource(file).toURI();
final TransformingSAXEventProvider tsep = new TransformingSAXEventProvider(resourceURI,
osissep);
// set parameters here
setOptions(tsep, options, bookData.getBooks());
setInterlinearOptions(tsep,
bookData.getBooks()[0].getInitials(),
masterVersification,
getInterlinearVersion(interlinearVersion),
bookData.getKey()
.getOsisID(), displayMode, bookData.getKey(), options);
setInterleavingOptions(tsep, displayMode, bookData);
return tsep;
} catch (final URISyntaxException e) {
throw new StepInternalException("Failed to load resource correctly", e);
}
}
}.convert(osissep);
}
/**
* At the moment, we only support one stylesheet at the moment, so we only need to return one This may change, but
* at that point we'll have a cleared view on requirements. For now, if one of the options triggers anything but the
* default, then we return that. returns the stylesheet that should be used to generate the text
*
* @param options the list of options that are currently applied to the passage
* @param displayMode the display mode with wich to display the style sheet
* @return the stylesheet (of stylesheets)
*/
private XslConversionType identifyStyleSheet(final List<LookupOption> options, final InterlinearMode displayMode) {
// for interlinears, we automatically add that option
if (displayMode == InterlinearMode.INTERLINEAR) {
options.add(LookupOption.INTERLINEAR);
}
for (final LookupOption lo : options) {
// TODO refactor to remove completely the options adding / removing in preference for putting in
// trim() in BibleInformationServiceImpl
if (!XslConversionType.DEFAULT.equals(lo.getStylesheet())) {
if (XslConversionType.INTERLINEAR.equals(lo.getStylesheet())) {
options.add(LookupOption.CHAPTER_VERSE);
// FIXME: also remove headers, as not yet supported
options.remove(LookupOption.HEADINGS);
}
return lo.getStylesheet();
}
}
return XslConversionType.DEFAULT;
}
/**
* sets up the default interlinear options
*
* @param tsep the transformer that we want to set up
* @param masterVersion the master version for this lookup
* @param masterVersification the versification of the top line
* @param interlinearVersion the interlinear version(s) that the users have requested
* @param reference the reference the user is interested in
* @param displayMode the mode to display the passage, i.e. interlinear, interleaved, etc.
* @param key the key to the passage
* @param options the list of options to be applied (used to determine accenting
*/
private MultiInterlinearProvider setInterlinearOptions(final TransformingSAXEventProvider tsep,
final String masterVersion,
final Versification masterVersification,
final String interlinearVersion,
final String reference,
final InterlinearMode displayMode,
final Key key, final List<LookupOption> options) {
if (displayMode == InterlinearMode.INTERLINEAR) {
tsep.setParameter("VLine", false);
//TODO: work out OT or NT
Iterator<Key> keys = key.iterator();
if (keys.hasNext()) {
Key firstKey = keys.next();
if (firstKey instanceof Verse) {
final Verse verse = (Verse) firstKey;
Testament t = masterVersification.getTestament(verse.getOrdinal());
tsep.setParameter("isOT", t == Testament.OLD);
}
}
if (isNotBlank(interlinearVersion)) {
tsep.setParameter("interlinearVersion", interlinearVersion);
}
boolean stripGreekAccents, stripHebrewAccents, stripVowels = stripHebrewAccents = stripGreekAccents = true;
for (LookupOption option : options) {
if (LookupOption.GREEK_ACCENTS == option) {
stripGreekAccents = false;
} else if (LookupOption.HEBREW_ACCENTS == option) {
stripHebrewAccents = false;
} else if (LookupOption.HEBREW_VOWELS == option) {
stripVowels = false;
}
}
final MultiInterlinearProviderImpl multiInterlinear = new MultiInterlinearProviderImpl(masterVersion, masterVersification,
interlinearVersion, reference, this.versificationService, this.vocabProvider, stripGreekAccents, stripHebrewAccents, stripVowels);
tsep.setParameter("interlinearProvider", multiInterlinear);
return multiInterlinear;
}
return null;
}
/**
* Sets up interleaving vs column view
*
* @param tsep the transformer
* @param bookData the book data object containing the list of books we are interested in.
* @param displayMode the display mode that we are interested in
*/
private void setInterleavingOptions(final TransformingSAXEventProvider tsep,
final InterlinearMode displayMode, final BookData bookData) {
// so long as we're not NONE or INTERLINEAR, we almost always need an InterlinearProvider
final Book[] books = bookData.getBooks();
final String[] versions = new String[books.length];
for (int ii = 0; ii < books.length; ii++) {
versions[ii] = this.resolver.getShortName(books[ii].getInitials());
}
if (displayMode != NONE && displayMode != INTERLINEAR) {
tsep.setParameter("interleavingProvider", new InterleavingProviderImpl(this.versificationService,
versions, displayMode == INTERLEAVED_COMPARE || displayMode == COLUMN_COMPARE));
tsep.setParameter("HideXGen", true);
}
if (displayMode == INTERLEAVED || displayMode == INTERLEAVED_COMPARE) {
tsep.setParameter("Interleave", true);
}
if (displayMode == INTERLEAVED_COMPARE || displayMode == COLUMN_COMPARE) {
tsep.setParameter("comparing", true);
}
}
/**
* This method sets up the options for the XSLT transformation. Note: the set of options is trimmed to those
* actually available
*
* @param tsep the xslt transformer
* @param options the options available
* @param books the version to initialise a potential interlinear with
*/
protected void setOptions(final TransformingSAXEventProvider tsep, final List<LookupOption> options,
final Book[] books) {
final boolean isHebrew = JSwordUtils.isAncientHebrewBook(books);
final boolean isGreek = JSwordUtils.isAncientGreekBook(books);
// options.remove(LookupOption.VERSE_NEW_LINE);
for (final LookupOption lookupOption : options) {
if (lookupOption.getXsltParameterName() != null) {
tsep.setParameter(lookupOption.getXsltParameterName(), true);
switch (lookupOption) {
case VERSE_NUMBERS:
tsep.setParameter(LookupOption.TINY_VERSE_NUMBERS.getXsltParameterName(), true);
break;
case CHAPTER_BOOK_VERSE_NUMBER:
tsep.setParameter(LookupOption.VERSE_NUMBERS.getXsltParameterName(), true);
break;
case MORPHOLOGY:
tsep.setParameter("morphologyProvider", this.morphologyProvider);
break;
case ENGLISH_VOCAB:
case TRANSLITERATION:
case GREEK_VOCAB:
case TRANSLITERATE_ORIGINAL:
tsep.setParameter("vocabProvider", this.vocabProvider);
break;
case COLOUR_CODE:
tsep.setParameter("colorCodingProvider", this.colorCoder);
break;
case GREEK_ACCENTS:
if (isGreek) {
tsep.setParameter("RemovePointing", "false");
tsep.setParameter("RemoveVowels", "false");
}
break;
case HEBREW_VOWELS:
if (isHebrew) {
tsep.setParameter("RemoveVowels", "false");
}
break;
case HEBREW_ACCENTS:
if (isHebrew) {
tsep.setParameter("RemovePointing", "false");
tsep.setParameter("RemoveVowels", "false");
}
break;
}
}
}
//if no greek or hebrew, then override to false
if (!isGreek && !isHebrew) {
tsep.setParameter("RemovePointing", false);
tsep.setParameter("RemoveVowels", false);
}
if (!books[0].getBookMetaData().isLeftToRight()) {
tsep.setParameter(LookupOption.VERSE_NEW_LINE.getXsltParameterName(), true);
}
tsep.setParameter("direction", books[0].getBookMetaData().isLeftToRight() ? "ltr" : "rtl");
tsep.setParameter("baseVersion", this.resolver.getShortName(books[0].getInitials()));
}
/**
* @param references a list of references to be parsed
* @param version the version against which the refs are parsed
* @return a String representing all the references
*/
@Override
public String getAllReferences(final String references, final String version) {
return this.getAllReferencesAndCounts(references, version).getValue();
}
/**
* @param references a list of references to be parsed
* @param version the version against which the refs are parsed
* @return a String representing all the references
*/
@Override
public StringAndCount getAllReferencesAndCounts(final String references, final String version) {
int count = 0;
//TODO - can be refactored to optimize the reference query when used in subject searches...
final PassageKeyFactory keyFactory = PassageKeyFactory.instance();
final Versification av11n = this.versificationService.getVersificationForVersion(version);
final StringBuilder referenceString = new StringBuilder(1024);
try {
final Key k = keyFactory.getKey(av11n, references);
final Iterator<Key> iterator = k.iterator();
while (iterator.hasNext()) {
referenceString.append(iterator.next().getOsisID());
count++;
if (iterator.hasNext()) {
referenceString.append(' ');
}
}
return new StringAndCount(referenceString.toString(), count);
} catch (final NoSuchKeyException e) {
throw new TranslatedException(e, "invalid_reference_in_book", references, version);
}
}
/**
* sanitizes the strings, removing leading commas and spaces
*
* @param interlinearVersion the input string
* @return the output
*/
String getInterlinearVersion(final String interlinearVersion) {
if (isBlank(interlinearVersion)) {
return null;
}
final String[] versions = StringUtils.split(interlinearVersion, "[ ,]+");
final StringBuilder sb = new StringBuilder(interlinearVersion.length());
for (int i = 0; i < versions.length; i++) {
final String s = versions[i];
if (s.length() == 0) {
continue;
}
sb.append(s);
if (i + 1 < versions.length) {
sb.append(',');
}
}
return sb.toString();
}
}