| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| OsisParser |
|
| 7.6;7.6 |
| 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, 2005 - 2016 | |
| 18 | * | |
| 19 | */ | |
| 20 | package org.crosswire.jsword.passage; | |
| 21 | ||
| 22 | import java.util.ArrayList; | |
| 23 | import java.util.Arrays; | |
| 24 | import java.util.List; | |
| 25 | ||
| 26 | import org.crosswire.common.util.StringUtil; | |
| 27 | import org.crosswire.jsword.versification.BibleBook; | |
| 28 | import org.crosswire.jsword.versification.Versification; | |
| 29 | ||
| 30 | /** | |
| 31 | * The Osis ID parser simply assumes 1-3 parts divided by '.'. | |
| 32 | * Any deviation from the dot-delimited formatted yields nulls. | |
| 33 | * | |
| 34 | * OSIS Refs should be separated by a '-' if there are 2 refs signifying a range e.g. Gen.1-Gen.3. | |
| 35 | * | |
| 36 | * The current implementation doesn't support an OSIS ID or OSIS ref with a missing chapter, | |
| 37 | * as are currently returned by the getOsisRef() calls occasionally. | |
| 38 | * | |
| 39 | * Algorithm: | |
| 40 | * If ony 1 ID passed in then create ending id from it to enable a single flow through algorithm. | |
| 41 | * Missing chapter or verse of starting id will be set to 0/1. | |
| 42 | * Missing chapter or verse of ending id will be set to last chapter/verse. | |
| 43 | * | |
| 44 | * @author Chris Burrell, mjdenham | |
| 45 | */ | |
| 46 | 0 | public final class OsisParser { |
| 47 | ||
| 48 | // This could be 1 or 0 but for now I have used 1 | |
| 49 | private static final String START_CHAPTER_OR_VERSE = "1"; | |
| 50 | ||
| 51 | /** | |
| 52 | * String OSIS Ref parser, assumes a - separating two osis IDs | |
| 53 | * @param v11n the v11n | |
| 54 | * @param osisRef the ref | |
| 55 | * @return the equivalent verse range | |
| 56 | */ | |
| 57 | public VerseRange parseOsisRef(final Versification v11n, final String osisRef) { | |
| 58 | 0 | final String[] osisIDs = StringUtil.splitAll(osisRef, VerseRange.RANGE_OSIS_DELIM); |
| 59 | // Too many or few parts? | |
| 60 | 0 | if (osisIDs.length < 1 || osisIDs.length > 2) { |
| 61 | 0 | return null; |
| 62 | } | |
| 63 | ||
| 64 | // Parts must have content. | |
| 65 | 0 | if (osisIDs[0].length() == 0 || (osisIDs.length == 2 && osisIDs[1].length() == 0)) { |
| 66 | 0 | return null; |
| 67 | } | |
| 68 | ||
| 69 | // Ensure ending OSIS id exists to simplify future logic - yes it is okay to use the start id as the end id here | |
| 70 | 0 | String startOsisID = osisIDs[0]; |
| 71 | String endOsisID; | |
| 72 | 0 | if (osisIDs.length == 1) { |
| 73 | 0 | endOsisID = startOsisID; |
| 74 | } else { | |
| 75 | 0 | endOsisID = osisIDs[1]; |
| 76 | } | |
| 77 | ||
| 78 | // ensure no empty parts in osis id1 and not too many parts | |
| 79 | 0 | List<String> startOsisIDParts = splitOsisId(startOsisID); |
| 80 | 0 | if (isAnEmptyPart(startOsisIDParts) || startOsisIDParts.size() > 3) { |
| 81 | 0 | return null; |
| 82 | } | |
| 83 | ||
| 84 | // manipulate first osis id to 3 parts, padding with first chapter or verse | |
| 85 | 0 | while (startOsisIDParts.size() < 3) { |
| 86 | 0 | startOsisIDParts.add(START_CHAPTER_OR_VERSE); |
| 87 | } | |
| 88 | // now we have a full 3 part start OSIS id | |
| 89 | ||
| 90 | // Now let us manufacture a 3 part end OSIS id | |
| 91 | ||
| 92 | // First check no empty parts were passed in for osis id 2 | |
| 93 | 0 | List<String> endOsisIDParts = splitOsisId(endOsisID); |
| 94 | 0 | if (isAnEmptyPart(endOsisIDParts)) { |
| 95 | 0 | return null; |
| 96 | } | |
| 97 | ||
| 98 | // Add end chapter/verse if missing | |
| 99 | 0 | int endOsisIDPartCount = endOsisIDParts.size(); |
| 100 | 0 | if (endOsisIDPartCount < 3) { |
| 101 | // need to calculate chapter and verse for osis id 2 | |
| 102 | ||
| 103 | // there will always be a book | |
| 104 | 0 | String bookName = endOsisIDParts.get(0); |
| 105 | 0 | final BibleBook book = BibleBook.fromExactOSIS(bookName); |
| 106 | ||
| 107 | // can asssume last chapter if unspecified because this is the trailing osis Id | |
| 108 | int chapter; | |
| 109 | 0 | if (endOsisIDPartCount == 1) { |
| 110 | 0 | chapter = v11n.getLastChapter(book); |
| 111 | 0 | endOsisIDParts.add(Integer.toString(chapter)); |
| 112 | } else { | |
| 113 | 0 | chapter = Integer.parseInt(endOsisIDParts.get(1)); |
| 114 | } | |
| 115 | ||
| 116 | // can asssume last verse if unspecified because this is the trailing osis Id | |
| 117 | int verse; | |
| 118 | 0 | if (endOsisIDPartCount < 3) { |
| 119 | 0 | verse = v11n.getLastVerse(book, chapter); |
| 120 | 0 | endOsisIDParts.add(Integer.toString(verse)); |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | // Now there is exactly 1 beginning and 1 ending 3-part verse only beyond this point | |
| 125 | 0 | Verse start = parseOsisID(v11n, startOsisIDParts); |
| 126 | 0 | if (start == null) { |
| 127 | 0 | return null; |
| 128 | } | |
| 129 | ||
| 130 | 0 | Verse end = parseOsisID(v11n, endOsisIDParts); |
| 131 | 0 | if (end == null) { |
| 132 | 0 | return null; |
| 133 | } | |
| 134 | ||
| 135 | 0 | return new VerseRange(v11n, start, end); |
| 136 | } | |
| 137 | ||
| 138 | /** | |
| 139 | * Strict OSIS ID parsers, case-sensitive | |
| 140 | * @param v11n the versification to use when constructing the verse | |
| 141 | * @param osisID the ID we want to parse | |
| 142 | * @return the verse that matches the OSIS ID | |
| 143 | */ | |
| 144 | public Verse parseOsisID(final Versification v11n, final String osisID) { | |
| 145 | 0 | if (osisID == null) { |
| 146 | 0 | return null; |
| 147 | } | |
| 148 | ||
| 149 | 0 | final List<String> osisIDParts = splitOsisId(osisID); |
| 150 | ||
| 151 | 0 | if (osisIDParts.size() != 3 || isAnEmptyPart(osisIDParts)) { |
| 152 | 0 | return null; |
| 153 | } | |
| 154 | ||
| 155 | 0 | return parseOsisID(v11n, osisIDParts); |
| 156 | } | |
| 157 | ||
| 158 | private Verse parseOsisID(final Versification v11n, final List<String> osisIDParts) { | |
| 159 | ||
| 160 | 0 | final BibleBook b = BibleBook.fromExactOSIS(osisIDParts.get(0)); |
| 161 | 0 | if (b == null) { |
| 162 | 0 | return null; |
| 163 | } | |
| 164 | ||
| 165 | // Allow a Verse to have a sub identifier on the last part. | |
| 166 | // We should use it, but throwing it away for now. | |
| 167 | 0 | String[] endParts = StringUtil.splitAll(osisIDParts.get(2), Verse.VERSE_OSIS_SUB_PREFIX); |
| 168 | 0 | String subIdentifier = null; |
| 169 | 0 | if (endParts.length == 2 && endParts[1].length() > 0) { |
| 170 | 0 | subIdentifier = endParts[1]; |
| 171 | } | |
| 172 | 0 | return new Verse(v11n, b, Integer.parseInt(osisIDParts.get(1)), Integer.parseInt(endParts[0]), subIdentifier); |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * Split string like 'Gen.1.1' into a 3 element list | |
| 177 | */ | |
| 178 | private List<String> splitOsisId(String osisID1) { | |
| 179 | 0 | String[] partArray = StringUtil.splitAll(osisID1, Verse.VERSE_OSIS_DELIM); |
| 180 | ||
| 181 | // add to an appropriately sized editable list | |
| 182 | 0 | List<String> list = new ArrayList(3); |
| 183 | 0 | list.addAll(Arrays.asList(partArray)); |
| 184 | 0 | return list; |
| 185 | } | |
| 186 | ||
| 187 | /** | |
| 188 | * Check no part of the Osis ref is empty | |
| 189 | */ | |
| 190 | private static boolean isAnEmptyPart(List<String> parts) { | |
| 191 | 0 | for (String part : parts) { |
| 192 | 0 | if (part.length() == 0) { |
| 193 | 0 | return true; |
| 194 | } | |
| 195 | } | |
| 196 | 0 | return false; |
| 197 | } | |
| 198 | } |