Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AccuracyType |
|
| 3.081081081081081;3.081 | ||||
AccuracyType$1 |
|
| 3.081081081081081;3.081 | ||||
AccuracyType$2 |
|
| 3.081081081081081;3.081 | ||||
AccuracyType$3 |
|
| 3.081081081081081;3.081 | ||||
AccuracyType$4 |
|
| 3.081081081081081;3.081 | ||||
AccuracyType$5 |
|
| 3.081081081081081;3.081 | ||||
AccuracyType$6 |
|
| 3.081081081081081;3.081 |
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 org.crosswire.common.icu.NumberShaper; | |
23 | import org.crosswire.jsword.JSMsg; | |
24 | import org.crosswire.jsword.versification.BibleBook; | |
25 | import org.crosswire.jsword.versification.Versification; | |
26 | ||
27 | /** | |
28 | * Types of Accuracy for verse references. For example: | |
29 | * <ul> | |
30 | * <li>Gen == BOOK_ONLY;</li> | |
31 | * <li>Gen 1 == BOOK_CHAPTER;</li> | |
32 | * <li>Gen 1:1 == BOOK_VERSE;</li> | |
33 | * <li>Jude 1 == BOOK_VERSE;</li> | |
34 | * <li>Jude 1:1 == BOOK_VERSE;</li> | |
35 | * <li>1:1 == CHAPTER_VERSE;</li> | |
36 | * <li>10 == BOOK_ONLY, CHAPTER_ONLY, or VERSE_ONLY</li> | |
37 | * </ul> | |
38 | * | |
39 | * <p> | |
40 | * With the last one, you will note that there is a choice. By itself there is | |
41 | * not enough information to determine which one it is. There has to be a | |
42 | * context in which it is used. | |
43 | * </p><p> | |
44 | * It may be found in a verse range like: Gen 1:2 - 10. In this case the context | |
45 | * of 10 is Gen 1:2, which is BOOK_VERSE. So in this case, 10 is VERSE_ONLY. | |
46 | * </p><p> | |
47 | * If it is at the beginning of a range like 10 - 22:3, it has to have more | |
48 | * context. If the context is a prior entry like Gen 2:5, 10 - 22:3, then its | |
49 | * context is Gen 2:5, which is BOOK_VERSE and 10 is VERSE_ONLY. | |
50 | * </p><p> | |
51 | * However if it is Gen 2, 10 - 22:3 then the context is Gen 2, BOOK_CHAPTER so | |
52 | * 10 is understood as BOOK_CHAPTER. | |
53 | * </p><p> | |
54 | * As a special case, if the preceding range is an entire chapter or book then | |
55 | * 10 would understood as CHAPTER_ONLY or BOOK_ONLY (respectively) | |
56 | * </p><p> | |
57 | * If the number has no preceding context, then it is understood as being | |
58 | * BOOK_ONLY. | |
59 | * </p><p> | |
60 | * In all of these examples, the start verse was being interpreted. In the case | |
61 | * of a verse that is the end of a range, it is interpreted in the context of | |
62 | * the range's start. | |
63 | * </p> | |
64 | * | |
65 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
66 | * @author Joe Walker | |
67 | * @author DM Smith | |
68 | */ | |
69 | 0 | public enum AccuracyType { |
70 | /** | |
71 | * The verse was specified as book, chapter and verse. For example, Gen 1:1, | |
72 | * Jude 3 (which only has one chapter) | |
73 | */ | |
74 | 0 | BOOK_VERSE { |
75 | @Override | |
76 | public boolean isVerse() { | |
77 | 0 | return true; |
78 | } | |
79 | ||
80 | @Override | |
81 | public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException { | |
82 | 0 | BibleBook book = v11n.getBook(parts[0]); |
83 | 0 | int chapter = 1; |
84 | 0 | int verse = 1; |
85 | 0 | final String subIdentifier = getSubIdentifier(parts); |
86 | 0 | final boolean hasSub = subIdentifier != null; |
87 | ||
88 | //can be of form, BCV, BCV!sub, BV, BV!a | |
89 | //we only have a chapter and verse number if | |
90 | // a- BCV (3 parts) or b- BCV!sub (4 parts) | |
91 | // however, we have 3 parts if BV!a | |
92 | 0 | if (hasSub && parts.length == 4 || !hasSub && parts.length == 3) { |
93 | 0 | chapter = getChapter(v11n, book, parts[1]); |
94 | 0 | verse = getVerse(v11n, book, chapter, parts[2]); |
95 | } else { | |
96 | // Some books only have 1 chapter | |
97 | 0 | verse = getVerse(v11n, book, chapter, parts[1]); |
98 | } | |
99 | 0 | return new Verse(v11n, book, chapter, verse, subIdentifier); |
100 | } | |
101 | ||
102 | @Override | |
103 | public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException { | |
104 | // A fully specified verse is the same regardless of whether it is a | |
105 | // start or an end to a range. | |
106 | 0 | return createStartVerse(v11n, null, endParts); |
107 | } | |
108 | }, | |
109 | ||
110 | /** | |
111 | * The passage was specified to a book and chapter (no verse). For example, | |
112 | * Gen 1 | |
113 | */ | |
114 | 0 | BOOK_CHAPTER { |
115 | @Override | |
116 | public boolean isChapter() { | |
117 | 0 | return true; |
118 | } | |
119 | ||
120 | @Override | |
121 | public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException { | |
122 | 0 | BibleBook book = v11n.getBook(parts[0]); |
123 | 0 | int chapter = getChapter(v11n, book, parts[1]); |
124 | 0 | int verse = 0; // chapter > 0 ? 1 : 0; // 0 ? |
125 | 0 | return new Verse(v11n, book, chapter, verse); |
126 | } | |
127 | ||
128 | @Override | |
129 | public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException { | |
130 | // Very similar to the start verse but we want the end of the chapter | |
131 | 0 | BibleBook book = v11n.getBook(endParts[0]); |
132 | 0 | int chapter = getChapter(v11n, book, endParts[1]); |
133 | 0 | int verse = v11n.getLastVerse(book, chapter); |
134 | 0 | return new Verse(v11n, book, chapter, verse); |
135 | } | |
136 | }, | |
137 | ||
138 | /** | |
139 | * The passage was specified to a book only (no chapter or verse). For | |
140 | * example, Gen | |
141 | */ | |
142 | 0 | BOOK_ONLY { |
143 | @Override | |
144 | public boolean isBook() { | |
145 | 0 | return true; |
146 | } | |
147 | ||
148 | @Override | |
149 | public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException { | |
150 | 0 | BibleBook book = v11n.getBook(parts[0]); |
151 | 0 | int chapter = 0; // v11n.getLastChapter(book) > 0 ? 1 : 0; // 0 ? |
152 | 0 | int verse = 0; // v11n.getLastVerse(book, chapter) > 0 ? 1 : 0; // 0 ? |
153 | 0 | return new Verse(v11n, book, chapter, verse); |
154 | } | |
155 | ||
156 | @Override | |
157 | public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException { | |
158 | 0 | BibleBook book = v11n.getBook(endParts[0]); |
159 | 0 | int chapter = v11n.getLastChapter(book); |
160 | 0 | int verse = v11n.getLastVerse(book, chapter); |
161 | 0 | return new Verse(v11n, book, chapter, verse); |
162 | } | |
163 | }, | |
164 | ||
165 | /** | |
166 | * The passage was specified to a chapter and verse (no book). For example, | |
167 | * 1:1 | |
168 | */ | |
169 | 0 | CHAPTER_VERSE { |
170 | @Override | |
171 | public boolean isVerse() { | |
172 | 0 | return true; |
173 | } | |
174 | ||
175 | @Override | |
176 | public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException { | |
177 | 0 | if (verseRangeBasis == null) { |
178 | // TRANSLATOR: The user supplied a verse reference but did not give the book of the Bible. | |
179 | 0 | throw new NoSuchVerseException(JSMsg.gettext("Book is missing")); |
180 | } | |
181 | 0 | BibleBook book = verseRangeBasis.getEnd().getBook(); |
182 | 0 | int chapter = getChapter(v11n, book, parts[0]); |
183 | 0 | int verse = getVerse(v11n, book, chapter, parts[1]); |
184 | ||
185 | 0 | return new Verse(v11n, book, chapter, verse, getSubIdentifier(parts)); |
186 | } | |
187 | ||
188 | @Override | |
189 | public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException { | |
190 | // Very similar to the start verse but use the verse as a basis | |
191 | 0 | BibleBook book = verseBasis.getBook(); |
192 | 0 | int chapter = getChapter(v11n, book, endParts[0]); |
193 | 0 | int verse = getVerse(v11n, book, chapter, endParts[1]); |
194 | 0 | return new Verse(v11n, book, chapter, verse, getSubIdentifier(endParts)); |
195 | } | |
196 | }, | |
197 | ||
198 | /** | |
199 | * There was only a chapter number | |
200 | */ | |
201 | 0 | CHAPTER_ONLY { |
202 | @Override | |
203 | public boolean isChapter() { | |
204 | 0 | return true; |
205 | } | |
206 | ||
207 | @Override | |
208 | public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException { | |
209 | 0 | if (verseRangeBasis == null) { |
210 | // TRANSLATOR: The user supplied a verse reference but did not give the book of the Bible. | |
211 | 0 | throw new NoSuchVerseException(JSMsg.gettext("Book is missing")); |
212 | } | |
213 | 0 | BibleBook book = verseRangeBasis.getEnd().getBook(); |
214 | 0 | int chapter = getChapter(v11n, book, parts[0]); |
215 | 0 | int verse = 0; // chapter > 0 ? 1 : 0; // 0 ? |
216 | 0 | return new Verse(v11n, book, chapter, verse); |
217 | } | |
218 | ||
219 | @Override | |
220 | public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException { | |
221 | // Very similar to the start verse but use the verse as a basis | |
222 | // and it gets the end of the chapter | |
223 | 0 | BibleBook book = verseBasis.getBook(); |
224 | 0 | int chapter = getChapter(v11n, book, endParts[0]); |
225 | 0 | return new Verse(v11n, book, chapter, v11n.getLastVerse(book, chapter)); |
226 | } | |
227 | }, | |
228 | ||
229 | /** | |
230 | * There was only a verse number | |
231 | */ | |
232 | 0 | VERSE_ONLY { |
233 | @Override | |
234 | public boolean isVerse() { | |
235 | 0 | return true; |
236 | } | |
237 | ||
238 | @Override | |
239 | public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException { | |
240 | 0 | if (verseRangeBasis == null) { |
241 | // TRANSLATOR: The user supplied a verse reference but did not give the book or chapter of the Bible. | |
242 | 0 | throw new NoSuchVerseException(JSMsg.gettext("Book and chapter are missing")); |
243 | } | |
244 | 0 | BibleBook book = verseRangeBasis.getEnd().getBook(); |
245 | 0 | int chapter = verseRangeBasis.getEnd().getChapter(); |
246 | 0 | int verse = getVerse(v11n, book, chapter, parts[0]); |
247 | 0 | return new Verse(v11n, book, chapter, verse, getSubIdentifier(parts)); |
248 | } | |
249 | ||
250 | @Override | |
251 | public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException { | |
252 | // Very similar to the start verse but use the verse as a basis | |
253 | // and it gets the end of the chapter | |
254 | 0 | BibleBook book = verseBasis.getBook(); |
255 | 0 | int chapter = verseBasis.getChapter(); |
256 | 0 | int verse = getVerse(v11n, book, chapter, endParts[0]); |
257 | 0 | return new Verse(v11n, book, chapter, verse, getSubIdentifier(endParts)); |
258 | } | |
259 | }; | |
260 | ||
261 | /** | |
262 | * @param v11n | |
263 | * the versification to which this reference pertains | |
264 | * @param verseRangeBasis | |
265 | * the range that stood before the string reference | |
266 | * @param parts | |
267 | * a tokenized version of the original | |
268 | * @return a <code>Verse</code> for the original | |
269 | * @throws NoSuchVerseException | |
270 | */ | |
271 | public abstract Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException; | |
272 | ||
273 | /** | |
274 | * @param v11n | |
275 | * the versification to which this reference pertains | |
276 | * @param verseBasis | |
277 | * the verse at the beginning of the range | |
278 | * @param endParts | |
279 | * a tokenized version of the original | |
280 | * @return a <code>Verse</code> for the original | |
281 | * @throws NoSuchVerseException | |
282 | */ | |
283 | public abstract Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException; | |
284 | ||
285 | /** | |
286 | * @return true if this AccuracyType specifies down to the book but not | |
287 | * chapter or verse. | |
288 | */ | |
289 | public boolean isBook() { | |
290 | 0 | return false; |
291 | } | |
292 | ||
293 | /** | |
294 | * @return true if this AccuracyType specifies down to the chapter but not | |
295 | * the verse. | |
296 | */ | |
297 | public boolean isChapter() { | |
298 | 0 | return false; |
299 | } | |
300 | ||
301 | /** | |
302 | * @return true if this AccuracyType specifies down to the verse. | |
303 | */ | |
304 | public boolean isVerse() { | |
305 | 0 | return false; |
306 | } | |
307 | ||
308 | /** | |
309 | * Interprets the chapter value, which is either a number or "ff" or "$" | |
310 | * (meaning "what follows") | |
311 | * | |
312 | * @param v11n | |
313 | * the versification to which this reference pertains | |
314 | * @param lbook | |
315 | * the book | |
316 | * @param chapter | |
317 | * a string representation of the chapter. May be "ff" or "$" for | |
318 | * "what follows". | |
319 | * @return the number of the chapter | |
320 | * @throws NoSuchVerseException | |
321 | */ | |
322 | public static final int getChapter(Versification v11n, BibleBook lbook, String chapter) throws NoSuchVerseException { | |
323 | 0 | if (isEndMarker(chapter)) { |
324 | 0 | return v11n.getLastChapter(lbook); |
325 | } | |
326 | 0 | return parseInt(chapter); |
327 | } | |
328 | ||
329 | /** | |
330 | * Interprets the verse value, which is either a number or "ff" or "$" | |
331 | * (meaning "what follows") | |
332 | * | |
333 | * @param v11n | |
334 | * the versification to which this reference pertains | |
335 | * @param lbook | |
336 | * the integer representation of the book | |
337 | * @param lchapter | |
338 | * the integer representation of the chapter | |
339 | * @param verse | |
340 | * the string representation of the verse | |
341 | * @return the number of the verse | |
342 | * @throws NoSuchVerseException | |
343 | */ | |
344 | public static final int getVerse(Versification v11n, BibleBook lbook, int lchapter, String verse) throws NoSuchVerseException { | |
345 | 0 | if (isEndMarker(verse)) { |
346 | 0 | return v11n.getLastVerse(lbook, lchapter); |
347 | } | |
348 | 0 | return parseInt(verse); |
349 | } | |
350 | ||
351 | /** | |
352 | * Get an integer representation for this AccuracyType | |
353 | * | |
354 | * @return the ordinal representation | |
355 | */ | |
356 | public int toInteger() { | |
357 | 0 | return ordinal(); |
358 | } | |
359 | ||
360 | /** | |
361 | * Determine how closely the string defines a verse. | |
362 | * | |
363 | * @param v11n | |
364 | * the versification to which this reference pertains | |
365 | * @param original | |
366 | * @param parts | |
367 | * is a reference split into parts | |
368 | * @return what is the kind of accuracy of the string reference. | |
369 | * @throws NoSuchVerseException | |
370 | */ | |
371 | public static AccuracyType fromText(Versification v11n, String original, String[] parts) throws NoSuchVerseException { | |
372 | 0 | return fromText(v11n, original, parts, null, null); |
373 | } | |
374 | ||
375 | /** | |
376 | * @param v11n | |
377 | * the versification to which this reference pertains | |
378 | * @param original | |
379 | * @param parts | |
380 | * @param verseAccuracy | |
381 | * @return the accuracy of the parts | |
382 | * @throws NoSuchVerseException | |
383 | */ | |
384 | public static AccuracyType fromText(Versification v11n, String original, String[] parts, AccuracyType verseAccuracy) throws NoSuchVerseException { | |
385 | 0 | return fromText(v11n, original, parts, verseAccuracy, null); |
386 | } | |
387 | ||
388 | /** | |
389 | * @param v11n | |
390 | * the versification to which this reference pertains | |
391 | * @param original | |
392 | * @param parts | |
393 | * @param basis | |
394 | * @return the accuracy of the parts | |
395 | * @throws NoSuchVerseException | |
396 | */ | |
397 | public static AccuracyType fromText(Versification v11n, String original, String[] parts, VerseRange basis) throws NoSuchVerseException { | |
398 | 0 | return fromText(v11n, original, parts, null, basis); |
399 | } | |
400 | ||
401 | /** | |
402 | * Does this string exactly define a Verse. For example: | |
403 | * <ul> | |
404 | * <li>fromText("Gen") == AccuracyType.BOOK_ONLY;</li> | |
405 | * <li>fromText("Gen 1:1") == AccuracyType.BOOK_VERSE;</li> | |
406 | * <li>fromText("Gen 1") == AccuracyType.BOOK_CHAPTER;</li> | |
407 | * <li>fromText("Jude 1") == AccuracyType.BOOK_VERSE;</li> | |
408 | * <li>fromText("Jude 1:1") == AccuracyType.BOOK_VERSE;</li> | |
409 | * <li>fromText("1:1") == AccuracyType.CHAPTER_VERSE;</li> | |
410 | * <li>fromText("1") == AccuracyType.VERSE_ONLY;</li> | |
411 | * </ul> | |
412 | * | |
413 | * @param v11n | |
414 | * the versification to which this reference pertains | |
415 | * @param original | |
416 | * @param parts | |
417 | * @param verseAccuracy | |
418 | * @param basis | |
419 | * @return the accuracy of the parts | |
420 | * @throws NoSuchVerseException | |
421 | */ | |
422 | public static AccuracyType fromText(Versification v11n, String original, String[] parts, AccuracyType verseAccuracy, VerseRange basis) throws NoSuchVerseException { | |
423 | 0 | int partsLength = parts.length; |
424 | 0 | String lastPart = parts[partsLength - 1]; |
425 | 0 | if (lastPart.length() > 0 && lastPart.charAt(0) == '!') { |
426 | 0 | --partsLength; |
427 | } | |
428 | 0 | switch (partsLength) { |
429 | case 1: | |
430 | 0 | if (v11n.isBook(parts[0])) { |
431 | 0 | return BOOK_ONLY; |
432 | } | |
433 | ||
434 | // At this point we should have a number. | |
435 | // But double check it | |
436 | 0 | checkValidChapterOrVerse(parts[0]); |
437 | ||
438 | // What it is depends upon what stands before it. | |
439 | 0 | if (verseAccuracy != null) { |
440 | 0 | if (verseAccuracy.isVerse()) { |
441 | 0 | return VERSE_ONLY; |
442 | } | |
443 | ||
444 | 0 | if (verseAccuracy.isChapter()) { |
445 | 0 | return CHAPTER_ONLY; |
446 | } | |
447 | } | |
448 | ||
449 | 0 | if (basis != null) { |
450 | 0 | if (basis.isWholeChapter()) { |
451 | 0 | return CHAPTER_ONLY; |
452 | } | |
453 | 0 | return VERSE_ONLY; |
454 | } | |
455 | ||
456 | 0 | throw buildVersePartsException(original, parts); |
457 | ||
458 | case 2: | |
459 | // Does it start with a book? | |
460 | 0 | BibleBook pbook = v11n.getBook(parts[0]); |
461 | 0 | if (pbook == null) { |
462 | 0 | checkValidChapterOrVerse(parts[0]); |
463 | 0 | checkValidChapterOrVerse(parts[1]); |
464 | 0 | return CHAPTER_VERSE; |
465 | } | |
466 | ||
467 | 0 | if (v11n.getLastChapter(pbook) == 1) { |
468 | 0 | return BOOK_VERSE; |
469 | } | |
470 | ||
471 | 0 | return BOOK_CHAPTER; |
472 | ||
473 | case 3: | |
474 | 0 | if (v11n.getBook(parts[0]) != null) { |
475 | 0 | checkValidChapterOrVerse(parts[1]); |
476 | 0 | checkValidChapterOrVerse(parts[2]); |
477 | 0 | return BOOK_VERSE; |
478 | } | |
479 | ||
480 | 0 | throw buildVersePartsException(original, parts); |
481 | ||
482 | default: | |
483 | 0 | throw buildVersePartsException(original, parts); |
484 | } | |
485 | } | |
486 | ||
487 | private static NoSuchVerseException buildVersePartsException(String original, String[] parts) { | |
488 | 0 | StringBuilder buffer = new StringBuilder(original); |
489 | 0 | for (int i = 0; i < parts.length; i++) { |
490 | 0 | buffer.append(", ").append(parts[i]); |
491 | } | |
492 | // TRANSLATOR: The user specified a verse with too many separators. {0} is a placeholder for the allowable separators. | |
493 | 0 | return new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", buffer.toString())); |
494 | } | |
495 | ||
496 | /** | |
497 | * Is this text valid in a chapter/verse context | |
498 | * | |
499 | * @param text | |
500 | * The string to test for validity | |
501 | * @throws NoSuchVerseException | |
502 | * If the text is invalid | |
503 | */ | |
504 | private static void checkValidChapterOrVerse(String text) throws NoSuchVerseException { | |
505 | 0 | if (!isEndMarker(text)) { |
506 | 0 | parseInt(text); |
507 | } | |
508 | 0 | } |
509 | ||
510 | /** | |
511 | * This is simply a convenience function to wrap Integer.parseInt() and give | |
512 | * us a reasonable exception on failure. It is called by VerseRange hence | |
513 | * protected, however I would prefer private | |
514 | * | |
515 | * @param text | |
516 | * The string to be parsed | |
517 | * @return The correctly parsed chapter or verse | |
518 | * @exception NoSuchVerseException | |
519 | * If the reference is illegal | |
520 | */ | |
521 | private static int parseInt(String text) throws NoSuchVerseException { | |
522 | try { | |
523 | 0 | return Integer.parseInt(new NumberShaper().unshape(text)); |
524 | 0 | } catch (NumberFormatException ex) { |
525 | // TRANSLATOR: The chapter or verse number is actually not a number, but something else. | |
526 | // {0} is a placeholder for what the user supplied. | |
527 | 0 | throw new NoSuchVerseException(JSMsg.gettext("Cannot understand {0} as a chapter or verse.", text)); |
528 | } | |
529 | } | |
530 | ||
531 | /** | |
532 | * Is this string a legal marker for 'to the end of the chapter' | |
533 | * | |
534 | * @param text | |
535 | * The string to be checked | |
536 | * @return true if this is a legal marker | |
537 | */ | |
538 | private static boolean isEndMarker(String text) { | |
539 | 0 | if (text.equals(AccuracyType.VERSE_END_MARK1)) { |
540 | 0 | return true; |
541 | } | |
542 | ||
543 | 0 | if (text.equals(AccuracyType.VERSE_END_MARK2)) { |
544 | 0 | return true; |
545 | } | |
546 | ||
547 | 0 | return false; |
548 | } | |
549 | ||
550 | private static boolean hasSubIdentifier(String[] parts) { | |
551 | 0 | String subIdentifier = parts[parts.length - 1]; |
552 | 0 | return subIdentifier != null && subIdentifier.length() > 0 && subIdentifier.charAt(0) == '!'; |
553 | } | |
554 | ||
555 | protected static String getSubIdentifier(String[] parts) { | |
556 | 0 | String subIdentifier = null; |
557 | 0 | if (hasSubIdentifier(parts)) { |
558 | 0 | subIdentifier = parts[parts.length - 1].substring(1); |
559 | } | |
560 | 0 | return subIdentifier; |
561 | } | |
562 | ||
563 | /** | |
564 | * Take a string representation of a verse and parse it into an Array of | |
565 | * Strings where each part is likely to be a verse part. The goal is to | |
566 | * allow the greatest possible variations in user input. | |
567 | * <p> | |
568 | * Parts can be separated by pretty much anything. No distinction is made | |
569 | * between them. While chapter and verse need to be separated, a separator | |
570 | * is assumed between digits and non-digits. Adjacent words, (i.e. sequences | |
571 | * of non-digits) are understood to be a book reference. If a number runs up | |
572 | * against a book name, it is considered to be either part of the book name | |
573 | * (i.e. it is before it) or a chapter number (i.e. it stands after it.) | |
574 | * </p> | |
575 | * <p> | |
576 | * Note: ff and $ are considered to be digits. | |
577 | * </p> | |
578 | * <p> | |
579 | * Note: it is not necessary for this to be a BCV (book, chapter, verse), it | |
580 | * may just be BC, B, C, V or CV. No distinction is needed here for a number | |
581 | * that stands alone. | |
582 | * </p> | |
583 | * | |
584 | * @param input | |
585 | * The string to parse. | |
586 | * @return The string array | |
587 | * @throws NoSuchVerseException | |
588 | */ | |
589 | public static String[] tokenize(String input) throws NoSuchVerseException { | |
590 | // The results are expected to be no more than 3 parts | |
591 | 0 | String[] args = { |
592 | null, null, null, null, null, null, null, null | |
593 | }; | |
594 | ||
595 | // Normalize the input by replacing non-digits and non-letters with | |
596 | // spaces. | |
597 | 0 | int length = input.length(); |
598 | // Create an output array big enough to add ' ' where needed | |
599 | 0 | char[] normalized = new char[length * 2]; |
600 | 0 | char lastChar = '0'; // start with a digit so normalized won't start |
601 | // with a space | |
602 | 0 | char curChar = ' '; // can be anything |
603 | 0 | int tokenCount = 0; |
604 | 0 | int normalizedLength = 0; |
605 | 0 | int startIndex = 0; |
606 | 0 | String token = null; |
607 | 0 | boolean foundBoundary = false; |
608 | 0 | boolean foundSubIdentifier = false; |
609 | 0 | for (int i = 0; i < length; i++) { |
610 | 0 | curChar = input.charAt(i); |
611 | 0 | if (curChar == '!') { |
612 | 0 | foundSubIdentifier = true; |
613 | 0 | token = new String(normalized, startIndex, normalizedLength - startIndex); |
614 | 0 | args[tokenCount++] = token; |
615 | 0 | normalizedLength = 0; |
616 | } | |
617 | 0 | if (foundSubIdentifier) { |
618 | 0 | normalized[normalizedLength++] = curChar; |
619 | 0 | continue; |
620 | } | |
621 | 0 | boolean charIsDigit = curChar == '$' || Character.isDigit(curChar) |
622 | || (curChar == 'f' && (i + 1 < length ? input.charAt(i + 1) : ' ') == 'f' && !Character.isLetter(lastChar)); | |
623 | 0 | if (charIsDigit || Character.isLetter(curChar)) { |
624 | 0 | foundBoundary = true; |
625 | 0 | boolean charWasDigit = lastChar == '$' || Character.isDigit(lastChar) || (lastChar == 'f' && (i > 2 ? input.charAt(i - 2) : '0') == 'f'); |
626 | 0 | if (charWasDigit || Character.isLetter(lastChar)) { |
627 | 0 | foundBoundary = false; |
628 | // Replace transitions between digits and letters with | |
629 | // spaces. | |
630 | 0 | if (normalizedLength > 0 && charWasDigit != charIsDigit) { |
631 | 0 | foundBoundary = true; |
632 | } | |
633 | } | |
634 | 0 | if (foundBoundary) { |
635 | // On a boundary: | |
636 | // Digits always start a new token | |
637 | // Letters always continue a previous token | |
638 | 0 | if (charIsDigit) { |
639 | 0 | if (tokenCount >= args.length) { |
640 | // TRANSLATOR: The user specified a verse with too many separators. {0} is a placeholder for the allowable separators. | |
641 | 0 | throw new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", input)); |
642 | } | |
643 | ||
644 | 0 | token = new String(normalized, startIndex, normalizedLength - startIndex); |
645 | 0 | args[tokenCount++] = token; |
646 | 0 | normalizedLength = 0; |
647 | } else { | |
648 | 0 | normalized[normalizedLength++] = ' '; |
649 | } | |
650 | } | |
651 | 0 | normalized[normalizedLength++] = curChar; |
652 | } | |
653 | ||
654 | // Until the first character is copied, there is no last char | |
655 | 0 | if (normalizedLength > 0) { |
656 | 0 | lastChar = curChar; |
657 | } | |
658 | } | |
659 | ||
660 | 0 | if (tokenCount >= args.length) { |
661 | // TRANSLATOR: The user specified a verse with too many separators. {0} is a placeholder for the allowable separators. | |
662 | 0 | throw new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", input)); |
663 | } | |
664 | ||
665 | 0 | token = new String(normalized, startIndex, normalizedLength - startIndex); |
666 | 0 | args[tokenCount++] = token; |
667 | ||
668 | 0 | String[] results = new String[tokenCount]; |
669 | 0 | System.arraycopy(args, 0, results, 0, tokenCount); |
670 | 0 | return results; |
671 | } | |
672 | ||
673 | /** | |
674 | * What characters can we use to separate parts to a verse | |
675 | */ | |
676 | public static final String VERSE_ALLOWED_DELIMS = " :."; | |
677 | ||
678 | /** | |
679 | * Characters that are used to indicate end of verse/chapter, part 1 | |
680 | */ | |
681 | public static final String VERSE_END_MARK1 = "$"; | |
682 | ||
683 | /** | |
684 | * Characters that are used to indicate end of verse/chapter, part 2 | |
685 | */ | |
686 | public static final String VERSE_END_MARK2 = "ff"; | |
687 | } |